// 内容脚本 - 在小红书页面上添加收藏功能 class XHSNoteSaver { constructor() { this.currentNoteData = null; this.saveButton = null; this.isAutoClicking = false; // 添加停止标志 this.processingNotes = new Set(); // 跟踪正在处理的笔记 this.cleanupTimers = new Set(); // 跟踪清理定时器 this.maxConcurrentNotes = 3; // 限制并发处理的笔记数量 this.maxRetries = 3; // 最大重试次数 this.mutationObserver = null; // 跟踪MutationObserver this.detectContentDebounceTimer = null; // 防抖定时器 this.lastDetectTime = 0; // 上次检测时间 this.detectInterval = 2000; // 检测间隔(毫秒) this.memoryCheckInterval = null; // 内存检查定时器 this.lastMemoryCheck = 0; // 上次内存检查时间 this.memoryThreshold = 50 * 1024 * 1024; // 内存阈值(50MB) // 评论模式配置 this.commentMode = 'custom'; // 'custom' / 'text' / 'ai' this.customComments = []; // 固定评论池 this.currentCommentIndex = 0; // 当前评论索引 this.init(); } init() { this.createSaveButton(); this.detectNoteContent(); this.setupMessageListener(); // 监听页面变化 - 使用防抖和间隔控制 this.mutationObserver = new MutationObserver((mutations) => { // 防抖处理,避免频繁调用 if (this.detectContentDebounceTimer) { clearTimeout(this.detectContentDebounceTimer); } // 检查时间间隔,避免过于频繁的检测 const now = Date.now(); if (now - this.lastDetectTime < this.detectInterval) { return; } this.detectContentDebounceTimer = setTimeout(() => { this.lastDetectTime = Date.now(); this.detectNoteContent(); }, 500); // 500ms防抖 // 添加到清理定时器集合 this.cleanupTimers.add(this.detectContentDebounceTimer); }); this.mutationObserver.observe(document.body, { childList: true, subtree: true }); // 监听页面卸载,确保清理资源 window.addEventListener('beforeunload', () => { this.cleanup(); }); // 监听页面隐藏,减少资源消耗 document.addEventListener('visibilitychange', this.handleVisibilityChange); // 启动内存监控 this.startMemoryMonitoring(); // 启动内存泄漏防护 this.setupMemoryLeakProtection(); // 初始化自定义评论 this.initializeCustomComments(); // 恢复评论模式设置 this.restoreCommentMode(); // 侧边栏修改评论模式时立即生效(无需刷新页面) chrome.storage.onChanged.addListener((changes, namespace) => { if (namespace !== 'local' || !changes.commentMode) return; const mode = changes.commentMode.newValue === 'image' ? 'custom' : changes.commentMode.newValue; if (mode && mode !== this.commentMode) { this.commentMode = mode; console.log('评论模式已同步:', mode); this.updateCommentModeButton(); this.showToast(`评论模式已切换为: ${this.getCommentModeLabel(mode)}`); } }); } // 恢复评论模式设置 async restoreCommentMode() { try { const result = await chrome.storage.local.get(['commentMode']); if (result.commentMode) { this.commentMode = result.commentMode === 'image' ? 'custom' : result.commentMode; console.log(`评论模式已恢复: ${this.commentMode}`); } } catch (error) { console.error('恢复评论模式失败:', error); } } // 初始化自定义评论 async initializeCustomComments() { try { const result = await chrome.storage.local.get(['customComments']); this.customComments = result.customComments || []; // 如果没有自定义评论,添加一些默认评论 if (this.customComments.length === 0) { const defaultComments = [ '👍 赞!', '💯 太棒了!', '🔥 很实用!', '👏 学习了!', '💪 继续加油!', '🌟 分享得很棒!', '📚 学到了很多!', '🎯 很精准!', '💡 很有想法!', '❤️ 喜欢!' ]; this.customComments = defaultComments; await chrome.storage.local.set({ customComments: defaultComments }); console.log('已初始化默认自定义评论'); } console.log(`自定义评论已加载,共${this.customComments.length}条`); } catch (error) { console.error('初始化自定义评论失败:', error); } } // 启动内存监控 startMemoryMonitoring() { if (this.memoryCheckInterval) { clearInterval(this.memoryCheckInterval); } this.memoryCheckInterval = setInterval(() => { this.checkMemoryUsage(); }, 30000); // 每30秒检查一次,减少频率 this.cleanupTimers.add(this.memoryCheckInterval); } // 检查内存使用情况 checkMemoryUsage() { try { // 检查内存使用(如果浏览器支持) if (performance.memory) { const usedMemory = performance.memory.usedJSHeapSize; const totalMemory = performance.memory.totalJSHeapSize; console.log(`内存使用: ${(usedMemory / 1024 / 1024).toFixed(2)}MB / ${(totalMemory / 1024 / 1024).toFixed(2)}MB`); // 如果内存使用超过阈值,执行清理 if (usedMemory > this.memoryThreshold) { console.warn('内存使用超过阈值,执行紧急清理'); this.emergencyMemoryCleanup(); } } // 检查DOM节点数量 const domNodeCount = document.querySelectorAll('*').length; if (domNodeCount > 15000) { console.warn(`DOM节点数量过多: ${domNodeCount},执行DOM清理`); this.cleanupDOMNodes(); } } catch (error) { console.error('内存检查失败:', error); } } // 紧急内存清理 emergencyMemoryCleanup() { console.log('执行紧急内存清理...'); // 停止所有操作 this.isAutoClicking = false; // 清理所有定时器 this.cleanupTimers.forEach(timerId => { clearTimeout(timerId); clearInterval(timerId); }); this.cleanupTimers.clear(); // 清理DOM样式和元素 this.cleanupHighlightedElements(); this.cleanupDOMNodes(); // 清理进度条 this.hideProgressBar(); // 强制垃圾回收 if (window.gc) { try { window.gc(); console.log('紧急垃圾回收已执行'); } catch (e) { console.log('紧急垃圾回收失败:', e); } } // 显示清理完成提示 this.showToast('内存清理完成'); console.log('紧急内存清理完成'); } // 清理DOM节点 cleanupDOMNodes() { try { // 清理可能累积的临时元素 const tempElements = document.querySelectorAll('.xhs-temp-element, .xhs-highlight-temp'); tempElements.forEach(el => el.remove()); // 清理可能的内存泄漏 const elementsWithLargeData = document.querySelectorAll('[data-large-data]'); elementsWithLargeData.forEach(el => { delete el.dataset.largeData; }); console.log('DOM节点清理完成'); } catch (error) { console.error('DOM节点清理失败:', error); } } // 清理所有资源 cleanup() { this.isAutoClicking = false; this.processingNotes.clear(); // 清理防抖定时器 if (this.detectContentDebounceTimer) { clearTimeout(this.detectContentDebounceTimer); this.detectContentDebounceTimer = null; } // 清理内存监控定时器 if (this.memoryCheckInterval) { clearInterval(this.memoryCheckInterval); this.memoryCheckInterval = null; } // 断开MutationObserver if (this.mutationObserver) { this.mutationObserver.disconnect(); this.mutationObserver = null; } // 清理所有定时器 this.cleanupTimers.forEach(timerId => { clearTimeout(timerId); clearInterval(timerId); }); this.cleanupTimers.clear(); // 清理DOM样式 this.cleanupHighlightedElements(); // 清理进度条 this.hideProgressBar(); // 清理事件监听器 this.removeEventListeners(); console.log('所有资源已清理'); } // 移除事件监听器 removeEventListeners() { // 移除页面卸载监听器 window.removeEventListener('beforeunload', this.cleanup); // 移除页面隐藏监听器 document.removeEventListener('visibilitychange', this.handleVisibilityChange); } // 处理页面可见性变化 handleVisibilityChange = () => { if (document.hidden && this.isAutoClicking) { console.log('页面隐藏,暂停自动点击'); this.isAutoClicking = false; } } // 清理高亮元素 cleanupHighlightedElements() { const highlightedElements = document.querySelectorAll('[style*="outline: 3px solid #4caf50"]'); highlightedElements.forEach(el => { el.style.outline = ''; el.style.outlineOffset = ''; el.style.position = ''; }); // 清理标签 const labels = document.querySelectorAll('[style*="background: #4caf50"][style*="z-index: 10000"]'); labels.forEach(label => label.remove()); } createSaveButton() { if (this.saveButton) { this.saveButton.remove(); } this.saveButton = document.createElement('button'); this.saveButton.className = 'xhs-save-btn'; this.saveButton.textContent = '📝 收藏笔记'; this.saveButton.style.display = 'none'; this.saveButton.addEventListener('click', () => { this.handleSaveNote(); }); document.body.appendChild(this.saveButton); } detectNoteContent() { // 检测是否在笔记详情页 const isNoteDetailPage = window.location.pathname.includes('/explore/'); if (isNoteDetailPage) { this.extractNoteData(); this.showSaveButton(); } else { this.hideSaveButton(); } } extractNoteData() { try { // 提取笔记标题 const titleElement = document.querySelector('.note-detail-title') || document.querySelector('[class*="title"]') || document.querySelector('h1'); // 提取笔记图片 const imageElement = document.querySelector('.note-detail-img img') || document.querySelector('[class*="carousel"] img') || document.querySelector('.swiper-slide img') || document.querySelector('img[src*="xiaohongshu"]'); // 提取作者信息 const authorElement = document.querySelector('.user-name') || document.querySelector('[class*="author"]') || document.querySelector('[class*="username"]'); // 提取笔记内容 const contentElement = document.querySelector('.note-detail-desc') || document.querySelector('[class*="content"]') || document.querySelector('[class*="description"]'); this.currentNoteData = { id: this.extractNoteId(), title: titleElement ? titleElement.textContent.trim() : '无标题', image: imageElement ? imageElement.src : '', author: authorElement ? authorElement.textContent.trim() : '未知作者', content: contentElement ? contentElement.textContent.trim() : '', url: window.location.href, timestamp: Date.now() }; // 检查是否已经收藏 this.checkIfSaved(); } catch (error) { console.error('提取笔记数据失败:', error); } } extractNoteId() { const urlMatch = window.location.pathname.match(/\/explore\/([a-f0-9]+)/); return urlMatch ? urlMatch[1] : window.location.href; } async checkIfSaved() { try { const result = await chrome.storage.local.get(['savedNotes']); const savedNotes = result.savedNotes || []; const isSaved = savedNotes.some(note => note.id === this.currentNoteData.id); this.updateSaveButtonState(isSaved); } catch (error) { console.error('检查收藏状态失败:', error); } } updateSaveButtonState(isSaved) { if (this.saveButton) { if (isSaved) { this.saveButton.textContent = '✅ 已收藏'; this.saveButton.classList.add('saved'); } else { this.saveButton.textContent = '📝 收藏笔记'; this.saveButton.classList.remove('saved'); } } } showSaveButton() { if (this.saveButton) { this.saveButton.style.display = 'block'; } } hideSaveButton() { if (this.saveButton) { this.saveButton.style.display = 'none'; } } async handleSaveNote() { if (!this.currentNoteData) { this.showToast('无法获取笔记信息'); return; } try { // 检查是否已经收藏 const result = await chrome.storage.local.get(['savedNotes', 'tags']); const savedNotes = result.savedNotes || []; const existingIndex = savedNotes.findIndex(note => note.id === this.currentNoteData.id); if (existingIndex >= 0) { // 如果已收藏,则取消收藏 savedNotes.splice(existingIndex, 1); await chrome.storage.local.set({ savedNotes }); this.updateSaveButtonState(false); this.showToast('已取消收藏'); } else { // 如果未收藏,显示标签选择弹窗 this.showTagModal(); } } catch (error) { console.error('保存笔记失败:', error); this.showToast('保存失败,请重试'); } } async showTagModal() { // 获取已有标签 const result = await chrome.storage.local.get(['tags']); const tags = result.tags || []; // 创建弹窗 const modal = document.createElement('div'); modal.className = 'xhs-tag-modal'; modal.innerHTML = `

选择标签

${tags.map(tag => `
${tag.name} (${tag.count || 0})
`).join('')}
`; document.body.appendChild(modal); // 绑定事件 const tagInput = modal.querySelector('.xhs-tag-input'); const tagItems = modal.querySelectorAll('.xhs-tag-item'); const cancelBtn = modal.querySelector('#cancel-save'); const confirmBtn = modal.querySelector('#confirm-save'); let selectedTags = []; // 标签选择 tagItems.forEach(item => { item.addEventListener('click', () => { const tagName = item.dataset.tag; if (item.classList.contains('selected')) { item.classList.remove('selected'); selectedTags = selectedTags.filter(tag => tag !== tagName); } else { item.classList.add('selected'); selectedTags.push(tagName); } }); }); // 新标签输入 tagInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { const newTag = tagInput.value.trim(); if (newTag && !selectedTags.includes(newTag)) { selectedTags.push(newTag); // 添加到标签列表显示 const tagList = modal.querySelector('.xhs-tag-list'); const newTagElement = document.createElement('div'); newTagElement.className = 'xhs-tag-item selected'; newTagElement.dataset.tag = newTag; newTagElement.textContent = newTag; tagList.appendChild(newTagElement); newTagElement.addEventListener('click', () => { newTagElement.classList.remove('selected'); selectedTags = selectedTags.filter(tag => tag !== newTag); newTagElement.remove(); }); tagInput.value = ''; } } }); // 取消按钮 cancelBtn.addEventListener('click', () => { modal.remove(); }); // 确认保存 confirmBtn.addEventListener('click', async () => { await this.saveNoteWithTags(selectedTags); modal.remove(); }); // 点击背景关闭 modal.addEventListener('click', (e) => { if (e.target === modal) { modal.remove(); } }); } async saveNoteWithTags(selectedTags) { try { // 保存笔记 const result = await chrome.storage.local.get(['savedNotes', 'tags']); const savedNotes = result.savedNotes || []; const tags = result.tags || []; // 添加标签到笔记 this.currentNoteData.tags = selectedTags; savedNotes.push(this.currentNoteData); // 更新标签计数 selectedTags.forEach(tagName => { const existingTag = tags.find(tag => tag.name === tagName); if (existingTag) { existingTag.count = (existingTag.count || 0) + 1; } else { tags.push({ name: tagName, count: 1 }); } }); // 保存到存储 await chrome.storage.local.set({ savedNotes, tags }); this.updateSaveButtonState(true); this.showToast(`已收藏 ${selectedTags.length > 0 ? '并添加标签' : ''}`); } catch (error) { console.error('保存笔记失败:', error); this.showToast('保存失败,请重试'); } } showToast(message) { const toast = document.createElement('div'); toast.className = 'xhs-toast'; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.remove(); }, 3000); } setupMessageListener() { console.log('设置消息监听器...'); chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.log('收到消息:', request, '来自:', sender); if (request.action === 'getCurrentNote') { console.log('处理获取当前笔记请求'); sendResponse({ noteData: this.currentNoteData, isNotePage: this.currentNoteData !== null }); } if (request.action === 'autoClickNotes') { console.log('收到自动点击请求,关键词:', request.keyword); // 立即响应,表示消息已收到 sendResponse({ success: true, message: '开始自动点击' }); // 开始自动点击 this.startAutoClickNotes(); } if (request.action === 'testElementDetection') { console.log('收到测试元素检测请求'); // 测试元素检测 const primarySelector = '#global > div.main-container > div.with-side-bar.main-content > div > div > div.search-layout__main > div.feeds-container > section'; let primaryElements = []; try { primaryElements = document.querySelectorAll(primarySelector); } catch (error) { console.log('主要选择器测试失败:', error); } const elements = this.findNoteElements(); const visibleElements = this.findVisibleNoteElements(); // 分析第一个元素的详细结构 let elementAnalysis = []; if (elements.length > 0) { const firstElement = elements[0]; const links = firstElement.querySelectorAll('a'); const buttons = firstElement.querySelectorAll('button, [role="button"]'); const clickableDivs = firstElement.querySelectorAll('div[class*="click"], div[class*="expand"], div[class*="more"]'); // 专门检测目标链接 const targetLink = firstElement.querySelector('div > a.cover.mask.ld'); elementAnalysis = { tagName: firstElement.tagName, className: firstElement.className, noteIndex: firstElement.dataset.noteIndex, selector: firstElement.dataset.selector, targetLink: targetLink ? { href: targetLink.href, text: targetLink.textContent.trim(), className: targetLink.className, found: true } : { found: false, message: '未找到目标链接 div > a.cover.mask.ld' }, links: Array.from(links).map(link => ({ href: link.href, text: link.textContent.trim(), className: link.className })), buttons: Array.from(buttons).map(btn => ({ text: btn.textContent.trim(), className: btn.className, role: btn.getAttribute('role') })), clickableDivs: Array.from(clickableDivs).map(div => ({ text: div.textContent.trim(), className: div.className })) }; } const result = { success: true, primarySelector: primarySelector, primaryElementsCount: primaryElements.length, totalElements: elements.length, visibleElements: visibleElements.length, elementDetails: elements.slice(0, 5).map(el => ({ tagName: el.tagName, className: el.className, hasLink: !!el.querySelector('a[href*="/explore/"], a[href*="/discovery/"], a[href*="/note/"]'), hasImage: !!el.querySelector('img'), textLength: el.textContent ? el.textContent.trim().length : 0 })), firstElementAnalysis: elementAnalysis }; console.log('测试结果:', result); sendResponse(result); } // 返回true表示异步响应 return true; }); console.log('消息监听器设置完成'); } // 自动点击笔记功能 async startAutoClickNotes() { console.log('开始自动点击笔记...'); try { // 检查内存使用情况 this.checkMemoryUsage(); // 设置自动点击状态 this.isAutoClicking = true; // 显示进度条 this.showProgressBar(); // 等待页面完全加载 await this.waitForPageLoad(); // 高亮显示检测到的元素(限制数量) this.highlightDetectedElements(); // 开始自动点击 await this.autoClickVisibleNotes(); } catch (error) { console.error('自动点击过程中发生错误:', error); this.showToast('自动点击出错,已停止'); } finally { // 隐藏进度条 this.hideProgressBar(); // 重置状态 this.isAutoClicking = false; // 执行清理 this.performMemoryCleanup(); } } // 等待页面加载完成 async waitForPageLoad() { return new Promise((resolve, reject) => { let attempts = 0; const maxAttempts = 20; // 最多等待10秒 const checkNotes = () => { // 检查是否被停止 if (!this.isAutoClicking) { reject(new Error('等待页面加载被停止')); return; } attempts++; const notes = this.findNoteElements(); if (notes.length > 0) { resolve(); } else if (attempts >= maxAttempts) { reject(new Error('等待页面加载超时')); } else { const timerId = setTimeout(checkNotes, 500); this.cleanupTimers.add(timerId); } }; checkNotes(); }); } // 查找笔记元素 findNoteElements() { console.log('开始查找笔记元素...'); // 使用用户提供的精确选择器,查找所有笔记 const baseSelector = '#global > div.main-container > div.with-side-bar.main-content > div > div > div.search-layout__main > div.feeds-container > section'; console.log('使用基础选择器:', baseSelector); // 查找所有section元素 const allSections = document.querySelectorAll(baseSelector); console.log(`找到 ${allSections.length} 个section元素`); if (allSections.length > 0) { // 为每个section添加索引信息 const notesWithIndex = Array.from(allSections).map((section, index) => { section.dataset.noteIndex = index + 1; section.dataset.selector = `${baseSelector}:nth-child(${index + 1})`; return section; }); console.log('笔记元素已添加索引信息'); return notesWithIndex; } // 如果主要选择器失败,使用备用选择器 console.log('主要选择器未找到元素,尝试备用选择器...'); const fallbackSelectors = [ '.note-item', '.search-item', '.feed-item', '.card-item', '.note-card', '.search-note-item', '.search-result-item', '.note-list-item' ]; for (const selector of fallbackSelectors) { try { const elements = document.querySelectorAll(selector); if (elements.length > 0) { console.log(`备用选择器 "${selector}" 找到 ${elements.length} 个元素`); return Array.from(elements); } } catch (error) { console.log(`备用选择器 "${selector}" 无效:`, error); } } console.log('未找到任何笔记元素'); return []; } // 自动点击可见的笔记 async autoClickVisibleNotes() { let totalClicked = 0; let hasMoreNotes = true; let totalFound = 0; let consecutiveFailures = 0; // 连续失败计数 const maxConsecutiveFailures = 5; // 最大连续失败次数 try { while (hasMoreNotes && this.isAutoClicking) { // 检查是否被停止 if (!this.isAutoClicking) { console.log('自动点击被停止'); break; } // 检查连续失败次数 if (consecutiveFailures >= maxConsecutiveFailures) { console.log(`连续失败${maxConsecutiveFailures}次,停止自动点击`); this.showToast('连续失败次数过多,已停止自动点击'); break; } // 查找当前可见的笔记 const visibleNotes = this.findVisibleNoteElements(); if (visibleNotes.length === 0) { console.log('没有找到更多笔记'); break; } totalFound += visibleNotes.length; console.log(`找到 ${visibleNotes.length} 个可见笔记`); // 更新进度条 this.updateProgress(totalClicked, totalFound); // 限制并发处理的笔记数量 const notesToProcess = visibleNotes.slice(0, this.maxConcurrentNotes); // 依次点击每个笔记 for (let i = 0; i < notesToProcess.length && this.isAutoClicking; i++) { const note = notesToProcess[i]; try { // 检查是否已经点击过 if (note.dataset.clicked === 'true') { continue; } // 检查是否正在处理 if (this.processingNotes.has(note)) { continue; } // 标记为正在处理 this.processingNotes.add(note); // 标记为已点击 note.dataset.clicked = 'true'; // 点击笔记(展开内容) await this.clickNote(note); // 检查是否被停止 if (!this.isAutoClicking) { console.log('自动点击被停止'); break; } totalClicked++; consecutiveFailures = 0; // 重置失败计数 // 更新进度条 this.updateProgress(totalClicked, totalFound); console.log(`已点击第 ${totalClicked} 个笔记`); // 评论间隔:在设定范围内随机等待(如1~2分钟),模拟真人节奏 await this.randomCommentDelay(); } catch (error) { console.error('点击笔记失败:', error); consecutiveFailures++; } finally { // 清理处理状态 this.processingNotes.delete(note); } } // 检查是否被停止 if (!this.isAutoClicking) { console.log('自动点击被停止'); break; } // 滚动页面加载更多笔记 try { hasMoreNotes = await this.scrollToLoadMore(); if (hasMoreNotes) { // 等待新内容加载 await this.sleep(2000); } } catch (error) { console.error('滚动加载失败:', error); hasMoreNotes = false; } // 添加内存清理 if (totalClicked % 10 === 0) { this.performMemoryCleanup(); } } } catch (error) { console.error('自动点击过程中发生错误:', error); } finally { // 清理所有资源 this.cleanup(); } console.log(`自动点击完成,总共点击了 ${totalClicked} 个笔记`); this.showToast(`自动点击完成,共点击 ${totalClicked} 个笔记`); } // 查找可见的笔记元素 findVisibleNoteElements() { const notes = this.findNoteElements(); const visibleNotes = []; for (const note of notes) { if (this.isElementVisible(note) && note.dataset.clicked !== 'true') { visibleNotes.push(note); } } return visibleNotes; } // 检查元素是否可见 isElementVisible(element) { const rect = element.getBoundingClientRect(); const windowHeight = window.innerHeight; // 元素在视口内且有一定高度 return rect.top < windowHeight && rect.bottom > 0 && rect.height > 50; } // 点击笔记 async clickNote(noteElement) { console.log('尝试点击笔记元素:', noteElement); console.log('元素标签:', noteElement.tagName, '类名:', noteElement.className); // 获取用户选择的点击模式 const shouldExpand = await this.getClickMode(); console.log('点击模式 - 展开内容:', shouldExpand); if (shouldExpand) { // 展开内容模式 await this.expandNoteContent(noteElement); } else { // 简单点击模式 await this.simpleClickNote(noteElement); } } // 获取用户选择的点击模式 async getClickMode() { try { const result = await chrome.storage.local.get(['expandContent']); return result.expandContent !== false; // 默认展开内容 } catch (error) { console.log('获取点击模式失败,使用默认模式:', error); return true; } } // 展开笔记内容 async expandNoteContent(noteElement) { console.log(`处理笔记 ${noteElement.dataset.noteIndex || '未知'}:`, noteElement.dataset.selector || '未知选择器'); // 专门查找并点击 div > a.cover.mask.ld 链接 const targetLink = noteElement.querySelector('div > a.cover.mask.ld'); if (targetLink) { console.log('找到目标链接:', targetLink.href); console.log('链接文本:', targetLink.textContent.trim()); console.log('链接类名:', targetLink.className); try { // 滚动到链接位置,确保可见 targetLink.scrollIntoView({ behavior: 'smooth', block: 'center' }); await this.sleep(500); // 点击链接 targetLink.click(); console.log('成功点击目标链接'); // 等待内容加载并执行评论交互流程 await this.executeCommentInteraction(); return; } catch (error) { console.log('点击目标链接失败:', error); } } else { console.log('未找到目标链接 div > a.cover.mask.ld'); } // 如果没找到目标链接,尝试其他方法 console.log('尝试备用点击方法...'); // 查找笔记中的其他可点击元素,但排除链接,只选择能展开内容的元素 const clickableElements = noteElement.querySelectorAll('[role="button"], [tabindex], [onclick], [class*="click"], [class*="expand"], [class*="open"], [class*="toggle"], [class*="more"], [class*="view"]'); console.log(`找到 ${clickableElements.length} 个其他可点击元素`); // 优先选择看起来像能展开内容的元素 let bestTarget = null; for (const clickable of clickableElements) { const className = clickable.className.toLowerCase(); const text = clickable.textContent.toLowerCase(); // 检查是否看起来像展开按钮 if (className.includes('expand') || className.includes('open') || className.includes('click') || className.includes('toggle') || className.includes('more') || className.includes('view') || text.includes('展开') || text.includes('更多') || text.includes('查看') || text.includes('显示') || text.includes('详情')) { bestTarget = clickable; console.log('找到展开按钮:', clickable.tagName, clickable.className, clickable.textContent); break; } } // 如果没有找到明确的展开按钮,尝试查找其他可点击元素(但绝对不是链接) if (!bestTarget) { // 查找按钮、div等非链接元素 const buttonElements = noteElement.querySelectorAll('button, [role="button"], div[class*="click"], div[class*="expand"], div[class*="more"]'); console.log(`找到 ${buttonElements.length} 个按钮元素`); if (buttonElements.length > 0) { bestTarget = buttonElements[0]; console.log('选择按钮元素:', bestTarget.tagName, bestTarget.className); } } // 尝试点击选中的目标 if (bestTarget) { try { console.log('尝试点击备用目标元素:', bestTarget.tagName, bestTarget.className); // 滚动到元素位置,确保可见 bestTarget.scrollIntoView({ behavior: 'smooth', block: 'center' }); await this.sleep(500); // 模拟点击 bestTarget.click(); console.log('成功点击备用目标元素'); // 等待内容展开并执行评论交互流程 await this.executeCommentInteraction(); return; } catch (error) { console.log('点击备用目标元素失败:', error); } } // 如果所有方法都失败了,尝试点击整个笔记元素(但阻止默认行为) try { console.log('尝试点击整个笔记元素(阻止默认行为)'); // 滚动到笔记位置 noteElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); await this.sleep(500); // 模拟鼠标点击,但阻止默认行为 this.simulateClickWithPreventDefault(noteElement); console.log('成功点击整个笔记元素'); // 等待内容展开并执行评论交互流程 await this.executeCommentInteraction(); } catch (error) { console.log('点击整个笔记元素失败:', error); } } // 简单点击笔记 async simpleClickNote(noteElement) { try { console.log(`执行简单点击模式 - 笔记 ${noteElement.dataset.noteIndex || '未知'}`); // 优先查找并点击目标链接 const targetLink = noteElement.querySelector('div > a.cover.mask.ld'); if (targetLink) { console.log('找到目标链接,执行简单点击'); // 滚动到链接位置 targetLink.scrollIntoView({ behavior: 'smooth', block: 'center' }); await this.sleep(300); // 点击链接 targetLink.click(); console.log('成功点击目标链接(简单模式)'); // 等待内容加载并执行评论交互流程 await this.executeCommentInteraction(); return; } // 如果没有找到目标链接,执行原来的简单点击逻辑 console.log('未找到目标链接,执行备用简单点击'); // 滚动到笔记位置 noteElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); await this.sleep(300); // 直接点击整个笔记元素,但阻止默认行为 this.simulateClickWithPreventDefault(noteElement); console.log('成功执行备用简单点击'); // 短暂等待 await this.sleep(500); } catch (error) { console.log('简单点击失败:', error); } } // 模拟鼠标点击 simulateClick(element) { const rect = element.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; // 创建鼠标事件 const clickEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true, clientX: centerX, clientY: centerY }); element.dispatchEvent(clickEvent); } // 模拟鼠标点击并阻止默认行为 simulateClickWithPreventDefault(element) { const rect = element.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const clickEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true, clientX: centerX, clientY: centerY }); element.dispatchEvent(clickEvent); clickEvent.preventDefault(); // 阻止默认行为 } // 显示进度条 showProgressBar() { // 移除已存在的进度条 this.hideProgressBar(); const progressBar = document.createElement('div'); progressBar.className = 'xhs-progress-bar'; progressBar.innerHTML = `
🤖 自动点击进行中...
已点击: 0 总计: 0 内存: --
💡 长按评论模式按钮可管理自定义评论
`; document.body.appendChild(progressBar); // 绑定停止按钮事件 const stopBtn = progressBar.querySelector('#xhs-stop-auto-click'); stopBtn.addEventListener('click', () => { this.stopAutoClick(); }); // 绑定清理内存按钮事件 const cleanupBtn = progressBar.querySelector('#xhs-cleanup-memory'); cleanupBtn.addEventListener('click', () => { this.emergencyMemoryCleanup(); }); // 绑定评论模式切换按钮事件 const commentModeBtn = progressBar.querySelector('#xhs-comment-mode'); commentModeBtn.addEventListener('click', () => { this.toggleCommentMode(); }); // 长按显示自定义评论管理弹窗 let longPressTimer = null; commentModeBtn.addEventListener('mousedown', () => { longPressTimer = setTimeout(() => { if (this.commentMode === 'custom') { this.showCustomCommentModal(); } }, 800); // 800ms长按 }); commentModeBtn.addEventListener('mouseup', () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } }); commentModeBtn.addEventListener('mouseleave', () => { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } }); // 更新评论模式按钮显示 this.updateCommentModeButton(); // 添加动画效果 setTimeout(() => { progressBar.classList.add('show'); }, 100); // 启动内存使用更新 this.startMemoryUsageUpdate(); } // 启动内存使用更新 startMemoryUsageUpdate() { const updateMemory = () => { if (!this.isAutoClicking) return; const memoryElement = document.getElementById('xhs-memory-usage'); if (memoryElement) { const stats = this.getMemoryStats(); if (stats) { memoryElement.textContent = `内存: ${stats.usedMB}MB`; // 根据内存使用情况改变颜色 if (stats.used > this.memoryThreshold) { memoryElement.style.color = '#ff4444'; // 红色警告 } else if (stats.used > this.memoryThreshold * 0.7) { memoryElement.style.color = '#ffaa00'; // 黄色警告 } else { memoryElement.style.color = '#44ff44'; // 绿色正常 } } } }; // 立即更新一次 updateMemory(); // 每5秒更新一次 const updateTimer = setInterval(updateMemory, 5000); this.cleanupTimers.add(updateTimer); } // 高亮显示检测到的元素 highlightDetectedElements() { // 先清理之前的高亮 this.cleanupHighlightedElements(); const elements = this.findNoteElements(); console.log(`高亮显示 ${elements.length} 个检测到的元素`); // 限制高亮元素数量,避免内存问题 const maxHighlightElements = Math.min(elements.length, 20); const elementsToHighlight = elements.slice(0, maxHighlightElements); elementsToHighlight.forEach((el, index) => { // 添加高亮样式 el.style.outline = '3px solid #4caf50'; el.style.outlineOffset = '2px'; el.style.position = 'relative'; // 添加序号标签 const label = document.createElement('div'); label.style.cssText = ` position: absolute; top: -10px; left: -10px; background: #4caf50; color: white; padding: 2px 6px; border-radius: 10px; font-size: 12px; font-weight: bold; z-index: 10000; pointer-events: none; `; label.textContent = index + 1; el.appendChild(label); // 设置清理定时器 const cleanupTimer = setTimeout(() => { if (el.style.outline) { el.style.outline = ''; el.style.outlineOffset = ''; el.style.position = ''; if (label.parentNode) { label.remove(); } } // 从清理定时器集合中移除 this.cleanupTimers.delete(cleanupTimer); }, 3000); // 添加到清理定时器集合 this.cleanupTimers.add(cleanupTimer); }); // 如果元素数量超过限制,显示提示 if (elements.length > maxHighlightElements) { console.log(`高亮显示限制为${maxHighlightElements}个元素,避免内存问题`); } } // 停止自动点击 stopAutoClick() { console.log('用户停止自动点击'); this.isAutoClicking = false; // 清理所有资源 this.cleanup(); // 显示停止提示 this.showToast('已停止自动点击'); console.log('自动点击已完全停止'); } // 强制停止所有操作 forceStopAllOperations() { console.log('强制停止所有操作...'); // 清理所有资源 this.cleanup(); console.log('所有操作已被强制停止'); } // 获取内存使用统计 getMemoryStats() { try { if (performance.memory) { return { used: performance.memory.usedJSHeapSize, total: performance.memory.totalJSHeapSize, limit: performance.memory.jsHeapSizeLimit, usedMB: (performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2), totalMB: (performance.memory.totalJSHeapSize / 1024 / 1024).toFixed(2), limitMB: (performance.memory.jsHeapSizeLimit / 1024 / 1024).toFixed(2) }; } return null; } catch (error) { console.error('获取内存统计失败:', error); return null; } } // 隐藏进度条 hideProgressBar() { const existingBar = document.querySelector('.xhs-progress-bar'); if (existingBar) { existingBar.remove(); } } // 更新进度条 updateProgress(clicked, total) { const clickedElement = document.getElementById('xhs-clicked-count'); const totalElement = document.getElementById('xhs-total-count'); const fillElement = document.querySelector('.xhs-progress-fill'); if (clickedElement) clickedElement.textContent = `已点击: ${clicked}`; if (totalElement) totalElement.textContent = `总计: ${total}`; if (fillElement && total > 0) { const percentage = Math.min((clicked / total) * 100, 100); fillElement.style.width = `${percentage}%`; } } // 滚动加载更多内容 async scrollToLoadMore() { // 检查是否被停止 if (!this.isAutoClicking) { console.log('滚动加载被停止'); return false; } const scrollHeight = document.documentElement.scrollHeight; const scrollTop = window.pageYOffset; const clientHeight = document.documentElement.clientHeight; // 如果已经滚动到底部,尝试加载更多 if (scrollTop + clientHeight >= scrollHeight - 100) { // 滚动到底部 window.scrollTo(0, scrollHeight); // 等待新内容加载 await this.sleep(2000); // 检查是否被停止 if (!this.isAutoClicking) { console.log('滚动加载被停止'); return false; } // 检查是否有新内容 const newScrollHeight = document.documentElement.scrollHeight; return newScrollHeight > scrollHeight; } else { // 滚动到当前可见区域的底部 window.scrollBy(0, clientHeight); return true; } } // 延时函数 sleep(ms) { return new Promise(resolve => { // 如果已经被停止,立即返回 if (!this.isAutoClicking) { resolve(); return; } const timeoutId = setTimeout(() => { resolve(); // 清理定时器ID this.cleanupTimers.delete(timeoutId); }, ms); // 添加到清理定时器集合 this.cleanupTimers.add(timeoutId); // 创建一个检查停止的定时器 const checkStopInterval = setInterval(() => { if (!this.isAutoClicking) { clearTimeout(timeoutId); clearInterval(checkStopInterval); // 清理定时器ID this.cleanupTimers.delete(timeoutId); this.cleanupTimers.delete(checkStopInterval); resolve(); } }, 100); // 添加到清理定时器集合 this.cleanupTimers.add(checkStopInterval); }); } // 评论间隔:最小~最大分钟之间随机等待,进度条实时倒计时 async randomCommentDelay() { let min = 1; let max = 2; try { const result = await chrome.storage.local.get(['commentDelayMin', 'commentDelayMax']); const parsedMin = Number(result.commentDelayMin); const parsedMax = Number(result.commentDelayMax); if (Number.isFinite(parsedMin) && parsedMin >= 0) min = parsedMin; if (Number.isFinite(parsedMax) && parsedMax >= 0) max = parsedMax; } catch (error) { console.log('读取评论间隔失败,使用默认 1~2 分钟:', error); } if (max < min) [min, max] = [max, min]; // 都是0则不等待 if (min === 0 && max === 0) return; const totalSec = Math.round((min + Math.random() * (max - min)) * 60); console.log(`评论间隔:随机等待 ${totalSec} 秒(范围 ${min}~${max} 分钟)`); // 进度条提示实时倒计时 const tip = document.querySelector('.xhs-progress-tip'); let remain = totalSec; const renderTip = () => { if (tip) { tip.textContent = remain > 0 ? `⏳ 评论间隔:随机等待 ${remain} 秒后继续下一篇…` : '💡 长按评论模式按钮可管理自定义评论'; } }; renderTip(); const countdownTimer = setInterval(() => { remain--; renderTip(); }, 1000); this.cleanupTimers.add(countdownTimer); await this.sleep(totalSec * 1000); clearInterval(countdownTimer); this.cleanupTimers.delete(countdownTimer); renderTip(); } // 阻止笔记元素内的所有链接跳转 preventLinkNavigation(noteElement) { const links = noteElement.querySelectorAll('a'); links.forEach(link => { link.addEventListener('click', (event) => { event.preventDefault(); // 阻止默认跳转 console.log('阻止链接跳转:', link.href); }); }); } // 执行评论交互流程 async executeCommentInteraction() { console.log('开始执行评论交互流程...'); // 检查是否被停止 if (!this.isAutoClicking) { console.log('评论交互被停止'); return; } try { // 1. 等待评论框出现 await this.waitForCommentBox(); console.log('评论框已出现'); // 检查是否被停止 if (!this.isAutoClicking) { console.log('评论交互被停止'); return; } // 2. 点击评论框 await this.clickCommentBox(); console.log('评论框已点击'); // 检查是否被停止 if (!this.isAutoClicking) { console.log('评论交互被停止'); return; } // 3. 等待输入框出现 await this.waitForTextarea(); console.log('输入框已出现'); // 检查是否被停止 if (!this.isAutoClicking) { console.log('评论交互被停止'); return; } // 4. 输入评论 const sentComment = await this.inputComment(); console.log('评论已输入'); // 检查是否被停止 if (!this.isAutoClicking) { console.log('评论交互被停止'); return; } // 5. 提交评论 await this.submitComment(); console.log('评论已提交'); // 6. 校验是否发送成功并记录 const posted = await this.verifyCommentPosted(sentComment); if (posted) { await this.logCommentSuccess(sentComment); // 试用额度计数(授权用户不计);额度用完自动停止 await this.checkUsageQuota(); } else { console.warn('评论可能未发送成功,不计入记录'); this.showToast('⚠️ 评论可能未发送成功,本篇未记录'); } // 检查是否被停止 if (!this.isAutoClicking) { console.log('评论交互被停止'); return; } // 7. 等待3秒 await this.sleep(3000); console.log('等待3秒完成'); // 检查是否被停止 if (!this.isAutoClicking) { console.log('评论交互被停止'); return; } // 8. 按ESC键 await this.pressEscape(); console.log('ESC键已按下'); // 检查是否被停止 if (!this.isAutoClicking) { console.log('评论交互被停止'); return; } // 9. 等待评论框关闭 await this.waitForCommentBoxClose(); console.log('评论框已关闭,准备处理下一个笔记'); } catch (error) { console.error('评论交互失败:', error); } } // 等待评论框出现 async waitForCommentBox() { console.log('等待评论框出现...'); return new Promise((resolve, reject) => { let attempts = 0; const maxAttempts = 60; // 最多等待30秒 const checkCommentBox = () => { // 检查是否被停止 if (!this.isAutoClicking) { reject(new Error('等待评论框被停止')); return; } attempts++; const commentBox = document.querySelector('#noteContainer > div.interaction-container > div.interactions.engage-bar > div > div > div.input-box > div.content-edit > div > div > span'); if (commentBox) { console.log('评论框已找到:', commentBox); resolve(); } else if (attempts >= maxAttempts) { reject(new Error('等待评论框超时')); } else { setTimeout(checkCommentBox, 500); } }; checkCommentBox(); }); } // 点击评论框 async clickCommentBox() { const commentBox = document.querySelector('#noteContainer > div.interaction-container > div.interactions.engage-bar > div > div > div.input-box > div.content-edit > div > div > span'); if (commentBox) { console.log('点击评论框:', commentBox); commentBox.click(); await this.sleep(1000); // 等待点击生效 } else { throw new Error('评论框元素未找到'); } } // 等待输入框出现 async waitForTextarea() { console.log('等待输入框出现...'); return new Promise((resolve, reject) => { let attempts = 0; const maxAttempts = 30; // 最多等待15秒 const checkTextarea = () => { // 检查是否被停止 if (!this.isAutoClicking) { reject(new Error('等待输入框被停止')); return; } attempts++; const textarea = document.querySelector('#content-textarea'); if (textarea) { console.log('输入框已找到:', textarea); resolve(); } else if (attempts >= maxAttempts) { reject(new Error('等待输入框超时')); } else { setTimeout(checkTextarea, 500); } }; checkTextarea(); }); } // 输入评论 async inputComment() { const textarea = document.querySelector('#content-textarea'); if (textarea) { console.log(`开始生成评论,模式: ${this.commentMode}`); try { let commentContent = ''; if (this.commentMode === 'ai') { // AI评论模式 commentContent = await this.generateAiComment(); } else if (this.commentMode === 'text') { // 用户自定义文字模式 commentContent = await this.getUserTextComment(); } else { // 固定评论池模式 commentContent = await this.getCustomComment(); } if (!commentContent) { throw new Error(this.commentMode === 'text' ? '自定义文字库为空,请在侧边栏填写' : '无法生成评论内容'); } // 聚焦到输入框 textarea.focus(); await this.sleep(500); // 设置评论内容 textarea.textContent = commentContent; // 触发输入事件 const inputEvent = new Event('input', { bubbles: true }); textarea.dispatchEvent(inputEvent); console.log(`评论已输入 (${this.commentMode}):`, commentContent); await this.sleep(1000); // 等待输入生效 return commentContent; } catch (error) { console.error('评论生成失败:', error); // 如果评论生成失败,使用默认评论,并明确提示(避免误以为是AI生成的) const defaultComment = '👍 赞!'; textarea.textContent = defaultComment; const inputEvent = new Event('input', { bubbles: true }); textarea.dispatchEvent(inputEvent); this.showToast(`评论生成失败(${this.getCommentModeLabel()}),已改用固定评论: ${error.message}`); console.log('使用默认评论:', defaultComment); return defaultComment; } } else { throw new Error('输入框元素未找到'); } } // 获取用户自定义评论 async getCustomComment() { try { // 从存储中获取自定义评论列表 const result = await chrome.storage.local.get(['customComments']); this.customComments = result.customComments || []; if (this.customComments.length === 0) { // 如果没有自定义评论,返回默认评论 return '👍 赞!'; } // 循环使用评论列表 const comment = this.customComments[this.currentCommentIndex % this.customComments.length]; this.currentCommentIndex++; console.log(`使用自定义评论 (${this.currentCommentIndex}/${this.customComments.length}):`, comment); return comment; } catch (error) { console.error('获取自定义评论失败:', error); return '👍 赞!'; } } // 获取用户自定义文字(text模式,一行一条循环发送) async getUserTextComment() { try { const result = await chrome.storage.local.get(['userComments']); const userComments = (result.userComments || []).filter((line) => line && line.trim()); if (userComments.length === 0) { throw new Error('自定义文字库为空,请在侧边栏填写并保存'); } const comment = userComments[this.currentCommentIndex % userComments.length]; this.currentCommentIndex++; console.log(`使用自定义文字 (${this.currentCommentIndex}/${userComments.length}):`, comment); return comment; } catch (error) { console.error('获取自定义文字失败:', error); throw error; } } // 获取评论模式的中文名称 getCommentModeLabel(mode = this.commentMode) { const labels = { custom: '固定评论池', text: '自定义文字', ai: 'AI按笔记生成' }; return labels[mode] || mode; } // 校验评论是否发送成功(提交后延迟检查) async verifyCommentPosted(commentText) { await this.sleep(1500); try { // 评论框清空 或 评论区出现该文字,都算成功 const textarea = document.querySelector('#content-textarea'); const textareaEmpty = !textarea || !(textarea.textContent || '').trim(); const commentsContainer = document.querySelector('#noteContainer .comments-container') || document.querySelector('.comments-container'); const textVisible = !!(commentsContainer && commentText && commentsContainer.textContent.includes(commentText)); return textareaEmpty || textVisible; } catch (error) { console.log('校验评论结果失败,默认按成功处理:', error); return true; } } // 试用额度:每成功一条让后台计数;allowed=false 时立即停止本轮任务 async checkUsageQuota() { try { const res = await chrome.runtime.sendMessage({ action: 'usageTick' }); const d = res && res.data; if (!d) return; if (d.allowed === false) { this.isAutoClicking = false; this.cleanup(); this.showToast('⛔ ' + (d.message || '体验额度已用完,已自动停止')); console.log('额度用完,已停止:', d); return; } if (d.state === 'trial' && typeof d.quotaLeft === 'number' && d.quotaLeft > 0 && d.quotaLeft <= 10) { this.showToast(`试用额度还剩 ${d.quotaLeft} 条`); } else if (d.state === 'grace' && typeof d.dayLeft === 'number' && d.dayLeft > 0) { console.log(`体验模式:今日剩余 ${d.dayLeft} 条`); } } catch (error) { console.log('额度检查失败(不阻断):', error); } } // 记录一条成功的评论(写入 storage,侧边栏展示统计与列表) async logCommentSuccess(commentText) { try { let url = ''; if (this.currentNoteData && this.currentNoteData.url) { url = this.currentNoteData.url; } else if (window.location.pathname.includes('/explore/')) { url = window.location.href; } let title = this.currentNoteData && this.currentNoteData.title && this.currentNoteData.title !== '无标题' ? this.currentNoteData.title : ''; if (!title) { const titleEl = document.querySelector('#noteContainer .title') || document.querySelector('.note-detail-title') || document.querySelector('#noteContainer [class*="title"]'); title = titleEl ? titleEl.textContent.trim() : ''; } if (!title) title = '无标题笔记'; const record = { id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, url, title: title.slice(0, 100), comment: (commentText || '').slice(0, 200), mode: this.commentMode, timestamp: Date.now() }; const result = await chrome.storage.local.get(['commentLogs']); const logs = result.commentLogs || []; logs.unshift(record); if (logs.length > 300) logs.length = 300; // 最多保留300条 await chrome.storage.local.set({ commentLogs: logs }); console.log('已记录评论:', record.title, record.comment); } catch (error) { console.error('记录评论失败:', error); } } // 生成AI评论(简化版,避免重复代码) async generateAiComment() { try { // 获取笔记内容 const noteContent = await this.extractNoteContent(); console.log('提取的笔记内容:', noteContent); // 使用AI生成评论 const aiComment = await this.generateAiCommentContent(noteContent); console.log('AI生成的评论:', aiComment); return aiComment; } catch (error) { console.error('AI评论生成失败:', error); throw error; } } // 提取笔记内容 async extractNoteContent() { // 尝试多种方式提取笔记内容 const contentSelectors = [ '.note-detail-desc', '.note-content', '.content-text', '[class*="content"]', '[class*="desc"]', '.note-text' ]; let noteContent = ''; for (const selector of contentSelectors) { const element = document.querySelector(selector); if (element && element.textContent) { noteContent = element.textContent.trim(); if (noteContent.length > 10) { console.log('使用选择器提取内容:', selector); break; } } } // 如果没有找到内容,尝试从页面标题获取 if (!noteContent || noteContent.length < 10) { const titleElement = document.querySelector('h1, .title, [class*="title"]'); if (titleElement) { noteContent = titleElement.textContent.trim(); console.log('从标题提取内容'); } } // 如果还是没有内容,使用页面URL作为内容 if (!noteContent || noteContent.length < 5) { noteContent = window.location.href; console.log('使用URL作为内容'); } // 限制内容长度,避免内存溢出 const maxLength = 300; if (noteContent.length > maxLength) { noteContent = noteContent.substring(0, maxLength) + '...'; console.log(`内容过长,已截取前${maxLength}字符,避免内存溢出`); } console.log(`最终提取的内容长度: ${noteContent.length} 字符`); return noteContent; } // 使用AI生成评论 async generateAiCommentContent(noteContent, retryCount = 0) { try { // 获取API密钥 const apiKey = await this.getApiKey(); if (!apiKey) { throw new Error('未配置API密钥'); } // 限制内容长度,避免内存溢出 const maxContentLength = 200; // 减少到200字符 let processedContent = noteContent; if (processedContent.length > maxContentLength) { processedContent = processedContent.substring(0, maxContentLength) + '...'; console.log(`内容过长,已截取前${maxContentLength}字符,避免内存溢出`); } // 构建AI提示词 const promptTemplate = (await chrome.storage.local.get(['aiPrompt'])).aiPrompt || `根据笔记内容来生成符合笔记的评论。最后只需要输出评论内容,不要输出你的推理,不要输出任何与评论无关的内容,不要输出#相关的内容,不要输出笔记关键词相关的内容。评论不要超过300字。这个是小红书的笔记内容:\n\n{{content}}\n\n`; const prompt = promptTemplate.replace(/\{\{\s*content\s*\}\}/g, processedContent); console.log('发送AI请求,提示词长度:', prompt.length, '字符'); // 读取自定义模型配置,实际请求交给后台脚本执行(规避页面 CORS / 混合内容限制) const modelConfig = await chrome.storage.local.get(['apiBaseUrl', 'modelName']); console.log('发送AI请求到后台,模型:', modelConfig.modelName || '(默认)', '提示词长度:', prompt.length, '字符'); const apiResult = await chrome.runtime.sendMessage({ action: 'callAiApi', apiKey: apiKey, baseUrl: modelConfig.apiBaseUrl, model: modelConfig.modelName, prompt: prompt, maxTokens: 300 }); if (!apiResult || !apiResult.ok) { const errorMessage = (apiResult && apiResult.error) || '后台服务未响应,请在浏览器扩展页重新加载插件'; // 如果是内存溢出错误,尝试重试 if (errorMessage.includes('out of memory') && retryCount < this.maxRetries) { console.log(`内存溢出,尝试截取更短内容重试 (${retryCount + 1}/${this.maxRetries + 1})`); // 截取更短的内容重试 const shorterContent = processedContent.substring(0, 100) + '...'; return await this.generateAiCommentContent(shorterContent, retryCount + 1); } throw new Error(`AI API调用失败: ${errorMessage}`); } const aiComment = String(apiResult.content || '').trim(); if (!aiComment) { throw new Error('AI返回的评论内容为空'); } console.log('AI生成评论成功:', aiComment, '| 模型:', apiResult.model, '| 端点:', apiResult.url); return aiComment; } catch (error) { console.error('AI评论生成失败:', error); // 如果是内存溢出且还有重试机会,尝试重试 if (error.message.includes('out of memory') && retryCount < this.maxRetries) { console.log(`内存溢出,尝试截取更短内容重试 (${retryCount + 1}/${this.maxRetries + 1})`); const shorterContent = noteContent.substring(0, 50) + '...'; return await this.generateAiCommentContent(shorterContent, retryCount + 1); } throw error; } } // 获取API密钥 async getApiKey() { try { const result = await chrome.storage.local.get(['apiKey']); return result.apiKey || ''; } catch (error) { console.log('获取API密钥失败:', error); return ''; } } // 提交评论 async submitComment() { const submitButton = document.querySelector('#noteContainer > div.interaction-container > div.interactions.engage-bar > div > div > div.bottom > div > div.right-btn-area > button.btn.submit'); if (submitButton) { console.log('点击提交按钮:', submitButton); submitButton.click(); await this.sleep(2000); // 等待提交完成 } else { throw new Error('提交按钮未找到'); } } // 按ESC键 async pressEscape() { console.log('模拟按ESC键...'); // 创建ESC键事件 const keydownEvent = new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true, cancelable: true }); const keyupEvent = new KeyboardEvent('keyup', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true, cancelable: true }); // 在document上触发ESC键事件 document.dispatchEvent(keydownEvent); document.dispatchEvent(keyupEvent); console.log('ESC键事件已触发'); await this.sleep(500); } // 等待评论框关闭 async waitForCommentBoxClose() { console.log('等待评论框关闭...'); return new Promise((resolve) => { let attempts = 0; const maxAttempts = 20; // 最多等待10秒 const checkCommentBoxClosed = () => { // 检查是否被停止 if (!this.isAutoClicking) { console.log('等待评论框关闭被停止'); resolve(); return; } attempts++; const textarea = document.querySelector('#content-textarea'); if (!textarea) { console.log('评论框已关闭'); resolve(); } else if (attempts >= maxAttempts) { console.log('等待评论框关闭超时,继续执行'); resolve(); } else { setTimeout(checkCommentBoxClosed, 500); } }; checkCommentBoxClosed(); }); } // 执行内存清理 performMemoryCleanup() { console.log('执行内存清理...'); // 清理高亮元素 this.cleanupHighlightedElements(); // 清理已点击的标记 const clickedElements = document.querySelectorAll('[data-clicked="true"]'); clickedElements.forEach(el => { delete el.dataset.clicked; }); // 强制垃圾回收(如果浏览器支持) if (window.gc) { try { window.gc(); console.log('垃圾回收已执行'); } catch (e) { console.log('垃圾回收失败:', e); } } console.log('内存清理完成'); } // 页面级别的内存泄漏防护 setupMemoryLeakProtection() { // 监听页面错误 window.addEventListener('error', (event) => { if (event.error && event.error.message.includes('out of memory')) { console.error('检测到内存溢出错误,执行紧急清理'); this.emergencyMemoryCleanup(); } }); // 监听未处理的Promise拒绝 window.addEventListener('unhandledrejection', (event) => { if (event.reason && event.reason.message && event.reason.message.includes('out of memory')) { console.error('检测到未处理的Promise内存错误,执行紧急清理'); this.emergencyMemoryCleanup(); } }); // 监听页面性能警告 if ('PerformanceObserver' in window) { try { const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.entryType === 'measure' && entry.name.includes('memory')) { console.log('性能测量:', entry); } } }); observer.observe({ entryTypes: ['measure'] }); } catch (error) { console.log('性能观察器设置失败:', error); } } } // 调试信息输出 logDebugInfo() { console.log('=== 内存管理调试信息 ==='); console.log('当前状态:', { isAutoClicking: this.isAutoClicking, processingNotesCount: this.processingNotes.size, cleanupTimersCount: this.cleanupTimers.size, memoryThreshold: `${(this.memoryThreshold / 1024 / 1024).toFixed(2)}MB` }); const memoryStats = this.getMemoryStats(); if (memoryStats) { console.log('内存统计:', memoryStats); } console.log('DOM节点数量:', document.querySelectorAll('*').length); console.log('高亮元素数量:', document.querySelectorAll('[style*="outline: 3px solid #4caf50"]').length); console.log('========================'); } // 切换评论模式(固定评论池 → 自定义文字 → AI → 循环) async toggleCommentMode() { const modes = ['custom', 'text', 'ai']; const currentIndex = modes.indexOf(this.commentMode); this.commentMode = modes[(currentIndex + 1) % modes.length]; console.log(`切换评论模式为: ${this.commentMode}`); // 保存设置到存储 try { await chrome.storage.local.set({ commentMode: this.commentMode }); console.log('评论模式设置已保存'); } catch (error) { console.error('保存评论模式设置失败:', error); } this.updateCommentModeButton(); this.showToast(`评论模式已切换为: ${this.getCommentModeLabel()}`); } // 更新评论模式按钮显示 updateCommentModeButton() { const commentModeBtn = document.getElementById('xhs-comment-mode'); if (commentModeBtn) { const icons = { custom: '📋', text: '📝', ai: '🤖' }; commentModeBtn.textContent = `${icons[this.commentMode] || '💬'} ${this.getCommentModeLabel()}`; if (this.commentMode === 'ai') { commentModeBtn.classList.remove('xhs-secondary-btn'); commentModeBtn.classList.add('xhs-primary-btn'); } else { commentModeBtn.classList.remove('xhs-primary-btn'); commentModeBtn.classList.add('xhs-secondary-btn'); } } } // 添加自定义评论 async addCustomComment(comment) { try { if (!comment || comment.trim().length === 0) { throw new Error('评论内容不能为空'); } const result = await chrome.storage.local.get(['customComments']); const customComments = result.customComments || []; // 检查是否已存在 if (customComments.includes(comment.trim())) { throw new Error('该评论已存在'); } // 添加新评论 customComments.push(comment.trim()); // 保存到存储 await chrome.storage.local.set({ customComments }); // 更新本地列表 this.customComments = customComments; console.log('自定义评论已添加:', comment); this.showToast('自定义评论已添加'); return true; } catch (error) { console.error('添加自定义评论失败:', error); this.showToast(`添加失败: ${error.message}`); return false; } } // 删除自定义评论 async removeCustomComment(comment) { try { const result = await chrome.storage.local.get(['customComments']); let customComments = result.customComments || []; // 移除指定评论 customComments = customComments.filter(c => c !== comment); // 保存到存储 await chrome.storage.local.set({ customComments }); // 更新本地列表 this.customComments = customComments; console.log('自定义评论已删除:', comment); this.showToast('自定义评论已删除'); return true; } catch (error) { console.error('删除自定义评论失败:', error); this.showToast(`删除失败: ${error.message}`); return false; } } // 获取所有自定义评论 async getAllCustomComments() { try { const result = await chrome.storage.local.get(['customComments']); return result.customComments || []; } catch (error) { console.error('获取自定义评论失败:', error); return []; } } // 显示自定义评论管理弹窗 showCustomCommentModal() { // 创建弹窗 const modal = document.createElement('div'); modal.className = 'xhs-comment-modal'; modal.innerHTML = `

自定义评论管理

已添加的评论:
`; document.body.appendChild(modal); // 绑定事件 this.bindCustomCommentModalEvents(modal); // 加载现有评论 this.loadCustomCommentsToModal(modal); } // 绑定自定义评论弹窗事件 bindCustomCommentModalEvents(modal) { const addBtn = modal.querySelector('.xhs-add-comment-btn'); const input = modal.querySelector('.xhs-comment-input'); const closeBtn = modal.querySelector('#close-comment-modal'); // 添加评论 addBtn.addEventListener('click', async () => { const comment = input.value.trim(); if (comment) { const success = await this.addCustomComment(comment); if (success) { input.value = ''; this.loadCustomCommentsToModal(modal); } } }); // 回车添加评论 input.addEventListener('keypress', async (e) => { if (e.key === 'Enter') { const comment = input.value.trim(); if (comment) { const success = await this.addCustomComment(comment); if (success) { input.value = ''; this.loadCustomCommentsToModal(modal); } } } }); // 关闭弹窗 closeBtn.addEventListener('click', () => { modal.remove(); }); // 点击背景关闭 modal.addEventListener('click', (e) => { if (e.target === modal) { modal.remove(); } }); } // 加载自定义评论到弹窗 async loadCustomCommentsToModal(modal) { const commentItems = modal.querySelector('.xhs-comment-items'); const comments = await this.getAllCustomComments(); if (comments.length === 0) { commentItems.innerHTML = '
暂无自定义评论
'; return; } commentItems.innerHTML = comments.map(comment => `
${comment}
`).join(''); // 绑定删除按钮事件 const deleteBtns = modal.querySelectorAll('.xhs-delete-comment-btn'); deleteBtns.forEach(btn => { btn.addEventListener('click', async () => { const comment = btn.dataset.comment; const success = await this.removeCustomComment(comment); if (success) { this.loadCustomCommentsToModal(modal); } }); }); } } // 初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { new XHSNoteSaver(); }); } else { new XHSNoteSaver(); }