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:
1 parent
2dacba8244
commit
4fc4c4b92f
3 files changed
+31
-49
No files matched your search
@@ -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
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
Reference in new issue
Block a user