feat: 评论去重——评过的笔记自动跳过,不再重复评论 v1.8.3
成功评论后把笔记 ID 写入本机「已评论索引」(上限5000条,自动淘汰最旧),列表流程与直接打开笔记页两条路径评论前都查索引,命中即跳过;进度条新增「已跳过已评论」实时统计,侧边栏评论记录新增「已去重」;进度条统计行加 flex-wrap+nowrap 防窄窗口文字挤压。
This commit is contained in:
1 parent
51fd1bc1b4
commit
98170e6d8a
8 files changed
+126
-8
No files matched your search
+100
@@ -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 {
|
||||
</div>
|
||||
<div class="xhs-progress-stats">
|
||||
<span id="xhs-clicked-count">已点击: 0</span>
|
||||
<span id="xhs-skipped-count">已跳过已评论: 0</span>
|
||||
<span id="xhs-total-count">总计: 0</span>
|
||||
<span id="xhs-memory-usage">内存: --</span>
|
||||
</div>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user