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;
|
||||
|
||||
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);
|
||||
sendSuccessMessage(pluginData, msg.channel, `Alias set! Use it with: \`${prefix}${args.tag}\``);
|
||||
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 { GuildPluginData } from "knub";
|
||||
import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager";
|
||||
|
||||
import { StrictMessageContent } from "../../../utils";
|
||||
import { TagsPluginType, TTagCategory } from "../types";
|
||||
|
||||
import { distance } from "./fuzzySearch";
|
||||
import { renderTagFromString } from "./renderTagFromString";
|
||||
|
||||
interface BaseResult {
|
||||
|
@ -93,7 +96,39 @@ export async function matchAndRenderTagFromString(
|
|||
const dynamicTag = await pluginData.state.tags.find(tagName);
|
||||
|
||||
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;
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { Snowflake, TextChannel } from "discord.js";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions";
|
||||
|
||||
import { SavedMessage } from "../../../data/entities/SavedMessage";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { convertDelayStringToMS, resolveMember, tStrictMessageContent } from "../../../utils";
|
||||
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
|
||||
import { validate } from "../../../validatorUtils";
|
||||
import { TagsPluginType } from "../types";
|
||||
import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { TagsPluginType } from "../types";
|
||||
|
||||
import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString";
|
||||
|
||||
export async function onMessageCreate(pluginData: GuildPluginData<TagsPluginType>, msg: SavedMessage) {
|
||||
if (msg.is_bot) return;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue