QuickPatrol_v2

MediaWiki巡查工具 | A patrol tool for MediaWiki

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

Advertisement:

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         QuickPatrol_v2
// @namespace    qp_tool_v2
// @version      2.53
// @description  MediaWiki巡查工具 | A patrol tool for MediaWiki
// @author       teaSummer
// @source       https://github.com/teaSummer/QuickPatrol_v2
// @match        *://*/wiki/*
// @match        *://*/w/*
// @match        *://*/index.php?*
// @match        *://*.wiki/*
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCI+PHBhdGggZD0iTTU2IDdjLTEuNDgyIDQuMTktMy41ODggNy41NjctNi4wNjMgMTEuMjVDNDEuNzEgMzAuNzk2IDM1LjIxOCA0My45NDYgMzAgNThjLTIuODIzLjIxLTUuMjI0LjM4NC04IDAtMi4wNjYtMS45MDYtMi4wNjYtMS45MDYtMy41NjMtNC41LTIuNzA3LTQuMzk4LTUuNi04LjA2OS05LjE1Mi0xMS44MDlDOCA0MCA4IDQwIDggMzdjMi45MDEtMS40NSA0Ljc4NS0xLjQwOCA4LTEgMi40ODQgMi4zNzEgMi40ODQgMi4zNzEgNC43NSA1LjQzOEMyMy4zODQgNDUuMjk0IDIzLjM4NCA0NS4yOTQgMjcgNDhsLjI4MS0xLjk3N2MuOTU3LTQuMDIyIDIuNTM3LTcuNjMgNC4yMTktMTEuMzk4bDEuMDYzLTIuMzgzYTE2NC41NjYgMTY0LjU2NiAwIDAgMSA4LjE4Ny0xNS42OGwxLjEwNC0xLjg3OEM0NS44MTkgOC4yMTUgNDguMzkyIDYuNTYxIDU2IDd6IiBmaWxsPSJncmVlbiIvPjxwYXRoIGQ9Ik01NSAxNmwzIDF2NGg0djJoLTRsLTEgNWgtMnYtNWgtNXYtMmg1di01eiIgZmlsbD0iI0YxRkYwRSIvPjwvc3ZnPg==
// ==/UserScript==

(() => {
    'use strict';

    const l10n = {
        'en': {
            un: 'This edit has not yet been patrolled',
            ing: 'Quick patrolling...',
            done: 'Quick patrolled',
            t_un: 'Unpatrolled',
            g_un: 'Unpatrolled',
            hl: 'Highlighted: ',
            s: 'Additional summary',
            h_1: 'Apply',
            h_2: '[[Special:AbuseLog/$1|AbuseLog/$1]] by [[Special:Contribs/$2|$2]] ([[User talk:$2|talk]]), ',
            h_3: 'the original summary is "$1"',
            h_4: 'no original summary',
            g_cr_1: 'Sure to rollback this edit?',
            g_er_1: 'Revert edits by [[Special:Contribs/$1|$1]] ([[User talk:$1|talk]])',
            g_er_2: 'Edit rollback summary',
            c: ': ',
            n_1: 'Applied',
            n_2: 'Failed to apply',
            n_3: '⚙️ Settings',
            n_4: 'Patrol all logs (in emergency)',
            p_1: 'Failed to patrol',
            p_2: 'Arguments',
            p_3: 'Cancel',
            r_1: 'Reverting...',
            r_2: 'Reverted',
            r_3: 'Failed to rollback',
            r_summary: 'Summary',
            r_confirm: 'Confirm',
            r_default: 'Default',
            r_markbot: 'Robotize',
            qp_patrol: 'Quick Patrol',
            qp_patrol_confirm: 'Patrol Confirmation',
            qp_rollback: 'Quick Rollback',
            qp_rollback_mode: 'Rollback Mode',
            qp_rollback_show: 'Show Diff After Rollback',
            qp_rollback_show_tooltip: 'This defaults to off when the "Show a confirmation prompt when clicking on a rollback link" preference is enabled.',
            qp_abuse_edit: 'Helper that Filtered Edits',
            qp_jump_blank: 'Links Open in New Tabs',
            qp_max_tries: 'Maximum Tries of Initialization',
            qp_local_summary: 'Localization Maps English Summary',
            map: ['unnecessary edit', 'unnecessary( information| content)?|#UN#|#U#',
                'false content', 'false( information)?|#F#',
                'outdated content', 'outdated?( information)?|#OUT#|#OD#|#O#',
                'redundant content', 'redundant( information)?|#RD#|#R#',
                'non-consensus', '#NC#|#N#',
                'machine translation', 'machine-trans|#MT#|#M#',
                'duplicated content', 'duplicated?( information)?|#DUP#|#DP#|#D#',
                'unsourced', '#CITE#|#US#',
                'unambiguous content', 'unambig(uous)?|#NAM#|#NA#',
                'ambiguous content', 'ambig(uous)?|#AM#|#A#',
                'WAI for content', '(sic|WAI)( content|-C)|#SIC#|#SC#|#S#',
                'Works As Intended', '#WAI#|WAI|#W#',
                'Cannot Reproduce', '#CR#|#C#',
                'N-ST', 'delete without reason']
        },
        'zh-hans': {
            un: '该编辑尚未巡查',
            ing: '快速巡查中…',
            done: '已快速巡查',
            t_un: '未巡查',
            g_un: '尚未巡查',
            hl: '已高亮:',
            s: '附加摘要',
            h_1: '应用',
            h_2: '[[Special:AbuseLog/$1|滥用日志/$1]] — [[Special:Contribs/$2|$2]]([[User talk:$2|留言]]),',
            h_3: '原始摘要为“$1”',
            h_4: '没有原始摘要',
            g_cr_1: '确定要回退此编辑吗?',
            g_er_1: '回退[[Special:Contribs/$1|$1]]([[User talk:$1|留言]])所做的编辑',
            g_er_2: '编辑回退摘要',
            c: ':',
            n_1: '应用完成',
            n_2: '应用失败',
            n_3: '⚙️ 设置',
            n_4: '巡查所有日志(紧急时)',
            p_1: '巡查失败',
            p_2: '参数',
            p_3: '取消',
            r_1: '回退中…',
            r_2: '已回退',
            r_3: '回退失败',
            r_summary: '摘要',
            r_confirm: '确定',
            r_default: '默认',
            r_markbot: '机器化',
            qp_patrol: '快速巡查',
            qp_patrol_confirm: '巡查二次确认',
            qp_rollback: '快速回退',
            qp_rollback_mode: '回退模式',
            qp_rollback_show: '回退后显示差异',
            qp_rollback_show_tooltip: '已在参数设置启用“当点击回退链接时显示确认提示”,此项默认禁用。',
            qp_abuse_edit: '过滤内容编辑助手',
            qp_jump_blank: '在新标签页打开链接',
            qp_max_tries: '初始化最大尝试次数',
            qp_local_summary: '本地化映射英语摘要',
            map: ['不必要的编辑', '不必要的编辑', //
                '含不实内容', '含不实内容', //
                '含过时内容', '含过时内容', //
                '包含无用的冗余内容', '包含无用的冗余内容', //
                '不符合现有共识', '不符合现有共识', //
                '疑似使用机器翻译', '疑似使用机器翻译', //
                '重复内容', '重复内容', //
                '来源不明', '来源不明', //
                '内容无歧义', '内容无歧义', //
                '内容含有歧义', '内容含有歧义', //
                '原文无误', '原文无误', //
                '有意为之', '有意为之', //
                '无法复现', '无法复现', //
                '含非标准译名的编辑', '无故删除']
        },
        'zh-hant': {
            un: '該編輯尚未巡查',
            ing: '快速巡查中…',
            done: '已快速巡查',
            t_un: '未巡查',
            g_un: '尚未巡查',
            hl: '已明顯標示:',
            s: '附加摘要',
            h_1: '應用',
            h_2: '[[Special:AbuseLog/$1|濫用日誌/$1]] — [[Special:Contribs/$2|$2]]([[User talk:$2|留言]]),',
            h_3: '原始摘要為「$1」',
            h_4: '沒有原始摘要',
            g_cr_1: '確定要回退此編輯嗎?',
            g_er_1: '回退[[Special:Contribs/$1|$1]]([[User talk:$1|留言]])所做的編輯',
            g_er_2: '編輯回退摘要',
            c: ':',
            n_1: '應用完成',
            n_2: '應用失敗',
            n_3: '⚙️ 設定',
            n_4: '巡查所有日誌(緊急時)',
            p_1: '巡查失敗',
            p_2: '引數',
            p_3: '取消',
            r_1: '回退中…',
            r_2: '已回退',
            r_3: '回退失敗',
            r_summary: '摘要',
            r_confirm: '確定',
            r_default: '預設',
            r_markbot: '機器化',
            qp_patrol: '快速巡查',
            qp_patrol_confirm: '巡查二次確認',
            qp_rollback: '快速回退',
            qp_rollback_mode: '回退模式',
            qp_rollback_show: '回退後顯示差異',
            qp_rollback_show_tooltip: '已在偏好設定啟用「當點擊回退連結時顯示確認提示」,此項預設停用。',
            qp_abuse_edit: '過濾內容編輯助手',
            qp_jump_blank: '在新標籤頁開啟連結',
            qp_max_tries: '初始化最大嘗試次數',
            qp_local_summary: '本地化對映英語摘要',
            map: ['不必要的編輯', '不必要的編輯', //
                '含不實內容', '含不實內容', //
                '含過時內容', '含過時內容', //
                '包含無用的冗餘內容', '包含無用的冗餘內容', //
                '不符合現有共識', '不符合現有共識', //
                '疑似使用機器翻譯', '疑似使用機器翻譯', //
                '重複內容', '重複內容', //
                '來源不明', '來源不明', //
                '內容無歧義', '內容無歧義', //
                '內容含有歧義', '內容含有歧義', //
                '原文無誤', '原文無誤', //
                '有意為之', '有意為之', //
                '無法復現', '無法復現', //
                '含非標準譯名的編輯', '無故刪除']
        }
    };

    // ES2017 -> ES2015
    function asyncGeneratorStep(ag_n, ag_t, ag_e, ag_r, ag_o, ag_a, ag_c) { let ag_i, ag_u; try { ag_i = ag_n[ag_a](ag_c); ag_u = ag_i.value; } catch (n) { return void ag_e(n); } return ag_i.done ? ag_t(ag_u) : Promise.resolve(ag_u).then(ag_r, ag_o); }
    function _asyncToGenerator(ag_n) { return function () { let ag_t = this, ag_e = arguments, ag_a; return new Promise(function (r, o) { ag_a = ag_n.apply(ag_t, ag_e); function _next(n) { asyncGeneratorStep(ag_a, r, o, _next, _throw, 'next', n); } function _throw(n) { asyncGeneratorStep(ag_a, r, o, _next, _throw, 'throw', n); } _next(void 0); }); }; }

    const $E = (e, f, r) => {
        if ($(e).length) return typeof f === 'function' ? f($(e)) : $(e);
        return typeof f === 'function' ? r : f;
    };
    const status = {};
    let mwApi, mwLang, rights, l, reverted, load, fails = 0;
    status.np = '.mw-changeslist-reviewstatus-unpatrolled:not(.mw-rcfilters-ui-highlights-enhanced-toplevel):not(.quickpatrol), .revisionpatrol-unpatrolled';
    status.load = _asyncToGenerator(function* (prefix, with_script) {
        for (let s of with_script ? ['.css', '.js'] : ['.css']) yield mw.loader.load(`https://fastly.jsdelivr.net/${prefix}${s}`, s === '.css' ? 'text/css' : void 0);
    });

    function helper() {
        if (!$E('.mw-abuselog-details')) return;
        const g = (e) => $E(`.mw-abuselog-details-${e} div.mw-abuselog-var-value`, (e) => e.text().replace(/^'|'$/g, ''));
        const v = {
            user: g('user_name'),
            action: g('action'),
            summary: g('summary'),
            new_text: g('new_wikitext'),
            page: g('page_prefixedtitle'),
            logid: location.href.replace(/\/+$/, '').split('/').slice(-1)[0].replace(/[^0-9]/g, '')
        };
        if (v.action !== 'edit') return;
        let o_summary = l.h_2.replace(/\$1/g, v.logid).replace(/\$2/g, v.user);
        $('#mw-content-text p').after(`<button class="quickpatrol-abuseedit cdx-button mw-ui-button">${l.h_1}</button>`);
        o_summary = o_summary + (v.summary ? l.h_3.replace('$1', v.summary) : l.h_4);
        if (v.summary.trim()) {
            $('.quickpatrol-abuseedit').after(`<div class="quickpatrol-abuseedit-summary"/>`);
            $('.quickpatrol-abuseedit-summary').text(v.summary);
        }
        $E('.diff-type-table', $('.quickpatrol-abuseedit-summary')).after('<textarea class="new_wikitext" cols="80" rows="25"/>');
        $('.new_wikitext').val(v.new_text).before('<input class="page_prefixedtitle"/>');
        if (mw.addWikiEditor) mw.addWikiEditor($('.new_wikitext'));
        $('.page_prefixedtitle').val(v.page).attr('placeholder', v.page);
        $('.quickpatrol-abuseedit').click(function () {
            const me = $(this);
            const abuse_edit = (r) => {
                const outputs = {
                    page: $E('.page_prefixedtitle', (e) => e.val() ? e.val() : v.page, v.page),
                    summary: r ? o_summary + l.c + r : o_summary,
                    online: navigator.onLine,
                    platform: navigator.platform
                };
                const notice = outputs.summary.replace(/\[\[[^|[\]]*\|(.*?)]]/g, '$1');
                me.attr('disabled', true);
                $(window).on('beforeunload', () => me.attr('disabled'));
                console.debug(`[QuickPatrol] Trying - AbuseEdit - ${JSON.stringify(outputs)}`);
                mwApi.post({
                    action: 'query',
                    prop: 'revisions|info',
                    titles: outputs.page
                }).done((data) => {
                    const p = data.query.pages;
                    outputs.now = !p.length || p['-1'] ? new Date().toISOString() : null;
                    mwApi.postWithToken('csrf', {
                        action: 'edit',
                        starttimestamp: outputs.now || p[0].touched,
                        basetimestamp: outputs.now || Object.values(p[0].revisions)[0].timestamp,
                        text: outputs.text,
                        title: outputs.page,
                        summary: outputs.summary
                    }).done(() => {
                        console.debug(`[QuickPatrol] Succeeded - AbuseEdit - ${JSON.stringify(outputs)}`);
                        me.removeAttr('disabled').remove();
                        toast(l.n_1, notice, 'success');
                    }).fail((msg, log) => {
                        outputs.error = msg;
                        console.warn(`[QuickPatrol] Failed - AbuseEdit - ${JSON.stringify(outputs)}`);
                        me.removeAttr('disabled');
                        mwApi.post({
                            action: 'parse',
                            page: `MediaWiki:${msg}`,
                            prop: 'text'
                        }).done((data) => Swal.fire({
                            title: l.n_2,
                            html: data.parse.text['*'].replace(/\$1/g, log.error.abusefilter.description),
                            icon: 'error',
                            customClass: 'quickpatrol-output',
                            backdrop: '#0002',
                            showConfirmButton: true,
                            confirmButtonText: l.r_confirm
                        })).fail(() => toast(l.n_2, mw.msg(msg)));
                    });
                }).fail((err) => {
                    outputs.error = err;
                    console.warn(`[QuickPatrol] Failed - AbuseEdit - ${JSON.stringify(outputs)}`);
                    me.removeAttr('disabled');
                    toast(l.n_2, err);
                });
            };
            dialog('prompt', l.s, abuse_edit);
        });
    }

    function g_cr() {
        for (let e of $('.mw-rollback-link a:not(.quickpatrol-rollback), button[data-mw-revid]')) {
            const me = $(e);
            me.click((event) => {
                event.preventDefault();
                me.parent().find('.quickpatrol-rollback, .edit-rollback').addClass('quickpatrol-click');
                dialog('confirm', l.g_cr_1, () => rollback(me));
            });
            status.gen_rollback(me);
        }
    }

    function g_er(p) {
        let el = '.mw-rollback-link a, button[data-mw-revid]';
        if (p) {
            if (p === 2) g_cr();
            for (let e of $(el)) {
                const me = $(e);
                if (me.find('+.edit-rollback').length) continue;
                me.after(`<span class="edit-rollback" href="${me.attr('href')}" title="${l.g_er_2}"></span>`);
            }
            el = '.edit-rollback';
        }
        el = el.replace(/(?=,)|$/g, ':not(.quickpatrol-rollback)');
        for (let e of $(el)) {
            const me = $(e);
            me.click((event) => {
                event.preventDefault();
                me.parent().find('.quickpatrol-rollback, .edit-rollback').addClass('quickpatrol-click');
                dialog('prompt', l.s, (summary) => rollback(me, summary));
            });
            status.gen_rollback(me);
        }
    }

    function rollback_util() {
        if (GM_getValue('qp_rollback_mode') === 'd' || !load) return;
        load[0](load[1]);
    }

    function dialog(type, msg, f) {
        Swal.fire(Object.assign({
            width: '24em',
            text: msg,
            draggable: true,
            backdrop: '#0002',
            reverseButtons: true,
            showConfirmButton: true,
            confirmButtonText: l.r_confirm,
            showCancelButton: true,
            cancelButtonText: l.p_3,
        }, type === 'prompt' ? { input: 'text' } : {})).then((data) => {
            $('.quickpatrol-click').removeClass('quickpatrol-click');
            if (data.isConfirmed) f(data.value);
        });
    }

    function toast(title, msg, icon = 'error') {
        Swal.fire({
            toast: true,
            position: 'top-end',
            title: title,
            text: msg,
            icon: icon,
            timer: 5000,
            showConfirmButton: false,
            timerProgressBar: true
        });
    }

    function init() {
        const default_settings = {
            qp_patrol: true,
            qp_patrol_confirm: false,
            qp_rollback: true,
            qp_rollback_mode: 's',
            qp_rollback_show: true,
            qp_abuse_edit: true,
            qp_jump_blank: false,
            qp_max_tries: 10,
            qp_local_summary: false
        };
        Object.entries(default_settings).forEach(([k, v]) => {
            if (GM_getValue(k) === void 0) GM_setValue(k, v);
        });
        if (fails >= GM_getValue('qp_max_tries')) return;
        _asyncToGenerator(function* () {
            try {
                yield mw.loader.using(['jquery', 'mediawiki.base', 'mediawiki.api', 'mediawiki.user']);
                mwApi = new mw.Api();
                mwLang = mw.config.get('wgUserLanguage');
                if (['zh-cn', 'zh-hans', 'zh-hans-cn', 'zh'].includes(mwLang)) mwLang = 'zh-hans';
                else if (mwLang.startsWith('zh-')) mwLang = 'zh-hant';
                if (!Object.keys(l10n).includes(mwLang)) mwLang = 'en';
                l = l10n[mwLang];

                if (load) return;
                load = true;
                GM_registerMenuCommand(l.n_3, () => {
                    Swal.fire({
                        title: l.n_3,
                        html: `<div class="quickpatrol-settings">
                             <label>${l.qp_patrol}<input type="checkbox" id="quickpatrol-option-patrol" ${GM_getValue('qp_patrol') ? 'checked' : ''}></label>
                             <label>${l.qp_patrol_confirm}<input type="checkbox" id="quickpatrol-option-patrol-confirm" ${GM_getValue('qp_patrol_confirm') ? 'checked' : ''}></label>
                             <label>${l.qp_rollback}<input type="checkbox" id="quickpatrol-option-rollback" ${GM_getValue('qp_rollback') ? 'checked' : ''}></label>
                             <label>${l.qp_rollback_mode}<select id="quickpatrol-option-rollback-mode">
                             <option value="s" ${GM_getValue('qp_rollback_mode') === 's' ? 'selected' : ''}>${l.r_summary}</option>
                             <option value="c" ${GM_getValue('qp_rollback_mode') === 'c' ? 'selected' : ''}>${l.r_confirm}</option>
                             <option value="d" ${GM_getValue('qp_rollback_mode') === 'd' ? 'selected' : ''}>${l.r_default}</option>
                             <option value="c+s" ${GM_getValue('qp_rollback_mode') === 'c+s' ? 'selected' : ''}>${l.r_confirm}+${l.r_summary}</option>
                             <option value="d+s" ${GM_getValue('qp_rollback_mode') === 'd+s' ? 'selected' : ''}>${l.r_default}+${l.r_summary}</option>
                             <option value="b+c" ${GM_getValue('qp_rollback_mode') === 'b+c' ? 'selected' : ''}>${l.r_markbot}|${l.r_confirm}</option>
                             </select></label> ${mw.user.options.get('showrollbackconfirmation') ? `
                             <label>${l.qp_rollback_show}<input type="checkbox" id="quickpatrol-option-rollback-show" disabled></label><span style="color: #358cc6; font-size: 12px;">${l.qp_rollback_show_tooltip}</span>
                             `.trim() : `
                             <label>${l.qp_rollback_show}<input type="checkbox" id="quickpatrol-option-rollback-show" ${GM_getValue('qp_rollback_show') ? 'checked' : ''}></label>`.trim()}
                             <label>${l.qp_abuse_edit}<input type="checkbox" id="quickpatrol-option-abuse-edit" ${GM_getValue('qp_abuse_edit') ? 'checked' : ''}></label>
                             <label>${l.qp_jump_blank}<input type="checkbox" id="quickpatrol-option-jump-blank" ${GM_getValue('qp_jump_blank') ? 'checked' : ''}></label>
                             <label>${l.qp_max_tries}<input type="range" min="1" max="20" id="quickpatrol-option-max-tries" value="${GM_getValue('qp_max_tries')}"><span>${GM_getValue('qp_max_tries')}</span></label>
                             <label>${l.qp_local_summary}<input type="checkbox" id="quickpatrol-option-local-summary" ${GM_getValue('qp_local_summary') ? 'checked' : ''} ${mwLang === 'en' ? 'disabled' : ''}></label>
                        </div>`,
                        backdrop: '#0002',
                        reverseButtons: true,
                        showConfirmButton: true,
                        confirmButtonText: l.r_confirm,
                        showCancelButton: true,
                        cancelButtonText: l.p_3,
                    }).then((res) => {
                        if (!res.isConfirmed) return;
                        Object.entries(default_settings).forEach(([k, v]) => {
                            const e = $('#quickpatrol-option-' + k.slice(3).replace(/_/g, '-'));
                            GM_setValue(k, typeof v === 'boolean' ? e.is(':checked') : e.val());
                        });
                        location.reload();
                    });
                    $('#quickpatrol-option-patrol').change(function () {
                        $('#quickpatrol-option-patrol-confirm').attr('disabled', !$(this).is(':checked'));
                    });
                    $('#quickpatrol-option-rollback').change(function () {
                        $('#quickpatrol-option-rollback-mode').attr('disabled', !$(this).is(':checked'));
                        if (!mw.user.options.get('showrollbackconfirmation')) $('#quickpatrol-option-rollback-show').attr('disabled', !$(this).is(':checked'));
                        $('#quickpatrol-option-local-summary').attr('disabled', !$(this).is(':checked') && !$('#quickpatrol-option-abuse-edit').is(':checked'));
                    });
                    $('#quickpatrol-option-max-tries').on('input', function () {
                        $(this).find('+span').text($(this).val());
                    });
                    $('#quickpatrol-option-abuse-edit').change(function () {
                        $('#quickpatrol-option-local-summary').attr('disabled', !$(this).is(':checked') && !$('#quickpatrol-option-rollback').is(':checked'));
                    });
                });
                $E('.mw-changeslist', () => GM_registerMenuCommand(l.n_4, () => dialog('confirm', l.n_4, () => $('[data-mw-logid] .unpatrolled').trigger('click'))));
                const oe = ['.mw-changeslist', '#mw-diff-ntitle1 strong', '#mw-diff-otitle1 strong', '.mw-contributions-list li:nth-child(20)', '.mw-contributions-list li:last-child'];
                const ob_IPE = new MutationObserver(with_IPE);
                const ob = new MutationObserver((ml) => {
                    ml.forEach((m) => {
                        const t = $(m.target);
                        if (t.attr('class').includes('revisionpatrol-') || t.hasClass('mw-changeslist')) return main();
                        $E(m.addedNodes, (e) => {
                            $('.quick-diff .quickpatrol-rollback:not(.quickpatrol-click)').remove();
                            if (e.hasClass('quick-diff')) {
                                ob_IPE.observe(e.find('.ipe-progress')[0], { attributes: true });
                            }
                            if (e.hasClass('ipe-modal-modal')) {
                                // InPageEdit NEXT
                                ob_IPE.observe(e.find('.ipe-modal-modal__title')[0], { childList: true });
                            }
                        });
                    });
                });
                ob.observe(document.body, { childList: true });
                for (let x of oe) $E(x, (e) => ob.observe(e[0], { childList: true, attributes: true }));
                status.load('gh/teaSummer/QuickPatrol_v2@main/styles');
                status.load('npm/sweetalert2/dist/sweetalert2.min', true);
                status.load(`gh/teaSummer/QuickPatrol_v2@minecraft-wiki/patrol/${mwLang}/Gadget-revisionPatrol`, true);
                load = ((m) => m === 's' ? [g_er] : m === 'b+c' || m === 'c' ? [g_cr] : m === 'd+s' ? [g_er, 1] : m === 'c+s' ? [g_er, 2] : null)(GM_getValue('qp_rollback_mode'));
                if (load.length === 2) status.load(`gh/teaSummer/QuickPatrol_v2@minecraft-wiki/rollback/${mwLang}/Gadget-editableRollback`);
            } catch (e) {
                fails = fails + 1;
                if (fails < GM_getValue('qp_max_tries')) console.warn(`[QuickPatrol] Failed to call MediaWiki. Retrying... (${fails}/${GM_getValue('qp_max_tries')})`);
                else console.error(`[QuickPatrol] Failed to call MediaWiki. (${fails}/${GM_getValue('qp_max_tries')})`);
                return new Promise(() => setTimeout(init, 2000));
            }

            console.log('[QuickPatrol] Checking rights...');
            rights = (yield mwApi.post({
                action: 'query',
                meta: 'userinfo',
                uiprop: 'rights'
            })).query.userinfo.rights;
            Object.assign(status, {
                patrol: rights.includes('patrol') && GM_getValue('qp_patrol'),
                rollback: rights.includes('rollback') && GM_getValue('qp_rollback'),
                abuselog: rights.includes('abusefilter-log-detail') && GM_getValue('qp_abuse_edit'),
                gen_rollback: (me) => me.addClass(`quickpatrol-rollback ${mwLang.startsWith('zh-') ? 'consolas' : ''}`).removeAttr('data-mw')
            });

            if (GM_getValue('qp_jump_blank')) $('.interlanguage-link-target, .vector-menu-content .selected a, #p-personal a, #mw-panel a:not([href^="#"]), a:not([href^="#"],[href$="/doc"],[href*="section="],:has(span))').attr('target', '_blank');
            main();
        })();
    }

    function with_IPE() {
        const diff = $('.quick-diff');
        if (!diff) return;
        for (let e of diff.find('.mw-diff-title--title')) {
            // InPageEdit NEXT
            $(e).contents().wrap('<span/>');
            const [a, b] = $(e).find('span');
            $(a).text(`${$(a).text()} (`).contents().unwrap();
            $(b).text($(b).text().slice(2, -1)).after(')').addClass('diff-version');
        }
        for (let e of diff.find('.diff-version')) {
            const me = $(e);
            if (me.attr('title')) return;
            me.attr('data-quickpatrol', me.text().replace(/[^0-9]/g, ''));
            if (!status.patrol || me.hasClass('diff-hidden-history')) continue;
            me.click(() => {
                me.addClass('patrolling').attr('title', l.ing);
                patrol(me, me.data('quickpatrol'));
            });
        }
        diff.find('.quickpatrol-rollback:not(.quickpatrol-click)').remove();
        const next = diff.find('.nextVersion[style], .mw-diff-title--navigation>td+td>i');
        // InPageEdit NEXT <<< .mw-diff-title--navigation>td+td>i
        const revid = $E(diff.find('.diff-version')[1], (e) => e.data('quickpatrol'));
        if (!next.length || !revid) return;
        $E(`[data-mw-revid=${revid}] .quickpatrol-rollback`, (e) => {
            diff.find('.btn-secondary').before(`<button class="btn btn-secondary" type="button" data-mw-revid="${revid}">${e.text()}</button>`);
            if ($E('.mw-diff-title--navigation')) diff.find('[data-mw-revid]').addClass('ipe-modal-btn');
            rollback_util();
        });
    }

    function clean_rcfilters(revid, start = false) {
        const that = $(`[data-mw-revid=${revid}]`);
        const clear = (e) => {
            $E(`.mw-rcfilters-ui-filterTagItemWidget:contains(${l.t_un}) .mw-rcfilters-ui-tagItemWidget-highlight`, (c) => {
                e.removeClass(`mw-rcfilters-highlight-color-${c.data('color')}`);
            });
            e.removeClass('mw-changeslist-reviewstatus-unpatrolled');
            if (e.attr('title')) e.attr('title', e.attr('title').replace(new RegExp(`${l.t_un}(, |、\u200B)|(, |、\u200B)?${l.t_un}`, 'g'), ''));
            if (e.attr('title') === l.hl) e.removeClass('mw-rcfilters-ui-changesListWrapperWidget-enhanced-grey').removeAttr('title');
            e.find('.unpatrolled').text('✔').addClass('quickpatrol').removeClass('unpatrolled').attr('title', l.done).off('click');
        };
        const top = $E(that.parent().find('.mw-rcfilters-ui-highlights-enhanced-toplevel'));
        if (!top) return clear(that);
        if (start) {
            for (let u of top.siblings().find('.unpatrolled')) {
                const e = $(u);
                if (e.data('mw-revid') <= revid) break;
                clear(e);
            }
        } else clear(that);
        if (!$E(top.siblings().find('.unpatrolled, .patrolling'))) clear(top);
    }

    function main() {
        console.debug('[QuickPatrol] Triggered main method.');
        if (!rights && (fails >= GM_getValue('qp_max_tries') || !fails)) return init();
        if (status.abuselog) helper();
        if (status.rollback) {
            reverted = false;
            rollback_util();
        }

        if (!status.patrol) return;
        for (let e of $(status.np)) {
            const that = $(e);
            that.find('.revisionpatrol-icon-unpatrolled').after(`<span class="unpatrolled quickpatrol-icon-unpatrolled" title="${l.g_un}">!</span>`);
            that.find('.revisionpatrol-icon-unpatrolled').remove();
            that.find('.unpatrolled').addClass('quickpatrol').click(_asyncToGenerator(function* () {
                const me = $(this);
                if (me.text() !== '!') return;
                me.addClass('patrolling').removeClass('unpatrolled');
                me.text('#').attr('title', l.ing);
                let revid = that.data('mw-revid');
                if (that.data('mw-logid')) {
                    yield mwApi.post({
                        action: 'query',
                        list: 'logevents',
                        leprop: 'ids',
                        letitle: that.find('td.mw-enhanced-rc-nested').data('target-page'),
                        letype: that.data('mw-logaction').split('/')[0],
                        lelimit: 'max'
                    }).done((data) => {
                        const q = data.query.logevents.find((i) => i.logid === that.data('mw-logid'));
                        if (q) revid = q.revid;
                    });
                }
                if (!revid) return me.text('!').addClass('unpatrolled').removeClass('patrolling').attr('title', l.un);
                that.attr('data-mw-revid', revid);
                patrol(me, revid, void 0, that.data('mw-logid'));
            }));
        }
    }

    function patrol(me, revid, rcid, logid, isConfirmed) {
        const target_page = $(`[data-mw-revid=${revid}] [data-target-page]`).data('target-page');
        const outputs = {
            revid: revid,
            rcid: rcid,
            logid: logid,
            ts: $(`[data-mw-revid=${revid}]`).data('mw-ts'),
            page: target_page || mw.config.get('wgPageName'),
            online: navigator.onLine,
            platform: navigator.platform
        };
        if (!isConfirmed && GM_getValue('qp_patrol_confirm')) {
            me.text('?').addClass('quickpatrol-confirm unpatrolled').removeClass('patrolling').attr('title', l.qp_patrol_confirm);
            me.click(() => {
                me.addClass('patrolling').attr('title', l.ing);
                patrol(me, revid, rcid, logid, true);
            });
            return;
        }
        const params = { action: 'patrol' };
        if (rcid) params.rcid = rcid;
        else params.revid = revid;
        console.debug(`[QuickPatrol] Trying - Patrol - ${JSON.stringify(outputs)}`);
        mwApi.postWithToken('patrol', params).done(() => {
            console.debug(`[QuickPatrol] Succeeded - Patrol - ${JSON.stringify(outputs)}`);
            me.text('✔').addClass('quickpatrol').removeClass('patrolling unpatrolled').attr('title', l.done).off('click');
            if (revid) clean_rcfilters(revid);
        }).fail((err) => {
            const fail = () => {
                outputs.error = err;
                console.warn(`[QuickPatrol] Failed - Patrol - ${JSON.stringify(outputs)}`);
                toast(l.p_1, l.p_2 + l.c + JSON.stringify(outputs));
                me.text('!').addClass('unpatrolled').removeClass('patrolling').attr('title', l.un);
                if (me.hasClass('quickpatrol-icon-unpatrolled')) me.attr('title', l.g_un);
            };
            if (!target_page) return fail();
            mwApi.post({
                action: 'query',
                list: 'recentchanges',
                rcprop: 'ids',
                rctitle: outputs.page,
                rclimit: 'max'
            }).done((data) => {
                const q = data.query.recentchanges.find((i) => i.revid === revid);
                if (!q) return fail();
                patrol(me, revid, q.rcid, void 0, true);
            }).fail(fail);
        });
    }

    function rollback(me, summary) {
        const href = me.attr('href');
        if (GM_getValue('qp_rollback_show') && !mw.user.options.get('showrollbackconfirmation')) {
            location.href = href;
            return;
        }
        const temp = { text: me.text(), title: me.attr('title') };
        const search = new URL(`THIS.IS.A.FOOL:${href}`).searchParams;
        let _sum = summary || '';
        if (_sum === '-') _sum = '';
        me.text(l.r_1).addClass('reverting').attr('data-href', href).removeAttr('title href').removeClass('quickpatrol-rollback');
        if (GM_getValue('qp_local_summary')) {
            Object.entries(l10n).forEach(([k, v]) => {
                for (let i = 0; i < v.map.length; i++) _sum = _sum.replace(new RegExp(v.map[i], 'gi'), l.map[i]);
            });
        }
        const outputs = {
            page: $(`[data-target-page]:has(.reverting[data-href="${href}"])`).data('target-page') || mw.config.get('wgPageName'),
            user: search.get('from'),
            summary: _sum || void 0,
            markbot: GM_getValue('qp_rollback_mode') === 'b+c' || void 0,
            refresh: reverted,
            online: navigator.onLine,
            platform: navigator.platform
        };
        const default_summary = mw.format(l.g_er_1, outputs.user);
        if (summary !== '-') _sum = _sum ? default_summary + l.c + _sum : default_summary;
        const params = {
            action: 'rollback',
            title: outputs.page,
            user: outputs.user,
            summary: _sum,
            markbot: outputs.markbot,
            token: search.get('token')
        };
        let action = (params) => mwApi.post(params);
        if (reverted) {
            delete params.token;
            action = (params) => mwApi.postWithToken('rollback', params);
        }
        reverted = true;
        console.debug(`[QuickPatrol] Trying - Rollback - ${JSON.stringify(outputs)}`);
        action(params).done((data) => {
            outputs.revids = {
                new: data.rollback.revid,
                old: data.rollback.old_revid,
                last: data.rollback.last_revid
            };
            console.debug(`[QuickPatrol] Succeeded - Rollback - ${JSON.stringify(outputs)}`);
            me.parent().text(l.r_2);
            clean_rcfilters(outputs.revids.last, true);
        }).fail((err) => {
            outputs.error = err;
            console.warn(`[QuickPatrol] Failed - Rollback - ${JSON.stringify(outputs)}`);
            me.text(temp.text).attr('title', temp.title).attr('href', href).addClass('quickpatrol-rollback').removeClass('reverting');
            toast(l.r_3, l.p_2 + l.c + JSON.stringify(outputs));
        });
    }

    init();
})();