dsh-deepseek-web-login 0.6.13 → 0.6.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,51 @@
2
2
 
3
3
  本项目遵循大致语义化版本;日期为本地时间。
4
4
 
5
+ ## 0.6.14 — 2026-09-30
6
+
7
+ **邮箱密码自动重登:「重登」一键点下去就上,另加「一键重登全部」+ 到期前自动续(开关,默认关)。**
8
+
9
+ 用户诉求(原话):「我点击重登之后又是让我重新输入账号密码,就不能直接点个重登按钮就登录上去
10
+ 不就行了?然后再加个一键重登的按钮」——因为凭证实测寿命只有 ≈2 小时(88 / 119 / 120 分钟),
11
+ 于是一天要重登好几次,每次都手敲邮箱密码。
12
+
13
+ ### 怎么做到免人工(这条是靠实测打通的)
14
+
15
+ 1. 密码登录接口:**`POST /api/v0/users/login`**,body `{email, mobile, password, area_code, device_id, os:'web'}`,
16
+ **不需要 token**,成功时 token 就在响应体的 `data.biz_data.user.token`。
17
+ ⚠️ 它**不在 `/users/` 前缀下**(网页端是 `baseUrl + "/login"` 拼的)—— 按完整路径 grep 找不到。
18
+ 2. Node 直接打会被风控挡:`{"biz_code":11,"biz_msg":"RISK_DEVICE_DETECTED"}`
19
+ —— `device_id` 只由**数美设备指纹 SDK** 产出(页面里 `window.SMSdk.getDeviceId()`)。
20
+ 3. 所以走**真实浏览器**:复用已有的 `browserLogin`(系统 Edge/Chrome + CDP + 独立 profile),
21
+ 新增 `credentials` 选项 —— 在**页面上下文**里发登录请求,指纹/WAF cookie/数美 SDK 全是真的。
22
+ 实测 `biz_code: 0` 登录成功。
23
+ 4. 成功后脚本把 token 按网页端格式写回 `localStorage.userToken` ⇒ 原来那套
24
+ 「轮询 localStorage → 抓 cookie + 指纹头」**一行都不用改**。
25
+ 5. **自动登录失败不会掐死手动路径**:退回"等用户手动登录",原因写进 `autoLoginError`。
26
+
27
+ ### 新增能力
28
+
29
+ - **「重登」一键**:`POST /login/relogin` 在"授权确实失效"那条路上,若能按账号的脱敏名匹配到
30
+ 邮箱密码凭证,就直接自动重登(不再只是弹一句提示让你手敲)。匹配不上才回退到原流程。
31
+ - **「一键重登全部」**:`POST /login/relogin-all`(面板按钮「用存的密码重登全部」)。
32
+ 串行执行、逐个回报 ✅/❌ —— 并行开 6 个浏览器只会让风控看着更像机器。
33
+ - **「到期前自动重登」开关**:存 `gate.json` 的 `autoRelogin`,**默认关闭**。
34
+ 打开后每 10 分钟检查一次(每轮重读设置文件 ⇒ 改了即时生效,不用重启),
35
+ 凭证失效或捕获超过 100 分钟就静默跑一次无头浏览器把凭证换新。
36
+ - 凭证库:`~/.dsh/web-login/credentials.json`。**单独文件、不写进账号记录** ⇒
37
+ 账号导出/导入不会把密码带出去;原子写;坏文件当"没有凭证"(不许因此崩面板)。
38
+ - 匹配规则:按**服务端的脱敏规则**本地重算比对(`davi*******+w4@gmail.com` 这种),
39
+ 对不上就返回"没有凭证"——**绝不模糊匹配**(否则会拿 +w5 的密码去登 +w6)。
40
+
41
+ ### 关于"开浏览器"这件事(用户专门问过,写在这里免得再误解)
42
+
43
+ 用的是系统里已装好的 Edge/Chrome,`--headless=new` **没有窗口**、`--user-data-dir=<临时目录>`
44
+ 是**一次性干净 profile**(不碰用户平时的浏览器数据,跑完连目录一起删),跑完即杀。
45
+ 代价:约每 2 小时一次(自动续开启时)或每次点击一次(手动),各几百毫秒到几秒。
46
+
47
+ 用例:新增 `check-relogin` 13 项(脱敏匹配含真实样本 + 逐条反例 / 凭证读写去重 /
48
+ 挑号判据三种 reason / 页面脚本的接口与特殊字符转义 / 三处接线守卫)。
49
+
5
50
  ## 0.6.13 — 2026-09-30
6
51
 
7
52
  **账号列表加「邮箱 / 手机号」标志。**
package/lib/client.js CHANGED
@@ -1781,6 +1781,38 @@ background:var(--bg2);white-space:pre-wrap;font-size:12px}
1781
1781
  });
1782
1782
  cleanupNowRow.append(cleanupNowBtn, cleanupNowHint);
1783
1783
  gateCard.append(cleanupNowRow);
1784
+ const reloginAllRow = el("div", "dsw-gate-row");
1785
+ reloginAllRow.append(el("span", "dsw-gate-label", "一键重登"));
1786
+ const reloginAllBtn = el("button", "dsw-btn ghost", "用存的密码重登全部");
1787
+ const reloginAllHint = el("span", "dsw-hint", "");
1788
+ reloginAllBtn.addEventListener("click", () => {
1789
+ reloginAllBtn.disabled = true;
1790
+ reloginAllHint.textContent = "正在自动重登(每个号起一次无头浏览器,请稍候)…";
1791
+ api("/login/relogin-all", {
1792
+ method: "POST",
1793
+ body: "{}"
1794
+ }).then((result) => {
1795
+ const rows = Array.isArray(result?.results) ? result.results : [];
1796
+ reloginAllHint.textContent = rows.length ? `完成 ${result?.okCount ?? 0}/${rows.length}:` + rows.map((r) => `${r.ok ? "✅" : "❌"}${String(r.display ?? "").slice(0, 20)}`).join(" ") : result?.error ?? "没有可重登的账号";
1797
+ }).catch((error) => {
1798
+ reloginAllHint.textContent = `重登失败:${error?.message ?? error}`;
1799
+ }).finally(() => {
1800
+ reloginAllBtn.disabled = false;
1801
+ });
1802
+ });
1803
+ reloginAllRow.append(reloginAllBtn, reloginAllHint);
1804
+ gateCard.append(reloginAllRow);
1805
+ const autoReloginRow = el("div", "dsw-gate-row");
1806
+ const autoReloginLabel = el("label", "dsw-switch");
1807
+ const autoReloginInput = el("input");
1808
+ autoReloginInput.type = "checkbox";
1809
+ const autoReloginTrack = el("span", "dsw-switch-track");
1810
+ autoReloginLabel.append(autoReloginInput, autoReloginTrack, el("span", void 0, "到期前自动重登(后台每约 2 小时静默跑一次无头浏览器)"));
1811
+ autoReloginInput.addEventListener("change", () => void saveGate({ autoRelogin: autoReloginInput.checked }));
1812
+ autoReloginRow.append(autoReloginLabel);
1813
+ gateCard.append(autoReloginRow);
1814
+ const autoReloginHint = el("p", "dsw-hint", "缓存凭证实测寿命约 2 小时;关掉它就用上面的「一键重登」手动刷。");
1815
+ gateCard.append(autoReloginHint);
1784
1816
  const cleanupHint = el("p", "dsw-hint", "");
1785
1817
  gateCard.append(cleanupHint);
1786
1818
  const cleanupRanges = el("div");
@@ -2454,6 +2486,7 @@ background:var(--bg2);white-space:pre-wrap;font-size:12px}
2454
2486
  paintAutoSwitch();
2455
2487
  serialInput.checked = g.serialToolCalls === false;
2456
2488
  paintSerialTools();
2489
+ autoReloginInput.checked = g.autoRelogin === true;
2457
2490
  const mode = g.cleanup?.mode ?? "deferred";
2458
2491
  for (const key of Object.keys(cleanupBtns)) cleanupBtns[key].classList.toggle("active", key === mode);
2459
2492
  const showRanges = mode === "deferred";