@senguoyun/dsh-arkme 0.1.85 → 0.1.86
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/docs/self-role-avatar-cache.md +59 -0
- package/lib/client.js +460 -116
- package/lib/client.js.map +1 -1
- package/lib/harness-model-client.js +14 -0
- package/lib/harness-native-selection-client.js +14 -0
- package/lib/harness-sidebar-client.js +14 -0
- package/lib/index.js +22 -9
- package/lib/persistent-extension.js +1 -1
- package/lib/{persistent-runtime-adGFmkIa.js → persistent-runtime-DfmCJcxH.js} +412 -121
- package/lib/sdk.js +14 -0
- package/lib/types/arkme-service.d.ts +1 -0
- package/lib/types/arkme-service.d.ts.map +1 -1
- package/lib/types/client/ArkmeSelfRolePicker.d.ts.map +1 -1
- package/lib/types/client/ArkmeSidebar.d.ts.map +1 -1
- package/lib/types/client/avatar-image-runtime.d.ts.map +1 -1
- package/lib/types/client/avatar-image-store.d.ts +10 -0
- package/lib/types/client/avatar-image-store.d.ts.map +1 -1
- package/lib/types/client/avatar-persistent-cache.d.ts +16 -0
- package/lib/types/client/avatar-persistent-cache.d.ts.map +1 -0
- package/lib/types/client/reaction-notifications.d.ts.map +1 -1
- package/lib/types/client/reaction-preview-store.d.ts +4 -1
- package/lib/types/client/reaction-preview-store.d.ts.map +1 -1
- package/lib/types/client/reaction-transport.d.ts +4 -0
- package/lib/types/client/reaction-transport.d.ts.map +1 -0
- package/lib/types/client/realtime-client-events.d.ts.map +1 -1
- package/lib/types/host-api.d.ts.map +1 -1
- package/lib/types/local-database.d.ts +1 -0
- package/lib/types/local-database.d.ts.map +1 -1
- package/lib/types/reaction-contract.d.ts +4 -0
- package/lib/types/reaction-contract.d.ts.map +1 -1
- package/lib/types/reaction-diagnostics.d.ts +4 -0
- package/lib/types/reaction-diagnostics.d.ts.map +1 -0
- package/lib/types/reaction-host-diagnostics.d.ts +6 -0
- package/lib/types/reaction-host-diagnostics.d.ts.map +1 -0
- package/lib/types/request-coordinator.d.ts +2 -0
- package/lib/types/request-coordinator.d.ts.map +1 -1
- package/lib/types/sdk/index.d.ts +2 -0
- package/lib/types/sdk/index.d.ts.map +1 -1
- package/lib/types/services/chat-service.d.ts +1 -0
- package/lib/types/services/chat-service.d.ts.map +1 -1
- package/lib/types/services/media-service.d.ts +14 -0
- package/lib/types/services/media-service.d.ts.map +1 -1
- package/lib/types/services/profile-service.d.ts.map +1 -1
- package/lib/types/services/reaction-service.d.ts.map +1 -1
- package/lib/types/services/service.d.ts +1 -0
- package/lib/types/services/service.d.ts.map +1 -1
- package/lib/types/tools/business/media/read-image.d.ts.map +1 -1
- package/lib/types/tools/ports/media.d.ts +1 -0
- package/lib/types/tools/ports/media.d.ts.map +1 -1
- package/lib/types/types.d.ts +1 -0
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -10,6 +10,7 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
|
10
10
|
import { access, chmod as chmod$1, copyFile, lstat, mkdir, mkdtemp, open, opendir, readFile, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
11
11
|
import { fileURLToPath, fileURLToPath as urlToPath } from "node:url";
|
|
12
12
|
import { request } from "node:http";
|
|
13
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
14
|
import { homedir, hostname, platform, tmpdir } from "node:os";
|
|
14
15
|
import { request as request$1 } from "node:https";
|
|
15
16
|
import { BlockList, isIP } from "node:net";
|
|
@@ -537,6 +538,56 @@ function stringifyOwnerJson(value) {
|
|
|
537
538
|
});
|
|
538
539
|
}
|
|
539
540
|
//#endregion
|
|
541
|
+
//#region src/reaction-diagnostics.ts
|
|
542
|
+
/** Timing metadata only: no credentials, message refs, names or message bodies. */
|
|
543
|
+
function reactionTraceId(value) {
|
|
544
|
+
return value && /^[a-f0-9-]{36}$/.test(value) ? value : globalThis.crypto.randomUUID();
|
|
545
|
+
}
|
|
546
|
+
function logReactionTiming(traceId, stage, started, detail = {}) {
|
|
547
|
+
console.info(`[ArkmeReactionTiming] ${JSON.stringify({
|
|
548
|
+
traceId,
|
|
549
|
+
stage,
|
|
550
|
+
at: Date.now(),
|
|
551
|
+
durationMs: Math.round((performance.now() - started) * 100) / 100,
|
|
552
|
+
...detail
|
|
553
|
+
})}`);
|
|
554
|
+
}
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/reaction-host-diagnostics.ts
|
|
557
|
+
const context = new AsyncLocalStorage();
|
|
558
|
+
function bindReactionTrace(work) {
|
|
559
|
+
return AsyncLocalStorage.bind(work);
|
|
560
|
+
}
|
|
561
|
+
async function withReactionTrace(input, work) {
|
|
562
|
+
if (process.env.ARKME_REACTION_DIAGNOSTICS !== "1") return await work();
|
|
563
|
+
return await context.run(reactionTraceId(input.traceId), () => measureReaction("host-total", { action: input.action }, work));
|
|
564
|
+
}
|
|
565
|
+
async function measureReaction(stage, detail, work) {
|
|
566
|
+
const traceId = context.getStore();
|
|
567
|
+
if (!traceId) return await work();
|
|
568
|
+
const started = performance.now();
|
|
569
|
+
let ok = false;
|
|
570
|
+
try {
|
|
571
|
+
const result = await work();
|
|
572
|
+
ok = true;
|
|
573
|
+
return result;
|
|
574
|
+
} finally {
|
|
575
|
+
logReactionTiming(traceId, stage, started, {
|
|
576
|
+
...detail,
|
|
577
|
+
ok
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function reactionQueueObserver(route) {
|
|
582
|
+
const traceId = context.getStore();
|
|
583
|
+
if (!traceId) return void 0;
|
|
584
|
+
const started = performance.now();
|
|
585
|
+
return (event, detail) => logReactionTiming(traceId, `queue-${event}`, started, {
|
|
586
|
+
route,
|
|
587
|
+
...detail
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
//#endregion
|
|
540
591
|
//#region src/http-retry-after.ts
|
|
541
592
|
/** HTTP delta-seconds and HTTP-date share one parser; never shorten a server hint. */
|
|
542
593
|
function retryAfterMillis(value) {
|
|
@@ -723,6 +774,10 @@ var ArkmeRequestCoordinator = class {
|
|
|
723
774
|
const existing = this.inFlight.get(fullKey);
|
|
724
775
|
if (existing !== void 0 && existing.epoch === epoch && !existing.controller.signal.aborted) {
|
|
725
776
|
this.bump(request, "joined");
|
|
777
|
+
request.onQueue?.("join", {
|
|
778
|
+
lane: request.lane,
|
|
779
|
+
service: request.service
|
|
780
|
+
});
|
|
726
781
|
return await this.joinFlight(existing, request.signal).then(clone);
|
|
727
782
|
}
|
|
728
783
|
const controller = new AbortController();
|
|
@@ -825,7 +880,7 @@ var ArkmeRequestCoordinator = class {
|
|
|
825
880
|
const deadline = recovery === void 0 ? void 0 : setTimeout(() => controller.abort(recovery.timeout()), recovery.deadlineMs);
|
|
826
881
|
try {
|
|
827
882
|
while (true) {
|
|
828
|
-
release = await this.acquire(request.lane, request.service, controller.signal, route);
|
|
883
|
+
release = await this.acquire(request.lane, request.service, controller.signal, route, request.onQueue);
|
|
829
884
|
this.bump(request, "started");
|
|
830
885
|
if (controller.signal.aborted) throw abortError(controller.signal.reason);
|
|
831
886
|
if (this.epoch(scope) !== epoch) throw new ArkmeStaleRequestError();
|
|
@@ -902,7 +957,7 @@ var ArkmeRequestCoordinator = class {
|
|
|
902
957
|
});
|
|
903
958
|
});
|
|
904
959
|
}
|
|
905
|
-
acquire(lane, service, signal, route) {
|
|
960
|
+
acquire(lane, service, signal, route, onQueue) {
|
|
906
961
|
if (signal.aborted) return Promise.reject(abortError(signal.reason));
|
|
907
962
|
const laneLimit = this.laneLimits.get(lane);
|
|
908
963
|
const serviceLimit = this.serviceLimits.get(service);
|
|
@@ -928,6 +983,12 @@ var ArkmeRequestCoordinator = class {
|
|
|
928
983
|
const index = this.queue.indexOf(queued);
|
|
929
984
|
if (index >= 0) this.queue.splice(index, 1);
|
|
930
985
|
signal.removeEventListener("abort", abort);
|
|
986
|
+
onQueue?.("cancel", {
|
|
987
|
+
lane,
|
|
988
|
+
service,
|
|
989
|
+
queueWaitMs: this.now() - queued.queuedAt,
|
|
990
|
+
blockedBy: [...queued.blockedBy ?? []].join(",")
|
|
991
|
+
});
|
|
931
992
|
reject(abortError(signal.reason));
|
|
932
993
|
};
|
|
933
994
|
queued = {
|
|
@@ -938,10 +999,24 @@ var ArkmeRequestCoordinator = class {
|
|
|
938
999
|
signal,
|
|
939
1000
|
resolve,
|
|
940
1001
|
reject,
|
|
941
|
-
abort
|
|
1002
|
+
abort,
|
|
1003
|
+
queuedAt: this.now(),
|
|
1004
|
+
...onQueue ? {
|
|
1005
|
+
onQueue,
|
|
1006
|
+
blockedBy: /* @__PURE__ */ new Set()
|
|
1007
|
+
} : {}
|
|
942
1008
|
};
|
|
943
1009
|
signal.addEventListener("abort", abort, { once: true });
|
|
944
1010
|
this.queue.push(queued);
|
|
1011
|
+
onQueue?.("enter", {
|
|
1012
|
+
lane,
|
|
1013
|
+
service,
|
|
1014
|
+
queueLength: this.queue.length,
|
|
1015
|
+
laneQueued,
|
|
1016
|
+
serviceQueued,
|
|
1017
|
+
laneActive: laneLimit.active,
|
|
1018
|
+
serviceActive: serviceLimit.active
|
|
1019
|
+
});
|
|
945
1020
|
this.queue.sort((left, right) => LANE_PRIORITY[left.lane] - LANE_PRIORITY[right.lane] || left.sequence - right.sequence);
|
|
946
1021
|
this.drain();
|
|
947
1022
|
});
|
|
@@ -969,6 +1044,13 @@ var ArkmeRequestCoordinator = class {
|
|
|
969
1044
|
queued.route.tokens = Math.max(0, queued.route.tokens - 1);
|
|
970
1045
|
queued.route.active += 1;
|
|
971
1046
|
}
|
|
1047
|
+
queued.onQueue?.("start", {
|
|
1048
|
+
lane: queued.lane,
|
|
1049
|
+
service: queued.service,
|
|
1050
|
+
queueWaitMs: this.now() - queued.queuedAt,
|
|
1051
|
+
queueLength: this.queue.length,
|
|
1052
|
+
blockedBy: [...queued.blockedBy ?? []].join(",")
|
|
1053
|
+
});
|
|
972
1054
|
let released = false;
|
|
973
1055
|
queued.resolve(() => {
|
|
974
1056
|
if (released) return;
|
|
@@ -993,17 +1075,23 @@ var ArkmeRequestCoordinator = class {
|
|
|
993
1075
|
}
|
|
994
1076
|
const lane = this.laneLimits.get(queued.lane);
|
|
995
1077
|
const service = this.serviceLimits.get(queued.service);
|
|
996
|
-
this.
|
|
997
|
-
this.
|
|
998
|
-
const
|
|
999
|
-
if (
|
|
1000
|
-
this.refill(route);
|
|
1001
|
-
if (route.active >= route.maxConcurrent || route.tokens < 1 || this.now() < route.cooldownUntil) continue;
|
|
1002
|
-
}
|
|
1003
|
-
if (lane.active < lane.maxConcurrent && service.active < service.maxConcurrent && this.now() >= lane.cooldownUntil && this.now() >= service.cooldownUntil && lane.tokens >= 1 && service.tokens >= 1) return index;
|
|
1078
|
+
const laneBlocked = this.blocked(lane, "lane", queued.blockedBy);
|
|
1079
|
+
const serviceBlocked = this.blocked(service, "service", queued.blockedBy);
|
|
1080
|
+
const routeBlocked = queued.route !== void 0 && this.blocked(queued.route, "route", queued.blockedBy);
|
|
1081
|
+
if (!laneBlocked && !serviceBlocked && !routeBlocked) return index;
|
|
1004
1082
|
}
|
|
1005
1083
|
return -1;
|
|
1006
1084
|
}
|
|
1085
|
+
blocked(limit, kind, reasons) {
|
|
1086
|
+
this.refill(limit);
|
|
1087
|
+
const concurrency = limit.active >= limit.maxConcurrent, rate = limit.tokens < 1, cooldown = this.now() < limit.cooldownUntil;
|
|
1088
|
+
if (reasons) {
|
|
1089
|
+
if (concurrency) reasons.add(`${kind}-concurrency`);
|
|
1090
|
+
if (rate) reasons.add(`${kind}-rate`);
|
|
1091
|
+
if (cooldown) reasons.add(`${kind}-cooldown`);
|
|
1092
|
+
}
|
|
1093
|
+
return concurrency || rate || cooldown;
|
|
1094
|
+
}
|
|
1007
1095
|
refill(limit) {
|
|
1008
1096
|
const now = this.now();
|
|
1009
1097
|
if (now <= limit.lastRefillAt) return;
|
|
@@ -1631,6 +1719,16 @@ var ServiceRuntime = class {
|
|
|
1631
1719
|
"/api/v1/chats/contacts/list",
|
|
1632
1720
|
"/api/v1/chats/common-group/query"
|
|
1633
1721
|
])).has(path)) return true;
|
|
1722
|
+
if (baseUrl === this.config.recordBaseUrl && (/* @__PURE__ */ new Set([
|
|
1723
|
+
"/api/v1/reactions/query",
|
|
1724
|
+
"/api/v1/reactions/notifications/query",
|
|
1725
|
+
"/api/v1/reactions/actors/query",
|
|
1726
|
+
"/api/v1/reactions/groups/query",
|
|
1727
|
+
"/api/v1/reactions/history/query",
|
|
1728
|
+
"/api/v1/reactions/received/query",
|
|
1729
|
+
"/api/v1/reactions/history-policy/query",
|
|
1730
|
+
"/api/v1/reactions/library/query"
|
|
1731
|
+
])).has(path)) return true;
|
|
1634
1732
|
if (baseUrl === this.config.botBaseUrl && path === "/api/v1/bot/list") return true;
|
|
1635
1733
|
if (baseUrl === this.config.audioBaseUrl && path === "/api/v1/audio/unmarked-speakers/list") return true;
|
|
1636
1734
|
return false;
|
|
@@ -1638,7 +1736,8 @@ var ServiceRuntime = class {
|
|
|
1638
1736
|
async post(baseUrl, path, body, bearer, successCodes, signal, preferDataError = false, options = {}, preserveHttpError = false, preserveForbiddenError = false) {
|
|
1639
1737
|
const read = this.registeredRead(baseUrl, path) && !(body instanceof FormData);
|
|
1640
1738
|
const route = `${baseUrl.replace(/\/+$/, "")}${path}`;
|
|
1641
|
-
|
|
1739
|
+
const onQueue = reactionQueueObserver(path);
|
|
1740
|
+
return await measureReaction("upstream-total", { route: path }, () => this.requestCoordinator.run({
|
|
1642
1741
|
scope: options.scope ?? "public",
|
|
1643
1742
|
lane: options.lane ?? "write",
|
|
1644
1743
|
service: options.service ?? this.requestService(baseUrl),
|
|
@@ -1649,7 +1748,7 @@ var ServiceRuntime = class {
|
|
|
1649
1748
|
...options.cancelWhenUnobserved === void 0 ? {} : { cancelWhenUnobserved: options.cancelWhenUnobserved },
|
|
1650
1749
|
...signal === void 0 ? {} : { signal },
|
|
1651
1750
|
...read ? {
|
|
1652
|
-
lane: options.lane === "background-read" ? "background-read" : "interactive-read",
|
|
1751
|
+
lane: options.lane === "background-read" || path === "/api/v1/reactions/notifications/query" ? "background-read" : "interactive-read",
|
|
1653
1752
|
route,
|
|
1654
1753
|
key: `owner-read:${route}:${stableReadParameters(body)}`,
|
|
1655
1754
|
cacheMs: 0,
|
|
@@ -1670,8 +1769,9 @@ var ServiceRuntime = class {
|
|
|
1670
1769
|
"login-expired"
|
|
1671
1770
|
].includes(error.code),
|
|
1672
1771
|
serviceCooldownMs: (error) => read || options.publishServiceCooldown === false ? 0 : this.remoteServiceCooldownMs(error),
|
|
1673
|
-
|
|
1674
|
-
|
|
1772
|
+
...onQueue ? { onQueue } : {},
|
|
1773
|
+
operation: bindReactionTrace(async (coordinatedSignal) => await measureReaction("upstream-http", { route: path }, () => this.postDirect(baseUrl, path, body, bearer, successCodes, coordinatedSignal, preferDataError, preserveHttpError, preserveForbiddenError)))
|
|
1774
|
+
})).catch((error) => {
|
|
1675
1775
|
if (options.trackWriteOutcome === true && error instanceof ArkmeRequestQueueOverflowError) throw new ArkmePluginError("arkme-request-queue-full", "发送请求较多,请稍后重试", true, 503, { cause: error });
|
|
1676
1776
|
if (options.trackWriteOutcome === true && error instanceof Error && error.name === "AbortError") throw new ArkmePluginError("arkme-request-aborted", "发送请求已取消", true, 409, { cause: error });
|
|
1677
1777
|
if (options.trackWriteOutcome === true && error instanceof ArkmePluginError && remoteWriteOutcomeUnknown(error)) throw withUnknownWriteOutcome(error);
|
|
@@ -1855,6 +1955,13 @@ var ServiceRuntime = class {
|
|
|
1855
1955
|
if (!(error instanceof ArkmePluginError) || !["auth-http-401", "auth-http-403"].includes(error.code)) throw error;
|
|
1856
1956
|
session = await this.refreshAccessToken(session);
|
|
1857
1957
|
return await this.post(this.config.recordBaseUrl, path, body, session.accessToken, [0], signal, false, requestOptions());
|
|
1958
|
+
} finally {
|
|
1959
|
+
if ([
|
|
1960
|
+
"/api/v1/reactions/set",
|
|
1961
|
+
"/api/v1/reactions/notifications/read",
|
|
1962
|
+
"/api/v1/reactions/history-policy/set",
|
|
1963
|
+
"/api/v1/reactions/library/set"
|
|
1964
|
+
].includes(path)) this.requestCoordinator.invalidateKey(this.requestScope(session.userId), `owner-read:${this.config.recordBaseUrl.replace(/\/+$/, "")}/api/v1/reactions/`);
|
|
1858
1965
|
}
|
|
1859
1966
|
}
|
|
1860
1967
|
async authenticatedCalendarPost(path, body, initialSession, signal, options = {}) {
|
|
@@ -2946,7 +3053,7 @@ var ReactionService = class {
|
|
|
2946
3053
|
const ids = /* @__PURE__ */ new Map();
|
|
2947
3054
|
const resolve = async (target) => {
|
|
2948
3055
|
if (!target || typeof target.id !== "string" || !target.id || target.id.length > 1024 || !(typeof target.worldRecordRef === "string" && !target.sourceRef && !target.messageActionRef || !target.worldRecordRef && typeof target.sourceRef === "string" && typeof target.messageActionRef === "string")) throw new ArkmePluginError("reaction-target-invalid", "表态目标无效", false);
|
|
2949
|
-
return await this.resolve(target);
|
|
3056
|
+
return await measureReaction("target-resolve", {}, () => this.resolve(target));
|
|
2950
3057
|
};
|
|
2951
3058
|
switch (input.action) {
|
|
2952
3059
|
case "notifications":
|
|
@@ -2965,6 +3072,7 @@ var ReactionService = class {
|
|
|
2965
3072
|
})) };
|
|
2966
3073
|
break;
|
|
2967
3074
|
case "query": {
|
|
3075
|
+
if (input.actorPresentation !== void 0 && input.actorPresentation !== "deferred") throw new ArkmePluginError("reaction-query-invalid", "表态展示选项无效", false);
|
|
2968
3076
|
if (!Array.isArray(input.targets) || input.targets.length < 1 || input.targets.length > 50) throw new ArkmePluginError("reaction-query-invalid", "表态查询数量无效", false);
|
|
2969
3077
|
const targets = [], seenIDs = /* @__PURE__ */ new Set();
|
|
2970
3078
|
for (const target of input.targets) {
|
|
@@ -2978,6 +3086,7 @@ var ReactionService = class {
|
|
|
2978
3086
|
targets.push(wire);
|
|
2979
3087
|
}
|
|
2980
3088
|
}
|
|
3089
|
+
targets.sort((left, right) => targetKey(left).localeCompare(targetKey(right)));
|
|
2981
3090
|
path = "query";
|
|
2982
3091
|
body = { targets };
|
|
2983
3092
|
break;
|
|
@@ -3060,7 +3169,7 @@ var ReactionService = class {
|
|
|
3060
3169
|
}
|
|
3061
3170
|
const before = await this.runtime.requireSession();
|
|
3062
3171
|
if (`${this.runtime.config.environment}:${before.userId}` !== accountKey) throw new ArkmePluginError("reaction-account-changed", "账号已变化", false);
|
|
3063
|
-
const result = objectValue$2(await this.runtime.authenticatedPost(`/api/v1/reactions/${path}`, body, session, signal));
|
|
3172
|
+
const result = objectValue$2(await measureReaction("reaction-owner", { action: input.action }, () => this.runtime.authenticatedPost(`/api/v1/reactions/${path}`, body, session, signal)));
|
|
3064
3173
|
const current = await this.runtime.requireSession();
|
|
3065
3174
|
if (signal?.aborted || `${this.runtime.config.environment}:${current.userId}` !== accountKey) throw new ArkmePluginError("reaction-account-changed", "账号已变化", false);
|
|
3066
3175
|
if (input.action === "query") {
|
|
@@ -52490,6 +52599,12 @@ var ChatService = class {
|
|
|
52490
52599
|
rel_uid: reference.relationUid
|
|
52491
52600
|
};
|
|
52492
52601
|
}
|
|
52602
|
+
async reactionActorReferences(sourceRef, userIds) {
|
|
52603
|
+
const session = await this.runtime.requireSession();
|
|
52604
|
+
const source = await this.source.openSourceRef(sourceRef, session.userId);
|
|
52605
|
+
if (source.kind !== "private_chat" && source.kind !== "group_chat") return /* @__PURE__ */ new Map();
|
|
52606
|
+
return new Map(await Promise.all([...new Set(userIds)].map(async (id) => [id, await this.sealChatMemberRef(session.userId, source.ownerRef, id)])));
|
|
52607
|
+
}
|
|
52493
52608
|
/** Keep viewer-owned remarks separate from group nicknames and public profiles. */
|
|
52494
52609
|
async reactionActorLabels(sourceRef, userIds, signal) {
|
|
52495
52610
|
const session = await this.runtime.requireSession();
|
|
@@ -88088,6 +88203,10 @@ var MediaService = class {
|
|
|
88088
88203
|
imageCacheBytes = 0;
|
|
88089
88204
|
activeImageDownloads = 0;
|
|
88090
88205
|
imageDownloadWaiters = [];
|
|
88206
|
+
avatarAssets = /* @__PURE__ */ new Map();
|
|
88207
|
+
avatarAssetTimer;
|
|
88208
|
+
imageGeneration = 0;
|
|
88209
|
+
localAvatarReads = /* @__PURE__ */ new Map();
|
|
88091
88210
|
constructor(runtime, profile, worldImages, recordIdentity, botImages, selfRoleAvatars) {
|
|
88092
88211
|
this.runtime = runtime;
|
|
88093
88212
|
this.profile = profile;
|
|
@@ -88097,19 +88216,27 @@ var MediaService = class {
|
|
|
88097
88216
|
this.selfRoleAvatars = selfRoleAvatars;
|
|
88098
88217
|
}
|
|
88099
88218
|
dispose() {
|
|
88219
|
+
this.imageGeneration += 1;
|
|
88220
|
+
clearTimeout(this.avatarAssetTimer);
|
|
88221
|
+
this.avatarAssetTimer = void 0;
|
|
88222
|
+
for (const pending of this.avatarAssets.values()) pending.reject(/* @__PURE__ */ new Error("Avatar image scope changed"));
|
|
88223
|
+
this.avatarAssets.clear();
|
|
88100
88224
|
this.mediaRefs.clear();
|
|
88101
88225
|
this.stableMediaRefs.clear();
|
|
88102
88226
|
this.imageCache.clear();
|
|
88103
88227
|
this.imageInFlight.clear();
|
|
88228
|
+
this.localAvatarReads.clear();
|
|
88104
88229
|
this.imageCacheBytes = 0;
|
|
88105
88230
|
this.imageDownloadWaiters.splice(0).forEach((resolve) => {
|
|
88106
88231
|
resolve();
|
|
88107
88232
|
});
|
|
88108
88233
|
}
|
|
88109
88234
|
async queryFileAssets(fileAssetUids, signal) {
|
|
88235
|
+
return await this.queryFileAssetsForSession(await this.runtime.requireSession(), fileAssetUids, signal);
|
|
88236
|
+
}
|
|
88237
|
+
async queryFileAssetsForSession(session, fileAssetUids, signal) {
|
|
88110
88238
|
const unique = [...new Set(fileAssetUids.map((uid) => uid.trim()).filter((uid) => uid !== ""))].slice(0, 50);
|
|
88111
88239
|
if (unique.length === 0) return [];
|
|
88112
|
-
const session = await this.runtime.requireSession();
|
|
88113
88240
|
return listValue$10((await this.runtime.authenticatedPost("/api/v1/files/assets/query", { file_asset_uids: unique }, session, signal, { lane: "interactive-read" })).items).map((raw) => {
|
|
88114
88241
|
const item = objectValue$2(raw);
|
|
88115
88242
|
const previewUrl = safeHttpsUrl(item.preview_url);
|
|
@@ -88371,7 +88498,11 @@ var MediaService = class {
|
|
|
88371
88498
|
}
|
|
88372
88499
|
async readImage(imageRef, options = {}) {
|
|
88373
88500
|
options.signal?.throwIfAborted();
|
|
88501
|
+
const generation = this.imageGeneration;
|
|
88374
88502
|
const session = await this.runtime.requireSession();
|
|
88503
|
+
const assetMatch = /^file_asset:\/\/([A-Za-z0-9_-]{8,128})$/.exec(imageRef.trim());
|
|
88504
|
+
if (assetMatch !== null) return await this.readFileAssetImage(session, assetMatch[1], options, generation);
|
|
88505
|
+
if (options.cacheOnly) throw new ArkmePluginError("image-cache-miss", "本地没有可用的资产头像", false, 404);
|
|
88375
88506
|
const isProfileImage = imageRef.trim().startsWith("arkme-profile-image-v1.");
|
|
88376
88507
|
const isBotImage = imageRef.trim().startsWith("arkme-bot-image-v1.");
|
|
88377
88508
|
const isSelfRoleImage = imageRef.trim().startsWith("arkme-self-role-image-v1.");
|
|
@@ -88420,6 +88551,145 @@ var MediaService = class {
|
|
|
88420
88551
|
if (this.imageInFlight.get(cacheKey) === pending) this.imageInFlight.delete(cacheKey);
|
|
88421
88552
|
}
|
|
88422
88553
|
}
|
|
88554
|
+
/** Immutable asset bytes survive URL expiry and Host restarts in the existing avatar store. */
|
|
88555
|
+
async readFileAssetImage(session, uid, options, generation) {
|
|
88556
|
+
await this.assertImageAccount(session, generation);
|
|
88557
|
+
options.signal?.throwIfAborted();
|
|
88558
|
+
const byteLimit = Math.min(MAX_SELF_ROLE_AVATAR_BYTES, Math.max(1, Math.trunc(options.maxBytes ?? 8388608)));
|
|
88559
|
+
const diskKey = `asset:${this.runtime.config.environment}:${uid}`;
|
|
88560
|
+
const cacheKey = `${session.userId}:${byteLimit}:${diskKey}`;
|
|
88561
|
+
const cached = this.cachedImage(cacheKey);
|
|
88562
|
+
if (cached !== void 0) return cached;
|
|
88563
|
+
if (options.cacheOnly) {
|
|
88564
|
+
const value = await this.readLocalFileAssetImage(session, uid, diskKey, byteLimit, generation);
|
|
88565
|
+
options.signal?.throwIfAborted();
|
|
88566
|
+
await this.assertImageAccount(session, generation);
|
|
88567
|
+
if (value === void 0) throw new ArkmePluginError("image-cache-miss", "本地没有可用的资产头像", false, 404);
|
|
88568
|
+
this.cacheImage(cacheKey, value);
|
|
88569
|
+
return cloneImageBytes(value);
|
|
88570
|
+
}
|
|
88571
|
+
let pending = this.imageInFlight.get(cacheKey);
|
|
88572
|
+
if (pending === void 0) {
|
|
88573
|
+
pending = this.loadFileAssetImage(session, uid, diskKey, byteLimit, generation);
|
|
88574
|
+
this.imageInFlight.set(cacheKey, pending);
|
|
88575
|
+
}
|
|
88576
|
+
try {
|
|
88577
|
+
const value = await pending;
|
|
88578
|
+
options.signal?.throwIfAborted();
|
|
88579
|
+
await this.assertImageAccount(session, generation);
|
|
88580
|
+
this.cacheImage(cacheKey, value);
|
|
88581
|
+
return cloneImageBytes(value);
|
|
88582
|
+
} finally {
|
|
88583
|
+
if (this.imageInFlight.get(cacheKey) === pending) this.imageInFlight.delete(cacheKey);
|
|
88584
|
+
}
|
|
88585
|
+
}
|
|
88586
|
+
async assertImageAccount(session, generation) {
|
|
88587
|
+
const current = await this.runtime.requireSession();
|
|
88588
|
+
if (generation !== this.imageGeneration || current.userId !== session.userId) throw new ArkmePluginError("image-account-changed", "账号已切换,请重试", false, 409);
|
|
88589
|
+
}
|
|
88590
|
+
async validAvatar(image, byteLimit) {
|
|
88591
|
+
if (image.bytes <= 0 || image.bytes !== image.data.byteLength || image.bytes > byteLimit || imageMediaType(image.data) !== image.mediaType) return false;
|
|
88592
|
+
try {
|
|
88593
|
+
const { default: sharp } = await import("sharp");
|
|
88594
|
+
await sharp(image.data, {
|
|
88595
|
+
failOn: "warning",
|
|
88596
|
+
limitInputPixels: 64 * 1024 * 1024
|
|
88597
|
+
}).resize(1, 1).raw().toBuffer();
|
|
88598
|
+
return true;
|
|
88599
|
+
} catch {
|
|
88600
|
+
return false;
|
|
88601
|
+
}
|
|
88602
|
+
}
|
|
88603
|
+
async readLocalFileAssetImage(session, uid, diskKey, byteLimit, generation) {
|
|
88604
|
+
const key = `${generation}:${session.userId}:${byteLimit}:${diskKey}`;
|
|
88605
|
+
let pending = this.localAvatarReads.get(key);
|
|
88606
|
+
if (pending === void 0) {
|
|
88607
|
+
pending = (async () => {
|
|
88608
|
+
const persisted = await this.runtime.stateStore.readAvatarCache?.(session.userId, diskKey).catch(() => void 0);
|
|
88609
|
+
if (persisted !== void 0 && await this.validAvatar(persisted, byteLimit)) return persisted;
|
|
88610
|
+
await this.assertImageAccount(session, generation);
|
|
88611
|
+
const localRef = await this.runtime.stateStore.selfRoleAvatarLocalRef?.(session.userId, uid).catch(() => void 0);
|
|
88612
|
+
const original = localRef === void 0 ? void 0 : await this.selfRoleAvatars?.read(session.userId, localRef, byteLimit).catch(() => void 0);
|
|
88613
|
+
return original !== void 0 && await this.validAvatar(original, byteLimit) ? original : void 0;
|
|
88614
|
+
})();
|
|
88615
|
+
this.localAvatarReads.set(key, pending);
|
|
88616
|
+
}
|
|
88617
|
+
try {
|
|
88618
|
+
const value = await pending;
|
|
88619
|
+
await this.assertImageAccount(session, generation);
|
|
88620
|
+
return value;
|
|
88621
|
+
} finally {
|
|
88622
|
+
if (this.localAvatarReads.get(key) === pending) this.localAvatarReads.delete(key);
|
|
88623
|
+
}
|
|
88624
|
+
}
|
|
88625
|
+
async loadFileAssetImage(session, uid, diskKey, byteLimit, generation) {
|
|
88626
|
+
let value = await this.readLocalFileAssetImage(session, uid, diskKey, byteLimit, generation);
|
|
88627
|
+
if (value === void 0) {
|
|
88628
|
+
await this.assertImageAccount(session, generation);
|
|
88629
|
+
const asset = await this.queueAvatarAsset(session, uid, generation);
|
|
88630
|
+
const url = asset?.previewUrl ?? asset?.downloadUrl;
|
|
88631
|
+
if (url === void 0) throw new ArkmePluginError("image-ref-unavailable", "历史头像当前不可用", true, 404);
|
|
88632
|
+
await this.assertImageAccount(session, generation);
|
|
88633
|
+
value = await this.withImageDownloadPermit(async () => {
|
|
88634
|
+
await this.assertImageAccount(session, generation);
|
|
88635
|
+
return await this.downloadSignedImage(trustedSignedImageUrl$1(this.runtime.config.environment, url), byteLimit, void 0, this.runtime.requestScope(session.userId));
|
|
88636
|
+
});
|
|
88637
|
+
if (!await this.validAvatar(value, byteLimit)) throw new ArkmePluginError("image-bytes-invalid", "头像数据损坏,请重试", true, 502);
|
|
88638
|
+
}
|
|
88639
|
+
await this.assertImageAccount(session, generation);
|
|
88640
|
+
await this.runtime.stateStore.writeAvatarCache?.(session.userId, diskKey, value).catch(() => {
|
|
88641
|
+
console.warn("dsh-arkme: avatar_cache_write_failed");
|
|
88642
|
+
});
|
|
88643
|
+
return value;
|
|
88644
|
+
}
|
|
88645
|
+
queueAvatarAsset(session, uid, generation) {
|
|
88646
|
+
const key = `${session.userId}:${uid}`;
|
|
88647
|
+
const existing = this.avatarAssets.get(key);
|
|
88648
|
+
if (existing !== void 0) return existing.promise;
|
|
88649
|
+
let resolve;
|
|
88650
|
+
let reject;
|
|
88651
|
+
const promise = new Promise((done, fail) => {
|
|
88652
|
+
resolve = done;
|
|
88653
|
+
reject = fail;
|
|
88654
|
+
});
|
|
88655
|
+
this.avatarAssets.set(key, {
|
|
88656
|
+
session,
|
|
88657
|
+
uid,
|
|
88658
|
+
generation,
|
|
88659
|
+
started: false,
|
|
88660
|
+
promise,
|
|
88661
|
+
resolve,
|
|
88662
|
+
reject
|
|
88663
|
+
});
|
|
88664
|
+
this.avatarAssetTimer ??= setTimeout(() => {
|
|
88665
|
+
this.avatarAssetTimer = void 0;
|
|
88666
|
+
this.flushAvatarAssets();
|
|
88667
|
+
}, 8);
|
|
88668
|
+
return promise;
|
|
88669
|
+
}
|
|
88670
|
+
async flushAvatarAssets() {
|
|
88671
|
+
const groups = /* @__PURE__ */ new Map();
|
|
88672
|
+
for (const entry of this.avatarAssets.entries()) {
|
|
88673
|
+
if (entry[1].started) continue;
|
|
88674
|
+
entry[1].started = true;
|
|
88675
|
+
const group = groups.get(entry[1].session.userId) ?? [];
|
|
88676
|
+
group.push(entry);
|
|
88677
|
+
groups.set(entry[1].session.userId, group);
|
|
88678
|
+
}
|
|
88679
|
+
for (const group of groups.values()) for (let start = 0; start < group.length; start += 50) {
|
|
88680
|
+
const batch = group.slice(start, start + 50);
|
|
88681
|
+
try {
|
|
88682
|
+
await this.assertImageAccount(batch[0][1].session, batch[0][1].generation);
|
|
88683
|
+
const items = await this.queryFileAssetsForSession(batch[0][1].session, batch.map(([, pending]) => pending.uid));
|
|
88684
|
+
const byUid = new Map(items.map((item) => [item.fileAssetUid, item]));
|
|
88685
|
+
for (const [, pending] of batch) pending.resolve(byUid.get(pending.uid));
|
|
88686
|
+
} catch (error) {
|
|
88687
|
+
for (const [, pending] of batch) pending.reject(error);
|
|
88688
|
+
} finally {
|
|
88689
|
+
for (const [key, pending] of batch) if (this.avatarAssets.get(key) === pending) this.avatarAssets.delete(key);
|
|
88690
|
+
}
|
|
88691
|
+
}
|
|
88692
|
+
}
|
|
88423
88693
|
cachedImage(cacheKey) {
|
|
88424
88694
|
const cached = this.imageCache.get(cacheKey);
|
|
88425
88695
|
if (cached === void 0) return void 0;
|
|
@@ -88467,13 +88737,6 @@ var MediaService = class {
|
|
|
88467
88737
|
if (this.selfRoleAvatars === void 0) throw new ArkmePluginError("self-role-avatar-missing", "本地角色头像不可用", false, 404);
|
|
88468
88738
|
return await this.selfRoleAvatars.read(session.userId, imageRef.trim(), byteLimit);
|
|
88469
88739
|
}
|
|
88470
|
-
const snapshotAsset = /^file_asset:\/\/([A-Za-z0-9_-]{8,128})$/.exec(imageRef.trim());
|
|
88471
|
-
if (snapshotAsset !== null) {
|
|
88472
|
-
const asset = (await this.queryFileAssets([snapshotAsset[1]], signal)).find((item) => item.fileAssetUid === snapshotAsset[1]);
|
|
88473
|
-
const url = asset?.previewUrl ?? asset?.downloadUrl;
|
|
88474
|
-
if (url === void 0) throw new ArkmePluginError("image-ref-unavailable", "历史头像当前不可用", true, 404);
|
|
88475
|
-
return await this.downloadSignedImage(trustedSignedImageUrl$1(this.runtime.config.environment, url), byteLimit, signal, this.runtime.requestScope(session.userId));
|
|
88476
|
-
}
|
|
88477
88740
|
if (imageRef.trim().startsWith("arkme-bot-image-v1.")) {
|
|
88478
88741
|
if (this.botImages === void 0) throw new ArkmePluginError("bot-image-ref-invalid", "Bot 头像引用不可用", false, 403);
|
|
88479
88742
|
const reference = await this.botImages.openBotImageRef(imageRef, session.userId);
|
|
@@ -90302,6 +90565,11 @@ var ProfileService = class {
|
|
|
90302
90565
|
}
|
|
90303
90566
|
if (cached.value !== null) profiles.set(userId, cached.value);
|
|
90304
90567
|
}
|
|
90568
|
+
await measureReaction("profile-cache", {
|
|
90569
|
+
requested: normalized.length,
|
|
90570
|
+
hits: normalized.length - missing.length,
|
|
90571
|
+
misses: missing.length
|
|
90572
|
+
}, async () => void 0);
|
|
90305
90573
|
for (const batch of chunksOf(missing, 50)) {
|
|
90306
90574
|
if (batch.length === 0) continue;
|
|
90307
90575
|
const startedAtMillis = Date.now();
|
|
@@ -98960,6 +99228,7 @@ var ArkmeService = class {
|
|
|
98960
99228
|
messageReport: true,
|
|
98961
99229
|
userBanManagement: true,
|
|
98962
99230
|
selfRoles: true,
|
|
99231
|
+
imageCacheRead: true,
|
|
98963
99232
|
directMessageAdmission: true,
|
|
98964
99233
|
reactionsV1: true,
|
|
98965
99234
|
groupOwnerGovernance: true,
|
|
@@ -99933,110 +100202,132 @@ var ArkmeService = class {
|
|
|
99933
100202
|
return await this.chat.favoriteStickers(signal);
|
|
99934
100203
|
}
|
|
99935
100204
|
async reactions(input, signal) {
|
|
99936
|
-
|
|
99937
|
-
const
|
|
99938
|
-
|
|
99939
|
-
|
|
99940
|
-
|
|
99941
|
-
|
|
99942
|
-
|
|
99943
|
-
|
|
100205
|
+
return await withReactionTrace(input, async () => {
|
|
100206
|
+
const result = await new ReactionService(this.runtime, (target) => target.worldRecordRef !== void 0 ? this.world.reactionTarget(target.worldRecordRef) : this.chat.reactionTarget(target.sourceRef, target.messageActionRef), (uid) => this.world.reactionReference(uid), (viewer, uid) => this.source.chatDirectorySourceKey(viewer, uid), async (items, signal) => {
|
|
100207
|
+
const session = await this.runtime.requireSession();
|
|
100208
|
+
const [sources, profiles] = await Promise.all([this.source.chatSourcesBySessionUids([...new Set(items.map((item) => item.sessionUID).filter(Boolean))], signal), this.profile.publicProfileSummariesByUserIds([...new Set(items.map((item) => item.senderID).filter((id) => id > 0))], session, signal, 5e3)]);
|
|
100209
|
+
const missingAvatars = [...sources].filter(([, source]) => !source.avatarRef && !source.avatarRefs?.length && !source.groupAvatar);
|
|
100210
|
+
if (missingAvatars.length > 0) try {
|
|
100211
|
+
const hydrated = await this.source.hydrateDirectoryPage(missingAvatars.map(([, source]) => source), signal ?? new AbortController().signal);
|
|
100212
|
+
missingAvatars.forEach(([uid], index) => {
|
|
100213
|
+
if (hydrated[index]) sources.set(uid, hydrated[index]);
|
|
100214
|
+
});
|
|
100215
|
+
} catch {
|
|
100216
|
+
signal?.throwIfAborted();
|
|
100217
|
+
}
|
|
100218
|
+
const selfSource = items.some((item) => item.sourceKind === "send_to_self" && item.message) ? {
|
|
100219
|
+
sourceRef: await this.source.sealSourceRef(session.userId, "send_to_self", "all", "发给自己"),
|
|
100220
|
+
kind: "send_to_self",
|
|
100221
|
+
displayName: "发给自己",
|
|
100222
|
+
activeAtMillis: 0,
|
|
100223
|
+
unreadCount: 0
|
|
100224
|
+
} : void 0;
|
|
100225
|
+
return items.map((item) => {
|
|
100226
|
+
const source = item.sourceKind === "send_to_self" ? selfSource : sources.get(item.sessionUID);
|
|
100227
|
+
return {
|
|
100228
|
+
sourceName: item.sourceKind === "send_to_self" ? "发给自己" : source?.displayName,
|
|
100229
|
+
authorName: item.senderID === session.userId ? "我" : profiles.get(item.senderID)?.displayName,
|
|
100230
|
+
...source && item.message ? { originalMessage: {
|
|
100231
|
+
source,
|
|
100232
|
+
itemUid: item.message.recordUID,
|
|
100233
|
+
recordOwnerUserId: item.message.ownerUserID,
|
|
100234
|
+
sendAtMillis: item.message.sendAtMillis
|
|
100235
|
+
} } : {},
|
|
100236
|
+
...source ? { avatar: {
|
|
100237
|
+
...source.avatarRef ? { avatarRef: source.avatarRef } : {},
|
|
100238
|
+
...source.avatarRefs ? { avatarRefs: source.avatarRefs } : {},
|
|
100239
|
+
...source.groupAvatar ? { groupAvatar: source.groupAvatar } : {}
|
|
100240
|
+
} } : {}
|
|
100241
|
+
};
|
|
99944
100242
|
});
|
|
99945
|
-
}
|
|
99946
|
-
|
|
99947
|
-
|
|
99948
|
-
|
|
99949
|
-
|
|
99950
|
-
|
|
99951
|
-
|
|
99952
|
-
|
|
99953
|
-
|
|
99954
|
-
|
|
99955
|
-
|
|
99956
|
-
|
|
99957
|
-
|
|
99958
|
-
|
|
99959
|
-
|
|
99960
|
-
|
|
99961
|
-
|
|
99962
|
-
itemUid: item.message.recordUID,
|
|
99963
|
-
recordOwnerUserId: item.message.ownerUserID,
|
|
99964
|
-
sendAtMillis: item.message.sendAtMillis
|
|
99965
|
-
} } : {},
|
|
99966
|
-
...source ? { avatar: {
|
|
99967
|
-
...source.avatarRef ? { avatarRef: source.avatarRef } : {},
|
|
99968
|
-
...source.avatarRefs ? { avatarRefs: source.avatarRefs } : {},
|
|
99969
|
-
...source.groupAvatar ? { groupAvatar: source.groupAvatar } : {}
|
|
99970
|
-
} } : {}
|
|
99971
|
-
};
|
|
100243
|
+
}).request(input, signal);
|
|
100244
|
+
if (`${this.config.environment}:${(await this.runtime.requireSession()).userId}` !== input.accountKey || signal?.aborted) throw new ArkmePluginError("reaction-account-changed", "账号已变化", false);
|
|
100245
|
+
const actorLabels = async (sourceRef, ids) => {
|
|
100246
|
+
if (!sourceRef || !ids.length) return /* @__PURE__ */ new Map();
|
|
100247
|
+
try {
|
|
100248
|
+
return await this.chat.reactionActorLabels(sourceRef, ids, signal);
|
|
100249
|
+
} catch (error) {
|
|
100250
|
+
if (signal?.aborted) throw error;
|
|
100251
|
+
return /* @__PURE__ */ new Map();
|
|
100252
|
+
}
|
|
100253
|
+
};
|
|
100254
|
+
const actorAvatars = async (viewer, profiles) => new Map(await Promise.all([...profiles].filter(([, profile]) => profile.avatarUrl).map(async ([id]) => [id, await this.profile.sealProfileImageRef(viewer, id)])));
|
|
100255
|
+
const presentActor = (userId, profileName, label, avatarRef) => ({
|
|
100256
|
+
userId,
|
|
100257
|
+
displayName: label?.remark || label?.groupNickname || profileName?.trim() || "用户",
|
|
100258
|
+
...label?.groupNickname ? { groupNickname: label.groupNickname } : {},
|
|
100259
|
+
...avatarRef ? { avatarRef } : {}
|
|
99972
100260
|
});
|
|
99973
|
-
|
|
99974
|
-
|
|
99975
|
-
|
|
99976
|
-
|
|
99977
|
-
|
|
99978
|
-
|
|
99979
|
-
|
|
99980
|
-
|
|
99981
|
-
|
|
100261
|
+
if (input.action === "query" || input.action === "groups") {
|
|
100262
|
+
const page = result;
|
|
100263
|
+
const groupPage = result;
|
|
100264
|
+
const groups = input.action === "query" ? page.items.flatMap((item) => item.actors_visible ? item.groups : []) : groupPage.items;
|
|
100265
|
+
const ids = [...new Set(groups.flatMap((group) => group.actorIds))];
|
|
100266
|
+
const session = await this.runtime.requireSession();
|
|
100267
|
+
const sourceByTarget = new Map(input.action === "query" ? input.targets.map((target) => [target.id, target.sourceRef]) : []);
|
|
100268
|
+
const sourceIds = /* @__PURE__ */ new Map();
|
|
100269
|
+
const include = (sourceRef, actorIds) => {
|
|
100270
|
+
if (!sourceRef || !actorIds.length) return;
|
|
100271
|
+
const users = sourceIds.get(sourceRef) ?? /* @__PURE__ */ new Set();
|
|
100272
|
+
actorIds.forEach((id) => users.add(id));
|
|
100273
|
+
sourceIds.set(sourceRef, users);
|
|
100274
|
+
};
|
|
100275
|
+
if (input.action === "query") {
|
|
100276
|
+
for (const item of page.items) if (item.actors_visible) include(sourceByTarget.get(item.target_id), item.groups.flatMap((group) => group.actorIds));
|
|
100277
|
+
} else include(input.target.sourceRef, ids);
|
|
100278
|
+
if (input.action === "query" && input.actorPresentation === "deferred") {
|
|
100279
|
+
const references = /* @__PURE__ */ new Map();
|
|
100280
|
+
for (const [sourceRef, users] of sourceIds) references.set(sourceRef, await this.chat.reactionActorReferences(sourceRef, [...users]));
|
|
100281
|
+
if (`${this.config.environment}:${(await this.runtime.requireSession()).userId}` !== input.accountKey || signal?.aborted) throw new ArkmePluginError("reaction-account-changed", "账号已变化", false);
|
|
100282
|
+
return {
|
|
100283
|
+
...page,
|
|
100284
|
+
items: page.items.map((item) => ({
|
|
100285
|
+
...item,
|
|
100286
|
+
groups: item.groups.map(({ actorIds, ...group }) => ({
|
|
100287
|
+
...group,
|
|
100288
|
+
actors: item.actors_visible ? actorIds.map((userId) => ({
|
|
100289
|
+
userId,
|
|
100290
|
+
displayName: "用户",
|
|
100291
|
+
presentationPending: true,
|
|
100292
|
+
...references.get(sourceByTarget.get(item.target_id) ?? "")?.get(userId) ? { memberRef: references.get(sourceByTarget.get(item.target_id)).get(userId) } : {}
|
|
100293
|
+
})) : []
|
|
100294
|
+
}))
|
|
100295
|
+
}))
|
|
100296
|
+
};
|
|
100297
|
+
}
|
|
100298
|
+
const [profiles, labels] = await Promise.all([measureReaction("actor-profiles", { count: ids.length }, () => this.profile.publicProfileSummariesByUserIds(ids, session, signal, 5e3)), (async () => {
|
|
100299
|
+
const labels = /* @__PURE__ */ new Map();
|
|
100300
|
+
for (const [sourceRef, users] of sourceIds) labels.set(sourceRef, await measureReaction("actor-labels", { count: users.size }, () => actorLabels(sourceRef, [...users])));
|
|
100301
|
+
return labels;
|
|
100302
|
+
})()]);
|
|
100303
|
+
const avatars = await actorAvatars(session.userId, profiles);
|
|
100304
|
+
if (`${this.config.environment}:${(await this.runtime.requireSession()).userId}` !== input.accountKey || signal?.aborted) throw new ArkmePluginError("reaction-account-changed", "账号已变化", false);
|
|
100305
|
+
const present = ({ actorIds, ...group }, sourceRef, visible = true) => ({
|
|
100306
|
+
...group,
|
|
100307
|
+
actors: visible ? actorIds.map((userId) => presentActor(userId, profiles.get(userId)?.displayName, labels.get(sourceRef ?? "")?.get(userId), avatars.get(userId))) : []
|
|
100308
|
+
});
|
|
100309
|
+
return input.action === "query" ? {
|
|
100310
|
+
...page,
|
|
100311
|
+
items: page.items.map((item) => ({
|
|
100312
|
+
...item,
|
|
100313
|
+
groups: item.groups.map((group) => present(group, sourceByTarget.get(item.target_id), item.actors_visible))
|
|
100314
|
+
}))
|
|
100315
|
+
} : {
|
|
100316
|
+
...groupPage,
|
|
100317
|
+
items: groupPage.items.map((group) => present(group, input.target.sourceRef))
|
|
100318
|
+
};
|
|
99982
100319
|
}
|
|
99983
|
-
|
|
99984
|
-
const actorAvatars = async (viewer, profiles) => new Map(await Promise.all([...profiles].filter(([, profile]) => profile.avatarUrl).map(async ([id]) => [id, await this.profile.sealProfileImageRef(viewer, id)])));
|
|
99985
|
-
const presentActor = (userId, profileName, label, avatarRef) => ({
|
|
99986
|
-
userId,
|
|
99987
|
-
displayName: label?.remark || label?.groupNickname || profileName?.trim() || "用户",
|
|
99988
|
-
...label?.groupNickname ? { groupNickname: label.groupNickname } : {},
|
|
99989
|
-
...avatarRef ? { avatarRef } : {}
|
|
99990
|
-
});
|
|
99991
|
-
if (input.action === "query" || input.action === "groups") {
|
|
100320
|
+
if (input.action !== "actors") return result;
|
|
99992
100321
|
const page = result;
|
|
99993
|
-
const groupPage = result;
|
|
99994
|
-
const groups = input.action === "query" ? page.items.flatMap((item) => item.actors_visible ? item.groups : []) : groupPage.items;
|
|
99995
|
-
const ids = [...new Set(groups.flatMap((group) => group.actorIds))];
|
|
99996
100322
|
const session = await this.runtime.requireSession();
|
|
99997
|
-
const
|
|
99998
|
-
const sourceIds = /* @__PURE__ */ new Map();
|
|
99999
|
-
const include = (sourceRef, actorIds) => {
|
|
100000
|
-
if (!sourceRef || !actorIds.length) return;
|
|
100001
|
-
const users = sourceIds.get(sourceRef) ?? /* @__PURE__ */ new Set();
|
|
100002
|
-
actorIds.forEach((id) => users.add(id));
|
|
100003
|
-
sourceIds.set(sourceRef, users);
|
|
100004
|
-
};
|
|
100005
|
-
if (input.action === "query") {
|
|
100006
|
-
for (const item of page.items) if (item.actors_visible) include(sourceByTarget.get(item.target_id), item.groups.flatMap((group) => group.actorIds));
|
|
100007
|
-
} else include(input.target.sourceRef, ids);
|
|
100008
|
-
const [profiles, labels] = await Promise.all([this.profile.publicProfileSummariesByUserIds(ids, session, signal, 5e3), (async () => {
|
|
100009
|
-
const labels = /* @__PURE__ */ new Map();
|
|
100010
|
-
for (const [sourceRef, users] of sourceIds) labels.set(sourceRef, await actorLabels(sourceRef, [...users]));
|
|
100011
|
-
return labels;
|
|
100012
|
-
})()]);
|
|
100323
|
+
const [profiles, labels] = await Promise.all([this.profile.publicProfileSummariesByUserIds(page.user_ids, session, signal, 5e3), actorLabels(input.target.sourceRef, page.user_ids)]);
|
|
100013
100324
|
const avatars = await actorAvatars(session.userId, profiles);
|
|
100014
100325
|
if (`${this.config.environment}:${(await this.runtime.requireSession()).userId}` !== input.accountKey || signal?.aborted) throw new ArkmePluginError("reaction-account-changed", "账号已变化", false);
|
|
100015
|
-
|
|
100016
|
-
|
|
100017
|
-
|
|
100018
|
-
});
|
|
100019
|
-
return input.action === "query" ? {
|
|
100020
|
-
...page,
|
|
100021
|
-
items: page.items.map((item) => ({
|
|
100022
|
-
...item,
|
|
100023
|
-
groups: item.groups.map((group) => present(group, sourceByTarget.get(item.target_id), item.actors_visible))
|
|
100024
|
-
}))
|
|
100025
|
-
} : {
|
|
100026
|
-
...groupPage,
|
|
100027
|
-
items: groupPage.items.map((group) => present(group, input.target.sourceRef))
|
|
100326
|
+
return {
|
|
100327
|
+
items: page.user_ids.map((userId) => presentActor(userId, profiles.get(userId)?.displayName, labels.get(userId), avatars.get(userId))),
|
|
100328
|
+
has_more: page.has_more
|
|
100028
100329
|
};
|
|
100029
|
-
}
|
|
100030
|
-
if (input.action !== "actors") return result;
|
|
100031
|
-
const page = result;
|
|
100032
|
-
const session = await this.runtime.requireSession();
|
|
100033
|
-
const [profiles, labels] = await Promise.all([this.profile.publicProfileSummariesByUserIds(page.user_ids, session, signal, 5e3), actorLabels(input.target.sourceRef, page.user_ids)]);
|
|
100034
|
-
const avatars = await actorAvatars(session.userId, profiles);
|
|
100035
|
-
if (`${this.config.environment}:${(await this.runtime.requireSession()).userId}` !== input.accountKey || signal?.aborted) throw new ArkmePluginError("reaction-account-changed", "账号已变化", false);
|
|
100036
|
-
return {
|
|
100037
|
-
items: page.user_ids.map((userId) => presentActor(userId, profiles.get(userId)?.displayName, labels.get(userId), avatars.get(userId))),
|
|
100038
|
-
has_more: page.has_more
|
|
100039
|
-
};
|
|
100330
|
+
});
|
|
100040
100331
|
}
|
|
100041
100332
|
async addFavoriteSticker(item, signal) {
|
|
100042
100333
|
return await this.chat.addFavoriteSticker(item, signal);
|