您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展(如 Stylus)后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
您需要先安装一款用户样式管理器扩展后才能安装此样式。
(我已经安装了用户样式管理器,让我安装!)
- // ==UserScript==
- // @name 多邻国选词快捷键
- // @namespace http://tampermonkey.net/
- // @version 2024-05-07
- // @description 使用快捷键刷多邻国. 在主页面使用l键快速开始学习;在学习页使用ctrl键播放语音, 使用回车键提交答案时为选词添加序号,退格键删除选词,删除键删除全部选词. 如果官方和脚本的快捷键无法正常使用, 需要在`vimium-c`等快捷键相关插件中排除多邻国网站
- // @author v
- // @match https://www.duolingo.cn/*
- // @match https://www.duolingo.com/*
- // @license MIT
- // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
- // @require https://cdn.staticfile.org/jquery/3.3.1/jquery.min.js
- // @require http://cdn.staticfile.org/jquery/1.8.3/jquery.min.js
- // @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js
- // @grant GM_log
- // @grant GM_addStyle
- // ==/UserScript==
-
- // 序号样式
- // todo GM_addStyle(...) is not a function
- // GM_addStyle(".p_item_tip {position: absolute; color: dodgerblue; background-color: greenyellow; border-radius: 5px; !important;}")
-
- ;(function () {
- 'use strict'
- // 选词键顺序
- var chars = 'abcdefghijklnopqrstuvxyz1234567890-=[],./'
- // 题目区元素相关数据对象
- // type -1: 无效 0: 选择题(自带[数字]快捷键) 1: 组句题 2: 配对题(自带[数字]快捷键)
- // 3: 填空题(自带[首字母]快捷键) 4: 听写题(不需要处理) 5: 听写填空题(不需要处理) 6: 小故事
- // el: 主要题目区元素
- // el2: 次要题目区元素
- var question = { type: -1 }
- // 始初化题目数据对象方法
- var init_question = function () {
- if (question.type > -1) {
- return question
- }
-
- // 小故事
- question.el = document.getElementsByClassName('kbjat')
- if (question.el.length) {
- question.el = question.el[0].children
- question.type = 6
- // 每段数据所在属性名
- question.prop_field = Object.keys(question.el[0]).find(p =>
- p.startsWith('__reactFiber')
- )
- return
- }
- // 听写填空题(不需要处理)
- question.el = document.querySelector(
- 'div[data-test="challenge challenge-listenComplete"]'
- )
- if (question.el) {
- question.type = 5
- return
- }
- // 听写题(不需要处理)
- question.el = document.querySelector(
- 'div[data-test="challenge challenge-listenTap"]'
- )
- if (question.el) {
- question.type = 4
- return
- }
- // 填空题(自带,不需要处理)
- question.el = document.querySelector(
- 'div[data-test="challenge challenge-tapComplete"]'
- )
- if (question.el) {
- question.type = 3
- return
- }
- // 配对题(自带,不需要处理)
- question.el = document.querySelector(
- 'div[data-test="challenge challenge-listenMatch"]'
- )
- if (question.el) {
- question.el = question.el.children[0].children[1].children[0]
- question.type = 2
- return
- }
- // 组句题
- question.el = document.querySelector('div[data-test="word-bank"]')
- if (question.el) {
- question.type = 1
- question.el2 =
- question.el.parentElement.previousElementSibling.children[0].children[0].children[1]
- return
- }
- // 选择题(自带,不需要处理)
- question.el = document.querySelector('div[aria-label="choice"]')
- if (question.el) {
- question.type = 0
- return
- }
- // 未知题型
- question.type = -1
- }
-
- // 防抖方法
- function debounce (func, delay) {
- let timeout
- return function () {
- const _this = this
- const args = [...arguments]
- if (timeout) {
- clearTimeout(timeout)
- }
- timeout = setTimeout(() => {
- func.apply(_this, args)
- }, delay)
- }
- }
-
- // 为单词/短语添加序号方法
- var process_order = function () {
- var play_btn = document.querySelector('button[data-test="player-next"]')
- if (!play_btn || play_btn.getAttribute('aria-disabled') != 'true') {
- return
- }
- init_question()
- if (question.type == 1) {
- for (var i = 0; i < question.el.children.length; i++) {
- var item = question.el.children[i]
- // 修改按钮padding, 加宽以防止序号覆盖文字
- item
- .querySelector('button')
- .setAttribute('style', '--web-ui_button-padding: 8px 4px 8px 10px;')
- // 添加序号
- let new_el = document.createElement('span')
- new_el.textContent = chars.charAt(i)
- // new_el.className = 'p_item_tip'
- new_el.setAttribute(
- 'style',
- 'position: absolute; color: dodgerblue; background-color: greenyellow; border-radius: 5px;'
- )
- item.appendChild(new_el)
- }
- }
- }
- // 为单词/短语添加序号方法(防抖)
- var process_order_debounce = debounce(process_order, 500)
-
- // 按键事件监听
- document.addEventListener('keyup', function (event) {
- // GM_log('按键:' + event.key)
- // 当前页
- var page_name = window.location.pathname
- // 在主页
- if (page_name == '/learn') {
- // 按l键直接学习(跳转/lesson页)
- if (event.key == 'l') {
- window.location.href = '/lesson'
- }
-
- // 按回车键直接学习(跳转/lesson页)
- // 官方回车键失效?
- if (event.key == 'Enter') {
- setTimeout(function () {
- var el = document.querySelector('a[href="/lesson"]')
- if (el) {
- el.click()
- }
- }, 150)
- }
- return
- }
-
- // 在学习页
- if (page_name.startsWith('/lesson')) {
- // 初始化题目区数据
- init_question()
- // 回车键: 延时为下一题单词/短语添加序号
- if (event.key == 'Enter') {
- question.type = -1
-
- process_order_debounce()
- return
- }
- // 退格键, 删除最后一个选词
- if (event.key == 'Backspace') {
- if (question.el2) {
- var selects = question.el2.children
- var cnt = selects.length
- var last_select = selects[cnt - 1]
- last_select.querySelector('button').click()
- }
- return
- }
- // 删除键, 删除所有选词
- if (event.key == 'Delete') {
- if (question.el2) {
- var selects = question.el2.children
- for (var i = selects.length; i > 0; i--) {
- var select = selects[i - 1]
- select.querySelector('button').click()
- }
- }
- return
- }
- // 没有题目区时, 按z键时如果页面有"不,谢谢"按钮, 就点击它
- if (event.key == 'z' && question.type == -1) {
- // 跳过按钮
- var skip_el = document.querySelector(
- 'button[data-test="plus-no-thanks"], button[data-test="practice-hub-ad-no-thanks-button"]'
- )
- if (skip_el) {
- skip_el.click()
- return
- }
- // 挑战传奇按钮
- var legendary_el = document.querySelector(
- 'a[data-test="legendary-start-button"]'
- )
- if (legendary_el) {
- // 找到"继续"按钮并点击
- legendary_el.parentElement.nextElementSibling.children[0].children[1].click()
- return
- }
- }
-
- // Control键, 点击扬声器按钮播放语音
- if (event.key == 'Control') {
- if (question.type == 6) {
- // 小故事, 找最后一个已读的音频
- var last_listen
- for (var i = 0; i < question.el.length; i++) {
- // 当前遍历的元素
- var el = question.el[i]
- // 当前遍历的元素包含的类列表
- var class_list = Array.from(el.classList)
- // 当前元素的音频是否已听过
- var flag = el[question.prop_field].flags
- // 只有一个类, 答题区, 忽略
- if (class_list.length == 1) {
- continue
- }
- // 有两个类
- if (class_list.length == 2) {
- // flag>0时已经听过, flag=0时为标题, 设为最后听过的音频元素
- last_listen = el
- continue
- }
- // 有三个类
- if (class_list.length == 3) {
- if (flag == 0) {
- // 没有听过, 结束循环
- break
- } else {
- // 有三个类但flag>0的情况, 第三个类不同于常规音频,
- // 表明该元素非音频, 属于有四个类(听力组句题)的在答情况
- continue
- }
- }
- // 有四个类, 听力组句题, flag=0时未答, flag>0时已答
- if (class_list.length == 4) {
- continue
- }
- }
- if (last_listen) {
- last_listen.querySelector('div[data-test="audio-button"]').click()
- }
- } else {
- // 常规题, 找第一个播放按钮
- var els = document.getElementsByClassName('fs-exclude')
- if (els) {
- els[0].click()
- }
- }
-
- return
- }
-
- // 到这里已经处理完特殊按键事件
- // 剩下按字母/数字键的情况
- // 将按键转为序号, 未找到时不处理
- var idx = chars.indexOf(event.key)
- if (idx < 0) {
- return
- }
-
- // 小故事
- if (question.type == 6) {
- for (var i = 0; i < question.el.length; i++) {
- var el = question.el[i]
- var class_list = Array.from(el.classList)
- if (
- class_list.length == 1 &&
- el.children.length > 0 &&
- i < question.el.length - 1
- ) {
- // 当前处于答题状态
- // 除了最后的配对题和选项是汉字的选择题外其余题几乎都是可以首字母匹配的
- // 因此除了最后的配对题不做处理外其余添加使用数字作选择
- var btn_list = el.querySelectorAll('button')
- var no = Number(event.key)
- if (!isNaN(no) && no <= btn_list.length) {
- btn_list[no - 1].click()
- }
- break
- }
- }
- return
- }
-
- // 选择题
- if (question.type == 0) {
- if (question.el.children.length >= idx) {
- question.el.children[idx].click()
- }
- return
- }
-
- // 组句题
- if (question.type == 1) {
- if (question.el.children.length >= idx) {
- var el = question.el.children[idx]
- var item = el.children[0].children[0]
- if (item.getAttribute('aria-disabled') != 'true') {
- item.click()
- return
- }
- var text = item.querySelector(
- 'span[data-test="challenge-tap-token-text"'
- ).innerHTML
- var selects = question.el2.querySelectorAll(
- 'span[data-test="challenge-tap-token-text"]'
- )
- for (var i = 0; i < selects.length; i++) {
- var select = selects[i]
- if (select.innerHTML == text) {
- select.parentElement.parentElement.click()
- return
- }
- }
- }
- }
-
- // 配对题(自带,不需要处理)
- // if (question.type == 2) {
- // var no = Number(event.key)
- // if (!isNaN(no)) {
- // idx = no - 1
- // var length = question.el.children.length
- // if (idx >=0 && idx < length) {
- // var el = question.el.children[idx].getElementsByClassName('fs-exclude')
- // if (el) {
- // el.click()
- // return
- // }
- // el = question.el.children[idx].querySelector('button').click()
- // }
- // }
- // return
- // }
- }
- })
-
- // 页面加载后第一个题就是组句题的情况, 添加序号
- setTimeout(process_order, 6000)
- })()