Greasy Fork

Greasy Fork is available in English.

WELearn英语网课答案显示

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

当前为 2020-05-13 提交的版本,查看 最新版本

// ==UserScript==
// @name         WELearn英语网课答案显示
// @namespace    http://tampermonkey.net/
// @version      0.5.1
// @description  悬浮窗显示选择题、填空题、判断题、连线题答案,口语参考;
// @author       SSmJaE
// @match        https://course.sflep.com/*
// @match        https://welearn.sflep.com/*
// @match        https://centercourseware.sflep.com/*
// @grant        GM_xmlhttpRequest
// @connect      *
// @license      MIT
// @compatible   chrome
// ==/UserScript==
(function () {
    'use strict';
    const USER_SETTINGS = {
        // autoSolve: false, //自动答题开关,还未完成
        // solveInterval: 3000, //自动答题间隔,单位毫秒
        checkInterval: 3000, //答案查询间隔,单位毫秒;多久检测一次页面是否改变
        showReference: true, //是否显示听力、口语参考
        containerColor: 'rgba(255,255,255,0.95)', //悬浮窗背景色
        debugMode: true, //调试用,正常使用不用开
    };
    var container, title, bufferUrl, bufferTag, realOrder = 1;
    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.setAttribute('style', "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 + "; width=400px;min-width: 150px;max-width:400px; max-height: 500px; min-height: 100px;overflow:auto;")
        container.style.visibility = 'hidden';
        if (!top.document.querySelector('#container')) {
            top.document.body.appendChild(container);
        }

        title = document.createElement('div');
        title.textContent = '参考答案';
        title.setAttribute("style", "background: rgba(0,0,0,0); height: 25px; margin-top: 10px; text-align: center; font-size: x-large;cursor:move;");
        container.appendChild(title);
    }

    function drag_box(drag, wrap) {
        function getCss(ele, prop) {
            return parseInt(window.getComputedStyle(ele)[prop]);
        }

        var initX, initY,
            dragable = false,
            wrapLeft = getCss(wrap, "left"),
            wrapRight = getCss(wrap, "top");

        drag.addEventListener("mousedown", function (e) {
            dragable = true;
            initX = e.clientX;
            initY = e.clientY;
        }, false);

        document.addEventListener("mousemove", function (e) {
            if (dragable === true) {
                var nowX = e.clientX,
                    nowY = e.clientY,
                    disX = nowX - initX,
                    disY = nowY - initY;
                wrap.style.left = wrapLeft + disX + "px";
                wrap.style.top = wrapRight + disY + "px";
            }
        });

        drag.addEventListener("mouseup", function (e) {
            dragable = false;
            wrapLeft = getCss(wrap, "left");
            wrapRight = getCss(wrap, "top");
        }, false);

    };

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

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

    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();
    }

    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 += 1;
                }
                if (parentTag == 'ET-WEB-ONLY') {
                    webFlag += 1;
                }
            }
        } 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 add_to_container(htmlDOM, answers) {
        if (answers.length > 0) {
            for (let i = 0; i < answers.length; i++) {
                if (USER_SETTINGS.debugMode) {
                    console.log(answers[i]);
                }
                let content = document.createElement('div');
                let hr = document.createElement('hr');
                // let br = document.createElement('br')
                let parentTag, web = false;
                let tag = answers[i].tagName;
                switch (tag) {
                    case 'ET-BLANK':
                        if (isRepeat(answers[i])) continue;
                        content.textContent = answers[i].textContent.split("|")[0];
                        if (USER_SETTINGS.autoSolve) {
                            //et-blank span span[style].value填空题
                            //
                            //et-blank span.blank
                            //et-blank textarea.blank问答题
                            //对于et类型,都先清空在输入,因为可能重复显示
                            //是否有必要实现逐个单词录入效果?
                            //是否有必要实现打字音效
                        }
                        break;
                    case 'ET-TOF':
                        //et-tof span.controls
                    case 'ET-SELECT':
                    case 'ET-CHOICE':
                        if (isRepeat(answers[i])) //针对有只有inline的情况(视听说2 4-2),也就是说,不能跳
                            if (answers[i].hasAttribute('inline')) continue; //针对视听说2 7-1重复,



                        //et-choice li.textContent
                        //多选,需要分块解决tbody et-choice div span
                    case 'ET-MATCHING':
                        if (USER_SETTINGS.debugMode) console.log(isRepeat(answers[i]))
                        if (isRepeat(answers[i])) {
                            continue;
                        }
                        content.textContent = answers[i].getAttribute('key');
                        try {

                            if (!content.textContent.length) content.textContent = answers[i].firstElementChild.textContent;
                        } catch (error) {
                            content.textContent = 'Answers will vary.';
                        }
                        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;
                }
                if (content.textContent.length) {
                    let order; //控制序号的宽度一致
                    if (realOrder < 10) {
                        order = '  ' + String(realOrder);
                    } else {
                        order = String(realOrder);
                    }
                    content.textContent = order + '、' + content.textContent;
                    realOrder += 1;
                } 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)) {
                    // content.innerHTML = '<hr>' += content.innerHTML;
                    container.appendChild(hr);
                }
                container.appendChild(content);

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

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