您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
让整个网络世界都变成超可爱的猫娘语调喵~
// ==UserScript== // @name 🐱 全世界都要变成可爱猫猫! // @version 4.3.4 // @description 让整个网络世界都变成超可爱的猫娘语调喵~ // @author 超萌猫娘开发队 // @match *://*/* // @include *://*.bilibili.com/video/* // @include *://*.bilibili.com/anime/* // @include *://*.bilibili.com/bangumi/play/* // @exclude *://greasyfork.org/* // @exclude *://*.gov/* // @exclude *://*.edu/* // @icon https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Cat/3D/cat_3d.png // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @license MIT // @namespace https://greasyfork.org/users/1503554 // ==/UserScript== (function() { 'use strict'; // ===== 版本管理 ===== const SCRIPT_VERSION = "4.3.4"; const isVersionUpdate = GM_getValue("SCRIPT_VERSION") !== SCRIPT_VERSION; if (isVersionUpdate) { GM_setValue("SCRIPT_VERSION", SCRIPT_VERSION); console.log('🎉 猫娘脚本已更新到版本', SCRIPT_VERSION); } const UPdate_What = "修复黑名单只能添加一个的问题 😽" // ===== 增强配置系统 ===== const defaultConfig = { // 性能配置 performance: { processInterval: 5000, maxProcessingTimeSlice: 8, batchSize: 5, observerThrottle: 1000, maxRetryAttempts: 10, idleCallbackTimeout: 2000, debounceDelay: 500 }, // 功能开关 features: { affectInput: false, bilibiliMergeALinks: true, bilibiliRandomizeUserNames: true, // 用户名前缀功能开关 autoProcessNewContent: true, shadowDomSupport: true, performanceMonitoring: false, debugMode: false, smartProcessing: true, enableBlacklist: true, // 黑名单功能开关 showOriginalOnHover: false // 鼠标悬停显示原文功能开关 }, // 站点配置 sites: { excludeDomains: [ 'github.com', 'stackoverflow.com', 'google.com', 'gov.cn', 'edu.cn', 'greasyfork.org' ], bilibili: { smartPause: true, retryInterval: 1000, maxRetryDelay: 5000, commentSelector: 'bili-comment-thread-renderer', userNameSelector: '#user-name a', contentSelector: '#contents span' } }, // 黑名单配置 blacklist: { sites: [], // 格式: [{domain: 'example.com', type: 'site'|'page', url: '', expiry: timestamp, reason: ''}] defaultDuration: 24 * 60 * 60 * 1000, // 24小时 enabled: true }, // 用户偏好 - 优化描述 preferences: { cuteLevel: 'normal', // low, normal, high customEndings: [], disabledWords: [], processingMode: 'contextual', // gentle(保守替换), contextual(上下文感知), aggressive(积极替换) intelligentReplacement: true, replacementIntensity: 0.3, // 替换强度 0.1-1.0 endingFrequency: 0.3, // 结尾词频率 0.1-1.0 (降低频率) decorativeFrequency: 0.2 // 装饰符频率 0.1-1.0 }, // 统计信息 stats: { processedElements: 0, replacedWords: 0, lastActive: new Date().toISOString(), installDate: new Date().toISOString(), sessionProcessed: 0, blacklistHits: 0 // 黑名单命中次数 } }; // 加载用户配置 let userConfig = GM_getValue("catgirlConfig") || {}; // 正确的合并方式:始终创建一个新对象。 // 以 defaultConfig 为基础,然后用 userConfig 中的设置覆盖它。 // 这样既能保留用户的设置,又能在脚本更新时补充上新增的默认选项。 let CONFIG = Object.assign({}, defaultConfig, userConfig); // 如果是版本更新,将合并后的新版配置保存回去,并显示更新通知。 if (isVersionUpdate) { GM_setValue("catgirlConfig", CONFIG); showUpdateNotification(); } // ===== 扩展的可爱元素库 ===== const cuteLibrary = { endings: { low: ['喵', '呢', '哦', '啊'], normal: ['喵~', 'にゃん', '喵呜', 'nya~', '喵喵', '呢~'], high: ['喵~♪', 'にゃん♡', '喵呜~', 'nya~♡', '喵喵desu', 'にゃ♡', 'mew~', '喵♪', 'nyaa~', '喵desu~', '喵呢~', '喵哈~'] }, userPrefixes: ['🏳️⚧️', '✨', '💕', '🌸', '🎀', '🌟'], decorativePrefixes: ['✨', '💫', '⭐', '🌸', '🎀', '💎'], emotionalEndings: { excited: ['喵!', 'にゃん!', '哇喵~', '好棒喵~'], calm: ['喵~', '呢~', '嗯喵', '是这样喵'], happy: ['开心喵~', '嘻嘻喵', '哈哈喵~', '好开心喵'], confused: ['诶喵?', '嗯?喵', '咦喵~', '不懂喵'], sad: ['呜呜喵', '难过喵', '555喵', '想哭喵'] } }; // =================================================================== // ===== 网站适配器模块 (Site Adapter Modules) ===== // =================================================================== const siteModules = { // Bilibili 适配器 'bilibili.com': { name: 'Bilibili', // 需要处理的评论区选择器 commentSelectors: [ '.reply-item .reply-content', '.comment-item .comment-content', '.bili-comment-content', '#contents span', '.comment-text' ], // 用户名选择器 usernameSelectors: [ '.user-name', '.reply-author', '.comment-author', '#user-name a', '.author-name' ], // 动态内容容器(用于 MutationObserver 监控) dynamicContentContainer: '.reply-list', // 针对该网站的特殊处理函数 postProcessing: function(app) { // 将原有的 B 站链接转文本功能放在这里 if (CONFIG.features.bilibiliMergeALinks) { app.processBilibiliLinks(); // 假设 processBilibiliLinks 已被正确实现 } } }, // YouTube 适配器 (示例) 'youtube.com': { name: 'YouTube', commentSelectors: [ '#content-text', // YouTube 评论文本 'yt-formatted-string.ytd-comment-renderer' ], usernameSelectors: [ '#author-text' // YouTube 用户名 ], dynamicContentContainer: '#comments #contents', // YouTube 加载新评论的容器 // YouTube 没有像B站那样的特殊需求,所以这里留空或不定义 postProcessing: null }, // 如果未来要支持 Twitter, 可以这样添加 // 'twitter.com': { ... } }; // ===== 黑名单管理器 ===== class BlacklistManager { constructor() { this.panel = null; this.isVisible = false; } isBlacklisted() { if (!CONFIG.blacklist.enabled) return false; const currentDomain = location.hostname; const currentUrl = location.href; const now = Date.now(); for (const item of CONFIG.blacklist.sites) { // 检查是否过期 if (item.expiry && item.expiry < now) { this.removeExpiredItem(item); continue; } // 检查域名匹配 if (item.type === 'site' && currentDomain.includes(item.domain)) { CONFIG.stats.blacklistHits++; return true; } // 检查页面匹配 if (item.type === 'page' && currentUrl === item.url) { CONFIG.stats.blacklistHits++; return true; } } return false; } addToBlacklist(type, duration, reason = '') { const now = Date.now(); const expiry = duration === -1 ? null : now + duration; const domain = location.hostname; const url = type === 'page' ? location.href : ''; // 检查是否存在相同的域名,如果存在则更新而不是添加新项 const existingIndex = CONFIG.blacklist.sites.findIndex(item => { if (item.type === 'site' && type === 'site') { return item.domain === domain; } if (item.type === 'page' && type === 'page') { return item.url === url; } return false; }); if (existingIndex !== -1) { // 更新现有项目,按照最新的进行计时 CONFIG.blacklist.sites[existingIndex] = { ...CONFIG.blacklist.sites[existingIndex], expiry: expiry, reason: reason, addedAt: now }; const durationText = duration === -1 ? '永久' : this.formatDuration(duration); const typeText = type === 'site' ? '整站' : '单页面'; showToast(`已更新${typeText}黑名单时间 (${durationText})`, 'success'); } else { // 添加新项目 const item = { id: this.generateId(), domain: domain, url: url, type: type, expiry: expiry, reason: reason, addedAt: now }; CONFIG.blacklist.sites.push(item); const durationText = duration === -1 ? '永久' : this.formatDuration(duration); const typeText = type === 'site' ? '整站' : '单页面'; showToast(`已将${typeText}加入黑名单 (${durationText})`, 'success'); } GM_setValue("catgirlConfig", CONFIG); } updateDisplay() { this.displayItems(CONFIG.blacklist.sites); } removeFromBlacklist(id) { CONFIG.blacklist.sites = CONFIG.blacklist.sites.filter(item => item.id !== id); GM_setValue("catgirlConfig", CONFIG); this.updateDisplay(); showToast('已从黑名单移除', 'success'); } removeExpiredItem(item) { CONFIG.blacklist.sites = CONFIG.blacklist.sites.filter(i => i.id !== item.id); GM_setValue("catgirlConfig", CONFIG); } generateId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } formatDuration(ms) { if (ms === -1) return '永久'; const days = Math.floor(ms / (24 * 60 * 60 * 1000)); const hours = Math.floor((ms % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000)); if (days > 0) return `${days}天${hours}小时`; if (hours > 0) return `${hours}小时`; return '不到1小时'; } show() { if (!this.panel) this.create(); this.panel.style.display = 'block'; this.isVisible = true; this.updateDisplay(); } hide() { if (this.panel) { this.panel.style.display = 'none'; this.isVisible = false; } } create() { if (this.panel) return; this.panel = document.createElement('div'); this.panel.id = 'catgirl-blacklist'; this.panel.innerHTML = this.getHTML(); this.panel.style.cssText = this.getCSS(); document.body.appendChild(this.panel); this.bindEvents(); } getHTML() { return ` <div class="blacklist-header"> <h3>🚫 网站黑名单管理</h3> <button class="close-btn" data-action="close">×</button> </div> <div class="blacklist-content"> <div class="current-site-section"> <h4>🌐 当前网站操作</h4> <div class="current-site-info"> <strong>域名:</strong> <code>${location.hostname}</code><br> <strong>页面:</strong> <code>${location.pathname}</code> </div> <div class="blacklist-actions"> <div class="action-group"> <label>拉黑类型</label> <select id="blacklist-type"> <option value="site">整个网站</option> <option value="page">仅当前页面</option> </select> <small>选择要屏蔽的范围</small> </div> <div class="action-group"> <label>拉黑时长</label> <select id="blacklist-duration"> <option value="3600000">1小时</option> <option value="21600000">6小时</option> <option value="86400000">1天</option> <option value="604800000">1周</option> <option value="2592000000">1个月</option> <option value="-1">永久</option> </select> <small>选择屏蔽的持续时间</small> </div> <div class="action-group"> <label>拉黑原因</label> <input type="text" id="blacklist-reason" placeholder="可选,记录拉黑原因"> <small>记录屏蔽原因,方便后续管理</small> </div> <button id="add-to-blacklist" class="btn-danger">🚫 加入黑名单</button> </div> </div> <div class="blacklist-section"> <h4>📋 黑名单列表</h4> <div class="search-section"> <input type="text" id="blacklist-search" class="search-input" placeholder="搜索域名或原因..."> <div class="search-hint">💡 支持域名和屏蔽原因搜索</div> </div> <div id="blacklist-items" class="blacklist-scroll"></div> <div class="blacklist-stats"> <small>黑名单命中次数: <span id="blacklist-hits">${CONFIG.stats.blacklistHits}</span></small> </div> </div> <div class="blacklist-settings"> <h4>⚙️ 黑名单设置</h4> <label> <input type="checkbox" id="enable-blacklist" ${CONFIG.blacklist.enabled ? 'checked' : ''}> 启用黑名单功能 </label> <small>关闭后将忽略所有黑名单规则</small> </div> <div class="actions"> <button id="save-blacklist" class="btn-primary">💾 保存设置</button> <button id="clear-expired" class="btn-secondary">🧹 清理过期</button> </div> </div> <canvas id="cat-paw-canvas-blacklist" width="60" height="60"></canvas> `; } getCSS() { return ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 600px; max-height: 85vh; background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.3); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif; display: none; overflow: hidden; `; } // 显示黑名单项目 // 显示黑名单项目 displayItems(items, searchQuery = '') { const container = document.getElementById('blacklist-items'); if (!container) return; const now = Date.now(); const itemsHTML = items.map(item => { const isExpired = item.expiry && item.expiry < now; const timeLeft = item.expiry ? this.formatDuration(item.expiry - now) : '永久'; const addedAt = new Date(item.addedAt).toLocaleString(); let displayDomain = item.domain; let displayReason = item.reason || ''; // 修正后的高亮逻辑 if (searchQuery) { // 过滤掉无效的关键词并为正则表达式转义特殊字符 const keywords = searchQuery.split(' ').filter(k => k.trim()).map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); if (keywords.length > 0) { const regex = new RegExp(`(${keywords.join('|')})`, 'gi'); displayDomain = displayDomain.replace(regex, '<span class="search-highlight">$1</span>'); displayReason = displayReason.replace(regex, '<span class="search-highlight">$1</span>'); } } return ` <div class="blacklist-item ${isExpired ? 'expired' : ''}"> <div class="item-info"> <div class="item-domain">${displayDomain}</div> <div class="item-details"> 类型: ${item.type === 'site' ? '整站' : '单页面'} | 剩余: ${isExpired ? '已过期' : timeLeft} | 添加: ${addedAt} ${displayReason ? `<br>原因: ${displayReason}` : ''} </div> </div> <button data-remove-id="${item.id}" class="remove-btn">移除</button> </div> `; }).join(''); container.innerHTML = itemsHTML || '<div class="empty-state">暂无匹配的黑名单项目</div>'; const removeButtons = container.querySelectorAll('.remove-btn[data-remove-id]'); removeButtons.forEach(btn => { btn.onclick = () => { const id = btn.getAttribute('data-remove-id'); this.removeFromBlacklist(id); }; }); const hitsEl = document.getElementById('blacklist-hits'); if (hitsEl) hitsEl.textContent = CONFIG.stats.blacklistHits; } // 黑名单搜索功能 searchBlacklist(queryString) { const trimmedQuery = queryString?.trim() || ''; const allItems = CONFIG.blacklist.sites; if (!trimmedQuery) { // 重置搜索,显示所有项目 this.displayItems(allItems, ''); return; } // 创建竞争映射 const raceMap = new Map(); allItems.forEach((item, index) => { raceMap.set(index, [ item.domain, item.reason || '', '', 0 ]); }); // 执行搜索竞争 const keywords = trimmedQuery.trim().replace(/\s+/g, " ").split(" "); for (const [key, value] of raceMap) { let totalScore = 0; const domain = value[0].toLowerCase(); const reason = value[1].toLowerCase(); keywords.forEach((keyword) => { const keywordLower = keyword.toLowerCase(); // 域名完全匹配 if (domain === keywordLower) { totalScore += 10; } // 域名开头匹配 if (domain.startsWith(keywordLower)) { totalScore += 5; } // 域名包含关键词 const domainMatches = (domain.match(new RegExp(keywordLower, 'g')) || []).length; totalScore += domainMatches * 2; // 原因匹配 const reasonMatches = (reason.match(new RegExp(keywordLower, 'g')) || []).length; totalScore += reasonMatches; }); raceMap.set(key, [ value[0], value[1], trimmedQuery, totalScore ]); } // 排序并返回结果 const sortedResults = Array.from(raceMap.entries()) .filter(([_, value]) => value[3] > 0) .sort((a, b) => b[1][3] - a[1][3]) .map(([key]) => { const item = { ...allItems[key] }; item.user_word = trimmedQuery; return item; }); this.displayItems(sortedResults, trimmedQuery); } drawCatPaw() { const canvas = document.getElementById('cat-paw-canvas-blacklist'); if (!canvas) return; const ctx = canvas.getContext('2d'); const size = 60; // 清空画布 ctx.clearRect(0, 0, size, size); // 设置样式 ctx.fillStyle = '#ffb6c1'; // 樱花粉色 ctx.strokeStyle = '#ff69b4'; ctx.lineWidth = 2; // 绘制猫爪垫(主要部分) ctx.beginPath(); ctx.arc(30, 35, 12, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); // 绘制四个小爪垫 const pads = [ { x: 20, y: 20, size: 6 }, { x: 40, y: 20, size: 6 }, { x: 15, y: 30, size: 5 }, { x: 45, y: 30, size: 5 } ]; pads.forEach(pad => { ctx.beginPath(); ctx.arc(pad.x, pad.y, pad.size, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); }); // 添加高光效果 ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; ctx.beginPath(); ctx.arc(25, 30, 4, 0, Math.PI * 2); ctx.fill(); // 设置canvas位置 canvas.style.cssText = ` position: absolute; bottom: 15px; right: 15px; opacity: 0.6; pointer-events: none; `; } bindEvents() { // 关闭按钮 this.panel.querySelector('[data-action="close"]').onclick = () => this.hide(); // 添加到黑名单 document.getElementById('add-to-blacklist').onclick = () => { const type = document.getElementById('blacklist-type').value; const duration = parseInt(document.getElementById('blacklist-duration').value); const reason = document.getElementById('blacklist-reason').value; this.addToBlacklist(type, duration, reason); this.updateDisplay(); }; // 保存设置 document.getElementById('save-blacklist').onclick = () => { CONFIG.blacklist.enabled = document.getElementById('enable-blacklist').checked; GM_setValue("catgirlConfig", CONFIG); showToast('黑名单设置已保存', 'success'); }; // 清理过期 document.getElementById('clear-expired').onclick = () => { this.clearExpired(); }; // 搜索功能 const searchInput = document.getElementById('blacklist-search'); if (searchInput) { searchInput.oninput = (e) => { this.searchBlacklist(e.target.value); }; } // 绘制猫爪 this.drawCatPaw(); } updateDisplay() { const container = document.getElementById('blacklist-items'); if (!container) return; const now = Date.now(); const items = CONFIG.blacklist.sites.map(item => { const isExpired = item.expiry && item.expiry < now; const timeLeft = item.expiry ? this.formatDuration(item.expiry - now) : '永久'; const addedAt = new Date(item.addedAt).toLocaleString(); return ` <div class="blacklist-item ${isExpired ? 'expired' : ''}"> <div class="item-info"> <div class="item-domain">${item.domain}</div> <div class="item-details"> 类型: ${item.type === 'site' ? '整站' : '单页面'} | 剩余: ${isExpired ? '已过期' : timeLeft} | 添加: ${addedAt} ${item.reason ? `<br>原因: ${item.reason}` : ''} </div> </div> <button data-remove-id="${item.id}" class="remove-btn">移除</button> </div> `; }).join(''); container.innerHTML = items || '<div class="empty-state">暂无黑名单项目</div>'; // 绑定移除按钮事件 const removeButtons = container.querySelectorAll('.remove-btn[data-remove-id]'); removeButtons.forEach(btn => { btn.onclick = () => { const id = btn.getAttribute('data-remove-id'); this.removeFromBlacklist(id); }; }); // 更新统计 const hitsEl = document.getElementById('blacklist-hits'); if (hitsEl) hitsEl.textContent = CONFIG.stats.blacklistHits; } clearExpired() { const now = Date.now(); const before = CONFIG.blacklist.sites.length; CONFIG.blacklist.sites = CONFIG.blacklist.sites.filter(item => !item.expiry || item.expiry > now ); const after = CONFIG.blacklist.sites.length; const removed = before - after; GM_setValue("catgirlConfig", CONFIG); this.updateDisplay(); showToast(`已清理 ${removed} 个过期项目`, 'success'); } } // ===== 防抖工具类 ===== class DebounceUtils { static debounce(func, delay) { let timeoutId; return function (...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), delay); }; } static throttleWithDebounce(func, throttleMs, debounceMs) { let lastCallTime = 0; let debounceTimer; return function (...args) { const now = Date.now(); clearTimeout(debounceTimer); if (now - lastCallTime >= throttleMs) { lastCallTime = now; func.apply(this, args); } else { debounceTimer = setTimeout(() => { lastCallTime = Date.now(); func.apply(this, args); }, debounceMs); } }; } } // ===== 增强的性能工具类 ===== class EnhancedPerformanceUtils { static createTimeSliceProcessor(items, processor, options = {}) { const { batchSize = CONFIG.performance.batchSize, maxTime = CONFIG.performance.maxProcessingTimeSlice, onProgress = null, onComplete = null } = options; const processedKeys = new Set(); const uniqueItems = items.filter(item => { const key = this.getItemKey(item); if (processedKeys.has(key)) return false; processedKeys.add(key); return true; }); if (uniqueItems.length === 0) { if (onComplete) onComplete(); return; } let index = 0; let startTime = Date.now(); const processNextBatch = () => { const batchStartTime = performance.now(); let processedInBatch = 0; while (index < uniqueItems.length && processedInBatch < batchSize && (performance.now() - batchStartTime) < maxTime) { try { processor(uniqueItems[index], index, uniqueItems); } catch (error) { console.error('🐱 处理项目出错:', error); } index++; processedInBatch++; } if (onProgress) { onProgress(index, uniqueItems.length, (index / uniqueItems.length) * 100); } if (index < uniqueItems.length) { if (window.requestIdleCallback) { requestIdleCallback(processNextBatch, { timeout: CONFIG.performance.idleCallbackTimeout }); } else { setTimeout(processNextBatch, 16); } } else { const duration = Date.now() - startTime; if (CONFIG.features.debugMode) { console.log(`🎉 完成处理 ${uniqueItems.length} 个项目,耗时 ${duration}ms`); } if (onComplete) onComplete(); } }; processNextBatch(); } static getItemKey(item) { if (item && item.nodeType === Node.ELEMENT_NODE) { return `${item.tagName}-${item.textContent ? item.textContent.slice(0, 20) : ''}-${item.offsetTop || 0}`; } return String(item); } } // ===== 状态管理类 ===== class StateManager { constructor() { this.state = { isEnabled: true, currentUrl: location.href, processingQueue: new Set(), urlChangeHandlers: [], lastProcessTime: 0, bilibili: { isCompleted: false, lastProcessedUrl: '', lastProcessedTime: 0, retryCount: 0, commentObserver: null, lastCommentCount: 0 } }; } onUrlChange(handler) { if (!this.state.urlChangeHandlers) { this.state.urlChangeHandlers = []; } this.state.urlChangeHandlers.push(handler); } checkUrlChange() { const newUrl = location.href; if (newUrl !== this.state.currentUrl) { if (CONFIG.features.debugMode) { console.log('🔄 页面切换:', this.state.currentUrl, '->', newUrl); } this.state.currentUrl = newUrl; this.state.bilibili.isCompleted = false; this.state.bilibili.lastProcessedUrl = newUrl; this.state.bilibili.retryCount = 0; this.state.bilibili.lastCommentCount = 0; if (this.state.urlChangeHandlers && Array.isArray(this.state.urlChangeHandlers)) { this.state.urlChangeHandlers.forEach(handler => { try { if (typeof handler === 'function') { handler(newUrl); } } catch (error) { console.error('🐱 URL变化处理器出错:', error); } }); } return true; } return false; } shouldProcess() { const now = Date.now(); const timeSinceLastProcess = now - this.state.lastProcessTime; if (timeSinceLastProcess < 1000) { return false; } this.state.lastProcessTime = now; return true; } shouldSkipBilibiliProcessing() { if (!this.isBilibili()) return true; const { isCompleted, lastProcessedUrl, lastProcessedTime } = this.state.bilibili; const now = Date.now(); return isCompleted && lastProcessedUrl === location.href && (now - lastProcessedTime) < 30000; } markBilibiliCompleted() { this.state.bilibili.isCompleted = true; this.state.bilibili.lastProcessedUrl = location.href; this.state.bilibili.lastProcessedTime = Date.now(); } isBilibili() { return location.hostname.includes('bilibili.com'); } checkBilibiliCommentChange() { if (!this.isBilibili()) return false; const commentThreads = document.querySelectorAll('bili-comment-thread-renderer'); const currentCount = commentThreads.length; if (currentCount !== this.state.bilibili.lastCommentCount) { this.state.bilibili.lastCommentCount = currentCount; this.state.bilibili.isCompleted = false; return true; } return false; } } // ===== 增强的文本处理器 ===== class EnhancedTextProcessor { constructor() { this.processedTexts = new Set(); this.replacementStats = new Map(); this.contextAnalyzer = new ContextAnalyzer(); this.originalTexts = new WeakMap(); // 存储元素的原始文本 } isProcessed(text) { return /喵[~~呜哈呢♪♡!]|nya|にゃ"|meow|🏳️⚧️|已处理标记/i.test(text) || this.processedTexts.has(text); } getCuteEnding(context = 'normal') { const level = CONFIG.preferences.cuteLevel; const mode = CONFIG.preferences.processingMode; let endings = cuteLibrary.endings[level] || cuteLibrary.endings.normal; switch (mode) { case 'gentle': endings = endings.slice(0, Math.ceil(endings.length / 2)); break; case 'aggressive': endings = [...endings, ...cuteLibrary.emotionalEndings[context] || []]; break; case 'contextual': default: if (cuteLibrary.emotionalEndings[context]) { endings = [...endings, ...cuteLibrary.emotionalEndings[context]]; } break; } return endings[Math.floor(Math.random() * endings.length)]; } analyzeContext(text) { const excitedMarkers = /[!!??]{2,}|哇|哟|啊{2,}/; const happyMarkers = /笑|哈哈|嘻嘻|开心|快乐|爽|棒/; const sadMarkers = /哭|难过|伤心|555|呜呜|痛苦/; const angryMarkers = /生气|愤怒|气死|烦|讨厌|恶心/; const confusedMarkers = /[??]{2,}|什么|啥|诶|咦|奇怪/; if (excitedMarkers.test(text)) return 'excited'; if (happyMarkers.test(text)) return 'happy'; if (sadMarkers.test(text)) return 'sad'; if (angryMarkers.test(text)) return 'excited'; if (confusedMarkers.test(text)) return 'confused'; if (/[。.,,;;]/.test(text)) return 'calm'; return 'normal'; } processText(text, options = {}) { if (!text?.trim() || this.isProcessed(text)) return text; if (CONFIG.preferences.disabledWords.some(word => text.includes(word))) { return text; } this.processedTexts.add(text); let result = text; let replacementCount = 0; const context = this.analyzeContext(text); const cleanups = this.getCleanupRules(context); cleanups.forEach(([regex, replacement]) => { const matches = result.match(regex); if (matches) { const finalReplacement = this.getSmartReplacement(replacement, context); result = result.replace(regex, finalReplacement); replacementCount += matches.length; } }); if (CONFIG.preferences.processingMode !== 'gentle' || replacementCount > 0) { result = this.addCuteEndings(result, context); } if (CONFIG.preferences.processingMode === 'aggressive' && Math.random() < CONFIG.preferences.decorativeFrequency) { const prefix = cuteLibrary.decorativePrefixes[Math.floor(Math.random() * cuteLibrary.decorativePrefixes.length)]; result = `${prefix} ${result}`; } if (replacementCount > 0) { CONFIG.stats.replacedWords += replacementCount; CONFIG.stats.sessionProcessed += replacementCount; this.updateReplacementStats(text, result); } return result; } getSmartReplacement(baseReplacement, context) { if (!CONFIG.preferences.intelligentReplacement) return baseReplacement; const contextEnding = cuteLibrary.emotionalEndings[context]; if (contextEnding && Math.random() < 0.4) { const ending = contextEnding[Math.floor(Math.random() * contextEnding.length)]; return `${baseReplacement}${ending}`; } return baseReplacement; } addCuteEndings(text, context = 'normal') { const getCuteEnding = () => this.getCuteEnding(context); const addDesu = () => { const chance = CONFIG.preferences.cuteLevel === 'high' ? 0.4 : CONFIG.preferences.cuteLevel === 'normal' ? 0.2 : 0.1; return Math.random() < chance ? 'です' : ''; }; let probability = CONFIG.preferences.endingFrequency; switch (CONFIG.preferences.processingMode) { case 'gentle': probability *= 0.5; break; case 'aggressive': probability *= 2; break; } return text .replace(/([也知兮之者焉啉]|[啊嗯呢吧哇哟哦嘛喔咯呵哼末])([\s\p{P}]|$)/gu, (_, $1, $2) => `${getCuteEnding()}${addDesu()}${$2}`) .replace(/([的了辣])([\s\p{P}]|$)/gu, (_, $1, $2) => Math.random() < probability ? `${$1}${getCuteEnding()}${addDesu()}${$2}` : `${$1}${$2}`); } // ===== 大幅扩展的清理规则 - 针对B站和百度贴吧 ===== getCleanupRules(context = 'normal') { const baseRules = [ // ===== 极端攻击与侮辱性词汇 ===== [/操你妈|操你娘|操你全家|肏你妈|干你妈|干你娘|去你妈的|去你娘的|去你全家/gi, '去睡觉觉'], [/妈了个?逼|妈的?智障|妈的/gi, '喵喵喵'], [/狗娘养的|狗杂种|狗东西|狗逼|狗比/gi, '不太好的小家伙'], [/操你大爷|去你大爷的|你大爷的/gi, '去玩耍啦'], [/去你老师的|你全家死光|你妈死了|你妈没了/gi, '嗯...安静一点'], [/你妈妈叫你回家吃饭|你妈炸了/gi, '你妈妈叫你回家吃饭'], // ===== 性相关及不雅词汇 ===== [/鸡巴|鸡叭|鸡把|屌|吊|\bjb\b|\bJB\b|\bJj\b/gi, '小鱼干'], [/逼你|逼样|逼毛|逼崽子|什么逼|傻逼|煞逼|沙逼|装逼|牛逼|吹逼/gi, '小淘气'], [/肏|干你|草你|cao你|cao你妈|操逼|日你|日了|艹你/gi, '去玩耍啦'], [/生殖器|阴茎|阴道|性器官|做爱|啪啪|上床|嘿咻/gi, '小秘密'], // ===== B站特色脏话 ===== [/小鬼|小学生|初中生|孤儿|没爹|没妈|爹妈死了/gi, '小朋友'], [/死妈|死全家|死开|去死|死了算了/gi, '去睡觉觉'], [/脑瘫|弱智|智障|残疾|白痴|傻子|蠢货|蠢蛋/gi, '小糊涂虫'], [/废物|废柴|废狗|垃圾|拉圾|辣鸡|人渣|渣渣/gi, '要抱抱的小家伙'], [/恶心|想吐|反胃|讨厌死了|烦死了/gi, '有点不开心'], // ===== 百度贴吧常见脏话 ===== [/楼主是猪|楼主智障|楼主有病|lz有毒|楼主滚|lz滚/gi, '楼主很可爱'], [/水贴|灌水|刷屏|占楼|抢沙发|前排|火钳刘明/gi, '路过留名'], [/举报了|封号|删帖|水军|托儿|五毛|美分/gi, '认真讨论中'], [/撕逼|掐架|开撕|互喷|对骂|群嘲/gi, '友好交流'], // ===== 网络用语和缩写 ===== [/\bcnm\b|\bCNM\b|c\s*n\s*m/gi, '你好软糯'], [/\bnmsl\b|\bNMSL\b|n\s*m\s*s\s*l/gi, '你超棒棒'], [/\bmlgb\b|\bMLGB\b|m\s*l\s*g\s*b/gi, '哇好厉害'], [/tmd|TMD|t\s*m\s*d|他妈的/gi, '太萌啦'], [/wtf|WTF|w\s*t\s*f|what\s*the\s*fuck/gi, '哇好神奇'], [/\bf\*\*k|\bf\*ck|fuck|\bFC\b|\bF\*\b/gi, '哇哦'], [/\bsh\*t|shit|\bs\*\*t/gi, '小意外'], [/\bbitch|\bb\*tch|\bb\*\*\*\*/gi, '小坏蛋'], // ===== 地域攻击相关 ===== [/河南人|东北人|农村人|乡下人|山沟里|土包子/gi, '各地朋友'], [/北上广|屌丝|土豪|装富|穷逼|没钱|破产/gi, '普通人'], // ===== 游戏相关脏话 ===== [/菜鸡|菜逼|坑货|坑爹|坑队友|演员|挂逼|开挂/gi, '游戏新手'], [/noob|萌新杀手|虐菜|吊打|碾压|秒杀/gi, '游戏高手'], // ===== 饭圈和明星相关 ===== [/黑粉|脑残粉|私生饭|蹭热度|营销号|炒作|塌房/gi, '追星族'], [/爬|滚|死开|别来|有毒|拉黑|取关/gi, '不太喜欢'], // ===== 学历和职业攻击 ===== [/小学毕业|没文化|文盲|初中肄业|高中都没毕业/gi, '正在学习中'], [/打工仔|搬砖|送外卖|快递员|保安|清洁工/gi, '勤劳的人'], // ===== 年龄相关攻击 ===== [/老不死|老东西|老头子|老太婆|更年期|中年油腻/gi, '年长者'], [/熊孩子|小屁孩|幼稚|没长大|巨婴/gi, '年轻朋友'], // ===== 外貌身材攻击 ===== [/丑逼|长得丑|颜值低|矮子|胖子|瘦猴|秃头|光头/gi, '独特的人'], [/整容|假脸|网红脸|蛇精脸|锥子脸/gi, '美丽的人'], // ===== 常见口头禅和语气词 ===== [/我靠|我擦|我操|卧槽|握草|我草|尼玛|你妹/gi, '哇哦'], [/妈蛋|蛋疼|扯蛋|完蛋|滚蛋|鸡蛋|咸蛋/gi, '天哪'], [/见鬼|见了鬼|活见鬼|撞鬼了/gi, '好奇怪'], // ===== 网络流行语 ===== [/笑死我了|笑死|xswl|XSWL|笑尿了/gi, '好有趣'], [/绝绝子|yyds|YYDS|永远的神|真香|真tm香/gi, '超级棒棒'], [/emo了|emo|EMO|破防了|破大防|血压高/gi, '有点难过'], [/社死|社会性死亡|尴尬死了|丢人现眼/gi, '有点害羞'], // ===== B站弹幕常见词汇 ===== [/前方高能|高能预警|非战斗人员撤离|前排吃瓜/gi, '注意啦'], [/弹幕护体|弹幕保护|人类的本质|复读机/gi, '大家一起说'], [/鬼畜|魔性|洗脑|单曲循环|dssq|DSSQ/gi, '很有趣'], // ===== 百度贴吧表情包文字 ===== [/滑稽|斜眼笑|狗头保命|手动狗头|\[狗头\]|\[滑稽\]/gi, '嘿嘿嘿'], [/微笑|呵呵|嘿嘿|嘻嘻|哈哈|哈哈哈/gi, '开心笑'], // ===== 政治敏感和争议话题 ===== [/五毛党|美分党|公知|带路党|精神外国人|慕洋犬/gi, '不同观点的人'], [/粉红|小粉红|战狼|玻璃心|民族主义/gi, '爱国人士'], // ===== 其他常见不当用词 ===== [/有病|脑子有问题|神经病|精神病|疯子|疯了/gi, '想法特别'], [/你有毒|有毒|中毒了|下毒|毒瘤/gi, '很特殊'], [/癌症|艾滋|梅毒|性病|传染病/gi, '不舒服'], [/自杀|跳楼|上吊|服毒|割腕/gi, '要好好的'] ]; // 根据可爱程度调整规则严格度 if (CONFIG.preferences.cuteLevel === 'high') { // 高可爱程度下添加更多轻微词汇的替换 const extraRules = [ [/靠|擦|艹|草/gi, '哎呀'], [/烦|闷|郁/gi, '有点小情绪'], [/累|疲惫|困|想睡|犯困/gi, '需要休息'], [/痛|疼|难受|不舒|头疼/gi, '不太好'], [/怒|生气|愤怒|漏火|火大/gi, '有点不开心'], [/哭|难过|伤心|委屈|想哭/gi, '需要抱抱'], [/怕|害怕|恐惧|担心|紧张/gi, '有点紧张'], [/尴尬|囧|汗|无语|无奈/gi, '有点小尴尬'], [/晕|蒙|糊涂|迷茫/gi, '有点迷茫'], [/急|着急|焦虑|慌|慌张/gi, '有点小急'], ]; baseRules.push(...extraRules); } // 根据上下文返回不同强度的规则 return baseRules.map(([regex, replacement]) => { const contextEnding = this.getCuteEnding(context); return [regex, `${replacement}${contextEnding}`]; }); } updateReplacementStats(original, processed) { const key = `${original.length}:${processed.length}`; this.replacementStats.set(key, (this.replacementStats.get(key) || 0) + 1); } // 保存原始文本 storeOriginalText(element, originalText) { this.originalTexts.set(element, originalText); } // 获取原始文本 getOriginalText(element) { return this.originalTexts.get(element); } } // ===== 上下文分析器类 ===== class ContextAnalyzer { constructor() { this.patterns = { excited: /[!!??]{2,}|哇|哟|啊{2,}/, happy: /笑|哈哈|嘻嘻|开心|快乐|爽|棒/, sad: /哭|难过|伤心|555|呜呜|痛苦/, angry: /生气|愤怒|气死|烦|讨厌|恶心/, confused: /[??]{2,}|什么|啥|诶|咦|奇怪/, calm: /[。.,,;;]/ }; } analyze(text) { for (const [emotion, pattern] of Object.entries(this.patterns)) { if (pattern.test(text)) { return emotion; } } return 'normal'; } } // ===== 增强设置面板类 ===== class SettingsPanel { constructor() { this.isVisible = false; this.panel = null; } create() { if (this.panel) return; this.panel = document.createElement('div'); this.panel.id = 'catgirl-settings'; this.panel.innerHTML = this.getHTML(); this.panel.style.cssText = this.getCSS(); document.body.appendChild(this.panel); this.bindEvents(); } getHTML() { return ` <div class="settings-header"> <h3>🐱 猫娘化设置面板</h3> <button class="close-btn" data-action="close">×</button> </div> <div class="settings-content"> <div class="tab-container"> <button class="tab-btn active" data-tab="basic">基础设置</button> <button class="tab-btn" data-tab="advanced">高级设置</button> <button class="tab-btn" data-tab="control">控制选项</button> <button class="tab-btn" data-tab="stats">统计信息</button> </div> <div class="tab-content" id="basic-tab"> <div class="setting-group"> <label>🎀 可爱程度</label> <select id="cute-level"> <option value="low">低 (温和可爱)</option> <option value="normal" selected>普通 (标准可爱)</option> <option value="high">高 (超级可爱,更多替换)</option> </select> <small>控制添加可爱词汇的数量和频率,高档位会替换更多轻微词汇</small> </div> <div class="setting-group"> <label>⚙️ 猫娘化风格</label> <select id="processing-mode"> <option value="gentle">温柔模式 (仅替换攻击性词汇)</option> <option value="contextual" selected>智能模式 (分析情感,智能选择词汇)</option> <option value="aggressive">活力模式 (最大化可爱词汇和句尾)</option> </select> <small>选择猫娘化的整体风格。“智能模式”是兼顾自然与可爱的最佳选项。</small> </div> <div class="setting-group"> <label>📊 替换强度: <span id="intensity-value">30%</span></label> <input type="range" id="replacement-intensity" min="0.1" max="1.0" step="0.1" value="0.3"> <small>控制词汇替换的概率,数值越高替换越频繁</small> </div> <div class="setting-group"> <label>🎵 结尾词频率: <span id="ending-value">30%</span></label> <input type="range" id="ending-frequency" min="0.1" max="1.0" step="0.1" value="0.3"> <small>控制"喵~"等可爱结尾词的出现频率</small> </div> <div class="setting-group"> <label>✨ 装饰符频率: <span id="decorative-value">20%</span></label> <input type="range" id="decorative-frequency" min="0.1" max="1.0" step="0.1" value="0.2"> <small>控制"✨"等装饰性符号的出现频率</small> </div> </div> <div class="tab-content" id="advanced-tab" style="display: none;"> <div class="setting-group"> <label> <input type="checkbox" id="intelligent-replacement"> 🧠 启用句尾情感关联 </label> <small>开启后,替换词会根据上下文智能附带上符合当前情绪的句尾(如“好棒喵~”),让表达更生动。</small> </div> <div class="setting-group"> <label> <input type="checkbox" id="debug-mode"> 🛠 调试模式 </label> <small>显示详细的处理日志信息</small> </div> <div class="setting-group"> <label> <input type="checkbox" id="performance-monitoring"> 📊 性能监控 </label> <small>启用内存和性能监控</small> </div> <div class="setting-group"> <label>🕐 处理间隔 (毫秒): <span id="interval-value">5000</span></label> <input type="range" id="process-interval" min="1000" max="10000" step="500" value="5000"> <small>控制自动处理的时间间隔,数值越小响应越快但消耗更多资源</small> </div> <div class="setting-group"> <label>📦 批处理大小: <span id="batch-value">5</span></label> <input type="range" id="batch-size" min="3" max="20" step="1" value="5"> <small>单次处理的元素数量,数值越大处理越快但可能卡顿</small> </div> <div class="setting-group"> <label>⏱️ 防抖延迟 (毫秒): <span id="debounce-value">500</span></label> <input type="range" id="debounce-delay" min="100" max="2000" step="100" value="500"> <small>防止重复处理的延迟时间,数值越大越省资源</small> </div> </div> <div class="tab-content" id="control-tab" style="display: none;"> <div class="setting-group"> <label> <input type="checkbox" id="affect-input"> 📝 影响输入框 </label> <small>是否处理输入框和文本域中的内容</small> </div> <div class="setting-group"> <label> <input type="checkbox" id="show-original-hover"> 👆 鼠标悬停显示原文 </label> <small>鼠标悬停在处理过的文本上时显示原始内容</small> </div> <div class="setting-group"> <label> <input type="checkbox" id="enable-user-prefix"> 👤 启用用户名前缀 </label> <small>为用户名添加可爱的表情符号前缀</small> </div> <div class="setting-group"> <label> <input type="checkbox" id="bilibili-merge-links"> 🔗 B站链接转文本 </label> <small>将B站评论中的链接转换为纯文本</small> </div> <div class="setting-group"> <label> <input type="checkbox" id="auto-process-content"> 🔄 自动处理新内容 </label> <small>自动处理动态加载的新内容(通过DOM变化监听)</small> </div> <div class="setting-group"> <label> <input type="checkbox" id="shadow-dom-support"> 🌐 Shadow DOM 支持 </label> <small>处理 Shadow DOM 中的内容(如B站评论区)</small> </div> <div class="setting-group"> <label> <input type="checkbox" id="smart-processing"> 💡 启用高级情感分析引擎 </label> <small>脚本的核心智能开关。开启后才能启用所有与上下文、情感相关的分析功能。为获得最佳体验,请保持开启。</small> </div> <div class="setting-group"> <label>🚫 排除词汇</label> <textarea id="disabled-words" placeholder="输入不想被替换的词汇,每行一个" rows="3"></textarea> <small>这些词汇不会被猫娘化处理</small> </div> </div> <div class="tab-content" id="stats-tab" style="display: none;"> <div class="stats-grid"> <div class="stat-item"> <div class="stat-number" id="processed-count">0</div> <div class="stat-label">已处理元素</div> </div> <div class="stat-item"> <div class="stat-number" id="replaced-count">0</div> <div class="stat-label">已替换词汇</div> </div> <div class="stat-item"> <div class="stat-number" id="session-count">0</div> <div class="stat-label">本次会话</div> </div> <div class="stat-item"> <div class="stat-number" id="blacklist-hits-count">0</div> <div class="stat-label">黑名单命中</div> </div> </div> <div class="info-section"> <h4>📅 系统信息</h4> <p><strong>版本:</strong> ${SCRIPT_VERSION}</p> <p><strong>安装时间:</strong> <span id="install-date">获取中...</span></p> <p><strong>最后活动:</strong> <span id="last-active">获取中...</span></p> <p><strong>当前网站:</strong> ${location.hostname}</p> <p><strong>运行状态:</strong> <span id="running-status">检测中...</span></p> </div> <div class="performance-section"> <h4>⚡ 动态信息</h4> <div id="performance-info"> <p>当前页面处理元素数: <span id="session-elements">计算中...</span></p> <p>黑名单规则命中数: <span id="blacklist-hits-info">计算中...</span></p> </div> </div> </div> <div class="actions"> <button id="save-settings" class="btn-primary">💾 保存设置</button> <button id="reset-settings" class="btn-warning">🔄 重置设置</button> <button id="clear-cache" class="btn-secondary">🧹 清理缓存</button> </div> </div> `; } getCSS() { return ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 600px; max-height: 85vh; background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.3); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif; display: none; overflow: hidden; `; } bindEvents() { // 关闭按钮 const closeBtn = this.panel.querySelector('[data-action="close"]'); if (closeBtn) { closeBtn.onclick = () => this.hide(); } // 标签切换 const tabBtns = this.panel.querySelectorAll('.tab-btn'); tabBtns.forEach(btn => { btn.onclick = () => this.switchTab(btn.dataset.tab); }); // 滑块事件 this.bindSliderEvents(); // 保存设置 const saveBtn = document.getElementById('save-settings'); if (saveBtn) { saveBtn.onclick = () => { this.saveSettings(); this.hide(); showToast('设置已保存喵~ ✨', 'success'); }; } // 重置设置 const resetBtn = document.getElementById('reset-settings'); if (resetBtn) { resetBtn.onclick = () => { if (confirm('确定要重置所有设置吗?这将清除所有自定义配置。')) { this.resetSettings(); showToast('设置已重置喵~ 🔄', 'info'); } }; } // 清理缓存 const clearBtn = document.getElementById('clear-cache'); if (clearBtn) { clearBtn.onclick = () => { this.clearCache(); showToast('缓存已清理喵~ 🧹', 'info'); }; } } bindSliderEvents() { const sliders = [ { id: 'replacement-intensity', valueId: 'intensity-value', isPercent: true }, { id: 'ending-frequency', valueId: 'ending-value', isPercent: true }, { id: 'decorative-frequency', valueId: 'decorative-value', isPercent: true }, { id: 'process-interval', valueId: 'interval-value', isPercent: false }, { id: 'batch-size', valueId: 'batch-value', isPercent: false }, { id: 'debounce-delay', valueId: 'debounce-value', isPercent: false } ]; sliders.forEach(({ id, valueId, isPercent }) => { const slider = document.getElementById(id); const valueSpan = document.getElementById(valueId); if (slider && valueSpan) { slider.oninput = (e) => { const value = parseFloat(e.target.value); valueSpan.textContent = isPercent ? `${Math.round(value * 100)}%` : value; }; } }); } switchTab(tabName) { // 切换按钮状态 this.panel.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('active'); }); this.panel.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); // 切换内容 this.panel.querySelectorAll('.tab-content').forEach(content => { content.style.display = 'none'; }); const targetTab = document.getElementById(`${tabName}-tab`); if (targetTab) { targetTab.style.display = 'block'; } } show() { if (!this.panel) this.create(); this.loadCurrentSettings(); this.panel.style.display = 'block'; this.isVisible = true; this.updateStats(); } hide() { if (this.panel) { this.panel.style.display = 'none'; this.isVisible = false; } } loadCurrentSettings() { // 基础设置 const elements = { 'cute-level': CONFIG.preferences.cuteLevel, 'processing-mode': CONFIG.preferences.processingMode, 'replacement-intensity': CONFIG.preferences.replacementIntensity, 'ending-frequency': CONFIG.preferences.endingFrequency, 'decorative-frequency': CONFIG.preferences.decorativeFrequency }; Object.entries(elements).forEach(([id, value]) => { const element = document.getElementById(id); if (element) { element.value = value; } }); // 复选框设置 const checkboxes = { 'affect-input': CONFIG.features.affectInput, 'enable-user-prefix': CONFIG.features.bilibiliRandomizeUserNames, 'bilibili-merge-links': CONFIG.features.bilibiliMergeALinks, 'show-original-hover': CONFIG.features.showOriginalOnHover, 'auto-process-content': CONFIG.features.autoProcessNewContent, 'shadow-dom-support': CONFIG.features.shadowDomSupport, 'smart-processing': CONFIG.features.smartProcessing, 'intelligent-replacement': CONFIG.preferences.intelligentReplacement, 'debug-mode': CONFIG.features.debugMode, 'performance-monitoring': CONFIG.features.performanceMonitoring }; Object.entries(checkboxes).forEach(([id, checked]) => { const element = document.getElementById(id); if (element) element.checked = checked; }); // 高级设置 (滑块) const advancedElements = { 'replacement-intensity': CONFIG.preferences.replacementIntensity, 'ending-frequency': CONFIG.preferences.endingFrequency, 'decorative-frequency': CONFIG.preferences.decorativeFrequency, 'process-interval': CONFIG.performance.processInterval, 'batch-size': CONFIG.performance.batchSize, 'debounce-delay': CONFIG.performance.debounceDelay }; const sliderToValueIdMap = { 'replacement-intensity': 'intensity-value', 'ending-frequency': 'ending-value', 'decorative-frequency': 'decorative-value', 'process-interval': 'interval-value', 'batch-size': 'batch-value', 'debounce-delay': 'debounce-value' }; Object.entries(advancedElements).forEach(([id, value]) => { const slider = document.getElementById(id); if (slider) { slider.value = value; // 设置滑块位置 const valueId = sliderToValueIdMap[id]; const valueSpan = document.getElementById(valueId); if (valueSpan) { const isPercent = ['replacement-intensity', 'ending-frequency', 'decorative-frequency'].includes(id); valueSpan.textContent = isPercent ? `${Math.round(parseFloat(value) * 100)}%` : value; } } }); // 排除词汇 const disabledWordsEl = document.getElementById('disabled-words'); if (disabledWordsEl) { disabledWordsEl.value = CONFIG.preferences.disabledWords.join('\n'); } } saveSettings() { // 基础设置 CONFIG.preferences.cuteLevel = document.getElementById('cute-level')?.value || CONFIG.preferences.cuteLevel; CONFIG.preferences.processingMode = document.getElementById('processing-mode')?.value || CONFIG.preferences.processingMode; CONFIG.preferences.replacementIntensity = parseFloat(document.getElementById('replacement-intensity')?.value || CONFIG.preferences.replacementIntensity); CONFIG.preferences.endingFrequency = parseFloat(document.getElementById('ending-frequency')?.value || CONFIG.preferences.endingFrequency); CONFIG.preferences.decorativeFrequency = parseFloat(document.getElementById('decorative-frequency')?.value || CONFIG.preferences.decorativeFrequency); // 复选框设置 CONFIG.features.affectInput = document.getElementById('affect-input')?.checked || false; CONFIG.features.bilibiliRandomizeUserNames = document.getElementById('enable-user-prefix')?.checked || false; CONFIG.features.bilibiliMergeALinks = document.getElementById('bilibili-merge-links')?.checked || false; CONFIG.features.showOriginalOnHover = document.getElementById('show-original-hover')?.checked || false; CONFIG.features.autoProcessNewContent = document.getElementById('auto-process-content')?.checked || false; CONFIG.features.shadowDomSupport = document.getElementById('shadow-dom-support')?.checked || false; CONFIG.features.smartProcessing = document.getElementById('smart-processing')?.checked || false; CONFIG.preferences.intelligentReplacement = document.getElementById('intelligent-replacement')?.checked || false; CONFIG.features.debugMode = document.getElementById('debug-mode')?.checked || false; CONFIG.features.performanceMonitoring = document.getElementById('performance-monitoring')?.checked || false; // 高级设置 CONFIG.performance.processInterval = parseInt(document.getElementById('process-interval')?.value || CONFIG.performance.processInterval); CONFIG.performance.batchSize = parseInt(document.getElementById('batch-size')?.value || CONFIG.performance.batchSize); CONFIG.performance.debounceDelay = parseInt(document.getElementById('debounce-delay')?.value || CONFIG.performance.debounceDelay); // 排除词汇 const disabledWordsEl = document.getElementById('disabled-words'); if (disabledWordsEl) { CONFIG.preferences.disabledWords = disabledWordsEl.value .split('\n') .map(word => word.trim()) .filter(word => word.length > 0); } GM_setValue("catgirlConfig", CONFIG); } resetSettings() { CONFIG = Object.assign({}, defaultConfig); CONFIG.stats = Object.assign({}, defaultConfig.stats, { installDate: GM_getValue("catgirlConfig")?.stats?.installDate || new Date().toISOString() }); GM_setValue("catgirlConfig", CONFIG); this.loadCurrentSettings(); } clearCache() { if (window.catgirlApp && window.catgirlApp.clearCache) { window.catgirlApp.clearCache(); } } updateStats() { const stats = { 'processed-count': CONFIG.stats.processedElements, 'replaced-count': CONFIG.stats.replacedWords, 'session-count': CONFIG.stats.sessionProcessed, 'blacklist-hits-count': CONFIG.stats.blacklistHits, // 新增的动态信息 'session-elements': CONFIG.stats.sessionProcessed, // 复用会话处理数 'blacklist-hits-info': CONFIG.stats.blacklistHits }; // ... 后续代码不变 Object.entries(stats).forEach(([id, value]) => { const element = document.getElementById(id); if (element) element.textContent = value; }); // 更新运行状态 const statusEl = document.getElementById('running-status'); if (statusEl && window.catgirlApp) { const isRunning = window.catgirlApp.app.isRunning; statusEl.textContent = isRunning ? '运行中 ✅' : '已暂停 ⏸️'; statusEl.style.color = isRunning ? '#28a745' : '#ffc107'; } // 更新时间信息 const installEl = document.getElementById('install-date'); if (installEl) { installEl.textContent = new Date(CONFIG.stats.installDate).toLocaleString(); } const activeEl = document.getElementById('last-active'); if (activeEl) { activeEl.textContent = new Date(CONFIG.stats.lastActive).toLocaleString(); } } } // ===== 主应用类 ===== class CatgirlApp { constructor() { this.stateManager = new StateManager(); this.textProcessor = new EnhancedTextProcessor(); this.settingsPanel = new SettingsPanel(); this.blacklistManager = new BlacklistManager(); this.processedElements = new WeakSet(); this.isRunning = false; this.intervalId = null; this.observer = null; this.lastProcessHash = ''; this.processLock = false; this.bilibiliCommentObserver = null; this.isBlacklisted = false; // 添加黑名单状态标记 } async initialize() { // 检查黑名单 this.isBlacklisted = this.blacklistManager.isBlacklisted(); if (this.isBlacklisted) { if (CONFIG.features.debugMode) { console.log('🐱 当前网站已被加入黑名单,不启动喵~'); } // 即使在黑名单中也注册菜单命令,但功能受限 this.registerLimitedMenuCommands(); return; } if (this.shouldExclude()) { if (CONFIG.features.debugMode) { console.log('🐱 域名已排除,不启动喵~'); } return; } console.log('🐱 增强版猫娘化系统启动喵~'); this.registerMenuCommands(); await this.waitForDOMReady(); this.stateManager.onUrlChange((newUrl) => { setTimeout(() => { if (location.href === newUrl) { this.processPage(); } }, CONFIG.performance.debounceDelay); }); this.start(); } shouldExclude() { return CONFIG.sites.excludeDomains.some(domain => location.hostname.includes(domain) ); } registerMenuCommands() { GM_registerMenuCommand("🐱 设置面板", () => this.settingsPanel.show()); GM_registerMenuCommand("🚫 网站黑名单", () => this.blacklistManager.show()); //GM_registerMenuCommand("⛔ 屏蔽当前网站", () => this.showBlockSiteDialog()); GM_registerMenuCommand("🔄 重新处理", () => this.handleReprocess()); //GM_registerMenuCommand("📊 显示统计", () => this.showStats()); GM_registerMenuCommand("🧹 清理缓存", () => this.clearCache()); } // 为黑名单网站注册受限菜单 registerLimitedMenuCommands() { GM_registerMenuCommand("🐱 设置面板", () => this.settingsPanel.show()); GM_registerMenuCommand("🚫 网站黑名单", () => this.blacklistManager.show()); GM_registerMenuCommand("🔄 重新处理", () => { showToast('该网站被列为黑名单内容,如需修改请到面板里调整', 'warning', 4000); }); GM_registerMenuCommand("📊 显示统计", () => this.showStats()); } handleReprocess() { if (this.isBlacklisted) { showToast('该网站被列为黑名单内容,如需修改请到面板里调整', 'warning', 4000); return; } this.restart(); } showBlockSiteDialog() { this.showBlockSiteUI(); } showBlockSiteUI() { if (document.getElementById('catgirl-block-ui')) return; const blockUI = document.createElement('div'); blockUI.id = 'catgirl-block-ui'; blockUI.innerHTML = this.getBlockSiteHTML(); blockUI.style.cssText = this.getBlockSiteCSS(); document.body.appendChild(blockUI); this.bindBlockSiteEvents(blockUI); this.drawCatPaw(blockUI); } getBlockSiteHTML() { const domain = location.hostname; return ` <div class="block-site-header"> <h3>🚫 屏蔽网站 ${domain}</h3> <button class="close-btn" data-action="close-block">×</button> </div> <div class="block-site-content"> <div class="site-info"> <div class="site-icon">🌐</div> <div class="site-details"> <div class="site-domain">${domain}</div> <div class="site-path">${location.pathname}</div> </div> </div> <div class="block-options"> <div class="option-group"> <label class="cute-label"> <input type="radio" name="block-type" value="site" checked> <span class="radio-custom"></span> <span class="option-text">🏠 整个网站</span> </label> <small>屏蔽整个 ${domain} 域名</small> </div> <div class="option-group"> <label class="cute-label"> <input type="radio" name="block-type" value="page"> <span class="radio-custom"></span> <span class="option-text">📄 仅当前页面</span> </label> <small>只屏蔽当前访问的页面</small> </div> </div> <div class="duration-selector"> <label class="cute-label-block">⏰ 屏蔽时长</label> <select id="block-duration" class="cute-select"> <option value="3600000">1小时</option> <option value="21600000">6小时</option> <option value="86400000" selected>1天</option> <option value="604800000">1周</option> <option value="2592000000">1个月</option> <option value="-1">永久</option> </select> </div> <div class="reason-input"> <label class="cute-label-block">💭 屏蔽原因</label> <input type="text" id="block-reason" class="cute-input" placeholder="记录屏蔽原因,方便管理喵~"> </div> <div class="block-actions"> <button id="confirm-block" class="btn-block-confirm">🚫 确认屏蔽</button> <button id="cancel-block" class="btn-block-cancel">❌ 取消</button> </div> </div> <canvas id="cat-paw-canvas" width="60" height="60"></canvas> `; } getBlockSiteCSS() { return ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 420px; background: linear-gradient(135deg, #fff5f8 0%, #ffeef5 100%); border-radius: 20px; box-shadow: 0 15px 35px rgba(255, 182, 193, 0.3); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif; overflow: hidden; border: 2px solid #ffb6c1; `; } bindBlockSiteEvents(blockUI) { blockUI.querySelector('[data-action="close-block"]').onclick = () => { document.body.removeChild(blockUI); }; blockUI.querySelector('#cancel-block').onclick = () => { document.body.removeChild(blockUI); }; blockUI.querySelector('#confirm-block').onclick = () => { const type = blockUI.querySelector('input[name="block-type"]:checked').value; const duration = parseInt(blockUI.querySelector('#block-duration').value); const reason = blockUI.querySelector('#block-reason').value.trim() || '用户手动屏蔽'; this.blacklistManager.addToBlacklist(type, duration, reason); document.body.removeChild(blockUI); if (type === 'site') { this.stop(); this.isBlacklisted = true; showToast('网站已屏蔽,脚本已停止运行喵~', 'info', 5000); } }; } showStats() { if (document.getElementById('catgirl-stats-ui')) return; const statsUI = document.createElement('div'); statsUI.id = 'catgirl-stats-ui'; statsUI.innerHTML = this.getStatsHTML(); statsUI.style.cssText = this.getStatsCSS(); document.body.appendChild(statsUI); this.bindStatsEvents(statsUI); this.drawCatPaw(statsUI); } getStatsHTML() { const processedElements = (CONFIG.stats.processedElements || 0); const replacedWords = (CONFIG.stats.replacedWords || 0); const sessionProcessed = (CONFIG.stats.sessionProcessed || 0); const blacklistHits = (CONFIG.stats.blacklistHits || 0); const installDate = CONFIG.stats.installDate || new Date().toISOString(); const lastActive = CONFIG.stats.lastActive || new Date().toISOString(); return ` <div class="stats-header"> <h3>📊 猫娘化统计面板</h3> <button class="close-btn" data-action="close-stats">×</button> </div> <div class="stats-content"> <div class="stats-cards"> <div class="stat-card"> <div class="stat-icon">🔄</div> <div class="stat-info"> <div class="stat-number">${processedElements.toLocaleString()}</div> <div class="stat-label">已处理元素</div> </div> </div> <div class="stat-card"> <div class="stat-icon">✨</div> <div class="stat-info"> <div class="stat-number">${replacedWords.toLocaleString()}</div> <div class="stat-label">已替换词汇</div> </div> </div> <div class="stat-card"> <div class="stat-icon">🎯</div> <div class="stat-info"> <div class="stat-number">${sessionProcessed.toLocaleString()}</div> <div class="stat-label">本次会话</div> </div> </div> <div class="stat-card"> <div class="stat-icon">🚫</div> <div class="stat-info"> <div class="stat-number">${blacklistHits.toLocaleString()}</div> <div class="stat-label">黑名单命中</div> </div> </div> </div> <div class="system-info"> <div class="info-row"> <span class="info-label">🐱 当前版本:</span> <span class="info-value">${SCRIPT_VERSION}</span> </div> <div class="info-row"> <span class="info-label">📅 安装时间:</span> <span class="info-value">${new Date(installDate).toLocaleString()}</span> </div> <div class="info-row"> <span class="info-label">⏰ 最后活动:</span> <span class="info-value">${new Date(lastActive).toLocaleString()}</span> </div> <div class="info-row"> <span class="info-label">🌐 当前网站:</span> <span class="info-value">${location.hostname}</span> </div> <div class="info-row"> <span class="info-label">⚡ 运行状态:</span> <span class="info-value status-${this.isBlacklisted ? 'blocked' : (this.isRunning ? 'running' : 'paused')}"> ${this.isBlacklisted ? '已屏蔽 🚫' : (this.isRunning ? '运行中 ✅' : '已暂停 ⏸️')} </span> </div> </div> <div class="stats-actions"> <button id="refresh-stats" class="btn-stats-refresh">🔄 刷新统计</button> <button id="export-stats" class="btn-stats-export">📊 导出数据</button> </div> </div> <canvas id="cat-paw-canvas-stats" width="60" height="60"></canvas> `; } getStatsCSS() { return ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 500px; background: linear-gradient(135deg, #f0f8ff 0%, #e6f3ff 100%); border-radius: 20px; box-shadow: 0 15px 35px rgba(135, 206, 250, 0.3); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif; overflow: hidden; border: 2px solid #87ceeb; `; } bindStatsEvents(statsUI) { statsUI.querySelector('[data-action="close-stats"]').onclick = () => { document.body.removeChild(statsUI); }; statsUI.querySelector('#refresh-stats').onclick = () => { document.body.removeChild(statsUI); setTimeout(() => this.showStats(), 100); }; statsUI.querySelector('#export-stats').onclick = () => { const data = { version: SCRIPT_VERSION, stats: CONFIG.stats, exportTime: new Date().toISOString(), website: location.hostname }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `catgirl-stats-${location.hostname}-${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); showToast('统计数据已导出喵~ 📊', 'success'); }; } drawCatPaw() { const canvas = document.getElementById('cat-paw-canvas-blacklist'); if (!canvas) return; const ctx = canvas.getContext('2d'); const size = 60; // 清空画布 ctx.clearRect(0, 0, size, size); // 设置样式 ctx.fillStyle = '#ffb6c1'; // 樱花粉色 ctx.strokeStyle = '#ff69b4'; ctx.lineWidth = 2; // 绘制猫爪垫(主要部分) ctx.beginPath(); ctx.arc(30, 35, 12, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); // 绘制四个小爪垫 const pads = [ { x: 20, y: 20, size: 6 }, { x: 40, y: 20, size: 6 }, { x: 15, y: 30, size: 5 }, { x: 45, y: 30, size: 5 } ]; pads.forEach(pad => { ctx.beginPath(); ctx.arc(pad.x, pad.y, pad.size, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); }); // 添加高光效果 ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; ctx.beginPath(); ctx.arc(25, 30, 4, 0, Math.PI * 2); ctx.fill(); // 设置canvas位置 canvas.style.cssText = ` position: absolute; bottom: 15px; right: 15px; opacity: 0.6; pointer-events: none; `; } async start() { if (this.isRunning || this.isBlacklisted) return; this.isRunning = true; setTimeout(() => { if (this.isRunning && !this.isBlacklisted) { this.processPage(); } }, 1000); if (CONFIG.performance.processInterval > 0) { const debouncedProcess = DebounceUtils.throttleWithDebounce( () => this.conditionalProcess(), CONFIG.performance.processInterval, CONFIG.performance.debounceDelay ); this.intervalId = setInterval(debouncedProcess, CONFIG.performance.processInterval); } this.setupMutationObserver(); if (this.stateManager.isBilibili()) { this.setupBilibiliCommentObserver(); } if (CONFIG.features.debugMode) { console.log('🎀 猫娘化系统已启动完成喵~'); } } conditionalProcess() { if (!this.isRunning || this.processLock || this.isBlacklisted) return; const urlChanged = this.stateManager.checkUrlChange(); const commentChanged = this.stateManager.checkBilibiliCommentChange(); const shouldProcess = this.stateManager.shouldProcess(); if (urlChanged || commentChanged || (!this.stateManager.shouldSkipBilibiliProcessing() && shouldProcess)) { this.processPage(); } } processPage() { if (!this.isRunning || !CONFIG.features.autoProcessNewContent || this.processLock || this.isBlacklisted) { return; } this.processLock = true; try { const elements = this.getProcessableElements(); const contentHash = this.generateContentHash(elements); if (contentHash === this.lastProcessHash) { return; } this.lastProcessHash = contentHash; if (elements.length === 0) { return; } EnhancedPerformanceUtils.createTimeSliceProcessor( elements, (element) => this.processElement(element), { onProgress: CONFIG.features.debugMode ? (current, total, percent) => { if (current % 50 === 0) { console.log(`🐱 处理进度: ${percent.toFixed(1)}% (${current}/${total})`); } } : null, onComplete: () => { CONFIG.stats.lastActive = new Date().toISOString(); GM_setValue("catgirlConfig", CONFIG); if (this.stateManager.isBilibili()) { this.processBilibiliSpecial(); } } } ); } finally { setTimeout(() => { this.processLock = false; }, 1000); } } getProcessableElements() { const baseSelector = 'title, h1, h2, h3, h4, h5, h6, p, article, section, blockquote, li, a, span, div:not([class*="settings"]):not([id*="catgirl"])'; const inputSelector = CONFIG.features.affectInput ? ', input, textarea' : ''; const elements = document.querySelectorAll(baseSelector + inputSelector); return Array.from(elements).filter(element => { return !this.processedElements.has(element) && !element.closest('#catgirl-settings, #catgirl-debug, #catgirl-blacklist') && this.shouldProcessElement(element); }); } shouldProcessElement(element) { if (!element.textContent?.trim()) return false; if (this.textProcessor.isProcessed(element.textContent)) return false; if (element.tagName && /^(SCRIPT|STYLE|NOSCRIPT)$/.test(element.tagName)) return false; if (element.offsetParent === null && element.style.display !== 'none') return false; return true; } generateContentHash(elements) { const textContent = elements.slice(0, 10).map(el => el.textContent?.slice(0, 50)).join('|'); return textContent.length + ':' + elements.length; } processElement(element) { if (!element || this.processedElements.has(element)) return; try { // 保存原始文本 const originalText = element.textContent; if (originalText && !this.textProcessor.getOriginalText(element)) { this.textProcessor.storeOriginalText(element, originalText); } if (element.matches && element.matches('input, textarea') && CONFIG.features.affectInput) { if (element.value?.trim()) { const processedValue = this.textProcessor.processText(element.value); element.value = processedValue; } } else { this.processElementText(element); } // 如果开启了鼠标悬停显示原文功能,且是评论元素 if (CONFIG.features.showOriginalOnHover && originalText && originalText !== element.textContent && this.isCommentElement(element)) { this.setupHoverOriginalText(element, originalText); } this.processedElements.add(element); CONFIG.stats.processedElements++; } catch (error) { if (CONFIG.features.debugMode) { console.error('🐱 处理元素出错:', error); } } } // 设置鼠标悬停显示原文功能 - 安全修复版本 setupHoverOriginalText(element, originalText) { // 安全检查:避免处理可能包含脚本或重要功能的元素 if (this.isUnsafeElement(element)) { return; } const processedText = element.textContent; let isHovering = false; let timeoutId = null; // 创建tooltip而不是直接修改元素内容 const showTooltip = () => { if (isHovering) return; isHovering = true; // 创建tooltip显示原文 const tooltip = document.createElement('div'); tooltip.className = 'catgirl-original-tooltip'; tooltip.textContent = originalText; tooltip.style.cssText = ` position: absolute; background: rgba(0,0,0,0.8); color: white; padding: 8px 12px; border-radius: 6px; font-size: 12px; z-index: 10002; max-width: 300px; word-wrap: break-word; pointer-events: none; opacity: 0; transition: opacity 0.2s ease; `; // 计算位置 const rect = element.getBoundingClientRect(); tooltip.style.left = `${rect.left + window.scrollX}px`; tooltip.style.top = `${rect.bottom + window.scrollY + 5}px`; document.body.appendChild(tooltip); // 淡入效果 setTimeout(() => { tooltip.style.opacity = '1'; }, 10); // 保存tooltip引用 element._catgirlTooltip = tooltip; // 添加视觉提示 element.style.backgroundColor = 'rgba(255, 248, 220, 0.3)'; element.style.transition = 'background-color 0.2s ease'; }; const hideTooltip = () => { if (!isHovering) return; isHovering = false; // 移除tooltip if (element._catgirlTooltip) { element._catgirlTooltip.style.opacity = '0'; setTimeout(() => { if (element._catgirlTooltip && element._catgirlTooltip.parentNode) { document.body.removeChild(element._catgirlTooltip); } element._catgirlTooltip = null; }, 200); } // 恢复元素样式 element.style.backgroundColor = ''; }; // 移除可能存在的旧事件监听器 if (element._catgirlMouseEnter) { element.removeEventListener('mouseenter', element._catgirlMouseEnter); } if (element._catgirlMouseLeave) { element.removeEventListener('mouseleave', element._catgirlMouseLeave); } // 保存事件处理器引用 element._catgirlMouseEnter = showTooltip; element._catgirlMouseLeave = hideTooltip; // 添加新的事件监听器 element.addEventListener('mouseenter', showTooltip); element.addEventListener('mouseleave', hideTooltip); } // 设置鼠标悬停显示原文功能 - 仅针对评论区的安全版本 setupHoverOriginalText(element, originalText) { // 只对评论相关元素启用悬停功能 if (!this.isCommentElement(element)) { return; } const processedText = element.textContent; let isHovering = false; const showTooltip = () => { if (isHovering) return; isHovering = true; // 创建tooltip显示原文 const tooltip = document.createElement('div'); tooltip.className = 'catgirl-original-tooltip'; tooltip.textContent = originalText; tooltip.style.cssText = ` position: absolute; background: rgba(0,0,0,0.8); color: white; padding: 8px 12px; border-radius: 6px; font-size: 12px; z-index: 10002; max-width: 300px; word-wrap: break-word; pointer-events: none; opacity: 0; transition: opacity 0.2s ease; `; // 计算位置 const rect = element.getBoundingClientRect(); tooltip.style.left = `${rect.left + window.scrollX}px`; tooltip.style.top = `${rect.bottom + window.scrollY + 5}px`; document.body.appendChild(tooltip); // 淡入效果 setTimeout(() => { tooltip.style.opacity = '1'; }, 10); // 保存tooltip引用 element._catgirlTooltip = tooltip; // 添加视觉提示 element.style.backgroundColor = 'rgba(255, 248, 220, 0.3)'; element.style.transition = 'background-color 0.2s ease'; }; const hideTooltip = () => { if (!isHovering) return; isHovering = false; // 移除tooltip if (element._catgirlTooltip) { element._catgirlTooltip.style.opacity = '0'; setTimeout(() => { if (element._catgirlTooltip && element._catgirlTooltip.parentNode) { document.body.removeChild(element._catgirlTooltip); } element._catgirlTooltip = null; }, 200); } // 恢复元素样式 element.style.backgroundColor = ''; }; // 移除可能存在的旧事件监听器 if (element._catgirlMouseEnter) { element.removeEventListener('mouseenter', element._catgirlMouseEnter); } if (element._catgirlMouseLeave) { element.removeEventListener('mouseleave', element._catgirlMouseLeave); } // 保存事件处理器引用 element._catgirlMouseEnter = showTooltip; element._catgirlMouseLeave = hideTooltip; // 添加新的事件监听器 element.addEventListener('mouseenter', showTooltip); element.addEventListener('mouseleave', hideTooltip); } // 检查是否是评论相关元素(使用与评论处理相同的选择器) isCommentElement(element) { if (!element) return false; // 使用与 processBilibiliComments 中相同的选择器逻辑 const commentSelectors = [ '.reply-item .reply-content', '.comment-item .comment-content', '.bili-comment-content', '.reply-content', '.comment-content', '[data-e2e="reply-item"] .reply-content', '.bili-comment .comment-text' ]; // B站 Shadow DOM 中的评论选择器 const shadowCommentSelectors = [ '#contents span', '.comment-text', '.reply-content', '.comment-content' ]; // 检查元素本身是否匹配评论选择器 for (const selector of commentSelectors) { try { if (element.matches && element.matches(selector)) return true; if (element.closest && element.closest(selector)) return true; } catch (e) { // 忽略选择器错误 } } // 检查是否在 Shadow DOM 的评论区中 for (const selector of shadowCommentSelectors) { try { // 检查元素是否直接匹配 shadow DOM 评论选择器 if (element.matches && element.matches(selector)) return true; // 检查父元素路径中是否有评论容器 let parent = element.parentElement; let depth = 0; while (parent && depth < 5) { if (parent.matches && parent.matches('bili-comment-thread-renderer, bili-comment-replies-renderer, bili-comments')) { return true; } parent = parent.parentElement; depth++; } } catch (e) { // 忽略选择器错误 } } return false; } processElementText(element) { if (element.children.length === 0) { const newText = this.textProcessor.processText(element.textContent); if (newText !== element.textContent) { element.textContent = newText; } } else { const textNodes = this.getTextNodes(element); textNodes.forEach(node => { if (node.textContent?.trim()) { const newText = this.textProcessor.processText(node.textContent); if (newText !== node.textContent) { node.textContent = newText; } } }); } } getTextNodes(element) { const textNodes = []; const walker = document.createTreeWalker( element, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { if (node.parentElement && /^(SCRIPT|STYLE|NOSCRIPT)$/.test(node.parentElement.tagName)) { return NodeFilter.FILTER_REJECT; } return node.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; } } ); let node; while (node = walker.nextNode()) { textNodes.push(node); } return textNodes; } processBilibiliSpecial() { if (this.stateManager.shouldSkipBilibiliProcessing()) return; if (CONFIG.features.debugMode) { console.log('🎯 执行B站特殊处理'); } try { this.processBilibiliComments(); this.processBilibiliShadowDOM(); // B站链接转文本功能 if (CONFIG.features.bilibiliMergeALinks) { this.processBilibiliLinks(); } } catch (error) { if (CONFIG.features.debugMode) { console.error('🐱 B站处理出错:', error); } } this.stateManager.markBilibiliCompleted(); } // B站链接转文本功能实现 processBilibiliLinks() { const linkSelectors = [ '.reply-content a:not([data-catgirl-link-processed])', '.comment-content a:not([data-catgirl-link-processed])', '.bili-comment-content a:not([data-catgirl-link-processed])', 'bili-comment-thread-renderer a:not([data-catgirl-link-processed])', '.reply-item a:not([data-catgirl-link-processed])' ]; linkSelectors.forEach(selector => { const links = document.querySelectorAll(selector); links.forEach(link => { this.convertLinkToText(link); }); }); // 处理 Shadow DOM 中的链接 if (CONFIG.features.shadowDomSupport) { this.processBilibiliLinksInShadowDOM(); } } // B站链接转文本功能实现 - 重写版本 processBilibiliLinks() { // 处理普通DOM中的评论 const commentContainers = document.querySelectorAll( '.reply-content, .comment-content, .bili-comment-content, .reply-item, .comment-item' ); commentContainers.forEach(container => { this.processLinksInContainer(container); }); // 处理 Shadow DOM 中的链接 if (CONFIG.features.shadowDomSupport) { this.processBilibiliLinksInShadowDOM(); } } processLinksInContainer(container) { if (!container || container.hasAttribute('data-catgirl-link-container-processed')) return; container.setAttribute('data-catgirl-link-container-processed', 'true'); // 查找所有 p 元素,这些通常包含评论的完整结构 const paragraphs = container.querySelectorAll('p'); paragraphs.forEach(p => { this.processBilibiliLinks() }); if (CONFIG.features.debugMode) { console.log('🔗 已处理评论容器的链接:', container); } } // 使用更稳健的逻辑重写 convertLinkToText(linkElement) { if (!linkElement || linkElement.hasAttribute('data-catgirl-link-processed')) return; // 创建一个新的span元素来替换a标签 const newSpan = document.createElement('span'); newSpan.className = 'catgirl-converted-link'; // 可选,用于样式 newSpan.textContent = linkElement.textContent || ''; // 使用a标签的文本内容 // 替换节点 linkElement.parentNode.replaceChild(newSpan, linkElement); // 标记容器,以便后续的文本处理可以重新扫描 const container = newSpan.closest('.reply-content, .comment-content, .bili-comment-content'); if (container) { container.removeAttribute('data-catgirl-processed'); } } processBilibiliLinks() { const linkSelectors = [ '.reply-content a:not([data-catgirl-link-processed])', '.comment-content a:not([data-catgirl-link-processed])', 'bili-comment-thread-renderer a:not([data-catgirl-link-processed])' ]; const processLinks = (rootNode) => { linkSelectors.forEach(selector => { rootNode.querySelectorAll(selector).forEach(link => { this.convertLinkToText(link); }); }); }; processLinks(document.body); // 处理 Shadow DOM document.querySelectorAll('bili-comment-thread-renderer').forEach(host => { if (host.shadowRoot) { processLinks(host.shadowRoot); } }); } extractTextFromLink(linkElement) { let text = ''; // 遍历a标签的所有子节点 for (const child of linkElement.childNodes) { if (child.nodeType === Node.TEXT_NODE) { text += child.textContent; } else if (child.nodeType === Node.ELEMENT_NODE && child.tagName !== 'IMG') { // 非图片元素的文本内容 text += child.textContent || ''; } } return text; } mergeContentToSpan(targetSpan, contentArray) { if (!targetSpan || contentArray.length === 0) return; const originalText = targetSpan.textContent || ''; const additionalText = contentArray.join(''); const mergedText = originalText + additionalText; // 应用猫娘化处理 const processedText = this.textProcessor.processText(mergedText); targetSpan.textContent = processedText; if (CONFIG.features.debugMode) { console.log('🔗 文本合并:', originalText, '+', additionalText, '->', processedText); } } processBilibiliLinksInShadowDOM() { const shadowHosts = document.querySelectorAll('bili-comment-thread-renderer, bili-comment-replies-renderer'); shadowHosts.forEach(host => { if (host.shadowRoot) { // 在Shadow DOM中查找 #contents 元素 const contentsElements = host.shadowRoot.querySelectorAll('#contents:not([data-catgirl-shadow-link-processed])'); contentsElements.forEach(contents => { contents.setAttribute('data-catgirl-shadow-link-processed', 'true'); // 处理 contents 中的 p 元素 const paragraphs = contents.querySelectorAll('p'); paragraphs.forEach(p => { this.processBilibiliLinks() }); }); } }); } processBilibiliComments() { const commentSelectors = [ '.reply-item .reply-content:not([data-catgirl-processed])', '.comment-item .comment-content:not([data-catgirl-processed])', '.bili-comment-content:not([data-catgirl-processed])', '.reply-content:not([data-catgirl-processed])', '.comment-content:not([data-catgirl-processed])', '[data-e2e="reply-item"] .reply-content:not([data-catgirl-processed])', '.bili-comment .comment-text:not([data-catgirl-processed])' ]; commentSelectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(element => { if (!this.processedElements.has(element)) { element.setAttribute('data-catgirl-processed', 'true'); this.processElement(element); } }); }); // 处理用户名 - 检查开关 if (CONFIG.features.bilibiliRandomizeUserNames) { const usernameSelectors = [ '.user-name:not([data-catgirl-processed])', '.reply-item .user-name:not([data-catgirl-processed])', '.comment-item .user-name:not([data-catgirl-processed])', '.bili-comment .user-name:not([data-catgirl-processed])', '.reply-author:not([data-catgirl-processed])', '.comment-author:not([data-catgirl-processed])' ]; usernameSelectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(element => { this.processBilibiliUsername(element); }); }); } } processBilibiliUsername(element) { if (!element || this.processedElements.has(element)) return; const userName = element.textContent?.trim(); if (!userName || !CONFIG.features.bilibiliRandomizeUserNames) return; if (this.hasUserPrefix(userName)) return; element.setAttribute('data-catgirl-processed', 'true'); this.processedElements.add(element); const processingType = Math.random(); if (processingType < 0.3) { const randomPrefix = this.getRandomUserPrefix(); element.textContent = `${randomPrefix}${userName}${randomPrefix}`; } else if (processingType < 0.6) { const randomPrefix = this.getRandomUserPrefix(); element.textContent = `${randomPrefix}${userName}`; } else if (processingType < 0.8) { const decorative = cuteLibrary.decorativePrefixes[Math.floor(Math.random() * cuteLibrary.decorativePrefixes.length)]; element.textContent = `${decorative}${userName}`; } if (CONFIG.features.debugMode) { console.log('🎀 处理用户名:', userName, '->', element.textContent); } } processBilibiliShadowDOM() { if (!CONFIG.features.shadowDomSupport) return; const biliComments = document.querySelector('bili-comments'); if (biliComments && biliComments.shadowRoot) { this.processElementsInShadowDOM(biliComments.shadowRoot); } const shadowHosts = document.querySelectorAll('bili-comment-thread-renderer, bili-comment-replies-renderer'); shadowHosts.forEach(host => { if (host.shadowRoot) { this.processElementsInShadowDOM(host.shadowRoot); } }); } processElementsInShadowDOM(shadowRoot) { try { const contentSelectors = [ '#contents span:not([data-catgirl-processed])', '.comment-text:not([data-catgirl-processed])', '.reply-content:not([data-catgirl-processed])', '.comment-content:not([data-catgirl-processed])' ]; contentSelectors.forEach(selector => { const elements = shadowRoot.querySelectorAll(selector); elements.forEach(element => { if (!this.processedElements.has(element)) { element.setAttribute('data-catgirl-processed', 'true'); // 保存原始文本用于悬停显示 const originalText = element.textContent; if (originalText && !this.textProcessor.getOriginalText(element)) { this.textProcessor.storeOriginalText(element, originalText); } this.processElement(element); // Shadow DOM 中的评论元素也需要悬停功能 if (CONFIG.features.showOriginalOnHover && originalText && originalText !== element.textContent) { this.setupHoverOriginalText(element, originalText); } } }); }); if (CONFIG.features.bilibiliRandomizeUserNames) { const usernameSelectors = [ '#user-name a:not([data-catgirl-processed])', '.user-name:not([data-catgirl-processed])', '.author-name:not([data-catgirl-processed])' ]; usernameSelectors.forEach(selector => { const elements = shadowRoot.querySelectorAll(selector); elements.forEach(element => { this.processBilibiliUsername(element); }); }); } const nestedHosts = shadowRoot.querySelectorAll('*'); nestedHosts.forEach(el => { if (el.shadowRoot && !el.hasAttribute('data-catgirl-shadow-processed')) { el.setAttribute('data-catgirl-shadow-processed', 'true'); this.processElementsInShadowDOM(el.shadowRoot); } }); } catch (error) { if (CONFIG.features.debugMode) { console.error('🐱 处理Shadow DOM出错:', error); } } } hasUserPrefix(userName) { if (!userName) return true; const hasPrefix = cuteLibrary.userPrefixes.some(prefix => userName.includes(prefix)); const hasDecorative = cuteLibrary.decorativePrefixes.some(prefix => userName.includes(prefix)); const isProcessed = /已处理|🏳️⚧️.*🏳️⚧️|✨.*✨|💕.*💕/.test(userName); const isTooLong = userName.length > 20; return hasPrefix || hasDecorative || isProcessed || isTooLong; } getRandomUserPrefix() { const prefixes = cuteLibrary.userPrefixes; return prefixes[Math.floor(Math.random() * prefixes.length)]; } setupBilibiliCommentObserver() { if (this.bilibiliCommentObserver) return; const targetNode = document.body; const config = { childList: true, subtree: true, attributes: false }; this.bilibiliCommentObserver = new MutationObserver( DebounceUtils.throttleWithDebounce((mutations) => { let hasCommentChanges = false; for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { if (node.matches && ( node.matches('bili-comment-thread-renderer, bili-comment, .reply-item, .comment-item') || (node.querySelector && ( node.querySelector('bili-comment-thread-renderer') || node.querySelector('.reply-item') || node.querySelector('.comment-item') || node.querySelector('.bili-comment') )) )) { hasCommentChanges = true; break; } } } if (hasCommentChanges) break; } if (hasCommentChanges) { setTimeout(() => { if (CONFIG.features.debugMode) { console.log('🔄 检测到B站评论变化,开始处理'); } this.processBilibiliSpecial(); }, 1200); } }, 800, 400) ); this.bilibiliCommentObserver.observe(targetNode, config); if (CONFIG.features.debugMode) { console.log('🎯 B站评论观察器已启动'); } } setupMutationObserver() { if (this.observer || !CONFIG.features.autoProcessNewContent) return; const throttledCallback = DebounceUtils.throttleWithDebounce( (mutations) => this.handleMutations(mutations), CONFIG.performance.observerThrottle, CONFIG.performance.debounceDelay ); this.observer = new MutationObserver(throttledCallback); this.observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false }); if (CONFIG.features.debugMode) { console.log('👁️ DOM变化监听器已启动'); } } handleMutations(mutations) { console.log('✨ 检测到DOM变化,立即处理新内容!'); if (this.processLock || this.isBlacklisted) return; try { const elementsToProcess = new Set(); mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && this.shouldProcessElement(node)) { elementsToProcess.add(node); const children = node.querySelectorAll && node.querySelectorAll('*'); if (children) { Array.from(children).forEach(child => { if (this.shouldProcessElement(child)) { elementsToProcess.add(child); } }); } } }); }); if (elementsToProcess.size > 0) { const limitedElements = Array.from(elementsToProcess).slice(0, 20); if (CONFIG.features.debugMode) { console.log(`🔄 DOM变化触发处理: ${limitedElements.length} 个新元素`); } EnhancedPerformanceUtils.createTimeSliceProcessor( limitedElements, (element) => this.processElement(element), { onComplete: () => { CONFIG.stats.lastActive = new Date().toISOString(); } } ); } } catch (error) { if (CONFIG.features.debugMode) { console.error('🐱 处理DOM变化出错:', error); } } } toggle() { if (this.isBlacklisted) { showToast('该网站被列为黑名单内容,如需修改请到面板里调整', 'warning', 4000); return; } if (this.isRunning) { this.stop(); showToast('猫娘化已暂停喵~ ⏸️', 'warning'); } else { this.start(); showToast('猫娘化已恢复喵~ ▶️', 'success'); } } stop() { this.isRunning = false; this.processLock = false; if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } if (this.observer) { this.observer.disconnect(); this.observer = null; } if (this.bilibiliCommentObserver) { this.bilibiliCommentObserver.disconnect(); this.bilibiliCommentObserver = null; } } restart() { if (this.isBlacklisted) { showToast('该网站被列为黑名单内容,如需修改请到面板里调整', 'warning', 4000); return; } this.stop(); this.processedElements = new WeakSet(); this.textProcessor.processedTexts = new Set(); this.lastProcessHash = ''; setTimeout(() => { this.start(); showToast('系统已重启喵~ 🔄', 'info'); }, 500); } showStats() { if (document.getElementById('catgirl-stats-ui')) return; const statsUI = document.createElement('div'); statsUI.id = 'catgirl-stats-ui'; statsUI.innerHTML = this.getStatsHTML(); statsUI.style.cssText = this.getStatsCSS(); document.body.appendChild(statsUI); this.bindStatsEvents(statsUI); this.drawCatPaw(statsUI); } clearCache() { this.processedElements = new WeakSet(); this.textProcessor.processedTexts = new Set(); this.lastProcessHash = ''; CONFIG.stats.sessionProcessed = 0; // 清理处理标记 const processedElements = document.querySelectorAll('[data-catgirl-processed]'); processedElements.forEach(el => { el.removeAttribute('data-catgirl-processed'); }); const processedLinks = document.querySelectorAll('[data-catgirl-link-processed]'); processedLinks.forEach(el => { el.removeAttribute('data-catgirl-link-processed'); }); const processedShadows = document.querySelectorAll('[data-catgirl-shadow-processed]'); processedShadows.forEach(el => { el.removeAttribute('data-catgirl-shadow-processed'); }); GM_setValue("catgirlConfig", CONFIG); showToast('缓存已清理,统计已重置喵~ 🧹', 'success'); } waitForDOMReady() { return new Promise(resolve => { if (document.readyState !== 'loading') { resolve(); } else { document.addEventListener('DOMContentLoaded', resolve, { once: true }); } }); } } // ===== 工具函数 ===== function showToast(message, type = 'info', duration = 3000) { // 计算已存在的toast的总高度,为新toast腾出位置 let offset = 20; document.querySelectorAll('.catgirl-toast.show').forEach(existingToast => { offset += existingToast.offsetHeight + 10; }); const toast = document.createElement('div'); toast.className = 'catgirl-toast'; toast.style.top = `${offset}px`; // 设置初始偏移 toast.innerHTML = ` <div class="toast-icon">${getToastIcon(type)}</div> <div class="toast-message">${message}</div> `; toast.classList.add(`toast-${type}`); document.body.appendChild(toast); setTimeout(() => toast.classList.add('show'), 10); setTimeout(() => { toast.classList.remove('show'); setTimeout(() => { if (toast.parentNode) { document.body.removeChild(toast); } }, 300); }, duration); } function getToastIcon(type) { const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; return icons[type] || icons.info; } function showUpdateNotification() { showToast(`🎉 猫娘脚本已更新到 v${SCRIPT_VERSION} 喵~\n新增功能:${UPdate_What}`, 'success', 5000); } // ===== 启动应用 ===== const catgirlApp = new CatgirlApp(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => catgirlApp.initialize(), { once: true }); } else { setTimeout(() => catgirlApp.initialize(), 100); } // ===== 样式注入 ===== GM_addStyle(` /* Toast 通知样式 - 增强版 */ .catgirl-toast { position: fixed; top: 20px; right: 20px; z-index: 10001; padding: 16px 20px; border-radius: 12px; font-size: 14px; font-weight: 500; max-width: 350px; box-shadow: 0 8px 32px rgba(0,0,0,0.15); transform: translateX(120%); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); backdrop-filter: blur(10px); display: flex; align-items: center; gap: 12px; } .catgirl-toast.show { transform: translateX(0); } .catgirl-toast.toast-success { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; } .catgirl-toast.toast-error { background: linear-gradient(135deg, #dc3545 0%, #e74c3c 100%); color: white; } .catgirl-toast.toast-warning { background: linear-gradient(135deg, #ffc107 0%, #fd7e14 100%); color: #212529; } .catgirl-toast.toast-info { background: linear-gradient(135deg, #17a2b8 0%, #6f42c1 100%); color: white; } .catgirl-toast .toast-icon { font-size: 18px; } .catgirl-toast .toast-message { flex: 1; line-height: 1.4; } /* 设置面板样式 - 增强版 */ #catgirl-settings { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif; line-height: 1.5; } #catgirl-settings .settings-header, #catgirl-blacklist .blacklist-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px 12px 0 0; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } #catgirl-settings .settings-header h3, #catgirl-blacklist .blacklist-header h3 { margin: 0; font-size: 20px; font-weight: 600; } #catgirl-settings .close-btn, #catgirl-blacklist .close-btn { background: rgba(255,255,255,0.2); border: none; color: white; font-size: 24px; cursor: pointer; width: 32px; height: 32px; border-radius: 50%; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; } #catgirl-settings .close-btn:hover, #catgirl-blacklist .close-btn:hover { background: rgba(255,255,255,0.3); transform: scale(1.1); } #catgirl-settings .settings-content, #catgirl-blacklist .blacklist-content { padding: 24px; background: #ffffff; height: 590px; overflow-y: auto; border-radius: 0 0 12px 12px; } #catgirl-settings .tab-container { display: flex; margin-bottom: 24px; background: #f8f9fa; border-radius: 8px; padding: 4px; flex-wrap: wrap; } #catgirl-settings .tab-btn { flex: 1; padding: 12px 16px; border: none; background: transparent; cursor: pointer; border-radius: 6px; transition: all 0.3s ease; font-weight: 500; color: #6c757d; min-width: 80px; } #catgirl-settings .tab-btn.active { background: white; color: #495057; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #catgirl-settings .tab-btn:hover:not(.active) { color: #495057; background: rgba(255,255,255,0.7); } #catgirl-settings .setting-group { margin-bottom: 20px; } #catgirl-settings label, #catgirl-blacklist label { display: block; margin-bottom: 8px; font-weight: 500; color: #333; font-size: 14px; } #catgirl-settings small, #catgirl-blacklist small { display: block; color: #6c757d; font-size: 12px; margin-top: 4px; line-height: 1.4; } #catgirl-settings select, #catgirl-settings input[type="text"], #catgirl-settings input[type="range"], #catgirl-settings textarea, #catgirl-blacklist select, #catgirl-blacklist input[type="text"], #catgirl-blacklist textarea { color: #333; width: 100%; padding: 10px 12px; border: 2px solid #e9ecef; border-radius: 6px; font-size: 14px; transition: all 0.3s ease; background: #fff; box-sizing: border-box; } #catgirl-settings select:focus, #catgirl-settings input:focus, #catgirl-settings textarea:focus, #catgirl-blacklist select:focus, #catgirl-blacklist input:focus, #catgirl-blacklist textarea:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } #catgirl-settings input[type="checkbox"], #catgirl-blacklist input[type="checkbox"] { width: auto; margin-right: 8px; transform: scale(1.2); accent-color: #667eea; } #catgirl-settings input[type="range"] { height: 6px; -webkit-appearance: none; background: #e9ecef; border-radius: 3px; padding: 0; } #catgirl-settings input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%; background: #667eea; cursor: pointer; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } #catgirl-settings .stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 20px; } #catgirl-settings .stat-item { text-align: center; padding: 16px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 8px; border: 1px solid #dee2e6; } #catgirl-settings .stat-number { font-size: 24px; font-weight: 700; color: #667eea; margin-bottom: 4px; } #catgirl-settings .stat-label { font-size: 12px; color: #6c757d; font-weight: 500; } #catgirl-settings .info-section, #catgirl-settings .performance-section { background: #f8f9fa; padding: 16px; border-radius: 8px; border-left: 4px solid #667eea; margin-bottom: 16px; } #catgirl-settings .info-section h4, #catgirl-settings .performance-section h4, #catgirl-blacklist h4 { margin: 0 0 12px 0; color: #495057; font-size: 16px; } #catgirl-settings .info-section p, #catgirl-settings .performance-section p { margin: 6px 0; color: #6c757d; font-size: 14px; } #catgirl-settings .actions, #catgirl-blacklist .actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 24px; padding-top: 24px; border-top: 1px solid #e9ecef; } #catgirl-settings button, #catgirl-blacklist button { padding: 12px 20px; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 6px; } #catgirl-settings .btn-primary, #catgirl-blacklist .btn-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; flex: 1; } #catgirl-settings .btn-primary:hover, #catgirl-blacklist .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); } #catgirl-settings .btn-warning { background: linear-gradient(135deg, #ffc107 0%, #ff8c00 100%); color: #212529; flex: 1; } #catgirl-settings .btn-warning:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(255, 193, 7, 0.4); } #catgirl-settings .btn-secondary, #catgirl-blacklist .btn-secondary { background: linear-gradient(135deg, #6c757d 0%, #495057 100%); color: white; flex: 1; } #catgirl-settings .btn-secondary:hover, #catgirl-blacklist .btn-secondary:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(108, 117, 125, 0.4); } /* 黑名单面板特殊样式 */ #catgirl-blacklist { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif; line-height: 1.5; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 600px; max-height: 80vh; background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.3); z-index: 10000; display: none; overflow: hidden; } #catgirl-blacklist .current-site-section { background: #f8f9fa; padding: 16px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #28a745; } #catgirl-blacklist .current-site-info { color: #333; background: #e9ecef; padding: 12px; border-radius: 6px; margin-bottom: 16px; font-family: 'Courier New', monospace; font-size: 13px; } #catgirl-blacklist .action-group { margin-bottom: 12px; } #catgirl-blacklist .btn-danger { background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); color: white; width: 100%; margin-top: 10px; } #catgirl-blacklist .btn-danger:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(220, 53, 69, 0.4); } #catgirl-blacklist .blacklist-section { margin-bottom: 24px; } #catgirl-blacklist .blacklist-item { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 16px; margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; transition: all 0.3s ease; } #catgirl-blacklist .blacklist-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); } #catgirl-blacklist .blacklist-item.expired { background: #f8f9fa; border-color: #adb5bd; opacity: 0.7; } #catgirl-blacklist .item-info { flex: 1; } #catgirl-blacklist .item-domain { font-weight: 600; color: #495057; margin-bottom: 4px; } #catgirl-blacklist .item-details { font-size: 12px; color: #6c757d; line-height: 1.4; } #catgirl-blacklist .remove-btn { background: #dc3545; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: all 0.3s ease; } #catgirl-blacklist .remove-btn:hover { background: #c82333; transform: scale(1.05); } #catgirl-blacklist .empty-state { text-align: center; color: #6c757d; font-style: italic; padding: 32px; background: #f8f9fa; border-radius: 8px; } #catgirl-blacklist .blacklist-stats { text-align: center; padding-top: 16px; border-top: 1px solid #e9ecef; margin-top: 16px; } #catgirl-blacklist .blacklist-settings { background: #e3f2fd; padding: 16px; border-radius: 8px; border-left: 4px solid #2196f3; margin-bottom: 20px; } /* 设置面板定位样式 */ #catgirl-settings { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 500px; max-height: 80vh; background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.3); z-index: 10000; display: none; overflow: hidden; } /* 原文提示tooltip样式 */ .catgirl-original-tooltip { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif !important; white-space: pre-wrap; box-shadow: 0 4px 12px rgba(0,0,0,0.3) !important; backdrop-filter: blur(8px); } /* 链接转换样式 */ .catgirl-converted-link { color: #667eea !important; text-decoration: none !important; } .catgirl-converted-link:hover { opacity: 0.8; } background: linear-gradient(135deg, #ff6b9d 0%, #ff8cc8 100%); color: white; padding: 20px; border-radius: 20px 20px 0 0; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 2px 15px rgba(255, 107, 157, 0.3); } #catgirl-block-ui .block-site-content { padding: 25px; } #catgirl-block-ui .site-info { display: flex; align-items: center; background: rgba(255, 182, 193, 0.1); padding: 15px; border-radius: 12px; margin-bottom: 20px; border: 1px solid #ffb6c1; } #catgirl-block-ui .site-icon { font-size: 24px; margin-right: 15px; } #catgirl-block-ui .site-domain { font-weight: bold; color: #d63384; font-size: 16px; } #catgirl-block-ui .site-path { color: #6c757d; font-size: 12px; margin-top: 2px; } #catgirl-block-ui .block-options { margin-bottom: 20px; } #catgirl-block-ui .option-group { margin-bottom: 15px; } #catgirl-block-ui .cute-label { display: flex; align-items: center; cursor: pointer; padding: 10px; border-radius: 8px; transition: all 0.3s ease; } #catgirl-block-ui .cute-label:hover { background: rgba(255, 182, 193, 0.1); } #catgirl-block-ui .radio-custom { width: 18px; height: 18px; border-radius: 50%; border: 2px solid #ffb6c1; margin-right: 10px; position: relative; transition: all 0.3s ease; } #catgirl-block-ui input[type="radio"]:checked + .radio-custom { background: #ff69b4; border-color: #ff69b4; } #catgirl-block-ui input[type="radio"]:checked + .radio-custom::after { content: ''; width: 6px; height: 6px; background: white; border-radius: 50%; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } #catgirl-block-ui input[type="radio"] { display: none; } #catgirl-block-ui .option-text { font-weight: 500; color: #495057; } #catgirl-block-ui .cute-label-block { display: block; margin-bottom: 8px; font-weight: 500; color: #d63384; } #catgirl-block-ui .cute-select, #catgirl-block-ui .cute-input { width: 100%; padding: 12px; border: 2px solid #ffb6c1; border-radius: 10px; font-size: 14px; transition: all 0.3s ease; background: rgba(255, 255, 255, 0.8); } #catgirl-block-ui .cute-select:focus, #catgirl-block-ui .cute-input:focus { outline: none; border-color: #ff69b4; box-shadow: 0 0 0 3px rgba(255, 105, 180, 0.1); } #catgirl-block-ui .block-actions { display: flex; gap: 12px; margin-top: 25px; } #catgirl-block-ui .btn-block-confirm { flex: 1; padding: 12px 20px; background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); color: white; border: none; border-radius: 10px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; } #catgirl-block-ui .btn-block-confirm:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(220, 53, 69, 0.4); } #catgirl-block-ui .btn-block-cancel { flex: 1; padding: 12px 20px; background: linear-gradient(135deg, #6c757d 0%, #495057 100%); color: white; border: none; border-radius: 10px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; } #catgirl-block-ui .btn-block-cancel:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(108, 117, 125, 0.4); } /* 统计UI样式 */ #catgirl-stats-ui .stats-header { background: linear-gradient(135deg, #20c997 0%, #17a2b8 100%); color: white; padding: 20px; border-radius: 20px 20px 0 0; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 2px 15px rgba(32, 201, 151, 0.3); } #catgirl-stats-ui .stats-content { padding: 25px; } #catgirl-stats-ui .stats-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-bottom: 25px; } #catgirl-stats-ui .stat-card { background: rgba(32, 201, 151, 0.1); border: 1px solid #20c997; border-radius: 12px; padding: 15px; display: flex; align-items: center; transition: all 0.3s ease; } #catgirl-stats-ui .stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(32, 201, 151, 0.2); } #catgirl-stats-ui .stat-icon { font-size: 24px; margin-right: 12px; } #catgirl-stats-ui .stat-number { font-size: 24px; font-weight: bold; color: #17a2b8; line-height: 1; } #catgirl-stats-ui .stat-label { font-size: 12px; color: #6c757d; margin-top: 2px; } #catgirl-stats-ui .system-info { background: rgba(23, 162, 184, 0.1); border: 1px solid #17a2b8; border-radius: 12px; padding: 20px; margin-bottom: 20px; } #catgirl-stats-ui .info-row { display: flex; justify-content: space-between; margin-bottom: 8px; align-items: center; } #catgirl-stats-ui .info-row:last-child { margin-bottom: 0; } #catgirl-stats-ui .info-label { font-weight: 500; color: #495057; } #catgirl-stats-ui .info-value { color: #17a2b8; font-weight: 500; } #catgirl-stats-ui .status-running { color: #28a745; } #catgirl-stats-ui .status-paused { color: #ffc107; } #catgirl-stats-ui .status-blocked { color: #dc3545; } #catgirl-stats-ui .stats-actions { display: flex; gap: 12px; } #catgirl-stats-ui .btn-stats-refresh, #catgirl-stats-ui .btn-stats-export { flex: 1; padding: 12px 20px; border: none; border-radius: 10px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; } #catgirl-stats-ui .btn-stats-refresh { background: linear-gradient(135deg, #20c997 0%, #17a2b8 100%); color: white; } #catgirl-stats-ui .btn-stats-refresh:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(32, 201, 151, 0.4); } #catgirl-stats-ui .btn-stats-export { background: linear-gradient(135deg, #6f42c1 0%, #6610f2 100%); color: white; } #catgirl-stats-ui .btn-stats-export:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(111, 66, 193, 0.4); } /* 黑名单搜索样式 */ #catgirl-blacklist .search-section { margin-bottom: 15px; } #catgirl-blacklist .search-input { width: 100%; padding: 10px 15px; border: 2px solid #e9ecef; border-radius: 8px; font-size: 14px; transition: all 0.3s ease; background: #fff; } #catgirl-blacklist .search-input:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } #catgirl-blacklist .search-hint { font-size: 12px; color: #6c757d; margin-top: 5px; } #catgirl-blacklist .blacklist-scroll { max-height: 300px; overflow-y: auto; border: 1px solid #e9ecef; border-radius: 8px; padding: 10px; } #catgirl-blacklist .search-highlight { background: #fff3cd; color: #856404; padding: 1px 3px; border-radius: 3px; font-weight: 500; } /* 响应式设计 */ @media (max-width: 700px) { #catgirl-settings, #catgirl-blacklist { width: 95vw; max-height: 90vh; } #catgirl-settings .tab-container { flex-direction: column; } #catgirl-settings .tab-btn { flex: none; } #catgirl-settings .actions, #catgirl-blacklist .actions { flex-direction: column; } #catgirl-settings .stats-grid { grid-template-columns: 1fr; } #catgirl-blacklist .blacklist-item { flex-direction: column; align-items: stretch; gap: 12px; } #catgirl-blacklist .remove-btn { align-self: flex-end; } } /* 滚动条美化 */ #catgirl-settings ::-webkit-scrollbar, #catgirl-blacklist ::-webkit-scrollbar { width: 8px; } #catgirl-settings ::-webkit-scrollbar-track, #catgirl-blacklist ::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } #catgirl-settings ::-webkit-scrollbar-thumb, #catgirl-blacklist ::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; } #catgirl-settings ::-webkit-scrollbar-thumb:hover, #catgirl-blacklist ::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } `); // ===== 全局调试接口 ===== window.catgirlApp = { get app() { return catgirlApp; }, get blacklistManager() { return catgirlApp?.blacklistManager; }, get config() { return CONFIG; }, get version() { return SCRIPT_VERSION; }, clearCache: function() { if (catgirlApp && typeof catgirlApp.clearCache === 'function') { catgirlApp.clearCache(); } } }; })();