// ==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 => `
  1. ${page}
  2. `).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(); } })();