// ==UserScript==
// @name 网站URL简化|去除杂乱参数
// @namespace http://tampermonkey.net/
// @version 4.0-beta
// @description 自动清理必应搜索、B站视频、百度搜索、KIMI AI、360搜索、CSDN博客和搜狗搜索等的URL中的多余参数,支持清理规则清理任意网站,优化浏览体验
// @author xjy666a
// @license MIT
// @match https://cn.bing.com/search*
// @match https://www.bing.com/search*
// @match https://www.bilibili.com/video/*
// @match https://www.baidu.com/*
// @match https://kimi.moonshot.cn/*
// @match https://minecraft.fandom.com/*
// @match https://www.so.com/s*
// @match https://blog.csdn.net/*
// @match https://www.sogou.com/sogou*
// @match *://*/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_setClipboard
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @run-at document-start
// @priority 1
// @icon https://pic1.imgdb.cn/item/69a5c1f7dcdb109d1d43d75f.png
// ==/UserScript==
/* MIT License
Copyright (c) 2025 xjy666a
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
(function() {
'use strict';
// 默认设置
const defaultSettings = {
enableCleaner: true,
enableBing: true,
enableBilibili: true,
enableBaidu: true,
enableKimi: true,
enableMinecraft: true,
enable360: true,
enableCSDN: true,
enableSogou: true,
enableClipboardCleaner: true,
cleanerMode: 'silent', // 清理模式:'notify' 提示模式,'silent' 静默模式
enableCustomRules: true, // 启用清理规则
usageCount: 0,
ratingRequested: false,
envWarningShown1: false,
envWarningShown500: false,
envWarningShown1000: false
};
// 获取设置
function getSettings() {
return {
enableCleaner: GM_getValue('enableCleaner', defaultSettings.enableCleaner),
enableBing: GM_getValue('enableBing', defaultSettings.enableBing),
enableBilibili: GM_getValue('enableBilibili', defaultSettings.enableBilibili),
enableBaidu: GM_getValue('enableBaidu', defaultSettings.enableBaidu),
enableKimi: GM_getValue('enableKimi', defaultSettings.enableKimi),
enableMinecraft: GM_getValue('enableMinecraft', defaultSettings.enableMinecraft),
enable360: GM_getValue('enable360', defaultSettings.enable360),
enableCSDN: GM_getValue('enableCSDN', defaultSettings.enableCSDN),
enableSogou: GM_getValue('enableSogou', defaultSettings.enableSogou),
enableClipboardCleaner: GM_getValue('enableClipboardCleaner', defaultSettings.enableClipboardCleaner),
cleanerMode: GM_getValue('cleanerMode', defaultSettings.cleanerMode),
enableCustomRules: GM_getValue('enableCustomRules', defaultSettings.enableCustomRules),
customRules: GM_getValue('customRules', []),
usageCount: GM_getValue('usageCount', 0),
ratingRequested: GM_getValue('ratingRequested', false),
envWarningShown1: GM_getValue('envWarningShown1', false),
envWarningShown500: GM_getValue('envWarningShown500', false),
envWarningShown1000: GM_getValue('envWarningShown1000', false)
};
}
// ==================== 运行环境检测系统 ====================
// 检测运行环境
function detectEnvironment() {
const env = {
browser: 'unknown',
scriptManager: 'unknown',
isFirefox: false,
isGreasemonkey: false,
isFiremonkey: false,
isTampermonkey: false,
hasClipboardIssue: false
};
// 检测浏览器
const ua = navigator.userAgent.toLowerCase();
if (ua.includes('firefox')) {
env.browser = 'Firefox';
env.isFirefox = true;
} else if (ua.includes('chrome') || ua.includes('chromium')) {
env.browser = 'Chrome';
} else if (ua.includes('edge')) {
env.browser = 'Edge';
} else if (ua.includes('safari') && !ua.includes('chrome')) {
env.browser = 'Safari';
}
// 检测脚本管理器
if (typeof GM_info !== 'undefined') {
const scriptHandler = GM_info.scriptHandler || '';
const version = GM_info.version || '';
if (scriptHandler.toLowerCase().includes('tampermonkey')) {
env.scriptManager = 'Tampermonkey';
env.isTampermonkey = true;
} else if (scriptHandler.toLowerCase().includes('greasemonkey')) {
env.scriptManager = 'Greasemonkey';
env.isGreasemonkey = true;
env.hasClipboardIssue = true;
} else if (scriptHandler.toLowerCase().includes('firemonkey')) {
env.scriptManager = 'Firemonkey';
env.isFiremonkey = true;
} else if (scriptHandler.toLowerCase().includes('violentmonkey')) {
env.scriptManager = 'Violentmonkey';
} else {
env.scriptManager = scriptHandler || 'Unknown';
}
}
// Firefox + Greasemonkey 组合可能有剪贴板问题
if (env.isFirefox && env.isGreasemonkey) {
env.hasClipboardIssue = true;
}
return env;
}
// 显示环境警告提示
function showEnvironmentWarning(env, usageCount) {
if (document.querySelector('.env-warning-prompt')) {
return;
}
const prompt = document.createElement('div');
prompt.className = 'env-warning-prompt';
prompt.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(135deg, rgba(255, 152, 0, 0.95) 0%, rgba(245, 124, 0, 0.95) 100%);
color: white;
padding: 25px;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
z-index: 10003;
font-size: 14px;
max-width: 500px;
width: 90%;
text-align: left;
backdrop-filter: blur(10px);
`;
prompt.innerHTML = `
⚠️
运行环境兼容性提示
已使用 ${usageCount} 次
检测到您的运行环境:
浏览器:
${env.browser}
脚本管理器:
${env.scriptManager}
🔴 可能存在的问题:
- 剪贴板复制轮询问题:B站分享链接自动清理功能可能无法正常工作
- 部分功能受限:部分 GM_* API 可能不完全支持
- 存储功能异常:设置和记录可能无法正确保存
✅ 推荐解决方案:
建议改用 Tampermonkey 脚本管理器,以获得最佳体验和完整功能支持。
`;
document.body.appendChild(prompt);
// 关闭按钮事件
prompt.querySelector('.close-env-warning').addEventListener('click', function() {
const dontShow = prompt.querySelector('#dontShowEnvWarning').checked;
if (dontShow) {
// 用户选择不再显示,标记所有警告为已显示
GM_setValue('envWarningShown1', true);
GM_setValue('envWarningShown500', true);
GM_setValue('envWarningShown1000', true);
}
document.body.removeChild(prompt);
});
}
// 检查并显示环境警告
function checkEnvironmentWarning() {
const env = detectEnvironment();
// 如果没有剪贴板问题,不需要警告
if (!env.hasClipboardIssue) {
return;
}
const settings = getSettings();
const usageCount = settings.usageCount;
// 检查是否需要显示警告(第1次、500次、1000次)
let shouldShow = false;
let warningType = '';
if (usageCount >= 1 && !settings.envWarningShown1) {
shouldShow = true;
warningType = 'envWarningShown1';
} else if (usageCount >= 500 && !settings.envWarningShown500) {
shouldShow = true;
warningType = 'envWarningShown500';
} else if (usageCount >= 1000 && !settings.envWarningShown1000) {
shouldShow = true;
warningType = 'envWarningShown1000';
}
if (shouldShow) {
// 标记该阶段警告已显示
GM_setValue(warningType, true);
// 延迟显示,避免页面加载时立即弹出
setTimeout(() => {
showEnvironmentWarning(env, usageCount);
}, 1500);
}
}
// 切换设置并返回新状态
function toggleSetting(key) {
const currentValue = GM_getValue(key, defaultSettings[key]);
GM_setValue(key, !currentValue);
return !currentValue;
}
// ==================== 清理记录管理系统 ====================
// 获取清理记录
function getCleaningLogs() {
const logs = GM_getValue('cleaningLogs', []);
return logs;
}
// 添加清理记录
function addCleaningLog(originalUrl, cleanedUrl, siteName, action) {
const logs = getCleaningLogs();
const log = {
id: Date.now() + '_' + Math.random().toString(36).substr(2, 9),
timestamp: new Date().toISOString(),
timestampLocal: new Date().toLocaleString('zh-CN'),
originalUrl: originalUrl,
cleanedUrl: cleanedUrl,
siteName: siteName,
action: action, // 'redirect', 'replaceState', 'noChange'
savedChars: originalUrl.length - cleanedUrl.length,
pageTitle: document.title || '未知页面',
userAgent: navigator.userAgent
};
// 只保留最近100条记录,避免存储过多
logs.unshift(log);
if (logs.length > 100) {
logs.splice(100);
}
GM_setValue('cleaningLogs', logs);
return log;
}
// 清空清理记录
function clearCleaningLogs() {
GM_setValue('cleaningLogs', []);
showNotification('已清空清理记录');
}
// 导出清理记录为JSON
function exportCleaningLogs() {
const logs = getCleaningLogs();
const dataStr = JSON.stringify(logs, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `url_cleaner_logs_${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
showNotification('清理记录已导出');
}
// 导出清理记录为可读文本
function exportCleaningLogsAsText() {
const logs = getCleaningLogs();
let text = '====== URL 清理记录 ======\n\n';
text += `导出时间: ${new Date().toLocaleString('zh-CN')}\n`;
text += `总记录数: ${logs.length}\n\n`;
logs.forEach((log, index) => {
text += `[记录 ${index + 1}]\n`;
text += `时间: ${log.timestampLocal || '无'}\n`;
text += `网站: ${log.siteName || '无'}\n`;
text += `页面标题: ${log.pageTitle || '无'}\n`;
text += `处理方式: ${log.action || '无'}\n`;
text += `节省字符: ${log.savedChars || 0} 个\n`;
text += `原始URL: ${log.originalUrl || '无'}\n`;
text += `清理后URL: ${log.cleanedUrl || '无'}\n`;
text += `\n${'='.repeat(60)}\n\n`;
});
const dataBlob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `url_cleaner_logs_${new Date().toISOString().split('T')[0]}.txt`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
showNotification('清理记录已导出为文本');
}
// 复制清理记录到剪贴板(用于反馈)
function copyLogsForFeedback() {
const logs = getCleaningLogs();
const recentLogs = logs.slice(0, 10); // 只复制最近10条
let text = '【URL清理脚本 - BUG反馈】\n\n';
text += `脚本版本: 2.2\n`;
text += `浏览器: ${navigator.userAgent}\n`;
text += `导出时间: ${new Date().toLocaleString('zh-CN')}\n\n`;
text += `最近 ${recentLogs.length} 条清理记录:\n\n`;
recentLogs.forEach((log, index) => {
text += `${index + 1}. [${log.timestampLocal || '无'}] ${log.siteName || '无'}\n`;
text += ` 原始: ${log.originalUrl || '无'}\n`;
text += ` 清理: ${log.cleanedUrl || '无'}\n`;
text += ` 操作: ${log.action || '无'}, 节省: ${log.savedChars || 0}字符\n\n`;
});
GM_setClipboard(text);
showNotification('已复制最近10条记录,可用于反馈BUG');
}
// 注册菜单命令
function registerMenuCommands() {
const settings = getSettings();
// 主菜单项
GM_registerMenuCommand(
`📊 使用次数: ${settings.usageCount} 次`,
() => showUsageStats()
);
GM_registerMenuCommand(
`💬 提供反馈或建议`,
() => showFeedbackPrompt()
);
GM_registerMenuCommand(
`${settings.enableCleaner ? '✅' : '❌'} 启用URL清理`,
() => {
toggleSetting('enableCleaner');
location.reload();
}
);
// 清理规则菜单
const customRulesCount = (settings.customRules || []).length;
GM_registerMenuCommand(
`⚙️ 清理规则和其它功能设置 (${customRulesCount}条)`,
() => showCustomRulesPanel()
);
GM_registerMenuCommand(
`🔄 重置默认规则`,
() => {
if (confirm('确定要重置默认规则吗?\n这将恢复必应、百度、B站、360、CSDN、搜狗的默认清理规则。\n\n注意:不会删除您自定义的规则。')) {
// 删除所有默认规则
const rules = getSettings().customRules || [];
const userRules = rules.filter(rule => !rule.id || !rule.id.startsWith('default-'));
GM_setValue('customRules', userRules);
// 重新初始化默认规则
initDefaultRules();
showNotification('默认规则已重置');
location.reload();
}
}
);
GM_registerMenuCommand(
`📅 显示信息面板`,
() => showInfoOverlay()
);
// 清理记录相关菜单
const logsCount = getCleaningLogs().length;
GM_registerMenuCommand(
`📝 查看清理记录 (${logsCount}条)`,
() => showCleaningLogsPanel()
);
GM_registerMenuCommand(
`📋 复制记录用于反馈`,
() => copyLogsForFeedback()
);
GM_registerMenuCommand(
`💾 导出清理记录`,
() => {
const choice = confirm('选择导出格式:\n确定 = JSON格式\n取消 = 文本格式');
if (choice) {
exportCleaningLogs();
} else {
exportCleaningLogsAsText();
}
}
);
GM_registerMenuCommand(
`🗑️ 清空清理记录`,
() => {
if (confirm(`确定要清空所有 ${logsCount} 条清理记录吗?`)) {
clearCleaningLogs();
}
}
);
}
// 显示使用统计详情
function showUsageStats() {
const settings = getSettings();
// 创建统计信息弹窗
const statsPrompt = document.createElement('div');
statsPrompt.className = 'usage-stats-prompt';
statsPrompt.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(34, 34, 34, 0.95);
color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 10001;
font-size: 14px;
min-width: 300px;
text-align: center;
transition: opacity 0.3s;
`;
// 计算使用天数(假设脚本安装日期存储在installDate中)
const installDate = GM_getValue('installDate', Date.now());
const daysUsed = Math.ceil((Date.now() - installDate) / (1000 * 60 * 60 * 24));
// 弹窗内容
statsPrompt.innerHTML = `
URL简化脚本使用统计
总使用次数:
${settings.usageCount} 次
已使用天数:
${daysUsed} 天
平均每天使用:
${(settings.usageCount / Math.max(daysUsed, 1)).toFixed(1)} 次
`;
// 添加到页面
document.body.appendChild(statsPrompt);
// 关闭按钮点击事件
statsPrompt.querySelector('.close-stats-prompt').addEventListener('click', function() {
document.body.removeChild(statsPrompt);
});
}
// 显示反馈弹窗
function showFeedbackPrompt() {
// 检查是否已存在弹窗
if (document.querySelector('.feedback-prompt')) {
return;
}
// 创建弹窗
const prompt = document.createElement('div');
prompt.className = 'feedback-prompt';
prompt.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(34, 34, 34, 0.95);
color: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
z-index: 10001;
font-size: 14px;
max-width: 480px;
width: 90%;
max-height: 85vh;
overflow-y: auto;
text-align: center;
transition: opacity 0.3s;
backdrop-filter: blur(10px);
`;
// 弹窗内容
prompt.innerHTML = `
💬 功能反馈与建议
⚠️ 反馈 BUG 必读
请务必提供调试信息,以便快速定位问题:
1️⃣ 点击下方"📅 打开信息面板"
2️⃣ 点击"显示调试信息"
3️⃣ 点击"📋 复制全部调试信息"或截图
4️⃣ 在反馈页面粘贴信息或上传截图
目前本脚本功能较少,你可以反馈,若可以实现,我们会尽量满足!
`;
// 添加到页面
document.body.appendChild(prompt);
// "打开信息面板"按钮点击事件
prompt.querySelector('.open-info-panel-btn').addEventListener('click', function() {
// 关闭反馈弹窗
document.body.removeChild(prompt);
// 打开信息面板
showInfoOverlay();
// 提示用户下一步操作
setTimeout(() => {
showNotification('请点击"显示调试信息"按钮获取完整调试信息');
}, 800);
});
// 关闭按钮点击事件
prompt.querySelector('.close-feedback-prompt').addEventListener('click', function() {
document.body.removeChild(prompt);
});
// 点击反馈链接时关闭弹窗
prompt.querySelectorAll('a').forEach(link => {
link.addEventListener('click', function() {
setTimeout(() => {
if (document.body.contains(prompt)) {
document.body.removeChild(prompt);
}
}, 500);
});
});
}
// 显示清理记录面板
function showCleaningLogsPanel() {
const logs = getCleaningLogs();
// 创建面板
const panel = document.createElement('div');
panel.className = 'cleaning-logs-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(34, 34, 34, 0.95);
color: white;
padding: 25px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 10001;
font-size: 14px;
width: 90%;
max-width: 1000px;
max-height: 85vh;
overflow: hidden;
display: flex;
flex-direction: column;
`;
// 构建内容
panel.innerHTML = `
📝 URL 清理记录
${logs.length === 0 ? '
暂无清理记录
' : ''}
`;
document.body.appendChild(panel);
// 渲染日志列表
function renderLogs(logsToRender = logs) {
const container = panel.querySelector('.logs-container');
if (logsToRender.length === 0) {
container.innerHTML = '没有找到匹配的记录
';
return;
}
container.innerHTML = logsToRender.map((log, index) => `
${log.action === 'redirect' ? '🔄 跳转' : log.action === 'replaceState' ? '✏️ 替换' : '⏸️ 无变化'}
${log.siteName}
${log.timestampLocal}
${log.savedChars > 0 ? '✔' : '='} 节省 ${log.savedChars} 字符
🔗 原始 URL:
${log.originalUrl || '无'}
✅ 清理后 URL:
${log.cleanedUrl || '无'}
`).join('');
}
// 初始渲染
renderLogs();
// 搜索功能
const searchInput = panel.querySelector('.search-logs-input');
searchInput.addEventListener('input', (e) => {
const keyword = e.target.value.toLowerCase();
if (!keyword) {
renderLogs(logs);
return;
}
const filtered = logs.filter(log =>
(log.siteName || '').toLowerCase().includes(keyword) ||
(log.originalUrl || '').toLowerCase().includes(keyword) ||
(log.cleanedUrl || '').toLowerCase().includes(keyword) ||
(log.pageTitle || '').toLowerCase().includes(keyword)
);
renderLogs(filtered);
});
// 刷新按钮
panel.querySelector('.refresh-logs-btn').addEventListener('click', () => {
document.body.removeChild(panel);
showCleaningLogsPanel();
});
// 复制反馈按钮
panel.querySelector('.copy-feedback-btn').addEventListener('click', () => {
copyLogsForFeedback();
});
// 导出按钮
panel.querySelector('.export-logs-btn').addEventListener('click', () => {
const choice = confirm('选择导出格式:\n确定 = JSON格式\n取消 = 文本格式');
if (choice) {
exportCleaningLogs();
} else {
exportCleaningLogsAsText();
}
});
// 清空按钮
panel.querySelector('.clear-logs-btn').addEventListener('click', () => {
if (confirm(`确定要清空所有 ${logs.length} 条清理记录吗?`)) {
clearCleaningLogs();
document.body.removeChild(panel);
}
});
// 关闭按钮
panel.querySelector('.close-logs-panel').addEventListener('click', () => {
document.body.removeChild(panel);
});
}
// 增加使用计数并检查是否需要请求评分
function incrementUsageCount() {
const settings = getSettings();
// 增加使用计数
const newCount = settings.usageCount + 1;
GM_setValue('usageCount', newCount);
// 检查是否需要请求评分 - 将阈值从50改为10
if (newCount >= 10 && !settings.ratingRequested) {
// 显示评分请求
showRatingPrompt();
// 标记已请求评分
GM_setValue('ratingRequested', true);
}
}
// 显示评分请求提示
function showRatingPrompt() {
// 检查是否已存在提示框
if (document.querySelector('.rating-prompt')) {
return;
}
// 创建提示框
const prompt = document.createElement('div');
prompt.className = 'rating-prompt';
prompt.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(34, 34, 34, 0.95);
color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 10001;
font-size: 14px;
max-width: 400px;
text-align: center;
transition: opacity 0.3s;
`;
// 提示框内容 - 修改文本以反映新的阈值
prompt.innerHTML = `
感谢您使用URL简化脚本!
您已经使用本脚本超过10次,如果觉得它对您有帮助,希望您能花一点时间给它评个分,这将帮助更多人发现它。
您的支持是我们持续改进的动力!
`;
// 添加到页面
document.body.appendChild(prompt);
// 关闭按钮点击事件
prompt.querySelector('.close-rating-prompt').addEventListener('click', function() {
document.body.removeChild(prompt);
});
// 点击评分链接时关闭提示框
prompt.querySelectorAll('a').forEach(link => {
link.addEventListener('click', function() {
// 标记用户已点击评分链接
GM_setValue('userRated', true);
setTimeout(() => {
if (document.body.contains(prompt)) {
document.body.removeChild(prompt);
}
}, 500);
});
});
}
// 清理URL的函数
function cleanUrl(url) {
try {
const settings = getSettings();
if (!settings.enableCleaner) {
return url;
}
// 增加使用计数
incrementUsageCount();
// 先尝试应用自定义规则
if (settings.customRules && settings.customRules.length > 0) {
const customResult = applyCustomRules(url, settings.customRules);
if (customResult.matched && customResult.rule) {
// 根据规则的动作类型决定处理方式
if (customResult.rule.action === 'redirect') {
// 跳转模式 - 返回新URL,让checkAndCleanUrl处理跳转
if (settings.cleanerMode === 'notify') {
sessionStorage.setItem('urlCleanNotification', JSON.stringify({
siteName: customResult.rule.name || '自定义规则',
originalUrl: url,
cleanedUrl: customResult.url
}));
}
// 返回新URL,让调用者处理跳转
return customResult.url;
} else {
// 替换URL模式(replaceState)
return customResult.url;
}
}
}
const urlObj = new URL(url);
// 处理Minecraft Wiki重定向
if (settings.enableMinecraft && urlObj.hostname === 'minecraft.fandom.com') {
const pathParts = urlObj.pathname.split('/');
let newUrl;
if (pathParts[1] === 'wiki') {
const pageName = pathParts.slice(2).join('/');
newUrl = `https://en.minecraft.wiki/w/${pageName}`;
} else if (pathParts[2] === 'wiki') {
const lang = pathParts[1];
const pageName = pathParts.slice(3).join('/');
newUrl = `https://${lang}.minecraft.wiki/w/${pageName}`;
}
if (newUrl && newUrl !== url) {
// 记录清理日志
addCleaningLog(url, newUrl, 'Minecraft Wiki', 'redirect');
// Wiki 跳转无提示
window.location.href = newUrl;
return url;
}
}
// 处理KIMI AI URL
if (settings.enableKimi && urlObj.hostname === 'kimi.moonshot.cn') {
if (urlObj.pathname === '/' || urlObj.pathname === '') {
const newUrl = 'https://kimi.moonshot.cn/';
if (newUrl !== url) {
// 记录清理日志
addCleaningLog(url, newUrl, 'KIMI AI', 'redirect');
if (settings.cleanerMode === 'notify') {
sessionStorage.setItem('urlCleanNotification', JSON.stringify({
siteName: 'KIMI AI',
originalUrl: url,
cleanedUrl: newUrl
}));
}
window.location.href = newUrl;
return url;
}
return newUrl;
}
}
// 其他网站的清理都通过自定义规则处理
return url;
} catch (error) {
console.error('URL处理错误:', error);
return url;
}
}
// 检查并清理当前URL
function checkAndCleanUrl() {
const currentUrl = window.location.href;
const cleanedUrl = cleanUrl(currentUrl);
if (cleanedUrl !== currentUrl) {
const settings = getSettings();
// 检查是否是跳转动作(通过比较域名和路径来判断)
try {
const currentUrlObj = new URL(currentUrl);
const cleanedUrlObj = new URL(cleanedUrl);
// 如果域名或路径不同,说明是跳转动作
const isRedirect = currentUrlObj.hostname !== cleanedUrlObj.hostname ||
currentUrlObj.pathname !== cleanedUrlObj.pathname;
if (isRedirect) {
// 跳转模式:使用 window.location.href 进行跳转
window.location.href = cleanedUrl;
return;
}
} catch (e) {
// 如果解析失败,回退到字符串比较
}
// 替换URL模式:使用 history.replaceState 更新URL而不刷新页面
window.history.replaceState(null, '', cleanedUrl);
// 显示提示(延迟显示,确保页面已加载)
if (settings.cleanerMode === 'notify') {
// 延迟显示提示,确保页面已完全加载
setTimeout(() => {
showNotification('已自动清理~');
}, 500);
}
}
}
// 监听URL变化
let lastUrl = window.location.href;
new MutationObserver(() => {
const currentUrl = window.location.href;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
checkAndCleanUrl();
}
}).observe(document, {subtree: true, childList: true});
// 处理必应搜索结果中的Minecraft Wiki链接
function processBingSearchResults() {
// 同时支持中国版和国际版必应
if (!window.location.href.includes('.bing.com/search')) return;
// 检查页面是否有内容
const mainResults = document.getElementById('b_results') ||
document.querySelector('.b_results') ||
document.querySelector('#main');
// 修改判断条件:检查搜索结果是否存在且内容是否足够
if (!mainResults || mainResults.children.length < 2) {
console.log('必应搜索结果似乎为空,准备重试...');
// 重试机制
if (typeof window.bingRetryCount === 'undefined') {
window.bingRetryCount = 0;
}
if (window.bingRetryCount < 3) {
window.bingRetryCount++;
console.log(`重试第 ${window.bingRetryCount} 次...`);
// 延迟2秒后重试,给予更多加载时间
setTimeout(() => {
// 如果已经重试了但还是没有结果,保留参数重新加载
if (window.bingRetryCount >= 2) {
console.log('尝试保留参数重新加载...');
sessionStorage.setItem('cleanUrlAfterLoad', 'true');
window.location.reload(true); // 强制从服务器重新加载
} else {
window.location.reload();
}
}, 2000);
return;
} else {
console.log('已达到最大重试次数,保留参数加载页面');
// 标记为已处理,避免无限循环
window.bingRetryHandled = true;
// 获取当前URL并保留所有参数
const currentUrl = window.location.href;
// 设置一个标记,表示页面已经加载完成后再清理URL
sessionStorage.setItem('cleanUrlAfterLoad', 'true');
sessionStorage.setItem('originalUrl', currentUrl);
// 不再尝试清理URL,让页面正常加载
return;
}
} else {
// 如果页面加载成功,重置计数器
window.bingRetryCount = 0;
// 检查是否需要在页面加载后清理URL
if (sessionStorage.getItem('cleanUrlAfterLoad') === 'true') {
const originalUrl = sessionStorage.getItem('originalUrl');
sessionStorage.removeItem('cleanUrlAfterLoad');
sessionStorage.removeItem('originalUrl');
// 延迟执行URL清理,确保页面已完全加载
setTimeout(() => {
if (mainResults && mainResults.children.length > 2) {
checkAndCleanUrl();
}
}, 2000);
}
}
// 获取所有未处理的搜索结果链接
const searchResults = mainResults.querySelectorAll('a[href*="minecraft.fandom.com"]:not([data-wiki-processed])');
searchResults.forEach(link => {
try {
// 标记该链接已处理
link.setAttribute('data-wiki-processed', 'true');
const url = new URL(link.href);
if (url.hostname === 'minecraft.fandom.com') {
const pathParts = url.pathname.split('/');
let newUrl;
// 构建新的Wiki URL
if (pathParts[1] === 'wiki') {
const pageName = pathParts.slice(2).join('/');
newUrl = `https://en.minecraft.wiki/w/${pageName}`;
} else if (pathParts[2] === 'wiki') {
const lang = pathParts[1];
const pageName = pathParts.slice(3).join('/');
newUrl = `https://${lang}.minecraft.wiki/w/${pageName}`;
}
if (newUrl) {
// 获取搜索结果容器
const resultContainer = link.closest('li') || link.parentElement;
// 设置结果容器样式
resultContainer.style.position = 'relative';
resultContainer.style.color = '#666';
resultContainer.style.pointerEvents = 'none';
// 创建新链接提示
const notice = document.createElement('div');
notice.style.cssText = `
margin-top: 8px;
padding: 8px;
background: #f8f8f8;
border-radius: 4px;
pointer-events: auto;
`;
notice.innerHTML = `
⚠️ 上述链接指向已弃用的旧版Wiki
👉 访问新版Wiki页面
`;
// 添加新链接提示
resultContainer.appendChild(notice);
}
}
} catch (error) {
console.error('处理搜索结果链接时出错:', error);
}
});
}
// 使用防抖函数来限制处理频率
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 监听页面变化以处理动态加载的搜索结果
function observeSearchResults() {
const debouncedProcess = debounce(processBingSearchResults, 300);
// 创建观察器
const observer = new MutationObserver(() => {
// 兼容不同版本的必应
if (document.getElementById('b_results') ||
document.querySelector('.b_results') ||
document.querySelector('#main')) {
debouncedProcess();
}
});
// 观察整个body
observer.observe(document.body, {
childList: true,
subtree: true
});
// 首次处理
processBingSearchResults();
// 监听URL变化
let lastUrl = location.href;
const urlChecker = setInterval(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
// 重置重试计数器
window.bingRetryCount = 0;
processBingSearchResults();
}
}, 500);
// 清理函数
return () => {
observer.disconnect();
clearInterval(urlChecker);
};
}
// 修改B站分享链接清理函数
function cleanBilibiliShareLink(text) {
// 检查是否包含B站视频链接
if (!text.includes('bilibili.com/video/BV')) {
return text;
}
try {
// 检查是否已经是清理过的链接(被||包围的标题)
if (text.match(/\|\|.+?\|\|\s+https:\/\/www\.bilibili\.com\/video\/BV[\w]+\//)) {
return text;
}
// 提取BV号
const bvMatch = text.match(/bilibili\.com\/video\/(BV[\w]+)/);
if (!bvMatch) return text;
const bvid = bvMatch[1];
// 检查是否有标题格式【标题】
const titleMatch = text.match(/【(.+?)】/);
const title = titleMatch ? titleMatch[1] : '';
// 构建清理后的链接
const cleanedUrl = `https://www.bilibili.com/video/${bvid}/`;
// 返回清理后的完整文本,使用||包围标题
if (title) {
return `||${title}|| ${cleanedUrl}`;
} else {
return cleanedUrl;
}
} catch (error) {
console.error('清理B站分享链接时出错:', error);
return text;
}
}
// B站专用剪贴板监听函数
function monitorBilibiliClipboard() {
// 只在B站页面上运行
if (!window.location.hostname.includes('bilibili.com')) return;
const settings = getSettings();
if (!settings.enableClipboardCleaner || !settings.enableBilibili) return;
// 存储已处理的链接,避免重复处理
const processedLinks = new Set();
// 监听分享按钮点击事件
function setupShareButtonListener() {
// 查找分享按钮(使用更通用的选择器)
const shareButton = document.querySelector('#share-btn') ||
document.querySelector('[data-v-7224d4fc]#share-btn') ||
document.querySelector('.video-share');
if (shareButton) {
// 检查是否已经添加过监听器
if (!shareButton._hasShareListener) {
shareButton._hasShareListener = true;
shareButton.addEventListener('click', function() {
// 1秒后开始轮询剪贴板,给用户时间完成复制操作
setTimeout(() => {
let pollCount = 0;
const maxPolls = 8; // 最多轮询8次(1秒开始,5秒结束,每500ms一次)
const pollInterval = 500; // 每500ms轮询一次
const pollClipboard = setInterval(() => {
navigator.clipboard.readText().then(text => {
// 如果文本已经是清理过的格式,跳过
if (text.match(/\|\|.+?\|\|\s+https:\/\/www\.bilibili\.com\/video\/BV[\w]+\//)) {
return;
}
if (text && text.includes('bilibili.com/video/BV') && text.includes('?')) {
const linkId = text.trim();
if (processedLinks.has(linkId)) return;
processedLinks.add(linkId);
const cleanedText = cleanBilibiliShareLink(text);
if (cleanedText !== text) {
// 增加使用计数
incrementUsageCount();
// B站分享链接清理无提示
// 直接复制清理后的链接到剪贴板
navigator.clipboard.writeText(cleanedText).then(() => {
console.log('B站分享链接已自动清理并复制到剪贴板');
}).catch(err => {
console.error('复制到剪贴板失败:', err);
});
}
// 限制已处理链接集合大小,避免内存泄漏
if (processedLinks.size > 50) {
const iterator = processedLinks.values();
processedLinks.delete(iterator.next().value);
}
}
}).catch(err => {
console.error('读取剪贴板失败:', err);
});
pollCount++;
if (pollCount >= maxPolls) {
clearInterval(pollClipboard);
}
}, pollInterval);
}, 1000); // 1秒延迟
});
}
}
}
// 初始设置监听器
setupShareButtonListener();
// 使用MutationObserver监听DOM变化,确保分享按钮出现时能及时添加监听器
const observer = new MutationObserver(() => {
setupShareButtonListener();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// 显示清理链接提示框
function showCleanLinkPrompt(cleanedText) {
// 检查是否已存在提示框,避免重复显示
if (document.querySelector('.clean-link-prompt')) {
return;
}
// 创建提示框
const prompt = document.createElement('div');
prompt.className = 'clean-link-prompt';
prompt.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
background-color: rgba(34, 34, 34, 0.9);
color: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
z-index: 10000;
font-size: 14px;
max-width: 300px;
transition: opacity 0.3s;
`;
// 提示框内容
prompt.innerHTML = `
检测到B站分享链接
${cleanedText}
`;
// 添加到页面
document.body.appendChild(prompt);
// 复制按钮点击事件
prompt.querySelector('.copy-clean-link').addEventListener('click', function() {
GM_setClipboard(cleanedText);
showNotification('已复制简化链接');
document.body.removeChild(prompt);
});
// 关闭按钮点击事件
prompt.querySelector('.close-prompt').addEventListener('click', function() {
document.body.removeChild(prompt);
});
// 10秒后自动关闭
setTimeout(() => {
if (document.body.contains(prompt)) {
prompt.style.opacity = '0';
setTimeout(() => {
if (document.body.contains(prompt)) {
document.body.removeChild(prompt);
}
}, 300);
}
}, 10000);
}
// ==================== 自定义规则系统 ====================
// 初始化默认规则(将硬编码规则转换为自定义规则)
// 默认规则定义
const DEFAULT_RULES = [
{
id: 'default-bing',
name: '必应搜索',
domain: 'bing.com',
path: '/search',
keepParams: ['q', 'first', 'mkt'],
removeParams: [],
action: 'redirect',
enabled: true
},
{
id: 'default-baidu',
name: '百度搜索',
domain: 'baidu.com',
path: '/s',
keepParams: ['wd', 'pn', 'si', 'gpc'],
removeParams: ['tfflag'],
action: 'redirect',
enabled: true
},
{
id: 'default-bilibili',
name: 'B站视频',
domain: 'bilibili.com',
path: '/video/',
keepParams: [],
removeParams: ['*'],
action: 'replace',
enabled: true
},
{
id: 'default-kimi',
name: 'KIMI AI',
domain: 'kimi.moonshot.cn',
path: '',
keepParams: [],
removeParams: ['*'],
action: 'redirect',
enabled: true
},
{
id: 'default-360',
name: '360搜索',
domain: 'so.com',
path: '/s',
keepParams: ['q', 'pn'],
removeParams: [],
action: 'redirect',
enabled: true
},
{
id: 'default-csdn',
name: 'CSDN博客',
domain: 'blog.csdn.net',
path: '/article/details/',
keepParams: [],
removeParams: ['*'],
action: 'replace',
enabled: true
},
{
id: 'default-sogou',
name: '搜狗搜索',
domain: 'sogou.com',
path: '/sogou',
keepParams: ['query', 'tsn', 'page'],
removeParams: [],
action: 'redirect',
enabled: true
},
{
id: 'default-google',
name: 'Google搜索',
domain: 'google.com',
path: '/search',
keepParams: ['q', 'start'],
removeParams: ['*'],
action: 'redirect',
enabled: true
}
];
function initDefaultRules() {
const settings = getSettings();
const existingRules = settings.customRules || [];
// 检查是否需要添加默认规则
const hasDefaultRules = existingRules.some(rule => rule.id && rule.id.startsWith('default-'));
if (!hasDefaultRules) {
// 合并默认规则和用户自定义规则
const mergedRules = [...DEFAULT_RULES, ...existingRules];
GM_setValue('customRules', mergedRules);
return mergedRules;
}
return existingRules;
}
// 解析自定义规则
function parseCustomRule(ruleText) {
const lines = ruleText.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
const rule = {
id: Date.now().toString(36),
name: '',
domain: '',
path: '',
keepParams: [],
removeParams: [],
action: 'replace', // 'replace' 或 'redirect'
enabled: true,
raw: ruleText
};
for (const line of lines) {
// 解析规则格式
if (line.startsWith('name:')) {
rule.name = line.substring(5).trim();
} else if (line.startsWith('domain:')) {
rule.domain = line.substring(7).trim().toLowerCase();
} else if (line.startsWith('path:')) {
rule.path = line.substring(5).trim();
} else if (line.startsWith('keep:')) {
rule.keepParams = line.substring(5).trim().split(',').map(p => p.trim()).filter(p => p);
} else if (line.startsWith('remove:')) {
rule.removeParams = line.substring(7).trim().split(',').map(p => p.trim()).filter(p => p);
} else if (line.startsWith('action:')) {
const action = line.substring(7).trim().toLowerCase();
if (action === 'redirect' || action === 'replace') {
rule.action = action;
}
}
}
// 验证规则有效性
if (!rule.domain) {
return null;
}
// 如果没有指定keep或remove,默认移除所有参数
if (rule.keepParams.length === 0 && rule.removeParams.length === 0) {
rule.removeParams = ['*'];
}
return rule;
}
// 验证自定义规则格式
function validateCustomRuleFormat(ruleText) {
const errors = [];
const lines = ruleText.split('\n');
let hasDomain = false;
let hasKeepOrRemove = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (!line || line.startsWith('#')) continue;
// 检查有效的前缀
const validPrefixes = ['name:', 'domain:', 'path:', 'keep:', 'remove:', 'action:'];
const isValid = validPrefixes.some(prefix => line.startsWith(prefix));
if (!isValid) {
errors.push(`第${i + 1}行格式错误: "${line}",应以 name:、domain:、path:、keep:、remove: 或 action: 开头`);
}
if (line.startsWith('domain:')) {
hasDomain = true;
}
if (line.startsWith('keep:') || line.startsWith('remove:')) {
hasKeepOrRemove = true;
}
}
if (!hasDomain) {
errors.push('缺少 domain: 字段(必需)');
}
return errors;
}
// 应用自定义规则清理URL
function applyCustomRules(url, rules) {
const settings = getSettings();
if (!settings.enableCustomRules) {
return { url, matched: false, rule: null };
}
// 初始化默认规则(如果没有规则)
const allRules = rules || initDefaultRules();
if (!allRules || allRules.length === 0) {
return { url, matched: false, rule: null };
}
try {
const urlObj = new URL(url);
const hostname = urlObj.hostname.toLowerCase();
for (const rule of allRules) {
if (!rule.enabled) continue;
// 检查域名匹配
const domainMatch = hostname === rule.domain || hostname.endsWith('.' + rule.domain);
if (!domainMatch) continue;
// 检查路径匹配(如果指定)
if (rule.path && !urlObj.pathname.includes(rule.path)) {
continue;
}
// 记录匹配的自定义规则
console.log('匹配到自定义规则:', rule.name);
// 构建新参数
const newParams = new URLSearchParams();
if (rule.keepParams.length > 0) {
// 保留指定的参数
for (const param of rule.keepParams) {
if (param === '*') {
// 保留所有参数
for (const [key, value] of urlObj.searchParams.entries()) {
newParams.append(key, value);
}
} else {
// 使用getAll获取参数的所有值(支持同名参数)
const values = urlObj.searchParams.getAll(param);
for (const value of values) {
if (value !== null) {
newParams.append(param, value);
}
}
}
}
// 如果设置了keep参数,忽略remove参数(因为keep已经明确了要保留的参数)
} else if (rule.removeParams.length > 0) {
// 移除指定的参数
if (rule.removeParams.includes('*')) {
// * 表示移除所有参数,不添加任何参数
} else {
for (const [key, value] of urlObj.searchParams.entries()) {
if (rule.removeParams.includes(key)) continue;
newParams.append(key, value);
}
}
}
// 构建新URL
let newUrl = urlObj.origin + urlObj.pathname;
if (newParams.toString()) {
newUrl += '?' + newParams.toString();
}
if (urlObj.hash) {
newUrl += urlObj.hash;
}
// 规范化比较URL,避免因编码差异导致的重复刷新
try {
const currentUrlObj = new URL(url);
const newUrlObj = new URL(newUrl);
// 比较协议、主机名、路径
if (currentUrlObj.protocol === newUrlObj.protocol &&
currentUrlObj.hostname === newUrlObj.hostname &&
currentUrlObj.pathname === newUrlObj.pathname &&
currentUrlObj.hash === newUrlObj.hash) {
// 比较查询参数(解码后比较值)
const currentParams = new URLSearchParams(currentUrlObj.search);
const newParamsCheck = new URLSearchParams(newUrlObj.search);
let paramsEqual = true;
// 检查参数数量是否相同
if ([...currentParams.keys()].length !== [...newParamsCheck.keys()].length) {
paramsEqual = false;
} else {
// 比较每个参数的值
for (const [key, value] of currentParams.entries()) {
if (newParamsCheck.get(key) !== value) {
paramsEqual = false;
break;
}
}
}
// 如果URL实际上相同,不执行跳转
if (paramsEqual) {
return { url, matched: true, rule: rule };
}
}
} catch (e) {
// 如果规范化比较失败,回退到字符串比较
if (newUrl === url) {
return { url, matched: true, rule: rule };
}
}
// 记录清理日志
addCleaningLog(url, newUrl, rule.name || '自定义规则', rule.action);
return {
url: newUrl,
matched: true,
rule: rule
};
}
} catch (error) {
console.error('应用自定义规则出错:', error);
}
return { url, matched: false, rule: null };
}
// 显示清理规则和功能设置面板
function showCustomRulesPanel() {
const settings = getSettings();
const rules = settings.customRules || [];
// 检查是否已存在面板
if (document.querySelector('.custom-rules-panel')) {
document.body.removeChild(document.querySelector('.custom-rules-panel'));
}
// 创建面板
const panel = document.createElement('div');
panel.className = 'custom-rules-panel';
panel.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(34, 34, 34, 0.98);
color: white;
z-index: 10001;
font-size: 14px;
overflow: hidden;
display: flex;
`;
// 生成规则列表HTML
let rulesListHTML = '';
if (rules.length === 0) {
rulesListHTML = '暂无清理规则
';
} else {
rules.forEach((rule, index) => {
rulesListHTML += `
${rule.name || '未命名规则'}
域名: ${rule.domain} | 路径: ${rule.path || '无'} | 动作: ${rule.action === 'redirect' ? '跳转' : '替换URL'}
保留: ${rule.keepParams.join(', ') || '无'} | 移除: ${rule.removeParams.join(', ') || '无'}
`;
});
}
// 面板内容
panel.innerHTML = `
🔧 清理规则管理
已有规则 (${rules.length}条)
${rulesListHTML}
✏️ 正在编辑规则,点击下方"保存修改"保存更改
➕ 添加新规则
格式说明:
name: 规则名称(可选)
domain: 域名(必需,如 example.com)
path: 路径(可选,如 /search)
keep: 保留的参数(逗号分隔,如 q,page)
remove: 移除的参数(逗号分隔,如 utm,ref)
action: 动作(replace=替换URL,redirect=跳转)
📌 其它功能设置
🎮
Minecraft Wiki重定向
将 fandom.com 的Minecraft Wiki重定向到官方Wiki
📺
B站分享链接清理
自动清理剪贴板中的B站分享链接
🤖
KIMI AI公式复制
从KIMI AI页面提取LaTeX公式并支持复制
`;
// 添加到页面
document.body.appendChild(panel);
// 启用/禁用规则按钮
panel.querySelectorAll('.toggle-rule').forEach(btn => {
btn.addEventListener('click', function() {
const index = parseInt(this.dataset.index);
const rules = getSettings().customRules || [];
if (rules[index]) {
rules[index].enabled = !rules[index].enabled;
GM_setValue('customRules', rules);
showNotification(rules[index].enabled ? '规则已启用' : '规则已禁用');
showCustomRulesPanel();
}
});
});
// 切换开关样式
panel.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const slider = this.nextElementSibling;
if (slider && slider.className === 'slider') {
slider.style.backgroundColor = this.checked ? '#4CAF50' : '#ccc';
}
});
});
// 编辑规则按钮
panel.querySelectorAll('.edit-rule').forEach(btn => {
btn.addEventListener('click', function() {
const index = parseInt(this.dataset.index);
const rules = getSettings().customRules || [];
const rule = rules[index];
if (!rule) return;
// 填充编辑区域
const ruleText = `name: ${rule.name}
domain: ${rule.domain}
path: ${rule.path || ''}
keep: ${rule.keepParams.join(',')}
remove: ${rule.removeParams.join(',')}
action: ${rule.action}`;
panel.querySelector('#newRuleText').value = ruleText;
panel.querySelector('#editingRuleIndex').value = index;
// 显示编辑模式UI
panel.querySelector('#editModeIndicator').style.display = 'block';
panel.querySelector('#addRuleTitle').textContent = '✏️ 编辑规则';
panel.querySelector('#addRuleBtn').style.display = 'none';
panel.querySelector('#saveEditBtn').style.display = 'inline-block';
panel.querySelector('#cancelEditBtn').style.display = 'inline-block';
// 滚动到编辑区域
panel.querySelector('#addRuleTitle').scrollIntoView({ behavior: 'smooth' });
});
});
// 删除规则按钮
panel.querySelectorAll('.delete-rule').forEach(btn => {
btn.addEventListener('click', function() {
const index = parseInt(this.dataset.index);
if (confirm('确定要删除这条规则吗?')) {
const rules = getSettings().customRules || [];
rules.splice(index, 1);
GM_setValue('customRules', rules);
showNotification('规则已删除');
showCustomRulesPanel();
}
});
});
// 保存修改按钮
panel.querySelector('#saveEditBtn').addEventListener('click', function() {
const ruleText = panel.querySelector('#newRuleText').value;
const index = parseInt(panel.querySelector('#editingRuleIndex').value);
const errorDiv = panel.querySelector('#ruleError');
// 验证规则格式
const errors = validateCustomRuleFormat(ruleText);
if (errors.length > 0) {
errorDiv.innerHTML = errors.join('
');
errorDiv.style.display = 'block';
return;
}
// 解析规则
const rule = parseCustomRule(ruleText);
if (!rule) {
errorDiv.innerHTML = '规则解析失败,请检查格式';
errorDiv.style.display = 'block';
return;
}
// 保留原规则的ID和启用状态
const rules = getSettings().customRules || [];
if (rules[index]) {
rule.id = rules[index].id;
rule.enabled = rules[index].enabled;
rules[index] = rule;
GM_setValue('customRules', rules);
errorDiv.style.display = 'none';
showNotification('规则修改成功');
// 重置编辑模式
panel.querySelector('#newRuleText').value = '';
panel.querySelector('#editingRuleIndex').value = '';
panel.querySelector('#editModeIndicator').style.display = 'none';
panel.querySelector('#addRuleTitle').textContent = '➕ 添加新规则';
panel.querySelector('#addRuleBtn').style.display = 'inline-block';
panel.querySelector('#saveEditBtn').style.display = 'none';
panel.querySelector('#cancelEditBtn').style.display = 'none';
showCustomRulesPanel();
}
});
// 取消编辑按钮
panel.querySelector('#cancelEditBtn').addEventListener('click', function() {
panel.querySelector('#newRuleText').value = '';
panel.querySelector('#editingRuleIndex').value = '';
panel.querySelector('#editModeIndicator').style.display = 'none';
panel.querySelector('#addRuleTitle').textContent = '➕ 添加新规则';
panel.querySelector('#addRuleBtn').style.display = 'inline-block';
panel.querySelector('#saveEditBtn').style.display = 'none';
panel.querySelector('#cancelEditBtn').style.display = 'none';
panel.querySelector('#ruleError').style.display = 'none';
});
// 添加规则按钮
panel.querySelector('.add-rule').addEventListener('click', function() {
const ruleText = panel.querySelector('#newRuleText').value;
const errorDiv = panel.querySelector('#ruleError');
// 验证规则格式
const errors = validateCustomRuleFormat(ruleText);
if (errors.length > 0) {
errorDiv.innerHTML = errors.join('
');
errorDiv.style.display = 'block';
return;
}
// 解析规则
const rule = parseCustomRule(ruleText);
if (!rule) {
errorDiv.innerHTML = '规则解析失败,请检查格式';
errorDiv.style.display = 'block';
return;
}
// 保存规则
const rules = getSettings().customRules || [];
rules.push(rule);
GM_setValue('customRules', rules);
errorDiv.style.display = 'none';
showNotification('规则添加成功');
showCustomRulesPanel();
});
// 保存设置按钮
panel.querySelector('.save-panel').addEventListener('click', function() {
const enableCustomRules = panel.querySelector('#enableCustomRules').checked;
GM_setValue('enableCustomRules', enableCustomRules);
// 保存清理模式设置
const cleanerModeNotify = panel.querySelector('#cleanerModeNotify');
if (cleanerModeNotify) {
GM_setValue('cleanerMode', cleanerModeNotify.checked ? 'notify' : 'silent');
}
// 保存其它功能设置
const enableMinecraft = panel.querySelector('#enableMinecraft').checked;
GM_setValue('enableMinecraft', enableMinecraft);
const enableClipboardCleaner = panel.querySelector('#enableClipboardCleaner').checked;
GM_setValue('enableClipboardCleaner', enableClipboardCleaner);
const enableKimi = panel.querySelector('#enableKimi').checked;
GM_setValue('enableKimi', enableKimi);
showNotification('设置已保存');
document.body.removeChild(panel);
location.reload();
});
// 关闭按钮
panel.querySelector('.close-panel').addEventListener('click', function() {
document.body.removeChild(panel);
});
}
// 显示通知
function showNotification(message) {
// 创建通知元素
const notification = document.createElement('div');
notification.textContent = message;
notification.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 15px;
border-radius: 4px;
z-index: 9999;
font-size: 14px;
transition: opacity 0.3s;
`;
// 添加到页面
document.body.appendChild(notification);
// 2秒后淡出
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 2000);
}
// 初始化
function init() {
// 记录安装日期(如果尚未记录)
if (!GM_getValue('installDate')) {
GM_setValue('installDate', Date.now());
}
// 检查运行环境并显示警告(如果需要)
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', checkEnvironmentWarning);
} else {
checkEnvironmentWarning();
}
// 检查是否需要显示清理提示
const urlCleanNotification = sessionStorage.getItem('urlCleanNotification');
if (urlCleanNotification) {
try {
const notificationData = JSON.parse(urlCleanNotification);
const settings = getSettings();
if (settings.cleanerMode === 'notify') {
// 延迟显示提示,确保页面已完全加载
setTimeout(() => {
showNotification('已自动清理~');
}, 500);
}
// 清除通知标记
sessionStorage.removeItem('urlCleanNotification');
} catch (error) {
console.error('解析清理通知数据失败:', error);
sessionStorage.removeItem('urlCleanNotification');
}
}
// 检查是否是从重试后的页面加载
const needCleanAfterLoad = sessionStorage.getItem('cleanUrlAfterLoad') === 'true';
// 如果不是重试后的页面加载,正常注册菜单和清理URL
if (!needCleanAfterLoad) {
registerMenuCommands();
checkAndCleanUrl();
} else {
// 如果是重试后的页面加载,只注册菜单,不立即清理URL
registerMenuCommands();
console.log('页面通过保留参数加载,将在加载完成后清理URL');
}
// 重置必应重试计数器
window.bingRetryCount = 0;
window.bingRetryHandled = false;
// 如果是必应搜索页面(包括国际版),处理搜索结果
if (window.location.href.includes('.bing.com/search')) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', observeSearchResults);
} else {
observeSearchResults();
}
}
// 设置B站专用剪贴板监听
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', monitorBilibiliClipboard);
} else {
monitorBilibiliClipboard();
}
// 添加KIMI AI公式处理
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupKimiFormulaHandler);
} else {
setupKimiFormulaHandler();
}
}
// 设置KIMI AI公式处理
function setupKimiFormulaHandler() {
const settings = getSettings();
if (!settings.enableKimi || !settings.enableCleaner) return;
// 只在KIMI AI页面上运行
if (!window.location.hostname.includes('kimi.moonshot.cn')) return;
// 使用MutationObserver监听DOM变化,处理动态加载的公式
const observer = new MutationObserver(debounce(() => {
addFormulaClickHandlers();
}, 500));
// 观察整个body的变化
observer.observe(document.body, {
childList: true,
subtree: true
});
// 首次运行
addFormulaClickHandlers();
// 页面卸载时清除观察器
window.addEventListener('unload', () => {
observer.disconnect();
});
}
// 为公式添加点击处理器
function addFormulaClickHandlers() {
// 查找所有KaTeX公式元素
const formulas = document.querySelectorAll('.katex-html:not([data-formula-processed])');
formulas.forEach(formula => {
// 标记为已处理
formula.setAttribute('data-formula-processed', 'true');
// 获取公式的父元素,使整个公式可点击
const formulaContainer = formula.closest('.katex') || formula;
// 添加视觉提示样式
formulaContainer.style.cursor = 'pointer';
formulaContainer.title = '点击复制公式';
// 添加悬停效果
formulaContainer.addEventListener('mouseenter', () => {
formulaContainer.style.boxShadow = '0 0 3px 1px rgba(0, 161, 214, 0.5)';
});
formulaContainer.addEventListener('mouseleave', () => {
formulaContainer.style.boxShadow = 'none';
});
// 添加点击事件
formulaContainer.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
// 使用改进的公式提取方法
let latexFormula = extractLatexFormula(formulaContainer);
// 显示复制公式提示
showFormulaPrompt(latexFormula, formulaContainer);
// 增加使用计数
incrementUsageCount();
});
});
}
// 改进的公式提取函数
function extractLatexFormula(formulaContainer) {
// 重置所有已处理标记
formulaContainer.querySelectorAll('[data-processed]').forEach(el => {
el.removeAttribute('data-processed');
});
// 首先尝试从annotation元素获取(这是最准确的来源)
const annotation = formulaContainer.querySelector('.katex-mathml annotation');
if (annotation) {
return annotation.textContent;
}
// 如果找不到annotation,从HTML结构重建LaTeX
const formula = formulaContainer.querySelector('.katex-html');
if (!formula) return '';
// 处理分式
function processFraction(element) {
const numerator = element.querySelector('.vlist-t:first-child .vlist-r:first-child .vlist > span:last-child');
const denominator = element.querySelector('.vlist-t:first-child .vlist-r:first-child .vlist > span:first-child');
if (!numerator || !denominator) return '';
// 递归处理分子和分母
const numText = processElement(numerator);
const denText = processElement(denominator);
return `\\frac{${numText}}{${denText}}`;
}
// 处理根号
function processSqrt(element) {
// 获取根号内容的容器
const baseContent = element.querySelector('.vlist-t .vlist-r .vlist > span:last-child .vlist');
if (!baseContent) {
// 尝试其他选择器
const altContent = element.querySelector('.sqrt-line + .vlist-t .vlist-r .vlist > span:last-child');
if (!altContent) return '';
return `\\sqrt{${processElement(altContent)}}`;
}
// 收集根号内所有内容
let sqrtContent = '';
const nodes = Array.from(baseContent.children);
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (!node) continue;
// 处理基本元素
if (node.classList.contains('mord') ||
node.classList.contains('mbin') ||
node.classList.contains('mrel') ||
node.classList.contains('mop')) {
// 处理上标
const sup = node.querySelector('.msupsub');
if (sup) {
const base = node.childNodes[0];
const power = sup.querySelector('.vlist-t .vlist-r .vlist > span:last-child');
if (base && power) {
sqrtContent += `${base.textContent}^{${power.textContent}}`;
continue;
}
}
// 处理普通文本
if (!node.children.length || node.children.length === 1) {
const text = node.textContent;
if (text === '±') sqrtContent += '\\pm';
else if (text === '×') sqrtContent += '\\times';
else if (text === '−') sqrtContent += '-';
else sqrtContent += text;
continue;
}
}
// 处理运算符
if (node.classList.contains('mbin') || node.classList.contains('mrel')) {
sqrtContent += node.textContent;
continue;
}
// 递归处理其他元素
const result = processElement(node);
if (result) {
if (sqrtContent &&
/[a-zA-Z0-9]}]$/.test(sqrtContent) &&
/^[a-zA-Z0-9{]/.test(result)) {
sqrtContent += ' ';
}
sqrtContent += result;
}
}
return `\\sqrt{${sqrtContent}}`;
}
// 处理上标
function processSup(element) {
const base = element.previousElementSibling;
const sup = element.querySelector('.vlist-t .vlist-r .vlist > span:last-child');
if (!base || !sup) return '';
// 递归处理基数和指数
const baseText = processElement(base);
const supText = processElement(sup);
// 检查是否需要添加括号
const needBrackets = baseText.length > 1 && !baseText.match(/^[a-zA-Z0-9]$/);
const formattedBase = needBrackets ? `{${baseText}}` : baseText;
return `${formattedBase}^{${supText}}`;
}
// 处理下标
function processSub(element) {
const base = element.previousElementSibling;
const sub = element.querySelector('.vlist-t .vlist-r .vlist > span:first-child');
if (!base || !sub) return '';
// 递归处理基数和下标
const baseText = processElement(base);
const subText = processElement(sub);
return `${baseText}_{${subText}}`;
}
// 修改递归处理元素函数
function processElement(element) {
if (!element) return '';
// 避免重复处理
if (element.dataset.processed) return '';
element.dataset.processed = 'true';
// 处理不同类型的元素
if (element.classList.contains('mfrac')) {
return processFraction(element);
}
if (element.classList.contains('sqrt')) {
return processSqrt(element);
}
// 处理上标和下标
if (element.classList.contains('msupsub')) {
const vlist = element.querySelector('.vlist-t .vlist-r .vlist');
if (!vlist) return '';
const spans = vlist.children;
let result = '';
// 检查是否有上标和下标
let sup = null;
let sub = null;
for (const span of spans) {
if (span.style.top.includes('-2.55')) {
// 这是下标
sub = span.querySelector('.sizing');
} else if (span.style.top.includes('-3.063')) {
// 这是上标
sup = span.querySelector('.sizing');
}
}
// 获取基数
const base = element.previousElementSibling;
if (!base) return '';
const baseText = processElement(base);
// 添加上标和下标
if (sup) {
result = `${baseText}^{${processElement(sup)}}`;
}
if (sub) {
result = result || baseText;
result += `_{${processElement(sub)}}`;
}
return result;
}
// 处理基本元素
if (element.classList.contains('mord') ||
element.classList.contains('mbin') ||
element.classList.contains('mrel') ||
element.classList.contains('mop')) {
if (!element.children.length) {
// 处理特殊字符
const text = element.textContent;
if (text === '±') return '\\pm';
if (text === '×') return '\\times';
if (text === '÷') return '\\div';
if (text === '·') return '\\cdot';
if (text === '−') return '-';
return text;
}
}
// 递归处理子元素
let result = '';
const children = Array.from(element.children);
for (let i = 0; i < children.length; i++) {
const child = children[i];
const childResult = processElement(child);
if (childResult) {
// 检查是否需要添加空格
if (i > 0 &&
/[a-zA-Z0-9]}]$/.test(result) &&
/^[a-zA-Z0-9{]/.test(childResult) &&
!child.classList.contains('msupsub')) {
result += ' ';
}
result += childResult;
}
}
// 如果没有子元素但有文本内容
if (!result && element.textContent) {
result = element.textContent;
}
return result;
}
// 开始处理整个公式
let result = processElement(formula);
// 如果重建失败,返回基本文本
if (!result) {
result = formula.textContent.replace(/\s+/g, ' ').trim();
}
// 清理和格式化结果
return formatLatexFormula(result);
}
// 格式化LaTeX公式
function formatLatexFormula(formula) {
return formula
// 修复可能的语法问题
.replace(/([a-zA-Z0-9])\\/g, '$1 \\')
// 处理连续的负号
.replace(/--/g, '-')
// 修复特殊命令后的空格
.replace(/\\(times|pm|div|cdot)(?=[a-zA-Z])/g, '\\$1 ')
// 修复运算符周围的空格
.replace(/\s*([=+\-*/±])\s*/g, ' $1 ')
// 修复括号周围的空格
.replace(/\s*([{}()])\s*/g, '$1')
// 修复根号内的空格
.replace(/\\sqrt\{\s+/g, '\\sqrt{')
.replace(/\s+\}/g, '}')
// 修复上标和下标格式
.replace(/\^{(\d+)}/g, '^{$1}')
.replace(/_{(\d+)}/g, '_{$1}')
// 修复多余的空格
.replace(/\s+/g, ' ')
.trim();
}
// 显示公式复制提示
function showFormulaPrompt(formula, sourceElement) {
// 检查是否已存在提示框
if (document.querySelector('.formula-prompt')) {
document.body.removeChild(document.querySelector('.formula-prompt'));
}
// 创建提示框
const prompt = document.createElement('div');
prompt.className = 'formula-prompt';
prompt.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
background-color: rgba(34, 34, 34, 0.9);
color: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
z-index: 10000;
font-size: 14px;
max-width: 300px;
transition: opacity 0.3s;
`;
// 提示框内容
prompt.innerHTML = `
数学公式
${formula}
`;
// 添加到页面
document.body.appendChild(prompt);
// 复制LaTeX按钮点击事件
prompt.querySelector('.copy-latex').addEventListener('click', function() {
GM_setClipboard(formula);
showNotification('已复制LaTeX公式');
document.body.removeChild(prompt);
});
// 复制文本按钮点击事件
prompt.querySelector('.copy-text').addEventListener('click', function() {
let textFormula = formula
// 处理分式
.replace(/\\frac\{([^{}]+)\}\{([^{}]+)\}/g, '($1)/($2)')
// 处理上标
.replace(/\^{([^{}]+)}/g, '^($1)')
.replace(/\^(\d+)(?![)}])/g, '^($1)')
// 处理下标
.replace(/_{([^{}]+)}/g, '_($1)')
.replace(/_(\d+)(?![)}])/g, '_($1)')
// 处理根号
.replace(/\\sqrt\{([^{}]+)\}/g, 'sqrt($1)')
// 处理特殊符号(确保添加空格)
.replace(/\\times(?!\s)/g, '* ')
.replace(/\\pm(?!\s)/g, '± ')
.replace(/\\div(?!\s)/g, '/ ')
.replace(/\\cdot(?!\s)/g, '* ')
// 处理希腊字母
.replace(/\\(alpha|beta|gamma|delta|epsilon|theta|pi|sigma|omega)/g, '\\$1')
// 保持运算符周围的空格
.replace(/([a-zA-Z0-9])([\+\-\*\/=±])/g, '$1 $2')
.replace(/([\+\-\*\/=±])([a-zA-Z0-9])/g, '$1 $2')
// 清理多余的空格和括号
.replace(/\(\s+/g, '(')
.replace(/\s+\)/g, ')')
.replace(/\s+/g, ' ')
.trim();
GM_setClipboard(textFormula);
showNotification('已复制文本形式');
document.body.removeChild(prompt);
});
// 关闭按钮点击事件
prompt.querySelector('.close-prompt').addEventListener('click', function() {
document.body.removeChild(prompt);
});
// 10秒后自动关闭
setTimeout(() => {
if (document.body.contains(prompt)) {
prompt.style.opacity = '0';
setTimeout(() => {
if (document.body.contains(prompt)) {
document.body.removeChild(prompt);
}
}, 300);
}
}, 10000);
}
// 显示信息遮罩层
function showInfoOverlay() {
// 检查是否已存在卡片
if (document.querySelector('.info-overlay')) {
return;
}
// 创建卡片元素
const overlay = document.createElement('div');
overlay.className = 'info-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.85); /* 半透明黑色背景 */
color: white;
padding: 20px;
z-index: 10002; /* 比其他UI高 */
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(5px); /* 背景模糊效果 */
transition: opacity 0.3s ease-in-out;
`;
// 创建内容容器
const contentContainer = document.createElement('div');
contentContainer.style.cssText = `
background: linear-gradient(135deg, rgba(30, 30, 30, 0.95) 0%, rgba(20, 20, 20, 0.98) 100%);
padding: 35px;
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
position: relative;
width: 95%;
height: 95vh;
max-height: 90vh;
overflow-y: auto;
font-size: 14px;
text-align: center;
display: flex;
flex-direction: column;
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
`;
// 动态更新的定时器ID
let intervalId = null;
let resizeListener = null;
// 更新时间和日历信息的函数
function updateDynamicInfo() {
const now = new Date();
// 格式化日期时间 (YYYY-MM-DD HH:MM:SS)
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
const formattedDateTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
const timeElement = overlay.querySelector('.info-time');
if (timeElement) {
timeElement.textContent = formattedDateTime;
}
const viewportElement = overlay.querySelector('.info-viewport');
if (viewportElement) {
viewportElement.textContent = `${window.innerWidth} x ${window.innerHeight}`;
}
// 更新日历信息
const yearEl = overlay.querySelector('.calendar-year');
if (yearEl) yearEl.textContent = year;
const monthEl = overlay.querySelector('.calendar-month');
if (monthEl) monthEl.textContent = parseInt(month);
const dayEl = overlay.querySelector('.calendar-day');
if (dayEl) dayEl.textContent = parseInt(day);
// 计算一年中的第几天
const startOfYear = new Date(year, 0, 0);
const diff = now - startOfYear;
const oneDay = 1000 * 60 * 60 * 24;
const dayOfYear = Math.floor(diff / oneDay);
const dayOfYearEl = overlay.querySelector('.calendar-day-of-year');
if (dayOfYearEl) dayOfYearEl.textContent = dayOfYear;
// 计算周数
const weekNumber = Math.ceil(dayOfYear / 7);
const weekEl = overlay.querySelector('.calendar-week');
if (weekEl) weekEl.textContent = weekNumber;
// 更新星期
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
const weekdayEl = overlay.querySelector('.calendar-weekday');
if (weekdayEl) weekdayEl.textContent = weekdays[now.getDay()];
}
// 获取设置和其他静态信息
const settings = getSettings();
const installDate = GM_getValue('installDate', Date.now());
const daysUsed = Math.ceil((Date.now() - installDate) / (1000 * 60 * 60 * 24));
// 获取运行环境信息
const env = detectEnvironment();
// 构建HTML内容
contentContainer.innerHTML = `
信息面板
📸
🔍 反馈 BUG 必看
如遇到问题,请将下方所有调试信息截图(包括清理记录),然后前往脚本猫或 Greasy Fork 反馈。
截图包含的详细信息能帮助我们更快定位和修复问题!
💻 系统信息
浏览器: ${env.browser}${env.hasClipboardIssue ? ' ⚠️' : ''}
脚本管理器: ${env.scriptManager}${env.hasClipboardIssue ? ' ⚠️' : ''}
浏览器语言: ${navigator.language}
操作系统: ${navigator.platform}
屏幕分辨率: ${screen.width} x ${screen.height}
浏览器视口:
脚本总使用: ${settings.usageCount} 次
脚本已使用: ${daysUsed} 天
${env.hasClipboardIssue ? `
⚠️ 当前环境可能存在兼容性问题,建议使用 Tampermonkey
` : ''}
📝 URL 清理记录 (最近10条)
${(() => {
const logs = getCleaningLogs().slice(0, 10);
if (logs.length === 0) {
return '
暂无清理记录
';
}
return logs.map((log, index) => `
${log.action === 'redirect' ? '🔄' : log.action === 'replaceState' ? '✏️' : '⏸️'}
${log.siteName}
${log.timestampLocal}
节省 ${log.savedChars} 字符
原始: ${(log.originalUrl || '').length > 100 ? (log.originalUrl || '').substring(0, 100) + '...' : (log.originalUrl || '无')}
清理: ${(log.cleanedUrl || '').length > 100 ? (log.cleanedUrl || '').substring(0, 100) + '...' : (log.cleanedUrl || '无')}
`).join('');
})()}
`;
// 创建关闭按钮
const closeButton = document.createElement('button');
closeButton.innerHTML = '×'; // 'X'的HTML实体
closeButton.style.cssText = `
position: absolute;
top: 15px;
right: 15px;
background: transparent;
border: none;
z-index: 10003; /* Ensure button is above content */
color: #aaa;
font-size: 28px;
font-weight: bold;
cursor: pointer;
padding: 0 10px;
line-height: 1;
transition: color 0.2s;
`;
closeButton.onmouseover = () => closeButton.style.color = '#fff';
closeButton.onmouseout = () => closeButton.style.color = '#aaa';
// 清理函数
function closeOverlay() {
clearInterval(intervalId);
window.removeEventListener('resize', resizeListener);
document.body.removeChild(overlay);
}
// 为关闭按钮添加事件
closeButton.addEventListener('click', closeOverlay);
// 为显示/隐藏调试信息按钮添加事件
// 使用setTimeout确保DOM完全加载后再绑定事件
setTimeout(() => {
const debugToggleBtn = overlay.querySelector('.debug-info-toggle');
const debugPanel = overlay.querySelector('.debug-info-panel');
if (debugToggleBtn && debugPanel) {
debugToggleBtn.addEventListener('click', function() {
const isVisible = debugPanel.style.display !== 'none';
debugPanel.style.display = isVisible ? 'none' : 'block';
debugToggleBtn.textContent = isVisible ? '显示调试信息' : '隐藏调试信息';
debugToggleBtn.style.color = isVisible ? '#757575' : '#4fc3f7';
// 如果显示调试面板,则滚动到底部
if (!isVisible) {
setTimeout(() => {
contentContainer.scrollTo({
top: contentContainer.scrollHeight,
behavior: 'smooth'
});
}, 100);
}
});
} else {
console.error('调试按钮或面板元素未找到');
}
// 为清理记录按钮添加事件监听器
const viewAllLogsBtn = overlay.querySelector('.view-all-logs-btn');
if (viewAllLogsBtn) {
viewAllLogsBtn.addEventListener('click', function() {
showCleaningLogsPanel();
});
}
const copyLogsFeedbackBtn = overlay.querySelector('.copy-logs-feedback-btn');
if (copyLogsFeedbackBtn) {
copyLogsFeedbackBtn.addEventListener('click', function() {
copyLogsForFeedback();
});
}
// 为"复制全部调试信息"按钮添加事件监听器
const copyAllDebugBtn = overlay.querySelector('.copy-all-debug-info');
if (copyAllDebugBtn) {
copyAllDebugBtn.addEventListener('click', function() {
// 收集所有调试信息
const logs = getCleaningLogs().slice(0, 10);
const settings = getSettings();
const installDate = GM_getValue('installDate', Date.now());
const daysUsed = Math.ceil((Date.now() - installDate) / (1000 * 60 * 60 * 24));
let debugInfo = '====== URL 清理脚本 - 完整调试信息 ======\n\n';
debugInfo += `导出时间: ${new Date().toLocaleString('zh-CN')}\n`;
debugInfo += `脚本版本: 2.2\n\n`;
debugInfo += '=== 运行环境 ===\n';
debugInfo += `浏览器: ${env.browser}\n`;
debugInfo += `脚本管理器: ${env.scriptManager}\n`;
debugInfo += `环境兼容性: ${env.hasClipboardIssue ? '⚠️ 可能存在问题(建议使用 Tampermonkey)' : '✅ 正常'}\n\n`;
debugInfo += '=== 系统信息 ===\n';
debugInfo += `浏览器语言: ${navigator.language}\n`;
debugInfo += `操作系统: ${navigator.platform}\n`;
debugInfo += `User Agent: ${navigator.userAgent}\n`;
debugInfo += `屏幕分辨率: ${screen.width} x ${screen.height}\n`;
debugInfo += `浏览器视口: ${window.innerWidth} x ${window.innerHeight}\n`;
debugInfo += `脚本总使用: ${settings.usageCount} 次\n`;
debugInfo += `脚本已使用: ${daysUsed} 天\n\n`;
debugInfo += '=== 页面信息 ===\n';
debugInfo += `页面标题: ${document.title}\n`;
debugInfo += `页面URL: ${window.location.href}\n\n`;
debugInfo += '=== 脚本设置 ===\n';
debugInfo += `总开关: ${settings.enableCleaner ? '启用' : '禁用'}\n`;
debugInfo += `清理模式: ${settings.cleanerMode === 'notify' ? '提示模式' : '静默模式'}\n`;
debugInfo += `必应搜索: ${settings.enableBing ? '启用' : '禁用'}\n`;
debugInfo += `B站视频: ${settings.enableBilibili ? '启用' : '禁用'}\n`;
debugInfo += `百度搜索: ${settings.enableBaidu ? '启用' : '禁用'}\n`;
debugInfo += `KIMI AI: ${settings.enableKimi ? '启用' : '禁用'}\n`;
debugInfo += `Minecraft Wiki: ${settings.enableMinecraft ? '启用' : '禁用'}\n`;
debugInfo += `360搜索: ${settings.enable360 ? '启用' : '禁用'}\n`;
debugInfo += `B站剪贴板清理: ${settings.enableClipboardCleaner ? '启用' : '禁用'}\n\n`;
debugInfo += `=== URL 清理记录 (最近 ${logs.length} 条) ===\n\n`;
if (logs.length === 0) {
debugInfo += '暂无清理记录\n';
} else {
logs.forEach((log, index) => {
debugInfo += `[记录 ${index + 1}]\n`;
debugInfo += `时间: ${log.timestampLocal || '无'}\n`;
debugInfo += `网站: ${log.siteName || '无'}\n`;
debugInfo += `页面: ${log.pageTitle || '无'}\n`;
debugInfo += `操作: ${log.action || '无'}\n`;
debugInfo += `节省: ${log.savedChars || 0} 字符\n`;
debugInfo += `原始URL: ${log.originalUrl || '无'}\n`;
debugInfo += `清理后: ${log.cleanedUrl || '无'}\n`;
debugInfo += `\n${'='.repeat(60)}\n\n`;
});
}
debugInfo += '\n提示: 请将此信息连同截图一起反馈给开发者\n';
debugInfo += '脚本猫反馈地址: https://scriptcat.org/zh-CN/script-show-page/2654/code\n';
debugInfo += 'Greasy Fork反馈地址: https://greasyfork.org.cn/zh-CN/scripts/524527\n';
// 复制到剪贴板
GM_setClipboard(debugInfo);
// 修改按钮文本提示已复制
const originalText = copyAllDebugBtn.textContent;
copyAllDebugBtn.textContent = '✅ 已复制!';
copyAllDebugBtn.style.background = 'rgba(76, 175, 80, 0.3)';
setTimeout(() => {
copyAllDebugBtn.textContent = originalText;
copyAllDebugBtn.style.background = 'rgba(255, 255, 255, 0.2)';
}, 2000);
showNotification('调试信息已复制到剪贴板');
});
}
}, 0);
// 组合元素
overlay.appendChild(contentContainer);
contentContainer.appendChild(closeButton); // 将按钮添加到内容卡片
// 添加到页面
document.body.appendChild(overlay);
// 初始时间更新
updateDynamicInfo();
// 启动动态更新定时器
intervalId = setInterval(updateDynamicInfo, 1000);
// 添加视口大小调整监听器
resizeListener = updateDynamicInfo; // 直接赋值函数
window.addEventListener('resize', resizeListener);
}
init();
})();