dsh-astudio-connect 0.2.0 → 0.2.1
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/README.en.md +11 -1
- package/README.md +11 -1
- package/lib/bin.js +1 -1
- package/lib/{catalog-CF9LFZ4P.js → catalog-CILzWwRh.js} +36 -0
- package/lib/client.js +12 -3
- package/lib/index.d.ts +128 -97
- package/lib/index.js +44 -11
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -214,12 +214,19 @@ One press is two requests: read the pending list, then report completion. The ba
|
|
|
214
214
|
| Outcome | Display |
|
|
215
215
|
|---|---|
|
|
216
216
|
| Claimed | Balance updates in place, `+100` appears in green beside the button and fades after 6 seconds |
|
|
217
|
-
|
|
|
217
|
+
| Upstream answered `11003002` — booked before this press | "Already claimed today"; the balance does not move |
|
|
218
|
+
| The pending list holds no reward | "Nothing to claim right now"; the balance does not move |
|
|
218
219
|
| Sign-in expired (`code: 80000`, or 401/403) | Numbers cleared, "Sign-in expired" |
|
|
219
220
|
| Upstream error / no `clientType` for this platform | "Unavailable" |
|
|
220
221
|
|
|
222
|
+
The third row is deliberately not worded as "already claimed": the two are not the same state. `11003002` is upstream *saying* the popup was booked; an empty list only says the service is holding no reward at this moment — it may not have been issued yet, or the account may not have travelled with the request. Calling that "already claimed" tells someone to stop pressing a button that would still have worked, which is exactly what an earlier version did.
|
|
223
|
+
|
|
221
224
|
Two identity headers are mandatory (without either, the gateway answers `11001001`): `clientType` (`21` on Windows, `22` on macOS) and `studioVersion`. The latter is only shape-checked, which is why the plugin sends a constant instead of reading the installed app's version — and why this works with AStudio uninstalled.
|
|
222
225
|
|
|
226
|
+
One more detail `pending` will not forgive: `popupId` is a **positive integer**, and `complete` is parsed with `Number.isSafeInteger` upstream — a string makes the desktop client treat the whole popup config as invalid. The plugin used to read it as a string, which filtered out every reward the service sent while still reporting "already claimed", leaving the reward sitting there for the client to book. It now parses ids the way the client does and skips anything non-positive (the `-3` a preview uses, for instance).
|
|
227
|
+
|
|
228
|
+
These endpoints are also scoped to the **account**, not just the session: the desktop client sends `ssoSessionId`, `sso_sessionid`, `account_id` and `token` on every account call. The plugin now sends all four, where it used to send only the session.
|
|
229
|
+
|
|
223
230
|
> This is the plugin's **only** route that changes account state (`POST /astudio/claim`). It writes no local file, and it never touches other popup kinds in the pending list (announcements, feature tours): completing a popup it does not understand would mean deciding something on the user's behalf without knowing what.
|
|
224
231
|
|
|
225
232
|
If this page reports "Unavailable" and **Refresh** does nothing, first confirm the plugin artifacts match the running DSH version: DSH loads plugins at startup, so after rebuilding you must **fully quit and restart DSH** (refreshing the page is not enough). If the nav row is missing entirely, the installed plugin predates this section — it shipped in the same change that replaced the composer badge with it.
|
|
@@ -276,6 +283,7 @@ Five write routes (probing, visibility, claim, and the two sign-in routes) requi
|
|
|
276
283
|
|
|
277
284
|
| Plugin version | DSH kernel range it supports |
|
|
278
285
|
|---|---|
|
|
286
|
+
| `0.2.1` | `^0.2.1-rc.2` (from `0.2.1-rc.2`, below `0.3.0`) |
|
|
279
287
|
| `0.2.0` | `^0.2.1-rc.2` (from `0.2.1-rc.2`, below `0.3.0`) |
|
|
280
288
|
| `0.1.1` | `^0.2.1-rc.2` (from `0.2.1-rc.2`, below `0.3.0`) |
|
|
281
289
|
| `0.1.0` | `0.2.0-rc.2` (exact) |
|
|
@@ -284,6 +292,8 @@ The plugin declares a kernel range in `peerDependencies`; the host's `evaluatePl
|
|
|
284
292
|
|
|
285
293
|
`0.2.0` carries the same kernel range as `0.1.1` and adds nothing but features: the points section in Settings (balance and the daily claim) and WeChat QR sign-in. One thing to know before upgrading — **from `0.2.0` the plugin writes a credential file to disk**. That is unavoidable for QR sign-in: without the app, somebody has to hold the session. It lives at `$DSH_HOME/.astudio-login.json`, mode `0600`, created only when you scan a code and deleted when you sign out. If you want the old behaviour — read the app's credentials and write nothing at all — stay on `0.1.1`.
|
|
286
294
|
|
|
295
|
+
`0.2.1` is a fix-only release with an unchanged kernel range. **If you are on `0.2.0`, upgrade: its Claim button never actually claimed anything.** Pending popups carry `popupId` as a positive integer, but the plugin read it as a string, so every real reward was filtered out locally, the UI always said "already claimed today", and the reward sat untouched on the server — opening AStudio still claimed it. `0.2.1` parses the id the way the app does, separates "no pending reward" from an explicit `11003002` answer, and sends the four account-scoped cookies the claim endpoints expect.
|
|
296
|
+
|
|
287
297
|
## When the upstream refuses a request
|
|
288
298
|
|
|
289
299
|
maas-api answers a non-conforming request with a bare `10005 invalid request` and never names the field. The plugin heals the request in the forwarding layer, in this order, without DSH or pi-ai having to change:
|
package/README.md
CHANGED
|
@@ -214,12 +214,19 @@ GET agent.xfyun.cn/xingchen-studio/bot/models/configs → Cookie 用上
|
|
|
214
214
|
| 结果 | 表现 |
|
|
215
215
|
|---|---|
|
|
216
216
|
| 领取成功 | 余额就地更新,按钮左侧显示 `+100`(绿色),6 秒后淡出 |
|
|
217
|
-
|
|
|
217
|
+
| 上游明确回 `11003002`(这次点击之前就已入账) | 显示「今日已领过」,余额不动 |
|
|
218
|
+
| 待领列表里没有这份奖励 | 显示「当前没有可领取的奖励」,余额不动 |
|
|
218
219
|
| 登录已失效(`code: 80000` 或 401/403) | 数字清空并显示「登录已失效」 |
|
|
219
220
|
| 上游异常 / 平台无对应 `clientType` | 显示「暂不可用」 |
|
|
220
221
|
|
|
222
|
+
第三种情况不写成「今日已领过」,因为这两种状态并不等价:`11003002` 是上游**明确**说这条弹窗早就被核销了;而列表为空只说明服务端此刻没挂着奖励,也可能只是今天还没下发,或者账号信息没有传全。把后者说成「已领过」,人就再也不会去点第二次了——早期版本正是在这里骗了用户。
|
|
223
|
+
|
|
221
224
|
两个身份头是硬性要求(缺任一个,网关直接回 `11001001`):`clientType`(Windows `21` / macOS `22`)与 `studioVersion`。后者只做形态校验,因此插件用常量而不是去读已安装的 AStudio 版本——这正是它能做到「不需要装 AStudio」的原因。
|
|
222
225
|
|
|
226
|
+
`pending` 的返回里还有一个必须按原样解析的细节:`popupId` 是**正整数**,`complete` 上游也用 `Number.isSafeInteger` 校验,客户端拿到字符串会直接判定整条弹窗配置非法。插件早期按字符串收,结果服务端下发的每一条奖励都被自己过滤掉了,界面照样报「今日已领过」,而奖励其实还老老实实躺在服务端——客户端一点就能领。现在按客户端的方式解析,非正整数的 id(例如预览用的 `-3`)直接跳过。
|
|
227
|
+
|
|
228
|
+
这几个接口同时是按**账户**鉴权的:桌面端每个账号请求都带 `ssoSessionId`、`sso_sessionid`、`account_id`、`token` 四个 cookie。插件现在也照原样带上(早期只带 `ssoSessionId`)。
|
|
229
|
+
|
|
223
230
|
> 这是本插件**唯一**会改动账户状态的接口(`POST /astudio/claim`)。它不改本地任何文件,也不碰 `pending` 里其它类型的弹窗(公告、功能引导等):不理解的弹窗一律代用户完成,等于替用户做了一个不知道是什么的决定。
|
|
224
231
|
|
|
225
232
|
若这一页显示「暂不可用」而「刷新」也无效,先确认插件产物是否与运行中的 DSH 版本匹配:DSH 在启动时加载插件,重新构建后必须**完全退出并重启 DSH**(仅刷新页面不够)。整行导航都不见了,则说明当前装的插件版本早于这个分区——它和输入框徽标是同一批改动,徽标被替换成了这一页。
|
|
@@ -276,6 +283,7 @@ GET agent.xfyun.cn/xingchen-studio/bot/models/configs → Cookie 用上
|
|
|
276
283
|
|
|
277
284
|
| 插件版本 | 支持的 DSH 内核版本 |
|
|
278
285
|
|---|---|
|
|
286
|
+
| `0.2.1` | `^0.2.1-rc.2`(`0.2.1-rc.2` 起,`0.3.0` 以下) |
|
|
279
287
|
| `0.2.0` | `^0.2.1-rc.2`(`0.2.1-rc.2` 起,`0.3.0` 以下) |
|
|
280
288
|
| `0.1.1` | `^0.2.1-rc.2`(`0.2.1-rc.2` 起,`0.3.0` 以下) |
|
|
281
289
|
| `0.1.0` | `0.2.0-rc.2`(精确版本) |
|
|
@@ -284,6 +292,8 @@ GET agent.xfyun.cn/xingchen-studio/bot/models/configs → Cookie 用上
|
|
|
284
292
|
|
|
285
293
|
`0.2.0` 的内核范围与 `0.1.1` 完全一致,发的只有功能:设置页的积分分区(看余额、领每日积分)与微信扫码登录。升级前需要知道一件事——**从 `0.2.0` 起插件会在本地落盘一个凭据文件**。这是扫码登录的必然结果:不装客户端就只能自己存会话。文件是 `$DSH_HOME/.astudio-login.json`,权限 `0600`,只在你主动扫码或点「退出登录」时增删;如果你只想要「读完客户端凭据就走、一个字节也不写」的旧行为,留在 `0.1.1`。
|
|
286
294
|
|
|
295
|
+
`0.2.1` 是纯修复版,内核范围不变。**装了 `0.2.0` 的请升级:那一版的「领取」按钮实际领不到积分。** 待领弹窗的 `popupId` 是正整数,插件却按字符串接收,于是每条真实奖励都在本地被过滤掉,界面一律报「今日已领过」,而奖励还挂在服务端——打开 AStudio 客户端照样能领。`0.2.1` 按客户端的方式解析 id,把「待领列表为空」与「上游明确回 `11003002`」拆成两种结果,领取请求也补齐了按账户鉴权的四个 cookie。
|
|
296
|
+
|
|
287
297
|
## 请求被上游拒绝时
|
|
288
298
|
|
|
289
299
|
maas-api 对不合规的请求只回一句 `10005 invalid request`,不说明是哪个字段。插件按下面的顺序自愈,全部发生在转发层,DSH 与 pi-ai 不需要改动:
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as createAStudioAuth, t as createAStudioCatalog } from "./catalog-
|
|
2
|
+
import { i as createAStudioAuth, t as createAStudioCatalog } from "./catalog-CILzWwRh.js";
|
|
3
3
|
import { existsSync, realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/bin.ts
|
|
@@ -518,6 +518,32 @@ function createAStudioAuth(options = {}) {
|
|
|
518
518
|
};
|
|
519
519
|
/** The chat token in use: our own sign-in wins over the app's. */
|
|
520
520
|
const resolvedToken = () => own()?.modelBearerToken ?? clientToken();
|
|
521
|
+
/**
|
|
522
|
+
* The cookie parts the account APIs authenticate with.
|
|
523
|
+
*
|
|
524
|
+
* The desktop client sends all four cookies (`ssoSessionId`, `sso_sessionid`,
|
|
525
|
+
* `account_id`, `token`) on every account call, and the reward endpoints are
|
|
526
|
+
* the ones that care about the extra two: `pending` is written against an
|
|
527
|
+
* identified account, not just a session. `token` is missing for a sign-in
|
|
528
|
+
* this plugin made itself — the QR exchange returns a model key, not an
|
|
529
|
+
* account token — so it is omitted rather than faked.
|
|
530
|
+
*/
|
|
531
|
+
const claimCredentials = () => {
|
|
532
|
+
const session = own();
|
|
533
|
+
if (session !== void 0) return {
|
|
534
|
+
ssoSessionId: session.ssoSessionId,
|
|
535
|
+
accountId: session.accountId
|
|
536
|
+
};
|
|
537
|
+
const ssoSessionId = readClientSsoSessionId(sessionPath());
|
|
538
|
+
if (ssoSessionId === void 0) return void 0;
|
|
539
|
+
const accountId = readSessionField(sessionPath(), "accountId");
|
|
540
|
+
const token = readSessionField(sessionPath(), "token");
|
|
541
|
+
return {
|
|
542
|
+
ssoSessionId,
|
|
543
|
+
...accountId === void 0 ? {} : { accountId },
|
|
544
|
+
...token === void 0 ? {} : { token }
|
|
545
|
+
};
|
|
546
|
+
};
|
|
521
547
|
/** The model key in use, with the same precedence. */
|
|
522
548
|
const resolvedModelToken = () => own()?.modelBearerToken ?? clientModelToken();
|
|
523
549
|
return {
|
|
@@ -538,6 +564,16 @@ function createAStudioAuth(options = {}) {
|
|
|
538
564
|
return own()?.ssoSessionId ?? readClientSsoSessionId(sessionPath());
|
|
539
565
|
},
|
|
540
566
|
/**
|
|
567
|
+
* The same credential, in the shape the claim's cookie needs.
|
|
568
|
+
*
|
|
569
|
+
* Separate from {@link ssoSessionId} because the reward endpoints want the
|
|
570
|
+
* account identified by cookie rather than by session alone, and only this
|
|
571
|
+
* module knows where the account id and token live.
|
|
572
|
+
*/
|
|
573
|
+
claimCredentials() {
|
|
574
|
+
return claimCredentials();
|
|
575
|
+
},
|
|
576
|
+
/**
|
|
541
577
|
* The `astron-session.json` this store reads the app's credentials from.
|
|
542
578
|
*
|
|
543
579
|
* Still reported for the status card, which names the file it looked at —
|
package/lib/client.js
CHANGED
|
@@ -1015,7 +1015,7 @@ window.__ModuleLoader__.load({
|
|
|
1015
1015
|
function isClaimDocument(value) {
|
|
1016
1016
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
1017
1017
|
const status = value["status"];
|
|
1018
|
-
return status === "claimed" || status === "none" || status === "signed-out" || status === "expired" || status === "unavailable";
|
|
1018
|
+
return status === "claimed" || status === "already" || status === "none" || status === "signed-out" || status === "expired" || status === "unavailable";
|
|
1019
1019
|
}
|
|
1020
1020
|
/** Narrow an unknown JSON value to the QR start route's document. */
|
|
1021
1021
|
function isQrStartDocument(value) {
|
|
@@ -1636,6 +1636,13 @@ window.__ModuleLoader__.load({
|
|
|
1636
1636
|
});
|
|
1637
1637
|
return;
|
|
1638
1638
|
}
|
|
1639
|
+
if (result.status === "already") {
|
|
1640
|
+
setNotice({
|
|
1641
|
+
ok: false,
|
|
1642
|
+
text: t("claimAlready")
|
|
1643
|
+
});
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1639
1646
|
if (result.status === "none") {
|
|
1640
1647
|
setNotice({
|
|
1641
1648
|
ok: false,
|
|
@@ -1792,7 +1799,8 @@ window.__ModuleLoader__.load({
|
|
|
1792
1799
|
claiming: "Claiming…",
|
|
1793
1800
|
claimed: "+{points}",
|
|
1794
1801
|
claimedUnknown: "Claimed",
|
|
1795
|
-
|
|
1802
|
+
claimAlready: "Already claimed today",
|
|
1803
|
+
claimNone: "Nothing to claim right now — it may already be booked by AStudio, or not issued yet",
|
|
1796
1804
|
pointsNav: "AStudio points",
|
|
1797
1805
|
pointsDailyHint: "One daily login reward per day — the same one the AStudio app books.",
|
|
1798
1806
|
pointsSignedOut: "AStudio is not signed in; the balances appear here once it is.",
|
|
@@ -1868,7 +1876,8 @@ window.__ModuleLoader__.load({
|
|
|
1868
1876
|
claiming: "领取中…",
|
|
1869
1877
|
claimed: "+{points}",
|
|
1870
1878
|
claimedUnknown: "已领取",
|
|
1871
|
-
|
|
1879
|
+
claimAlready: "今日已领过",
|
|
1880
|
+
claimNone: "当前没有可领取的奖励——可能已随 AStudio 入账,或今日尚未发放",
|
|
1872
1881
|
pointsNav: "AStudio 积分",
|
|
1873
1882
|
pointsDailyHint: "每日登录奖励每天一份,与 AStudio 客户端领的是同一份。",
|
|
1874
1883
|
pointsSignedOut: "AStudio 未登录,登录后这里会显示账户积分。",
|
package/lib/index.d.ts
CHANGED
|
@@ -246,7 +246,15 @@ type AStudioWebClaim = {
|
|
|
246
246
|
points?: number;
|
|
247
247
|
balance: AStudioWebPoints;
|
|
248
248
|
} |
|
|
249
|
-
/**
|
|
249
|
+
/**
|
|
250
|
+
* Upstream answered `11003002` for every popup found: today's reward was
|
|
251
|
+
* booked before this press. The one state that proves a claim already
|
|
252
|
+
* happened, as opposed to merely finding nothing.
|
|
253
|
+
*/
|
|
254
|
+
{
|
|
255
|
+
status: 'already';
|
|
256
|
+
} |
|
|
257
|
+
/** Signed in, but the account service is holding no reward for today. */
|
|
250
258
|
{
|
|
251
259
|
status: 'none';
|
|
252
260
|
} | {
|
|
@@ -257,6 +265,117 @@ type AStudioWebClaim = {
|
|
|
257
265
|
status: 'unavailable';
|
|
258
266
|
};
|
|
259
267
|
//#endregion
|
|
268
|
+
//#region src/points.d.ts
|
|
269
|
+
/** A read the points surface can present. */
|
|
270
|
+
type AStudioPointsResult = AStudioWebPoints;
|
|
271
|
+
/** Options for {@link createAStudioPointsClient}. */
|
|
272
|
+
interface AStudioPointsClientOptions {
|
|
273
|
+
/**
|
|
274
|
+
* The account cookie to call with, or undefined when nobody is signed in.
|
|
275
|
+
*
|
|
276
|
+
* A provider rather than a file path: the cookie can come from the AStudio
|
|
277
|
+
* app's `astron-session.json` or from a sign-in this plugin made itself, and
|
|
278
|
+
* this module has no business knowing which.
|
|
279
|
+
*/
|
|
280
|
+
sessionId: () => string | undefined;
|
|
281
|
+
/** Injected for tests. */
|
|
282
|
+
fetchImpl?: typeof fetch;
|
|
283
|
+
/** Injected clock, for TTL tests. */
|
|
284
|
+
now?: () => number;
|
|
285
|
+
/** Per-read timeout. */
|
|
286
|
+
timeoutMs?: number;
|
|
287
|
+
}
|
|
288
|
+
/** What the plugin and route consume. */
|
|
289
|
+
interface AStudioPointsClient {
|
|
290
|
+
/**
|
|
291
|
+
* Read the balance.
|
|
292
|
+
*
|
|
293
|
+
* `fresh` bypasses the TTL cache — the section's ↻ uses it so a manual click
|
|
294
|
+
* always reaches upstream.
|
|
295
|
+
*/
|
|
296
|
+
read(options?: {
|
|
297
|
+
fresh?: boolean;
|
|
298
|
+
}): Promise<AStudioPointsResult>;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Read the `ssoSessionId` from an AStudio session file.
|
|
302
|
+
*
|
|
303
|
+
* The app's way of finding the cookie. Kept exported because the auth store and
|
|
304
|
+
* the tests still read a file directly; the points client itself takes whichever
|
|
305
|
+
* provider it is handed.
|
|
306
|
+
*
|
|
307
|
+
* Returns undefined for a missing file, malformed JSON, an absent field, or a
|
|
308
|
+
* blank value — all of which mean "not signed in", a normal state the points
|
|
309
|
+
* section names rather than an error it reports.
|
|
310
|
+
*/
|
|
311
|
+
declare function readSsoSessionId(sessionPath: string): string | undefined;
|
|
312
|
+
/**
|
|
313
|
+
* Parse the account service's balance envelope.
|
|
314
|
+
*
|
|
315
|
+
* The envelope is the legacy Astron shape — `{ flag, code, desc, data }` — and a
|
|
316
|
+
* dead session is answered with HTTP 200 plus `code: 80000`, which is why the
|
|
317
|
+
* status has to be read from the body as well as the HTTP status.
|
|
318
|
+
*/
|
|
319
|
+
declare function parseAStudioPointsBalance(payload: unknown): AStudioPointsResult;
|
|
320
|
+
/**
|
|
321
|
+
* Create the balance client.
|
|
322
|
+
*
|
|
323
|
+
* The session file is re-read on every upstream call rather than cached: when
|
|
324
|
+
* AStudio is signed in again it rewrites that file with a new `ssoSessionId`, and
|
|
325
|
+
* a cached credential would keep the points section pinned to an expired session.
|
|
326
|
+
*/
|
|
327
|
+
declare function createAStudioPointsClient(options: AStudioPointsClientOptions): AStudioPointsClient;
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/daily-reward.d.ts
|
|
330
|
+
/** Options for {@link createAStudioDailyRewardClient}. */
|
|
331
|
+
interface AStudioDailyRewardClientOptions {
|
|
332
|
+
/**
|
|
333
|
+
* The account this claim is made against, or undefined when nobody is signed in.
|
|
334
|
+
*
|
|
335
|
+
* A provider, so the credential can come from the AStudio app's session file
|
|
336
|
+
* or from a sign-in this plugin made itself — and so a re-login between two
|
|
337
|
+
* presses is picked up instead of pinning the first session.
|
|
338
|
+
*/
|
|
339
|
+
credentials: () => AStudioDailyRewardCredentials | undefined;
|
|
340
|
+
/** Balance client used to report the post-claim balance. */
|
|
341
|
+
points: AStudioPointsClient;
|
|
342
|
+
/** Injected for tests. */
|
|
343
|
+
fetchImpl?: typeof fetch;
|
|
344
|
+
/** Overrides {@link astudioClientType}; `undefined` means "this platform has none". */
|
|
345
|
+
clientType?: string | undefined;
|
|
346
|
+
/** Overrides {@link ASTRO_STUDIO_VERSION}. */
|
|
347
|
+
studioVersion?: string;
|
|
348
|
+
/** Per-call timeout. */
|
|
349
|
+
timeoutMs?: number;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The cookie parts the account APIs authenticate with.
|
|
353
|
+
*
|
|
354
|
+
* Mirrors what the desktop client sends: `ssoSessionId` and `sso_sessionid`
|
|
355
|
+
* are the same value under both names, and `account_id`/`token` are what make
|
|
356
|
+
* the request *account-scoped* rather than merely session-scoped. `token` is
|
|
357
|
+
* absent for a QR sign-in this plugin made itself — that exchange returns a
|
|
358
|
+
* model key, not an account token — and is then left out rather than guessed.
|
|
359
|
+
*/
|
|
360
|
+
interface AStudioDailyRewardCredentials {
|
|
361
|
+
ssoSessionId: string;
|
|
362
|
+
accountId?: string;
|
|
363
|
+
token?: string;
|
|
364
|
+
}
|
|
365
|
+
/** What the plugin and the claim route consume. */
|
|
366
|
+
interface AStudioDailyRewardClient {
|
|
367
|
+
/** Book today's reward, if the account service is holding one. */
|
|
368
|
+
claim(): Promise<AStudioWebClaim>;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Create the claim client.
|
|
372
|
+
*
|
|
373
|
+
* The session file is re-read on every call for the same reason the balance
|
|
374
|
+
* reader does: AStudio rewrites it on re-login, and a cached credential would
|
|
375
|
+
* pin this to an expired session.
|
|
376
|
+
*/
|
|
377
|
+
declare function createAStudioDailyRewardClient(options: AStudioDailyRewardClientOptions): AStudioDailyRewardClient;
|
|
378
|
+
//#endregion
|
|
260
379
|
//#region src/auth.d.ts
|
|
261
380
|
/** What the shim and upstream need from a credential. */
|
|
262
381
|
interface AStudioCredentialStore {
|
|
@@ -321,6 +440,14 @@ declare function createAStudioAuth(options?: AStudioAuthOptions): {
|
|
|
321
440
|
* precedence too.
|
|
322
441
|
*/
|
|
323
442
|
ssoSessionId(): AStudioCredential;
|
|
443
|
+
/**
|
|
444
|
+
* The same credential, in the shape the claim's cookie needs.
|
|
445
|
+
*
|
|
446
|
+
* Separate from {@link ssoSessionId} because the reward endpoints want the
|
|
447
|
+
* account identified by cookie rather than by session alone, and only this
|
|
448
|
+
* module knows where the account id and token live.
|
|
449
|
+
*/
|
|
450
|
+
claimCredentials(): AStudioDailyRewardCredentials | undefined;
|
|
324
451
|
/**
|
|
325
452
|
* The `astron-session.json` this store reads the app's credentials from.
|
|
326
453
|
*
|
|
@@ -398,102 +525,6 @@ interface AStudioAdapter {
|
|
|
398
525
|
*/
|
|
399
526
|
declare function createAStudioAdapter(options: AStudioAdapterOptions): AStudioAdapter;
|
|
400
527
|
//#endregion
|
|
401
|
-
//#region src/points.d.ts
|
|
402
|
-
/** A read the points surface can present. */
|
|
403
|
-
type AStudioPointsResult = AStudioWebPoints;
|
|
404
|
-
/** Options for {@link createAStudioPointsClient}. */
|
|
405
|
-
interface AStudioPointsClientOptions {
|
|
406
|
-
/**
|
|
407
|
-
* The account cookie to call with, or undefined when nobody is signed in.
|
|
408
|
-
*
|
|
409
|
-
* A provider rather than a file path: the cookie can come from the AStudio
|
|
410
|
-
* app's `astron-session.json` or from a sign-in this plugin made itself, and
|
|
411
|
-
* this module has no business knowing which.
|
|
412
|
-
*/
|
|
413
|
-
sessionId: () => string | undefined;
|
|
414
|
-
/** Injected for tests. */
|
|
415
|
-
fetchImpl?: typeof fetch;
|
|
416
|
-
/** Injected clock, for TTL tests. */
|
|
417
|
-
now?: () => number;
|
|
418
|
-
/** Per-read timeout. */
|
|
419
|
-
timeoutMs?: number;
|
|
420
|
-
}
|
|
421
|
-
/** What the plugin and route consume. */
|
|
422
|
-
interface AStudioPointsClient {
|
|
423
|
-
/**
|
|
424
|
-
* Read the balance.
|
|
425
|
-
*
|
|
426
|
-
* `fresh` bypasses the TTL cache — the section's ↻ uses it so a manual click
|
|
427
|
-
* always reaches upstream.
|
|
428
|
-
*/
|
|
429
|
-
read(options?: {
|
|
430
|
-
fresh?: boolean;
|
|
431
|
-
}): Promise<AStudioPointsResult>;
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Read the `ssoSessionId` from an AStudio session file.
|
|
435
|
-
*
|
|
436
|
-
* The app's way of finding the cookie. Kept exported because the auth store and
|
|
437
|
-
* the tests still read a file directly; the points client itself takes whichever
|
|
438
|
-
* provider it is handed.
|
|
439
|
-
*
|
|
440
|
-
* Returns undefined for a missing file, malformed JSON, an absent field, or a
|
|
441
|
-
* blank value — all of which mean "not signed in", a normal state the points
|
|
442
|
-
* section names rather than an error it reports.
|
|
443
|
-
*/
|
|
444
|
-
declare function readSsoSessionId(sessionPath: string): string | undefined;
|
|
445
|
-
/**
|
|
446
|
-
* Parse the account service's balance envelope.
|
|
447
|
-
*
|
|
448
|
-
* The envelope is the legacy Astron shape — `{ flag, code, desc, data }` — and a
|
|
449
|
-
* dead session is answered with HTTP 200 plus `code: 80000`, which is why the
|
|
450
|
-
* status has to be read from the body as well as the HTTP status.
|
|
451
|
-
*/
|
|
452
|
-
declare function parseAStudioPointsBalance(payload: unknown): AStudioPointsResult;
|
|
453
|
-
/**
|
|
454
|
-
* Create the balance client.
|
|
455
|
-
*
|
|
456
|
-
* The session file is re-read on every upstream call rather than cached: when
|
|
457
|
-
* AStudio is signed in again it rewrites that file with a new `ssoSessionId`, and
|
|
458
|
-
* a cached credential would keep the points section pinned to an expired session.
|
|
459
|
-
*/
|
|
460
|
-
declare function createAStudioPointsClient(options: AStudioPointsClientOptions): AStudioPointsClient;
|
|
461
|
-
//#endregion
|
|
462
|
-
//#region src/daily-reward.d.ts
|
|
463
|
-
/** Options for {@link createAStudioDailyRewardClient}. */
|
|
464
|
-
interface AStudioDailyRewardClientOptions {
|
|
465
|
-
/**
|
|
466
|
-
* The account cookie, or undefined when nobody is signed in.
|
|
467
|
-
*
|
|
468
|
-
* A provider, so the cookie can come from the AStudio app's session file or
|
|
469
|
-
* from a sign-in this plugin made itself.
|
|
470
|
-
*/
|
|
471
|
-
sessionId: () => string | undefined;
|
|
472
|
-
/** Balance client used to report the post-claim balance. */
|
|
473
|
-
points: AStudioPointsClient;
|
|
474
|
-
/** Injected for tests. */
|
|
475
|
-
fetchImpl?: typeof fetch;
|
|
476
|
-
/** Overrides {@link astudioClientType}; `undefined` means "this platform has none". */
|
|
477
|
-
clientType?: string | undefined;
|
|
478
|
-
/** Overrides {@link ASTRO_STUDIO_VERSION}. */
|
|
479
|
-
studioVersion?: string;
|
|
480
|
-
/** Per-call timeout. */
|
|
481
|
-
timeoutMs?: number;
|
|
482
|
-
}
|
|
483
|
-
/** What the plugin and the claim route consume. */
|
|
484
|
-
interface AStudioDailyRewardClient {
|
|
485
|
-
/** Book today's reward, if the account service is holding one. */
|
|
486
|
-
claim(): Promise<AStudioWebClaim>;
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Create the claim client.
|
|
490
|
-
*
|
|
491
|
-
* The session file is re-read on every call for the same reason the balance
|
|
492
|
-
* reader does: AStudio rewrites it on re-login, and a cached credential would
|
|
493
|
-
* pin this to an expired session.
|
|
494
|
-
*/
|
|
495
|
-
declare function createAStudioDailyRewardClient(options: AStudioDailyRewardClientOptions): AStudioDailyRewardClient;
|
|
496
|
-
//#endregion
|
|
497
528
|
//#region src/login-session.d.ts
|
|
498
529
|
/** On-disk format; readers reject anything else. */
|
|
499
530
|
declare const LOGIN_SESSION_FORMAT_VERSION = 1;
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as readTokenFromConfig, c as describeLoginSession, d as writeLoginSession, i as createAStudioAuth, l as parseLoginSession, o as astudioLoginSessionPath, r as localIdentity, s as clearLoginSession, t as createAStudioCatalog, u as readLoginSession } from "./catalog-
|
|
1
|
+
import { a as readTokenFromConfig, c as describeLoginSession, d as writeLoginSession, i as createAStudioAuth, l as parseLoginSession, o as astudioLoginSessionPath, r as localIdentity, s as clearLoginSession, t as createAStudioCatalog, u as readLoginSession } from "./catalog-CILzWwRh.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
@@ -2387,6 +2387,19 @@ function astudioClientType(platform = process.platform) {
|
|
|
2387
2387
|
if (platform === "darwin") return "22";
|
|
2388
2388
|
}
|
|
2389
2389
|
/**
|
|
2390
|
+
* Read a popup id the way the desktop client does.
|
|
2391
|
+
*
|
|
2392
|
+
* The id is a positive integer on the wire, and `client-popups/complete` is
|
|
2393
|
+
* parsed with `Number.isSafeInteger` upstream. Accepting only strings here is
|
|
2394
|
+
* what once made every real reward look like "nothing pending": the popup was
|
|
2395
|
+
* delivered, failed this test, and the section announced a reward that had
|
|
2396
|
+
* never been booked.
|
|
2397
|
+
*/
|
|
2398
|
+
function popupIdOf(value) {
|
|
2399
|
+
const id = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
2400
|
+
return Number.isSafeInteger(id) && id > 0 ? id : void 0;
|
|
2401
|
+
}
|
|
2402
|
+
/**
|
|
2390
2403
|
* Reduce the gateway's `{ flag, code, desc, data }` envelope.
|
|
2391
2404
|
*
|
|
2392
2405
|
* A dead session is answered with HTTP 200 plus `code: 80000`, so the status has
|
|
@@ -2427,9 +2440,9 @@ function pendingRewards(data) {
|
|
|
2427
2440
|
if (typeof raw !== "object" || raw === null) continue;
|
|
2428
2441
|
const item = raw;
|
|
2429
2442
|
if (item["componentType"] !== "DAILY_REWARD_DIALOG") continue;
|
|
2430
|
-
const popupId = item["popupId"];
|
|
2443
|
+
const popupId = popupIdOf(item["popupId"]);
|
|
2431
2444
|
const instanceKey = item["instanceKey"];
|
|
2432
|
-
if (
|
|
2445
|
+
if (popupId === void 0) continue;
|
|
2433
2446
|
if (typeof instanceKey !== "string" || instanceKey === "") continue;
|
|
2434
2447
|
rewards.push({
|
|
2435
2448
|
popupId,
|
|
@@ -2440,6 +2453,20 @@ function pendingRewards(data) {
|
|
|
2440
2453
|
return rewards;
|
|
2441
2454
|
}
|
|
2442
2455
|
/**
|
|
2456
|
+
* Build the cookie header the account APIs expect.
|
|
2457
|
+
*
|
|
2458
|
+
* Same four pairs the desktop client sends, in the same order. The claim is not
|
|
2459
|
+
* an anonymous "is there a reward?" question — the popup service answers an
|
|
2460
|
+
* unidentified caller with an empty list, which is indistinguishable from "no
|
|
2461
|
+
* reward today" unless the account travels with the request.
|
|
2462
|
+
*/
|
|
2463
|
+
function claimCookie(credentials) {
|
|
2464
|
+
const parts = [`ssoSessionId=${credentials.ssoSessionId}`, `sso_sessionid=${credentials.ssoSessionId}`];
|
|
2465
|
+
if (credentials.accountId !== void 0) parts.push(`account_id=${credentials.accountId}`);
|
|
2466
|
+
if (credentials.token !== void 0) parts.push(`token=${credentials.token}`);
|
|
2467
|
+
return parts.join("; ");
|
|
2468
|
+
}
|
|
2469
|
+
/**
|
|
2443
2470
|
* Create the claim client.
|
|
2444
2471
|
*
|
|
2445
2472
|
* The session file is re-read on every call for the same reason the balance
|
|
@@ -2451,14 +2478,14 @@ function createAStudioDailyRewardClient(options) {
|
|
|
2451
2478
|
const clientType = "clientType" in options ? options.clientType : astudioClientType();
|
|
2452
2479
|
const studioVersion = options.studioVersion ?? "3.5.5";
|
|
2453
2480
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
|
|
2454
|
-
const call = async (
|
|
2481
|
+
const call = async (credentials, path, init = {}) => {
|
|
2455
2482
|
let response;
|
|
2456
2483
|
try {
|
|
2457
2484
|
response = await fetchImpl(new URL(path, ASTRO_STUDIO_API_BASE), {
|
|
2458
2485
|
...init,
|
|
2459
2486
|
headers: {
|
|
2460
2487
|
Accept: "application/json",
|
|
2461
|
-
Cookie:
|
|
2488
|
+
Cookie: claimCookie(credentials),
|
|
2462
2489
|
...clientType === void 0 ? {} : { clientType },
|
|
2463
2490
|
studioVersion,
|
|
2464
2491
|
...init.headers ?? {}
|
|
@@ -2479,18 +2506,19 @@ function createAStudioDailyRewardClient(options) {
|
|
|
2479
2506
|
return readEnvelope(payload);
|
|
2480
2507
|
};
|
|
2481
2508
|
return { async claim() {
|
|
2482
|
-
const
|
|
2483
|
-
if (
|
|
2509
|
+
const credentials = options.credentials();
|
|
2510
|
+
if (credentials === void 0) return { status: "signed-out" };
|
|
2484
2511
|
if (clientType === void 0) return { status: "unavailable" };
|
|
2485
|
-
const pending = await call(
|
|
2512
|
+
const pending = await call(credentials, ASTRO_DAILY_REWARD_PENDING_PATH);
|
|
2486
2513
|
if (pending.kind === "expired") return { status: "expired" };
|
|
2487
2514
|
if (pending.kind !== "ok") return { status: "unavailable" };
|
|
2488
2515
|
const rewards = pendingRewards(pending.data);
|
|
2489
2516
|
if (rewards.length === 0) return { status: "none" };
|
|
2490
2517
|
let booked = 0;
|
|
2518
|
+
let alreadyBooked = 0;
|
|
2491
2519
|
let bookedAmount;
|
|
2492
2520
|
for (const reward of rewards) {
|
|
2493
|
-
const answer = await call(
|
|
2521
|
+
const answer = await call(credentials, ASTRO_DAILY_REWARD_COMPLETE_PATH, {
|
|
2494
2522
|
method: "POST",
|
|
2495
2523
|
headers: { "Content-Type": "application/json" },
|
|
2496
2524
|
body: JSON.stringify({
|
|
@@ -2499,7 +2527,11 @@ function createAStudioDailyRewardClient(options) {
|
|
|
2499
2527
|
})
|
|
2500
2528
|
});
|
|
2501
2529
|
if (answer.kind === "expired") return { status: "expired" };
|
|
2502
|
-
if (answer.kind === "
|
|
2530
|
+
if (answer.kind === "already") {
|
|
2531
|
+
alreadyBooked += 1;
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
if (answer.kind === "ok") {
|
|
2503
2535
|
booked += 1;
|
|
2504
2536
|
if (reward.points !== void 0) bookedAmount = (bookedAmount ?? 0) + reward.points;
|
|
2505
2537
|
continue;
|
|
@@ -2507,6 +2539,7 @@ function createAStudioDailyRewardClient(options) {
|
|
|
2507
2539
|
if (booked === 0) return { status: "unavailable" };
|
|
2508
2540
|
break;
|
|
2509
2541
|
}
|
|
2542
|
+
if (booked === 0) return { status: alreadyBooked > 0 ? "already" : "none" };
|
|
2510
2543
|
const balance = await options.points.read({ fresh: true });
|
|
2511
2544
|
return bookedAmount === void 0 ? {
|
|
2512
2545
|
status: "claimed",
|
|
@@ -3267,7 +3300,7 @@ function registerAStudioWebRoutes(ctx, deps) {
|
|
|
3267
3300
|
const pointsClient = createAStudioPointsClient({ sessionId: () => store.ssoSessionId() });
|
|
3268
3301
|
registerAStudioPointsRoute(ctx, { client: pointsClient });
|
|
3269
3302
|
registerAStudioClaimRoute(ctx, { client: createAStudioDailyRewardClient({
|
|
3270
|
-
|
|
3303
|
+
credentials: () => store.claimCredentials(),
|
|
3271
3304
|
points: pointsClient
|
|
3272
3305
|
}) }, controlKey);
|
|
3273
3306
|
const afterSignInChange = () => {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-astudio-connect",
|
|
3
3
|
"displayName": "DSH AStudio Connect",
|
|
4
4
|
"description": "将 AStudio 应用中已登录的模型复用到 DeepSeek Harness — bring AStudio app models into DeepSeek Harness with zero configuration.",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.1",
|
|
6
6
|
"author": "astudio-connect",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|