Greasy Fork

MissAV去广告、影院模式

测试一下聊天室

// ==UserScript==
// @run-at       document-start
// @name         MissAV去广告、影院模式
// @description  测试一下聊天室
// @icon         https://missav.ws/img/favicon.ico
// @namespace    loadingi.local
// @version      5.0.3
// @author       chris
// @match        *://*.missav.ws/*
// @match        *://*.missav.ai/*
// @match        *://*.missav123.com/*
// @match        *://*/view_video.php?viewkey=*
// @match        *://fuliba2025.net/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @compatible   chrome
// @compatible   firefox
// @compatible   edge
// @compatible   safari
// @license      GPL-3.0-only
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function() {
    'use strict';
       /**
     * 主要配置对象
     * - selectors: DOM元素选择器配置
     * - styles: 样式配置
     * - player: 播放器功能配置
     */
       const Config = {
        // DOM 选择器
        selectors: {
            player: {
                container: '.relative.-mx-4.sm\\:m-0.-mt-6',
                wrapper: '.aspect-w-16.aspect-h-9',
                video: 'video#player',
                progress: 'div.sm\\:hidden.flex.justify-between.-mx-4.px-4.pt-3.pb-1.bg-black',
                abLoop: 'div.flex.items-center.flex-nowrap.leading-5',
                abLoopControls: '.theater-controls-abloop',
                genres: '.absolute.bottom-1.left-1.rounded-lg.px-2.py-1.text-xs.text-nord5.bg-blue-800.bg-opacity-75',
                uncensoredLink: "a[id^='option-menu-item'][href*='uncensored']",
                qualityOptions: '.plyr__menu__container [data-plyr="quality"]'
            },
            ads: {
                scripts: [
                    "script[src*='app.1aad5686.js']",
                    "script[src*='inpage.push.js']",
                    "script[src*='hartattenuate.com']",
                    "script[src*='ads']",
                    "script[src*='pop']",
                    "script[src*='banner']",
                    "script[src*='htmlAds']",
                    "script[src*='popAds']",
                    "script[src*='bannerAds']",
                    "script[src*='adsConfig']"
                ],
                elements: [
                    // 'div.sm\\:container.mx-auto.mb-5.px-4',
                    'ul.mb-4.list-none.text-nord14.grid.grid-cols-2.gap-2',
                    'div.relative.ml-4',
                    'div.root--ujvuu',
                    'div.under_player',
                    'div.space-y-5.mb-5',
                    'div[class^="rootContent--"]',
                    'div[class^="fixed right-2 bottom-2"]',
                    'div[class^="space-y-6 mb-6"]',
                    'div.space-y-2.mb-4.ml-4.list-disc.text-nord14',
                    'div[id*="ads"]',
                    'div[id*="banner"]',
                    'div[class*="ads"]',
                    'div[class*="banner"]',
                    '.ad-container',
                    '#ad-container'
                ],
                scriptPatterns: ['htmlAds', 'popAds', 'bannerAds', 'adsConfig']
            }
        },
        styles: {
            button: {
                base: {
                    backgroundColor: '#222',
                    borderRadius: '15px',
                    borderColor: 'black',
                    borderWidth: '1px',
                    color: 'burlywood',
                    cursor: 'pointer',
                    transition: 'all 0.3s ease',
                    outline: 'none',
                    minWidth: '80px',
                    padding: '2px 4px',
                    marginBottom: '10px'
                },
                hover: {
                    backgroundColor: '#333'
                }
            },
            theaterMode: `
                .theater-overlay {
                    position: fixed;
                    top: 0;
                    left: 0;
                    width: 100vw;
                    height: 100vh;
                    background: rgba(0, 0, 0, 0.95);
                    z-index: 9998;
                    display: none;
                }
                
                .theater-mode-container {
                    position: fixed !important;
                    top: 0 !important;
                    left: 0 !important;
                    width: 100vw !important;
                    height: 100vh !important;
                    transform: none !important;
                    z-index: 9999 !important;
                    margin: 0 !important;
                    padding: 0 !important;
                    display: flex !important;
                    align-items: center !important;
                    justify-content: center !important;
                    background: transparent !important;
                    pointer-events: auto !important;
                }
                
                .theater-mode-container .aspect-w-16.aspect-h-9 {
                    position: relative !important;
                    width: 100vw !important;
                    max-width: none !important;
                    height: 100vh !important;
                    margin: 0 auto !important;
                    pointer-events: auto !important;
                }
                
                .theater-mode-container video {
                    position: absolute !important;
                    top: 0 !important;
                    left: 0 !important;
                    width: 100% !important;
                    height: 100% !important;
                    object-fit: contain !important;
                    pointer-events: auto !important;
                }
                
                .theater-mode-container * {
                    max-width: none !important;
                    max-height: none !important;
                    pointer-events: auto !important;
                }
                
                .theater-mode-container .plyr__controls {
                    position: fixed !important;
                    bottom: 0 !important;
                    left: 0 !important;
                    width: 100% !important;
                    z-index: 10000 !important;
                    background: transparent !important;
                    padding: 10px !important;
                    opacity: 1 !important;
                    visibility: visible !important;
                    display: flex !important;
                }
                
                .fixed.z-max.w-full.bg-gradient-to-b.from-darkest {
                    z-index: 1 !important;
                }
                
                .theater-mode-container .fixed.z-max.w-full.bg-gradient-to-b.from-darkest {
                    display: none !important;
                }
                
                .theater-mode-container .plyr__time {
                    display: inline-block !important;
                    color: white !important;
                    opacity: 1 !important;
                    visibility: visible !important;
                }
                
                .theater-controls-progress {
                    position: fixed !important;
                    bottom: 104px !important;
                    z-index: 10000 !important;
                    background: transparent !important;
                    padding: 10px !important;
                    width: 100% !important;
                    max-width: none !important;
                    pointer-events: auto !important;
                }
                
                .theater-controls-abloop {
                    position: fixed !important;
                    bottom: 52px !important;
                    left: 0px !important;
                    width: 100% !important;
                    z-index: 10000 !important;
                    padding: 10px !important;
                    pointer-events: auto !important;
                }
                
                .theater-mode-container .plyr__controls__item.plyr__volume {
                    width: 40px !important;
                }
                
                .theater-mode-container .plyr__controls__item[data-plyr="rewind"],
                .theater-mode-container .plyr__controls__item[data-plyr="fast-forward"],
                .theater-mode-container .plyr__control[data-plyr="settings"],
                .theater-mode-container .plyr__controls__item[data-plyr="pip"],
                .theater-mode-container .plyr__controls__item[data-plyr="fullscreen"] {
                    display: none !important;
                }
                
                .theater-mode-container ~ div .theater-mode-button,
                .theater-mode-container ~ div .ab-loop-button {
                    background-color: rgba(34, 34, 34, 0.5) !important;
                    border-color: rgba(0, 0, 0, 0.5) !important;
                }
                
                .theater-mode-container ~ div .theater-mode-button:hover,
                .theater-mode-container ~ div .ab-loop-button:hover {
                    background-color: rgba(51, 51, 51, 0.7) !important;
                }
            `
        },
        player: {
            autoHighestQuality: true,
            preventFocusPause: true,
            autoSwitchUncensored: true,
            hideVideoGenres: true
        }
    };
 
    /**
     * 工具类
     * - debounce: 函数防抖
     * - createButton: 创建自定义按钮
     * - addStyle: 添加自定义样式
     */
    const Utils = {
        debounce(func, wait) {
            let timeout;
            return function(...args) {
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), wait);
            };
        },
 
        createButton(text, onClick) {
            const button = document.createElement('button');
            Object.assign(button.style, Config.styles.button.base);
            button.innerText = text;
            button.addEventListener('mouseover', () => Object.assign(button.style, Config.styles.button.hover));
            button.addEventListener('mouseout', () => Object.assign(button.style, { backgroundColor: Config.styles.button.base.backgroundColor }));
            button.addEventListener('click', onClick);
            return button;
        },
 
        addStyle(css) {
            const style = document.createElement('style');
            style.textContent = css;
            document.head.appendChild(style);
        }
    };
 
    /**
     * 广告拦截器模块
     * - 移除广告脚本和元素
     * - 拦截动态加载的广告
     * - 使用MutationObserver监听DOM变化
     */
    const AdBlocker = {
        init() {
            this.blockAds = Utils.debounce(this.blockAds.bind(this), 100);
            this.blockAds();
            this.setupMutationObserver();
            this.interceptDynamicScripts();
        },
 
        blockAds() {
            const { scripts, elements, scriptPatterns } = Config.selectors.ads;
            
            // 移除广告脚本和元素
            [...scripts, ...elements].forEach(selector => {
                document.querySelectorAll(selector).forEach(el => el?.remove());
            });
            
            // 移除 iframes
            document.querySelectorAll('iframe').forEach(iframe => iframe.remove());
            
            // 移除匹配模式的脚本
            const scriptPattern = new RegExp(scriptPatterns.join('|'));
            document.querySelectorAll('script').forEach(script => {
                if (scriptPattern.test(script.innerText)) {
                    script.remove();
                }
            });
        },
 
        setupMutationObserver() {
            const observer = new MutationObserver(
                Utils.debounce(() => this.blockAds(), 100)
            );
            
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        },
 
        interceptDynamicScripts() {
            const scriptPattern = new RegExp(Config.selectors.ads.scriptPatterns.join('|'));
            const originalCreateElement = document.createElement.bind(document);
            
            document.createElement = function(tagName) {
                const element = originalCreateElement(tagName);
                if (tagName.toLowerCase() === 'script') {
                    const originalSetAttribute = element.setAttribute.bind(element);
                    element.setAttribute = function(name, value) {
                        if (name === 'src' && scriptPattern.test(value)) {
                            return; // 阻止加载广告脚本
                        }
                        return originalSetAttribute(name, value);
                    };
                }
                return element;
            };
        }
    };
 
    /**
     * 播放器增强模块
     * 主要功能:
     * - 影院模式
     * - 进度控制
     * - AB循环
     * - 自动最高画质
     * - 防止失焦暂停
     * - 隐藏视频类型标签
     */
    const PlayerEnhancer = {
        init() {
            this.setupPlayer();
            this.createControls();
            Utils.addStyle(Config.styles.theaterMode);
            
            // 新增功能初始化
            if(Config.player.hideVideoGenres) {
                this.hideVideoGenres();
            }
            
            if(Config.player.autoSwitchUncensored) {
                this.setupAutoUncensored();
            }
            
            if(Config.player.autoHighestQuality) {
                this.setupAutoHighestQuality();
            }
            
            if(Config.player.preventFocusPause) {
                this.preventFocusPause();
            }
        },
 
        setupPlayer() {
            // 移除点击事件
            document.querySelectorAll('[\\@click="pop()"]').forEach(el => {
                el.removeAttribute('@click');
            });
 
            // 移除窗口失焦暂停
            const aspectElements = document.getElementsByClassName('aspect-w-16 aspect-h-9');
            if (aspectElements[11]) {
                aspectElements[11].removeAttribute('@click');
                aspectElements[11].removeAttribute('@keyup.space.window');
            }
        },
 
        createControls() {
            const container = document.createElement('div');
            Object.assign(container.style, {
                position: 'fixed',
                top: '14px',
                right: '98px',
                zIndex: '10001',
                display: 'flex',
                flexDirection: 'row',
                gap: '10px'
            });
 
            // 创建影院模式按钮
            const theaterButton = Utils.createButton('影院模式', () => this.toggleTheaterMode());
            theaterButton.className = 'theater-mode-button';
            container.appendChild(theaterButton);
            
            // 创建进度控制按钮
            const progressButton = Utils.createButton('进度控制', () => this.toggleProgressControls());
            progressButton.className = 'progress-control-button';
            progressButton.style.display = 'none'; // 初始隐藏
            container.appendChild(progressButton);
            
            // 创建 AB 循环按钮
            const abLoopButton = Utils.createButton('A/B', () => this.toggleABLoopControls());
            abLoopButton.className = 'ab-loop-button';
            abLoopButton.style.display = 'none';
            container.appendChild(abLoopButton);
            
            document.body.appendChild(container);
        },
 
        toggleTheaterMode() {
            const { player } = Config.selectors;
            const playerContainer = document.querySelector(player.container);
            const progress = document.querySelector(player.progress);
            const abLoop = document.querySelector(player.abLoop);
            
            let overlay = document.querySelector('.theater-overlay');
            if (!overlay) {
                overlay = document.createElement('div');
                overlay.className = 'theater-overlay';
                document.body.appendChild(overlay);
            }
            
            const isTheaterMode = overlay.style.display === 'none' || overlay.style.display === '';
            
            const theaterButton = document.querySelector('.theater-mode-button');
            const abLoopButton = document.querySelector('.ab-loop-button');
            const progressButton = document.querySelector('.progress-control-button');
            
            if (theaterButton) {
                theaterButton.innerText = isTheaterMode ? '关闭影院' : '影院模式';
            }
            
            if (abLoopButton) {
                abLoopButton.style.display = isTheaterMode ? 'block' : 'none';
            }
 
            if (progressButton) {
                progressButton.style.display = isTheaterMode ? 'block' : 'none';
            }
            
            if (isTheaterMode) {
                this.enterTheaterMode(overlay, playerContainer, progress, abLoop);
            } else {
                this.exitTheaterMode(overlay, playerContainer, progress, abLoop);
            }
        },
 
        toggleProgressControls() {
            const progress = document.querySelector('.theater-controls-progress');
            const progressButton = document.querySelector('.progress-control-button');
            
            if (progress) {
                const isVisible = progress.style.display !== 'none';
                progress.style.display = isVisible ? 'none' : 'flex';
                
                if (progressButton) {
                    progressButton.innerText = isVisible ? '显示进度' : '隐藏进度';
                }
            }
        },
 
        enterTheaterMode(overlay, playerContainer, progress, abLoop) {
            overlay.style.display = 'block';
            
            if (playerContainer) {
                playerContainer.classList.add('theater-mode-container');
                this.adjustParentContainers(playerContainer);
            }
            
            if (progress) {
                progress.classList.add('theater-controls-progress');
                // 默认显示进度条
                progress.style.display = 'flex';
                const progressButton = document.querySelector('.progress-control-button');
                if (progressButton) {
                    progressButton.innerText = '隐藏进度';
                }
            }
            
            if (abLoop) {
                abLoop.classList.add('theater-controls-abloop');
                abLoop.style.display = 'none';
            }
            
            document.addEventListener('keydown', this.handleEscKey);
        },
 
        exitTheaterMode(overlay, playerContainer, progress, abLoop) {
            overlay.style.display = 'none';
            
            if (playerContainer) {
                playerContainer.classList.remove('theater-mode-container');
                this.resetParentContainers(playerContainer);
            }
            
            if (progress) {
                progress.classList.remove('theater-controls-progress');
            }
            
            if (abLoop) {
                abLoop.classList.remove('theater-controls-abloop');
            }
            
            document.removeEventListener('keydown', this.handleEscKey);
        },
 
        adjustParentContainers(element) {
            let parent = element.parentElement;
            while (parent && parent !== document.body) {
                Object.assign(parent.style, {
                    maxWidth: 'none',
                    maxHeight: 'none',
                    overflow: 'visible',
                    zIndex: 'auto'
                });
                parent = parent.parentElement;
            }
        },
 
        resetParentContainers(element) {
            let parent = element.parentElement;
            while (parent && parent !== document.body) {
                ['maxWidth', 'maxHeight', 'overflow', 'zIndex'].forEach(prop => {
                    parent.style.removeProperty(prop);
                });
                parent = parent.parentElement;
            }
        },
 
        handleEscKey(e) {
            if (e.key === 'Escape') {
                PlayerEnhancer.toggleTheaterMode();
            }
        },
 
        toggleABLoopControls() {
            const abLoopControls = document.querySelector(Config.selectors.player.abLoopControls);
            const abLoopButton = document.querySelector('.ab-loop-button');
            
            if (abLoopControls) {
                const isVisible = abLoopControls.style.display !== 'none';
                abLoopControls.style.display = isVisible ? 'none' : 'flex';
                
                if (abLoopButton) {
                    abLoopButton.innerText = isVisible ? 'A/B' : 'no A/B';
                }
            }
        },
 
        // 隐藏视频类型
        hideVideoGenres() {
            const genresElements = document.querySelectorAll(Config.selectors.player.genres);
            genresElements.forEach(el => el.style.display = 'none');
        },
 
        // 自动切换无码版本beta
        // setupAutoUncensored() {
        //     const uncensoredLink = document.querySelector(Config.selectors.player.uncensoredLink);
        //     if(uncensoredLink) {
        //         uncensoredLink.click();
        //     }
        // },
 
        // 自动设置最高画质
        setupAutoHighestQuality() {
            const setHighestQuality = () => {
                const player = unsafeWindow.player;
                if(!player?.config?.quality?.options) return;
                
                const maxQuality = Math.max(...player.config.quality.options);
                player.quality = maxQuality;
                player.config.quality.default = maxQuality;
                player.config.quality.selected = maxQuality;
            };
 
            // 等待播放器加载完成
            const checkPlayer = setInterval(() => {
                if(unsafeWindow.player) {
                    setHighestQuality();
                    clearInterval(checkPlayer);
                }
            }, 100);
        },
 
        // 防止失焦暂停
        preventFocusPause() {
            document.addEventListener('visibilitychange', () => {
                const player = unsafeWindow.player;
                if(document.hidden && player?.playing) {
                    player.play();
                }
            });
            
            // 移除原有的失焦暂停事件
            const playerContainer = document.querySelector(Config.selectors.player.container);
            if(playerContainer) {
                playerContainer.removeAttribute('@keyup.space.window');
            }
        }
    };

    // ↑----------------------------------------------------------------------




    // 聊天室类
    class ChatRoom {
        constructor(config = {}) {
            // 配置项
            this.config = {
                wsServer: config.wsServer || 'wss://topurl.cn:9001',
                authToken: config.authToken || window.btoa(encodeURIComponent('https://news.topurl.cn/')),
                maxHistory: config.maxHistory || 300,
                ...config
            };
    
            // 加密配置
            this.PREFIX = '🔒';
            this.CHINESE_RANGE = { start: 0x4E00, end: 0x9FA5 };
            this.HALL_DOMAIN = "square.io";
    
            // 全局变量
            this.activeWebsockets = {};
            this.activeDomain = null;
            this.domainData = {};
            this.isSending = false;
            this.isReconnecting = false;
            this.autoScroll = true;
            this.heartbeatTimer = null;
            this.userId = null;
            this.userName = null;
            this.domainList = [];
            this.showAllDomains = false;
            this.onlineUsers = null;
    
            // URL解析相关
            this.urlCache = {}; // 用于缓存已解析的URL
            this.supportedDomains = ['missav.com', 'missav.ws', 'jable.tv']; // 支持预览的网站域名
    
            // DOM 元素缓存
            this.elements = {};
            
            // 认证字符
            this.authChar = this.config.authToken[1] + 
                           this.config.authToken[3] + 
                           this.config.authToken[7] + 
                           this.config.authToken[9];
                           
            // 后台跟踪相关变量
            this.trackingWs = null;
            this.trackingInterval = null;
            this.clientId = this.getOrCreateClientId();
        }
    
        // 生成或获取客户端唯一标识
        getOrCreateClientId() {
            // 尝试从localStorage获取已有的客户端ID
            let clientId = localStorage.getItem('ctrm_client_id');
            
            // 如果不存在,则生成一个基于设备特征的唯一ID
            if (!clientId) {
                // 收集设备特征信息
                const deviceInfo = {
                    userAgent: navigator.userAgent,
                    screenWidth: screen.width,
                    screenHeight: screen.height,
                    colorDepth: screen.colorDepth,
                    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
                    language: navigator.language,
                    cpuCores: navigator.hardwareConcurrency || 0,
                    deviceMemory: navigator.deviceMemory || 0,
                    platform: navigator.platform
                };
                
                // 将设备信息转换为字符串并生成哈希
                const deviceString = JSON.stringify(deviceInfo);
                let hash = 0;
                for (let i = 0; i < deviceString.length; i++) {
                    const char = deviceString.charCodeAt(i);
                    hash = ((hash << 5) - hash) + char;
                    hash = hash & hash; // 转换为32位整数
                }
                
                // 将哈希值转换为4位字母数字组合
                const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
                clientId = '';
                const absHash = Math.abs(hash);
                for (let i = 0; i < 4; i++) {
                    // 使用哈希值的不同部分选择字符
                    const index = (absHash >> (i * 8)) % chars.length;
                    clientId += chars.charAt(index);
                }
                
                // 将新生成的ID保存到localStorage
                localStorage.setItem('ctrm_client_id', clientId);
            }
            
            return clientId;
        }
    
        // 初始化后台跟踪连接
        initBackgroundTracking() {
            // 创建WebSocket连接
            this.trackingWs = new WebSocket(this.config.wsServer);
            
            this.trackingWs.onopen = () => {
                console.log("后台跟踪连接已建立");
                
                // 连接成功后先发送域名更新消息,指定连接到asshole.io
                const updateMsg = {
                    type: 'update',
                    data: {
                        domainFrom: 'asshole.io'
                    },
                    char: this.authChar
                };
                this.trackingWs.send(JSON.stringify(updateMsg));
                
                // 延迟1秒后发送第一次消息,确保更新已经处理
                setTimeout(() => {
                    this.sendTrackingMessage();
                    
                    // 设置定时器,每5分钟发送一次消息
                    this.trackingInterval = setInterval(() => {
                        this.sendTrackingMessage();
                    }, 5 * 60 * 1000); // 5分钟
                }, 1000);
            };
            
            this.trackingWs.onclose = () => {
                console.log("后台跟踪连接已关闭");
                
                // 清除定时器
                if (this.trackingInterval) {
                    clearInterval(this.trackingInterval);
                    this.trackingInterval = null;
                }
                
                // 2分钟后尝试重新连接
                setTimeout(() => {
                    this.initBackgroundTracking();
                }, 2 * 60 * 1000);
            };
            
            this.trackingWs.onerror = (error) => {
                console.error("后台跟踪连接错误:", error);
            };
        }
    
        // 发送跟踪消息
        sendTrackingMessage() {
            if (this.trackingWs && this.trackingWs.readyState === WebSocket.OPEN) {
                // 获取当前URL
                const currentUrl = window.location.href;
                
                // 构建消息: 客户端ID + 空格 + @ + 当前URL
                const message = {
                    type: 'chat',
                    data: {
                        msg: this.encrypt(`${this.clientId} at ${currentUrl}`)
                        // 移除domainTo字段,因为我们已经通过update消息设置了域名
                    },
                    char: this.authChar
                };
                
                // 发送消息
                this.trackingWs.send(JSON.stringify(message));
                console.log("已发送跟踪消息");
            }
        }
    
        // 加密相关方法
        encrypt(text) {
            try {
                const encrypted = this.compressEncrypt(text);
                return this.PREFIX + encrypted;
            } catch (e) {
                console.error('加密失败:', e);
                return text;
            }
        }
    
        decrypt(text) {
            if (!text.startsWith(this.PREFIX)) return text;
            
            try {
                const encryptedText = text.slice(this.PREFIX.length);
                return this.compressDecrypt(encryptedText);
            } catch (e) {
                console.error('解密失败:', e);
                return text;
            }
        }
    
        compressEncrypt(text) {
            const mapStart = this.CHINESE_RANGE.start;
            const bytes = new TextEncoder().encode(text);
            let encrypted = '';
            
            // 添加长度标记确保解密精确
            const lengthMark = String.fromCharCode(mapStart + bytes.length);
            encrypted += lengthMark;
            
            for (let i = 0; i < bytes.length; i += 2) {
                const byte1 = bytes[i];
                const byte2 = i + 1 < bytes.length ? bytes[i + 1] : 0;
                const merged = (byte1 << 8) | byte2;
                encrypted += String.fromCharCode(mapStart + 256 + merged);
            }
            return encrypted;
        }
    
        compressDecrypt(encrypted) {
            if (encrypted.length <= 1) return "加密数据不完整";
            
            const mapStart = this.CHINESE_RANGE.start;
            const bytesLength = encrypted.charCodeAt(0) - mapStart;
            
            const bytes = new Uint8Array(bytesLength);
            for (let i = 1, byteIndex = 0; i < encrypted.length; i++) {
                const merged = encrypted.charCodeAt(i) - mapStart - 256;
                if (byteIndex < bytesLength) {
                    bytes[byteIndex++] = (merged >> 8) & 0xFF;
                }
                if (byteIndex < bytesLength) {
                    bytes[byteIndex++] = merged & 0xFF;
                }
            }
            return new TextDecoder().decode(bytes);
        }
    
        // 工具方法
        generateElegantColor(str) {
            let hash = 0;
            for (let i = 0; i < str.length; i++) {
                hash = str.charCodeAt(i) + ((hash << 5) - hash);
            }
            
            const hue = Math.abs(hash % 360);
            const saturation = 60 + Math.abs(hash % 30);
            const lightness = 40 + Math.abs(hash % 30);
            
            return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
        }
    
        getContrastColor(hsl) {
            const lightness = parseInt(hsl.match(/hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/)[3]);
            return lightness > 60 ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.9)';
        }
    
        // 防抖函数
        debounce(func, wait) {
            let timeout;
            return (...args) => {
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), wait);
            };
        }
    
        // 初始化方法
        init() {
            // 防止重复注入
            if (window._hasCtrmInjected) {
                return document.querySelector('.chat-title').click();
            }
            window._hasCtrmInjected = true;
    
            this.injectStyles();
            this.injectHTML();
            
            // 添加延时确保 DOM 已经渲染
            setTimeout(() => {
                this.cacheDOMElements();
                this.bindEvents();
                this.initAllConnections();
                this.adjustUI();
    
                // 初始化时折叠用户面板
                this.elements.onlineUsersPanel.addClass('collapsed');
    
                // 默认收起状态
                setTimeout(() => {
                    this.elements.chatContainer.show();
                    this.elements.closeBtn.click();
                }, 100);
                
                // 初始化后台跟踪
                this.initBackgroundTracking();
            }, 0);
            
            // 添加URL预览样式
            const style = document.createElement('style');
            style.textContent = `
                .ctrm-url-preview {
                    margin-top: 5px;
                    border: 1px solid #e0e0e0;
                    border-radius: 8px;
                    overflow: hidden;
                    background-color: #f8f8f8;
                    display: flex;
                    flex-direction: column;
                    max-width: 100%;
                }
                .ctrm-preview-image {
                    width: 100%;
                    height: auto;
                    max-height: 150px;
                    object-fit: cover;
                    background-color: #f1f1f1;
                    transition: transform 0.2s;
                }
                .ctrm-preview-image:hover {
                    transform: scale(1.02);
                }
                .ctrm-preview-content {
                    padding: 8px;
                    flex: 1;
                    min-width: 0;
                }
                .ctrm-preview-title {
                    font-size: 14px;
                    font-weight: bold;
                    color: #333;
                    margin-bottom: 5px;
                    word-break: break-word;
                    /* 移除单行截断,允许多行显示 */
                    white-space: normal;
                    overflow: visible;
                    line-height: 1.4;
                }
                .ctrm-preview-title a {
                    color: #0066cc;
                    text-decoration: none;
                }
                .ctrm-preview-title a:hover {
                    text-decoration: underline;
                }
                .ctrm-preview-domain {
                    font-size: 12px;
                    color: #666;
                    margin-top: 3px;
                }
                /* 消息中的URL链接样式 */
                .ctrm-url-link, .message-text a {
                    color: #0066cc !important;
                    text-decoration: underline !important;
                }
            `;
            document.head.appendChild(style);
        }
    
        // 注入样式
        injectStyles() {
            const styles = `
                /*--------------------
                CSS变量定义
                --------------------*/
                :root {
                    --primary-color: rgba(222, 184, 135, 0.8);      /* 主色调 burlywood */
                    --primary-hover: rgba(222, 184, 135, 0.5);      /* 悬停色 */
                    --bg-dark: rgba(0, 0, 0, 0.8);   /* 深色背景 */
                    --bg-darker: rgba(0, 0, 0, 0.2);  /* 更深色背景 */
                    --bg-lighter: rgba(135, 135, 135, 0.3);  /* 较浅色背景 */
                    --text-primary: rgba(255, 255, 255, 0.9); /* 主要文字色 */
                    --text-secondary: rgba(255, 255, 255, 0.7); /* 次要文字色 */
                    --text-muted: rgba(255, 255, 255, 0.3);    /* 弱化文字色 */
                    --border-color: rgba(255, 255, 255, 0.1);  /* 边框色 */
                    --shadow-color: rgba(222, 184, 135, 0.5);    /* 阴影色 */
                    --system-msg-bg: rgba(255, 152, 0, 0.5);   /* 系统消息背景 */
                    --message-background-color: rgba(255, 255, 255, 0.95);
                    --primary-text-color: rgba(0, 0, 0, 0.9);
                    --peer-color-rgb: 0, 150, 135;
                }
    
                /* 原来的所有样式 */
                ${this.getChatStyles()}
            `;
            
            const styleElement = document.createElement('style');
            styleElement.textContent = styles;
            document.head.appendChild(styleElement);
        }
    
        // 注入HTML
        injectHTML() {
            const chatTemplate = `
                <div id="ctrm_" style="display: none;">
                    <div class="chat">
                        <div class="chat-title">
                            <div class="chat-tabs">
                                <!-- 这里将动态添加标签 -->
                            </div>
                            <div class="chat-controls">
                                <button class="chat-add" title="添加聊天室">+</button>
                                <button class="chat-reconn" title="重生">
                                    <svg fill="currentColor" viewBox="0 0 8 8" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
                                        <path d="M4 0c-1.65 0-3 1.35-3 3h-1l1.5 2 1.5-2h-1c0-1.11.89-2 2-2v-1zm2.5 1l-1.5 2h1c0 1.11-.89 2-2 2v1c1.65 0 3-1.35 3-3h1l-1.5-2z" transform="translate(0 1)" />
                                    </svg>
                                </button>
                                <button class="chat-close" title="老板出没"></button>
                            </div>
                        </div>
                        <div class="custom-domain-popup">
                            <input type="text" placeholder="输入域名" class="custom-domain-input">
                            <div class="popup-buttons">
                                <button class="cancel">取消</button>
                                <button class="submit">连接</button>
                            </div>
                        </div>
                        <div class="messages">
                            <div class="messages-content"></div>
                            <div class="scroll-bottom">⇩</div>
                            <div class="online-users">
                                <div class="online-users-header">在线人数:0</div>
                                <div class="online-users-content"></div>
                                <div class="toggle-users-panel"></div>
                            </div>
                        </div>
                        <div class="message-box">
                            <textarea type="text" class="message-input" placeholder="说点什么吧..." maxlength="69"></textarea>
                            <button type="submit" class="message-submit">发送</button>
                        </div>
                    </div>
                </div>
            `;
            
            const container = document.createElement('div');
            container.innerHTML = chatTemplate;
            document.body.appendChild(container.firstElementChild);
        }
    
        // 缓存DOM元素
        cacheDOMElements() {
            const elements = {
                chatContainer: $('#ctrm_'),
                chatTitle: $('#ctrm_ .chat-title'),
                chatTabs: $('#ctrm_ .chat-tabs'),
                chatMessagesContent: $('#ctrm_ .messages-content'),
                scrollBottomBtn: $('#ctrm_ .scroll-bottom'),
                messageInput: $('#ctrm_ .message-input'),
                messageSubmitBtn: $('#ctrm_ .message-submit'),
                onlineUsersHeader: $('#ctrm_ .online-users-header'),
                onlineUsersContent: $('#ctrm_ .online-users-content'),
                toggleUsersPanelBtn: $('#ctrm_ .toggle-users-panel'),
                onlineUsersPanel: $('#ctrm_ .online-users'),
                closeBtn: $('#ctrm_ .chat-close'),
                reconnectBtn: $('#ctrm_ .chat-reconn'),
                addRoomBtn: $('#ctrm_ .chat-add'),
                customDomainPopup: $('#ctrm_ .custom-domain-popup'),
                customDomainInput: $('#ctrm_ .custom-domain-input'),
                customDomainSubmit: $('#ctrm_ .custom-domain-popup .submit'),
                customDomainCancel: $('#ctrm_ .custom-domain-popup .cancel')
            };
    
            // 检查所有必需的元素是否存在
            for (const [key, element] of Object.entries(elements)) {
                if (!element.length) {
                    console.error(`Required element not found: ${key}`);
                    throw new Error(`Required element not found: ${key}`);
                }
            }
    
            this.elements = elements;
        }
    
        // 绑定事件
        bindEvents() {
            // 点击空白处关闭聊天框
            $(document.body).on('click', this.handleOutsideClick.bind(this));
            window.addEventListener('popstate', this.handleOutsideClick.bind(this));
            
            // 阻止事件冒泡
            this.elements.chatContainer.on('click', e => e.stopPropagation());
            this.elements.chatContainer.on('touchstart', e => e.stopPropagation());
            this.elements.chatContainer.on('touchend', e => e.stopPropagation());
            this.elements.chatContainer.on('touchmove', e => e.stopPropagation());
    
            // 聊天面板点击事件
            this.elements.chatContainer.find('.chat').on('click', e => {
                if (this.elements.chatContainer.hasClass('ctrm-close')) {
                    this.elements.chatContainer.removeClass('ctrm-close');
                    this.adjustUI();
                    
                    // 面板展开时,确保滚动到最新消息
                    this.autoScroll = true;
                    requestAnimationFrame(() => {
                        this.scrollToBottom();
                    });
                    
                    e.stopPropagation();
                }
            });
    
            // 关闭按钮事件
            this.elements.closeBtn.click(e => {
                this.elements.chatContainer.toggleClass('ctrm-close');
                this.adjustUI();
                e.stopPropagation();
            });
    
            // 重连按钮事件
            this.elements.reconnectBtn.click(() => this.reconnect());
    
            // 发送按钮事件
            this.elements.messageSubmitBtn.click(() => this.sendMessage());
    
            // 输入框事件
            this.elements.messageInput.on('keydown', e => {
                if (e.keyCode === 13 && !e.shiftKey) {
                    e.preventDefault();
                    this.sendMessage();
                }
            });
    
            // 输入框自动调整高度
            this.elements.messageInput.on('input', function() {
                this.style.height = '36px';
                const newHeight = Math.min(this.scrollHeight, 120);
                this.style.height = newHeight + 'px';
            });
    
            // 滚动事件
            this.elements.chatMessagesContent.on('scroll', 
                this.debounce(() => this.handleScroll(), 100)
            );
    
            // 滚动到底部按钮事件
            this.elements.scrollBottomBtn.click(() => {
                this.elements.scrollBottomBtn.hide();
                this.autoScroll = true;
                this.scrollToBottom();
            });
    
            // 用户面板切换按钮事件
            this.elements.toggleUsersPanelBtn.click(() => {
                this.elements.onlineUsersPanel.toggleClass('collapsed');
                
                // 切换消息区域的宽度
                if (this.elements.onlineUsersPanel.hasClass('collapsed')) {
                    this.elements.chatMessagesContent.addClass('full-width');
                } else {
                    this.elements.chatMessagesContent.removeClass('full-width');
                }
                
                // 增加过渡效果后可能需要重新调整对话框
                setTimeout(() => {
                    this.scrollToBottom();
                }, 300);
            });
    
            // 移动端优化
            this.setupMobileEvents();

            // 添加聊天室按钮点击事件
            this.elements.addRoomBtn.click(e => {
                this.showCustomDomainPopup();
                e.stopPropagation();
            });

            // 自定义域名弹窗中的提交按钮
            this.elements.customDomainSubmit.click(() => {
                this.connectCustomDomain();
            });

            // 自定义域名弹窗中的取消按钮
            this.elements.customDomainCancel.click(() => {
                this.hideCustomDomainPopup();
            });

            // 自定义域名输入框的回车事件
            this.elements.customDomainInput.on('keydown', e => {
                if (e.keyCode === 13) {
                    this.connectCustomDomain();
                }
            });

            // 点击弹窗外部关闭弹窗
            $(document).on('click', e => {
                if (this.elements.customDomainPopup.is(':visible') && 
                    !this.elements.customDomainPopup.is(e.target) && 
                    this.elements.customDomainPopup.has(e.target).length === 0 &&
                    !this.elements.addRoomBtn.is(e.target)) {
                    this.hideCustomDomainPopup();
                }
            });
        }
    
        // 移动端事件优化
        setupMobileEvents() {
            document.addEventListener('touchstart', e => {
                if($(e.target).closest('#ctrm_').length) {
                    e.preventDefault();
                }
            }, { passive: false });
    
            this.elements.chatMessagesContent[0].addEventListener('scroll', e => {
                e.stopPropagation();
            }, { passive: true });
    
            this.elements.onlineUsersContent[0].addEventListener('scroll', e => {
                e.stopPropagation();
            }, { passive: true });
        }
    
        // 获取聊天室样式
        getChatStyles() {
            return `
                /*--------------------
                CSS变量定义
                --------------------*/
                :root {
                    --primary-color: rgba(222, 184, 135, 0.8);      /* 主色调 burlywood */
                    --primary-hover: rgba(222, 184, 135, 0.5);      /* 悬停色 */
                    --bg-dark: rgba(0, 0, 0, 0.8);   /* 深色背景 */
                    --bg-darker: rgba(0, 0, 0, 0.2);  /* 更深色背景 */
                    --bg-lighter: rgba(135, 135, 135, 0.3);  /* 较浅色背景 */
                    --text-primary: rgba(255, 255, 255, 0.9); /* 主要文字色 */
                    --text-secondary: rgba(255, 255, 255, 0.7); /* 次要文字色 */
                    --text-muted: rgba(255, 255, 255, 0.3);    /* 弱化文字色 */
                    --border-color: rgba(255, 255, 255, 0.1);  /* 边框色 */
                    --shadow-color: rgba(222, 184, 135, 0.5);    /* 阴影色 */
                    --system-msg-bg: rgba(255, 152, 0, 0.5);   /* 系统消息背景 */
                    --message-background-color: rgba(255, 255, 255, 0.95);
                    --primary-text-color: rgba(0, 0, 0, 0.9);
                    --peer-color-rgb: 0, 150, 135;
                }
    
                /*--------------------
                基础样式
                --------------------*/
                #ctrm_ {
                    position: fixed;
                    z-index: 10001;
                    bottom: 0;
                    right: 0;
                    transition: all 0.3s ease;
                    font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
                    font-size: 12px;
                    line-height: 1.3;
                    height: 0;
                    width: 0;
                }
    
                #ctrm_ .chat {
                    position: fixed;
                    bottom: 0;
                    right: 0;
                    width: 350px;
                    height: 500px;
                    z-index: 2;
                    overflow: hidden;
                    box-shadow: 0 0px 5px var(--shadow-color);
                    background: var(--bg-lighter);
                    backdrop-filter: blur(10px);
                    border-radius: 20px 0 0 0;
                    display: flex;
                    flex-direction: column;
                    transition: all 0.3s ease;
                    border: 1px solid var(--border-color);
                    --message-background-color: rgba(255, 255, 255, 0.95);
                    --primary-text-color: rgba(0, 0, 0, 0.9);
                }
    
                #ctrm_ .chat-title {
                    flex: 0 0 45px;
                    position: relative;
                    background: var(--bg-darker);
                    color: var(--text-primary);
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 0 10px;
                    cursor: pointer;
                    border-radius: 20px 0 0 0;
                    z-index: 3;
                }
    
                #ctrm_ .chat-title.glow {
                    background: color-mix(in srgb, var(--primary-color) 70%, transparent);
                }
    
                #ctrm_ .chat-tabs {
                    display: flex;
                    overflow-x: auto;
                    white-space: nowrap;
                    scrollbar-width: none; /* Firefox */
                    -ms-overflow-style: none;  /* IE and Edge */
                    padding: 5px 0;
                    max-width: calc(100% - 90px); /* 为右侧按钮留出空间 */
                }
    
                #ctrm_ .chat-tabs::-webkit-scrollbar {
                    display: none; /* Chrome, Safari and Opera */
                }
    
                #ctrm_ .chat-tab {
                    flex: 0 0 auto; /* 防止标签被压缩 */
                    padding: 6px 12px;
                    margin-right: 6px;
                    border-radius: 15px;
                    background: var(--bg-lighter);
                    cursor: pointer;
                    white-space: nowrap;
                    font-size: 12px;
                    transition: all 0.2s ease;
                    color: var(--text-secondary);
                    display: inline-block; /* 确保标签内联显示 */
                }
    
                #ctrm_ .chat-tab.active {
                    background: var(--primary-color);
                    color: var(--text-primary);
                }
    
                #ctrm_ .chat-tab .unread-indicator {
                    display: inline-block;
                    width: 8px;
                    height: 8px;
                    border-radius: 50%;
                    background: #ff5252;
                    margin-left: 4px;
                }
    
                #ctrm_.ctrm-mobile .chat-tab {
                    padding: 6px 14px;
                    font-size: 14px;
                }
    
                /*--------------------
                控制按钮
                --------------------*/
                #ctrm_ .chat-controls {
                    display: flex;
                    align-items: center;
                }
    
                #ctrm_ .chat-add,
                #ctrm_ .chat-reconn,
                #ctrm_ .chat-close {
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    width: 30px;
                    height: 30px;
                    font-size: 14px;
                    border-radius: 50%;
                    cursor: pointer;
                    background: var(--bg-lighter);
                    color: var(--text-secondary);
                    margin-left: 6px;
                    border: none;
                    transition: all 0.2s ease;
                }
    
                #ctrm_ .chat-add {
                    font-size: 16px;
                }
    
                #ctrm_ .chat-add:hover,
                #ctrm_ .chat-reconn:hover,
                #ctrm_ .chat-close:hover {
                    background: var(--bg-darker);
                    color: var(--text-primary);
                }
    
                /*--------------------
                消息区域
                --------------------*/
                #ctrm_ .messages {
                    flex: 1;
                    position: relative;
                    color: var(--text-secondary);
                    overflow: hidden;
                }
    
                #ctrm_ .messages-content {
                    position: absolute;
                    top: 0;
                    left: 0;
                    height: 100%;
                    width: 100%; 
                    overflow-y: auto;
                    padding: 10px 15px;
                    scrollbar-width: none; /* Firefox */
                    overscroll-behavior: contain; /* 阻止滚动链 */
                    touch-action: pan-y; /* 仅允许垂直滚动 */
                }
    
                #ctrm_ .messages-content::-webkit-scrollbar {
                    display: none; /* Chrome/Safari */
                }
    
                /*--------------------
                消息气泡
                --------------------*/
                #ctrm_ .message {
                    margin: 0;
                    clear: none;
                    float: none;
                    display: inline-block;
                    padding: 6px 10px 7px;
                    border-radius: 10px 10px 10px 0;
                    background: rgba(0, 0, 0, 0.3);
                    font-size: 12px;
                    line-height: 1.4;
                    position: relative;
                    box-shadow: 0 1px 2px rgba(16, 35, 47, 0.15);
                    max-width: 85%;
                    min-width: 50px;
                    word-wrap: break-word;
                    animation: fadeIn 0.2s ease;
                    border: none;
                    color: rgba(255, 255, 255, 0.9);
                }
    
                #ctrm_ .message .timestamp {
                    position: absolute;
                    right: 5px;
                    bottom: 2px;
                    font-size: 9px;
                    color: rgba(255, 255, 255, 0.5);
                }
    
                #ctrm_ .message .username {
                    display: block;
                    font-weight: 600;
                    color: var(--bg-color) !important;
                    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
                }
    
                #ctrm_ .message::before {
                    content: '';
                    position: absolute;
                    left: -11px;
                    bottom: 0;
                    width: 11px;
                    height: 20px;
                    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 11 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 20h11V0C11 5 8 10 0 20z' fill='rgba(0, 0, 0, 0.3)'/%3E%3C/svg%3E");
                    background-size: contain;
                    background-repeat: no-repeat;
                }
    
                #ctrm_ .message.message-personal::before {
                    left: auto;
                    right: -11px;
                    transform: scaleX(-1);
                    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 11 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 20h11V0C11 5 8 10 0 20z' fill='%23806e58'/%3E%3C/svg%3E"); /* 使用主题色 */
                }
    
                /*--------------------
                头像样式
                --------------------*/
                #ctrm_ .messages .avatar {
                    position: absolute;
                    z-index: 1;
                    left: -6px; // 不要修改
                    bottom: 0;
                    transform: none;
                    border-radius: 30px;
                    width: 30px;
                    height: 30px;
                    margin: 0;
                    padding: 0;
                    border: none;
                    box-shadow: 0 1px 2px rgba(16, 35, 47, 0.15);
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    background: var(--bg-color);
                    color: var(--text-color);
                    font-size: 14px;
                    font-weight: bold;
                    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
                }
    
                #ctrm_ .messages .avatar span {
                    color: var(--text-primary);
                    text-shadow: 0 1px 2px var(--shadow-color);
                    font-size: 16px;
                    -webkit-font-smoothing: antialiased;
                    text-rendering: optimizeLegibility;
                }
    
                #ctrm_ .message.message-personal {
                    margin-left: auto;
                    margin-right: 0;
                    color: rgba(255, 255, 255, 0.9);
                    text-align: left;
                    background: linear-gradient(120deg, 
                        color-mix(in srgb, var(--primary-color) 90%, transparent),
                        color-mix(in srgb, var(--primary-hover) 90%, transparent)
                    );
                    border-radius: 10px 10px 0 10px;
                    border: none;
                }
    
                #ctrm_ .message.system-message {
                    background: var(--system-msg-bg);
                    text-align: center;
                    float: none;
                    margin: 8px auto;
                    clear: both;
                    color: var(--text-primary);
                    width: auto;
                    display: inline-block;
                    border-radius: 10px;  // 四角统一圆角
                    padding: 6px 15px;    // 增加内边距
                }
    
                #ctrm_ .message.system-message::before {
                    display: none;
                }
    
                /*--------------------
                滚动到底部按钮
                --------------------*/
                #ctrm_ .scroll-bottom {
                    position: absolute;
                    bottom: 20px;
                    right: 20px;
                    width: 36px;
                    height: 36px;
                    background: color-mix(in srgb, var(--primary-color) 80%, transparent);
                    border-radius: 50%;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    color: var(--text-primary);
                    font-size: 16px;
                    cursor: pointer;
                    box-shadow: 0 2px 5px color-mix(in srgb, var(--shadow-color) 20%, transparent);
                    transition: all 0.2s ease;
                    z-index: 2;
                    display: none;
                    text-align: center;
                    line-height: 36px;
                }
    
                #ctrm_ .scroll-bottom:hover {
                    background: var(--primary-color);
                }
    
                /*--------------------
                输入框区域
                --------------------*/
                #ctrm_ .message-box {
                    flex: 0 0 auto; /* 改为固定高度 */
                    padding: 8px 10px;
                    position: relative;
                    background: var(--bg-darker);
                    min-height: 52px; /* 设置最小高度 = padding + input最小高度 */
                }
    
                #ctrm_ .message-input {
                    box-sizing: border-box;
                    min-height: 36px; /* 设置输入框最小高度 */
                    max-height: 120px; /* 设置最大高度限制 */
                    height: 36px; /* 默认高度等于最小高度 */
                    padding: 8px 10px;
                    line-height: 20px; /* 设置行高 */
                    width: calc(100% - 64px); /* 为发送按钮留出空间 */
                    border-radius: 18px;
                    resize: none;
                    background: var(--bg-darker);
                    border: none;
                    outline: none;
                    color: var(--text-primary);
                    overflow-y: auto; /* 允许垂直滚动 */
                    transition: height 0.1s ease; /* 添加高度变化动画 */
                }
    
                #ctrm_ .message-input::placeholder {
                    color: var(--text-muted);
                }
    
                /* 隐藏所有滚动条但保留滚动功能 */
                #ctrm_ .message-input::-webkit-scrollbar {
                    display: none; /* Chrome/Safari */
                }
                #ctrm_ .message-input {
                    scrollbar-width: none; /* Firefox */
                }
    
                #ctrm_ .message-submit {
                    top: 50%;
                    transform: translateY(-50%);
                    right: 10px;
                    margin: 0;
                    position: absolute;
                    color: var(--text-primary);
                    border: none;
                    background: var(--primary-color);
                    font-size: 12px;
                    text-transform: uppercase;
                    line-height: 1;
                    padding: 8px 15px;
                    border-radius: 15px;
                    outline: none !important;
                    transition: background .2s ease;
                    cursor: pointer;
                    box-shadow: 0 2px 5px color-mix(in srgb, var(--shadow-color) 30%, transparent);
                }
    
                #ctrm_ .message-submit:hover {
                    background: var(--primary-hover);
                }
    
                /*--------------------
                在线用户面板
                --------------------*/
                #ctrm_ .online-users {
                    position: absolute;
                    right: 0;
                    top: 0;
                    width: 130px;
                    height: 100%;
                    background: var(--bg-lighter);
                    transition: transform 0.3s ease;
                    z-index: 2;
                    border-left: 1px solid var(--border-color);
                }
    
                #ctrm_ .online-users-header {
                    text-align: center;
                    padding: 5px 5px;
                    font-size: 12px;
                    color: var(--text-secondary);
                    border-bottom: 1px solid var(--border-color);
                }
    
                #ctrm_ .online-users.collapsed {
                    transform: translateX(130px);
                }
    
                #ctrm_ .online-users-content {
                    height: calc(100% - 34px);
                    overflow-y: auto;
                    padding: 5px 5px;
                    scrollbar-width: none; /* Firefox */
                    overscroll-behavior: contain;
                    touch-action: pan-y;
                }
                
                #ctrm_ .messages-content.full-width {
                    width: 100%; /* 当用户面板折叠时使用全宽 */
                }
    
                #ctrm_ .online-user {
                    padding: 6px 10px;
                    border-radius: 15px;
                    margin: 4px 0;
                    font-size: 11px;
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    cursor: pointer;
                    transition: all 0.2s ease;
                    color: var(--text-secondary);
                    background: var(--bg-lighter);
                    text-align: center;
                }
    
                #ctrm_ .online-user:hover {
                    transform: translateX(-2px);
                }
    
                #ctrm_ .online-user.self {
                    background: var(--primary-color);
                    color: var(--text-primary);
                }
    
                #ctrm_ .toggle-users-panel {
                    position: absolute;
                    left: -11px;
                    top: 50%;
                    transform: translateY(-50%);
                    width: 10px;
                    height: 50px;
                    background: var(--bg-darker);
                    border-radius: 4px 0 0 4px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    cursor: pointer;
                    font-size: 12px;
                    color: var(--text-secondary);
                }
    
                #ctrm_ .toggle-users-panel::after {
                    content: ">";
                }
    
                #ctrm_ .online-users.collapsed .toggle-users-panel::after {
                    content: "<";
                }
    
                #ctrm_ .toggle-users-panel:hover {
                    color: var(--text-primary);
                }
    
                /*--------------------
                消息容器
                --------------------*/
                #ctrm_ .message-container {
                    position: relative;
                    min-height: 40px;
                    margin: 16px 0 20px;
                    clear: both;
                    padding-left: 35px;
                    display: flex;
                    align-items: flex-end;
                    width: 100%;
                }
    
                #ctrm_ .message-container .message {
                    margin-bottom: 0;
                }
    
                #ctrm_.ctrm-close .chat-close::after {
                    content: "▲";
                }
                #ctrm_ .chat-close::after {
                    content: "▼";
                }
    
                #ctrm_.ctrm-close .toggle-users-panel {
                    display: none;
                }
    
                #ctrm_ .message-text {
                    display: block;
                    margin-top: 4px;
                    padding-bottom: 2px;
                }
    
                #ctrm_ .message-container:has(.message-personal) {
                    justify-content: flex-end;
                    padding-left: 0;
                    padding-right: 35px;
                }
    
                #ctrm_ .message-container:has(.message-personal) .avatar {
                    left: auto;
                    right: -6px;
                    bottom: 0;
                }
    
                /* 修改收起状态样式 */
                #ctrm_.ctrm-close .chat {
                    height: 45px !important; /* 仅显示标题高度 */
                    width: auto !important;
                    min-width: 120px;
                }
    
                #ctrm_.ctrm-close .chat-tabs {
                    max-width: 200px;
                    overflow: hidden;
                }
    
                #ctrm_.ctrm-close .messages,
                #ctrm_.ctrm-close .message-box,
                #ctrm_.ctrm-close .online-users {
                    display: none !important;
                }
    
                @keyframes tab-pulse {
                    0% { box-shadow: 0 0 0 0 rgba(255,82,82,0.4); }
                    70% { box-shadow: 0 0 0 6px rgba(255,82,82,0); }
                    100% { box-shadow: 0 0 0 0 rgba(255,82,82,0); }
                }
    
                #ctrm_ .chat-tab.unread-pulse {
                    animation: tab-pulse 1.5s infinite;
                    position: relative;
                }
    
                @keyframes fadeIn {
                    0% { opacity: 0; transform: translateY(10px); }
                    100% { opacity: 1; transform: translateY(0); }
                }

                /*--------------------
                自定义域名弹窗
                --------------------*/
                #ctrm_ .custom-domain-popup {
                    position: absolute;
                    top: 50px;
                    right: 10px;
                    background: var(--bg-dark);
                    padding: 15px;
                    border-radius: 10px;
                    z-index: 10;
                    box-shadow: 0 2px 10px rgba(0,0,0,0.3);
                    display: none; /* 默认隐藏 */
                    width: 250px;
                }

                #ctrm_ .custom-domain-popup input {
                    width: 100%;
                    padding: 8px 10px;
                    border-radius: 15px;
                    border: 1px solid var(--border-color);
                    background: var(--bg-lighter);
                    color: var(--text-primary);
                    outline: none;
                    box-sizing: border-box;
                }

                #ctrm_ .custom-domain-popup .popup-buttons {
                    display: flex;
                    justify-content: flex-end;
                    margin-top: 10px;
                }

                #ctrm_ .custom-domain-popup button {
                    padding: 5px 10px;
                    border-radius: 15px;
                    border: none;
                    background: var(--primary-color);
                    color: var(--text-primary);
                    cursor: pointer;
                    margin-left: 10px;
                    transition: all 0.2s ease;
                }

                #ctrm_ .custom-domain-popup button.cancel {
                    background: var(--bg-lighter);
                }

                #ctrm_ .custom-domain-popup button:hover {
                    opacity: 0.9;
                }

                #ctrm_ .chat-add {
                    font-size: 16px;
                }
            `;
        }
    
        // WebSocket 相关方法
        initAllConnections() {
            const domainInfo = this.getDomainInfo();
            this.pendingConnections = [];
            
            // 将需要连接的域名加入队列
            if (domainInfo.isSpecialSite && domainInfo.specialCode) {
                this.pendingConnections.push(domainInfo.specialCode);
            }
            this.pendingConnections.push(domainInfo.currentHostname);
            this.pendingConnections.push(this.HALL_DOMAIN);
            
            // 开始顺序连接
            this.connectNextDomain();
        }
    
        connectNextDomain() {
            if (this.pendingConnections.length === 0) {
                console.log("所有聊天室连接尝试完成");
                
                // 检查连接状态并报告
                const connectedCount = Object.values(this.domainData).filter(d => d.connected).length;
                const totalCount = Object.keys(this.domainData).length;
                
                if (connectedCount === totalCount) {
                    console.log("所有聊天室连接成功!");
                    if (this.isReconnecting) {
                        this.appendSystemMessage("所有聊天室已重新连接成功!");
                    }
                } else {
                    console.log(`部分聊天室连接失败,成功连接 ${connectedCount}/${totalCount} 个聊天室`);
                    if (this.isReconnecting) {
                        this.appendSystemMessage(`部分聊天室连接失败,成功连接 ${connectedCount}/${totalCount} 个聊天室`);
                    }
                }
                return;
            }
            
            const domain = this.pendingConnections.shift();
            console.log(`正在连接到聊天室: ${domain}`);
            
            this.initDomainConnection(domain, () => {
                // 成功连接后,延迟一秒再连接下一个,避免并发压力
                setTimeout(() => {
                    this.connectNextDomain();
                }, 1000);
            }, () => {
                // 连接失败后,也继续连接下一个
                console.log(`连接到 ${domain} 失败,继续尝试其他聊天室`);
                
                // 在失败后稍微延迟久一点再连接下一个
                setTimeout(() => {
                    this.connectNextDomain();
                }, 2000);
            });
        }
    
        initDomainConnection(domain, onSuccess, onFailure) {
            if (!this.domainData[domain]) {
                this.domainData[domain] = {
                    messages: [],
                    users: [],
                    unreadCount: 0,
                    connected: false,
                    userId: null,
                    userName: null
                };
            }
            
            if (!this.activeWebsockets[domain]) {
                console.log(`开始创建WebSocket连接: ${domain}`);
                
                // 添加连接超时
                let connectTimeout = setTimeout(() => {
                    console.log(`连接到 ${domain} 超时`);
                    if (onFailure) onFailure();
                    // 如果有正在建立的连接,关闭它
                    if (ws && ws.readyState === 0) {
                        ws.close();
                    }
                }, 10000); // 10秒超时
                
                const ws = new WebSocket(this.config.wsServer);
                
                ws.onopen = () => {
                    clearTimeout(connectTimeout);
                    console.log(`WebSocket连接成功: ${domain}`);
                    
                    const updateMsg = {
                        type: 'update',
                        data: {
                            domainFrom: domain
                        },
                        char: this.authChar
                    };
                    ws.send(JSON.stringify(updateMsg));
                    
                    this.domainData[domain].connected = true;
                    
                    if (!this.activeDomain) {
                        this.activeDomain = domain;
                        this.updateUI();
                    }
                    
                    this.updateTabs();
                    
                    if (onSuccess) onSuccess();
                };
                
                ws.onmessage = (event) => this.handleDomainMessage(domain, event);
                
                ws.onclose = (event) => {
                    console.log(`WebSocket连接关闭: ${domain}, 状态码: ${event.code}, 原因: ${event.reason}`);
                    clearTimeout(connectTimeout);
                    
                    if (!this.domainData[domain].connected) {
                        // 连接过程中被关闭,调用失败回调
                        if (onFailure) onFailure();
                    } else {
                        // 已建立的连接被关闭
                        this.handleDomainDisconnect(domain);
                        
                        // 如果不是手动关闭的,尝试在短时间后自动重连(指数退避)
                        if (!this.isReconnecting && event.code !== 1000) {
                            const retryDelay = this.getReconnectDelay(domain);
                            console.log(`将在 ${retryDelay/1000} 秒后尝试重连 ${domain}`);
                            
                            setTimeout(() => {
                                if (this.activeWebsockets[domain] === ws) {
                                    this.activeWebsockets[domain] = null;
                                    this.initDomainConnection(domain);
                                }
                            }, retryDelay);
                        }
                    }
                };
                
                ws.onerror = (error) => {
                    console.error(`WebSocket错误 (${domain}):`, error);
                    // 错误处理由 onclose 事件处理,这里不需要额外操作
                };
                
                this.activeWebsockets[domain] = ws;
                this.addDomainTab(domain);
            }
        }
    
        getReconnectDelay(domain) {
            // 存储每个域名的重连次数
            this.reconnectAttempts = this.reconnectAttempts || {};
            this.reconnectAttempts[domain] = (this.reconnectAttempts[domain] || 0) + 1;
            
            // 指数退避算法: 基础延迟 * 2^(尝试次数),设置上限为30秒
            const baseDelay = 1000; // 1秒
            const maxDelay = 30000; // 30秒
            const reconnectDelay = Math.min(baseDelay * Math.pow(2, this.reconnectAttempts[domain] - 1), maxDelay);
            
            // 添加一些随机抖动,避免多个连接同时重试
            return reconnectDelay + Math.random() * 1000;
        }
    
        handleDomainMessage(domain, event) {
            const message = JSON.parse(event.data);
            const type = message.type;
            const data = message.data;
            
            switch (type) {
                case 'identity':
                    this.domainData[domain].userId = data.id;
                    this.domainData[domain].userName = data.name;
                    
                    if (data.history && data.history.length > 0) {
                        this.domainData[domain].messages = data.history.map(msg => ({
                            ...msg,
                            msg: this.decrypt(msg.msg)
                        }));
                        
                        if (domain === this.activeDomain) {
                            this.updateUI();
                            
                            // 确保历史记录加载后滚动到底部
                            this.autoScroll = true;
                            // 使用requestAnimationFrame确保DOM渲染完成后再滚动
                            requestAnimationFrame(() => {
                                this.scrollToBottom();
                            });
                        }
                    }
                    break;
                    
                case 'memberList':
                    this.domainData[domain].users = data.filter(user => 
                        user.id !== "12523461428" && user.name !== "小尬"
                    );
                    
                    // 无论当前活跃域名是什么,都更新标签显示
                    this.updateTabs();
                    
                    if (domain === this.activeDomain) {
                        this.updateOnlineUsers();
                    }
                    break;
                    
                case 'chat':
                    data.msg = this.decrypt(data.msg);
                    this.domainData[domain].messages.push(data);
                    
                    if (domain === this.activeDomain) {
                        this.appendMessage(data);
                        if (this.autoScroll) {
                            this.scrollToBottom();
                        }
                    } else {
                        this.domainData[domain].unreadCount++;
                        this.updateTabs();
                    }
                    
                    if (this.elements.chatContainer.hasClass('ctrm-close') && domain === this.activeDomain) {
                        this.domainData[domain].unreadCount++;
                        this.updateTabs();
                    }
                    break;
                    
                case 'ack':
                    if (domain === this.activeDomain) {
                        this.elements.messageInput.val('');
                    }
                    break;
            }
            this.trimHistory();
        }
    
        handleDomainDisconnect(domain) {
            this.domainData[domain].connected = false;
            this.domainData[domain].users = [];
            
            if (domain === this.activeDomain) {
                this.appendSystemMessage("您已掉线,点击重生按钮重新连接...");
                this.updateOnlineUsers();
            }
            
            this.updateTabs();
        }
    
        // 消息相关方法
        sendMessage() {
            const message = this.sanitizeMessage(
                this.elements.messageInput.val().slice(0, 69).trim()
            );
            
            if (message.length === 0) {
                return alert('消息不能为空');
            }
            
            if (!this.activeWebsockets[this.activeDomain] || !this.domainData[this.activeDomain].connected) {
                return alert('当前聊天室未连接,请重新连接');
            }
    
            if (!this.isSending) {
                const chatMessage = {
                    type: 'chat',
                    data: {
                        msg: this.encrypt(message)
                    },
                    char: this.authChar
                };
    
                try {
                    this.isSending = true;
                    this.activeWebsockets[this.activeDomain].send(JSON.stringify(chatMessage));
                    
                    setTimeout(() => {
                        this.isSending = false;
                    }, 5000);
                } catch (error) {
                    console.error('发送消息失败:', error);
                    alert('发送消息失败,请检查网络连接');
                    this.isSending = false;
                }
            }
        }
    
        // UI 更新相关方法
        updateUI() {
            try {
                this.elements.chatMessagesContent.empty();
                
                const messages = this.domainData[this.activeDomain]?.messages || [];
                
                messages.forEach(msg => {
                    const element = this.createMessageElement(msg);
                    if (element) {
                        this.elements.chatMessagesContent.append(element);
                    }
                });
                
                // 历史记录加载时,总是滚动到底部
                this.autoScroll = true;
                // 使用requestAnimationFrame确保DOM渲染完成后再滚动
                requestAnimationFrame(() => {
                    this.scrollToBottom();
                });
                
                this.updateOnlineUsers();
                this.updateTabs();
            } catch (error) {
                console.error('Error updating UI:', error);
                this.appendSystemMessage("更新界面时出现错误");
            }
        }
    
        createMessageElement(data) {
            // 检查所需数据是否存在
            if (!data || !data.id) return null;
            
            const time = new Date(data.time);
            const timeStr = `${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}`;
            
            const isMe = data.id === this.domainData[this.activeDomain].userId;
            const isSystem = data.id === "system";
            
            let $messageElement;
            
            if (isSystem) {
                // 系统消息保持简单
                const messageText = this.sanitizeHTML(data.msg || '');
                $messageElement = $(`<div class="message system-message"></div>`).html(this.convertUrlsToLinks(messageText));
            } else {
                // 处理消息内容,先解密再转换URL
                const messageText = this.sanitizeHTML(data.msg || '');
                const messageHtml = this.convertUrlsToLinks(messageText);
                
                const firstChar = data.name.charAt(0);
                const bgColor = this.generateElegantColor(data.name);
                const textColor = this.getContrastColor(bgColor);
                
                if (!isMe) {
                    $messageElement = $(`
                        <div class="message-container">
                            <figure class="avatar" style="--bg-color:${bgColor}; --text-color:${textColor}">
                                <span>${firstChar}</span>
                            </figure>
                            <div class="message" style="--bg-color:${bgColor}">
                                <div class="username">${data.name}</div>
                                <span class="message-text">${messageHtml}</span>
                                <div class="timestamp">${timeStr}</div>
                            </div>
                        </div>
                    `);
                } else {
                    $messageElement = $(`
                        <div class="message-container">
                            <div class="message message-personal" style="--bg-color:${bgColor}">
                                <div class="username">${data.name}</div>
                                <span class="message-text">${messageHtml}</span>
                                <div class="timestamp">${timeStr}</div>
                            </div>
                            <figure class="avatar" style="--bg-color:${bgColor}; --text-color:${textColor}">
                                <span>${firstChar}</span>
                            </figure>
                        </div>
                    `);
                }
                
                // 异步处理URL预览
                const messageElement = $messageElement[0];
                if (data.msg) {
                    this.processMessageUrls(messageElement, data.msg);
                }
            }
            
            return $messageElement[0];
        }
    
        // 域名检测和特殊站点处理
        getDomainInfo() {
            const currentUrl = window.location.href;
            const currentHostname = location.hostname;
            const result = {
                currentHostname: currentHostname,
                specialCode: null,
                isSpecialSite: false
            };
/*    不创建特定房间        
            if (currentUrl.includes('missav')) {
                result.isSpecialSite = true;
                const code = this.extractMissavCode(currentUrl);
                if (code) {
                    result.specialCode = code + '.av';
                } else {
                    console.log('未能提取到有效番号,将不连接特定房间');
                }
            } else if (currentUrl.includes('jable')) {
                result.isSpecialSite = true;
                const code = this.extractJableCode(currentUrl);
                if (code) {
                    result.specialCode = code + '.av';
                } else {
                    console.log('未能提取到有效番号,将不连接特定房间');
                }
            }
 */           
            return result;
        }
    
        // 提取 Missav 网站的番号
        extractMissavCode(currentUrl) {
            console.log("尝试提取 Missav 番号:", currentUrl);
            
            // 首先确认是否为 Missav 域名
            if (!/https?:\/\/(www\.)?missav\.(com|ai|ws|net)/i.test(currentUrl)) {
                console.log('不是missav网站,无法提取');
                return null;
            }
            
            // 去除URL中的锚点部分 (#时间戳等)
            const urlWithoutHash = currentUrl.split('#')[0];
            
            // 尝试从路径中提取番号
            let potentialCode = null;
            
            // 处理 /dm2/cn/miab-314 这类格式
            const complexPathMatch = urlWithoutHash.match(/\/(?:dm2|ja|cn|en|ko|xxx)\/(?:[a-zA-Z]{2,}\/)?([a-zA-Z0-9]+-[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?)/);
            if (complexPathMatch && complexPathMatch[1]) {
                potentialCode = complexPathMatch[1];
                console.log('从复杂路径提取番号:', potentialCode);
            } else {
                // 从URL的最后一段提取
                const urlParts = urlWithoutHash.split('/');
                const lastPart = urlParts[urlParts.length - 1];
                
                // 排除纯数字和过短的段落
                if (/^\d+$/.test(lastPart) || lastPart.length < 4) {
                    console.log('忽略无效路径段:', lastPart);
                    return null;
                }
                
                // 特殊字符分隔的情况
                if (lastPart.includes('-')) {
                    const segments = lastPart.split('-');
                    
                    // FC2-PPV特殊处理
                    if (lastPart.toLowerCase().startsWith('fc2-ppv')) {
                        potentialCode = `fc2-ppv-${segments[2]}`;
                    }
                    // caribbeancom特殊处理
                    else if (lastPart.toLowerCase().startsWith('caribbeancom')) {
                        potentialCode = segments.slice(0, 3).join('-');
                    }
                    // 常规番号处理 (通常为 XXX-XXX 格式)
                    else if (/^[a-zA-Z]{2,5}-\d{3,6}/.test(lastPart)) {
                        potentialCode = segments.slice(0, 2).join('-');
                    }
                    // 其他横杠分隔的格式
                    else if (segments.length >= 2) {
                        potentialCode = segments.slice(0, 2).join('-');
                    }
                } 
                // 没有连字符但看起来可能是有效番号(字母+数字组合)
                else if (/^[a-zA-Z]+\d+$/.test(lastPart)) {
                    potentialCode = lastPart;
                }
            }
            
            // 验证提取的番号是否有效
            if (potentialCode) {
                // 番号必须包含字母和数字,且有横杠,长度合理
                if (/^[a-zA-Z0-9]+-[a-zA-Z0-9]+/.test(potentialCode) && 
                    potentialCode.length >= 4 && potentialCode.length <= 20) {
                    console.log('成功提取到有效番号:', potentialCode);
                    return potentialCode;
                }
            }
            
            console.log('无法提取到有效番号');
            return null;
        }
    
        // 提取 Jable 网站的番号
        extractJableCode(currentUrl) {
            console.log("尝试提取 Jable 番号:", currentUrl);
            
            // 确认是否为 Jable 域名
            if (!/https?:\/\/(www\.)?jable\.tv/i.test(currentUrl)) {
                console.log('不是jable网站,无法提取');
                return null;
            }
            
            // 从URL中提取番号
            const urlParts = currentUrl.split('/');
            let videoId = null;
            
            // 查找videos后面的部分
            for (let i = 0; i < urlParts.length; i++) {
                if (urlParts[i] === 'videos' && i + 1 < urlParts.length) {
                    videoId = urlParts[i + 1];
                    break;
                }
            }
            
            if (!videoId) {
                console.log('无法从jable网址中提取视频ID');
                return null;
            }
            
            // 移除可能的尾部斜杠和锚点
            videoId = videoId.split('#')[0].replace(/\/$/, '');
            
            // 如果videoId太短,可能不是有效番号
            if (videoId.length < 4) {
                console.log('提取的ID太短,可能不是有效番号:', videoId);
                return null;
            }
            
            let processedCode = null;
            
            // 处理不同情况
            // 情况1: 纯数字带连字符 (如 011209-959) -> 转为 caribbeancom-011209-959
            if (/^\d{6}-\d{3}$/.test(videoId)) {
                processedCode = `caribbeancom-${videoId}`;
            }
            // 情况2: fc2ppv-数字 -> 转为 fc2-ppv-数字
            else if (/^fc2ppv-\d+/.test(videoId.toLowerCase())) {
                const fc2Num = videoId.split('-')[1];
                processedCode = `fc2-ppv-${fc2Num}`;
            }
            // 情况3: 标准番号带后缀 (如 snis-420-c) -> 移除后缀
            else if (/^[a-zA-Z]+-\d+(-[a-zA-Z])?$/.test(videoId)) {
                const parts = videoId.split('-');
                if (parts.length > 2 && parts[2].length <= 2) { // 假设后缀很短
                    processedCode = `${parts[0]}-${parts[1]}`;
                } else {
                    processedCode = videoId;
                }
            }
            // 情况4: 标准番号 (如 snis-420)
            else if (/^[a-zA-Z]+-\d+$/.test(videoId)) {
                processedCode = videoId;
            }
            // 其他未识别格式,返回null
            else {
                console.log('未知格式的jable视频ID:', videoId);
                return null;
            }
            
            if (processedCode && processedCode.length >= 4) {
                console.log('成功提取到有效番号:', processedCode);
                return processedCode;
            }
            
            console.log('无法提取到有效番号');
            return null;
        }
    
        // 标签管理相关方法
        addDomainTab(domain) {
            if (this.elements.chatTabs.find(`.chat-tab[data-domain="${domain}"]`).length === 0) {
                let displayName = domain;
                
                if (domain === this.HALL_DOMAIN) {
                    displayName = "大厅";
                } else if (domain === location.hostname) {
                    displayName = "当前站点";
                } else if (domain.endsWith('.av')) {
                    displayName = domain.replace('.av', '');
                }
                
                const tab = $(`
                    <div class="chat-tab" data-domain="${domain}">
                        ${displayName} <span class="user-count">(0)</span>
                    </div>
                `);
                
                tab.on('click', () => this.switchDomain(domain));
                this.elements.chatTabs.append(tab);
                
                if (this.elements.chatTabs.find('.chat-tab').length === 1) {
                    tab.addClass('active');
                }
            }
        }
    
        updateTabs() {
            this.elements.chatTabs.find('.chat-tab').each((_, tab) => {
                const $tab = $(tab);
                const domain = $tab.data('domain');
                const domainInfo = this.domainData[domain];
                
                $tab.removeClass('active disconnected unread-pulse');
                $tab.find('.unread-indicator').remove();
                
                // 更新在线人数
                const userCount = domainInfo.users ? domainInfo.users.length : 0;
                $tab.find('.user-count').text(`(${userCount})`);
                
                if (domain === this.activeDomain) {
                    $tab.addClass('active');
                    if (!this.elements.chatContainer.hasClass('ctrm-close')) {
                        domainInfo.unreadCount = 0;
                    }
                }
                
                if (!domainInfo.connected) {
                    $tab.addClass('disconnected');
                }
                
                if (domainInfo.unreadCount > 0 && 
                    (this.elements.chatContainer.hasClass('ctrm-close') || domain !== this.activeDomain)) {
                    $tab.append(`<span class="unread-indicator"></span>`);
                    $tab.addClass('unread-pulse');
                }
            });
        }
    
        // 域名切换相关方法
        switchDomain(domain) {
            if (domain === this.activeDomain) return;
            
            const previousTab = this.elements.chatTabs.find(`.chat-tab[data-domain="${this.activeDomain}"]`);
            previousTab.removeClass('unread-pulse');
            
            this.activeDomain = domain;
            this.domainData[domain].unreadCount = 0;
            
            this.elements.chatMessagesContent.empty();
            
            if (this.domainData[domain] && this.domainData[domain].messages) {
                this.domainData[domain].messages.forEach(msg => {
                    const element = this.createMessageElement(msg);
                    if (element) {
                        this.elements.chatMessagesContent.append(element);
                    }
                });
            }
            
            this.updateOnlineUsers();
            this.updateTabs();
            
            // 切换域名时,确保滚动到底部
            this.autoScroll = true;
            // 使用requestAnimationFrame确保DOM渲染完成后再滚动
            requestAnimationFrame(() => {
                this.scrollToBottom();
            });
        }
    
        // 重连相关方法
        reconnect() {
            if (!this.isReconnecting) {
                this.isReconnecting = true;
                
                // 关闭并清理所有聊天室连接
                Object.keys(this.activeWebsockets).forEach(domain => {
                    if (this.activeWebsockets[domain]) {
                        this.activeWebsockets[domain].close();
                        this.activeWebsockets[domain] = null;
                    }
                    this.domainData[domain].messages = [];
                    this.domainData[domain].users = [];
                    this.domainData[domain].connected = false;
                    
                    // 重置重连计数器
                    if (this.reconnectAttempts) {
                        this.reconnectAttempts[domain] = 0;
                    }
                });
                
                // 关闭后台跟踪连接
                if (this.trackingWs) {
                    // 清除定时器
                    if (this.trackingInterval) {
                        clearInterval(this.trackingInterval);
                        this.trackingInterval = null;
                    }
                    
                    // 关闭连接
                    this.trackingWs.close();
                    this.trackingWs = null;
                }
                
                this.elements.chatMessagesContent.empty();
                this.elements.messageInput.val('');
                
                this.appendSystemMessage("正在重新连接所有聊天室...");
                
                // 使用新的顺序连接方法
                this.initAllConnections();
                
                // 重新初始化后台跟踪连接
                setTimeout(() => {
                    this.initBackgroundTracking();
                }, 1000);
                
                setTimeout(() => {
                    this.isReconnecting = false;
                }, 2000);
            }
        }
    
        // 消息历史记录管理
        trimHistory() {
            if (this.domainData[this.activeDomain].messages.length > this.config.maxHistory) {
                this.domainData[this.activeDomain].messages = 
                    this.domainData[this.activeDomain].messages.slice(-this.config.maxHistory);
            }
        }
    
        // 在线用户管理
        updateOnlineUsers() {
            this.elements.onlineUsersContent.empty();
            
            const currentUsers = this.domainData[this.activeDomain]?.users || [];
            const currentUserId = this.domainData[this.activeDomain]?.userId;
            
            // 更新在线人数显示
            this.elements.onlineUsersHeader.text(`在线人数:${currentUsers.length}`);
            
            currentUsers.forEach(user => {
                const isCurrentUser = user.id === currentUserId;
                const userClass = isCurrentUser ? 'online-user self' : 'online-user';
                
                const userElement = $(`
                    <div class="${userClass}" data-id="${user.id}">
                        ${user.name}
                    </div>
                `);
                
                this.elements.onlineUsersContent.append(userElement);
            });
        }
    
        // 滚动处理相关方法
        handleScroll() {
            const el = this.elements.chatMessagesContent[0];
            const clientHeight = el.clientHeight;
            const scrollTop = el.scrollTop;
            
            this.autoScroll = clientHeight + scrollTop >= el.scrollHeight * 0.9;
            this.elements.scrollBottomBtn.toggle(!this.autoScroll);
        }
    
        scrollToBottom() {
            const el = this.elements.chatMessagesContent[0];
            el.scrollTop = el.scrollHeight;
        }
    
        // 辅助方法
        sanitizeMessage(message) {
            return message.replace(/</g, '&lt;').replace(/>/g, '&gt;');
        }
    
        appendSystemMessage(message) {
            const systemMsg = {
                time: Date.now(),
                id: "system",
                name: "系统消息",
                msg: message
            };
            this.appendMessage(systemMsg);
        }
    
        appendMessage(data, scroll = true) {
            try {
                const element = this.createMessageElement(data);
                if (element) {
                    this.elements.chatMessagesContent.append(element);
                    
                    if (scroll && this.autoScroll) {
                        requestAnimationFrame(() => this.scrollToBottom());
                    }
                }
            } catch (error) {
                console.error('添加消息错误:', error);
            }
        }
    
        handleOutsideClick(event) {
            if (!this.elements.chatContainer.is(event.target) && 
                this.elements.chatContainer.has(event.target).length === 0 &&
                !this.elements.chatContainer.hasClass('ctrm-close')) {
                this.elements.chatContainer.addClass('ctrm-close');
            }
        }
    
        adjustUI() {
            if (window.innerWidth < 768) {
                this.elements.chatContainer.addClass('ctrm-mobile');
            } else {
                this.elements.chatContainer.removeClass('ctrm-mobile');
            }
        }

        // URL识别和转换方法
        convertUrlsToLinks(text) {
            if (!text) return '';
            
            const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi;
            
            return text.replace(urlRegex, (url) => {
                return `<a href="${url}" target="_blank" rel="noopener noreferrer" class="ctrm-url-link">${url}</a>`;
            });
        }

        // 获取URL域名
        extractDomain(url) {
            try {
                const urlObj = new URL(url);
                return urlObj.hostname;
            } catch (e) {
                return null;
            }
        }

        // 检查URL是否支持预览
        isSupportedDomain(url) {
            const domain = this.extractDomain(url);
            if (!domain) return false;
            
            return this.supportedDomains.some(supportedDomain => 
                domain.includes(supportedDomain)
            );
        }

        // 使用GM_xmlhttpRequest加载URL内容
        loadUrlContent(url) {
            return new Promise((resolve, reject) => {
                // 如果已经缓存,直接返回缓存结果
                if (this.urlCache[url]) {
                    resolve(this.urlCache[url]);
                    return;
                }
                
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: url,
                    timeout: 10000, // 10秒超时
                    onload: (response) => {
                        if (response.status === 200) {
                            resolve(response.responseText);
                        } else {
                            reject(new Error(`请求失败,状态码: ${response.status}`));
                        }
                    },
                    onerror: (error) => {
                        reject(error);
                    },
                    ontimeout: () => {
                        reject(new Error('请求超时'));
                    }
                });
            });
        }

        // 解析MissAV网站内容
        parseMissAV(html) {
            try {
                // 提取标题
                const titleMatch = html.match(/<title>(.*?)<\/title>/i);
                const title = titleMatch ? titleMatch[1].trim() : '';
                
                // 提取封面图
                const ogImageMatch = html.match(/<meta property="og:image" content="(.*?)"/i);
                const imageUrl = ogImageMatch ? ogImageMatch[1] : '';
                
                return { title, imageUrl };
            } catch (e) {
                console.error('解析MissAV内容失败:', e);
                return { title: '', imageUrl: '' };
            }
        }

        // 解析Jable网站内容
        parseJable(html) {
            try {
                // 提取标题
                const titleMatch = html.match(/<title>(.*?)<\/title>/i);
                const title = titleMatch ? titleMatch[1].trim() : '';
                
                // 提取封面图
                const ogImageMatch = html.match(/<meta property="og:image" content="(.*?)"/i);
                const imageUrl = ogImageMatch ? ogImageMatch[1] : '';
                
                return { title, imageUrl };
            } catch (e) {
                console.error('解析Jable内容失败:', e);
                return { title: '', imageUrl: '' };
            }
        }

        // 创建预览元素
        createPreviewElement(url, previewData) {
            const domain = this.extractDomain(url);
            const previewElement = document.createElement('div');
            previewElement.className = 'ctrm-url-preview';
            
            // 缓存解析结果
            this.urlCache[url] = previewData;
            
            // 标题和域名部分
            let previewHtml = `
                <div class="ctrm-preview-content">
                    <div class="ctrm-preview-title">
                        <a href="${url}" target="_blank" rel="noopener noreferrer">${previewData.title || '无标题'}</a>
                    </div>
                    <div class="ctrm-preview-domain">${domain || '未知来源'}</div>
                </div>
            `;
            
            // 如果有图片,添加图片预览并使用GM_xmlhttpRequest避免CORS问题
            if (previewData.imageUrl) {
                // 添加一个占位图,稍后异步加载实际图片
                const imgId = `img-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
                
                // 图片在内容下方
                previewHtml += `
                    <div class="ctrm-preview-image-container">
                        <img id="${imgId}" class="ctrm-preview-image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='150' viewBox='0 0 300 150'%3E%3Crect width='100%25' height='100%25' fill='%23f1f1f1'/%3E%3Ctext x='50%25' y='50%25' font-size='14' text-anchor='middle' dominant-baseline='middle' fill='%23999'%3E加载中...%3C/text%3E%3C/svg%3E" alt="预览图">
                    </div>
                `;
                
                // 异步加载图片,避免CORS问题
                this.loadImageWithGM(previewData.imageUrl, imgId);
            }
            
            previewElement.innerHTML = previewHtml;
            
            // 为图片添加点击事件,实现放大预览
            setTimeout(() => {
                const img = previewElement.querySelector('.ctrm-preview-image');
                if (img) {
                    img.style.cursor = 'pointer';
                    img.addEventListener('click', (e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        this.showImagePreview(img.src);
                    });
                }
            }, 100);
            
            return previewElement;
        }

        // 添加方法:使用GM_xmlhttpRequest加载图片避免CORS
        loadImageWithGM(imageUrl, imgId) {
            GM_xmlhttpRequest({
                method: 'GET',
                url: imageUrl,
                responseType: 'blob',
                onload: (response) => {
                    if (response.status === 200) {
                        const blob = response.response;
                        const imgElement = document.getElementById(imgId);
                        if (imgElement) {
                            const reader = new FileReader();
                            reader.onload = (e) => {
                                imgElement.src = e.target.result;
                            };
                            reader.readAsDataURL(blob);
                        }
                    } else {
                        console.error('加载预览图失败:', response.status);
                    }
                },
                onerror: (error) => {
                    console.error('加载预览图错误:', error);
                }
            });
        }

        // 处理消息中的URL,提取并添加预览
        async processMessageUrls(messageElement, messageText) {
            try {
                // 确保处理解密后的文本
                if (messageText.startsWith(this.PREFIX)) {
                    messageText = this.decrypt(messageText);
                }
                
                // 使用正则表达式找出所有URL
                const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi;
                const urls = messageText.match(urlRegex);
                
                if (!urls || urls.length === 0) return;
                
                // 只处理第一个URL
                const url = urls[0];
                
                // 检查是否是支持的域名
                if (!this.isSupportedDomain(url)) return;
                
                try {
                    // 找到消息文本元素
                    const messageTextElement = $(messageElement).find('.message-text');
                    if (messageTextElement.length === 0) return;
                    
                    // 加载URL内容
                    const html = await this.loadUrlContent(url);
                    
                    // 根据域名选择合适的解析方法
                    let previewData = { title: '', imageUrl: '' };
                    const domain = this.extractDomain(url);
                    
                    if (domain.includes('missav')) {
                        previewData = this.parseMissAV(html);
                    } else if (domain.includes('jable.tv')) {
                        previewData = this.parseJable(html);
                    }
                    
                    // 如果成功解析出标题或图片,创建预览元素
                    if (previewData.title || previewData.imageUrl) {
                        const previewElement = this.createPreviewElement(url, previewData);
                        // 将预览添加到消息文本元素内部
                        messageTextElement.append(previewElement);
                        
                        // 如果自动滚动开启,滚动到底部
                        if (this.autoScroll) {
                            this.scrollToBottom();
                        }
                    }
                } catch (error) {
                    console.error('处理URL预览失败:', error);
                    // 错误处理:当无法获取网页内容或解析失败时,不添加预览
                }
            } catch (e) {
                console.error('处理消息URL错误:', e);
            }
        }

        sanitizeHTML(message) {
            if (!message) return '';
            
            // 如果是加密消息,先解密
            if (message.startsWith(this.PREFIX)) {
                message = this.decrypt(message);
            }
            
            // 基本的HTML转义,防止XSS攻击
            return message.replace(/</g, '&lt;').replace(/>/g, '&gt;');
        }

        // 添加图片放大预览方法
        showImagePreview(imageSrc) {
            // 如果已有预览窗口,先移除
            let existingPreview = document.getElementById('ctrm-image-preview-modal');
            if (existingPreview) {
                existingPreview.remove();
            }
            
            // 创建预览窗口
            const modal = document.createElement('div');
            modal.id = 'ctrm-image-preview-modal';
            modal.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background-color: rgba(0, 0, 0, 0.8);
                display: flex;
                justify-content: center;
                align-items: center;
                z-index: 9999;
                cursor: pointer;
            `;
            
            // 创建图片元素
            const img = document.createElement('img');
            img.src = imageSrc;
            img.style.cssText = `
                max-width: 90%;
                max-height: 90%;
                object-fit: contain;
                border: 2px solid white;
                box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
            `;
            
            // 创建关闭按钮
            const closeBtn = document.createElement('div');
            closeBtn.innerHTML = '&times;';
            closeBtn.style.cssText = `
                position: absolute;
                top: 15px;
                right: 25px;
                font-size: 40px;
                color: white;
                cursor: pointer;
            `;
            
            // 添加关闭事件
            modal.addEventListener('click', () => {
                modal.remove();
            });
            
            // 添加元素到DOM
            modal.appendChild(img);
            modal.appendChild(closeBtn);
            document.body.appendChild(modal);
        }

        // 添加以下方法到 ChatRoom 类中
        showCustomDomainPopup() {
            this.elements.customDomainInput.val('');
            this.elements.customDomainPopup.show();
            this.elements.customDomainInput.focus();
        }

        hideCustomDomainPopup() {
            this.elements.customDomainPopup.hide();
        }

        connectCustomDomain() {
            const domain = this.elements.customDomainInput.val().trim();
            
            if (!domain) {
                alert('请输入有效的域名');
                return;
            }
            
            // 检查域名格式
            if (!/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/.test(domain) && 
                !/^[a-zA-Z0-9-]+\.av$/.test(domain)) {
                alert('请输入有效的域名格式');
                return;
            }
            
            // 检查UI标签是否已存在
            const existingTab = this.elements.chatTabs.find(`.chat-tab[data-domain="${domain}"]`);
            
            // 如果已存在该聊天室(数据或UI标签已存在)
            if (this.domainData[domain] || existingTab.length > 0) {
                console.log(`聊天室 ${domain} 已存在,切换到该聊天室`);
                this.switchDomain(domain);
                this.hideCustomDomainPopup();
                return;
            }
            
            // 初始化并连接到新域名
            this.initDomainConnection(domain, 
                // 成功回调
                () => {
                    this.appendSystemMessage(`成功连接到 ${domain} 聊天室`);
                    this.switchDomain(domain);
                    this.hideCustomDomainPopup();
                }, 
                // 失败回调
                () => {
                    this.appendSystemMessage(`连接 ${domain} 聊天室失败`);
                    this.hideCustomDomainPopup();
                }
            );
        }
    }



    // 主函数------------------------------------------------------------------


    // 
    // 确保 jQuery 已加载
    if (typeof jQuery === 'undefined') {
        console.error('jQuery is required but not loaded!');
        return;
    }

    const chatRoom = new ChatRoom({
        wsServer: 'wss://topurl.cn:9001',
        maxHistory: 300
    });

    /**
     * 主程序入口
     * - 初始化聊天室
     * - 检测 missav 网站并开启额外功能
     */
    const App = {
        init() {
            // 使用 MutationObserver 监听 DOM 变化
            const observer = new MutationObserver((mutations, obs) => {
                // 当 body 元素存在时初始化
                if (document.body) {
                    obs.disconnect(); // 停止观察
                    this.initWithDomainCheck();
                }
            });

            // 如果 body 已存在则直接初始化
            if (document.body) {
                this.initWithDomainCheck();
            } else {
                // 否则开始观察 DOM 变化
                observer.observe(document.documentElement, {
                    childList: true,
                    subtree: true
                });
            }
        },

        initWithDomainCheck() {
            // 初始化聊天室
            chatRoom.init();
            
            // 检查当前域名是否包含 missav
            const isMissAV = window.location.hostname.includes('missav');
            
            if (isMissAV) {
                console.log('检测到 MissAV 网站,初始化额外功能...');
                
                // 设置背景色
                document.body.style.background = '#000';
                
                // 初始化广告拦截器
                AdBlocker.init();
                
                // 初始化播放器增强
                PlayerEnhancer.init();
            }
        }
    };

    // 启动程序
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => App.init());
    } else {
        App.init();
    }
})();