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

使用快捷键刷多邻国. 在主页面使用Enter键快速开始学习;在学习页使用ctrl键播放语音, 对中文选词题使用回车键为选词添加拼音(多音字可能有偏差),退格键删除选词. 如果官方和脚本的快捷键无法正常使用, 需要在`vimium-c`等快捷键相关插件中排除多邻国网站. 如果发生无法输入文字的情况可以尝试在网页限制解除/文本选中复制相关脚本中排除多邻国网站 参考多邻国选词快捷键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

2024/06/15のページです。最新版はこちら。

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         多邻国拼音选词快捷键简化版(快捷键不包括删除选词)
// @namespace    http://tampermonkey.net/
// @version      1.26
// @description  使用快捷键刷多邻国. 在主页面使用Enter键快速开始学习;在学习页使用ctrl键播放语音, 对中文选词题使用回车键为选词添加拼音(多音字可能有偏差),退格键删除选词. 如果官方和脚本的快捷键无法正常使用, 需要在`vimium-c`等快捷键相关插件中排除多邻国网站. 如果发生无法输入文字的情况可以尝试在网页限制解除/文本选中复制相关脚本中排除多邻国网站 参考多邻国选词快捷键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
// @author       Gelan
// @match        https://www.duolingo.cn/*
// @match        https://www.duolingo.com/*
// @license      MIT
// @icon         https://d35aaqx5ub95lt.cloudfront.net/images/super/fb7130289a205fadd2e196b9cc866555.svg
// @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/[email protected]/dist/jquery.min.js
// @require      https://unpkg.com/pinyin-pro
// @grant        GM_log
// @grant        GM_addStyle
// ==/UserScript==

;(function () {
    'use strict'
    // 引入pinyin库
    var { pinyin } = pinyinPro;
    // 题目区元素相关数据对象
    // type -1: 无效 0: 选择题(自带[数字]快捷键) 1: 中文组句题 2: 配对题(自带[数字]快捷键)
    // 3: 填空题(自带[首字母]快捷键) 4: 听写题(不需要处理) 5: 听写填空题(不需要处理)
    // 6: 小故事 7: 补全题(自带[首字母]快捷键) 8: 口语题
    // el: 主题目区元素
    // el2:次题目区元素
    var question = { type: -1 }
    var items = [];
    // 始初化题目数据对象方法
    var init_question = function () {
        // 口语题
        question.el = document.querySelector(
            'div[data-test="challenge challenge-speak"], div[data-test="challenge challenge-listenSpeak"]'
        )
        if (question.el) {
            question.type = 8
            return
        }
        // 补全题(不需要处理)
        question.el = document.querySelector(
            'div[data-test="challenge challenge-tapCloze"]'
        )
        if (question.el) {
            question.type = 7
            return
        }
        // 小故事
        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
        }
        // 中文组句题
        var challengeHeader = document.querySelector('h1[data-test="challenge-header"]');
        if (challengeHeader) {
            var spanElement = challengeHeader.querySelector('span');
            if (spanElement && spanElement.textContent.trim() === "用中文写出这句话") {
                question.el = document.querySelector('div[data-test="word-bank"]')
                question.el2 =
                    question.el.parentElement.previousElementSibling.children[0].children[0].children[1]
                question.type = 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) {
            return
        }
        init_question()
        if (question.type == 1) {
            if(document.querySelector('rt'))
            {
                return;
            }
            for (var i = 0; i < question.el.children.length; i++) {
                var span = question.el.children[i].querySelector('span[data-test="challenge-tap-token-text"]');
                var rt = document.createElement('rt');
                rt.style.cssText = 'color: gray; display: flex; flex-direction: row;';
                rt.style.fontSize = parseFloat(window.getComputedStyle(span).fontSize) * 0.5 + 'px';
                rt.textContent = pinyin(span.textContent, { toneType: 'none' });

                span.parentElement.style.cssText = 'display: flex; flex-direction: column;';
                span.parentElement.insertBefore(rt, span);
            }
        }
    }
    // 为单词/短语添加拼音方法(防抖)
    var process_order_debounce = debounce(process_order, 500)

    let matchIndex = 0;
    let pinyinIndex = 0;
    let matchs = [];
    let pinyins = [];
    let spancolor = 'red';
    let isCombi = false;

    // 按键事件监听
    document.addEventListener('keyup', function (event) {
        GM_log('按键:' + event.key)
        // 当前页
        var page_name = window.location.pathname
        // 在主页
        if (page_name == '/learn') {
            // 按Enter键直接学习(跳转/lesson页)
            if (event.key == 'Enter') {
                window.location.href = '/lesson'
            }
        }

        // 初始化题目区数据
        if (event.key == 'Enter') {
            process_order_debounce()
        }

        // Tab键,跳过题目或自动答题(需下载Duolingo Pro BETA)
        if (event.key == 'Tab') {
            let solve = document.querySelector('#solveAllButton')
            if(solve) {
                solve.previousElementSibling.click()
            }
            else {
                document.querySelector('button[data-test="player-skip"]').click()
            }
        }
        // Backspace键, 删除最后一个选词
        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
        }
        // Shift键, 在按钮选词和直接输入中切换 或 在减小难度和增大难度中切换
        if (!event.shiftKey && event.key == 'Shift') {
            if(!isCombi) {
                document.querySelector('button[data-test="player-toggle-keyboard"]').click()
            }
            isCombi = false;
            return
        }
        else if (event.shiftKey && event.key != 'Shift') {
            isCombi = true;
        }
        // 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
        }

        // 组句题
        if (question.type == 1) {
            pinyins = document.querySelectorAll('rt');
            spancolor = document.querySelector('span[data-test="challenge-tap-token-text"]').style.color;

            const keyPressed = event.key;
            matchs = [];

            for (let i = 0; i < pinyins.length; i++) {
                let pinyin = pinyins[i];
                if (pinyin.innerText[pinyinIndex] === keyPressed && pinyin.parentElement.parentElement.getAttribute('aria-disabled') != 'true') {
                    if (pinyinIndex === 0) {
                        pinyin.style.color = 'lightblue';
                    }
                    else {
                        if (pinyin.style.color !== 'lightblue') {
                            continue;
                        }
                    }

                    if(pinyin.innerText[pinyinIndex] === ' ')
                    {
                        event.preventDefault();
                    }

                    matchs.push(pinyin);
                }
                else {
                    pinyin.style.color = 'gray';
                }
            }
            if (matchs.length === 1) {
                matchs[0].parentElement.parentElement.click();
                pinyinIndex = 0;
                matchs = [];
            }
            else if (matchs.length > 1) {
                let num = 1;
                for (let i = 0; i < matchs.length; i++) {
                    let match = matchs[i]
                    if (pinyinIndex + 1 === match.textContent.length && !match.textContent.match(/\d+/g)) {
                        match.textContent += num++;
                    }
                }
                pinyinIndex++;
            } else {
                pinyinIndex = 0;
                matchs = [];
            }
        }
    })
})()