Greasy Fork镜像 支持简体中文。

多邻国选词快捷键

使用快捷键刷多邻国. 在主页面使用l键快速开始学习;在学习页使用ctrl键播放语音, 使用回车键提交答案时为选词添加序号,退格键删除选词,删除键删除全部选词. 如果官方和脚本的快捷键无法正常使用, 需要在`vimium-c`等快捷键相关插件中排除多邻国网站. 如果发生无法输入文字的情况可以尝试在网页限制解除/文本选中复制相关脚本中排除多邻国网站

目前為 2024-05-10 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name 多邻国选词快捷键
  3. // @namespace http://tampermonkey.net/
  4. // @version 2024-05-07
  5. // @description 使用快捷键刷多邻国. 在主页面使用l键快速开始学习;在学习页使用ctrl键播放语音, 使用回车键提交答案时为选词添加序号,退格键删除选词,删除键删除全部选词. 如果官方和脚本的快捷键无法正常使用, 需要在`vimium-c`等快捷键相关插件中排除多邻国网站. 如果发生无法输入文字的情况可以尝试在网页限制解除/文本选中复制相关脚本中排除多邻国网站
  6. // @author v
  7. // @match https://www.duolingo.cn/*
  8. // @match https://www.duolingo.com/*
  9. // @license MIT
  10. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  11. // @require https://cdn.staticfile.org/jquery/3.3.1/jquery.min.js
  12. // @require http://cdn.staticfile.org/jquery/1.8.3/jquery.min.js
  13. // @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js
  14. // @grant GM_log
  15. // @grant GM_addStyle
  16. // ==/UserScript==
  17.  
  18. // 序号样式
  19. // todo GM_addStyle(...) is not a function
  20. // GM_addStyle(".p_item_tip {position: absolute; color: dodgerblue; background-color: greenyellow; border-radius: 5px; !important;}")
  21.  
  22. ;(function () {
  23. 'use strict'
  24. // 选词键顺序
  25. var chars = 'abcdefghijklnopqrstuvxyz1234567890-=[],./'
  26. // 题目区元素相关数据对象
  27. // type -1: 无效 0: 选择题(自带[数字]快捷键) 1: 组句题 2: 配对题(自带[数字]快捷键)
  28. // 3: 填空题(自带[首字母]快捷键) 4: 听写题(不需要处理) 5: 听写填空题(不需要处理)
  29. // 6: 小故事 7: 补全题(自带[首字母]快捷键)
  30. // el: 主要题目区元素
  31. // el2: 次要题目区元素
  32. var question = { type: -1 }
  33. // 始初化题目数据对象方法
  34. var init_question = function () {
  35. if (question.type > -1) {
  36. return question
  37. }
  38.  
  39. // 补全题(不需要处理)
  40. question.el = document.querySelector(
  41. 'div[data-test="challenge challenge-tapCloze"]'
  42. )
  43. if (question.el) {
  44. question.type = 7
  45. return
  46. }
  47. // 小故事
  48. question.el = document.getElementsByClassName('kbjat')
  49. if (question.el.length) {
  50. question.el = question.el[0].children
  51. question.type = 6
  52. // 每段数据所在属性名
  53. question.prop_field = Object.keys(question.el[0]).find(p =>
  54. p.startsWith('__reactFiber')
  55. )
  56. return
  57. }
  58. // 听写填空题(不需要处理)
  59. question.el = document.querySelector(
  60. 'div[data-test="challenge challenge-listenComplete"]'
  61. )
  62. if (question.el) {
  63. question.type = 5
  64. return
  65. }
  66. // 听写题(不需要处理)
  67. question.el = document.querySelector(
  68. 'div[data-test="challenge challenge-listenTap"]'
  69. )
  70. if (question.el) {
  71. question.type = 4
  72. return
  73. }
  74. // 填空题(自带,不需要处理)
  75. question.el = document.querySelector(
  76. 'div[data-test="challenge challenge-tapComplete"]'
  77. )
  78. if (question.el) {
  79. question.type = 3
  80. return
  81. }
  82. // 配对题(自带,不需要处理)
  83. question.el = document.querySelector(
  84. 'div[data-test="challenge challenge-listenMatch"]'
  85. )
  86. if (question.el) {
  87. question.el = question.el.children[0].children[1].children[0]
  88. question.type = 2
  89. return
  90. }
  91. // 组句题
  92. question.el = document.querySelector('div[data-test="word-bank"]')
  93. if (question.el) {
  94. question.type = 1
  95. question.el2 =
  96. question.el.parentElement.previousElementSibling.children[0].children[0].children[1]
  97. return
  98. }
  99. // 选择题(自带,不需要处理)
  100. question.el = document.querySelector('div[aria-label="choice"]')
  101. if (question.el) {
  102. question.type = 0
  103. return
  104. }
  105. // 未知题型
  106. question.type = -1
  107. }
  108.  
  109. // 防抖方法
  110. function debounce (func, delay) {
  111. let timeout
  112. return function () {
  113. const _this = this
  114. const args = [...arguments]
  115. if (timeout) {
  116. clearTimeout(timeout)
  117. }
  118. timeout = setTimeout(() => {
  119. func.apply(_this, args)
  120. }, delay)
  121. }
  122. }
  123.  
  124. // 为单词/短语添加序号方法
  125. var process_order = function () {
  126. var play_btn = document.querySelector('button[data-test="player-next"]')
  127. if (!play_btn) {
  128. // || play_btn.getAttribute('aria-disabled') != 'true'
  129. return
  130. }
  131. init_question()
  132. if (question.type == 1) {
  133. for (var i = 0; i < question.el.children.length; i++) {
  134. var item = question.el.children[i]
  135. // 修改按钮padding, 加宽以防止序号覆盖文字
  136. item
  137. .querySelector('button')
  138. .setAttribute('style', '--web-ui_button-padding: 8px 4px 8px 10px;')
  139. // 添加序号
  140. let new_el = document.createElement('span')
  141. new_el.textContent = chars.charAt(i)
  142. // new_el.className = 'p_item_tip'
  143. new_el.setAttribute(
  144. 'style',
  145. 'position: absolute; color: dodgerblue; background-color: greenyellow; border-radius: 5px;'
  146. )
  147. item.appendChild(new_el)
  148. }
  149. }
  150. }
  151. // 为单词/短语添加序号方法(防抖)
  152. var process_order_debounce = debounce(process_order, 500)
  153.  
  154. // 按键事件监听
  155. document.addEventListener('keyup', function (event) {
  156. // GM_log('按键:' + event.key)
  157. // 当前页
  158. var page_name = window.location.pathname
  159. // 在主页
  160. if (page_name == '/learn') {
  161. // 按l键直接学习(跳转/lesson页)
  162. if (event.key == 'l') {
  163. window.location.href = '/lesson'
  164. }
  165.  
  166. // 按回车键直接学习(跳转/lesson页)
  167. // 官方回车键失效?
  168. if (event.key == 'Enter') {
  169. setTimeout(function () {
  170. var el = document.querySelector('a[href="/lesson"], a[href="/lesson?mode=LISTEN"]')
  171. if (el) {
  172. el.click()
  173. }
  174. }, 150)
  175. }
  176. return
  177. }
  178.  
  179. // 在学习页
  180. if (page_name.startsWith('/lesson')) {
  181. // 初始化题目区数据
  182. init_question()
  183. // 回车键: 延时为下一题单词/短语添加序号
  184. if (event.key == 'Enter') {
  185. question.type = -1
  186.  
  187. process_order_debounce()
  188. return
  189. }
  190. // 退格键, 删除最后一个选词
  191. if (event.key == 'Backspace') {
  192. if (question.el2) {
  193. var selects = question.el2.children
  194. var cnt = selects.length
  195. var last_select = selects[cnt - 1]
  196. last_select.querySelector('button').click()
  197. }
  198. return
  199. }
  200. // 删除键, 删除所有选词
  201. if (event.key == 'Delete') {
  202. if (question.el2) {
  203. var selects = question.el2.children
  204. for (var i = selects.length; i > 0; i--) {
  205. var select = selects[i - 1]
  206. select.querySelector('button').click()
  207. }
  208. }
  209. return
  210. }
  211. // 没有题目区时, 按z键时如果页面有"不,谢谢"按钮, 就点击它
  212. if (event.key == 'z' && question.type == -1) {
  213. // 跳过按钮
  214. var skip_el = document.querySelector(
  215. 'button[data-test="plus-no-thanks"], button[data-test="practice-hub-ad-no-thanks-button"]'
  216. )
  217. if (skip_el) {
  218. skip_el.click()
  219. return
  220. }
  221. // 挑战传奇按钮
  222. var legendary_el = document.querySelector(
  223. 'a[data-test="legendary-start-button"]'
  224. )
  225. if (legendary_el) {
  226. // 找到"继续"按钮并点击
  227. legendary_el.parentElement.nextElementSibling.children[0].children[1].click()
  228. return
  229. }
  230. }
  231.  
  232. // Control键, 点击扬声器按钮播放语音
  233. if (event.key == 'Control') {
  234. if (question.type == 6) {
  235. // 小故事, 找最后一个已读的音频
  236. var last_listen
  237. for (var i = 0; i < question.el.length; i++) {
  238. // 当前遍历的元素
  239. var el = question.el[i]
  240. // 当前遍历的元素包含的类列表
  241. var class_list = Array.from(el.classList)
  242. // 当前元素的音频是否已听过
  243. var flag = el[question.prop_field].flags
  244. // 只有一个类, 答题区, 忽略
  245. if (class_list.length == 1) {
  246. continue
  247. }
  248. // 有两个类
  249. if (class_list.length == 2) {
  250. // flag>0时已经听过, flag=0时为标题, 设为最后听过的音频元素
  251. last_listen = el
  252. continue
  253. }
  254. // 有三个类
  255. if (class_list.length == 3) {
  256. if (flag == 0) {
  257. // 没有听过, 结束循环
  258. break
  259. } else {
  260. // 有三个类但flag>0的情况, 第三个类不同于常规音频,
  261. // 表明该元素非音频, 属于有四个类(听力组句题)的在答情况
  262. continue
  263. }
  264. }
  265. // 有四个类, 听力组句题, flag=0时未答, flag>0时已答
  266. if (class_list.length == 4) {
  267. continue
  268. }
  269. }
  270. if (last_listen) {
  271. last_listen.querySelector('div[data-test="audio-button"]').click()
  272. }
  273. } else {
  274. // 常规题, 找第一个播放按钮
  275. var els = document.getElementsByClassName('fs-exclude')
  276. if (els) {
  277. els[0].click()
  278. }
  279. }
  280.  
  281. return
  282. }
  283.  
  284. // 到这里已经处理完特殊按键事件
  285. // 剩下按字母/数字键的情况
  286. // 将按键转为序号, 未找到时不处理
  287. var idx = chars.indexOf(event.key)
  288. if (idx < 0) {
  289. return
  290. }
  291.  
  292. // 小故事
  293. if (question.type == 6) {
  294. for (var i = 0; i < question.el.length; i++) {
  295. var el = question.el[i]
  296. var class_list = Array.from(el.classList)
  297. if (
  298. class_list.length == 1 &&
  299. el.children.length > 0 &&
  300. i < question.el.length - 1
  301. ) {
  302. // 当前处于答题状态
  303. // 除了最后的配对题和选项是汉字的选择题外其余题几乎都是可以首字母匹配的
  304. // 因此除了最后的配对题不做处理外其余添加使用数字作选择
  305. var btn_list = el.querySelectorAll('button')
  306. var no = Number(event.key)
  307. if (!isNaN(no) && no <= btn_list.length) {
  308. btn_list[no - 1].click()
  309. }
  310. break
  311. }
  312. }
  313. return
  314. }
  315.  
  316. // 选择题
  317. if (question.type == 0) {
  318. if (question.el.children.length >= idx) {
  319. question.el.children[idx].click()
  320. }
  321. return
  322. }
  323.  
  324. // 组句题
  325. if (question.type == 1) {
  326. if (question.el.children.length >= idx) {
  327. var el = question.el.children[idx]
  328. var item = el.children[0].children[0]
  329. if (item.getAttribute('aria-disabled') != 'true') {
  330. item.click()
  331. return
  332. }
  333. var text = item.querySelector(
  334. 'span[data-test="challenge-tap-token-text"'
  335. ).innerHTML
  336. var selects = question.el2.querySelectorAll(
  337. 'span[data-test="challenge-tap-token-text"]'
  338. )
  339. for (var i = 0; i < selects.length; i++) {
  340. var select = selects[i]
  341. if (select.innerHTML == text) {
  342. select.parentElement.parentElement.click()
  343. return
  344. }
  345. }
  346. }
  347. }
  348.  
  349. // 配对题(自带,不需要处理)
  350. // if (question.type == 2) {
  351. // var no = Number(event.key)
  352. // if (!isNaN(no)) {
  353. // idx = no - 1
  354. // var length = question.el.children.length
  355. // if (idx >=0 && idx < length) {
  356. // var el = question.el.children[idx].getElementsByClassName('fs-exclude')
  357. // if (el) {
  358. // el.click()
  359. // return
  360. // }
  361. // el = question.el.children[idx].querySelector('button').click()
  362. // }
  363. // }
  364. // return
  365. // }
  366. }
  367. })
  368. })()

QingJ © 2025

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