3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-05-25 18:25:03 +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:
Rstar284 2022-04-26 15:34:22 +04:00
parent 00e5759233
commit 824b237515
No known key found for this signature in database
GPG key ID: FB4E41B2606FAE1A
4 changed files with 76 additions and 4 deletions

View file

@ -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;

View 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);
}

View file

@ -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;

View file

@ -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;