feat(extension+backend): 插件直接在浏览器里抓 B 站字幕,跳过后端 download_subtitles

之前 B 站字幕优先逻辑放在后端的 BilibiliSubtitleFetcher,需要后端通过 CookieConfigManager
管理 SESSDATA cookie 才能拿 AI 字幕。这次改为:插件在用户浏览器里直接抓字幕,
天然带着用户当前登录态的 cookie;后端只负责把传过来的字幕当作转写缓存。

extension:
- 新增 logic/bilibili-subtitle.ts,调 /x/web-interface/view → /x/player/wbi/v2 → 字幕 URL JSON
  · service worker fetch 走 credentials:'include',借 manifest host_permissions:'*://*/*'
    自动带 .bilibili.com 域 cookie,并绕过 CORS
  · 优先级:人工中文 > AI 中文 > 任意非空
- popup start() 与 background startTask() 在 platform === 'bilibili' 时先调一次抓取,
  结果作为 prefetched_transcript 字段塞到 /api/generate_note payload
- types.ts GenerateRequest 增加 prefetched_transcript 字段

backend:
- VideoRequest 增加可选 prefetched_transcript: dict
- generate_note endpoint 收到时调 _persist_prefetched_transcript() 写到
  NOTE_OUTPUT_DIR/<task_id>_transcript.json;NoteGenerator 的 cache-hit 逻辑天然命中,
  跳过 downloader.download_subtitles 和音频转写,直接走 GPT 总结
- 字幕清洗:去掉空 segment、必要时合成 full_text、language 默认 'zh'

效果:B 站登录用户的视频,从用户点击到 GPT 拿到全文,省掉一次后端 → B 站 API 的来回,
也彻底告别了 backend 那侧的 cookie 配置心智负担。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
huangjianwu
2026-05-07 12:23:16 +08:00
parent d64741628b
commit 406789f834
5 changed files with 187 additions and 0 deletions

View File

@@ -2,6 +2,7 @@ import { onMessage } from 'webext-bridge/background'
import type { Settings, TaskRecord } from '~/logic/types'
import { DEFAULT_SETTINGS, MAX_TASKS, SETTINGS_KEY, TASKS_KEY } from '~/logic/constants'
import { detectPlatform } from '~/logic/platform'
import { fetchBilibiliSubtitle } from '~/logic/bilibili-subtitle'
// only on dev mode
if (import.meta.hot) {
@@ -65,6 +66,10 @@ async function startTask(url: string): Promise<{ ok: boolean, taskId?: string, e
return { ok: false, error: '请先在设置页选择供应商与模型' }
const backend = settings.backendUrl.replace(/\/$/, '')
// B 站:先在浏览器里抓字幕(带本地登录态 cookie随提交带过去
const prefetched = platform === 'bilibili' ? await fetchBilibiliSubtitle(url) : null
try {
const res = await fetch(`${backend}/api/generate_note`, {
method: 'POST',
@@ -82,6 +87,7 @@ async function startTask(url: string): Promise<{ ok: boolean, taskId?: string, e
...(settings.screenshot ? ['screenshot'] : []),
...(settings.link ? ['link'] : []),
],
prefetched_transcript: prefetched ?? undefined,
}),
})
if (!res.ok)