UNPKG

@grammyjs/commands

Version:
117 lines (116 loc) 4.37 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.distance = distance; exports.JaroWinklerDistance = JaroWinklerDistance; exports.isLanguageCode = isLanguageCode; exports.fuzzyMatch = fuzzyMatch; const language_codes_js_1 = require("../language-codes.js"); function distance(s1, s2) { if (s1.length === 0 || s2.length === 0) { return 0; } const matchWindow = Math.floor(Math.max(s1.length, s2.length) / 2.0) - 1; const matches1 = new Array(s1.length); const matches2 = new Array(s2.length); let m = 0; // number of matches let t = 0; // number of transpositions let i = 0; // index for string 1 let k = 0; // index for string 2 for (i = 0; i < s1.length; i++) { // loop to find matched characters const start = Math.max(0, i - matchWindow); // use the higher of the window diff const end = Math.min(i + matchWindow + 1, s2.length); // use the min of the window and string 2 length for (k = start; k < end; k++) { // iterate second string index if (matches2[k]) { // if second string character already matched continue; } if (s1[i] !== s2[k]) { // characters don't match continue; } // assume match if the above 2 checks don't continue matches1[i] = true; matches2[k] = true; m++; break; } } // nothing matched if (m === 0) { return 0.0; } k = 0; // reset string 2 index for (i = 0; i < s1.length; i++) { // loop to find transpositions if (!matches1[i]) { // non-matching character continue; } while (!matches2[k]) { // move k index to the next match k++; } if (s1[i] !== s2[k]) { // if the characters don't match, increase transposition // HtD: t is always less than the number of matches m, because transpositions are a subset of matches t++; } k++; // iterate k index normally } // transpositions divided by 2 t /= 2.0; return (m / s1.length + m / s2.length + (m - t) / m) / 3.0; // HtD: therefore, m - t > 0, and m - t < m // HtD: => return value is between 0 and 1 } // Computes the Winkler distance between two string -- intrepreted from: // http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance // s1 is the first string to compare // s2 is the second string to compare // dj is the Jaro Distance (if you've already computed it), leave blank and the method handles it // ignoreCase: if true strings are first converted to lower case before comparison function JaroWinklerDistance(s1, s2, options) { if (s1 === s2) { return 1; } else { if (options.ignoreCase) { s1 = s1.toLowerCase(); s2 = s2.toLowerCase(); } const jaro = distance(s1, s2); const p = 0.1; // default scaling factor let l = 0; // length of the matching prefix while (s1[l] === s2[l] && l < 4) { l++; } // HtD: 1 - jaro >= 0 return jaro + l * p * (1 - jaro); } } function isLanguageCode(value) { return Object.values(language_codes_js_1.LanguageCodes).includes(value); } function fuzzyMatch(userInput, commands, options) { var _a; const defaultSimilarityThreshold = 0.82; const similarityThreshold = options.similarityThreshold || defaultSimilarityThreshold; /** * ctx.from.id is IETF * https://en.wikipedia.org/wiki/IETF_language_tag */ const possiblyISO639 = (_a = options.language) === null || _a === void 0 ? void 0 : _a.split("-")[0]; const language = isLanguageCode(possiblyISO639) ? possiblyISO639 : undefined; const cmds = options.ignoreLocalization ? commands.toElementals() : commands.toElementals(language); const bestMatch = cmds.reduce((best, command) => { const similarity = JaroWinklerDistance(userInput, command.command, { ...options, }); return similarity > best.similarity ? { command, similarity } : best; }, { command: null, similarity: 0 }); return bestMatch.similarity > similarityThreshold ? bestMatch : null; }