iquestgod

Amq Autocomplete improvement 1.25

Jun 10th, 2021 (edited)
671
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Amq Autocomplete Improvement 1.25
  3. // @namespace    http://tampermonkey.net/
  4. // @version      1.25
  5. // @description  faster and better autocomplete
  6. // First searches for text startingWith, then includes and finally if input words match words in anime (in any order). Special characters can be in any place in any order
  7. // @author       Juvian
  8. // @match        https://animemusicquiz.com/*
  9. // @require      https://cdnjs.cloudflare.com/ajax/libs/fuzzyset.js/0.0.8/fuzzyset.min.js
  10. // @grant        none
  11. // @copyright MIT license
  12. // ==/UserScript==
  13.  
  14. let isNode = typeof window === 'undefined';
  15.  
  16. if (!isNode && typeof Listener === 'undefined') throw "rip";
  17.  
  18. if (isNode) {
  19.     FuzzySet = require('fuzzyset.js')
  20. }
  21.  
  22. const cleanString = (str) => {
  23.     return removeDiacritics(str).replace(/[^\w\s]/gi, ' ').replace(/  +/g, ' ').toLowerCase().trim();
  24. }
  25.  
  26. const semiCleanString = (str) => {
  27.     return removeDiacritics(str).replace(/  +/g, ' ').toLowerCase().trim();
  28. }
  29.  
  30. const onlySpecialChars = (str) => {
  31.     return str.replace(/[\w\s]/gi, '').split('').sort().join('');
  32. }
  33.  
  34. var options = {
  35.     highlight: true, // highlight or not the match
  36.     sorting : "partial", // Sets the order of the anime in the dropdown. total sorts by last seen date order. Partial puts first the ones seen. Any other thing is amq default
  37.     sortList: true, // true = consider matching animes by last seen order (after checking startsWith)
  38.     allowRightLeftArrows: false, // use right and left arrows to move dropdown selected options
  39.     daysToRemember: 90, // sets amount of days to consider an anime as seen in sorting,
  40.     fuzzy: {
  41.         dropdown: true, // whether to show fuzzy matches if no matches found
  42.         answer: true, // whether to use top fuzzy match on round end as answer if no matches found
  43.     },
  44.     addEntries: true, // whether to allow oo, ou and uu as ō/ū,
  45.     entrySets: [
  46.         {
  47.             startsWith: false, //change to true if you want more specific results
  48.             contains: false, //change to true if you want more specific results
  49.             partial: false, //not recommended to change
  50.             clean: semiCleanString // does not remove special characters so you can filter with it
  51.         },
  52.         {
  53.             startsWith: true, // allow startsWith priotization (fastest matching)
  54.             contains: true, // allow to search by contains
  55.             partial: true, // allow the words to be on any order (no hero boku will match boku no hero),
  56.             clean: cleanString,
  57.             special: onlySpecialChars // adds filtering special chars instead of just ignoring
  58.         },
  59.     ]
  60. }
  61.  
  62.  
  63. var debug = false;
  64.  
  65. function log(msg) {
  66.     if(debug) console.log(msg)
  67. }
  68.  
  69. var storedData = {}
  70.  
  71. if (!isNode && window.localStorage) {
  72.     storedData = JSON.parse(localStorage.getItem("storedData") || "{}");
  73. }
  74.  
  75. class SortedList {
  76.     constructor(list) {
  77.         this.list = this.alphabeticalSort(list);
  78.         this.reset();
  79.     }
  80.  
  81.     alphabeticalSort (list) {
  82.         return list.sort(function(a, b){
  83.             if (a.str < b.str)
  84.                 return -1;
  85.             if (a.str > b.str)
  86.                 return 1;
  87.             return 0;
  88.         });
  89.     }
  90.  
  91.     reset() {
  92.         this.start = 0;
  93.         this.end = this.list.length;
  94.     }
  95. }
  96.  
  97. class SuperString {
  98.     constructor(list) {
  99.         this.str = list.join("$$$");
  100.  
  101.         this.lookup = [{str: 0}];
  102.  
  103.         for (var i = 0; i < list.length; i++) {
  104.             this.lookup.push({str: this.lookup[i].str + list[i].length + 3})
  105.         }
  106.  
  107.         this.reset();
  108.     }
  109.  
  110.     clone() {
  111.         let superString = new SuperString([]);
  112.         Object.assign(superString, this);
  113.         return superString;
  114.     }
  115.  
  116.     reset() {
  117.         this.lastIndex = 0;
  118.     }
  119. }
  120. //, specialStr: onlySpecialChars(v), splittedStr: cleanString(v).split(" ")
  121.  
  122. class EntrySet {
  123.     constructor(list, config, manager) {
  124.         this.list = list;
  125.         this.manager = manager;
  126.         this.sorted = new SortedList(config.startsWith ? list : []);
  127.         this.contains = new SuperString(config.contains || config.partial ? list.map(v => v.str) : []);
  128.         this.partial = this.contains.clone();
  129.         this.config = config;
  130.  
  131.         if (config.partial) this.list.forEach(e => e.splittedStr = e.str.split(' '));
  132.  
  133.         this.results = new Set();
  134.     }
  135.  
  136.     reset() {
  137.         this.sorted.reset();
  138.         this.partial.reset();
  139.         this.contains.reset();
  140.  
  141.         this.lastQry = "";
  142.         this.lastSpecialStr = "";
  143.         this.lastQrySplit = [];
  144.         this.results.clear();
  145.         this.lastSpecialIndex = 0;
  146.     }
  147.  
  148.     partialMatches (idx) {
  149.         var animeSplitted = this.list[idx].splittedStr;
  150.  
  151.         if (this.lastQrySplit.every((v) => this.list[idx].str.indexOf(v) != -1)) {
  152.             var used = animeSplitted.map(v => false);
  153.             var matches = []
  154.             for (var i = 0; i < this.lastQrySplit.length; i++) {
  155.                 matches.push([])
  156.                 for (var j = 0; j < animeSplitted.length; j++) {
  157.                     matches[i].push(animeSplitted[j].indexOf(this.lastQrySplit[i]) != -1);
  158.                 }
  159.             }
  160.             return this.matches(0, matches, used);
  161.         }
  162.     }
  163.  
  164.     matches (currentIndex, matches, used) {
  165.         if (currentIndex == matches.length) return true;
  166.  
  167.         for (var i = 0; i < matches[0].length; i++) {
  168.             if (!used[i] && matches[currentIndex][i]) {
  169.                 used[i] = true;
  170.                 if (this.matches(currentIndex + 1, matches, used)) return true;
  171.                 used[i] = false;
  172.             }
  173.         }
  174.  
  175.         return false;
  176.     }
  177.  
  178.     setQuery(str) {
  179.         let specialStr = this.config.special ? this.config.special(str) : '';
  180.         str = this.config.clean(str);
  181.  
  182.         if (!str.startsWith(this.lastQry) || (this.config.special && !specialMatchesStr(this.lastSpecialStr, specialStr))) this.reset();
  183.  
  184.         this.lastQry = str;
  185.         this.lastQrySplit = str.split(" ").filter((s) => s.trim());
  186.         this.lastSpecialStr = specialStr;
  187.     }
  188.  
  189.     specialMatches(idx) {
  190.         return !this.config.special || specialMatchesStr(this.lastSpecialStr, this.list[idx].specialStr);
  191.     }
  192.  
  193.     addResult(idx) {
  194.         if (this.specialMatches(idx) && (!this.config.partial || this.partialMatches(idx))) {
  195.             this.results.add(idx);
  196.             this.manager.originalIndexResults.add(this.list[idx].originalIndex)
  197.         }
  198.     }
  199.  
  200.     checkOldResults() {
  201.         let oldResults = Array.from(this.results);
  202.  
  203.         this.results.clear();
  204.  
  205.         for (let idx of oldResults) {
  206.             let anime = this.list[idx].str;
  207.             if (anime.indexOf(this.lastQry) != -1 || (this.config.partial && this.partialMatches(idx))) this.addResult(idx);
  208.         }
  209.     }
  210.  
  211.     addResults (range) {
  212.         for (var i = range.first; i <= range.last && this.manager.originalIndexResults.size < this.manager.limit; i++) {
  213.             this.addResult(i);
  214.         }
  215.     }
  216.  
  217.     addContainingResults (superString, qry) {
  218.         let re = new RegExp(escapeRegExp(qry), "g")
  219.         let match;
  220.  
  221.         re.lastIndex = superString.lastIndex
  222.         while (this.manager.originalIndexResults.size < this.manager.limit && (match = re.exec(superString.str))) {
  223.             let idx = first(superString.lookup, v => v > match.index, 0, superString.lookup.length) - 1
  224.             this.addResult(idx);
  225.             superString.lastIndex = re.lastIndex;
  226.         }
  227.     }
  228. }
  229.  
  230. class FilterManager {
  231.     constructor (list, limit, opts) {
  232.         this.list = list.filter(v => v.trim().length).map((v, idx) => ({idx: idx, originalStr: v, originalIndex: idx}));
  233.         this.limit = limit
  234.         this.specialChars = {}
  235.         this.options = Object.assign({}, options, opts || {});
  236.         this.now = new Date(); this.now.setDate(this.now.getDate() - this.options.daysToRemember);
  237.  
  238.         this.addEntries();
  239.  
  240.         if (this.options.sortList) {
  241.             this.list = this.sortBySeen(this.list);
  242.         }
  243.  
  244.         this.list.forEach((v, idx) => v.idx = idx);
  245.  
  246.         this.addEntrySets();
  247.  
  248.         if (this.options.fuzzy.dropdown || this.options.fuzzy.answer) {
  249.             let cleaned = this.list.map(v => cleanString(v.originalStr));
  250.             this.fuzzy = FuzzySet(cleaned);
  251.             this.reverseMapping = {}
  252.             this.list.forEach((v, idx) => {
  253.                 this.reverseMapping[cleaned[idx]] = this.reverseMapping[cleaned[idx]] || []
  254.                 this.reverseMapping[cleaned[idx]].push(v.idx)
  255.             });
  256.         }
  257.  
  258.         this.originalIndexResults = new Set();
  259.  
  260.         this.reset();
  261.     }
  262.  
  263.     addEntries() {
  264.         if (!this.options.addEntries) return;
  265.  
  266.         this.list.filter(e => e.originalStr.includes('ō') || e.originalStr.includes('ū')).forEach(e => {
  267.             let additional = [""];
  268.             for (let i = 0; i < e.originalStr.length; i++) {
  269.                 if (e.originalStr[i] == 'ō') {
  270.                    additional = additional.map(s => s + 'oo').concat(additional.map(s => s + 'ou'));
  271.                 } else if (e.originalStr[i] == 'ū') {
  272.                    additional = additional.map(s => s + 'uu');
  273.                 } else {
  274.                    additional = additional.map(s => s + e.originalStr[i]);
  275.                 }
  276.             }
  277.             this.list = this.list.concat(additional.map((v, idx) => ({str: v, idx: e.idx, originalStr: e.originalStr, originalIndex: e.originalIndex})));
  278.         });
  279.     }
  280.  
  281.     addEntrySets() {
  282.         this.entrySets = [];
  283.  
  284.         let cache = new Set();
  285.         for (let entrySet of this.options.entrySets) {
  286.             if (entrySet.startsWith || entrySet.contains || entrySet.partial) {
  287.                 let list = this.list.map((e, idx) => ({str: entrySet.clean(e.str || e.originalStr), specialStr: entrySet.special ? entrySet.special(e.str || e.originalStr) : '', originalIndex: e.originalIndex, listIndex: e.idx})).filter(e => {
  288.                     if (cache.has(e.str + '|||' + e.specialStr)) return false;
  289.                     return cache.add(e.str + '|||' + e.specialStr);
  290.                 });
  291.                 this.entrySets.push(new EntrySet(list, entrySet, this));
  292.             }
  293.         }
  294.     }
  295.  
  296.  
  297.     getLastSeen(anime) {
  298.         return storedData[anime.toLowerCase()] >= this.now.getTime() ? storedData[anime.toLowerCase()] : 1
  299.     }
  300.  
  301.  
  302.     sortBySeen(arr) {
  303.        return arr.map((v) => ({val : v, d: this.getLastSeen(v.originalStr)})).sort(function(a, b){
  304.            if (a.d < b.d) return 1;
  305.            if (b.d < a.d) return -1;
  306.            if (a.val.idx < b.val.idx) return -1;
  307.            return 1;
  308.        }).map((v) => v.val);
  309.     }
  310.  
  311.     reset () {
  312.         this.entrySets.forEach(e => e.reset());
  313.         this.originalIndexResults.clear();
  314.     }
  315.  
  316.     checkOldResults() {
  317.         this.originalIndexResults.clear();
  318.         this.entrySets.forEach(e => e.checkOldResults());
  319.     }
  320.  
  321.     processResultsFor(str) {
  322.         this.entrySets.forEach(e => e.setQuery(str));
  323.         this.lastStr = str;
  324.         this.checkOldResults();
  325.  
  326.         for (let entrySet of this.entrySets) {
  327.             if (entrySet.config.startsWith && entrySet.lastQry.length) {
  328.                 entrySet.addResults(range(entrySet.sorted, entrySet.lastQry));
  329.             }
  330.         }
  331.  
  332.         for (let entrySet of this.entrySets) {
  333.             if (entrySet.config.contains && entrySet.lastQry.length) {
  334.                 entrySet.addContainingResults(entrySet.contains, entrySet.lastQry);
  335.             }
  336.             if (!entrySet.lastQry.length && entrySet.lastSpecialStr.length) {
  337.                 for (; entrySet.lastSpecialIndex < entrySet.list.length && this.originalIndexResults.size < this.limit; entrySet.lastSpecialIndex++) {
  338.                     entrySet.addResult(entrySet.lastSpecialIndex);
  339.                 }
  340.             }
  341.         }
  342.  
  343.         for (let entrySet of this.entrySets) {
  344.             if (entrySet.config.partial && entrySet.lastQrySplit.length >= 2) {
  345.                 let qry = entrySet.lastQrySplit.filter((s) => s.trim()).sort((a, b) => b.length - a.length)[0];
  346.                 entrySet.addContainingResults(entrySet.partial, qry);
  347.             }
  348.         }
  349.     }
  350.  
  351.     filterBy (str, fuzzy) {
  352.         if(debug) console.time(str + " filter")
  353.  
  354.         if (str.trim().length == 0) return [];
  355.         this.processResultsFor(str);
  356.  
  357.         let results = [];
  358.         let s = new Set();
  359.  
  360.         for (let entrySet of this.entrySets) {
  361.             for (let idx of entrySet.results) {
  362.                 if (!s.has(entrySet.list[idx].originalIndex)) {
  363.                     results.push({lastQry: entrySet.lastQry, match: entrySet.list[idx], listMatch: this.list[entrySet.list[idx].listIndex]});
  364.                     s.add(entrySet.list[idx].originalIndex);
  365.                 }
  366.             }
  367.         }
  368.  
  369.         //add fuzzy results
  370.         if (this.originalIndexResults.size == 0 && fuzzy) {
  371.             let fuzzyResults = new Set((this.fuzzy.get(cleanString(str)) || []).slice(0, this.limit).map(r => this.reverseMapping[r[1]]).reduce((acc, val) => acc.concat(val), []).slice(0, this.limit));
  372.             for (let idx of Array.from(fuzzyResults)) {
  373.                 if (!s.has(this.list[idx].originalIndex)) {
  374.                     results.push({match: this.list[idx], listMatch: this.list[idx], lastQry: str})
  375.                     s.add(this.list[idx].originalIndex);
  376.                 }
  377.             }
  378.         }
  379.  
  380.         if(debug) console.timeEnd(str + " filter")
  381.  
  382.         return results
  383.     }
  384. }
  385.  
  386. const specialMatchesStr = (qry, strToMatch) => {
  387.     let curIdx = 0;
  388.  
  389.     for (let i = 0; strToMatch && i < strToMatch.length && curIdx < qry.length && strToMatch[i] <= qry[curIdx]; i++) {
  390.         if (strToMatch[i] == qry[curIdx]) {
  391.             curIdx++;
  392.         }
  393.     }
  394.  
  395.     return curIdx == qry.length;
  396. }
  397.  
  398.  
  399. const range = (data, lastQry) => {
  400.     let firstIndex = first(data.list, f => f >= lastQry, data.start, data.end);
  401.  
  402.     if (firstIndex < data.end && data.list[firstIndex].str.startsWith(lastQry)) {
  403.         return {
  404.             first: firstIndex,
  405.             last: first(data.list, f => !f.startsWith(lastQry), firstIndex, data.end) - 1,
  406.             list: data.list
  407.         }
  408.     }
  409.  
  410.     return {
  411.         first: data.end,
  412.         last: data.end - 1, // on purpose
  413.         list: data.list
  414.     }
  415. }
  416.  
  417. const first = (array, pred, lo, hi) => {
  418.     while (lo != hi) {
  419.         const mi = lo + ((hi - lo) >> 1);
  420.         if (pred(array[mi].str)) {
  421.             hi = mi;
  422.         } else {
  423.             lo = mi + 1;
  424.         }
  425.     }
  426.     return hi;
  427. }
  428.  
  429. class HightLightManager {
  430.     constructor (awesomeplete) {
  431.         $(awesomeplete.input).on("awesomplete-highlight", function(event) {
  432.             awesomeplete.input.value = event.originalEvent.text.value;
  433.         })
  434.     }
  435. }
  436.  
  437. if (!isNode) {
  438.  
  439.     var oldProto = AmqAwesomeplete.prototype;
  440.     var oldEvaluate = AmqAwesomeplete.prototype.evaluate;
  441.  
  442.     AmqAwesomeplete = function(input, o) {
  443.         oldProto.constructor.apply(this, Array.from(arguments))
  444.         this.isAnimeAutocomplete = this._list.indexOf("Serial Experiments Lain") != -1;
  445.         if (this.isAnimeAutocomplete) this.preprocess();
  446.     }
  447.  
  448.     AmqAwesomeplete.prototype = oldProto;
  449.  
  450.  
  451.     AmqAwesomeplete.prototype.preprocess = function () {
  452.         this.filterManager = new FilterManager(this._list.sort(this.sort), this.maxItems);
  453.         this.highLightManager = new HightLightManager(this);
  454.  
  455.         if (options.allowRightLeftArrows) {
  456.             $(this.input).on("keydown", (e) => {
  457.                 if (e.keyCode == 37) this.previous()
  458.                 else if (e.keyCode == 39) this.next();
  459.             })
  460.         }
  461.     }
  462.  
  463.     AmqAwesomeplete.prototype.evaluate = function () {
  464.         if (this.isAnimeAutocomplete == false) return oldEvaluate.call(this);
  465.  
  466.         let suggestions = this.filterManager.filterBy(this.input.value, options.fuzzy.dropdown);
  467.  
  468.         suggestions = suggestions.map((v) => ({v: v, d: this.filterManager.getLastSeen(v.listMatch.originalStr)}));
  469.  
  470.         if (!this.filterManager.fuzzySearched) suggestions = suggestions.sort((a, b) => {
  471.             if (options.sorting == "partial" || options.sorting == "total") {
  472.                 if (a.d != 1 && b.d == 1) return -1;
  473.                 if (a.d == 1 && b.d != 1) return 1;
  474.             }
  475.  
  476.             if (options.sorting == "total") {
  477.                 if (a.d < b.d) return 1;
  478.                 if (b.d < a.d) return -1;
  479.             }
  480.  
  481.             if (this.sort !== false) {
  482.                 if (a.v.idx < b.v.idx) return -1;
  483.             }
  484.  
  485.             return 1;
  486.         })
  487.  
  488.         this.suggestions = suggestions.map(v => new Suggestion(this.data(v.v.listMatch.originalStr, v.v.lastQry)));
  489.  
  490.         log(this.suggestions)
  491.  
  492.         $("#qpAnswerInputLoadingContainer").removeClass("hide");
  493.         this.$ul.children('li').remove();
  494.  
  495.         for (let i = this.suggestions.length - 1; i >= 0; i--) {
  496.             this.ul.insertBefore(this.item(this.suggestions[i], options.highlight ? suggestions[i].v.lastQry : "", i), this.ul.firstChild);
  497.         }
  498.  
  499.         if (this.ul.children.length === 0) {
  500.  
  501.             this.status.textContent = "No results found";
  502.  
  503.             this.close({ reason: "nomatches" });
  504.  
  505.         } else {
  506.             this.open();
  507.  
  508.             this.status.textContent = this.ul.children.length + " results found";
  509.         }
  510.  
  511.         $("#qpAnswerInputLoadingContainer").addClass("hide");
  512.     };
  513.  
  514.     //auto send incomplete answer
  515.     var oldSendAnwer = QuizAnswerInput.prototype.submitAnswer;
  516.  
  517.     QuizAnswerInput.prototype.submitAnswer = function () {
  518.         try{
  519.             var awesome = this.autoCompleteController.awesomepleteInstance;
  520.  
  521.             if(awesome && awesome.input.value == awesome.filterManager.lastStr && awesome.suggestions && awesome.input.value.trim() && awesome.suggestions.every(s => cleanString(s.value) != cleanString(awesome.input.value)) && (awesome.suggestions.length || (!options.fuzzy.dropdown && options.fuzzy.answer))) {
  522.                 awesome.input.value = awesome.suggestions.length ? awesome.suggestions[0].value : awesome.filterManager.filterBy(awesome.input.value, true)[0].originalStr;
  523.             }
  524.         } catch (ex) {
  525.             console.log(ex);
  526.         }
  527.         oldSendAnwer.apply(this, Array.from(arguments))
  528.     }
  529.  
  530.  
  531.     new Listener("answer results", function (result) {
  532.         for (var lang in result.songInfo.animeNames) {
  533.             storedData[result.songInfo.animeNames[lang].toLowerCase()] = new Date().getTime();
  534.         }
  535.  
  536.         log(storedData)
  537.  
  538.     }).bindListener();
  539.  
  540.     new Listener("quiz end result", function (payload) {
  541.         if (localStorage) localStorage.setItem("storedData", JSON.stringify(storedData))
  542.     }).bindListener();
  543. }
  544.  
  545. var defaultDiacriticsRemovalMap = [
  546.     {'base':'A', 'letters':'\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F'},
  547.     {'base':'AA','letters':'\uA732'},
  548.     {'base':'AE','letters':'\u00C6\u01FC\u01E2'},
  549.     {'base':'AO','letters':'\uA734'},
  550.     {'base':'AU','letters':'\uA736'},
  551.     {'base':'AV','letters':'\uA738\uA73A'},
  552.     {'base':'AY','letters':'\uA73C'},
  553.     {'base':'B', 'letters':'\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181'},
  554.     {'base':'C', 'letters':'\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E'},
  555.     {'base':'D', 'letters':'\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0'},
  556.     {'base':'DZ','letters':'\u01F1\u01C4'},
  557.     {'base':'Dz','letters':'\u01F2\u01C5'},
  558.     {'base':'E', 'letters':'\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E'},
  559.     {'base':'F', 'letters':'\u0046\u24BB\uFF26\u1E1E\u0191\uA77B'},
  560.     {'base':'G', 'letters':'\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E'},
  561.     {'base':'H', 'letters':'\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D'},
  562.     {'base':'I', 'letters':'\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197'},
  563.     {'base':'J', 'letters':'\u004A\u24BF\uFF2A\u0134\u0248'},
  564.     {'base':'K', 'letters':'\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2'},
  565.     {'base':'L', 'letters':'\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780'},
  566.     {'base':'LJ','letters':'\u01C7'},
  567.     {'base':'Lj','letters':'\u01C8'},
  568.     {'base':'M', 'letters':'\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C'},
  569.     {'base':'N', 'letters':'\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4'},
  570.     {'base':'NJ','letters':'\u01CA'},
  571.     {'base':'Nj','letters':'\u01CB'},
  572.     {'base':'O', 'letters':'\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C'},
  573.     {'base':'OI','letters':'\u01A2'},
  574.     {'base':'OO','letters':'\uA74E'},
  575.     {'base':'OU','letters':'\u0222'},
  576.     {'base':'OE','letters':'\u008C\u0152'},
  577.     {'base':'oe','letters':'\u009C\u0153'},
  578.     {'base':'P', 'letters':'\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754'},
  579.     {'base':'Q', 'letters':'\u0051\u24C6\uFF31\uA756\uA758\u024A'},
  580.     {'base':'R', 'letters':'\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782'},
  581.     {'base':'S', 'letters':'\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784'},
  582.     {'base':'T', 'letters':'\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786'},
  583.     {'base':'TZ','letters':'\uA728'},
  584.     {'base':'U', 'letters':'\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244'},
  585.     {'base':'V', 'letters':'\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245'},
  586.     {'base':'VY','letters':'\uA760'},
  587.     {'base':'W', 'letters':'\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72'},
  588.     {'base':'X', 'letters':'\u0058\u24CD\uFF38\u1E8A\u1E8C'},
  589.     {'base':'Y', 'letters':'\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE'},
  590.     {'base':'Z', 'letters':'\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762'},
  591.     {'base':'a', 'letters':'\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250'},
  592.     {'base':'aa','letters':'\uA733'},
  593.     {'base':'ae','letters':'\u00E6\u01FD\u01E3'},
  594.     {'base':'ao','letters':'\uA735'},
  595.     {'base':'au','letters':'\uA737'},
  596.     {'base':'av','letters':'\uA739\uA73B'},
  597.     {'base':'ay','letters':'\uA73D'},
  598.     {'base':'b', 'letters':'\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253'},
  599.     {'base':'c', 'letters':'\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184'},
  600.     {'base':'d', 'letters':'\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A'},
  601.     {'base':'dz','letters':'\u01F3\u01C6'},
  602.     {'base':'e', 'letters':'\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD'},
  603.     {'base':'f', 'letters':'\u0066\u24D5\uFF46\u1E1F\u0192\uA77C'},
  604.     {'base':'g', 'letters':'\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F'},
  605.     {'base':'h', 'letters':'\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265'},
  606.     {'base':'hv','letters':'\u0195'},
  607.     {'base':'i', 'letters':'\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131'},
  608.     {'base':'j', 'letters':'\u006A\u24D9\uFF4A\u0135\u01F0\u0249'},
  609.     {'base':'k', 'letters':'\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3'},
  610.     {'base':'l', 'letters':'\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747'},
  611.     {'base':'lj','letters':'\u01C9'},
  612.     {'base':'m', 'letters':'\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F'},
  613.     {'base':'n', 'letters':'\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5'},
  614.     {'base':'nj','letters':'\u01CC'},
  615.     {'base':'o', 'letters':'\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275'},
  616.     {'base':'oi','letters':'\u01A3'},
  617.     {'base':'ou','letters':'\u0223'},
  618.     {'base':'oo','letters':'\uA74F'},
  619.     {'base':'p','letters':'\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755'},
  620.     {'base':'q','letters':'\u0071\u24E0\uFF51\u024B\uA757\uA759'},
  621.     {'base':'r','letters':'\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783'},
  622.     {'base':'s','letters':'\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B'},
  623.     {'base':'t','letters':'\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787'},
  624.     {'base':'tz','letters':'\uA729'},
  625.     {'base':'u','letters': '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289'},
  626.     {'base':'v','letters':'\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C'},
  627.     {'base':'vy','letters':'\uA761'},
  628.     {'base':'w','letters':'\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73'},
  629.     {'base':'x','letters':'\u0078\u24E7\uFF58\u1E8B\u1E8D'},
  630.     {'base':'y','letters':'\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF'},
  631.     {'base':'z','letters':'\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763'}
  632. ];
  633.  
  634. var diacriticsMap = {};
  635.  
  636. for (var i=0; i < defaultDiacriticsRemovalMap.length; i++){
  637.     var letters = defaultDiacriticsRemovalMap[i].letters;
  638.     for (var j=0; j < letters.length ; j++){
  639.         diacriticsMap[letters[j]] = defaultDiacriticsRemovalMap[i].base;
  640.     }
  641. }
  642.  
  643. diacriticsMap["×"] = "x";
  644. diacriticsMap["³"] = "3";
  645. diacriticsMap["ß"] = "b";
  646. diacriticsMap["–"] = "-";
  647. diacriticsMap["²"] = "2";
  648. diacriticsMap["’"] = "'";
  649. diacriticsMap["@"] = "a";
  650. diacriticsMap["æ"] = "a";
  651. diacriticsMap["ñ"] = "n";
  652.  
  653.  
  654.  
  655. // "what?" version ... http://jsperf.com/diacritics/12
  656. function removeDiacritics (str) {
  657.     return str.replace(/[^a-z]/gi, function(a){
  658.        return diacriticsMap[a] || a;
  659.     });
  660. }
  661.  
  662. if (isNode) {
  663.     storedData["oh my kokoro"] = new Date().getTime()
  664.  
  665.     let l = new FilterManager(["o", "asd", "asd!", "asd!!*hi", "o", "tt", "oh my kokoro", "kokoro", "ktrōk"], 15);
  666.  
  667.     escapeRegExp = (v) => v.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  668.     search = (str, len, first, fuzzy) => {
  669.         s = l.filterBy(str, fuzzy);
  670.         if (s.length != len || (len > 0 && first !== s[0].listMatch.originalStr)) {
  671.             throw console.log(s, str, len)
  672.         }
  673.         return s
  674.     }
  675.  
  676.     expect = (s, str) => {
  677.         if (l.filterBy(s)[0] !== str) console.log(str)
  678.     }
  679.  
  680.     search("s", 3, "asd")
  681.     search("!s", 2, "asd!")
  682.     search("*", 1, "asd!!*hi")
  683.     search("*!", 1, "asd!!*hi")
  684.     search("!*", 1, "asd!!*hi")
  685.     search("!asd!*hi", 1, "asd!!*hi")
  686.     search("ko", 2, "kokoro")
  687.     search("kororo", 2, "kokoro", true)
  688.     search("ads", 3, "asd", true)
  689.     search("ktr", 1, "ktrōk")
  690.  
  691.     l = new FilterManager(["Kiss×sis", "Chōon Senshi Borgman: Lovers Rain", "idolm@aster"], 15)
  692.     search("kissx", 1, "Kiss×sis")
  693.     search("ooo", 1, "Chōon Senshi Borgman: Lovers Rain")
  694.     search("idolma", 1, "idolm@aster")
  695.  
  696.     l = new FilterManager(["asd!", "asd!!*hi"], 15, {entrySets: [{contains: true, clean: semiCleanString}]});
  697.     search("!", 2, "asd!")
  698.     search("!*", 1, "asd!!*hi")
  699.     search("*!", 0, "")
  700.  
  701.     module.exports = {FilterManager, options}
  702. }
  703.  
RAW Paste Data