dsh-plugin-subscription-oauth 0.10.1 → 0.10.2
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/lib/auth/mimo-flow.d.ts +12 -0
- package/lib/auth/mimo-flow.js +106 -0
- package/lib/providers/mimo-provider.js +17 -2
- package/package.json +1 -1
package/lib/auth/mimo-flow.d.ts
CHANGED
|
@@ -114,4 +114,16 @@ export declare function refreshMimoSession(creds: MimoCredentials, fetchFn: type
|
|
|
114
114
|
cookies: Record<string, string>;
|
|
115
115
|
expiresAt: number;
|
|
116
116
|
}>;
|
|
117
|
+
/** One metered window the Settings card reads. */
|
|
118
|
+
export interface MimoUsageWindow {
|
|
119
|
+
label: string;
|
|
120
|
+
usedPercent?: number;
|
|
121
|
+
resetsAt?: number;
|
|
122
|
+
}
|
|
123
|
+
/** The account's plan and weekly allowance, for the Settings card and chip. */
|
|
124
|
+
export declare function readMimoUsage(session: MimoSessionCookies, creds: MimoCredentials, fetchFn: typeof fetch): Promise<{
|
|
125
|
+
plan: string;
|
|
126
|
+
renewsAt?: number;
|
|
127
|
+
windows: MimoUsageWindow[];
|
|
128
|
+
}>;
|
|
117
129
|
export {};
|
package/lib/auth/mimo-flow.js
CHANGED
|
@@ -353,3 +353,109 @@ export async function refreshMimoSession(creds, fetchFn) {
|
|
|
353
353
|
const session = await mimoSignOn(creds, creds.base, fetchFn);
|
|
354
354
|
return { cookies: session.cookies, expiresAt: Date.now() + MIMO_RENEW_AFTER_MS };
|
|
355
355
|
}
|
|
356
|
+
/** The MiMo server's names for its plans' tiers. */
|
|
357
|
+
const TIERS = { 1: 'Starter', 2: 'Plus', 3: 'Pro', 4: 'Ultra' };
|
|
358
|
+
/** Read one of the MiMo server's pages with the session's cookies; signs on again once when refused. */
|
|
359
|
+
async function mimoPage(session, creds, path, fetchFn) {
|
|
360
|
+
for (let attempt = 0;; attempt++) {
|
|
361
|
+
const headers = { Cookie: cookieHeaderOf(session.cookies), ...appHeaders() };
|
|
362
|
+
const response = await fetchFn(creds.base.replace(/\/+$/, '') + path, {
|
|
363
|
+
headers,
|
|
364
|
+
signal: AbortSignal.timeout(20_000),
|
|
365
|
+
});
|
|
366
|
+
const text = await response.text();
|
|
367
|
+
let env = {};
|
|
368
|
+
try {
|
|
369
|
+
env = JSON.parse(text);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
env = {};
|
|
373
|
+
}
|
|
374
|
+
const bad = !isRecord(env) || Array.isArray(env) || (env.code != null && !Number.isInteger(env.code)) ||
|
|
375
|
+
(env.msg != null && typeof env.msg !== 'string');
|
|
376
|
+
if (response.status === 401 || (response.status === 200 && bad)) {
|
|
377
|
+
if (attempt === 0) {
|
|
378
|
+
// The session went stale: re-sign on once and retry.
|
|
379
|
+
const renewed = await refreshMimoSession(creds, fetchFn);
|
|
380
|
+
session = { cookies: renewed.cookies, userId: session.userId };
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
throw new MimoSignInLapsed('the Xiaomi MiMo sign-in has expired — sign in again');
|
|
384
|
+
}
|
|
385
|
+
if (response.status !== 200 || bad) {
|
|
386
|
+
throw new Error(`Xiaomi MiMo ${path}: HTTP ${response.status}`);
|
|
387
|
+
}
|
|
388
|
+
if ((env.code ?? 0) !== 0) {
|
|
389
|
+
throw new Error(`Xiaomi MiMo ${path}: code ${String(env.code)} ${String(env.msg ?? '')}`.trim());
|
|
390
|
+
}
|
|
391
|
+
return isRecord(env.data) ? env.data : null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/** The session's cookies as one Cookie header, in a steady order. */
|
|
395
|
+
function cookieHeaderOf(cookies) {
|
|
396
|
+
const first = ['serviceToken', 'userId', 'cUserId'];
|
|
397
|
+
const parts = first.filter((key) => key in cookies).map((key) => `${key}=${cookies[key]}`);
|
|
398
|
+
for (const [key, value] of Object.entries(cookies)) {
|
|
399
|
+
if (!first.includes(key))
|
|
400
|
+
parts.push(`${key}=${value}`);
|
|
401
|
+
}
|
|
402
|
+
return parts.join('; ');
|
|
403
|
+
}
|
|
404
|
+
/** Read one of the server's times; they carry no zone and are Beijing/Singapore time. */
|
|
405
|
+
function mimoServerTime(value) {
|
|
406
|
+
const text = String(value ?? '').trim();
|
|
407
|
+
let iso = '';
|
|
408
|
+
if (/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(Z|[+-]\d\d:\d\d)$/i.test(text))
|
|
409
|
+
iso = text;
|
|
410
|
+
else if (/^\d{4}-\d\d-\d\d[T ]\d\d:\d\d:\d\d(\.\d+)?$/.test(text))
|
|
411
|
+
iso = `${text.replace(' ', 'T')}+08:00`;
|
|
412
|
+
else if (/^\d{4}-\d\d-\d\d$/.test(text))
|
|
413
|
+
iso = `${text}T00:00:00+08:00`;
|
|
414
|
+
const at = iso === '' ? NaN : Date.parse(iso);
|
|
415
|
+
return Number.isNaN(at) ? undefined : at;
|
|
416
|
+
}
|
|
417
|
+
/** The account's plan and weekly allowance, for the Settings card and chip. */
|
|
418
|
+
export async function readMimoUsage(session, creds, fetchFn) {
|
|
419
|
+
// The plan, from the app's subscription page.
|
|
420
|
+
let self = null;
|
|
421
|
+
try {
|
|
422
|
+
self = await mimoPage(session, creds, '/user/xiaomi/subscription/self', fetchFn);
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
self = null;
|
|
426
|
+
}
|
|
427
|
+
const current = self?.current !== undefined && isRecord(self.current) ? self.current : undefined;
|
|
428
|
+
let plan = 'Free';
|
|
429
|
+
let renewsAt;
|
|
430
|
+
if (current !== undefined) {
|
|
431
|
+
const title = typeof current.title === 'string' ? current.title.trim() : '';
|
|
432
|
+
const tier = typeof current.planTier === 'number' ? TIERS[current.planTier] : undefined;
|
|
433
|
+
const code = typeof current.planCode === 'string' ? current.planCode.trim() : '';
|
|
434
|
+
plan = title || tier || code || 'MiMo';
|
|
435
|
+
const end = mimoServerTime(current.endTime);
|
|
436
|
+
if (end !== undefined)
|
|
437
|
+
renewsAt = end;
|
|
438
|
+
}
|
|
439
|
+
// The week's allowance, from the app's usage page.
|
|
440
|
+
const windows = [];
|
|
441
|
+
try {
|
|
442
|
+
const use = await mimoPage(session, creds, '/user/usage', fetchFn);
|
|
443
|
+
const percent = typeof use?.percent === 'number' ? use.percent : undefined;
|
|
444
|
+
const reset = typeof use?.resetDate === 'string' ? use.resetDate : undefined;
|
|
445
|
+
// No plan: the free offer shows no allowance.
|
|
446
|
+
if (percent !== undefined && reset !== undefined) {
|
|
447
|
+
const window = {
|
|
448
|
+
label: '7 days',
|
|
449
|
+
usedPercent: Math.max(0, Math.min(100, 100 - percent)),
|
|
450
|
+
};
|
|
451
|
+
const at = mimoServerTime(reset);
|
|
452
|
+
if (at !== undefined)
|
|
453
|
+
window.resetsAt = at;
|
|
454
|
+
windows.push(window);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
// The plan alone, when the week's allowance can't be read.
|
|
459
|
+
}
|
|
460
|
+
return { plan, ...renewsAt === undefined ? {} : { renewsAt }, windows };
|
|
461
|
+
}
|
|
@@ -8,7 +8,7 @@ import { REAUTH_REQUIRED, ProviderError } from '../errors.js';
|
|
|
8
8
|
import { TokenStore } from '../auth/store.js';
|
|
9
9
|
import { TokenManager } from '../auth/tokens.js';
|
|
10
10
|
import { LOGIN_CANCELLED_MESSAGE } from '../auth/device-flow.js';
|
|
11
|
-
import { finishMimoSignIn, mimoCredentialsFrom, refreshMimoSession, startMimoSignIn, waitMimoPoll, } from '../auth/mimo-flow.js';
|
|
11
|
+
import { finishMimoSignIn, mimoCredentialsFrom, readMimoUsage, refreshMimoSession, startMimoSignIn, waitMimoPoll, } from '../auth/mimo-flow.js';
|
|
12
12
|
import { MimoAdapter, MIMO_CODE_URL, MIMO_CONTEXT_WINDOW, MIMO_DEFAULT_MODEL, MIMO_MODELS, MIMO_PROVIDER_ID, MIMO_PROVIDER_NAME, parseMimoSession, } from './mimo.js';
|
|
13
13
|
import { ProviderController } from '../auth/provider-controller.js';
|
|
14
14
|
import { proxiedFetch } from '../http.js';
|
|
@@ -132,7 +132,22 @@ export function createMimoProvider(options) {
|
|
|
132
132
|
catalogOwn: () => adapter.ownCatalogEntries(),
|
|
133
133
|
fallbackContextWindow: MIMO_CONTEXT_WINDOW,
|
|
134
134
|
discover: () => Promise.resolve(undefined),
|
|
135
|
-
usage: () =>
|
|
135
|
+
usage: async (_accessToken, account) => {
|
|
136
|
+
const held = await store.get(account);
|
|
137
|
+
if (held === undefined)
|
|
138
|
+
return undefined;
|
|
139
|
+
let creds;
|
|
140
|
+
let cookies;
|
|
141
|
+
try {
|
|
142
|
+
creds = JSON.parse(held.mimoCreds);
|
|
143
|
+
cookies = JSON.parse(held.mimoCookies);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const read = await readMimoUsage({ cookies, userId: creds.userId }, creds, fetchFn);
|
|
149
|
+
return { plan: read.plan, ...read.renewsAt === undefined ? {} : { renewsAt: read.renewsAt }, windows: read.windows };
|
|
150
|
+
},
|
|
136
151
|
details: () => [
|
|
137
152
|
{ key: 'provider', value: MIMO_PROVIDER_NAME },
|
|
138
153
|
{ key: 'credentialFile', value: options.credentialFile },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-subscription-oauth",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Use subscription accounts as DeepSeek Harness LLM providers — Kimi Code (OAuth device flow), Grok / X Premium (OIDC + PKCE), Google Antigravity (Google OAuth + Cloud Code Assist) and GLM / BigModel (ZCode's browser sign-in) — with per-model visibility, drag-to-reorder Auto fallback and plan quotas from the web Settings page",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "EricFL1998",
|