diff --git a/CHANGELOG.md b/CHANGELOG.md index c720233..eec454f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ B站评论助手 版本历史。下载:https://dl.88531.cn/bili.html ·  --- +## v1.9.0 · 2026-10-14 + +### ✨ 新功能 +- **试用策略改为 3 天 / 30 条评论,任一先到即止**:试用时长从 1 天延长到 3 天,同时设置 30 条评论上限。额度用完或时间到期,哪个先到就停哪个 +- **价值时刻提示**:每批跑完直接显示「本批 N 条已发布,平均间隔 X 秒」,并同步刷新顶部横幅的剩余条数——让用户亲眼看到成果的同时看到还剩多少 +- **到期后 24 小时折扣窗口**:试用结束后 24 小时内,面板横幅显示「24 小时内购买享 8 折」并倒计时;窗口关闭后恢复原价 +- **过期只锁评论**:试用到期后仅锁定自动评论,搜索视频、AI 生成文案、下载当前页视频继续可用,用户不会被彻底踢走 + +### 🔧 调整 +- 顶部横幅文案:试用中显示「剩余 X 时 · 还可评论 N 条」;额度用完与时间到期分别给出不同提示 +- 试用计数只在未授权状态下累加,已激活用户不受任何影响 +- 官网与 README 同步更新试用规则、折扣窗口说明 + +--- + ## v1.8.1 · 2026-10-13 ### 🐛 修复 diff --git a/README.md b/README.md index 902f70f..20eba1c 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Chrome/Edge 浏览器插件(Manifest V3):按**搜索关键词**找 B站视 - ✅ 输出真正的 mp4(Blob 带 MIME + 文件头魔数校验,CDN 回网页/错误页直接报错,不会存成 txt) ### 授权与运维 -- 🔑 1 天免费试用(倒计时精确到秒),卡密激活绑定本机永久可用;**下载视频不受授权限制** +- 🔑 3 天免费试用 / 30 条评论(任一先到即止,倒计时精确到秒并显示剩余条数),卡密激活绑定本机永久可用、不限量;试用到期后 24 小时内购买享 8 折;**过期只锁自动评论,搜索/AI 生成/下载视频不受影响** - 📊 安装统计上报(仅机器码 + 版本,不碰B站账号),后台可看安装数与版本分布 - 📢 公告推送到面板顶部,有新版本时提示并跳官网 - 🔁 可中断:随时 ⏹ 停止,停止标记跨页面刷新保留,发送前二次检查 diff --git a/background.js b/background.js index d4c16bb..647a1f8 100644 --- a/background.js +++ b/background.js @@ -69,7 +69,8 @@ reportInstall(false); // SW 启动即登记一次(同一浏览器只记一条 const AUTH_BASE = "https://auth.88531.cn/api"; const PROJECT_NAME = "B站评论插件"; const PROJECT_ID = 4; -const TRIAL_MS = 24 * 3600 * 1000; // 未授权试用1天 +const TRIAL_MS = 72 * 3600 * 1000; // 未授权试用3天 +const TRIAL_COMMENT_LIMIT = 30; // 试用期内最多30条评论,用完或到期任一先到即止 const CARD_API_KEY = "ak_ab99ed40b2296d6d2db06a4b5692b987"; async function getMachineId() { @@ -124,14 +125,21 @@ async function checkLicense() { offline = true; // 网络/超时 → 放行 } - const st = await chrome.storage.local.get("trialStart"); + const st = await chrome.storage.local.get(["trialStart", "trialComments"]); let trialStart = Number(st.trialStart) || 0; if (!trialStart) { trialStart = Date.now(); await chrome.storage.local.set({ trialStart: trialStart }); } const trialEnd = trialStart + TRIAL_MS; - const leftMs = Math.max(0, trialEnd - Date.now()); + const now = Date.now(); + const leftMs = Math.max(0, trialEnd - now); + const usedComments = Number(st.trialComments) || 0; + const leftComments = Math.max(0, TRIAL_COMMENT_LIMIT - usedComments); + // 到期后24小时内购买享8折(折扣窗口从试用到期时刻起算) + const discountEnd = trialEnd + 24 * 3600 * 1000; + const inDiscount = leftMs <= 0 && now < discountEnd; + const discountLeftMs = inDiscount ? discountEnd - now : 0; return { authorized: authorized || offline, // offline 时视为放行 @@ -141,7 +149,12 @@ async function checkLicense() { machine: machine, trialStart: trialStart, trialEnd: trialEnd, - trialLeftMs: leftMs + trialLeftMs: leftMs, + trialCommentsUsed: usedComments, + trialCommentsLimit: TRIAL_COMMENT_LIMIT, + trialCommentsLeft: leftComments, + inDiscount: inDiscount, + discountLeftMs: discountLeftMs }; } @@ -450,11 +463,23 @@ async function sendWithRetry(tabId, msg, tries) { async function handleStartRun(p, sendResponse) { try { - // 授权闸门:未授权且试用到期 → 拦截自动评论(下载视频不走这里,不受影响) + // 授权闸门:未授权时同时检查试用时长与试用条数(下载视频不走这里,不受影响) const lic = await checkLicense(); - if (!lic.authorized && lic.trialLeftMs <= 0) { - sendResponse({ ok: false, error: "免费试用已到期,自动评论已锁定。购买卡密激活后即可继续使用(下载视频不受影响)" }); - return; + if (!lic.authorized) { + if (lic.trialLeftMs <= 0) { + sendResponse({ ok: false, error: "免费试用已到期,自动评论已锁定。购买卡密激活后即可继续使用(下载视频不受影响)" }); + return; + } + if (lic.trialCommentsLeft <= 0) { + sendResponse({ ok: false, error: "试用期 " + lic.trialCommentsLimit + " 条评论已用完,购买卡密激活后不限量(下载视频不受影响)" }); + return; + } + // 试用中:把剩余额度写进任务配置,内容脚本发到额度会自动停 + p.config = p.config || {}; + p.config.trialQuota = lic.trialCommentsLeft; + if (Number(p.config.maxN) > lic.trialCommentsLeft) { + p.config.maxN = lic.trialCommentsLeft; + } } ACTIVE_TASK = null; // 本次点「开始」= 全新任务,作废残留的旧任务(旧任务会带着预生成的旧文案继续跑) let tab = await findBiliTab(); @@ -604,7 +629,15 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { (async () => { const h = await addRecord({ bvid: msg.bvid, title: msg.title, text: msg.text, keyword: msg.keyword, ts: msg.ts }); const s = statsOf(h); - sendResponse({ ok: true, stats: { total: s.total, today: s.today, week: s.week } }); + // 试用计数:每成功发一条评论消耗一次额度(仅未授权时计数) + let trialCommentsLeft = null; + const lic = await checkLicense(); + if (!lic.realAuthorized && !lic.offline) { + const used = lic.trialCommentsUsed + 1; + await chrome.storage.local.set({ trialComments: used }); + trialCommentsLeft = Math.max(0, lic.trialCommentsLimit - used); + } + sendResponse({ ok: true, stats: { total: s.total, today: s.today, week: s.week }, trialCommentsLeft: trialCommentsLeft }); })(); return true; } diff --git a/content.js b/content.js index 3407626..ab127b0 100644 --- a/content.js +++ b/content.js @@ -855,7 +855,11 @@ function finish(task, ok, reason) { clearTask(); running = false; - relay({ type: "finished", ok: !!ok, reason: reason || "", done: task ? (task.sent || 0) : 0, failed: task ? (task.failed || 0) : 0 }); + let avgGapSec = 0; + if (task && task.firstSentAt && task.lastSentAt && (task.sent || 0) >= 2) { + avgGapSec = Math.round((task.lastSentAt - task.firstSentAt) / ((task.sent || 1) - 1) / 1000); + } + relay({ type: "finished", ok: !!ok, reason: reason || "", done: task ? (task.sent || 0) : 0, failed: task ? (task.failed || 0) : 0, avgGapSec: avgGapSec }); step((ok ? "✅ " : "⏹ ") + (reason || (ok ? "执行完成" : "已停止")), ok ? "ok" : "fail"); } @@ -966,6 +970,11 @@ const doDm = (what === "dm" || what === "both"); if (doComment) { + // 试用额度检查:发到额度就自动停,不用等用户发现 + if (cfg.trialQuota != null && (task.sent || 0) >= cfg.trialQuota) { + finish(task, true, "试用期 " + cfg.trialQuota + " 条额度已用完,购买卡密激活后不限量"); + return; + } step("评论[" + (SRC_LABEL[video.src] || "模板") + "]:" + text.slice(0, 30) + (text.length > 30 ? "…" : "")); const r = await sendOne(video, text, cfg.sendMode !== "api"); @@ -974,6 +983,8 @@ if (r.ok) { step(r.verified ? "✅ 评论成功" : "⚠️ 已发送但未在页面确认,记为已评论", r.verified ? "ok" : "fail"); + if (!task.firstSentAt) task.firstSentAt = Date.now(); + task.lastSentAt = Date.now(); await recordOne(task, video, text, true); } else { step("❌ 失败:" + r.error, "fail"); diff --git a/manifest.json b/manifest.json index 271a4d9..b3eb1af 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "B站评论助手", - "version": "1.8.1", + "version": "1.9.0", "description": "B站网页版关键词评论助手:按搜索关键词找视频,默认模拟真人(打开视频页、滚到评论区、逐字输入、点发布),也可快速API直发;支持下载当前页视频;带防重复记录、可中断执行与页面实时进度浮层。", "permissions": ["storage", "tabs", "scripting", "sidePanel", "downloads", "declarativeNetRequest"], "host_permissions": [ diff --git a/sidepanel.js b/sidepanel.js index f9a5f8a..e9191b8 100644 --- a/sidepanel.js +++ b/sidepanel.js @@ -157,11 +157,25 @@ function refreshCustomVisibility() { sub.textContent = "感谢支持!全部功能可用"; actRow.style.display = "none"; if (buy) buy.style.display = "none"; - } else if (lic.trialLeftMs > 0) { + } else if (lic.trialLeftMs > 0 && lic.trialCommentsLeft > 0) { icon.textContent = "🎁"; - text.textContent = "免费试用中 · 剩余 " + fmtRemain(lic.trialLeftMs); + text.textContent = "免费试用中 · 剩余 " + fmtRemain(lic.trialLeftMs) + " · 还可评论 " + lic.trialCommentsLeft + " 条"; text.style.color = "#e67e22"; - sub.textContent = "未授权可试用 1 天,购买卡密激活后不限时使用"; + sub.textContent = "未授权可试用 3 天 / 30 条评论,购买卡密激活后不限时不限量"; + actRow.style.display = "flex"; + if (buy) buy.style.display = ""; + } else if (lic.trialLeftMs > 0 && lic.trialCommentsLeft <= 0) { + icon.textContent = "🎁"; + text.textContent = "试用期 " + lic.trialCommentsLimit + " 条评论已用完"; + text.style.color = "#e67e22"; + sub.textContent = "剩余时间还有效,但评论额度已用完;购买卡密激活后不限量(下载视频不受影响)"; + actRow.style.display = "flex"; + if (buy) buy.style.display = ""; + } else if (lic.inDiscount) { + icon.textContent = "⏰"; + text.textContent = "试用已到期 · 24 小时内购买享 8 折(剩 " + fmtRemain(lic.discountLeftMs) + ")"; + text.style.color = "#e67e22"; + sub.textContent = "折扣窗口关闭后恢复原价,别错过"; actRow.style.display = "flex"; if (buy) buy.style.display = ""; } else { @@ -180,7 +194,7 @@ function refreshCustomVisibility() { licState.trialLeftMs = Math.max(0, left); if (left <= 0) { renderLicense(); if (licTimer) { clearInterval(licTimer); licTimer = null; } return; } const text = $("licText"); - if (text) text.textContent = "免费试用中 · 剩余 " + fmtRemain(left); + if (text) text.textContent = "免费试用中 · 剩余 " + fmtRemain(left) + " · 还可评论 " + licState.trialCommentsLeft + " 条"; } async function loadLicense() { @@ -517,9 +531,15 @@ function refreshCustomVisibility() { function onFinished(ev) { setRunning(false); if (ev.ok) { - const msg = "执行完成:成功 " + (ev.done || 0) + " 条" + (ev.failed ? ",失败 " + ev.failed + " 条" : ""); + let msg = "本批 " + (ev.done || 0) + " 条已发布"; + if (ev.avgGapSec > 0) msg += ",平均间隔 " + ev.avgGapSec + " 秒"; + if (ev.failed) msg += ",失败 " + ev.failed + " 条"; setStatus(msg); - log("✅ " + msg, "ok"); + log("🎉 " + msg, "ok"); + // 试用中:刷新授权横幅,让用户立刻看到剩余额度 + if (licState && !licState.authorized && !licState.offline) { + loadLicense(); + } } else { const msg = ev.reason || "执行未完成"; setStatus(msg, true);