Wanikani Open Framework: Visually similar kanji filter

Adds a wkof filter for visually similar kanji

  1. // ==UserScript==
  2. // @name Wanikani Open Framework: Visually similar kanji filter
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0.0
  5. // @description Adds a wkof filter for visually similar kanji
  6. // @author Kumirei
  7. // @include https://www.wanikani.com/*
  8. // @include *preview.wanikani.com*
  9. // @grant none
  10. // ==/UserScript==
  11. /*jshint esversion: 8 */
  12.  
  13. (function(wkof) {
  14. let script_id = "VSKFilter";
  15. if (!wkof) return;
  16.  
  17. var visuallySimilarFilename = 'https://raw.githubusercontent.com/mwil/wanikani-userscripts/master/wanikani-similar-kanji/db/stroke_edit_dist_esc.json';
  18. var visuallySimilarData;
  19. async function get_data() {
  20. let data = await wkof.load_file(visuallySimilarFilename, true);
  21. visuallySimilarData = JSON.parse(data);
  22. }
  23.  
  24. function ageCache(){
  25. // periodically ages the cache
  26. let oneMonth = 1000*60*60*24*30;
  27. var now = Date.now();
  28. if (wkof.settings[script_id] === undefined) wkof.settings[script_id] = {};
  29. if (wkof.settings[script_id].lastTime === undefined){
  30. wkof.settings[script_id].lastTime = now;
  31. wkof.Settings.save(script_id);
  32. }
  33. let lastTime = wkof.settings[script_id].lastTime;
  34. if (now > lastTime + oneMonth){
  35. wkof.file_cache.delete(visuallySimilarFilename);
  36. wkof.settings[script_id].lastTime = now;
  37. wkof.Settings.save(script_id);
  38. }
  39. }
  40.  
  41. // Turns comma separated lists into arrays. Supports latin and Japanese commas. Converts Japanese periods into latin decimal points
  42. // Adapted from a handy function of rfindley
  43. function split_list(str) {return str.replace(/./g,'.').replace(/[、,]/g,',').replace(/[\s ]+/g,' ').trim().replace(/ *, */g, ',').toLowerCase().split(',').filter(function(name) {return (name.length > 0);});}
  44.  
  45. // Translate Japanese numerals into latin so they can be parsed into numbers by Number()
  46. function tr(text) {
  47. return text.replace( /[0123456789]/g, (chr => '0123456789'.charAt('0123456789'.indexOf( chr ))));
  48. }
  49.  
  50. // Create an array of visually similar kanji for all search terms
  51. function makeVisuallySimilarArrays(str){
  52. var arr = split_list(str);
  53. if (arr.length === 0) return [];
  54. // compute the similarity threshold if one is provided
  55. var threshold = 0.0; // default if threshold is absent
  56. var first = Number(tr(arr[0]));
  57. if (!isNaN(first)){
  58. // number is present - calculate threshold
  59. if (arr.length <= 1) return [];
  60. var second = Number(tr(arr[1]));
  61. if (isNaN(second)) {
  62. // decimal separator not a comma - first is threshold
  63. threshold = first;
  64. arr = arr.splice(1);
  65. } else {
  66. // decimal separator is a comma - calculate threshold
  67. threshold = first + second/10;
  68. arr = arr.splice(2);
  69. }
  70. }
  71. return arr.reduce(filterThreshold, []);
  72.  
  73. function filterThreshold(running, kanji){
  74. if (visuallySimilarData[kanji] === undefined) return running; // ignore trash input
  75. for (var data of visuallySimilarData[kanji]){
  76. if (data.score >= threshold) running.push(data.kan);
  77. }
  78. return running;
  79. }
  80. }
  81.  
  82. // Adds the filter to wkof
  83. function register_filter(data) {
  84. wkof.ready('ItemData.registry').then(()=>{
  85. wkof.ItemData.registry.sources.wk_items.filters.include_visually_similar_kanji = {
  86. filter_func: function(filter_value, item){if (item.object === "kanji") item.visually_similar_kanji = visuallySimilarData[item.data.characters]; return true;}
  87. };
  88. wkof.ItemData.registry.sources.wk_items.filters.visually_similar_kanji = {
  89. type: 'text',
  90. label: 'LY Visually Similar',
  91. placeholder: '0.5,人、能、力',
  92. hover_tip: 'Select the kanji whose similar kanji you want to study.\nMultiple search terms are separated by commas.\n\n'+
  93. 'You may specify a similarity threshold as as a real number\nbetween 0 and 1 at the beginning of the search terms.\n'+
  94. '0 means all similar kanji. Higher numbers give fewer kanji.\n\nThis filter uses similarity data by Lars Yencken.',
  95. default: '',
  96. filter_func: function (filterValue, item){return (item.object === 'kanji' && filterValue.indexOf(item.data.characters) >= 0);},
  97. filter_value_map: makeVisuallySimilarArrays,
  98. };
  99. wkof.set_state(script_id, 'ready');
  100. });
  101. }
  102.  
  103. // startup
  104. get_data();
  105. register_filter();
  106. wkof.include('Settings');
  107. wkof.ready('Settings')
  108. .then(function(){return wkof.Settings.load(script_id);})
  109. .then(ageCache);
  110.  
  111. })(window.wkof);