mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-25 10:25:01 +00:00
feat: allow the bot to search through the list of tags and aliases to get the closest matching tag
This commit is contained in:
parent
adce597c14
commit
49a2a92f49
4 changed files with 76 additions and 4 deletions
|
@ -17,6 +17,11 @@ export const TagCreateCmd = tagsCmd({
|
||||||
const prefix = pluginData.config.get().prefix;
|
const prefix = pluginData.config.get().prefix;
|
||||||
|
|
||||||
if (args.alias) {
|
if (args.alias) {
|
||||||
|
const existingTag = await pluginData.state.tagAliases.find(args.body);
|
||||||
|
if (existingTag) {
|
||||||
|
sendErrorMessage(pluginData, msg.channel, `You cannot create an alias of an alias`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
await pluginData.state.tagAliases.createOrUpdate(args.tag, args.body, msg.author.id);
|
await pluginData.state.tagAliases.createOrUpdate(args.tag, args.body, msg.author.id);
|
||||||
sendSuccessMessage(pluginData, msg.channel, `Alias set! Use it with: \`${prefix}${args.tag}\``);
|
sendSuccessMessage(pluginData, msg.channel, `Alias set! Use it with: \`${prefix}${args.tag}\``);
|
||||||
return;
|
return;
|
||||||
|
|
30
backend/src/plugins/Tags/util/fuzzySearch.ts
Normal file
30
backend/src/plugins/Tags/util/fuzzySearch.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// https://rosettacode.org/wiki/Levenshtein_distance#JavaScript
|
||||||
|
|
||||||
|
function levenshtein(a: string, b: string): number {
|
||||||
|
let t: number[] = [];
|
||||||
|
let u: number[] = [];
|
||||||
|
const m = a.length;
|
||||||
|
const n = b.length;
|
||||||
|
if (!m) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
if (!n) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
for (let j = 0; j <= n; j++) {
|
||||||
|
t[j] = j;
|
||||||
|
}
|
||||||
|
for (let i = 1; i <= m; i++) {
|
||||||
|
let j: number;
|
||||||
|
// tslint:disable-next-line:ban-comma-operator
|
||||||
|
for (u = [i], j = 1; j <= n; j++) {
|
||||||
|
u[j] = a[i - 1] === b[j - 1] ? t[j - 1] : Math.min(t[j - 1], t[j], u[j - 1]) + 1;
|
||||||
|
}
|
||||||
|
t = u;
|
||||||
|
}
|
||||||
|
return u[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function distance(str: string, t: string): number {
|
||||||
|
return levenshtein(str, t);
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
import { GuildMember } from "discord.js";
|
import { GuildMember, Snowflake, TextChannel } from "discord.js";
|
||||||
import escapeStringRegexp from "escape-string-regexp";
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
import { GuildPluginData } from "knub";
|
import { GuildPluginData } from "knub";
|
||||||
import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager";
|
import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager";
|
||||||
|
|
||||||
import { StrictMessageContent } from "../../../utils";
|
import { StrictMessageContent } from "../../../utils";
|
||||||
import { TagsPluginType, TTagCategory } from "../types";
|
import { TagsPluginType, TTagCategory } from "../types";
|
||||||
|
|
||||||
|
import { distance } from "./fuzzySearch";
|
||||||
import { renderTagFromString } from "./renderTagFromString";
|
import { renderTagFromString } from "./renderTagFromString";
|
||||||
|
|
||||||
interface BaseResult {
|
interface BaseResult {
|
||||||
|
@ -93,7 +96,39 @@ export async function matchAndRenderTagFromString(
|
||||||
const dynamicTag = await pluginData.state.tags.find(tagName);
|
const dynamicTag = await pluginData.state.tags.find(tagName);
|
||||||
|
|
||||||
if (!aliasedTag && !dynamicTag) {
|
if (!aliasedTag && !dynamicTag) {
|
||||||
return null;
|
// fuzzy search the list of aliases and tags to see if there's a match and
|
||||||
|
// inform the user
|
||||||
|
const tags = await pluginData.state.tags.all();
|
||||||
|
const aliases = await pluginData.state.tagAliases.all();
|
||||||
|
let lowest: [number, [string]] = [999999, [""]];
|
||||||
|
tags.forEach((tag) => {
|
||||||
|
const tagname = tag?.tag;
|
||||||
|
const dist = distance(tagname, tagName);
|
||||||
|
if (dist < lowest[0]) {
|
||||||
|
lowest = [dist, [`**${tagname}**`]];
|
||||||
|
} else if (dist === lowest[0]) {
|
||||||
|
lowest[1].push(`**${tagname}**`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
aliases.forEach((alias) => {
|
||||||
|
const aliasname = alias?.alias;
|
||||||
|
const dist = distance(aliasname, tagName);
|
||||||
|
if (dist < lowest[0]) {
|
||||||
|
lowest = [dist, [`**${aliasname}**`]];
|
||||||
|
} else if (dist === lowest[0]) {
|
||||||
|
lowest[1].push(`**${aliasname}**`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (lowest[0] > 6) return null;
|
||||||
|
const content: StrictMessageContent = {
|
||||||
|
content: `Did you mean:\n${lowest[1].join("\n")}`,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
renderedContent: content,
|
||||||
|
tagName: "",
|
||||||
|
category: null,
|
||||||
|
categoryName: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagBody = aliasedTag?.body ?? dynamicTag?.body;
|
const tagBody = aliasedTag?.body ?? dynamicTag?.body;
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import { Snowflake, TextChannel } from "discord.js";
|
import { Snowflake, TextChannel } from "discord.js";
|
||||||
import { GuildPluginData } from "knub";
|
import { GuildPluginData } from "knub";
|
||||||
import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions";
|
import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions";
|
||||||
|
|
||||||
import { SavedMessage } from "../../../data/entities/SavedMessage";
|
import { SavedMessage } from "../../../data/entities/SavedMessage";
|
||||||
import { LogType } from "../../../data/LogType";
|
import { LogType } from "../../../data/LogType";
|
||||||
import { convertDelayStringToMS, resolveMember, tStrictMessageContent } from "../../../utils";
|
import { convertDelayStringToMS, resolveMember, tStrictMessageContent } from "../../../utils";
|
||||||
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
|
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
|
||||||
import { validate } from "../../../validatorUtils";
|
import { validate } from "../../../validatorUtils";
|
||||||
import { TagsPluginType } from "../types";
|
|
||||||
import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString";
|
|
||||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||||
|
import { TagsPluginType } from "../types";
|
||||||
|
|
||||||
|
import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString";
|
||||||
|
|
||||||
export async function onMessageCreate(pluginData: GuildPluginData<TagsPluginType>, msg: SavedMessage) {
|
export async function onMessageCreate(pluginData: GuildPluginData<TagsPluginType>, msg: SavedMessage) {
|
||||||
if (msg.is_bot) return;
|
if (msg.is_bot) return;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue