您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
核心功能:直播倒计时、自动名片、自动回复、语音提醒与播报、自动关播
// ==UserScript== // @name 阿里国际站直播助手(树洞先生) // @version 1.0 // @description 核心功能:直播倒计时、自动名片、自动回复、语音提醒与播报、自动关播 // @author 树洞先生 // @license MIT // @match https://content.alibaba.com/live/live-detail.htm* // @run-at document-end // @grant none // @namespace https://greasyfork.org/users/1485135 // ==/UserScript== (function () { 'use strict'; // ------------- 持久化工具 ------------- const LS = { get(key, d=null){ try { return JSON.parse(localStorage.getItem(key)); } catch(e){ return localStorage.getItem(key) ?? d; } }, set(key, val){ localStorage.setItem(key, typeof val === 'string' ? val : JSON.stringify(val)); }, del(key){ localStorage.removeItem(key); } }; // keys 与原扩展保持一致 const K = { isRobotOn: 'isRobotOn', startLiveTime: 'startLiveTime', endLiveTime: 'endLiveTime', currentLive_URL: 'currentLive_URL', currentLive_ID: 'currentLive_ID', isAutoSendCard: 'isAutoSendCard', isAutoCloseLive: 'isAutoCloseLive', isNoticeCommit: 'isNoticeCommit', isWelcomeVoice: 'isWelcomeVoice', isAutoComment: 'isAutoComment', isPlayCommentVoice: 'isPlayCommentVoice' }; // ------------- 全局运行态 ------------- let countdownTimer = null; let observeComments = null; let observeInto = null; let isDragging = false; let dragStartX = 0, dragStartY = 0, startLeft = 0, startTop = 0; // ------------- DOM/查询帮助 ------------- function $(sel, root=document){ return root.querySelector(sel); } function $shadowRoot(){ return $("#__qiankun_microapp_wrapper_for_live_interactive_micro_app__")?.shadowRoot || null; } function nowFmt(ts=Date.now()){ const d = new Date(ts); const p = n => (n<10?'0'+n:n); return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`; } async function sleep(ms){ return new Promise(r => setTimeout(r, ms)); } // ------------- 语音与提示音 ------------- function playBeep(){ let a = $('#tm-myAudio'); if(!a){ a = document.createElement('audio'); a.id = 'tm-myAudio'; a.src = 'https://rfq-tuchuang.oss-cn-hangzhou.aliyuncs.com/voice.ogg'; document.body.appendChild(a); } a.play().catch(()=>{}); } function speak(text){ try{ if(!text) return; const u = new SpeechSynthesisUtterance(text); u.rate = 1; u.pitch = 1; u.lang = 'en-US'; // 如需中文可改为 zh-CN window.speechSynthesis.speak(u); }catch(e){} } // ------------- 智能回复(关键词匹配) ------------- let smartReplyRules = [ { keywords: ['price','how much','报价','价格','cost','best price','报价多少'], reply: 'Thanks for your interest. Please tell me your quantity and destination country, I will quote the best price quickly.' }, { keywords: ['moq','minimum','起订量','最小起订','起订'], reply: 'Our MOQ is flexible. What quantity do you plan to order for the first trial?' }, { keywords: ['sample','样品','free sample','样板','打样'], reply: 'Samples are available. Could you share your address and express account, or we can quote the freight for you.' }, { keywords: ['shipping','ship','运费','发货','运输','物流','delivery'], reply: 'We can ship by sea, air or express. Please tell me your destination city to check the shipping cost and time.' }, { keywords: ['lead time','交期','生产时间','delivery time','多久','when'], reply: 'Regular lead time is 7-15 days depending on quantity. What quantity do you need?' }, { keywords: ['custom','定制','logo','oem','odm','尺寸','颜色'], reply: 'OEM/ODM is supported. Please send your logo/file or requirements, we will make a solution and quote soon.' }, { keywords: ['payment','付款','支付','terms','方式','paypal','tt','信用证','lc'], reply: 'Payment terms are flexible (T/T, PayPal, etc.). Which method do you prefer?' }, { keywords: ['catalog','目录','产品册','brochure','catalogue'], reply: 'I can send you our latest catalog. May I have your email or WhatsApp?' }, { keywords: ['warranty','保修','售后','质保'], reply: 'We provide reliable quality and warranty service. What usage scenario do you have? I will recommend suitable models.' } ]; // 加载保存的规则 function loadSmartReplyRules() { const savedRules = LS.get('smartReplyRules'); if (savedRules && Array.isArray(savedRules)) { smartReplyRules = savedRules; } } // 保存规则到本地存储 function saveSmartReplyRules() { LS.set('smartReplyRules', smartReplyRules); } function getSmartReplyByText(commentText){ const text = (commentText || '').toLowerCase(); for(const rule of smartReplyRules){ if(rule.keywords.some(k => text.includes(k))){ return rule.reply; } } // 默认回复 return 'Thanks for your message. Our sales will reply with details shortly. Could you tell me your quantity and destination country?'; } // 导出Excel格式的智能回复模板 function exportSmartReplyTemplate() { // 创建CSV内容(Excel可以打开) let csvContent = "关键词,回复内容\n"; smartReplyRules.forEach(rule => { const keywords = rule.keywords.join(';'); const reply = rule.reply.replace(/"/g, '""'); // 转义双引号 csvContent += `"${keywords}","${reply}"\n`; }); // 创建下载链接 const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', '智能回复模板.csv'); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } // 导入Excel格式的智能回复模板 function importSmartReplyTemplate() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.csv,.xlsx,.xls'; input.onchange = function(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { try { const content = e.target.result; // 简单的CSV解析 const lines = content.split('\n'); const newRules = []; for (let i = 1; i < lines.length; i++) { // 跳过标题行 const line = lines[i].trim(); if (line) { // 简单的CSV解析,处理引号内的逗号 const parts = []; let current = ''; let inQuotes = false; for (let j = 0; j < line.length; j++) { const char = line[j]; if (char === '"') { inQuotes = !inQuotes; } else if (char === ',' && !inQuotes) { parts.push(current.trim()); current = ''; } else { current += char; } } parts.push(current.trim()); if (parts.length >= 2) { const keywords = parts[0].split(';').map(k => k.trim()).filter(k => k); const reply = parts[1].replace(/^"|"$/g, ''); // 移除首尾引号 if (keywords.length > 0 && reply) { newRules.push({ keywords, reply }); } } } } if (newRules.length > 0) { smartReplyRules = newRules; saveSmartReplyRules(); alert(`成功导入 ${newRules.length} 条智能回复规则!`); } else { alert('导入失败:文件格式不正确或没有有效数据'); } } catch (error) { alert('导入失败:' + error.message); } }; reader.readAsText(file); } }; input.click(); } // 显示智能回复模板管理界面 function showSmartReplyManager() { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000000; display: flex; align-items: center; justify-content: center; `; const content = document.createElement('div'); content.style.cssText = ` background: white; border-radius: 8px; padding: 20px; max-width: 800px; max-height: 80vh; overflow-y: auto; position: relative; `; content.innerHTML = ` <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> <h3 style="margin: 0;">智能回复模板管理</h3> <button id="close-modal" style="background: none; border: none; font-size: 20px; cursor: pointer;">×</button> </div> <div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin-bottom: 20px;"> <div style="display: flex; gap: 10px; align-items: center;"> <button id="add-rule" style="background: #007bff; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">+ 添加规则</button> <button id="export-template" style="background: #28a745; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">📤 导出模板</button> <button id="import-template" style="background: #ffc107; color: black; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">📥 导入模板</button> </div> </div> <div style="overflow-x: auto;"> <table style="width: 100%; border-collapse: collapse; border: 1px solid #ddd;"> <thead> <tr style="background: #f8f9fa;"> <th style="border: 1px solid #ddd; padding: 10px; text-align: left;">关键词</th> <th style="border: 1px solid #ddd; padding: 10px; text-align: left;">回复内容</th> <th style="border: 1px solid #ddd; padding: 10px; text-align: center;">操作</th> </tr> </thead> <tbody id="rules-table-body"> ${smartReplyRules.map((rule, index) => ` <tr> <td style="border: 1px solid #ddd; padding: 10px;"> <input type="text" value="${rule.keywords.join(';')}" style="width: 100%; border: 1px solid #ddd; padding: 5px;" onchange="updateRule(${index}, 'keywords', this.value)"> </td> <td style="border: 1px solid #ddd; padding: 10px;"> <textarea style="width: 100%; border: 1px solid #ddd; padding: 5px; height: 60px; resize: vertical;" onchange="updateRule(${index}, 'reply', this.value)">${rule.reply}</textarea> </td> <td style="border: 1px solid #ddd; padding: 10px; text-align: center;"> <button onclick="deleteRule(${index})" style="background: #dc3545; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer;">删除</button> </td> </tr> `).join('')} </tbody> </table> </div> `; modal.appendChild(content); document.body.appendChild(modal); // 绑定事件 document.getElementById('close-modal').onclick = () => { document.body.removeChild(modal); }; document.getElementById('add-rule').onclick = () => { smartReplyRules.push({ keywords: ['新关键词'], reply: '新回复内容' }); saveSmartReplyRules(); showSmartReplyManager(); // 刷新界面 }; document.getElementById('export-template').onclick = exportSmartReplyTemplate; document.getElementById('import-template').onclick = importSmartReplyTemplate; // 添加全局函数 window.updateRule = function(index, field, value) { if (field === 'keywords') { smartReplyRules[index].keywords = value.split(';').map(k => k.trim()).filter(k => k); } else { smartReplyRules[index][field] = value; } saveSmartReplyRules(); }; window.deleteRule = function(index) { if (confirm('确定要删除这条规则吗?')) { smartReplyRules.splice(index, 1); saveSmartReplyRules(); showSmartReplyManager(); // 刷新界面 } }; } // ------------- 面板 UI ------------- function createPanel(){ if($('#bp_start')) return; const div = document.createElement('div'); div.id = 'bp_start'; div.style = 'position:fixed;z-index:999999;left:50%;top:50%;transform:translate(-50%, -50%) translateX(-20%);background:#87CEEB;color:#000;border:2px solid #4682B4;border-radius:8px;font-family:Arial,Helvetica,sans-serif;user-select:none;'; div.innerHTML = ` <div class="bp-start" style="width:300px;padding:12px 12px 8px 12px;cursor:move;position:relative;"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;"> <div class="bp-title" style="font-weight:bold;">国际站直播小助手(树洞先生)</div> <button id="tm-close" style="background:none;border:none;color:#000;font-size:18px;cursor:pointer;padding:0;width:24px;height:24px;line-height:1;">×</button> </div> <div style="display:flex;gap:8px;align-items:center;margin-bottom:6px;"> <button id="tm-start" style="padding:6px 10px;">开始</button> <button id="tm-stop" style="padding:6px 10px;">停止</button> <div style="margin-left:auto;">倒计时: <span id="tm-H">00</span>:<span id="tm-M">00</span>:<span id="tm-S">00</span> </div> </div> <div style="display:flex;gap:8px;align-items:center;margin:6px 0 10px 0;font-size:12px;"> <label style="opacity:.9;">时长(小时):</label> <input id="tm-duration" type="number" step="0.5" min="0.1" value="2" style="width:60px;padding:4px 6px;background:#E0F6FF;color:#000;border:1px solid #4682B4;border-radius:4px;"> <span style="opacity:.65;">开始前设置</span> </div> <div style="display:grid;grid-template-columns:1fr auto;gap:6px 8px;align-items:center;font-size:12px;padding:8px;background:#B0E0E6;border-radius:6px;"> <div>结束后关闭直播</div> <label><input id="tm-isAutoCloseLive" type="checkbox"></label> <div>评论语音提醒</div> <label><input id="tm-isNoticeCommit" type="checkbox"></label> <div>进场自动发送名片</div> <label><input id="tm-isAutoSendCard" type="checkbox"></label> <div>进场语音欢迎</div> <label><input id="tm-isWelcomeVoice" type="checkbox"></label> <div>评论自动文本回复</div> <label><input id="tm-isAutoComment" type="checkbox"></label> <div>评论语音播报回复语</div> <label><input id="tm-isPlayCommentVoice" type="checkbox"></label> </div> <div style="text-align:center;margin-top:10px;"> <button id="tm-manage-templates" style="background:#4682B4;color:white;border:none;padding:8px 16px;border-radius:4px;cursor:pointer;font-size:12px;">📝 管理智能回复模板</button> </div> <div style="font-size:12px;margin-top:8px;opacity:.8;"> 开始时间:<span id="tm-startTime">-</span><br> 结束时间:<span id="tm-endTime">-</span> </div> </div> `; document.body.appendChild(div); // 拖拽 const header = div.querySelector('.bp-start'); header.addEventListener('mousedown', (e)=>{ isDragging = true; dragStartX = e.clientX; dragStartY = e.clientY; startLeft = div.offsetLeft; startTop = div.offsetTop; }); document.addEventListener('mousemove', (e)=>{ if(!isDragging) return; const nx = Math.max(0, Math.min(startLeft + (e.clientX-dragStartX), document.body.clientWidth - header.clientWidth)); const ny = Math.max(0, Math.min(startTop + (e.clientY-dragStartY), document.body.clientHeight - header.clientHeight)); div.style.left = nx+'px'; div.style.top = ny+'px'; }); document.addEventListener('mouseup', ()=>{ isDragging = false; }); // 恢复设置 $('#tm-isAutoCloseLive').checked = (LS.get(K.isAutoCloseLive, 'off') === 'on'); $('#tm-isNoticeCommit').checked = (LS.get(K.isNoticeCommit, 'off') === 'on'); $('#tm-isAutoSendCard').checked = (LS.get(K.isAutoSendCard, 'off') === 'on'); $('#tm-isWelcomeVoice').checked = (LS.get(K.isWelcomeVoice, 'off') === 'on'); $('#tm-isAutoComment').checked = (LS.get(K.isAutoComment, 'off') === 'on'); $('#tm-isPlayCommentVoice').checked = (LS.get(K.isPlayCommentVoice, 'off') === 'on'); // 恢复时长 const savedDur = Number(LS.get('tm_duration_hours')) || 2; const durInput = $('#tm-duration'); if(durInput){ durInput.value = String(savedDur); } // 监听设置变化 $('#tm-isAutoCloseLive').addEventListener('change', e => LS.set(K.isAutoCloseLive, e.target.checked?'on':'off')); $('#tm-isNoticeCommit').addEventListener('change', e => LS.set(K.isNoticeCommit, e.target.checked?'on':'off')); $('#tm-isAutoSendCard').addEventListener('change', e => LS.set(K.isAutoSendCard, e.target.checked?'on':'off')); $('#tm-isWelcomeVoice').addEventListener('change', e => LS.set(K.isWelcomeVoice, e.target.checked?'on':'off')); $('#tm-isAutoComment').addEventListener('change', e => LS.set(K.isAutoComment, e.target.checked?'on':'off')); $('#tm-isPlayCommentVoice').addEventListener('change', e => LS.set(K.isPlayCommentVoice, e.target.checked?'on':'off')); $('#tm-start').addEventListener('click', onStart); $('#tm-stop').addEventListener('click', ()=> stopRobot(true)); $('#tm-close').addEventListener('click', ()=> { div.style.display = 'none'; }); $('#tm-manage-templates').addEventListener('click', showSmartReplyManager); $('#tm-duration') && $('#tm-duration').addEventListener('change', (e)=>{ const v = Math.max(0.1, Number(e.target.value)||2); e.target.value = String(v); LS.set('tm_duration_hours', v); }); refreshPanel(); } // 切换按钮(点击后创建或显示/隐藏面板) function createToggleBtn(){ if($('#tm-toggle')) return; // 智能查找目标元素 - 多种选择器策略 let targetElement = null; const selectors = [ '.header--title--teC2aTV', // 原始选择器 '.header--titleBox--Mt15Y50', // 新的父容器选择器 '[class*="header--title"]', // 包含header--title的类 '[class*="title"]', // 包含title的类 '.header--headerInfo--y954c6x', // 头部信息区域 '[data-spm-anchor-id*="live"]', // 包含live的spm元素 '.header--header--msGoWRD', // 头部区域 '[class*="header"]' // 包含header的类 ]; for (const selector of selectors) { targetElement = document.querySelector(selector); if (targetElement) { console.log('找到目标元素:', selector, targetElement); break; } } // 如果还是找不到,尝试查找包含特定文本的元素 if (!targetElement) { const allElements = document.querySelectorAll('*'); for (const element of allElements) { if (element.textContent && ( element.textContent.includes('live room') || element.textContent.includes('直播') || element.textContent.includes('Live') || element.textContent.includes('reception') || element.textContent.includes('In real-time reception') )) { targetElement = element; console.log('通过文本内容找到目标元素:', element); break; } } } // 如果还是找不到,直接放在页面正中间 if(!targetElement) { console.log('未找到目标元素,使用页面正中间定位'); const btn = document.createElement('button'); btn.id = 'tm-toggle'; btn.textContent = '🎥 阿里直播助手'; btn.title = '打开/隐藏 国际站直播小助手'; btn.style = 'position:fixed;z-index:999998;left:50%;top:50%;transform:translate(-50%, -50%);padding:12px 20px;background:#87CEEB;color:#000;border:2px solid #4682B4;border-radius:10px;cursor:pointer;font-size:16px;font-weight:bold;box-shadow:0 6px 20px rgba(0,0,0,0.2);transition:all 0.3s ease;'; // 添加悬停效果 btn.addEventListener('mouseenter', () => { btn.style.background = '#4682B4'; btn.style.color = '#fff'; btn.style.transform = 'translate(-50%, -50%) scale(1.05)'; btn.style.boxShadow = '0 8px 25px rgba(0,0,0,0.3)'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#87CEEB'; btn.style.color = '#000'; btn.style.transform = 'translate(-50%, -50%) scale(1)'; btn.style.boxShadow = '0 6px 20px rgba(0,0,0,0.2)'; }); btn.addEventListener('click', ()=>{ const panel = $('#bp_start'); if(panel){ panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; } else { createPanel(); } }); document.body.appendChild(btn); return; } const btn = document.createElement('button'); btn.id = 'tm-toggle'; btn.textContent = '阿里直播助手'; btn.title = '打开/隐藏 国际站直播小助手'; btn.style = 'margin-left:10px;padding:6px 10px;background:#87CEEB;color:#000;border:2px solid #4682B4;border-radius:6px;cursor:pointer;display:inline-block;vertical-align:middle;'; btn.addEventListener('click', ()=>{ const panel = $('#bp_start'); if(panel){ panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; } else { createPanel(); } }); // 将按钮插入到目标元素后面 targetElement.parentNode.insertBefore(btn, targetElement.nextSibling); } function refreshPanel(){ $('#tm-startTime') && ($('#tm-startTime').textContent = LS.get(K.startLiveTime) || '-'); $('#tm-endTime') && ($('#tm-endTime').textContent = LS.get(K.endLiveTime) || '-'); $('#tm-url') && ($('#tm-url').textContent = LS.get(K.currentLive_URL) || location.href); } // ------------- 业务:开始/停止/倒计时 ------------- function onStart(){ // 若页面结构未就绪,也允许先开始倒计时;观察器等待就绪后再挂载 const flag = $("#container .header--iconShowItem--B7GxSsm"); const qk = $shadowRoot(); const pageReady = !!(flag && flag.innerText === '直播中' && qk); if(!pageReady){ console.warn('页面结构未完全就绪,将先启动倒计时,稍后自动挂载监听'); } const hours = Number($('#tm-duration')?.value) || Number(LS.get('tm_duration_hours')) || 2; // 时长(小时) const now = Date.now(); const end = now + hours*3600*1000; LS.set(K.isRobotOn, true); LS.set(K.startLiveTime, nowFmt(now)); LS.set(K.endLiveTime, nowFmt(end)); LS.set(K.currentLive_URL, location.href); LS.set(K.currentLive_ID, null); refreshPanel(); startCountdown(end); if(pageReady){ startObservers(); } else { waitAndMountObservers(); } alert('小助手已启动'); } // 页面未就绪时,轮询等待后挂载监听 function waitAndMountObservers(){ let tries = 0; const t = setInterval(()=>{ tries++; const flag = $("#container .header--iconShowItem--B7GxSsm"); const qk = $shadowRoot(); if(flag && flag.innerText === '直播中' && qk){ clearInterval(t); startObservers(); } if(tries > 60){ // 最长约3分钟 clearInterval(t); } }, 3000); } function stopRobot(manual=false){ clearCountdown(); stopObservers(); LS.set(K.isRobotOn, false); if(manual){ location.reload(); }else{ // 到点自动关播 if(LS.get(K.isAutoCloseLive, 'off') === 'on'){ autoCloseLive(); } setTimeout(()=>location.reload(), 1000); } } function startCountdown(endTs){ clearCountdown(); countdownTimer = setInterval(()=>{ const rest = Math.max(0, Math.floor((endTs - Date.now())/1000)); setTimerView(rest); if(rest === 0){ stopRobot(false); } }, 1000); } function clearCountdown(){ if(countdownTimer){ clearInterval(countdownTimer); countdownTimer = null; } setTimerView(0); } function setTimerView(sec){ const H = Math.floor(sec/3600), M = Math.floor((sec%3600)/60), S = Math.floor(sec%60); const p = n => n<10?('0'+n):''+n; $('#tm-H') && ($('#tm-H').textContent = p(H)); $('#tm-M') && ($('#tm-M').textContent = p(M)); $('#tm-S') && ($('#tm-S').textContent = p(S)); } // ------------- 业务:监听与动作 ------------- function startObservers(){ const root = $shadowRoot(); if(!root) return; // 评论数节点,用于等待首次>0再挂评论列表 const commentsCountEle = $("#container div.video-container--data--GCIOjpa > div:nth-child(4) > div.video-container--dataItemValue--JqK3lMx"); if(commentsCountEle && Number(commentsCountEle.innerText) > 0){ watchComments(root); }else{ const tar = commentsCountEle; if(tar){ const ob = new MutationObserver(()=>{ dealFirstComments(root); ob.disconnect(); watchComments(root); }); ob.observe(tar, { characterData:true, attributes:true, childList:true, subtree:true }); } } // 进场数节点,用于等待首次>0再挂进场列表 const lookerCountEle = $("#container div.video-container--data--GCIOjpa > div:nth-child(2) > div.video-container--dataItemValue--JqK3lMx"); if(lookerCountEle && Number(lookerCountEle.innerText) > 0){ watchInto(root); }else{ const ob2 = new MutationObserver(()=>{ dealFirstInto(root); ob2.disconnect(); watchInto(root); }); lookerCountEle && ob2.observe(lookerCountEle, { characterData:true, attributes:true, childList:true, subtree:true }); } } function stopObservers(){ if(observeComments){ observeComments.disconnect(); observeComments = null; } if(observeInto){ observeInto.disconnect(); observeInto = null; } } function watchComments(root){ const list = root.querySelector(".comment-list-box .comment-content>div"); if(!list) return; observeComments = new MutationObserver(async (muts)=>{ await sleep(300); const changed = muts[0]?.target || list; handleNewComment(changed); }); observeComments.observe(list, { childList:true }); } function watchInto(root){ // 切换到进场tab const tab2 = root.querySelector("#parentContainer .interactive-header.pc .interactive-tab-list > div:nth-child(2)"); tab2 && tab2.click(); const box = root.querySelector(".interactive-approach-container .approach-page .approach-box.pc"); if(!box) return; observeInto = new MutationObserver(async (muts)=>{ await sleep(300); const changed = muts[0]?.target || box; handleNewInto(changed); }); observeInto.observe(box, { childList:true }); } function dealFirstComments(root){ const box = root.querySelector(".comment-list-box .comment-content>div"); box && handleNewComment(box); } function dealFirstInto(root){ const box = root.querySelector(".interactive-approach-container .approach-page .approach-box.pc"); box && handleNewInto(box); } // 新评论处理:自动回复 + 提示音 + 回复语音 async function handleNewComment(container){ if(LS.get(K.isNoticeCommit, 'off') === 'on'){ playBeep(); } if(LS.get(K.isAutoComment, 'off') !== 'on') return; const last = container?.childNodes?.[0]; if(!last) return; const text = last.innerText?.trim() || ''; // 定位回复按钮(影子DOM中的结构) let replyBtn = last.querySelector("div.sp7juUVC7TtSm4K0acmH > div > span:last-child"); if(!replyBtn) return; replyBtn.click(); await sleep(200); // 弹窗输入并确认 const root = $shadowRoot(); let textarea = root?.querySelector(".next-input.next-input-textarea>textarea") || $(".next-input.next-input-textarea>textarea"); let confirmBtn = root?.querySelector(".next-btn.next-medium.next-btn-normal") || $(".next-btn.next-medium.next-btn-primary.next-dialog-btn"); const replyContent = getSmartReplyByText(text); if(textarea){ textarea.value = replyContent; textarea.dispatchEvent(new Event('input', { bubbles:true, cancelable:true })); } await sleep(200); confirmBtn && confirmBtn.click(); if(LS.get(K.isPlayCommentVoice, 'off') === 'on'){ speak(replyContent); } } // 新进场处理:发送名片 + 欢迎语音 function handleNewInto(container){ const last = container?.childNodes?.[0]; if(!last) return; // 自动名片 if(LS.get(K.isAutoSendCard, 'off') === 'on'){ let btn = last.querySelector(".icon-namecard"); if(!btn){ const root = $shadowRoot(); btn = root?.querySelector("#parentContainer .interactive-approach-tab-panel.active .iconfont.icon-namecard"); } btn && btn.click(); } // 欢迎语 if(LS.get(K.isWelcomeVoice, 'off') === 'on'){ const raw = last.innerText?.trim() || ''; const username = (raw.split('\n')[0] || 'Friend'); speak(`Welcome, ${username}`); } } // ------------- 自动关播 ------------- async function autoCloseLive(){ // 顶部“关闭直播”按钮 const closeBtn = $("#container div.header--header--msGoWRD > div.header--headerInfo--y954c6x > div > button.next-btn.next-medium.next-btn-primary > span"); if(closeBtn){ closeBtn.click(); await sleep(600); // 确认按钮 const okBtn = $("body > div.next-overlay-wrapper.opened > div.next-dialog.next-closeable.next-overlay-inner.next-dialog-quick > div.next-dialog-footer.next-align-right > button.next-btn.next-medium.next-btn-primary.next-dialog-btn > span"); okBtn && okBtn.click(); } } // ------------- 启动入口 ------------- function init(){ createToggleBtn(); loadSmartReplyRules(); // 加载智能回复规则 // 若上次处于运行态,自动恢复 if(LS.get(K.isRobotOn, false)){ const endStr = LS.get(K.endLiveTime); if(endStr){ const endTs = new Date(endStr.replace(/-/g,'/')).getTime(); if(endTs > Date.now()){ startCountdown(endTs); startObservers(); } } } } // 等待大容器就绪后初始化 window.addEventListener('load', ()=>{ try{ // 页面结构加载稍慢,延迟一点 setTimeout(init, 1200); }catch(e){} }); // 监听页面变化,自动重新定位按钮 let lastButtonPosition = null; const observer = new MutationObserver((mutations) => { // 检查是否有按钮位置变化或页面结构变化 const currentButton = $('#tm-toggle'); if (currentButton) { const rect = currentButton.getBoundingClientRect(); const currentPosition = { left: rect.left, top: rect.top }; if (lastButtonPosition && ( Math.abs(currentPosition.left - lastButtonPosition.left) > 10 || Math.abs(currentPosition.top - lastButtonPosition.top) > 10 )) { console.log('检测到按钮位置变化,重新定位'); repositionButton(); } lastButtonPosition = currentPosition; } }); // 重新定位按钮的函数 function repositionButton() { const existingButton = $('#tm-toggle'); if (existingButton) { existingButton.remove(); } // 延迟一点再创建,确保页面结构稳定 setTimeout(() => { createToggleBtn(); }, 500); } // 启动观察器 setTimeout(() => { observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] }); }, 2000); })();