v1.6.6: 使用说明+官网页链接、下载改夸克网盘、评论3字下限、AI读取视频真实内容、模拟真人逐字评论
This commit is contained in:
commit
38b90c1448
12 files changed
+2614
No files matched your search
+623
@@ -0,0 +1,623 @@
|
||||
// sidepanel: 配置 + 运行控制 + 实时日志
|
||||
(() => {
|
||||
const DEFAULTS = {
|
||||
baseUrl: "https://ai.88531.cn/v1",
|
||||
apiKey: "",
|
||||
model: "mimo-v2.6-flash",
|
||||
keyword: "",
|
||||
order: "hot",
|
||||
mode: "tpl",
|
||||
customText: "",
|
||||
sendMode: "human",
|
||||
tid: 0,
|
||||
region: "",
|
||||
dlQuality: "80",
|
||||
maxN: 3,
|
||||
minPlay: 0
|
||||
};
|
||||
const FIELDS = ["baseUrl", "apiKey", "model", "keyword", "order", "mode", "customText", "sendMode", "tid", "region", "dlQuality", "maxN", "minPlay"];
|
||||
|
||||
let settings = Object.assign({}, DEFAULTS);
|
||||
let running = false;
|
||||
let timerHandle = null;
|
||||
let timerStart = 0;
|
||||
|
||||
const $ = id => document.getElementById(id);
|
||||
|
||||
// ============ 日志 / 状态 ============
|
||||
|
||||
function log(text, cls) {
|
||||
const box = $("logBox");
|
||||
if (!box) return;
|
||||
const line = document.createElement("div");
|
||||
if (cls) line.className = cls;
|
||||
line.textContent = new Date().toTimeString().slice(0, 8) + " " + text;
|
||||
box.appendChild(line);
|
||||
while (box.children.length > 300) box.removeChild(box.firstChild);
|
||||
box.scrollTop = box.scrollHeight;
|
||||
}
|
||||
|
||||
function setStatus(text, isErr) {
|
||||
const el = $("status");
|
||||
if (!el) return;
|
||||
el.textContent = text || "";
|
||||
el.style.color = isErr ? "#d93025" : "#0a7";
|
||||
}
|
||||
|
||||
// ============ 设置读写(自动保存,无保存按钮) ============
|
||||
|
||||
function fillForm() {
|
||||
for (const k of FIELDS) {
|
||||
const el = $(k);
|
||||
if (!el) continue;
|
||||
el.value = settings[k];
|
||||
}
|
||||
// 地区不记忆:每次打开默认「全部地区」,只在本次会话内有效
|
||||
settings.region = "";
|
||||
const reg = $("region");
|
||||
if (reg) reg.value = "";
|
||||
refreshCustomVisibility();
|
||||
refreshModeWarn();
|
||||
}
|
||||
|
||||
async function saveField(key, value) {
|
||||
settings[key] = value;
|
||||
const toSave = Object.assign({}, settings);
|
||||
delete toSave.region; // 地区不写入存储(默认永远是不限)
|
||||
await chrome.storage.local.set({ settings: toSave });
|
||||
}
|
||||
|
||||
function refreshCustomVisibility() {
|
||||
const ta = $("customText");
|
||||
if (ta) ta.style.display = settings.mode === "custom" ? "block" : "none";
|
||||
}
|
||||
|
||||
function customValidLine(s) {
|
||||
if (s.length < 3 || s.length > 200) return false;
|
||||
if (/(?:https?:\/\/|www\.[a-z0-9-]+\.[a-z]{2,})/i.test(s) && !/bilibili\.com|b23\.tv/i.test(s)) return false;
|
||||
if (new Set(s).size < Math.min(6, Math.ceil(s.length / 2))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function refreshModeWarn() {
|
||||
const warn = $("modeWarn");
|
||||
if (!warn) return;
|
||||
const hasModel = !!(settings.baseUrl && settings.model);
|
||||
if (settings.mode === "custom") {
|
||||
const raw = String(settings.customText || "").split("\n").map(s => s.trim()).filter(Boolean);
|
||||
const bad = raw.filter(line => !customValidLine(line));
|
||||
if (!raw.length) {
|
||||
warn.textContent = "⚠ 选了自定义但还没填内容,开始执行会被拒绝";
|
||||
warn.style.display = "block";
|
||||
} else if (bad.length === raw.length) {
|
||||
warn.textContent = "⚠ 自定义内容全部不合规(每行需3~200字、不含链接、不能全是重复字),开始执行会被拒绝";
|
||||
warn.style.display = "block";
|
||||
} else if (bad.length) {
|
||||
warn.textContent = "⚠ " + bad.length + " 行不合规(每行需3~200字),这些行不会被使用;有效 " + (raw.length - bad.length) + " 条";
|
||||
warn.style.display = "block";
|
||||
} else {
|
||||
warn.style.display = "none";
|
||||
}
|
||||
} else if (settings.mode === "ai" && !settings.apiKey) {
|
||||
warn.textContent = "⚠ 选了 AI 生成但 API Key 为空,将自动改用固定模板";
|
||||
warn.style.display = "block";
|
||||
} else if (settings.mode === "ai" && !hasModel) {
|
||||
warn.textContent = "⚠ 选了 AI 生成但模型配置不全,将自动改用固定模板";
|
||||
warn.style.display = "block";
|
||||
} else if (settings.mode === "tpl" && settings.apiKey) {
|
||||
warn.textContent = "⚠ 已填 API Key,但评论方式是固定模板(AI 配置暂不生效)";
|
||||
warn.style.display = "block";
|
||||
} else {
|
||||
warn.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 授权状态 ============
|
||||
|
||||
let licState = null; // {authorized, offline, realAuthorized, expire, trialEnd, trialLeftMs, machine}
|
||||
let licTimer = null;
|
||||
|
||||
function fmtRemain(ms) {
|
||||
if (ms <= 0) return "00:00:00";
|
||||
const s = Math.floor(ms / 1000);
|
||||
const h = String(Math.floor(s / 3600)).padStart(2, "0");
|
||||
const m = String(Math.floor((s % 3600) / 60)).padStart(2, "0");
|
||||
const ss = String(s % 60).padStart(2, "0");
|
||||
return h + ":" + m + ":" + ss;
|
||||
}
|
||||
|
||||
function renderLicense() {
|
||||
const icon = $("licIcon"), text = $("licText"), sub = $("licSub"),
|
||||
actRow = $("licActRow"), buy = $("licBuy");
|
||||
if (!licState || !icon) return;
|
||||
const lic = licState;
|
||||
|
||||
if (lic.offline) {
|
||||
icon.textContent = "🌐";
|
||||
text.textContent = "授权服务器暂不可用,已临时放行";
|
||||
text.style.color = "#666";
|
||||
sub.textContent = "网络恢复后会自动重新校验";
|
||||
actRow.style.display = "none";
|
||||
} else if (lic.authorized) {
|
||||
icon.textContent = "✅";
|
||||
text.textContent = "已授权" + (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) {
|
||||
icon.textContent = "🎁";
|
||||
text.textContent = "免费试用中 · 剩余 " + fmtRemain(lic.trialLeftMs);
|
||||
text.style.color = "#e67e22";
|
||||
sub.textContent = "未授权可试用 1 天,购买卡密激活后不限时使用";
|
||||
actRow.style.display = "flex";
|
||||
if (buy) buy.style.display = "";
|
||||
} else {
|
||||
icon.textContent = "🔒";
|
||||
text.textContent = "试用已到期,自动评论已锁定";
|
||||
text.style.color = "#d93025";
|
||||
sub.textContent = "购买卡密激活即可解锁(下载视频不受影响)";
|
||||
actRow.style.display = "flex";
|
||||
if (buy) buy.style.display = "";
|
||||
}
|
||||
}
|
||||
|
||||
function tickCountdown() {
|
||||
if (!licState || licState.authorized || licState.offline) return;
|
||||
const left = licState.trialEnd - Date.now();
|
||||
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);
|
||||
}
|
||||
|
||||
async function loadLicense() {
|
||||
let res = null;
|
||||
try { res = await chrome.runtime.sendMessage({ action: "licenseStatus" }); }
|
||||
catch (e) { res = null; }
|
||||
if (!res || !res.ok || !res.license) {
|
||||
licState = { authorized: true, offline: true, trialEnd: Date.now() };
|
||||
renderLicense();
|
||||
return;
|
||||
}
|
||||
licState = res.license;
|
||||
const mt = $("machineIdText");
|
||||
if (mt) mt.textContent = licState.machine || "";
|
||||
renderLicense();
|
||||
// 倒计时(仅试用中)
|
||||
if (licTimer) { clearInterval(licTimer); licTimer = null; }
|
||||
if (licState && !licState.authorized && !licState.offline && licState.trialLeftMs > 0) {
|
||||
licTimer = setInterval(tickCountdown, 1000);
|
||||
}
|
||||
// 公告
|
||||
const notice = $("licNotice");
|
||||
const anns = Array.isArray(res.announcements) ? res.announcements : [];
|
||||
if (notice && anns.length) {
|
||||
notice.style.display = "block";
|
||||
notice.innerHTML = anns.map(a => {
|
||||
const t = document.createElement("span");
|
||||
t.textContent = "📌 " + a.title + (a.content ? ":" + a.content : "");
|
||||
let html = "";
|
||||
try { html = t.innerHTML; } catch (e) { html = "📌 " + a.title; }
|
||||
if (a.link) html += ' <a href="#" class="annLink" data-url="' + String(a.link).replace(/"/g, """) + '" style="color:#00A1D6">详情 →</a>';
|
||||
return html;
|
||||
}).join("<br>");
|
||||
for (const a of notice.querySelectorAll(".annLink")) {
|
||||
a.addEventListener("click", ev => {
|
||||
ev.preventDefault();
|
||||
const url = a.getAttribute("data-url");
|
||||
if (url) chrome.tabs.create({ url: url });
|
||||
});
|
||||
}
|
||||
} else if (notice) {
|
||||
notice.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
async function activateCard() {
|
||||
const btn = $("btnActivate"), input = $("cardKey"), msg = $("actMsg");
|
||||
if (!btn || !input || !msg) return;
|
||||
const card = String(input.value || "").trim();
|
||||
if (!card) { msg.textContent = "请输入卡密"; msg.style.color = "#d93025"; return; }
|
||||
btn.disabled = true;
|
||||
msg.textContent = "激活中…";
|
||||
msg.style.color = "#666";
|
||||
let res = null;
|
||||
try { res = await chrome.runtime.sendMessage({ action: "activateCard", card: card }); }
|
||||
catch (e) { res = { ok: false, error: String(e) }; }
|
||||
if (res && res.ok) {
|
||||
msg.textContent = "✓ " + (res.message || "激活成功");
|
||||
msg.style.color = "#0a7";
|
||||
log("授权激活成功:" + (res.message || ""), "ok");
|
||||
input.value = "";
|
||||
await loadLicense(); // 重新拉状态,横幅变已授权
|
||||
} else {
|
||||
msg.textContent = "✗ " + ((res && (res.message || res.error)) || "激活失败");
|
||||
msg.style.color = "#d93025";
|
||||
log("授权激活失败:" + ((res && (res.message || res.error)) || ""), "fail");
|
||||
}
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
function licenseBlocked() {
|
||||
return !!(licState && !licState.authorized && !licState.offline && licState.trialLeftMs <= 0);
|
||||
}
|
||||
|
||||
// ============ 运行控制 ============
|
||||
|
||||
function fmtElapsed(ms) {
|
||||
const s = Math.floor(ms / 1000);
|
||||
return String(Math.floor(s / 60)).padStart(2, "0") + ":" + String(s % 60).padStart(2, "0");
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
stopTimer(false);
|
||||
timerStart = Date.now();
|
||||
const el = $("runTimer");
|
||||
if (el) el.textContent = "计时 00:00";
|
||||
timerHandle = setInterval(() => {
|
||||
const t = $("runTimer");
|
||||
if (t) t.textContent = "计时 " + fmtElapsed(Date.now() - timerStart);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopTimer(freeze) {
|
||||
if (timerHandle) { clearInterval(timerHandle); timerHandle = null; }
|
||||
if (freeze) {
|
||||
const t = $("runTimer");
|
||||
if (t && timerStart) t.textContent = "完成 " + fmtElapsed(Date.now() - timerStart);
|
||||
}
|
||||
}
|
||||
|
||||
function setRunning(on) {
|
||||
running = on;
|
||||
const btnStart = $("btnStart"), stopRow = $("stopRow");
|
||||
if (btnStart) btnStart.style.display = on ? "none" : "block";
|
||||
if (stopRow) stopRow.style.display = on ? "flex" : "none";
|
||||
if (!on) { stopTimer(true); }
|
||||
}
|
||||
|
||||
async function startRun() {
|
||||
const kw = String(settings.keyword || "").trim();
|
||||
if (!kw) { setStatus("请先填搜索关键词", true); const el = $("keyword"); if (el) el.focus(); return; }
|
||||
// 授权闸门(本地先拦,后台还有一道)
|
||||
if (licenseBlocked()) {
|
||||
setStatus("试用已到期,自动评论已锁定。购买卡密激活后即可使用(下载视频不受影响)", true);
|
||||
log("⛔ 试用已到期,自动评论被锁定", "fail");
|
||||
const ck = $("cardKey"); if (ck) ck.focus();
|
||||
return;
|
||||
}
|
||||
// 自定义内容本地预检:无效就不开始,绝不换成模板乱评
|
||||
if (settings.mode === "custom") {
|
||||
const raw = String(settings.customText || "").split("\n").map(s => s.trim()).filter(Boolean);
|
||||
const valid = raw.filter(customValidLine);
|
||||
if (!raw.length) {
|
||||
setStatus("选了自定义评论但内容为空:请在「评论方式=自定义」下方填写,每行一条", true);
|
||||
const ta = $("customText"); if (ta) ta.focus();
|
||||
return;
|
||||
}
|
||||
if (!valid.length) {
|
||||
setStatus("自定义内容全部不合规(每行需3~200字、不含外链),请修改后再开始", true);
|
||||
log("⛔ 自定义内容不合规,已拒绝开始", "fail");
|
||||
const ta = $("customText"); if (ta) ta.focus();
|
||||
return;
|
||||
}
|
||||
log("自定义评论 " + valid.length + " 条已启用" + (raw.length > valid.length ? "(" + (raw.length - valid.length) + " 行不合规已忽略)" : ""), "step");
|
||||
}
|
||||
if (settings.mode === "ai" && !(settings.baseUrl && settings.model)) {
|
||||
log("⚠ 未配置模型,本次将使用固定模板", "fail");
|
||||
}
|
||||
$("doneList").innerHTML = "";
|
||||
setStatus("正在连接B站页面…");
|
||||
setRunning(true);
|
||||
startTimer();
|
||||
log("开始执行:关键词「" + kw + "」最多 " + settings.maxN + " 条,每条间隔 1~2 分钟随机", "step");
|
||||
|
||||
let res = null;
|
||||
try {
|
||||
res = await chrome.runtime.sendMessage({ action: "startRun", config: Object.assign({}, settings, { keyword: kw, maxN: Number(settings.maxN) || 3, minPlay: Number(settings.minPlay) || 0 }) });
|
||||
} catch (e) {
|
||||
res = { ok: false, error: String(e) };
|
||||
}
|
||||
if (!res || !res.ok) {
|
||||
const err = (res && res.error) || "启动失败";
|
||||
setStatus(err, true);
|
||||
log("❌ " + err, "fail");
|
||||
setRunning(false);
|
||||
} else {
|
||||
setStatus("执行中,进度见页面右上角浮层");
|
||||
}
|
||||
}
|
||||
|
||||
async function stopRun() {
|
||||
setStatus("停止中,将在当前步骤后停止…");
|
||||
log("发送停止指令", "step");
|
||||
try {
|
||||
const res = await chrome.runtime.sendMessage({ action: "stopRun" });
|
||||
if (res && !res.ok) setStatus("停止失败:" + res.error, true);
|
||||
} catch (e) {
|
||||
setStatus("停止失败:" + String(e), true);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 下载当前页视频 ============
|
||||
|
||||
function fmtMb(n) { return (n / 1048576).toFixed(1) + " MB"; }
|
||||
|
||||
// 面板页自己抓文件(扩展页 fetch 免 CORS,DNR 规则注入 Referer)→ Blob → 触发保存
|
||||
async function fetchToBlob(url, onProgress) {
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) throw new Error("HTTP " + resp.status + "(CDN拒绝,可能需要重新打开视频页)");
|
||||
const total = Number(resp.headers.get("content-length") || 0);
|
||||
const reader = resp.body.getReader();
|
||||
const chunks = [];
|
||||
let got = 0;
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
got += value.length;
|
||||
if (onProgress) onProgress(got, total);
|
||||
}
|
||||
return new Blob(chunks);
|
||||
}
|
||||
|
||||
async function downloadCurrent() {
|
||||
const btn = $("btnDownload"), msg = $("dlMsg");
|
||||
if (!btn || !msg) return;
|
||||
btn.disabled = true;
|
||||
const setText = (t, color) => { msg.textContent = t; msg.style.color = color || "#666"; };
|
||||
setText("获取视频地址…");
|
||||
log("⬇ 开始下载当前页视频(清晰度 " + (settings.dlQuality === "80" ? "最高可用" : settings.dlQuality + "P") + ")", "step");
|
||||
|
||||
let res = null;
|
||||
try {
|
||||
res = await chrome.runtime.sendMessage({ action: "getDownloadInfo", qn: Number(settings.dlQuality) || 80 });
|
||||
} catch (e) {
|
||||
res = { ok: false, error: String(e) };
|
||||
}
|
||||
if (!res || !res.ok || !res.info) {
|
||||
const err = (res && res.error) || "获取失败";
|
||||
setText("✗ " + err, "#d93025");
|
||||
log("⬇ 下载失败:" + err, "fail");
|
||||
btn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const info = res.info;
|
||||
const files = info.files;
|
||||
const base = String(info.title || "bilibili视频").replace(/[\\/:*?"<>|]/g, "_").slice(0, 80);
|
||||
const saved = [], errors = [];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const f = files[i];
|
||||
let name = base;
|
||||
if (f.tag) name += "_" + f.tag;
|
||||
else if (files.length > 1) name += "_第" + (i + 1) + "段";
|
||||
name += "." + (f.ext || "mp4");
|
||||
try {
|
||||
let lastShow = 0;
|
||||
const blob = await fetchToBlob(f.url, (got, total) => {
|
||||
const now = Date.now();
|
||||
if (now - lastShow > 500) {
|
||||
lastShow = now;
|
||||
setText("下载中 " + fmtMb(got) + (total ? " / " + fmtMb(total) : "") + "(" + name.slice(0, 18) + "…)");
|
||||
}
|
||||
});
|
||||
const objUrl = URL.createObjectURL(blob);
|
||||
await chrome.downloads.download({
|
||||
url: objUrl, filename: "B站视频/" + name, conflictAction: "uniquify", saveAs: false
|
||||
});
|
||||
setTimeout(() => URL.revokeObjectURL(objUrl), 60000); // 给下载留足时间
|
||||
saved.push(name + "(" + fmtMb(blob.size) + ")");
|
||||
log("⬇ 已保存:" + name + "(" + fmtMb(blob.size) + ")", "ok");
|
||||
} catch (e) {
|
||||
errors.push(name + ":" + String(e && e.message || e));
|
||||
log("⬇ 失败:" + name + ":" + String(e && e.message || e), "fail");
|
||||
}
|
||||
}
|
||||
|
||||
if (saved.length) {
|
||||
const note = info.note ? "," + info.note : "";
|
||||
const partial = errors.length ? ",部分失败:" + errors.join(";") : "";
|
||||
setText("✓ " + saved.length + " 个文件(" + info.quality + ")" + (info.note ? "(分离格式)" : ""), "#0a7");
|
||||
log("⬇ 完成:" + saved.join("、") + note + partial, "ok");
|
||||
} else {
|
||||
setText("✗ 下载失败:" + errors.join(";"), "#d93025");
|
||||
log("⬇ 下载失败:" + errors.join(";"), "fail");
|
||||
}
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
// ============ 事件回传 ============
|
||||
|
||||
function addDoneItem(item) {
|
||||
const box = $("doneList");
|
||||
if (!box) return;
|
||||
const bvid = item.bvid || "";
|
||||
// 同一视频只显示一条(防止重复消息把成功列表刷成两条)
|
||||
let exist = null;
|
||||
for (const child of box.children) {
|
||||
if (child.dataset && child.dataset.bvid === bvid) { exist = child; break; }
|
||||
}
|
||||
const div = exist || document.createElement("div");
|
||||
div.className = "done";
|
||||
div.dataset.bvid = bvid;
|
||||
div.innerHTML = "";
|
||||
// 标题可点击 → 打开视频页
|
||||
const t = document.createElement("a");
|
||||
t.className = "dt";
|
||||
t.textContent = item.title || bvid;
|
||||
t.href = "https://www.bilibili.com/video/" + bvid;
|
||||
t.target = "_blank";
|
||||
t.rel = "noopener";
|
||||
t.title = "点击打开视频";
|
||||
const c = document.createElement("div"); c.className = "dc"; c.textContent = item.text || "";
|
||||
const m = document.createElement("div"); m.className = "dm";
|
||||
m.textContent = "BV:" + bvid + "(点标题查看视频)";
|
||||
div.appendChild(t); div.appendChild(c); div.appendChild(m);
|
||||
if (!exist) box.prepend(div);
|
||||
}
|
||||
|
||||
function onFinished(ev) {
|
||||
setRunning(false);
|
||||
if (ev.ok) {
|
||||
const msg = "执行完成:成功 " + (ev.done || 0) + " 条" + (ev.failed ? ",失败 " + ev.failed + " 条" : "");
|
||||
setStatus(msg);
|
||||
log("✅ " + msg, "ok");
|
||||
} else {
|
||||
const msg = ev.reason || "执行未完成";
|
||||
setStatus(msg, true);
|
||||
log("⏹ " + msg, "fail");
|
||||
}
|
||||
refreshStats();
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener(msg => {
|
||||
if (!msg || !msg.relay) return;
|
||||
const ev = msg.relay;
|
||||
if (ev.type === "log") log(ev.text, ev.cls === "ok" ? "ok" : ev.cls === "fail" ? "fail" : "step");
|
||||
else if (ev.type === "done") addDoneItem(ev);
|
||||
else if (ev.type === "finished") onFinished(ev);
|
||||
else if (ev.type === "ready") log("B站页面内容脚本就绪", "ok");
|
||||
});
|
||||
|
||||
// ============ 评论记录统计 ============
|
||||
|
||||
async function refreshStats() {
|
||||
try {
|
||||
const res = await chrome.runtime.sendMessage({ action: "getStats" });
|
||||
if (res && res.stats) {
|
||||
$("stTotal").textContent = res.stats.total || 0;
|
||||
$("stWeek").textContent = res.stats.week || 0;
|
||||
$("stToday").textContent = res.stats.today || 0;
|
||||
}
|
||||
} catch (e) { /* 忽略 */ }
|
||||
}
|
||||
|
||||
let clearArmed = false, clearTimer = null;
|
||||
async function onClearHist() {
|
||||
const btn = $("btnClearHist");
|
||||
if (!clearArmed) {
|
||||
clearArmed = true;
|
||||
if (btn) btn.textContent = "再点一次清空";
|
||||
clearTimer = setTimeout(() => { clearArmed = false; if (btn) btn.textContent = "清空"; }, 3000);
|
||||
return;
|
||||
}
|
||||
clearTimeout(clearTimer); clearArmed = false;
|
||||
if (btn) btn.textContent = "清空";
|
||||
try {
|
||||
await chrome.runtime.sendMessage({ action: "clearHistory" });
|
||||
setStatus("评论记录已清空");
|
||||
log("评论记录已清空", "step");
|
||||
} catch (e) { setStatus("清空失败:" + String(e), true); }
|
||||
refreshStats();
|
||||
}
|
||||
|
||||
// ============ LLM 测试连接 ============
|
||||
|
||||
async function testCfg() {
|
||||
const btn = $("testCfg"), msg = $("testMsg");
|
||||
if (btn) btn.disabled = true;
|
||||
if (msg) msg.textContent = "测试中…";
|
||||
try {
|
||||
const res = await chrome.runtime.sendMessage({
|
||||
action: "callAiApi",
|
||||
baseUrl: settings.baseUrl, apiKey: settings.apiKey, model: settings.model,
|
||||
prompt: "只回复两个字:正常", maxTokens: 60, timeoutMs: 60000
|
||||
});
|
||||
if (res && res.ok) {
|
||||
if (msg) { msg.textContent = "✓ 成功:" + String(res.content).slice(0, 20); msg.style.color = "#0a7"; }
|
||||
log("LLM 测试成功", "ok");
|
||||
} else {
|
||||
if (msg) { msg.textContent = "✗ 失败:" + ((res && res.error) || "未知"); msg.style.color = "#d93025"; }
|
||||
log("LLM 测试失败:" + ((res && res.error) || "未知"), "fail");
|
||||
}
|
||||
} catch (e) {
|
||||
if (msg) { msg.textContent = "✗ 失败:" + String(e); msg.style.color = "#d93025"; }
|
||||
}
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
|
||||
// ============ 初始化 ============
|
||||
|
||||
async function init() {
|
||||
let saved = {};
|
||||
try { saved = (await chrome.storage.local.get("settings")).settings || {}; } catch (e) {}
|
||||
settings = Object.assign({}, DEFAULTS, saved);
|
||||
fillForm();
|
||||
refreshStats();
|
||||
log("就绪:填关键词后点「▶ 开始执行」");
|
||||
|
||||
for (const k of FIELDS) {
|
||||
const el = $(k);
|
||||
if (!el) continue;
|
||||
el.addEventListener("change", () => {
|
||||
const v = (el.type === "number") ? (Number(el.value) || 0) : el.value;
|
||||
saveField(k, v);
|
||||
if (k === "mode") refreshCustomVisibility();
|
||||
if (k === "mode" || k === "apiKey" || k === "baseUrl" || k === "model" || k === "customText") refreshModeWarn();
|
||||
});
|
||||
if (el.tagName === "TEXTAREA" || el.type === "text" || el.type === "password") {
|
||||
el.addEventListener("input", () => {
|
||||
settings[k] = el.value;
|
||||
saveField(k, el.value);
|
||||
if (k === "customText") refreshModeWarn(); // 边填边消警告
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const promo = $("promoLink");
|
||||
if (promo) promo.addEventListener("click", () => {
|
||||
if (!settings.baseUrl) saveField("baseUrl", DEFAULTS.baseUrl);
|
||||
chrome.tabs.create({ url: "https://ai.88531.cn/register" });
|
||||
});
|
||||
|
||||
const start = $("btnStart");
|
||||
if (start) start.addEventListener("click", startRun);
|
||||
const stop = $("btnStopRun");
|
||||
if (stop) stop.addEventListener("click", stopRun);
|
||||
const test = $("testCfg");
|
||||
if (test) test.addEventListener("click", testCfg);
|
||||
const clear = $("btnClearHist");
|
||||
if (clear) clear.addEventListener("click", onClearHist);
|
||||
const dl = $("btnDownload");
|
||||
if (dl) dl.addEventListener("click", downloadCurrent);
|
||||
|
||||
// 获取最新版本 → 官网页(新标签页)
|
||||
const latest = $("btnLatest");
|
||||
if (latest) latest.addEventListener("click", () => {
|
||||
chrome.tabs.create({ url: "https://dl.88531.cn/bili.html" });
|
||||
log("已打开官网页面(新标签页):dl.88531.cn/bili.html", "step");
|
||||
});
|
||||
|
||||
// 授权区
|
||||
const buy = $("licBuy");
|
||||
if (buy) buy.addEventListener("click", () => chrome.tabs.create({ url: "https://auth.88531.cn" }));
|
||||
const act = $("btnActivate");
|
||||
if (act) act.addEventListener("click", activateCard);
|
||||
const card = $("cardKey");
|
||||
if (card) card.addEventListener("input", () => {
|
||||
const msg = $("actMsg");
|
||||
if (msg && msg.textContent) { msg.textContent = ""; }
|
||||
});
|
||||
const mach = $("licMachine");
|
||||
if (mach) mach.addEventListener("click", async () => {
|
||||
const id = licState && licState.machine;
|
||||
if (!id) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(id);
|
||||
mach.title = "已复制:" + id;
|
||||
} catch (e) {
|
||||
const tmp = document.createElement("textarea");
|
||||
tmp.value = id; document.body.appendChild(tmp); tmp.select();
|
||||
try { document.execCommand("copy"); } catch (e2) {}
|
||||
document.body.removeChild(tmp);
|
||||
}
|
||||
const t = $("machineIdText");
|
||||
if (t) { const old = t.textContent; t.textContent = old + "(已复制)"; setTimeout(() => { t.textContent = old; }, 1500); }
|
||||
});
|
||||
loadLicense();
|
||||
}
|
||||
|
||||
init();
|
||||
})();
|
||||
Reference in new issue
Block a user