diff --git a/src/clis/bilibili/subtitle.ts b/src/clis/bilibili/subtitle.ts index 742a499..c50f737 100644 --- a/src/clis/bilibili/subtitle.ts +++ b/src/clis/bilibili/subtitle.ts @@ -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', diff --git a/src/explore.ts b/src/explore.ts index ea5ae21..caef897 100644 --- a/src/explore.ts +++ b/src/explore.ts @@ -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; diff --git a/test_subtitle.ts b/test_subtitle.ts deleted file mode 100644 index 9f57ca8..0000000 --- a/test_subtitle.ts +++ /dev/null @@ -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();