Greasy Fork镜像 支持简体中文。

WaniKani Multiple Answer Input (2023)

Input multiple readings/meanings into Wanikani

目前為 2023-05-20 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name WaniKani Multiple Answer Input (2023)
  3. // @namespace http://www.wanikani.com
  4. // @version 2.0.2
  5. // @description Input multiple readings/meanings into Wanikani
  6. // @author polv
  7. // @match https://www.wanikani.com/extra_study/session*
  8. // @match https://www.wanikani.com/review/session*
  9. // @match https://www.wanikani.com/subjects/*
  10. // @match https://preview.wanikani.com/extra_study/session*
  11. // @match https://preview.wanikani.com/review/session*
  12. // @match https://preview.wanikani.com/subjects/*
  13. // @icon https://www.google.com/s2/favicons?sz=64&domain=wanikani.com
  14. // @license MIT
  15. // @homepage https://gf.qytechs.cn/en/scripts/466680-wanikani-multiple-answer-input-2023
  16. // @supportURL https://community.wanikani.com/t/userscript-multiple-input-answer-2023/61931
  17. // @source https://github.com/patarapolw/wanikani-userscript/blob/master/userscripts/mulitple-answer.user.js
  18. // @grant none
  19. // ==/UserScript==
  20.  
  21. // @ts-check
  22. (function () {
  23. 'use strict';
  24.  
  25. /** @typedef {'whitelist' | 'blacklist' | 'warning'} AuxiliaryType */
  26.  
  27. /**
  28. * @typedef {{
  29. * questionType: string
  30. * item: {
  31. * type: string
  32. * characters: string
  33. * readings?: string[]
  34. * auxiliary_readings?: {
  35. * reading: string
  36. * type: AuxiliaryType
  37. * }[]
  38. * meanings: string[]
  39. * auxiliary_meanings: {
  40. * meaning: string
  41. * type: AuxiliaryType
  42. * }[]
  43. * }
  44. * userSynonyms: string[]
  45. * response: string
  46. * }} EvaluationParam
  47. */
  48.  
  49. /**
  50. * @typedef {{
  51. * action: 'pass' | 'fail' | 'retry'
  52. * message: null | {
  53. * text: string
  54. * type: 'itemInfoException' | 'answerException'
  55. * }
  56. * }} Evaluation
  57. */
  58.  
  59. /** @typedef {((e: EvaluationParam) => Evaluation)} EvaluationFunction */
  60. /** @typedef {((e: EvaluationParam, check: EvaluationFunction) => Evaluation | null)} TryEvaluationFunction */
  61.  
  62. class ModAnswerChecker {
  63. /**
  64. * @type {TryEvaluationFunction[]}
  65. */
  66. mods = [];
  67.  
  68. /**
  69. *
  70. * @param {TryEvaluationFunction} fn
  71. */
  72. register(fn) {
  73. this.mods.push(fn);
  74. }
  75.  
  76. constructor() {
  77. // Automatically init on new instance
  78. this.init();
  79. }
  80.  
  81. async init() {
  82. const answerChecker = await this.getAnswerChecker(60000);
  83.  
  84. answerChecker.oldEvaluate = answerChecker.evaluate.bind(answerChecker);
  85.  
  86. /** @type {(fns: TryEvaluationFunction[]) => EvaluationFunction} */
  87. const evaluateWith = (fns) => {
  88. return (e) => {
  89. for (const fn of fns) {
  90. const r = fn(e, evaluateWith(fns.filter((it) => it !== fn)));
  91. if (r) return r;
  92. }
  93. return answerChecker.oldEvaluate(e);
  94. };
  95. };
  96.  
  97. answerChecker.evaluate = evaluateWith(this.mods);
  98. }
  99.  
  100. /**
  101. * Get answerChecker Object
  102. * @param {number} timeout
  103. * @returns {Promise<{
  104. * oldEvaluate: EvaluationFunction
  105. * evaluate: EvaluationFunction
  106. * }>}
  107. */
  108. async getAnswerChecker(timeout) {
  109. //Stimulus.controllers.filter((x)=>{return x.answerChecker;})[0]
  110. const start = Date.now();
  111.  
  112. function waitForAnswerChecker(resolve, reject) {
  113. // @ts-ignore
  114. const Stimulus = window.Stimulus;
  115. if (
  116. Stimulus &&
  117. Stimulus.controllers.filter((x) => {
  118. return x.answerChecker;
  119. })[0]
  120. ) {
  121. var answerChecker = Stimulus.controllers.filter((x) => {
  122. return x.answerChecker;
  123. })[0].answerChecker;
  124. resolve(answerChecker);
  125. } else if (timeout && Date.now() - start >= timeout)
  126. reject(new Error('timeout'));
  127. else setTimeout(waitForAnswerChecker.bind(this, resolve, reject), 30);
  128. }
  129.  
  130. return new Promise(waitForAnswerChecker);
  131. }
  132. }
  133.  
  134. // @ts-ignore
  135. window.modAnswerChecker = window.modAnswerChecker || new ModAnswerChecker();
  136. /** @type {ModAnswerChecker} */
  137. // @ts-ignore
  138. const modAnswerChecker = window.modAnswerChecker;
  139.  
  140. //////////////////////////////////////////////////////////////////////////////
  141.  
  142. /**
  143. * !Multiple Answer Input section
  144. * @see https://community.wanikani.com/t/userscript-multiple-answer-input-revamped/49075/44
  145. */
  146. modAnswerChecker.register((e, tryCheck) => {
  147. const splitter = e.questionType === 'reading' ? /・/g : /;/g;
  148.  
  149. /** @type {Record<string, Evaluation[]>} */
  150. const evalActionMap = {};
  151.  
  152. for (const subResponse of e.response.split(splitter)) {
  153. let response = subResponse.trim();
  154. if (!response) continue;
  155.  
  156. const result = tryCheck({ ...e, response });
  157.  
  158. const sect = evalActionMap[result.action] || [];
  159. sect.push(result);
  160. evalActionMap[result.action] = sect;
  161. }
  162.  
  163. for (const actionType of ['fail', 'retry', 'pass']) {
  164. if (evalActionMap[actionType]) {
  165. return (
  166. evalActionMap[actionType].find((r) => r.message) ||
  167. evalActionMap[actionType][0]
  168. );
  169. }
  170. }
  171.  
  172. return null;
  173. });
  174. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址