5 Commits
Author SHA1 Message Date
personal82555 f2af2955be feat: 侧边栏标题旁显示版本号徽章(manifest 动态读取)v1.8.5
顶部标题右侧新增圆角版本徽章(如 v1.8.5),页脚版本同步;两处均 chrome.runtime.getManifest() 动态填充,后续升版只改 manifest 一处,杜绝版本号不一致。
2026-10-21 00:07:12 +08:00
personal82555 5be9027fe7 feat: 支付成功页直接显示卡密(激活失败也显示+手动指引)v1.8.4
购买页轮询到服务端发卡即渲染卡密框:等宽大字 + 一键复制(clipboard API + execCommand 降级)。自动激活成功显示「已自动激活本机」;失败显示「自动激活未生效」并指引粘贴卡密到侧边栏手动激活——付了钱拿不到卡密、只能找客服的情况不再出现。
2026-10-20 22:17:52 +08:00
personal82555 98170e6d8a feat: 评论去重——评过的笔记自动跳过,不再重复评论 v1.8.3
成功评论后把笔记 ID 写入本机「已评论索引」(上限5000条,自动淘汰最旧),列表流程与直接打开笔记页两条路径评论前都查索引,命中即跳过;进度条新增「已跳过已评论」实时统计,侧边栏评论记录新增「已去重」;进度条统计行加 flex-wrap+nowrap 防窄窗口文字挤压。
2026-10-20 20:35:12 +08:00
personal82555 51fd1bc1b4 feat: 未授权时在卡密输入框下方显示本机机器码(带一键复制)v1.8.2
解决用户找不到机器码的问题:去商城购买/手动激活/联系客服都需要提供机器码,现在未授权(试用/体验/异常)状态直接显示并可一键复制;已授权用户不显示,避免界面杂乱。
2026-10-20 19:41:22 +08:00
personal82555 69b110ec78 feat: 订单轮询 12 小时自动停止,避免永久占用后台任务 v1.8.1
对齐 B站评论助手 v1.10.0 的既有行为:超过 12 小时未完成支付的订单停止轮询并清理 pendingOrder。
2026-10-20 19:19:55 +08:00
12 changed files with 374 additions and 24 deletions

No files matched your search

+21 -3
View File
@@ -2,7 +2,7 @@
> 一键搜索 · 自动点击 · 智能评论 —— 小红书运营引流的效率神器
[![版本](https://img.shields.io/badge/版本-1.8.0-ff2442.svg)](https://dl.88531.cn/xhs.html)
[![版本](https://img.shields.io/badge/版本-1.8.5-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,28 @@
## 📜 版本历史
当前版本:**1.8.0**
当前版本:**1.8.5**
<details>
<summary>展开查看完整变更记录(v1.2.0 ~ v1.8.0)</summary>
<summary>展开查看完整变更记录(v1.2.0 ~ v1.8.5)</summary>
### 1.8.5
- **标题旁显示版本号**:侧边栏顶部标题右侧新增版本徽章(如 `v1.8.5`),一眼确认装的是哪一版;页脚版本同步显示。徽章与页脚均**从 manifest 动态读取**,以后升版只需改 manifest 一处,不会再出现两处版本号不一致
### 1.8.4
- **支付成功页直接显示卡密**:只要服务端发了卡就把卡密明文展示在购买页(等宽大字 + 一键复制),**自动激活失败也照常显示**,并附手动激活指引(侧边栏「输入卡密」粘贴 → 激活)——付了钱拿不到卡密、只能找客服的情况不会再出现
- 打包结构修正:manifest.json 直接位于压缩包根目录,解压后选中同名文件夹即可加载(此前需进内层文件夹,Windows 用户按直觉选外层会报「清单文件缺失或不可读取」)
### 1.8.3
- **评论去重:评过的笔记不再重复评论**。每次成功评论后把笔记 ID 写入「已评论索引」(本机保存,最多 5000 条),之后任何时候再刷到同一篇(同关键词、不同关键词、换个时间重新跑都一样)都会**自动跳过**,不会给同一篇笔记发第二条评论——避免重复评论触发平台风控限流
- 运行时进度条新增「**已跳过已评论**」一栏,实时显示本轮跳过了几篇;侧边栏评论记录统计新增「**已去重**」,显示累计跳过的笔记总数
- 即使不是从列表进入(比如直接打开某篇笔记页),评论前也会先查索引,已评过就直接跳过
### 1.8.2
- **未授权时显示机器码**:卡密输入框下方常驻展示本机机器码 + 一键复制,去商城购买或手动激活时直接粘贴,不用再翻文档问客服怎么找
### 1.8.1
- **订单轮询 12 小时自动停止**:超过 12 小时仍未完成支付的订单不再后台追问,避免永久占用后台定时任务(与 B站评论助手行为一致)
### 1.8.0
- **购买流程重做**:点「购买授权」不再原地展开支付方式,而是**打开新页面** → ① 展示本项目全部在售套餐供选择 → ② 选套餐后才出现支付方式(支付宝/微信)→ ③ 下单支付,支付成功**自动发卡并激活本机**
+7 -1
View File
@@ -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.0';
const CLIENT_VERSION = '1.8.5';
const TRIAL_MS = 72 * 60 * 60 * 1000; // 试用 72 小时(3 天)
const TRIAL_QUOTA = 100; // 试用期最多 100 条评论(时间/条数先到先停,防止白嫖薅量)
const GRACE_DAILY = 3; // 试用到期不硬锁:每天保留 3 条体验额度
@@ -541,6 +541,12 @@ async function pollPendingOrder() {
try { await chrome.alarms.clear('orderPoll'); } catch (e) { /* 无该 alarm */ }
return;
}
// 超过 12 小时未完成就不再追问,避免永久轮询(与 B站评论助手行为一致)
if (Date.now() - (Number(pendingOrder.createdAt) || 0) > 12 * 3600 * 1000) {
await chrome.storage.local.remove(['pendingOrder']);
try { await chrome.alarms.clear('orderPoll'); } catch (e) { /* 无该 alarm */ }
return;
}
try {
const result = await authQueryOrder(pendingOrder.orderNo);
if (result && result.activated) {
+64
View File
@@ -254,3 +254,67 @@ h2 {
color: #ff2442;
text-decoration: none;
}
/* 支付成功卡密展示区:无论自动激活成功与否都显示,用户可随时复制 */
.card-box {
margin-top: 14px;
border: 2px dashed #ff2442;
border-radius: 8px;
padding: 14px;
background: #fff5f6;
}
.card-label {
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
.card-row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.card-key {
flex: 1;
min-width: 240px;
font-family: Consolas, 'Courier New', monospace;
font-size: 16px;
font-weight: 700;
color: #1a1a1a;
background: #fff;
border: 1px solid #f0c0c6;
border-radius: 6px;
padding: 10px 12px;
user-select: all;
word-break: break-all;
}
.copy-btn {
padding: 10px 18px;
border: none;
border-radius: 6px;
background: #ff2442;
color: #fff;
font-size: 14px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
}
.copy-btn:hover {
background: #e01f3a;
}
.card-hint {
margin-top: 8px;
font-size: 12px;
color: #999;
}
.ok-line.warn {
color: #c62828;
font-weight: 600;
}
+8
View File
@@ -33,6 +33,14 @@
<section id="order-panel" hidden>
<h2><span class="num">3</span> 订单状态</h2>
<div id="order-status" class="order-status">等待创建订单…</div>
<div id="card-box" class="card-box" hidden>
<div class="card-label">您的卡密(请保存好,可用于换机 / 手动激活)</div>
<div class="card-row">
<code id="card-key" class="card-key">…</code>
<button id="copy-card" class="copy-btn">复制卡密</button>
</div>
<div class="card-hint" id="card-hint"></div>
</div>
</section>
</main>
+45 -15
View File
@@ -102,16 +102,11 @@ function startPolling() {
try {
const res = await chrome.runtime.sendMessage({ action: 'authQueryOrder', orderNo });
if (!res || !res.ok) return;
if (res.activated) {
if (res.cardKey) {
// 只要服务端已发卡就把卡密显示出来——哪怕自动激活失败,用户也能自己复制激活
clearInterval(pollTimer);
pollTimer = null;
showActivated(res);
} else if (res.cardKey && !res.activated) {
clearInterval(pollTimer);
pollTimer = null;
const status = $('order-status');
status.className = 'order-status error';
status.textContent = '✗ 已支付但激活失败:' + ((res.licenseState && res.licenseState.message) || '请把订单号发给客服手动处理');
showCard(res);
} else {
const status = $('order-status');
status.className = 'order-status pending';
@@ -123,16 +118,51 @@ function startPolling() {
}, 5000);
}
function showActivated(res) {
// 支付成功:显示卡密(激活成功/失败都显示,激活失败时给出手动激活指引)
function showCard(res) {
const status = $('order-status');
status.className = 'order-status success';
status.innerHTML = `
<div class="ok-title">✓ 支付成功,已自动激活</div>
<div class="ok-line">卡密:<b>${esc(res.cardKey || '')}</b></div>
<div class="ok-line">到期时间:${esc(res.expireTime || '永久')}</div>
<div class="ok-line muted">回到插件侧边栏即可使用全部功能(本机已授权)。</div>`;
const ok = !!res.activated;
status.className = 'order-status ' + (ok ? 'success' : 'error');
status.innerHTML = ok
? `<div class="ok-title">✓ 支付成功,已自动激活本机</div>
<div class="ok-line">到期时间:${esc(res.expireTime || '永久')}</div>
<div class="ok-line muted">回到插件侧边栏即可使用全部功能。</div>`
: `<div class="ok-title">✓ 支付成功</div>
<div class="ok-line warn">自动激活未生效${(res.licenseState && res.licenseState.message) ? ':' + esc(res.licenseState.message) : ''}</div>
<div class="ok-line muted">请复制下面的卡密,回到插件侧边栏「输入卡密」框粘贴 → 点「激活」即可。</div>`;
$('card-key').textContent = res.cardKey || '';
$('card-hint').textContent = ok
? '卡密已随本机自动激活,无需手动输入;保存好可换机或重装后使用。'
: '本页不会关闭也不会失效,卡密就在这里,随时可以复制。';
$('card-box').hidden = false;
$('card-box').scrollIntoView({ behavior: 'smooth', block: 'center' });
}
// 复制卡密(剪贴板 API 失败时降级 execCommand)
$('copy-card').addEventListener('click', async () => {
const key = ($('card-key').textContent || '').trim();
if (!key) return;
let ok = false;
try {
await navigator.clipboard.writeText(key);
ok = true;
} catch (e) { /* 降级 */ }
if (!ok) {
try {
const ta = document.createElement('textarea');
ta.value = key;
ta.style.cssText = 'position:fixed;left:-9999px';
document.body.appendChild(ta);
ta.select();
ok = document.execCommand('copy');
ta.remove();
} catch (e) { ok = false; }
}
$('copy-card').textContent = ok ? '✓ 已复制' : '复制失败,请手动选中卡密复制';
setTimeout(() => { $('copy-card').textContent = '复制卡密'; }, 2500);
});
document.querySelectorAll('.pay-btn').forEach((btn) => {
btn.addEventListener('click', () => createOrder(btn.dataset.pay));
});
+6
View File
@@ -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;
+100
View File
@@ -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);
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "小红书自动评论助手",
"version": "1.8.0",
"version": "1.8.5",
"description": "右侧侧边栏常驻的自动评论助手,支持试用/卡密授权、固定池/自定义文字/图片/AI评论",
"permissions": [
"storage",
+1 -1
View File
@@ -16,7 +16,7 @@
</head>
<body>
<h1>小红书自动评论助手 · 隐私政策</h1>
<div class="meta">生效日期:2026年10月10日 · 适用版本:v1.8.0 及以上</div>
<div class="meta">生效日期:2026年10月10日 · 适用版本:v1.8.5 及以上</div>
<p>感谢使用「小红书自动评论助手」浏览器扩展(以下简称"本插件")。我们重视你的隐私。本页面说明本插件收集哪些信息、如何使用以及如何保护。</p>
+53
View File
@@ -30,6 +30,19 @@ body {
font-weight: 600;
}
.header h1 .ver-badge {
display: inline-block;
margin-left: 4px;
padding: 1px 8px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.28);
color: #fff;
font-size: 11px;
font-weight: 700;
vertical-align: middle;
letter-spacing: 0.3px;
}
.search-section {
padding: 16px;
border-bottom: 1px solid #eee;
@@ -894,6 +907,46 @@ body {
background: linear-gradient(135deg, #f57c00, #ff9800);
}
.machine-code-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 6px;
font-size: 11px;
color: #999;
}
.machine-code-label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.machine-code-label code {
font-family: "SF Mono", Consolas, "Courier New", monospace;
color: #666;
user-select: all;
}
.machine-code-copy {
flex: none;
padding: 2px 8px;
border: 1px solid #ddd;
border-radius: 4px;
background: #fff;
color: #888;
font-size: 11px;
cursor: pointer;
transition: all 0.2s ease;
}
.machine-code-copy:hover {
border-color: #bbb;
color: #444;
}
.buy-hint {
margin-top: 8px;
font-size: 12px;
+7 -2
View File
@@ -9,7 +9,7 @@
<body>
<div class="container">
<div class="header">
<h1>🤖 小红书自动评论助手</h1>
<h1>🤖 小红书自动评论助手 <span class="ver-badge" id="ver-badge"></span></h1>
</div>
<div class="auth-section">
@@ -22,6 +22,10 @@
<button id="activate-btn" class="activate-btn">激活</button>
<button id="buy-btn" class="buy-btn">购买授权</button>
</div>
<div class="machine-code-row" id="machine-code-row" hidden>
<span class="machine-code-label">机器码 <code id="machine-code-text"></code></span>
<button type="button" id="machine-code-copy" class="machine-code-copy">复制</button>
</div>
<div class="coupon-bar" id="coupon-bar" hidden>
<span class="coupon-text">⏰ 试用到期限时优惠:码 <b id="coupon-code">XHS24H</b> · <span id="coupon-percent">8 折</span> · 剩余 <b id="coupon-left">--:--:--</b></span>
<button type="button" id="coupon-copy-btn" class="coupon-copy-btn">复制</button>
@@ -167,6 +171,7 @@
<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-total">0</strong> 总计</span>
<span class="log-stat" title="已评论过的笔记会自动跳过,不再重复评论"><strong id="stat-dedup">0</strong> 已去重</span>
</div>
<div id="log-list" class="log-list">
<div class="log-empty">暂无评论记录,成功评论后会自动出现在这里</div>
@@ -199,7 +204,7 @@
<div class="footer">
<div class="version-info">
<span>版本: 1.8.0</span>
<span id="footer-ver">版本: --</span>
<span>侧边栏常驻 · 专注于自动评论</span>
</div>
</div>
+61 -1
View File
@@ -644,6 +644,27 @@ class AutoCommentManager {
});
}
// 机器码一键复制(未授权时展示在卡密输入框下方)
const machineCopyBtn = document.getElementById('machine-code-copy');
if (machineCopyBtn) {
machineCopyBtn.addEventListener('click', async () => {
const codeEl = document.getElementById('machine-code-text');
const code = (codeEl ? codeEl.textContent : '').trim();
if (!code) return;
try {
await navigator.clipboard.writeText(code);
} catch (error) {
const ta = document.createElement('textarea');
ta.value = code;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
}
this.showActionStatus(true, '✓ 机器码已复制,去商城购买或手动激活时粘贴即可');
});
}
// 后台复验或购买激活后自动刷新
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace !== 'local') return;
@@ -695,6 +716,9 @@ class AutoCommentManager {
detail.textContent = '授权状态异常,请重新打开侧边栏';
this.hideCouponBar();
}
// 未授权(试用/体验/异常)时在卡密输入框下方展示机器码,便于去商城购买或手动激活时提供
this.renderMachineCode(s.state !== 'authorized');
} catch (error) {
console.error('获取授权状态失败:', error);
badge.classList.add('locked');
@@ -703,6 +727,28 @@ class AutoCommentManager {
}
}
// 机器码展示(未授权时显示在卡密输入框下方,可一键复制)
async renderMachineCode(show) {
const row = document.getElementById('machine-code-row');
const text = document.getElementById('machine-code-text');
if (!row || !text) return;
if (!show) {
row.hidden = true;
return;
}
try {
const { deviceId } = await chrome.storage.local.get(['deviceId']);
if (!deviceId) {
row.hidden = true;
return;
}
text.textContent = deviceId;
row.hidden = false;
} catch (error) {
row.hidden = true;
}
}
// 试用倒计时(精确到秒,每秒刷新)
startTrialCountdown(endAt, status) {
this.stopTrialCountdown();
@@ -948,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);
}
@@ -1242,5 +1294,13 @@ class AutoCommentManager {
// 初始化
document.addEventListener('DOMContentLoaded', () => {
// 标题旁徽章 + 页脚版本统一从 manifest 读取:升版只改 manifest 一处,不会再漏
try {
const v = chrome.runtime.getManifest().version;
const badge = document.getElementById('ver-badge');
if (badge) badge.textContent = 'v' + v;
const fv = document.getElementById('footer-ver');
if (fv) fv.textContent = '版本: ' + v;
} catch (e) { /* 版本显示失败不阻断初始化 */ }
new AutoCommentManager();
});