diff --git a/README.md b/README.md index 25f8e34..c3cf454 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > 一键搜索 · 自动点击 · 智能评论 —— 小红书运营引流的效率神器 -[![版本](https://img.shields.io/badge/版本-1.8.2-ff2442.svg)](https://dl.88531.cn/xhs.html) +[![版本](https://img.shields.io/badge/版本-1.8.3-ff2442.svg)](https://dl.88531.cn/xhs.html) [![平台](https://img.shields.io/badge/平台-Chrome%20%7C%20Edge-4caf50.svg)](#安装方法) [![语言](https://img.shields.io/badge/语言-纯%20JavaScript-2196f3.svg)](#开发说明) @@ -104,10 +104,15 @@ ## 📜 版本历史 -当前版本:**1.8.2** +当前版本:**1.8.3**
-展开查看完整变更记录(v1.2.0 ~ v1.8.2) +展开查看完整变更记录(v1.2.0 ~ v1.8.3) + +### 1.8.3 +- **评论去重:评过的笔记不再重复评论**。每次成功评论后把笔记 ID 写入「已评论索引」(本机保存,最多 5000 条),之后任何时候再刷到同一篇(同关键词、不同关键词、换个时间重新跑都一样)都会**自动跳过**,不会给同一篇笔记发第二条评论——避免重复评论触发平台风控限流 +- 运行时进度条新增「**已跳过已评论**」一栏,实时显示本轮跳过了几篇;侧边栏评论记录统计新增「**已去重**」,显示累计跳过的笔记总数 +- 即使不是从列表进入(比如直接打开某篇笔记页),评论前也会先查索引,已评过就直接跳过 ### 1.8.2 - **未授权时显示机器码**:卡密输入框下方常驻展示本机机器码 + 一键复制,去商城购买或手动激活时直接粘贴,不用再翻文档问客服怎么找 diff --git a/background.js b/background.js index 97f74fe..71c956b 100644 --- a/background.js +++ b/background.js @@ -16,7 +16,7 @@ const DEFAULT_AI = { const AUTH_BASE = 'https://auth.88531.cn'; const AUTH_PROJECT_ID = 3; const AUTH_API_KEY = 'ak_bbf83b76df8219388d7f60b9e67c7db2'; -const CLIENT_VERSION = '1.8.2'; +const CLIENT_VERSION = '1.8.3'; const TRIAL_MS = 72 * 60 * 60 * 1000; // 试用 72 小时(3 天) const TRIAL_QUOTA = 100; // 试用期最多 100 条评论(时间/条数先到先停,防止白嫖薅量) const GRACE_DAILY = 3; // 试用到期不硬锁:每天保留 3 条体验额度 diff --git a/content.css b/content.css index 3634bf6..6fd0e46 100644 --- a/content.css +++ b/content.css @@ -203,10 +203,16 @@ .xhs-progress-stats { display: flex !important; justify-content: space-between !important; + flex-wrap: wrap !important; + gap: 4px 12px !important; font-size: 12px !important; color: #ccc !important; } +.xhs-progress-stats span { + white-space: nowrap !important; +} + .xhs-progress-tip { text-align: center !important; font-size: 11px !important; diff --git a/content.js b/content.js index a314ba2..1f064c5 100644 --- a/content.js +++ b/content.js @@ -381,6 +381,67 @@ class XHSNoteSaver { return urlMatch ? urlMatch[1] : window.location.href; } + // 从任意 URL 中提取笔记 ID(兼容 /explore/、/search_result/ 等路径) + extractNoteIdFromUrl(url) { + if (!url) return ''; + try { + const m = String(url).match(/\/(?:explore|search_result|discovery\/item)\/([a-f0-9]{16,})/); + return m ? m[1] : ''; + } catch (error) { + return ''; + } + } + + // 从列表页的笔记元素上取笔记 ID(元素本身或其内部链接) + extractNoteIdFromElement(noteEl) { + if (!noteEl) return ''; + try { + const ownHref = noteEl.getAttribute ? noteEl.getAttribute('href') : ''; + const fromOwn = this.extractNoteIdFromUrl(ownHref || ''); + if (fromOwn) return fromOwn; + const anchor = noteEl.querySelector + ? noteEl.querySelector('a[href*="/explore/"], a[href*="/search_result/"]') + : null; + return anchor ? this.extractNoteIdFromUrl(anchor.getAttribute('href')) : ''; + } catch (error) { + return ''; + } + } + + // 读取「已评论笔记」索引(跨会话持久,去重核心) + async loadCommentedNoteIds() { + if (this._commentedIds) return this._commentedIds; + try { + const result = await chrome.storage.local.get(['commentedNotes']); + const map = result.commentedNotes; + this._commentedIds = (map && typeof map === 'object') ? map : {}; + } catch (error) { + console.error('读取已评论记录失败:', error); + this._commentedIds = {}; + } + return this._commentedIds; + } + + // 标记一条笔记为「已评论」(成功评论后调用) + async markNoteCommented(noteId) { + if (!noteId) return; + try { + const map = await this.loadCommentedNoteIds(); + map[noteId] = Date.now(); + // 控制体积:最多保留 5000 条,超出淘汰最旧的 + const keys = Object.keys(map); + if (keys.length > 5000) { + keys.sort((a, b) => (map[a] || 0) - (map[b] || 0)) + .slice(0, keys.length - 5000) + .forEach((k) => delete map[k]); + } + this._commentedIds = map; + await chrome.storage.local.set({ commentedNotes: map }); + } catch (error) { + console.error('记录已评论笔记失败:', error); + } + } + async checkIfSaved() { try { const result = await chrome.storage.local.get(['savedNotes']); @@ -824,8 +885,12 @@ class XHSNoteSaver { let totalFound = 0; let consecutiveFailures = 0; // 连续失败计数 const maxConsecutiveFailures = 5; // 最大连续失败次数 + this.skippedCommented = 0; // 本次运行中因「已评论过」跳过的数量 try { + // 预加载已评论索引:评过的笔记一律不再评(跨关键词、跨会话去重) + this.commentedIds = await this.loadCommentedNoteIds(); + while (hasMoreNotes && this.isAutoClicking) { // 检查是否被停止 if (!this.isAutoClicking) { @@ -867,6 +932,19 @@ class XHSNoteSaver { continue; } + // 已评论过的笔记直接跳过(去重核心:评过就不能再评) + const dedupId = this.extractNoteIdFromElement(note); + if (dedupId && this.commentedIds && this.commentedIds[dedupId]) { + note.dataset.clicked = 'true'; + this.skippedCommented++; + if (this.skippedCommented === 1 || this.skippedCommented % 5 === 0) { + this.showToast(`已跳过 ${this.skippedCommented} 个已评论过的笔记`); + } + console.log(`该笔记已评论过,跳过: ${dedupId}(累计跳过 ${this.skippedCommented} 个)`); + this.updateProgress(totalClicked, totalFound); + continue; + } + // 检查是否正在处理 if (this.processingNotes.has(note)) { continue; @@ -1201,6 +1279,7 @@ class XHSNoteSaver {
已点击: 0 + 已跳过已评论: 0 总计: 0 内存: --
@@ -1418,10 +1497,12 @@ class XHSNoteSaver { updateProgress(clicked, total) { const clickedElement = document.getElementById('xhs-clicked-count'); const totalElement = document.getElementById('xhs-total-count'); + const skippedElement = document.getElementById('xhs-skipped-count'); const fillElement = document.querySelector('.xhs-progress-fill'); if (clickedElement) clickedElement.textContent = `已点击: ${clicked}`; if (totalElement) totalElement.textContent = `总计: ${total}`; + if (skippedElement) skippedElement.textContent = `已跳过已评论: ${this.skippedCommented || 0}`; if (fillElement && total > 0) { const percentage = Math.min((clicked / total) * 100, 100); @@ -1567,6 +1648,19 @@ class XHSNoteSaver { return; } + // 去重兜底:当前笔记若已评论过,直接跳过(覆盖列表之外的入口,如手动打开) + try { + const currentNoteId = this.extractNoteId(); + const idMap = await this.loadCommentedNoteIds(); + if (currentNoteId && idMap && idMap[currentNoteId]) { + console.log('该笔记已评论过,跳过评论:', currentNoteId); + this.showToast('该笔记已评论过,自动跳过'); + return; + } + } catch (error) { + console.log('去重检查失败(不阻断):', error); + } + try { // 1. 等待评论框出现 await this.waitForCommentBox(); @@ -1926,6 +2020,12 @@ class XHSNoteSaver { if (logs.length > 300) logs.length = 300; // 最多保留300条 await chrome.storage.local.set({ commentLogs: logs }); console.log('已记录评论:', record.title, record.comment); + + // 去重核心:把该笔记写入「已评论索引」,之后任何时候都不再重复评论 + const commentedId = this.extractNoteIdFromUrl(url) || this.extractNoteId(); + if (commentedId && !String(commentedId).startsWith('http')) { + await this.markNoteCommented(commentedId); + } } catch (error) { console.error('记录评论失败:', error); } diff --git a/manifest.json b/manifest.json index f887040..25ab1c0 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "小红书自动评论助手", - "version": "1.8.2", + "version": "1.8.3", "description": "右侧侧边栏常驻的自动评论助手,支持试用/卡密授权、固定池/自定义文字/图片/AI评论", "permissions": [ "storage", diff --git a/privacy.html b/privacy.html index e7a5f19..13150ec 100644 --- a/privacy.html +++ b/privacy.html @@ -16,7 +16,7 @@

小红书自动评论助手 · 隐私政策

-
生效日期:2026年10月10日 · 适用版本:v1.8.2 及以上
+
生效日期:2026年10月10日 · 适用版本:v1.8.3 及以上

感谢使用「小红书自动评论助手」浏览器扩展(以下简称"本插件")。我们重视你的隐私。本页面说明本插件收集哪些信息、如何使用以及如何保护。

diff --git a/sidepanel.html b/sidepanel.html index 0e32792..d971235 100644 --- a/sidepanel.html +++ b/sidepanel.html @@ -171,6 +171,7 @@ 0 今日 0 本月 0 总计 + 0 已去重
暂无评论记录,成功评论后会自动出现在这里
@@ -203,7 +204,7 @@ diff --git a/sidepanel.js b/sidepanel.js index 1f99597..3967d99 100644 --- a/sidepanel.js +++ b/sidepanel.js @@ -994,8 +994,14 @@ class AutoCommentManager { // 从存储加载评论记录 async loadCommentLogs() { try { - const result = await chrome.storage.local.get(['commentLogs']); + const result = await chrome.storage.local.get(['commentLogs', 'commentedNotes']); this.renderCommentLogs(result.commentLogs || []); + // 已去重统计:这些笔记之后都会自动跳过,不再重复评论 + const dedupEl = document.getElementById('stat-dedup'); + if (dedupEl) { + const map = result.commentedNotes; + dedupEl.textContent = (map && typeof map === 'object') ? Object.keys(map).length : 0; + } } catch (error) { console.error('加载评论记录失败:', error); }