聊天室

网页聊天室 ver 0.1 实现重写了基本功能

// ==UserScript==
// @name         聊天室
// @namespace    http://tampermonkey.net/
// @version      2025-02-24
// @description  网页聊天室 ver 0.1 实现重写了基本功能
// @description  网页聊天室 ver 0.2 实现加解密功能,支持69字符的消息
// @description  网页聊天室 ver 0.3 新增功能:1.多房间支持 2.域名过滤 3.移动端优化 4.掉线提示 5.排行榜功能 6.消息加密优化 7.界面交互改进 8.重连机制增强
// @author       You
// @match        https://*/*
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 加密配置
    const PREFIX = '🔒'; // 加密消息前缀
    const CHINESE_RANGE = { start: 0x4E00, end: 0x9FA5 }; // 中文字符范围

    // 加密函数
    function encrypt(text) {
        try {
            const encrypted = compressEncrypt(text);
            return PREFIX + encrypted;
        } catch (e) {
            console.error('加密失败:', e);
            return text;
        }
    }

    // 解密函数
    function decrypt(text) {
        if (!text.startsWith(PREFIX)) return text;
        
        try {
            const encryptedText = text.slice(PREFIX.length);
            return compressDecrypt(encryptedText);
        } catch (e) {
            console.error('解密失败:', e);
            return text;
        }
    }

    // 压缩加密算法
    function compressEncrypt(text) {
        const mapStart = 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;
    }

    // 压缩解密算法
    function compressDecrypt(encrypted) {
        if (encrypted.length <= 1) return "加密数据不完整";
        
        const mapStart = 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);
    }

    // 在全局工具函数部分添加颜色生成函数
    function generateElegantColor(str) {
        // 基于用户名生成确定性的随机种子
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            hash = str.charCodeAt(i) + ((hash << 5) - hash);
        }
        
        // 使用HSL颜色空间生成优雅颜色
        const hue = Math.abs(hash % 360);
        const saturation = 60 + Math.abs(hash % 30); // 60-90%
        const lightness = 40 + Math.abs(hash % 30);  // 40-70%
        
        return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
    }

    function getContrastColor(hsl) {
        // 从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)';
    }

    // 等待页面加载完成后执行
    function initChatRoom() {

        // 防止重复注入
        if (window._hasCtrmInjected) {
            return document.querySelector('.chat-title').click();
        }
        window._hasCtrmInjected = true;

        // 全局变量声明
        let activeWebsockets = {}; // 存储多个域名的websocket连接 {domain: websocket}
        let activeDomain = null; // 当前激活的域名
        let domainData = {}; // 每个域名的数据 {domain: {messages: [], users: [], userId: null, userName: null}}
        let isSending = undefined;
        let isReconnecting = undefined;
        let autoScroll = true;
        let heartbeatTimer = undefined;
        let userId = undefined;
        let userName = undefined;
        let domainList = [];
        let showAllDomains = false;
        let onlineUsers = undefined;
        const HALL_DOMAIN = "square.io"; // 大厅聊天室的固定域名

        // WebSocket服务器地址
        const WS_SERVER = 'wss://topurl.cn:9001';

        // 使用 @require 引入的 jQuery
        const $ = window.jQuery;
        
        // 注入聊天室 DOM 结构(参考tab.html风格)
        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-reconn" title="重生">
                                <svg fill="currentColor" viewBox="0 0 8 8" 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="messages">
                        <div class="messages-content"></div>
                        <div class="scroll-bottom">⇩</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 class="online-users">
                        <div class="online-users-content"></div>
                        <div class="toggle-users-panel"></div>
                    </div>
                </div>
            </div>
        `;

        // 注入样式
        const styles = `
            <style>
            /*--------------------
            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-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-reconn:hover,
            #ctrm_ .chat-close:hover {
                background: var(--bg-darker);
                color: var(--text-primary);
            }

            /*--------------------
            重连按钮SVG样式
            --------------------*/
            #ctrm_ .chat-reconn svg {
                width: 16px;
                height: 16px;
                transition: transform 0.3s ease;
            }

            #ctrm_ .chat-reconn:hover svg {
                transform: rotate(180deg);
            }

            /*--------------------
            消息区域
            --------------------*/
            #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-lighter);
                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: 45px;
                width: 130px;
                height: calc(100% - 105px);
                background: var(--bg-dark);
                transition: transform 0.3s ease;
                z-index: 2;
                border-left: 1px solid var(--border-color);
            }

            #ctrm_ .online-users.collapsed {
                transform: translateX(130px);
            }

            #ctrm_ .online-users-content {
                height: 100%;
                overflow-y: auto;
                padding: 10px 5px;
                scrollbar-width: none; /* Firefox */
                overscroll-behavior: contain;
                touch-action: pan-y;
            }

            #ctrm_ .online-users-content::-webkit-scrollbar {
                display: none; /* Chrome/Safari */
            }

            #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: -10px;
                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);
            }

            /*--------------------
            动画效果
            --------------------*/
            @keyframes fadeIn {
                from { opacity: 0; }
                to { opacity: 1; }
            }

            #ctrm_ {
                animation: fadeIn 0.3s ease;
            }

            /*--------------------
            移动设备适配
            --------------------*/
            @media (max-width: 768px) {
                #ctrm_ .message-input {
                    font-size: 14px;
                }
                
                #ctrm_ .message {
                    font-size: 14px;
                }
                
                #ctrm_ .chat-tabs {
                    max-width: calc(100% - 100px); /* 移动端可能需要更多按钮空间 */
                    padding: 8px 0;
                }

                #ctrm_ .chat-tab {
                    padding: 8px 14px;
                    font-size: 14px;
                }
            }

            /*--------------------
            消息容器
            --------------------*/
            #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;
            }
            </style>
        `;

        // 注入DOM和样式
        $(document.body).append(chatTemplate);
        $(document.head).append(styles);

        // DOM元素缓存
        const chatContainer = $('#ctrm_');
        const chatTitle = chatContainer.find('.chat-title');
        const chatTabs = chatContainer.find('.chat-tabs');
        const chatMessagesContent = chatContainer.find('.messages-content');
        const scrollBottomBtn = chatContainer.find('.scroll-bottom');
        const messageInput = chatContainer.find('.message-input');
        const messageSubmitBtn = chatContainer.find('.message-submit');
        const onlineUsersContent = chatContainer.find('.online-users-content');
        const toggleUsersPanelBtn = chatContainer.find('.toggle-users-panel');
        const onlineUsersPanel = chatContainer.find('.online-users');
        const closeBtn = chatContainer.find('.chat-close');
        const reconnectBtn = chatContainer.find('.chat-reconn');

        // 用于身份验证的token
        const authToken = window.btoa(encodeURIComponent('https://news.topurl.cn/'));
        const authChar = authToken[1] + authToken[3] + authToken[7] + authToken[9];

        // 点击空白处关闭聊天框
        function handleOutsideClick(event) {
            if (!chatContainer.is(event.target) && 
                chatContainer.has(event.target).length === 0 &&
                !chatContainer.hasClass('ctrm-close')) {
                chatContainer.addClass('ctrm-close');
            }
        }

        // Missav域名提取番号
        function extractMissavCode(currentUrl) {
            // 检查是否是missav域名,而不仅仅是包含missav字符串
            if (/https?:\/\/(www\.)?missav\.(com|ai|ws|net)/i.test(currentUrl)) {
                // 从url中提取番号
                const urlParts = currentUrl.split('/');
                // 获取最后一部分
                const lastPart = urlParts[urlParts.length - 1];
                
                // 排除纯数字的情况,这些通常不是有效的番号
                if (/^\d+$/.test(lastPart)) {
                    console.log('纯数字路径,可能不是有效番号');
                    return null;
                }
                
                // 处理番号提取
                if (lastPart.includes('-')) {
                    const segments = lastPart.split('-');
                    
                    // FC2-PPV特殊处理
                    if (lastPart.toLowerCase().startsWith('fc2-ppv')) {
                        return `fc2-ppv-${segments[2]}`;
                    }
                    
                    // caribbeancom特殊处理 (格式为: caribbeancom-XXXXXX-XXX)
                    if (lastPart.toLowerCase().startsWith('caribbeancom')) {
                        // 保留caribbeancom完整格式
                        return segments.slice(0, 3).join('-');
                    }
                    
                    // 常规番号处理 (通常为 XXX-XXX 格式)
                    // 检查是否是常见的AV番号格式
                    if (/^[a-zA-Z]{2,5}-\d{3,6}/.test(lastPart)) {
                        // 移除不相关的后缀,只保留基本番号部分
                        return segments.slice(0, 2).join('-');
                    }
                    
                    // 其他情况,对于未识别但包含连字符的内容
                    return segments.slice(0, 2).join('-');
                } else {
                    // 没有连字符但看起来可能是有效番号(字母+数字组合)
                    if (/^[a-zA-Z]+\d+$/.test(lastPart)) {
                        return lastPart;
                    }
                    
                    console.log('无法识别的番号格式');
                    return null;
                }
            }
            
            console.log('不是missav网站,无法提取');
            return null;
        }
        // jable.tv域名提取番号
        function extractJableCode(currentUrl) {
            // 检查是否是jable域名
            if (/https?:\/\/(www\.)?jable\.tv/i.test(currentUrl)) {
                // 从URL中提取番号
                const urlParts = currentUrl.split('/');
                // jable格式通常为 jable.tv/videos/番号/
                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.replace(/\/$/, '');
                
                // 处理不同情况
                
                // 情况1: 纯数字带连字符 (如 011209-959) -> 转为 caribbeancom-011209-959
                if (/^\d{6}-\d{3}$/.test(videoId)) {
                    return `caribbeancom-${videoId}`;
                }
                
                // 情况2: fc2ppv-数字 -> 转为 fc2-ppv-数字
                if (/^fc2ppv-\d+/.test(videoId.toLowerCase())) {
                    const fc2Num = videoId.split('-')[1];
                    return `fc2-ppv-${fc2Num}`;
                }
                
                // 情况3: 标准番号带后缀 (如 snis-420-c) -> 移除后缀
                if (/^[a-zA-Z]+-\d+(-[a-zA-Z])?$/.test(videoId)) {
                    const parts = videoId.split('-');
                    if (parts.length > 2 && parts[2].length <= 2) { // 假设后缀很短
                        return `${parts[0]}-${parts[1]}`;
                    }
                }
                
                // 情况4: 标准番号 (如 snis-420)
                if (/^[a-zA-Z]+-\d+$/.test(videoId)) {
                    return videoId;
                }
                
                // 其他未识别格式,直接返回
                return videoId;
            }
            
            console.log('不是jable网站,无法提取');
            return null;
        }
        


        //  当前域名判断 遇到需要定制的域名,则进行定制
        function getDomainInfo() {
            // 获取当前网址和主机名
            const currentUrl = window.location.href;
            const currentHostname = location.hostname;
            const result = {
                currentHostname: currentHostname,
                specialCode: null,
                isSpecialSite: false
            };
            
            // 检查是否是特殊网站 (missav或jable)
            if (currentUrl.includes('missav')) {
                result.isSpecialSite = true;
                const code = extractMissavCode(currentUrl);
                if (code) {
                    result.specialCode = code + '.av';
                    console.log('提取到番号:', code);
                }
            } else if (currentUrl.includes('jable')) {
                result.isSpecialSite = true;
                const code = extractJableCode(currentUrl);
                if (code) {
                    result.specialCode = code + '.av';
                    console.log('提取到番号:', code);
                }
            }
            
            return result;
        }

        // 在初始化聊天室时就建立所有连接
        initAllConnections();

        // 修改重连函数
        function reconnect() {
            if (!isReconnecting) {
                isReconnecting = true;
                
                // 关闭所有现有连接
                Object.keys(activeWebsockets).forEach(domain => {
                    if (activeWebsockets[domain]) {
                        activeWebsockets[domain].close();
                        activeWebsockets[domain] = null;
                    }
                    // 清空该域名的消息和用户列表
                    domainData[domain].messages = [];
                    domainData[domain].users = [];
                    domainData[domain].connected = false;
                });
                
                // 清空当前显示的消息
                chatMessagesContent.empty();
                messageInput.val('');
                
                // 更新UI显示重连状态
                appendSystemMessage("正在重新连接所有聊天室...");
                
                // 重新初始化所有连接
                initAllConnections();
                
                // 防止短时间内重复点击
                setTimeout(() => {
                    isReconnecting = false;
                }, 2000);
            }
        }

        // 修改初始化所有连接函数,添加成功回调
        function initAllConnections() {
            const domainInfo = getDomainInfo();
            let connectionsInitiated = 0;
            let connectionsSucceeded = 0;
            
            // 计算需要建立的连接数
            const totalConnections = 1 + 1 + (domainInfo.isSpecialSite && domainInfo.specialCode ? 1 : 0); // 大厅 + 当前站点 + (可能的)特殊房间
            
            function checkAllConnections() {
                if (connectionsSucceeded === totalConnections) {
                    console.log("所有聊天室连接成功!");
                    // 只在重连时显示消息
                    if (isReconnecting) {
                        appendSystemMessage("所有聊天室已重新连接成功!");
                    }
                } else if (connectionsInitiated === totalConnections && connectionsSucceeded < totalConnections) {
                    console.log(`部分聊天室连接失败,成功连接 ${connectionsSucceeded}/${totalConnections} 个聊天室`);
                    // 只在重连时显示消息
                    if (isReconnecting) {
                        appendSystemMessage(`部分聊天室连接失败,成功连接 ${connectionsSucceeded}/${totalConnections} 个聊天室`);
                    }
                }
            }
            
            // 如果是特殊站点且有提取到番号,则连接番号房间
            if (domainInfo.isSpecialSite && domainInfo.specialCode) {
                connectionsInitiated++;
                initDomainConnection(domainInfo.specialCode, () => {
                    connectionsSucceeded++;
                    checkAllConnections();
                });
            }
            
            // 连接当前站点
            connectionsInitiated++;
            initDomainConnection(domainInfo.currentHostname, () => {
                connectionsSucceeded++;
                checkAllConnections();
            });
            
            // 连接大厅
            connectionsInitiated++;
            initDomainConnection(HALL_DOMAIN, () => {
                connectionsSucceeded++;
                checkAllConnections();
            });
        }

        // 修改域名连接初始化函数,添加成功回调参数
        function initDomainConnection(domain, onSuccess) {
            if (!domainData[domain]) {
                domainData[domain] = {
                    messages: [],
                    users: [],
                    unreadCount: 0,
                    connected: false,
                    userId: null,    // 添加用户ID字段
                    userName: null   // 添加用户名字段
                };
            }
            
            if (!activeWebsockets[domain]) {
                const ws = new WebSocket(WS_SERVER);
                
                ws.onopen = function() {
                    // 发送身份更新,指定域名
                    const updateMsg = {
                        type: 'update',
                        data: {
                            domainFrom: domain
                        },
                        char: authChar
                    };
                    ws.send(JSON.stringify(updateMsg));
                    
                    domainData[domain].connected = true;
                    
                    // 如果这是第一个连接,设为活动域名
                    if (!activeDomain) {
                        activeDomain = domain;
                        updateUI();
                    }
                    
                    // 更新标签状态
                    updateTabs();
                    
                    // 调用成功回调
                    if (onSuccess) onSuccess();
                };
                
                ws.onmessage = function(event) {
                    handleDomainMessage(domain, event);
                };
                
                ws.onclose = function() {
                    handleDomainDisconnect(domain);
                };
                
                ws.onerror = function(error) {
                    console.error(`WebSocket Error (${domain}):`, error);
                    handleDomainDisconnect(domain);
                };
                
                // 保存连接
                activeWebsockets[domain] = ws;
                
                // 添加标签(如果不存在)
                addDomainTab(domain);
            }
        }
        
        // 修改 handleDomainMessage 函数
        function handleDomainMessage(domain, event) {
            const message = JSON.parse(event.data);
            const type = message.type;
            const data = message.data;
            
            switch (type) {
                case 'identity':
                    // 保存用户ID和名称
                    domainData[domain].userId = data.id;
                    domainData[domain].userName = data.name;
                    
                    // 保存历史消息
                    if (data.history && data.history.length > 0) {
                        domainData[domain].messages = data.history.map(msg => ({
                            ...msg,
                            msg: decrypt(msg.msg)
                        }));
                        
                        if (domain === activeDomain) {
                            updateUI();
                        }
                    }
                    break;
                    
                case 'memberList':
                    // 更新在线用户列表(添加过滤条件)
                    domainData[domain].users = data.filter(user => 
                        user.id !== "12523461428" && user.name !== "小尬"
                    );
                    
                    if (domain === activeDomain) {
                        updateOnlineUsers();
                    }
                    break;
                    
                case 'chat':
                    // 处理新消息
                    data.msg = decrypt(data.msg);
                    domainData[domain].messages.push(data);
                    
                    if (domain === activeDomain) {
                        appendMessage(data);
                        if (autoScroll) {
                            scrollToBottom();
                        }
                    } else {
                        domainData[domain].unreadCount++;
                        updateTabs();
                    }
                    
                    if (chatContainer.hasClass('ctrm-close') && domain === activeDomain) {
                        domainData[domain].unreadCount++;
                        updateTabs();
                    }
                    break;
                    
                case 'ack':
                    if (domain === activeDomain) {
                        messageInput.val('');
                    }
                    break;
            }
            trimHistory();
        }

        // 处理域名断开连接
        function handleDomainDisconnect(domain) {
            domainData[domain].connected = false;
            
            // 清空该域名的用户列表
            domainData[domain].users = [];
            
            // 如果是当前活动域名,显示断开消息并更新用户列表
            if (domain === activeDomain) {
                appendSystemMessage("您已掉线,点击重生按钮重新连接...");
                updateOnlineUsers();
            }
            
            // 更新标签状态
            updateTabs();
        }
        
        // 添加域名标签
        function addDomainTab(domain) {
            // 检查标签是否已存在
            if (chatTabs.find(`.chat-tab[data-domain="${domain}"]`).length === 0) {
                // 根据域名显示不同的标签名称
                let displayName = domain;
                
                if (domain === 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}
                    </div>
                `);
                
                // 点击标签切换域名
                tab.on('click', function() {
                    const clickedDomain = $(this).data('domain');
                    switchDomain(clickedDomain);
                });
                
                chatTabs.append(tab);
                
                // 如果这是第一个标签,设为活动状态
                if (chatTabs.find('.chat-tab').length === 1) {
                    tab.addClass('active');
                }
            }
        }
        
        // 更新所有标签的状态
        function updateTabs() {
            chatTabs.find('.chat-tab').each(function() {
                const domain = $(this).data('domain');
                const domainInfo = domainData[domain];
                
                // 清除现有状态
                $(this).removeClass('active disconnected unread-pulse');
                $(this).find('.unread-indicator').remove();
                
                // 设置活动状态
                if (domain === activeDomain) {
                    $(this).addClass('active');
                    // 只在面板展开时重置未读计数
                    if (!chatContainer.hasClass('ctrm-close')) {
                        domainInfo.unreadCount = 0;
                    }
                }
                
                // 设置连接状态
                if (!domainInfo.connected) {
                    $(this).addClass('disconnected');
                }
                
                // 修改未读显示条件:面板收起时显示所有未读,展开时只显示非当前
                if (domainInfo.unreadCount > 0 && 
                    (chatContainer.hasClass('ctrm-close') || domain !== activeDomain)) {
                    $(this).append(`<span class="unread-indicator"></span>`);
                    $(this).addClass('unread-pulse');
                }
            });
        }
        
        // 切换到指定域名
        function switchDomain(domain) {
            if (domain === activeDomain) return;
            
            // 停止之前标签的闪烁
            const previousTab = chatTabs.find(`.chat-tab[data-domain="${activeDomain}"]`);
            previousTab.removeClass('unread-pulse');
            
            // 保存之前的活动域名
            const previousDomain = activeDomain;
            
            // 更新活动域名
            activeDomain = domain;
            
            // 重置未读计数
            domainData[domain].unreadCount = 0;
            
            // 清空当前消息显示
            chatMessagesContent.empty();
            
            // 显示新域名的消息
            if (domainData[domain] && domainData[domain].messages) {
                domainData[domain].messages.forEach(msg => {
                    const element = createMessageElement(msg);
                    if (element) {
                        chatMessagesContent.append(element);
                    }
                });
            }
            
            // 更新在线用户列表
            updateOnlineUsers();
            
            // 更新标签状态
            updateTabs();
            
            // 滚动到底部
            scrollToBottom();
        }
        
        // 更新UI以显示当前活动域名的数据
        function updateUI() {
            try {
                chatMessagesContent.empty();
                
                const messages = domainData[activeDomain]?.messages || [];
                
                messages.forEach(msg => {
                    const element = createMessageElement(msg);
                    if (element) {
                        chatMessagesContent.append(element);
                    }
                });
                
                scrollToBottom();
                updateOnlineUsers();
                updateTabs();
            } catch (error) {
                console.error('Error updating UI:', error);
                appendSystemMessage("更新界面时出现错误");
            }
        }
        
        // 新增消息元素创建函数
        function createMessageElement(msg) {
            const time = new Date(msg.time);
            const timeStr = `${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}`;
            
            const isMe = msg.id === domainData[activeDomain].userId;
            const isSystem = msg.id === "system";
            
            let $messageElement;
            
            if (isSystem) {
                $messageElement = $(`<div class="message system-message"></div>`).text(msg.msg);
            } else {
                const firstChar = msg.name.charAt(0);
                const bgColor = generateElegantColor(msg.name);
                const textColor = 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">${msg.name}</div>
                                <span class="message-text">${msg.msg}</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">${msg.name}</div>
                                <span class="message-text">${msg.msg}</span>
                                <div class="timestamp">${timeStr}</div>
                            </div>
                            <figure class="avatar" style="--bg-color:${bgColor}; --text-color:${textColor}">
                                <span>${firstChar}</span>
                            </figure>
                        </div>
                    `);
                }
            }
            
            return $messageElement[0];
        }
        
        // 添加系统消息
        function appendSystemMessage(message) {
            const systemMsg = {
                time: Date.now(),
                id: "system",
                name: "系统消息",
                msg: message
            };
            appendMessage(systemMsg);
        }

        // 修改发送消息函数
        function sendMessage() {
            const message = sanitizeMessage(messageInput.val().slice(0, 69).trim());
            
            if (message.length === 0) {
                return alert('消息不能为空');
            }
            
            // 检查当前域名是否已连接
            if (!activeWebsockets[activeDomain] || !domainData[activeDomain].connected) {
                return alert('当前聊天室未连接,请重新连接');
            }

            if (!isSending) {
                const chatMessage = {
                    type: 'chat',
                    data: {
                        msg: encrypt(message)
                    },
                    char: authChar
                };

                try {
                    isSending = true;
                    activeWebsockets[activeDomain].send(JSON.stringify(chatMessage));
                    
                    setTimeout(() => {
                        isSending = false;
                    }, 5000);
                } catch (error) {
                    console.error('发送消息失败:', error);
                    alert('发送消息失败,请检查网络连接');
                    isSending = false;
                }
            }
        }
        
        // 绑定事件处理
        document.body.addEventListener('click', handleOutsideClick);
        window.addEventListener('popstate', handleOutsideClick);
        
        chatContainer.on('click', e => e.stopPropagation());
        chatContainer.on('touchstart', e => e.stopPropagation());
        chatContainer.on('touchend', e => e.stopPropagation());
        chatContainer.on('touchmove', e => e.stopPropagation());

        // 添加聊天面板点击事件
        chatContainer.find('.chat').on('click', e => {
            if (chatContainer.hasClass('ctrm-close')) {
                chatContainer.removeClass('ctrm-close');
                adjustUI();
                e.stopPropagation();
            }
        });

        closeBtn.click(e => {
            chatContainer.toggleClass('ctrm-close');
            adjustUI();
            e.stopPropagation();
        });

        reconnectBtn.click(reconnect);
        messageSubmitBtn.click(sendMessage);

        messageInput.on('keydown', e => {
            if (e.keyCode === 13 && !e.shiftKey) {
                e.preventDefault();
                sendMessage();
            }
        });

        // 在事件绑定部分添加自动调整高度的功能
        messageInput.on('input', function() {
            // 重置高度
            this.style.height = '36px';
            // 计算实际需要的高度
            const newHeight = Math.min(this.scrollHeight, 120);
            // 设置新高度
            this.style.height = newHeight + 'px';
        });

        // 修改滚动事件处理(添加防抖)
        const debounceScroll = debounce(() => {
            const el = chatMessagesContent[0];
            const clientHeight = el.clientHeight;
            const scrollTop = el.scrollTop;
            
            autoScroll = clientHeight + scrollTop >= el.scrollHeight * 0.9;
            scrollBottomBtn.toggle(!autoScroll);
        }, 100);

        chatMessagesContent.on('scroll', debounceScroll);

        scrollBottomBtn.click(() => {
            scrollBottomBtn.hide();
            autoScroll = true;
            scrollToBottom();
        });

        toggleUsersPanelBtn.click(() => {
            onlineUsersPanel.toggleClass('collapsed');
            
            // 增加过渡效果后可能需要重新调整对话框
            setTimeout(() => {
                scrollToBottom();
            }, 300);
        });

        // 初始化UI
        adjustUI();

        // 处理初始折叠状态
        setTimeout(() => {
            chatContainer.show();
            closeBtn.click(); // 默认触发收起状态
        }, 100);

        // 监听窗口大小变化
        window.addEventListener('resize', adjustUI);

        // 添加缺失的函数定义
        function adjustUI() {
            // 根据窗口大小调整UI
            if (window.innerWidth < 768) {
                chatContainer.addClass('ctrm-mobile');
            } else {
                chatContainer.removeClass('ctrm-mobile');
            }
        }

        function scrollToBottom() {
            // 滚动到聊天记录底部
            const el = chatMessagesContent[0];
            el.scrollTop = el.scrollHeight;
        }

        function updateOnlineUsers() {
            onlineUsersContent.empty();
            
            const currentUsers = domainData[activeDomain]?.users || [];
            const currentUserId = domainData[activeDomain]?.userId;
            
            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>
                `);
                
                onlineUsersContent.append(userElement);
            });
        }

        function sanitizeMessage(message) {
            // 简单的消息清理函数,防止XSS攻击
            return message.replace(/</g, '&lt;').replace(/>/g, '&gt;');
        }

        function appendMessage(data, scroll = true) {
            try {
                const element = createMessageElement(data);
                if (element) {
                    chatMessagesContent.append(element);
                    
                    if (scroll && autoScroll) {
                        requestAnimationFrame(() => scrollToBottom());
                    }
                }
            } catch (error) {
                console.error('Error appending message:', error);
            }
        }
        
        // 初始化时折叠用户面板
        onlineUsersPanel.addClass('collapsed');

        // 添加防抖函数
        function debounce(func, wait) {
            let timeout;
            return function(...args) {
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), wait);
            };
        }

        // 限制历史消息数量
        const MAX_HISTORY = 300;
        function trimHistory() {
            if (domainData[activeDomain].messages.length > MAX_HISTORY) {
                domainData[activeDomain].messages = domainData[activeDomain].messages.slice(-MAX_HISTORY);
            }
        }

        // 添加主题切换功能
        function toggleTheme(theme) {
            const root = document.documentElement;
            if (theme === 'dark') {
                root.style.setProperty('--primary-color', '#248A52');
                root.style.setProperty('--primary-hover', '#1D7745');
                root.style.setProperty('--bg-dark', 'rgba(0, 0, 0, 0.8)');
                root.style.setProperty('--bg-darker', 'rgba(0, 0, 0, 0.2)');
                root.style.setProperty('--text-primary', 'rgba(255, 255, 255, 0.9)');
            } else {
                root.style.setProperty('--primary-color', '#4CAF50');
                root.style.setProperty('--primary-hover', '#45A049');
                root.style.setProperty('--bg-dark', 'rgba(255, 255, 255, 0.95)');
                root.style.setProperty('--bg-darker', 'rgba(0, 0, 0, 0.05)');
                root.style.setProperty('--text-primary', 'rgba(0, 0, 0, 0.9)');
            }
        }

        // 阻止消息区域的滚动事件冒泡
        chatMessagesContent.on('wheel touchmove', function(e) {
            e.stopPropagation();
            // 阻止默认行为仅限触摸事件
            if(e.type === 'touchmove') {
                e.preventDefault();
            }
        });

        // 阻止在线用户列表的滚动事件冒泡
        onlineUsersContent.on('wheel touchmove', function(e) {
            e.stopPropagation();
            if(e.type === 'touchmove') {
                e.preventDefault();
            }
        });

        // 添加被动事件监听器改善移动端性能
        document.addEventListener('touchstart', function(e) {
            // 检查事件是否发生在聊天容器内
            if($(e.target).closest('#ctrm_').length) {
                e.preventDefault();
            }
        }, { passive: false });

        // 在消息容器初始化后添加以下代码
        chatMessagesContent[0].addEventListener('scroll', function(e) {
            e.stopPropagation();
        }, { passive: true });

        // 在在线用户列表初始化后添加
        onlineUsersContent[0].addEventListener('scroll', function(e) {
            e.stopPropagation();
        }, { passive: true });
    }

    // 判断页面加载状态并执行初始化
    if (document.readyState === 'complete') {
        initChatRoom();
    } else {
        window.addEventListener('load', initChatRoom);
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址