您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动短信登录流程 + 文件下载监控 + 黑名单过滤,支持自定义配置,提升工作效率
// ==UserScript== // @name DevBlueChat // @namespace http://tampermonkey.net/ // @version 1.0.4 // @description 自动短信登录流程 + 文件下载监控 + 黑名单过滤,支持自定义配置,提升工作效率 // @author Eachann // @match https://codigger.onecloud.cn/* // @icon https://files.catbox.moe/8l13tx.jpg // @homepage https://github.com/eachann1024/ILoveWork/blob/master/DevXiaoHui.js // @supportURL https://github.com/eachann1024/ILoveWork/issues // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ==================== 配置管理 ==================== const CONFIG_KEYS = { DOWNLOAD_ENABLED: 'downloadEnabled', BLACKLIST_ENABLED: 'blacklistEnabled', FILE_BLACKLIST: 'fileBlacklist', PHONE_NUMBER: 'phoneNumber', AUTO_RECONNECT: 'autoReconnect', CUSTOM_ICON_TITLE: 'customIconTitle' }; // 默认配置 const DEFAULT_CONFIG = { [CONFIG_KEYS.DOWNLOAD_ENABLED]: true, [CONFIG_KEYS.BLACKLIST_ENABLED]: false, [CONFIG_KEYS.FILE_BLACKLIST]: '.exe,.bat,.cmd,.scr,.pif', [CONFIG_KEYS.PHONE_NUMBER]: '', [CONFIG_KEYS.AUTO_RECONNECT]: true, [CONFIG_KEYS.CUSTOM_ICON_TITLE]: true }; // 上传状态标记 let isUploading = false; let uploadTimer = null; // 已下载文件记录 (使用 Set 存储文件的唯一标识) const downloadedFiles = new Set(); // 下载节流控制 let lastDownloadTime = 0; const DOWNLOAD_THROTTLE_INTERVAL = 3000; // 3秒内只允许触发一次下载 // 生成文件唯一标识 function generateFileId(requestData) { // 使用文件名、消息ID、用户ID等信息生成唯一标识 const fileName = requestData.fileName || ''; const msgId = requestData.msgId || ''; const userId = requestData.chatUserId || ''; return `${fileName}_${msgId}_${userId}`; } // 检查文件是否已下载过 function isFileAlreadyDownloaded(requestData) { const fileId = generateFileId(requestData); return downloadedFiles.has(fileId); } // 记录文件已下载 function markFileAsDownloaded(requestData) { const fileId = generateFileId(requestData); downloadedFiles.add(fileId); console.log('📝 记录文件下载:', requestData.fileName, '(ID:', fileId, ')'); } // 设置上传状态 function setUploadingState(isUpload) { isUploading = isUpload; if (isUpload) { console.log('📤 检测到文件上传,临时禁用下载功能'); // 清除之前的定时器 if (uploadTimer) { clearTimeout(uploadTimer); } // 20秒后恢复下载功能 uploadTimer = setTimeout(() => { isUploading = false; console.log('✅ 文件上传冷却结束,恢复下载功能'); }, 20000); } } // 获取配置 function getConfig(key) { return GM_getValue(key, DEFAULT_CONFIG[key]); } // 设置配置 function setConfig(key, value) { GM_setValue(key, value); } // 创建设置界面 function createSettingsPanel() { const panel = document.createElement('div'); panel.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%); border: 1px solid #333; border-radius: 16px; padding: 0; z-index: 10000; box-shadow: 0 20px 60px rgba(0,0,0,0.8); min-width: 480px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: white; overflow: hidden; `; const downloadEnabled = getConfig(CONFIG_KEYS.DOWNLOAD_ENABLED); const blacklistEnabled = getConfig(CONFIG_KEYS.BLACKLIST_ENABLED); const autoReconnect = getConfig(CONFIG_KEYS.AUTO_RECONNECT); const customIconTitle = getConfig(CONFIG_KEYS.CUSTOM_ICON_TITLE); panel.innerHTML = ` <div style="padding: 32px;"> <h3 style="margin: 0 0 32px 0; font-size: 28px; font-weight: 300; text-align: center;">脚本设置</h3> <!-- 自定义图标和标题开关 --> <div style="margin: 24px 0; display: flex; justify-content: space-between; align-items: center; padding: 16px 0;"> <div> <div style="font-size: 18px; font-weight: 500; margin-bottom: 4px;">修改标题与图标</div> <div style="color: #888; font-size: 14px;">DDDD</div> </div> <div id="iconTitleToggle" style=" width: 60px; height: 32px; border-radius: 16px; cursor: pointer; position: relative; background: ${customIconTitle ? '#E31937' : '#333'}; transition: all 0.3s ease; "> <div style=" width: 28px; height: 28px; border-radius: 50%; background: white; position: absolute; top: 2px; left: ${customIconTitle ? '30px' : '2px'}; transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0,0,0,0.3); "></div> </div> </div> <!-- 自动重新连接开关 --> <div style="margin: 24px 0; display: flex; justify-content: space-between; align-items: center; padding: 16px 0;"> <div> <div style="font-size: 18px; font-weight: 500; margin-bottom: 4px;">自动重新连接(Beta)</div> <div style="color: #888; font-size: 14px;">连接断开时自动点击重新连接</div> </div> <div id="reconnectToggle" style=" width: 60px; height: 32px; border-radius: 16px; cursor: pointer; position: relative; background: ${autoReconnect ? '#E31937' : '#333'}; transition: all 0.3s ease; "> <div style=" width: 28px; height: 28px; border-radius: 50%; background: white; position: absolute; top: 2px; left: ${autoReconnect ? '30px' : '2px'}; transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0,0,0,0.3); "></div> </div> </div> <!-- 下载功能开关 --> <div style="margin: 24px 0; display: flex; justify-content: space-between; align-items: center; padding: 16px 0;"> <div> <div style="font-size: 18px; font-weight: 500; margin-bottom: 4px;">启用文件下载功能</div> <div style="color: #888; font-size: 14px;">自动检测并下载聊天中的文件</div> </div> <div id="downloadToggle" style=" width: 60px; height: 32px; border-radius: 16px; cursor: pointer; position: relative; background: ${downloadEnabled ? '#E31937' : '#333'}; transition: all 0.3s ease; "> <div style=" width: 28px; height: 28px; border-radius: 50%; background: white; position: absolute; top: 2px; left: ${downloadEnabled ? '30px' : '2px'}; transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0,0,0,0.3); "></div> </div> </div> <!-- 黑名单开关 --> <div style="margin: 24px 0; display: flex; justify-content: space-between; align-items: center; padding: 16px 0;"> <div> <div style="font-size: 18px; font-weight: 500; margin-bottom: 4px;">启用黑名单过滤</div> <div style="color: #888; font-size: 14px;">过滤指定类型的文件</div> </div> <div id="blacklistToggle" style=" width: 60px; height: 32px; border-radius: 16px; cursor: pointer; position: relative; background: ${blacklistEnabled ? '#E31937' : '#333'}; transition: all 0.3s ease; "> <div style=" width: 28px; height: 28px; border-radius: 50%; background: white; position: absolute; top: 2px; left: ${blacklistEnabled ? '30px' : '2px'}; transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0,0,0,0.3); "></div> </div> </div> <!-- 黑名单输入 --> <div style="margin: 24px 0;"> <label style="font-size: 18px; font-weight: 500; display: block; margin-bottom: 12px;">文件扩展名黑名单</label> <input type="text" id="fileBlacklist" value="${getConfig(CONFIG_KEYS.FILE_BLACKLIST)}" style=" width: 100%; padding: 16px; border: 1px solid #333; border-radius: 8px; background: #1a1a1a; color: white; font-size: 16px; box-sizing: border-box; transition: border-color 0.3s ease; " placeholder="例如:.js,.zip,.exe,.bat"> <small style="color: #888; font-size: 12px; margin-top: 8px; display: block;">用逗号分隔多个扩展名</small> </div> <!-- 手机号输入 --> <div style="margin: 24px 0;"> <label style="font-size: 18px; font-weight: 500; display: block; margin-bottom: 12px;">手机号码</label> <input type="tel" id="phoneNumber" value="${getConfig(CONFIG_KEYS.PHONE_NUMBER)}" style=" width: 100%; padding: 16px; border: 1px solid #333; border-radius: 8px; background: #1a1a1a; color: white; font-size: 16px; box-sizing: border-box; transition: border-color 0.3s ease; " placeholder="请输入手机号码"> <small style="color: #888; font-size: 12px; margin-top: 8px; display: block;">用于自动登录功能</small> </div> <!-- 按钮区域 --> <div style="margin-top: 40px; display: flex; gap: 16px; justify-content: flex-end;"> <button id="cancelSettings" style=" padding: 12px 32px; border: 1px solid #333; border-radius: 8px; background: transparent; color: #888; font-size: 16px; cursor: pointer; transition: all 0.3s ease; ">取消</button> <button id="saveSettings" style=" padding: 12px 32px; border: none; border-radius: 8px; background: #E31937; color: white; font-size: 16px; cursor: pointer; transition: all 0.3s ease; font-weight: 500; ">保存设置</button> </div> </div> `; document.body.appendChild(panel); // 切换按钮状态 let downloadState = downloadEnabled; let blacklistState = blacklistEnabled; let reconnectState = autoReconnect; let iconTitleState = customIconTitle; // 自定义图标标题切换 const iconTitleToggle = panel.querySelector('#iconTitleToggle'); iconTitleToggle.onclick = () => { iconTitleState = !iconTitleState; const toggle = iconTitleToggle.querySelector('div'); iconTitleToggle.style.background = iconTitleState ? '#E31937' : '#333'; toggle.style.left = iconTitleState ? '30px' : '2px'; }; // 自动重新连接切换 const reconnectToggle = panel.querySelector('#reconnectToggle'); reconnectToggle.onclick = () => { reconnectState = !reconnectState; const toggle = reconnectToggle.querySelector('div'); reconnectToggle.style.background = reconnectState ? '#E31937' : '#333'; toggle.style.left = reconnectState ? '30px' : '2px'; }; // 下载功能切换 const downloadToggle = panel.querySelector('#downloadToggle'); downloadToggle.onclick = () => { downloadState = !downloadState; const toggle = downloadToggle.querySelector('div'); downloadToggle.style.background = downloadState ? '#E31937' : '#333'; toggle.style.left = downloadState ? '30px' : '2px'; }; // 黑名单切换 const blacklistToggle = panel.querySelector('#blacklistToggle'); blacklistToggle.onclick = () => { blacklistState = !blacklistState; const toggle = blacklistToggle.querySelector('div'); blacklistToggle.style.background = blacklistState ? '#E31937' : '#333'; toggle.style.left = blacklistState ? '30px' : '2px'; }; // 输入框焦点效果 const fileBlacklistInput = panel.querySelector('#fileBlacklist'); fileBlacklistInput.onfocus = () => { fileBlacklistInput.style.borderColor = '#E31937'; }; fileBlacklistInput.onblur = () => { fileBlacklistInput.style.borderColor = '#333'; }; // 按钮悬停效果 const saveBtn = panel.querySelector('#saveSettings'); const cancelBtn = panel.querySelector('#cancelSettings'); saveBtn.onmouseenter = () => { saveBtn.style.background = '#ff1f47'; saveBtn.style.transform = 'translateY(-2px)'; }; saveBtn.onmouseleave = () => { saveBtn.style.background = '#E31937'; saveBtn.style.transform = 'translateY(0)'; }; cancelBtn.onmouseenter = () => { cancelBtn.style.borderColor = '#666'; cancelBtn.style.color = '#fff'; }; cancelBtn.onmouseleave = () => { cancelBtn.style.borderColor = '#333'; cancelBtn.style.color = '#888'; }; // 保存设置 saveBtn.onclick = () => { setConfig(CONFIG_KEYS.DOWNLOAD_ENABLED, downloadState); setConfig(CONFIG_KEYS.BLACKLIST_ENABLED, blacklistState); setConfig(CONFIG_KEYS.FILE_BLACKLIST, fileBlacklistInput.value); setConfig(CONFIG_KEYS.PHONE_NUMBER, document.querySelector('#phoneNumber').value); setConfig(CONFIG_KEYS.AUTO_RECONNECT, reconnectState); setConfig(CONFIG_KEYS.CUSTOM_ICON_TITLE, iconTitleState); document.body.removeChild(panel); // 显示保存成功提示并刷新页面 const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 10001; background: #E31937; color: white; padding: 16px 24px; border-radius: 8px; font-size: 16px; font-weight: 500; box-shadow: 0 4px 20px rgba(227, 25, 55, 0.3); animation: slideIn 0.3s ease; `; notification.innerHTML = '✅ 设置已保存,页面即将刷新...'; // 添加动画样式 const style = document.createElement('style'); style.textContent = ` @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } `; document.head.appendChild(style); document.body.appendChild(notification); // 2秒后刷新页面 setTimeout(() => { window.location.reload(); }, 2000); }; // 取消设置 cancelBtn.onclick = () => { document.body.removeChild(panel); }; } // 注册菜单命令 GM_registerMenuCommand('打开设置', createSettingsPanel); // 检查当前路由是否为登录页 function isLoginPage() { return window.location.href.includes('/chat/#/login'); } // 检查当前路由是否为聊天页 function isChatPage() { return window.location.href.includes('/chat/#/chat'); } // ==================== HTTP 请求监听 ==================== // 检查文件是否在黑名单中 function isFileBlacklisted(fileName) { if (!getConfig(CONFIG_KEYS.BLACKLIST_ENABLED)) return false; const blacklist = getConfig(CONFIG_KEYS.FILE_BLACKLIST).split(',').map(ext => ext.trim().toLowerCase()); const fileExt = fileName.toLowerCase().substring(fileName.lastIndexOf('.')); return blacklist.includes(fileExt); } // 触发下载按钮点击 function triggerDownload() { // 检查下载节流 const currentTime = Date.now(); if (currentTime - lastDownloadTime < DOWNLOAD_THROTTLE_INTERVAL) { console.log('⏱️ 下载节流中,跳过本次触发 (距离上次下载', Math.round((currentTime - lastDownloadTime) / 1000), '秒)'); return false; } try { // 多种选择器尝试查找下载图标 const selectors = [ 'svg.svg-icon.link use[href="#icon-下载"]', 'svg[aria-hidden="true"].svg-icon.link use[href="#icon-下载"]', 'use[href="#icon-下载"]', 'svg.svg-icon.link', '.svg-icon.link' ]; let downloadIcons = []; // 尝试不同的选择器 for (let selector of selectors) { downloadIcons = document.querySelectorAll(selector); if (downloadIcons.length > 0) { // console.log(`✅ 找到 ${downloadIcons.length} 个下载图标,使用选择器: ${selector}`); break; } } if (downloadIcons.length > 0) { // 获取最后一个(最新的)下载图标 const lastDownloadIcon = downloadIcons[downloadIcons.length - 1]; // 调试信息:输出元素结构 console.warn('🔍 找到的下载图标元素:', { tagName: lastDownloadIcon.tagName, className: lastDownloadIcon.className, outerHTML: lastDownloadIcon.outerHTML.substring(0, 200), parentElement: lastDownloadIcon.parentElement ? lastDownloadIcon.parentElement.outerHTML.substring(0, 200) : 'null' }); // 尝试不同的点击目标 let clickTarget = null; if (lastDownloadIcon.tagName === 'use') { // 如果是 use 元素,找到父级 svg clickTarget = lastDownloadIcon.closest('svg'); } else if (lastDownloadIcon.tagName === 'svg') { // 如果直接是 svg 元素 clickTarget = lastDownloadIcon; } else { // 其他情况,直接使用该元素 clickTarget = lastDownloadIcon; } console.warn('🎯 选择的点击目标:', { tagName: clickTarget ? clickTarget.tagName : 'null', className: clickTarget ? clickTarget.className : 'null', outerHTML: clickTarget ? clickTarget.outerHTML.substring(0, 200) : 'null' }); if (clickTarget) { // 使用最有效的点击方式 try { // 方式1: 直接点击 clickTarget.click(); lastDownloadTime = Date.now(); // 更新最后下载时间 console.log('✅ 自动触发文件下载 (直接点击)'); return true; } catch (e) { try { // 方式2: 简化的事件分发 (已验证有效) const clickEvent = new Event('click', { bubbles: true }); clickTarget.dispatchEvent(clickEvent); lastDownloadTime = Date.now(); // 更新最后下载时间 console.log('✅ 自动触发文件下载 (事件分发)'); return true; } catch (e2) { console.error('❌ 下载触发失败:', e2); } } } } else { console.warn('⚠️ 未找到下载图标,可能页面还未完全加载'); // 尝试查找所有可能的下载相关元素 const allSvgs = document.querySelectorAll('svg'); console.log(`🔍 页面中共有 ${allSvgs.length} 个 SVG 元素`); // 输出一些调试信息 allSvgs.forEach((svg, index) => { if (svg.classList.contains('svg-icon') || svg.classList.contains('link')) { console.log(`SVG ${index}:`, svg.outerHTML.substring(0, 100)); } }); } } catch (error) { console.error('❌ 触发下载失败:', error); } return false; } // 监听DOM变化,等待新的下载按钮出现 function watchForNewDownloadButton() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { // 检查新增的节点中是否有下载按钮 mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { // 在新增的节点中查找下载图标 const downloadIcon = node.querySelector && node.querySelector('svg.svg-icon.link use[href="#icon-下载"]'); if (downloadIcon) { console.log('🎯 检测到新的下载按钮,尝试点击'); setTimeout(() => { if (triggerDownload()) { observer.disconnect(); // 成功后停止监听 } }, 100); } } }); } }); }); // 监听聊天区域的变化 const chatContainer = document.querySelector('.chat-content') || document.querySelector('.message-list') || document.querySelector('.chat-messages') || document.body; if (chatContainer) { observer.observe(chatContainer, { childList: true, subtree: true }); // 5秒后停止监听,避免无限监听 setTimeout(() => { observer.disconnect(); console.warn('⏰ DOM监听超时,停止监听新下载按钮'); }, 5000); } } // 拦截 XMLHttpRequest function interceptXHR() { const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url, ...args) { this._url = url; return originalOpen.apply(this, [method, url, ...args]); }; XMLHttpRequest.prototype.send = function(data) { const xhr = this; // 监听请求载荷(发送的数据) if (xhr._url) { // 检查是否是上传请求 if (xhr._url.includes('file/upload')) { setUploadingState(true); } // 检查是否是 add/record 请求 else if (xhr._url.includes('add/record') && data) { try { let requestData; // 尝试解析请求数据 if (typeof data === 'string') { requestData = JSON.parse(data); } else if (data instanceof FormData) { // 如果是 FormData,尝试获取数据 const formDataObj = {}; for (let [key, value] of data.entries()) { formDataObj[key] = value; } requestData = formDataObj; } else { requestData = data; } console.log('🔍 监听到 add/record 请求载荷:', requestData); // 检查是否是文件类型 if (requestData && requestData.chatType === 'file') { console.log('📁 检测到文件消息:', requestData.fileName); // 检查是否启用下载功能且不在上传状态 if (!getConfig(CONFIG_KEYS.DOWNLOAD_ENABLED) || isUploading) { console.log('⚠️ 文件下载功能已禁用或正在上传中'); return originalSend.call(this, data); } // 检查文件是否在黑名单中 if (isFileBlacklisted(requestData.fileName)) { console.log('🚫 文件在黑名单中,跳过下载:', requestData.fileName); return originalSend.call(this, data); } // 延迟触发下载,等待DOM更新 setTimeout(() => { triggerDownload(); }, 1000); // 如果第一次尝试失败,使用 MutationObserver 监听DOM变化 setTimeout(() => { if (!triggerDownload()) { watchForNewDownloadButton(); } }, 2000); } } catch (error) { console.error('❌ 解析请求载荷失败:', error); } } } return originalSend.call(this, data); }; } // 拦截 fetch function interceptFetch() { const originalFetch = window.fetch; window.fetch = function(...args) { const url = args[0]; const options = args[1] || {}; // 检查是否是上传请求 if (url && url.includes && url.includes('file/upload')) { setUploadingState(true); } // 检查是否是 add/record 请求并且有请求体 else if (url && url.includes && url.includes('add/record') && options.body) { try { let requestData; // 尝试解析请求数据 if (typeof options.body === 'string') { requestData = JSON.parse(options.body); } else { requestData = options.body; } console.log('🔍 监听到 fetch add/record 请求载荷:', requestData); // 检查是否是文件类型 if (requestData && requestData.chatType === 'file') { console.log('📁 检测到文件消息:', requestData.fileName); // 检查是否启用下载功能且不在上传状态 if (!getConfig(CONFIG_KEYS.DOWNLOAD_ENABLED) || isUploading) { console.log('⚠️ 文件下载功能已禁用或正在上传中'); return originalFetch.apply(this, args); } // 检查文件是否在黑名单中 if (isFileBlacklisted(requestData.fileName)) { console.log('🚫 文件在黑名单中,跳过下载:', requestData.fileName); return originalFetch.apply(this, args); } // 延迟触发下载,等待DOM更新 setTimeout(() => { triggerDownload(); }, 1000); // 如果第一次尝试失败,使用 MutationObserver 监听DOM变化 setTimeout(() => { if (!triggerDownload()) { watchForNewDownloadButton(); } }, 2000); } } catch (error) { console.error('❌ 解析 fetch 请求载荷失败:', error); } } return originalFetch.apply(this, args); }; } // ==================== 页面图标和标题修改 ==================== // 设置图标 SVG (直接嵌入,无背景,居中显示) const SETTINGS_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>`; // 将 SVG 转换为 Data URL (无背景,透明) const CHROME_SETTINGS_ICON = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(SETTINGS_ICON_SVG)}`; // 修改页面图标和标题 function updatePageIconAndTitle() { // 检查是否启用自定义图标和标题功能 if (!getConfig(CONFIG_KEYS.CUSTOM_ICON_TITLE)) { console.log('⚠️ 自定义图标和标题功能已禁用'); return; } try { // 修改页面标题 document.title = 'Settings'; // 移除所有现有的图标链接 const existingIcons = document.querySelectorAll('link[rel*="icon"]'); existingIcons.forEach(icon => icon.remove()); // 创建新的 favicon (SVG格式,透明背景,居中显示) const favicon = document.createElement('link'); favicon.rel = 'icon'; favicon.type = 'image/svg+xml'; favicon.href = CHROME_SETTINGS_ICON; document.head.appendChild(favicon); // 添加额外的图标类型以确保兼容性 const iconTypes = [ { rel: 'shortcut icon', type: 'image/svg+xml' }, { rel: 'apple-touch-icon', type: 'image/svg+xml' } ]; iconTypes.forEach(iconType => { const newIcon = document.createElement('link'); newIcon.rel = iconType.rel; newIcon.type = iconType.type; newIcon.href = CHROME_SETTINGS_ICON; document.head.appendChild(newIcon); }); console.log('✅ 页面图标和标题已更新为 Settings (使用嵌入的SVG图标,无背景,居中显示)'); } catch (error) { console.error('❌ 更新页面图标和标题失败:', error); } } // ==================== 主要功能初始化 ==================== // 初始化所有功能 function initializeFeatures() { // 修改页面图标和标题 updatePageIconAndTitle(); // 监听页面标题变化 watchTitleChanges(); // 如果在登录页,执行登录逻辑 if (isLoginPage()) { console.warn('当前是登录页面,执行自动登录'); executeAutoLogin(); } // 如果在聊天页或登录页,都启动HTTP监听 if (isChatPage() || isLoginPage()) { console.warn('启动HTTP请求监听'); interceptXHR(); interceptFetch(); } // 监听连接断开弹窗 watchDisconnectModal(); } /** * 等待元素加载完成 * @param {string} selector - CSS选择器 * @param {number} timeout - 超时时间(ms) * @returns {Promise<Element>} */ function waitForElement(selector, timeout = 3000) { return new Promise((resolve, reject) => { const element = document.querySelector(selector); if (element) { return resolve(element); } const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { resolve(element); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); console.error(`等待元素超时: ${selector}`); reject(new Error(`等待元素超时: ${selector}`)); }, timeout); }); } /** * 设置输入框的值并触发事件 * @param {Element} input - 输入框元素 * @param {string} value - 要设置的值 */ function setInputValue(input, value) { input.value = value; input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); } // 主要逻辑 - 短信登录流程 async function executeAutoLogin() { try { const phoneNumber = getConfig(CONFIG_KEYS.PHONE_NUMBER); if (!phoneNumber) { console.warn('未设置手机号,跳过自动登录'); return; } // 1.5. 选择COC域名 try { const cocDomain = document.querySelector('.domain-item span'); if (cocDomain && cocDomain.textContent.includes('COC')) { cocDomain.closest('.domain-item').click(); await new Promise(resolve => setTimeout(resolve, 100)); } else { // 如果没有直接找到,尝试查找所有域名选项 const domainItems = document.querySelectorAll('.domain-item'); for (let item of domainItems) { if (item.textContent.includes('COC')) { item.click(); await new Promise(resolve => setTimeout(resolve, 100)); break; } } } } catch (error) { console.error('域名选择失败:', error); } // 2. 点击短信登录标签 const smsTab = await waitForElement('.ant-tabs-tab'); if (smsTab) { // 查找包含"短信登录"文本的标签 const tabs = document.querySelectorAll('.ant-tabs-tab'); for (let tab of tabs) { if (tab.textContent.includes('短信登录')) { tab.click(); await new Promise(resolve => setTimeout(resolve, 100)); break; } } } // 3. 等待手机号输入框并输入手机号 const phoneInput = await waitForElement('input[name="phone"]'); setInputValue(phoneInput, phoneNumber); await new Promise(resolve => setTimeout(resolve, 100)); // 4. 点击发送验证码按钮 const codeBtn = await waitForElement('.code-btn.mini-font-size'); codeBtn.click(); await new Promise(resolve => setTimeout(resolve, 200)); // 5. 等待验证码输入框并自动填写验证码 const codeInput = await waitForElement('input[name="code"]'); setInputValue(codeInput, '123456'); await new Promise(resolve => setTimeout(resolve, 100)); // 6. 点击登录按钮 const loginBtn = await waitForElement('.ant-btn.ant-btn-primary.login-btn'); loginBtn.click(); } catch (error) { console.error('自动短信登录脚本执行失败:', error); } } // 监听连接断开弹窗 function watchDisconnectModal() { // 检查是否启用自动重新连接 if (!getConfig(CONFIG_KEYS.AUTO_RECONNECT)) { console.log('⚠️ 自动重新连接功能已禁用'); return; } const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { // 检查是否是连接断开弹窗 const modalContent = node.querySelector('.ant-modal-confirm-content'); if (modalContent && modalContent.textContent.includes('连接断开,请刷新重试')) { console.log('🔍 检测到连接断开弹窗,尝试自动重新连接'); // 查找并点击重新连接按钮 const reconnectBtn = node.querySelector('.ant-modal-confirm-btns .ant-btn:not(.ant-btn-primary)'); if (reconnectBtn) { setTimeout(() => { reconnectBtn.click(); console.log('✅ 已点击重新连接按钮'); }, 500); } } } }); } }); }); // 监听整个文档的变化 observer.observe(document.body, { childList: true, subtree: true }); } // 监听页面标题变化,确保始终显示 Settings function watchTitleChanges() { // 检查是否启用自定义图标和标题功能 if (!getConfig(CONFIG_KEYS.CUSTOM_ICON_TITLE)) { return; } // 创建一个 MutationObserver 来监听 title 元素的变化 const titleObserver = new MutationObserver(() => { if (getConfig(CONFIG_KEYS.CUSTOM_ICON_TITLE) && document.title !== 'Settings') { document.title = 'Settings'; console.log('🔄 页面标题已重置为 Settings'); } }); // 监听 head 元素的变化 if (document.head) { titleObserver.observe(document.head, { childList: true, subtree: true, characterData: true }); } // 定期检查标题(备用方案) setInterval(() => { if (getConfig(CONFIG_KEYS.CUSTOM_ICON_TITLE) && document.title !== 'Settings') { document.title = 'Settings'; } }, 2000); } // ==================== 脚本启动 ==================== // 页面加载完成后执行 if (document.readyState === 'complete') { initializeFeatures(); } else { window.addEventListener('load', () => { initializeFeatures(); }); } // 添加延迟执行作为备用方案 setTimeout(() => { initializeFeatures(); }, 1000); })();