Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
226e4fbbf9 | ||
|
|
99d1d1fe92 | ||
|
|
32f48613c4 | ||
|
|
ec8bfda853 | ||
|
|
6a697a2663 | ||
|
|
557f75fc54 |
No files matched your search
@@ -4,6 +4,68 @@ B站评论助手 版本历史。下载:https://dl.88531.cn/bili.html ·
|
||||
|
||||
---
|
||||
|
||||
## v1.10.3 · 2026-10-20
|
||||
|
||||
### 🐛 修复
|
||||
- **买完仍显示试用倒计时**:授权系统的自动发卡只把卡密标成「已使用」并绑定机器码,**从不写入授权表**,而插件原来只查授权表 → 已购用户永远查不到自己的授权、倒计时照旧。现在插件改用**本地记住的卡密直接向卡密系统校验**(`/public/verify`,它读的是卡表),拿到真实授权状态与到期时间
|
||||
- **到期时间显示成「永久」**:购买页原先是 `到期时间 || '永久'` 兜底,而订单接口根本不返回到期时间 → **买 1 天也显示永久**。现在到期时间只在服务端明确给出时才显示,「永久」只在卡类型确为永久时出现
|
||||
|
||||
### ✨ 新功能
|
||||
- 激活成功后插件**永久记住这张卡**(换机绑定或过期时自动清除),不再依赖服务端授权表
|
||||
- 侧边栏已授权状态显示**套餐名 + 真实到期时间**:`已授权 · 1天试用 · 到期 2026-10-21 23:06:36`
|
||||
|
||||
---
|
||||
|
||||
## v1.10.2 · 2026-10-20
|
||||
|
||||
### 🔧 调整
|
||||
- 侧边栏「🤖 没有模型?注册即送额度」**移到 LLM 配置正上方**——原先它卡在授权区和评论记录之间,和 LLM 之间隔着一大块,现在配模型时一眼就能看到注册送额度的入口
|
||||
|
||||
---
|
||||
|
||||
## v1.10.1 · 2026-10-20
|
||||
|
||||
### ✨ 新功能
|
||||
- **支付成功后立刻显示卡密**:只要服务端发出卡,订单状态区马上出现独立的「你的卡密」展示区(大号等宽字体 + 一键复制),**不等自动激活结果**——自动激活失败时用户手里也有码,回侧边栏粘贴即可激活
|
||||
- **「已支付但还没发卡」的明确状态**:订单已是 `paid` 却还没拿到卡密时显示「已支付,正在为你发卡」,超过 90 秒改为「**权益不会丢失**,可凭订单号联系客服补发」,不再无限转在「等待支付中」
|
||||
- 卡密一键复制,剪贴板 API 无权限时自动降级 `execCommand`
|
||||
|
||||
### 🔧 调整
|
||||
- 激活失败时给出可执行路径(卡密展示 + 侧边栏操作步骤 + 订单号),不再只报一句错误
|
||||
- 卡密展示区用 B站蓝虚线框独立成块,`user-select: all` 便于长按全选
|
||||
|
||||
---
|
||||
|
||||
## v1.10.0 · 2026-10-20
|
||||
|
||||
### ✨ 新功能
|
||||
- **新增「当前商品选择页」`buy.html`**:侧边栏「购买授权 →」不再跳外部授权站点,改为在插件内打开新标签页,实时展示授权系统**当前在售的商品**(名称 / 价格 / 时长),选中后进入下一步
|
||||
- **三步购买流程**:选当前商品 → 选支付方式(支付宝 / 微信)→ 完成支付后**自动发卡并激活本机**,全程无需手动输入卡密
|
||||
- **后台订单轮询**(新增 `alarms` 权限):购买页或侧边栏被关掉,后台仍继续轮询,支付成功后自动完成激活;超过 12 小时未完成的订单自动停止轮询,不会永久占用
|
||||
- 订单状态区分「等待支付 / 支付成功 / 激活失败 / 超时」,等待时实时显示已等待秒数,超时给出订单号供人工补发
|
||||
|
||||
### 🔧 调整
|
||||
- 购买入口从 `https://auth.88531.cn` 改为插件内置页 `buy.html`(B站蓝 `#00A1D6` 配色),页脚保留官网 `dl.88531.cn/bili.html`、GitHub、Telegram 入口
|
||||
- 商品列表由授权系统实时下发:显示的就是当前在售商品,下架商品不会出现在页面上
|
||||
- 本版本只调用授权系统的公开读写接口,**不改动服务端任何配置**
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Chrome/Edge 浏览器插件(Manifest V3):按**搜索关键词**找 B站视频,自动**评论**或**私信评论区用户**,也可**下载当前页视频**。直接复用你浏览器里的 B站登录态,**不需要手动填 Cookie**。
|
||||
|
||||
- 官网 / 下载:https://dl.88531.cn/bili.html
|
||||
- 夸克网盘:https://pan.quark.cn/s/95e3ee9d8e0f
|
||||
- **最新版下载**:[GitHub Releases](https://github.com/personal82555/bili-auto-comment/releases/latest) · [Gitee Releases](https://gitee.com/jinghui111/bili-auto-comment/releases) · [夸克网盘](https://pan.quark.cn/s/95e3ee9d8e0f)
|
||||
- 授权中心:https://auth.88531.cn · AI 模型 KEY:https://ai.88531.cn/
|
||||
|
||||
## 功能
|
||||
@@ -39,7 +39,8 @@ Chrome/Edge 浏览器插件(Manifest V3):按**搜索关键词**找 B站视
|
||||
- ✅ 输出真正的 mp4(Blob 带 MIME + 文件头魔数校验,CDN 回网页/错误页直接报错,不会存成 txt)
|
||||
|
||||
### 授权与运维
|
||||
- 🔑 1 天免费试用(倒计时精确到秒),卡密激活绑定本机永久可用;**下载视频不受授权限制**
|
||||
- 🔑 3 天免费试用 / 30 条评论(任一先到即止,倒计时精确到秒并显示剩余条数),卡密激活绑定本机永久可用、不限量;试用到期后 24 小时内购买享 8 折;**过期只锁自动评论,搜索/AI 生成/下载视频不受影响**
|
||||
- 🛒 **内置当前商品选择页**:点「购买授权 →」在插件内打开新标签页,实时展示授权系统当前在售商品 → 选商品 → 选支付方式(支付宝/微信)→ 支付成功**自动发卡并激活本机**;购买页关掉后台仍会轮询完成激活
|
||||
- 📊 安装统计上报(仅机器码 + 版本,不碰B站账号),后台可看安装数与版本分布
|
||||
- 📢 公告推送到面板顶部,有新版本时提示并跳官网
|
||||
- 🔁 可中断:随时 ⏹ 停止,停止标记跨页面刷新保留,发送前二次检查
|
||||
@@ -48,7 +49,7 @@ Chrome/Edge 浏览器插件(Manifest V3):按**搜索关键词**找 B站视
|
||||
|
||||
## 安装
|
||||
|
||||
1. 解压 zip(或直接用解压后的目录)
|
||||
1. 从 [Releases](https://github.com/personal82555/bili-auto-comment/releases/latest) 下载最新 zip 并解压(或直接用解压后的目录)
|
||||
2. 打开 `chrome://extensions` → 打开右上角「开发者模式」
|
||||
3. 「加载已解压的扩展程序」→ 选 `BiliKeywordComment` 目录
|
||||
4. 打开 B站并登录,点工具栏图标打开侧边栏
|
||||
@@ -80,6 +81,7 @@ AI 模式需填 LLM 配置(已预填 `https://ai.88531.cn/v1` + `mimo-v2.6-fla
|
||||
| `background.js` | Service worker:LLM 代理、API 兜底、评论历史、运行编排、授权、安装上报 |
|
||||
| `content.js` / `content.css` | 页面内执行:搜索 → 生成 → 发布/私信 + 进度浮层 + 视频下载取址 |
|
||||
| `sidepanel.html` / `sidepanel.js` | 侧边栏 UI(含视频下载落盘) |
|
||||
| `buy.html` / `buy.css` / `buy.js` | 当前商品选择页(选商品 → 选支付方式 → 下单 → 支付后自动激活本机) |
|
||||
| `icons/` | 16/32/48/128 图标 |
|
||||
|
||||
## 关键技术点(踩坑记录)
|
||||
@@ -100,6 +102,7 @@ AI 模式需填 LLM 配置(已预填 `https://ai.88531.cn/v1` + `mimo-v2.6-fla
|
||||
|
||||
- GitHub:https://github.com/personal82555/bili-auto-comment
|
||||
- Gitee:https://gitee.com/jinghui111/bili-auto-comment
|
||||
- Gitea(自建):http://192.168.68.19:3200/jinghui/bili-auto-comment
|
||||
|
||||
## 免责声明
|
||||
|
||||
|
||||
+216
-10
@@ -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,24 +125,67 @@ async function checkLicense() {
|
||||
offline = true; // 网络/超时 → 放行
|
||||
}
|
||||
|
||||
const st = await chrome.storage.local.get("trialStart");
|
||||
// 兜底:卡密直验。自动发卡不写 ca_authorizations,上面的查询对已购用户恒为空;
|
||||
// 用本地记住的卡密直接调 /public/verify(它读 ca_cards,返回真实到期时间)。
|
||||
let cardInfo = null;
|
||||
if (!authorized) {
|
||||
try {
|
||||
const saved = await chrome.storage.local.get(["activeCardKey"]);
|
||||
const key = String(saved.activeCardKey || "").trim();
|
||||
if (key) {
|
||||
const v = await authFetch("/public/verify", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-Api-Key": CARD_API_KEY },
|
||||
body: JSON.stringify({
|
||||
card_key: key,
|
||||
machine_id: machine,
|
||||
client_version: chrome.runtime.getManifest().version
|
||||
})
|
||||
}, 12000);
|
||||
const vd = (v && v.data) || {};
|
||||
if (v && v.code === 200 && vd.valid) {
|
||||
authorized = true;
|
||||
expire = vd.expire_time || "";
|
||||
cardInfo = { type: vd.type || "", expireTime: expire, isPermanent: !!vd.is_permanent, remainingDays: vd.remaining_days };
|
||||
} else if (v && v.code === 200 && vd.valid === false) {
|
||||
// 换机绑定或已过期:清掉本地卡密,别每次都拿废卡打接口
|
||||
await chrome.storage.local.remove(["activeCardKey", "activeCardInfo"]);
|
||||
}
|
||||
}
|
||||
} catch (e) { /* 直验失败不影响试用判定 */ }
|
||||
}
|
||||
|
||||
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 时视为放行
|
||||
offline: offline,
|
||||
realAuthorized: authorized,
|
||||
expire: expire,
|
||||
cardInfo: cardInfo,
|
||||
machine: machine,
|
||||
trialStart: trialStart,
|
||||
trialEnd: trialEnd,
|
||||
trialLeftMs: leftMs
|
||||
trialLeftMs: leftMs,
|
||||
trialCommentsUsed: usedComments,
|
||||
trialCommentsLimit: TRIAL_COMMENT_LIMIT,
|
||||
trialCommentsLeft: leftComments,
|
||||
inDiscount: inDiscount,
|
||||
discountLeftMs: discountLeftMs
|
||||
};
|
||||
}
|
||||
|
||||
@@ -159,7 +203,27 @@ async function activateCard(cardKey) {
|
||||
}, 12000);
|
||||
const d = (r && r.data) || {};
|
||||
if (r && r.code === 200 && d.valid) {
|
||||
return { ok: true, message: d.message || "激活成功", days: d.duration_days || 0 };
|
||||
// 记住这张卡:CardAuth 的自动发卡只把 ca_cards 标成 used 并绑定机器码,
|
||||
// 从不写 ca_authorizations,所以 /public/authorizations/query 对已购用户恒为空。
|
||||
// 插件自己留着卡密,才能持续向 /public/verify 校验真实授权状态。
|
||||
const key = String(cardKey || "").trim();
|
||||
await chrome.storage.local.set({
|
||||
activeCardKey: key,
|
||||
activeCardInfo: {
|
||||
type: d.type || "",
|
||||
durationDays: Number(d.duration_days) || 0,
|
||||
expireTime: d.expire_time || "",
|
||||
isPermanent: !!d.is_permanent,
|
||||
activatedAt: Date.now()
|
||||
}
|
||||
});
|
||||
return {
|
||||
ok: true, message: d.message || "激活成功",
|
||||
days: d.duration_days || 0,
|
||||
cardType: d.type || "",
|
||||
expireTime: d.expire_time || "",
|
||||
isPermanent: !!d.is_permanent
|
||||
};
|
||||
}
|
||||
return { ok: false, message: (d && d.message) || (r && r.message) || "激活失败" };
|
||||
}
|
||||
@@ -450,11 +514,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();
|
||||
@@ -527,6 +603,115 @@ async function handleGetDownloadInfo(p, sendResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 购买:当前商品(套餐)→ 订单 → 支付 → 自动激活本机 ============
|
||||
// 只调用授权系统的公开接口读取在售商品、创建订单,不改动服务端任何配置。
|
||||
|
||||
// 当前在售商品:由授权系统实时下发(页面显示的就是当前在售的)
|
||||
async function getCardTypes() {
|
||||
try {
|
||||
const r = await authFetch("/public/projects/" + PROJECT_ID + "/card-types", {}, 8000);
|
||||
if (r && r.code === 200 && Array.isArray(r.data)) return { ok: true, data: r.data };
|
||||
return { ok: false, data: [], message: (r && r.message) || "商品加载失败" };
|
||||
} catch (e) {
|
||||
return { ok: false, data: [], message: String(e && e.message || e) };
|
||||
}
|
||||
}
|
||||
|
||||
// 创建订单:contact_info 带机器码 → 支付成功后服务端自动发卡并绑定本机
|
||||
async function authCreateOrder(payType, cardTypeId) {
|
||||
const pay = payType === "wxpay" ? "wxpay" : "alipay";
|
||||
const types = await getCardTypes();
|
||||
if (!types.ok || !types.data.length) return { ok: false, message: types.message || "当前暂无在售商品" };
|
||||
const cardType = types.data.find(t => String(t.id) === String(cardTypeId))
|
||||
|| types.data.find(t => Number(t.price) > 0)
|
||||
|| types.data[0];
|
||||
if (!cardType) return { ok: false, message: "商品不存在,请重新选择" };
|
||||
|
||||
const machine = await getMachineId();
|
||||
const price = Number(cardType.price);
|
||||
const r = await authFetch("/public/orders", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-Api-Key": CARD_API_KEY },
|
||||
body: JSON.stringify({
|
||||
project_id: PROJECT_ID,
|
||||
card_type_id: cardType.id,
|
||||
amount: price,
|
||||
pay_type: pay,
|
||||
contact_info: "MACHINE:" + machine
|
||||
})
|
||||
}, 15000);
|
||||
|
||||
if (!r || r.code !== 200 || !r.data) return { ok: false, message: (r && r.message) || "创建订单失败" };
|
||||
|
||||
// 记住进行中的订单,供后台轮询恢复
|
||||
const pendingOrder = {
|
||||
orderNo: r.data.order_no,
|
||||
payType: pay,
|
||||
amount: r.data.pay_amount != null ? Number(r.data.pay_amount) : price,
|
||||
originalPrice: price,
|
||||
packageName: cardType.name || "",
|
||||
createdAt: Date.now()
|
||||
};
|
||||
await chrome.storage.local.set({ pendingOrder });
|
||||
scheduleOrderPolling();
|
||||
|
||||
return { ok: true, order: pendingOrder, payUrl: r.data.pay_url || "", cardTypeName: cardType.name };
|
||||
}
|
||||
|
||||
// 查询订单:服务端发卡后返回 card_key,拿到即自动激活本机
|
||||
async function authQueryOrder(orderNo) {
|
||||
const r = await authFetch("/public/orders/query?order_no=" + encodeURIComponent(orderNo), {}, 8000);
|
||||
if (!r || r.code !== 200 || !r.data) return { ok: false, message: (r && r.message) || "订单查询失败" };
|
||||
const d = r.data;
|
||||
const out = {
|
||||
ok: true,
|
||||
paid: d.status === "paid" || !!d.card_key,
|
||||
status: d.status || "",
|
||||
cardKey: d.card_key || "",
|
||||
expireTime: d.expire_time || ""
|
||||
};
|
||||
if (out.cardKey) {
|
||||
const act = await activateCard(out.cardKey);
|
||||
out.activated = !!(act && act.ok);
|
||||
out.message = (act && act.message) || "";
|
||||
// /public/orders/query 不返回到期时间,verify 返回的才是真值——别用「永久」兜底
|
||||
out.expireTime = (act && act.expireTime) || "";
|
||||
out.isPermanent = !!(act && act.isPermanent);
|
||||
out.planName = d.card_type_name || "";
|
||||
if (out.activated) {
|
||||
await chrome.storage.local.remove(["pendingOrder"]);
|
||||
await clearOrderAlarm();
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// 后台轮询:购买页被关掉也能走完「支付 → 发卡 → 激活」
|
||||
async function scheduleOrderPolling() {
|
||||
try { if (chrome.alarms) await chrome.alarms.create("orderPoll", { periodInMinutes: 1 }); } catch (e) {}
|
||||
}
|
||||
async function clearOrderAlarm() {
|
||||
try { if (chrome.alarms) await chrome.alarms.clear("orderPoll"); } catch (e) {}
|
||||
}
|
||||
if (chrome.alarms && chrome.alarms.onAlarm) {
|
||||
chrome.alarms.onAlarm.addListener(async (alarm) => {
|
||||
if (!alarm || alarm.name !== "orderPoll") return;
|
||||
try {
|
||||
const st = await chrome.storage.local.get("pendingOrder");
|
||||
const po = st.pendingOrder;
|
||||
if (!po || !po.orderNo) { await clearOrderAlarm(); return; }
|
||||
// 超过 12 小时未完成就不再追问,避免永久轮询
|
||||
if (Date.now() - (Number(po.createdAt) || 0) > 12 * 3600 * 1000) {
|
||||
await chrome.storage.local.remove(["pendingOrder"]);
|
||||
await clearOrderAlarm();
|
||||
return;
|
||||
}
|
||||
const res = await authQueryOrder(po.orderNo);
|
||||
if (res && res.activated) await clearOrderAlarm();
|
||||
} catch (e) { /* 单次轮询失败不影响下次 */ }
|
||||
});
|
||||
}
|
||||
|
||||
// ============ 消息路由 ============
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
@@ -568,6 +753,19 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
})();
|
||||
return true;
|
||||
}
|
||||
// 当前商品选择页(buy.html)用
|
||||
if (msg.action === "getCardTypes") {
|
||||
getCardTypes().then(sendResponse).catch(e => sendResponse({ ok: false, data: [], message: String(e && e.message || e) }));
|
||||
return true;
|
||||
}
|
||||
if (msg.action === "authCreateOrder") {
|
||||
authCreateOrder(msg.payType, msg.cardTypeId).then(sendResponse).catch(e => sendResponse({ ok: false, message: String(e && e.message || e) }));
|
||||
return true;
|
||||
}
|
||||
if (msg.action === "authQueryOrder") {
|
||||
authQueryOrder(msg.orderNo).then(sendResponse).catch(e => sendResponse({ ok: false, message: String(e && e.message || e) }));
|
||||
return true;
|
||||
}
|
||||
if (msg.action === "setTask") {
|
||||
ACTIVE_TASK = msg.task || null;
|
||||
sendResponse({ ok: true });
|
||||
@@ -604,7 +802,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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
/* 当前商品选择页 —— B站评论助手(B站蓝 #00A1D6) */
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: #f6f7f9;
|
||||
color: #222;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page { max-width: 720px; margin: 0 auto; padding: 28px 18px 40px; }
|
||||
|
||||
.head { text-align: center; margin-bottom: 26px; }
|
||||
|
||||
.logo {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
background: #00a1d6;
|
||||
border-radius: 20px;
|
||||
padding: 4px 14px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.head h1 { font-size: 26px; margin-bottom: 10px; }
|
||||
.flow { font-size: 13px; color: #888; }
|
||||
|
||||
section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 17px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.num {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: #00a1d6;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.package-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 14px; }
|
||||
|
||||
.package {
|
||||
border: 2px solid #eee;
|
||||
border-radius: 10px;
|
||||
padding: 16px 14px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.18s ease;
|
||||
}
|
||||
|
||||
.package:hover { border-color: #a8ddf0; transform: translateY(-2px); }
|
||||
.package.active { border-color: #00a1d6; background: #f2fbff; }
|
||||
|
||||
.pkg-name { font-size: 15px; font-weight: 600; margin-bottom: 8px; }
|
||||
.pkg-price { font-size: 24px; font-weight: 700; color: #00a1d6; margin-bottom: 6px; }
|
||||
.pkg-dur { font-size: 12px; color: #999; margin-bottom: 12px; }
|
||||
|
||||
.pkg-btn {
|
||||
width: 100%;
|
||||
padding: 8px 0;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: #00a1d6;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background 0.18s ease;
|
||||
}
|
||||
|
||||
.pkg-btn:hover { background: #0089ba; }
|
||||
.package.active .pkg-btn { background: #0089ba; }
|
||||
|
||||
.loading, .empty { grid-column: 1 / -1; text-align: center; color: #999; font-size: 13px; padding: 24px 0; }
|
||||
|
||||
.chosen { font-size: 13px; color: #666; margin-bottom: 14px; }
|
||||
.chosen b { color: #00a1d6; }
|
||||
|
||||
.pay-methods { display: flex; gap: 14px; }
|
||||
|
||||
.pay-btn {
|
||||
flex: 1;
|
||||
padding: 14px 0;
|
||||
border: 2px solid #eee;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.18s ease;
|
||||
}
|
||||
|
||||
.pay-btn.alipay { color: #1677ff; border-color: #bcd6ff; }
|
||||
.pay-btn.wxpay { color: #09bb07; border-color: #b6e6b8; }
|
||||
.pay-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); }
|
||||
.pay-btn.alipay:hover { border-color: #1677ff; }
|
||||
.pay-btn.wxpay:hover { border-color: #09bb07; }
|
||||
|
||||
.tip { margin-top: 12px; font-size: 12px; color: #999; }
|
||||
|
||||
.order-status {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
padding: 14px;
|
||||
border-radius: 8px;
|
||||
background: #f6f7f9;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.order-status.pending { color: #b26a00; background: #fff8e6; }
|
||||
.order-status.error { color: #c62828; background: #fdecea; }
|
||||
.order-status.success { color: #1b5e20; background: #eef7ee; }
|
||||
|
||||
/* 卡密展示区:支付成功后必须让用户亲眼看到卡密 */
|
||||
.card-box {
|
||||
margin-top: 14px;
|
||||
border: 2px dashed #00a1d6;
|
||||
border-radius: 10px;
|
||||
background: #f2fbff;
|
||||
padding: 16px 14px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.cb-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #0089ba;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.cb-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #00a1d6;
|
||||
flex: none;
|
||||
box-shadow: 0 0 0 4px rgba(0, 161, 214, 0.18);
|
||||
}
|
||||
|
||||
.cb-key {
|
||||
background: #fff;
|
||||
border: 1px solid #cdeaf7;
|
||||
border-radius: 8px;
|
||||
padding: 14px 12px;
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.cb-key code {
|
||||
font-family: "SF Mono", Consolas, "Courier New", monospace;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
color: #111;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
.cb-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
|
||||
.cb-copy {
|
||||
padding: 9px 22px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: #00a1d6;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background 0.18s ease;
|
||||
}
|
||||
|
||||
.cb-copy:hover { background: #0089ba; }
|
||||
.cb-tip { font-size: 12px; color: #0089ba; }
|
||||
|
||||
.cb-hint {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
color: #7a7f86;
|
||||
}
|
||||
|
||||
.ok-title { font-size: 16px; font-weight: 700; margin-bottom: 8px; }
|
||||
.ok-line { font-size: 13px; margin-bottom: 4px; }
|
||||
.ok-line b { color: #1b5e20; }
|
||||
.muted { color: #999; font-size: 12px; }
|
||||
|
||||
.foot { text-align: center; font-size: 12px; color: #999; line-height: 1.9; }
|
||||
.foot a { color: #00a1d6; text-decoration: none; }
|
||||
.foot a:hover { text-decoration: underline; }
|
||||
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>选择当前商品 · B站评论助手</title>
|
||||
<link rel="stylesheet" href="buy.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="head">
|
||||
<div class="logo">B站评论助手</div>
|
||||
<h1>选择当前商品</h1>
|
||||
<p class="flow">① 选择套餐 → ② 选择支付方式 → ③ 完成支付(支付成功自动激活本机)</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section id="step-package">
|
||||
<h2><span class="num">1</span> 当前在售商品</h2>
|
||||
<div id="package-list" class="package-list"></div>
|
||||
<p class="tip">套餐由授权系统实时拉取,显示的就是当前在售的商品。</p>
|
||||
</section>
|
||||
|
||||
<section id="step-pay" hidden>
|
||||
<h2><span class="num">2</span> 选择支付方式</h2>
|
||||
<p class="chosen" id="chosen-package"></p>
|
||||
<div class="pay-methods">
|
||||
<button class="pay-btn alipay" data-pay="alipay">支付宝</button>
|
||||
<button class="pay-btn wxpay" data-pay="wxpay">微信支付</button>
|
||||
</div>
|
||||
<p class="tip">点击后会打开支付页面,付款完成回到本页会自动显示激活结果。</p>
|
||||
</section>
|
||||
|
||||
<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="cb-title"><span class="cb-dot"></span>你的卡密</div>
|
||||
<div class="cb-key"><code id="cb-key-text"></code></div>
|
||||
<div class="cb-row">
|
||||
<button type="button" id="cb-copy" class="cb-copy">复制卡密</button>
|
||||
<span id="cb-copy-tip" class="cb-tip"></span>
|
||||
</div>
|
||||
<p class="cb-hint" id="cb-hint">万一自动激活没成功:回到插件侧边栏,在卡密输入框粘贴并点「粘贴卡密并激活」即可,不影响你的权益。</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="foot">
|
||||
<p>官网 <a href="https://dl.88531.cn/bili.html" target="_blank" rel="noopener">dl.88531.cn/bili.html</a> · 开源仓库 <a href="https://github.com/personal82555/bili-auto-comment" target="_blank" rel="noopener">GitHub</a> · <a href="https://t.me/+7YVPMu9EOChkYmM1" target="_blank" rel="noopener">Telegram 交流群</a></p>
|
||||
<p class="muted">支付成功后无需手动输入卡密,系统自动发卡并激活本机。</p>
|
||||
<p class="muted">试用期内 3 天 / 30 条评论,下载视频始终免费。</p>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="buy.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,199 @@
|
||||
/* 当前商品选择页:选套餐 → 选支付方式 → 下单 → 支付 → 自动激活本机 */
|
||||
const $ = (id) => document.getElementById(id);
|
||||
let packages = [];
|
||||
let selected = null;
|
||||
let orderNo = null;
|
||||
let pollTimer = null;
|
||||
|
||||
function esc(s) {
|
||||
return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({
|
||||
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
||||
}[c]));
|
||||
}
|
||||
|
||||
function durationText(days) {
|
||||
const n = Number(days);
|
||||
if (!n) return '限时体验';
|
||||
if (n >= 999999) return '永久有效';
|
||||
return n + ' 天';
|
||||
}
|
||||
|
||||
async function loadPackages() {
|
||||
$('package-list').innerHTML = '<div class="loading">正在加载当前商品…</div>';
|
||||
try {
|
||||
const res = await chrome.runtime.sendMessage({ action: 'getCardTypes' });
|
||||
if (!res || !res.ok) throw new Error((res && res.message) || '加载失败');
|
||||
packages = (res.data || []).filter((p) => Number(p.price) > 0);
|
||||
if (!packages.length) {
|
||||
$('package-list').innerHTML = '<div class="empty">当前暂无在售商品,请稍后再试</div>';
|
||||
return;
|
||||
}
|
||||
renderPackages();
|
||||
} catch (e) {
|
||||
$('package-list').innerHTML = '<div class="empty">商品加载失败:' + esc(e.message) + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderPackages() {
|
||||
$('package-list').innerHTML = packages.map((p) => `
|
||||
<div class="package" data-id="${esc(p.id)}">
|
||||
<div class="pkg-name">${esc(p.name)}</div>
|
||||
<div class="pkg-price">¥${esc(p.price)}</div>
|
||||
<div class="pkg-dur">${esc(durationText(p.duration_days))}</div>
|
||||
<button class="pkg-btn" data-id="${esc(p.id)}">选择此商品</button>
|
||||
</div>`).join('');
|
||||
document.querySelectorAll('.pkg-btn').forEach((btn) => {
|
||||
btn.addEventListener('click', () => selectPackage(btn.dataset.id));
|
||||
});
|
||||
}
|
||||
|
||||
function selectPackage(id) {
|
||||
selected = packages.find((p) => String(p.id) === String(id));
|
||||
if (!selected) return;
|
||||
document.querySelectorAll('.package').forEach((el) => {
|
||||
el.classList.toggle('active', el.dataset.id === String(id));
|
||||
});
|
||||
$('chosen-package').innerHTML = `已选商品:<b>${esc(selected.name)}</b> 价格 <b>¥${esc(selected.price)}</b> ${esc(durationText(selected.duration_days))}`;
|
||||
$('step-pay').hidden = false;
|
||||
$('step-pay').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
async function createOrder(payType) {
|
||||
if (!selected) return;
|
||||
$('order-panel').hidden = false;
|
||||
$('order-panel').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
const status = $('order-status');
|
||||
status.className = 'order-status pending';
|
||||
status.textContent = '⏳ 正在创建订单…';
|
||||
try {
|
||||
const res = await chrome.runtime.sendMessage({
|
||||
action: 'authCreateOrder',
|
||||
payType,
|
||||
cardTypeId: selected.id
|
||||
});
|
||||
if (!res || !res.ok) throw new Error((res && res.message) || '创建订单失败');
|
||||
|
||||
orderNo = res.order.orderNo;
|
||||
status.textContent = `订单 ${orderNo}(¥${res.order.amount})已创建,正在打开支付页面…`;
|
||||
if (res.payUrl) window.open(res.payUrl, '_blank');
|
||||
startPolling();
|
||||
} catch (e) {
|
||||
status.className = 'order-status error';
|
||||
status.textContent = '✗ ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (pollTimer) clearInterval(pollTimer);
|
||||
let attempts = 0;
|
||||
pollTimer = setInterval(async () => {
|
||||
attempts++;
|
||||
if (attempts > 240) {
|
||||
clearInterval(pollTimer);
|
||||
pollTimer = null;
|
||||
const status = $('order-status');
|
||||
status.className = 'order-status error';
|
||||
status.textContent = `✗ 等待支付超时。订单号 ${orderNo},如已付款请把订单号发给官网客服处理。`;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await chrome.runtime.sendMessage({ action: 'authQueryOrder', orderNo });
|
||||
if (!res || !res.ok) return;
|
||||
// 只要拿到卡密就先展示出来,不等激活结果——自动激活失败时用户手里必须有码
|
||||
if (res.cardKey) showCardBox(res.cardKey, !!res.activated);
|
||||
|
||||
if (res.activated) {
|
||||
clearInterval(pollTimer);
|
||||
pollTimer = null;
|
||||
showActivated(res);
|
||||
} else if (res.cardKey) {
|
||||
clearInterval(pollTimer);
|
||||
pollTimer = null;
|
||||
const status = $('order-status');
|
||||
status.className = 'order-status error';
|
||||
status.innerHTML = '✗ <b>已支付并已发卡</b>,但自动激活未成功(' +
|
||||
esc(res.message || '本机绑定可能被占用') + ')。请用上方卡密手动激活:回到插件侧边栏 → 卡密输入框粘贴 → 点「粘贴卡密并激活」。<br>' +
|
||||
'订单号:<b>' + esc(orderNo || '') + '</b>';
|
||||
} else if (res.paid) {
|
||||
// 已付款但服务端还没把卡发出来(最常见原因是卡池库存不足)
|
||||
const waited = attempts * 5;
|
||||
const status = $('order-status');
|
||||
status.className = 'order-status pending';
|
||||
status.textContent = '⏳ 已支付,正在为你发卡…(订单 ' + orderNo + ',已等待 ' + waited + ' 秒)';
|
||||
if (waited >= 90) {
|
||||
status.className = 'order-status error';
|
||||
status.innerHTML = '⏳ <b>已支付</b>,但发卡耗时较长(已 ' + waited + ' 秒)。订单 <b>' + esc(orderNo) +
|
||||
'</b> 付款状态为已支付,<b>权益不会丢失</b>:继续等会自动发放,或凭此订单号联系客服补发。';
|
||||
}
|
||||
} else {
|
||||
const status = $('order-status');
|
||||
status.className = 'order-status pending';
|
||||
status.textContent = '⏳ 等待支付中…(订单 ' + orderNo + ',已等待 ' + attempts * 5 + ' 秒)';
|
||||
}
|
||||
} catch (e) {
|
||||
/* 单次查询失败不中断轮询 */
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/** 支付成功后立刻把卡密展示给用户(含自动激活失败的场景) */
|
||||
function showCardBox(key, activated) {
|
||||
const box = $('card-box');
|
||||
if (!box || !key) return;
|
||||
if (!box.hidden && $('cb-key-text').textContent === key) return;
|
||||
$('cb-key-text').textContent = key;
|
||||
box.hidden = false;
|
||||
$('cb-copy-tip').textContent = activated ? '已自动激活本机,这是你的卡密,建议留存' : '';
|
||||
box.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
function showActivated(res) {
|
||||
const status = $('order-status');
|
||||
const permanent = !!res.isPermanent;
|
||||
const exp = String(res.expireTime || '').trim();
|
||||
const plan = String(res.planName || '').trim();
|
||||
// 绝不默认「永久」:到期时间只在服务端明确给出时才显示
|
||||
let expireLine;
|
||||
if (permanent) expireLine = '到期时间:<b>永久</b>';
|
||||
else if (exp) expireLine = '到期时间:<b>' + esc(exp) + '</b>';
|
||||
else if (plan) expireLine = '套餐:<b>' + esc(plan) + '</b>(实际到期时间以插件侧边栏「已授权 · 到期 …」为准)';
|
||||
else expireLine = '到期时间:以插件侧边栏显示为准';
|
||||
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">${expireLine}</div>
|
||||
<div class="ok-line muted">回到插件侧边栏即可使用全部功能(本机已授权)。</div>`;
|
||||
}
|
||||
|
||||
document.querySelectorAll('.pay-btn').forEach((btn) => {
|
||||
btn.addEventListener('click', () => createOrder(btn.dataset.pay));
|
||||
});
|
||||
|
||||
// 复制卡密:优先 clipboard API,无权限时降级 execCommand
|
||||
$('cb-copy') && $('cb-copy').addEventListener('click', async () => {
|
||||
const key = ($('cb-key-text').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.position = 'fixed';
|
||||
ta.style.opacity = '0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
ok = document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
} catch (e2) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
$('cb-copy-tip').textContent = ok ? '已复制,可直接粘贴到侧边栏激活' : '复制失败,请手动长按选择卡密';
|
||||
});
|
||||
|
||||
loadPackages();
|
||||
+12
-1
@@ -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");
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "B站评论助手",
|
||||
"version": "1.8.1",
|
||||
"version": "1.10.3",
|
||||
"description": "B站网页版关键词评论助手:按搜索关键词找视频,默认模拟真人(打开视频页、滚到评论区、逐字输入、点发布),也可快速API直发;支持下载当前页视频;带防重复记录、可中断执行与页面实时进度浮层。",
|
||||
"permissions": ["storage", "tabs", "scripting", "sidePanel", "downloads", "declarativeNetRequest"],
|
||||
"permissions": ["storage", "tabs", "scripting", "sidePanel", "downloads", "declarativeNetRequest", "alarms"],
|
||||
"host_permissions": [
|
||||
"https://*.bilibili.com/*",
|
||||
"https://api.bilibili.com/*",
|
||||
|
||||
+5
-5
@@ -64,11 +64,6 @@
|
||||
<div id="licMachine" style="font-size:10px;color:#bbb;margin-top:4px;cursor:pointer" title="点击复制机器码">机器码:<span id="machineIdText">…</span>(点击复制)</div>
|
||||
</div>
|
||||
|
||||
<div class="promo">
|
||||
<span>🤖 没有模型?注册即送额度</span>
|
||||
<a id="promoLink">ai.88531.cn 注册 →</a>
|
||||
</div>
|
||||
|
||||
<div class="sec">
|
||||
<div class="lbl">📊 评论记录</div>
|
||||
<div class="statRow">
|
||||
@@ -79,6 +74,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="promo">
|
||||
<span>🤖 没有模型?注册即送额度</span>
|
||||
<a id="promoLink">ai.88531.cn 注册 →</a>
|
||||
</div>
|
||||
|
||||
<div class="sec">
|
||||
<div class="lbl">LLM 配置(自动保存,仅 AI 模式需要)</div>
|
||||
<input type="text" id="baseUrl" placeholder="https://ai.88531.cn/v1">
|
||||
|
||||
+29
-8
@@ -152,16 +152,31 @@ function refreshCustomVisibility() {
|
||||
actRow.style.display = "none";
|
||||
} else if (lic.authorized) {
|
||||
icon.textContent = "✅";
|
||||
text.textContent = "已授权" + (lic.expire ? " · 到期 " + lic.expire : "(长期)");
|
||||
const planTag = (lic.cardInfo && lic.cardInfo.type) ? " · " + lic.cardInfo.type : "";
|
||||
text.textContent = "已授权" + planTag + (lic.expire ? " · 到期 " + lic.expire : "(长期)");
|
||||
text.style.color = "#0a7";
|
||||
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 +195,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 +532,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);
|
||||
@@ -650,7 +671,7 @@ function refreshCustomVisibility() {
|
||||
|
||||
// 授权区
|
||||
const buy = $("licBuy");
|
||||
if (buy) buy.addEventListener("click", () => chrome.tabs.create({ url: "https://auth.88531.cn" }));
|
||||
if (buy) buy.addEventListener("click", () => chrome.tabs.create({ url: chrome.runtime.getURL("buy.html") }));
|
||||
const act = $("btnActivate");
|
||||
if (act) act.addEventListener("click", activateCard);
|
||||
const card = $("cardKey");
|
||||
|
||||
Reference in new issue
Block a user