1
0
mirror of https://github.com/toast-ts/Daggerbot-TS.git synced 2024-09-29 00:10:58 -04:00

Use Levenshtein algorithm

This commit is contained in:
toast-ts 2024-09-25 12:27:21 +10:00
parent 7a0c245311
commit 3afc39c903

View File

@ -8,11 +8,51 @@ import MessageTool from '../helpers/MessageTool.js';
import {FSData} from 'src/interfaces'; import {FSData} from 'src/interfaces';
import {requestServerData, mpModuleDisabled, refreshTimerSecs, playtimeStat, MPChannels} from '../modules/MPModule.js'; import {requestServerData, mpModuleDisabled, refreshTimerSecs, playtimeStat, MPChannels} from '../modules/MPModule.js';
// I asked Copilot lol, yes I said "wtf is that" when it suggested it to me.
// Works wonders, I'm surprised.
function levenshtein(a: string, b: string): number {
const matrix = Array.from({length: a.length+1}, ()=>Array(b.length+1).fill(0));
for (let i = 0; i <= a.length; i++) matrix[i][0] = i;
for (let j = 0; j <= b.length; j++) matrix[0][j] = j;
for (let i = 1; i <= a.length; i++) {
for (let j = 1; j <= b.length; j++) {
const cost = a[i-1] === b[j-1] ? 0 : 1;
matrix[i][j] = Math.min(
matrix[i-1][j] + 1, // Deletion
matrix[i][j-1] + 1, // Insertion
matrix[i-1][j-1] + cost // Substitution
);
}
}
return matrix[a.length][b.length];
}
function normalizeString(s: string): string {
return s.toLowerCase().replace(/[^a-z0-9]/g, '');
}
async function fetchData(client:TClient, interaction:Discord.ChatInputCommandInteraction, serverName:string):Promise<FSData|Discord.InteractionResponse> { async function fetchData(client:TClient, interaction:Discord.ChatInputCommandInteraction, serverName:string):Promise<FSData|Discord.InteractionResponse> {
try { try {
await interaction.deferReply(); await interaction.deferReply();
const db = await client.MPServer.findInCache(); const db = await client.MPServer.findInCache();
const data = await requestServerData(client, db.find(x=>x.serverName === serverName)); const server = db.find(x=>x.serverName === serverName);
if (!server) {
const normalizedServerName = normalizeString(serverName);
const servers = db.filter(x=>x.isActive).map(x=>x.serverName);
const closestMatch = servers.reduce((closest, current)=>{
const currentSimilarity = levenshtein(normalizeString(current), normalizedServerName);
const closestSimilarity = levenshtein(normalizeString(closest), normalizedServerName);
return currentSimilarity < closestSimilarity ? current : closest;
}, servers[0]);
await interaction.editReply(`**${serverName}** does not exist in database, closest server is **${closestMatch}**.`);
return;
}
const data = await requestServerData(client, server);
return data.dss as FSData; return data.dss as FSData;
} catch { } catch {
Logger.console('error', 'MPDB', 'Function failed - fetchData'); Logger.console('error', 'MPDB', 'Function failed - fetchData');
@ -21,7 +61,7 @@ async function fetchData(client:TClient, interaction:Discord.ChatInputCommandInt
} }
const logPrefix = 'MPDB'; const logPrefix = 'MPDB';
const PALLET_FILTER = ['PALLETS', 'BIGBAGPALLETS']; const CATEGORY_FILTER = ['PALLETS', 'BIGBAGS', 'BIGBAGPALLETS'];
export default class MP { export default class MP {
static async autocomplete(client:TClient, interaction:Discord.AutocompleteInteraction<'cached'>) { static async autocomplete(client:TClient, interaction:Discord.AutocompleteInteraction<'cached'>) {
@ -102,7 +142,7 @@ export default class MP {
pallets: async()=>{ pallets: async()=>{
const DSS = await fetchData(client, interaction, choiceSelector) as FSData; const DSS = await fetchData(client, interaction, choiceSelector) as FSData;
if (!DSS) return console.log('Endpoint failed - pallets'); if (!DSS) return console.log('Endpoint failed - pallets');
const filter = DSS?.vehicles.filter(x=>PALLET_FILTER.includes(x.category)); const filter = DSS?.vehicles.filter(x=>CATEGORY_FILTER.includes(x.category));
const rules = { const rules = {
one: 'single pallet', one: 'single pallet',
two: 'pallets', two: 'pallets',
@ -114,7 +154,7 @@ export default class MP {
const getLongestName = Object.entries(this.getPalletCounts(DSS)).map(([name, _])=>name.length).sort((a,b)=>b-a)[0]; const getLongestName = Object.entries(this.getPalletCounts(DSS)).map(([name, _])=>name.length).sort((a,b)=>b-a)[0];
await interaction.editReply(MessageTool.concatMessage( await interaction.editReply(MessageTool.concatMessage(
`There are currently **${filter.length}** ${rules} on the server. Here\'s the breakdown:\`\`\`ansi`, `There are currently **${filter.length}** ${rules} on the server. Here\'s the breakdown:\`\`\`ansi`,
Object.entries(this.getPalletCounts(DSS)).map(([name, count])=>`${ansi.blue(name.padEnd(getLongestName+3))}${ansi.yellow(count.toString())}`).join('\n'), Object.entries(this.getPalletCounts(DSS)).map(([name, count])=>`${ansi.bold(ansi.blue(name.padEnd(getLongestName+3)))}${ansi.bold(ansi.yellow(count.toString()))}`).join('\n'),
'```' '```'
)) ))
} }
@ -244,7 +284,7 @@ export default class MP {
await Promise.all(numbersArr.slice(0, length).map(emote=>message.react(emote))); await Promise.all(numbersArr.slice(0, length).map(emote=>message.react(emote)));
} }
private static getPalletCounts(data:FSData) { private static getPalletCounts(data:FSData) {
const pallets = data.vehicles.filter(x=>PALLET_FILTER.includes(x.category)); const pallets = data.vehicles.filter(x=>CATEGORY_FILTER.includes(x.category));
const counts = pallets.reduce((acc, name)=>{ const counts = pallets.reduce((acc, name)=>{
acc[name.name] = (acc[name.name] ?? 0) + 1; acc[name.name] = (acc[name.name] ?? 0) + 1;
return acc; return acc;
@ -328,7 +368,7 @@ export default class MP {
.setName('reason') .setName('reason')
.setDescription('The message to send to the channel after toggling') .setDescription('The message to send to the channel after toggling')
.setRequired(true))) .setRequired(true)))
.addSubcommandGroup(x=>x /* .addSubcommandGroup(x=>x
.setName('poll') .setName('poll')
.setDescription('Create or end a map poll in #mp-announcements channel') .setDescription('Create or end a map poll in #mp-announcements channel')
.addSubcommand(x=>x .addSubcommand(x=>x
@ -347,5 +387,5 @@ export default class MP {
.setRequired(true))) .setRequired(true)))
.addSubcommand(x=>x .addSubcommand(x=>x
.setName('maps') .setName('maps')
.setDescription('Fetch the list of maps currently in the suggestion pool'))) .setDescription('Fetch the list of maps currently in the suggestion pool'))) */
} }