多邻国拼音选词快捷键简化版(快捷键不包括删除选词)

Enter键:快速学习 或 添加小故事,中文选词快捷键 或 确认消息;ctrl键播放语音,alt慢速播放;Backspace键删除选词;Tab键跳过题目或自动答题;Esc退出学习页面. 参考多邻国选词快捷键https://greasyfork.org/zh-CN/scripts/493966-%E5%A4%9A%E9%82%BB%E5%9B%BD%E9%80%89%E8%AF%8D%E5%BF%AB%E6%8D%B7%E9%94%AE

Versione datata 23/06/2024. Vedi la nuova versione l'ultima versione.

  1. // ==UserScript==
  2. // @name 多邻国拼音选词快捷键简化版(快捷键不包括删除选词)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.39.6
  5. // @description Enter键:快速学习 或 添加小故事,中文选词快捷键 或 确认消息;ctrl键播放语音,alt慢速播放;Backspace键删除选词;Tab键跳过题目或自动答题;Esc退出学习页面. 参考多邻国选词快捷键https://greasyfork.org/zh-CN/scripts/493966-%E5%A4%9A%E9%82%BB%E5%9B%BD%E9%80%89%E8%AF%8D%E5%BF%AB%E6%8D%B7%E9%94%AE
  6. // @author Gelan
  7. // @match https://www.duolingo.com/*
  8. // @match https://www.duolingo.cn/*
  9. // @license MIT
  10. // @icon https://d35aaqx5ub95lt.cloudfront.net/images/super/fb7130289a205fadd2e196b9cc866555.svg
  11. // @require https://unpkg.com/pinyin-pro
  12. // ==/UserScript==
  13.  
  14. ;(function () {
  15. 'use strict'
  16. var { pinyin } = pinyinPro;
  17. var question = { type: -1 , type2: -1 }
  18. var userInput = document.createElement('span')
  19. // 始初化题目数据对象方法
  20. var init_question = function () {
  21. let challengeHeader = document.querySelector('h1[data-test="challenge-header"]');
  22. // 对话
  23. question.el = document.querySelector(
  24. 'div[data-test="challenge challenge-dialogue"]'
  25. )
  26. if (question.el) {
  27. question.type = 10
  28. return
  29. }
  30. // 听写
  31. question.el = document.querySelector(
  32. 'div[data-test="challenge challenge-listen"]'
  33. )
  34. if (question.el) {
  35. question.type = 9
  36. }
  37. // 口语
  38. question.el = document.querySelector(
  39. 'div[data-test="challenge challenge-speak"], div[data-test="challenge challenge-listenSpeak"]'
  40. )
  41. if (question.el) {
  42. question.type = 8
  43. return
  44. }
  45. // 补全
  46. question.el = document.querySelector(
  47. 'div[data-test="challenge challenge-tapCloze"]'
  48. )
  49. if (question.el) {
  50. question.type = 7
  51. return
  52. }
  53. // 小故事
  54. question.el = document.getElementsByClassName('kbjat')
  55. if (question.el.length) {
  56. question.el = question.el[0].children
  57. question.type = 6
  58. // 每段数据所在属性名
  59. question.prop_field = Object.keys(question.el[0]).find(p => p.startsWith('__reactFiber'))
  60. return
  61. }
  62. // 听写填空
  63. question.el = document.querySelector(
  64. 'div[data-test="challenge challenge-listenComplete"]'
  65. )
  66. if (question.el) {
  67. question.type = 5
  68. return
  69. }
  70. // 选项听写
  71. question.el = document.querySelector(
  72. 'div[data-test="challenge challenge-listenTap"]'
  73. )
  74. if (question.el) {
  75. question.type = 4
  76. return
  77. }
  78. // 选项填空
  79. question.el = document.querySelector(
  80. 'div[data-test="challenge challenge-tapComplete"]'
  81. )
  82. if (question.el) {
  83. question.type = 3
  84. return
  85. }
  86. // 配对
  87. question.el = document.querySelector(
  88. 'div[data-test="challenge challenge-listenMatch"]'
  89. )
  90. if (question.el) {
  91. question.el = question.el.children[0].children[1].children[0]
  92. question.type = 2
  93. return
  94. }
  95. // 中文组句
  96. question.el = document.querySelector('div[data-test="word-bank"]')
  97. if (question.el) {
  98. let spanElement = challengeHeader.querySelector('span');
  99. if (spanElement.textContent === "用中文写出这句话") {
  100. question.el2 =
  101. question.el.parentElement.previousElementSibling.children[0].children[0].children[1]
  102. question.type = 1
  103. return
  104. }
  105. userInput.textContent = ''
  106. challengeHeader.append(userInput)
  107. }
  108. // 选择
  109. question.el = document.querySelector('div[aria-label="choice"]')
  110. if (question.el) {
  111. question.type = 0
  112. if(challengeHeader) {
  113. let spanElement = challengeHeader.querySelector('span');
  114. if (spanElement.textContent === "阅读并回答") {
  115. question.type2 = 0
  116. }
  117. else if (spanElement.textContent === "你听到了什么?") {
  118. question.type2 = 1
  119. }
  120. else if (spanElement.textContent === "选择听到的内容") {
  121. question.type2 = 2
  122. }
  123. else if (spanElement.textContent === "听音辩词") {
  124. question.type2 = 3
  125. }
  126. return
  127. }
  128. }
  129. // 其他
  130. question.el = null
  131. question.type = -1
  132. }
  133. var process = function () {
  134. if (question.type == 1) {
  135. if (document.querySelector('rt'))
  136. {
  137. return;
  138. }
  139. for (let i = 0; i < question.el.children.length; i++) {
  140. let span = question.el.children[i].querySelector('span[data-test="challenge-tap-token-text"]');
  141. let rt = document.createElement('rt');
  142. rt.style.cssText = 'color: gray; display: flex; flex-direction: row;';
  143. rt.style.fontSize = parseFloat(window.getComputedStyle(span).fontSize) * 0.5 + 'px';
  144. rt.textContent = pinyin(span.textContent, { toneType: 'none' });
  145. rt.textContent = rt.textContent.replace(/ü/g, 'v');
  146.  
  147. span.parentElement.style.cssText = 'display: flex; flex-direction: column;';
  148. span.parentElement.insertBefore(rt, span);
  149. }
  150. }
  151. else if (question.type == 6) {
  152. if (document.querySelector('span.append')) {
  153. return
  154. }
  155. let ul = document.querySelector('ul._13ieZ')
  156. if(!ul) {
  157. return
  158. }
  159. for (let i = 0; i < ul.children.length; i++) {
  160. let li = ul.children[i]
  161. let span = document.createElement('span')
  162. span.className = 'append'
  163. span.textContent = i + 1
  164. li.insertBefore(span,li.querySelector('button[data-test="stories-choice"]'))
  165. }
  166. }
  167. }
  168. var remove = function () {
  169. let rts = document.querySelectorAll('rt')
  170. rts.forEach(function(rt) {
  171. rt.parentElement.removeChild(rt);
  172. })
  173. if (question.type == 1) {
  174. let selects = question.el2.children
  175. try {
  176. selects[0].querySelector('button')
  177. } catch {
  178. return true
  179. }
  180. for (let i = selects.length; i > 0; i--) {
  181. let select = selects[i - 1]
  182. select.querySelector('button').click()
  183. }
  184. return false
  185. }
  186. return true
  187. }
  188. let pinyinIndex = 0; // 匹配拼音按键索引
  189. let matchs = []; // 匹配按钮
  190. let pinyins = []; // 所有拼音按键
  191. let isCombi = false; // 是否为组合键(shift+任意字符)
  192.  
  193. document.addEventListener('keyup', function (event) {
  194. if (!document.querySelector('div.kPqwA')) {
  195. if (event.key == 'Enter') {
  196. if (window.location.pathname == '/learn') {
  197. window.location.href = '/lesson'
  198. }
  199. }
  200. return
  201. }
  202. init_question()
  203. // Esc键,退出练习按钮 或 不,谢谢
  204. if (event.key == 'Escape') {
  205. let quit = document.querySelector('button[data-test="quit-button"]')
  206. let no = document.querySelector('button[data-test="notification-drawer-no-thanks-button"]')
  207. if (no) {
  208. no.click()
  209. }
  210. if (quit) {
  211. quit.click()
  212. }
  213. return
  214. }
  215. // Backspace键, 删除最后一个选词
  216. if (event.key == 'Backspace') {
  217. if (question.el2) {
  218. var selects = question.el2.children
  219. var cnt = selects.length
  220. var last_select = selects[cnt - 1]
  221. last_select.querySelector('button').click()
  222. }
  223. return
  224. }
  225. // Shift键, 在按钮选词和直接输入中切换 或 在减小难度和增大难度中切换
  226. if (!event.shiftKey && event.key == 'Shift') {
  227. if(!isCombi) {
  228. document.querySelector('button[data-test="player-toggle-keyboard"]').click()
  229. }
  230. isCombi = false;
  231. return
  232. }
  233. else if (event.shiftKey && event.key != 'Shift') {
  234. isCombi = true;
  235. return
  236. }
  237. // Control键, 播放音频
  238. if (event.key == 'Control') {
  239. if (question.type == 0 && question.type2 == 0) {
  240. let el = document.querySelector('button._15600')
  241. el.click()
  242. }
  243. else if (question.type == 0 && question.type2 == 1) {
  244. let el = document.querySelector('button._3U_eC')
  245. el.click()
  246. }
  247. else if (question.type == 0 && question.type2 == 3) {
  248. let el = document.querySelector('div._1j8q_')
  249. el.children[0].click()
  250. }
  251. else if (question.type == 1 || question.type == 8 || question.type == 10) {
  252. let el = document.querySelector('button._1GJVt')
  253. el.click()
  254. }
  255. else if ((question.type == 0 && question.type2 == 2) || question.type == 4) {
  256. let el = document.querySelector('div._3qAs-')
  257. el.children[0].children[0].click()
  258. }
  259. else if (question.type == 5 || question.type == 9) {
  260. let el = document.querySelector('div._1DLP9')
  261. el.children[0].children[0].children[0].click()
  262. }
  263. else if (question.type == 6) {
  264. var last_listen
  265. for (var i = 0; i < question.el.length; i++) {
  266. var el = question.el[i]
  267. var class_list = Array.from(el.classList)
  268. var flag = el[question.prop_field].flags
  269. if (class_list.length == 1) {
  270. continue
  271. }
  272. if (class_list.length == 2) {
  273. last_listen = el
  274. continue
  275. }
  276. if (class_list.length == 3) {
  277. if (flag == 0) {
  278. break
  279. } else {
  280. continue
  281. }
  282. }
  283. if (class_list.length == 4) {
  284. continue
  285. }
  286. }
  287. if (last_listen) {
  288. last_listen.querySelector('div[data-test="audio-button"]').click()
  289. }
  290. }
  291. else {
  292. var els = document.getElementsByClassName('fs-exclude')
  293. if (els) {
  294. els[0].click()
  295. }
  296. }
  297. return
  298. }
  299. // Alt键,慢速播放音频
  300. if (event.key == 'Alt') {
  301. event.preventDefault();
  302. if ((question.type == 0 && question.type2 == 2)|| question.type == 4) {
  303. let el = document.querySelector('div._3qAs-')
  304. el.children[1].children[0].children[0].click()
  305. }
  306. else if (question.type == 0 && question.type2 == 3) {
  307. let el = document.querySelector('div._1j8q_')
  308. el.children[1].click()
  309. }
  310. else if(question.type == 5 || question.type == 9) {
  311. let el = document.querySelector('div._1DLP9')
  312. el.children[1].children[0].children[0].click()
  313. }
  314. return
  315. }
  316. // Tab键,跳过题目或自动答题(需下载Duolingo Pro BETA)
  317. if (event.key == 'Tab') {
  318. event.preventDefault();
  319. let solve = document.querySelector('#solveAllButton')
  320. if (solve) {
  321. if (remove()) {
  322. solve.previousElementSibling.click()
  323. }
  324. }
  325. else {
  326. document.querySelector('button[data-test="player-skip"]').click()
  327. }
  328. return
  329. }
  330. // Enter键,呼出process 或 确认消息
  331. if (event.key == 'Enter') {
  332. let yes = document.querySelector('button[data-test="notification-button"]')
  333. if(yes) {
  334. yes.click()
  335. } else {
  336. process()
  337. }
  338. return
  339. }
  340.  
  341. const keyPressed = event.key;
  342. if (!document.querySelector('rt')) {
  343. return
  344. }
  345. if (question.type == 1) {
  346. pinyins = document.querySelectorAll('rt');
  347. matchs = [];
  348. userInput.textContent = " 用户输入: " + keyPressed
  349.  
  350. for (let i = 0; i < pinyins.length; i++) {
  351. let pinyin = pinyins[i];
  352. if (pinyin.innerText[pinyinIndex] === keyPressed && pinyin.parentElement.parentElement.getAttribute('aria-disabled') != 'true') {
  353. if (pinyinIndex === 0) {
  354. pinyin.style.color = 'lightblue';
  355. }
  356. else {
  357. if (pinyin.style.color !== 'lightblue') {
  358. continue;
  359. }
  360. }
  361.  
  362. if(pinyin.innerText[pinyinIndex] === ' ')
  363. {
  364. event.preventDefault();
  365. }
  366.  
  367. matchs.push(pinyin);
  368. }
  369. else {
  370. pinyin.style.color = 'gray';
  371. }
  372. }
  373. if (matchs.length === 1) {
  374. matchs[0].parentElement.parentElement.click();
  375. pinyinIndex = 0;
  376. matchs = [];
  377. }
  378. else if (matchs.length > 1) {
  379. let num = 1;
  380. for (let i = 0; i < matchs.length; i++) {
  381. let match = matchs[i]
  382. if (pinyinIndex + 1 === match.textContent.length && !match.textContent.match(/\d+/g)) {
  383. match.textContent += num++;
  384. }
  385. }
  386. pinyinIndex++;
  387. } else {
  388. pinyinIndex = 0;
  389. matchs = [];
  390. }
  391. }
  392. else if (question.type == 6) {
  393. let spans = document.querySelectorAll('span.append')
  394. for(let i = 0 ; i < spans.length; i++)
  395. {
  396. let span = spans[i]
  397. if (keyPressed == span.textContent)
  398. {
  399. span.nextElementSibling.click()
  400. break
  401. }
  402. }
  403. }
  404. })
  405. })()