@senguoyun/dsh-arkme 0.1.54 → 0.1.56

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.
Files changed (73) hide show
  1. package/assets/licenses/MaterialIcons_LICENSE.txt +393 -0
  2. package/docs/desktop-directory-badge.md +14 -0
  3. package/docs/dsh-remote-liveness.md +22 -0
  4. package/docs/vip-badge.md +17 -0
  5. package/lib/client.js +48116 -47530
  6. package/lib/client.js.map +1 -1
  7. package/lib/harness-model-client.js +10 -1
  8. package/lib/index.js +378 -31
  9. package/lib/persistent-extension.js +1 -1
  10. package/lib/{persistent-runtime-CWpiUoAR.js → persistent-runtime-Bsk6in2a.js} +432 -131
  11. package/lib/sdk.js +10 -1
  12. package/lib/types/arkme-service.d.ts +2 -1
  13. package/lib/types/arkme-service.d.ts.map +1 -1
  14. package/lib/types/chat-sender-display.d.ts +36 -0
  15. package/lib/types/chat-sender-display.d.ts.map +1 -0
  16. package/lib/types/client/ArkmeArkoSurface.d.ts.map +1 -1
  17. package/lib/types/client/ArkmeAvatar.d.ts +2 -1
  18. package/lib/types/client/ArkmeAvatar.d.ts.map +1 -1
  19. package/lib/types/client/ArkmeBotIdentity.d.ts +14 -0
  20. package/lib/types/client/ArkmeBotIdentity.d.ts.map +1 -0
  21. package/lib/types/client/ArkmeDocumentComposerInput.d.ts +1 -0
  22. package/lib/types/client/ArkmeDocumentComposerInput.d.ts.map +1 -1
  23. package/lib/types/client/ArkmeFileViewer.d.ts +2 -2
  24. package/lib/types/client/ArkmeFileViewer.d.ts.map +1 -1
  25. package/lib/types/client/ArkmeLivePhotoBadge.d.ts +5 -0
  26. package/lib/types/client/ArkmeLivePhotoBadge.d.ts.map +1 -0
  27. package/lib/types/client/ArkmeMembershipBadge.d.ts +6 -0
  28. package/lib/types/client/ArkmeMembershipBadge.d.ts.map +1 -0
  29. package/lib/types/client/ArkmeNoteDetails.d.ts.map +1 -1
  30. package/lib/types/client/ArkmePersistentShell.d.ts.map +1 -1
  31. package/lib/types/client/ArkmeRichContent.d.ts.map +1 -1
  32. package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
  33. package/lib/types/client/chat-directory-store.d.ts.map +1 -1
  34. package/lib/types/client/desktop-notification-runtime.d.ts +1 -0
  35. package/lib/types/client/desktop-notification-runtime.d.ts.map +1 -1
  36. package/lib/types/client/directory-badge-runtime.d.ts +3 -0
  37. package/lib/types/client/directory-badge-runtime.d.ts.map +1 -0
  38. package/lib/types/client/live-photo-playback.d.ts +8 -0
  39. package/lib/types/client/live-photo-playback.d.ts.map +1 -0
  40. package/lib/types/client/timeline-media.d.ts.map +1 -1
  41. package/lib/types/client/ui-controller.d.ts +3 -2
  42. package/lib/types/client/ui-controller.d.ts.map +1 -1
  43. package/lib/types/dsh-remote/gateway-api.d.ts +28 -0
  44. package/lib/types/dsh-remote/gateway-api.d.ts.map +1 -0
  45. package/lib/types/host-api.d.ts +2 -1
  46. package/lib/types/host-api.d.ts.map +1 -1
  47. package/lib/types/index.d.ts.map +1 -1
  48. package/lib/types/peer-membership.d.ts +3 -0
  49. package/lib/types/peer-membership.d.ts.map +1 -0
  50. package/lib/types/record-owner-id.d.ts +8 -0
  51. package/lib/types/record-owner-id.d.ts.map +1 -0
  52. package/lib/types/sdk/index.d.ts +2 -1
  53. package/lib/types/sdk/index.d.ts.map +1 -1
  54. package/lib/types/services/bot-service.d.ts +5 -1
  55. package/lib/types/services/bot-service.d.ts.map +1 -1
  56. package/lib/types/services/chat-realtime-service.d.ts +1 -1
  57. package/lib/types/services/chat-realtime-service.d.ts.map +1 -1
  58. package/lib/types/services/chat-sender-display-reader.d.ts +14 -0
  59. package/lib/types/services/chat-sender-display-reader.d.ts.map +1 -0
  60. package/lib/types/services/chat-service.d.ts +9 -3
  61. package/lib/types/services/chat-service.d.ts.map +1 -1
  62. package/lib/types/services/dynamic-photo.d.ts +8 -0
  63. package/lib/types/services/dynamic-photo.d.ts.map +1 -0
  64. package/lib/types/services/media-service.d.ts.map +1 -1
  65. package/lib/types/services/related-quick-note-service.d.ts +2 -1
  66. package/lib/types/services/related-quick-note-service.d.ts.map +1 -1
  67. package/lib/types/services/search-service.d.ts.map +1 -1
  68. package/lib/types/services/service.d.ts +3 -1
  69. package/lib/types/services/service.d.ts.map +1 -1
  70. package/lib/types/services/source-service.d.ts.map +1 -1
  71. package/lib/types/types.d.ts +18 -4
  72. package/lib/types/types.d.ts.map +1 -1
  73. package/package.json +6 -5
@@ -269,6 +269,31 @@ function createDesktopLifecycleReader(fetchImpl) {
269
269
  };
270
270
  }
271
271
  //#endregion
272
+ //#region src/record-owner-id.ts
273
+ function recordOwnerId(value) {
274
+ if (typeof value === "number") return Number.isSafeInteger(value) && value > 0 ? value : 0;
275
+ if (typeof value !== "string" || !/^[1-9]\d*$/.test(value)) return 0;
276
+ const id = BigInt(value);
277
+ if (id > 9223372036854775807n) return 0;
278
+ return id <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(id) : value;
279
+ }
280
+ /** Node 22 preserves the original JSON token in reviver context. */
281
+ function parseOwnerJson(text) {
282
+ const parse = JSON.parse;
283
+ return parse(text, (key, value, context) => {
284
+ if (/(?:^|_)(?:owner|user)_id$/.test(key) && typeof value === "number" && !Number.isSafeInteger(value) && context.source !== void 0 && /^[1-9]\d*$/.test(context.source)) return context.source;
285
+ return value;
286
+ });
287
+ }
288
+ /** Keep the owner API's numeric wire contract without rounding decimal identifiers. */
289
+ function stringifyOwnerJson(value) {
290
+ const json = JSON;
291
+ return JSON.stringify(value, (key, item) => {
292
+ if (/(?:^|_)(?:owner|user)_id$/.test(key) && typeof item === "string" && typeof recordOwnerId(item) === "string") return json.rawJSON(item);
293
+ return item;
294
+ });
295
+ }
296
+ //#endregion
272
297
  //#region src/http-retry-after.ts
273
298
  /** HTTP delta-seconds and HTTP-date share one parser; never shorten a server hint. */
274
299
  function retryAfterMillis(value) {
@@ -1355,7 +1380,7 @@ var ServiceRuntime = class {
1355
1380
  Usersource: "3",
1356
1381
  ...bearer === void 0 ? {} : { Authorization: `Bearer ${bearer}` }
1357
1382
  },
1358
- body: multipart ? body : JSON.stringify(body),
1383
+ body: multipart ? body : stringifyOwnerJson(body),
1359
1384
  signal: controller.signal
1360
1385
  });
1361
1386
  if (response.status === 401 || response.status === 403 && !preserveForbiddenError) throw new ArkmePluginError(`auth-http-${response.status}`, "Arkme 登录凭据已失效", false, response.status);
@@ -1380,7 +1405,7 @@ var ServiceRuntime = class {
1380
1405
  }
1381
1406
  let envelope;
1382
1407
  try {
1383
- envelope = await response.json();
1408
+ envelope = parseOwnerJson(await response.text());
1384
1409
  } catch (error) {
1385
1410
  throw new ArkmePluginError("arkme-response-invalid", "Arkme 服务返回了无效响应", true, 502, { cause: error });
1386
1411
  }
@@ -1457,7 +1482,7 @@ var ServiceRuntime = class {
1457
1482
  }
1458
1483
  let envelope;
1459
1484
  try {
1460
- envelope = await response.json();
1485
+ envelope = parseOwnerJson(await response.text());
1461
1486
  } catch (error) {
1462
1487
  throw new ArkmePluginError("arkme-response-invalid", "Arkme 服务返回了无效响应", true, 502, { cause: error });
1463
1488
  }
@@ -1607,14 +1632,16 @@ var ServiceRuntime = class {
1607
1632
  return await this.post(this.config.chatBaseUrl, path, body, session.accessToken, [200], signal, false, requestOptions());
1608
1633
  }
1609
1634
  }
1610
- async authenticatedBotPost(path, body, initialSession, signal) {
1635
+ async authenticatedBotPost(path, body, initialSession, signal, options) {
1636
+ const { refreshOnUnauthorized = true, ...remoteOptions } = options ?? {};
1611
1637
  let session = initialSession ?? await this.requireSession();
1638
+ const requestOptions = () => options === void 0 ? void 0 : this.authenticatedRequestOptions(session, "other", "interactive-read", remoteOptions);
1612
1639
  try {
1613
- return await this.post(this.config.botBaseUrl, path, body, session.accessToken, [200], signal);
1640
+ return await this.post(this.config.botBaseUrl, path, body, session.accessToken, [200], signal, false, requestOptions());
1614
1641
  } catch (error) {
1615
- if (!(error instanceof ArkmePluginError) || !["auth-http-401", "auth-http-403"].includes(error.code)) throw error;
1642
+ if (!refreshOnUnauthorized || !(error instanceof ArkmePluginError) || !["auth-http-401", "auth-http-403"].includes(error.code)) throw error;
1616
1643
  session = await this.refreshAccessToken(session);
1617
- return await this.post(this.config.botBaseUrl, path, body, session.accessToken, [200], signal);
1644
+ return await this.post(this.config.botBaseUrl, path, body, session.accessToken, [200], signal, false, requestOptions());
1618
1645
  }
1619
1646
  }
1620
1647
  async authenticatedWebrtcPost(path, body, initialSession, signal, options = {}) {
@@ -16088,6 +16115,25 @@ var SharedReadGroup = class {
16088
16115
  }
16089
16116
  };
16090
16117
  //#endregion
16118
+ //#region src/peer-membership.ts
16119
+ function arkmePeerMemberType(value) {
16120
+ const normalized = typeof value === "string" ? value.trim().toLowerCase() : String(value);
16121
+ if ([
16122
+ "svip",
16123
+ "super_vip",
16124
+ "super-vip",
16125
+ "svip_member",
16126
+ "2"
16127
+ ].includes(normalized)) return "svip";
16128
+ if ([
16129
+ "vip",
16130
+ "vip_member",
16131
+ "1"
16132
+ ].includes(normalized)) return "vip";
16133
+ if (normalized === "free" || normalized === "0") return "free";
16134
+ return "unknown";
16135
+ }
16136
+ //#endregion
16091
16137
  //#region src/services/chat-policy.ts
16092
16138
  async function patchChatPolicy(runtime, session, chatSessionUid, patch, signal) {
16093
16139
  signal?.throwIfAborted();
@@ -26658,7 +26704,7 @@ function tokenizeThematicBreak(effects, ok, nok) {
26658
26704
  * } from 'micromark-util-types'
26659
26705
  */
26660
26706
  /** @type {Construct} */
26661
- const list$1 = {
26707
+ const list$2 = {
26662
26708
  continuation: { tokenize: tokenizeListContinuation },
26663
26709
  exit: tokenizeListEnd,
26664
26710
  name: "list",
@@ -26778,7 +26824,7 @@ function tokenizeListContinuation(effects, ok, nok) {
26778
26824
  function notInCurrentItem(code) {
26779
26825
  self.containerState._closeFlow = true;
26780
26826
  self.interrupt = void 0;
26781
- return factorySpace(effects, effects.attempt(list$1, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
26827
+ return factorySpace(effects, effects.attempt(list$2, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes("codeIndented") ? void 0 : 4)(code);
26782
26828
  }
26783
26829
  }
26784
26830
  /**
@@ -27030,7 +27076,7 @@ function initializeFlow(effects) {
27030
27076
  */
27031
27077
  const resolver = { resolveAll: createResolver() };
27032
27078
  const string$1 = initializeFactory("string");
27033
- const text$3 = initializeFactory("text");
27079
+ const text$4 = initializeFactory("text");
27034
27080
  /**
27035
27081
  * @param {'string' | 'text'} field
27036
27082
  * Field.
@@ -27207,23 +27253,23 @@ var constructs_exports = /* @__PURE__ */ __exportAll({
27207
27253
  flowInitial: () => flowInitial,
27208
27254
  insideSpan: () => insideSpan,
27209
27255
  string: () => string,
27210
- text: () => text$2
27256
+ text: () => text$3
27211
27257
  });
27212
27258
  /** @satisfies {Extension['document']} */
27213
27259
  const document$1 = {
27214
- [42]: list$1,
27215
- [43]: list$1,
27216
- [45]: list$1,
27217
- [48]: list$1,
27218
- [49]: list$1,
27219
- [50]: list$1,
27220
- [51]: list$1,
27221
- [52]: list$1,
27222
- [53]: list$1,
27223
- [54]: list$1,
27224
- [55]: list$1,
27225
- [56]: list$1,
27226
- [57]: list$1,
27260
+ [42]: list$2,
27261
+ [43]: list$2,
27262
+ [45]: list$2,
27263
+ [48]: list$2,
27264
+ [49]: list$2,
27265
+ [50]: list$2,
27266
+ [51]: list$2,
27267
+ [52]: list$2,
27268
+ [53]: list$2,
27269
+ [54]: list$2,
27270
+ [55]: list$2,
27271
+ [56]: list$2,
27272
+ [57]: list$2,
27227
27273
  [62]: blockQuote
27228
27274
  };
27229
27275
  /** @satisfies {Extension['contentInitial']} */
@@ -27251,7 +27297,7 @@ const string = {
27251
27297
  [92]: characterEscape
27252
27298
  };
27253
27299
  /** @satisfies {Extension['text']} */
27254
- const text$2 = {
27300
+ const text$3 = {
27255
27301
  [-5]: lineEnding,
27256
27302
  [-4]: lineEnding,
27257
27303
  [-3]: lineEnding,
@@ -27767,7 +27813,7 @@ function parse(options) {
27767
27813
  flow: create(flow$1),
27768
27814
  lazy: {},
27769
27815
  string: create(string$1),
27770
- text: create(text$3)
27816
+ text: create(text$4)
27771
27817
  };
27772
27818
  return parser;
27773
27819
  /**
@@ -31561,7 +31607,7 @@ function checkRule(state) {
31561
31607
  * @param {Info} info
31562
31608
  * @returns {string}
31563
31609
  */
31564
- function list(node, parent, state, info) {
31610
+ function list$1(node, parent, state, info) {
31565
31611
  const exit = state.enter("list");
31566
31612
  const bulletCurrent = state.bulletCurrent;
31567
31613
  /** @type {string} */
@@ -31784,7 +31830,7 @@ function strongPeek(_, _1, state) {
31784
31830
  * @param {Info} info
31785
31831
  * @returns {string}
31786
31832
  */
31787
- function text$1(node, _, state, info) {
31833
+ function text$2(node, _, state, info) {
31788
31834
  return state.safe(node.value, info);
31789
31835
  }
31790
31836
  //#endregion
@@ -31836,12 +31882,12 @@ const handle = {
31836
31882
  inlineCode,
31837
31883
  link: link$1,
31838
31884
  linkReference,
31839
- list,
31885
+ list: list$1,
31840
31886
  listItem,
31841
31887
  paragraph,
31842
31888
  root,
31843
31889
  strong,
31844
- text: text$1,
31890
+ text: text$2,
31845
31891
  thematicBreak
31846
31892
  };
31847
31893
  //#endregion
@@ -32302,7 +32348,7 @@ const emailAutolink = {
32302
32348
  previous: previousEmail
32303
32349
  };
32304
32350
  /** @type {ConstructRecord} */
32305
- const text = {};
32351
+ const text$1 = {};
32306
32352
  /**
32307
32353
  * Create an extension for `micromark` to support GitHub autolink literal
32308
32354
  * syntax.
@@ -32312,24 +32358,24 @@ const text = {};
32312
32358
  * autolink literal syntax.
32313
32359
  */
32314
32360
  function gfmAutolinkLiteral() {
32315
- return { text };
32361
+ return { text: text$1 };
32316
32362
  }
32317
32363
  /** @type {Code} */
32318
32364
  let code = 48;
32319
32365
  while (code < 123) {
32320
- text[code] = emailAutolink;
32366
+ text$1[code] = emailAutolink;
32321
32367
  code++;
32322
32368
  if (code === 58) code = 65;
32323
32369
  else if (code === 91) code = 97;
32324
32370
  }
32325
- text[43] = emailAutolink;
32326
- text[45] = emailAutolink;
32327
- text[46] = emailAutolink;
32328
- text[95] = emailAutolink;
32329
- text[72] = [emailAutolink, protocolAutolink];
32330
- text[104] = [emailAutolink, protocolAutolink];
32331
- text[87] = [emailAutolink, wwwAutolink];
32332
- text[119] = [emailAutolink, wwwAutolink];
32371
+ text$1[43] = emailAutolink;
32372
+ text$1[45] = emailAutolink;
32373
+ text$1[46] = emailAutolink;
32374
+ text$1[95] = emailAutolink;
32375
+ text$1[72] = [emailAutolink, protocolAutolink];
32376
+ text$1[104] = [emailAutolink, protocolAutolink];
32377
+ text$1[87] = [emailAutolink, wwwAutolink];
32378
+ text$1[119] = [emailAutolink, wwwAutolink];
32333
32379
  /**
32334
32380
  * Email autolink literal.
32335
32381
  *
@@ -36559,6 +36605,7 @@ var SourceService = class {
36559
36605
  const counterpartUserId = numberValue$26(counterpart.user_id);
36560
36606
  if (Number.isSafeInteger(counterpartUserId) && counterpartUserId > 0) {
36561
36607
  item.peerUserId = counterpartUserId;
36608
+ item.peerMemberType = arkmePeerMemberType(counterpart.member_type);
36562
36609
  privateUserIdByIndex.set(itemIndex, counterpartUserId);
36563
36610
  }
36564
36611
  } else groupSessionUidByIndex.set(itemIndex, uid);
@@ -36852,6 +36899,7 @@ var SourceService = class {
36852
36899
  directMessageAdmissionApplicable: sessionKind === 1 && numberValue$26(counterpart.user_id) > 0,
36853
36900
  displayName,
36854
36901
  ...kind === "private_chat" && Number.isSafeInteger(peerUserId) && peerUserId > 0 ? { peerUserId } : {},
36902
+ ...kind === "private_chat" && Number.isSafeInteger(peerUserId) && peerUserId > 0 ? { peerMemberType: arkmePeerMemberType(counterpart.member_type) } : {},
36855
36903
  ...cached?.avatarRef === void 0 ? {} : { avatarRef: cached.avatarRef },
36856
36904
  ...cached?.avatarRefs === void 0 ? {} : { avatarRefs: cached.avatarRefs },
36857
36905
  ...cached?.groupAvatar === void 0 ? {} : { groupAvatar: cached.groupAvatar },
@@ -37232,6 +37280,19 @@ var BotService = class {
37232
37280
  if (source.kind !== "group_chat") throw new ArkmePluginError("bot-group-source-invalid", "Bot 只能安装到群聊", false);
37233
37281
  return source;
37234
37282
  }
37283
+ async senderDisplayProfiles(session, signal) {
37284
+ const data = await this.runtime.authenticatedBotPost("/api/v1/bot/list", {}, session, signal, {
37285
+ refreshOnUnauthorized: false,
37286
+ key: "bot-sender-display-names"
37287
+ });
37288
+ return new Map(listValue$22(data.bots).map((value) => {
37289
+ const raw = objectValue$2(value);
37290
+ return [stringValue$2(raw.bot_id).trim(), {
37291
+ displayName: stringValue$2(raw.name).trim(),
37292
+ avatarUrl: stringValue$2(raw.avatar_url).trim() || stringValue$2(raw.avatar).trim()
37293
+ }];
37294
+ }).filter(([uid]) => uid !== ""));
37295
+ }
37235
37296
  async listMentionableGroupBots(group, session, signal) {
37236
37297
  const groupTargetBody = await this.resolveGroupBotBindingBody(group, session, signal);
37237
37298
  return listValue$22((await this.runtime.authenticatedBotPost("/api/v1/bot/group/list", groupTargetBody, session, signal)).bots).flatMap((value) => {
@@ -37358,7 +37419,7 @@ var BotService = class {
37358
37419
  const normalized = imageRef.trim();
37359
37420
  const entry = BOT_IMAGE_REF_PATTERN.test(normalized) ? this.botImageRefs.get(normalized) : void 0;
37360
37421
  if (entry === void 0 || entry.viewerUserId !== expectedViewerUserId || entry.expiresAtMillis <= this.now()) {
37361
- if (entry !== void 0) {
37422
+ if (entry !== void 0 && entry.expiresAtMillis <= this.now()) {
37362
37423
  this.botImageRefs.delete(normalized);
37363
37424
  if (this.botImageRefByKey.get(entry.key) === normalized) this.botImageRefByKey.delete(entry.key);
37364
37425
  }
@@ -38004,12 +38065,12 @@ function object(value) {
38004
38065
  }
38005
38066
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
38006
38067
  }
38007
- function first(source, keys) {
38068
+ function first$1(source, keys) {
38008
38069
  return keys.map((key) => source[key]).find((value) => value !== void 0 && value !== null && value !== "");
38009
38070
  }
38010
38071
  /** Flutter-compatible summary templates, resolved on the host before projection. */
38011
38072
  function renderCallRecordSummary(source, viewerUserId, displayNames = /* @__PURE__ */ new Map()) {
38012
- const fallback = String(first(source, [
38073
+ const fallback = String(first$1(source, [
38013
38074
  "sm",
38014
38075
  "call_summary",
38015
38076
  "callSummary",
@@ -38017,19 +38078,19 @@ function renderCallRecordSummary(source, viewerUserId, displayNames = /* @__PURE
38017
38078
  "summaryText",
38018
38079
  "summary"
38019
38080
  ]) ?? "").trim();
38020
- const template = String(first(source, [
38081
+ const template = String(first$1(source, [
38021
38082
  "smt",
38022
38083
  "call_summary_template",
38023
38084
  "callSummaryTemplate",
38024
38085
  "summary_template",
38025
38086
  "summaryTemplate"
38026
38087
  ]) ?? "").trim();
38027
- const labels = object(first(source, [
38088
+ const labels = object(first$1(source, [
38028
38089
  "ssl",
38029
38090
  "summary_speaker_labels",
38030
38091
  "call_summary_speaker_labels"
38031
38092
  ]));
38032
- const users = object(first(source, [
38093
+ const users = object(first$1(source, [
38033
38094
  "ssu",
38034
38095
  "summary_speaker_user_ids",
38035
38096
  "call_summary_speaker_user_ids"
@@ -38074,13 +38135,13 @@ function callRecordSource(raw) {
38074
38135
  }
38075
38136
  const extra = value.extra == null ? void 0 : details(object(value.extra), depth + 1);
38076
38137
  if (extra) return extra;
38077
- if (first(value, [
38138
+ if (first$1(value, [
38078
38139
  "mt",
38079
38140
  "media_type",
38080
38141
  "mediaType",
38081
38142
  "call_media_type",
38082
38143
  "callMediaType"
38083
- ]) !== void 0 && first(value, [
38144
+ ]) !== void 0 && first$1(value, [
38084
38145
  "rs",
38085
38146
  "call_result",
38086
38147
  "callResult",
@@ -38093,7 +38154,7 @@ function callRecordSource(raw) {
38093
38154
  }
38094
38155
  function callRecordRoomId(raw) {
38095
38156
  const source = callRecordSource(raw);
38096
- const room = source && first(source, [
38157
+ const room = source && first$1(source, [
38097
38158
  "ri",
38098
38159
  "room_id",
38099
38160
  "roomId",
@@ -38113,7 +38174,7 @@ function callRecordRoomId(raw) {
38113
38174
  function projectCallRecord(raw, viewerUserId) {
38114
38175
  const source = callRecordSource(raw);
38115
38176
  if (!source) return void 0;
38116
- const media = String(first(source, [
38177
+ const media = String(first$1(source, [
38117
38178
  "mt",
38118
38179
  "media_type",
38119
38180
  "mediaType",
@@ -38127,20 +38188,20 @@ function projectCallRecord(raw, viewerUserId) {
38127
38188
  "2"
38128
38189
  ].includes(media)) return void 0;
38129
38190
  const mediaType = media === "video" || media === "2" ? "video" : "audio";
38130
- const result = String(first(source, [
38191
+ const result = String(first$1(source, [
38131
38192
  "rs",
38132
38193
  "call_result",
38133
38194
  "callResult",
38134
38195
  "result"
38135
38196
  ]) ?? "").toLowerCase().replace(/[\s_-]+/g, "");
38136
- const isCaller = viewerUserId > 0 && Number(first(source, [
38197
+ const isCaller = viewerUserId > 0 && Number(first$1(source, [
38137
38198
  "cr",
38138
38199
  "caller_id",
38139
38200
  "callerId",
38140
38201
  "caller_user_id",
38141
38202
  "callerUserId"
38142
38203
  ])) === viewerUserId;
38143
- const seconds = Number(first(source, [
38204
+ const seconds = Number(first$1(source, [
38144
38205
  "du",
38145
38206
  "duration",
38146
38207
  "duration_sec",
@@ -38179,7 +38240,7 @@ function projectCallRecord(raw, viewerUserId) {
38179
38240
  break;
38180
38241
  }
38181
38242
  const summary = renderCallRecordSummary(source, viewerUserId);
38182
- const rawStatus = String(first(source, [
38243
+ const rawStatus = String(first$1(source, [
38183
38244
  "ss",
38184
38245
  "call_summary_status",
38185
38246
  "summary_status",
@@ -40724,7 +40785,7 @@ var ChatRealtimeService = class {
40724
40785
  const sessionKind = numberValue$21(objectValue$2(bundle).session_kind ?? objectValue$2(objectValue$2(bundle).session).session_kind);
40725
40786
  const sourceKind = sessionKind === 2 ? "group_chat" : sessionKind === 1 || sessionKind === 3 ? "private_chat" : cached?.kind === "group_chat" || cached?.kind === "private_chat" ? cached.kind : void 0;
40726
40787
  return [uid, {
40727
- items: await this.projectionReader.chatTimelineItems(data, session, uid, sourceKind),
40788
+ items: await this.projectionReader.chatTimelineItems(data, session, uid, sourceKind, bundle),
40728
40789
  notificationIdentities: timelineNotificationIdentities(data)
40729
40790
  }];
40730
40791
  }))).forEach((result, index) => {
@@ -40967,6 +41028,142 @@ var ChatRealtimeService = class {
40967
41028
  }
40968
41029
  };
40969
41030
  //#endregion
41031
+ //#region src/chat-sender-display.ts
41032
+ function identifyTimelineSender(input) {
41033
+ const uid = input.recordUid.trim();
41034
+ const prefix = uid.startsWith("bot_reply_") ? "bot_reply_" : uid.startsWith("bot_outbound_") ? "bot_outbound_" : "";
41035
+ const oldFormat = /^\d+_bot[0-9A-Za-z]+~\d+_\d+_\d+~\d+$/.test(uid);
41036
+ if (input.actorKind !== 2 && prefix === "" && !oldFormat) return { kind: "human" };
41037
+ let legacyUid = "";
41038
+ if (prefix !== "") {
41039
+ const main = uid.slice(prefix.length).split("__")[0] ?? "";
41040
+ legacyUid = /^([+-]?\d+)_([^_]+)_(.+)$/.exec(main)?.[2]?.trim() ?? /^([^_]+)_(.+)$/.exec(main)?.[1]?.trim() ?? "";
41041
+ } else legacyUid = /^\d+_(bot[0-9A-Za-z]+)~/.exec(uid)?.[1] ?? "";
41042
+ return {
41043
+ kind: "bot",
41044
+ botUid: input.botUid.trim() || legacyUid
41045
+ };
41046
+ }
41047
+ function hasMissingBotProfile(ids, profiles) {
41048
+ return [...ids].some((uid) => !profiles.get(uid)?.displayName.trim() || !profiles.get(uid)?.avatarUrl?.trim());
41049
+ }
41050
+ /** Participant fields win independently; directory profiles fill missing fields. */
41051
+ function supplementBotProfiles(profiles, ids, additions) {
41052
+ const result = new Map(profiles);
41053
+ for (const [uid, profile] of additions) {
41054
+ if (!ids.has(uid)) continue;
41055
+ const current = result.get(uid);
41056
+ const avatarUrl = current?.avatarUrl?.trim() || profile.avatarUrl?.trim();
41057
+ result.set(uid, {
41058
+ displayName: current?.displayName.trim() || profile.displayName.trim(),
41059
+ ...avatarUrl ? { avatarUrl } : {}
41060
+ });
41061
+ }
41062
+ return result;
41063
+ }
41064
+ function botDisplayName(profiles, uid) {
41065
+ return profiles.get(uid)?.displayName.trim() || "Bot";
41066
+ }
41067
+ //#endregion
41068
+ //#region src/services/chat-sender-display-reader.ts
41069
+ const list = (value) => Array.isArray(value) ? value : [];
41070
+ const text = (value) => stringValue$2(value).trim();
41071
+ const first = (raw, keys) => keys.map((key) => text(raw[key])).find(Boolean) ?? "";
41072
+ /** Normalize wire data once. Neither raw participants nor their extra fields escape this adapter. */
41073
+ function botDisplaySnapshot(bundle) {
41074
+ const session = objectValue$2(bundle.session);
41075
+ const chatSessionUid = text(session.chat_session_uid);
41076
+ const extra = objectValue$2(session.extra);
41077
+ const groupTarget = arkmeGroupBotBindingTargetFromBundle(bundle) ?? arkmeGroupBotBindingTargetFromBundle({ rm_subject_id: extra.subject_id ?? extra.legacy_subject_id });
41078
+ const profiles = /* @__PURE__ */ new Map();
41079
+ for (const value of list(bundle.bot_participants)) {
41080
+ const participant = objectValue$2(value);
41081
+ const uid = text(participant.bot_uid);
41082
+ if (uid === "" || text(participant.chat_session_uid) !== chatSessionUid) continue;
41083
+ const details = objectValue$2(participant.extra);
41084
+ const avatarUrl = first(details, [
41085
+ "avatar_url",
41086
+ "avatarUrl",
41087
+ "head_img",
41088
+ "headImg"
41089
+ ]);
41090
+ profiles.set(uid, {
41091
+ displayName: text(participant.display_name_snapshot) || first(details, [
41092
+ "display_name_snapshot",
41093
+ "displayNameSnapshot",
41094
+ "bot_name",
41095
+ "botName",
41096
+ "name",
41097
+ "nickname"
41098
+ ]),
41099
+ ...avatarUrl === "" ? {} : { avatarUrl }
41100
+ });
41101
+ }
41102
+ return {
41103
+ chatSessionUid,
41104
+ profiles,
41105
+ ...groupTarget === void 0 ? {} : { groupTarget }
41106
+ };
41107
+ }
41108
+ var RuntimeBotDisplayProfilesReader = class {
41109
+ runtime;
41110
+ bot;
41111
+ constructor(runtime, bot) {
41112
+ this.runtime = runtime;
41113
+ this.bot = bot;
41114
+ }
41115
+ async read(input, session, signal) {
41116
+ if (input.botUids.size === 0) return /* @__PURE__ */ new Map();
41117
+ const budget = AbortSignal.timeout(1500);
41118
+ const identitySignal = signal === void 0 ? budget : AbortSignal.any([signal, budget]);
41119
+ const optionalFailure = (error) => {
41120
+ if (signal?.aborted || error instanceof ArkmeStaleRequestError) throw error;
41121
+ };
41122
+ let snapshot = input.snapshot;
41123
+ if (snapshot === void 0) try {
41124
+ const data = await this.runtime.authenticatedChatPost("/api/v1/chats/display-snapshots", { chat_session_uids: [input.chatSessionUid] }, session, identitySignal, { refreshOnUnauthorized: false });
41125
+ snapshot = list(data.items).map((value) => botDisplaySnapshot(objectValue$2(value))).find((item) => item.chatSessionUid === input.chatSessionUid);
41126
+ } catch (error) {
41127
+ optionalFailure(error);
41128
+ }
41129
+ if (snapshot?.chatSessionUid !== input.chatSessionUid) snapshot = void 0;
41130
+ let profiles = new Map([...snapshot?.profiles ?? []].filter(([uid]) => input.botUids.has(uid)));
41131
+ if (!identitySignal.aborted && hasMissingBotProfile(input.botUids, profiles)) try {
41132
+ profiles = supplementBotProfiles(profiles, input.botUids, await this.bot.senderDisplayProfiles(session, identitySignal));
41133
+ } catch (error) {
41134
+ optionalFailure(error);
41135
+ }
41136
+ if (input.isGroup && !identitySignal.aborted && hasMissingBotProfile(input.botUids, profiles)) try {
41137
+ profiles = supplementBotProfiles(profiles, input.botUids, await this.groupSenderDisplayProfiles(input.chatSessionUid, session, identitySignal, snapshot));
41138
+ } catch (error) {
41139
+ optionalFailure(error);
41140
+ }
41141
+ signal?.throwIfAborted();
41142
+ return profiles;
41143
+ }
41144
+ async groupSenderDisplayProfiles(chatSessionUid, session, signal, known) {
41145
+ const snapshot = known?.groupTarget !== void 0 ? known : botDisplaySnapshot(await this.runtime.authenticatedChatPost("/api/v1/chats/detail", { chat_session_uid: chatSessionUid }, session, signal, { refreshOnUnauthorized: false }));
41146
+ if (snapshot.chatSessionUid !== chatSessionUid || snapshot.groupTarget === void 0) return /* @__PURE__ */ new Map();
41147
+ const data = await this.runtime.authenticatedBotPost("/api/v1/bot/group/list", arkmeGroupBotBindingBody({
41148
+ ownerRef: chatSessionUid,
41149
+ botGroupTarget: snapshot.groupTarget
41150
+ }), session, signal, {
41151
+ refreshOnUnauthorized: false,
41152
+ key: `group-bot-sender-display-names:${chatSessionUid}`,
41153
+ cancelWhenUnobserved: true
41154
+ });
41155
+ const profiles = /* @__PURE__ */ new Map();
41156
+ for (const value of list(data.bots)) {
41157
+ const raw = objectValue$2(value);
41158
+ if (raw.installed === true && text(raw.bot_id) !== "" && text(raw.name) !== "") profiles.set(text(raw.bot_id), {
41159
+ displayName: text(raw.name),
41160
+ avatarUrl: text(raw.avatar_url) || text(raw.avatar)
41161
+ });
41162
+ }
41163
+ return profiles;
41164
+ }
41165
+ };
41166
+ //#endregion
40970
41167
  //#region src/member-directory.ts
40971
41168
  /** Only for the members/page and members/by-user-ids read contracts: 2001/2002 mean unreadable membership/session. */
40972
41169
  function invalidatesMemberSnapshot(error) {
@@ -43621,11 +43818,16 @@ function chatRecordOwnerUserId(relation, record, payload, senderUserId) {
43621
43818
  senderUserId
43622
43819
  ];
43623
43820
  for (const candidate of candidates) {
43624
- const userId = integerLikeValue(candidate);
43625
- if (userId > 0) return userId;
43821
+ const userId = recordOwnerId(candidate);
43822
+ if (userId !== 0) return userId;
43626
43823
  }
43627
43824
  return 0;
43628
43825
  }
43826
+ function chatTimelineItemSupportsQuickNoteDetails(relation, recordOwnerUserId) {
43827
+ const senderActorKind = integerLikeValue(relation.sender_actor_kind ?? relation.senderActorKind);
43828
+ const senderBotUid = stringValue$2(relation.sender_bot_uid ?? relation.senderBotUid).trim();
43829
+ return senderActorKind !== 2 && senderBotUid === "" && typeof recordOwnerUserId === "number" && Number.isSafeInteger(recordOwnerUserId) && recordOwnerUserId > 0;
43830
+ }
43629
43831
  function epochMillisValue(value) {
43630
43832
  const timestamp = integerLikeValue(value);
43631
43833
  return timestamp > 0 && timestamp < 1e11 ? timestamp * 1e3 : timestamp;
@@ -43701,7 +43903,7 @@ function messageCopyLinkSourceAnchorFromData(value) {
43701
43903
  return {
43702
43904
  relationUid: stringValue$2(data.rel_uid),
43703
43905
  recordUid: stringValue$2(data.record_uid),
43704
- recordOwnerUserId: integerLikeValue(data.record_owner_user_id),
43906
+ recordOwnerUserId: recordOwnerId(data.record_owner_user_id),
43705
43907
  sequence: integerLikeValue(data.seq)
43706
43908
  };
43707
43909
  }
@@ -43750,7 +43952,7 @@ function messageCopyLinkExtensionItemFromData(value) {
43750
43952
  return {
43751
43953
  recordUid,
43752
43954
  ...firstTextValue(data, ["parent_record_uid", "parentRecordUid"]) === "" ? {} : { parentRecordUid: firstTextValue(data, ["parent_record_uid", "parentRecordUid"]) },
43753
- ...integerLikeValue(data.record_owner_user_id ?? data.recordOwnerUserId ?? data.user_id ?? data.userId ?? core.record_owner_user_id ?? core.recordOwnerUserId ?? core.user_id ?? core.userId) <= 0 ? {} : { recordOwnerUserId: integerLikeValue(data.record_owner_user_id ?? data.recordOwnerUserId ?? data.user_id ?? data.userId ?? core.record_owner_user_id ?? core.recordOwnerUserId ?? core.user_id ?? core.userId) },
43955
+ ...recordOwnerId(data.record_owner_user_id ?? data.recordOwnerUserId ?? data.user_id ?? data.userId ?? core.record_owner_user_id ?? core.recordOwnerUserId ?? core.user_id ?? core.userId) === 0 ? {} : { recordOwnerUserId: recordOwnerId(data.record_owner_user_id ?? data.recordOwnerUserId ?? data.user_id ?? data.userId ?? core.record_owner_user_id ?? core.recordOwnerUserId ?? core.user_id ?? core.userId) },
43754
43956
  level: Math.max(2, integerLikeValue(data.level) || 2),
43755
43957
  sourceKind: stringValue$2(data.source_kind ?? data.sourceKind) || "record_extension",
43756
43958
  senderDisplayName,
@@ -43784,7 +43986,7 @@ function chatExtensionTreeItemFromData(value, fallbackParentRecordUid) {
43784
43986
  const recordUid = firstTextValue(edge, ["child_record_uid", "childRecordUid"]) || firstTextValue(relation, ["record_uid", "recordUid"]) || firstTextValue(payload, ["record_uid", "recordUid"]);
43785
43987
  if (recordUid === "") return void 0;
43786
43988
  const parentRecordUid = firstTextValue(edge, ["parent_record_uid", "parentRecordUid"]) || fallbackParentRecordUid;
43787
- const recordOwnerUserId = integerLikeValue(edge.child_record_owner_user_id ?? edge.childRecordOwnerUserId);
43989
+ const recordOwnerUserId = recordOwnerId(edge.child_record_owner_user_id ?? edge.childRecordOwnerUserId);
43788
43990
  const senderDisplayName = firstTextValue(relation, [
43789
43991
  "display_name_snapshot",
43790
43992
  "displayNameSnapshot",
@@ -43800,7 +44002,7 @@ function chatExtensionTreeItemFromData(value, fallbackParentRecordUid) {
43800
44002
  return {
43801
44003
  recordUid,
43802
44004
  ...parentRecordUid === "" ? {} : { parentRecordUid },
43803
- ...recordOwnerUserId <= 0 ? {} : { recordOwnerUserId },
44005
+ ...recordOwnerUserId === 0 ? {} : { recordOwnerUserId },
43804
44006
  level: 2,
43805
44007
  sourceKind: "record_extension",
43806
44008
  senderDisplayName,
@@ -44153,6 +44355,27 @@ function encodeOpaqueJson$5(value) {
44153
44355
  function decodeOpaqueJson$4(value) {
44154
44356
  return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
44155
44357
  }
44358
+ function timelineSenderRelation(item) {
44359
+ const relation = objectValue$2(item.relation);
44360
+ return {
44361
+ ...relation,
44362
+ record_uid: relation.record_uid ?? objectValue$2(objectValue$2(item.record).payload).record_uid
44363
+ };
44364
+ }
44365
+ function timelineSender(relation) {
44366
+ return identifyTimelineSender({
44367
+ recordUid: stringValue$2(relation.record_uid),
44368
+ actorKind: integerLikeValue(relation.sender_actor_kind),
44369
+ botUid: stringValue$2(relation.sender_bot_uid)
44370
+ });
44371
+ }
44372
+ function timelineSenderIsBot(relation) {
44373
+ return timelineSender(relation).kind === "bot";
44374
+ }
44375
+ function timelineSenderBotUid(relation) {
44376
+ const sender = timelineSender(relation);
44377
+ return sender.kind === "bot" ? sender.botUid : "";
44378
+ }
44156
44379
  var ChatService = class {
44157
44380
  runtime;
44158
44381
  source;
@@ -44166,9 +44389,10 @@ var ChatService = class {
44166
44389
  privacy;
44167
44390
  messageActions;
44168
44391
  callHistory;
44392
+ senderProfiles;
44169
44393
  memberEvents;
44170
44394
  favoriteStickerMutationTail = Promise.resolve();
44171
- constructor(runtime, source, profile, media, record, bot, arko, aiPolish, realtime, privacy = new ArkmePrivacyVisibilityService(runtime), messageActions, callHistory = new CallHistoryService(runtime, profile, media)) {
44395
+ constructor(runtime, source, profile, media, record, bot, arko, aiPolish, realtime, privacy = new ArkmePrivacyVisibilityService(runtime), messageActions, callHistory = new CallHistoryService(runtime, profile, media), senderProfiles = new RuntimeBotDisplayProfilesReader(runtime, bot)) {
44172
44396
  this.runtime = runtime;
44173
44397
  this.source = source;
44174
44398
  this.profile = profile;
@@ -44181,6 +44405,7 @@ var ChatService = class {
44181
44405
  this.privacy = privacy;
44182
44406
  this.messageActions = messageActions;
44183
44407
  this.callHistory = callHistory;
44408
+ this.senderProfiles = senderProfiles;
44184
44409
  this.memberEvents = new MemberEventService(runtime, source, profile, (userId, options) => this.openPrivateChatFromUser(userId, options));
44185
44410
  }
44186
44411
  async hydrateExtensionMedia(projections, session, signal) {
@@ -44887,8 +45112,8 @@ var ChatService = class {
44887
45112
  }
44888
45113
  async readSourceAround(sourceRef, itemUid, recordOwnerUserId, options = {}) {
44889
45114
  const normalizedItemUid = itemUid.trim();
44890
- const normalizedOwnerUserId = Math.trunc(recordOwnerUserId);
44891
- if (normalizedItemUid === "" || normalizedOwnerUserId <= 0) throw new ArkmePluginError("chat-timeline-around-target-invalid", "要定位的快记信息不完整", false, 400);
45115
+ const normalizedOwnerUserId = recordOwnerId(recordOwnerUserId);
45116
+ if (normalizedItemUid === "" || normalizedOwnerUserId === 0) throw new ArkmePluginError("chat-timeline-around-target-invalid", "要定位的快记信息不完整", false, 400);
44892
45117
  const session = await this.runtime.requireSession();
44893
45118
  const source = await this.source.openSourceRef(sourceRef, session.userId);
44894
45119
  if (source.kind !== "private_chat" && source.kind !== "group_chat") throw new ArkmePluginError("chat-timeline-around-source-invalid", "仅聊天中的快记支持精确定位", false, 400);
@@ -45356,7 +45581,7 @@ var ChatService = class {
45356
45581
  const sourceContext = objectValue$2(data.source_context);
45357
45582
  const accessMode = stringValue$2(data.access_mode);
45358
45583
  if (accessMode !== "normal" && accessMode !== "link_read_only") throw new ArkmePluginError("message-copy-link-unavailable", "链接暂不可用", false, 404);
45359
- const anchors = accessMode === "normal" ? listValue$16(sourceContext.anchors).map(messageCopyLinkSourceAnchorFromData).filter((anchor) => anchor.relationUid.trim() !== "" && anchor.recordUid.trim() !== "" && anchor.recordOwnerUserId > 0) : [];
45584
+ const anchors = accessMode === "normal" ? listValue$16(sourceContext.anchors).map(messageCopyLinkSourceAnchorFromData).filter((anchor) => anchor.relationUid.trim() !== "" && anchor.recordUid.trim() !== "" && anchor.recordOwnerUserId !== 0) : [];
45360
45585
  const recordContext = mergeMessageCopyLinkRecordContexts(messageCopyLinkRecordContextFromData(data), accessMode === "normal" ? await this.loadMessageCopyLinkPublicExtensions(messageCopyLinkRecordUidCandidates(items, anchors), session, options.signal) : void 0);
45361
45586
  const result = {
45362
45587
  sid: stringValue$2(data.sid),
@@ -45431,8 +45656,8 @@ var ChatService = class {
45431
45656
  async loadChatMessageExtensions(reference, session, signal) {
45432
45657
  const chatSessionUid = reference.chatSessionUid.trim();
45433
45658
  const parentRecordUid = reference.recordUid.trim();
45434
- const parentRecordOwnerUserId = Math.trunc(reference.recordOwnerUserId);
45435
- if (chatSessionUid === "" || parentRecordUid === "" || parentRecordOwnerUserId <= 0) throw new ArkmePluginError("source-message-extension-context-invalid", "快记延展上下文无效,请刷新后重试", true, 409);
45659
+ const parentRecordOwnerUserId = recordOwnerId(reference.recordOwnerUserId);
45660
+ if (chatSessionUid === "" || parentRecordUid === "" || parentRecordOwnerUserId === 0) throw new ArkmePluginError("source-message-extension-context-invalid", "快记延展上下文无效,请刷新后重试", true, 409);
45436
45661
  const data = await this.runtime.authenticatedChatPost("/api/v1/chats/extensions/tree/page", {
45437
45662
  chat_session_uid: chatSessionUid,
45438
45663
  parent_record_owner_user_id: parentRecordOwnerUserId,
@@ -45488,8 +45713,8 @@ var ChatService = class {
45488
45713
  if (requestedParentRecordUid !== "" && requestedParentRecordUid !== rootReference.recordUid) {
45489
45714
  const target = (source.kind === "private_chat" || source.kind === "group_chat" ? await this.loadChatMessageExtensions(rootReference, session, options.signal) : await this.loadMessageCopyLinkPublicExtensions([rootReference.recordUid], session, options.signal, { strict: true }))?.extensions.find((extension) => extension.recordUid === requestedParentRecordUid);
45490
45715
  if (target === void 0) throw new ArkmePluginError("source-message-extension-target-invalid", "延展目标已变化,请刷新后重试", true, 409);
45491
- const recordOwnerUserId = target.recordOwnerUserId ?? 0;
45492
- if (!Number.isSafeInteger(recordOwnerUserId) || recordOwnerUserId <= 0) throw new ArkmePluginError("source-message-extension-target-owner-missing", "无法确认延展目标作者,请刷新后重试", true, 409);
45716
+ const recordOwnerUserId = recordOwnerId(target.recordOwnerUserId);
45717
+ if (recordOwnerUserId === 0) throw new ArkmePluginError("source-message-extension-target-owner-missing", "无法确认延展目标作者,请刷新后重试", true, 409);
45493
45718
  reference = {
45494
45719
  ...rootReference,
45495
45720
  recordUid: target.recordUid,
@@ -46758,20 +46983,24 @@ var ChatService = class {
46758
46983
  targets.sort((left, right) => left.startIndex - right.startIndex);
46759
46984
  return targets.length === 0 ? void 0 : targets;
46760
46985
  }
46761
- async chatTimelineItems(data, session, chatSessionUid, projectedSourceKind) {
46986
+ async chatTimelineItems(data, session, chatSessionUid, projectedSourceKind, bundle) {
46762
46987
  const items = [];
46988
+ const botProfiles = await this.timelineBotDisplayProfiles(listValue$16(data.items), session, chatSessionUid, projectedSourceKind, void 0, bundle);
46763
46989
  const signingKey = await this.runtime.stateStore.uniqueCode();
46764
46990
  const sourceKind = projectedSourceKind;
46765
46991
  for (const raw of listValue$16(data.items)) {
46766
46992
  const item = objectValue$2(raw);
46767
- const relation = objectValue$2(item.relation);
46993
+ const relation = timelineSenderRelation(item);
46768
46994
  const record = objectValue$2(item.record);
46769
46995
  const payload = objectValue$2(record.payload);
46770
46996
  const uid = stringValue$2(relation.record_uid ?? payload.record_uid).trim();
46771
46997
  if (uid === "") continue;
46772
46998
  const senderUserId = integerLikeValue(relation.sender_user_id);
46999
+ const isBot = timelineSenderIsBot(relation);
47000
+ const isMe = !isBot && senderUserId === session.userId;
46773
47001
  const relationUid = stringValue$2(relation.rel_uid ?? relation.relUid).trim();
46774
47002
  const recordOwnerUserId = chatRecordOwnerUserId(relation, record, payload, senderUserId);
47003
+ const quickNoteDetailsSupported = chatTimelineItemSupportsQuickNoteDetails(relation, recordOwnerUserId);
46775
47004
  const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
46776
47005
  const sendAtMillis = numberValue$19(relation.attach_at ?? payload.send_at);
46777
47006
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
@@ -46786,14 +47015,14 @@ var ChatService = class {
46786
47015
  signingKey
46787
47016
  });
46788
47017
  const rawAgentSource = timelineAgentSource(relation, record, payload);
46789
- const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
47018
+ const agentSource = isMe ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
46790
47019
  const contentBlocks = this.media.richContentBlocks(item, session.userId);
46791
- const extensionProjection = this.timelineExtensionProjection(item, session.userId);
47020
+ const extensionProjection = this.timelineExtensionProjection(item, session.userId, botProfiles);
46792
47021
  const conversationPreview = arkmeChatConversationPreview(item);
46793
47022
  const callRecord = await this.callHistory.timelineCallRecord(item, session.userId);
46794
- const senderName = stringValue$2(relation.display_name_snapshot).trim() || "Arkme用户";
47023
+ const senderName = this.timelineSenderName(relation, botProfiles);
46795
47024
  const mentions = await this.timelineMentionTargets(record, payload, session.userId, chatSessionUid);
46796
- const mentionsViewer = senderUserId !== session.userId && arkmeMentionMetadataMentionsViewer(record, payload, session.userId);
47025
+ const mentionsViewer = !isMe && arkmeMentionMetadataMentionsViewer(record, payload, session.userId);
46797
47026
  items.push({
46798
47027
  ...numberValue$19(record.status) === 1 ? recordDeletionCapability({
46799
47028
  userId: session.userId,
@@ -46807,7 +47036,7 @@ var ChatService = class {
46807
47036
  }, signingKey) : {},
46808
47037
  itemUid: uid,
46809
47038
  ...relationUid === "" ? {} : { timelineItemKey: await this.source.chatTimelineItemKey(session.userId, chatSessionUid, relationUid) },
46810
- ...sourceKind !== "group_chat" || relationUid === "" || senderUserId === session.userId ? {} : {
47039
+ ...sourceKind !== "group_chat" || relationUid === "" || isMe ? {} : {
46811
47040
  messageRef: this.sealMessageRef(session.userId, chatSessionUid, relationUid, signingKey),
46812
47041
  messageWithdrawalRef: this.sealMessageWithdrawalRef(session.userId, chatSessionUid, relationUid, signingKey)
46813
47042
  },
@@ -46819,7 +47048,7 @@ var ChatService = class {
46819
47048
  relationUid,
46820
47049
  recordOwnerUserId,
46821
47050
  recordUid: uid,
46822
- senderUserId,
47051
+ senderUserId: recordOwnerId(relation.sender_user_id),
46823
47052
  senderName,
46824
47053
  title: stringValue$2(payload.title),
46825
47054
  textContent: stringValue$2(payload.text_content),
@@ -46830,11 +47059,13 @@ var ChatService = class {
46830
47059
  displayKind: numberValue$19(payload.display_kind),
46831
47060
  contentBlocks
46832
47061
  }), signingKey) },
46833
- ...senderUserId > 0 ? { memberRef: await this.sealChatMemberRef(session.userId, chatSessionUid, senderUserId) } : {},
47062
+ ...quickNoteDetailsSupported ? {} : { quickNoteDetailsSupported: false },
47063
+ ...!isBot && senderUserId > 0 ? { memberRef: await this.sealChatMemberRef(session.userId, chatSessionUid, senderUserId) } : {},
46834
47064
  senderName,
46835
47065
  ...agentSource === void 0 ? {} : { agentSource },
46836
- ...senderUserId > 0 ? { avatarRef: await this.profile.sealProfileImageRef(session.userId, senderUserId) } : {},
46837
- isMe: senderUserId === session.userId,
47066
+ ...isBot ? { senderKind: "bot" } : {},
47067
+ ...isBot ? this.timelineBotAvatar(relation, botProfiles, session.userId) : senderUserId > 0 ? { avatarRef: await this.profile.sealProfileImageRef(session.userId, senderUserId) } : {},
47068
+ isMe,
46838
47069
  ...callRecord === void 0 ? {} : { callRecord },
46839
47070
  ...mentionsViewer ? { mentionsViewer: true } : {},
46840
47071
  ...mentions === void 0 ? {} : { mentions },
@@ -46864,8 +47095,8 @@ var ChatService = class {
46864
47095
  const normalizedSourceRef = sourceRef.trim();
46865
47096
  const source = await this.source.openSourceRef(normalizedSourceRef, session.userId);
46866
47097
  const reference = await this.openMessageActionRef(messageActionRef, session.userId, source);
46867
- const ownerUserId = reference.recordOwnerUserId > 0 ? reference.recordOwnerUserId : reference.senderUserId;
46868
- if (!Number.isSafeInteger(ownerUserId) || ownerUserId <= 0) throw new ArkmePluginError("related-quick-note-source-invalid", "当前快记缺少作者定位,请刷新后重试", false, 409);
47098
+ const ownerUserId = reference.recordOwnerUserId !== 0 ? reference.recordOwnerUserId : reference.senderUserId;
47099
+ if (recordOwnerId(ownerUserId) === 0) throw new ArkmePluginError("related-quick-note-source-invalid", "当前快记缺少作者定位,请刷新后重试", false, 409);
46869
47100
  return {
46870
47101
  viewerUserId: session.userId,
46871
47102
  sourceRef: normalizedSourceRef,
@@ -47110,9 +47341,9 @@ var ChatService = class {
47110
47341
  withSharedRecordingDetailRef(recording, input) {
47111
47342
  if (recording === void 0) return void 0;
47112
47343
  const recordUid = input.recordUid.trim();
47113
- const recordOwnerUserId = input.recordOwnerUserId > 0 ? input.recordOwnerUserId : recording.sharedByUserId > 0 ? recording.sharedByUserId : input.senderUserId;
47344
+ const recordOwnerUserId = input.recordOwnerUserId !== 0 ? input.recordOwnerUserId : recording.sharedByUserId > 0 ? recording.sharedByUserId : input.senderUserId;
47114
47345
  const chatSessionUid = input.chatSessionUid.trim();
47115
- if (chatSessionUid === "" || recordUid === "" || recordOwnerUserId <= 0) return recording;
47346
+ if (chatSessionUid === "" || recordUid === "" || recordOwnerUserId === 0) return recording;
47116
47347
  return {
47117
47348
  ...recording,
47118
47349
  detailRef: this.sealSharedRecordingDetailRef({
@@ -47120,7 +47351,7 @@ var ChatService = class {
47120
47351
  viewerUserId: input.viewerUserId,
47121
47352
  chatSessionUid,
47122
47353
  relationUid: input.relationUid.trim(),
47123
- recordOwnerUserId: Math.trunc(recordOwnerUserId),
47354
+ recordOwnerUserId: recordOwnerId(recordOwnerUserId),
47124
47355
  recordUid,
47125
47356
  sequence: Math.max(0, Math.trunc(input.sequence))
47126
47357
  }, input.signingKey)
@@ -47148,11 +47379,11 @@ var ChatService = class {
47148
47379
  viewerUserId: integerLikeValue(parsed.viewerUserId),
47149
47380
  chatSessionUid: stringValue$2(parsed.chatSessionUid).trim(),
47150
47381
  relationUid: stringValue$2(parsed.relationUid).trim(),
47151
- recordOwnerUserId: integerLikeValue(parsed.recordOwnerUserId),
47382
+ recordOwnerUserId: recordOwnerId(parsed.recordOwnerUserId),
47152
47383
  recordUid: stringValue$2(parsed.recordUid).trim(),
47153
47384
  sequence: Math.max(0, integerLikeValue(parsed.sequence))
47154
47385
  };
47155
- if (parsed.version !== 1 || result.viewerUserId !== expectedViewerUserId || result.chatSessionUid === "" || result.recordUid === "" || result.recordOwnerUserId <= 0) throw new ArkmePluginError("shared-recording-detail-ref-invalid", "共享录音详情引用与当前账号不匹配,请刷新后重试", false, 403);
47386
+ if (parsed.version !== 1 || result.viewerUserId !== expectedViewerUserId || result.chatSessionUid === "" || result.recordUid === "" || result.recordOwnerUserId === 0) throw new ArkmePluginError("shared-recording-detail-ref-invalid", "共享录音详情引用与当前账号不匹配,请刷新后重试", false, 403);
47156
47387
  return result;
47157
47388
  }
47158
47389
  async sharedRecordingDetail(detailRef, options = {}) {
@@ -47185,8 +47416,8 @@ var ChatService = class {
47185
47416
  const reference = await this.openMessageActionRef(actionRef, session.userId, source);
47186
47417
  if (reference.recordUid === "") throw new ArkmePluginError("message-snapshot-record-invalid", "快记记录身份无效,请刷新后重试", false, 400);
47187
47418
  if (reference.senderUserId !== session.userId) throw new ArkmePluginError("message-snapshot-not-owned", "只能查看自己发送的快记详情", false, 403);
47188
- const recordOwnerUserId = reference.recordOwnerUserId > 0 ? reference.recordOwnerUserId : reference.senderUserId;
47189
- if (recordOwnerUserId <= 0) throw new ArkmePluginError("message-snapshot-owner-missing", "快记记录归属暂时无法确认,请刷新后重试", true, 502);
47419
+ const recordOwnerUserId = reference.recordOwnerUserId !== 0 ? reference.recordOwnerUserId : reference.senderUserId;
47420
+ if (recordOwnerUserId === 0) throw new ArkmePluginError("message-snapshot-owner-missing", "快记记录归属暂时无法确认,请刷新后重试", true, 502);
47190
47421
  if (reference.sourceKind === "record") {
47191
47422
  if (!isRecordSource) throw new ArkmePluginError("message-snapshot-record-invalid", "快记记录身份与当前来源不匹配,请刷新后重试", false, 400);
47192
47423
  return this.hydratedMessageSnapshotDetail(reference, session, options);
@@ -47408,20 +47639,24 @@ var ChatService = class {
47408
47639
  }
47409
47640
  async projectChatTimelineItems(rawItems, source, session, signal) {
47410
47641
  const items = [];
47642
+ const renderableItems = rawItems.filter((raw) => numberValue$19(objectValue$2(objectValue$2(raw).record).status) === 1);
47643
+ const botProfiles = await this.timelineBotDisplayProfiles(renderableItems, session, source.ownerRef, source.kind, signal);
47411
47644
  const senderUserIdByIndex = /* @__PURE__ */ new Map();
47412
47645
  const signingKey = await this.runtime.stateStore.uniqueCode();
47413
- for (const raw of rawItems) {
47646
+ for (const raw of renderableItems) {
47414
47647
  const item = objectValue$2(raw);
47415
- const relation = objectValue$2(item.relation);
47648
+ const relation = timelineSenderRelation(item);
47416
47649
  const record = objectValue$2(item.record);
47417
47650
  const payload = objectValue$2(record.payload);
47418
47651
  const recordStatus = numberValue$19(record.status);
47419
- if (recordStatus !== 1) continue;
47420
47652
  const uid = stringValue$2(relation.record_uid ?? payload.record_uid).trim();
47421
47653
  if (uid === "") continue;
47422
47654
  const relationUid = stringValue$2(relation.rel_uid).trim();
47423
47655
  const senderUserId = Math.trunc(numberValue$19(relation.sender_user_id));
47656
+ const isBot = timelineSenderIsBot(relation);
47657
+ const isMe = !isBot && senderUserId === session.userId;
47424
47658
  const recordOwnerUserId = chatRecordOwnerUserId(relation, record, payload, senderUserId);
47659
+ const quickNoteDetailsSupported = chatTimelineItemSupportsQuickNoteDetails(relation, recordOwnerUserId);
47425
47660
  const aiPolish = this.aiPolish.timelineAiPolish(record, payload);
47426
47661
  const sendAtMillis = numberValue$19(relation.attach_at ?? payload.send_at);
47427
47662
  const forwardRecords = await this.chatForwardRecordsPreview(item, session.userId, sendAtMillis);
@@ -47436,10 +47671,10 @@ var ChatService = class {
47436
47671
  signingKey
47437
47672
  });
47438
47673
  const rawAgentSource = timelineAgentSource(relation, record, payload);
47439
- const agentSource = senderUserId === session.userId ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
47674
+ const agentSource = isMe ? this.arko.currentUserAgentSourceFallback(session.userId, rawAgentSource) : rawAgentSource;
47440
47675
  const contentBlocks = this.media.richContentBlocks(item, session.userId);
47441
- const extensionProjection = this.timelineExtensionProjection(item, session.userId);
47442
- const senderName = stringValue$2(relation.display_name_snapshot).trim() || "Arkme用户";
47676
+ const extensionProjection = this.timelineExtensionProjection(item, session.userId, botProfiles);
47677
+ const senderName = this.timelineSenderName(relation, botProfiles);
47443
47678
  const mentions = await this.timelineMentionTargets(record, payload, session.userId, source.ownerRef);
47444
47679
  const captureContext = timelineCaptureContext({
47445
47680
  ...record,
@@ -47461,7 +47696,7 @@ var ChatService = class {
47461
47696
  }, signingKey),
47462
47697
  itemUid: uid,
47463
47698
  ...relationUid === "" ? {} : { timelineItemKey: await this.source.chatTimelineItemKey(session.userId, source.ownerRef, relationUid) },
47464
- ...source.kind !== "group_chat" || relationUid === "" || senderUserId === session.userId ? {} : {
47699
+ ...source.kind !== "group_chat" || relationUid === "" || isMe ? {} : {
47465
47700
  messageRef: this.sealMessageRef(session.userId, source.ownerRef, relationUid, signingKey),
47466
47701
  messageWithdrawalRef: this.sealMessageWithdrawalRef(session.userId, source.ownerRef, relationUid, signingKey)
47467
47702
  },
@@ -47473,7 +47708,7 @@ var ChatService = class {
47473
47708
  relationUid,
47474
47709
  recordOwnerUserId,
47475
47710
  recordUid: uid,
47476
- senderUserId,
47711
+ senderUserId: recordOwnerId(relation.sender_user_id),
47477
47712
  senderName,
47478
47713
  title: stringValue$2(payload.title),
47479
47714
  textContent: stringValue$2(payload.text_content),
@@ -47484,10 +47719,13 @@ var ChatService = class {
47484
47719
  displayKind: numberValue$19(payload.display_kind),
47485
47720
  contentBlocks
47486
47721
  }), signingKey) },
47487
- ...senderUserId > 0 ? { memberRef: await this.sealChatMemberRef(session.userId, source.ownerRef, senderUserId) } : {},
47722
+ ...quickNoteDetailsSupported ? {} : { quickNoteDetailsSupported: false },
47723
+ ...!isBot && senderUserId > 0 ? { memberRef: await this.sealChatMemberRef(session.userId, source.ownerRef, senderUserId) } : {},
47488
47724
  senderName,
47725
+ ...isBot ? { senderKind: "bot" } : {},
47726
+ ...isBot ? this.timelineBotAvatar(relation, botProfiles, session.userId) : {},
47489
47727
  ...agentSource === void 0 ? {} : { agentSource },
47490
- isMe: senderUserId === session.userId,
47728
+ isMe,
47491
47729
  ...callRecord === void 0 ? {} : { callRecord },
47492
47730
  ...mentions === void 0 ? {} : { mentions },
47493
47731
  sendAtMillis,
@@ -47511,7 +47749,7 @@ var ChatService = class {
47511
47749
  ...sharedRecording === void 0 ? {} : { sharedRecording },
47512
47750
  ...extensionProjection === void 0 ? {} : extensionProjection
47513
47751
  }) - 1;
47514
- if (senderUserId > 0) senderUserIdByIndex.set(itemIndex, senderUserId);
47752
+ if (!isBot && senderUserId > 0) senderUserIdByIndex.set(itemIndex, senderUserId);
47515
47753
  }
47516
47754
  try {
47517
47755
  const profiles = await this.profile.publicProfilesByUserIds([...new Set(senderUserIdByIndex.values())], session, signal);
@@ -47523,27 +47761,51 @@ var ChatService = class {
47523
47761
  this.hydrateTimelineExtensionParents(items);
47524
47762
  return items;
47525
47763
  }
47526
- timelineExtensionProjection(item, viewerUserId) {
47764
+ async timelineBotDisplayProfiles(rawItems, session, chatSessionUid, sourceKind, signal, knownBundle) {
47765
+ const botIds = new Set(rawItems.flatMap((raw) => {
47766
+ const item = objectValue$2(raw);
47767
+ if (numberValue$19(objectValue$2(item.record).status) !== 1) return [];
47768
+ const preview = objectValue$2(item.extension_parent_preview ?? item.extensionParentPreview);
47769
+ return [timelineSenderBotUid(timelineSenderRelation(item)), timelineSenderBotUid(timelineSenderRelation(preview))].filter(Boolean);
47770
+ }));
47771
+ return await this.senderProfiles.read({
47772
+ botUids: botIds,
47773
+ chatSessionUid,
47774
+ isGroup: sourceKind === "group_chat",
47775
+ ...knownBundle === void 0 ? {} : { snapshot: botDisplaySnapshot(knownBundle) }
47776
+ }, session, signal);
47777
+ }
47778
+ timelineSenderName(relation, profiles) {
47779
+ if (!timelineSenderIsBot(relation)) return stringValue$2(relation.display_name_snapshot ?? relation.displayNameSnapshot).trim() || "Arkme用户";
47780
+ return botDisplayName(profiles, timelineSenderBotUid(relation));
47781
+ }
47782
+ timelineBotAvatar(relation, profiles, viewerUserId) {
47783
+ const botUid = timelineSenderBotUid(relation);
47784
+ if (botUid === "") return {};
47785
+ const avatarUrl = stringValue$2(relation.sender_avatar_url).trim() || profiles.get(botUid)?.avatarUrl || "";
47786
+ return avatarUrl === "" ? {} : this.bot.botAvatarProjection({ avatar_url: avatarUrl }, viewerUserId, botUid);
47787
+ }
47788
+ timelineExtensionProjection(item, viewerUserId, botProfiles) {
47527
47789
  const edge = objectValue$2(item.extension_edge ?? item.extensionEdge);
47528
47790
  const preview = objectValue$2(item.extension_parent_preview ?? item.extensionParentPreview);
47529
- const previewRelation = objectValue$2(preview.relation);
47791
+ const previewRelation = timelineSenderRelation(preview);
47530
47792
  const previewPayload = objectValue$2(objectValue$2(preview.record).payload);
47531
47793
  const parentRecordUid = stringValue$2(edge.parent_record_uid ?? edge.parentRecordUid ?? previewRelation.record_uid ?? previewRelation.recordUid ?? previewPayload.record_uid ?? previewPayload.recordUid).trim();
47532
47794
  if (parentRecordUid === "") return void 0;
47533
47795
  const previewRecordUid = stringValue$2(previewRelation.record_uid ?? previewRelation.recordUid ?? previewPayload.record_uid ?? previewPayload.recordUid).trim();
47534
47796
  if (previewRecordUid === "" || previewRecordUid !== parentRecordUid) return { extensionParentRecordUid: parentRecordUid };
47535
47797
  const contentBlocks = this.media.richContentBlocks(preview, viewerUserId);
47536
- const recordOwnerUserId = Math.trunc(numberValue$19(previewRelation.record_owner_user_id ?? previewRelation.recordOwnerUserId ?? edge.parent_record_owner_user_id ?? edge.parentRecordOwnerUserId));
47798
+ const recordOwnerUserId = recordOwnerId(previewRelation.record_owner_user_id ?? previewRelation.recordOwnerUserId ?? edge.parent_record_owner_user_id ?? edge.parentRecordOwnerUserId);
47537
47799
  const sequence = Math.trunc(numberValue$19(previewRelation.seq));
47538
47800
  const sendAtMillis = numberValue$19(previewRelation.attach_at ?? previewRelation.attachAt);
47539
47801
  return {
47540
47802
  extensionParentRecordUid: parentRecordUid,
47541
47803
  extensionParent: {
47542
47804
  itemUid: parentRecordUid,
47543
- senderName: stringValue$2(previewRelation.display_name_snapshot ?? previewRelation.displayNameSnapshot).trim() || "Arkme用户",
47805
+ senderName: this.timelineSenderName(previewRelation, botProfiles),
47544
47806
  title: stringValue$2(previewPayload.title),
47545
47807
  textContent: stringValue$2(previewPayload.text_content ?? previewPayload.textContent),
47546
- ...recordOwnerUserId > 0 ? { recordOwnerUserId } : {},
47808
+ ...recordOwnerUserId !== 0 ? { recordOwnerUserId } : {},
47547
47809
  ...sequence > 0 ? { sequence } : {},
47548
47810
  ...sendAtMillis > 0 ? { sendAtMillis } : {},
47549
47811
  contentBlocks
@@ -47645,9 +47907,9 @@ var ChatService = class {
47645
47907
  sourceOwnerRef: input.sourceOwnerRef,
47646
47908
  chatSessionUid: input.chatSessionUid,
47647
47909
  relationUid: input.relationUid,
47648
- recordOwnerUserId: Math.trunc(input.recordOwnerUserId),
47910
+ recordOwnerUserId: recordOwnerId(input.recordOwnerUserId),
47649
47911
  recordUid: input.recordUid,
47650
- senderUserId: Math.trunc(input.senderUserId),
47912
+ senderUserId: recordOwnerId(input.senderUserId),
47651
47913
  senderName: input.senderName.trim() || "Arkme用户",
47652
47914
  title: arkmeEmojiTokenSafePrefix(input.title, 500, "codeUnits"),
47653
47915
  textContent: arkmeEmojiTokenSafePrefix(input.textContent, this.runtime.config.maxTextLength, "codeUnits"),
@@ -47816,9 +48078,9 @@ var ChatService = class {
47816
48078
  sourceOwnerRef: stringValue$2(parsed.sourceOwnerRef).trim(),
47817
48079
  chatSessionUid: stringValue$2(parsed.chatSessionUid).trim(),
47818
48080
  relationUid: stringValue$2(parsed.relationUid).trim(),
47819
- recordOwnerUserId: Math.trunc(numberValue$19(parsed.recordOwnerUserId)),
48081
+ recordOwnerUserId: recordOwnerId(parsed.recordOwnerUserId),
47820
48082
  recordUid: stringValue$2(parsed.recordUid).trim(),
47821
- senderUserId: Math.trunc(numberValue$19(parsed.senderUserId)),
48083
+ senderUserId: recordOwnerId(parsed.senderUserId),
47822
48084
  senderName: stringValue$2(parsed.senderName).trim() || "Arkme用户",
47823
48085
  title: stringValue$2(parsed.title),
47824
48086
  textContent: stringValue$2(parsed.textContent),
@@ -47843,7 +48105,7 @@ var ChatService = class {
47843
48105
  source_kind: reference.sourceKind,
47844
48106
  record_uid: reference.recordUid,
47845
48107
  source_type: reference.sourceKind === "chat_relation" ? "chat_record" : "record",
47846
- ...reference.recordOwnerUserId > 0 ? { owner_id: reference.recordOwnerUserId } : {},
48108
+ ...reference.recordOwnerUserId !== 0 ? { owner_id: reference.recordOwnerUserId } : {},
47847
48109
  ...reference.relationUid === "" ? {} : { source_rel_uid: reference.relationUid },
47848
48110
  ...reference.chatSessionUid === "" ? {} : { source_chat_session_uid: reference.chatSessionUid },
47849
48111
  ...reference.sourceSequence > 0 ? { source_seq: reference.sourceSequence } : {},
@@ -79559,6 +79821,29 @@ var ArkmeLinkMetadataService = class {
79559
79821
  }
79560
79822
  };
79561
79823
  //#endregion
79824
+ //#region src/services/dynamic-photo.ts
79825
+ /** Record-owned logical pairing; unrelated videos and background sound never participate. */
79826
+ function recordDynamicPhotoGroups(refs) {
79827
+ const covers = /* @__PURE__ */ new Map();
79828
+ const motions = /* @__PURE__ */ new Map();
79829
+ for (const ref of refs) {
79830
+ if (ref.content_file_role === 4) continue;
79831
+ const metadata = objectValue$2(ref.dynamic_photo);
79832
+ const uid = stringValue$2(metadata.logical_uid).trim();
79833
+ if (uid === "") continue;
79834
+ const target = metadata.role === "cover" && (ref.render_role === 1 || ref.render_role === 2) ? covers : metadata.role === "motion" && ref.render_role === 4 ? motions : void 0;
79835
+ if (target !== void 0) target.set(uid, [...target.get(uid) ?? [], ref]);
79836
+ }
79837
+ return [...covers].flatMap(([logicalUid, coverRefs]) => coverRefs.map((cover) => ({
79838
+ logicalUid,
79839
+ cover,
79840
+ motion: coverRefs.length === 1 && motions.get(logicalUid)?.length === 1 ? motions.get(logicalUid)[0] : void 0
79841
+ })));
79842
+ }
79843
+ function isRecordDynamicPhotoMotion(ref) {
79844
+ return ref.content_file_role !== 4 && ref.render_role === 4 && objectValue$2(ref.dynamic_photo).role === "motion";
79845
+ }
79846
+ //#endregion
79562
79847
  //#region src/services/media-service.ts
79563
79848
  const MAX_ARKME_IMAGE_BYTES = 2 * 1024 * 1024;
79564
79849
  const MAX_ARKME_PROFILE_IMAGE_BYTES = 8 * 1024 * 1024;
@@ -80434,7 +80719,7 @@ var MediaService = class {
80434
80719
  /** A current Record version does not imply that every media URL was resolved. */
80435
80720
  recordMediaUnavailable(raw, blocks) {
80436
80721
  if (this.runtime.config.richMediaRenderEnabled === false) return false;
80437
- const displayed = new Set(blocks.map((block) => block.fileAssetUid));
80722
+ const displayed = new Set(blocks.flatMap((block) => [block.fileAssetUid, block.dynamicPhoto?.motion?.fileAssetUid]));
80438
80723
  return this.recordMediaRefs(raw).some((ref) => !displayed.has(stringValue$2(ref.file_asset_uid).trim()));
80439
80724
  }
80440
80725
  richContentBlocks(raw, viewerUserId, hydratedDisplayItems = []) {
@@ -80457,10 +80742,11 @@ var MediaService = class {
80457
80742
  if (uid !== "") displayByAsset.set(uid, item);
80458
80743
  }
80459
80744
  const mediaRefs = this.recordMediaRefs(raw);
80460
- return (mediaRefs.length > 0 ? mediaRefs.map((ref) => ({
80745
+ const candidates = mediaRefs.length > 0 ? mediaRefs.map((ref) => ({
80461
80746
  ...displayByAsset.get(stringValue$2(ref.file_asset_uid).trim()) ?? {},
80462
80747
  ...ref
80463
- })) : displayItems).filter((item) => {
80748
+ })) : displayItems;
80749
+ const projected = candidates.filter((item) => {
80464
80750
  return Math.trunc(numberValue$12(item.content_file_role)) !== RECORD_CONTENT_FILE_ROLE_BACKGROUND_SOUND;
80465
80751
  }).flatMap((item, index) => {
80466
80752
  const fileAssetUid = stringValue$2(item.file_asset_uid).trim();
@@ -80495,7 +80781,23 @@ var MediaService = class {
80495
80781
  sortOrder: Math.trunc(numberValue$12(item.sort_order ?? index)),
80496
80782
  ...[1, 3].includes(Math.trunc(numberValue$12(item.render_role))) ? { renderRole: Math.trunc(numberValue$12(item.render_role)) } : {}
80497
80783
  }];
80498
- }).sort((left, right) => left.sortOrder - right.sortOrder);
80784
+ });
80785
+ const byAsset = new Map(projected.map((block) => [block.fileAssetUid, block]));
80786
+ for (const group of recordDynamicPhotoGroups(candidates)) {
80787
+ const cover = byAsset.get(stringValue$2(group.cover.file_asset_uid));
80788
+ if (cover?.kind !== "image") continue;
80789
+ const motion = group.motion === void 0 ? void 0 : byAsset.get(stringValue$2(group.motion.file_asset_uid));
80790
+ cover.dynamicPhoto = {
80791
+ logicalUid: group.logicalUid,
80792
+ ...group.motion === void 0 ? {} : { motionFileAssetUid: stringValue$2(group.motion.file_asset_uid) },
80793
+ ...motion?.kind === "video" ? { motion: {
80794
+ ...motion,
80795
+ kind: "video"
80796
+ } } : {}
80797
+ };
80798
+ }
80799
+ const motionAssets = new Set(candidates.filter(isRecordDynamicPhotoMotion).map((item) => stringValue$2(item.file_asset_uid)));
80800
+ return projected.filter((block) => !motionAssets.has(block.fileAssetUid ?? "")).sort((left, right) => left.sortOrder - right.sortOrder);
80499
80801
  }
80500
80802
  async ossCredentials(session, signal) {
80501
80803
  const credentials = await this.runtime.authenticatedAuthGet(`/api/v1/synch/get/sts-credentials?md_5_user_id=${encodeURIComponent(md5Text(String(session.userId)))}`, session, signal);
@@ -81845,17 +82147,16 @@ function recordUid(raw) {
81845
82147
  function recordOwnerUserId(raw) {
81846
82148
  const item = objectValue$2(raw);
81847
82149
  const core = recordCore(raw);
81848
- return firstPositiveInteger(item, [
81849
- "record_owner_user_id",
81850
- "recordOwnerUserId",
81851
- "owner_user_id",
81852
- "ownerUserId"
81853
- ]) || firstPositiveInteger(core, [
82150
+ for (const source of [item, core]) for (const key of [
81854
82151
  "record_owner_user_id",
81855
82152
  "recordOwnerUserId",
81856
82153
  "owner_user_id",
81857
82154
  "ownerUserId"
81858
- ]);
82155
+ ]) {
82156
+ const owner = recordOwnerId(source[key]);
82157
+ if (owner !== 0) return owner;
82158
+ }
82159
+ return 0;
81859
82160
  }
81860
82161
  function matchesRecordIdentity(raw, expectedUid, expectedOwnerUserId) {
81861
82162
  const uidKeys = [
@@ -81873,7 +82174,7 @@ function matchesRecordIdentity(raw, expectedUid, expectedOwnerUserId) {
81873
82174
  ];
81874
82175
  const identities = [objectValue$2(raw), recordCore(raw)];
81875
82176
  const uids = identities.flatMap((item) => uidKeys.map((key) => stringValue$2(item[key]).trim()).filter((value) => value !== ""));
81876
- const ownerUserIds = identities.flatMap((item) => ownerKeys.map((key) => Math.trunc(numberValue$7(item[key]))).filter((value) => Number.isSafeInteger(value) && value > 0));
82177
+ const ownerUserIds = identities.flatMap((item) => ownerKeys.map((key) => recordOwnerId(item[key])).filter((value) => value !== 0));
81877
82178
  return uids.length > 0 && ownerUserIds.length > 0 && uids.every((value) => value === expectedUid) && ownerUserIds.every((value) => value === expectedOwnerUserId);
81878
82179
  }
81879
82180
  function millisValue(value) {
@@ -81889,13 +82190,13 @@ function descriptorFromRaw(raw) {
81889
82190
  const core = recordCore(raw);
81890
82191
  const uid = recordUid(raw);
81891
82192
  const ownerUserId = recordOwnerUserId(raw);
81892
- if (uid === "" || ownerUserId <= 0) return void 0;
82193
+ if (uid === "" || ownerUserId === 0) return void 0;
81893
82194
  const authorUserId = firstPositiveInteger(item, [
81894
82195
  "author_user_id",
81895
82196
  "authorUserId",
81896
82197
  "creator_user_id",
81897
82198
  "creatorUserId"
81898
- ]) || firstPositiveInteger(core, ["creator_user_id", "creatorUserId"]) || ownerUserId;
82199
+ ]) || firstPositiveInteger(core, ["creator_user_id", "creatorUserId"]) || (typeof ownerUserId === "number" ? ownerUserId : 0);
81899
82200
  const senderName = firstString(item, [
81900
82201
  "author_name",
81901
82202
  "authorName",
@@ -81969,7 +82270,7 @@ var RelatedQuickNoteService = class {
81969
82270
  }
81970
82271
  }
81971
82272
  const descriptors = this.descriptorsFromItems(candidates.flatMap((candidate) => descriptorFromRaw(candidate.raw) === void 0 ? hydratedByUid.has(candidate.uid) ? [hydratedByUid.get(candidate.uid)] : [] : [candidate.raw]), excluded, lockedRecordUids);
81972
- const profiles = await this.profile.publicProfileSummariesByUserIds(descriptors.map((item) => item.authorUserId), session, signal).catch(() => /* @__PURE__ */ new Map());
82273
+ const profiles = await this.profile.publicProfileSummariesByUserIds(descriptors.map((item) => item.authorUserId).filter((id) => id > 0), session, signal).catch(() => /* @__PURE__ */ new Map());
81973
82274
  const items = [];
81974
82275
  for (const descriptor of descriptors) {
81975
82276
  const profile = profiles.get(descriptor.authorUserId);
@@ -82042,7 +82343,7 @@ var RelatedQuickNoteService = class {
82042
82343
  const sourceOwnerRef = locator.sourceOwnerRef.trim();
82043
82344
  const recordUidValue = locator.recordUid.trim();
82044
82345
  const chatSessionUid = locator.chatSessionUid.trim();
82045
- if (locator.viewerUserId !== expectedViewerUserId || sourceRef === "" || sourceOwnerRef === "" || recordUidValue === "" || !Number.isSafeInteger(locator.recordOwnerUserId) || locator.recordOwnerUserId <= 0 || locator.contextType !== "record" && locator.contextType !== "chat" || locator.contextType === "chat" && chatSessionUid === "") throw new ArkmePluginError("related-quick-note-source-invalid", "当前快记来源无效,请刷新后重试", false, 400);
82346
+ if (locator.viewerUserId !== expectedViewerUserId || sourceRef === "" || sourceOwnerRef === "" || recordUidValue === "" || recordOwnerId(locator.recordOwnerUserId) === 0 || locator.contextType !== "record" && locator.contextType !== "chat" || locator.contextType === "chat" && chatSessionUid === "") throw new ArkmePluginError("related-quick-note-source-invalid", "当前快记来源无效,请刷新后重试", false, 400);
82046
82347
  return {
82047
82348
  ...locator,
82048
82349
  sourceRef,
@@ -85360,10 +85661,10 @@ var SearchService = class {
85360
85661
  const linkMatch = textContent.match(/https:\/\/[^\s<>()]+/u);
85361
85662
  const sourceTitle = stringValue$2(topic.title ?? chat.title).trim();
85362
85663
  const creationSource = Math.trunc(numberValue$2(core.creation_source ?? item.creation_source));
85363
- const recordOwnerUserId = numberValue$2(core.owner_user_id);
85664
+ const recordOwnerUserId = recordOwnerId(core.owner_user_id);
85364
85665
  return {
85365
85666
  recordUid,
85366
- ...Number.isSafeInteger(recordOwnerUserId) && recordOwnerUserId > 0 ? { recordOwnerUserId } : {},
85667
+ ...recordOwnerUserId !== 0 ? { recordOwnerUserId } : {},
85367
85668
  sourceKind: Math.trunc(numberValue$2(item.source_kind)),
85368
85669
  ...stringValue$2(item.source_uid).trim() === "" ? {} : { sourceUid: stringValue$2(item.source_uid).trim() },
85369
85670
  routeTargetKind: stringValue$2(item.route_target_kind).trim(),
@@ -89074,7 +89375,7 @@ var ArkmeService = class {
89074
89375
  this.interwoven = new InterwovenService(this.runtime, this.source, this.profile);
89075
89376
  this.linkMetadata = new ArkmeLinkMetadataService(linkDocumentReader);
89076
89377
  this.aiPolish = new GroupAiPolishService(this.runtime, this.source, { sendChatSourceTextRaw: async (...args) => await this.chat.sendChatSourceTextRaw(...args) });
89077
- this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (data, session, chatSessionUid, sourceKind) => await this.chat.chatTimelineItems(data, session, chatSessionUid, sourceKind) });
89378
+ this.realtime = new ChatRealtimeService(this.runtime, this.source, { chatTimelineItems: async (...args) => await this.chat.chatTimelineItems(...args) });
89078
89379
  this.conversationDirectoryVisibility = new ConversationDirectoryVisibilityService(new ConversationListPreferenceService(this.runtime), this.source, this.bot, this.realtime);
89079
89380
  this.directory = new ConversationDirectoryService(this.runtime, this.source, this.conversationDirectoryVisibility, async (signal) => await this.botConversation.directory({ signal }), async (ref, signal) => await this.media.readImage(ref, {
89080
89381
  signal,
@@ -92029,4 +92330,4 @@ async function invokePersistentArkmeExtension(extensionId, method, args) {
92029
92330
  return jsonValue(await handler(jsonValue(args)));
92030
92331
  }
92031
92332
  //#endregion
92032
- export { ARKME_DEFAULT_SHARE_WEBSITE as $, inspectNativeBundleArtifactV3 as A, __commonJSMin as At, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, MAX_RECORDING_IMPORT_BYTES as Ct, bundleSha256 as D, ArkmePluginError as Dt, arkmeSandboxEntryId as E, normalizeRecentEmojiIds as Et, assertExtensionArtifactSize as F, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, arkmeRequiredLinkMetadataFallback as J, arkmeExtensionShareRefFromLink as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, __exportAll as Mt, readNativeBundleTarballV3 as N, __require as Nt, canonicalBundleJson as O, retryAfterMillis as Ot, renderArkmeSandboxHostEntry as P, __toCommonJS as Pt, parseArkmeRecordReeditAttachments as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, sameRecordingImportIdentity as St, ArkmeBundleArtifactError as T, isRecentEmojiId as Tt, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, arkmeRichBackgroundSound as X, arkmeFileBackgroundSound as Y, ArkmeRecordReeditDraftConflict as Z, arkmeClientContentDigest as _, SecretValue as _t, persistentArkmeExtensionActive as a, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as at, verifyExtensionResolutionSignature as b, isUnresolvedRecordingImportJob as bt, persistentExtensionBundleDirectory as c, mergeMemberJoinEvents as ct, readPersistentExtensionActivation as d, ARKME_TOOL_FILE_MAX_BYTES as dt, ARKME_PROVIDER_CONTRACT_VERSION as et, refreshPersistentClientWrapper as f, arkmeCanInlineLocalFile as ft, activeProfileExtensionOwnerConflicts as g, ArkmeOutgoingCallError as gt, renderArkmeBundleClientBundle as h, isArkmeBotAvatarRef as ht, invokePersistentArkmeExtension as i, ARKME_WORLD_PUBLISH_MAX_IMAGES as it, packBundleFiles as j, __esmMin as jt, inspectBundleArtifact as k, createDesktopLifecycleReader as kt, persistentExtensionPackageIdentity as l, validateMemberUpdate as lt, writePersistentExtensionActivation as m, arkmePickedFileKind as mt, applyPersistentArkmeHostExtension as n, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as nt, persistentArkmeExtensionRuntimeState as o, applyMemberUpdate as ot, repairPersistentExtensionInstallation as p, arkmeNormalizedFileMimeType as pt, arkmeIsGenericLinkMetadataTitle as q, deactivatePersistentArkmeExtension as r, ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as rt, materializePersistentExtensionBundle as s, cachedMemberItem as st, activatePersistentArkmeExtension as t, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as tt, quarantinePersistentExtension as u, ARKME_RUNTIME_INSTANCE_ID as ut, arkmeClientContentDigestFromSource as v, ArkmeDirectMessageAdmissionError as vt, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, recordingImportFileNameKey as wt, verifyNativeBundleResolutionSignature as x, recordingImportFileKind as xt, verifyBundleResolutionSignature as y, isRecordingInstantOnOrAfterUnixEpoch as yt, ARKME_EXTENSION_FORMAT as z };
92333
+ export { ARKME_DEFAULT_SHARE_WEBSITE as $, inspectNativeBundleArtifactV3 as A, createDesktopLifecycleReader as At, ARKME_EXTENSION_ICON_MAX_BYTES as B, ARKME_BUNDLE_MAX_BYTES as C, MAX_RECORDING_IMPORT_BYTES as Ct, bundleSha256 as D, ArkmePluginError as Dt, arkmeSandboxEntryId as E, normalizeRecentEmojiIds as Et, assertExtensionArtifactSize as F, __toCommonJS as Ft, validateVoiceprintPcm16Wav as G, ARKME_EXTENSION_PREVIEW_MAX_BYTES as H, canonicalExtensionJson as I, arkmeRequiredLinkMetadataFallback as J, arkmeExtensionShareRefFromLink as K, sha256Hex as L, packLocalNativeBundleDirectoryV3 as M, __esmMin as Mt, readNativeBundleTarballV3 as N, __exportAll as Nt, canonicalBundleJson as O, retryAfterMillis as Ot, renderArkmeSandboxHostEntry as P, __require as Pt, parseArkmeRecordReeditAttachments as Q, unpackArkmeExtension as R, ARKME_BUNDLE_ARTIFACT_KIND as S, sameRecordingImportIdentity as St, ArkmeBundleArtifactError as T, isRecentEmojiId as Tt, ARKME_EXTENSION_RUNTIME_UNAVAILABLE_MESSAGE as U, ARKME_EXTENSION_MAX_BYTES as V, ArkmeService as W, arkmeRichBackgroundSound as X, arkmeFileBackgroundSound as Y, ArkmeRecordReeditDraftConflict as Z, arkmeClientContentDigest as _, SecretValue as _t, persistentArkmeExtensionActive as a, ARKME_WORLD_PUBLISH_MAX_IMAGE_BYTES as at, verifyExtensionResolutionSignature as b, isUnresolvedRecordingImportJob as bt, persistentExtensionBundleDirectory as c, mergeMemberJoinEvents as ct, readPersistentExtensionActivation as d, ARKME_TOOL_FILE_MAX_BYTES as dt, ARKME_PROVIDER_CONTRACT_VERSION as et, refreshPersistentClientWrapper as f, arkmeCanInlineLocalFile as ft, activeProfileExtensionOwnerConflicts as g, ArkmeOutgoingCallError as gt, renderArkmeBundleClientBundle as h, isArkmeBotAvatarRef as ht, invokePersistentArkmeExtension as i, ARKME_WORLD_PUBLISH_MAX_IMAGES as it, packBundleFiles as j, __commonJSMin as jt, inspectBundleArtifact as k, recordOwnerId as kt, persistentExtensionPackageIdentity as l, validateMemberUpdate as lt, writePersistentExtensionActivation as m, arkmePickedFileKind as mt, applyPersistentArkmeHostExtension as n, ARKME_VOICEPRINT_ENROLLMENT_MAX_DURATION_MS as nt, persistentArkmeExtensionRuntimeState as o, applyMemberUpdate as ot, repairPersistentExtensionInstallation as p, arkmeNormalizedFileMimeType as pt, arkmeIsGenericLinkMetadataTitle as q, deactivatePersistentArkmeExtension as r, ARKME_VOICEPRINT_ENROLLMENT_MIN_DURATION_MS as rt, materializePersistentExtensionBundle as s, cachedMemberItem as st, activatePersistentArkmeExtension as t, ARKME_VOICEPRINT_ENROLLMENT_MAX_AUDIO_BYTES as tt, quarantinePersistentExtension as u, ARKME_RUNTIME_INSTANCE_ID as ut, arkmeClientContentDigestFromSource as v, ArkmeDirectMessageAdmissionError as vt, ARKME_NATIVE_BUNDLE_ARTIFACT_KIND as w, recordingImportFileNameKey as wt, verifyNativeBundleResolutionSignature as x, recordingImportFileKind as xt, verifyBundleResolutionSignature as y, isRecordingInstantOnOrAfterUnixEpoch as yt, ARKME_EXTENSION_FORMAT as z };