您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
聊天全屏 + 多栏布局 + 自动调整消息数量 + 背景颜色切换 + 全屏输入框
// ==UserScript== // @name MWI公屏聊天全屏 // @version 3.0 // @description 聊天全屏 + 多栏布局 + 自动调整消息数量 + 背景颜色切换 + 全屏输入框 // @match https://www.milkywayidle.com/* // @grant none // @license MIT // @namespace http://tampermonkey.net/ // ==/UserScript== (function() { 'use strict'; // 配置常量 const CONFIG = { DEFAULT_LAYOUT: 3, // 默认三栏布局 BG_COLORS: [ { color: '#ffffff', name: '白色' }, { color: '#f0fff0', name: '护眼绿' }, { color: '#fff8e7', name: '护眼黄' }, { color: '#e6f2ff', name: '护眼蓝' } ], SELECTORS: { CHAT: '[class^="ChatHistory_chatHistory"]', CHAT_INPUT: '.Chat_chatInput__16dhX' }, MESSAGE_HEIGHT_ESTIMATE: 40, // 预估每条消息的高度(包括间距) PADDING: 40, // 容器内边距 INPUT_BOX_HEIGHT: 60 // 输入框区域高度 }; // 应用状态 const state = { layoutMode: CONFIG.DEFAULT_LAYOUT, messageCount: 0, // 动态计算,不再固定 fullscreenMode: false, bgColorIndex: 0, observer: null, allMessages: [], firstLoad: true, lastCalculatedHeight: 0, // 记录上次计算的容器高度 originalInput: null // 原始输入框引用 }; // DOM 元素管理 const elements = { buttons: {}, chat: null, fullscreenInput: null, inputContainer: null }; /** * 工具函数 */ const utils = { // 防抖函数 debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func.apply(this, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, // 获取聊天元素 getChatElement() { return document.querySelector(CONFIG.SELECTORS.CHAT); }, // 获取原始输入框 getOriginalInput() { return document.querySelector(CONFIG.SELECTORS.CHAT_INPUT); }, // 计算每栏可显示的消息数量 calculateMessagesPerColumn(chat, forceRecalculate = false) { if (!chat) return 20; // 默认值 const containerHeight = chat.clientHeight - CONFIG.PADDING - CONFIG.INPUT_BOX_HEIGHT; const columnHeight = containerHeight; // 如果不强制重新计算且容器高度没有变化,返回缓存结果 if (!forceRecalculate && state.lastCalculatedHeight === containerHeight && state.messageCount > 0) { return Math.floor(state.messageCount / state.layoutMode); } // 临时显示一些消息来测量实际高度 const testMessages = state.allMessages.slice(-Math.min(50, state.allMessages.length)); // 先隐藏所有消息,然后显示测试消息 state.allMessages.forEach(msg => msg.style.display = 'none'); testMessages.forEach(msg => msg.style.display = 'block'); // 计算平均消息高度 let totalHeight = 0; let visibleCount = 0; testMessages.forEach(msg => { const rect = msg.getBoundingClientRect(); if (rect.height > 0) { totalHeight += rect.height + parseInt(getComputedStyle(msg).marginBottom || 0); visibleCount++; } }); const avgMessageHeight = visibleCount > 0 ? totalHeight / visibleCount : CONFIG.MESSAGE_HEIGHT_ESTIMATE; const messagesPerColumn = Math.floor(columnHeight / avgMessageHeight); state.lastCalculatedHeight = containerHeight; return Math.max(1, messagesPerColumn); // 至少显示1条消息 }, // 计算总消息数量 calculateTotalMessages(chat, forceRecalculate = false) { const messagesPerColumn = this.calculateMessagesPerColumn(chat, forceRecalculate); return messagesPerColumn * state.layoutMode; }, // 强制DOM回流 forceReflow(element) { return element.offsetHeight; }, // 创建样式字符串 createStyleString(styles) { return Object.entries(styles) .map(([key, value]) => `${key}: ${value}`) .join('; '); }, // 模拟按键事件 simulateKeyPress(element, key) { const event = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: key, code: key === 'Enter' ? 'Enter' : key, which: key === 'Enter' ? 13 : key.charCodeAt(0) }); element.dispatchEvent(event); }, // 模拟输入事件 simulateInput(element, value) { element.value = value; element.dispatchEvent(new Event('input', { bubbles: true })); } }; /** * 样式注入 */ function injectStyles() { if (document.getElementById('mwi-chat-styles')) return; const style = document.createElement('style'); style.id = 'mwi-chat-styles'; style.textContent = ` /* 基础消息样式 */ ${CONFIG.SELECTORS.CHAT} > div { margin-bottom: 6px; padding-bottom: 2px; font-size: 14px; line-height: 1.4; word-wrap: break-word; white-space: pre-wrap; word-break: break-word; user-select: text; } /* 全屏聊天样式 */ .fullscreen-chat { position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: calc(100vh - ${CONFIG.INPUT_BOX_HEIGHT}px) !important; z-index: 9998 !important; overflow-y: auto !important; padding: 20px; box-sizing: border-box; color: #333 !important; column-gap: 40px; font-size: 16px !important; background-color: var(--chat-bg-color, #ffffff); } .fullscreen-chat > div { color: #111 !important; break-inside: avoid; margin-bottom: 10px; transition: opacity 0.3s ease; } /* 全屏输入框容器 */ .fullscreen-input-container { position: fixed; bottom: 0; left: 0; width: 100vw; height: ${CONFIG.INPUT_BOX_HEIGHT}px; background-color: var(--chat-bg-color, #ffffff); border-top: 2px solid #ddd; display: flex; align-items: center; padding: 0 20px; box-sizing: border-box; z-index: 9999; } /* 全屏输入框样式 */ .fullscreen-chat-input { flex: 1; height: 40px; padding: 0 15px; border: 1px solid #000; border-radius: 20px; font-size: 16px; outline: none; transition: border-color 0.3s ease; background-color: #ffffff !important; color: #000 !important; caret-color: #000 !important; } .fullscreen-chat-input:focus { border-color: #000; } .fullscreen-chat-input::placeholder { color: #666 !important; } /* 发送按钮样式 */ .fullscreen-send-btn { margin-left: 10px; padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 20px; cursor: pointer; font-size: 14px; transition: background-color 0.3s ease; } .fullscreen-send-btn:hover { background-color: #0056b3; } .fullscreen-send-btn:disabled { background-color: #ccc; cursor: not-allowed; } /* 按钮基础样式 */ .mwi-btn { position: fixed; right: 10px; z-index: 9999; padding: 10px 15px; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; color: white; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); transition: background-color 0.3s ease; } /* 主按钮样式 */ #fullscreen-toggle-btn { top: 10px; background-color: #007bff; } #fullscreen-toggle-btn:hover { background-color: #0056b3; } /* 布局按钮样式 */ #layout-btn { top: 60px; background-color: #6c757d; display: none; } #layout-btn:hover { background-color: #5a6268; } /* 背景颜色按钮样式 */ #bgcolor-btn { top: 110px; background-color: #28a745; display: none; } #bgcolor-btn:hover { background-color: #218838; } /* 响应式调整 */ @media (max-width: 768px) { .fullscreen-chat { padding: 10px; column-gap: 20px; } .mwi-btn { font-size: 12px; padding: 8px 12px; } .fullscreen-input-container { padding: 0 10px; } } `; document.head.appendChild(style); } /** * 输入框管理 */ const inputManager = { // 创建全屏输入框 createFullscreenInput() { if (elements.inputContainer) return; // 创建输入框容器 const container = document.createElement('div'); container.className = 'fullscreen-input-container'; container.style.display = 'none'; // 创建输入框 const input = document.createElement('input'); input.type = 'text'; input.className = 'fullscreen-chat-input'; input.placeholder = '输入消息...'; // 创建发送按钮 const sendBtn = document.createElement('button'); sendBtn.className = 'fullscreen-send-btn'; sendBtn.textContent = '发送'; // 组装元素 container.appendChild(input); container.appendChild(sendBtn); document.body.appendChild(container); // 保存引用 elements.inputContainer = container; elements.fullscreenInput = input; // 绑定事件 this.bindInputEvents(input, sendBtn); console.log('全屏输入框创建完成'); }, // 绑定输入框事件 bindInputEvents(input, sendBtn) { // 回车发送 input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } }); // 按钮发送 sendBtn.addEventListener('click', () => { this.sendMessage(); }); // 输入时更新按钮状态 input.addEventListener('input', () => { sendBtn.disabled = !input.value.trim(); }); // 初始化按钮状态 sendBtn.disabled = true; }, // 发送消息 sendMessage() { const message = elements.fullscreenInput.value.trim(); if (!message) return; const originalInput = utils.getOriginalInput(); if (!originalInput) { alert('❗ 找不到原始输入框,无法发送消息'); return; } try { // 聚焦原始输入框 originalInput.focus(); // 设置输入内容 utils.simulateInput(originalInput, message); // 模拟回车发送 setTimeout(() => { utils.simulateKeyPress(originalInput, 'Enter'); // 清空全屏输入框 elements.fullscreenInput.value = ''; elements.fullscreenInput.dispatchEvent(new Event('input', { bubbles: true })); console.log('消息已发送:', message); }, 50); } catch (error) { console.error('发送消息失败:', error); alert('❗ 发送消息失败,请检查页面状态'); } }, // 显示输入框 showInput() { if (elements.inputContainer) { elements.inputContainer.style.display = 'flex'; // 聚焦输入框 setTimeout(() => { if (elements.fullscreenInput) { elements.fullscreenInput.focus(); } }, 100); } }, // 隐藏输入框 hideInput() { if (elements.inputContainer) { elements.inputContainer.style.display = 'none'; // 清空输入框 if (elements.fullscreenInput) { elements.fullscreenInput.value = ''; } } }, // 更新输入框背景色 updateInputBgColor() { if (elements.inputContainer) { const currentBg = CONFIG.BG_COLORS[state.bgColorIndex]; elements.inputContainer.style.backgroundColor = currentBg.color; } } }; /** * 按钮创建和管理 */ const buttonManager = { // 创建主切换按钮 createToggleButton() { const button = document.createElement('button'); button.id = 'fullscreen-toggle-btn'; button.className = 'mwi-btn'; button.textContent = '聊天全屏'; button.addEventListener('click', chatManager.toggleFullscreen.bind(chatManager)); if (!document.getElementById('fullscreen-toggle-btn')) { document.body.appendChild(button); elements.buttons.toggle = button; } }, // 创建布局切换按钮 createLayoutButton() { const button = document.createElement('button'); button.id = 'layout-btn'; button.className = 'mwi-btn'; button.textContent = '布局: 三栏'; button.addEventListener('click', layoutManager.toggleLayout.bind(layoutManager)); if (!document.getElementById('layout-btn')) { document.body.appendChild(button); elements.buttons.layout = button; } }, // 创建背景颜色按钮 createBgColorButton() { const button = document.createElement('button'); button.id = 'bgcolor-btn'; button.className = 'mwi-btn'; button.textContent = '背景: 白色'; button.addEventListener('click', backgroundManager.toggleBgColor.bind(backgroundManager)); if (!document.getElementById('bgcolor-btn')) { document.body.appendChild(button); elements.buttons.bgcolor = button; } }, // 更新按钮文本 updateButtonText(buttonId, text) { const button = elements.buttons[buttonId] || document.getElementById(buttonId); if (button) { button.textContent = text; } }, // 显示/隐藏辅助按钮 toggleAuxiliaryButtons(show) { const buttons = ['layout-btn', 'bgcolor-btn']; buttons.forEach(id => { const button = document.getElementById(id); if (button) { button.style.display = show ? 'block' : 'none'; } }); } }; /** * 布局管理 */ const layoutManager = { // 切换布局模式 toggleLayout() { state.layoutMode = (state.layoutMode % 3) + 1; this.updateLayout(); }, // 更新布局 updateLayout() { const chat = utils.getChatElement(); if (!chat || !chat.classList.contains('fullscreen-chat')) return; const layoutNames = ['一栏', '两栏', '三栏']; buttonManager.updateButtonText('layout', `布局: ${layoutNames[state.layoutMode - 1]}`); chat.style.columnCount = state.layoutMode; // 强制重新计算并应用消息数量 utils.forceReflow(chat); // 确保布局更新 // 延迟执行以确保DOM完全更新 setTimeout(() => { messageManager.recalculateAndApply(); }, 50); console.log(`切换到${layoutNames[state.layoutMode - 1]}布局`); } }; /** * 背景管理 */ const backgroundManager = { // 切换背景颜色 toggleBgColor() { state.bgColorIndex = (state.bgColorIndex + 1) % CONFIG.BG_COLORS.length; this.updateBgColor(); }, // 更新背景颜色 updateBgColor() { const chat = utils.getChatElement(); if (!chat || !chat.classList.contains('fullscreen-chat')) return; const currentBg = CONFIG.BG_COLORS[state.bgColorIndex]; buttonManager.updateButtonText('bgcolor', `背景: ${currentBg.name}`); // 使用CSS自定义属性 document.documentElement.style.setProperty('--chat-bg-color', currentBg.color); // 更新输入框背景色 inputManager.updateInputBgColor(); } }; /** * 消息管理 */ const messageManager = { // 应用消息数量限制 applyMessageLimit() { const chat = utils.getChatElement(); if (!chat || state.allMessages.length === 0) return; // 先隐藏所有消息 state.allMessages.forEach(msg => msg.style.display = 'none'); // 显示最近的消息 const recentMessages = state.allMessages.slice(-state.messageCount); recentMessages.forEach(msg => msg.style.display = 'block'); console.log(`当前显示最近 ${recentMessages.length} 条消息,分 ${state.layoutMode} 栏显示`); }, // 重新计算并应用消息限制(用于分栏切换) recalculateAndApply() { const chat = utils.getChatElement(); if (!chat || !chat.classList.contains('fullscreen-chat')) return; // 更新消息列表 state.allMessages = Array.from(chat.children); // 强制重新计算消息数量 state.messageCount = utils.calculateTotalMessages(chat, true); // 应用新的消息限制 this.applyMessageLimit(); }, // 设置消息观察器 setupObserver(chat) { this.disconnectObserver(); state.observer = new MutationObserver(utils.debounce((mutations) => { let shouldUpdate = false; mutations.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { state.allMessages = Array.from(chat.children); shouldUpdate = true; } }); if (shouldUpdate) { // 新消息到达时,使用当前的消息数量设置,不重新计算 this.applyMessageLimit(); } }, 100)); state.observer.observe(chat, { childList: true, subtree: false }); }, // 断开观察器 disconnectObserver() { if (state.observer) { state.observer.disconnect(); state.observer = null; } }, // 重置消息显示 resetMessageDisplay() { state.allMessages.forEach(msg => msg.style.display = 'block'); state.allMessages = []; state.lastCalculatedHeight = 0; // 重置高度缓存 state.messageCount = 0; // 重置消息计数 } }; /** * 聊天管理 */ const chatManager = { // 切换全屏模式 toggleFullscreen() { const chat = utils.getChatElement(); if (!chat) { alert('❗ 找不到聊天区域'); return; } state.fullscreenMode = !chat.classList.contains('fullscreen-chat'); if (state.fullscreenMode) { this.enterFullscreen(chat); } else { this.exitFullscreen(chat); } }, // 进入全屏模式 enterFullscreen(chat) { chat.classList.add('fullscreen-chat'); buttonManager.updateButtonText('toggle', '退出全屏'); buttonManager.toggleAuxiliaryButtons(true); utils.forceReflow(chat); // 获取所有消息 state.allMessages = Array.from(chat.children); messageManager.setupObserver(chat); layoutManager.updateLayout(); backgroundManager.updateBgColor(); // 显示输入框 inputManager.showInput(); state.firstLoad = false; }, // 退出全屏模式 exitFullscreen(chat) { chat.classList.remove('fullscreen-chat'); buttonManager.updateButtonText('toggle', '聊天全屏'); buttonManager.toggleAuxiliaryButtons(false); messageManager.disconnectObserver(); messageManager.resetMessageDisplay(); // 隐藏输入框 inputManager.hideInput(); state.firstLoad = true; } }; /** * 初始化管理 */ const initManager = { // 等待聊天区域加载 waitForChat() { const observer = new MutationObserver((mutations, obs) => { const chat = utils.getChatElement(); if (chat) { this.initializePlugin(); obs.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); // 设置超时,避免无限等待 setTimeout(() => { observer.disconnect(); console.warn('MWI Chat Plugin: 等待聊天区域超时'); }, 10000); }, // 初始化插件 initializePlugin() { injectStyles(); buttonManager.createToggleButton(); buttonManager.createLayoutButton(); buttonManager.createBgColorButton(); inputManager.createFullscreenInput(); this.bindEvents(); console.log('MWI Chat Plugin: 初始化完成'); }, // 绑定事件 bindEvents() { // 窗口大小变化事件 - 需要重新计算消息数量 window.addEventListener('resize', utils.debounce(() => { const chat = utils.getChatElement(); if (chat && chat.classList.contains('fullscreen-chat')) { // 窗口大小变化时重新计算并应用 messageManager.recalculateAndApply(); } }, 250)); // 页面卸载清理 window.addEventListener('beforeunload', () => { messageManager.disconnectObserver(); }); } }; /** * 主程序入口 */ function main() { // 检查页面是否已加载 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initManager.waitForChat.bind(initManager)); } else { initManager.waitForChat(); } } // 启动插件 main(); })();