v1.10.0: 新增「当前商品选择页」——侧边栏购买入口改为插件内置 buy.html

- buy.html/css/js:实时拉取授权系统当前在售商品 → 选商品 → 选支付方式 → 下单
- background.js:新增 getCardTypes / authCreateOrder / authQueryOrder 三个消息与实现
- 支付成功自动发卡并激活本机;新增 alarms 权限做后台订单轮询(关页仍完成),12 小时未完成自动停
- sidepanel.js:licBuy 从跳 https://auth.88531.cn 改为打开内置 buy.html(不改服务端)
- manifest 1.9.0 → 1.10.0 + alarms 权限
- CHANGELOG/README 同步:新增条目、文件结构、内置购买页说明、Gitea 源
This commit is contained in:
jinghui committed 2026-10-20 18:55:05 +08:00
1 parent 6a697a2663
commit 8685980529
8 files changed
+460 -3

No files matched your search

+15
View File
@@ -4,6 +4,21 @@ B站评论助手 版本历史。下载:https://dl.88531.cn/bili.html · 
---
## 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
View File
@@ -40,6 +40,7 @@ Chrome/Edge 浏览器插件(Manifest V3):按**搜索关键词**找 B站视
### 授权与运维
- 🔑 3 天免费试用 / 30 条评论(任一先到即止,倒计时精确到秒并显示剩余条数),卡密激活绑定本机永久可用、不限量;试用到期后 24 小时内购买享 8 折;**过期只锁自动评论,搜索/AI 生成/下载视频不受影响**
- 🛒 **内置当前商品选择页**:点「购买授权 →」在插件内打开新标签页,实时展示授权系统当前在售商品 → 选商品 → 选支付方式(支付宝/微信)→ 支付成功**自动发卡并激活本机**;购买页关掉后台仍会轮询完成激活
- 📊 安装统计上报(仅机器码 + 版本,不碰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
## 免责声明
+118
View File
@@ -552,6 +552,111 @@ 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) || "";
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) => {
@@ -593,6 +698,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 });
+136
View File
@@ -0,0 +1,136 @@
/* 当前商品选择页 —— 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; }
.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; }
+48
View File
@@ -0,0 +1,48 @@
<!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>
</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>
+137
View File
@@ -0,0 +1,137 @@
/* 当前商品选择页:选套餐 → 选支付方式 → 下单 → 支付 → 自动激活本机 */
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) => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
}[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.activated) {
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.message || '请联系客服,提供订单号补发');
} else {
const status = $('order-status');
status.className = 'order-status pending';
status.textContent = `⏳ 等待支付中…(订单 ${orderNo},已等待 ${attempts * 5} 秒)`;
}
} catch (e) {
/* 单次查询失败不中断轮询 */
}
}, 5000);
}
function showActivated(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>`;
}
document.querySelectorAll('.pay-btn').forEach((btn) => {
btn.addEventListener('click', () => createOrder(btn.dataset.pay));
});
loadPackages();
+2 -2
View File
@@ -1,9 +1,9 @@
{
"manifest_version": 3,
"name": "B站评论助手",
"version": "1.9.0",
"version": "1.10.0",
"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/*",
+1 -1
View File
@@ -670,7 +670,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");