Njord平台插件

Njord平台增强插件

// ==UserScript==
// @name         Njord平台插件
// @namespace    Njord Script
// @description  Njord平台增强插件
// @author       yhw
// @version      2.2.10
// @match        http://mark.meituan.com/*
// @match        https://mark.meituan.com/*
// @run-at       document-start
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_info
// @grant		     GM_getValue
// @grant		     GM_setValue
// @require      https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js
// @require      http://cdn.staticfile.org/TimeMe.js/2.0.0/timeme.min.js
// ==/UserScript==//

(function () {
    'use strict';

    const VERSION = GM_info.script.version; // 脚本@version
    const ADDRESS = 'http://ai.speechocean.com'; // 预识别服务器地址
    const WAVEITEMS = new Array(); // 音频信息集合
    const USER = {}; // 用户信息
    const PROJECT = {}; // 项目信息
    const queryTimeOut = 1000; // 长音频轮询间隔
    var APIKEY = ""; // 预识别apikey
    var Type = 0;
    var IsStopObserver = false;

    const log_body = {
        project_id: -1, // 当前的项目id
        user_id: -1, // 当前用户id
        user_group: "0", // 客户平台的用户所属供应商标识,需要与pm沟通确认
        role: "0", // 当前用户的角色
        user_action: "", // 用户做的操作
        others: ""
    }
    const send_log = (log_body) => {
        log_body.crx_code = "b556d7bb-ce3e-4d8b-9f17-a1a777b8308b"; // 插件代号,每个插件代号唯一
        log_body.crx_platform = "Meituan-Njord" + "-" + APIKEY; // 插件平台,插件应用的客户平台
        const timestamp = Date.parse(new Date());
        //console.log(timestamp + log_body);
        const signature = md5(`unQaoqsPZhSuX5ni${timestamp}`);
        sendLog(signature, {
            ...log_body,
            timestamp: timestamp
        });
    }

    function appendInput() {
        const style = ".__j-container { top: 15%; font-size: 12px; right: 0px; background:violet; padding:5px; position: fixed; z-index: 100000; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none}.__j-input { width: 100px; }";
        var html =
            '<div class="__j-container">' +
            '<div>\n' +
            '    <label class="__j-label">语言设置</label>\n' +
            '    <select class="__j-input" id="__j-language">\n' +
            '        <option>普通话</option>\n' +
            '        <option>粤语(中国广州)</option>\n' +
            '        <option>广东粤语</option>\n' +
            '        <option>粤语(中国香港)</option>\n' +
            '    </select>\n' +
            '</div>' +
            '<div>\n' +
            '    <label class="__j-label">API KEY</label>\n' +
            '    <input class="__j-input" id="__j-apikey" type="text" name="APIKEY"></input>\n' +
            '</div>' +
            '</div>'
        '';
        var stylenode = document.createElement('style');
        stylenode.setAttribute("type", "text/css");
        if (stylenode.styleSheet) {// IE
            stylenode.styleSheet.cssText = style;
        } else {// w3c
            var cssText = document.createTextNode(style);
            stylenode.appendChild(cssText);
        }
        var node = document.createElement('div');
        node.innerHTML = html;
        document.head.appendChild(stylenode);
        document.body.appendChild(node);
        Type = GM_getValue(GM_info.script.name + "-Type", "");
        document.getElementById("__j-language").options.selectedIndex = Type;
        document.getElementById("__j-language").onchange = (e) => {
            Type = e.srcElement.selectedIndex;
            GM_setValue(GM_info.script.name + "-Type", Type);
        };
        APIKEY = GM_getValue(GM_info.script.name + "-APIKEY", "");
        document.getElementById("__j-apikey").value = APIKEY;
        document.getElementById("__j-apikey").onchange = (e) => {
            APIKEY = e.srcElement.value;
            GM_setValue(GM_info.script.name + "-APIKEY", APIKEY);
        };
    }
    // appendInput();

    // 页面元素监控,如果添加片段,重新进行文本检查
    var observer = new MutationObserver(function (mutations, observer1) {
        if (IsStopObserver) return;
        console.log('-------------------事件触发-----------------------');
        queryCheck();
        addElement();
    });

    /**
     * 重载fetch,用于拦截网页发送的fetch请求
     */
    let originFetch = fetch;
    window.unsafeWindow.fetch = async function (...args) {
        const response = await originFetch(...args);
        if (USER["info"] == null && localStorage["user"] != null) {
            USER = JSON.parse(localStorage["user"]);
        }
        if (PROJECT["info"] == null && localStorage["project"] != null) {
            PROJECT = JSON.parse(localStorage["project"]);
        }
       if(args[0].url.startsWith('https://mark.meituan.com/process/file/proxy')) {
            /*observer.observe(getObserverElement(), {
                childList: true,
                subtree: true,
            });
            addTextElement("加载中...");
            let url = args[0].url;
            //let waveInfo = Object.fromEntries(new URLSearchParams(url.substring(url.indexOf('?'))).entries());
            let waveURL = decodeURIComponent(url.substring(url.indexOf("jump=") + 5));
            // 开启页面有效停留计时器
            TimeMe.resetRecordedPageTime(waveURL);
            TimeMe.initialize({
                currentPageName: waveURL,
                idleTimeoutInSeconds: 10 // seconds
            });*/
            queryCheck();
            addElement();
       }
       return response;
    }

    /**
     * 重载xhr的send,用于拦截网页发送的xhr请求
     */
    let xhr = XMLHttpRequest.prototype;
    let open = xhr.open;
    let send = xhr.send;
    xhr.open = function (method, url) {
        this._method = method;
        this._url = url;
        return open.apply(this, arguments);
    };

    xhr.send = async function (postData) {
        if (this._method.toLowerCase() === 'post') {
            this.addEventListener('load', function () { // 获取响应数据
                /// 用户信息
                if (this._url.indexOf('/process/user/profile') > -1) {
                //if (this._url.startsWith('/process/user/profile')) {
                    console.log('--------用户信息');
                    let user = JSON.parse(this.response)['data'];
                    USER["valid"] = user['departmentName'] == "HT";
                    USER["info"] = user;
                    localStorage["user"] = JSON.stringify(USER);
                }
                /// 选择项目
                if (this._url.indexOf('/process/acl/switchproject') > -1) {
                //if (this._url.startsWith('/process/acl/switchproject')) {
                    console.log('--------选择项目');
                    let project = JSON.parse(this.response)['data'];
                    PROJECT["info"] = project;
                    localStorage["project"] = JSON.stringify(PROJECT);
                }
            });
            // if (this._url.startsWith('/process/assist/autoquality')) {
            // 	console.log('--------提交检查');
            // 	let vue = getVueInstance();
            // 	if (vue != null) {
            // 		var time = TimeMe.getTimeOnPageInMilliseconds(vue["sourceData"]["content"]);
            // 		TimeMe.stopTimer();
            // 		let data = getSubmitData(postData, time);
            // 		let ret = await postResult({ address: ADDRESS, authKey: APIKEY, version: VERSION, data: data });
            // 		log_body.others = JSON.stringify(data);
            // 		log_body.user_action = "SubmitCheck";
            // 		send_log(log_body);
            // 		console.log(ret);
            // 	}
            // }
            /// 提交质检数据(只取提交的请求信息)
            if (this._url.indexOf('/process/annotate/updatetask') > -1) {
            // if (this._url.startsWith('/process/annotate/updatetask')) {
                console.log('--------提交数据');
                /*let vue = getVueInstance();
                if (vue != null) {
                    var time = TimeMe.getTimeOnPageInMilliseconds(vue["sourceData"]["content"]);
                    TimeMe.stopTimer();
                    let data = getSubmitData(postData, time);
                    let ret = await postResult({ address: ADDRESS, authKey: APIKEY, version: VERSION, data: data });
                    log_body.others = JSON.stringify(data);
                    log_body.user_action = "Submit";
                    send_log(log_body);
                    console.log(ret);
                }*/
            }
        }
        return send.apply(this, arguments);
    };

    function getLastWaveItem(waveURL) {
        let items = WAVEITEMS.filter(v => v['waveSource'] == waveURL);
        return items.length > 0 ? items[items.length - 1] : null;
    }

    function getSubmitData(postData, time) {
        let vue = getVueInstance();
        if (vue != null) {
            let submit = Object.fromEntries(new URLSearchParams(postData).entries());
            submit['sourceData'] = vue['sourceData'];
            submit['meta'] = JSON.parse(submit['meta']);
            submit['meta']['data']['markarea'] = submit['meta']['data']['markarea'] != undefined ? JSON.parse(submit['meta']['data']['markarea']) : [];
            let isQaData = submit['sourceData']['data']['recordId'] != null; // 有recordId的是质检数据,否则是标注数据
            let waveURL = submit['meta']['content'];
            let waveItem = getLastWaveItem(waveURL);
            if (waveItem != null) {
                let taskid = waveItem['waveTaskId'];
                let json = {
                    project_name: PROJECT["info"]['projectName'] + "[" + submit['annLogId'] + "]",
                    task_id: taskid,
                    info: {
                        provider: "meituan",
                        name: "Njord",
                        time: time,
                        audioDuration: submit['sourceData']['data']['duration_ms'],
                        label: !isQaData,
                        valid: submit['meta']['data']['has_invalid'] == "1",
                        text: submit['meta']['data']['custom'],
                        user: USER["info"] == null ? null : USER["info"]['userName'],
                        row: submit,
                        userFull: USER,
                    },
                    data: submit['meta']['data']['markarea'].map(item => {
                        return {
                            min: item['area'].split(',')[0],
                            max: item['area'].split(',')[1],
                            text: item['content'],
                            noise: item['has-noise'],
                            accent: item['has-accent'],
                            sex: item['sex'],
                        };
                    }),
                    origin: waveItem["origin"]["data"],
                    full: waveItem,
                    version: VERSION
                };
                return json;
            }

        }
    }

    function getVueInstance() {
        var elements = document.getElementsByClassName("now-wrap");
        if (elements.length > 0) {
            return elements[0].__vue__;
        }
        return null;
    }

    /**
     * 获取音频预识别id
     * @param {{
     * address:String;
     * authKey:String;
     * bolbUrl:String;
     * language:String;
     * type:Number;
     * normal:Boolean;
     * version:String;
     * }} waveInfo 音频信息
     */
    function getWaveId(waveInfo) {
        return new Promise((resolve, reject) => {
            try {
                if (waveInfo.type == 2) {
                    GM_xmlhttpRequest({
                        url: `${waveInfo.address}/speech/api/v1/asr/create_task`,
                        method: "POST",
                        headers: {
                            "Authorization": "Bearer " + waveInfo.authKey,
                            "Version": waveInfo.version
                        },
                        onreadystatechange: function (responseDetails) {
                            if (responseDetails.readyState === 4) {
                                let d = JSON.parse(responseDetails.response);
                                d.data = { "task_id": d.task_id };
                                resolve(d);
                            }
                        }
                    });
                }
                else if (waveInfo.type == 1) {
                    getLongAudioId(waveInfo).then(v => resolve(v));
                }
                else {
                    reject(`audio type error ${waveInfo.type}`);
                }
            } catch (ex) {
                reject(ex);
            }
        });
    }

    function getLongAudioId(waveInfo) {
        return new Promise((reslove, reject) => {
            fetch(waveInfo.bolbUrl)
                .then(response => response.blob())
                .then(blob => {
                    let formdata = new FormData();
                    formdata.append("domain", waveInfo.language);
                    formdata.append("file_path", blob, "proxy.wav");
                    let headerdata = {
                        "Authorization": "Bearer " + waveInfo.authKey,
                        "Version": waveInfo.version,
                    };
                    if (waveInfo.normal == true)
                        headerdata["Knative-Serving-Tag"] = "normal";
                    GM_xmlhttpRequest({
                        url: `${waveInfo.address}/speech/api/v1/asr/task/create`,
                        method: "POST",
                        headers: headerdata,
                        data: formdata,
                        onreadystatechange: function (responseDetails) {
                            if (responseDetails.readyState === 4) {
                                reslove(JSON.parse(responseDetails.response));
                            }
                        }
                    });
                }).catch(e => reject(e));
        });
    }

    function queryWaveId(waveInfo, taskid) {
        return new Promise((reslove, reject) => {
            fetch(waveInfo.bolbUrl)
                .then(response => response.blob())
                .then(blob => {
                    let headerdata = {
                        "Authorization": "Bearer " + waveInfo.authKey,
                        "Version": waveInfo.version,
                    };

                    if (waveInfo.normal == true)
                        headerdata["Knative-Serving-Tag"] = "normal";
                    if (waveInfo.type == 2) { // 短音频
                        let formdata = new FormData();
                        formdata.append("domain", waveInfo.language);
                        formdata.append("wav_path", blob, "proxy.wav");
                        formdata.append("task_id", taskid)
                        GM_xmlhttpRequest({
                            url: `${waveInfo.address}/speech/api/v2/asr/recognize`,
                            method: "POST",
                            headers: headerdata,
                            data: formdata,
                            onreadystatechange: function (responseDetails) {
                                if (responseDetails.readyState === 4) {
                                    reslove(JSON.parse(responseDetails.response));
                                }
                            }
                        });
                    }
                    else if (waveInfo.type == 1) { // 长音频
                        queryTaskId(waveInfo, taskid, reslove);
                    } else {
                        reject(`audio type error ${waveInfo.type}`);
                    }
                }).catch(e => reject(e));

        });
    }

    function queryTaskId(waveInfo, taskId, reslove) {
        let ids = new Array();
        ids.push(taskId);
        GM_xmlhttpRequest({
            url: `${waveInfo.address}/speech/api/v1/asr/task/query`,
            method: "POST",
            headers: {
                "Authorization": "Bearer " + waveInfo.authKey,
                "Version": waveInfo.version,
                "Content-Type": "application/json"
            },
            data: JSON.stringify({
                task_ids: ids
            }),
            onreadystatechange: function (responseDetails) {
                if (responseDetails.readyState === 4) {
                    let queryResponse = JSON.parse(responseDetails.response);
                    if (queryResponse["data"][0]["task_status"].toLowerCase() == "succeed") {
                        queryResponse["data"] = queryResponse["data"][0];
                        reslove(queryResponse);
                    }
                    else {
                        // 根据指定的时间间隔轮询
                        setTimeout(queryTaskId, waveInfo.queryInterval, waveInfo, taskId, reslove);
                    }
                }
            }
        });
    }

    /**
     * 提交音频进行预识别
     * @param {{
     * address:String;
     * authKey:String;
     * bolbUrl:String;
     * type:Number;
     * normal:Boolean;
     * queryInterval:Number;
     * version:String;
     * }} waveInfo 音频信息
     * @returns {Promise} aa
     */
    function postWave(waveInfo) {
        return new Promise((reslove, reject) => {
            fetch(waveInfo.bolbUrl)
                .then(response => response.blob())
                .then(blob => {
                    let headerdata = {
                        "Authorization": "Bearer " + waveInfo.authKey,
                        "Version": waveInfo.version,
                    };
                    if (waveInfo.normal == true)
                        headerdata["Knative-Serving-Tag"] = "normal";
                    if (waveInfo.type == 2) { // 短音频
                        let formdata = new FormData();
                        formdata.append("domain", "zh_cn");
                        formdata.append("wav_path", blob, "proxy.wav");
                        GM_xmlhttpRequest({
                            url: `${waveInfo.address}/speech/api/v2/asr/recognize`,
                            method: "POST",
                            headers: headerdata,
                            data: formdata,
                            onreadystatechange: function (responseDetails) {
                                if (responseDetails.readyState === 4) {
                                    reslove(JSON.parse(responseDetails.response));
                                }
                            }
                        });
                    }
                    else if (waveInfo.type == 1) { // 长音频
                        let formdata = new FormData();
                        formdata.append("domain", "zh_cn");
                        formdata.append("file_path", blob, "proxy.wav");
                        GM_xmlhttpRequest({
                            url: `${waveInfo.address}/speech/api/v1/asr/task/create`,
                            method: "POST",
                            headers: headerdata,
                            data: formdata,
                            onreadystatechange: function (responseDetails) {
                                if (responseDetails.readyState === 4) {
                                    let createResponse = JSON.parse(responseDetails.response);
                                    queryTaskId(waveInfo, createResponse["data"]["task_id"], reslove);
                                }
                            }
                        });
                    } else {
                        reject(`audio type error ${waveInfo.type}`);
                    }
                }).catch(e => reject(e));

        });
    }

    /**
     * 提交数据到后端服务
     * @param {{
     * address:String;
     * authKey:String;
     * version:String;
     * data:object;
     * }} postData
     * @param {*} callback
     */
    function postResult(postData) {
        return new Promise((reslove, reject) => {
            try {
                GM_xmlhttpRequest({
                    url: `${postData.address}/speech/api/v1/asr/label_result`,
                    method: "POST",
                    headers: {
                        "Authorization": "Bearer " + postData.authKey,
                        "Version": postData.version,
                        "Content-Type": "application/json"
                    },
                    data: JSON.stringify(postData.data),
                    onreadystatechange: function (responseDetails) {
                        if (responseDetails.readyState === 4) {
                            reslove(JSON.parse(responseDetails.response));
                        }
                    }
                });
            }
            catch (e) {
                reject(e);
            }
        })
    }

    function autoSplit(vueInstance, data) {
        if (vueInstance != null) {
            let userData = vueInstance['userData'];
            /// 如果没有标注结果,自动填充预识别结果
            if (userData['markarea'] == null && userData["recordId"] == null) {
                if ((data.length > 1 || data[0].text != "")) {
                    let markarea = new Array();
                    let reg6 = new RegExp("[。?!]$", "g");
                    for (let i = 0; i < data.length; i++) {
                        let start = data[i]['start'];
                        start = start == 0 ? 0.05 : start;
                        let end = data[i]['end'];
                        let text = data[i]['text'];
                        text = reg6.test(text) ? text : text.replace(/[,.?!,]+$/g, "") + "。";
                        markarea.push({
                            area: start + "," + end,
                            'has-noise': "无",
                            'has-accent': "无",
                            sex: "男",
                            content: text
                        });
                    }
                    IsStopObserver = true;
                    try {
                        userData['has_invalid'] = 1;
                        userData['markarea'] = JSON.stringify(markarea);
                        vueInstance.updateValue();
                    } finally {
                        IsStopObserver = false;
                    }
                }
            }
        }
    }

    function addTextElement(text) {
        var labels = document.getElementsByClassName('source-content');
        if (labels.length > 0) {
            let label = labels[0];
            if (label.childElementCount > 0) {
                var audioSource = label.children[0];
                if (audioSource.childElementCount > 0) {
                    if (audioSource.__SPOC__ == null) {
                        let div = document.createElement("div");
                        div.className = "flex";
                        let span = document.createElement("span");
                        span.id = "SPOC"
                        let name = document.createElement("div");
                        name.innerText = "参考内容";
                        name.style["left"] = "-100px";
                        name.style["font-size"] = "10px";
                        name.style["z-index"] = "1000";
                        name.style["color"] = "#409eff";
                        name.style["margin-top"] = "3px";
                        name.style["position"] = "absolute";
                        div.appendChild(span);
                        div.appendChild(name);
                        audioSource.appendChild(div);
                        audioSource.__SPOC__ = true;
                    }
                    document.getElementById("SPOC").innerText = text;
                }
            }
        }
    }

    function getObserverElement() {
        // var forms = document.getElementsByTagName("form");
        // for (let i = 0; i < forms.length; i++) {
        //     let form = forms[i];
        //     if (form.innerText.startsWith("音频截取")) {
        //         return form;
        //     }
        // }
        // return null;
        return document.getElementsByClassName('el-main')[0];
    }

    function queryCheck() {
        // if (USER["valid"] != true) return;
        observer.disconnect();
        try {
            const elementMain = document.getElementsByClassName('el-main')[0];
            var buttons = elementMain.getElementsByClassName('el-button--default');
            var buttonsCount = buttons.length;
            for (var i = 0; i < buttonsCount; i++) {
                var button = buttons[i];
                var spans = button.getElementsByTagName('span');
                if (spans != null && spans.length == 1) {
                    if (spans[0].innerText.startsWith('删除("')) {
                        buttonCheck(buttons[i]);
                    }
                }
            }
            var textareas = elementMain.getElementsByClassName('el-textarea__inner');
            var textareasCount = textareas.length;
            for (var i = 0; i < textareasCount; i++) {
                var textarea = textareas[i];
                if (textarea.__SPOC__ == null) {
                    textarea.__SPOC__ = textareaCheck;
                    if (textarea.onkeyup == null) {
                        textarea.onkeyup = function () {
                            this.__SPOC__(this);
                        }
                    }
                }
                textarea.__SPOC__(textarea);
            }
        } finally {
            observer.observe(getObserverElement(), {
                childList: true,
                subtree: true,
            });
        }
    }


    function addElement() {
        if (USER["valid"] != true) return;
        observer.disconnect();
        try {
            let ds = document.getElementsByClassName('el-pagination');
            if (ds.length == 0) return;
            let d = ds[0].parentElement;
            let divCount = d.childElementCount;
            for (let i = 1; i < divCount; i++) {
                let item = d.children[i];
                if (item.children[0].childElementCount == 3) {
                    let tmp = document.createElement('div');
                    tmp.innerHTML = '<button type="button" class="el-button el-button--default" style="margin-left: 10px;"><span>插入{}</span></button>'
                        + '<button type="button" class="el-button el-button--default" style="margin-left: 10px;"><span>插入{er}</span></button>';
                    var buttonCount = tmp.childElementCount;
                    for (let j = 0; j < buttonCount; j++) {
                        let button = tmp.children[0]; // 被使用的button会从tmp中移除,每次都取第一个即可
                        button.onclick = function () {
                            var textarea = this.parentElement.parentElement.getElementsByTagName('textarea')[0];
                            let startPos = textarea.selectionStart; // 获取光标开始的位置
                            let endPos = textarea.selectionEnd; // 获取光标结束的位置
                            if (startPos === undefined || endPos === undefined) return; // 如果没有光标位置 不操作
                            let oldText = textarea.value; // 获取输入框的文本内容
                            let insertText = this.innerText.match(new RegExp("{[^{}]*}", "g"))[0];
                            let newText = oldText.substring(0, startPos) + insertText + oldText.substring(endPos); // 将文本插入
                            textarea.value = newText; // 将拼接好的文本设置为输入框的值
                            textarea.focus(); // 重新聚焦输入框
                            textarea.selectionStart = startPos + (insertText.length == 2 ? 1 : insertText.length);
                            textarea.selectionEnd = startPos + (insertText.length == 2 ? 1 : insertText.length);
                            textarea.dispatchEvent(new Event('input')); // 触发input事件
                            textareaCheck(textarea);
                        };
                        item.children[0].append(button);
                    };
                }
            }
        } finally {
            observer.observe(getObserverElement(), {
                childList: true,
                subtree: true,
            });
        }
    }




    function textareaCheck(textarea) {
        let target = textarea;
        let results = checkText(target.value);
        let errors = results['errors'];
        let warns = results['warns'];
        // 先清理掉之前的错误提示
        target.style.borderColor = null;
        let parent = target.parentNode;
        if (parent != null) {
            let span = parent.getElementsByClassName('error-tip')[0];
            if (span != undefined) {
                target.parentNode.removeChild(span);
            }
            // 如果有错误,则添加错误+警告
            if (errors.length > 0) {
                target.style.borderColor = 'red';
                let span = document.createElement('span');
                span.className = "error-tip";
                span.innerText = errors.concat(warns).join(',');
                span.style.color = 'red';
                parent.appendChild(span);
            } else if (warns.length > 0) {
                target.style.borderColor = 'orange';
                let span = document.createElement('span');
                span.className = "error-tip";
                span.innerText = warns.join(',');
                span.style.color = 'orange';
                parent.appendChild(span);
            }
        }
    }

    function buttonCheck(button) {
        var target = button;
        var text = button.getElementsByTagName("span")[0].innerText;
        var hasError = checkTime(text);
        // 先清理掉之前的错误提示
        target.style.borderColor = null;
        var parent = target.parentNode;
        if (parent != null) {
            var span = parent.getElementsByClassName('error-tip')[0];
            if (span != undefined) {
                target.parentNode.removeChild(span);
            }
            // 如果有错误,则添加错误提示
            if (!hasError) {
                target.style.borderColor = 'red';
                var span = document.createElement('span');
                span.className = "error-tip";
                span.innerText = '时长错误';
                span.style.color = 'red';
                parent.appendChild(span);
            }
        }
    }


    function checkTime(text) {
        var timeStr = text.split('"')[1];
        var time = timeStr.split(',');
        var min = parseFloat(time[0]);
        var max = parseFloat(time[1]);
        return (max - min) <= 20;
    }

    function checkText(text) {
        //console.info('文本检查:', text)
        let errors = new Array();
        let warns = new Array();
        if (text == undefined) return { errors: errors, warns: warns };
        // 空文本
        if (text.length == 0) {
            errors.push('文本为空');
            return { errors: errors, warns: warns };
        }
        let regEnglish = new RegExp("([A-Za-z]+['-]+[A-Za-z]+)|([A-Za-z]+)", "g");
        /// 一个字符或者一个英文单词
        if (text.length == 1 || text.match(regEnglish) == text) {
            errors.push('字数过少');
            return { errors: errors, warns: warns };
        }
        let letterText = text.replace(/[\ |\~|\`|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\||\\|\[|\]|\{|\}|\;|\:|\"|\'|\,|\<|\.|\>|\/|\?|\、|\,|\;|\。|\?|\!|\“|\”|\‘|\’|\:|\(|\)|\─|\…|\—|\·|\《|\》]/g, "");
        let regUseless = new RegExp("[嗯啊呀哇哦啦呢咚呐吧吗呵呃唔嘛唉哎诶]+", "g");
        if (letterText.match(regUseless) == letterText || letterText.match(/(.)\1+/g) == letterText) {
            errors.push('全部语气词或重复');
            return { errors: errors, warns: warns };
        }
        // 判断标签是否正确
        let regTag = new RegExp("{[^{}]*}", "g");
        let tags = text.match(regTag);
        if (tags != null) {
            tags.forEach(function (item) {
                if (!checkTag(item))
                    errors.push("标签错误" + item);
            })
        }
        let noTagText = text.replace(regTag, ''); // 无标签文本
        // 有数字
        let reg1 = new RegExp("[0-9]+", "g");
        if (noTagText.match(reg1) != null) {
            errors.push("有数字");
        }
        // 英文首字母大写
        let reg11Match = noTagText.match(regEnglish);
        if (reg11Match != null) {
            let reg111 = new RegExp("^[^A-Z]", "g");
            let enErr = false;
            reg11Match.forEach(function (item) {
                if (item.match(reg111) != null)
                    enErr = true;
            })
            if (enErr)
                errors.push("英文首字母非大写");
        }
        // 英文和英文间有标点
        let reg13 = new RegExp("[a-zA-Z][,.!.,。?!]+[a-zA-Z]", "g");
        if (noTagText.match(reg13) != null) {
            errors.push("英文和英文间有标点");
        }

        // 英文连续大写错误
        let reg12 = new RegExp("[A-Z]{2,}", "g");
        if (noTagText.match(reg12) != null) {
            errors.push("连续大写错误");
        }

        let reg3 = new RegExp("[A-Za-z]+\\s+[\u4e00-\u9fa5]+", "g");
        if (text.match(reg3) != null) {
            errors.push("英文和中文有空格");
        }
        let reg4 = new RegExp("[\u4e00-\u9fa5]+\\s+[A-Za-z]+", "g");
        if (text.match(reg4) != null) {
            errors.push("中文和英文有空格");
        }
        let reg41 = new RegExp("[\u4e00-\u9fa5]+\\s+[\u4e00-\u9fa5]+", "g");
        if (text.match(reg41) != null) {
            errors.push("中文和中文有空格");
        }
        let reg5 = new RegExp("^\\s+", "g");
        if (text.match(reg5) != null) {
            errors.push("句头多余空格");
        }
        let reg5a = new RegExp("\\s{2,}", "g");
        if (text.trim().match(reg5a) != null) {
            errors.push("句中有多余空格");
        }
        let reg5b = new RegExp("\\s$", "g");
        if (text.match(reg5b) != null) {
            errors.push("句尾多余空格");
        }
        let reg6 = new RegExp("[。?!]$", "g");
        if (text.trim().match(reg6) == null) {
            errors.push("句尾标点错误");
        }
        let reg72 = new RegExp("( [,。?!])|([,。?!] )", "g");
        if (noTagText.match(reg72) != null) {
            errors.push("标点前后后空格");
        }
        let reg71 = new RegExp("[,。?!]{2,}", "g");
        if (noTagText.match(reg71) != null) {
            errors.push("有连续标点");
        }
        let reg7 = new RegExp("[^\u4e00-\u9fa5,。?!\\s\\w\\d]", "g");
        if (noTagText.match(reg7) != null) {
            errors.push("有非中文标点");
        }
        let reg8 = noTagText.match(/[^0-9\W]+|[\u4e00-\u9fa5]/g);
        if (reg8 != null && reg8.length == 1) {
            errors.push("只有一个字或单词");
        }
        if (noTagText.indexOf('儿') >= 0)
            warns.push('儿化音需输入{er},请确认"儿"书写正确');

        return { errors: errors, warns: warns };
    }

    function checkTag(text) {
        text = text.substring(1, text.length - 1);
        var reg = new RegExp("^[a-z]+[0-9]*", "g");
        return text.match(reg) == text;
    }

})();