// ==UserScript== // @name BoxIM高情商回复插件 // @namespace http://tampermonkey.net/ // @version 2.0.0 // @description 为BoxIM即时通讯平台添加AI高情商回复功能 // @author You // @match *://www.boxim.online/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 配置选项 const config = { apiUrl: 'https://airoe.cn/v1', apiKey: 'sk-VcwihzmALRpa4M6TRjoVd60jt0QatuKYbUz21Kz0KceZq1in', modelId: 'qwen-plus-2025-09-11', // 可配置的选择器(基于BoxIM界面截图) selectors: { messageInput: 'input[type="text"], textarea, [placeholder*="消息"], [placeholder*="输入"]', sendButton: 'button:contains("发送"), [class*="send"], [class*="submit"]', messageList: '.message-list, .chat-messages, [class*="message"][class*="list"], [class*="chat"][class*="messages"], .im-chat-main', messageItem: '.message-item, .chat-message, [class*="message"][class*="item"], [class*="chat"][class*="message"], .im-message-item', messageContent: '.message-content, .chat-content, [class*="message"][class*="content"], [class*="chat"][class*="content"], .im-message-content', messageSender: '.message-sender, .chat-sender, [class*="message"][class*="sender"], [class*="chat"][class*="sender"], .im-message-sender' }, // 回复风格 replyStyle: '高情商、友好、专业', // 是否自动生成回复 autoGenerate: false, // 调试模式 debug: true }; // 加载配置 function loadConfig() { const savedConfig = GM_getValue('boximAiConfig'); if (savedConfig) { Object.assign(config, JSON.parse(savedConfig)); } } // 保存配置 function saveConfig() { GM_setValue('boximAiConfig', JSON.stringify(config)); } // 初始化配置菜单 function initConfigMenu() { GM_registerMenuCommand('BoxIM AI回复设置', () => { const newConfig = prompt('请输入配置(JSON格式):', JSON.stringify(config, null, 2)); if (newConfig) { try { Object.assign(config, JSON.parse(newConfig)); saveConfig(); alert('配置已保存'); } catch (e) { alert('配置格式错误'); } } }); } // 日志函数 function log(message, level = 'info') { if (config.debug) { console[level](`[BoxIM AI] ${message}`); } } // AI API调用函数 async function callAIAPI(prompt) { return new Promise((resolve, reject) => { log(`调用AI API: ${prompt.substring(0, 50)}...`); GM_xmlhttpRequest({ method: 'POST', url: config.apiUrl + '/chat/completions', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + config.apiKey }, data: JSON.stringify({ model: config.modelId, messages: [ { role: 'system', content: `你是一个高情商的聊天助手,回复风格:${config.replyStyle}。请根据用户的消息生成合适的回复。` }, { role: 'user', content: prompt } ], temperature: 0.7, max_tokens: 500 }), onload: function(response) { try { log(`API响应状态: ${response.status}`); const data = JSON.parse(response.responseText); if (data.choices && data.choices[0]) { const reply = data.choices[0].message.content.trim(); log(`AI回复: ${reply.substring(0, 50)}...`); resolve(reply); } else { log('API返回格式错误', 'error'); reject(new Error('API返回格式错误')); } } catch (e) { log(`API响应解析错误: ${e}`, 'error'); reject(e); } }, onerror: function(error) { log(`API调用错误: ${error}`, 'error'); reject(error); } }); }); } // 获取元素(增强版) function getElement(selector) { const element = document.querySelector(selector); if (element) { log(`找到元素: ${selector}`); } else { log(`未找到元素: ${selector}`, 'warn'); } return element; } // 获取消息输入框 function getMessageInput() { // 优先查找聊天界面的消息输入框 const chatInputs = document.querySelectorAll('input[type="text"], textarea'); // 优先选择带有特定占位符的输入框 for (const input of chatInputs) { // 优先选择包含"消息"或"输入"的占位符 if (input.placeholder && (input.placeholder.includes('消息') || input.placeholder.includes('输入'))) { // 排除搜索框 if (!input.placeholder.includes('搜索') && !input.placeholder.includes('查找')) { // 检查是否在聊天区域 let parent = input.parentNode; let isChatInput = false; for (let i = 0; i < 5; i++) { if (parent) { // 检查父元素是否包含聊天相关的类名 if (parent.className.includes('chat') || parent.className.includes('message') || parent.className.includes('input') || parent.className.includes('main')) { isChatInput = true; break; } // 检查父元素是否包含发送按钮 if (parent.querySelector('button')) { const buttons = parent.querySelectorAll('button'); for (const button of buttons) { if (button.textContent.includes('发送') || button.textContent.includes('Send')) { isChatInput = true; break; } } if (isChatInput) break; } parent = parent.parentNode; } } if (isChatInput) { log(`找到聊天输入框: ${input.placeholder}`); return input; } } } } // 尝试查找页面底部的输入框(通常是聊天输入框) let bottomInput = null; let maxBottom = 0; for (const input of chatInputs) { const rect = input.getBoundingClientRect(); if (rect.bottom > maxBottom) { maxBottom = rect.bottom; bottomInput = input; } } if (bottomInput) { log(`找到页面底部的输入框: ${bottomInput.placeholder || '底部输入框'}`); return bottomInput; } // 尝试使用配置的选择器 for (const selector of Object.values(config.selectors)) { if (selector === config.selectors.messageInput) { const input = getElement(selector); if (input) return input; } } // 尝试更广泛的选择 return getElement('input, textarea'); } // 获取发送按钮 function getSendButton() { // 尝试使用选择器 for (const selector of Object.values(config.selectors)) { if (selector === config.selectors.sendButton) { // 处理包含文本的选择器 if (selector.includes(':contains')) { const text = selector.match(/:contains\("([^"]+)"\)/)[1]; const buttons = document.querySelectorAll('button'); for (const button of buttons) { if (button.textContent.includes(text)) { log(`找到发送按钮: ${button.textContent}`); return button; } } } else { const button = getElement(selector); if (button) return button; } } } // 尝试更广泛的选择 const buttons = document.querySelectorAll('button'); for (const button of buttons) { if (button.textContent.includes('发送') || button.textContent.includes('Send')) { log(`找到发送按钮: ${button.textContent}`); return button; } } return getElement('button'); } // 获取消息列表 function getMessageList() { for (const selector of Object.values(config.selectors)) { if (selector === config.selectors.messageList) { const list = getElement(selector); if (list) return list; } } // 尝试更广泛的选择 return getElement('[class*="list"], [class*="messages"]'); } // 提取消息内容 function extractMessageContent(element) { for (const selector of Object.values(config.selectors)) { if (selector === config.selectors.messageContent) { const contentElement = element.querySelector(selector); if (contentElement) { const content = contentElement.textContent.trim(); if (content) { log(`提取消息内容: ${content.substring(0, 50)}...`); return content; } } } } // 尝试直接获取文本 const content = element.textContent.trim(); if (content) { log(`直接提取消息内容: ${content.substring(0, 50)}...`); } return content; } // 提取消息发送者 function extractMessageSender(element) { for (const selector of Object.values(config.selectors)) { if (selector === config.selectors.messageSender) { const senderElement = element.querySelector(selector); if (senderElement) { const sender = senderElement.textContent.trim(); log(`提取发送者: ${sender}`); return sender; } } } return '未知用户'; } // 监听新消息 function monitorMessages() { const messageList = getMessageList(); if (!messageList) { log('无法找到消息列表,将在5秒后重试', 'warn'); setTimeout(monitorMessages, 5000); return; } log('开始监听消息'); const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { // 检查是否是消息项 let isMessageItem = false; // 尝试直接匹配消息项选择器 for (const selector of config.selectors.messageItem.split(', ')) { if (node.matches(selector.trim())) { isMessageItem = true; break; } } if (!isMessageItem) { // 尝试检查子元素 for (const selector of config.selectors.messageItem.split(', ')) { if (node.querySelector(selector.trim())) { isMessageItem = true; break; } } } // 额外的检查:查看是否包含消息内容 if (!isMessageItem && (node.querySelector('div') && node.querySelector('div').textContent.trim())) { isMessageItem = true; } if (isMessageItem) { const content = extractMessageContent(node); const sender = extractMessageSender(node); if (content) { log(`收到新消息: ${content.substring(0, 50)}... 来自: ${sender}`); if (config.autoGenerate) { generateReply(content, sender); } } } } }); }); }); observer.observe(messageList, { childList: true, subtree: true }); } // 获取聊天历史 function getChatHistory() { const messageList = getMessageList(); if (!messageList) { log('无法找到消息列表', 'warn'); return []; } const messages = []; const messageItems = messageList.querySelectorAll(config.selectors.messageItem); // 获取最近的10条消息 const startIndex = Math.max(0, messageItems.length - 10); for (let i = startIndex; i < messageItems.length; i++) { const item = messageItems[i]; const content = extractMessageContent(item); const sender = extractMessageSender(item); if (content) { messages.push({ sender: sender, content: content }); } } log(`获取到 ${messages.length} 条聊天历史`); return messages; } // 生成AI回复 async function generateReply(message, sender) { try { // 获取聊天历史 const chatHistory = getChatHistory(); // 构建包含上下文的提示 let prompt = "以下是用户的聊天历史:\n"; // 添加聊天历史 chatHistory.forEach((msg, index) => { prompt += `[${msg.sender}]: ${msg.content}\n`; }); // 添加最新消息 prompt += `\n[${sender}]: ${message}\n\n请基于以上聊天历史,生成一个高情商的回复:`; log(`生成包含上下文的提示,长度: ${prompt.length}`); const reply = await callAIAPI(prompt); showReplySuggestion(reply); } catch (error) { log(`生成回复失败: ${error}`, 'error'); showReplySuggestion('生成回复失败,请重试'); } } // 显示回复建议 function showReplySuggestion(reply) { const input = getMessageInput(); if (!input) { log('无法找到消息输入框', 'warn'); return; } // 移除已有的建议 const existingSuggestion = document.getElementById('boxim-ai-suggestion'); if (existingSuggestion) { existingSuggestion.remove(); } // 创建建议容器 const suggestionContainer = document.createElement('div'); suggestionContainer.id = 'boxim-ai-suggestion'; // 获取输入框的位置(考虑页面滚动) const inputRect = input.getBoundingClientRect(); const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; const scrollTop = window.pageYOffset || document.documentElement.scrollTop; suggestionContainer.style.cssText = ` position: fixed; left: ${inputRect.left}px; top: ${inputRect.bottom + 10}px; width: ${Math.min(inputRect.width, 400)}px; padding: 12px; background: #f5f5f5; border-radius: 8px; font-size: 14px; color: #333; box-shadow: 0 2px 4px rgba(0,0,0,0.1); z-index: 99999; border: 1px solid #ddd; max-width: 90vw; box-sizing: border-box; `; // 添加标题 const title = document.createElement('div'); title.textContent = 'AI建议回复:'; title.style.cssText = ` font-weight: bold; margin-bottom: 8px; color: #666; `; suggestionContainer.appendChild(title); // 添加回复内容 const replyContent = document.createElement('div'); replyContent.textContent = reply; replyContent.style.cssText = ` margin-bottom: 12px; line-height: 1.4; word-wrap: break-word; `; suggestionContainer.appendChild(replyContent); // 添加操作按钮 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; gap: 8px; justify-content: flex-end; `; // 复制按钮 const copyButton = document.createElement('button'); copyButton.textContent = '复制'; copyButton.style.cssText = ` padding: 6px 12px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; `; copyButton.addEventListener('click', () => { // 复制回复内容到剪贴板 navigator.clipboard.writeText(reply).then(() => { log('回复内容已复制到剪贴板'); // 显示复制成功提示 const originalText = copyButton.textContent; copyButton.textContent = '已复制'; setTimeout(() => { copyButton.textContent = originalText; }, 2000); }).catch(err => { log(`复制失败: ${err}`, 'error'); }); }); // 关闭按钮 const closeButton = document.createElement('button'); closeButton.textContent = '关闭'; closeButton.style.cssText = ` padding: 6px 12px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; `; closeButton.addEventListener('click', () => { suggestionContainer.remove(); log('关闭AI回复建议'); }); buttonContainer.appendChild(copyButton); buttonContainer.appendChild(closeButton); suggestionContainer.appendChild(buttonContainer); // 添加到body中,确保它在最上层 document.body.appendChild(suggestionContainer); log('显示AI回复建议'); } // 添加AI回复按钮 function addAIReplyButton() { // 尝试多种方法找到输入框 let input = getMessageInput(); if (!input) { // 尝试查找所有输入框 const inputs = document.querySelectorAll('input, textarea'); for (const i of inputs) { if (i.type === 'text' || i.type === 'textarea' || i.placeholder) { input = i; log(`找到输入框: ${i.placeholder || '文本输入框'}`); break; } } } // 尝试多种方法找到发送按钮 let sendButton = getSendButton(); if (!sendButton) { // 尝试查找所有按钮 const buttons = document.querySelectorAll('button'); for (const button of buttons) { if (button.textContent.includes('发送') || button.textContent.includes('Send') || button.innerText.includes('发送')) { sendButton = button; log(`找到发送按钮: ${button.textContent}`); break; } } } if (!input || !sendButton) { log('无法找到输入框或发送按钮,将在5秒后重试', 'warn'); setTimeout(addAIReplyButton, 5000); return; } // 检查按钮是否已存在 if (document.getElementById('boxim-ai-button')) { return; } // 创建AI回复按钮 const aiButton = document.createElement('button'); aiButton.id = 'boxim-ai-button'; aiButton.textContent = 'AI回复'; aiButton.style.cssText = ` margin-right: 8px; padding: 6px 12px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; z-index: 999; display: inline-block; `; // 添加点击事件 aiButton.addEventListener('click', async () => { log('点击AI回复按钮'); // 获取最近的消息 const messageList = getMessageList(); if (messageList) { // 尝试多种方法获取消息项 let messages = []; // 尝试使用配置的选择器 for (const selector of config.selectors.messageItem.split(', ')) { const items = messageList.querySelectorAll(selector.trim()); if (items.length > 0) { messages = Array.from(items); break; } } // 如果没有找到,尝试查找所有div元素 if (messages.length === 0) { const allDivs = messageList.querySelectorAll('div'); messages = Array.from(allDivs).filter(div => { return div.textContent.trim() !== '' && !div.querySelector('button'); }); } if (messages.length > 0) { const lastMessage = messages[messages.length - 1]; const content = extractMessageContent(lastMessage); const sender = extractMessageSender(lastMessage); if (content) { log(`使用消息: ${content.substring(0, 50)}...`); await generateReply(content, sender); } else { log('无法提取消息内容', 'warn'); // 尝试直接获取文本 const text = lastMessage.textContent.trim(); if (text) { log(`直接使用文本: ${text.substring(0, 50)}...`); await generateReply(text, '未知用户'); } } } else { log('消息列表为空', 'warn'); } } else { log('无法找到消息列表', 'warn'); } }); // 方法1: 在页面右下角创建一个浮动按钮(优先使用) try { document.body.appendChild(aiButton); aiButton.style.cssText = ` position: fixed !important; bottom: 80px !important; right: 20px !important; padding: 12px 24px !important; background: #28a745 !important; color: white !important; border: none !important; border-radius: 50px !important; cursor: pointer !important; font-size: 14px !important; font-weight: bold !important; z-index: 99999 !important; box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important; transition: all 0.3s ease !important; display: block !important; visibility: visible !important; opacity: 1 !important; `; // 添加悬停效果 aiButton.addEventListener('mouseover', function() { this.style.transform = 'scale(1.05)'; this.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)'; }); aiButton.addEventListener('mouseout', function() { this.style.transform = 'scale(1)'; this.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'; }); log('添加AI回复按钮到页面右下角'); } catch (e) { log(`方法1失败: ${e}`, 'error'); // 方法2: 尝试插入到发送按钮旁边 try { const sendParent = sendButton.parentNode; log(`发送按钮父节点: ${sendParent.tagName} ${sendParent.className}`); // 创建一个新的容器来存放发送按钮和AI回复按钮 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex !important; align-items: center !important; gap: 8px !important; margin-top: 8px !important; z-index: 9999 !important; `; // 将发送按钮移动到新容器 sendParent.removeChild(sendButton); buttonContainer.appendChild(aiButton); buttonContainer.appendChild(sendButton); // 将新容器插入到原来发送按钮的位置 sendParent.appendChild(buttonContainer); log('添加AI回复按钮到发送按钮旁边'); } catch (e2) { log(`方法2失败: ${e2}`, 'error'); // 方法3: 直接在输入框下方创建一个容器 try { // 创建一个容器来存放AI回复按钮 const aiContainer = document.createElement('div'); aiContainer.id = 'boxim-ai-container'; aiContainer.style.cssText = ` margin: 8px 0 !important; display: flex !important; align-items: center !important; gap: 8px !important; z-index: 9999 !important; `; // 添加AI回复按钮到容器 aiContainer.appendChild(aiButton); // 找到输入框的父容器 const inputParent = input.parentNode; log(`输入框父节点: ${inputParent.tagName} ${inputParent.className}`); // 插入到输入框之后 inputParent.insertBefore(aiContainer, input.nextSibling); log('添加AI回复按钮到输入框下方'); log(`AI回复按钮容器位置: ${aiContainer.offsetLeft}, ${aiContainer.offsetTop}`); } catch (e3) { log(`方法3失败: ${e3}`, 'error'); } } } // 确保按钮可见 aiButton.style.display = 'block !important'; aiButton.style.visibility = 'visible !important'; aiButton.style.opacity = '1 !important'; aiButton.style.zIndex = '99999 !important'; aiButton.style.position = 'fixed !important'; aiButton.style.bottom = '80px !important'; aiButton.style.right = '20px !important'; log(`最终AI回复按钮状态: 显示=${aiButton.style.display}, 可见=${aiButton.style.visibility}, 不透明度=${aiButton.style.opacity}`); log(`AI回复按钮HTML: ${aiButton.outerHTML}`); // 强制显示按钮 setTimeout(() => { const button = document.getElementById('boxim-ai-button'); if (button) { button.style.display = 'block !important'; button.style.visibility = 'visible !important'; button.style.opacity = '1 !important'; button.style.zIndex = '99999 !important'; log('强制显示AI回复按钮'); } }, 1000); } // 初始化函数 function init() { log('初始化BoxIM AI回复插件'); loadConfig(); initConfigMenu(); // 等待DOM加载完成 setTimeout(() => { monitorMessages(); addAIReplyButton(); // 定期检查并添加按钮(处理动态加载) setInterval(() => { // 检查按钮是否存在 if (!document.getElementById('boxim-ai-button')) { log('AI回复按钮不存在,重新添加'); addAIReplyButton(); } }, 3000); // 监听DOM变化,处理动态加载的内容 const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { // 检查是否有新的元素添加 if (mutation.addedNodes.length > 0) { // 检查是否包含输入框或发送按钮 let hasInputOrButton = false; mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { if (node.querySelector('input, textarea, button')) { hasInputOrButton = true; } } }); if (hasInputOrButton) { log('检测到新的输入框或按钮,检查AI回复按钮'); setTimeout(() => { if (!document.getElementById('boxim-ai-button')) { addAIReplyButton(); } }, 500); } } }); }); // 观察整个文档的变化 observer.observe(document.body, { childList: true, subtree: true }); log('添加DOM变化监听器'); }, 1000); } // 启动插件 init(); })();