// ==UserScript==
// @name Fandom批量删除工具
// @author PandaFiredoge
// @version 1.0.1
// @description 一个用于Fandom/MediaWiki站点的批量删除页面工具
// @match *://*.fandom.com/*/wiki/Special:*
// @match *://*.wikia.com/*/wiki/Special:*
// @grant none
// @license GPL-3.0-or-later
// @namespace https://greasyfork.org/users/1413764
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// 确保mw API可用
if (typeof mw === 'undefined' || typeof mw.Api === 'undefined') {
console.error('MediaWiki API不可用,工具无法加载');
return;
}
// 只在特殊页面运行
if (!mw.config.get('wgCanonicalSpecialPageName')) return;
// 确保用户有管理员权限
if (!mw.config.get('wgUserGroups') || !mw.config.get('wgUserGroups').includes('sysop')) {
console.log('用户没有管理员权限,批量删除工具不会加载');
return;
}
// 创建工具界面
function createInterface() {
const container = document.createElement('div');
container.id = 'bulk-delete-tool';
container.style.cssText = 'padding: 15px; margin: 15px 0; border: 1px solid #ccc; border-radius: 4px; background-color: #f9f9f9;';
container.innerHTML = `
批量删除页面工具
输入要删除的页面标题(每行一个):
`;
// 将工具添加到页面
const mainContent = document.querySelector('#WikiaMainContent, .WikiaMainContent, #mw-content-text, .mw-body-content');
if (mainContent) {
mainContent.prepend(container);
} else {
document.body.prepend(container);
}
// 添加事件监听器
document.getElementById('preview-button').addEventListener('click', previewPages);
document.getElementById('delete-button').addEventListener('click', startDeletion);
document.getElementById('load-category-button').addEventListener('click', showCategoryModal);
document.getElementById('load-prefix-button').addEventListener('click', showPrefixModal);
document.getElementById('modal-close').addEventListener('click', closeModal);
}
// 显示消息
function showMessage(message, type) {
const messageElement = document.getElementById('tool-message');
messageElement.textContent = message;
messageElement.style.display = 'block';
if (type === 'error') {
messageElement.style.backgroundColor = '#f2dede';
messageElement.style.borderColor = '#ebccd1';
messageElement.style.color = '#a94442';
} else if (type === 'success') {
messageElement.style.backgroundColor = '#dff0d8';
messageElement.style.borderColor = '#d6e9c6';
messageElement.style.color = '#3c763d';
} else {
messageElement.style.backgroundColor = '#d9edf7';
messageElement.style.borderColor = '#bce8f1';
messageElement.style.color = '#31708f';
}
// 5秒后自动隐藏
setTimeout(() => {
messageElement.style.display = 'none';
}, 5000);
}
// 显示模态框
function showModal(title, content) {
document.getElementById('modal-container').style.display = 'block';
document.getElementById('modal-body').innerHTML = `
${title}
${content}
`;
}
// 关闭模态框
function closeModal() {
document.getElementById('modal-container').style.display = 'none';
}
// 显示分类模态框
function showCategoryModal() {
const content = `
`;
showModal('从分类加载页面', content);
document.getElementById('load-category-pages-button').addEventListener('click', function() {
const categoryName = document.getElementById('category-name').value.trim();
const depth = parseInt(document.getElementById('category-depth').value);
if (!categoryName) {
showMessage('请输入有效的分类名称', 'error');
return;
}
document.getElementById('category-results').innerHTML = '正在加载分类页面,请稍候...
';
loadPagesFromCategory(categoryName, depth);
});
}
// 显示前缀模态框
function showPrefixModal() {
// 获取命名空间列表
const namespaces = [
{id: '0', name: '(主命名空间)'},
{id: '1', name: 'Talk'},
{id: '2', name: 'User'},
{id: '3', name: 'User talk'},
{id: '4', name: 'Project'},
{id: '6', name: 'File'},
{id: '10', name: 'Template'},
{id: '14', name: 'Category'},
{id: '110', name: 'Forum'},
{id: '828', name: 'Module'}
];
let namespaceOptions = '';
namespaces.forEach(ns => {
namespaceOptions += ``;
});
const content = `
`;
showModal('从前缀加载页面', content);
document.getElementById('load-prefix-pages-button').addEventListener('click', function() {
const prefix = document.getElementById('prefix-name').value.trim();
const namespace = document.getElementById('namespace-select').value;
if (!prefix) {
showMessage('请输入有效的页面前缀', 'error');
return;
}
document.getElementById('prefix-results').innerHTML = '正在加载前缀页面,请稍候...
';
loadPagesFromPrefix(prefix, namespace);
});
}
// 从分类加载页面
function loadPagesFromCategory(categoryName, depth) {
const api = new mw.Api();
api.get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:' + categoryName,
cmlimit: 500,
cmnamespace: '*',
cmtype: 'page|subcat',
format: 'json'
}).done(function(data) {
const pages = [];
const subcats = [];
if (data.query && data.query.categorymembers) {
data.query.categorymembers.forEach(function(member) {
if (member.ns === 14) {
// 这是子分类
subcats.push(member.title.replace('Category:', ''));
} else {
// 这是页面
pages.push(member.title);
}
});
// 显示结果
displayCategoryResults(categoryName, pages);
// 如果需要处理子分类并且深度大于0
if (depth > 0 && subcats.length > 0) {
loadSubcategories(subcats, pages, depth - 1);
}
} else {
document.getElementById('category-results').innerHTML = '在分类 "' + categoryName + '" 中未找到页面。
';
}
}).fail(function() {
document.getElementById('category-results').innerHTML = '无法加载分类页面。请检查分类名称后重试。
';
});
}
// 递归加载子分类
function loadSubcategories(subcats, allPages, remainingDepth) {
if (subcats.length === 0 || remainingDepth < 0) return;
const currentCat = subcats.shift();
const api = new mw.Api();
api.get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:' + currentCat,
cmlimit: 500,
cmnamespace: '*',
cmtype: 'page|subcat',
format: 'json'
}).done(function(data) {
const newSubcats = [];
if (data.query && data.query.categorymembers) {
data.query.categorymembers.forEach(function(member) {
if (member.ns === 14) {
// 这是子分类
newSubcats.push(member.title.replace('Category:', ''));
} else {
// 这是页面
allPages.push(member.title);
}
});
// 更新显示
displayCategoryResults('所有分类', allPages);
// 处理任何新发现的子分类
if (remainingDepth > 0) {
subcats.push(...newSubcats);
}
// 继续处理队列中的下一个子分类
if (subcats.length > 0) {
loadSubcategories(subcats, allPages, remainingDepth);
}
}
});
}
// 显示分类结果
function displayCategoryResults(categoryName, pages) {
const resultContainer = document.getElementById('category-results');
if (pages.length === 0) {
resultContainer.innerHTML = '在分类 "' + categoryName + '" 中未找到页面。
';
return;
}
let html = `在分类 "${categoryName}" 中找到 ${pages.length} 个页面:
`;
html += `
`;
pages.forEach((page, index) => {
html += `
`;
});
html += `
`;
resultContainer.innerHTML = html;
// 添加全选/取消全选功能
document.getElementById('select-all-category').addEventListener('change', function() {
const checkboxes = document.querySelectorAll('#category-results .page-checkbox');
checkboxes.forEach(cb => {
cb.checked = this.checked;
});
});
// 添加添加到列表功能
document.getElementById('add-category-pages-button').addEventListener('click', function() {
const selectedPages = [];
document.querySelectorAll('#category-results .page-checkbox:checked').forEach(cb => {
selectedPages.push(cb.value);
});
if (selectedPages.length === 0) {
showMessage('请至少选择一个页面', 'error');
return;
}
const textarea = document.getElementById('pages-to-delete');
const existingText = textarea.value.trim();
const newText = selectedPages.join('\n');
textarea.value = existingText ? existingText + '\n' + newText : newText;
closeModal();
showMessage(`已添加 ${selectedPages.length} 个页面到删除列表`, 'success');
});
}
// 从前缀加载页面
function loadPagesFromPrefix(prefix, namespace) {
const api = new mw.Api();
api.get({
action: 'query',
list: 'allpages',
apprefix: prefix,
apnamespace: namespace,
aplimit: 500,
format: 'json'
}).done(function(data) {
const pages = [];
if (data.query && data.query.allpages) {
data.query.allpages.forEach(function(page) {
pages.push(page.title);
});
// 显示结果
displayPrefixResults(prefix, namespace, pages);
} else {
document.getElementById('prefix-results').innerHTML = '使用前缀 "' + prefix + '" 未找到页面。
';
}
}).fail(function() {
document.getElementById('prefix-results').innerHTML = '无法加载前缀页面。请检查前缀后重试。
';
});
}
// 显示前缀结果
function displayPrefixResults(prefix, namespace, pages) {
const resultContainer = document.getElementById('prefix-results');
if (pages.length === 0) {
resultContainer.innerHTML = '使用前缀 "' + prefix + '" 未找到页面。
';
return;
}
const namespaceText = document.querySelector('#namespace-select option[value="' + namespace + '"]')?.textContent || namespace;
let html = `在命名空间 "${namespaceText}" 中找到 ${pages.length} 个以 "${prefix}" 开头的页面:
`;
html += `
`;
pages.forEach((page, index) => {
html += `
`;
});
html += `
`;
resultContainer.innerHTML = html;
// 添加全选/取消全选功能
document.getElementById('select-all-prefix').addEventListener('change', function() {
const checkboxes = document.querySelectorAll('#prefix-results .page-checkbox');
checkboxes.forEach(cb => {
cb.checked = this.checked;
});
});
// 添加添加到列表功能
document.getElementById('add-prefix-pages-button').addEventListener('click', function() {
const selectedPages = [];
document.querySelectorAll('#prefix-results .page-checkbox:checked').forEach(cb => {
selectedPages.push(cb.value);
});
if (selectedPages.length === 0) {
showMessage('请至少选择一个页面', 'error');
return;
}
const textarea = document.getElementById('pages-to-delete');
const existingText = textarea.value.trim();
const newText = selectedPages.join('\n');
textarea.value = existingText ? existingText + '\n' + newText : newText;
closeModal();
showMessage(`已添加 ${selectedPages.length} 个页面到删除列表`, 'success');
});
}
// 预览页面
function previewPages() {
const pagesText = document.getElementById('pages-to-delete').value.trim();
if (!pagesText) {
showMessage('请先添加要删除的页面', 'error');
return;
}
// 将文本分割成页面数组
const pagesToDelete = pagesText.split('\n')
.map(page => page.trim())
.filter(page => page.length > 0);
if (pagesToDelete.length === 0) {
showMessage('没有找到有效的页面标题', 'error');
return;
}
// 显示预览
const content = `
总页面数: ${pagesToDelete.length}
删除原因: ${document.getElementById('delete-reason').value}
${pagesToDelete.map(page => `- ${page}
`).join('')}
警告: 请确认以上列表。点击"开始删除"后,这些页面将被永久删除。此操作无法撤销,请确保您有删除权限。
`;
showModal('预览删除列表', content);
}
// 开始删除
function startDeletion() {
const pagesText = document.getElementById('pages-to-delete').value.trim();
if (!pagesText) {
showMessage('请先添加要删除的页面', 'error');
return;
}
// 将文本分割成页面数组
const pagesToDelete = pagesText.split('\n')
.map(page => page.trim())
.filter(page => page.length > 0);
if (pagesToDelete.length === 0) {
showMessage('没有找到有效的页面标题', 'error');
return;
}
// 删除确认
if (!confirm('您即将删除 ' + pagesToDelete.length + ' 个页面。此操作无法撤销。是否继续?')) {
return;
}
// 准备删除
const reason = document.getElementById('delete-reason').value;
const statusContainer = document.getElementById('deletion-status');
statusContainer.style.display = 'block';
const resultsElement = document.getElementById('deletion-results');
resultsElement.innerHTML = '';
// 开始删除过程
processPageDeletion(pagesToDelete, 0, reason);
}
// 处理页面删除(递归)
function processPageDeletion(pages, index, reason) {
if (index >= pages.length) {
// 所有页面处理完毕
document.getElementById('progress-text').textContent = '完成! 删除操作已结束。';
return;
}
const page = pages[index];
const progressElement = document.getElementById('progress');
const progressTextElement = document.getElementById('progress-text');
const resultsElement = document.getElementById('deletion-results');
// 更新进度
const progressPercentage = Math.round((index / pages.length) * 100);
progressElement.style.width = progressPercentage + '%';
progressTextElement.textContent = '正在处理: ' + page + ' (' + (index + 1) + '/' + pages.length + ', ' + progressPercentage + '%)';
// 执行删除API调用
const api = new mw.Api();
api.postWithToken('csrf', {
action: 'delete',
title: page,
reason: reason,
format: 'json'
}).done(function() {
// 删除成功
const resultItem = document.createElement('div');
resultItem.style.color = '#3c763d';
resultItem.textContent = '✓ 成功删除: ' + page;
resultsElement.appendChild(resultItem);
// 继续下一个
setTimeout(function() {
processPageDeletion(pages, index + 1, reason);
}, 500); // 添加短暂延迟以避免过快发送请求
}).fail(function(code, result) {
// 删除失败
const resultItem = document.createElement('div');
resultItem.style.color = '#a94442';
resultItem.textContent = '✗ 删除失败: ' + page + ' - ' + (result.error ? result.error.info : code);
resultsElement.appendChild(resultItem);
// 继续下一个
setTimeout(function() {
processPageDeletion(pages, index + 1, reason);
}, 500);
});
}
// 等待DOM加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createInterface);
} else {
createInterface();
}
})();