Skip to content

feat(auth): 支持 --cookie 手动导入登录态 & 修复 macOS 登录链路#2

Open
1yx wants to merge 1 commit into
Marvae:mainfrom
1yx:feat/cookie-login-and-keychain-hint
Open

feat(auth): 支持 --cookie 手动导入登录态 & 修复 macOS 登录链路#2
1yx wants to merge 1 commit into
Marvae:mainfrom
1yx:feat/cookie-login-and-keychain-hint

Conversation

@1yx

@1yx 1yx commented Jul 4, 2026

Copy link
Copy Markdown

feat(auth): 支持 --cookie 手动导入登录态 & 修复 puppeteer dbcl2 抓取

背景

在 macOS 上,douban login 自动登录需授予 keychain 权限以解密豆瓣网的 cookie,常因钥匙串/磁盘权限失败/用户不愿授权而抓不到登录态;同时 puppeteer 自动登录流程存在一个导致必然超时的 bug,且交互式登录路径在多 Chrome profile 用户身上抓不到 cookie、会触发无关浏览器的授权弹窗、存在惰性写盘导致的「假成功」。本 PR 提供手动导入 Cookie 的兜底方式,修复 puppeteer 路径,自动对齐 Chrome profile,选择系统默认浏览器,并修正 douban whoami 命令会误触发交互式登录的问题。

改动

1. 新增 douban login --cookie-c)手动导入登录态

支持两种入参:

  • Netscape cookies.txt 文件路径(如用「Get cookies.txt LOCALLY」扩展导出)
  • 原始 Cookie 字符串(F12 复制请求头里的 cookie 整行)

跳过浏览器登录流程,从输入中解析 dbcl2(必需)与 ck(可选)。

2. 修复 puppeteer 自动登录必然超时的 bug

旧实现用 page.waitForFunction(() => document.cookie.includes('dbcl2=')) 等待登录成功,但 dbcl2 是 HttpOnly cookie,document.cookie 读不到,必然 180s 超时。改为轮询 CDP 的 page.cookies(),并在 extractFromBrowsers 失败时于 macOS 上给出 keychain / 手动导入提示。

3. 改 ensureAuth 不再自动触发交互式登录(行为变更)

upstream 的 ensureAuth 在无缓存时会调用 loginWithBrowser(),导致 whoami / mark 等命令在 spinner 内触发 readline 等待回车,提示被 spinner(每 80ms 重写一行)盖住,表现为卡死。改为只读本地缓存,无缓存则抛错并提示显式运行 douban login

4. 自动对齐 Chrome profile

openLoginPage() 用系统默认浏览器打开登录页,Chrome 会在「最近使用的 profile」里打开;但 extractFromBrowsers() 只读 Default profile,两者错位就抓不到 cookie(多 profile 用户尤其常见)。新增 resolveChromeProfile():读取 Chrome Local Stateprofile.last_used,作为 chromeProfile 传给 sweet-cookie,使提取与实际登录的 profile 对齐。

5. 只从默认浏览器提取 cookie,避免无关授权弹窗

sweet-cookiebrowsers 列表是「全试 + 合并」、不短路;旧实现传入全部 4 个浏览器,导致 Chrome 用户也会被 Edge/Safari 的 keychain/磁盘权限弹窗打扰。新增 resolveDefaultBrowser():通过 macOS LaunchServices plist / Linux xdg-settings / Windows 注册表解析系统默认浏览器(即 openLoginPage 实际打开页面的那个)。由于 openLoginPage 只在默认浏览器打开登录页,用户只能在那里登录,cookie 也必然在那里,因此只试默认浏览器即可(检测失败退回最常见的 chrome),不再全量试所有浏览器。

6. 登录时校验 cookie 服务端有效,杜绝「假成功」

Chrome cookie 是惰性写盘:浏览器里登出后,磁盘 SQLite 仍可能残留失效 dbcl2,extractFromBrowsers 读文件会拿到失效 cookie。旧实现直接存盘并报「登录成功」,用户直到跑 whoami/mark 会出现没登录。新增 isValidSession(用 getCurrentUserProfile 校验 dbcl2 服务端有效):

  • loginWithBrowser 的「已登录」预检查:existing 必须通过校验才存盘返回,否则继续走登录流程
  • loginWithCookie--cookie 导入):解析成功后校验,失败抛明确错误(避免导入过期/错误 cookie 造成假成功)

7. 回车后轮询提取 cookie,应对 Chrome 惰性写盘(对称问题)

惰性写盘的对称问题:刚登录时 dbcl2 先在 Chrome 内存里、未及时写盘(Chromium 批量异步刷盘,实测约 30-50s),用户按回车时 extractFromBrowsers 读文件读不到,误报「未提取到 dbcl2」。改为按回车后轮询:最多 30 次、间隔 2s(覆盖约 60s flush 窗口),用 spinner 反馈(此时 readline 已关闭,spinner 安全)。若磁盘残留旧失效 dbcl2,首次读到的是旧值,因此用「值变化」判断新登录生效(新登录会换发新 token;之前未登录则任何 dbcl2 都算新);命中后再 isValidSession 校验一次。失败提示补充「Chrome 约每 30-50s 落盘一次,可稍后重试」。

8. 已登录时跳过登录页

loginWithBrowser 开头先 extractFromBrowsers() 检查是否已处于登录态,命中则直接返回,不再打开登录页或触发 puppeteer。豆瓣在浏览器里已是登录状态时,douban login 直接复用现有 cookie。

9. loginWithBrowser 增加 spinner 提示

puppeteer 阶段用 spinner 给反馈(puppeteer 缺失/失败会快速返回 null);交互阶段不用 spinner 以免盖住 readline 提示。

10. 测试

新增 src/__tests__/auth.test.ts,覆盖 parseCookieHeaderparseNetscapeCookies(含 #HttpOnly_ 前缀、引号去除、缺失 dbcl2 等边界)。

11. 文档

README 新增「登录方式」章节(手动 Cookie / puppeteer 自动登录,及 puppeteer 需自行安装以避免 ~300MB Chromium 下载)。

文件

 README.md                  |  25 +++++-
 src/__tests__/auth.test.ts |  60 ++++++++++++++
 src/auth.ts                | 375 +++++++++++++++++++++++++++++++++++++++------
 src/commands/auth.ts       |  17 ++--
 4 files changed, 423 insertions(+), 54 deletions(-)

使用示例

# 1) Netscape cookies.txt 文件
douban login --cookie www.douban.com_cookies.txt

# 2) Cookie 字符串
douban login --cookie "dbcl2=xxxxx"

# 3) 浏览器已登录豆瓣时,直接复用(不会重复打开登录页)
douban login

Fixes #1

在 macOS 上 douban login 自动登录常因 keychain 权限失败而抓不到登录态,
puppeteer 路径存在必然超时的 bug,交互式路径在多 profile 用户身上也抓不到
cookie、会触发无关浏览器授权弹窗、并存在惰性写盘导致的「假成功」。本提交
补齐手动导入兜底并修复整条登录链路。

主要改动:

1. login --cookie:支持 Netscape cookies.txt 文件路径或原始 Cookie 字符串,
   解析 dbcl2(必需)+ ck(可选),跳过浏览器流程。

2. 修复 puppeteer:dbcl2 是 HttpOnly,旧实现 waitForFunction 读
   document.cookie 永远不成立、必然 180s 超时;改为轮询 CDP page.cookies()。

3. ensureAuth 不再自动触发交互式登录(行为变更):upstream 在 whoami/mark
   等命令无缓存时会调 loginWithBrowser,spinner 会盖住 readline 提示表现为
   卡死;改为只读缓存,无缓存抛错提示显式 douban login。

4. 自动对齐 Chrome profile:resolveChromeProfile 读 Local State 的
   profile.last_used 作为 chromeProfile,与 openLoginPage 实际打开的 profile
   对齐(旧实现只读 Default,多 profile 用户抓不到 cookie)。

5. 只从默认浏览器提取 cookie:resolveDefaultBrowser 通过 macOS
   LaunchServices plist / Linux xdg-settings / Windows 注册表解析系统默认
   浏览器,只试它一个(检测失败退回 chrome),消除无关浏览器的授权弹窗。

6. 登录时校验 cookie 服务端有效:Chrome cookie 惰性写盘,磁盘可能残留失效
   dbcl2;新增 isValidSession(用 getCurrentUserProfile 校验),loginWithBrowser
   预检查与 loginWithCookie 导入都需通过校验才存盘,杜绝「假成功」。

7. 回车后轮询提取 cookie:刚登录时新 dbcl2 未及时写盘(Chromium 批量异步
   刷盘,实测约 30-50s),改为最多轮询 30 次、间隔 2s(覆盖约 60s),用值
   变化判断新登录生效,spinner 反馈,命中后再校验一次。

8. 已登录时跳过登录页:loginWithBrowser 开头先 extractFromBrowsers,命中
   有效 session 直接复用,不再重复打开登录页。

9. loginWithBrowser 加 spinner 反馈(交互阶段除外,避免盖住 readline)。

测试:新增 src/__tests__/auth.test.ts,覆盖 parseCookieHeader 与
parseNetscapeCookies(含 #HttpOnly_ 前缀、引号去除、缺失 dbcl2 等边界)。

文档:README 新增「登录方式」章节。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failed Login

1 participant