@senguoyun/dsh-arkme 0.1.28 → 0.1.30
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/client.js +8151 -4250
- package/lib/client.js.map +1 -1
- package/lib/extension-profile-restart-helper.js +1 -1
- package/lib/index.js +547 -23
- package/lib/persistent-extension.js +1 -1
- package/lib/{persistent-runtime-CaPUsLwg.js → persistent-runtime-D-NwFt9z.js} +1631 -397
- package/lib/{plugin-updater-helper-Ddppbids.js → plugin-updater-helper-BByro9U_.js} +1 -1
- package/lib/{plugin-updater-helper-Lmz7W2ph.js → plugin-updater-helper-BByw0ZyG.js} +171 -13
- package/lib/plugin-updater-helper.js +1 -1
- package/lib/sdk.js +44 -0
- package/lib/types/arkme-service.d.ts +36 -4
- package/lib/types/arkme-service.d.ts.map +1 -1
- package/lib/types/billing-gateway.d.ts +28 -0
- package/lib/types/billing-gateway.d.ts.map +1 -0
- package/lib/types/client/ArkmeBillingSettings.d.ts +52 -0
- package/lib/types/client/ArkmeBillingSettings.d.ts.map +1 -0
- package/lib/types/client/ArkmeCallSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeLogin.d.ts +3 -1
- package/lib/types/client/ArkmeLogin.d.ts.map +1 -1
- package/lib/types/client/ArkmeNoteDetails.d.ts +3 -1
- package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
- package/lib/types/client/ArkmePersistentShell.d.ts +3 -0
- package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
- package/lib/types/client/ArkmeProductNavigation.d.ts +2 -1
- package/lib/types/client/ArkmeProductNavigation.d.ts.map +1 -1
- package/lib/types/client/ArkmeRichContent.d.ts +15 -1
- package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
- package/lib/types/client/ArkmeSettingsSurface.d.ts +18 -0
- package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeSidebar.d.ts +17 -3
- package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts +9 -2
- package/lib/types/client/ArkmeVirtualWorkspace.d.ts.map +1 -1
- package/lib/types/client/ArkmeWebLoginOverlay.d.ts +6 -0
- package/lib/types/client/ArkmeWebLoginOverlay.d.ts.map +1 -0
- package/lib/types/client/DeepSeekHarnessSurface.d.ts +7 -4
- package/lib/types/client/DeepSeekHarnessSurface.d.ts.map +1 -1
- package/lib/types/client/api.d.ts +1 -1
- package/lib/types/client/api.d.ts.map +1 -1
- package/lib/types/client/arkme-auth-flow.d.ts +5 -0
- package/lib/types/client/arkme-auth-flow.d.ts.map +1 -1
- package/lib/types/client/arkme-billing.d.ts +37 -0
- package/lib/types/client/arkme-billing.d.ts.map +1 -0
- package/lib/types/client/arkme-login-locales.d.ts +5 -0
- package/lib/types/client/arkme-login-locales.d.ts.map +1 -1
- package/lib/types/client/chat-directory-store.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/ui-controller.d.ts +4 -0
- package/lib/types/client/ui-controller.d.ts.map +1 -1
- package/lib/types/extensions/manager.d.ts +4 -0
- package/lib/types/extensions/manager.d.ts.map +1 -1
- package/lib/types/extensions/persistent-bundle.d.ts +14 -0
- package/lib/types/extensions/persistent-bundle.d.ts.map +1 -1
- package/lib/types/extensions/persistent-client-bundle.d.ts +1 -1
- package/lib/types/extensions/persistent-client-bundle.d.ts.map +1 -1
- package/lib/types/extensions/persistent-runtime.d.ts.map +1 -1
- package/lib/types/extensions/profile-installer.d.ts.map +1 -1
- package/lib/types/harness-embed-contract.d.ts +2 -0
- package/lib/types/harness-embed-contract.d.ts.map +1 -0
- package/lib/types/harness-embed-route.d.ts +32 -0
- package/lib/types/harness-embed-route.d.ts.map +1 -0
- package/lib/types/host-api.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/managed-ai/adapter.d.ts +20 -0
- package/lib/types/managed-ai/adapter.d.ts.map +1 -0
- package/lib/types/managed-ai/credential.d.ts +11 -0
- package/lib/types/managed-ai/credential.d.ts.map +1 -0
- package/lib/types/plugin-update-lifecycle-log.d.ts +17 -0
- package/lib/types/plugin-update-lifecycle-log.d.ts.map +1 -0
- package/lib/types/plugin-update.d.ts +3 -0
- package/lib/types/plugin-update.d.ts.map +1 -1
- package/lib/types/plugin-updater-helper.d.ts +1 -2
- package/lib/types/plugin-updater-helper.d.ts.map +1 -1
- package/lib/types/profile-plugin-entry.d.ts +6 -0
- package/lib/types/profile-plugin-entry.d.ts.map +1 -0
- package/lib/types/sdk/index.d.ts +17 -2
- package/lib/types/sdk/index.d.ts.map +1 -1
- package/lib/types/services/auth-service.d.ts +10 -0
- package/lib/types/services/auth-service.d.ts.map +1 -1
- package/lib/types/services/chat-service.d.ts +37 -4
- package/lib/types/services/chat-service.d.ts.map +1 -1
- package/lib/types/tools/business/conversation/copy-link-extend.d.ts +2 -0
- package/lib/types/tools/business/conversation/copy-link-extend.d.ts.map +1 -0
- package/lib/types/tools/business/index.d.ts.map +1 -1
- package/lib/types/tools/ports/conversations.d.ts +4 -1
- package/lib/types/tools/ports/conversations.d.ts.map +1 -1
- package/lib/types/tools/prompts/business.d.ts.map +1 -1
- package/lib/types/types.d.ts +137 -1
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +12 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { chmodSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicKey, randomBytes, randomUUID, timingSafeEqual, verify } from "node:crypto";
|
|
3
|
-
import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { open } from "node:fs/promises";
|
|
6
6
|
import { pinyin } from "pinyin-pro";
|
|
@@ -10,6 +10,37 @@ import { createContext, runInContext } from "node:vm";
|
|
|
10
10
|
import { Context } from "@deepseek-ai/cordis";
|
|
11
11
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
12
12
|
import { parseDocument } from "yaml";
|
|
13
|
+
//#region src/secret-value.ts
|
|
14
|
+
var SecretValue = class {
|
|
15
|
+
#value;
|
|
16
|
+
constructor(value) {
|
|
17
|
+
this.#value = value;
|
|
18
|
+
}
|
|
19
|
+
reveal() {
|
|
20
|
+
return this.#value;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/managed-ai/credential.ts
|
|
25
|
+
const MAX_JWT_PAYLOAD_CHARS = 16 * 1024;
|
|
26
|
+
/** Resolve the Host-owned bearer, refreshing a near-expiry JWT before it reaches the relay. */
|
|
27
|
+
async function resolveManagedAccessCredential(runtime, refreshWindowMillis = 6e4) {
|
|
28
|
+
let session = await runtime.requireSession();
|
|
29
|
+
if (managedAccessTokenExpiresWithin(session.accessToken, refreshWindowMillis)) session = await runtime.refreshAccessToken(session);
|
|
30
|
+
return new SecretValue(session.accessToken);
|
|
31
|
+
}
|
|
32
|
+
/** Treat a parseable JWT expiry as a refresh hint; opaque access tokens remain usable. */
|
|
33
|
+
function managedAccessTokenExpiresWithin(accessToken, windowMillis, nowMillis = Date.now()) {
|
|
34
|
+
const payload = accessToken.split(".")[1];
|
|
35
|
+
if (payload === void 0 || payload === "" || payload.length > MAX_JWT_PAYLOAD_CHARS) return false;
|
|
36
|
+
try {
|
|
37
|
+
const value = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
38
|
+
return typeof value.exp === "number" && Number.isSafeInteger(value.exp) && value.exp > 0 && value.exp * 1e3 - nowMillis < windowMillis;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
13
44
|
//#region src/outgoing-call-contract.ts
|
|
14
45
|
var ArkmeOutgoingCallError = class extends Error {
|
|
15
46
|
code;
|
|
@@ -296,353 +327,13 @@ var ArkmeOutgoingCallBroker = class {
|
|
|
296
327
|
intentId: intent.intentId,
|
|
297
328
|
callRequestId: intent.callRequestId,
|
|
298
329
|
claimed: intent.claimed,
|
|
299
|
-
toolSettled: intent.toolSettled
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
detachAbort(intent) {
|
|
303
|
-
if (intent.signal !== void 0 && intent.abort !== void 0) intent.signal.removeEventListener("abort", intent.abort);
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
const contentCopies = {
|
|
307
|
-
unclear: {
|
|
308
|
-
prompt: "有点看不懂,反而更想听TA怎么说",
|
|
309
|
-
alternatePrompts: ["越看不懂,越想听TA亲口表达", "这段有点跳脱,想听TA怎么读"],
|
|
310
|
-
invitationReactions: [
|
|
311
|
-
"我有点没看懂,却不是想划走,反而更想听你亲口解释它是什么意思。",
|
|
312
|
-
"这段像藏着一点只有你知道的意思,让我很好奇你会怎么说出来。",
|
|
313
|
-
"它读起来有点跳脱,也正因为这样,我更想听见你自己的语气。",
|
|
314
|
-
"我盯着这段看了一会儿,还是觉得由你亲口讲出来一定更明白。"
|
|
315
|
-
]
|
|
316
|
-
},
|
|
317
|
-
numeric: {
|
|
318
|
-
prompt: "像一串暗号,想听TA怎么读",
|
|
319
|
-
alternatePrompts: ["这串数字藏着什么,想听TA念出来", "看起来像个暗号,想听TA怎么解开"],
|
|
320
|
-
invitationReactions: [
|
|
321
|
-
"这串内容看起来像个小暗号,我很好奇它从你嘴里说出来会是什么语气。",
|
|
322
|
-
"我看到它时忍不住猜了好一会儿,真想听你亲口把这个暗号解开。",
|
|
323
|
-
"只有这些字符,反而给人留下了很多想象,我想听听你会怎么读。",
|
|
324
|
-
"它越简短越让人好奇,感觉只有听你说出来才算完整。"
|
|
325
|
-
]
|
|
326
|
-
},
|
|
327
|
-
short: {
|
|
328
|
-
prompt: "字不多,更想听TA怎么说",
|
|
329
|
-
alternatePrompts: ["只有几个字,反而更好奇TA的语气", "话很短,想听TA会怎么念"],
|
|
330
|
-
invitationReactions: [
|
|
331
|
-
"你只写了短短几个字,反而让我更好奇你当时真正的语气。",
|
|
332
|
-
"这句话很短,却挺有余味的,我很想听你亲口说一遍。",
|
|
333
|
-
"字不多,但感觉里面还有没写出来的意思,想听听你会怎么表达。",
|
|
334
|
-
"我看到这句时停了一下,总觉得带上你的声音会更有感觉。"
|
|
335
|
-
]
|
|
336
|
-
},
|
|
337
|
-
sad: {
|
|
338
|
-
prompt: "文字里有些情绪,更想听见TA的语气",
|
|
339
|
-
alternatePrompts: ["这段情绪藏在字里,想听TA怎么说", "有些感受看得见,也想听得见"],
|
|
340
|
-
invitationReactions: [
|
|
341
|
-
"读到这里有点心疼。文字能写下经历,但我更想听见你当时真正的语气。",
|
|
342
|
-
"这段话里的情绪很真实,让我没有办法只把它当成普通文字划过去。",
|
|
343
|
-
"看到这些话,我很想更认真地听你说说,而不只是隔着文字猜你的感受。",
|
|
344
|
-
"有些心情写出来已经很不容易了,如果能听见你的声音,也许会更懂你。"
|
|
345
|
-
]
|
|
346
|
-
},
|
|
347
|
-
funny: {
|
|
348
|
-
prompt: "这段很有画面,带上声音会更有趣",
|
|
349
|
-
alternatePrompts: ["光看文字就有画面,想听TA怎么演绎", "这段有点好玩,想听TA会怎么说"],
|
|
350
|
-
invitationReactions: [
|
|
351
|
-
"光看文字就已经很有画面了,我开始好奇你亲口讲出来会有多好玩。",
|
|
352
|
-
"我看到这里真的笑了一下,要是配上你的语气,感觉一定更有意思。",
|
|
353
|
-
"这段太适合听你现场讲了,文字已经有趣,声音应该会更生动。",
|
|
354
|
-
"你把这件事写得很有意思,我很想知道你说到这里时会是什么语气。"
|
|
355
|
-
]
|
|
356
|
-
},
|
|
357
|
-
opinion: {
|
|
358
|
-
prompt: "这段很有态度,想听TA怎么说",
|
|
359
|
-
alternatePrompts: ["观点写在字里,也想听见TA的语气", "这几句话很有主张,想听TA亲口说"],
|
|
360
|
-
invitationReactions: [
|
|
361
|
-
"这个想法很有你的态度,我很想听你亲口说出来时的语气和停顿。",
|
|
362
|
-
"你的观点让我停下来想了想,也让我好奇你会怎样当面讲清楚它。",
|
|
363
|
-
"这几句话很有主张,不只是想看文字,也想听你完整地表达一次。",
|
|
364
|
-
"我能从字里看到你的判断,但还是很想听见这份态度本来的声音。"
|
|
365
|
-
]
|
|
366
|
-
},
|
|
367
|
-
travel: {
|
|
368
|
-
prompt: "这段旅途很有画面,想听TA亲口讲",
|
|
369
|
-
alternatePrompts: ["风景写进文字里,也想听TA说出来", "这段经历,让人想听见沿途的心情"],
|
|
370
|
-
invitationReactions: [
|
|
371
|
-
"你写的这段经历特别有画面感,我读完真的很想听你亲口讲讲当时的见闻。",
|
|
372
|
-
"看到这些沿途的片段,我好像也跟着走了一小段,很想听听你当时的心情。",
|
|
373
|
-
"这段旅途被你写得很有感觉,如果配上你的讲述,应该会更像身临其境。",
|
|
374
|
-
"我很喜欢你记录风景的方式,也开始期待从你的声音里再走一遍这段路。"
|
|
375
|
-
]
|
|
376
|
-
},
|
|
377
|
-
food: {
|
|
378
|
-
prompt: "这份味道写得很诱人,想听TA聊聊",
|
|
379
|
-
alternatePrompts: ["光看就有点馋,想听TA亲口安利", "这段吃喝日常,也想听见TA的满足"],
|
|
380
|
-
invitationReactions: [
|
|
381
|
-
"你写得我都有点馋了,很想听你亲口讲讲它到底有多好吃。",
|
|
382
|
-
"光看这段分享就能想象到味道,如果听你安利,应该会更有感染力。",
|
|
383
|
-
"这份吃喝日常很有生活气,我好奇你说起它时是不是也会忍不住开心。",
|
|
384
|
-
"你把这份味道写得很诱人,我真的很想听到你的现场评价。"
|
|
385
|
-
]
|
|
386
|
-
},
|
|
387
|
-
workStudy: {
|
|
388
|
-
prompt: "这段思考很认真,想听TA展开说说",
|
|
389
|
-
alternatePrompts: ["字里有不少思考,也想听TA讲明白", "这段成长和复盘,值得被亲口说出来"],
|
|
390
|
-
invitationReactions: [
|
|
391
|
-
"这段记录里有很多认真思考,我很想听你用自己的方式再展开讲讲。",
|
|
392
|
-
"能看出你在这件事上花了不少心思,我也很好奇你一路是怎么想过来的。",
|
|
393
|
-
"这份复盘很真诚,文字说清了结果,我还想听见你经历过程时的感受。",
|
|
394
|
-
"你的思路让我很受启发,如果由你亲口讲出来,应该会更清楚也更有力量。"
|
|
395
|
-
]
|
|
396
|
-
},
|
|
397
|
-
creative: {
|
|
398
|
-
prompt: "这份创作有自己的味道,想听TA聊聊",
|
|
399
|
-
alternatePrompts: ["作品被看见了,也想听见背后的想法", "这份表达不只有画面,也该有声音"],
|
|
400
|
-
invitationReactions: [
|
|
401
|
-
"这份表达很有你自己的味道,我不只想看作品,也想听你聊聊背后的想法。",
|
|
402
|
-
"我很喜欢你记录和创作的方式,也开始好奇你会怎样介绍它。",
|
|
403
|
-
"作品已经让人停下来看了,如果再听见你的讲述,感觉会更完整。",
|
|
404
|
-
"这段内容有很鲜明的个人感觉,我想听听你创作它时真正想表达什么。"
|
|
405
|
-
]
|
|
406
|
-
},
|
|
407
|
-
pet: {
|
|
408
|
-
prompt: "这份可爱很有画面,想听TA聊聊",
|
|
409
|
-
alternatePrompts: ["小家伙被记录下来,也想听TA的宠爱", "光看就很可爱,想听TA亲口分享"],
|
|
410
|
-
invitationReactions: [
|
|
411
|
-
"这个小家伙也太可爱了,我很想听你亲口讲讲它平时是什么样子。",
|
|
412
|
-
"看得出来你很喜欢它,照片和文字之外,我也想听见你说起它时的语气。",
|
|
413
|
-
"这份可爱让我忍不住多看了一会儿,感觉听你分享一定更有意思。",
|
|
414
|
-
"你记录它的方式很温柔,我也开始期待听你聊聊你们之间的小故事。"
|
|
415
|
-
]
|
|
416
|
-
},
|
|
417
|
-
story: {
|
|
418
|
-
prompt: "这个故事,也让人想听见",
|
|
419
|
-
alternatePrompts: ["故事写下来了,也想听TA讲出来", "这段像在讲故事,想听TA亲口说"],
|
|
420
|
-
invitationReactions: [
|
|
421
|
-
"这段经历读起来像一个有画面的故事,我很想听你亲口把它讲出来。",
|
|
422
|
-
"你写下来的这些细节让我跟着看了进去,也开始好奇你的真实语气。",
|
|
423
|
-
"故事写在这里已经很动人,如果听你亲自讲,感觉会离它更近一点。",
|
|
424
|
-
"我读完还在想后面的画面,很想听你用自己的声音把这段经历补完整。"
|
|
425
|
-
]
|
|
426
|
-
},
|
|
427
|
-
warm: {
|
|
428
|
-
prompt: "这段很温柔,带上声音会更完整",
|
|
429
|
-
alternatePrompts: ["文字里有温度,想听TA怎么说", "这份温柔,也想从TA的声音里听见"],
|
|
430
|
-
invitationReactions: [
|
|
431
|
-
"这段文字很温柔,我也很想从你的声音里听见这份温度。",
|
|
432
|
-
"读到这里心里会松一下,如果由你亲口说出来,应该会更治愈。",
|
|
433
|
-
"你的表达让人觉得很舒服,我开始期待听见你说这些话时的声音。",
|
|
434
|
-
"这份温柔不该只停在文字里,我很想听你亲自把它说出来。"
|
|
435
|
-
]
|
|
436
|
-
},
|
|
437
|
-
useful: {
|
|
438
|
-
prompt: "这段分享,听起来可能更容易懂",
|
|
439
|
-
alternatePrompts: ["这段很有用,想听TA讲得更明白", "不只想看这段分享,也想听TA说"],
|
|
440
|
-
invitationReactions: [
|
|
441
|
-
"这段分享很有用,我也很想听你用自己的方式再讲一遍。",
|
|
442
|
-
"你整理得很清楚,看完之后还会想听你补充那些文字没展开的细节。",
|
|
443
|
-
"这个方法让我觉得很受用,如果听你亲口解释,应该会更容易记住。",
|
|
444
|
-
"能感受到你是真心想把经验分享出来,我也很期待听见你的讲解。"
|
|
445
|
-
]
|
|
446
|
-
},
|
|
447
|
-
default: {
|
|
448
|
-
prompt: "想让这段文字带上TA的声音",
|
|
449
|
-
alternatePrompts: ["这段话如果有声音,会是什么感觉", "看见了TA的文字,也想听见TA的声音"],
|
|
450
|
-
invitationReactions: [
|
|
451
|
-
"这段话让我停下来多看了一会儿,也开始好奇,如果由你亲口说出来会是什么感觉。",
|
|
452
|
-
"我不是只想看完就划过去,而是真的很想听听你当时是怎样说这些话的。",
|
|
453
|
-
"文字让我看见了你的表达,也让我开始期待它带上属于你的声音。",
|
|
454
|
-
"看到这条快记时,我第一反应就是:如果能听见你亲口说,应该会更有感觉。"
|
|
455
|
-
]
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
const categoryKeywords = {
|
|
459
|
-
sad: [
|
|
460
|
-
"难过",
|
|
461
|
-
"悲伤",
|
|
462
|
-
"遗憾",
|
|
463
|
-
"绝望",
|
|
464
|
-
"失去",
|
|
465
|
-
"离开",
|
|
466
|
-
"哭",
|
|
467
|
-
"痛苦",
|
|
468
|
-
"疲惫",
|
|
469
|
-
"好难",
|
|
470
|
-
"难熬"
|
|
471
|
-
],
|
|
472
|
-
funny: [
|
|
473
|
-
"哈哈",
|
|
474
|
-
"笑死",
|
|
475
|
-
"搞笑",
|
|
476
|
-
"好玩",
|
|
477
|
-
"有趣",
|
|
478
|
-
"逗"
|
|
479
|
-
],
|
|
480
|
-
opinion: [
|
|
481
|
-
"我觉得",
|
|
482
|
-
"我认为",
|
|
483
|
-
"观点",
|
|
484
|
-
"想法",
|
|
485
|
-
"其实",
|
|
486
|
-
"应该",
|
|
487
|
-
"不能"
|
|
488
|
-
],
|
|
489
|
-
travel: [
|
|
490
|
-
"旅行",
|
|
491
|
-
"旅途",
|
|
492
|
-
"出发",
|
|
493
|
-
"风景",
|
|
494
|
-
"景色",
|
|
495
|
-
"海边",
|
|
496
|
-
"爬山",
|
|
497
|
-
"城市",
|
|
498
|
-
"远方",
|
|
499
|
-
"路上",
|
|
500
|
-
"散步"
|
|
501
|
-
],
|
|
502
|
-
food: [
|
|
503
|
-
"好吃",
|
|
504
|
-
"美食",
|
|
505
|
-
"餐厅",
|
|
506
|
-
"咖啡",
|
|
507
|
-
"甜品",
|
|
508
|
-
"味道",
|
|
509
|
-
"烤肉",
|
|
510
|
-
"火锅",
|
|
511
|
-
"做饭"
|
|
512
|
-
],
|
|
513
|
-
workStudy: [
|
|
514
|
-
"工作",
|
|
515
|
-
"项目",
|
|
516
|
-
"学习",
|
|
517
|
-
"论文",
|
|
518
|
-
"考试",
|
|
519
|
-
"复盘",
|
|
520
|
-
"成长",
|
|
521
|
-
"加班",
|
|
522
|
-
"研究",
|
|
523
|
-
"产品"
|
|
524
|
-
],
|
|
525
|
-
creative: [
|
|
526
|
-
"摄影",
|
|
527
|
-
"音乐",
|
|
528
|
-
"电影",
|
|
529
|
-
"读书",
|
|
530
|
-
"作品",
|
|
531
|
-
"创作",
|
|
532
|
-
"设计",
|
|
533
|
-
"照片",
|
|
534
|
-
"画画"
|
|
535
|
-
],
|
|
536
|
-
pet: [
|
|
537
|
-
"小猫",
|
|
538
|
-
"小狗",
|
|
539
|
-
"猫咪",
|
|
540
|
-
"狗狗",
|
|
541
|
-
"宠物",
|
|
542
|
-
"毛孩子"
|
|
543
|
-
],
|
|
544
|
-
useful: [
|
|
545
|
-
"教程",
|
|
546
|
-
"方法",
|
|
547
|
-
"步骤",
|
|
548
|
-
"经验",
|
|
549
|
-
"分享",
|
|
550
|
-
"建议",
|
|
551
|
-
"怎么做"
|
|
552
|
-
],
|
|
553
|
-
warm: [
|
|
554
|
-
"温柔",
|
|
555
|
-
"治愈",
|
|
556
|
-
"希望",
|
|
557
|
-
"拥抱",
|
|
558
|
-
"阳光",
|
|
559
|
-
"安慰"
|
|
560
|
-
],
|
|
561
|
-
story: [
|
|
562
|
-
"曾经",
|
|
563
|
-
"后来",
|
|
564
|
-
"那年",
|
|
565
|
-
"小时候",
|
|
566
|
-
"有一天",
|
|
567
|
-
"今天",
|
|
568
|
-
"昨天",
|
|
569
|
-
"故事"
|
|
570
|
-
]
|
|
571
|
-
};
|
|
572
|
-
const invitationClosings = [
|
|
573
|
-
"如果你愿意,来录入一下声纹吧。以后再看到你的世界快记,我也能直接听见你的声音:",
|
|
574
|
-
"愿意的话,也来录入声纹,给这条快记添上你的声音吧。我很期待以后不只看到文字,也能听见你:",
|
|
575
|
-
"所以想认真邀请你录入声纹。这样下次在世界遇见你的文字,也能听到你亲口表达:",
|
|
576
|
-
"要是你愿意,就来留下声纹吧。以后你的世界快记不只有文字,也会有属于你的声音:"
|
|
577
|
-
];
|
|
578
|
-
const urlPattern = /(?:(?:https?|ftp):\/\/|www\.)[^\s,。!?;:、"“”‘’()【】《》<>]+/gi;
|
|
579
|
-
const trailingUrlPunctuation = /[.,;:!?,。;:!?))\]}>…]+$/;
|
|
580
|
-
const whitespace = /\s+/g;
|
|
581
|
-
const meaningfulCharacter = /[\p{L}\p{N}]/gu;
|
|
582
|
-
const digitCharacter = /\p{N}/u;
|
|
583
|
-
const latinCharacter = /[a-z]/i;
|
|
584
|
-
const chineseCharacter = /[\u3400-\u9fff]/;
|
|
585
|
-
const mathSymbol = /[+\-×÷*/=<>]/;
|
|
586
|
-
function worldVoiceprintReadableText(value) {
|
|
587
|
-
const normalized = value.replace(urlPattern, (matchedUrl) => matchedUrl.match(trailingUrlPunctuation)?.[0] ?? "").replace(/<>/g, " ").replace(whitespace, " ").trim();
|
|
588
|
-
return /[\p{L}\p{N}]/u.test(normalized) ? normalized : "";
|
|
589
|
-
}
|
|
590
|
-
function buildWorldVoiceprintInviteMessage(input) {
|
|
591
|
-
const preview = input.textPreview?.replace(whitespace, " ").trim();
|
|
592
|
-
const variant = normalizedVariant(input.variantIndex ?? 0, 4);
|
|
593
|
-
const copy = contentCopies[worldVoiceprintContentKind(preview ?? "")];
|
|
594
|
-
const name = input.peerDisplayName.replace(whitespace, " ").trim();
|
|
595
|
-
const greeting = name === "" || name === "这位用户" ? "我是看到你在世界发的这条快记才来找你的:" : `${name},我是看到你在世界发的这条快记才来找你的:`;
|
|
596
|
-
const media = mediaSummary(input);
|
|
597
|
-
return `${greeting}\n\n【来自世界的快记】\n${preview === void 0 || preview === "" ? `(你发布了一条${media === "" ? "" : `${media}的`}世界快记)` : `“${preview}”${media === "" ? "" : `\n(还包含${media})`}`}\n\n${copy.invitationReactions[variant]}\n\n${invitationClosings[variant]}\n${input.inviteUrl}`;
|
|
598
|
-
}
|
|
599
|
-
function mediaSummary(input) {
|
|
600
|
-
const parts = [];
|
|
601
|
-
if ((input.imageCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.imageCount))}张图片`);
|
|
602
|
-
if ((input.videoCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.videoCount))}段视频`);
|
|
603
|
-
if ((input.voiceCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.voiceCount))}段语音`);
|
|
604
|
-
return parts.join("、");
|
|
605
|
-
}
|
|
606
|
-
function normalizedVariant(value, count) {
|
|
607
|
-
if (!Number.isFinite(value)) return 0;
|
|
608
|
-
return (Math.trunc(value) % count + count) % count;
|
|
609
|
-
}
|
|
610
|
-
function worldVoiceprintContentKind(rawText) {
|
|
611
|
-
const text = worldVoiceprintReadableText(rawText).toLowerCase();
|
|
612
|
-
const meaningful = text.match(meaningfulCharacter) ?? [];
|
|
613
|
-
if (meaningful.length === 0) return "unclear";
|
|
614
|
-
if (looksNumericOrFormula(text, meaningful)) return "numeric";
|
|
615
|
-
if (looksUnclear(text, meaningful)) return "unclear";
|
|
616
|
-
if (meaningful.length <= 6) return "short";
|
|
617
|
-
return bestMatchingCategory(text) ?? "default";
|
|
618
|
-
}
|
|
619
|
-
function looksNumericOrFormula(text, meaningful) {
|
|
620
|
-
const digitCount = meaningful.filter((character) => digitCharacter.test(character)).length;
|
|
621
|
-
if (digitCount === 0) return false;
|
|
622
|
-
if (mathSymbol.test(text)) return true;
|
|
623
|
-
return digitCount / meaningful.length >= .7;
|
|
624
|
-
}
|
|
625
|
-
function looksUnclear(text, meaningful) {
|
|
626
|
-
const uniqueCharacters = new Set(meaningful);
|
|
627
|
-
if (meaningful.length >= 5 && uniqueCharacters.size === 1) return true;
|
|
628
|
-
if (meaningful.length >= 8 && uniqueCharacters.size <= 2) return true;
|
|
629
|
-
const hasChinese = chineseCharacter.test(text);
|
|
630
|
-
const hasLatin = latinCharacter.test(text);
|
|
631
|
-
const hasDigit = digitCharacter.test(text);
|
|
632
|
-
return !hasChinese && hasLatin && hasDigit && !text.includes(" ");
|
|
633
|
-
}
|
|
634
|
-
function bestMatchingCategory(text) {
|
|
635
|
-
let bestCategory;
|
|
636
|
-
let bestScore = 0;
|
|
637
|
-
for (const [category, keywords] of Object.entries(categoryKeywords)) {
|
|
638
|
-
const score = keywords.filter((keyword) => text.includes(keyword)).length;
|
|
639
|
-
if (score > bestScore) {
|
|
640
|
-
bestCategory = category;
|
|
641
|
-
bestScore = score;
|
|
642
|
-
}
|
|
330
|
+
toolSettled: intent.toolSettled
|
|
331
|
+
});
|
|
643
332
|
}
|
|
644
|
-
|
|
645
|
-
|
|
333
|
+
detachAbort(intent) {
|
|
334
|
+
if (intent.signal !== void 0 && intent.abort !== void 0) intent.signal.removeEventListener("abort", intent.abort);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
646
337
|
//#endregion
|
|
647
338
|
//#region src/request-coordinator.ts
|
|
648
339
|
const DEFAULT_LANE_LIMITS = {
|
|
@@ -1570,7 +1261,555 @@ var ServiceRuntime = class {
|
|
|
1570
1261
|
return await this.post(baseUrl, path, body, session.accessToken, [0], signal, false, requestOptions(), true, true);
|
|
1571
1262
|
}
|
|
1572
1263
|
}
|
|
1573
|
-
};
|
|
1264
|
+
};
|
|
1265
|
+
//#endregion
|
|
1266
|
+
//#region src/billing-gateway.ts
|
|
1267
|
+
var ArkmeBillingUnavailableError = class extends Error {
|
|
1268
|
+
constructor() {
|
|
1269
|
+
super("购买服务暂不可用");
|
|
1270
|
+
this.name = "ArkmeBillingUnavailableError";
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
function nanoCny(value, field) {
|
|
1274
|
+
const normalized = stringValue$1(value).trim();
|
|
1275
|
+
if (!/^(?:0|[1-9]\d*)$/.test(normalized)) throw new ArkmePluginError("billing-contract-invalid", `余额接口字段 ${field} 无效`, true, 502);
|
|
1276
|
+
return normalized;
|
|
1277
|
+
}
|
|
1278
|
+
function requiredString$1(value, field) {
|
|
1279
|
+
const normalized = stringValue$1(value).trim();
|
|
1280
|
+
if (normalized === "") throw new ArkmePluginError("billing-contract-invalid", `支付接口字段 ${field} 无效`, true, 502);
|
|
1281
|
+
return normalized;
|
|
1282
|
+
}
|
|
1283
|
+
function positiveSafeInteger(value, field) {
|
|
1284
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new ArkmePluginError("billing-contract-invalid", `支付接口字段 ${field} 无效`, true, 502);
|
|
1285
|
+
return value;
|
|
1286
|
+
}
|
|
1287
|
+
function optionalTimestampMillis(value, field) {
|
|
1288
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
1289
|
+
const millis = Date.parse(requiredString$1(value, field));
|
|
1290
|
+
if (!Number.isFinite(millis)) throw new ArkmePluginError("billing-contract-invalid", `支付接口字段 ${field} 无效`, true, 502);
|
|
1291
|
+
return millis;
|
|
1292
|
+
}
|
|
1293
|
+
function uuid(value, field, errorCode = "billing-contract-invalid") {
|
|
1294
|
+
const normalized = stringValue$1(value).trim().toLowerCase();
|
|
1295
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(normalized)) {
|
|
1296
|
+
const upstreamContract = errorCode === "billing-contract-invalid";
|
|
1297
|
+
throw new ArkmePluginError(errorCode, `支付接口字段 ${field} 无效`, upstreamContract, upstreamContract ? 502 : 400);
|
|
1298
|
+
}
|
|
1299
|
+
return normalized;
|
|
1300
|
+
}
|
|
1301
|
+
function paymentMethods(value) {
|
|
1302
|
+
if (!Array.isArray(value)) throw new ArkmePluginError("billing-contract-invalid", "支付接口字段 payment_methods 无效", true, 502);
|
|
1303
|
+
const result = [];
|
|
1304
|
+
for (const rawMethod of value) {
|
|
1305
|
+
const method = objectValue$1(rawMethod);
|
|
1306
|
+
const id = requiredString$1(method.id, "payment_methods.id");
|
|
1307
|
+
const provider = requiredString$1(method.provider, "payment_methods.provider");
|
|
1308
|
+
const actionType = requiredString$1(method.action_type, "payment_methods.action_type");
|
|
1309
|
+
if (!(id === "wechat_native" && provider === "wechat" && actionType === "display_qr" || id === "alipay_pc_web" && provider === "alipay" && actionType === "open_url")) throw new ArkmePluginError("billing-contract-invalid", "支付接口包含未知支付方式", true, 502);
|
|
1310
|
+
if (!result.some((item) => item.id === id)) result.push({
|
|
1311
|
+
id,
|
|
1312
|
+
provider,
|
|
1313
|
+
actionType
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
if (result.length === 0) throw new ArkmePluginError("billing-contract-invalid", "支付接口未返回可用渠道", true, 502);
|
|
1317
|
+
return result;
|
|
1318
|
+
}
|
|
1319
|
+
function productSnapshot(value, currency) {
|
|
1320
|
+
const product = objectValue$1(value);
|
|
1321
|
+
const description = stringValue$1(product.description).trim();
|
|
1322
|
+
return {
|
|
1323
|
+
productId: requiredString$1(product.product_code, "product_code"),
|
|
1324
|
+
title: requiredString$1(product.title, "title"),
|
|
1325
|
+
...description === "" ? {} : { description },
|
|
1326
|
+
creditNanoCny: nanoCny(product.credit_nano_cny, "credit_nano_cny"),
|
|
1327
|
+
priceMinor: positiveSafeInteger(product.price_cent, "price_cent"),
|
|
1328
|
+
currency,
|
|
1329
|
+
paymentMethods: paymentMethods(product.payment_methods),
|
|
1330
|
+
enabled: true
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
function paymentMethodContract(paymentMethod, provider, actionType) {
|
|
1334
|
+
return paymentMethod === "wechat_native" && provider === "wechat" && actionType === "display_qr" || paymentMethod === "alipay_pc_web" && provider === "alipay" && actionType === "open_url";
|
|
1335
|
+
}
|
|
1336
|
+
function paymentAction(value) {
|
|
1337
|
+
const action = objectValue$1(value);
|
|
1338
|
+
const type = requiredString$1(action.type, "payment_action.type");
|
|
1339
|
+
if (type === "open_url") {
|
|
1340
|
+
const url = requiredString$1(action.url, "payment_action.url");
|
|
1341
|
+
let parsed;
|
|
1342
|
+
try {
|
|
1343
|
+
parsed = new URL(url);
|
|
1344
|
+
} catch {
|
|
1345
|
+
throw new ArkmePluginError("billing-contract-invalid", "支付接口字段 payment_action.url 无效", true, 502);
|
|
1346
|
+
}
|
|
1347
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "") throw new ArkmePluginError("billing-contract-invalid", "支付接口字段 payment_action.url 无效", true, 502);
|
|
1348
|
+
return {
|
|
1349
|
+
type,
|
|
1350
|
+
url: parsed.href
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
if (type === "display_qr") return {
|
|
1354
|
+
type,
|
|
1355
|
+
qrContent: requiredString$1(action.qr_code_content, "payment_action.qr_code_content")
|
|
1356
|
+
};
|
|
1357
|
+
throw new ArkmePluginError("billing-contract-invalid", "支付接口返回未知支付动作", true, 502);
|
|
1358
|
+
}
|
|
1359
|
+
function orderSnapshot(value) {
|
|
1360
|
+
const order = objectValue$1(value);
|
|
1361
|
+
const mappedStatus = {
|
|
1362
|
+
pending_payment: "pending",
|
|
1363
|
+
crediting: "crediting",
|
|
1364
|
+
credited: "paid",
|
|
1365
|
+
closed: "closed"
|
|
1366
|
+
}[requiredString$1(order.status, "status")];
|
|
1367
|
+
if (mappedStatus === void 0) throw new ArkmePluginError("billing-contract-invalid", "支付接口返回未知订单状态", true, 502);
|
|
1368
|
+
const paymentProvider = requiredString$1(order.payment_provider, "payment_provider");
|
|
1369
|
+
const paymentMethod = requiredString$1(order.payment_method, "payment_method");
|
|
1370
|
+
const rawAction = order.payment_action;
|
|
1371
|
+
const action = rawAction === void 0 || rawAction === null ? void 0 : paymentAction(rawAction);
|
|
1372
|
+
if (!paymentMethodContract(paymentMethod, paymentProvider, action?.type ?? (paymentMethod === "wechat_native" ? "display_qr" : paymentMethod === "alipay_pc_web" ? "open_url" : ""))) throw new ArkmePluginError("billing-contract-invalid", "支付接口返回未知支付方式", true, 502);
|
|
1373
|
+
if (requiredString$1(order.currency, "currency") !== "CNY") throw new ArkmePluginError("billing-contract-invalid", "支付接口币种无效", true, 502);
|
|
1374
|
+
const expiresAtMillis = optionalTimestampMillis(order.expires_at, "expires_at");
|
|
1375
|
+
if (expiresAtMillis === void 0) throw new ArkmePluginError("billing-contract-invalid", "支付接口字段 expires_at 无效", true, 502);
|
|
1376
|
+
if (mappedStatus === "pending" && action === void 0) throw new ArkmePluginError("billing-contract-invalid", "支付接口未返回有效支付动作", true, 502);
|
|
1377
|
+
const pollInterval = order.poll_interval_ms;
|
|
1378
|
+
const pollIntervalMillis = typeof pollInterval === "number" && Number.isSafeInteger(pollInterval) && pollInterval > 0 ? Math.min(1e4, Math.max(500, pollInterval)) : void 0;
|
|
1379
|
+
return {
|
|
1380
|
+
orderId: uuid(order.order_uid, "order_uid"),
|
|
1381
|
+
paymentProvider,
|
|
1382
|
+
paymentMethod,
|
|
1383
|
+
status: mappedStatus,
|
|
1384
|
+
amountMinor: positiveSafeInteger(order.price_cent, "price_cent"),
|
|
1385
|
+
currency: "CNY",
|
|
1386
|
+
creditNanoCny: nanoCny(order.credit_nano_cny, "credit_nano_cny"),
|
|
1387
|
+
expiresAtMillis,
|
|
1388
|
+
...action === void 0 ? {} : { paymentAction: action },
|
|
1389
|
+
...pollIntervalMillis === void 0 ? {} : { pollIntervalMillis },
|
|
1390
|
+
...optionalTimestamp("paidAtMillis", order.paid_at, "paid_at"),
|
|
1391
|
+
...optionalTimestamp("creditedAtMillis", order.credited_at, "credited_at")
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
function optionalTimestamp(key, value, field) {
|
|
1395
|
+
const millis = optionalTimestampMillis(value, field);
|
|
1396
|
+
return millis === void 0 ? {} : { [key]: millis };
|
|
1397
|
+
}
|
|
1398
|
+
var HttpArkmeBillingGateway = class {
|
|
1399
|
+
runtime;
|
|
1400
|
+
constructor(runtime) {
|
|
1401
|
+
this.runtime = runtime;
|
|
1402
|
+
}
|
|
1403
|
+
async post(service, path, body, lane, signal, key) {
|
|
1404
|
+
let session = await this.runtime.requireSession();
|
|
1405
|
+
const request = async () => await this.runtime.post(service === "auth" ? this.runtime.config.authBaseUrl : this.runtime.config.intelligentBaseUrl, path, body, session.accessToken, [200], signal, true, this.runtime.authenticatedRequestOptions(session, service, lane, {
|
|
1406
|
+
lane,
|
|
1407
|
+
...key === void 0 ? {} : {
|
|
1408
|
+
key,
|
|
1409
|
+
bypassCache: true
|
|
1410
|
+
}
|
|
1411
|
+
}));
|
|
1412
|
+
try {
|
|
1413
|
+
return await request();
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
if (!(error instanceof ArkmePluginError) || ![
|
|
1416
|
+
"auth-http-401",
|
|
1417
|
+
"auth-http-403",
|
|
1418
|
+
"invalid_access_token"
|
|
1419
|
+
].includes(error.code)) throw this.normalizeError(error);
|
|
1420
|
+
session = await this.runtime.refreshAccessToken(session);
|
|
1421
|
+
try {
|
|
1422
|
+
return await request();
|
|
1423
|
+
} catch (retryError) {
|
|
1424
|
+
throw this.normalizeError(retryError);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
normalizeError(error) {
|
|
1429
|
+
if (!(error instanceof ArkmePluginError)) return error;
|
|
1430
|
+
const retryable = [
|
|
1431
|
+
"order_processing",
|
|
1432
|
+
"purchase_unavailable",
|
|
1433
|
+
"balance_unavailable",
|
|
1434
|
+
"arkme-network-error",
|
|
1435
|
+
"arkme-timeout"
|
|
1436
|
+
].includes(error.code);
|
|
1437
|
+
if (retryable === error.retryable) return error;
|
|
1438
|
+
return new ArkmePluginError(error.code, error.message, retryable, error.httpStatus, { cause: error });
|
|
1439
|
+
}
|
|
1440
|
+
async quota(signal) {
|
|
1441
|
+
const data = objectValue$1(await this.post("intelligent", "/api/v1/managed-ai/balance/query", {}, "interactive-read", signal, "managed-ai-balance"));
|
|
1442
|
+
const currency = stringValue$1(data.currency).trim();
|
|
1443
|
+
if (currency !== "CNY") throw new ArkmePluginError("billing-contract-invalid", "余额接口币种无效", true, 502);
|
|
1444
|
+
const availableNanoCny = nanoCny(data.available_nano_cny, "available_nano_cny");
|
|
1445
|
+
const totalNanoCny = nanoCny(data.total_nano_cny, "total_nano_cny");
|
|
1446
|
+
const reservedNanoCny = nanoCny(data.reserved_nano_cny, "reserved_nano_cny");
|
|
1447
|
+
if (BigInt(availableNanoCny) + BigInt(reservedNanoCny) !== BigInt(totalNanoCny)) throw new ArkmePluginError("billing-contract-invalid", "余额接口金额不一致", true, 502);
|
|
1448
|
+
return {
|
|
1449
|
+
availableNanoCny,
|
|
1450
|
+
totalNanoCny,
|
|
1451
|
+
reservedNanoCny,
|
|
1452
|
+
currency
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
async products(signal) {
|
|
1456
|
+
const data = objectValue$1(await this.post("auth", "/api/v1/managed-ai/products/query", {}, "interactive-read", signal, "managed-ai-products"));
|
|
1457
|
+
if (stringValue$1(data.currency).trim() !== "CNY" || !Array.isArray(data.products)) throw new ArkmePluginError("billing-contract-invalid", "商品接口响应无效", true, 502);
|
|
1458
|
+
return { items: data.products.map((product) => productSnapshot(product, "CNY")) };
|
|
1459
|
+
}
|
|
1460
|
+
async createOrder(input, signal) {
|
|
1461
|
+
const clientOrderUid = uuid(input.clientRequestId, "client_order_uid", "billing-client-request-id-invalid");
|
|
1462
|
+
return orderSnapshot(await this.post("auth", "/api/v1/managed-ai/purchase/create", {
|
|
1463
|
+
client_order_uid: clientOrderUid,
|
|
1464
|
+
product_code: requiredString$1(input.productId, "product_code"),
|
|
1465
|
+
payment_method: input.paymentMethod
|
|
1466
|
+
}, "write", signal));
|
|
1467
|
+
}
|
|
1468
|
+
async orderStatus(orderId, signal) {
|
|
1469
|
+
const orderUid = uuid(orderId, "order_uid", "billing-order-id-invalid");
|
|
1470
|
+
return orderSnapshot(await this.post("auth", "/api/v1/managed-ai/purchase/status", { order_uid: orderUid }, "interactive-read", signal));
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
const contentCopies = {
|
|
1474
|
+
unclear: {
|
|
1475
|
+
prompt: "有点看不懂,反而更想听TA怎么说",
|
|
1476
|
+
alternatePrompts: ["越看不懂,越想听TA亲口表达", "这段有点跳脱,想听TA怎么读"],
|
|
1477
|
+
invitationReactions: [
|
|
1478
|
+
"我有点没看懂,却不是想划走,反而更想听你亲口解释它是什么意思。",
|
|
1479
|
+
"这段像藏着一点只有你知道的意思,让我很好奇你会怎么说出来。",
|
|
1480
|
+
"它读起来有点跳脱,也正因为这样,我更想听见你自己的语气。",
|
|
1481
|
+
"我盯着这段看了一会儿,还是觉得由你亲口讲出来一定更明白。"
|
|
1482
|
+
]
|
|
1483
|
+
},
|
|
1484
|
+
numeric: {
|
|
1485
|
+
prompt: "像一串暗号,想听TA怎么读",
|
|
1486
|
+
alternatePrompts: ["这串数字藏着什么,想听TA念出来", "看起来像个暗号,想听TA怎么解开"],
|
|
1487
|
+
invitationReactions: [
|
|
1488
|
+
"这串内容看起来像个小暗号,我很好奇它从你嘴里说出来会是什么语气。",
|
|
1489
|
+
"我看到它时忍不住猜了好一会儿,真想听你亲口把这个暗号解开。",
|
|
1490
|
+
"只有这些字符,反而给人留下了很多想象,我想听听你会怎么读。",
|
|
1491
|
+
"它越简短越让人好奇,感觉只有听你说出来才算完整。"
|
|
1492
|
+
]
|
|
1493
|
+
},
|
|
1494
|
+
short: {
|
|
1495
|
+
prompt: "字不多,更想听TA怎么说",
|
|
1496
|
+
alternatePrompts: ["只有几个字,反而更好奇TA的语气", "话很短,想听TA会怎么念"],
|
|
1497
|
+
invitationReactions: [
|
|
1498
|
+
"你只写了短短几个字,反而让我更好奇你当时真正的语气。",
|
|
1499
|
+
"这句话很短,却挺有余味的,我很想听你亲口说一遍。",
|
|
1500
|
+
"字不多,但感觉里面还有没写出来的意思,想听听你会怎么表达。",
|
|
1501
|
+
"我看到这句时停了一下,总觉得带上你的声音会更有感觉。"
|
|
1502
|
+
]
|
|
1503
|
+
},
|
|
1504
|
+
sad: {
|
|
1505
|
+
prompt: "文字里有些情绪,更想听见TA的语气",
|
|
1506
|
+
alternatePrompts: ["这段情绪藏在字里,想听TA怎么说", "有些感受看得见,也想听得见"],
|
|
1507
|
+
invitationReactions: [
|
|
1508
|
+
"读到这里有点心疼。文字能写下经历,但我更想听见你当时真正的语气。",
|
|
1509
|
+
"这段话里的情绪很真实,让我没有办法只把它当成普通文字划过去。",
|
|
1510
|
+
"看到这些话,我很想更认真地听你说说,而不只是隔着文字猜你的感受。",
|
|
1511
|
+
"有些心情写出来已经很不容易了,如果能听见你的声音,也许会更懂你。"
|
|
1512
|
+
]
|
|
1513
|
+
},
|
|
1514
|
+
funny: {
|
|
1515
|
+
prompt: "这段很有画面,带上声音会更有趣",
|
|
1516
|
+
alternatePrompts: ["光看文字就有画面,想听TA怎么演绎", "这段有点好玩,想听TA会怎么说"],
|
|
1517
|
+
invitationReactions: [
|
|
1518
|
+
"光看文字就已经很有画面了,我开始好奇你亲口讲出来会有多好玩。",
|
|
1519
|
+
"我看到这里真的笑了一下,要是配上你的语气,感觉一定更有意思。",
|
|
1520
|
+
"这段太适合听你现场讲了,文字已经有趣,声音应该会更生动。",
|
|
1521
|
+
"你把这件事写得很有意思,我很想知道你说到这里时会是什么语气。"
|
|
1522
|
+
]
|
|
1523
|
+
},
|
|
1524
|
+
opinion: {
|
|
1525
|
+
prompt: "这段很有态度,想听TA怎么说",
|
|
1526
|
+
alternatePrompts: ["观点写在字里,也想听见TA的语气", "这几句话很有主张,想听TA亲口说"],
|
|
1527
|
+
invitationReactions: [
|
|
1528
|
+
"这个想法很有你的态度,我很想听你亲口说出来时的语气和停顿。",
|
|
1529
|
+
"你的观点让我停下来想了想,也让我好奇你会怎样当面讲清楚它。",
|
|
1530
|
+
"这几句话很有主张,不只是想看文字,也想听你完整地表达一次。",
|
|
1531
|
+
"我能从字里看到你的判断,但还是很想听见这份态度本来的声音。"
|
|
1532
|
+
]
|
|
1533
|
+
},
|
|
1534
|
+
travel: {
|
|
1535
|
+
prompt: "这段旅途很有画面,想听TA亲口讲",
|
|
1536
|
+
alternatePrompts: ["风景写进文字里,也想听TA说出来", "这段经历,让人想听见沿途的心情"],
|
|
1537
|
+
invitationReactions: [
|
|
1538
|
+
"你写的这段经历特别有画面感,我读完真的很想听你亲口讲讲当时的见闻。",
|
|
1539
|
+
"看到这些沿途的片段,我好像也跟着走了一小段,很想听听你当时的心情。",
|
|
1540
|
+
"这段旅途被你写得很有感觉,如果配上你的讲述,应该会更像身临其境。",
|
|
1541
|
+
"我很喜欢你记录风景的方式,也开始期待从你的声音里再走一遍这段路。"
|
|
1542
|
+
]
|
|
1543
|
+
},
|
|
1544
|
+
food: {
|
|
1545
|
+
prompt: "这份味道写得很诱人,想听TA聊聊",
|
|
1546
|
+
alternatePrompts: ["光看就有点馋,想听TA亲口安利", "这段吃喝日常,也想听见TA的满足"],
|
|
1547
|
+
invitationReactions: [
|
|
1548
|
+
"你写得我都有点馋了,很想听你亲口讲讲它到底有多好吃。",
|
|
1549
|
+
"光看这段分享就能想象到味道,如果听你安利,应该会更有感染力。",
|
|
1550
|
+
"这份吃喝日常很有生活气,我好奇你说起它时是不是也会忍不住开心。",
|
|
1551
|
+
"你把这份味道写得很诱人,我真的很想听到你的现场评价。"
|
|
1552
|
+
]
|
|
1553
|
+
},
|
|
1554
|
+
workStudy: {
|
|
1555
|
+
prompt: "这段思考很认真,想听TA展开说说",
|
|
1556
|
+
alternatePrompts: ["字里有不少思考,也想听TA讲明白", "这段成长和复盘,值得被亲口说出来"],
|
|
1557
|
+
invitationReactions: [
|
|
1558
|
+
"这段记录里有很多认真思考,我很想听你用自己的方式再展开讲讲。",
|
|
1559
|
+
"能看出你在这件事上花了不少心思,我也很好奇你一路是怎么想过来的。",
|
|
1560
|
+
"这份复盘很真诚,文字说清了结果,我还想听见你经历过程时的感受。",
|
|
1561
|
+
"你的思路让我很受启发,如果由你亲口讲出来,应该会更清楚也更有力量。"
|
|
1562
|
+
]
|
|
1563
|
+
},
|
|
1564
|
+
creative: {
|
|
1565
|
+
prompt: "这份创作有自己的味道,想听TA聊聊",
|
|
1566
|
+
alternatePrompts: ["作品被看见了,也想听见背后的想法", "这份表达不只有画面,也该有声音"],
|
|
1567
|
+
invitationReactions: [
|
|
1568
|
+
"这份表达很有你自己的味道,我不只想看作品,也想听你聊聊背后的想法。",
|
|
1569
|
+
"我很喜欢你记录和创作的方式,也开始好奇你会怎样介绍它。",
|
|
1570
|
+
"作品已经让人停下来看了,如果再听见你的讲述,感觉会更完整。",
|
|
1571
|
+
"这段内容有很鲜明的个人感觉,我想听听你创作它时真正想表达什么。"
|
|
1572
|
+
]
|
|
1573
|
+
},
|
|
1574
|
+
pet: {
|
|
1575
|
+
prompt: "这份可爱很有画面,想听TA聊聊",
|
|
1576
|
+
alternatePrompts: ["小家伙被记录下来,也想听TA的宠爱", "光看就很可爱,想听TA亲口分享"],
|
|
1577
|
+
invitationReactions: [
|
|
1578
|
+
"这个小家伙也太可爱了,我很想听你亲口讲讲它平时是什么样子。",
|
|
1579
|
+
"看得出来你很喜欢它,照片和文字之外,我也想听见你说起它时的语气。",
|
|
1580
|
+
"这份可爱让我忍不住多看了一会儿,感觉听你分享一定更有意思。",
|
|
1581
|
+
"你记录它的方式很温柔,我也开始期待听你聊聊你们之间的小故事。"
|
|
1582
|
+
]
|
|
1583
|
+
},
|
|
1584
|
+
story: {
|
|
1585
|
+
prompt: "这个故事,也让人想听见",
|
|
1586
|
+
alternatePrompts: ["故事写下来了,也想听TA讲出来", "这段像在讲故事,想听TA亲口说"],
|
|
1587
|
+
invitationReactions: [
|
|
1588
|
+
"这段经历读起来像一个有画面的故事,我很想听你亲口把它讲出来。",
|
|
1589
|
+
"你写下来的这些细节让我跟着看了进去,也开始好奇你的真实语气。",
|
|
1590
|
+
"故事写在这里已经很动人,如果听你亲自讲,感觉会离它更近一点。",
|
|
1591
|
+
"我读完还在想后面的画面,很想听你用自己的声音把这段经历补完整。"
|
|
1592
|
+
]
|
|
1593
|
+
},
|
|
1594
|
+
warm: {
|
|
1595
|
+
prompt: "这段很温柔,带上声音会更完整",
|
|
1596
|
+
alternatePrompts: ["文字里有温度,想听TA怎么说", "这份温柔,也想从TA的声音里听见"],
|
|
1597
|
+
invitationReactions: [
|
|
1598
|
+
"这段文字很温柔,我也很想从你的声音里听见这份温度。",
|
|
1599
|
+
"读到这里心里会松一下,如果由你亲口说出来,应该会更治愈。",
|
|
1600
|
+
"你的表达让人觉得很舒服,我开始期待听见你说这些话时的声音。",
|
|
1601
|
+
"这份温柔不该只停在文字里,我很想听你亲自把它说出来。"
|
|
1602
|
+
]
|
|
1603
|
+
},
|
|
1604
|
+
useful: {
|
|
1605
|
+
prompt: "这段分享,听起来可能更容易懂",
|
|
1606
|
+
alternatePrompts: ["这段很有用,想听TA讲得更明白", "不只想看这段分享,也想听TA说"],
|
|
1607
|
+
invitationReactions: [
|
|
1608
|
+
"这段分享很有用,我也很想听你用自己的方式再讲一遍。",
|
|
1609
|
+
"你整理得很清楚,看完之后还会想听你补充那些文字没展开的细节。",
|
|
1610
|
+
"这个方法让我觉得很受用,如果听你亲口解释,应该会更容易记住。",
|
|
1611
|
+
"能感受到你是真心想把经验分享出来,我也很期待听见你的讲解。"
|
|
1612
|
+
]
|
|
1613
|
+
},
|
|
1614
|
+
default: {
|
|
1615
|
+
prompt: "想让这段文字带上TA的声音",
|
|
1616
|
+
alternatePrompts: ["这段话如果有声音,会是什么感觉", "看见了TA的文字,也想听见TA的声音"],
|
|
1617
|
+
invitationReactions: [
|
|
1618
|
+
"这段话让我停下来多看了一会儿,也开始好奇,如果由你亲口说出来会是什么感觉。",
|
|
1619
|
+
"我不是只想看完就划过去,而是真的很想听听你当时是怎样说这些话的。",
|
|
1620
|
+
"文字让我看见了你的表达,也让我开始期待它带上属于你的声音。",
|
|
1621
|
+
"看到这条快记时,我第一反应就是:如果能听见你亲口说,应该会更有感觉。"
|
|
1622
|
+
]
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
const categoryKeywords = {
|
|
1626
|
+
sad: [
|
|
1627
|
+
"难过",
|
|
1628
|
+
"悲伤",
|
|
1629
|
+
"遗憾",
|
|
1630
|
+
"绝望",
|
|
1631
|
+
"失去",
|
|
1632
|
+
"离开",
|
|
1633
|
+
"哭",
|
|
1634
|
+
"痛苦",
|
|
1635
|
+
"疲惫",
|
|
1636
|
+
"好难",
|
|
1637
|
+
"难熬"
|
|
1638
|
+
],
|
|
1639
|
+
funny: [
|
|
1640
|
+
"哈哈",
|
|
1641
|
+
"笑死",
|
|
1642
|
+
"搞笑",
|
|
1643
|
+
"好玩",
|
|
1644
|
+
"有趣",
|
|
1645
|
+
"逗"
|
|
1646
|
+
],
|
|
1647
|
+
opinion: [
|
|
1648
|
+
"我觉得",
|
|
1649
|
+
"我认为",
|
|
1650
|
+
"观点",
|
|
1651
|
+
"想法",
|
|
1652
|
+
"其实",
|
|
1653
|
+
"应该",
|
|
1654
|
+
"不能"
|
|
1655
|
+
],
|
|
1656
|
+
travel: [
|
|
1657
|
+
"旅行",
|
|
1658
|
+
"旅途",
|
|
1659
|
+
"出发",
|
|
1660
|
+
"风景",
|
|
1661
|
+
"景色",
|
|
1662
|
+
"海边",
|
|
1663
|
+
"爬山",
|
|
1664
|
+
"城市",
|
|
1665
|
+
"远方",
|
|
1666
|
+
"路上",
|
|
1667
|
+
"散步"
|
|
1668
|
+
],
|
|
1669
|
+
food: [
|
|
1670
|
+
"好吃",
|
|
1671
|
+
"美食",
|
|
1672
|
+
"餐厅",
|
|
1673
|
+
"咖啡",
|
|
1674
|
+
"甜品",
|
|
1675
|
+
"味道",
|
|
1676
|
+
"烤肉",
|
|
1677
|
+
"火锅",
|
|
1678
|
+
"做饭"
|
|
1679
|
+
],
|
|
1680
|
+
workStudy: [
|
|
1681
|
+
"工作",
|
|
1682
|
+
"项目",
|
|
1683
|
+
"学习",
|
|
1684
|
+
"论文",
|
|
1685
|
+
"考试",
|
|
1686
|
+
"复盘",
|
|
1687
|
+
"成长",
|
|
1688
|
+
"加班",
|
|
1689
|
+
"研究",
|
|
1690
|
+
"产品"
|
|
1691
|
+
],
|
|
1692
|
+
creative: [
|
|
1693
|
+
"摄影",
|
|
1694
|
+
"音乐",
|
|
1695
|
+
"电影",
|
|
1696
|
+
"读书",
|
|
1697
|
+
"作品",
|
|
1698
|
+
"创作",
|
|
1699
|
+
"设计",
|
|
1700
|
+
"照片",
|
|
1701
|
+
"画画"
|
|
1702
|
+
],
|
|
1703
|
+
pet: [
|
|
1704
|
+
"小猫",
|
|
1705
|
+
"小狗",
|
|
1706
|
+
"猫咪",
|
|
1707
|
+
"狗狗",
|
|
1708
|
+
"宠物",
|
|
1709
|
+
"毛孩子"
|
|
1710
|
+
],
|
|
1711
|
+
useful: [
|
|
1712
|
+
"教程",
|
|
1713
|
+
"方法",
|
|
1714
|
+
"步骤",
|
|
1715
|
+
"经验",
|
|
1716
|
+
"分享",
|
|
1717
|
+
"建议",
|
|
1718
|
+
"怎么做"
|
|
1719
|
+
],
|
|
1720
|
+
warm: [
|
|
1721
|
+
"温柔",
|
|
1722
|
+
"治愈",
|
|
1723
|
+
"希望",
|
|
1724
|
+
"拥抱",
|
|
1725
|
+
"阳光",
|
|
1726
|
+
"安慰"
|
|
1727
|
+
],
|
|
1728
|
+
story: [
|
|
1729
|
+
"曾经",
|
|
1730
|
+
"后来",
|
|
1731
|
+
"那年",
|
|
1732
|
+
"小时候",
|
|
1733
|
+
"有一天",
|
|
1734
|
+
"今天",
|
|
1735
|
+
"昨天",
|
|
1736
|
+
"故事"
|
|
1737
|
+
]
|
|
1738
|
+
};
|
|
1739
|
+
const invitationClosings = [
|
|
1740
|
+
"如果你愿意,来录入一下声纹吧。以后再看到你的世界快记,我也能直接听见你的声音:",
|
|
1741
|
+
"愿意的话,也来录入声纹,给这条快记添上你的声音吧。我很期待以后不只看到文字,也能听见你:",
|
|
1742
|
+
"所以想认真邀请你录入声纹。这样下次在世界遇见你的文字,也能听到你亲口表达:",
|
|
1743
|
+
"要是你愿意,就来留下声纹吧。以后你的世界快记不只有文字,也会有属于你的声音:"
|
|
1744
|
+
];
|
|
1745
|
+
const urlPattern = /(?:(?:https?|ftp):\/\/|www\.)[^\s,。!?;:、"“”‘’()【】《》<>]+/gi;
|
|
1746
|
+
const trailingUrlPunctuation = /[.,;:!?,。;:!?))\]}>…]+$/;
|
|
1747
|
+
const whitespace = /\s+/g;
|
|
1748
|
+
const meaningfulCharacter = /[\p{L}\p{N}]/gu;
|
|
1749
|
+
const digitCharacter = /\p{N}/u;
|
|
1750
|
+
const latinCharacter = /[a-z]/i;
|
|
1751
|
+
const chineseCharacter = /[\u3400-\u9fff]/;
|
|
1752
|
+
const mathSymbol = /[+\-×÷*/=<>]/;
|
|
1753
|
+
function worldVoiceprintReadableText(value) {
|
|
1754
|
+
const normalized = value.replace(urlPattern, (matchedUrl) => matchedUrl.match(trailingUrlPunctuation)?.[0] ?? "").replace(/<>/g, " ").replace(whitespace, " ").trim();
|
|
1755
|
+
return /[\p{L}\p{N}]/u.test(normalized) ? normalized : "";
|
|
1756
|
+
}
|
|
1757
|
+
function buildWorldVoiceprintInviteMessage(input) {
|
|
1758
|
+
const preview = input.textPreview?.replace(whitespace, " ").trim();
|
|
1759
|
+
const variant = normalizedVariant(input.variantIndex ?? 0, 4);
|
|
1760
|
+
const copy = contentCopies[worldVoiceprintContentKind(preview ?? "")];
|
|
1761
|
+
const name = input.peerDisplayName.replace(whitespace, " ").trim();
|
|
1762
|
+
const greeting = name === "" || name === "这位用户" ? "我是看到你在世界发的这条快记才来找你的:" : `${name},我是看到你在世界发的这条快记才来找你的:`;
|
|
1763
|
+
const media = mediaSummary(input);
|
|
1764
|
+
return `${greeting}\n\n【来自世界的快记】\n${preview === void 0 || preview === "" ? `(你发布了一条${media === "" ? "" : `${media}的`}世界快记)` : `“${preview}”${media === "" ? "" : `\n(还包含${media})`}`}\n\n${copy.invitationReactions[variant]}\n\n${invitationClosings[variant]}\n${input.inviteUrl}`;
|
|
1765
|
+
}
|
|
1766
|
+
function mediaSummary(input) {
|
|
1767
|
+
const parts = [];
|
|
1768
|
+
if ((input.imageCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.imageCount))}张图片`);
|
|
1769
|
+
if ((input.videoCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.videoCount))}段视频`);
|
|
1770
|
+
if ((input.voiceCount ?? 0) > 0) parts.push(`${String(Math.trunc(input.voiceCount))}段语音`);
|
|
1771
|
+
return parts.join("、");
|
|
1772
|
+
}
|
|
1773
|
+
function normalizedVariant(value, count) {
|
|
1774
|
+
if (!Number.isFinite(value)) return 0;
|
|
1775
|
+
return (Math.trunc(value) % count + count) % count;
|
|
1776
|
+
}
|
|
1777
|
+
function worldVoiceprintContentKind(rawText) {
|
|
1778
|
+
const text = worldVoiceprintReadableText(rawText).toLowerCase();
|
|
1779
|
+
const meaningful = text.match(meaningfulCharacter) ?? [];
|
|
1780
|
+
if (meaningful.length === 0) return "unclear";
|
|
1781
|
+
if (looksNumericOrFormula(text, meaningful)) return "numeric";
|
|
1782
|
+
if (looksUnclear(text, meaningful)) return "unclear";
|
|
1783
|
+
if (meaningful.length <= 6) return "short";
|
|
1784
|
+
return bestMatchingCategory(text) ?? "default";
|
|
1785
|
+
}
|
|
1786
|
+
function looksNumericOrFormula(text, meaningful) {
|
|
1787
|
+
const digitCount = meaningful.filter((character) => digitCharacter.test(character)).length;
|
|
1788
|
+
if (digitCount === 0) return false;
|
|
1789
|
+
if (mathSymbol.test(text)) return true;
|
|
1790
|
+
return digitCount / meaningful.length >= .7;
|
|
1791
|
+
}
|
|
1792
|
+
function looksUnclear(text, meaningful) {
|
|
1793
|
+
const uniqueCharacters = new Set(meaningful);
|
|
1794
|
+
if (meaningful.length >= 5 && uniqueCharacters.size === 1) return true;
|
|
1795
|
+
if (meaningful.length >= 8 && uniqueCharacters.size <= 2) return true;
|
|
1796
|
+
const hasChinese = chineseCharacter.test(text);
|
|
1797
|
+
const hasLatin = latinCharacter.test(text);
|
|
1798
|
+
const hasDigit = digitCharacter.test(text);
|
|
1799
|
+
return !hasChinese && hasLatin && hasDigit && !text.includes(" ");
|
|
1800
|
+
}
|
|
1801
|
+
function bestMatchingCategory(text) {
|
|
1802
|
+
let bestCategory;
|
|
1803
|
+
let bestScore = 0;
|
|
1804
|
+
for (const [category, keywords] of Object.entries(categoryKeywords)) {
|
|
1805
|
+
const score = keywords.filter((keyword) => text.includes(keyword)).length;
|
|
1806
|
+
if (score > bestScore) {
|
|
1807
|
+
bestCategory = category;
|
|
1808
|
+
bestScore = score;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
return bestCategory;
|
|
1812
|
+
}
|
|
1574
1813
|
//#endregion
|
|
1575
1814
|
//#region src/services/ai-video-service.ts
|
|
1576
1815
|
function numberValue$24(value) {
|
|
@@ -2714,18 +2953,33 @@ const ARKME_PHONE_BIND_CODE_ERR = 3;
|
|
|
2714
2953
|
function numberValue$21(value) {
|
|
2715
2954
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2716
2955
|
}
|
|
2956
|
+
function jiwoScanLoginAvailable(config) {
|
|
2957
|
+
try {
|
|
2958
|
+
const authUrl = new URL(config.authBaseUrl);
|
|
2959
|
+
const shareUrl = new URL(config.shareWebsite ?? "");
|
|
2960
|
+
if ([authUrl, shareUrl].some((url) => url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "")) return false;
|
|
2961
|
+
const authHost = authUrl.hostname.toLowerCase();
|
|
2962
|
+
const shareHost = shareUrl.hostname.toLowerCase();
|
|
2963
|
+
return config.environment === "prod" ? authHost === "api.jotmo.cc" && shareHost === "jiwo.cc" : authHost === "jotmo.senguo.me" && shareHost === "jotmo-app.senguo.me";
|
|
2964
|
+
} catch {
|
|
2965
|
+
return false;
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2717
2968
|
var AuthService = class {
|
|
2718
2969
|
runtime;
|
|
2719
2970
|
profile;
|
|
2720
2971
|
lifecycle;
|
|
2721
2972
|
attempts = /* @__PURE__ */ new Map();
|
|
2973
|
+
jiwoAttemptGeneration = 0;
|
|
2722
2974
|
constructor(runtime, profile, lifecycle) {
|
|
2723
2975
|
this.runtime = runtime;
|
|
2724
2976
|
this.profile = profile;
|
|
2725
2977
|
this.lifecycle = lifecycle;
|
|
2726
2978
|
}
|
|
2727
2979
|
dispose() {
|
|
2728
|
-
this.
|
|
2980
|
+
this.jiwoAttemptGeneration += 1;
|
|
2981
|
+
this.cancelAllJiwoLoginAttempts();
|
|
2982
|
+
for (const [attemptId, attempt] of this.attempts) if (attempt.kind === "wechat") this.attempts.delete(attemptId);
|
|
2729
2983
|
}
|
|
2730
2984
|
async logout() {
|
|
2731
2985
|
const activeSession = await this.runtime.sessionStore.read();
|
|
@@ -2738,6 +2992,8 @@ var AuthService = class {
|
|
|
2738
2992
|
await this.runtime.sessionStore.delete();
|
|
2739
2993
|
await this.runtime.clearPendingBindingSession();
|
|
2740
2994
|
this.profile.invalidate();
|
|
2995
|
+
this.jiwoAttemptGeneration += 1;
|
|
2996
|
+
await this.cancelAllJiwoLoginAttempts();
|
|
2741
2997
|
this.attempts.clear();
|
|
2742
2998
|
this.lifecycle.clearAccountState(userIds);
|
|
2743
2999
|
this.lifecycle.reconnectChatRealtime();
|
|
@@ -2773,6 +3029,8 @@ var AuthService = class {
|
|
|
2773
3029
|
};
|
|
2774
3030
|
}
|
|
2775
3031
|
async beginWechatLogin() {
|
|
3032
|
+
this.jiwoAttemptGeneration += 1;
|
|
3033
|
+
await this.cancelAllJiwoLoginAttempts();
|
|
2776
3034
|
const data = await this.runtime.post(this.runtime.config.authBaseUrl, "/api/public/v1/auth/wechat-oauth-login-qrcode", {}, void 0, [200]);
|
|
2777
3035
|
const sceneStr = stringValue$1(data.scene_str).trim();
|
|
2778
3036
|
const pollToken = stringValue$1(data.poll_token).trim();
|
|
@@ -2782,6 +3040,7 @@ var AuthService = class {
|
|
|
2782
3040
|
if (sceneStr === "" || pollToken === "" || qrContent === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录二维码响应不完整", true, 502);
|
|
2783
3041
|
const attemptId = crypto.randomUUID();
|
|
2784
3042
|
const attempt = {
|
|
3043
|
+
kind: "wechat",
|
|
2785
3044
|
attemptId,
|
|
2786
3045
|
sceneStr,
|
|
2787
3046
|
pollToken,
|
|
@@ -2800,7 +3059,7 @@ var AuthService = class {
|
|
|
2800
3059
|
}
|
|
2801
3060
|
async pollWechatLogin(attemptId) {
|
|
2802
3061
|
const attempt = this.attempts.get(attemptId);
|
|
2803
|
-
if (attempt === void 0) throw new ArkmePluginError("login-attempt-not-found", "登录二维码已失效,请重新获取", false, 404);
|
|
3062
|
+
if (attempt === void 0 || attempt.kind !== "wechat") throw new ArkmePluginError("login-attempt-not-found", "登录二维码已失效,请重新获取", false, 404);
|
|
2804
3063
|
if (Date.now() >= attempt.expiresAtMillis) {
|
|
2805
3064
|
this.attempts.delete(attemptId);
|
|
2806
3065
|
return {
|
|
@@ -2834,6 +3093,111 @@ var AuthService = class {
|
|
|
2834
3093
|
this.attempts.delete(attemptId);
|
|
2835
3094
|
return await this.acceptLoginSession(session);
|
|
2836
3095
|
}
|
|
3096
|
+
async beginJiwoLogin() {
|
|
3097
|
+
if (!jiwoScanLoginAvailable(this.runtime.config)) throw new ArkmePluginError("jiwo-scan-login-disabled", "即我扫码登录当前未启用", false, 403);
|
|
3098
|
+
const attemptGeneration = ++this.jiwoAttemptGeneration;
|
|
3099
|
+
await this.cancelAllJiwoLoginAttempts();
|
|
3100
|
+
const data = await this.runtime.post(this.runtime.config.authBaseUrl, "/api/public/v1/auth/app-scan-login/start", {
|
|
3101
|
+
unique_code: await this.runtime.stateStore.uniqueCode(),
|
|
3102
|
+
ref: 0,
|
|
3103
|
+
keep_cancel: true
|
|
3104
|
+
}, void 0, [200]);
|
|
3105
|
+
const ticket = stringValue$1(data.ticket).trim();
|
|
3106
|
+
const pollSecret = stringValue$1(data.poll_secret).trim();
|
|
3107
|
+
const expiresAtMillis = numberValue$21(data.expires_at);
|
|
3108
|
+
if (!/^[A-Za-z0-9_-]{43}$/.test(ticket) || !/^[A-Za-z0-9_-]{43}$/.test(pollSecret) || expiresAtMillis <= Date.now()) throw new ArkmePluginError("jiwo-login-contract-invalid", "即我登录二维码响应不完整", true, 502);
|
|
3109
|
+
if (attemptGeneration !== this.jiwoAttemptGeneration) {
|
|
3110
|
+
await this.cancelJiwoCredentials(ticket, pollSecret);
|
|
3111
|
+
throw new ArkmePluginError("login-attempt-canceled", "登录方式已切换", false, 409);
|
|
3112
|
+
}
|
|
3113
|
+
const shareWebsite = this.runtime.config.shareWebsite ?? "";
|
|
3114
|
+
const qrUrl = new URL("/login/desktop", shareWebsite);
|
|
3115
|
+
qrUrl.searchParams.set("ticket", ticket);
|
|
3116
|
+
const attemptId = crypto.randomUUID();
|
|
3117
|
+
const attempt = {
|
|
3118
|
+
kind: "jiwo",
|
|
3119
|
+
attemptId,
|
|
3120
|
+
ticket,
|
|
3121
|
+
pollSecret,
|
|
3122
|
+
qrContent: qrUrl.toString(),
|
|
3123
|
+
expiresAtMillis
|
|
3124
|
+
};
|
|
3125
|
+
this.attempts.clear();
|
|
3126
|
+
this.attempts.set(attemptId, attempt);
|
|
3127
|
+
return this.jiwoPendingSnapshot(attempt);
|
|
3128
|
+
}
|
|
3129
|
+
async pollJiwoLogin(attemptId) {
|
|
3130
|
+
const attempt = this.attempts.get(attemptId);
|
|
3131
|
+
if (attempt === void 0 || attempt.kind !== "jiwo") throw new ArkmePluginError("login-attempt-not-found", "登录二维码已失效,请重新获取", false, 404);
|
|
3132
|
+
if (Date.now() >= attempt.expiresAtMillis) {
|
|
3133
|
+
await this.cancelJiwoLogin(attemptId);
|
|
3134
|
+
return {
|
|
3135
|
+
status: "expired",
|
|
3136
|
+
environment: this.runtime.config.environment
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
const data = await this.runtime.post(this.runtime.config.authBaseUrl, "/api/public/v1/auth/app-scan-login/poll", {
|
|
3140
|
+
ticket: attempt.ticket,
|
|
3141
|
+
poll_secret: attempt.pollSecret
|
|
3142
|
+
}, void 0, [200]);
|
|
3143
|
+
if (this.attempts.get(attemptId) !== attempt) return {
|
|
3144
|
+
status: "expired",
|
|
3145
|
+
environment: this.runtime.config.environment
|
|
3146
|
+
};
|
|
3147
|
+
const status = stringValue$1(data.status).trim();
|
|
3148
|
+
if (status === "pending") return this.jiwoPendingSnapshot(attempt);
|
|
3149
|
+
if (status === "expired") {
|
|
3150
|
+
this.attempts.delete(attemptId);
|
|
3151
|
+
return {
|
|
3152
|
+
status: "expired",
|
|
3153
|
+
environment: this.runtime.config.environment
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
if (status !== "authenticated") throw new ArkmePluginError("jiwo-login-contract-invalid", "即我扫码登录状态无效", true, 502);
|
|
3157
|
+
const userId = numberValue$21(data.user_id);
|
|
3158
|
+
const accessToken = stringValue$1(data.access_token);
|
|
3159
|
+
const refreshToken = stringValue$1(data.refresh_token);
|
|
3160
|
+
if (userId <= 0 || accessToken === "" || refreshToken === "") throw new ArkmePluginError("login-contract-invalid", "Arkme 登录成功响应缺少凭据", false, 502);
|
|
3161
|
+
this.attempts.delete(attemptId);
|
|
3162
|
+
return await this.acceptLoginSession({
|
|
3163
|
+
accessToken,
|
|
3164
|
+
refreshToken,
|
|
3165
|
+
userId
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
async cancelJiwoLogin(attemptId) {
|
|
3169
|
+
if (attemptId.trim() === "") return { canceled: true };
|
|
3170
|
+
const attempt = this.attempts.get(attemptId);
|
|
3171
|
+
if (attempt === void 0 || attempt.kind !== "jiwo") return { canceled: true };
|
|
3172
|
+
this.jiwoAttemptGeneration += 1;
|
|
3173
|
+
this.attempts.delete(attemptId);
|
|
3174
|
+
await this.cancelJiwoCredentials(attempt.ticket, attempt.pollSecret);
|
|
3175
|
+
return { canceled: true };
|
|
3176
|
+
}
|
|
3177
|
+
async cancelJiwoCredentials(ticket, pollSecret) {
|
|
3178
|
+
try {
|
|
3179
|
+
await this.runtime.post(this.runtime.config.authBaseUrl, "/api/public/v1/auth/app-scan-login/cancel", {
|
|
3180
|
+
ticket,
|
|
3181
|
+
poll_secret: pollSecret
|
|
3182
|
+
}, void 0, [200]);
|
|
3183
|
+
} catch {}
|
|
3184
|
+
}
|
|
3185
|
+
jiwoPendingSnapshot(attempt) {
|
|
3186
|
+
return {
|
|
3187
|
+
status: "pending",
|
|
3188
|
+
environment: this.runtime.config.environment,
|
|
3189
|
+
attemptId: attempt.attemptId,
|
|
3190
|
+
qrContent: attempt.qrContent,
|
|
3191
|
+
expiresAtMillis: attempt.expiresAtMillis
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
async cancelAllJiwoLoginAttempts() {
|
|
3195
|
+
const attempts = [...this.attempts.values()].filter((attempt) => attempt.kind === "jiwo");
|
|
3196
|
+
for (const attempt of attempts) this.attempts.delete(attempt.attemptId);
|
|
3197
|
+
await Promise.all(attempts.map(async (attempt) => {
|
|
3198
|
+
await this.cancelJiwoCredentials(attempt.ticket, attempt.pollSecret);
|
|
3199
|
+
}));
|
|
3200
|
+
}
|
|
2837
3201
|
async testLogin(userId) {
|
|
2838
3202
|
if (this.runtime.config.environment !== "test") throw new ArkmePluginError("test-login-disabled", "测试账号登录仅允许测试环境使用", false, 403);
|
|
2839
3203
|
if (!Number.isSafeInteger(userId) || userId <= 0) throw new ArkmePluginError("test-user-id-invalid", "请输入有效的测试账号 user_id", false);
|
|
@@ -2964,17 +3328,6 @@ function isArkmeBotAvatarRef(value) {
|
|
|
2964
3328
|
return ARKME_BOT_AVATAR_REF_PATTERN.test(value.trim());
|
|
2965
3329
|
}
|
|
2966
3330
|
//#endregion
|
|
2967
|
-
//#region src/secret-value.ts
|
|
2968
|
-
var SecretValue = class {
|
|
2969
|
-
#value;
|
|
2970
|
-
constructor(value) {
|
|
2971
|
-
this.#value = value;
|
|
2972
|
-
}
|
|
2973
|
-
reveal() {
|
|
2974
|
-
return this.#value;
|
|
2975
|
-
}
|
|
2976
|
-
};
|
|
2977
|
-
//#endregion
|
|
2978
3331
|
//#region src/services/privacy-visibility.ts
|
|
2979
3332
|
const CONTENT_ACCESS_PROTECTED = 2;
|
|
2980
3333
|
const TOPIC_PRIVACY_PRIVATE = 2;
|
|
@@ -6724,30 +7077,124 @@ const ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
|
6724
7077
|
//#region src/services/chat-service.ts
|
|
6725
7078
|
const OFFICIAL_AUTHOR_USER_ID = 11;
|
|
6726
7079
|
const OFFICIAL_AUTHOR_FALLBACK_DISPLAY_NAME = "即我作者";
|
|
7080
|
+
const MAX_MESSAGE_COPY_LINK_ITEMS = 100;
|
|
7081
|
+
const LINK_METADATA_MAX_BYTES = 128 * 1024;
|
|
7082
|
+
const LINK_METADATA_MAX_REDIRECTS = 3;
|
|
7083
|
+
const RECORD_UID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7084
|
+
const HTML_ENTITY_MAP = {
|
|
7085
|
+
amp: "&",
|
|
7086
|
+
lt: "<",
|
|
7087
|
+
gt: ">",
|
|
7088
|
+
quot: "\"",
|
|
7089
|
+
apos: "'",
|
|
7090
|
+
nbsp: " "
|
|
7091
|
+
};
|
|
6727
7092
|
function numberValue$16(value) {
|
|
6728
7093
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
6729
7094
|
}
|
|
6730
7095
|
function booleanValue$7(value) {
|
|
6731
7096
|
return value === true;
|
|
6732
7097
|
}
|
|
6733
|
-
function favoriteStickerPersistenceItem(item) {
|
|
7098
|
+
function favoriteStickerPersistenceItem(item) {
|
|
7099
|
+
return {
|
|
7100
|
+
file_asset_uid: stringValue$1(item.file_asset_uid).trim(),
|
|
7101
|
+
file_id: stringValue$1(item.file_id).trim(),
|
|
7102
|
+
file_hash: stringValue$1(item.file_hash).trim(),
|
|
7103
|
+
file_name: stringValue$1(item.file_name).trim(),
|
|
7104
|
+
mime_type: stringValue$1(item.mime_type).trim(),
|
|
7105
|
+
file_kind: Math.trunc(numberValue$16(item.file_kind)),
|
|
7106
|
+
file_type: Math.trunc(numberValue$16(item.file_type)),
|
|
7107
|
+
is_animated: booleanValue$7(item.is_animated),
|
|
7108
|
+
file_size: Math.max(0, Math.trunc(numberValue$16(item.file_size))),
|
|
7109
|
+
file_create_at: Math.max(0, Math.trunc(numberValue$16(item.file_create_at))),
|
|
7110
|
+
file_push_at: Math.max(0, Math.trunc(numberValue$16(item.file_push_at)))
|
|
7111
|
+
};
|
|
7112
|
+
}
|
|
7113
|
+
function listValue$16(value) {
|
|
7114
|
+
return Array.isArray(value) ? value : [];
|
|
7115
|
+
}
|
|
7116
|
+
function htmlTextValue(value) {
|
|
7117
|
+
return value.replace(/<script[\s\S]*?<\/script>/giu, " ").replace(/<style[\s\S]*?<\/style>/giu, " ").replace(/<[^>]+>/gu, " ").replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/giu, (_match, entity) => {
|
|
7118
|
+
const normalized = entity.toLowerCase();
|
|
7119
|
+
if (normalized.startsWith("#x")) {
|
|
7120
|
+
const code = Number.parseInt(normalized.slice(2), 16);
|
|
7121
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : " ";
|
|
7122
|
+
}
|
|
7123
|
+
if (normalized.startsWith("#")) {
|
|
7124
|
+
const code = Number.parseInt(normalized.slice(1), 10);
|
|
7125
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : " ";
|
|
7126
|
+
}
|
|
7127
|
+
return HTML_ENTITY_MAP[normalized] ?? " ";
|
|
7128
|
+
}).replace(/\s+/gu, " ").trim();
|
|
7129
|
+
}
|
|
7130
|
+
function firstHtmlMetaContent(html, key) {
|
|
7131
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
7132
|
+
const patterns = [new RegExp(`<meta\\b(?=[^>]*(?:property|name)=["']${escaped}["'])(?=[^>]*content=["']([^"']*)["'])[^>]*>`, "iu"), new RegExp(`<meta\\b(?=[^>]*content=["']([^"']*)["'])(?=[^>]*(?:property|name)=["']${escaped}["'])[^>]*>`, "iu")];
|
|
7133
|
+
for (const pattern of patterns) {
|
|
7134
|
+
const value = pattern.exec(html)?.[1];
|
|
7135
|
+
if (value !== void 0) return htmlTextValue(value);
|
|
7136
|
+
}
|
|
7137
|
+
return "";
|
|
7138
|
+
}
|
|
7139
|
+
function fallbackLinkTitle(url) {
|
|
7140
|
+
const host = url.hostname.replace(/^www\./iu, "");
|
|
7141
|
+
if (host === "github.com") {
|
|
7142
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
7143
|
+
if (parts.length >= 4 && parts[2] === "pull") return `Pull Request #${parts[3]} · ${parts[0]}/${parts[1]}`;
|
|
7144
|
+
if (parts.length >= 2) return `${parts[0]}/${parts[1]}`;
|
|
7145
|
+
}
|
|
7146
|
+
if (host === "codeup.aliyun.com" || host.endsWith(".codeup.aliyun.com")) {
|
|
7147
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
7148
|
+
const changeIndex = parts.indexOf("change");
|
|
7149
|
+
const repository = changeIndex >= 2 ? parts[changeIndex - 1] : parts.at(-1);
|
|
7150
|
+
const changeNo = changeIndex >= 0 ? parts[changeIndex + 1] : void 0;
|
|
7151
|
+
if (repository !== void 0 && changeNo !== void 0) return `${repository} · Change #${changeNo}`;
|
|
7152
|
+
}
|
|
7153
|
+
return "分享链接";
|
|
7154
|
+
}
|
|
7155
|
+
function ensureMetadataUrl(rawUrl) {
|
|
7156
|
+
let url;
|
|
7157
|
+
try {
|
|
7158
|
+
url = new URL(rawUrl.trim());
|
|
7159
|
+
} catch {
|
|
7160
|
+
throw new ArkmePluginError("link-metadata-url-invalid", "链接暂不可预览", false, 400);
|
|
7161
|
+
}
|
|
7162
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new ArkmePluginError("link-metadata-url-invalid", "链接暂不可预览", false, 400);
|
|
7163
|
+
if (url.username !== "" || url.password !== "") throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
|
|
7164
|
+
const hostname = url.hostname.toLowerCase();
|
|
7165
|
+
if (hostname === "localhost" || hostname.endsWith(".local")) throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
|
|
7166
|
+
if (/^(?:127|10|0|169\.254|192\.168)\./u.test(hostname) || /^172\.(?:1[6-9]|2\d|3[0-1])\./u.test(hostname)) throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
|
|
7167
|
+
if (hostname === "::1" || hostname.startsWith("fc") || hostname.startsWith("fd") || hostname.startsWith("fe80")) throw new ArkmePluginError("link-metadata-url-forbidden", "链接暂不可预览", false, 403);
|
|
7168
|
+
return url;
|
|
7169
|
+
}
|
|
7170
|
+
async function readResponseTextPrefix(response) {
|
|
7171
|
+
if (response.body === null) return await response.text();
|
|
7172
|
+
const reader = response.body.getReader();
|
|
7173
|
+
const chunks = [];
|
|
7174
|
+
let bytes = 0;
|
|
7175
|
+
try {
|
|
7176
|
+
while (bytes < LINK_METADATA_MAX_BYTES) {
|
|
7177
|
+
const next = await reader.read();
|
|
7178
|
+
if (next.done) break;
|
|
7179
|
+
chunks.push(next.value);
|
|
7180
|
+
bytes += next.value.byteLength;
|
|
7181
|
+
}
|
|
7182
|
+
} finally {
|
|
7183
|
+
reader.releaseLock();
|
|
7184
|
+
}
|
|
7185
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(Uint8Array.from(chunks.flatMap((chunk) => [...chunk])));
|
|
7186
|
+
}
|
|
7187
|
+
function linkMetadataFromHtml(url, html) {
|
|
7188
|
+
const title = firstHtmlMetaContent(html, "og:title") || firstHtmlMetaContent(html, "twitter:title") || htmlTextValue(/<title\b[^>]*>([\s\S]*?)<\/title>/iu.exec(html)?.[1] ?? "") || fallbackLinkTitle(url);
|
|
7189
|
+
const description = firstHtmlMetaContent(html, "og:description") || firstHtmlMetaContent(html, "description");
|
|
7190
|
+
const siteName = firstHtmlMetaContent(html, "og:site_name") || url.hostname.replace(/^www\./iu, "");
|
|
6734
7191
|
return {
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
mime_type: stringValue$1(item.mime_type).trim(),
|
|
6740
|
-
file_kind: Math.trunc(numberValue$16(item.file_kind)),
|
|
6741
|
-
file_type: Math.trunc(numberValue$16(item.file_type)),
|
|
6742
|
-
is_animated: booleanValue$7(item.is_animated),
|
|
6743
|
-
file_size: Math.max(0, Math.trunc(numberValue$16(item.file_size))),
|
|
6744
|
-
file_create_at: Math.max(0, Math.trunc(numberValue$16(item.file_create_at))),
|
|
6745
|
-
file_push_at: Math.max(0, Math.trunc(numberValue$16(item.file_push_at)))
|
|
7192
|
+
url: url.toString(),
|
|
7193
|
+
title,
|
|
7194
|
+
...description === "" ? {} : { description },
|
|
7195
|
+
...siteName === "" ? {} : { siteName }
|
|
6746
7196
|
};
|
|
6747
7197
|
}
|
|
6748
|
-
function listValue$16(value) {
|
|
6749
|
-
return Array.isArray(value) ? value : [];
|
|
6750
|
-
}
|
|
6751
7198
|
function chatMemberRole$1(value) {
|
|
6752
7199
|
if (value === "owner" || value === 1) return "owner";
|
|
6753
7200
|
if (value === "admin" || value === 2) return "admin";
|
|
@@ -6768,6 +7215,185 @@ function integerLikeValue$1(value) {
|
|
|
6768
7215
|
}
|
|
6769
7216
|
return 0;
|
|
6770
7217
|
}
|
|
7218
|
+
function epochMillisValue(value) {
|
|
7219
|
+
const timestamp = integerLikeValue$1(value);
|
|
7220
|
+
return timestamp > 0 && timestamp < 1e11 ? timestamp * 1e3 : timestamp;
|
|
7221
|
+
}
|
|
7222
|
+
function messageCopyLinkMediaItemFromData(value) {
|
|
7223
|
+
const data = objectValue$1(value);
|
|
7224
|
+
return {
|
|
7225
|
+
fileKind: integerLikeValue$1(data.file_kind),
|
|
7226
|
+
fileName: stringValue$1(data.file_name),
|
|
7227
|
+
size: Math.max(0, integerLikeValue$1(data.size))
|
|
7228
|
+
};
|
|
7229
|
+
}
|
|
7230
|
+
function messageCopyLinkStructuredContentFromData(value) {
|
|
7231
|
+
const data = objectValue$1(value);
|
|
7232
|
+
if (Object.keys(data).length === 0) return void 0;
|
|
7233
|
+
return {
|
|
7234
|
+
structuredKind: integerLikeValue$1(data.structured_kind),
|
|
7235
|
+
durationMillis: Math.max(0, integerLikeValue$1(data.duration_millis))
|
|
7236
|
+
};
|
|
7237
|
+
}
|
|
7238
|
+
function messageCopyLinkSnapshotItemFromData(value) {
|
|
7239
|
+
const data = objectValue$1(value);
|
|
7240
|
+
const structuredContent = messageCopyLinkStructuredContentFromData(data.structured_content);
|
|
7241
|
+
const recordUid = stringValue$1(data.record_uid ?? data.recordUid ?? data.uid).trim();
|
|
7242
|
+
const item = {
|
|
7243
|
+
...recordUid === "" ? {} : { recordUid },
|
|
7244
|
+
sourceKind: stringValue$1(data.source_kind),
|
|
7245
|
+
senderDisplayName: stringValue$1(data.sender_display_name),
|
|
7246
|
+
senderAvatarUrl: stringValue$1(data.sender_avatar_url),
|
|
7247
|
+
title: stringValue$1(data.title),
|
|
7248
|
+
textContent: stringValue$1(data.text_content),
|
|
7249
|
+
sendAtMillis: epochMillisValue(data.send_at),
|
|
7250
|
+
templateKind: integerLikeValue$1(data.template_kind),
|
|
7251
|
+
displayKind: integerLikeValue$1(data.display_kind),
|
|
7252
|
+
officialMark: integerLikeValue$1(data.official_mark),
|
|
7253
|
+
mediaItems: listValue$16(data.media_items).map(messageCopyLinkMediaItemFromData),
|
|
7254
|
+
...structuredContent === void 0 ? {} : { structuredContent }
|
|
7255
|
+
};
|
|
7256
|
+
if (item.sendAtMillis <= 0 || item.senderDisplayName.trim() === "" && item.sourceKind !== "agent_message") throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
|
|
7257
|
+
if (item.title.trim() === "" && item.textContent.trim() === "" && item.mediaItems.length === 0 && item.structuredContent === void 0) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
|
|
7258
|
+
return item;
|
|
7259
|
+
}
|
|
7260
|
+
function messageCopyLinkPresentationNodesFromData(value, itemCount, depth = 1, seenItemIndexes = /* @__PURE__ */ new Set()) {
|
|
7261
|
+
const rawNodes = listValue$16(value);
|
|
7262
|
+
if (rawNodes.length === 0) return [];
|
|
7263
|
+
if (depth > 8) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
|
|
7264
|
+
return rawNodes.map((rawNode) => {
|
|
7265
|
+
const data = objectValue$1(rawNode);
|
|
7266
|
+
const kind = stringValue$1(data.kind);
|
|
7267
|
+
if (kind === "item") {
|
|
7268
|
+
const itemIndex = integerLikeValue$1(data.item_index);
|
|
7269
|
+
if (itemIndex < 0 || itemIndex >= itemCount || seenItemIndexes.has(itemIndex)) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
|
|
7270
|
+
seenItemIndexes.add(itemIndex);
|
|
7271
|
+
return {
|
|
7272
|
+
kind: "item",
|
|
7273
|
+
itemIndex
|
|
7274
|
+
};
|
|
7275
|
+
}
|
|
7276
|
+
if (kind === "forward_bundle") return {
|
|
7277
|
+
kind: "forward_bundle",
|
|
7278
|
+
title: stringValue$1(data.title),
|
|
7279
|
+
commentText: stringValue$1(data.comment_text),
|
|
7280
|
+
createdAtMillis: integerLikeValue$1(data.created_at),
|
|
7281
|
+
senderDisplayName: stringValue$1(data.sender_display_name),
|
|
7282
|
+
children: messageCopyLinkPresentationNodesFromData(data.children, itemCount, depth + 1, seenItemIndexes)
|
|
7283
|
+
};
|
|
7284
|
+
throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
|
|
7285
|
+
});
|
|
7286
|
+
}
|
|
7287
|
+
function messageCopyLinkSourceAnchorFromData(value) {
|
|
7288
|
+
const data = objectValue$1(value);
|
|
7289
|
+
return {
|
|
7290
|
+
relationUid: stringValue$1(data.rel_uid),
|
|
7291
|
+
recordUid: stringValue$1(data.record_uid),
|
|
7292
|
+
recordOwnerUserId: integerLikeValue$1(data.record_owner_user_id),
|
|
7293
|
+
sequence: integerLikeValue$1(data.seq)
|
|
7294
|
+
};
|
|
7295
|
+
}
|
|
7296
|
+
function firstTextValue(source, keys) {
|
|
7297
|
+
for (const key of keys) {
|
|
7298
|
+
const value = stringValue$1(source[key]).trim();
|
|
7299
|
+
if (value !== "") return value;
|
|
7300
|
+
}
|
|
7301
|
+
return "";
|
|
7302
|
+
}
|
|
7303
|
+
function messageCopyLinkExtensionItemFromData(value) {
|
|
7304
|
+
const data = objectValue$1(value);
|
|
7305
|
+
const core = objectValue$1(data.record_core ?? data.recordCore);
|
|
7306
|
+
const recordUid = firstTextValue(data, [
|
|
7307
|
+
"record_uid",
|
|
7308
|
+
"recordUid",
|
|
7309
|
+
"uid"
|
|
7310
|
+
]) || firstTextValue(core, [
|
|
7311
|
+
"record_uid",
|
|
7312
|
+
"recordUid",
|
|
7313
|
+
"uid"
|
|
7314
|
+
]);
|
|
7315
|
+
const senderDisplayName = firstTextValue(data, [
|
|
7316
|
+
"sender_display_name",
|
|
7317
|
+
"senderDisplayName",
|
|
7318
|
+
"nickname",
|
|
7319
|
+
"nick_name",
|
|
7320
|
+
"nickName",
|
|
7321
|
+
"owner_name",
|
|
7322
|
+
"ownerName"
|
|
7323
|
+
]) || firstTextValue(core, [
|
|
7324
|
+
"sender_display_name",
|
|
7325
|
+
"senderDisplayName",
|
|
7326
|
+
"nickname",
|
|
7327
|
+
"nick_name",
|
|
7328
|
+
"nickName",
|
|
7329
|
+
"owner_name",
|
|
7330
|
+
"ownerName"
|
|
7331
|
+
]) || "Arkme用户";
|
|
7332
|
+
const sendAtMillis = epochMillisValue(data.send_at ?? data.sendAt ?? data.created_at ?? data.createdAt ?? data.published_at ?? data.publishedAt ?? core.send_at ?? core.sendAt ?? core.created_at ?? core.createdAt ?? core.published_at ?? core.publishedAt);
|
|
7333
|
+
const title = stringValue$1(data.title ?? core.title);
|
|
7334
|
+
const textContent = stringValue$1(data.text_content ?? data.textContent ?? data.content ?? core.text_content ?? core.textContent ?? core.content);
|
|
7335
|
+
const mediaItems = listValue$16(data.media_items ?? data.mediaItems ?? core.media_items ?? core.mediaItems).map(messageCopyLinkMediaItemFromData);
|
|
7336
|
+
const structuredContent = messageCopyLinkStructuredContentFromData(data.structured_content ?? data.structuredContent ?? core.structured_content ?? core.structuredContent);
|
|
7337
|
+
if (recordUid === "" || sendAtMillis <= 0 || title.trim() === "" && textContent.trim() === "" && mediaItems.length === 0 && structuredContent === void 0) return;
|
|
7338
|
+
return {
|
|
7339
|
+
recordUid,
|
|
7340
|
+
level: Math.max(2, integerLikeValue$1(data.level) || 2),
|
|
7341
|
+
sourceKind: stringValue$1(data.source_kind ?? data.sourceKind) || "record_extension",
|
|
7342
|
+
senderDisplayName,
|
|
7343
|
+
senderAvatarUrl: firstTextValue(data, [
|
|
7344
|
+
"sender_avatar_url",
|
|
7345
|
+
"senderAvatarUrl",
|
|
7346
|
+
"avatar_ref",
|
|
7347
|
+
"avatarRef",
|
|
7348
|
+
"avatar_url",
|
|
7349
|
+
"avatarUrl",
|
|
7350
|
+
"avatar"
|
|
7351
|
+
]),
|
|
7352
|
+
title,
|
|
7353
|
+
textContent,
|
|
7354
|
+
sendAtMillis,
|
|
7355
|
+
templateKind: integerLikeValue$1(data.template_kind ?? data.templateKind ?? core.template_kind ?? core.templateKind) || 1,
|
|
7356
|
+
displayKind: integerLikeValue$1(data.display_kind ?? data.displayKind ?? core.display_kind ?? core.displayKind),
|
|
7357
|
+
officialMark: integerLikeValue$1(data.official_mark ?? data.officialMark ?? core.official_mark ?? core.officialMark),
|
|
7358
|
+
mediaItems,
|
|
7359
|
+
...structuredContent === void 0 ? {} : { structuredContent }
|
|
7360
|
+
};
|
|
7361
|
+
}
|
|
7362
|
+
function messageCopyLinkRecordContextFromData(data) {
|
|
7363
|
+
const context = objectValue$1(data.record_context ?? data.recordContext);
|
|
7364
|
+
const extensions = listValue$16(context.extensions ?? context.extend_records ?? context.extendRecords ?? context.list ?? data.extensions ?? data.extend_records ?? data.extendRecords ?? data.list).map(messageCopyLinkExtensionItemFromData).filter((item) => item !== void 0);
|
|
7365
|
+
const extensionCount = Math.max(integerLikeValue$1(context.extension_count ?? context.extensionCount ?? context.total ?? data.extension_count ?? data.extensionCount ?? data.total), extensions.length);
|
|
7366
|
+
if (extensionCount <= 0 && extensions.length === 0) return void 0;
|
|
7367
|
+
extensions.sort((left, right) => right.sendAtMillis - left.sendAtMillis);
|
|
7368
|
+
return {
|
|
7369
|
+
extensionCount,
|
|
7370
|
+
extensions
|
|
7371
|
+
};
|
|
7372
|
+
}
|
|
7373
|
+
function mergeMessageCopyLinkRecordContexts(left, right) {
|
|
7374
|
+
if (left === void 0) return right;
|
|
7375
|
+
if (right === void 0) return left;
|
|
7376
|
+
const byUid = /* @__PURE__ */ new Map();
|
|
7377
|
+
for (const item of [...left.extensions, ...right.extensions]) byUid.set(item.recordUid, item);
|
|
7378
|
+
const extensions = [...byUid.values()].sort((a, b) => b.sendAtMillis - a.sendAtMillis);
|
|
7379
|
+
return {
|
|
7380
|
+
extensionCount: Math.max(left.extensionCount, right.extensionCount, extensions.length),
|
|
7381
|
+
extensions
|
|
7382
|
+
};
|
|
7383
|
+
}
|
|
7384
|
+
function messageCopyLinkWorldTags(text) {
|
|
7385
|
+
return [...text.matchAll(/#(\S+)/gu)].map((match) => match[1]?.trim() ?? "").filter((tag) => tag !== "");
|
|
7386
|
+
}
|
|
7387
|
+
function messageCopyLinkRecordUidCandidates(items, anchors) {
|
|
7388
|
+
const candidates = [];
|
|
7389
|
+
const push = (value) => {
|
|
7390
|
+
const normalized = value?.trim() ?? "";
|
|
7391
|
+
if (normalized !== "" && !candidates.includes(normalized)) candidates.push(normalized);
|
|
7392
|
+
};
|
|
7393
|
+
for (const item of items) push(item.recordUid);
|
|
7394
|
+
for (const anchor of anchors) push(anchor.recordUid);
|
|
7395
|
+
return candidates;
|
|
7396
|
+
}
|
|
6771
7397
|
function optionalString$2(value) {
|
|
6772
7398
|
const text = stringValue$1(value).trim();
|
|
6773
7399
|
return text === "" ? void 0 : text;
|
|
@@ -7126,6 +7752,8 @@ var ChatService = class {
|
|
|
7126
7752
|
}
|
|
7127
7753
|
async openOfficialAuthorPrivateChat(options = {}) {
|
|
7128
7754
|
const session = await this.runtime.requireSession();
|
|
7755
|
+
const existing = await this.findPrivateChatByPeerUserId(OFFICIAL_AUTHOR_USER_ID, session, options.signal);
|
|
7756
|
+
if (existing !== void 0) return { source: existing };
|
|
7129
7757
|
const created = await this.runtime.authenticatedSubjectPost("/api/v1/private/create-chat-ref-asen", {
|
|
7130
7758
|
subject_uid: `dsh_official_author_${randomUUID().replace(/-/g, "")}`,
|
|
7131
7759
|
network: "dsh",
|
|
@@ -7147,6 +7775,25 @@ var ChatService = class {
|
|
|
7147
7775
|
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
7148
7776
|
});
|
|
7149
7777
|
}
|
|
7778
|
+
/**
|
|
7779
|
+
* Contact-author is an entry point, not a second conversation type. Scan the
|
|
7780
|
+
* ordinary directory first so repeated clicks always reuse the existing chat.
|
|
7781
|
+
*/
|
|
7782
|
+
async findPrivateChatByPeerUserId(peerUserId, session, signal) {
|
|
7783
|
+
let cursor;
|
|
7784
|
+
for (let pageIndex = 0; pageIndex < 10; pageIndex += 1) {
|
|
7785
|
+
const page = await this.source.listSources("root", {
|
|
7786
|
+
limit: 50,
|
|
7787
|
+
refresh: true,
|
|
7788
|
+
...cursor === void 0 ? {} : { cursor },
|
|
7789
|
+
...signal === void 0 ? {} : { signal }
|
|
7790
|
+
});
|
|
7791
|
+
const existing = page.items.find((source) => source.kind === "private_chat" && source.peerUserId === peerUserId);
|
|
7792
|
+
if (existing !== void 0) return existing;
|
|
7793
|
+
if (!page.hasMore || page.nextCursor === void 0) return void 0;
|
|
7794
|
+
cursor = page.nextCursor;
|
|
7795
|
+
}
|
|
7796
|
+
}
|
|
7150
7797
|
async officialAuthorProfile(options = {}) {
|
|
7151
7798
|
const session = await this.runtime.requireSession();
|
|
7152
7799
|
const profile = (await this.profile.publicProfileSummariesByUserIds([OFFICIAL_AUTHOR_USER_ID], session, options.signal)).get(OFFICIAL_AUTHOR_USER_ID);
|
|
@@ -7209,9 +7856,10 @@ var ChatService = class {
|
|
|
7209
7856
|
sendAtMillis: options.cursor.sendAtMillis,
|
|
7210
7857
|
recordUid: options.cursor.itemUid
|
|
7211
7858
|
} : void 0);
|
|
7859
|
+
const signingKey = await this.runtime.stateStore.uniqueCode();
|
|
7212
7860
|
return {
|
|
7213
7861
|
source: await this.source.sourceItem(source),
|
|
7214
|
-
items: page.items.map((item) => this.record.recordTimelineItem(item)),
|
|
7862
|
+
items: page.items.map((item) => this.withRecordMessageActionRef(source, this.record.recordTimelineItem(item), session.userId, signingKey)),
|
|
7215
7863
|
hasMore: page.hasMore,
|
|
7216
7864
|
...page.nextCursor === void 0 ? {} : { nextCursor: {
|
|
7217
7865
|
sendAtMillis: page.nextCursor.sendAtMillis,
|
|
@@ -7447,6 +8095,329 @@ var ChatService = class {
|
|
|
7447
8095
|
readReceiptItemKey(itemUid, sequence) {
|
|
7448
8096
|
return `${itemUid}\u0000${String(sequence)}`;
|
|
7449
8097
|
}
|
|
8098
|
+
async copySourceMessageLink(sourceRef, actionRefs, options = {}) {
|
|
8099
|
+
const session = await this.runtime.requireSession();
|
|
8100
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
8101
|
+
const references = this.forwardReferencesOrderedForCommand(await this.openMessageActionRefs(actionRefs, session.userId, source));
|
|
8102
|
+
if (references.length === 0 || references.length > 100) throw new ArkmePluginError("message-actions-selection-invalid", "请选择 1 至 100 条消息", false);
|
|
8103
|
+
this.validateForwardReferences(references);
|
|
8104
|
+
const sources = references.map((reference) => reference.sourceKind === "chat_relation" ? {
|
|
8105
|
+
kind: "chat_relation",
|
|
8106
|
+
chat_session_uid: reference.chatSessionUid,
|
|
8107
|
+
relation_uid: reference.relationUid
|
|
8108
|
+
} : {
|
|
8109
|
+
kind: "record",
|
|
8110
|
+
record_owner_user_id: reference.recordOwnerUserId,
|
|
8111
|
+
record_uid: reference.recordUid
|
|
8112
|
+
});
|
|
8113
|
+
const data = await this.runtime.authenticatedChatPost("/api/v1/chats/messages/copy-link/get-or-create", { sources }, session, options.signal);
|
|
8114
|
+
const sid = stringValue$1(data.sid).trim();
|
|
8115
|
+
const url = stringValue$1(data.url).trim();
|
|
8116
|
+
if (sid === "" || !/^https?:\/\//i.test(url)) throw new ArkmePluginError("message-copy-link-response-invalid", "复制链接失败,请稍后重试", true, 502);
|
|
8117
|
+
return {
|
|
8118
|
+
sid,
|
|
8119
|
+
url
|
|
8120
|
+
};
|
|
8121
|
+
}
|
|
8122
|
+
async resolveMessageCopyLink(sid, options = {}) {
|
|
8123
|
+
const normalizedSid = sid.trim();
|
|
8124
|
+
if (!/^[0-9A-Za-z]{16}$/.test(normalizedSid)) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
|
|
8125
|
+
const session = await this.runtime.requireSession();
|
|
8126
|
+
const data = await this.runtime.authenticatedChatPost("/api/v1/chats/messages/copy-link/resolve", { sid: normalizedSid }, session, options.signal);
|
|
8127
|
+
const rawItems = listValue$16(data.items);
|
|
8128
|
+
if (rawItems.length === 0 && stringValue$1(data.audit_status) === "auditing") throw new ArkmePluginError("message-copy-link-auditing", "审核中", true, 409);
|
|
8129
|
+
const items = rawItems.map(messageCopyLinkSnapshotItemFromData);
|
|
8130
|
+
if (items.length === 0 || items.length > MAX_MESSAGE_COPY_LINK_ITEMS) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
|
|
8131
|
+
const presentation = messageCopyLinkPresentationNodesFromData(data.presentation, items.length);
|
|
8132
|
+
const sourceContext = objectValue$1(data.source_context);
|
|
8133
|
+
const accessMode = stringValue$1(data.access_mode);
|
|
8134
|
+
if (accessMode !== "normal" && accessMode !== "link_read_only") throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
|
|
8135
|
+
const anchors = accessMode === "normal" ? listValue$16(sourceContext.anchors).map(messageCopyLinkSourceAnchorFromData).filter((anchor) => anchor.relationUid.trim() !== "" && anchor.recordUid.trim() !== "" && anchor.recordOwnerUserId > 0) : [];
|
|
8136
|
+
const recordContext = mergeMessageCopyLinkRecordContexts(messageCopyLinkRecordContextFromData(data), accessMode === "normal" ? await this.loadMessageCopyLinkPublicExtensions(messageCopyLinkRecordUidCandidates(items, anchors), session, options.signal) : void 0);
|
|
8137
|
+
const result = {
|
|
8138
|
+
sid: stringValue$1(data.sid),
|
|
8139
|
+
displayTitle: stringValue$1(data.display_title),
|
|
8140
|
+
generatedAtMillis: epochMillisValue(data.generated_at),
|
|
8141
|
+
accessMode,
|
|
8142
|
+
items,
|
|
8143
|
+
presentation,
|
|
8144
|
+
...accessMode === "normal" ? {
|
|
8145
|
+
sourceSessionUid: stringValue$1(sourceContext.chat_session_uid),
|
|
8146
|
+
sourceAnchors: anchors
|
|
8147
|
+
} : {},
|
|
8148
|
+
...recordContext === void 0 ? {} : { recordContext }
|
|
8149
|
+
};
|
|
8150
|
+
if (result.sid !== normalizedSid || accessMode === "normal" && (result.sourceSessionUid === void 0 || result.sourceSessionUid.trim() === "" || anchors.length === 0)) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
|
|
8151
|
+
return result;
|
|
8152
|
+
}
|
|
8153
|
+
async loadMessageCopyLinkPublicExtensions(recordUids, session, signal) {
|
|
8154
|
+
const worldPost = this.runtime.authenticatedWorldPost;
|
|
8155
|
+
const candidates = recordUids.map((recordUid) => recordUid.trim()).filter((recordUid, index, array) => recordUid !== "" && array.indexOf(recordUid) === index).slice(0, 3);
|
|
8156
|
+
if (candidates.length === 0 || worldPost === void 0) return void 0;
|
|
8157
|
+
for (const normalizedRecordUid of candidates) {
|
|
8158
|
+
const extensions = [];
|
|
8159
|
+
let offset = 0;
|
|
8160
|
+
let total = 0;
|
|
8161
|
+
let hasMore;
|
|
8162
|
+
try {
|
|
8163
|
+
while (true) {
|
|
8164
|
+
const data = await worldPost.call(this.runtime, "/api/v1/public-record/extend-list", {
|
|
8165
|
+
record_uid: normalizedRecordUid,
|
|
8166
|
+
limit: 50,
|
|
8167
|
+
offset
|
|
8168
|
+
}, session, signal, {
|
|
8169
|
+
lane: "interactive-read",
|
|
8170
|
+
bypassCache: true
|
|
8171
|
+
});
|
|
8172
|
+
const rawItems = listValue$16(data.list ?? data.items);
|
|
8173
|
+
if (offset === 0) total = Math.max(0, integerLikeValue$1(data.total));
|
|
8174
|
+
const pageItems = rawItems.map((raw) => messageCopyLinkExtensionItemFromData(raw)).filter((item) => item !== void 0);
|
|
8175
|
+
extensions.push(...pageItems);
|
|
8176
|
+
if (rawItems.length === 0) break;
|
|
8177
|
+
offset += rawItems.length;
|
|
8178
|
+
hasMore = data.has_more === true || data.hasMore === true ? true : data.has_more === false || data.hasMore === false ? false : void 0;
|
|
8179
|
+
if (total > 0 && offset >= total) break;
|
|
8180
|
+
if (hasMore === false) break;
|
|
8181
|
+
if (hasMore === void 0 && total <= 0) break;
|
|
8182
|
+
}
|
|
8183
|
+
} catch {
|
|
8184
|
+
continue;
|
|
8185
|
+
}
|
|
8186
|
+
const extensionCount = Math.max(total, extensions.length);
|
|
8187
|
+
if (extensionCount <= 0 && extensions.length === 0) continue;
|
|
8188
|
+
extensions.sort((left, right) => right.sendAtMillis - left.sendAtMillis);
|
|
8189
|
+
return {
|
|
8190
|
+
extensionCount,
|
|
8191
|
+
extensions
|
|
8192
|
+
};
|
|
8193
|
+
}
|
|
8194
|
+
}
|
|
8195
|
+
async extendMessageCopyLink(sid, itemIndex, textContent, recordUid, options = {}) {
|
|
8196
|
+
const normalizedSid = sid.trim();
|
|
8197
|
+
const normalizedIndex = Math.trunc(itemIndex);
|
|
8198
|
+
const normalizedUid = recordUid.trim();
|
|
8199
|
+
const normalizedText = textContent.trim();
|
|
8200
|
+
if (!/^[0-9A-Za-z]{16}$/.test(normalizedSid) || normalizedIndex < 0 || normalizedIndex >= MAX_MESSAGE_COPY_LINK_ITEMS) throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
|
|
8201
|
+
if (!RECORD_UID_PATTERN.test(normalizedUid)) throw new ArkmePluginError("record-uid-invalid", "写入标识无效,请重试", false);
|
|
8202
|
+
if (normalizedText === "") throw new ArkmePluginError("record-text-empty", "请输入延展内容", false);
|
|
8203
|
+
if (normalizedText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("record-text-too-long", `内容不能超过 ${this.runtime.config.maxTextLength} 个字符`, false);
|
|
8204
|
+
const session = await this.runtime.requireSession();
|
|
8205
|
+
const detail = await this.resolveMessageCopyLink(normalizedSid, options);
|
|
8206
|
+
const item = detail.items[normalizedIndex];
|
|
8207
|
+
const anchor = detail.accessMode === "normal" ? detail.sourceAnchors?.[normalizedIndex] : void 0;
|
|
8208
|
+
const parentRecordUid = item === void 0 ? "" : messageCopyLinkRecordUidCandidates([item], anchor === void 0 ? [] : [anchor])[0] ?? "";
|
|
8209
|
+
if (item === void 0 || parentRecordUid === "") throw new ArkmePluginError("message-copy-link-extension-unavailable", "链接暂不可延展", false, 409);
|
|
8210
|
+
const profile = (await this.profile.refreshProfile()).profile;
|
|
8211
|
+
if (profile === null) throw new ArkmePluginError("profile-unavailable", "无法读取当前 Arkme 账号资料", true);
|
|
8212
|
+
if (profile.contact.phoneMasked === void 0) throw new ArkmePluginError("world-phone-binding-required", "请先在 Arkme 客户端绑定手机号,再延展快记", false);
|
|
8213
|
+
const sendAtMillis = Date.now();
|
|
8214
|
+
const recordResult = await this.record.createTextForConversation(normalizedUid, normalizedText);
|
|
8215
|
+
if (recordResult.localState !== "synced") throw new ArkmePluginError("message-copy-link-extension-record-pending", recordResult.error ?? "延展已保存到待重试队列,请稍后重试", true);
|
|
8216
|
+
const createdRecordUid = recordResult.recordUid.trim() || normalizedUid;
|
|
8217
|
+
const data = await this.runtime.authenticatedWorldPost("/api/v1/public-record/publish", {
|
|
8218
|
+
record_uid: createdRecordUid,
|
|
8219
|
+
content: normalizedText,
|
|
8220
|
+
text_content: normalizedText,
|
|
8221
|
+
tags: messageCopyLinkWorldTags(normalizedText),
|
|
8222
|
+
original_topic_id: 0,
|
|
8223
|
+
created_at: sendAtMillis,
|
|
8224
|
+
nick_name: profile.nickname || profile.displayName,
|
|
8225
|
+
avatar: profile.avatarRef,
|
|
8226
|
+
template_kind: 1,
|
|
8227
|
+
parent_record_uid: parentRecordUid
|
|
8228
|
+
}, session, options.signal);
|
|
8229
|
+
const publishedRecordUid = stringValue$1(data.record_uid).trim() || createdRecordUid;
|
|
8230
|
+
const senderDisplayName = profile.nickname.trim() || profile.displayName.trim() || "我";
|
|
8231
|
+
const senderAvatarUrl = profile.avatarRef.trim();
|
|
8232
|
+
this.source.invalidateSourceListCache(session.userId, "send_to_self");
|
|
8233
|
+
this.realtime.emitChatClientEvent({
|
|
8234
|
+
type: "projection-invalidated",
|
|
8235
|
+
revision: this.realtime.nextChatClientRevision(),
|
|
8236
|
+
projection: "record"
|
|
8237
|
+
});
|
|
8238
|
+
return {
|
|
8239
|
+
sid: detail.sid,
|
|
8240
|
+
recordUid: publishedRecordUid,
|
|
8241
|
+
parentRecordUid,
|
|
8242
|
+
status: numberValue$16(data.status ?? data.check_status ?? recordResult.status),
|
|
8243
|
+
localState: "synced",
|
|
8244
|
+
extension: {
|
|
8245
|
+
recordUid: publishedRecordUid,
|
|
8246
|
+
level: 2,
|
|
8247
|
+
sourceKind: "record_extension",
|
|
8248
|
+
senderDisplayName,
|
|
8249
|
+
...senderAvatarUrl === "" ? {} : { senderAvatarUrl },
|
|
8250
|
+
title: "",
|
|
8251
|
+
textContent: normalizedText,
|
|
8252
|
+
sendAtMillis,
|
|
8253
|
+
templateKind: 1,
|
|
8254
|
+
displayKind: 0,
|
|
8255
|
+
officialMark: 0,
|
|
8256
|
+
mediaItems: []
|
|
8257
|
+
}
|
|
8258
|
+
};
|
|
8259
|
+
}
|
|
8260
|
+
async resolveLinkMetadata(rawUrl, options = {}) {
|
|
8261
|
+
const url = ensureMetadataUrl(rawUrl);
|
|
8262
|
+
try {
|
|
8263
|
+
return await this.fetchLinkMetadata(url, 0, options.signal);
|
|
8264
|
+
} catch (error) {
|
|
8265
|
+
if (error instanceof ArkmePluginError && (error.code === "link-metadata-url-invalid" || error.code === "link-metadata-url-forbidden")) throw error;
|
|
8266
|
+
return {
|
|
8267
|
+
url: url.toString(),
|
|
8268
|
+
title: fallbackLinkTitle(url),
|
|
8269
|
+
siteName: url.hostname.replace(/^www\./iu, "")
|
|
8270
|
+
};
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
8273
|
+
async fetchLinkMetadata(url, redirectCount, signal) {
|
|
8274
|
+
const controller = new AbortController();
|
|
8275
|
+
const abort = () => {
|
|
8276
|
+
controller.abort(signal?.reason);
|
|
8277
|
+
};
|
|
8278
|
+
if (signal?.aborted === true) abort();
|
|
8279
|
+
else signal?.addEventListener("abort", abort, { once: true });
|
|
8280
|
+
const timeout = setTimeout(() => {
|
|
8281
|
+
controller.abort();
|
|
8282
|
+
}, Math.min(8e3, this.runtime.config.requestTimeoutMs));
|
|
8283
|
+
try {
|
|
8284
|
+
const response = await this.runtime.fetchImpl(url, {
|
|
8285
|
+
method: "GET",
|
|
8286
|
+
redirect: "manual",
|
|
8287
|
+
headers: {
|
|
8288
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
8289
|
+
"Accept-Language": "zh-CN",
|
|
8290
|
+
"User-Agent": "Mozilla/5.0 ArkmeDSHPlugin/0.1 LinkPreview"
|
|
8291
|
+
},
|
|
8292
|
+
signal: controller.signal
|
|
8293
|
+
});
|
|
8294
|
+
if (response.status >= 300 && response.status < 400) {
|
|
8295
|
+
if (redirectCount >= LINK_METADATA_MAX_REDIRECTS) return {
|
|
8296
|
+
url: url.toString(),
|
|
8297
|
+
title: fallbackLinkTitle(url),
|
|
8298
|
+
siteName: url.hostname.replace(/^www\./iu, "")
|
|
8299
|
+
};
|
|
8300
|
+
const location = response.headers.get("location");
|
|
8301
|
+
if (location === null || location.trim() === "") return {
|
|
8302
|
+
url: url.toString(),
|
|
8303
|
+
title: fallbackLinkTitle(url),
|
|
8304
|
+
siteName: url.hostname.replace(/^www\./iu, "")
|
|
8305
|
+
};
|
|
8306
|
+
return await this.fetchLinkMetadata(ensureMetadataUrl(new URL(location, url).toString()), redirectCount + 1, signal);
|
|
8307
|
+
}
|
|
8308
|
+
if (!response.ok) return {
|
|
8309
|
+
url: url.toString(),
|
|
8310
|
+
title: fallbackLinkTitle(url),
|
|
8311
|
+
siteName: url.hostname.replace(/^www\./iu, "")
|
|
8312
|
+
};
|
|
8313
|
+
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
8314
|
+
if (contentType !== "" && !contentType.includes("html") && !contentType.includes("xml") && !contentType.includes("text/")) return {
|
|
8315
|
+
url: url.toString(),
|
|
8316
|
+
title: fallbackLinkTitle(url),
|
|
8317
|
+
siteName: url.hostname.replace(/^www\./iu, "")
|
|
8318
|
+
};
|
|
8319
|
+
return linkMetadataFromHtml(url, await readResponseTextPrefix(response));
|
|
8320
|
+
} finally {
|
|
8321
|
+
clearTimeout(timeout);
|
|
8322
|
+
signal?.removeEventListener("abort", abort);
|
|
8323
|
+
}
|
|
8324
|
+
}
|
|
8325
|
+
async forwardSourceMessages(sourceRef, actionRefs, options = {}) {
|
|
8326
|
+
const session = await this.runtime.requireSession();
|
|
8327
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
8328
|
+
const targetSourceRef = options.targetSourceRef?.trim() || sourceRef;
|
|
8329
|
+
const targetSource = targetSourceRef === sourceRef ? source : await this.source.openSourceRef(targetSourceRef, session.userId);
|
|
8330
|
+
const commentText = options.commentText?.trim() ?? "";
|
|
8331
|
+
if (commentText.length > this.runtime.config.maxTextLength) throw new ArkmePluginError("source-text-invalid", "发送内容超过长度限制", false);
|
|
8332
|
+
if (targetSource.kind !== "private_chat" && targetSource.kind !== "group_chat" && targetSource.kind !== "send_to_self" && targetSource.kind !== "default_category" && targetSource.kind !== "topic") throw new ArkmePluginError("message-actions-source-invalid", "当前数据源暂不支持转发", false, 409);
|
|
8333
|
+
const references = this.forwardReferencesOrderedForCommand(await this.openMessageActionRefs(actionRefs, session.userId, source));
|
|
8334
|
+
if (references.length === 0 || references.length > 100) throw new ArkmePluginError("message-actions-selection-invalid", "请选择 1 至 100 条消息", false);
|
|
8335
|
+
this.validateForwardReferences(references);
|
|
8336
|
+
const recordUid = options.recordUid?.trim() || randomUUID();
|
|
8337
|
+
const title = references.length === 1 ? "转发快记" : `转发 ${String(references.length)} 条快记`;
|
|
8338
|
+
const contentPayload = {
|
|
8339
|
+
payload_kind: 1,
|
|
8340
|
+
schema_version: 1,
|
|
8341
|
+
text_state: 1,
|
|
8342
|
+
forward_records: {
|
|
8343
|
+
render_kind: "forward_records",
|
|
8344
|
+
schema_version: 1,
|
|
8345
|
+
source_type: "quick_records",
|
|
8346
|
+
title: "转发快记",
|
|
8347
|
+
source_record_uids: references.map((reference) => reference.recordUid).filter((recordUid) => recordUid.trim() !== ""),
|
|
8348
|
+
created_at: Date.now(),
|
|
8349
|
+
summary_lines: references.slice(0, 3).map((reference) => `${reference.senderName}: ${reference.textContent.trim() || reference.title.trim() || "非文本内容"}`),
|
|
8350
|
+
items: references.map((reference, index) => this.forwardPayloadItem(reference, index))
|
|
8351
|
+
}
|
|
8352
|
+
};
|
|
8353
|
+
const appendCommentWarning = async (sent) => {
|
|
8354
|
+
if (commentText === "") return sent;
|
|
8355
|
+
try {
|
|
8356
|
+
await this.sendSourceText(targetSourceRef, commentText, { ...options.signal === void 0 ? {} : { signal: options.signal } });
|
|
8357
|
+
return sent;
|
|
8358
|
+
} catch {
|
|
8359
|
+
return {
|
|
8360
|
+
...sent,
|
|
8361
|
+
warningText: "转发已完成,附言发送失败"
|
|
8362
|
+
};
|
|
8363
|
+
}
|
|
8364
|
+
};
|
|
8365
|
+
if (targetSource.kind === "private_chat" || targetSource.kind === "group_chat") {
|
|
8366
|
+
const sendAtMillis = Date.now();
|
|
8367
|
+
const clientRequestId = this.forwardClientRequestId(targetSource.ownerRef, references, sendAtMillis);
|
|
8368
|
+
const sourceRecordUids = references.some((reference) => reference.sourceKind === "chat_relation") ? [] : references.map((reference) => reference.recordUid).filter((recordUid) => recordUid.trim() !== "");
|
|
8369
|
+
const data = await this.runtime.authenticatedChatPost("/api/v1/chats/records/forward", {
|
|
8370
|
+
chat_session_uid: targetSource.ownerRef,
|
|
8371
|
+
client_request_id: clientRequestId,
|
|
8372
|
+
...sourceRecordUids.length === 0 ? {} : { source_record_uids: sourceRecordUids },
|
|
8373
|
+
source_items: references.map((reference) => this.forwardSourceItemBody(reference)),
|
|
8374
|
+
...commentText === "" ? {} : { comment_text: commentText },
|
|
8375
|
+
send_at: sendAtMillis
|
|
8376
|
+
}, session, options.signal);
|
|
8377
|
+
const sequence = numberValue$16(data.seq ?? data.sequence);
|
|
8378
|
+
this.realtime.scheduleChatSessionProjection(targetSource.ownerRef, sequence);
|
|
8379
|
+
return {
|
|
8380
|
+
sourceRef: targetSourceRef,
|
|
8381
|
+
itemUid: stringValue$1(data.record_uid).trim() || recordUid,
|
|
8382
|
+
status: numberValue$16(data.audit_status ?? data.status) || 1,
|
|
8383
|
+
...sequence > 0 ? { sequence } : {},
|
|
8384
|
+
localState: "synced"
|
|
8385
|
+
};
|
|
8386
|
+
}
|
|
8387
|
+
if (targetSource.kind === "topic") {
|
|
8388
|
+
const data = await this.runtime.authenticatedPost("/api/v1/topics/records/create", {
|
|
8389
|
+
topic_uid: targetSource.ownerRef,
|
|
8390
|
+
record_uid: recordUid,
|
|
8391
|
+
template_kind: 1,
|
|
8392
|
+
title: "",
|
|
8393
|
+
text_content: title,
|
|
8394
|
+
content_payload: contentPayload,
|
|
8395
|
+
send_at: Date.now()
|
|
8396
|
+
}, session, options.signal);
|
|
8397
|
+
this.source.invalidateSourceListCache(session.userId, "send_to_self");
|
|
8398
|
+
return await appendCommentWarning({
|
|
8399
|
+
sourceRef: targetSourceRef,
|
|
8400
|
+
itemUid: stringValue$1(data.record_uid).trim() || recordUid,
|
|
8401
|
+
status: numberValue$16(data.status),
|
|
8402
|
+
localState: "synced"
|
|
8403
|
+
});
|
|
8404
|
+
}
|
|
8405
|
+
const data = await this.runtime.authenticatedPost("/api/v1/records/create", {
|
|
8406
|
+
record_uid: recordUid,
|
|
8407
|
+
template_kind: 1,
|
|
8408
|
+
title: "",
|
|
8409
|
+
text_content: title,
|
|
8410
|
+
content_payload: contentPayload,
|
|
8411
|
+
send_at: Date.now()
|
|
8412
|
+
}, session, options.signal);
|
|
8413
|
+
this.source.invalidateSourceListCache(session.userId, "send_to_self");
|
|
8414
|
+
return await appendCommentWarning({
|
|
8415
|
+
sourceRef: targetSourceRef,
|
|
8416
|
+
itemUid: stringValue$1(data.record_uid).trim() || recordUid,
|
|
8417
|
+
status: numberValue$16(data.status),
|
|
8418
|
+
localState: "synced"
|
|
8419
|
+
});
|
|
8420
|
+
}
|
|
7450
8421
|
async sendSourceText(sourceRef, textContent, options = {}) {
|
|
7451
8422
|
const session = await this.runtime.requireSession();
|
|
7452
8423
|
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
@@ -7700,9 +8671,6 @@ var ChatService = class {
|
|
|
7700
8671
|
localState: "synced"
|
|
7701
8672
|
};
|
|
7702
8673
|
}
|
|
7703
|
-
async sendGroupSourceTextWithAiPolish(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, options = {}) {
|
|
7704
|
-
return await this.aiPolish.sendGroupSourceTextWithAiPolish(sourceRef, chatSessionUid, originalText, recordUid, relationUid, session, options);
|
|
7705
|
-
}
|
|
7706
8674
|
async sendSourceRich(sourceRef, input, options = {}) {
|
|
7707
8675
|
if (this.runtime.config.richMediaSendEnabled === false) throw new ArkmePluginError("rich-content-disabled", "富内容发送已被插件配置关闭", false, 403);
|
|
7708
8676
|
const session = await this.runtime.requireSession();
|
|
@@ -7932,12 +8900,6 @@ var ChatService = class {
|
|
|
7932
8900
|
async fetchMedia(mediaRef, range, signal) {
|
|
7933
8901
|
return await this.media.fetchMedia(mediaRef, range, signal);
|
|
7934
8902
|
}
|
|
7935
|
-
currentUserAgentSourceFallback(userId, source) {
|
|
7936
|
-
return this.arko.currentUserAgentSourceFallback(userId, source);
|
|
7937
|
-
}
|
|
7938
|
-
async agentSourceDisplayName(session) {
|
|
7939
|
-
return await this.arko.agentSourceDisplayName(session);
|
|
7940
|
-
}
|
|
7941
8903
|
async sendDirectText(recipientArkmeId, textContent, options = {}) {
|
|
7942
8904
|
const session = await this.runtime.requireSession();
|
|
7943
8905
|
const recipient = recipientArkmeId.trim();
|
|
@@ -8030,6 +8992,7 @@ var ChatService = class {
|
|
|
8030
8992
|
const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
|
|
8031
8993
|
const rawAgentSource = timelineAgentSource(relation, record, payload);
|
|
8032
8994
|
const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
|
|
8995
|
+
const contentBlocks = this.media.richContentBlocks(item, session.userId);
|
|
8033
8996
|
const mentionsViewer = senderUserId !== session.userId && arkmeMentionMetadataMentionsViewer(record, payload, session.userId);
|
|
8034
8997
|
items.push({
|
|
8035
8998
|
itemUid: uid,
|
|
@@ -8048,7 +9011,7 @@ var ChatService = class {
|
|
|
8048
9011
|
...aiPolish === void 0 ? {} : { aiPolish },
|
|
8049
9012
|
...forwardRecords === void 0 ? {} : { forwardRecords },
|
|
8050
9013
|
displayKind: numberValue$16(payload.display_kind),
|
|
8051
|
-
contentBlocks
|
|
9014
|
+
contentBlocks
|
|
8052
9015
|
});
|
|
8053
9016
|
}
|
|
8054
9017
|
return items;
|
|
@@ -8218,11 +9181,31 @@ var ChatService = class {
|
|
|
8218
9181
|
const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
|
|
8219
9182
|
const rawAgentSource = timelineAgentSource(relation, record, payload);
|
|
8220
9183
|
const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
|
|
9184
|
+
const contentBlocks = this.media.richContentBlocks(item, session.userId);
|
|
9185
|
+
const senderName = stringValue$1(relation.display_name_snapshot).trim() || "Arkme用户";
|
|
8221
9186
|
const itemIndex = items.push({
|
|
8222
9187
|
itemUid: uid,
|
|
8223
9188
|
...source.kind !== "group_chat" || relationUid === "" || senderUserId === session.userId ? {} : { messageRef: this.sealMessageRef(session.userId, source.ownerRef, relationUid, signingKey) },
|
|
9189
|
+
...relationUid === "" ? {} : { messageActionRef: this.sealMessageActionRef(this.messageActionRefPayload({
|
|
9190
|
+
sourceKind: "chat_relation",
|
|
9191
|
+
userId: session.userId,
|
|
9192
|
+
sourceOwnerRef: source.ownerRef,
|
|
9193
|
+
chatSessionUid: source.ownerRef,
|
|
9194
|
+
relationUid,
|
|
9195
|
+
recordOwnerUserId: 0,
|
|
9196
|
+
recordUid: uid,
|
|
9197
|
+
senderUserId,
|
|
9198
|
+
senderName,
|
|
9199
|
+
title: stringValue$1(payload.title),
|
|
9200
|
+
textContent: stringValue$1(payload.text_content),
|
|
9201
|
+
sendAtMillis,
|
|
9202
|
+
sourceSequence: numberValue$16(relation.seq),
|
|
9203
|
+
templateKind: numberValue$16(payload.template_kind),
|
|
9204
|
+
displayKind: numberValue$16(payload.display_kind),
|
|
9205
|
+
contentBlocks
|
|
9206
|
+
}), signingKey) },
|
|
8224
9207
|
...senderUserId > 0 ? { memberRef: await this.sealChatMemberRef(session.userId, source.ownerRef, senderUserId) } : {},
|
|
8225
|
-
senderName
|
|
9208
|
+
senderName,
|
|
8226
9209
|
...agentSource === void 0 ? {} : { agentSource },
|
|
8227
9210
|
isMe: senderUserId === session.userId,
|
|
8228
9211
|
sendAtMillis,
|
|
@@ -8238,7 +9221,7 @@ var ChatService = class {
|
|
|
8238
9221
|
updateAtMillis: numberValue$16(payload.update_at ?? record.update_at),
|
|
8239
9222
|
recordDurationMillis: numberValue$16(payload.record_duration_millis),
|
|
8240
9223
|
editDurationMillis: numberValue$16(payload.edit_duration_millis),
|
|
8241
|
-
contentBlocks
|
|
9224
|
+
contentBlocks,
|
|
8242
9225
|
...forwardRecords === void 0 ? {} : { forwardRecords }
|
|
8243
9226
|
}) - 1;
|
|
8244
9227
|
if (senderUserId > 0) senderUserIdByIndex.set(itemIndex, senderUserId);
|
|
@@ -8283,6 +9266,176 @@ var ChatService = class {
|
|
|
8283
9266
|
if (raw.version !== 1 || result.viewerUserId !== expectedViewerUserId || result.chatSessionUid !== expectedChatSessionUid || !Number.isSafeInteger(result.targetUserId) || result.targetUserId <= 0) throw new ArkmePluginError("chat-member-ref-invalid", "聊天成员引用与当前账号或会话不匹配", false, 403);
|
|
8284
9267
|
return result;
|
|
8285
9268
|
}
|
|
9269
|
+
messageActionRefPayload(input) {
|
|
9270
|
+
const contentBlocks = input.contentBlocks ?? [];
|
|
9271
|
+
const fileNames = contentBlocks.filter((block) => block.kind === "file" && block.fileName.trim() !== "").slice(0, 5).map((block) => block.fileName.trim().slice(0, 256));
|
|
9272
|
+
return {
|
|
9273
|
+
version: 1,
|
|
9274
|
+
sourceKind: input.sourceKind,
|
|
9275
|
+
userId: input.userId,
|
|
9276
|
+
sourceOwnerRef: input.sourceOwnerRef,
|
|
9277
|
+
chatSessionUid: input.chatSessionUid,
|
|
9278
|
+
relationUid: input.relationUid,
|
|
9279
|
+
recordOwnerUserId: Math.trunc(input.recordOwnerUserId),
|
|
9280
|
+
recordUid: input.recordUid,
|
|
9281
|
+
senderUserId: Math.trunc(input.senderUserId),
|
|
9282
|
+
senderName: input.senderName.trim() || "Arkme用户",
|
|
9283
|
+
title: input.title.slice(0, 500),
|
|
9284
|
+
textContent: input.textContent.slice(0, this.runtime.config.maxTextLength),
|
|
9285
|
+
sendAtMillis: Math.trunc(input.sendAtMillis),
|
|
9286
|
+
sourceSequence: Math.max(0, Math.trunc(numberValue$16(input.sourceSequence))),
|
|
9287
|
+
templateKind: Math.trunc(input.templateKind),
|
|
9288
|
+
displayKind: Math.trunc(input.displayKind),
|
|
9289
|
+
imageCount: contentBlocks.filter((block) => block.kind === "image").length,
|
|
9290
|
+
voiceCount: contentBlocks.filter((block) => block.kind === "audio").length,
|
|
9291
|
+
fileCount: contentBlocks.filter((block) => block.kind === "file").length,
|
|
9292
|
+
fileNames
|
|
9293
|
+
};
|
|
9294
|
+
}
|
|
9295
|
+
withRecordMessageActionRef(source, item, userId, signingKey) {
|
|
9296
|
+
if (source.kind !== "send_to_self" && source.kind !== "default_category" && source.kind !== "topic" || item.itemUid.trim() === "" || !item.isMe) return item;
|
|
9297
|
+
return {
|
|
9298
|
+
...item,
|
|
9299
|
+
messageActionRef: this.sealMessageActionRef(this.messageActionRefPayload({
|
|
9300
|
+
sourceKind: "record",
|
|
9301
|
+
userId,
|
|
9302
|
+
sourceOwnerRef: source.ownerRef,
|
|
9303
|
+
chatSessionUid: "",
|
|
9304
|
+
relationUid: "",
|
|
9305
|
+
recordOwnerUserId: userId,
|
|
9306
|
+
recordUid: item.itemUid,
|
|
9307
|
+
senderUserId: userId,
|
|
9308
|
+
senderName: item.senderName,
|
|
9309
|
+
title: item.title,
|
|
9310
|
+
textContent: item.textContent,
|
|
9311
|
+
sendAtMillis: item.sendAtMillis,
|
|
9312
|
+
templateKind: item.templateKind ?? 1,
|
|
9313
|
+
displayKind: item.displayKind ?? 0,
|
|
9314
|
+
contentBlocks: item.contentBlocks
|
|
9315
|
+
}), signingKey)
|
|
9316
|
+
};
|
|
9317
|
+
}
|
|
9318
|
+
sealMessageActionRef(payload, signingKey) {
|
|
9319
|
+
const encoded = encodeOpaqueJson$5(payload);
|
|
9320
|
+
return `arkme-message-action-v1.${encoded}.${createHmac("sha256", signingKey).update(encoded).digest("base64url")}`;
|
|
9321
|
+
}
|
|
9322
|
+
async openMessageActionRefs(actionRefs, expectedUserId, source) {
|
|
9323
|
+
const normalizedRefs = actionRefs.map((value) => value.trim()).filter((value) => value !== "");
|
|
9324
|
+
const uniqueRefs = new Set(normalizedRefs);
|
|
9325
|
+
if (normalizedRefs.length !== uniqueRefs.size) throw new ArkmePluginError("message-actions-selection-invalid", "不能选择重复消息", false);
|
|
9326
|
+
const references = [];
|
|
9327
|
+
const relationIdentities = /* @__PURE__ */ new Set();
|
|
9328
|
+
for (const actionRef of normalizedRefs) {
|
|
9329
|
+
const reference = await this.openMessageActionRef(actionRef, expectedUserId, source);
|
|
9330
|
+
const relationIdentity = reference.sourceKind === "chat_relation" ? `chat_relation\u0000${reference.chatSessionUid}\u0000${reference.relationUid}` : `record\u0000${String(reference.recordOwnerUserId)}\u0000${reference.recordUid}`;
|
|
9331
|
+
if (!relationIdentities.add(relationIdentity)) throw new ArkmePluginError("message-actions-selection-invalid", "不能选择重复消息", false);
|
|
9332
|
+
references.push(reference);
|
|
9333
|
+
}
|
|
9334
|
+
return references;
|
|
9335
|
+
}
|
|
9336
|
+
async openMessageActionRef(actionRef, expectedUserId, source) {
|
|
9337
|
+
const parts = actionRef.trim().split(".");
|
|
9338
|
+
if (parts.length !== 3 || parts[0] !== "arkme-message-action-v1") throw new ArkmePluginError("message-action-ref-invalid", "消息操作引用无效,请刷新后重试", false);
|
|
9339
|
+
const payload = parts[1] ?? "";
|
|
9340
|
+
const supplied = Buffer.from(parts[2] ?? "", "base64url");
|
|
9341
|
+
const expected = createHmac("sha256", await this.runtime.stateStore.uniqueCode()).update(payload).digest();
|
|
9342
|
+
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new ArkmePluginError("message-action-ref-invalid", "消息操作引用无效,请刷新后重试", false);
|
|
9343
|
+
let parsed;
|
|
9344
|
+
try {
|
|
9345
|
+
parsed = objectValue$1(decodeOpaqueJson$5(payload));
|
|
9346
|
+
} catch (error) {
|
|
9347
|
+
throw new ArkmePluginError("message-action-ref-invalid", "消息操作引用无效,请刷新后重试", false, 400, { cause: error });
|
|
9348
|
+
}
|
|
9349
|
+
const fileNames = listValue$16(parsed.fileNames).map((value) => stringValue$1(value).trim()).filter((value) => value !== "").slice(0, 5);
|
|
9350
|
+
const result = {
|
|
9351
|
+
version: 1,
|
|
9352
|
+
sourceKind: stringValue$1(parsed.sourceKind).trim() === "record" ? "record" : "chat_relation",
|
|
9353
|
+
userId: Math.trunc(numberValue$16(parsed.userId)),
|
|
9354
|
+
sourceOwnerRef: stringValue$1(parsed.sourceOwnerRef).trim(),
|
|
9355
|
+
chatSessionUid: stringValue$1(parsed.chatSessionUid).trim(),
|
|
9356
|
+
relationUid: stringValue$1(parsed.relationUid).trim(),
|
|
9357
|
+
recordOwnerUserId: Math.trunc(numberValue$16(parsed.recordOwnerUserId)),
|
|
9358
|
+
recordUid: stringValue$1(parsed.recordUid).trim(),
|
|
9359
|
+
senderUserId: Math.trunc(numberValue$16(parsed.senderUserId)),
|
|
9360
|
+
senderName: stringValue$1(parsed.senderName).trim() || "Arkme用户",
|
|
9361
|
+
title: stringValue$1(parsed.title),
|
|
9362
|
+
textContent: stringValue$1(parsed.textContent),
|
|
9363
|
+
sendAtMillis: Math.trunc(numberValue$16(parsed.sendAtMillis)),
|
|
9364
|
+
sourceSequence: Math.max(0, Math.trunc(numberValue$16(parsed.sourceSequence))),
|
|
9365
|
+
templateKind: Math.trunc(numberValue$16(parsed.templateKind)),
|
|
9366
|
+
displayKind: Math.trunc(numberValue$16(parsed.displayKind)),
|
|
9367
|
+
imageCount: Math.max(0, Math.trunc(numberValue$16(parsed.imageCount))),
|
|
9368
|
+
voiceCount: Math.max(0, Math.trunc(numberValue$16(parsed.voiceCount))),
|
|
9369
|
+
fileCount: Math.max(0, Math.trunc(numberValue$16(parsed.fileCount))),
|
|
9370
|
+
fileNames
|
|
9371
|
+
};
|
|
9372
|
+
const matchesChatSource = result.sourceKind === "chat_relation" && (source.kind === "private_chat" || source.kind === "group_chat") && result.sourceOwnerRef === source.ownerRef && result.chatSessionUid === source.ownerRef && result.relationUid !== "" && result.recordUid !== "";
|
|
9373
|
+
const matchesRecordSource = result.sourceKind === "record" && (source.kind === "send_to_self" || source.kind === "default_category" || source.kind === "topic") && result.sourceOwnerRef === source.ownerRef && result.chatSessionUid === "" && result.relationUid === "" && result.recordOwnerUserId === expectedUserId && result.recordUid !== "";
|
|
9374
|
+
if (parsed.version !== 1 || result.userId !== expectedUserId || !matchesChatSource && !matchesRecordSource) throw new ArkmePluginError("message-action-ref-invalid", "消息操作引用与当前账号或会话不匹配,请刷新后重试", false, 403);
|
|
9375
|
+
return result;
|
|
9376
|
+
}
|
|
9377
|
+
forwardPayloadItem(reference, itemOrder) {
|
|
9378
|
+
return {
|
|
9379
|
+
item_order: itemOrder,
|
|
9380
|
+
source_kind: reference.sourceKind,
|
|
9381
|
+
record_uid: reference.recordUid,
|
|
9382
|
+
source_type: reference.sourceKind === "chat_relation" ? "chat_record" : "record",
|
|
9383
|
+
...reference.recordOwnerUserId > 0 ? { owner_id: reference.recordOwnerUserId } : {},
|
|
9384
|
+
...reference.relationUid === "" ? {} : { source_rel_uid: reference.relationUid },
|
|
9385
|
+
...reference.chatSessionUid === "" ? {} : { source_chat_session_uid: reference.chatSessionUid },
|
|
9386
|
+
...reference.sourceSequence > 0 ? { source_seq: reference.sourceSequence } : {},
|
|
9387
|
+
source_sender_user_id: reference.senderUserId,
|
|
9388
|
+
source_display_name: reference.senderName,
|
|
9389
|
+
owner_name: reference.senderName,
|
|
9390
|
+
send_at: reference.sendAtMillis,
|
|
9391
|
+
title: reference.title,
|
|
9392
|
+
text: reference.textContent,
|
|
9393
|
+
text_preview: reference.textContent.trim().slice(0, 500),
|
|
9394
|
+
template_kind: reference.templateKind,
|
|
9395
|
+
display_kind: reference.displayKind,
|
|
9396
|
+
image_count: reference.imageCount,
|
|
9397
|
+
voice_count: reference.voiceCount,
|
|
9398
|
+
file_count: reference.fileCount,
|
|
9399
|
+
file_names: reference.fileNames
|
|
9400
|
+
};
|
|
9401
|
+
}
|
|
9402
|
+
forwardClientRequestId(targetChatSessionUid, references, sendAtMillis) {
|
|
9403
|
+
const sourceIdentity = references.map((reference) => reference.sourceKind === "chat_relation" ? `chat_record:${reference.chatSessionUid}:${reference.relationUid}` : reference.recordUid).join("|");
|
|
9404
|
+
const sourceHash = createHash("sha256").update(sourceIdentity).digest("hex").slice(0, 16);
|
|
9405
|
+
return `forward_${targetChatSessionUid}_${String(Math.trunc(sendAtMillis))}_${sourceHash}`;
|
|
9406
|
+
}
|
|
9407
|
+
forwardReferencesOrderedForCommand(references) {
|
|
9408
|
+
if (references.length < 2 || !references.every((reference) => reference.sourceKind === "chat_relation" && reference.sourceSequence > 0 && reference.chatSessionUid.trim() !== "")) return references;
|
|
9409
|
+
const sourceSessionUid = references[0]?.chatSessionUid.trim() ?? "";
|
|
9410
|
+
if (sourceSessionUid === "" || !references.every((reference) => reference.chatSessionUid.trim() === sourceSessionUid)) return references;
|
|
9411
|
+
const ordered = references.map((reference, index) => ({
|
|
9412
|
+
reference,
|
|
9413
|
+
index
|
|
9414
|
+
})).sort((left, right) => {
|
|
9415
|
+
const sequenceCompare = left.reference.sourceSequence - right.reference.sourceSequence;
|
|
9416
|
+
return sequenceCompare === 0 ? left.index - right.index : sequenceCompare;
|
|
9417
|
+
}).map((item) => item.reference);
|
|
9418
|
+
return ordered.every((reference, index) => reference === references[index]) ? references : ordered;
|
|
9419
|
+
}
|
|
9420
|
+
validateForwardReferences(references) {
|
|
9421
|
+
const sourceTypes = new Set(references.map((reference) => reference.sourceKind));
|
|
9422
|
+
if (sourceTypes.size > 1) throw new ArkmePluginError("message-actions-selection-invalid", "暂不支持混合转发不同类型的消息", false);
|
|
9423
|
+
if (!sourceTypes.has("chat_relation")) return;
|
|
9424
|
+
if (new Set(references.map((reference) => reference.chatSessionUid.trim()).filter((value) => value !== "")).size > 1) throw new ArkmePluginError("message-actions-selection-invalid", "暂不支持跨会话转发聊天消息", false);
|
|
9425
|
+
}
|
|
9426
|
+
forwardSourceItemBody(reference) {
|
|
9427
|
+
if (reference.sourceKind === "chat_relation") return {
|
|
9428
|
+
source_type: "chat_record",
|
|
9429
|
+
record_uid: reference.recordUid,
|
|
9430
|
+
source_chat_session_uid: reference.chatSessionUid,
|
|
9431
|
+
source_rel_uid: reference.relationUid,
|
|
9432
|
+
...reference.sourceSequence > 0 ? { source_seq: reference.sourceSequence } : {}
|
|
9433
|
+
};
|
|
9434
|
+
return {
|
|
9435
|
+
source_type: "record",
|
|
9436
|
+
record_uid: reference.recordUid
|
|
9437
|
+
};
|
|
9438
|
+
}
|
|
8286
9439
|
sealMessageRef(userId, chatSessionUid, relationUid, signingKey) {
|
|
8287
9440
|
const payload = encodeOpaqueJson$5({
|
|
8288
9441
|
version: 1,
|
|
@@ -16076,6 +17229,7 @@ var ArkmeService = class {
|
|
|
16076
17229
|
fetchImpl;
|
|
16077
17230
|
pendingSessionStore;
|
|
16078
17231
|
runtime;
|
|
17232
|
+
billingGateway;
|
|
16079
17233
|
aiVideo;
|
|
16080
17234
|
arrangement;
|
|
16081
17235
|
calendar;
|
|
@@ -16106,13 +17260,14 @@ var ArkmeService = class {
|
|
|
16106
17260
|
unmarkedSpeaker;
|
|
16107
17261
|
voiceprint;
|
|
16108
17262
|
worldVoiceprintInviteVariantIndex = 0;
|
|
16109
|
-
constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker()) {
|
|
17263
|
+
constructor(config, sessionStore, stateStore, fetchImpl = fetch, pendingSessionStore, outgoingCallBroker = new ArkmeOutgoingCallBroker(), billingGateway) {
|
|
16110
17264
|
this.config = config;
|
|
16111
17265
|
this.sessionStore = sessionStore;
|
|
16112
17266
|
this.stateStore = stateStore;
|
|
16113
17267
|
this.fetchImpl = fetchImpl;
|
|
16114
17268
|
this.pendingSessionStore = pendingSessionStore;
|
|
16115
17269
|
this.runtime = new ServiceRuntime(config, sessionStore, stateStore, fetchImpl, pendingSessionStore);
|
|
17270
|
+
this.billingGateway = billingGateway ?? new HttpArkmeBillingGateway(this.runtime);
|
|
16116
17271
|
this.privacy = new ArkmePrivacyVisibilityService(this.runtime);
|
|
16117
17272
|
this.aiVideo = new AiVideoService(this.runtime);
|
|
16118
17273
|
this.arrangement = new ArrangementService(this.runtime);
|
|
@@ -16179,6 +17334,9 @@ var ArkmeService = class {
|
|
|
16179
17334
|
chatRealtimeState() {
|
|
16180
17335
|
return this.realtime.chatRealtimeState();
|
|
16181
17336
|
}
|
|
17337
|
+
async resolveManagedAccessCredential() {
|
|
17338
|
+
return await resolveManagedAccessCredential(this.runtime);
|
|
17339
|
+
}
|
|
16182
17340
|
subscribeChatRealtime(listener) {
|
|
16183
17341
|
return this.realtime.subscribeChatRealtime(listener);
|
|
16184
17342
|
}
|
|
@@ -16262,11 +17420,32 @@ var ArkmeService = class {
|
|
|
16262
17420
|
captchaId: this.config.geetestCaptchaId,
|
|
16263
17421
|
environment: this.config.environment,
|
|
16264
17422
|
testLoginEnabled: this.config.environment === "test",
|
|
17423
|
+
jiwoScanLoginEnabled: jiwoScanLoginAvailable(this.config),
|
|
16265
17424
|
callAssetBasePath: `${this.config.routePath}/call`,
|
|
16266
17425
|
voiceprintEnrollmentPath: `${this.config.routePath}/voiceprint/enroll`,
|
|
16267
17426
|
shareWebsite: this.config.shareWebsite ?? "https://app.arkme.ai"
|
|
16268
17427
|
};
|
|
16269
17428
|
}
|
|
17429
|
+
async billingQuota(signal) {
|
|
17430
|
+
return await this.callBillingGateway(() => this.billingGateway.quota(signal));
|
|
17431
|
+
}
|
|
17432
|
+
async billingProducts(signal) {
|
|
17433
|
+
return await this.callBillingGateway(() => this.billingGateway.products(signal));
|
|
17434
|
+
}
|
|
17435
|
+
async createBillingOrder(input, signal) {
|
|
17436
|
+
return await this.callBillingGateway(() => this.billingGateway.createOrder(input, signal));
|
|
17437
|
+
}
|
|
17438
|
+
async billingOrderStatus(orderId, signal) {
|
|
17439
|
+
return await this.callBillingGateway(() => this.billingGateway.orderStatus(orderId, signal));
|
|
17440
|
+
}
|
|
17441
|
+
async callBillingGateway(operation) {
|
|
17442
|
+
try {
|
|
17443
|
+
return await operation();
|
|
17444
|
+
} catch (error) {
|
|
17445
|
+
if (error instanceof ArkmeBillingUnavailableError) throw new ArkmePluginError("billing-unavailable", error.message, true, 503, { cause: error });
|
|
17446
|
+
throw error;
|
|
17447
|
+
}
|
|
17448
|
+
}
|
|
16270
17449
|
providerCapabilities() {
|
|
16271
17450
|
return {
|
|
16272
17451
|
contractVersion: 1,
|
|
@@ -16680,6 +17859,21 @@ var ArkmeService = class {
|
|
|
16680
17859
|
async reportMessage(messageRef, reportType, options = {}) {
|
|
16681
17860
|
return await this.chat.reportMessage(messageRef, reportType, options);
|
|
16682
17861
|
}
|
|
17862
|
+
async copySourceMessageLink(sourceRef, actionRefs, options = {}) {
|
|
17863
|
+
return await this.chat.copySourceMessageLink(sourceRef, actionRefs, options);
|
|
17864
|
+
}
|
|
17865
|
+
async resolveMessageCopyLink(sid, options = {}) {
|
|
17866
|
+
return await this.chat.resolveMessageCopyLink(sid, options);
|
|
17867
|
+
}
|
|
17868
|
+
async extendMessageCopyLink(sid, itemIndex, textContent, recordUid, options = {}) {
|
|
17869
|
+
return await this.chat.extendMessageCopyLink(sid, itemIndex, textContent, recordUid, options);
|
|
17870
|
+
}
|
|
17871
|
+
async resolveLinkMetadata(rawUrl, options = {}) {
|
|
17872
|
+
return await this.chat.resolveLinkMetadata(rawUrl, options);
|
|
17873
|
+
}
|
|
17874
|
+
async forwardSourceMessages(sourceRef, actionRefs, options = {}) {
|
|
17875
|
+
return await this.chat.forwardSourceMessages(sourceRef, actionRefs, options);
|
|
17876
|
+
}
|
|
16683
17877
|
async sendSourceText(sourceRef, textContent, options = {}) {
|
|
16684
17878
|
return await this.chat.sendSourceText(sourceRef, textContent, options);
|
|
16685
17879
|
}
|
|
@@ -16762,6 +17956,15 @@ var ArkmeService = class {
|
|
|
16762
17956
|
async pollWechatLogin(attemptId) {
|
|
16763
17957
|
return await this.auth.pollWechatLogin(attemptId);
|
|
16764
17958
|
}
|
|
17959
|
+
async beginJiwoLogin() {
|
|
17960
|
+
return await this.auth.beginJiwoLogin();
|
|
17961
|
+
}
|
|
17962
|
+
async pollJiwoLogin(attemptId) {
|
|
17963
|
+
return await this.auth.pollJiwoLogin(attemptId);
|
|
17964
|
+
}
|
|
17965
|
+
async cancelJiwoLogin(attemptId) {
|
|
17966
|
+
return await this.auth.cancelJiwoLogin(attemptId);
|
|
17967
|
+
}
|
|
16765
17968
|
async testLogin(userId) {
|
|
16766
17969
|
return await this.auth.testLogin(userId);
|
|
16767
17970
|
}
|
|
@@ -17918,6 +19121,7 @@ function persistentClientFactory(requireModule, spec) {
|
|
|
17918
19121
|
return {
|
|
17919
19122
|
name: `arkme-extension-client:${spec.extensionId}`,
|
|
17920
19123
|
async apply(ctx) {
|
|
19124
|
+
if ((typeof document.location?.search === "string" ? document.location.search : "").split(/[?&]/).includes("arkme-harness-embed=1")) return;
|
|
17921
19125
|
const styles = new Styles();
|
|
17922
19126
|
const taggedConsole = {
|
|
17923
19127
|
...console,
|
|
@@ -18032,7 +19236,7 @@ function renderPersistentClientBundle(packageName, spec) {
|
|
|
18032
19236
|
const renderedSpec = {
|
|
18033
19237
|
...spec,
|
|
18034
19238
|
clientContentDigest: spec.clientContentDigest ?? arkmeClientContentDigest(spec.code),
|
|
18035
|
-
wrapperVersion:
|
|
19239
|
+
wrapperVersion: 4
|
|
18036
19240
|
};
|
|
18037
19241
|
return [
|
|
18038
19242
|
`window.__ModuleLoader__.load({ id: ${JSON.stringify(packageName)}, factory: (require) => {`,
|
|
@@ -18050,13 +19254,16 @@ function renderArkmeBundleClientBundle(packageName, spec) {
|
|
|
18050
19254
|
identityKey: "packageName"
|
|
18051
19255
|
});
|
|
18052
19256
|
}
|
|
18053
|
-
function
|
|
19257
|
+
function persistentExtensionPackageIdentity(extensionId) {
|
|
18054
19258
|
const suffix = createHash("sha256").update(extensionId).digest("hex").slice(0, 16);
|
|
18055
19259
|
return {
|
|
18056
19260
|
packageName: `@arkme-local/ext-${suffix}`,
|
|
18057
19261
|
entryId: `arkme-extension-${suffix}`
|
|
18058
19262
|
};
|
|
18059
19263
|
}
|
|
19264
|
+
function persistentExtensionBundleDirectory(profileDirectory, extensionId, version) {
|
|
19265
|
+
return join(profileDirectory, "arkme-extensions", createHash("sha256").update(extensionId).digest("hex").slice(0, 16), version);
|
|
19266
|
+
}
|
|
18060
19267
|
function writeSecure(path, content) {
|
|
18061
19268
|
writeFileSync(path, content, {
|
|
18062
19269
|
encoding: "utf8",
|
|
@@ -18153,9 +19360,22 @@ function readPersistentExtensionActivation(installationUrl) {
|
|
|
18153
19360
|
}
|
|
18154
19361
|
return state;
|
|
18155
19362
|
}
|
|
19363
|
+
function repairPersistentExtensionInstallation(input) {
|
|
19364
|
+
const installationPath = join(input.bundleDirectory, "installation.json");
|
|
19365
|
+
const installation = JSON.parse(readFileSync(installationPath, "utf8"));
|
|
19366
|
+
if (installation.format_version !== 1 || installation.extension_id !== input.extensionId || installation.version !== input.version || typeof installation.artifact_path !== "string" || installation.artifact_path.trim() === "" || installation.artifact_sha256 !== input.artifactSha256 || installation.manifest_sha256 !== input.manifestSha256 || typeof installation.signature !== "string" || installation.signature.trim() === "" || typeof installation.signing_key_id !== "string" || installation.signing_key_id.trim() === "" || typeof installation.trusted_public_key !== "string" || installation.trusted_public_key.trim() === "" || !Number.isSafeInteger(installation.published_at) || (installation.published_at ?? -1) < 0) throw new Error("Arkme persistent extension installation identity mismatch");
|
|
19367
|
+
const manifest = JSON.parse(readFileSync(join(input.bundleDirectory, "package.json"), "utf8"));
|
|
19368
|
+
if (manifest.name !== input.packageName || manifest.version !== input.version) throw new Error("Arkme persistent extension package identity mismatch");
|
|
19369
|
+
if (installation.artifact_path === input.artifactPath) return false;
|
|
19370
|
+
replaceSecure(installationPath, `${JSON.stringify({
|
|
19371
|
+
...installation,
|
|
19372
|
+
artifact_path: input.artifactPath
|
|
19373
|
+
}, void 0, 2)}\n`);
|
|
19374
|
+
return true;
|
|
19375
|
+
}
|
|
18156
19376
|
function materializePersistentExtensionBundle(input) {
|
|
18157
|
-
const { packageName, entryId } =
|
|
18158
|
-
const bundleDirectory =
|
|
19377
|
+
const { packageName, entryId } = persistentExtensionPackageIdentity(input.resolution.extension_id);
|
|
19378
|
+
const bundleDirectory = persistentExtensionBundleDirectory(input.profileDirectory, input.resolution.extension_id, input.resolution.version);
|
|
18159
19379
|
const installation = {
|
|
18160
19380
|
format_version: 1,
|
|
18161
19381
|
extension_id: input.resolution.extension_id,
|
|
@@ -18180,7 +19400,7 @@ function materializePersistentExtensionBundle(input) {
|
|
|
18180
19400
|
try {
|
|
18181
19401
|
const manifest = JSON.parse(readFileSync(join(bundleDirectory, "package.json"), "utf8"));
|
|
18182
19402
|
const clientBundle = input.clientCode === void 0 ? void 0 : readFileSync(join(bundleDirectory, "lib", "client.js"), "utf8");
|
|
18183
|
-
const clientReady = input.clientCode === void 0 || manifest.exports?.["./client"] === "./lib/client.js" && manifest.dsh?.client?.inject?.length === 0 && clientBundle?.includes("extensions.persistent.invoke") === true && clientBundle.includes("extensions.persistent.client-state") && clientBundle.includes(`\"wrapperVersion\":${String(
|
|
19403
|
+
const clientReady = input.clientCode === void 0 || manifest.exports?.["./client"] === "./lib/client.js" && manifest.dsh?.client?.inject?.length === 0 && clientBundle?.includes("extensions.persistent.invoke") === true && clientBundle.includes("extensions.persistent.client-state") && clientBundle.includes(`\"wrapperVersion\":${String(4)}`);
|
|
18184
19404
|
if (manifest.exports?.["."] === "./lib/index.js" && manifest.exports?.["./package.json"] === "./package.json" && clientReady) {
|
|
18185
19405
|
if (!existsSync(activationPath)) writeSecure(activationPath, activationText(input.resolution.extension_id, true));
|
|
18186
19406
|
return {
|
|
@@ -18266,7 +19486,21 @@ const VM_TIMEOUT_MS = 5e3;
|
|
|
18266
19486
|
function readInstallation(url) {
|
|
18267
19487
|
const value = JSON.parse(readFileSync(url, "utf8"));
|
|
18268
19488
|
if (value.format_version !== 1 || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value.extension_id) || value.version.trim() === "" || value.artifact_path.trim() === "" || value.trusted_public_key.trim() === "") throw new Error("Arkme persistent extension installation metadata is invalid");
|
|
18269
|
-
return
|
|
19489
|
+
return {
|
|
19490
|
+
...value,
|
|
19491
|
+
artifact_path: resolveRuntimeArtifactPath(value, url)
|
|
19492
|
+
};
|
|
19493
|
+
}
|
|
19494
|
+
function resolveRuntimeArtifactPath(installation, installationUrl) {
|
|
19495
|
+
const dshHome = process.env.DSH_HOME?.trim();
|
|
19496
|
+
const filename = basename(installation.artifact_path);
|
|
19497
|
+
if (dshHome !== void 0 && dshHome !== "" && filename !== "" && filename !== "." && filename !== "..") try {
|
|
19498
|
+
const root = realpathSync(join(dshHome, "arkme-self", "extensions"));
|
|
19499
|
+
const candidate = realpathSync(join(root, installation.extension_id, installation.version, filename));
|
|
19500
|
+
const fromRoot = relative(root, candidate);
|
|
19501
|
+
if (fromRoot !== "" && fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`) && !isAbsolute(fromRoot) && sha256Hex(readFileSync(candidate)) === installation.artifact_sha256) return candidate;
|
|
19502
|
+
} catch {}
|
|
19503
|
+
return isAbsolute(installation.artifact_path) ? installation.artifact_path : resolve(dirname(fileURLToPath(installationUrl)), installation.artifact_path);
|
|
18270
19504
|
}
|
|
18271
19505
|
function jsonValue(value) {
|
|
18272
19506
|
if (value === void 0) return null;
|
|
@@ -18534,4 +19768,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
|
|
|
18534
19768
|
return jsonValue(await handler(jsonValue(args)));
|
|
18535
19769
|
}
|
|
18536
19770
|
//#endregion
|
|
18537
|
-
export {
|
|
19771
|
+
export { isArkmeBotAvatarRef as $, inspectNativeBundleArtifactV3 as A, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, bundleSha256 as D, arkmeSandboxEntryId as E, assertExtensionArtifactSize as F, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as J, ARKME_DEFAULT_SHARE_WEBSITE as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, readNativeBundleTarballV3 as N, canonicalBundleJson as O, renderArkmeSandboxHostEntry as P, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, ArkmeBundleArtifactError as T, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as X, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as Y, ARKME_WORLD_PUBLISH_MAX_IMAGES as Z, arkmeClientContentDigest as _, persistentArkmeExtensionActive as a, verifyExtensionResolutionSignature as b, persistentExtensionBundleDirectory as c, readPersistentExtensionActivation as d, ArkmePluginError as et, refreshPersistentClientWrapper as f, activeProfileExtensionOwnerConflicts as g, renderArkmeBundleClientBundle as h, invokePersistentArkmeExtension as i, packBundleFiles as j, inspectBundleArtifact as k, persistentExtensionPackageIdentity as l, writePersistentExtensionActivation as m, applyPersistentArkmeHostExtension as n, persistentArkmeExtensionRuntimeState as o, repairPersistentExtensionInstallation as p, ARKME_PROVIDER_CONTRACT_VERSION as q, deactivatePersistentArkmeExtension as r, materializePersistentExtensionBundle as s, activatePersistentArkmeExtension as t, ArkmeOutgoingCallError as tt, quarantinePersistentExtension as u, arkmeClientContentDigestFromSource as v, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, verifyNativeBundleResolutionSignature as x, verifyBundleResolutionSignature as y, ARKME_EXTENSION_FORMAT as z };
|