跳转到内容

User:Kurgenera/superTW.js

维基百科,自由的百科全书
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
// I WANT TO NUKE EVERYONE
// 该脚本用于在早期条目精建立的大量不合格条目挂维护模板,配合[[User:Kurgenera/coin.js]]和[[User:Kurgenera/cat.js]]效果更佳。
// 由于不合格条目过多,脚本使用者的编辑数会疯狂上涨,[[Wikipedia:刷编辑数]]了属于是……
// 注意!使用该插件可能会受人注意,建议征得共识后再大规模使用。
// 如该脚本出现问题请联系[[User talk:Kurgenera]]
// 该脚本目前存在已知的引注模板读取bug,且暂未解决。
(function($, mw) {
    if (typeof mw.config.get('wgUserName') === 'undefined') return;

    const UNREFERENCED_TEMPLATE = '{{Unreferenced}}';
    const ONESOURCE_TEMPLATE = '{{Onesource}}';
    const NOFOOTNOTES_TEMPLATE = '{{No footnotes}}';
    
    const MAINTENANCE_TEMPLATES_TO_CHECK = [
        '{{Refimprove', '{{Unreferenced', '{{Onesource', '{{No footnotes',
        '{{Rough translation', '{{Wikify', '{{Stub', '{{Cleanup', 
        '{{Prose', '{{Citation style', '{{Original research',
        '{{消歧', '{{Dab', '{{分歧', '{{Disambig', '{{Disambiguous', 
        '{{Aimai', '{{Disambiguation page', '{{Disambiguation',
        '{{dab','{{disambig','{{disambiguous','{{aimai','{{disambiguation',
        '{{MolFormIndex','{{MolFormDisambig','{{Molecular','{{Isomerdab',
        '重定向'
    ];

    const api = new mw.Api();
    let isPaused = false;
    let isRunning = false;

    // 注入 CSS 样式
    var modalCSS = 
        '#efficient-nuke-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 99999; }' +
        '#efficient-nuke-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; padding: 25px; border: 1px solid #ccc; border-radius: 5px; z-index: 100000; width: 450px; max-width: 90%; box-shadow: 0 0 15px rgba(0,0,0,0.5); }' +
        '#efficient-nuke-log { margin-top: 15px; border: 1px solid #eee; padding: 10px; max-height: 200px; overflow-y: auto; font-size: 12px; list-style: none; background: #fdfdfd; }' +
        '#efficient-nuke-log li { margin-bottom: 3px; border-bottom: 1px solid #f0f0f0; }' +
        '#nuke-progress-wrapper { height: 10px; background: #eee; border-radius: 5px; margin: 15px 0 5px 0; overflow: hidden; }' +
        '#nuke-progress-bar { height: 100%; background: #007bff; width: 0%; transition: width 0.3s; }' +
        '#nuke-counter-bar { display: flex; justify-content: space-between; font-size: 12px; color: #555; margin-bottom: 10px; padding: 5px; background: #f9f9f9; border-radius: 3px; }' +
        '.nuke-counter-item { text-align: center; flex: 1; }' +
        '.nuke-counter-val { font-weight: bold; color: #007bff; display: block; font-size: 14px; }' +
        '#efficient-nuke-modal .nuke-buttons { display: flex; gap: 10px; margin-top: 10px; }' +
        '#efficient-nuke-modal button { flex: 1; padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }' +
        '#efficient-nuke-modal button#nuke-pause-btn { background-color: #dc3545; display: none; }' +
        '#efficient-nuke-modal textarea { width: 95%; min-height: 100px; padding: 5px; border: 1px solid #ccc; font-size: 14px; }';
    mw.util.addCSS(modalCSS);

    function getISOTimestamp() {
        return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
    }

    function formatNukeTime(seconds) {
        if (seconds <= 0) return '0s';
        if (seconds >= 60) {
            return Math.floor(seconds / 60) + ' 分钟';
        }
        return '30s';
    }

    // 更新暂停按钮UI的辅助函数
    function updatePauseButtonUI($btn) {
        if (isPaused) {
            $btn.text('继续').css('background-color', '#ffc107').css('color', '#212529');
        } else {
            $btn.text('暂停').css('background-color', '#dc3545').css('color', '#fff');
        }
    }

    $(document).ready(function() {
        mw.util.addPortletLink(
            'p-cactions',
            '#',
            '⚡ 高效 NUKE',
            'ca-efficient-nuke-tool',
            '专用于批量添加 Unreferenced/Onesource/No footnotes 的工具 (多重检查)',
            null,
            '#ca-batch-maint'
        );

        $('#ca-efficient-nuke-tool a').on('click', function(e) {
            e.preventDefault();
            if ($('#efficient-nuke-overlay').length) return;

            isPaused = false;
            isRunning = false;

            var $overlay = $('<div>').attr('id', 'efficient-nuke-overlay');
            var $modal = $('<div>').attr('id', 'efficient-nuke-modal');
            
            $modal.html('<h2>⚡ 高效 NUKE 模式</h2>');
            $modal.append($('<p>').html('你们这群叛匪,给我老实呆着,看我派坦克来把你们一个个都送上天!'));
            
            var $pageListTextarea = $('<textarea>').attr({
                id: 'nuke-page-list-input',
                placeholder: '在此输入页面名称,每行一个。'
            });
            $modal.append($pageListTextarea);

            var $progressWrapper = $('<div id="nuke-progress-wrapper"><div id="nuke-progress-bar"></div></div>');
            var $counterBar = $(
                '<div id="nuke-counter-bar">' +
                    '<div class="nuke-counter-item"><span class="nuke-counter-val" id="cnt-total">0</span>总计</div>' +
                    '<div class="nuke-counter-item"><span class="nuke-counter-val" id="cnt-done" style="color:green">0</span>执行</div>' +
                    '<div class="nuke-counter-item"><span class="nuke-counter-val" id="cnt-skip" style="color:orange">0</span>跳过</div>' +
                    '<div class="nuke-counter-item"><span class="nuke-counter-val" id="cnt-fail" style="color:red">0</span>失败</div>' +
                    '<div class="nuke-counter-item"><span class="nuke-counter-val" id="cnt-time">0s</span>预计</div>' +
                '</div>'
            );
            
            $modal.append($progressWrapper);
            $modal.append($counterBar);
            
            var $btnContainer = $('<div class="nuke-buttons">');
            var $startButton = $('<button id="nuke-start-btn">').text('开始批量 NUKE');
            var $pauseButton = $('<button id="nuke-pause-btn">').text('暂停');
            
            $btnContainer.append($startButton, $pauseButton);
            $modal.append($btnContainer);
            $('body').append($overlay, $modal);

            $overlay.on('click', function(event) {
                if (event.target.id === 'efficient-nuke-overlay' && !isRunning) {
                    $overlay.remove(); $modal.remove();
                }
            });

            $pauseButton.on('click', function() {
                isPaused = !isPaused;
                updatePauseButtonUI($pauseButton);
            });

            $startButton.on('click', async function() {
                const listInput = $('#nuke-page-list-input').val();
                if (!listInput) {
                    alert('请提供至少一个页面名称。');
                    return;
                }
                const pageNames = listInput.split('\n').map(s => s.trim()).filter(p => p.length > 0);
                const total = pageNames.length;
                
                isRunning = true;
                $startButton.hide();
                $pauseButton.show();
                $pageListTextarea.prop('disabled', true);

                var $statusLog = $('#efficient-nuke-log');
                if (!$statusLog.length) {
                    $statusLog = $('<ul>').attr('id', 'efficient-nuke-log');
                    $modal.append($statusLog);
                }

                let completed = 0;
                let statDone = 0;
                let statSkip = 0;
                let statFail = 0;

                $('#cnt-total').text(total);
                const avgCycleTime = 5; 
                $('#cnt-time').text(formatNukeTime(total * avgCycleTime));

                for (const pageName of pageNames) {
                    while (isPaused) {
                        await new Promise(resolve => setTimeout(resolve, 500));
                    }

                    const result = await processNukeArticle(pageName, $statusLog);
                    completed++;
                    
                    if (result === 'done') {
                        statDone++;
                        // 只有真正执行了编辑操作,才需要休眠散热
                        if (completed < total) {
                            await new Promise(resolve => setTimeout(resolve, 3500));
                        }
                    } else if (result === 'skip') {
                        statSkip++;
                    } else if (result === 'fail') {
                        statFail++;
                        // 编辑出错,立即触发自动暂停,不强制休眠直接等待人工介入
                        isPaused = true;
                        updatePauseButtonUI($pauseButton);
                    }

                    $('#cnt-done').text(statDone);
                    $('#cnt-skip').text(statSkip);
                    $('#cnt-fail').text(statFail);
                    
                    // 剩余时间估算:剩余条目 * 预估周期
                    const remaining = total - completed;
                    $('#cnt-time').text(formatNukeTime(remaining * avgCycleTime));
                    $('#nuke-progress-bar').css('width', (completed / total * 100) + '%');
                }

                isRunning = false;
                alert('高效 NUKE 模式:所有页面处理完成!');
                $modal.find('h2').text('✅ 高效 NUKE 模式完成');
                $pauseButton.hide();
                $startButton.show().prop('disabled', true).text('✅ 完成').css('background-color', '#28a745');
                $('#cnt-time').text('0s');
            });
        });
    });

    async function processNukeArticle(pageName, $statusLog) {
        var $li = $('<li>').text('正在检查 ' + pageName + '...');
        $statusLog.append($li);
        $statusLog.scrollTop($statusLog[0].scrollHeight);

        try {
            const data = await api.get({
                action: 'query',
                prop: 'revisions',
                rvprop: 'content',
                titles: pageName,
                rvlimit: 1
            });

            const page = data.query.pages[Object.keys(data.query.pages)[0]];
            
            if (page.missing === "") { 
                 $li.css('color', 'gray').text('跳过 ' + pageName + ':条目不存在。');
                 return 'skip';
            }
            const articleContent = page.revisions ? page.revisions[0]['*'] : '';

            if (articleContent.match(/#REDIRECT/i) || articleContent.match(/#重定向/i)) {
                $li.css('color', 'gray').text('跳过 ' + pageName + ':重定向页。');
                return 'skip';
            }

            const hasMaintenanceTemplate = MAINTENANCE_TEMPLATES_TO_CHECK.some(template => articleContent.includes(template));
            if (hasMaintenanceTemplate) {
                $li.css('color', 'orange').text('跳过 ' + pageName + ':已存在维护模板。');
                return 'skip';
            }

            const HARVARD_TEMPLATES = ['{{sfn', '{{harv'];
            const hasHarvardTemplate = HARVARD_TEMPLATES.some(template => articleContent.includes(template));
            if (hasHarvardTemplate) {
                $li.css('color', 'gray').text('跳过 ' + pageName + ':发现哈佛引用模板 (sfn/harv)。');
                return 'skip';
            }

            const refTags = articleContent.match(/<ref(?:\s[^>]*?)?>/gi);
            const refCount = refTags ? refTags.length : 0;
            const CITE_TEMPLATES = ['{{Cite','{{Wayback'];
            const hasCiteTemplate = CITE_TEMPLATES.some(template => articleContent.includes(template));

            let templateToAdd = '';
            let summaryText = '';

            if (refCount === 0) {
                if (hasCiteTemplate) {
                    templateToAdd = NOFOOTNOTES_TEMPLATE;
                    summaryText = '标记有参考文献但缺乏内文脚注的条目';
                } else {
                    templateToAdd = UNREFERENCED_TEMPLATE;
                    summaryText = '标记严重缺乏来源的条目';
                }
            } else if (refCount === 1) {
                templateToAdd = ONESOURCE_TEMPLATE;
                summaryText = '标记单一来源的条目';
            } else {
                $li.css('color', 'gray').text(`跳过 ${pageName}:包含 ${refCount} 个 <ref> 标签。`);
                return 'skip';
            }

            $li.text(`执行 NUKE: 标记 ${pageName} (${templateToAdd.replace(/[{}]/g, '')})...`);

            var dynamicTime = getISOTimestamp();
            var prependText = templateToAdd.replace(/\}\}$/, '|time=' + dynamicTime + '}}') + '\n';
            const fullSummary = summaryText + ' (使用[[User:Kurgenera/superTW.js]]添加维护模板)';
            
            try {
                await api.postWithToken('csrf', {
                    action: 'edit',
                    title: pageName,
                    prependtext: prependText,
                    summary: fullSummary,
                    nocreate: true,
                    minor: true 
                });
                $li.css('color', 'green').text(`成功 NUKE ${pageName},标记为 ${templateToAdd.replace(/[{}]/g, '')}`);
                return 'done';
            } catch (error) {
                const checkData = await api.get({
                    action: 'query',
                    prop: 'revisions',
                    titles: pageName,
                    rvprop: 'user',
                    rvlimit: 1,
                    formatversion: 2,
                    cache: false
                });
                const lastUser = checkData.query.pages[0].revisions ? checkData.query.pages[0].revisions[0].user : '';
                
                if (lastUser === mw.config.get('wgUserName')) {
                    $li.css('color', 'green').text(`🎉 ${pageName} 实际成功标记 (通过回查确认)`);
                    return 'done';
                } else {
                    $li.css('color', 'red').text(`失败 NUKE ${pageName}: 提交错误,系统已自动暂停`);
                    return 'fail';
                }
            }
        } catch (e) {
            $li.css('color', 'red').text(`❌ ${pageName} 系统错误,已自动暂停`);
            return 'fail';
        }
    }
})(jQuery, mediaWiki);