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

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

As of 2024-06-27. See the latest version.

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