WELearn英语网课答案显示

悬浮窗显示选择题、填空题、判断题、连线题答案,口语参考;自动答题

目前为 2020-05-16 提交的版本,查看 最新版本

// ==UserScript==
// @name         WELearn英语网课答案显示
// @namespace    http://tampermonkey.net/
// @version      0.6
// @description  悬浮窗显示选择题、填空题、判断题、连线题答案,口语参考;自动答题
// @author       SSmJaE
// @match        https://course.sflep.com/*
// @match        https://welearn.sflep.com/*
// @match        https://centercourseware.sflep.com/*
// @grant        GM_xmlhttpRequest
// @require      https://code.jquery.com/jquery-3.5.0.min.js
// @connect      *
// @license      MIT
// @compatible   chrome
// ==/UserScript==

const USER_SETTINGS = {
    autoSolve: false, //自动答题开关
    solveInterval: 1000, //自动答题间隔,单位毫秒
    checkInterval: 3000, //答案查询间隔,单位毫秒;多久检测一次页面是否改变
    defaultBlankAnswer: 'Default answer.', //填空题没有固定/正确答案时,填入的默认值

    showReference: true, //是否显示听力、口语参考
    containerColor: 'rgba(255,255,255,0.95)', //悬浮窗背景色
    debugMode: false, //调试用,正常使用不用开
};

var container, title, bufferUrl, bufferTag, bufferLength, showOrder = 1,
    optionOrder = 0,
    spanOrder = 0,
    blankOrder = 0,
    textareaOrder = 0,
    tofOrder;
const PARSER = new DOMParser();
const MANIFEST = [
    'new%20century%20college%20english%20secedition%20integration%201',
    'new%20century%20college%20english%20secedition%20integration%202',
    'new%20century%20college%20english%20secedition%20integration%203',
    'new%20century%20college%20english%20secedition%20integration%204',
    'an%20integrated%20skills%20course%20(2nd%20edition)%201%20for%20vocational%20college%20english',
    'an%20integrated%20skills%20course%20(2nd%20edition)%202%20for%20vocational%20college%20english',
    'an%20integrated%20skills%20course%20(2nd%20edition)%203%20for%20vocational%20college%20english',
    'an%20integrated%20skills%20course%20(2nd%20edition)%204%20for%20vocational%20college%20english',
    'an%20integrated%20skills%20course%201',
    'an%20integrated%20skills%20course%202',
];
const ORIGIN = [
    'new%20target%20college%20english%20integrated%20course%201',
    'new%20target%20college%20english%20integrated%20course%202',
    'new%20target%20college%20english%20integrated%20course%203',
    'new%20target%20college%20english%20integrated%20course%204',
    'new%20progressive%20college%20english%20integrated%20course%201',
    'new%20progressive%20college%20english%20integrated%20course%202',
    'new%20progressive%20college%20english%20integrated%20course%203',
    'new%20progressive%20college%20english%20integrated%20course%204',
]
const ANSWER_TYPES = [
    'et-tof', //判断题
    'et-blank', //问答题+填空题
    'et-select', //下拉选择题
    'et-choice', //选择题(二选一,多选)
    'et-matching', //连线题
    'et-reference', //口语参考
    'wordDeclaration', //高职单词测试
    'correctresponse value', //identifier类型
];

function create_container() {
    container = document.createElement('div');
    container.id = 'container';
    container.style.visibility = 'hidden';

    title = document.createElement('div');
    title.id = 'containerTitle';
    title.textContent = '参考答案';
    container.appendChild(title);

    let style = window.document.createElement('style');
    style.setAttribute('type', 'text/css');
    style.innerHTML = `
        #container { 
            top: 100px; 
            left: 100px; 
            margin: 0 auto; 
            z-index: 99; 
            border-radius: 8px;
            box-shadow: 0 11px 15px -7px rgba(0,0,0,.2), 0 24px 38px 3px rgba(0,0,0,.14), 0 9px 46px 8px rgba(0,0,0,.12);
            position: absolute; 
            background: ${USER_SETTINGS.containerColor}; 
            min-width: 150px;
            max-width: 400px;
            min-height: 100px;
            max-height: 500px; 
            overflow: auto;
        }
        #containerTitle {
            background: rgba(0,0,0,0);
            height: 25px; 
            margin-top: 10px;
            text-align: center; 
            font-size: x-large;
            cursor: move;
        }`;

    if (!top.document.querySelector('#container')) {
        top.document.body.appendChild(container);
        top.document.body.appendChild(style);
    }
}

function get_css(ele, prop) {
    return parseInt(window.getComputedStyle(ele)[prop]);
}

/**实现拖动功能*/
function make_draggable(handle, container) {
    let initX, initY,
        draggable = false,
        containerLeft = get_css(container, "left"),
        containerRight = get_css(container, "top");

    handle.addEventListener("mousedown", e => {
        draggable = true;
        initX = e.clientX;
        initY = e.clientY;
    }, false);

    top.document.addEventListener("mousemove", e => {
        if (draggable === true) {
            var nowX = e.clientX,
                nowY = e.clientY,
                disX = nowX - initX,
                disY = nowY - initY;
            container.style.left = containerLeft + disX + "px";
            container.style.top = containerRight + disY + "px";
        }
    });

    handle.addEventListener("mouseup", e => {
        draggable = false;
        containerLeft = get_css(container, "left");
        containerRight = get_css(container, "top");
    }, false);
};

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function empty_container() {
    container.innerHTML = '';
    container.appendChild(title);
    is_show();
}

function is_show() {
    container.style.visibility = container.childNodes.length > 1 ? 'visible' : 'hidden';
}

function get_current_url() {
    let currentUrl;
    try {
        currentUrl = document.querySelector('div.courseware_main_1').firstElementChild.src;
    } catch (error) {
        currentUrl = top.frames[0].location.href;
    }
    return currentUrl;
}

function is_change() {
    let currentUrl = get_current_url();
    if (currentUrl != bufferUrl) {
        empty_container();
        determine_course_type(currentUrl);
    }
    bufferUrl = currentUrl;
}

function determine_course_type(answerUrl) {
    let courseInfo = /com\/(.*?)\//.exec(answerUrl)[1];
    let identifier;
    try {
        identifier = /#(.*)\?/.exec(answerUrl)[1];
    } catch (error) {}

    if (MANIFEST.includes(courseInfo)) { //需要查询名单
        let manifestUrl = `https://centercourseware.sflep.com/${courseInfo}/resource/manifest.xml`;
        query_manifest(manifestUrl, identifier, courseInfo);
    } else if (ORIGIN.includes(courseInfo)) { //直接在原始页面查找
        setTimeout(() => {
            let answers = top.frames[0].document.querySelectorAll('[data-solution]');
            add_to_container('', answers);
            is_show();
        }, 2000);
    } else { //默认(视听说)
        answerUrl = `https://centercourseware.sflep.com/${courseInfo}/data${identifier}.html`;
        send_ajax_request(answerUrl);
    }
}

function query_manifest(manifestUrl, identifier, courseInfo) {
    GM_xmlhttpRequest({
        method: "GET",
        url: manifestUrl,
        onload: response => {
            let html = response.response;
            let htmlDom = PARSER.parseFromString(html, 'text/html');
            let selector = `resource[identifier="${identifier}"] file`;
            let resource = htmlDom.querySelector(selector).getAttribute('href').replace('.html', '.xml').replace('.htm', '');
            let answerUrl = `https://centercourseware.sflep.com/${courseInfo}/${resource}`;
            send_ajax_request(answerUrl);
        }
    });
}

function send_ajax_request(answerUrl) {
    GM_xmlhttpRequest({
        method: "GET",
        url: answerUrl,
        onload: response => {
            let html = response.response;
            let htmlDom = PARSER.parseFromString(html, 'text/html');
            if (USER_SETTINGS.debugMode) console.log(htmlDom);

            parse_ajax_response(htmlDom);
        }
    });
}

function parse_ajax_response(htmlDom) {
    empty_container();

    ANSWER_TYPES.map(answerType => htmlDom.querySelectorAll(answerType)).forEach(answers => add_to_container(htmlDom, answers));
    is_show();

    showOrder = 1;
    optionOrder = 0;
    blankOrder = 0;
    tofOrder = 0;
    bufferTag = undefined;

}

/**通过检测父节点,解决答案重复的问题*/
function isRepeat(answerNode) {
    let parentElement = answerNode,
        parentTag;
    let webFlag = 0;
    let mobileFlag = 0;
    try {
        for (let i = 0; i < 9; i++) {
            if (i > 0) {
                parentElement = parentElement.parentElement;
            }
            parentTag = parentElement.tagName;
            // if (USER_SETTINGS.debugMode) console.log(parentTag);
            if (parentTag == 'ET-MOBILE-ONLY') {
                mobileFlag++;
            }
            if (parentTag == 'ET-WEB-ONLY') {
                webFlag++;
            }
        }
    } catch (error) {
        // if (USER_SETTINGS.debugMode) console.log(error);
    } finally {
        if (webFlag && mobileFlag) { //针对web下嵌套mobile的题目,如视听说2的3-2-3
            if (webFlag > 1) { //针对4重嵌套,unit test常见
                return true;
            } else {
                return false;
            }
        } else if (webFlag) { //web和mobile只留其一,这里保留mobile,丢弃web
            return true;
        } else {
            return false;
        }
    }
}

function ready_in(element) {
    $(element).trigger('click')
    $(element).trigger('focus')
    $(element).trigger('keydown')
    $(element).trigger('input')
}

function ready_out(element) {
    $(element).trigger('keyup')
    $(element).trigger('change')
    $(element).trigger('blur')
}

/**提取答案,并加入到容器*/
async function add_to_container(htmlDom, answers) {
    let tofOnPaper = document.querySelectorAll('et-tof span.controls span');
    let blankOnPaper = document.querySelectorAll('et-blank span.blank');
    let optionOnPaper = document.querySelectorAll('et-choice li');
    let selectOnPaper = document.querySelectorAll('et-select div');
    let textareaOnPaper = document.querySelectorAll('et-blank textarea.blank');
    let optionSpanOnPaper = document.querySelectorAll('et-choice span');
    let textareaFlag = false;
    if (answers.length > 0) {

        if (USER_SETTINGS.debugMode) console.log(optionOnPaper, textareaOnPaper, blankOnPaper);
        for (let i = 0; i < answers.length; i++) {
            if (USER_SETTINGS.debugMode)
                if (!isRepeat(answers[i])) console.log(answers[i]);

            let content = document.createElement('div');
            let hr = document.createElement('hr');
            let event = new InputEvent('input');
            let tag = answers[i].tagName;
            try {
                switch (tag) {
                    case 'ET-BLANK':
                        if (isRepeat(answers[i])) continue;
                        content.textContent = answers[i].textContent.split("|")[0];

                        if (USER_SETTINGS.autoSolve) {
                            await sleep(USER_SETTINGS.solveInterval);

                            if (answers[i].hasAttribute('block')) { //回答问题
                                if (content.textContent.length) {
                                    // ready_in(textareaOnPaper[textareaOrder]);
                                    textareaOnPaper[textareaOrder].textContent = content.textContent;
                                    textareaOnPaper[textareaOrder].dispatchEvent(event);
                                    // ready_out(textareaOnPaper[textareaOrder]);
                                } else { //有et-blank,但是无答案,不做处理
                                    textareaFlag = true;
                                }
                                textareaOrder++;
                            } else { //普通填空题
                                ready_in(blankOnPaper[blankOrder]);
                                // blankOnPaper[blankOrder].click();
                                blankOnPaper[blankOrder].textContent = content.textContent;
                                // blankOnPaper[blankOrder].dispatchEvent(event);
                                ready_out(blankOnPaper[blankOrder]);
                                blankOrder++;
                            }
                        }
                        break;

                    case 'ET-TOF':
                        content.textContent = answers[i].getAttribute('key');

                        if (USER_SETTINGS.autoSolve) {
                            await sleep(USER_SETTINGS.solveInterval);
                            let tofOption;
                            switch (content.textContent) {
                                case 't':
                                case 'T':
                                    tofOption = 2 * tofOrder;
                                    break;
                                case 'f':
                                case 'F':
                                    tofOption = 2 * tofOrder + 1;
                            }
                            tofOnPaper[tofOption].click();
                            tofOrder++;
                        }
                        break;

                    case 'ET-SELECT':
                        content.textContent = answers[i].getAttribute('key');
                        try { //todo 这是哪个类型的题的故障处理?
                            if (!content.textContent.length)
                                content.textContent = answers[i].firstElementChild.textContent;
                        } catch (error) {
                            content.textContent = 'Answers will vary.';
                        }

                        if (USER_SETTINGS.autoSolve) {
                            await sleep(USER_SETTINGS.solveInterval);
                            selectOnPaper[i].classList.add('correct');
                        }
                        break;

                    case 'ET-CHOICE':
                        if (isRepeat(answers[i])) //针对有只有inline的情况(视听说2 4-2),也就是说,不能跳
                            if (answers[i].hasAttribute('inline')) continue; //针对视听说2 7-1重复,    
                        content.textContent = answers[i].getAttribute('key');

                        if (USER_SETTINGS.autoSolve) {
                            await sleep(USER_SETTINGS.solveInterval);
                            let targetOption, options, optionCount;
                            let spanFlag = false;

                            try {
                                options = answers[i].getAttribute('key').split(',');
                            } catch (error) {
                                options = ['1'] //不检查答案的选择题
                            }
                            if (USER_SETTINGS.debugMode) console.log(options);

                            if (!(optionCount = answers[i].querySelectorAll('li').length)) {
                                optionCount = answers[i].querySelectorAll('span').length;
                                spanFlag = true;
                                optionOrder = spanOrder;
                            }

                            for (let option of options) {
                                if (isNaN(parseInt(option))) { //key是字母
                                    targetOption = optionCount * optionOrder + option.toUpperCase().charCodeAt() - 65;
                                } else { //key是数字
                                    targetOption = optionCount * optionOrder + parseInt(option) - 1;
                                }
                                if (USER_SETTINGS.debugMode)
                                    console.log(`题号${optionOrder} span${spanOrder} 选项${targetOption} 选项数${optionCount}`);
                                if (spanFlag && optionCount) {
                                    optionSpanOnPaper[targetOption].click();
                                } else {
                                    optionOnPaper[targetOption].click();
                                }
                            }

                            if (spanFlag) spanOrder++;
                            optionOrder++;
                        }
                        break;

                    case 'ET-MATCHING':
                        if (isRepeat(answers[i])) continue;
                        content.textContent = answers[i].getAttribute('key');

                        if (USER_SETTINGS.autoSolve) {
                            await sleep(USER_SETTINGS.solveInterval);
                        }
                        break;

                    case 'ET-REFERENCE':
                        if (!USER_SETTINGS.showReference) continue;
                    case 'WORDDECLARATION':
                        content.innerHTML = answers[i].innerHTML;
                        break;

                    case 'VALUE':
                        (() => {
                            let identifier = answers[i].textContent;
                            if (identifier.length == 36) { //选择题
                                if (answers[i].textContent.length == 36) {
                                    let selector = '[identifier="' + identifier + '"]';
                                    try {
                                        content.textContent = htmlDom.querySelector(selector).textContent;
                                    } catch (error) {
                                        content.textContent = answers[i].textContent; //高职第七八单元填空
                                    }
                                } else { //高职,非精编,综合,单元测试
                                    content.textContent = answers[i].textContent;
                                }

                            } else if (identifier.length > 200) { //纠错题
                                let selectors = identifier.split(',');
                                for (let i = 0; i < selectors.length; i++) {
                                    let selector = '[identifier="' + selectors[i] + '"]';
                                    content.innerHTML += htmlDom.querySelector(selector).textContent + "<br>";
                                }
                            } else { //填空题
                                content.textContent = answers[i].textContent;
                            }
                            //input[onfocus].value填空
                            //label[for].textContent选择
                            //document.querySelectorAll('input[style]')直接与identifier对应选择
                            //textarea[onchange]
                        })();
                        break;

                    default:
                        (() => {
                            if (answers[i].hasAttribute('data-solution')) {
                                let answer = answers[i].getAttribute('data-solution');
                                if (!answer.length) {
                                    try {
                                        content.textContent = answers[i].firstElementChild.textContent;
                                    } catch (error) {
                                        content.textContent = answers[i].textContent;
                                    }
                                } else {
                                    content.textContent = answer;
                                }
                            }
                        })();
                        //ul[data-itemtype] li 选择题
                        //input[data-itemtype]填空题
                        //最好有通解
                        break;
                }
            } catch (error) {
                if (USER_SETTINGS.debugMode) console.error(error);
            } finally {
                if (content.textContent.length) {
                    let order; //控制序号的宽度一致
                    if (showOrder < 10) {
                        order = '  ' + String(showOrder);
                    } else {
                        order = String(showOrder);
                    }
                    content.textContent = order + '、' + content.textContent;
                    showOrder += 1;

                    // let scrollTop = optionOnPaper[targetOption].parentElement.clientTop;
                    // console.log(scrollTop, optionOnPaper[targetOption].parentElement);
                    // top.frames[0].window.scrollTo(0, parseInt(scrollTop));
                } else {
                    continue;
                }
                content.setAttribute('style', ` margin: 10px 10px;
                                                color: orange; 
                                                font-size: medium;
                                                font-family:Georgia, 'Times New Roman', Times, serif;
                                                white-space:pre-wrap;`);
                if ((bufferTag !== tag) && (bufferTag !== undefined) && (bufferLength !== 0)) {
                    container.appendChild(hr);
                }
                container.appendChild(content);

                content.offsetWidth; //强制浏览器刷新悬浮窗宽度
                bufferTag = tag;
                bufferLength = content.textContent.length;

                is_show();
            }
        }
    } else {
        if (textareaOnPaper.length && textareaFlag) { //无et-blank,但是有textarea
            for (let textarea of textareaOnPaper) {
                textarea.textContent = USER_SETTINGS.defaultBlankAnswer;
            }
        }
    }
}

create_container();
make_draggable(title, container);
setInterval(is_change, USER_SETTINGS.checkInterval);

QingJ © 2025

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