🏠 Home 

聊天室库

网页聊天室库

สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @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, '&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);
}
};
})(typeof unsafeWindow !== 'undefined' ? unsafeWindow : window);