feat: 评论去重——评过的笔记自动跳过,不再重复评论 v1.8.3

成功评论后把笔记 ID 写入本机「已评论索引」(上限5000条,自动淘汰最旧),列表流程与直接打开笔记页两条路径评论前都查索引,命中即跳过;进度条新增「已跳过已评论」实时统计,侧边栏评论记录新增「已去重」;进度条统计行加 flex-wrap+nowrap 防窄窗口文字挤压。
This commit is contained in:
personal82555 committed 2026-10-20 20:35:12 +08:00
1 parent 51fd1bc1b4
commit 63ebd51518
8 files changed
+126 -8

No files matched your search

+8 -3
View File
@@ -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/平台-Chrome%20%7C%20Edge-4caf50.svg)](#安装方法)
[![语言](https://img.shields.io/badge/语言-纯%20JavaScript-2196f3.svg)](#开发说明) [![语言](https://img.shields.io/badge/语言-纯%20JavaScript-2196f3.svg)](#开发说明)
@@ -104,10 +104,15 @@
## 📜 版本历史 ## 📜 版本历史
当前版本:**1.8.2** 当前版本:**1.8.3**
<details> <details>
<summary>展开查看完整变更记录(v1.2.0 ~ v1.8.2)</summary> <summary>展开查看完整变更记录(v1.2.0 ~ v1.8.3)</summary>
### 1.8.3
- **评论去重:评过的笔记不再重复评论**。每次成功评论后把笔记 ID 写入「已评论索引」(本机保存,最多 5000 条),之后任何时候再刷到同一篇(同关键词、不同关键词、换个时间重新跑都一样)都会**自动跳过**,不会给同一篇笔记发第二条评论——避免重复评论触发平台风控限流
- 运行时进度条新增「**已跳过已评论**」一栏,实时显示本轮跳过了几篇;侧边栏评论记录统计新增「**已去重**」,显示累计跳过的笔记总数
- 即使不是从列表进入(比如直接打开某篇笔记页),评论前也会先查索引,已评过就直接跳过
### 1.8.2 ### 1.8.2
- **未授权时显示机器码**:卡密输入框下方常驻展示本机机器码 + 一键复制,去商城购买或手动激活时直接粘贴,不用再翻文档问客服怎么找 - **未授权时显示机器码**:卡密输入框下方常驻展示本机机器码 + 一键复制,去商城购买或手动激活时直接粘贴,不用再翻文档问客服怎么找
+1 -1
View File
@@ -16,7 +16,7 @@ const DEFAULT_AI = {
const AUTH_BASE = 'https://auth.88531.cn'; const AUTH_BASE = 'https://auth.88531.cn';
const AUTH_PROJECT_ID = 3; const AUTH_PROJECT_ID = 3;
const AUTH_API_KEY = 'ak_bbf83b76df8219388d7f60b9e67c7db2'; 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_MS = 72 * 60 * 60 * 1000; // 试用 72 小时(3 天)
const TRIAL_QUOTA = 100; // 试用期最多 100 条评论(时间/条数先到先停,防止白嫖薅量) const TRIAL_QUOTA = 100; // 试用期最多 100 条评论(时间/条数先到先停,防止白嫖薅量)
const GRACE_DAILY = 3; // 试用到期不硬锁:每天保留 3 条体验额度 const GRACE_DAILY = 3; // 试用到期不硬锁:每天保留 3 条体验额度
+6
View File
@@ -203,10 +203,16 @@
.xhs-progress-stats { .xhs-progress-stats {
display: flex !important; display: flex !important;
justify-content: space-between !important; justify-content: space-between !important;
flex-wrap: wrap !important;
gap: 4px 12px !important;
font-size: 12px !important; font-size: 12px !important;
color: #ccc !important; color: #ccc !important;
} }
.xhs-progress-stats span {
white-space: nowrap !important;
}
.xhs-progress-tip { .xhs-progress-tip {
text-align: center !important; text-align: center !important;
font-size: 11px !important; font-size: 11px !important;
+100
View File
@@ -381,6 +381,67 @@ class XHSNoteSaver {
return urlMatch ? urlMatch[1] : window.location.href; 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() { async checkIfSaved() {
try { try {
const result = await chrome.storage.local.get(['savedNotes']); const result = await chrome.storage.local.get(['savedNotes']);
@@ -824,8 +885,12 @@ class XHSNoteSaver {
let totalFound = 0; let totalFound = 0;
let consecutiveFailures = 0; // 连续失败计数 let consecutiveFailures = 0; // 连续失败计数
const maxConsecutiveFailures = 5; // 最大连续失败次数 const maxConsecutiveFailures = 5; // 最大连续失败次数
this.skippedCommented = 0; // 本次运行中因「已评论过」跳过的数量
try { try {
// 预加载已评论索引:评过的笔记一律不再评(跨关键词、跨会话去重)
this.commentedIds = await this.loadCommentedNoteIds();
while (hasMoreNotes && this.isAutoClicking) { while (hasMoreNotes && this.isAutoClicking) {
// 检查是否被停止 // 检查是否被停止
if (!this.isAutoClicking) { if (!this.isAutoClicking) {
@@ -867,6 +932,19 @@ class XHSNoteSaver {
continue; 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)) { if (this.processingNotes.has(note)) {
continue; continue;
@@ -1201,6 +1279,7 @@ class XHSNoteSaver {
</div> </div>
<div class="xhs-progress-stats"> <div class="xhs-progress-stats">
<span id="xhs-clicked-count">已点击: 0</span> <span id="xhs-clicked-count">已点击: 0</span>
<span id="xhs-skipped-count">已跳过已评论: 0</span>
<span id="xhs-total-count">总计: 0</span> <span id="xhs-total-count">总计: 0</span>
<span id="xhs-memory-usage">内存: --</span> <span id="xhs-memory-usage">内存: --</span>
</div> </div>
@@ -1418,10 +1497,12 @@ class XHSNoteSaver {
updateProgress(clicked, total) { updateProgress(clicked, total) {
const clickedElement = document.getElementById('xhs-clicked-count'); const clickedElement = document.getElementById('xhs-clicked-count');
const totalElement = document.getElementById('xhs-total-count'); const totalElement = document.getElementById('xhs-total-count');
const skippedElement = document.getElementById('xhs-skipped-count');
const fillElement = document.querySelector('.xhs-progress-fill'); const fillElement = document.querySelector('.xhs-progress-fill');
if (clickedElement) clickedElement.textContent = `已点击: ${clicked}`; if (clickedElement) clickedElement.textContent = `已点击: ${clicked}`;
if (totalElement) totalElement.textContent = `总计: ${total}`; if (totalElement) totalElement.textContent = `总计: ${total}`;
if (skippedElement) skippedElement.textContent = `已跳过已评论: ${this.skippedCommented || 0}`;
if (fillElement && total > 0) { if (fillElement && total > 0) {
const percentage = Math.min((clicked / total) * 100, 100); const percentage = Math.min((clicked / total) * 100, 100);
@@ -1567,6 +1648,19 @@ class XHSNoteSaver {
return; 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 { try {
// 1. 等待评论框出现 // 1. 等待评论框出现
await this.waitForCommentBox(); await this.waitForCommentBox();
@@ -1926,6 +2020,12 @@ class XHSNoteSaver {
if (logs.length > 300) logs.length = 300; // 最多保留300条 if (logs.length > 300) logs.length = 300; // 最多保留300条
await chrome.storage.local.set({ commentLogs: logs }); await chrome.storage.local.set({ commentLogs: logs });
console.log('已记录评论:', record.title, record.comment); 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) { } catch (error) {
console.error('记录评论失败:', error); console.error('记录评论失败:', error);
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "小红书自动评论助手", "name": "小红书自动评论助手",
"version": "1.8.2", "version": "1.8.3",
"description": "右侧侧边栏常驻的自动评论助手,支持试用/卡密授权、固定池/自定义文字/图片/AI评论", "description": "右侧侧边栏常驻的自动评论助手,支持试用/卡密授权、固定池/自定义文字/图片/AI评论",
"permissions": [ "permissions": [
"storage", "storage",
+1 -1
View File
@@ -16,7 +16,7 @@
</head> </head>
<body> <body>
<h1>小红书自动评论助手 · 隐私政策</h1> <h1>小红书自动评论助手 · 隐私政策</h1>
<div class="meta">生效日期:2026年10月10日 · 适用版本:v1.8.2 及以上</div> <div class="meta">生效日期:2026年10月10日 · 适用版本:v1.8.3 及以上</div>
<p>感谢使用「小红书自动评论助手」浏览器扩展(以下简称"本插件")。我们重视你的隐私。本页面说明本插件收集哪些信息、如何使用以及如何保护。</p> <p>感谢使用「小红书自动评论助手」浏览器扩展(以下简称"本插件")。我们重视你的隐私。本页面说明本插件收集哪些信息、如何使用以及如何保护。</p>
+2 -1
View File
@@ -171,6 +171,7 @@
<span class="log-stat"><strong id="stat-today">0</strong> 今日</span> <span class="log-stat"><strong id="stat-today">0</strong> 今日</span>
<span class="log-stat"><strong id="stat-month">0</strong> 本月</span> <span class="log-stat"><strong id="stat-month">0</strong> 本月</span>
<span class="log-stat"><strong id="stat-total">0</strong> 总计</span> <span class="log-stat"><strong id="stat-total">0</strong> 总计</span>
<span class="log-stat" title="已评论过的笔记会自动跳过,不再重复评论"><strong id="stat-dedup">0</strong> 已去重</span>
</div> </div>
<div id="log-list" class="log-list"> <div id="log-list" class="log-list">
<div class="log-empty">暂无评论记录,成功评论后会自动出现在这里</div> <div class="log-empty">暂无评论记录,成功评论后会自动出现在这里</div>
@@ -203,7 +204,7 @@
<div class="footer"> <div class="footer">
<div class="version-info"> <div class="version-info">
<span>版本: 1.8.2</span> <span>版本: 1.8.3</span>
<span>侧边栏常驻 · 专注于自动评论</span> <span>侧边栏常驻 · 专注于自动评论</span>
</div> </div>
</div> </div>
+7 -1
View File
@@ -994,8 +994,14 @@ class AutoCommentManager {
// 从存储加载评论记录 // 从存储加载评论记录
async loadCommentLogs() { async loadCommentLogs() {
try { try {
const result = await chrome.storage.local.get(['commentLogs']); const result = await chrome.storage.local.get(['commentLogs', 'commentedNotes']);
this.renderCommentLogs(result.commentLogs || []); 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) { } catch (error) {
console.error('加载评论记录失败:', error); console.error('加载评论记录失败:', error);
} }