网页聊天室库
สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/528253/1544248/%E8%81%8A%E5%A4%A9%E5%AE%A4%E5%BA%93.js
// ==UserScript== // @name 聊天室库 // @namespace http://tampermonkey.net/ // @version 0.4 // @description 网页聊天室库 // @author You // @grant none // ==/UserScript== (function(window) { 'use strict'; // 导出聊天室初始化函数 window.initChatRoom = function() { // ... 原有的 initChatRoom 函数内容 ... // 加密配置 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 messag###bmitBtn = 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 r###lt = { currentHostname: currentHostname, specialCode: null, isSpecialSite: false }; // 检查是否是特殊网站 (missav或jable) if (currentUrl.includes('missav')) { r###lt.isSpecialSite = true; const code = extractMissavCode(currentUrl); if (code) { r###lt.specialCode = code + '.av'; console.log('提取到番号:', code); } } else if (currentUrl.includes('jable')) { r###lt.isSpecialSite = true; const code = extractJableCode(currentUrl); if (code) { r###lt.specialCode = code + '.av'; console.log('提取到番号:', code); } } return r###lt; } // 在初始化聊天室时就建立所有连接 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); messag###bmitBtn.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, '<').replace(/>/g, '>'); } 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); } }; })(typeof unsafeWindow !== 'undefined' ? unsafeWindow : window);