fix: apply remaining code review fixes to subtitle.ts and explore.ts

- subtitle.ts: URL injection fix (JSON.stringify), --lang option,
  debug console.error removal, comment numbering
- explore.ts: clickLabels targeted clicking, empty-value penalty
  moved to scoreEndpoint(), removed console.log debug output
- Deleted stale test_subtitle.ts
This commit is contained in:
jackwener committed 2026-03-15 18:12:40 +08:00
1 parent 2dacba8244
commit 4fc4c4b92f
3 files changed
+31 -49

No files matched your search

+13 -8
View File
@@ -9,6 +9,7 @@ cli({
strategy: Strategy.COOKIE,
args: [
{ name: 'bvid', required: true },
{ name: 'lang', required: false, help: '字幕语言代码 (如 zh-CN, en-US, ai-zh),默认取第一个' },
],
columns: ['index', 'from', 'to', 'content'],
func: async (page: IPage | null, kwargs: any) => {
@@ -42,18 +43,23 @@ cli({
throw new Error('此视频没有发现外挂或智能字幕。');
}
const firstSubUrl = subtitles[0].subtitle_url;
if (!firstSubUrl || firstSubUrl === '') {
// 4. 选择目标字幕语言
const target = kwargs.lang
? subtitles.find((s: any) => s.lan === kwargs.lang) || subtitles[0]
: subtitles[0];
const targetSubUrl = target.subtitle_url;
if (!targetSubUrl || targetSubUrl === '') {
throw new Error('[风控拦截/未登录] 获取到的 subtitle_url 为空!请确保 CLI 已成功登录且风控未封锁此账号。');
}
const finalUrl = firstSubUrl.startsWith('//') ? 'https:' + firstSubUrl : firstSubUrl;
const finalUrl = targetSubUrl.startsWith('//') ? 'https:' + targetSubUrl : targetSubUrl;
// 4. 解析并拉取 CDN 的 JSON 文件
// 5. 解析并拉取 CDN 的 JSON 文件
const fetchJs = `
(async () => {
const url = '${finalUrl}';
const url = ${JSON.stringify(finalUrl)};
const res = await fetch(url);
const text = await res.text();
@@ -75,8 +81,7 @@ cli({
const items = await page.evaluate(fetchJs);
if (items?.error) {
console.error("[DEBUG] Subtitle Parse Error:", items);
throw new Error(`字幕获取失败: ${items.error}`);
throw new Error(`字幕获取失败: ${items.error}${items.text ? ' — ' + items.text : ''}`);
}
const finalItems = items?.data || [];
@@ -84,7 +89,7 @@ cli({
throw new Error('解析到的字幕列表对象不符合数组格式');
}
// 4. 数据映射
// 6. 数据映射
return finalItems.map((item: any, idx: number) => ({
index: idx + 1,
from: Number(item.from || 0).toFixed(2) + 's',
+18 -8
View File
@@ -184,6 +184,8 @@ function scoreEndpoint(ep: { contentType: string; responseAnalysis: any; pattern
if (ep.hasPaginationParam) s += 2;
if (ep.hasLimitParam) s += 2;
if (ep.status === 200) s += 2;
// Anti-Bot Empty Value Detection: penalize JSON endpoints returning empty data
if (ep.responseAnalysis && ep.responseAnalysis.itemCount === 0 && ep.contentType.includes('json')) s -= 3;
return s;
}
@@ -327,12 +329,25 @@ export async function exploreUrl(
// Step 2.5: Interactive Fuzzing (if requested)
if (opts.auto) {
try {
console.log("🤖 [Explore] Auto-fuzzing interactions enabled. Simulating clicks...");
// First: targeted clicks by label (e.g. "字幕", "CC", "评论")
if (opts.clickLabels?.length) {
for (const label of opts.clickLabels) {
const safeLabel = label.replace(/'/g, "\\'");
await page.evaluate(`
(() => {
const el = [...document.querySelectorAll('button, [role="button"], [role="tab"], a, span')]
.find(e => e.textContent && e.textContent.trim().includes('${safeLabel}'));
if (el) el.click();
})()
`);
await page.wait(1);
}
}
// Then: blind fuzzing on generic interactive elements
const clicks = await page.evaluate(INTERACT_FUZZ_JS);
console.log(`🤖 [Explore] Performed ${clicks} simulated clicks.`);
await page.wait(2); // wait for XHRs to settle
} catch (e) {
console.error("🤖 [Explore] Fuzzing failed:", e);
// fuzzing is best-effort, don't fail the whole explore
}
}
@@ -419,11 +434,6 @@ export async function exploreUrl(
args.push({ name: 'limit', type: 'int', required: false, default: 20 });
if (ep.hasPaginationParam) args.push({ name: 'page', type: 'int', required: false, default: 1 });
// Improve scoring for JSON endpoints that return empty arrays/objects (Anti-Bot Empty Value Detection)
if (ep.score >= 5 && ep.responseAnalysis?.itemCount === 0 && ep.contentType.includes('json')) {
ep.score -= 2; // Penalize empty drops
}
// Link store actions to capabilities when store-action strategy is recommended
const epStrategy = inferStrategy(ep.authIndicators);
let storeHint: { store: string; action: string } | undefined;
-33
View File
@@ -1,33 +0,0 @@
import { chromium } from 'playwright';
async function main() {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
const apis: string[] = [];
page.on('response', async (res) => {
const url = res.url();
if (url.includes('subtitle') || url.includes('player/v2') || url.includes('x/player/wbi/v2')) {
console.log("Found subtitle API candidate:", url);
}
});
console.log("Navigating to video...");
await page.goto('https://www.bilibili.com/video/BV1K3411r7F9');
await page.waitForTimeout(5000);
// Try to find subtitle info in __INITIAL_STATE__
const initialState = await page.evaluate(() => {
return (window as any).__INITIAL_STATE__;
});
if (initialState) {
console.log("Found __INITIAL_STATE__ exists!");
// check if there's bvid or aid
console.log("BVID:", initialState.bvid);
console.log("AID:", initialState.aid);
}
await browser.close();
}
main();