Greasy Fork镜像 支持简体中文。

unipus iTEST助手

自动翻译文章题目材料, 解析听力材料无限听, 内置翻译助手英汉互译, 解除切屏限制, 解除右键菜单与划词限制

目前為 2020-06-19 提交的版本,檢視 最新版本

// ==UserScript==
// @name         unipus iTEST助手
// @namespace    http://blog.z31.xyz/
// @version      3.0.6
// @description  自动翻译文章题目材料, 解析听力材料无限听, 内置翻译助手英汉互译, 解除切屏限制, 解除右键菜单与划词限制
// @author       simonkimi
// @match        *://*/*
// @grant        none
// @require      https://cdn.staticfile.org/blueimp-md5/2.16.0/js/md5.min.js
// ==/UserScript==
(async () => {
    'use strict';
    await delay(2000);

    /**
     * 强烈建议自己申请百度翻译api, 免费的
     * 地址: https://api.fanyi.baidu.com/product/11
     * 否则过多人同时使用导致接口反应缓慢, 甚至错误代码
     * 开关0为关, 1为开
     * @type {{appid: string, key: string}}
     */
    const SETTING = {
        appid: "20200604000485252",  // 左边20200604000485252替换自己申请的APPID
        key: "pNz3PfHcz_65fwPV_SYh",  // 左边pNz3PfHcz_65fwPV_SYh替换自己申请的key


        listenTest: 1,  // 听力开关
        singleSelect: 1,  // 单选题翻译开关
        select10from15: 1, // 15选10开关
        article: 1,  // 文章翻译开关
        articleChoice: 1, // 文章选项翻译开关
        disableSelect: 1,  // 解除复制粘贴限制开关
        translateHelper: 1  // 翻译助手开关
    }

    if (isItest()) {
        initCss();
        debug();
        SETTING.disableSelect === 1 ? disableSelect() : null;
        SETTING.singleSelect === 1 ? singleSelect() : null;
        SETTING.select10from15 === 1 ? select10from15() : null;
        SETTING.article === 1 ? article() : null;
        SETTING.articleChoice === 1 ? articleChoice() : null;
        SETTING.disableSelect === 1 ? listenTest() : null;
        window.onblur = null;
        window.onfocus = null;
        setInterval(function () {
            window.onblur = null;
            window.onfocus = null;
        }, 5 * 1000)
    }


    /**
     * 判断是否为Itest平台, 为了防止部分学校定制
     * @returns {boolean}
     */
    function isItest() {
        return document.getElementById('all-content') !== null;
    }


    /**
     * 是否为本地网页, 解除本地网页限制
     */
    function debug() {
        if (window.location.href.indexOf("localhost") !== -1) {
            $("head").append(`<script> src="https://cdn.staticfile.org/blueimp-md5/2.16.0/js/md5.min.js"</script>`)
            $('.goup').removeClass('dis').on('click', function () {
                $('.itest-ques-set').each(function () {
                    const $this = $(this);
                    if ($this.css('display') === 'block' && $this.parent().css('display') === 'block') {
                        if ($this.prev().is('.itest-ques-set')) {
                            $this.prev().css('display', 'block')
                            $this.css('display', 'none')
                        } else if ($this.parent().prev().is('.itest-section')) {
                            $this.parent().prev().css('display', 'block')
                            $this.parent().css('display', 'none')
                        }
                        return false;
                    }

                })
            })
            $('.goto').removeClass('dis').on('click', function () {
                $('.itest-ques-set').each(function () {
                    const $this = $(this);
                    if ($this.css('display') === 'block' && $this.parent().css('display') === 'block') {
                        if ($this.next().is('.itest-ques-set')) {
                            $this.next().css('display', 'block')
                            $this.css('display', 'none')
                        } else if ($this.parent().next().is('.itest-section')) {
                            $this.parent().next().css('display', 'block')
                            $($this.parent().next().find(".itest-ques-set")[0]).css('display', 'block')
                            $this.parent().css('display', 'none')
                        }
                        return false;
                    }
                })
            })
        }
    }


    /**
     * 封装的翻译api
     * @param obj
     * @returns {Promise<unknown>}
     */
    async function translateAjaxApi(obj) {
        return new Promise((resolve, reject) => {
            $.ajax({
                type: "post",
                async: false,
                url: "https://api.fanyi.baidu.com/api/trans/vip/translate",
                dataType: "jsonp",
                data: obj,
                success(data) {
                    resolve(data)
                },
                error() {
                    reject();
                }
            });
        })
    }


    /**
     * 调用翻译api
     * @param context_list
     * @param from
     * @param to
     * @returns {Promise<unknown>}
     */
    async function translateAPI(context_list, from, to) {
        const trans_salt = (new Date).getTime();
        const {appid, key} = getBaiduAPIKey()
        const trans_str = appid + context_list + trans_salt + key;
        const trans_sign = md5(trans_str);

        for (let i = 0; i < 5; i++) {
            const data = await translateAjaxApi({
                q: context_list,
                from,
                to,
                appid: appid,
                salt: trans_salt,
                sign: trans_sign
            })
            if (data.error_code) {
                const errmsg = {
                    "52001": "请求超时",
                    "52002": "系统错误",
                    "52003": "未授权用户",
                    "54003": "访问频率受限",
                    "54004": "账户余额不足",
                    "58002": "服务当前已关闭",
                    "90107": "认证未通过或未生效"
                }
                if (parseInt(data.error_code) !== 54003) {


                    if (errmsg[data.error_code.toString()]) {
                        alert(`iTEST助手:翻译错误代码${data.error_code} 信息:${errmsg[data.error_code.toString()]}`)
                    } else {
                        alert(`iTEST助手:翻译错误代码${data.error_code} 信息未知, 您的API一定出了问题!!`)
                    }
                    throw new Error(`翻译:错误代码${data.error_code}`)

                } else {
                    await delay(1000);
                    continue
                }
            }
            return data.trans_result;
        }
        alert("公共翻译接口不堪重负, 您可以更换自己api!")
        throw new Error("公共翻译接口不堪重负, 您可以更换自己api!");
    }

    /**
     * 返回百度翻译的appid的, 本来打算用户自定义, 最后懒得搞了
     * @returns {{appid: string, key: string}}
     */
    function getBaiduAPIKey() {
        return {appid: SETTING.appid, key: SETTING.key}
    }

    /**
     * 解除选择复制粘贴限制
     */
    function disableSelect() {
        function hackClass(className) {
            for (const i of document.getElementsByClassName(className)) {
                hackItem(i);
            }
        }

        function hackItem(item) {
            item.onpaste = () => true;
            item.oncontextmenu = () => true;
            item.onselectstart = () => true;
            item.ondragstart = () => true;
            item.oncopy = () => true;
            item.onbeforecopy = () => true;
            item.style = '';
        }

        hackClass('itest-ques');
        hackClass('itest-direction');
        hackItem(document.body);
        hackItem(document);
    }

    /**
     * async风格的延迟
     * @param time
     * @returns {Promise<unknown>}
     */
    async function delay(time) {
        return new Promise(resolve => {
            setTimeout(resolve, time)
        })
    }

    /**
     * 单选题注入
     */
    function singleSelect() {
        $('.row').each(function () {
            if ($(this).find('.option').length !== 0) {
                $(this).prepend(`<button class="skr-tr-s skr-translate-btn skr-btn-primary skr-btn-p">翻译</button>`)
            }
        })
        $('.skr-tr-s').on('click', function () {
            if ($(this).is('.skr-btn-p')) {
                $(this).parent().find('span').each(function () {
                    $(this).remove();
                })
                const $this = $(this).parent();
                const $head = $this.children('.option-head')
                // 题目
                const head = $head.text().replace("\n", '')
                translateAPI(head, 'en', 'zh').then(value => {
                    $head.append(`<span class="skr-tr-text"><br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${value[0].dst}</span>`)
                })
                // 题干
                const selectOption = [];
                $this.children('.option').each(function () {
                    selectOption.push($(this).find('label'))
                })
                const p = selectOption.map(value => value.text().replace('\n', '')).join('\n');
                translateAPI(p, 'en', 'zh').then(value => {
                    for (let i = 0; i < value.length; i++) {
                        selectOption[i].append(`<span class="skr-tr-text"><br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${value[i].dst}</span>`)
                    }
                    $(this).removeClass('skr-btn-p').addClass('skr-btn-g').html('清空');
                })
            } else {
                $(this).removeClass('skr-btn-g').addClass('skr-btn-p').html('翻译');
                $(this).parent().find('span').each(function () {
                    $(this).remove();
                })
            }
        })
    }

    /**
     * 15选10注入
     */
    function select10from15() {
        $('.itest-15xuan10>.xxcontent').each(function () {
            $(this).prepend(`<button class="skr-tr-15p skr-translate-btn skr-btn-p" >翻译</button>`)
        })
        $('.skr-tr-15p').on('click', function () {
            // 文章翻译
            $(this).parent().find('span').each(function () {
                $(this).remove();
            })
            const $button = $(this);
            if ($(this).is('.skr-btn-p')) {
                const $passage = $(this).next();
                const passage = $passage.text().replace(/\s+/g, ' ');
                translateAPI(passage, 'en', 'zh').then(value => {
                    value.forEach(i => {
                        $passage.append(`<span class="skr-tr-text"><br><br>${i.dst}</span>`)
                    })
                    $button.removeClass('skr-btn-p').addClass('skr-btn-g').html('清空');
                })
            } else {
                $(this).removeClass('skr-btn-g').addClass('skr-btn-p').html('翻译');
                $(this).parent().find('span').each(function () {
                    $(this).remove();
                })
            }

        })

        $('.itest-15xuan10>.xx').each(function () {
            $(this).prepend(`<button class="skr-tr-15w skr-translate-btn skr-btn-p" style="float: left">翻译</button>`)
        })

        $('.skr-tr-15w').on('click', function () {
            // 单词翻译
            $(this).parent().find('span').each(function () {
                $(this).remove();
            })
            const $button = $(this);
            if ($(this).is('.skr-btn-p')) {
                const $words = $(this).parent().find('a')
                const wordList = [];
                $words.each(function () {
                    wordList.push($(this));
                })
                const data = wordList.map(value => value.text().replace(/\s+/g, ' ')).join('\n');
                translateAPI(data, 'en', 'zh').then(value => {
                    for (let i = 0; i < value.length; i++) {
                        wordList[i].append(`<span class="skr-tr-text"><br/>${value[i].dst}</span>`)
                    }
                    $button.removeClass('skr-btn-p').addClass('skr-btn-g').html('清空');
                })
            } else {
                $(this).removeClass('skr-btn-g').addClass('skr-btn-p').html('翻译');
                $(this).parent().find('span').each(function () {
                    $(this).remove();
                })
            }


        })
    }

    /**
     * 文章注入
     */
    function article() {
        $('.con-left>.article').each(function () {
            $(this).prepend(`<button class="skr-tr-art skr-translate-btn skr-btn-p" >翻译</button>`)
        })
        $('.skr-tr-art').on('click', function () {
            const $button = $(this);
            if ($(this).is('.skr-btn-p')) {
                $(this).parent().find('span').each(function () {
                    $(this).remove();
                })
                const $article = $(this).parent().find("p, div");
                const outerData = []

                $article.each(function () {
                    $(this).children('br:even').remove();
                    $(this).append('<br>')
                    const $brs = [];
                    $(this).children('br').each(function () {
                        $brs.push($(this));
                    })
                    const text = $(this).html()
                        .split('<br>')
                        .map(value => value.trim().replace(/\s+/g, ' ').replace('&nbsp;', ''))
                        .filter(value => value.length !== 0);
                    outerData.push({
                        $brs,
                        text
                    })
                });

                (async () => {
                    for (let {text, $brs} of outerData) {
                        for (let i = 0; i < text.length; i++) {
                            const data = await translateAPI(text[i], 'en', 'zh')
                            $brs[i].before(`<span class="skr-tr-text"><br/>${data[0].dst}</span>`)
                            await delay(1000);
                        }
                        $button.removeClass('skr-btn-p').addClass('skr-btn-g').html('清空');
                    }
                })()

            } else {
                $(this).removeClass('skr-btn-g').addClass('skr-btn-p').html('翻译');
                $(this).parent().find('span').each(function () {
                    $(this).remove();
                })
            }
        })
    }

    /**
     * 文章单选题注入
     */
    function articleChoice() {
        $('.itest-need-layout').each(function () {
            if ($(this).find('span').length !== 0) {
                $(this).prepend(`<button class="skr-tr-art-r skr-translate-btn skr-btn-p" >翻译</button>`)
            }
        })

        $('.skr-tr-art-r').on('click', function () {
            if ($(this).is('.skr-btn-p')) {
                const $button = $(this);
                const $spanList = []
                $button.parent().find('span').each(function () {
                    $spanList.push($(this))
                })
                const text = $spanList.map(value => value.text())
                    .map(value => value.trim().replace(/\s+/g, ' ').replace('&nbsp;', ''))
                    .join('\n');
                translateAPI(text, 'en', 'zh').then(value => {
                    for (let i = 0; i < value.length; i++) {
                        $spanList[i].append(`<label class="skr-tr-text"><br/>${value[i].dst}</label>`)
                    }
                    $button.removeClass('skr-btn-p').addClass('skr-btn-g').html('清空');
                })
            } else {
                $(this).removeClass('skr-btn-g').addClass('skr-btn-p').html('翻译');
                $(this).parent().find('label').each(function () {
                    $(this).remove();
                })
            }


        })
    }

    /**
     * 解析听力题
     */
    function listenTest() {
        $('.itest-hear-reslist-duration+.itest-ques').each(function () {
            const $this = $(this);
            const $urlNode = $this.parent().find('.itest-hear-reslist');
            $this.children('.itest-danxuan').prepend(`<button class="skr-btn-p skr-translate-btn skr-listen-btn">进度</button>`)
            if ($urlNode.length !== 0) {
                const mp3List = JSON.parse($urlNode.html());
                mp3List.reverse();
                for (const url of mp3List) {
                    if (url.indexOf('http') !== -1) {
                        $this.children('.itest-danxuan').prepend(`<audio style="display: none; opacity: 0.1;" controls="controls" src=${url}>`)
                    }
                }
            }
        })
        $('.skr-listen-btn').on('click', function () {
            $(this).parent().find("audio").each(function () {
                const $this = $(this);
                if ($this.css('display') === 'none') {
                    $this.css('display', 'block')
                } else {
                    $this.css('display', 'none')
                }
            })
        })
    }

    /**
     * 初始化CSS
     */
    function initCss() {
        $("head").append(`
        <style>
            .skr-float-btn {
                border-right:2px #dcdcdc solid;

            }

            .skr-float-btn a {
                user-select: none;
                cursor: pointer;
                opacity: 0.1;
            }

            .skr-float-container {
                z-index: 9999;
                position: absolute;
                width: 500px;
                height: 400px;
                left: 500px;
                top: -410px;
                background: #FAFAFA;
                display: none;
                box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04)
            }

            .skr-form-item {
                margin: 5px 20px;
            }

            .skr-form-item p {
                width: 100%;
                text-align: center;
                padding: 0;
                margin: 5px 0;
            }

            .skr-form-item label {
                display: inline-block;
                width: 50px;
            }

            .skr-form-item .skr-form-or {
                width: 97%;
                height: 150px;
                resize: none;
                border: none;
                box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, 0.04)
            }

            .skr-form-item .skr-form-tr {
                width: 97%;
                height: 150px;
                resize: none;
                border: none;
                background-color: #EEEEEE;
            }

            .skr-form-item p {
                font-size: 18px;
                text-align: center;
                width: 100%;
            }

            .skr-form-item textarea {
                padding: 5px 10px;
            }

            .skr-translate-btn {
                font-size: 8px;
                color: #fff;
                padding: 3px 3px;
                border-radius: 20px;
                cursor: pointer;
                opacity: 0.1;
            }

            .skr-btn-p {
                background-color: rgb(248,248,248);
                border: 1px solid #000000;
                color: #000000;
            }

            .skr-btn-p:hover {
                background-color: rgb(248,248,248);
                border: 1px solid rgb(248,248,248);
            }

            .skr-btn-g {
                background-color: #909399;
                border: 1px solid #909399;
            }

            .skr-btn-g:hover {
                background-color: rgb(166, 169, 173);
                border: 1px solid rgb(166, 169, 173);
            }

            .skr-tr-text {
                color: #909399;
            }

            .skr-listen-container {
                display: none;
            }

            ::selection {
                background-color: rgb(248,248,248);
                color: #909399;
            }

            </style>
        `)
        const $footer = $('#footer .right');
        if (SETTING.translateHelper === 1) {
            $footer.prepend(`
            <li class="skr-float-btn">
                <a>翻译</a>
                <div class="skr-float-container">
                    <div class="skr-form">
                        <div class="skr-form-item">
                            <p>翻译助手</p>
                        </div>
                        <div class="skr-form-item">
                            <button class="skr-btn-p skr-translate-btn btn-e-z" style="opacity: 1 !important; font-size: 12px; padding: 5px 10px;">英 → 汉</button>
                            <button class="skr-btn-p skr-translate-btn btn-z-e" style="opacity: 1 !important; font-size: 12px; padding: 5px 10px;">汉 → 英</button>
                        </div>
                        <div class="skr-form-item">
                            <textarea class="skr-form-or skr-tx-f" placeholder="点击按钮进行翻译..."></textarea>
                        </div>
                        <div class="skr-form-item">
                            <textarea class="skr-form-tr skr-tx-t" readonly></textarea>
                        </div>
                    </div>
                </div>
            </li>
        `)
        }
        $('.btn-e-z').on('click', function () {
            translateAPI($('.skr-tx-f').val(), 'en', 'zh').then(value => {
                $('.skr-tx-t').val(value.map(value => value.dst).join(""))
            })
        })
        $('.btn-z-e').on('click', function () {
            translateAPI($('.skr-tx-f').val(), 'zh', 'en').then(value => {
                $('.skr-tx-t').val(value.map(value => value.dst).join(""))
            })
        })


        $('.skr-float-btn a').on('click', function () {
            const $float = $(this).parent().find('.skr-float-container');
            $float.css('display', $float.css('display') === 'none' ? 'block' : 'none')
        })
    }


})()

QingJ © 2025

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