1仕組みと動作原理
エントリポイントはシェルラッパー + vendored な TS CLI:baoyu-fetch が直接 bun を exec して lib/cli.ts を実行し、CLI が URL に応じてアダプター(x / youtube / hn / generic)を選択します。
exec bun "$script_dir/lib/cli.ts" "$@"
スクレイピングは puppeteer ではなく、実機 Chrome + 自作 CDP クライアント(chrome-launcher でブラウザを起動し、ws で DevTools に直接接続)に依存しています。
import WebSocket from "ws";
X アダプターは DOM を読み取るだけでなくセッション機構を内蔵しています:まず CDP を使って x.com/twitter.com の cookie(auth_token と ct0 が必須)を読み取り、profile ディレクトリ配下の sidecar ファイルに cookie を平文で書き出し、次回実行時に再注入します。
await writeFile(filePath, JSON.stringify(data, null, 2));
X のデータソースはページ自体が発行する GraphQL レスポンスです:ツールは CDP ネットワークログから /graphql/ の TweetDetail や TweetResultByRestId などのレスポンスボディを抽出して解析し、自動スクロールと「Show replies」のクリックでスレッドを展開します。
entry.url.includes("TweetDetail") ||
YouTube アダプターは自作の InnerTube 呼び出しです:ページ内の ytcfg から INNERTUBE_API_KEY を取得し、ページコンテキスト上で POST /youtubei/v1/player を実行(credentials: 'include' でページの cookie を付与)、ANDROID クライアントを明示的に偽装します。
context: { client: { clientName: 'ANDROID', clientVersion: '20.10.38' } },
実行中の Chrome デバッグセッションを再利用可能:まず ps aux を走査して同一プロファイルかつ --remote-debugging-port を持つプロセスを探すか、プロファイル内の DevToolsActivePort ファイルを読み取って検出時に接続します。
const activePort = parseDevToolsActivePort(path.join(options.profileDir, "DevToolsActivePort"));
ログインウォールや CAPTCHA に遭遇した際は強行突破せず待機モードを提供:ページ上に Cloudflare や reCAPTCHA などの出現を検知すると可視化された Chrome をポップアップ表示して手動解除を促し、進行を検知するか Enter 入力で処理を再開します。
text.includes("verify you are human") ||
Markdown 生成のメインパイプラインはローカルの Defuddle/Readability + 品質スコアリングであり、品質が不十分な場合にのみターゲット URL をサードパーティサービス https://defuddle.md に渡してよりクリーンな Markdown を取得します。
const DEFUDDLE_API_ORIGIN = "https://defuddle.md";
2コア機能
3外部依存関係
| Kind | Dependency / Endpoint |
|---|---|
| package | @mozilla/readability ^0.6.0(正文提取) |
| package | defuddle ^0.17.0(本地 markdown 提取) |
| package | jsdom ^30.0.1(HTML 清洗/解析,未开 runScripts) |
| package | turndown + turndown-plugin-gfm(HTML→markdown) |
| package | chrome-launcher ^1.2.1(浏览器启动) |
| package | ws ^8.18.3(CDP WebSocket 客户端) |
| package | unified + remark-parse + remark-gfm(媒体链接收集与重写) |
| cli | bun(CLI 运行时;shell 包装 exec bun) |
| cli | Chrome/Chromium 二进制(chrome-launcher,可用 --browser-path 指定) |
| cli | ps aux(POSIX 进程扫描)与 osascript(macOS 窗口激活) |
| network | Chrome DevTools HTTP/WebSocket 端点 |
| api | 第三方 markdown 服务 defuddle.md(质量不足时把目标 URL 发过去) |
| network | YouTube watch 页、InnerTube player、caption 轨与 i.ytimg.com 缩略图 |
| network | Hacker News 条目页(jsdom 解析) |
| network | X/Twitter 页面与页面自身发出的 /graphql/ 请求(工具只观察,不构造请求) |
| network | 用户指定 URL 与页面内媒体 URL(下载时带伪造的桌面 Chrome UA) |
4推奨統制措置と判定根拠
- SKILL.md / 脚本: 未公開またはリバースエンジニアリングされた非公開 API の呼び出し / クライアント識別子の偽装
- SKILL.md / 脚本参数: 自動検出の回避、ホストサンドボックスの無効化、または SSL 証明書検証のスキップ
- SKILL.md / 会话存储: ブラウザのユーザーログイン状態およびセッション Cookie の読み取りまたは再利用
- 本地凭据 / 浏览器配置: ユーザーのログイン Cookie/セッション/キーチェーンの読み取り: CDP Network.getCookies を経由して x.com/twitter.com の cookie を読み取り(auth_token および ct0 が必須)
- scripts/: ローカルスクリプトまたはコマンドの実行: {baseDir}/scripts/baoyu-fetch <url> …(shell exec bun → lib/cli.ts、初回は bun i が必要)
- 网络端点: 外部ネットワークアクセスの宣言: ユーザーが指定した対象 URL(実ブラウザでのレンダリング。汎用ページは Readability/Defuddle/defuddle.md を経由)
- 本地工作目录: ファイルの読み書きと成果物の出力: --output で指定された Markdown/JSON(親ディレクトリは自動作成)
5二重目の独立検証
- [ok] YouTube 适配器的逆向/冒充 — transcript.ts 有 window.ytcfg?.data_?.INNERTUBE_API_KEY、页面内 POST /youtubei/v1/player?key=…(credentials: 'include')与 context.client.clientName='ANDROID' 三处,与 Main 终裁的『逆向 youtubei + 冒充官方客户端』一致。
- [ok] X cookie 明文 sidecar 落盘 — cookie-sidecar.ts 用 Network.getCookies 取 cookie 后 JSON.stringify 明文写入 <profile>/x-session-cookies.json(x/index.ts 指定 filename),无加密无 chmod;convert.ts 在开头尝试回灌、finally 里导出。
- [ok] 接管已运行调试会话 — profile.ts 解析 DevToolsActivePort 并 spawnSync('ps', ['aux']) 匹配 profileDir + --remote-debugging-port;chrome-launcher.ts 支持 --cdp-url ws:// 直连与复用日志 'Reusing Chrome debugger at …'。
- [ok] 网络日志含请求头与响应体(--debug-dir 落盘) — network-journal.ts 记录 requestHeaders / requestBody / 响应体(Network.getResponseBody),convert.ts 在写 debug 前调用 toJSON({ includeBodies: true }) 并写入 network.json。
- [ok] 第三方 defuddle.md 兜底 — DEFUDDLE_API_ORIGIN=https://defuddle.md,构建 URL 后 fetch;通用适配器仅在 outputFormat==='markdown' 时启用该兜底,JSON 模式不会外发目标 URL。
- [discrepancy] 文档与实现不一致(默认无头) — SKILL.md 第 93 行写 '# Default: headless capture, markdown to stdout'、quality-gate.md 亦写 'Default | Headless Chrome',但 cli.ts 默认 headless: false(convert.ts 在非交互模式传该值),实际会启动可见 Chrome(--no-startup-window)。使用前应知道默认并非无头。
- [discrepancy] 文档与实现不一致(默认 profile 路径) — SKILL.md 写默认 ./baoyu-skills/chrome-profile,代码实际解析为按 OS 的绝对路径(XDG_DATA_HOME / Application Support / APPDATA 下的 baoyu-skills/chrome-profile),从不使用 cwd 相对路径。
- [discrepancy] 文档未披露 cookie 导出 — SKILL.md 只写 BAOYU_CHROME_PROFILE_DIR 为『Chrome user data directory』,从未提及会导出 X cookie 到 sidecar 文件;该行为只在代码中可见——这是本次侦查中风险披露落差最大的一处。
6結論
15be54497a8d6c30…