// ==UserScript==
// @name 网站URL简化|去除杂乱参数
// @namespace http://tampermonkey.net/
// @version 4.2
// @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
// @connect github.com
// @connect raw.githubusercontent.com
// @connect gitee.com
// @connect cdn.jsdelivr.net
// @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,
// 云端同步设置
enableCloudSync: false, // 是否启用云端同步
lastCloudSync: null, // 上次同步时间
cloudRules: [], // 云端规则缓存
cloudIndexVersion: null // 云端索引版本
};
// 获取设置
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),
// 云端同步设置
enableCloudSync: GM_getValue('enableCloudSync', defaultSettings.enableCloudSync),
lastCloudSync: GM_getValue('lastCloudSync', defaultSettings.lastCloudSync),
cloudRules: GM_getValue('cloudRules', defaultSettings.cloudRules),
cloudIndexVersion: GM_getValue('cloudIndexVersion', defaultSettings.cloudIndexVersion)
};
}
// ==================== 云端规则同步系统 ====================
const CloudSync = {
// GitHub 仓库信息
GITHUB_USER: 'xjy666a',
GITHUB_REPO: 'URL-Simpweb',
GITHUB_BRANCH: 'main',
// Gitee 仓库信息(默认)
GITEE_USER: 'small-bedrock-i',
GITEE_REPO: 'url-simpweb',
GITEE_BRANCH: 'main',
// 获取基础URL列表(Gitee默认,原版GitHub备用,jsdelivr CDN最后)
getBaseUrls() {
return [
`https://gitee.com/${this.GITEE_USER}/${this.GITEE_REPO}/raw/${this.GITEE_BRANCH}`,
`https://raw.githubusercontent.com/${this.GITHUB_USER}/${this.GITHUB_REPO}/${this.GITHUB_BRANCH}`,
`https://cdn.jsdelivr.net/gh/${this.GITHUB_USER}/${this.GITHUB_REPO}@${this.GITHUB_BRANCH}`
];
},
// 使用 GitHub raw 文件地址(无CDN缓存问题)
getBaseUrl() {
return `https://raw.githubusercontent.com/${this.GITHUB_USER}/${this.GITHUB_REPO}/${this.GITHUB_BRANCH}`;
},
// 同步状态
status: {
isSyncing: false,
lastError: null,
lastSuccess: null
},
// 检查是否应该同步(距离上次同步超过1小时)
shouldSync() {
const settings = getSettings();
if (!settings.enableCloudSync) return false;
const lastSync = settings.lastCloudSync;
if (!lastSync) return true;
const oneHour = 60 * 60 * 1000;
return (Date.now() - new Date(lastSync).getTime()) > oneHour;
},
// 执行自动同步(在清理URL时调用)
async autoSync() {
if (this.status.isSyncing) {
return { success: false, message: '正在同步中' };
}
// 执行同步
const syncResult = await this.sync();
if (syncResult.success) {
console.log('[网站URL简化|去除杂乱参数] 自动同步成功,共 ' + syncResult.count + ' 条云端规则');
}
return syncResult;
},
// 通用请求方法(支持多节点尝试)
async fetchJson(path) {
const baseUrls = this.getBaseUrls();
const fullUrls = baseUrls.map(baseUrl => `${baseUrl}/${path}`);
const sources = ['Gitee', '原版 GitHub', 'jsdelivr CDN'];
for (let i = 0; i < fullUrls.length; i++) {
const url = fullUrls[i];
const source = sources[i];
try {
console.log(`[网站URL简化|去除杂乱参数] 尝试从 ${source} 获取:${path}`);
const result = await this.fetchSingleUrl(url);
console.log(`[网站URL简化|去除杂乱参数] ${source} 请求成功`);
// 如果使用jsdelivr CDN,显示缓存提示
if (source === 'jsdelivr CDN') {
result._usedCdn = true;
}
return result;
} catch (error) {
console.warn(`[网站URL简化|去除杂乱参数] ${source} 请求失败:`, error.message);
if (i < fullUrls.length - 1) {
console.log(`[网站URL简化|去除杂乱参数] 尝试下一个节点...`);
continue;
} else {
throw new Error(`所有节点请求失败:${error.message}`);
}
}
}
},
// 单个URL请求方法
async fetchSingleUrl(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: url,
timeout: 15000,
onload: (response) => {
if (response.status === 200) {
try {
const data = JSON.parse(response.responseText);
resolve(data);
} catch (e) {
reject(new Error('JSON解析错误:' + e.message));
}
} else {
reject(new Error('请求失败,状态码:' + response.status));
}
},
onerror: () => reject(new Error('网络请求失败')),
ontimeout: () => reject(new Error('请求超时'))
});
});
},
// 获取索引文件
async fetchIndex() {
console.log('[网站URL简化|去除杂乱参数] 获取索引:index.json');
return await this.fetchJson('index.json');
},
// 获取网站的版本信息
async fetchVersions(folder) {
return await this.fetchJson(`${folder}/versions.json`);
},
// 获取网站的规则文件
async fetchRules(folder, filename) {
return await this.fetchJson(`${folder}/${filename}`);
},
// 验证规则格式
validateRules(rules) {
if (!Array.isArray(rules)) {
return { valid: false, error: '规则必须是数组' };
}
const requiredFields = ['id', 'name', 'domain'];
for (let i = 0; i < rules.length; i++) {
const rule = rules[i];
for (const field of requiredFields) {
if (!rule[field]) {
return { valid: false, error: `第 ${i + 1} 条规则缺少 ${field} 字段` };
}
}
}
return { valid: true };
},
// 执行同步
async sync(onProgress) {
if (this.status.isSyncing) {
return { success: false, message: '正在同步中...' };
}
const settings = getSettings();
if (!settings.enableCloudSync) {
return { success: false, message: '云端同步未启用' };
}
this.status.isSyncing = true;
this.status.lastError = null;
let usedCdn = false;
try {
console.log('[网站URL简化|去除杂乱参数] 开始同步云端规则...');
console.log('[网站URL简化|去除杂乱参数] 基础URL:', this.getBaseUrl());
// 1. 获取索引文件
let index;
try {
index = await this.fetchIndex();
if (index._usedCdn) {
usedCdn = true;
delete index._usedCdn;
}
} catch (e) {
throw new Error('获取索引失败:' + e.message);
}
if (!index.websites || !Array.isArray(index.websites)) {
throw new Error('索引文件格式错误:缺少 websites 字段');
}
console.log(`[网站URL简化|去除杂乱参数] 发现 ${index.websites.length} 个网站配置`);
// 2. 获取每个网站的规则
const allRules = [];
const errors = [];
let successCount = 0;
for (let i = 0; i < index.websites.length; i++) {
const website = index.websites[i];
try {
console.log(`[网站URL简化|去除杂乱参数] 正在获取 ${website.name}...`);
// 触发进度更新
if (onProgress) {
onProgress({
step: 'fetching',
current: i + 1,
total: index.websites.length,
website: website.name,
rulesCount: allRules.length
});
}
// 获取版本信息
let versions;
try {
versions = await this.fetchVersions(website.folder);
} catch (e) {
throw new Error('获取版本信息失败:' + e.message);
}
if (!versions.versions || versions.versions.length === 0) {
console.warn(`[网站URL简化|去除杂乱参数] ${website.name} 没有版本信息`);
continue;
}
// 获取最新版本
const latestVersion = versions.latestVersion;
const versionInfo = versions.versions.find(v => v.version === latestVersion);
if (!versionInfo) {
console.warn(`[网站URL简化|去除杂乱参数] ${website.name} 找不到版本 ${latestVersion}`);
continue;
}
// 获取规则文件
let rulesData;
try {
rulesData = await this.fetchRules(website.folder, versionInfo.filename);
} catch (e) {
throw new Error('获取规则文件失败:' + e.message);
}
if (rulesData.rules && Array.isArray(rulesData.rules)) {
// 标记规则来源
rulesData.rules.forEach(rule => {
rule.source = 'cloud';
rule.cloudVersion = latestVersion;
rule.cloudUpdatedAt = versionInfo.uploadedAt;
});
allRules.push(...rulesData.rules);
successCount++;
console.log(`[网站URL简化|去除杂乱参数] ${website.name} 获取成功:${rulesData.rules.length} 条规则`);
}
} catch (e) {
console.error(`[网站URL简化|去除杂乱参数] 获取 ${website.name} 失败:`, e);
errors.push(`${website.name}: ${e.message}`);
}
}
// 触发最终进度更新
if (onProgress) {
onProgress({
step: 'completed',
current: index.websites.length,
total: index.websites.length,
rulesCount: allRules.length
});
}
// 3. 验证规则
const validation = this.validateRules(allRules);
if (!validation.valid) {
throw new Error('规则验证失败:' + validation.error);
}
// 4. 保存云端规则
GM_setValue('cloudRules', allRules);
GM_setValue('lastCloudSync', new Date().toISOString());
GM_setValue('cloudIndexVersion', index.version);
this.status.lastSuccess = new Date();
console.log(`[网站URL简化|去除杂乱参数] 云端规则同步成功,共 ${allRules.length} 条规则`);
console.log(`[网站URL简化|去除杂乱参数] 成功获取 ${successCount}/${index.websites.length} 个网站`);
return {
success: true,
message: '同步成功',
count: allRules.length,
version: index.version,
successCount: successCount,
totalCount: index.websites.length,
errors: errors.length > 0 ? errors : null,
usedCdn: usedCdn
};
} catch (error) {
console.error('[网站URL简化|去除杂乱参数] 云端同步失败:', error);
this.status.lastError = error.message;
return { success: false, message: error.message };
} finally {
this.status.isSyncing = false;
}
},
// 获取合并后的规则(本地规则 + 云端规则)
getMergedRules() {
const settings = getSettings();
const localRules = settings.customRules || [];
// 为本地规则添加 source 标记
const markedLocalRules = localRules.map(r => ({
...r,
source: r.source || 'local'
}));
if (!settings.enableCloudSync) {
return markedLocalRules;
}
const cloudRules = settings.cloudRules || [];
// 合并规则:云端规则优先
const merged = [...cloudRules];
const cloudIds = new Set(cloudRules.map(r => r.id));
for (const localRule of markedLocalRules) {
if (!cloudIds.has(localRule.id)) {
merged.push(localRule);
}
}
return merged;
},
// 获取同步状态
getStatus() {
const settings = getSettings();
return {
enabled: settings.enableCloudSync,
isSyncing: this.status.isSyncing,
lastSync: settings.lastCloudSync,
lastError: this.status.lastError,
ruleCount: (settings.cloudRules || []).length,
indexVersion: settings.cloudIndexVersion
};
}
};
// ==================== 运行环境检测系统 ====================
// 检测运行环境
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 effectiveRules = CloudSync.getMergedRules();
const effectiveRulesCount = effectiveRules.length;
const rulesSourceLabel = settings.enableCloudSync && settings.cloudRules && settings.cloudRules.length > 0 ? '云端' : '本地';
GM_registerMenuCommand(
`⚙️ 清理规则和其它功能设置 (${rulesSourceLabel}${effectiveRulesCount}条)`,
() => showRulesSettingsMenu()
);
// 清理记录相关菜单
const logsCount = getCleaningLogs().length;
GM_registerMenuCommand(
`📋 复制记录用于反馈`,
() => copyLogsForFeedback()
);
GM_registerMenuCommand(
`🗑️ 清理记录管理`,
() => showCleaningLogsMenu()
);
// 云端同步子菜单
const cloudStatus = CloudSync.getStatus();
const cloudMenuText = cloudStatus.enabled
? `☁️ 云端同步 (${cloudStatus.ruleCount}条规则)`
: `☁️ 云端同步 (未启用)`;
GM_registerMenuCommand(
cloudMenuText,
() => showCloudSyncMenu()
);
}
// 显示规则设置子菜单
function showRulesSettingsMenu() {
// 创建子菜单面板
const panel = document.createElement('div');
panel.className = 'submenu-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(34, 34, 34, 0.98);
color: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
z-index: 10002;
font-size: 14px;
min-width: 300px;
text-align: center;
backdrop-filter: blur(10px);
`;
panel.innerHTML = `
⚙️ 规则设置
`;
document.body.appendChild(panel);
// 绑定事件
panel.querySelector('.rules-panel-btn').addEventListener('click', () => {
document.body.removeChild(panel);
showCustomRulesPanel();
});
panel.querySelector('.reset-rules-btn').addEventListener('click', () => {
document.body.removeChild(panel);
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();
}
});
panel.querySelector('.info-panel-btn').addEventListener('click', () => {
document.body.removeChild(panel);
showInfoOverlay();
});
panel.querySelector('.close-submenu').addEventListener('click', () => {
document.body.removeChild(panel);
});
// 点击外部关闭
panel.addEventListener('click', (e) => {
if (e.target === panel) {
document.body.removeChild(panel);
}
});
}
// 显示清理记录子菜单
function showCleaningLogsMenu() {
const logsCount = getCleaningLogs().length;
// 创建子菜单面板
const panel = document.createElement('div');
panel.className = 'submenu-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(34, 34, 34, 0.98);
color: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
z-index: 10002;
font-size: 14px;
min-width: 300px;
text-align: center;
backdrop-filter: blur(10px);
`;
panel.innerHTML = `
🗑️ 清理记录管理
${logsCount > 0 ? `
` : ''}
`;
document.body.appendChild(panel);
// 绑定事件
panel.querySelector('.export-logs-btn').addEventListener('click', () => {
document.body.removeChild(panel);
const choice = confirm('选择导出格式:\n确定 = JSON格式\n取消 = 文本格式');
if (choice) {
exportCleaningLogs();
} else {
exportCleaningLogsAsText();
}
});
if (panel.querySelector('.clear-logs-btn')) {
panel.querySelector('.clear-logs-btn').addEventListener('click', () => {
document.body.removeChild(panel);
if (confirm(`确定要清空所有 ${logsCount} 条清理记录吗?`)) {
clearCleaningLogs();
}
});
}
panel.querySelector('.close-submenu').addEventListener('click', () => {
document.body.removeChild(panel);
});
// 点击外部关闭
panel.addEventListener('click', (e) => {
if (e.target === panel) {
document.body.removeChild(panel);
}
});
}
// 显示云端同步子菜单
function showCloudSyncMenu() {
// 创建子菜单面板
const panel = document.createElement('div');
panel.className = 'submenu-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(34, 34, 34, 0.98);
color: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
z-index: 10002;
font-size: 14px;
min-width: 300px;
text-align: center;
backdrop-filter: blur(10px);
`;
panel.innerHTML = `
☁️ 云端同步
`;
document.body.appendChild(panel);
// 绑定事件
panel.querySelector('.cloud-settings-btn').addEventListener('click', () => {
document.body.removeChild(panel);
showCloudSyncPanel();
});
panel.querySelector('.sync-now-btn').addEventListener('click', async () => {
const syncButton = panel.querySelector('.sync-now-btn');
syncButton.disabled = true;
syncButton.textContent = '同步中...';
try {
showNotification('正在同步云端规则...');
const result = await CloudSync.sync();
if (result.success) {
if (result.usedCdn) {
showNotification('云端规则同步成功(使用jsdelivr CDN,规则可能存在缓存)');
} else {
showNotification('云端规则同步成功');
}
// 重新注册菜单以更新计数
registerMenuCommands();
} else {
showNotification('同步失败:' + result.message);
}
} catch (error) {
showNotification('同步错误:' + error.message);
console.error('[网站URL简化|去除杂乱参数] 同步错误:', error);
} finally {
syncButton.disabled = false;
syncButton.textContent = '立即同步云端规则';
}
});
panel.querySelector('.close-submenu').addEventListener('click', () => {
document.body.removeChild(panel);
});
// 点击外部关闭
panel.addEventListener('click', (e) => {
if (e.target === panel) {
document.body.removeChild(panel);
}
});
}
// 显示使用统计详情
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️⃣ 在反馈页面粘贴信息或上传截图
目前本脚本功能较少,你可以反馈,若可以实现,我们会尽量满足!
💡 规则问题反馈
如果您想反馈某个网页的规则问题,可以:
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);
// 检查是否需要提示云端同步(每20次提醒一次)
if (!settings.enableCloudSync) {
const promptCount = GM_getValue('cloudSyncPromptCount', 0);
if (newCount % 20 === 0 && newCount > promptCount) {
GM_setValue('cloudSyncPromptCount', newCount);
showCloudSyncPrompt();
}
}
// 检查是否需要请求评分 - 将阈值从50改为10
if (newCount >= 10 && !settings.ratingRequested) {
// 显示评分请求
showRatingPrompt();
// 标记已请求评分
GM_setValue('ratingRequested', true);
}
}
// 显示评分请求提示
function showCloudSyncPrompt() {
// 检查是否已存在提示框
if (document.querySelector('.cloud-sync-prompt')) {
return;
}
// 创建提示框
const prompt = document.createElement('div');
prompt.className = 'cloud-sync-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 = `
💡 开启云端同步
您当前使用的是本地规则。
开启云端同步可以获取最新的清理规则,无需手动更新。
每使用20次提醒一次
`;
// 添加到页面
document.body.appendChild(prompt);
// 开启云端同步按钮
prompt.querySelector('.open-cloud-settings').addEventListener('click', function() {
document.body.removeChild(prompt);
showCloudSyncPanel();
});
// 关闭按钮
prompt.querySelector('.close-cloud-prompt').addEventListener('click', function() {
document.body.removeChild(prompt);
});
}
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.enableCloudSync && CloudSync.shouldSync()) {
showNotification('正在自动同步云端规则...');
CloudSync.autoSync().then(result => {
if (result.success) {
console.log('[网站URL简化|去除杂乱参数] 清理时自动同步成功');
showNotification('云端规则已自动更新,共 ' + result.count + ' 条规则');
}
}).catch(err => {
console.error('[网站URL简化|去除杂乱参数] 自动同步出错:', err);
showNotification('自动同步失败:' + err.message);
});
}
// 先尝试应用自定义规则(包括云端规则)
const allRules = CloudSync.getMergedRules();
if (allRules && allRules.length > 0) {
const customResult = applyCustomRules(url, allRules);
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 (false && 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();
// B站分享链接清理功能暂时禁用,修复中
if (true || !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,
source: 'local'
},
{
id: 'default-baidu',
name: '百度搜索-本地默认',
domain: 'baidu.com',
path: '/s',
keepParams: ['wd', 'pn', 'si', 'gpc'],
removeParams: ['tfflag'],
action: 'redirect',
enabled: true,
source: 'local'
},
{
id: 'default-bilibili',
name: 'B站视频-本地默认',
domain: 'bilibili.com',
path: '/video/',
keepParams: [],
removeParams: ['*'],
action: 'replace',
enabled: true,
source: 'local'
},
{
id: 'default-kimi',
name: 'KIMI AI-本地默认',
domain: 'kimi.moonshot.cn',
path: '',
keepParams: [],
removeParams: ['*'],
action: 'redirect',
enabled: true,
source: 'local'
},
{
id: 'default-360',
name: '360搜索-本地默认',
domain: 'so.com',
path: '/s',
keepParams: ['q', 'pn'],
removeParams: [],
action: 'redirect',
enabled: true,
source: 'local'
},
{
id: 'default-csdn',
name: 'CSDN博客-本地默认',
domain: 'blog.csdn.net',
path: '/article/details/',
keepParams: [],
removeParams: ['*'],
action: 'replace',
enabled: true,
source: 'local'
},
{
id: 'default-sogou',
name: '搜狗搜索-本地默认',
domain: 'sogou.com',
path: '/sogou',
keepParams: ['query', 'tsn', 'page'],
removeParams: [],
action: 'redirect',
enabled: true,
source: 'local'
},
{
id: 'default-google',
name: 'Google搜索-本地默认',
domain: 'google.com',
path: '/search',
keepParams: ['q', 'start'],
removeParams: ['*'],
action: 'redirect',
enabled: true,
source: 'local'
}
];
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,
source: 'local', // 标记为本地规则
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();
// 检查是否启用了规则(本地规则需要 enableCustomRules,云端规则需要 enableCloudSync)
const hasLocalRules = rules && rules.some(r => r.source !== 'cloud');
const hasCloudRules = rules && rules.some(r => r.source === 'cloud');
// 过滤出启用的规则
let enabledRules = [];
if (rules) {
enabledRules = rules.filter(rule => {
if (!rule.enabled) return false;
if (rule.source === 'cloud') {
return settings.enableCloudSync;
} else {
return settings.enableCustomRules;
}
});
}
if (enabledRules.length === 0) {
return { url, matched: false, rule: null };
}
try {
const urlObj = new URL(url);
const hostname = urlObj.hostname.toLowerCase();
for (const rule of enabledRules) {
// 检查域名匹配
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(scrollPos = 0) {
const settings = getSettings();
// 合并本地规则和云端规则
const localRules = settings.customRules || [];
const cloudRules = settings.enableCloudSync ? (settings.cloudRules || []) : [];
const allRules = [...localRules, ...cloudRules];
// 按域名分组
const rulesByDomain = allRules.reduce((acc, rule) => {
const domain = rule.domain;
if (!acc[domain]) {
acc[domain] = [];
}
acc[domain].push(rule);
return acc;
}, {});
// 检查是否已存在面板,保存滚动位置
const existingPanel = document.querySelector('.custom-rules-panel');
if (existingPanel) {
const rulesList = existingPanel.querySelector('.rules-list');
if (rulesList) {
scrollPos = rulesList.scrollTop;
}
document.body.removeChild(existingPanel);
}
// 创建面板
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 = '';
const domains = Object.keys(rulesByDomain).sort();
if (domains.length === 0) {
rulesListHTML = '暂无清理规则
请通过"云端同步"→"云端同步设置"开启云端同步,或添加自定义规则
';
} else {
domains.forEach(domain => {
const domainRules = rulesByDomain[domain];
// 排序规则:云端规则在前,本地规则在后
const sortedRules = [...domainRules].sort((a, b) => {
if (a.source === 'cloud' && b.source !== 'cloud') return -1;
if (a.source !== 'cloud' && b.source === 'cloud') return 1;
return 0;
});
rulesListHTML += `
🌐 ${domain}
${domainRules.length} 条规则
`;
sortedRules.forEach((rule, ruleIndex) => {
const isCloudRule = rule.source === 'cloud';
const sourceBadge = isCloudRule ?
'
云端' :
'
本地';
rulesListHTML += `
${rule.name || '未命名规则'}
${sourceBadge}
${isCloudRule ? `${rule.cloudVersion || '未知'}` : ''}
${isCloudRule ? `
` : `
`}
路径: ${rule.path || '无'} | 动作: ${rule.action === 'redirect' ? '跳转' : '替换URL'}
保留: ${rule.keepParams.join(', ') || '无'} | 移除: ${rule.removeParams.join(', ') || '无'}
`;
});
rulesListHTML += `
`;
});
}
// 面板内容
panel.innerHTML = `
🔧 清理规则管理
所有规则 (${allRules.length}条)
${settings.enableCloudSync ? `
💡 提示:显示所有域名的规则,包括本地和云端规则。云端规则优先级高于本地规则。
` : ''}
📖
规则说明
本地规则:用户自己添加的规则,可以编辑和删除。
云端规则:从云端同步的规则,自动更新,优先级更高。
如何关闭网站清理:将对应域名下的所有规则都禁用即可。
${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站分享链接清理
修复中
由于网站更新,此功能暂时禁用,正在修复中
🤖
KIMI AI公式复制
修复中
从KIMI AI页面提取LaTeX公式并支持复制
`;
// 添加到页面
document.body.appendChild(panel);
// 恢复滚动位置
if (scrollPos > 0) {
const rulesList = panel.querySelector('.rules-list');
if (rulesList) {
rulesList.scrollTop = scrollPos;
}
}
// 启用/禁用规则按钮
panel.querySelectorAll('.toggle-rule').forEach(btn => {
btn.addEventListener('click', function() {
const source = this.dataset.source;
const index = parseInt(this.dataset.index);
// 保存当前滚动位置
const rulesList = panel.querySelector('.rules-list');
const currentScrollPos = rulesList ? rulesList.scrollTop : 0;
if (source === 'cloud') {
// 云端规则:修改云端规则列表
const cloudRules = GM_getValue('cloudRules', []);
if (cloudRules[index]) {
cloudRules[index].enabled = !cloudRules[index].enabled;
GM_setValue('cloudRules', cloudRules);
showNotification(cloudRules[index].enabled ? '云端规则已启用' : '云端规则已禁用');
showCustomRulesPanel(currentScrollPos);
}
} else {
// 本地规则
const rules = getSettings().customRules || [];
if (rules[index]) {
rules[index].enabled = !rules[index].enabled;
GM_setValue('customRules', rules);
showNotification(rules[index].enabled ? '规则已启用' : '规则已禁用');
showCustomRulesPanel(currentScrollPos);
}
}
});
});
// 切换开关样式
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);
// 保存当前滚动位置
const rulesList = panel.querySelector('.rules-list');
const currentScrollPos = rulesList ? rulesList.scrollTop : 0;
if (confirm('确定要删除这条规则吗?')) {
const rules = getSettings().customRules || [];
rules.splice(index, 1);
GM_setValue('customRules', rules);
showNotification('规则已删除');
showCustomRulesPanel(currentScrollPos);
}
});
});
// 反馈云端规则按钮
panel.querySelectorAll('.feedback-cloud-rule').forEach(btn => {
btn.addEventListener('click', function() {
const ruleName = decodeURIComponent(this.dataset.ruleName);
const ruleDomain = decodeURIComponent(this.dataset.ruleDomain);
const rulePath = decodeURIComponent(this.dataset.rulePath);
const ruleAction = decodeURIComponent(this.dataset.ruleAction);
const ruleKeep = decodeURIComponent(this.dataset.ruleKeep);
const ruleRemove = decodeURIComponent(this.dataset.ruleRemove);
const ruleVersion = decodeURIComponent(this.dataset.ruleVersion);
// 构建问题描述
const description = `**规则名称**:${ruleName}\n**规则版本**:${ruleVersion}\n**域名**:${ruleDomain}\n**路径**:${rulePath || '无'}\n**动作**:${ruleAction === 'redirect' ? '跳转' : '替换URL'}\n**保留参数**:${ruleKeep}\n**移除参数**:${ruleRemove}\n\n**问题描述**:\n请在此描述您遇到的问题,例如:\n- 某些参数被误删\n- 某些参数应该被保留但被移除了\n- 规则没有生效\n- 规则导致页面异常`;
// 构建复现步骤
const reproduction = `1. 打开浏览器,访问 ${ruleDomain}${rulePath || ''}\n2. 等待页面加载完成\n3. 观察地址栏链接是否被正确清理\n4. 看到什么问题(链接没有变化 / 页面报错 / 参数被误删 / 其他异常)`;
// 构建预期行为
const expected = `链接应该被正确清理,保留必要的参数,移除追踪参数。\n保留参数:${ruleKeep}\n移除参数:${ruleRemove}`;
// 构建相关链接
const url = `https://${ruleDomain}${rulePath || ''}`;
// 构建反馈标题
const feedbackTitle = `[Bug]云端规则反馈——${ruleName}(${ruleVersion})`;
// 构建GitHub Issue URL(只支持标题预填和labels)
const githubIssueUrl = `https://github.com/xjy666a/URL-Simpweb/issues/new?template=rule_feedback.yml&title=${encodeURIComponent(feedbackTitle)}&labels=rule-bug`;
// 构建Gitee Issue URL(尝试预填标题)
const giteeIssueUrl = `https://gitee.com/small-bedrock-i/url-simpweb/issues/new?template=rule_feedback.yml&title=${encodeURIComponent(feedbackTitle)}`;
// 显示选择弹窗
const choice = confirm(`即将反馈云端规则:${ruleName}\n规则版本:${ruleVersion}\n\n请选择反馈平台:\n\n点击【确定】跳转到 GitHub Issue\n点击【取消】跳转到 Gitee Issue\n\n提示:标题已自动预填,请在页面中填写剩余信息。`);
if (choice) {
// 跳转到GitHub
window.open(githubIssueUrl, '_blank');
showNotification('已打开GitHub Issue页面,请填写剩余信息');
} else {
// 跳转到Gitee
window.open(giteeIssueUrl, '_blank');
showNotification('已打开Gitee Issue页面,请填写剩余信息');
}
// 将标题复制到剪贴板
GM_setClipboard(feedbackTitle);
showNotification('反馈标题已复制到剪贴板:' + feedbackTitle);
});
});
// 保存修改按钮
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 rulesList = panel.querySelector('.rules-list');
const currentScrollPos = rulesList ? rulesList.scrollTop : 0;
// 验证规则格式
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(currentScrollPos);
}
});
// 取消编辑按钮
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('#loadTestRule').addEventListener('click', function() {
const templateRule = `name: 规则名称
domain: example.com
path: /search
keep: q,page
remove: utm,ref
action: redirect`;
panel.querySelector('#newRuleText').value = templateRule;
showNotification('规则模板已加载,请修改后点击"添加规则"');
});
// 添加规则按钮
panel.querySelector('.add-rule').addEventListener('click', function() {
const ruleText = panel.querySelector('#newRuleText').value;
const errorDiv = panel.querySelector('#ruleError');
// 保存当前滚动位置
const rulesList = panel.querySelector('.rules-list');
const currentScrollPos = rulesList ? rulesList.scrollTop : 0;
// 验证规则格式
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(currentScrollPos);
});
// 保存设置按钮
panel.querySelector('.save-panel').addEventListener('click', function() {
// 保存清理模式设置
const cleanerModeNotify = panel.querySelector('#cleanerModeNotify');
if (cleanerModeNotify) {
GM_setValue('cleanerMode', cleanerModeNotify.checked ? 'notify' : 'silent');
}
// 保存其它功能设置
const enableMinecraft = panel.querySelector('#enableMinecraft').checked;
GM_setValue('enableMinecraft', enableMinecraft);
// B站分享链接清理和KIMI AI公式复制功能暂时禁用,不保存设置
// 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 showNewUserTutorial() {
if (document.querySelector('.new-user-tutorial')) {
return;
}
const tutorial = document.createElement('div');
tutorial.className = 'new-user-tutorial';
tutorial.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
color: #333;
padding: 40px;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
z-index: 9999;
max-width: 550px;
min-width: 450px;
text-align: center;
border: 1px solid #e0e0e0;
`;
tutorial.innerHTML = `
✨
欢迎使用 网站URL简化
为了获得最佳体验,建议您完成以下步骤:
1
开启云端规则同步
2
立即同步获取最新规则
3
查看并管理清理规则
4
根据需要自定义规则
💡 如何查看清理规则:
1. 点击浏览器扩展图标
2. 选择「设置」菜单
3. 在设置面板中点击「规则设置」
4. 查看各网站的清理规则详情
`;
document.body.appendChild(tutorial);
// 开始设置按钮
tutorial.querySelector('.start-tutorial').addEventListener('click', function() {
document.body.removeChild(tutorial);
GM_setValue('newUserTutorialShown', true);
// 打开云端同步设置
showCloudSyncPanel();
});
// 稍后设置按钮
tutorial.querySelector('.skip-tutorial').addEventListener('click', function() {
document.body.removeChild(tutorial);
GM_setValue('newUserTutorialShown', true);
});
}
// 初始化
function init() {
// 初始化默认规则(确保首次使用时有默认规则)
initDefaultRules();
// 记录安装日期(如果尚未记录)
if (!GM_getValue('installDate')) {
GM_setValue('installDate', Date.now());
}
// 显示新手教程(仅首次使用)
if (!GM_getValue('newUserTutorialShown')) {
showNewUserTutorial();
}
// 自动同步云端规则(如果启用且需要同步)
if (CloudSync.shouldSync()) {
CloudSync.sync().then(result => {
if (result.success) {
console.log('[网站URL简化|去除杂乱参数] 自动同步成功,共 ' + result.count + ' 条云端规则');
} else {
console.log('[网站URL简化|去除杂乱参数] 自动同步失败:' + result.message);
}
}).catch(err => {
console.error('[网站URL简化|去除杂乱参数] 自动同步出错:', err);
});
}
// 检查运行环境并显示警告(如果需要)
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 (true || !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);
}
// 显示云端同步设置面板
function showCloudSyncPanel() {
const settings = getSettings();
const cloudStatus = CloudSync.getStatus();
// 检查是否已存在面板
if (document.querySelector('.cloud-sync-panel')) {
document.body.removeChild(document.querySelector('.cloud-sync-panel'));
}
// 创建面板
const panel = document.createElement('div');
panel.className = 'cloud-sync-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;
`;
// 格式化上次同步时间
let lastSyncText = '从未同步';
if (cloudStatus.lastSync) {
const lastSync = new Date(cloudStatus.lastSync);
const now = new Date();
const diff = now - lastSync;
if (diff < 60000) {
lastSyncText = '刚刚';
} else if (diff < 3600000) {
lastSyncText = Math.floor(diff / 60000) + ' 分钟前';
} else if (diff < 86400000) {
lastSyncText = Math.floor(diff / 3600000) + ' 小时前';
} else {
lastSyncText = lastSync.toLocaleString('zh-CN');
}
}
panel.innerHTML = `
☁️ 云端同步设置
${cloudStatus.enabled ? '✅' : '❌'}
${cloudStatus.enabled ? '云端同步已启用' : '云端同步未启用'}
${cloudStatus.enabled
? `上次同步:${lastSyncText} | 云端规则:${cloudStatus.ruleCount} 条`
: '启用后可自动获取最新清理规则'}
索引版本:
${cloudStatus.indexVersion || '未同步'}
💡 关于云端同步
• 云端规则由社区维护,包含各种网站的清理规则
• 本地规则优先于云端规则,同名规则以本地为准
• 同步间隔为1小时,避免频繁请求
• 您可以随时关闭云端同步,仅使用本地规则
`;
// 添加到页面
document.body.appendChild(panel);
// 绑定事件
panel.querySelector('.close-cloud-panel').addEventListener('click', () => {
document.body.removeChild(panel);
});
// 启用云端同步复选框 - 勾选后立即保存并更新状态显示
panel.querySelector('#enableCloudSync').addEventListener('change', (e) => {
const isEnabled = e.target.checked;
GM_setValue('enableCloudSync', isEnabled);
showNotification(isEnabled ? '云端同步已启用' : '云端同步已禁用');
// 实时更新状态显示
panel.querySelector('#cloudStatusIcon').textContent = isEnabled ? '✅' : '❌';
panel.querySelector('#cloudStatusText').textContent = isEnabled ? '云端同步已启用' : '云端同步未启用';
panel.querySelector('#cloudStatusDetail').textContent = isEnabled
? '上次同步:从未同步 | 云端规则:0 条'
: '启用后可自动获取最新清理规则';
});
panel.querySelector('#syncNowBtn').addEventListener('click', async () => {
const resultDiv = panel.querySelector('#syncResult');
const progressDiv = panel.querySelector('#syncProgress');
const progressBar = panel.querySelector('#syncProgressBar');
const progressText = panel.querySelector('#syncProgressText');
const syncButton = panel.querySelector('#syncNowBtn');
const enableCheckbox = panel.querySelector('#enableCloudSync');
// 检查是否已启用云端同步
if (!enableCheckbox.checked) {
resultDiv.style.display = 'block';
resultDiv.style.background = 'rgba(255, 152, 0, 0.2)';
resultDiv.style.color = '#ff9800';
resultDiv.innerHTML = '⚠️ 请先勾选"启用云端同步"';
return;
}
// 显示进度条,隐藏结果
progressDiv.style.display = 'block';
resultDiv.style.display = 'none';
syncButton.disabled = true;
syncButton.textContent = '同步中...';
try {
const result = await CloudSync.sync((progress) => {
if (progress.step === 'fetching') {
const percentage = Math.round((progress.current / progress.total) * 100);
progressBar.style.width = `${percentage}%`;
progressText.textContent = `正在获取 ${progress.website} (${progress.current}/${progress.total}) - 已获取 ${progress.rulesCount} 条规则`;
} else if (progress.step === 'completed') {
progressBar.style.width = '100%';
progressText.textContent = `同步完成 - 共获取 ${progress.rulesCount} 条规则`;
}
});
if (result.success) {
resultDiv.style.display = 'block';
resultDiv.style.background = 'rgba(76, 175, 80, 0.2)';
resultDiv.style.color = '#4caf50';
let html = `✅ 同步成功!
获取到 ${result.count} 条云端规则`;
if (result.version) {
html += `
索引版本: ${result.version}`;
}
if (result.successCount && result.totalCount) {
html += `
网站: ${result.successCount}/${result.totalCount}`;
}
if (result.usedCdn) {
html += `
⚠️ 本次同步使用的是jsdelivr CDN,规则可能不是最新。请稍后尝试重新同步。`;
}
if (result.errors && result.errors.length > 0) {
html += `
⚠️ ${result.errors.length} 个网站获取失败`;
}
resultDiv.innerHTML = html;
} else {
resultDiv.style.display = 'block';
resultDiv.style.background = 'rgba(244, 67, 54, 0.2)';
resultDiv.style.color = '#f44336';
resultDiv.innerHTML = `❌ 同步失败
${result.message}`;
}
} catch (e) {
resultDiv.style.display = 'block';
resultDiv.style.background = 'rgba(244, 67, 54, 0.2)';
resultDiv.style.color = '#f44336';
resultDiv.innerHTML = `❌ 同步异常
${e.message}`;
} finally {
// 隐藏进度条,启用按钮
setTimeout(() => {
progressDiv.style.display = 'none';
progressBar.style.width = '0%';
syncButton.disabled = false;
syncButton.textContent = '🔄 立即同步';
}, 500);
}
});
// 点击面板外部关闭
panel.addEventListener('click', (e) => {
if (e.target === panel) {
document.body.removeChild(panel);
}
});
}
init();
})();