// ==UserScript==
// @name 🐱 全世界都要变成可爱猫猫!增强版
// @version 4.1.0
// @description 让整个网络世界都变成超可爱的猫娘语调喵~增强版(修复版)
// @author 超萌猫娘开发队
// @match *://*/*
// @include *://*.bilibili.com/video/*
// @include *://*.bilibili.com/anime/*
// @include *://*.bilibili.com/bangumi/play/*
// @exclude *://gf.qytechs.cn/*
// @exclude *://*.gov/*
// @exclude *://*.edu/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @license MIT
// @namespace https://gf.qytechs.cn/users/1503554
// ==/UserScript==
(function() {
'use strict';
// ===== 版本管理 =====
const SCRIPT_VERSION = "4.1.0";
const isVersionUpdate = GM_getValue("SCRIPT_VERSION") !== SCRIPT_VERSION;
if (isVersionUpdate) {
GM_setValue("SCRIPT_VERSION", SCRIPT_VERSION);
console.log('🎉 猫娘脚本已更新到版本', SCRIPT_VERSION);
}
// ===== 增强配置系统 =====
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 // 新增智能处理开关
},
// 站点配置
sites: {
excludeDomains: [
'github.com', 'stackoverflow.com', 'google.com',
'gov.cn', 'edu.cn', 'gf.qytechs.cn'
],
bilibili: {
smartPause: true,
retryInterval: 1000,
maxRetryDelay: 5000,
commentSelector: 'bili-comment-thread-renderer',
userNameSelector: '#user-name a',
contentSelector: '#contents span'
}
},
// 快捷键配置
shortcuts: {
toggleScript: "100C", // Ctrl+C
resetProcessing: "101R", // Ctrl+Shift+R
showSettings: "100S", // Ctrl+S
debugMode: "101D" // Ctrl+Shift+D
},
// 用户偏好 - 重新设计处理模式
preferences: {
cuteLevel: 'normal', // low, normal, high - 现在有明显差异
customEndings: [],
disabledWords: [],
processingMode: 'smart', // gentle, smart, aggressive - 现在有明显差异
intelligentReplacement: true // 智能替换开关
},
// 统计信息
stats: {
processedElements: 0,
replacedWords: 0,
lastActive: new Date().toISOString(),
installDate: new Date().toISOString(),
sessionProcessed: 0
}
};
// 加载用户配置
let userConfig = GM_getValue("catgirlConfig") || {};
let CONFIG = Object.assign({}, defaultConfig, userConfig);
// 如果是版本更新,合并新配置
if (isVersionUpdate) {
CONFIG = Object.assign(defaultConfig, userConfig);
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喵', '想哭喵']
},
// 智能替换模板 - 多叉树结构
replacementTree: {
// 攻击性词汇 -> 温和表达
aggressive: {
high: ['要抱抱的小喵~', '生气气的小家伙', '炸毛的喵咪'],
medium: ['不开心的喵', '小脾气喵', '闹别扭喵'],
low: ['小喵', '喵~', '呢']
},
// 普通词汇 -> 可爱化
normal: {
high: ['超级', '特别', '很'],
medium: ['有点', '稍微', '还'],
low: ['', '', '']
}
}
};
// ===== 防抖工具类 =====
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) {
// 使用 requestIdleCallback 或 setTimeout
if (window.requestIdleCallback) {
requestIdleCallback(processNextBatch, {
timeout: CONFIG.performance.idleCallbackTimeout
});
} else {
setTimeout(processNextBatch, 16); // 约60fps
}
} 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);
}
static throttleWithLeading(func, limit) {
let inThrottle;
let lastFunc;
let lastRan;
return function() {
const context = this;
const args = arguments;
if (!inThrottle) {
func.apply(context, args);
lastRan = Date.now();
inThrottle = true;
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function() {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
}
// ===== 状态管理类 (修复版) =====
class StateManager {
constructor() {
this.state = {
isEnabled: true,
currentUrl: location.href,
processingQueue: new Set(),
urlChangeHandlers: [], // 确保初始化为数组
lastProcessTime: 0, // 新增:最后处理时间
// B站特殊状态
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;
// 重置B站状态
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;
// 至少间隔1秒才能再次处理
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; // 减少到30秒
}
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(); // 新增上下文分析器
}
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 'smart':
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() < 0.3) {
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 mode = CONFIG.preferences.processingMode;
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 = 0.3; // 默认概率
switch (CONFIG.preferences.processingMode) {
case 'gentle':
probability = 0.15;
break;
case 'aggressive':
probability = 0.6;
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}`);
}
getCleanupRules(context = 'normal') {
// 根据上下文返回不同的清理规则
const baseRules = [
// --------- 极端攻击与侮辱性词汇 ---------
[/操你妈|操你娘|操你全家|肏你妈|干你妈|干你娘|去你妈的|去你娘的|去你全家/gi, '去睡觉觉'],
[/妈了个?逼|妈的?智障|妈的/gi, '喵喵喵'],
[/狗娘养的|狗杂种|狗东西|狗逼/gi, '不太好的小家伙'],
[/操你大爷|去你大爷的/gi, '去玩耍啦'],
[/去你老师的|你全家死光/gi, '嗯...安静一点'],
[/你妈死了|你妈没了/gi, '你妈妈叫你回家吃饭'],
// --------- 性相关及不雅词汇 ---------
[/鸡巴|鸡叭|鸡把|\bjb\b|\bJB\b/gi, '小鱼干'],
[/逼你|逼样|逼毛|逼崽子|什么逼/gi, '小淘气'],
[/肏|干你|草你|cao你|cao你妈/gi, '去玩耍啦'],
// --------- 常见人身攻击与负面评价 ---------
[/你妹|你妹的|去你妹/gi, '小迷糊'],
[/滚犊子|滚蛋|滚开|滚开你/gi, '去玩耍啦'],
[/去死|死全家|死开|狗带/gi, '去睡觉觉'],
[/人渣|渣人|渣男|渣女/gi, '要抱抱的小家伙'],
[/废物|废柴|废狗/gi, '要抱抱的小家伙'],
[/垃圾人|辣鸡|拉圾/gi, '不太好的小家伙'],
[/王八蛋|王八羔子/gi, '坏坏的小家伙'],
// --------- 智力与能力相关的攻击性词语 ---------
[/傻逼|煞笔|沙雕|傻叉|笨蛋|二货|二逼/gi, '小糊涂虫'],
[/白痴|智障|弱智|脑残|脑袋进水/gi, '小呆呆'],
[/蠢货|蠢蛋/gi, '有点迷糊'],
// --------- 网络流行语与缩写 ---------
[/\bcnm\b|\bCNM\b|c\s*n\s*m/gi, '你好软糯'],
[/\bnmsl\b|\bNMSL\b/gi, '你超棒棒'],
[/\bmlgb\b|\bMLGB\b/gi, '哇好厉害'],
[/tmd|TMD/gi, '太萌啦'],
[/wtf|WTF/gi, '哇好神奇'],
// 更多规则...
[/我靠|我擦|我操|卧槽/gi, '哇哦'],
[/笑死我了|笑死|xswl|XSWL/gi, '好有趣'],
[/绝绝子/gi, '超级棒棒'],
[/yyds|YYDS/gi, '最棒棒'],
];
// 根据上下文添加特定的结尾
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);
}
}
// ===== 上下文分析器类 =====
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';
}
}
// ===== 设置面板类 - 优化UI =====
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="settings-tabs">
<button class="tab-btn active" data-tab="basic">基础设置</button>
<button class="tab-btn" data-tab="advanced">高级设置</button>
<button class="tab-btn" data-tab="stats">统计信息</button>
</div>
<div class="tab-content" id="basic-tab">
<div class="setting-group">
<label>🎀 可爱程度:
<select id="cute-level">
<option value="low">低 (温和可爱)</option>
<option value="normal" selected>普通 (标准可爱)</option>
<option value="high">高 (超级可爱)</option>
</select>
</label>
<small>控制添加可爱词汇的数量和频率</small>
</div>
<div class="setting-group">
<label>⚙️ 处理模式:
<select id="processing-mode">
<option value="gentle">温和 (少量替换)</option>
<option value="smart" selected>智能 (根据上下文)</option>
<option value="aggressive">积极 (大量替换)</option>
</select>
</label>
<small>控制文本处理的强度和策略</small>
</div>
<div class="setting-group">
<label>
<input type="checkbox" id="affect-input"> 📝 影响输入框
</label>
<small>是否处理输入框和文本域中的内容</small>
</div>
<div class="setting-group">
<label>
<input type="checkbox" id="intelligent-replacement"> 🧠 智能替换
</label>
<small>根据上下文智能选择替换词汇</small>
</div>
</div>
<div class="tab-content" id="advanced-tab" style="display: none;">
<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>🕐 处理间隔 (毫秒):
<input type="range" id="process-interval" min="1000" max="10000" step="500">
<span id="interval-value">${CONFIG.performance.processInterval}</span>
</label>
<small>控制自动处理的时间间隔</small>
</div>
<div class="setting-group">
<label>📦 批处理大小:
<input type="range" id="batch-size" min="3" max="20" step="1">
<span id="batch-value">${CONFIG.performance.batchSize}</span>
</label>
<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">${CONFIG.stats.processedElements}</div>
<div class="stat-label">已处理元素</div>
</div>
<div class="stat-item">
<div class="stat-number" id="replaced-count">${CONFIG.stats.replacedWords}</div>
<div class="stat-label">已替换词汇</div>
</div>
<div class="stat-item">
<div class="stat-number" id="session-count">${CONFIG.stats.sessionProcessed}</div>
<div class="stat-label">本次会话</div>
</div>
</div>
<div class="info-section">
<h4>📅 安装信息</h4>
<p><strong>版本:</strong> ${SCRIPT_VERSION}</p>
<p><strong>安装时间:</strong> ${new Date(CONFIG.stats.installDate).toLocaleString()}</p>
<p><strong>最后活动:</strong> ${new Date(CONFIG.stats.lastActive).toLocaleString()}</p>
</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>
<button id="export-config" class="btn-info">📤 导出配置</button>
</div>
</div>
`;
}
getCSS() {
return `
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;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 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);
});
// 滑块事件
const intervalSlider = document.getElementById('process-interval');
const batchSlider = document.getElementById('batch-size');
if (intervalSlider) {
intervalSlider.oninput = (e) => {
document.getElementById('interval-value').textContent = e.target.value;
};
}
if (batchSlider) {
batchSlider.oninput = (e) => {
document.getElementById('batch-value').textContent = e.target.value;
};
}
// 保存设置
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');
};
}
// 导出配置
const exportBtn = document.getElementById('export-config');
if (exportBtn) {
exportBtn.onclick = () => this.exportConfig();
}
}
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 cuteLevelEl = document.getElementById('cute-level');
const processingModeEl = document.getElementById('processing-mode');
const affectInputEl = document.getElementById('affect-input');
const intelligentEl = document.getElementById('intelligent-replacement');
if (cuteLevelEl) cuteLevelEl.value = CONFIG.preferences.cuteLevel;
if (processingModeEl) processingModeEl.value = CONFIG.preferences.processingMode;
if (affectInputEl) affectInputEl.checked = CONFIG.features.affectInput;
if (intelligentEl) intelligentEl.checked = CONFIG.preferences.intelligentReplacement;
// 高级设置
const debugModeEl = document.getElementById('debug-mode');
const performanceEl = document.getElementById('performance-monitoring');
const intervalEl = document.getElementById('process-interval');
const batchEl = document.getElementById('batch-size');
if (debugModeEl) debugModeEl.checked = CONFIG.features.debugMode;
if (performanceEl) performanceEl.checked = CONFIG.features.performanceMonitoring;
if (intervalEl) {
intervalEl.value = CONFIG.performance.processInterval;
document.getElementById('interval-value').textContent = CONFIG.performance.processInterval;
}
if (batchEl) {
batchEl.value = CONFIG.performance.batchSize;
document.getElementById('batch-value').textContent = CONFIG.performance.batchSize;
}
}
saveSettings() {
// 基础设置
const cuteLevelEl = document.getElementById('cute-level');
const processingModeEl = document.getElementById('processing-mode');
const affectInputEl = document.getElementById('affect-input');
const intelligentEl = document.getElementById('intelligent-replacement');
if (cuteLevelEl) CONFIG.preferences.cuteLevel = cuteLevelEl.value;
if (processingModeEl) CONFIG.preferences.processingMode = processingModeEl.value;
if (affectInputEl) CONFIG.features.affectInput = affectInputEl.checked;
if (intelligentEl) CONFIG.preferences.intelligentReplacement = intelligentEl.checked;
// 高级设置
const debugModeEl = document.getElementById('debug-mode');
const performanceEl = document.getElementById('performance-monitoring');
const intervalEl = document.getElementById('process-interval');
const batchEl = document.getElementById('batch-size');
if (debugModeEl) CONFIG.features.debugMode = debugModeEl.checked;
if (performanceEl) CONFIG.features.performanceMonitoring = performanceEl.checked;
if (intervalEl) CONFIG.performance.processInterval = parseInt(intervalEl.value);
if (batchEl) CONFIG.performance.batchSize = parseInt(batchEl.value);
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();
}
}
exportConfig() {
const configData = JSON.stringify(CONFIG, null, 2);
const blob = new Blob([configData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'catgirl-config.json';
a.click();
URL.revokeObjectURL(url);
showToast('配置已导出喵~ 📤', 'success');
}
updateStats() {
const processedEl = document.getElementById('processed-count');
const replacedEl = document.getElementById('replaced-count');
const sessionEl = document.getElementById('session-count');
if (processedEl) processedEl.textContent = CONFIG.stats.processedElements;
if (replacedEl) replacedEl.textContent = CONFIG.stats.replacedWords;
if (sessionEl) sessionEl.textContent = CONFIG.stats.sessionProcessed;
}
}
// ===== 主应用类 - 修复重复执行问题 =====
class CatgirlApp {
constructor() {
this.stateManager = new StateManager();
this.textProcessor = new EnhancedTextProcessor();
this.settingsPanel = new SettingsPanel();
this.processedElements = new WeakSet();
this.isRunning = false;
this.intervalId = null;
this.observer = null;
// 新增:防重复处理机制
this.lastProcessHash = '';
this.processLock = false;
// 新增:B站评论观察器
this.bilibiliCommentObserver = null;
}
async initialize() {
if (this.shouldExclude()) {
if (CONFIG.features.debugMode) {
console.log('🐱 域名已排除,不启动喵~');
}
return;
}
console.log('🐱 增强版猫娘化系统启动喵~');
// 注册(不可用)菜单命令
this.registerMenuCommands();
// 设置快捷键
this.setupKeyboardShortcuts();
// 等待DOM就绪
await this.waitForDOMReady();
// 设置URL变化监听
this.stateManager.onUrlChange((newUrl) => {
// 使用防抖延迟处理
setTimeout(() => {
if (location.href === newUrl) { // 确保URL没有再次变化
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.restart());
GM_registerMenuCommand("📊 显示统计", () => this.showStats());
GM_registerMenuCommand("🧹 清理缓存", () => this.clearCache());
}
setupKeyboardShortcuts() {
let inputFocused = false;
// 监听输入框焦点状态
const handleFocusIn = DebounceUtils.debounce((e) => {
if (e.target && e.target.matches && e.target.matches('input, textarea')) {
inputFocused = true;
}
}, 100);
const handleFocusOut = DebounceUtils.debounce((e) => {
if (e.target && e.target.matches && e.target.matches('input, textarea')) {
inputFocused = false;
}
}, 100);
document.addEventListener('focusin', handleFocusIn);
document.addEventListener('focusout', handleFocusOut);
document.addEventListener('keydown', (e) => {
if (inputFocused && !e.ctrlKey && !e.altKey) return;
const key = `${e.altKey?1:0}${e.ctrlKey?1:0}${e.shiftKey?1:0}${e.key.toUpperCase()}`;
switch (key) {
case CONFIG.shortcuts.toggleScript:
e.preventDefault();
this.toggle();
break;
case CONFIG.shortcuts.showSettings:
e.preventDefault();
this.settingsPanel.show();
break;
case CONFIG.shortcuts.resetProcessing:
e.preventDefault();
this.restart();
break;
case CONFIG.shortcuts.debugMode:
e.preventDefault();
this.toggleDebug();
break;
}
});
}
async start() {
if (this.isRunning) return;
this.isRunning = true;
// 初始处理 - 添加延迟避免重复
setTimeout(() => {
if (this.isRunning) {
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);
}
// 设置DOM观察器
this.setupMutationObserver();
// B站特殊处理
if (this.stateManager.isBilibili()) {
this.setupBilibiliCommentObserver();
}
if (CONFIG.features.debugMode) {
console.log('🎀 猫娘化系统已启动完成喵~');
}
}
// 条件处理 - 避免重复执行
conditionalProcess() {
if (!this.isRunning || this.processLock) 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) {
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') &&
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 {
if (element.matches && element.matches('input, textarea') && CONFIG.features.affectInput) {
if (element.value?.trim()) {
element.value = this.textProcessor.processText(element.value);
}
} else {
this.processElementText(element);
}
this.processedElements.add(element);
CONFIG.stats.processedElements++;
} catch (error) {
if (CONFIG.features.debugMode) {
// 使用新的错误处理方法
this.logError('处理元素出错', error);
}
}
}
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;
}
// ===== B站特殊处理 - 修复用户名处理问题 =====
processBilibiliSpecial() {
if (this.stateManager.shouldSkipBilibiliProcessing()) return;
if (CONFIG.features.debugMode) {
console.log('🎯 执行B站特殊处理');
}
try {
// 方法1: 直接查找评论元素
this.processBilibiliComments();
// 方法2: 处理Shadow DOM中的评论
this.processBilibiliShadowDOM();
} catch (error) {
if (CONFIG.features.debugMode) {
console.error('🐱 B站处理出错:', error);
}
}
this.stateManager.markBilibiliCompleted();
}
// 新增:直接处理B站评论的方法
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);
}
});
});
// 处理用户名 - 使用更严格的选择器
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);
});
});
}
// 新增:处理B站用户名
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) {
// 30% 概率:添加相同前后缀
const randomPrefix = this.getRandomUserPrefix();
element.textContent = `${randomPrefix}${userName}${randomPrefix}`;
} else if (processingType < 0.6) {
// 30% 概率:只添加前缀
const randomPrefix = this.getRandomUserPrefix();
element.textContent = `${randomPrefix}${userName}`;
} else if (processingType < 0.8) {
// 20% 概率:添加装饰性前缀
const decorative = cuteLibrary.decorativePrefixes[Math.floor(Math.random() * cuteLibrary.decorativePrefixes.length)];
element.textContent = `${decorative}${userName}`;
} else {
// 20% 概率:不处理,保持原样
return;
}
if (CONFIG.features.debugMode) {
console.log('🎀 处理用户名:', userName, '->', element.textContent);
}
}
// 修复:处理Shadow DOM中的B站评论
processBilibiliShadowDOM() {
const biliComments = document.querySelector('bili-comments');
if (biliComments && biliComments.shadowRoot) {
// 递归处理Shadow DOM
this.processElementsInShadowDOM(biliComments.shadowRoot);
}
// 处理其他可能的Shadow DOM容器
const shadowHosts = document.querySelectorAll('bili-comment-thread-renderer, bili-comment-replies-renderer');
shadowHosts.forEach(host => {
if (host.shadowRoot) {
this.processElementsInShadowDOM(host.shadowRoot);
}
});
}
// 新增:在Shadow DOM中处理元素
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');
this.processElement(element);
}
});
});
// 查找用户名 - 添加防重复选择器
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);
});
});
// 递归处理嵌套的Shadow DOM
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);
}
}
}
processBilibiliCommentThread(thread) {
if (!thread.shadowRoot) return;
try {
// 处理用户名 - 修复 prefixes 问题
const userNameElements = this.findInShadowDOM(thread.shadowRoot, CONFIG.sites.bilibili.userNameSelector);
userNameElements.forEach(nameEl => {
const userName = nameEl.textContent?.trim();
if (userName && !this.hasUserPrefix(userName)) {
const randomPrefix = this.getRandomUserPrefix();
nameEl.textContent = `${randomPrefix}${userName}${randomPrefix}`;
}
});
// 处理评论内容
const contentElements = this.findInShadowDOM(thread.shadowRoot, CONFIG.sites.bilibili.contentSelector);
contentElements.forEach(contentEl => {
// 处理链接 - 将链接转换为文本
if (CONFIG.features.bilibiliMergeALinks) {
const links = contentEl.querySelectorAll('a');
links.forEach(link => {
const linkText = link.textContent?.trim();
if (linkText) {
const textNode = document.createTextNode(linkText);
link.parentNode.replaceChild(textNode, link);
}
});
}
// 处理文本内容
const originalText = contentEl.textContent?.trim();
if (originalText && !this.textProcessor.isProcessed(originalText)) {
const newText = this.textProcessor.processText(originalText);
if (newText !== originalText) {
contentEl.textContent = newText;
}
}
});
} catch (error) {
if (CONFIG.features.debugMode) {
console.error('🐱 处理评论线程出错:', 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)];
}
findInShadowDOM(element, selector) {
const results = [];
try {
if (element.querySelectorAll) {
results.push(...element.querySelectorAll(selector));
}
// 递归搜索嵌套的 Shadow DOM
if (element.querySelectorAll) {
const allElements = element.querySelectorAll('*');
allElements.forEach(el => {
if (el.shadowRoot) {
results.push(...this.findInShadowDOM(el.shadowRoot, selector));
}
});
}
} catch (error) {
if (CONFIG.features.debugMode) {
console.error('🐱 搜索Shadow DOM出错:', error);
}
}
return results;
}
// 设置B站评论观察器 - 无痕监控
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) {
// 检查多种B站评论相关的元素
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) {
// 延迟处理,让B站完成DOM更新
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) 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 // 减少不必要的文本监听
});
}
handleMutations(mutations) {
if (this.processLock) 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);
EnhancedPerformanceUtils.createTimeSliceProcessor(
limitedElements,
(element) => this.processElement(element),
{
onComplete: () => {
CONFIG.stats.lastActive = new Date().toISOString();
}
}
);
}
}catch(error){
this.logError('处理DOM变化出错', error);
}
}
toggle() {
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() {
this.stop();
this.processedElements = new WeakSet();
this.textProcessor.processedTexts = new Set();
this.lastProcessHash = '';
setTimeout(() => {
this.start();
showToast('系统已重启喵~ 🔄', 'info');
}, 500);
}
toggleDebug() {
CONFIG.features.debugMode = !CONFIG.features.debugMode;
GM_setValue("catgirlConfig", CONFIG);
showToast(`调试模式${CONFIG.features.debugMode ? '开启' : '关闭'}喵~ 🐛`, 'info');
}
showStats() {
// 安全地获取统计数据,提供默认值
const processedElements = (CONFIG.stats.processedElements || 0);
const replacedWords = (CONFIG.stats.replacedWords || 0);
const sessionProcessed = (CONFIG.stats.sessionProcessed || 0);
const installDate = CONFIG.stats.installDate || new Date().toISOString();
const lastActive = CONFIG.stats.lastActive || new Date().toISOString();
const stats = `
📊 猫娘化统计信息:
• 已处理元素: ${processedElements.toLocaleString()}
• 已替换词汇: ${replacedWords.toLocaleString()}
• 本次会话: ${sessionProcessed.toLocaleString()}
• 当前版本: ${SCRIPT_VERSION}
• 安装时间: ${new Date(installDate).toLocaleString()}
• 最后活动: ${new Date(lastActive).toLocaleString()}
• 当前状态: ${this.isRunning ? '运行中 ✅' : '已暂停 ⏸️'}
`.trim();
alert(stats);
}
clearCache() {
this.processedElements = new WeakSet();
this.textProcessor.processedTexts = new Set();
this.lastProcessHash = '';
CONFIG.stats.sessionProcessed = 0;
GM_setValue("catgirlConfig", CONFIG);
showToast('缓存已清理,统计已重置喵~ 🧹', 'success');
}
waitForDOMReady() {
return new Promise(resolve => {
if (document.readyState !== 'loading') {
resolve();
} else {
document.addEventListener('DOMContentLoaded', resolve, { once: true });
}
});
}
}
// ===== 修复版请求拦截器 =====
class RequestInterceptor {
constructor() {
this.originalXHR = XMLHttpRequest.prototype.send;
this.handlers = new Map();
this.isStarted = false;
}
start() {
if (this.isStarted) return;
this.isStarted = true;
const self = this;
XMLHttpRequest.prototype.send = new Proxy(this.originalXHR, {
apply: (target, thisArg, args) => {
thisArg.addEventListener("load", (event) => {
try {
const xhr = event.target;
// 修复:检查 responseType,避免 InvalidStateError
if (xhr.responseType === '' || xhr.responseType === 'text') {
const { responseText, responseURL } = xhr;
if (responseText && /^{.*}$/.test(responseText.trim())) {
const result = JSON.parse(responseText);
self.executeHandlers(responseURL, result);
}
} else if (CONFIG.features.debugMode) {
console.log('🐱 跳过非文本响应类型:', xhr.responseType);
}
} catch (err) {
if (CONFIG.features.debugMode) {
console.error('🐱 请求拦截处理出错:', err);
}
}
});
return target.apply(thisArg, args);
}
});
}
addHandler(pattern, handler) {
this.handlers.set(pattern, handler);
}
executeHandlers(url, data) {
for (const [pattern, handler] of this.handlers) {
if (pattern.test(url)) {
try {
handler(data, url);
} catch (error) {
if (CONFIG.features.debugMode) {
console.error('🐱 请求处理器出错:', error);
}
}
}
}
}
stop() {
if (!this.isStarted) return;
XMLHttpRequest.prototype.send = this.originalXHR;
this.isStarted = false;
}
}
// ===== 调试面板类 =====
class DebugPanel {
constructor() {
this.panel = null;
this.isVisible = false;
this.logs = [];
this.maxLogs = 100;
}
create() {
if (this.panel) return;
this.panel = document.createElement('div');
this.panel.id = 'catgirl-debug';
this.panel.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; background: rgba(0,0,0,0.8); padding: 8px; border-radius: 4px;">
<strong style="color: #00ff00;">🐱 调试面板</strong>
<div>
<button onclick="window.catgirlApp.clearDebugLog()" style="background:none;border:1px solid #00ff00;color:#00ff00;cursor:pointer;margin-right:5px;padding:2px 6px;font-size:10px;">清理</button>
<button onclick="this.parentNode.parentNode.parentNode.style.display='none'" style="background:none;border:none;color:#ff6666;cursor:pointer;font-size:16px;">×</button>
</div>
</div>
<div id="debug-content" style="max-height: 250px; overflow-y: auto; font-size: 11px; background: rgba(0,0,0,0.9); padding: 8px; border-radius: 4px;"></div>
<div style="margin-top: 8px; font-size: 10px; opacity: 0.8; display: flex; justify-content: space-between;">
<span>内存: <span id="memory-usage">-</span></span>
<span>FPS: <span id="fps-counter">-</span></span>
<span>状态: <span id="status-indicator">运行中</span></span>
</div>
`;
// 设置样式
this.panel.style.cssText = `
position: fixed; bottom: 20px; right: 20px;
background: rgba(0,0,0,0.95); color: #00ff00;
padding: 12px; border-radius: 8px;
font-family: 'Courier New', monospace; font-size: 12px;
max-width: 400px; z-index: 9999; border: 1px solid #333;
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
`;
document.body.appendChild(this.panel);
this.startMonitoring();
}
show() {
if (!this.panel) this.create();
this.panel.style.display = 'block';
this.isVisible = true;
this.log('调试面板已开启', 'info');
}
hide() {
if (this.panel) {
this.panel.style.display = 'none';
this.isVisible = false;
}
}
log(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
const logEntry = {
message: `[${timestamp}] ${message}`,
type: type,
time: Date.now()
};
this.logs.push(logEntry);
if (this.logs.length > this.maxLogs) {
this.logs.shift();
}
this.updateDisplay();
}
updateDisplay() {
if (!this.panel || !this.isVisible) return;
const content = document.getElementById('debug-content');
if (content) {
content.innerHTML = this.logs.map(log =>
`<div style="color: ${this.getLogColor(log.type)}; margin: 2px 0;">${log.message}</div>`
).join('');
content.scrollTop = content.scrollHeight;
}
}
getLogColor(type) {
const colors = {
info: '#00ff00',
warn: '#ffaa00',
error: '#ff3333',
success: '#00aa00',
debug: '#888888'
};
return colors[type] || colors.info;
}
startMonitoring() {
// 内存使用监控
if (performance.memory) {
setInterval(() => {
const memory = performance.memory;
const usage = `${(memory.usedJSHeapSize / 1024 / 1024).toFixed(1)}MB`;
const memoryEl = document.getElementById('memory-usage');
if (memoryEl) memoryEl.textContent = usage;
}, 2000);
}
// FPS 监控
let lastTime = performance.now();
let frameCount = 0;
const measureFPS = () => {
frameCount++;
const currentTime = performance.now();
if (currentTime >= lastTime + 1000) {
const fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
const fpsEl = document.getElementById('fps-counter');
if (fpsEl) fpsEl.textContent = fps;
frameCount = 0;
lastTime = currentTime;
}
requestAnimationFrame(measureFPS);
};
measureFPS();
// 状态监控
setInterval(() => {
const statusEl = document.getElementById('status-indicator');
if (statusEl && window.catgirlApp) {
statusEl.textContent = window.catgirlApp.app.isRunning ? '运行中' : '已暂停';
statusEl.style.color = window.catgirlApp.app.isRunning ? '#00ff00' : '#ffaa00';
}
}, 1000);
}
clearLogs() {
this.logs = [];
this.updateDisplay();
this.log('调试日志已清理', 'info');
}
}
// ===== 增强版应用类 - 添加缺失的性能监控方法 =====
class EnhancedCatgirlApp extends CatgirlApp {
constructor() {
super();
this.debugPanel = new DebugPanel();
this.requestInterceptor = new RequestInterceptor();
this.performanceMonitor = null;
}
async initialize() {
// 先执行父类初始化
await super.initialize();
// 增强功能初始化 - 延迟执行避免依赖问题
setTimeout(() => {
try {
// 启动请求拦截
this.requestInterceptor.start();
this.setupRequestHandlers();
// 设置错误监控
this.setupErrorHandling();
// 调试面板
if (CONFIG.features.debugMode) {
this.debugPanel.show();
}
// 性能监控
if (CONFIG.features.performanceMonitoring) {
this.startPerformanceMonitoring();
}
} catch (error) {
console.error('🐱 增强功能初始化失败:', error);
}
}, 1000);
}
// 新增:性能监控方法
startPerformanceMonitoring() {
if (!performance || !performance.mark) {
if (CONFIG.features.debugMode) {
console.warn('🐱 浏览器不支持性能API');
}
return;
}
if (this.performanceMonitor) {
clearInterval(this.performanceMonitor);
}
this.performanceMonitor = setInterval(() => {
try {
performance.mark('catgirl-perf-start');
// 获取内存使用情况
const memUsage = performance.memory ?
(performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(1) : 'N/A';
performance.mark('catgirl-perf-end');
performance.measure('catgirl-perf', 'catgirl-perf-start', 'catgirl-perf-end');
const measures = performance.getEntriesByType('measure');
const lastMeasure = measures[measures.length - 1];
// 性能警告
if (lastMeasure && lastMeasure.duration > 100) {
if (this.debugPanel) {
this.debugPanel.log(`性能警告: 检查耗时 ${lastMeasure.duration.toFixed(2)}ms, 内存: ${memUsage}MB`, 'warn');
}
}
// 内存警告
if (performance.memory && performance.memory.usedJSHeapSize > 100 * 1024 * 1024) { // 100MB
if (this.debugPanel) {
this.debugPanel.log(`内存警告: 当前使用 ${memUsage}MB`, 'warn');
}
}
// 清理旧的性能记录
if (measures.length > 50) {
performance.clearMeasures('catgirl-perf');
performance.clearMarks('catgirl-perf-start');
performance.clearMarks('catgirl-perf-end');
}
} catch (error) {
if (CONFIG.features.debugMode) {
console.error('🐱 性能监控出错:', error);
}
}
}, 10000); // 每10秒检查一次
if (this.debugPanel) {
this.debugPanel.log('性能监控已启动', 'info');
}
}
// 新增:停止性能监控
stopPerformanceMonitoring() {
if (this.performanceMonitor) {
clearInterval(this.performanceMonitor);
this.performanceMonitor = null;
if (this.debugPanel) {
this.debugPanel.log('性能监控已停止', 'info');
}
}
}
setupRequestHandlers() {
try {
// B站评论相关请求
this.requestInterceptor.addHandler(
/x\/web-interface\/archive\/desc2|reply\/web/,
(data, url) => this.handleBilibiliComment(data, url)
);
// B站搜索请求
this.requestInterceptor.addHandler(
/x\/web-interface\/search/,
(data, url) => this.handleSearchResults(data, url)
);
} catch (error) {
if (CONFIG.features.debugMode) {
console.error('🐱 设置请求处理器失败:', error);
}
}
}
handleBilibiliComment(data, url) {
if (this.debugPanel) {
this.debugPanel.log('检测到B站评论数据加载', 'info');
}
setTimeout(() => {
if (this.isRunning) {
this.processBilibiliSpecial();
}
}, 1500);
}
handleSearchResults(data, url) {
if (this.debugPanel) {
this.debugPanel.log('检测到搜索结果', 'info');
}
setTimeout(() => {
if (this.isRunning) {
this.processPage();
}
}, 800);
}
setupErrorHandling() {
try {
// 使用更安全的错误监听方式
window.addEventListener('error', (event) => {
if (event.filename && event.filename.includes('catgirl')) {
if (this.debugPanel) {
this.debugPanel.log(`脚本错误: ${event.message} (第${event.lineno}行)`, 'error');
}
}
});
window.addEventListener('unhandledrejection', (event) => {
if (this.debugPanel) {
this.debugPanel.log(`未处理的Promise拒绝: ${event.reason}`, 'error');
}
});
// 添加自定义错误日志方法
this.logError = (message, error = null) => {
const errorMsg = error ? `${message}: ${error.message || error}` : message;
if (this.debugPanel) {
this.debugPanel.log(errorMsg, 'error');
}
if (CONFIG.features.debugMode) {
console.error('🐱 猫娘脚本错误:', errorMsg);
}
};
} catch (error) {
console.error('🐱 错误处理设置失败:', error);
}
}
toggleDebug() {
super.toggleDebug();
if (CONFIG.features.debugMode) {
this.debugPanel.show();
this.debugPanel.log('调试模式已开启', 'success');
// 同时启动性能监控
if (CONFIG.features.performanceMonitoring) {
this.startPerformanceMonitoring();
}
} else {
this.debugPanel.hide();
this.stopPerformanceMonitoring();
}
}
clearDebugLog() {
if (this.debugPanel) {
this.debugPanel.clearLogs();
}
}
stop() {
super.stop();
this.requestInterceptor.stop();
this.stopPerformanceMonitoring();
if (this.debugPanel) {
this.debugPanel.log('系统已停止', 'warn');
}
}
// 重写清理缓存方法,包含前缀重复问题的清理
clearCache() {
super.clearCache();
// 清理处理标记
const processedElements = document.querySelectorAll('[data-catgirl-processed]');
processedElements.forEach(el => {
el.removeAttribute('data-catgirl-processed');
});
const processedShadows = document.querySelectorAll('[data-catgirl-shadow-processed]');
processedShadows.forEach(el => {
el.removeAttribute('data-catgirl-shadow-processed');
});
// 清理性能记录
if (performance && performance.clearMeasures) {
performance.clearMeasures('catgirl-perf');
performance.clearMarks('catgirl-perf-start');
performance.clearMarks('catgirl-perf-end');
}
if (this.debugPanel) {
this.debugPanel.log('缓存、标记和性能记录已清理', 'success');
}
}
// 新增:获取性能统计
getPerformanceStats() {
if (!performance || !performance.memory) {
return {
memory: 'N/A',
measures: 0,
supported: false
};
}
return {
memory: (performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(1) + 'MB',
memoryLimit: (performance.memory.jsHeapSizeLimit / 1024 / 1024).toFixed(1) + 'MB',
measures: performance.getEntriesByType('measure').length,
supported: true
};
}
}
// ===== 工具函数 =====
function showToast(message, type = 'info', duration = 3000) {
const toast = document.createElement('div');
toast.className = 'catgirl-toast';
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新增功能:智能处理、性能优化、UI美化`, 'success', 5000);
}
// ===== 启动增强版应用 =====
const enhancedApp = new EnhancedCatgirlApp();
// 启动应用
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => enhancedApp.initialize(), { once: true });
} else {
setTimeout(() => enhancedApp.initialize(), 100);
}
// ===== 样式注入 - 优化UI =====
GM_addStyle(`
/* 设置面板样式 - 现代化设计 */
#catgirl-settings {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
line-height: 1.5;
}
#catgirl-settings .settings-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 {
margin: 0;
font-size: 20px;
font-weight: 600;
}
#catgirl-settings .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 {
background: rgba(255,255,255,0.3);
transform: scale(1.1);
}
#catgirl-settings .settings-content {
padding: 24px;
background: #ffffff;
border-radius: 0 0 12px 12px;
}
#catgirl-settings .settings-tabs {
display: flex;
margin-bottom: 24px;
background: #f8f9fa;
border-radius: 8px;
padding: 4px;
}
#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;
}
#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 {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
font-size: 14px;
}
#catgirl-settings 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 {
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 {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
#catgirl-settings 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(3, 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 {
background: #f8f9fa;
padding: 16px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
#catgirl-settings .info-section h4 {
margin: 0 0 12px 0;
color: #495057;
font-size: 16px;
}
#catgirl-settings .info-section p {
margin: 6px 0;
color: #6c757d;
font-size: 14px;
}
#catgirl-settings .actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid #e9ecef;
}
#catgirl-settings button {
padding: 12px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
flex: 1;
min-width: 100px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
#catgirl-settings .btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
#catgirl-settings .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;
}
#catgirl-settings .btn-warning:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.4);
}
#catgirl-settings .btn-secondary {
background: linear-gradient(135deg, #6c757d 0%, #495057 100%);
color: white;
}
#catgirl-settings .btn-secondary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(108, 117, 125, 0.4);
}
#catgirl-settings .btn-info {
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
color: white;
}
#catgirl-settings .btn-info:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(23, 162, 184, 0.4);
}
/* 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-debug {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
backdrop-filter: blur(10px);
}
/* 响应式设计 */
@media (max-width: 600px) {
#catgirl-settings {
width: 90vw;
max-height: 90vh;
}
#catgirl-settings .actions {
flex-direction: column;
}
#catgirl-settings .stats-grid {
grid-template-columns: 1fr;
}
}
/* 滚动条美化 */
#catgirl-settings ::-webkit-scrollbar {
width: 8px;
}
#catgirl-settings ::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
#catgirl-settings ::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
#catgirl-settings ::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
`);
// ===== 全局调试接口 =====
window.catgirlApp = {
app: enhancedApp,
config: CONFIG,
version: SCRIPT_VERSION,
utils: {
DebounceUtils,
EnhancedPerformanceUtils,
StateManager,
EnhancedTextProcessor,
ContextAnalyzer,
SettingsPanel,
DebugPanel,
RequestInterceptor
},
clearCache: function() {
enhancedApp.clearCache();
},
clearDebugLog: function() {
enhancedApp.clearDebugLog();
}
}; // ← 添加了这个分号
})(); // ← 添加了这两个闭合括号和分号