QuickPatrol_v2

MediaWiki巡查工具 | A patrol tool for MediaWiki

当前为 2024-11-17 提交的版本,查看 最新版本

// ==UserScript==
// @name         QuickPatrol_v2
// @namespace    qp_tool_v2
// @version      1.91
// @description  MediaWiki巡查工具 | A patrol tool for MediaWiki
// @author       teaSummer
// @match        *://*/wiki/*
// @match        *://*/w/*
// @match        *://*/index.php?*
// @license      MIT
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // 配置 | Configuration
    const config = {
        liveUpdate: false, // 全局实时更新 | Global Live Update
        liveUpdateInterval: 1000, // 全局实时更新间隔(毫秒) | Global Live Update Interval (Millisecond)
        usingIPE: false, // 使用InPageEdit | Using InPageEdit
        rollbackMode: 'summary', // 回退模式,值为'summary'(可编辑摘要)、'confirm'(需确认)或'default'(默认) | Rollback Mode, values are 'summary', 'confirm' or 'default'
        maxRetries: 5 // 获取mwApi最大重试次数 | Maximum Retries of Getting mwApi
    };

    // 本地化 | Localization
    const w = {
        'en': {
            un: 'This edit has not yet been patrolled',
            ing: 'Quick patrolling...',
            done: 'Quick patrolled',
            t_un: 'Unpatrolled',
            g_un: 'Unpatrolled',
            hl: 'Highlighted: ',
            g_cr_1: 'Sure to rollback this edit?',
            g_er_1: 'Rollback summary:',
            g_er_2: 'Revert edits by [[Special:Contribs/$1|$1]] ([[User talk:$1|talk]])'
        },
        'zh-hans': {
            un: '该编辑尚未巡查',
            ing: '快速巡查中…',
            done: '已快速巡查',
            t_un: '未巡查',
            g_un: '尚未巡查',
            hl: '已高亮:',
            g_cr_1: '确定要回退此编辑吗?',
            g_er_1: '回退摘要:',
            g_er_2: '回退[[Special:Contribs/$1|$1]]([[User talk:$1|留言]])所做的编辑'
        },
        'zh-hant': {
            un: '該編輯尚未巡查',
            ing: '快速巡查中…',
            done: '已快速巡查',
            t_un: '未巡查',
            g_un: '尚未巡查',
            hl: '已明顯標示:',
            g_cr_1: '確定要回退此編輯嗎?',
            g_er_1: '回退摘要:',
            g_er_2: '回退[[Special:Contribs/$1|$1]]([[User talk:$1|留言]])所做的編輯'
        },
    }

    let mwApi, mwLang, rights, l, fail = 0, load = false;

    function g_cr() {
        $('.mw-rollback-link a').each(function () {
            let href = $(this).attr('href');
            $(this).click((e) => {
                e.preventDefault();
                OO.ui.confirm(l.g_cr_1).then((confirmed) => {
                    if (confirmed) location.href = href;
                });
            });
        }).css({
            'font-style': 'italic',
            'font-family': 'Consolas'
        });
    }

    function g_er() {
        $('.mw-rollback-link a').each(function () {
            let href = $(this).attr('href');
            $(this).click((e) => {
                e.preventDefault();
                let name = decodeURIComponent(href.match(/&from=(.+)&token/)[1].replace(/\+/g, ' '));
                OO.ui.prompt(l.g_er_1, {
                    textInput: { value: mw.format(l.g_er_2, name) }
                }).then((result) => {
                    if (result) location.href = href + '&summary=' + encodeURIComponent(result);
                });
            });
        }).css({
            'font-style': 'italic',
            'font-family': 'Consolas'
        });
    }

    async function rollback_gadget() {
        if (config.rollbackMode == 'default' || !load) return;
        if (!(OO && OO.ui && OO.ui.confirm)) {
            await mw.loader.load('https://fastly.jsdelivr.net/npm/oojs-ui/dist/oojs-ui-windows-wikimediaui.min.css', 'text/css');
            await mw.loader.load('https://fastly.jsdelivr.net/npm/oojs-ui/dist/oojs-ui-windows.min.js');
        }
        load();
    }

    async function init() {
        if (fail >= config.maxRetries) return;
        try {
            mwApi = await new mw.Api();
            mwLang = Object.keys(await mw.language.data)[0];
            if (['zh-cn', 'zh-hans', 'zh-hans-cn', 'zh-cn-hans', 'zh', 'cn'].indexOf(mwLang) != -1) mwLang = 'zh-hans';
            else if (mwLang.startsWith('zh-')) mwLang = 'zh-hant';
            if (Object.keys(w).indexOf(mwLang) == -1) mwLang = 'en';
            l = w[mwLang];
            if (!load) {
                load = true;
                await mw.loader.load(`https://fastly.jsdelivr.net/gh/teaSummer/mcw@main/patrol/${mwLang}/Gadget-revisionPatrol.css`, 'text/css');
                await mw.loader.load(`https://fastly.jsdelivr.net/gh/teaSummer/mcw@main/patrol/${mwLang}/Gadget-revisionPatrol.js`);
                if (config.rollbackMode == 'summary') load = g_er;
                if (config.rollbackMode == 'confirm') load = g_cr;
            }
        } catch (e) {
            fail = fail + 1;
            if (fail < config.maxRetries) console.warn(`[QuickPatrol] Failed to call MediaWiki. Retrying... (${fail}/${config.maxRetries})`);
            else console.error(`[QuickPatrol] Failed to call MediaWiki. (${fail}/${config.maxRetries})`);
            new Promise(() => setTimeout(init, 2000));
            return;
        }

        console.log('[QuickPatrol] Checking rights...');
        rights = (
            await mwApi.get({
                action: 'query',
                meta: 'userinfo',
                uiprop: 'rights'
            })
        ).query.userinfo.rights;

        if (!config.liveUpdate) main();
        return;
    }

    function with_IPE() {
        $('.diff-version:not(.diff-hidden-history):not([title])').css({
            color: 'blue',
            cursor: 'pointer'
        });
        $('.diff-version:not(.diff-hidden-history)').click(function () {
            const me = $(this);
            if (me.attr('title')) return;
            const value = me.text().replace(/[^0-9]/g, '');
            $(this).attr('title', l.ing).css('color', '#ff1493');
            patrol(
                value,
                () => {
                    me.text('✔').attr('title', l.done).css({
                        color: 'green',
                        cursor: 'help'
                    });
                }
            );
            return value;
        });
    }

    async function main() {
        if (!rights) {
            if (fail >= config.maxRetries || !fail) init();
            return;
        }
        if (rights.includes('rollback')) rollback_gadget();
        if (rights.includes('patrol')) {
            $('.mw-changeslist-reviewstatus-unpatrolled:not(.mw-rcfilters-ui-highlights-enhanced-toplevel), .revisionpatrol-unpatrolled').attr('data-mw-revid', function (_i, value) {
                const that = $(this);
                that.find('.revisionpatrol-icon-unpatrolled').after(`<span class="unpatrolled custom-unpatrolled" title="${l.g_un}">!</span>`);
                that.find('.revisionpatrol-icon-unpatrolled+.custom-unpatrolled').css({
                    color: 'red',
                    'font-weight': 'bold',
                    position: 'absolute',
                    left: '-0.8em',
                    'margin-left': '-1px',
                    'text-decoration': 'underline dotted',
                    'font-family': 'sans-serif'
                });
                that.find('.revisionpatrol-icon-unpatrolled').remove();
                that.find('.unpatrolled')
                    .css('cursor', 'pointer')
                    .click(async function () {
                        let me = $(this);
                        if (me.hasClass('custom-unpatrolled')) me.css('left', '-1em');
                        if (me.text() == '!') {
                            $(this).text('#').attr('title', l.ing).css('color', 'magenta');
                            if (!value && that.attr('data-mw-logid')) {
                                try {
                                    value = new RegExp(`"logid":${that.attr('data-mw-logid')},.+?,"revid":([0-9]+)`).exec(JSON.stringify((await mwApi
                                        .get({
                                            action: 'query',
                                            list: 'logevents',
                                            leprop: 'ids',
                                            letitle: that.find('td.mw-enhanced-rc-nested').attr('data-target-page'),
                                            letype: that.attr('data-mw-logaction').split('/')[0],
                                            lelimit: 'max',
                                            format: 'json'
                                        })
                                    )))[1];
                                } catch (e) {
                                    $(this).text('!').attr('title', l.un).css('color', 'red');
                                    return;
                                }

                            }
                            patrol(
                                value,
                                () => {
                                    me.text('✔').removeClass('unpatrolled').attr('title', l.done).css({
                                        color: 'green',
                                        cursor: 'help'
                                    });
                                    if (me.hasClass('custom-unpatrolled')) {
                                        me.css({
                                            left: '-1.15em',
                                            'font-weight': 'normal'
                                        });
                                    } else {
                                        me.css('margin-left', '-0.1em');
                                    }
                                    that.removeClass('mw-rcfilters-highlight-color-c5').removeClass('mw-changeslist-reviewstatus-unpatrolled');
                                    that.attr('title', that.attr('title').replace(new RegExp(`(, |、\u200B)?${l.t_un}(、\u200B|, )?`, 'g'), ''));
                                    if (that.attr('title') == l.hl) that.removeAttr('title');
                                },
                                () => { if (me.hasClass('custom-unpatrolled')) me.css('left', '-0.8em') }
                            );
                        }
                    });
                return value;
            });
            if (config.usingIPE) with_IPE();
        }
    }

    function patrol(revid, successFallback, exFailFallback = () => { }) {
        const failFallback = () => {
            console.warn(`[QuickPatrol] FAILED (revid: ${value})`);
            me.text('!').attr('title', l.un).css({
                color: 'red',
                cursor: 'help'
            });
            if (me.hasClass('custom-unpatrolled')) me.attr('title', l.g_un);
            exFailFallback();
        }
        console.debug(`[QuickPatrol] TRYING (revid: ${revid})`);
        mwApi
            .get({
                action: 'query',
                meta: 'tokens',
                type: 'patrol',
                format: 'json'
            })
            .done((data) => {
                mwApi
                    .post({
                        action: 'patrol',
                        revid: revid,
                        token: data.query.tokens.patroltoken,
                        format: 'json'
                    })
                    .done(() => {
                        console.debug(`[QuickPatrol] SUCCEEDED (revid: ${revid})`);
                        successFallback();
                    })
                    .fail(failFallback);
            })
            .fail(failFallback);
    }


    window.addEventListener('load', init, false);
    if (config.liveUpdate) {
        window.setInterval(main, config.liveUpdateInterval);
    } else if (config.usingIPE) {
        window.setInterval(with_IPE, config.liveUpdateInterval);
    }
})();