User:Interaccoonale/DYKcheck.js

维基百科,自由的百科全书

注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。

// ***************************************************************** //
//                          DYKcheck tool                            //
//                         Version  zh-1.1                           //
// 要使用本脚本,请在您的个人js页面添加:                            //
// importScript('User:Interaccoonale/DYKcheck.js');                  //
// 修改自英维[[en:User:Shubinator/DYKcheck.js]],原作者列于其历史    //
// 记录页面,以下内容为英维的原始注释:                              //
// ***************************************************************** //
// For quick installation, add                                       //
// importScript('User:Shubinator/DYKcheck.js');                      //
// to your vector.js                                                 //
// See [[en:User:Shubinator/DYKcheck]] for more info, including      //
// configurable options and how to use the tool without installation //
// or logging in.                                                    //
// First version written by Shubinator in February 2009              //
// ***************************************************************** //
// 以下代码中的英文注释为英维原作者加入,中文注释为修改时添加。      //

mw.loader.using(['mediawiki.api', 'mediawiki.util'], function () {
    "use strict";

// 匹配CJK字符
// 注:参照StackOverflow的这个帖子来看:https://stackoverflow.com/questions/15389497
// JS不支持在正则表达式中使用基本多文种平面外的字符。
    const cjkRegex = /[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF]/g;

// 匹配非文字符号,包括标点符号和其它特殊字符,参见条目:[[Unicode字符列表]]
    const symbolRegex = /[\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E\u00A1-\u00BF\u00D7\u00F7\u2000-\u2BFF\u2E00-\u2E7F\u3001-\u303F\uFE30-\uFE4F\uFF01-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF40\uFF5b-\uFF65\uFF9E\uFF9F\uFFE0-\uFFE6\uFFE8-\uFFEE]/g;

    const spaceRegex = /\s+/g;

// 条目扩充的比例,例如英维应该是5,中维要求「扩充量达修订期之前原文的2/3以上」
// 也就是意味着1.67倍。
    const expansionScale = 1.67;
    const sizeLowerBound = 1500;

    const stubCat = 'Category:全部小作品';
    const spdCat = 'Category:快速删除候选';
    const afdCat = 'Category:条目删除候选';
    const alertCat = 'Category:拒绝当选首页新条目推荐栏目的条目';

    // dates 分别为:[创建页面或成为非重定向的时间、移动至主命名空间的时间、最后中断7日时间、最近一次获评DYK时间(英维原为获评GA时间)]
    let currentTitle, currentBytes, includeList = false, includeTable = false, dates;

    // Configurable options
    const unlock = window.unlock;

    const mwConfig = mw.config.get([
        "wgAction",
        "wgTitle",
        "wgPageName",
        "wgUserName",
        "wgNamespaceNumber",
        "skin"
    ]);

    const api = new mw.Api();

// Polyfill String.prototype.includes so that we don't have to write "!== -1" all over
// the codebase.
    if (!String.prototype.includes) {
        String.prototype.includes = function () {
            return String.prototype.indexOf.apply(this, arguments) !== -1;
        };
    }

    function escapeHtml(s) {
        // Use the browser's built-in ability to escape HTML.
        const div = document.createElement('div');
        div.appendChild(document.createTextNode(s));
        return div.innerHTML;
    }

    function scanArticle(title, output, html) {
        // the meat of the DYKcheck tool
        // calculates prose size of the given html
        // checks for inline citations and stub templates in the given html
        // passes info to checkTalk(), getFirstRevision(), checkMove(), and checkExpansion()

        // includeTable = window.confirm("是否要将条目中的表格计入字数?");
        // includeList = window.confirm("是否要将条目中的点列式列表计入字数?");


        dates = new Array(4);

        // calculate prose character size
        let [bytes, chars, words] = calculateProse(html, true);

        currentBytes = bytes;

        addLine(output, "dyk-prose", '<b>正文长度为</b>' + escapeHtml(currentBytes) + '个字节/位元组,' +
            escapeHtml(chars) + '个字符,' + escapeHtml(words) + '个字及外文单词(按照可读散文长度计算)',
            currentBytes < sizeLowerBound ? "pink" : "")

        // check for inline citations
        if (!html.innerHTML.includes('id="cite_ref-') && !html.innerHTML.includes('id=cite_ref-')) {
            addLine(output, "no-ref", '条目缺少<b>内文引证</b>', 'pink')
        }

        // 检查顺序为:创建时间、页面移动历史、主页面质量问题、讨论页、扩充长度
        // find creator of article and date
        checkArticleCreate(title, output);
    }

    function addLine(output, id, inner, color) {
        // 添加新的信息
        const element = document.createElement("li");
        element.id = id;
        output.appendChild(element);
        element.innerHTML = inner;
        if (color !== '') {
            element.style["background-color"] = color;
        }
    }

    function checkDocument() {
        // prepares for scan and passes info to scanArticle()
        if (document.getElementById("dyk-stats-0")) {
            // 移除之前的检测结果。
            clearStats();
        } else {
            const output = document.createElement("ul");
            output.id = "dyk-stats-0";

            const body = getBody();
            let dummy = body.getElementsByTagName("div")[0];
            if (dummy.nextSibling && dummy.nextSibling.id === 'siteNotice') { // if siteNotice is below siteSub
                dummy = dummy.nextSibling;
            } else if (dummy.nextSibling.nextSibling && dummy.nextSibling.nextSibling.id === 'siteNotice') {
                dummy = dummy.nextSibling.nextSibling;
            }
            dummy.parentNode.insertBefore(output, dummy.nextSibling);
            createHeaderAndProcessing(output);
            currentTitle = 0;
            let title = mwConfig.wgTitle;
            if (mwConfig.wgNamespaceNumber === 2) {
                title = "User:" + title;
            }
            scanArticle(title, output, body);
        }
    }
    function clearStats() {
        // if scan results already exist, turn them off and remove highlighting
        const oldStyle = document.getElementById("dyk-stats-0").className;
        const mainContent = getBody();
        const pList = mainContent.getElementsByTagName("p");
        for (let iPara = 0; iPara < pList.length; iPara++) {
            if (pList[iPara].parentNode === mainContent || pList[iPara].parentNode.parentNode === mainContent) {
                pList[iPara].style.cssText = oldStyle;
            }
        }
        if (document.getElementById("error-disp")) {
            const errorDisp = document.getElementById("error-disp");
            errorDisp.parentNode.removeChild(errorDisp);
        }
        let iStat = 0;
        while (document.getElementById("dyk-stats-" + iStat)) {
            const output = document.getElementById("dyk-stats-" + iStat);
            output.parentNode.removeChild(output);
            iStat++;
        }
        if (document.getElementById("hook-container")) {
            const hookOutput = document.getElementById("hook-container");
            hookOutput.parentNode.removeChild(hookOutput);
        }
        if (document.getElementById("dyk-header")) {
            const header = document.getElementById("dyk-header");
            header.parentNode.removeChild(header);
        }
        if (document.getElementById("dyk-processing")) {
            const processing = document.getElementById("dyk-processing");
            processing.parentNode.removeChild(processing);
        }
    }

    function calculateProse(doc, visible) {
        // calculates the prose of a given document
        // this function and its helper below are modified versions of
        // the prosesize tool (http://en.wikipedia.org/wiki/User:Dr_pda/prosesize.js)
        let pList = doc.getElementsByTagName("p");

        let prose = '';

        let i = 0;
        if (mwConfig.wgAction === 'submit' && visible) i = 1; // Avoid the "Remember that this is only a preview" text
        for (; i < pList.length; i++) {
            if (pList[i].parentNode.parentNode === doc || pList[i].parentNode.parentNode.parentNode.id === getBodyId()) {
                prose += getReadable(pList[i], visible);
                if (visible) {
                    pList[i].style.cssText = 'background-color:yellow';
                }
            }
        }
        prose = prose.replace(symbolRegex, "");

        let bytes = (new TextEncoder().encode(prose)).length;
        let chars = prose.length;

        let words = 0;


        // 计算CJK字数,一个字符算作一字
        words += (prose.match(cjkRegex)?.join('') || '').length;

        // 计算非CJK字符的单词数,以空格或CJK字符隔开,一个单词算作一字
        words += prose.replace(cjkRegex, ' ').split(spaceRegex).length;

        return [bytes, chars, words];
    }

    function getReadable(para, visible) {
        // helper method for calculateProse()
        let textReadable = '';

        for (let i = 0; i < para.childNodes.length; i++) {
            if (para.childNodes[i].nodeName === '#text') {
                textReadable += para.childNodes[i].nodeValue;
            } else if (para.childNodes[i].className !== 'reference' &&
                !(para.childNodes[i].className && para.childNodes[i].className.includes('emplate')) &&
                para.childNodes[i].id !== 'coordinates' && !para.childNodes[i].className.includes('noprint')) {
                textReadable += getReadable(para.childNodes[i], visible);
            } else if (visible) { // if it's an inline maintenance tag (like [citation needed]) or geocoordinates
                if (document.getElementById("dyk-stats-0").className) {
                    para.childNodes[i].style.cssText = document.getElementById("dyk-stats-0").className;
                } else {
                    para.childNodes[i].style.cssText = 'background-color:white';
                }
            }
        }
        return textReadable;
    }

    function checkExpansion(title, output, lastDYKDate) {
        // finds the start of expansion date (last 500 edits)
        // gets the last 500 unique revision ids for past revisions of the article and passes to helper function
        const promise = getRevisions({
            titles: title,
            rvlimit: 500,
            rvend: (lastDYKDate || new Date(0)).toISOString(),
            rvprop: ['ids', 'timestamp', 'sha1'],
            rvdir: 'older'
        });
        promise.done(function (revisions) {
            const lastBreak = findLastSevenDayBreak(revisions, output, lastDYKDate);
            if (lastBreak) {
                checkExpansionHelper(output, lastBreak.revid);
            } else if (lastDYKDate) {
                checkExpansionFromDYKBreak(title, output, lastDYKDate)
            } else {
                doneProcessing(output, null);
            }
        });
        promise.fail(function (error) {
            doneProcessing(output, error);
        })
    }

    function findLastSevenDayBreak(revisions, output, lastDYKDate) {
        const lastTimestamp = new Date(revisions[0].timestamp);
        const nowTimestamp = new Date();
        const timeDifferenceInDays0 = (nowTimestamp - lastTimestamp) / (1000 * 60 * 60 * 24);

        if (timeDifferenceInDays0 >= 7) {
            addLine(output, "last-break", '最后编辑于' +
                escapeHtml(toNormalDate(revisions[0].timestamp.substring(0, 10))) + ',至今超过7日未被编辑,<b>失去推荐资格</b>。',
                "pink");
            return null;
        }

        // 找到最后一次时间间隔大于七天的元素
        for (let i = 0; i < revisions.length - 1; i++) {
            const currentTimestamp = new Date(revisions[i].timestamp);
            const previousTimestamp = new Date(revisions[i + 1].timestamp);
            const timeDifferenceInDays1 = (currentTimestamp - previousTimestamp) / (1000 * 60 * 60 * 24);

            if (timeDifferenceInDays1 >= 7) {
                addLine(output, "last-break", '条目于' +
                    escapeHtml(toNormalDate(revisions[i + 1].timestamp.substring(0, 10))) + '至' +
                    escapeHtml(toNormalDate(revisions[i].timestamp.substring(0, 10))) + '超过7日未被编辑,视为一次<b>中断</b>。',
                    "");
                dates[2] = previousTimestamp;
                return revisions[i + 1];
            }
        }
        addLine(output, "last-break", '条目' + (revisions.length < 500 ? ('自' + (lastDYKDate ? '上次通过推荐' : '创建') + '至今') : '最近500笔编辑内') + '没有超过7日的中断', "");
    }

    function checkExpansionFromDYKBreak(title, output, lastDYKDate) {
        const promise = getRevisions({
            titles: title,
            rvlimit: 3,
            rvstart: lastDYKDate.toISOString(),
            rvprop: ['ids', 'timestamp', 'sha1'],
            rvdir: 'older'
        });
        promise.done(function (revisions) {
            if (revisions && revisions.length > 0) {
                checkExpansionHelper(output, revisions[0].revid);
            }
        });
        promise.fail(function (error) {
            doneProcessing(output, error);
        })
    }

    function checkExpansionHelper(output, revid) {
        const promise = api.get({
            format: 'json',
            action: 'parse',
            oldid: revid,
            prop: 'text'
        });
        promise.done(function (obj) {
            const expandTemp = document.createElement("div");
            expandTemp.id = "expand-temp";
            expandTemp.innerHTML = obj.parse.text['*'];
            let [bytes, , ] = calculateProse(expandTemp, false);
            // alert("Prose: " + prose + " 1x: " + current/5 + " Mid: " + mid + " Expand index: " + expandIndex);
            // use above line to debug the expansion check
            const scale = (currentBytes / bytes).toFixed(3)
            if (scale > expansionScale) {
                addLine(output, "expansion-check", '条目当前版本被扩充至上次中断时的' + scale + '倍,<b>符合扩充要求</b>', "");
            } else {
                addLine(output, "expansion-check", '条目当前版本被扩充至上次中断时的' + scale + '倍,<b>不符合扩充要求</b>', "pink");
            }
            doneProcessing(output, null);
        });
        promise.fail(function (error) {
            doneProcessing(output, error);
        });
    }

    function checkTalk(title0, output, stubChecked) {
        let title;
        // checks the talk page of the article for DYK, ITN, or stub templates
        if (mwConfig.wgNamespaceNumber !== 2) {
            title = "Talk:" + title0;
        } else {
            title = title0.replace("User:", "User talk:");
        }
        let lastDYKDate;
        const promise = getRevisions({
            titles: title,
            rvprop: 'content'
        });
        promise.done(function (revisions) {
            if (revisions && revisions[0]) {
                const talkPage = revisions[0]['*'];
                if (!stubChecked && talkPage.match(/class\s*=\s*[sS]tub/) &&
                    (document.getElementById("stub-alert") === null)) {
                    addLine(output, "talk-stub", '条目在讨论页上被评级为<b>小作品</b>。', 'yellow');
                }
                const dykTalkRegexMatches = talkPage.match(/{{\s*[dD](yk|YK\s?)talk[^}]*}}/g);
                const dykArticleHistoryMatches = talkPage.match(/dyk\d+date\s*=\s*(\d{4}(-|年\|?)\d{1,2}([-月])\d{1,2})/g);
                for (const matches of [dykTalkRegexMatches, dykArticleHistoryMatches]) {
                    if (matches) {
                        // if there's a DYK tag, try to find the date of previous appearance
                        for (const match of matches) {
                            const dateStr = match.match(/\d{4}(-|年\|?)\d{1,2}([-月])\d{1,2}/)[0];
                            // 注:中维DYKtalk模板有两种日期表示方式:{{DYKtalk|date=2020-03-07}}和{{DYKtalk|2006年|6月18日}},
                            // 另外实测{{DYKtalk|date=2020-3-7}}也可用,这个正则应当能比较好地匹配这些情况。
                            const [year, month, day] = dateStr.split(/\D+/);
                            const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
                            if (!lastDYKDate || date > lastDYKDate) {
                                lastDYKDate = date;
                                dates[3] = date;
                            }
                        }
                    }
                }
                if (lastDYKDate) {
                    addLine(output, "talk-dyk", '条目于' + toNormalDateYMD(lastDYKDate.getFullYear(), lastDYKDate.getMonth() + 1, lastDYKDate.getDate()) + '最近一次获评DYK,视为一次中断。', "");
                }
            }
            // check for expansion start date, assuming now expanded to 5x (last 500 edits)
            checkExpansion(title0, output, lastDYKDate);
        });
        promise.fail(function (error) {
            doneProcessing(output, error);
        });
    }

    function getCategories(options) {
        // 修改自`getRevisions`
        // 参见:https://www.mediawiki.org/wiki/API:Categories
        options = options || {};
        return api.get({
            format: 'json',
            action: 'query',
            prop: 'categories',
            titles: options.titles,
            cllimit: options.cllimit,
            clcategories: options.clcategories,
        }).then(
            // On success
            function (obj) {
                return Object.values(obj.query.pages)[0].categories;
            },
            // On failure
            function (err) {
                alert("API error");
                return err;
            }
        );
    }

    function checkAlerts(title, output) {
        const promise = getCategories({
            titles: title,
            cllimit: 5,
            clcategories: [stubCat, spdCat, afdCat, alertCat],
        });
        promise.done(function (categories) {
            // check for various tags
            let stubChecked = false;
            if (categories) {
                for (let iCat = 0; iCat < categories.length; iCat++) {
                    let category = categories[iCat];
                    switch (category.title) {
                        case stubCat:
                            addLine(output, "page-stub", '条目当前为<b>小作品</b>。', "yellow");
                            stubChecked = true;
                            break;
                        case spdCat:
                            addLine(output, "page-spd", '条目已被标记为<b>快速删除候选</b>。', "pink");
                            break;
                        case afdCat:
                            addLine(output, "page-afd", '条目已被提出<b>存废讨论</b>。', "pink");
                            break;
                        case alertCat:
                            addLine(output, "page-alert", '条目<b>存在不能获选为DYK的质量问题</b>。', "yellow");
                            break;
                        default:
                            break;
                    }
                }
            }
            // check if article is stub or if it has appeared in DYK or ITN
            checkTalk(title, output, stubChecked); //check talk page
        });
        promise.fail(function (error) {
            doneProcessing(output, error);
        });
    }

    function getRevisions(options) {
        // Returns a jQuery promise with an array of a title's revisions.
        // The first parameter is an options object accepting the following API fields:
        // - titles
        // - rvlimit
        // - rvprop
        // - rvdir
        // - rvstart
        options = options || {};
        return api.get({
            format: 'json',
            action: 'query',
            prop: 'revisions',
            titles: options.titles,
            rvlimit: options.rvlimit,
            rvprop: options.rvprop,
            rvdir: options.rvdir,
            rvstart: options.rvstart,
            rvend: options.rvend,
            indexpageids: true
        }).then(
            // On success
            function (obj) {
                const pageId = obj.query.pageids[0];
                return obj.query.pages[pageId].revisions;
            },
            // On failure
            function (err) {
                alert("API error");
                return err;
            }
        );
    }

    function checkArticleCreate(title, output) {
        // finds the creator of the article, and the date created
        // also checks if the article was created as a redirect, finds non-redirect date if so
        const promise = getRevisions({
            titles: title,
            rvlimit: 4,
            rvprop: ['timestamp', 'user', 'content'],
            rvdir: 'newer'
        });
        promise.done(function (revisions) {
            const user = revisions[0].user;
            const timestamp = revisions[0].timestamp;
            addLine(output, "creation-info", '条目由<b>编者</b>' + escapeHtml(user) +
                '<b>创建于</b>' + escapeHtml(toNormalDate(timestamp.substring(0, 10))), "");
            dates[0] = toDateObject(timestamp);
            for (let i = 0; i < revisions.length; i++) {
                const content = revisions[i]['*'];
                const isRedirect = content.toUpperCase().match(/#(REDIRECT|重定向)( ?)\[\[/g);
                if (i === 0 && !isRedirect) {
                    break;
                } else if (!isRedirect) {
                    if (i !== 0) {
                        const urUser = revisions[i].user;
                        const urTimestamp = revisions[i].timestamp;
                        addLine(output, "expanded-from-redirect", '条目由<b>编者</b>' + escapeHtml(urUser) + '在' +
                            escapeHtml(toNormalDate(urTimestamp.substring(0, 10))) + '<b>自重定向页面扩充为条目</b>。', "");
                        dates[0] = toDateObject(urTimestamp);
                    }
                    break;
                }
            }
            // check if the article has been moved from userspace within last 100 edits
            checkMove(title, output);
        });
        promise.fail(function (error) {
            doneProcessing(output, error);
        });
    }

    function checkMove(title, output) {
        if (mwConfig.wgNamespaceNumber !== 0) {
            // 检查条目是否是小作品、提删、速删、以及其他拒绝当选的问题
            checkAlerts(title, output);
        }
        //checks the last 100 edits of an article for a move from userspace or AfC to current location
        const promise = getRevisions({
            titles: title,
            rvlimit: 100,
            rvprop: ['flags', 'user', 'timestamp', 'comment'],
            rvdir: 'older',
        });
        promise.done(function (revisions) {
            for (let i = 0; i < revisions.length; i++) {
                const comment = revisions[i].comment;
                const commentMatch = comment.match(/(移動|移动)(頁面|页面)?((\[\[User:)|(\[\[Draft:)|(\[\[Wikipedia talk:Articles for creation\/))[\s\S]*至\[\[/);
                if ((revisions[i].minor === "") && commentMatch) {
                    const match0 = commentMatch[0];
                    const movedFrom = match0.substring(match0.indexOf("[[") + 2, match0.indexOf("]]至"));
                    const date = revisions[i].timestamp;
                    addLine(output, "moved-userspace", '条目原位于' + escapeHtml(movedFrom) + ',后于' +
                        escapeHtml(toNormalDate(date.substring(0, 10))) + '移动入主命名空间,视为条目创建时间。', "");
                    dates[1] = toDateObject(date);
                    break;
                }
            }
            // 检查条目是否是小作品、提删、速删、以及其他拒绝当选的问题
            checkAlerts(title, output);
        });
        promise.fail(function (error) {
            doneProcessing(output, error);
        });
    }

    function doneProcessing(output, error) {
        // checks if all parts are done processing
        // if they are, the dates of creation and expansion are checked for within 10 days (rounded down, in nominator's favor)
        // then the next title (for multiple article noms) is processed (required to combat asynchronous threads)
        // if there are no more titles left (or not on T:TDYK), the processing message is removed
        if (document.getElementById("dyk-processing")) {
            if (error) {
                addLine(output, 'end-check', '检测遇到错误而中断,请重试,如果持续遇到类似情况,请<a href="//zh.wikipedia.org/wiki/' +
                    'User talk:Interaccoonale">点此</a>回报问题。', 'pink')
            } else {
                addLine(output, 'end-check', '检测完毕。<br><small>此脚本不能替代人工,条目遇到破坏、侵权验证等情况可能会导致被清空,' +
                    '回退破坏或将排除侵权嫌疑的内容重新恢复不能被视为扩充达标,条目中如有复制或拆分自其它条目的内容也应当予以排除,' +
                    '被完全重写的条目可能字数上没有较大的增减,而被检测为不符合扩充标准,请注意人工处理这些情况。</small>', '')
            }
            // 结束
            const processing = document.getElementById("dyk-processing");
            processing.parentNode.removeChild(processing);
        }
    }

// taken from the prosesize tool (http://en.wikipedia.org/wiki/User:Dr_pda/prosesize.js)
    function getBodyId() {
        let contentName;
        if (mwConfig.skin === 'monobook' || mwConfig.skin === 'chick' || mwConfig.skin === 'mymwConfig.skin' || mwConfig.skin === 'simple') {
            contentName = 'bodyContent';
        } else if (mwConfig.skin === 'modern') {
            contentName = 'mw_contentholder';
        } else if (mwConfig.skin === 'standard' || mwConfig.skin === 'cologneblue' || mwConfig.skin === 'nostalgia') {
            contentName = 'article';
        } else {
            // fallback case; the above covers all currently existing skins
            contentName = 'bodyContent';
        }
        // Same for all skins if previewing page
        if (mwConfig.wgAction === 'submit') contentName = 'wikiPreview';
        return contentName;
    }

    function getBody() {
        // gets the HTML body of the page
        // taken from the prosesize tool (http://en.wikipedia.org/wiki/User:Dr_pda/prosesize.js)
        return document.getElementById(getBodyId());
    }

    function createHeaderAndProcessing(output) {
        // makes the header above the scan results
        const header = document.createElement("span");
        header.id = "dyk-header";
        header.innerHTML = '<br /><b>新条目推荐(DYK)候选条目检测:<small>(<a href="//en.wikipedia.org/wiki/' +
            'User:Shubinator/DYKcheck">点此</a>了解详情)</small></b>';
        output.parentNode.insertBefore(header, output);
        const processing = document.createElement("span");
        processing.id = "dyk-processing";
        processing.innerHTML = '<br /><b><span style="color: crimson; "> 正在进行检测…… </span></b>';
        // 这一信息最终会被移除,因此不要使用`addLine`
        output.parentNode.insertBefore(processing, header);
    }

    function toNormalDate(utc) {
		// 修改时直接将其改为中文日期格式
        return toNormalDateYMD(utc.substring(0, 4), utc.substring(5, 7) * 1, utc.substring(8, 10) * 1);
    }

    function toNormalDateYMD(year, month, day) {
        return year + '年' + month + '月' + day + '日';
    }

    function toDateObject(timestamp) {
        // converts a Wikipedia timestamp to a Javascript Date object
        const date = new Date();
        date.setUTCFullYear(timestamp.substring(0, 4), timestamp.substring(5, 7) - 1, timestamp.substring(8, 10));
        date.setUTCHours(timestamp.substring(11, 13), timestamp.substring(14, 16), timestamp.substring(17, 19));
        return date;
    }

    window.dykCheck = function () {
        // this function for casual use and anons
        if (((mwConfig.wgAction === 'view' || mwConfig.wgAction === 'submit' || mwConfig.wgAction === 'purge') &&
            (mwConfig.wgNamespaceNumber === 0 || mwConfig.wgNamespaceNumber === 2)) || unlock) {
            checkDocument();
        }
    };

    function addToolbarPortletLink(func, tooltip) {
        const link = mw.util.addPortletLink(
            'p-cactions',
            '#',
            'DYK check',
            't-dyk-check',
            tooltip
        );
        $(link).click(function (e) {
            e.preventDefault();
            func();
        });
    }

    // 在Wiki Tools里面加入DYK Check。
    if (unlock || (
        (mwConfig.wgAction === 'view' || mwConfig.wgAction === 'submit' || mwConfig.wgAction === 'purge') &&
        (mwConfig.wgNamespaceNumber === 0 || mwConfig.wgNamespaceNumber === 2)
    )) {
        addToolbarPortletLink(checkDocument, 'Check if this article qualifies for DYK');
    }
});