User:Kurgenera/superTW.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ 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);