@openclaw/matrix 2026.3.13 → 2026.5.10-beta.1

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 (206) hide show
  1. package/dist/account-config-D2W-V1eQ.js +96 -0
  2. package/dist/account-selection-BWwIruri.js +158 -0
  3. package/dist/accounts-Bm90Rzvp.js +130 -0
  4. package/dist/active-client-uhlxdhEy.js +20 -0
  5. package/dist/allowlist-sTzpCn5d.js +68 -0
  6. package/dist/api.js +12 -0
  7. package/dist/approval-handler.runtime-DWTQfd4m.js +370 -0
  8. package/dist/approval-ids-DoC2z7tR.js +7 -0
  9. package/dist/approval-reaction-auth-DbcA1gGd.js +27 -0
  10. package/dist/approval-reactions-o2_tuH8D.js +162 -0
  11. package/dist/async-lock-uQfhfQIY.js +19 -0
  12. package/dist/auth-presence.js +26 -0
  13. package/dist/backup-health-Cabu_WQC.js +60 -0
  14. package/dist/channel-H_6lMgwf.js +1116 -0
  15. package/dist/channel-plugin-api.js +2 -0
  16. package/dist/channel.runtime-BnO9f0pR.js +246 -0
  17. package/dist/cli-CYZ9yVcB.js +1340 -0
  18. package/dist/cli-metadata-DPIHnoa6.js +22 -0
  19. package/dist/cli-metadata.js +2 -0
  20. package/dist/client-DkcXnm0X.js +25 -0
  21. package/dist/client-_hckQNGW.js +31 -0
  22. package/dist/client-bootstrap-Rb8oHvhH.js +114 -0
  23. package/dist/config--5-S2Akv.js +452 -0
  24. package/dist/config-paths-nsVaysCu.js +19 -0
  25. package/dist/config-schema-nPLpEgHl.js +200 -0
  26. package/dist/config-secret-input.runtime-DiKFehsE.js +2 -0
  27. package/dist/config-update-wZX-HLMn.js +143 -0
  28. package/dist/contract-api.js +9 -0
  29. package/dist/create-client-DCnqDaqd.js +64 -0
  30. package/dist/credentials-DV6fWXhC.js +56 -0
  31. package/dist/credentials-read-cmHgousK.js +112 -0
  32. package/dist/credentials-write.runtime-zniTq-Gr.js +17 -0
  33. package/dist/crypto-node.runtime-pihzdpY7.js +12 -0
  34. package/dist/crypto-runtime-ZI0zAtn3.js +1214 -0
  35. package/dist/deps-C6WqKY7m.js +235 -0
  36. package/dist/device-health-UVYpbA_W.js +16 -0
  37. package/dist/direct-management-DMMMgtTB.js +249 -0
  38. package/dist/direct-room-XkutHjES.js +76 -0
  39. package/dist/directory-live-DmOtMhyr.js +150 -0
  40. package/dist/doctor-C4__7c-U.js +153 -0
  41. package/dist/doctor-contract-D4-64QuJ.js +246 -0
  42. package/dist/doctor-contract-api.js +2 -0
  43. package/dist/draft-stream-BE2QevQQ.js +144 -0
  44. package/dist/encryption-guidance-BPi3A_m3.js +15 -0
  45. package/dist/env-auth-BJqGI8M6.js +63 -0
  46. package/dist/env-vars-C7uQCTKn.js +63 -0
  47. package/dist/errors-CTcpEDq-.js +17 -0
  48. package/dist/exec-approval-resolver-Bza9Dhlm.js +15 -0
  49. package/dist/exec-approvals-Crnh543m.js +196 -0
  50. package/dist/helper-api.js +4 -0
  51. package/dist/http-client-C7AeVJay.js +319 -0
  52. package/dist/index.js +46 -0
  53. package/dist/legacy-crypto-inspector-poDWldgy.js +41 -0
  54. package/dist/legacy-crypto-restore-Biw-w2ng.js +85 -0
  55. package/dist/logger-CnZRVrux.js +78 -0
  56. package/dist/logging-DZHSPP5N.js +99 -0
  57. package/dist/matrix-migration.runtime-WY6ffcrf.js +525 -0
  58. package/dist/media-text-DU6nWZuj.js +146 -0
  59. package/dist/messages-BpihMh82.js +140 -0
  60. package/dist/migration-snapshot-backup-DaCHTp8C.js +69 -0
  61. package/dist/migration-snapshot.runtime-CKHE3xF9.js +2 -0
  62. package/dist/monitor-BaRCKyLd.js +4175 -0
  63. package/dist/plugin-entry.handlers.runtime.js +51 -0
  64. package/dist/probe.runtime-BvAzYAIe.js +3 -0
  65. package/dist/profile-BlHu0wDX.js +111 -0
  66. package/dist/profile-update-DjeBNgIV.js +69 -0
  67. package/dist/reaction-common-ejrL19w-.js +71 -0
  68. package/dist/reaction-events-CiARZfjk.js +121 -0
  69. package/dist/record-shared-CHWJCTWf.js +2 -0
  70. package/dist/recovery-key-store-BTJ6jz5v.js +294 -0
  71. package/dist/resolve-targets-YtJnw1Tb.js +140 -0
  72. package/dist/resolver.runtime-D9piiGEl.js +5 -0
  73. package/dist/rolldown-runtime-DUslC3ob.js +14 -0
  74. package/dist/route-D6rg-iXN.js +161 -0
  75. package/dist/runtime-C6X4h_SJ.js +6 -0
  76. package/dist/runtime-Dog86njy.js +8 -0
  77. package/dist/runtime-api-DTKcXOhp.js +24 -0
  78. package/dist/runtime-api.js +25 -0
  79. package/dist/runtime-heavy-api.js +3 -0
  80. package/dist/runtime-setter-api.js +2 -0
  81. package/dist/sdk-B2vZA27-.js +1416 -0
  82. package/dist/secret-contract-DcrJWCQI.js +120 -0
  83. package/dist/secret-contract-api.js +2 -0
  84. package/dist/send-Bo0DU1ca.js +1200 -0
  85. package/dist/session-store-metadata-DQXjgNLt.js +77 -0
  86. package/dist/setup-bootstrap-ImenBsMt.js +62 -0
  87. package/dist/setup-core-CfZy05oW.js +116 -0
  88. package/dist/setup-dm-policy-2-r1FrQh.js +194 -0
  89. package/dist/setup-entry.js +19 -0
  90. package/dist/setup-plugin-api.js +44 -0
  91. package/dist/setup-surface-CqT_o61M.js +540 -0
  92. package/dist/shared-CpMoYKm1.js +195 -0
  93. package/dist/startup-abort-56edvmbM.js +32 -0
  94. package/dist/startup-verification-Demyp0bP.js +132 -0
  95. package/dist/storage-paths-BJLdnCjV.js +52 -0
  96. package/dist/storage-tC3ujLiW.js +281 -0
  97. package/dist/subagent-hooks-DQbyqq9V.js +149 -0
  98. package/dist/subagent-hooks-api.js +23 -0
  99. package/dist/sync-state-C_beeevA.js +12 -0
  100. package/dist/target-ids-80nQ2gql.js +77 -0
  101. package/dist/test-api.js +4 -0
  102. package/dist/thread-binding-api-Cq_E-E1K.js +17 -0
  103. package/dist/thread-binding-api.js +2 -0
  104. package/dist/thread-bindings-B9mesxXk.js +352 -0
  105. package/dist/thread-bindings-runtime.js +2 -0
  106. package/dist/thread-bindings-shared-DK-d-oYX.js +97 -0
  107. package/dist/timeout-abort-signal-CtaIaP1v.js +2 -0
  108. package/dist/tool-actions.runtime-ThYhfHtZ.js +532 -0
  109. package/dist/url-validation-DiK9j7jz.js +36 -0
  110. package/dist/verification-CZ2rDeHL.js +345 -0
  111. package/openclaw.plugin.json +796 -1
  112. package/package.json +82 -16
  113. package/CHANGELOG.md +0 -104
  114. package/index.ts +0 -22
  115. package/src/actions.ts +0 -195
  116. package/src/channel.directory.test.ts +0 -135
  117. package/src/channel.ts +0 -461
  118. package/src/config-schema.test.ts +0 -26
  119. package/src/config-schema.ts +0 -62
  120. package/src/directory-live.test.ts +0 -85
  121. package/src/directory-live.ts +0 -209
  122. package/src/group-mentions.ts +0 -52
  123. package/src/matrix/accounts.test.ts +0 -131
  124. package/src/matrix/accounts.ts +0 -114
  125. package/src/matrix/actions/client.ts +0 -47
  126. package/src/matrix/actions/limits.test.ts +0 -15
  127. package/src/matrix/actions/limits.ts +0 -6
  128. package/src/matrix/actions/messages.ts +0 -126
  129. package/src/matrix/actions/pins.test.ts +0 -74
  130. package/src/matrix/actions/pins.ts +0 -84
  131. package/src/matrix/actions/reactions.test.ts +0 -109
  132. package/src/matrix/actions/reactions.ts +0 -102
  133. package/src/matrix/actions/room.ts +0 -85
  134. package/src/matrix/actions/summary.ts +0 -75
  135. package/src/matrix/actions/types.ts +0 -85
  136. package/src/matrix/actions.ts +0 -15
  137. package/src/matrix/active-client.ts +0 -32
  138. package/src/matrix/client/config.ts +0 -245
  139. package/src/matrix/client/create-client.ts +0 -125
  140. package/src/matrix/client/logging.ts +0 -46
  141. package/src/matrix/client/runtime.ts +0 -4
  142. package/src/matrix/client/shared.test.ts +0 -85
  143. package/src/matrix/client/shared.ts +0 -210
  144. package/src/matrix/client/startup.test.ts +0 -49
  145. package/src/matrix/client/startup.ts +0 -29
  146. package/src/matrix/client/storage.ts +0 -131
  147. package/src/matrix/client/types.ts +0 -34
  148. package/src/matrix/client-bootstrap.ts +0 -47
  149. package/src/matrix/client.test.ts +0 -56
  150. package/src/matrix/client.ts +0 -14
  151. package/src/matrix/credentials.ts +0 -125
  152. package/src/matrix/deps.test.ts +0 -74
  153. package/src/matrix/deps.ts +0 -126
  154. package/src/matrix/format.test.ts +0 -33
  155. package/src/matrix/format.ts +0 -22
  156. package/src/matrix/index.ts +0 -11
  157. package/src/matrix/monitor/access-policy.ts +0 -126
  158. package/src/matrix/monitor/allowlist.test.ts +0 -45
  159. package/src/matrix/monitor/allowlist.ts +0 -94
  160. package/src/matrix/monitor/auto-join.ts +0 -72
  161. package/src/matrix/monitor/direct.test.ts +0 -396
  162. package/src/matrix/monitor/direct.ts +0 -152
  163. package/src/matrix/monitor/events.test.ts +0 -186
  164. package/src/matrix/monitor/events.ts +0 -168
  165. package/src/matrix/monitor/handler.body-for-agent.test.ts +0 -196
  166. package/src/matrix/monitor/handler.ts +0 -768
  167. package/src/matrix/monitor/inbound-body.test.ts +0 -73
  168. package/src/matrix/monitor/inbound-body.ts +0 -28
  169. package/src/matrix/monitor/index.test.ts +0 -18
  170. package/src/matrix/monitor/index.ts +0 -414
  171. package/src/matrix/monitor/location.ts +0 -100
  172. package/src/matrix/monitor/media.test.ts +0 -86
  173. package/src/matrix/monitor/media.ts +0 -118
  174. package/src/matrix/monitor/mentions.test.ts +0 -154
  175. package/src/matrix/monitor/mentions.ts +0 -62
  176. package/src/matrix/monitor/replies.test.ts +0 -184
  177. package/src/matrix/monitor/replies.ts +0 -124
  178. package/src/matrix/monitor/room-info.ts +0 -55
  179. package/src/matrix/monitor/rooms.test.ts +0 -124
  180. package/src/matrix/monitor/rooms.ts +0 -47
  181. package/src/matrix/monitor/threads.ts +0 -68
  182. package/src/matrix/monitor/types.ts +0 -39
  183. package/src/matrix/poll-types.test.ts +0 -21
  184. package/src/matrix/poll-types.ts +0 -167
  185. package/src/matrix/probe.ts +0 -69
  186. package/src/matrix/sdk-runtime.ts +0 -18
  187. package/src/matrix/send/client.ts +0 -99
  188. package/src/matrix/send/formatting.ts +0 -93
  189. package/src/matrix/send/media.ts +0 -230
  190. package/src/matrix/send/targets.test.ts +0 -98
  191. package/src/matrix/send/targets.ts +0 -150
  192. package/src/matrix/send/types.ts +0 -110
  193. package/src/matrix/send-queue.test.ts +0 -145
  194. package/src/matrix/send-queue.ts +0 -28
  195. package/src/matrix/send.test.ts +0 -319
  196. package/src/matrix/send.ts +0 -267
  197. package/src/onboarding.ts +0 -462
  198. package/src/outbound.test.ts +0 -159
  199. package/src/outbound.ts +0 -58
  200. package/src/resolve-targets.test.ts +0 -68
  201. package/src/resolve-targets.ts +0 -125
  202. package/src/runtime.ts +0 -6
  203. package/src/secret-input.ts +0 -13
  204. package/src/test-mocks.ts +0 -53
  205. package/src/tool-actions.ts +0 -164
  206. package/src/types.ts +0 -118
@@ -1,69 +0,0 @@
1
- import type { BaseProbeResult } from "openclaw/plugin-sdk/matrix";
2
- import { createMatrixClient, isBunRuntime } from "./client.js";
3
-
4
- export type MatrixProbe = BaseProbeResult & {
5
- status?: number | null;
6
- elapsedMs: number;
7
- userId?: string | null;
8
- };
9
-
10
- export async function probeMatrix(params: {
11
- homeserver: string;
12
- accessToken: string;
13
- userId?: string;
14
- timeoutMs: number;
15
- }): Promise<MatrixProbe> {
16
- const started = Date.now();
17
- const result: MatrixProbe = {
18
- ok: false,
19
- status: null,
20
- error: null,
21
- elapsedMs: 0,
22
- };
23
- if (isBunRuntime()) {
24
- return {
25
- ...result,
26
- error: "Matrix probe requires Node (bun runtime not supported)",
27
- elapsedMs: Date.now() - started,
28
- };
29
- }
30
- if (!params.homeserver?.trim()) {
31
- return {
32
- ...result,
33
- error: "missing homeserver",
34
- elapsedMs: Date.now() - started,
35
- };
36
- }
37
- if (!params.accessToken?.trim()) {
38
- return {
39
- ...result,
40
- error: "missing access token",
41
- elapsedMs: Date.now() - started,
42
- };
43
- }
44
- try {
45
- const client = await createMatrixClient({
46
- homeserver: params.homeserver,
47
- userId: params.userId ?? "",
48
- accessToken: params.accessToken,
49
- localTimeoutMs: params.timeoutMs,
50
- });
51
- // @vector-im/matrix-bot-sdk uses getUserId() which calls whoami internally
52
- const userId = await client.getUserId();
53
- result.ok = true;
54
- result.userId = userId ?? null;
55
-
56
- result.elapsedMs = Date.now() - started;
57
- return result;
58
- } catch (err) {
59
- return {
60
- ...result,
61
- status:
62
- typeof err === "object" && err && "statusCode" in err
63
- ? Number((err as { statusCode?: number }).statusCode)
64
- : result.status,
65
- error: err instanceof Error ? err.message : String(err),
66
- elapsedMs: Date.now() - started,
67
- };
68
- }
69
- }
@@ -1,18 +0,0 @@
1
- import { createRequire } from "node:module";
2
-
3
- type MatrixSdkRuntime = typeof import("@vector-im/matrix-bot-sdk");
4
-
5
- let cachedMatrixSdkRuntime: MatrixSdkRuntime | null = null;
6
-
7
- export function loadMatrixSdk(): MatrixSdkRuntime {
8
- if (cachedMatrixSdkRuntime) {
9
- return cachedMatrixSdkRuntime;
10
- }
11
- const req = createRequire(import.meta.url);
12
- cachedMatrixSdkRuntime = req("@vector-im/matrix-bot-sdk") as MatrixSdkRuntime;
13
- return cachedMatrixSdkRuntime;
14
- }
15
-
16
- export function getMatrixLogService() {
17
- return loadMatrixSdk().LogService;
18
- }
@@ -1,99 +0,0 @@
1
- import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
3
- import { getMatrixRuntime } from "../../runtime.js";
4
- import type { CoreConfig } from "../../types.js";
5
- import { getActiveMatrixClient, getAnyActiveMatrixClient } from "../active-client.js";
6
- import { createPreparedMatrixClient } from "../client-bootstrap.js";
7
- import { isBunRuntime, resolveMatrixAuth, resolveSharedMatrixClient } from "../client.js";
8
-
9
- const getCore = () => getMatrixRuntime();
10
-
11
- export function ensureNodeRuntime() {
12
- if (isBunRuntime()) {
13
- throw new Error("Matrix support requires Node (bun runtime not supported)");
14
- }
15
- }
16
-
17
- /** Look up account config with case-insensitive key fallback. */
18
- function findAccountConfig(
19
- accounts: Record<string, unknown> | undefined,
20
- accountId: string,
21
- ): Record<string, unknown> | undefined {
22
- if (!accounts) return undefined;
23
- const normalized = normalizeAccountId(accountId);
24
- // Direct lookup first
25
- if (accounts[normalized]) return accounts[normalized] as Record<string, unknown>;
26
- // Case-insensitive fallback
27
- for (const key of Object.keys(accounts)) {
28
- if (normalizeAccountId(key) === normalized) {
29
- return accounts[key] as Record<string, unknown>;
30
- }
31
- }
32
- return undefined;
33
- }
34
-
35
- export function resolveMediaMaxBytes(accountId?: string, cfg?: CoreConfig): number | undefined {
36
- const resolvedCfg = cfg ?? (getCore().config.loadConfig() as CoreConfig);
37
- // Check account-specific config first (case-insensitive key matching)
38
- const accountConfig = findAccountConfig(
39
- resolvedCfg.channels?.matrix?.accounts as Record<string, unknown> | undefined,
40
- accountId ?? "",
41
- );
42
- if (typeof accountConfig?.mediaMaxMb === "number") {
43
- return (accountConfig.mediaMaxMb as number) * 1024 * 1024;
44
- }
45
- // Fall back to top-level config
46
- if (typeof resolvedCfg.channels?.matrix?.mediaMaxMb === "number") {
47
- return resolvedCfg.channels.matrix.mediaMaxMb * 1024 * 1024;
48
- }
49
- return undefined;
50
- }
51
-
52
- export async function resolveMatrixClient(opts: {
53
- client?: MatrixClient;
54
- timeoutMs?: number;
55
- accountId?: string;
56
- cfg?: CoreConfig;
57
- }): Promise<{ client: MatrixClient; stopOnDone: boolean }> {
58
- ensureNodeRuntime();
59
- if (opts.client) {
60
- return { client: opts.client, stopOnDone: false };
61
- }
62
- const accountId =
63
- typeof opts.accountId === "string" && opts.accountId.trim().length > 0
64
- ? normalizeAccountId(opts.accountId)
65
- : undefined;
66
- // Try to get the client for the specific account
67
- const active = getActiveMatrixClient(accountId);
68
- if (active) {
69
- return { client: active, stopOnDone: false };
70
- }
71
- // When no account is specified, try the default account first; only fall back to
72
- // any active client as a last resort (prevents sending from an arbitrary account).
73
- if (!accountId) {
74
- const defaultClient = getActiveMatrixClient(DEFAULT_ACCOUNT_ID);
75
- if (defaultClient) {
76
- return { client: defaultClient, stopOnDone: false };
77
- }
78
- const anyActive = getAnyActiveMatrixClient();
79
- if (anyActive) {
80
- return { client: anyActive, stopOnDone: false };
81
- }
82
- }
83
- const shouldShareClient = Boolean(process.env.OPENCLAW_GATEWAY_PORT);
84
- if (shouldShareClient) {
85
- const client = await resolveSharedMatrixClient({
86
- timeoutMs: opts.timeoutMs,
87
- accountId,
88
- cfg: opts.cfg,
89
- });
90
- return { client, stopOnDone: false };
91
- }
92
- const auth = await resolveMatrixAuth({ accountId, cfg: opts.cfg });
93
- const client = await createPreparedMatrixClient({
94
- auth,
95
- timeoutMs: opts.timeoutMs,
96
- accountId,
97
- });
98
- return { client, stopOnDone: true };
99
- }
@@ -1,93 +0,0 @@
1
- import { getMatrixRuntime } from "../../runtime.js";
2
- import { markdownToMatrixHtml } from "../format.js";
3
- import {
4
- MsgType,
5
- RelationType,
6
- type MatrixFormattedContent,
7
- type MatrixMediaMsgType,
8
- type MatrixRelation,
9
- type MatrixReplyRelation,
10
- type MatrixTextContent,
11
- type MatrixThreadRelation,
12
- } from "./types.js";
13
-
14
- const getCore = () => getMatrixRuntime();
15
-
16
- export function buildTextContent(body: string, relation?: MatrixRelation): MatrixTextContent {
17
- const content: MatrixTextContent = relation
18
- ? {
19
- msgtype: MsgType.Text,
20
- body,
21
- "m.relates_to": relation,
22
- }
23
- : {
24
- msgtype: MsgType.Text,
25
- body,
26
- };
27
- applyMatrixFormatting(content, body);
28
- return content;
29
- }
30
-
31
- export function applyMatrixFormatting(content: MatrixFormattedContent, body: string): void {
32
- const formatted = markdownToMatrixHtml(body ?? "");
33
- if (!formatted) {
34
- return;
35
- }
36
- content.format = "org.matrix.custom.html";
37
- content.formatted_body = formatted;
38
- }
39
-
40
- export function buildReplyRelation(replyToId?: string): MatrixReplyRelation | undefined {
41
- const trimmed = replyToId?.trim();
42
- if (!trimmed) {
43
- return undefined;
44
- }
45
- return { "m.in_reply_to": { event_id: trimmed } };
46
- }
47
-
48
- export function buildThreadRelation(threadId: string, replyToId?: string): MatrixThreadRelation {
49
- const trimmed = threadId.trim();
50
- return {
51
- rel_type: RelationType.Thread,
52
- event_id: trimmed,
53
- is_falling_back: true,
54
- "m.in_reply_to": { event_id: replyToId?.trim() || trimmed },
55
- };
56
- }
57
-
58
- export function resolveMatrixMsgType(contentType?: string, _fileName?: string): MatrixMediaMsgType {
59
- const kind = getCore().media.mediaKindFromMime(contentType ?? "");
60
- switch (kind) {
61
- case "image":
62
- return MsgType.Image;
63
- case "audio":
64
- return MsgType.Audio;
65
- case "video":
66
- return MsgType.Video;
67
- default:
68
- return MsgType.File;
69
- }
70
- }
71
-
72
- export function resolveMatrixVoiceDecision(opts: {
73
- wantsVoice: boolean;
74
- contentType?: string;
75
- fileName?: string;
76
- }): { useVoice: boolean } {
77
- if (!opts.wantsVoice) {
78
- return { useVoice: false };
79
- }
80
- if (isMatrixVoiceCompatibleAudio(opts)) {
81
- return { useVoice: true };
82
- }
83
- return { useVoice: false };
84
- }
85
-
86
- function isMatrixVoiceCompatibleAudio(opts: { contentType?: string; fileName?: string }): boolean {
87
- // Matrix currently shares the core voice compatibility policy.
88
- // Keep this wrapper as the seam if Matrix policy diverges later.
89
- return getCore().media.isVoiceCompatibleAudio({
90
- contentType: opts.contentType,
91
- fileName: opts.fileName,
92
- });
93
- }
@@ -1,230 +0,0 @@
1
- import type {
2
- DimensionalFileInfo,
3
- EncryptedFile,
4
- FileWithThumbnailInfo,
5
- MatrixClient,
6
- TimedFileInfo,
7
- VideoFileInfo,
8
- } from "@vector-im/matrix-bot-sdk";
9
- import { getMatrixRuntime } from "../../runtime.js";
10
- import { applyMatrixFormatting } from "./formatting.js";
11
- import {
12
- type MatrixMediaContent,
13
- type MatrixMediaInfo,
14
- type MatrixMediaMsgType,
15
- type MatrixRelation,
16
- type MediaKind,
17
- } from "./types.js";
18
-
19
- const getCore = () => getMatrixRuntime();
20
- type IFileInfo = import("music-metadata").IFileInfo;
21
-
22
- export function buildMatrixMediaInfo(params: {
23
- size: number;
24
- mimetype?: string;
25
- durationMs?: number;
26
- imageInfo?: DimensionalFileInfo;
27
- }): MatrixMediaInfo | undefined {
28
- const base: FileWithThumbnailInfo = {};
29
- if (Number.isFinite(params.size)) {
30
- base.size = params.size;
31
- }
32
- if (params.mimetype) {
33
- base.mimetype = params.mimetype;
34
- }
35
- if (params.imageInfo) {
36
- const dimensional: DimensionalFileInfo = {
37
- ...base,
38
- ...params.imageInfo,
39
- };
40
- if (typeof params.durationMs === "number") {
41
- const videoInfo: VideoFileInfo = {
42
- ...dimensional,
43
- duration: params.durationMs,
44
- };
45
- return videoInfo;
46
- }
47
- return dimensional;
48
- }
49
- if (typeof params.durationMs === "number") {
50
- const timedInfo: TimedFileInfo = {
51
- ...base,
52
- duration: params.durationMs,
53
- };
54
- return timedInfo;
55
- }
56
- if (Object.keys(base).length === 0) {
57
- return undefined;
58
- }
59
- return base;
60
- }
61
-
62
- export function buildMediaContent(params: {
63
- msgtype: MatrixMediaMsgType;
64
- body: string;
65
- url?: string;
66
- filename?: string;
67
- mimetype?: string;
68
- size: number;
69
- relation?: MatrixRelation;
70
- isVoice?: boolean;
71
- durationMs?: number;
72
- imageInfo?: DimensionalFileInfo;
73
- file?: EncryptedFile;
74
- }): MatrixMediaContent {
75
- const info = buildMatrixMediaInfo({
76
- size: params.size,
77
- mimetype: params.mimetype,
78
- durationMs: params.durationMs,
79
- imageInfo: params.imageInfo,
80
- });
81
- const base: MatrixMediaContent = {
82
- msgtype: params.msgtype,
83
- body: params.body,
84
- filename: params.filename,
85
- info: info ?? undefined,
86
- };
87
- // Encrypted media should only include the "file" payload, not top-level "url".
88
- if (!params.file && params.url) {
89
- base.url = params.url;
90
- }
91
- // For encrypted files, add the file object
92
- if (params.file) {
93
- base.file = params.file;
94
- }
95
- if (params.isVoice) {
96
- base["org.matrix.msc3245.voice"] = {};
97
- if (typeof params.durationMs === "number") {
98
- base["org.matrix.msc1767.audio"] = {
99
- duration: params.durationMs,
100
- };
101
- }
102
- }
103
- if (params.relation) {
104
- base["m.relates_to"] = params.relation;
105
- }
106
- applyMatrixFormatting(base, params.body);
107
- return base;
108
- }
109
-
110
- const THUMBNAIL_MAX_SIDE = 800;
111
- const THUMBNAIL_QUALITY = 80;
112
-
113
- export async function prepareImageInfo(params: {
114
- buffer: Buffer;
115
- client: MatrixClient;
116
- }): Promise<DimensionalFileInfo | undefined> {
117
- const meta = await getCore()
118
- .media.getImageMetadata(params.buffer)
119
- .catch(() => null);
120
- if (!meta) {
121
- return undefined;
122
- }
123
- const imageInfo: DimensionalFileInfo = { w: meta.width, h: meta.height };
124
- const maxDim = Math.max(meta.width, meta.height);
125
- if (maxDim > THUMBNAIL_MAX_SIDE) {
126
- try {
127
- const thumbBuffer = await getCore().media.resizeToJpeg({
128
- buffer: params.buffer,
129
- maxSide: THUMBNAIL_MAX_SIDE,
130
- quality: THUMBNAIL_QUALITY,
131
- withoutEnlargement: true,
132
- });
133
- const thumbMeta = await getCore()
134
- .media.getImageMetadata(thumbBuffer)
135
- .catch(() => null);
136
- const thumbUri = await params.client.uploadContent(
137
- thumbBuffer,
138
- "image/jpeg",
139
- "thumbnail.jpg",
140
- );
141
- imageInfo.thumbnail_url = thumbUri;
142
- if (thumbMeta) {
143
- imageInfo.thumbnail_info = {
144
- w: thumbMeta.width,
145
- h: thumbMeta.height,
146
- mimetype: "image/jpeg",
147
- size: thumbBuffer.byteLength,
148
- };
149
- }
150
- } catch {
151
- // Thumbnail generation failed, continue without it
152
- }
153
- }
154
- return imageInfo;
155
- }
156
-
157
- export async function resolveMediaDurationMs(params: {
158
- buffer: Buffer;
159
- contentType?: string;
160
- fileName?: string;
161
- kind: MediaKind;
162
- }): Promise<number | undefined> {
163
- if (params.kind !== "audio" && params.kind !== "video") {
164
- return undefined;
165
- }
166
- try {
167
- const { parseBuffer } = await import("music-metadata");
168
- const fileInfo: IFileInfo | string | undefined =
169
- params.contentType || params.fileName
170
- ? {
171
- mimeType: params.contentType,
172
- size: params.buffer.byteLength,
173
- path: params.fileName,
174
- }
175
- : undefined;
176
- const metadata = await parseBuffer(params.buffer, fileInfo, {
177
- duration: true,
178
- skipCovers: true,
179
- });
180
- const durationSeconds = metadata.format.duration;
181
- if (typeof durationSeconds === "number" && Number.isFinite(durationSeconds)) {
182
- return Math.max(0, Math.round(durationSeconds * 1000));
183
- }
184
- } catch {
185
- // Duration is optional; ignore parse failures.
186
- }
187
- return undefined;
188
- }
189
-
190
- async function uploadFile(
191
- client: MatrixClient,
192
- file: Buffer,
193
- params: {
194
- contentType?: string;
195
- filename?: string;
196
- },
197
- ): Promise<string> {
198
- return await client.uploadContent(file, params.contentType, params.filename);
199
- }
200
-
201
- /**
202
- * Upload media with optional encryption for E2EE rooms.
203
- */
204
- export async function uploadMediaMaybeEncrypted(
205
- client: MatrixClient,
206
- roomId: string,
207
- buffer: Buffer,
208
- params: {
209
- contentType?: string;
210
- filename?: string;
211
- },
212
- ): Promise<{ url: string; file?: EncryptedFile }> {
213
- // Check if room is encrypted and crypto is available
214
- const isEncrypted = client.crypto && (await client.crypto.isRoomEncrypted(roomId));
215
-
216
- if (isEncrypted && client.crypto) {
217
- // Encrypt the media before uploading
218
- const encrypted = await client.crypto.encryptMedia(buffer);
219
- const mxc = await client.uploadContent(encrypted.buffer, params.contentType, params.filename);
220
- const file: EncryptedFile = { url: mxc, ...encrypted.file };
221
- return {
222
- url: mxc,
223
- file,
224
- };
225
- }
226
-
227
- // Upload unencrypted
228
- const mxc = await uploadFile(client, buffer, params);
229
- return { url: mxc };
230
- }
@@ -1,98 +0,0 @@
1
- import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
- import { EventType } from "./types.js";
4
-
5
- let resolveMatrixRoomId: typeof import("./targets.js").resolveMatrixRoomId;
6
- let normalizeThreadId: typeof import("./targets.js").normalizeThreadId;
7
-
8
- beforeEach(async () => {
9
- vi.resetModules();
10
- ({ resolveMatrixRoomId, normalizeThreadId } = await import("./targets.js"));
11
- });
12
-
13
- describe("resolveMatrixRoomId", () => {
14
- it("uses m.direct when available", async () => {
15
- const userId = "@user:example.org";
16
- const client = {
17
- getAccountData: vi.fn().mockResolvedValue({
18
- [userId]: ["!room:example.org"],
19
- }),
20
- getJoinedRooms: vi.fn(),
21
- getJoinedRoomMembers: vi.fn(),
22
- setAccountData: vi.fn(),
23
- } as unknown as MatrixClient;
24
-
25
- const roomId = await resolveMatrixRoomId(client, userId);
26
-
27
- expect(roomId).toBe("!room:example.org");
28
- // oxlint-disable-next-line typescript/unbound-method
29
- expect(client.getJoinedRooms).not.toHaveBeenCalled();
30
- // oxlint-disable-next-line typescript/unbound-method
31
- expect(client.setAccountData).not.toHaveBeenCalled();
32
- });
33
-
34
- it("falls back to joined rooms and persists m.direct", async () => {
35
- const userId = "@fallback:example.org";
36
- const roomId = "!room:example.org";
37
- const setAccountData = vi.fn().mockResolvedValue(undefined);
38
- const client = {
39
- getAccountData: vi.fn().mockRejectedValue(new Error("nope")),
40
- getJoinedRooms: vi.fn().mockResolvedValue([roomId]),
41
- getJoinedRoomMembers: vi.fn().mockResolvedValue(["@bot:example.org", userId]),
42
- setAccountData,
43
- } as unknown as MatrixClient;
44
-
45
- const resolved = await resolveMatrixRoomId(client, userId);
46
-
47
- expect(resolved).toBe(roomId);
48
- expect(setAccountData).toHaveBeenCalledWith(
49
- EventType.Direct,
50
- expect.objectContaining({ [userId]: [roomId] }),
51
- );
52
- });
53
-
54
- it("continues when a room member lookup fails", async () => {
55
- const userId = "@continue:example.org";
56
- const roomId = "!good:example.org";
57
- const setAccountData = vi.fn().mockResolvedValue(undefined);
58
- const getJoinedRoomMembers = vi
59
- .fn()
60
- .mockRejectedValueOnce(new Error("boom"))
61
- .mockResolvedValueOnce(["@bot:example.org", userId]);
62
- const client = {
63
- getAccountData: vi.fn().mockRejectedValue(new Error("nope")),
64
- getJoinedRooms: vi.fn().mockResolvedValue(["!bad:example.org", roomId]),
65
- getJoinedRoomMembers,
66
- setAccountData,
67
- } as unknown as MatrixClient;
68
-
69
- const resolved = await resolveMatrixRoomId(client, userId);
70
-
71
- expect(resolved).toBe(roomId);
72
- expect(setAccountData).toHaveBeenCalled();
73
- });
74
-
75
- it("allows larger rooms when no 1:1 match exists", async () => {
76
- const userId = "@group:example.org";
77
- const roomId = "!group:example.org";
78
- const client = {
79
- getAccountData: vi.fn().mockRejectedValue(new Error("nope")),
80
- getJoinedRooms: vi.fn().mockResolvedValue([roomId]),
81
- getJoinedRoomMembers: vi
82
- .fn()
83
- .mockResolvedValue(["@bot:example.org", userId, "@extra:example.org"]),
84
- setAccountData: vi.fn().mockResolvedValue(undefined),
85
- } as unknown as MatrixClient;
86
-
87
- const resolved = await resolveMatrixRoomId(client, userId);
88
-
89
- expect(resolved).toBe(roomId);
90
- });
91
- });
92
-
93
- describe("normalizeThreadId", () => {
94
- it("returns null for empty thread ids", () => {
95
- expect(normalizeThreadId(" ")).toBeNull();
96
- expect(normalizeThreadId("$thread")).toBe("$thread");
97
- });
98
- });