dsh-scnet-connect 1.1.1 → 1.2.0

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.md CHANGED
@@ -141,6 +141,27 @@ dsh plugin --profile desktop add link:.
141
141
 
142
142
  登录成功后插件会自动换取模型 Key,无需其他操作。
143
143
 
144
+ **没有 SCNet 账号也能用。** 插件会根据平台返回的「该手机号是否已注册」自动选路:
145
+
146
+ | 手机号状态 | 插件行为 |
147
+ | --- | --- |
148
+ | 已注册 | 走短信快捷登录,直接进入 |
149
+ | 未注册 | **自动为你注册一个 SCNet 账号**,然后继续登录 |
150
+
151
+ 注册由平台完成(等同于你在官网用手机号注册),插件只负责发起与跟随跳转。
152
+ 完成后设置页会提示「登录成功,已为你自动注册 SCNet 账号」,不会静默建号。
153
+
154
+ 新账号第一次换取模型 Key 时,平台会回「模型服务尚未开通」;插件会自动调用
155
+ 平台的 `account/config/authorize` 完成开通再重试,全程无需你去控制台操作。
156
+
157
+ > **如果提示「平台把这次提交当成了账号密码登录」**:说明平台把这次短信提交
158
+ > 落到了它的密码分支。请点「刷新验证码」重新获取图形验证码,再重新发送一次
159
+ > 短信码重试。插件会把平台返回的原始码与原文记进
160
+ > `$DSH_HOME/scnet-connect-status.json` 的 `login.lastFailure`,
161
+ > 方便反馈问题时定位。
162
+ >
163
+ > 登录页面的报错一律显示中文;平台原始英文文案只进诊断文件,不会出现在界面上。
164
+
144
165
  ### 方式二:用本机 SCNet 客户端
145
166
 
146
167
  先装好 SCNet 客户端并登录,正常发起一次对话(让客户端把凭据落到本地)。
package/lib/client.js CHANGED
@@ -558,6 +558,11 @@ window.__ModuleLoader__.load({
558
558
  setSmsCode('');
559
559
  setSmsSent(false);
560
560
  setCooldown(0);
561
+ // 未注册手机号走注册通道:账号是这次顺带建出来的,必须告知用户,
562
+ // 否则他只会疑惑「我没注册过,怎么就能用了」。
563
+ if (result.via === 'register') {
564
+ setNotice({ kind: 'ok', text: '登录成功,已为你自动注册 SCNet 账号' });
565
+ }
561
566
  } else if (result && result.error) {
562
567
  setNotice({ kind: 'error', text: result.error });
563
568
  }
package/lib/index.js CHANGED
@@ -676,6 +676,10 @@ export async function apply(ctx, config) {
676
676
  lastValidatedAt: loginState.lastValidatedAt,
677
677
  pending: loginState.pending,
678
678
  error: loginState.error,
679
+ // 平台原始失败信息(码 + 原文)。界面只用中文提示,这里是排查依据:
680
+ // 「短信登录却回 Username or password is incorrect」这类问题,
681
+ // 没有原始码就只能靠猜。
682
+ lastFailure: loginState.lastFailure,
679
683
  loginPageUrl: loginState.loginPageUrl,
680
684
  sessionFile: login.sessionFilePath(),
681
685
  // 现读,不沿用启动时的快照:用户中途装了客户端也要如实反映。
@@ -904,7 +908,10 @@ export async function apply(ctx, config) {
904
908
  await catalog.refresh();
905
909
  rebuild();
906
910
  }
907
- await loginResponse(result.ok ? { ok: true } : { ok: false, error: result.error })(res);
911
+ await loginResponse(
912
+ // `via` 会把「这次是登录还是顺手注册了新账号」带到设置页。
913
+ result.ok ? { ok: true, via: result.via } : { ok: false, error: result.error },
914
+ )(res);
908
915
  }),
909
916
  );
910
917
 
@@ -30,7 +30,7 @@ import {
30
30
  sessionFilePath,
31
31
  webLoginUrl,
32
32
  } from './scnet-platform.js';
33
- import { createWebLogin, mapLoginError } from './scnet-web-login.js';
33
+ import { createWebLogin, mapLoginError, translatePlatformMessage } from './scnet-web-login.js';
34
34
  import { maskKey } from './scnet-credential.js';
35
35
 
36
36
  /** 平台会话换来的 key 缓存 6 天;过期或被清空时自动重换。 */
@@ -99,6 +99,13 @@ export function createLoginManager(options = {}) {
99
99
  let pending;
100
100
  let phase = session ? 'authorized' : 'idle';
101
101
  let lastError;
102
+ /**
103
+ * 最近一次登录失败的原始码与原文(诊断用)。
104
+ *
105
+ * 界面只显示中文提示;但「平台到底回了什么」是排查这类
106
+ * 「明明是短信登录却收到密码错误」问题的唯一线索,必须留下来。
107
+ */
108
+ let lastFailure;
102
109
  let inflight;
103
110
  let lastValidatedAt = 0;
104
111
  /**
@@ -123,6 +130,8 @@ export function createLoginManager(options = {}) {
123
130
  lastValidatedAt: lastValidatedAt ? new Date(lastValidatedAt).toISOString() : undefined,
124
131
  pending: pending ? { startedAt: pending.startedAt, expiresAt: pending.expiresAt } : undefined,
125
132
  error: lastError,
133
+ /** 原始失败信息(码 + 平台原文),供诊断;不含任何凭据。 */
134
+ lastFailure,
126
135
  loginPageUrl: webLoginUrl(),
127
136
  };
128
137
  }
@@ -154,10 +163,17 @@ export function createLoginManager(options = {}) {
154
163
  return { ok: false, error: reason, ...snapshot() };
155
164
  }
156
165
 
157
- /** 会话到手后立刻换 API Key。失败时保留会话(用户不必重新登录)。 */
158
- async function finishAuthorization(raw, signal) {
166
+ /**
167
+ * 会话到手后立刻换 API Key。失败时保留会话(用户不必重新登录)。
168
+ *
169
+ * @param {'register' | 'login'} [via] 这条会话是「自动注册」还是「登录」拿到的
170
+ */
171
+ async function finishAuthorization(raw, signal, via) {
159
172
  const startedAt = epoch;
160
- const keyed = await ensureApiKey({ cookies: raw.cookies, signal });
173
+ // 带上 accountId:新账号首次换 key 会回「模型服务尚未开通」,
174
+ // ensureApiKey 需要用它在平台侧开通服务后重试。
175
+ const accountId = raw.account?.id ?? raw.account?.accountId ?? raw.account?.userId;
176
+ const keyed = await ensureApiKey({ cookies: raw.cookies, accountId, signal });
161
177
  // 换 key 期间用户又点了退出/重新登录:这次结果不再代表当前意图。
162
178
  if (startedAt !== epoch) return { ok: false, error: '登录已被取消', ...snapshot() };
163
179
  if (!keyed.ok) {
@@ -176,15 +192,15 @@ export function createLoginManager(options = {}) {
176
192
  });
177
193
  lastValidatedAt = now.getTime();
178
194
  logger.info(
179
- `免客户端登录完成:${raw.account?.userName || raw.account?.userId || '未知账号'}(来源:${
180
- '短信登录'
195
+ `免客户端登录完成:${raw.account?.userName || raw.account?.userId || '未知账号'}(${
196
+ via === 'register' ? '短信登录 · 已自动注册新账号' : '短信登录'
181
197
  },key=${maskKey(keyed.key)})`,
182
198
  );
183
199
  return { ok: true, ...snapshot() };
184
200
  }
185
201
 
186
202
  /** 把任意会话来源收敛成「验证身份 → 换 key」。 */
187
- async function authorizeFromCookies(cookies, source, signal) {
203
+ async function authorizeFromCookies(cookies, source, signal, via) {
188
204
  const verified = await resolveSession({ cookies, signal });
189
205
  if (!verified.ok) {
190
206
  return fail(
@@ -201,6 +217,7 @@ export function createLoginManager(options = {}) {
201
217
  source,
202
218
  },
203
219
  signal,
220
+ via,
204
221
  );
205
222
  }
206
223
 
@@ -234,7 +251,9 @@ export function createLoginManager(options = {}) {
234
251
  return undefined;
235
252
  }
236
253
  lastValidatedAt = Date.now();
237
- const keyed = await ensureApiKey({ cookies: verified.cookies, signal });
254
+ const renewId =
255
+ verified.user?.id ?? verified.user?.accountId ?? session.account?.id ?? session.account?.accountId;
256
+ const keyed = await ensureApiKey({ cookies: verified.cookies, accountId: renewId, signal });
238
257
  if (startedAt !== epoch) return undefined;
239
258
  if (!keyed.ok) {
240
259
  phase = 'failed';
@@ -296,17 +315,29 @@ export function createLoginManager(options = {}) {
296
315
  mapped === 'captcha-invalid'
297
316
  ? '短信验证码不正确或已失效,请重新获取'
298
317
  : mapped === 'auth-failed'
299
- ? '手机号或验证码不正确'
300
- : mapped === 'invalid-params'
301
- ? '参数不完整,请检查手机号/验证码'
302
- : mapped === 'too-many-attempts'
303
- ? '尝试次数过多,请稍后再试'
304
- : result.message || '登录失败';
318
+ ? translatePlatformMessage(result.message) || '手机号或验证码不正确'
319
+ : mapped === 'user-not-exist'
320
+ ? '该手机号尚未注册 SCNet 账号,请先在平台注册'
321
+ : mapped === 'invalid-params'
322
+ ? '参数不完整,请检查手机号/验证码'
323
+ : mapped === 'too-many-attempts'
324
+ ? '尝试次数过多,请稍后再试'
325
+ : mapped === 'server-busy'
326
+ ? '平台繁忙,请稍后重试'
327
+ // 兜底也要说人话:平台原文多是英文,不能直接甩给用户。
328
+ : translatePlatformMessage(result.message) || '登录失败';
329
+ // 记下原始码与原文,供状态文件/诊断用(界面只显示上面的中文)。
330
+ lastFailure = {
331
+ code: result.code !== undefined ? String(result.code) : undefined,
332
+ rawMessage: result.message,
333
+ reason: result.reason,
334
+ at: new Date().toISOString(),
335
+ };
305
336
  // 验证码/风控状态可能需要重开表单。
306
337
  if (mapped === 'too-many-attempts' || mapped === 'captcha-invalid') webLogin = undefined;
307
338
  return fail(message);
308
339
  }
309
- return authorizeFromCookies(result.cookies, 'web', form.signal);
340
+ return authorizeFromCookies(result.cookies, 'web', form.signal, result.via);
310
341
  },
311
342
 
312
343
  /** 平台会话是否还活着。 */
@@ -198,19 +198,51 @@ export async function resolveSession(options = {}) {
198
198
  * 走「活动 Key」端点(客户端刷新凭据时优先用它):同一账号拿到的 key 与客户端
199
199
  * 落盘的是同一个,不会每次刷新都新建 key。
200
200
  *
201
- * @param {{ cookies: any, signal?: AbortSignal }} options
201
+ * **新账号要自动开通模型服务**:全新注册的账号调这个端点会得到
202
+ * `{"msg":"模型服务尚未开通"}`。平台客户端遇到它时会先调
203
+ * `/acx/llm/account/config/authorize` 开通,再重试 —— 这里照做,
204
+ * 否则「自动注册」只能走到「账号建好了但拿不到 key」,等于白注册。
205
+ *
206
+ * @param {{ cookies: any, accountId?: string, signal?: AbortSignal }} options
202
207
  * @returns {Promise<{ ok: true, key: string, baseUrl?: string } | { ok: false, reason: string, message?: string }>}
203
208
  */
204
209
  export async function ensureApiKey(options = {}) {
205
210
  const cookies = normalizeCookies(options.cookies);
206
211
  if (!cookies) return { ok: false, reason: 'missing-cookies' };
207
- try {
212
+
213
+ async function createOnce() {
208
214
  const result = await platformRequest('/acx/llm/api-key/createActivityApiKey', {
209
215
  method: 'POST',
210
216
  cookies,
211
217
  body: {},
212
218
  signal: options.signal,
213
219
  });
220
+ return result;
221
+ }
222
+
223
+ try {
224
+ let result = await createOnce();
225
+
226
+ // 「模型服务尚未开通」→ 开通一次后重试。只重试一次,避免开通失败时死循环。
227
+ const firstMsg = String(result.body?.msg || result.body?.message || '');
228
+ if (result.status === 200 && /尚未开通|not\s*(been\s*)?(opened|enabled|activated)/i.test(firstMsg)) {
229
+ const accountId = options.accountId || (await resolveAccountId(cookies, options.signal));
230
+ if (accountId) {
231
+ try {
232
+ await platformRequest('/acx/llm/account/config/authorize', {
233
+ method: 'POST',
234
+ cookies,
235
+ // accountId 必须放 body:放 query 平台会回 API010006「无权限访问」。
236
+ body: { accountId },
237
+ signal: options.signal,
238
+ });
239
+ } catch {
240
+ // 开通失败就继续往下走,让调用方看到原始的「尚未开通」,而不是被这里吞掉。
241
+ }
242
+ result = await createOnce();
243
+ }
244
+ }
245
+
214
246
  if (result.status === 401) return { ok: false, reason: 'expired' };
215
247
  if (result.status !== 200 || !result.body) {
216
248
  const msg = result.body?.msg || result.body?.message || `HTTP ${result.status}`;
@@ -219,7 +251,13 @@ export async function ensureApiKey(options = {}) {
219
251
  const data = unwrap(result.body);
220
252
  const key = typeof data === 'string' ? data : data?.apiKey;
221
253
  if (typeof key !== 'string' || !key.startsWith('sk-')) {
222
- return { ok: false, reason: 'invalid-response', message: '平台没有返回 sk- 开头的模型服务 Key' };
254
+ // 走到这里多半还是「模型服务尚未开通」一类,把平台原话带上,别只说「没返回 key」。
255
+ const msg = result.body?.msg || result.body?.message;
256
+ return {
257
+ ok: false,
258
+ reason: 'invalid-response',
259
+ message: msg ? `平台返回:${msg}` : '平台没有返回 sk- 开头的模型服务 Key',
260
+ };
223
261
  }
224
262
  return {
225
263
  ok: true,
@@ -235,6 +273,25 @@ export async function ensureApiKey(options = {}) {
235
273
  }
236
274
  }
237
275
 
276
+ /**
277
+ * 取当前登录账号的 id(开通模型服务要用)。
278
+ *
279
+ * 失败返回 `undefined`:拿不到 id 时上层仍然会尝试换 key,只是跳过开通。
280
+ */
281
+ async function resolveAccountId(cookies, signal) {
282
+ try {
283
+ const result = await platformRequest('/acx/user/users/current-user-info?includeToken=true&refresh=true', {
284
+ cookies,
285
+ signal,
286
+ });
287
+ const data = unwrap(result.body);
288
+ const id = data?.id ?? data?.accountId ?? data?.userId;
289
+ return id ? String(id) : undefined;
290
+ } catch {
291
+ return undefined;
292
+ }
293
+ }
294
+
238
295
  /** 让平台侧会话失效(尽力而为,失败不影响本地清理)。 */
239
296
  export async function logout(options = {}) {
240
297
  const cookies = normalizeCookies(options.cookies);
@@ -177,6 +177,12 @@ export function createWebLogin(options = {}) {
177
177
  const signal = options.signal;
178
178
  /** 上一次发码用的手机号(提交时要与之一致)。 */
179
179
  let pendingPhone;
180
+ /**
181
+ * 上一条短信码发给的号是否已注册(`sendSmsCode` 写入,`submit` 读取)。
182
+ *
183
+ * `undefined` = 还没发过码,不知道;`true`/`false` = 平台明确告知过。
184
+ */
185
+ let pendingRegistered;
180
186
 
181
187
  /** 抓一张图形验证码(平台在短信登录/风控时要求)。 */
182
188
  async function captcha() {
@@ -192,7 +198,15 @@ export function createWebLogin(options = {}) {
192
198
  /**
193
199
  * 发送手机验证码。
194
200
  *
195
- * `1004`(手机号已注册)是**成功**,不是失败 —— 见文件头注释。
201
+ * 返回值里的 `registered` 是**后续分派通道的依据**,不能丢:
202
+ *
203
+ * code === '1004' → 该手机号已注册 → 提交时走 `/sso/auth/login.action`
204
+ * code === '0' → 该手机号未注册 → 提交时走 `/sso/registerByMobilephoneNum.action`
205
+ *
206
+ * 平台前端就是这么做的(`Login-*.js` 里 `T.value="phone"` / `"quick"`)。
207
+ * 早期版本把两者都当「成功」但**没记住区别**,对未注册号仍调登录接口,
208
+ * 平台查无此人 → `401 Username or password is incorrect` —— 用户看到的是
209
+ * 密码错误,实际原因却是「这个手机号没有账号」。
196
210
  */
197
211
  async function sendSmsCode(phone, captchaCode) {
198
212
  if (!isPlausiblePhone(phone)) return { ok: false, reason: 'invalid-phone', message: '手机号格式不对' };
@@ -220,8 +234,9 @@ export function createWebLogin(options = {}) {
220
234
  const code = res.json ? String(res.json.code) : '';
221
235
  if (res.json && SMS_OK_CODES.has(code)) {
222
236
  pendingPhone = mobilephoneNum;
223
- // 1004 = 该手机号已注册 → 平台会走「快捷登录」,对登录来说完全正常。
224
- return { ok: true, registered: code === '1004', code };
237
+ /** 记住这个号注册没注册,`submit()` 要用它选通道。 */
238
+ pendingRegistered = code === '1004';
239
+ return { ok: true, registered: pendingRegistered, code };
225
240
  }
226
241
  return {
227
242
  ok: false,
@@ -245,20 +260,47 @@ export function createWebLogin(options = {}) {
245
260
  return { ok: false, reason: 'invalid-params', message: '请填写短信验证码' };
246
261
  }
247
262
 
248
- const res = await webPost(
249
- jar,
250
- '/sso/auth/login.action',
251
- encodeBody({
252
- service: serviceUrl(),
253
- originalUrl: webLoginUrl(),
254
- sensorsAnonId: '',
255
- captcha: form.captcha || '',
256
- mobilephoneNum,
257
- messageCode: form.smsCode,
258
- mode: '2',
259
- }),
260
- { signal, timeoutMs: 25_000 },
261
- );
263
+ /**
264
+ * 按发码时平台告知的注册状态选通道。
265
+ *
266
+ * 这一步是整套免客户端登录能否覆盖「新用户」的关键:
267
+ * · 已注册号 → `/sso/auth/login.action`(mode=2 快捷登录)
268
+ * · 未注册号 → `/sso/registerByMobilephoneNum.action`(注册即登录)
269
+ *
270
+ * 两者拿到的 `data` 都是带 ticket 的跳转地址,后续处理完全一致。
271
+ *
272
+ * `pendingRegistered` 为 `undefined`(没发过码,或用户直接提交)时退回登录
273
+ * 接口:平台对已注册号必然成功;对未注册号会回 401,届时错误信息已由
274
+ * `mapLoginError` + `translatePlatformMessage` 翻成可读中文。
275
+ */
276
+ const viaRegister = pendingRegistered === false;
277
+ const path = viaRegister ? '/sso/registerByMobilephoneNum.action' : '/sso/auth/login.action';
278
+
279
+ const fields = viaRegister
280
+ ? {
281
+ mobilephoneNum,
282
+ messageCode: form.smsCode,
283
+ // 注册接口把这几项当必填(缺 createBy 会 400)。
284
+ createBy: 'scnet',
285
+ siteCode: 'sacp',
286
+ invitationCode: '',
287
+ fullName: '',
288
+ institution: '',
289
+ service: serviceUrl(),
290
+ originalUrl: webLoginUrl(),
291
+ sensorsAnonId: '',
292
+ }
293
+ : {
294
+ service: serviceUrl(),
295
+ originalUrl: webLoginUrl(),
296
+ sensorsAnonId: '',
297
+ captcha: form.captcha || '',
298
+ mobilephoneNum,
299
+ messageCode: form.smsCode,
300
+ mode: '2',
301
+ };
302
+
303
+ const res = await webPost(jar, path, encodeBody(fields), { signal, timeoutMs: 25_000 });
262
304
 
263
305
  if (!res.json) {
264
306
  return { ok: false, reason: 'invalid-response', message: `HTTP ${res.status}` };
@@ -268,7 +310,9 @@ export function createWebLogin(options = {}) {
268
310
  ok: false,
269
311
  reason: mapLoginError(String(res.json.code), res.json.msg),
270
312
  code: res.json.code,
271
- message: res.json.msg || '登录失败',
313
+ message: res.json.msg || (viaRegister ? '注册失败' : '登录失败'),
314
+ /** 便于上层区分「注册通道失败」与「登录通道失败」。 */
315
+ via: viaRegister ? 'register' : 'login',
272
316
  };
273
317
  }
274
318
 
@@ -283,10 +327,12 @@ export function createWebLogin(options = {}) {
283
327
  return {
284
328
  ok: false,
285
329
  reason: 'no-session-cookie',
286
- message: '平台返回登录成功,但没有下发会话 Cookie(可能是风控拦截)。',
330
+ message: '平台返回成功,但没有下发会话 Cookie(可能是风控拦截)。',
287
331
  };
288
332
  }
289
- return { ok: true, cookies };
333
+ // 到此为止这个号一定已经在平台上存在了。
334
+ pendingRegistered = true;
335
+ return { ok: true, cookies, via: viaRegister ? 'register' : 'login' };
290
336
  }
291
337
 
292
338
  /** 跟随 ticket 跳转,收集平台下发的会话 Cookie。 */
@@ -361,15 +407,32 @@ export function mapSmsError(code, message) {
361
407
  }
362
408
  }
363
409
 
364
- /** 登录错误码 → 插件判定(平台码表见网页 `Login-*.js`)。 */
410
+ /**
411
+ * 登录错误码 → 插件判定(平台码表见网页 `Login-*.js` 的 `Ce` 表)。
412
+ *
413
+ * 平台对「短信验证码提交」的 `mode=2` 分支实测只会回 1009 / 1005 / 1004 / 3002;
414
+ * 但**密码**分支(`mode=0`)会回 `401 Username or password is incorrect`。
415
+ * v1.1.1 及更早版本的表里没有 `401`,于是它落到 `default` 被归成 `rejected`,
416
+ * 提示文案直接回退成平台那串英文原文 —— 用户在设置页看到
417
+ * 「Username or password is incorrect」却明明是在用短信登录,正是这个缺口。
418
+ *
419
+ * 因此这里补上 `401`/`1008` 分支,并给 `default` 加英文兜底识别:
420
+ * 不管平台以后新增什么码,都不该把英文原文直接甩给用户。
421
+ */
365
422
  export function mapLoginError(code, message) {
366
423
  const text = String(message || '');
367
424
  switch (code) {
425
+ case '401':
426
+ // 平台密码分支的「账号或密码错」。短信登录收到它说明提交没被当成 mode=2,
427
+ // 归到 auth-failed,由上层给出可操作的中文提示。
428
+ return 'auth-failed';
368
429
  case '1004':
369
430
  // 对登录来说 1004 不是错误;真出现时按「账号问题」提示,不要误导成失败。
370
431
  return 'auth-failed';
371
432
  case '1005':
372
433
  return 'auth-failed';
434
+ case '1008':
435
+ return 'user-not-exist';
373
436
  case '1009':
374
437
  case '3002':
375
438
  return 'captcha-invalid';
@@ -381,7 +444,36 @@ export function mapLoginError(code, message) {
381
444
  return 'too-many-attempts';
382
445
  default:
383
446
  if (/too many|频繁|次数/i.test(text)) return 'too-many-attempts';
447
+ if (/username or password/i.test(text)) return 'auth-failed';
448
+ if (/user (not exist|does not exist)|not registered/i.test(text)) return 'user-not-exist';
449
+ if (/expired|过期/i.test(text)) return 'captcha-invalid';
384
450
  if (/验证码|captcha/i.test(text)) return 'captcha-invalid';
385
451
  return 'rejected';
386
452
  }
387
453
  }
454
+
455
+ /**
456
+ * 把平台英文原文翻成中文,保证设置页永远不出现英文报错。
457
+ *
458
+ * 平台很多接口的 `msg` 是英文(`Username or password is incorrect`、
459
+ * `Invalid parameters`…),而插件此前只对**已知码**给中文、
460
+ * 未知码直接把原文抛给用户。这里做最后一道兜底。
461
+ *
462
+ * @param {string} message 平台原始 msg
463
+ * @returns {string} 中文提示;无匹配时给出通用文案
464
+ */
465
+ export function translatePlatformMessage(message) {
466
+ const text = String(message || '').trim();
467
+ if (!text) return '';
468
+ // 已经是中文就不用再翻。
469
+ if (/[\u4e00-\u9fa5]/.test(text)) return text;
470
+ if (/username or password/i.test(text)) return '平台把这次提交当成了「账号密码登录」,请重新获取短信验证码再试';
471
+ if (/invalid verification code|code has expired/i.test(text)) return '短信验证码不正确或已失效,请重新获取';
472
+ if (/invalid parameters/i.test(text)) return '提交参数不完整,请检查手机号与验证码';
473
+ if (/too often|too frequent|in 60 seconds/i.test(text)) return '发送太频繁,请稍后再试';
474
+ if (/virtual/i.test(text)) return '不支持虚拟号段';
475
+ if (/not exist|not registered/i.test(text)) return '该手机号尚未注册 SCNet 账号';
476
+ if (/too many/i.test(text)) return '尝试次数过多,请稍后再试';
477
+ if (/signature failed/i.test(text)) return '图形验证码签名校验失败,请刷新验证码';
478
+ return '平台拒绝了这次登录请求,请稍后重试';
479
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-scnet-connect",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "把 SCNet 模型接入 DeepSeek Harness,可免客户端登录,使用免费模型(SCNet-Max,基座基本可确定为 DeepSeek Flash)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",