@openclaw/matrix 2026.7.2-beta.7 → 2026.8.1-beta.2
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/dist/{account-selection-kqquRzCf.js → account-selection-CWuhuFn1.js} +7 -8
- package/dist/{accounts-DfngqebB.js → accounts-BCzyubQt.js} +3 -3
- package/dist/api.js +7 -10
- package/dist/{approval-handler.runtime-DSeaIsGa.js → approval-handler.runtime-CJXCUdRg.js} +5 -5
- package/dist/{approval-ids-BdJGPAJT.js → approval-ids-7rf7etWE.js} +1 -1
- package/dist/{approval-reaction-auth-BjOpELxu.js → approval-reaction-auth-wBwbEDpZ.js} +2 -2
- package/dist/{approval-reactions-D-BxBK_L.js → approval-reactions-tXh9KYrW.js} +2 -8
- package/dist/auth-presence.js +2 -2
- package/dist/{channel-vj788dNZ.js → channel-VSrHgTON.js} +156 -136
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-CLoF2S9T.js → channel.runtime-pOOL0uT_.js} +70 -56
- package/dist/{config-schema-Djxw3C6b.js → channel.setup-PpxRP9Du.js} +50 -3
- package/dist/{cli-DMqfDE-q.js → cli-CkTfKC_c.js} +16 -16
- package/dist/{cli-metadata-C7D53SND.js → cli-metadata-B1d1aGM5.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/client-CeGw0G5S.js +784 -0
- package/dist/{client-bootstrap-XsIkHNt0.js → client-bootstrap-BFwT_2VT.js} +25 -61
- package/dist/{client-6eRENr7k.js → client-sJesKQmp.js} +4 -4
- package/dist/client-support-DLj4vDeu.js +93 -0
- package/dist/config-update-Bor_Pjsv.js +150 -0
- package/dist/contract-api.js +2 -2
- package/dist/{credentials-W2EmwRhu.js → credentials-BdAaWuiR.js} +2 -1
- package/dist/credentials-read-B37cQknZ.js +51 -0
- package/dist/credentials-state-B3cuUFwZ.js +27 -0
- package/dist/{credentials-write.runtime-Be9zNSUw.js → credentials-write.runtime-nAV2-5uR.js} +1 -1
- package/dist/{crypto-runtime-D9_6J5tZ.js → crypto-runtime-Bnz3zEcS.js} +55 -32
- package/dist/direct-room-RinHjtqX.js +71 -0
- package/dist/{directory-live-C8dVBiCc.js → directory-live-Bvg2wAnR.js} +1 -2
- package/dist/{doctor-CM8ijNpW.js → doctor-EWf9CDso.js} +2 -2
- package/dist/{doctor-contract-BMSJkkAE.js → doctor-contract-DEt4O4Zt.js} +2 -3
- package/dist/doctor-contract-api.js +202 -90
- package/dist/{draft-stream-CtvOXq0Z.js → draft-stream-BRi7PRRS.js} +20 -23
- package/dist/{encryption-guidance-CbEFe38F.js → encryption-guidance-DmLY8NiO.js} +1 -1
- package/dist/{env-auth-CQsYbz-p.js → env-auth-MVS-Vv41.js} +4 -3
- package/dist/{env-vars-B0NxQnPk.js → env-vars-dGqak9dN.js} +1 -1
- package/dist/{errors-C47hvAF8.js → errors-DKVS3_DO.js} +2 -5
- package/dist/{file-sync-store-DiiFsayX.js → file-sync-store-DjskBX2o.js} +24 -3
- package/dist/helper-api.js +2 -2
- package/dist/{idb-persistence-B6x7dMsF.js → idb-persistence-BM0P27Zg.js} +3 -2
- package/dist/{inbound-dedupe-CaBLblef.js → inbound-dedupe-RvSLqdsz.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{logger-D0GCSDQq.js → logger-B_71hN6W.js} +1 -1
- package/dist/{logging-DV-p52N0.js → logging-DRjvUOIh.js} +1 -1
- package/dist/{media-text-BTwzADS_.js → media-text-JudlYOSc.js} +1 -1
- package/dist/{messages-DxQJkhxs.js → messages-BW0s-6Y9.js} +7 -7
- package/dist/{monitor-VyWlSLwo.js → monitor-Ddfc-vjX.js} +394 -384
- package/dist/plugin-entry.handlers.runtime.js +3 -3
- package/dist/probe.runtime-Dr91B_X3.js +2 -0
- package/dist/profile-D-Wubo0A.js +108 -0
- package/dist/{profile-update-CffmyW_4.js → profile-update-BkgWYDIp.js} +3 -2
- package/dist/{reaction-events-B00WSi98.js → reaction-events-nfUbFLdV.js} +9 -6
- package/dist/{recovery-key-store-BNnHvXdk.js → recovery-key-store-CognuDys.js} +5 -4
- package/dist/{resolve-targets-nm2L48sh.js → resolve-targets-BJAPY4Vb.js} +1 -1
- package/dist/{resolver.runtime-DS6uPaEe.js → resolver.runtime-LWJELYq5.js} +1 -1
- package/dist/{room-info-CAFZ3Z9J.js → room-info-BXQA7y10.js} +12 -3
- package/dist/runtime-api.js +6 -3
- package/dist/{sdk-bUytQHx7.js → sdk-DjaQFCHc.js} +229 -120
- package/dist/{secret-contract-M3hRmGO-.js → secret-contract-CufozuyJ.js} +1 -1
- package/dist/secret-contract-api.js +1 -1
- package/dist/{send-BldGYG8g.js → send-ZDLU4JTk.js} +1462 -1196
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/{setup-bootstrap-Deleiibz.js → setup-bootstrap-BaXkhgJg.js} +4 -4
- package/dist/{setup-core-BVfBv2ai.js → setup-core-DQn6b5vi.js} +17 -266
- package/dist/setup-plugin-api.js +1 -44
- package/dist/{setup-surface-BBq3WeqX.js → setup-surface-DlhJdrNI.js} +9 -11
- package/dist/{startup-verification-wm3_i88s.js → startup-verification-Cs08ca8u.js} +3 -3
- package/dist/{storage-BvAkgzvT.js → storage-BsGuHngK.js} +3 -3
- package/dist/{thread-bindings-DGbl3vDW.js → thread-bindings-CcHZP3w2.js} +7 -4
- package/dist/{tool-actions.runtime-uu9blSqO.js → tool-actions.runtime-DmdRL0bN.js} +35 -18
- package/dist/{verification-DYMQ1QpI.js → verification-C7ig-bQE.js} +2 -2
- package/openclaw.plugin.json +54 -1
- package/package.json +4 -4
- package/dist/active-client-DXvz2gCo.js +0 -20
- package/dist/client-Dbso3-0V.js +0 -30
- package/dist/create-client-BmSU-q-D.js +0 -61
- package/dist/credentials-read-DVmFCm7X.js +0 -77
- package/dist/exec-approval-resolver-BCgqRtZS.js +0 -16
- package/dist/preflight-audio.runtime-BZxxQsxY.js +0 -11
- package/dist/probe.runtime-BuieqmS3.js +0 -3
- package/dist/record-shared-CWWx6fvI.js +0 -2
- package/dist/shared-CUMWxcMz.js +0 -673
- package/dist/sync-state-CWbp0QSY.js +0 -12
- package/dist/{runtime-api-D1xI_cvh.js → runtime-api-BGO16Fu8.js} +3 -3
|
@@ -2,235 +2,819 @@ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
|
2
2
|
import { a as resolveMatrixAccountConfig } from "./account-config-BZWN9tGS.js";
|
|
3
3
|
import { t as getMatrixRuntime } from "./runtime-6S3DNFNv.js";
|
|
4
4
|
import { r as normalizeMatrixResolvableTarget, t as isMatrixQualifiedUserId } from "./target-ids-B-5aQxwn.js";
|
|
5
|
+
import { a as isStrictDirectRoom, r as inspectMatrixDirectRoomEvidence } from "./direct-room-RinHjtqX.js";
|
|
5
6
|
import { n as MATRIX_REACTION_EVENT_TYPE, r as buildMatrixReactionContent, t as MATRIX_ANNOTATION_RELATION_TYPE } from "./reaction-common-DkrQdBSZ.js";
|
|
6
7
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
7
8
|
import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
|
|
8
|
-
import { normalizeLowercaseStringOrEmpty,
|
|
9
|
+
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeOptionalStringifiedId } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
9
10
|
import { FormatCapabilityProfile, convertMarkdownTables, findCodeRegions, isInsideCode, markdownToIR, renderMarkdownWithMarkers, tokenizeHtmlTags } from "openclaw/plugin-sdk/text-chunking";
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
10
13
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
14
|
+
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
11
15
|
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
|
|
12
16
|
import { normalizePollInput } from "openclaw/plugin-sdk/poll-runtime";
|
|
13
17
|
import MarkdownIt from "markdown-it";
|
|
14
18
|
import { isAutoLinkedFileRef } from "openclaw/plugin-sdk/text-autolink-runtime";
|
|
15
19
|
import { isVoiceMessageCompatibleAudio } from "openclaw/plugin-sdk/media-runtime";
|
|
16
20
|
import { parseBuffer } from "music-metadata";
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
//#region extensions/matrix/src/matrix/send/client.ts
|
|
22
|
+
const loadMatrixSendClientRuntime = createLazyRuntimeModule(() => import("./client-bootstrap-BFwT_2VT.js").then((n) => n.t));
|
|
23
|
+
function resolveMediaMaxBytes(accountId, cfg) {
|
|
24
|
+
if (!cfg) throw new Error("Matrix media limits requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.");
|
|
25
|
+
const mediaMaxMb = resolveMatrixAccountConfig({
|
|
26
|
+
cfg: requireRuntimeConfig(cfg, "Matrix media limits"),
|
|
27
|
+
accountId
|
|
28
|
+
}).mediaMaxMb;
|
|
29
|
+
return typeof mediaMaxMb === "number" && mediaMaxMb > 0 ? mediaMaxMb * 1024 * 1024 : void 0;
|
|
30
|
+
}
|
|
31
|
+
async function withResolvedMatrixSendClient(opts, run) {
|
|
32
|
+
return await withResolvedMatrixClient({
|
|
33
|
+
...opts,
|
|
34
|
+
readiness: "started"
|
|
35
|
+
}, run, "persist");
|
|
36
|
+
}
|
|
37
|
+
async function withResolvedMatrixControlClient(opts, run) {
|
|
38
|
+
return await withResolvedMatrixClient({
|
|
39
|
+
...opts,
|
|
40
|
+
readiness: "none"
|
|
41
|
+
}, run);
|
|
42
|
+
}
|
|
43
|
+
async function withResolvedMatrixClient(opts, run, shutdownBehavior) {
|
|
44
|
+
if (opts.client) return await run(opts.client);
|
|
45
|
+
const { withResolvedRuntimeMatrixClient } = await loadMatrixSendClientRuntime();
|
|
46
|
+
return await withResolvedRuntimeMatrixClient(opts, run, shutdownBehavior);
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region extensions/matrix/src/matrix/send/types.ts
|
|
50
|
+
const MsgType = {
|
|
51
|
+
Text: "m.text",
|
|
52
|
+
Image: "m.image",
|
|
53
|
+
Audio: "m.audio",
|
|
54
|
+
Video: "m.video",
|
|
55
|
+
File: "m.file",
|
|
56
|
+
Notice: "m.notice"
|
|
57
|
+
};
|
|
58
|
+
const RelationType = {
|
|
59
|
+
Annotation: MATRIX_ANNOTATION_RELATION_TYPE,
|
|
60
|
+
Replace: "m.replace",
|
|
61
|
+
Thread: "m.thread"
|
|
62
|
+
};
|
|
63
|
+
const EventType = {
|
|
64
|
+
Direct: "m.direct",
|
|
65
|
+
Reaction: MATRIX_REACTION_EVENT_TYPE,
|
|
66
|
+
RoomMessage: "m.room.message"
|
|
67
|
+
};
|
|
68
|
+
const MATRIX_OPENCLAW_FINALIZED_PREVIEW_KEY = "com.openclaw.finalized_preview";
|
|
19
69
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
70
|
+
* MSC4357 live marker key.
|
|
71
|
+
* When present on event content, signals that the message is still being
|
|
72
|
+
* streamed (e.g. an LLM generating a response). Supporting clients render
|
|
73
|
+
* the message with a streaming animation until an edit without this marker
|
|
74
|
+
* arrives, indicating the stream is complete.
|
|
75
|
+
* @see https://github.com/matrix-org/matrix-spec-proposals/pull/4357
|
|
26
76
|
*/
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return POLL_START_TYPES.includes(eventType);
|
|
77
|
+
const MSC4357_LIVE_KEY = "org.matrix.msc4357.live";
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region extensions/matrix/src/matrix/direct-management.ts
|
|
80
|
+
var direct_management_exports = /* @__PURE__ */ __exportAll({
|
|
81
|
+
inspectMatrixDirectRooms: () => inspectMatrixDirectRooms,
|
|
82
|
+
persistMatrixDirectRoomMapping: () => persistMatrixDirectRoomMapping,
|
|
83
|
+
promoteMatrixDirectRoomCandidate: () => promoteMatrixDirectRoomCandidate,
|
|
84
|
+
repairMatrixDirectRooms: () => repairMatrixDirectRooms
|
|
85
|
+
});
|
|
86
|
+
const DIRECT_ACCOUNT_DATA_QUEUE_KEY = EventType.Direct;
|
|
87
|
+
const directAccountDataWriteQueues = /* @__PURE__ */ new WeakMap();
|
|
88
|
+
async function readMatrixDirectAccountData(client) {
|
|
89
|
+
try {
|
|
90
|
+
const direct = await client.getAccountData(EventType.Direct);
|
|
91
|
+
return direct && typeof direct === "object" && !Array.isArray(direct) ? direct : {};
|
|
92
|
+
} catch {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
46
95
|
}
|
|
47
|
-
function
|
|
48
|
-
|
|
96
|
+
function normalizeRemoteUserId(remoteUserId) {
|
|
97
|
+
const normalized = normalizeOptionalString(remoteUserId) ?? "";
|
|
98
|
+
if (!isMatrixQualifiedUserId(normalized)) throw new Error(`Matrix user IDs must be fully qualified (got "${remoteUserId}")`);
|
|
99
|
+
return normalized;
|
|
49
100
|
}
|
|
50
|
-
function
|
|
51
|
-
|
|
101
|
+
function normalizeMappedRoomIds(direct, remoteUserId) {
|
|
102
|
+
const current = direct[remoteUserId];
|
|
103
|
+
if (!Array.isArray(current)) return [];
|
|
104
|
+
const seen = /* @__PURE__ */ new Set();
|
|
105
|
+
const normalized = [];
|
|
106
|
+
for (const value of current) {
|
|
107
|
+
const roomId = normalizeOptionalString(value) ?? "";
|
|
108
|
+
if (!roomId || seen.has(roomId)) continue;
|
|
109
|
+
seen.add(roomId);
|
|
110
|
+
normalized.push(roomId);
|
|
111
|
+
}
|
|
112
|
+
return normalized;
|
|
52
113
|
}
|
|
53
|
-
function
|
|
54
|
-
|
|
114
|
+
function normalizeRoomIdList(values) {
|
|
115
|
+
const seen = /* @__PURE__ */ new Set();
|
|
116
|
+
const normalized = [];
|
|
117
|
+
for (const value of values) {
|
|
118
|
+
const roomId = value.trim();
|
|
119
|
+
if (!roomId || seen.has(roomId)) continue;
|
|
120
|
+
seen.add(roomId);
|
|
121
|
+
normalized.push(roomId);
|
|
122
|
+
}
|
|
123
|
+
return normalized;
|
|
55
124
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
125
|
+
function hasMatrixDirectRoomMappings(params) {
|
|
126
|
+
const current = normalizeMappedRoomIds(params.directContent, params.remoteUserId);
|
|
127
|
+
const next = normalizeRoomIdList([...params.roomIds, ...current]);
|
|
128
|
+
return current.length === next.length && current.every((roomId, index) => roomId === next[index]);
|
|
59
129
|
}
|
|
60
|
-
function
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
130
|
+
function resolveDirectAccountDataWriteQueue(client) {
|
|
131
|
+
const existing = directAccountDataWriteQueues.get(client);
|
|
132
|
+
if (existing) return existing;
|
|
133
|
+
const created = new KeyedAsyncQueue();
|
|
134
|
+
directAccountDataWriteQueues.set(client, created);
|
|
135
|
+
return created;
|
|
136
|
+
}
|
|
137
|
+
async function writeMatrixDirectRoomMappings(params) {
|
|
138
|
+
return await resolveDirectAccountDataWriteQueue(params.client).enqueue(DIRECT_ACCOUNT_DATA_QUEUE_KEY, async () => {
|
|
139
|
+
const directContentBefore = await readMatrixDirectAccountData(params.client);
|
|
140
|
+
const directContentAfter = buildNextDirectContent({
|
|
141
|
+
directContent: directContentBefore,
|
|
142
|
+
remoteUserId: params.remoteUserId,
|
|
143
|
+
roomIds: params.roomIds
|
|
144
|
+
});
|
|
145
|
+
const changed = !hasMatrixDirectRoomMappings({
|
|
146
|
+
directContent: directContentBefore,
|
|
147
|
+
remoteUserId: params.remoteUserId,
|
|
148
|
+
roomIds: params.roomIds
|
|
149
|
+
});
|
|
150
|
+
if (changed) await params.client.setAccountData(EventType.Direct, directContentAfter);
|
|
151
|
+
return {
|
|
152
|
+
changed,
|
|
153
|
+
directContentBefore,
|
|
154
|
+
directContentAfter
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async function classifyDirectRoomCandidate(params) {
|
|
159
|
+
const evidence = await inspectMatrixDirectRoomEvidence({
|
|
160
|
+
client: params.client,
|
|
161
|
+
roomId: params.roomId,
|
|
162
|
+
remoteUserId: params.remoteUserId,
|
|
163
|
+
selfUserId: params.selfUserId
|
|
164
|
+
});
|
|
72
165
|
return {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
166
|
+
roomId: params.roomId,
|
|
167
|
+
joinedMembers: evidence.joinedMembers,
|
|
168
|
+
strict: evidence.strict && (params.source === "account-data" || evidence.memberStateFlag !== false),
|
|
169
|
+
explicit: evidence.strict && (params.source === "account-data" || evidence.memberStateFlag !== false) && (params.source === "account-data" || evidence.viaMemberState),
|
|
170
|
+
source: params.source
|
|
77
171
|
};
|
|
78
172
|
}
|
|
79
|
-
function
|
|
80
|
-
const
|
|
81
|
-
|
|
173
|
+
function buildNextDirectContent(params) {
|
|
174
|
+
const current = normalizeMappedRoomIds(params.directContent, params.remoteUserId);
|
|
175
|
+
const nextRooms = normalizeRoomIdList([...params.roomIds, ...current]);
|
|
82
176
|
return {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
sender: "",
|
|
86
|
-
senderName: "",
|
|
87
|
-
question: parsed.question,
|
|
88
|
-
answers: parsed.answers.map((answer) => answer.text),
|
|
89
|
-
kind: parsed.kind,
|
|
90
|
-
maxSelections: parsed.maxSelections
|
|
177
|
+
...params.directContent,
|
|
178
|
+
[params.remoteUserId]: nextRooms
|
|
91
179
|
};
|
|
92
180
|
}
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
function resolvePollReferenceEventId(content) {
|
|
102
|
-
if (!content || typeof content !== "object") return null;
|
|
103
|
-
const relates = content["m.relates_to"];
|
|
104
|
-
if (!relates || typeof relates.event_id !== "string") return null;
|
|
105
|
-
const eventId = relates.event_id.trim();
|
|
106
|
-
return eventId.length > 0 ? eventId : null;
|
|
107
|
-
}
|
|
108
|
-
function parsePollResponseAnswerIds(content) {
|
|
109
|
-
if (!content || typeof content !== "object") return null;
|
|
110
|
-
const response = content[M_POLL_RESPONSE] ?? content[ORG_POLL_RESPONSE];
|
|
111
|
-
if (!response || !Array.isArray(response.answers)) return null;
|
|
112
|
-
return response.answers.filter((answer) => typeof answer === "string");
|
|
181
|
+
async function persistMatrixDirectRoomMapping(params) {
|
|
182
|
+
const remoteUserId = normalizeRemoteUserId(params.remoteUserId);
|
|
183
|
+
return (await writeMatrixDirectRoomMappings({
|
|
184
|
+
client: params.client,
|
|
185
|
+
remoteUserId,
|
|
186
|
+
roomIds: [params.roomId]
|
|
187
|
+
})).changed;
|
|
113
188
|
}
|
|
114
|
-
function
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (!senderId) continue;
|
|
138
|
-
const eventTs = typeof event.origin_server_ts === "number" && Number.isFinite(event.origin_server_ts) ? event.origin_server_ts : Number.POSITIVE_INFINITY;
|
|
139
|
-
if (eventTs > pollClosedAt) continue;
|
|
140
|
-
const rawAnswers = parsePollResponseAnswerIds(event.content) ?? [];
|
|
141
|
-
const normalizedAnswers = Array.from(new Set(rawAnswers.map((answerId) => normalizeOptionalString(answerId) ?? "").filter((answerId) => answerIds.has(answerId)).slice(0, parsed.maxSelections)));
|
|
142
|
-
latestVoteBySender.set(senderId, {
|
|
143
|
-
ts: eventTs,
|
|
144
|
-
eventId: typeof event.event_id === "string" ? event.event_id : "",
|
|
145
|
-
answerIds: normalizedAnswers
|
|
189
|
+
async function promoteMatrixDirectRoomCandidate(params) {
|
|
190
|
+
const remoteUserId = normalizeRemoteUserId(params.remoteUserId);
|
|
191
|
+
const evidence = await inspectMatrixDirectRoomEvidence({
|
|
192
|
+
client: params.client,
|
|
193
|
+
roomId: params.roomId,
|
|
194
|
+
remoteUserId,
|
|
195
|
+
selfUserId: params.selfUserId
|
|
196
|
+
});
|
|
197
|
+
if (!evidence.strict) return {
|
|
198
|
+
classifyAsDirect: false,
|
|
199
|
+
repaired: false,
|
|
200
|
+
reason: "not-strict"
|
|
201
|
+
};
|
|
202
|
+
if (evidence.memberStateFlag === false) return {
|
|
203
|
+
classifyAsDirect: false,
|
|
204
|
+
repaired: false,
|
|
205
|
+
reason: "local-explicit-false"
|
|
206
|
+
};
|
|
207
|
+
try {
|
|
208
|
+
const repaired = await persistMatrixDirectRoomMapping({
|
|
209
|
+
client: params.client,
|
|
210
|
+
remoteUserId,
|
|
211
|
+
roomId: params.roomId
|
|
146
212
|
});
|
|
213
|
+
return {
|
|
214
|
+
classifyAsDirect: true,
|
|
215
|
+
repaired,
|
|
216
|
+
roomId: params.roomId,
|
|
217
|
+
reason: repaired ? "promoted" : "already-mapped"
|
|
218
|
+
};
|
|
219
|
+
} catch {
|
|
220
|
+
return {
|
|
221
|
+
classifyAsDirect: true,
|
|
222
|
+
repaired: false,
|
|
223
|
+
roomId: params.roomId,
|
|
224
|
+
reason: "repair-failed"
|
|
225
|
+
};
|
|
147
226
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
227
|
+
}
|
|
228
|
+
async function inspectMatrixDirectRooms(params) {
|
|
229
|
+
const remoteUserId = normalizeRemoteUserId(params.remoteUserId);
|
|
230
|
+
const selfUserId = normalizeOptionalString(await params.client.getUserId().catch(() => null)) ?? null;
|
|
231
|
+
const mappedRoomIds = normalizeMappedRoomIds(await readMatrixDirectAccountData(params.client), remoteUserId);
|
|
232
|
+
const mappedRooms = await Promise.all(mappedRoomIds.map(async (roomId) => await classifyDirectRoomCandidate({
|
|
233
|
+
client: params.client,
|
|
234
|
+
roomId,
|
|
235
|
+
remoteUserId,
|
|
236
|
+
selfUserId,
|
|
237
|
+
source: "account-data"
|
|
238
|
+
})));
|
|
239
|
+
const mappedStrict = mappedRooms.find((room) => room.strict);
|
|
240
|
+
let joinedRooms = [];
|
|
241
|
+
if (typeof params.client.getJoinedRooms === "function") try {
|
|
242
|
+
const resolved = await params.client.getJoinedRooms();
|
|
243
|
+
joinedRooms = Array.isArray(resolved) ? resolved : [];
|
|
244
|
+
} catch {
|
|
245
|
+
joinedRooms = [];
|
|
246
|
+
}
|
|
247
|
+
const discoveredStrictRooms = [];
|
|
248
|
+
for (const roomId of normalizeRoomIdList(joinedRooms)) {
|
|
249
|
+
if (mappedRoomIds.includes(roomId)) continue;
|
|
250
|
+
const candidate = await classifyDirectRoomCandidate({
|
|
251
|
+
client: params.client,
|
|
252
|
+
roomId,
|
|
253
|
+
remoteUserId,
|
|
254
|
+
selfUserId,
|
|
255
|
+
source: "joined"
|
|
256
|
+
});
|
|
257
|
+
if (candidate.strict) discoveredStrictRooms.push(candidate);
|
|
154
258
|
}
|
|
259
|
+
const discoveredStrictRoomIds = discoveredStrictRooms.map((room) => room.roomId);
|
|
260
|
+
const discoveredExplicit = discoveredStrictRooms.find((room) => room.explicit);
|
|
155
261
|
return {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
kind: parsed.kind,
|
|
163
|
-
maxSelections: parsed.maxSelections,
|
|
164
|
-
entries: parsed.answers.map((answer) => ({
|
|
165
|
-
id: answer.id,
|
|
166
|
-
text: answer.text,
|
|
167
|
-
votes: voteCounts.get(answer.id) ?? 0
|
|
168
|
-
})),
|
|
169
|
-
totalVotes,
|
|
170
|
-
closed: Number.isFinite(pollClosedAt)
|
|
262
|
+
selfUserId,
|
|
263
|
+
remoteUserId,
|
|
264
|
+
mappedRoomIds,
|
|
265
|
+
mappedRooms,
|
|
266
|
+
discoveredStrictRoomIds,
|
|
267
|
+
activeRoomId: mappedStrict?.roomId ?? discoveredExplicit?.roomId ?? discoveredStrictRoomIds[0] ?? null
|
|
171
268
|
};
|
|
172
269
|
}
|
|
173
|
-
function
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
lines.push("");
|
|
188
|
-
if (!revealResults) lines.push("Responses are hidden until the poll closes.");
|
|
189
|
-
else lines.push(`Total voters: ${summary.totalVotes}`);
|
|
190
|
-
return lines.join("\n");
|
|
191
|
-
}
|
|
192
|
-
function buildTextContent$1(body) {
|
|
270
|
+
async function repairMatrixDirectRooms(params) {
|
|
271
|
+
const remoteUserId = normalizeRemoteUserId(params.remoteUserId);
|
|
272
|
+
const inspected = await inspectMatrixDirectRooms({
|
|
273
|
+
client: params.client,
|
|
274
|
+
remoteUserId
|
|
275
|
+
});
|
|
276
|
+
const activeRoomId = inspected.activeRoomId ?? await params.client.createDirectRoom(remoteUserId, { encrypted: params.encrypted === true });
|
|
277
|
+
const createdRoomId = inspected.activeRoomId ? null : activeRoomId;
|
|
278
|
+
const mappingWrite = await writeMatrixDirectRoomMappings({
|
|
279
|
+
client: params.client,
|
|
280
|
+
remoteUserId,
|
|
281
|
+
roomIds: [activeRoomId, ...inspected.discoveredStrictRoomIds]
|
|
282
|
+
});
|
|
193
283
|
return {
|
|
194
|
-
|
|
195
|
-
|
|
284
|
+
...inspected,
|
|
285
|
+
activeRoomId,
|
|
286
|
+
createdRoomId,
|
|
287
|
+
changed: mappingWrite.changed,
|
|
288
|
+
directContentBefore: mappingWrite.directContentBefore,
|
|
289
|
+
directContentAfter: mappingWrite.directContentAfter
|
|
196
290
|
};
|
|
197
291
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region extensions/matrix/src/matrix/send/targets.ts
|
|
294
|
+
function normalizeTarget(raw) {
|
|
295
|
+
const trimmed = raw.trim();
|
|
296
|
+
if (!trimmed) throw new Error("Matrix target is required (room:<id> or #alias)");
|
|
297
|
+
return trimmed;
|
|
201
298
|
}
|
|
202
|
-
function
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
299
|
+
function normalizeThreadId(raw) {
|
|
300
|
+
return normalizeOptionalStringifiedId(raw) ?? null;
|
|
301
|
+
}
|
|
302
|
+
const MAX_DIRECT_ROOM_CACHE_SIZE = 1024;
|
|
303
|
+
const directRoomCacheByClient = /* @__PURE__ */ new WeakMap();
|
|
304
|
+
function resolveDirectRoomCache(client) {
|
|
305
|
+
const existing = directRoomCacheByClient.get(client);
|
|
306
|
+
if (existing) return existing;
|
|
307
|
+
const created = /* @__PURE__ */ new Map();
|
|
308
|
+
directRoomCacheByClient.set(client, created);
|
|
309
|
+
return created;
|
|
310
|
+
}
|
|
311
|
+
function setDirectRoomCached(client, key, value) {
|
|
312
|
+
const directRoomCache = resolveDirectRoomCache(client);
|
|
313
|
+
directRoomCache.set(key, value);
|
|
314
|
+
if (directRoomCache.size > MAX_DIRECT_ROOM_CACHE_SIZE) {
|
|
315
|
+
const oldest = directRoomCache.keys().next().value;
|
|
316
|
+
if (oldest !== void 0) directRoomCache.delete(oldest);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function resolveDirectRoomId(client, userId) {
|
|
320
|
+
const trimmed = userId.trim();
|
|
321
|
+
if (!isMatrixQualifiedUserId(trimmed)) throw new Error(`Matrix user IDs must be fully qualified (got "${trimmed}")`);
|
|
322
|
+
const selfUserId = (await client.getUserId().catch(() => null))?.trim() || null;
|
|
323
|
+
const directRoomCache = resolveDirectRoomCache(client);
|
|
324
|
+
const cached = directRoomCache.get(trimmed);
|
|
325
|
+
if (cached && await isStrictDirectRoom({
|
|
326
|
+
client,
|
|
327
|
+
roomId: cached,
|
|
328
|
+
remoteUserId: trimmed,
|
|
329
|
+
selfUserId
|
|
330
|
+
})) return cached;
|
|
331
|
+
if (cached) directRoomCache.delete(trimmed);
|
|
332
|
+
const inspection = await inspectMatrixDirectRooms({
|
|
333
|
+
client,
|
|
334
|
+
remoteUserId: trimmed
|
|
335
|
+
});
|
|
336
|
+
if (inspection.activeRoomId) {
|
|
337
|
+
setDirectRoomCached(client, trimmed, inspection.activeRoomId);
|
|
338
|
+
if (inspection.mappedRoomIds[0] !== inspection.activeRoomId) await persistMatrixDirectRoomMapping({
|
|
339
|
+
client,
|
|
340
|
+
remoteUserId: trimmed,
|
|
341
|
+
roomId: inspection.activeRoomId
|
|
342
|
+
}).catch(() => {});
|
|
343
|
+
return inspection.activeRoomId;
|
|
344
|
+
}
|
|
345
|
+
throw new Error(`No direct room found for ${trimmed} (m.direct missing)`);
|
|
346
|
+
}
|
|
347
|
+
async function resolveMatrixRoomId(client, raw) {
|
|
348
|
+
const target = normalizeMatrixResolvableTarget(normalizeTarget(raw));
|
|
349
|
+
if (normalizeLowercaseStringOrEmpty(target).startsWith("user:")) return await resolveDirectRoomId(client, target.slice(5));
|
|
350
|
+
if (isMatrixQualifiedUserId(target)) return await resolveDirectRoomId(client, target);
|
|
351
|
+
if (target.startsWith("#")) {
|
|
352
|
+
const resolved = await client.resolveRoom(target);
|
|
353
|
+
if (!resolved) throw new Error(`Matrix alias ${target} could not be resolved`);
|
|
354
|
+
return resolved;
|
|
355
|
+
}
|
|
356
|
+
return target;
|
|
357
|
+
}
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region extensions/matrix/src/matrix/delivery-plan.ts
|
|
360
|
+
const DELIVERY_PLAN_VERSION = 1;
|
|
361
|
+
const DELIVERY_PLAN_NAMESPACE = "outbound-delivery-plans";
|
|
362
|
+
const DELIVERY_PLAN_TTL_MS = 1440 * 60 * 1e3;
|
|
363
|
+
var MatrixDeliveryPlanInvariantError = class extends Error {
|
|
364
|
+
constructor(message) {
|
|
365
|
+
super(message);
|
|
366
|
+
this.name = "MatrixDeliveryPlanInvariantError";
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
function createDeliveryPlanStore() {
|
|
370
|
+
return getMatrixRuntime().state.openBlobStore({
|
|
371
|
+
namespace: DELIVERY_PLAN_NAMESPACE,
|
|
372
|
+
maxEntries: 1e4,
|
|
373
|
+
maxBytesPerEntry: 8 * 1024 * 1024,
|
|
374
|
+
maxBytesPerNamespace: 256 * 1024 * 1024,
|
|
375
|
+
overflowPolicy: "reject-new",
|
|
376
|
+
defaultTtlMs: DELIVERY_PLAN_TTL_MS
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function requireIndex(value, label) {
|
|
380
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`Matrix durable delivery ${label} must be a non-negative integer`);
|
|
381
|
+
return value;
|
|
382
|
+
}
|
|
383
|
+
function requirePartCount(value) {
|
|
384
|
+
if (!Number.isSafeInteger(value) || (value ?? 0) < 1) throw new Error("Matrix durable delivery part count must be a positive integer");
|
|
385
|
+
return value;
|
|
386
|
+
}
|
|
387
|
+
function queuePrefix(queueId) {
|
|
388
|
+
const normalized = queueId.trim();
|
|
389
|
+
if (!normalized) throw new Error("Matrix durable delivery requires a queue id");
|
|
390
|
+
return `${createHash("sha256").update(normalized).digest("hex")}.`;
|
|
391
|
+
}
|
|
392
|
+
function planKey(identity) {
|
|
393
|
+
return `${queuePrefix(identity.queueId)}${requireIndex(identity.partIndex, "part index")}`;
|
|
394
|
+
}
|
|
395
|
+
function transactionId(identity, eventIndex) {
|
|
396
|
+
return `oc_${createHash("sha256").update(identity.queueId).update("\0").update(String(requireIndex(identity.partIndex, "part index"))).update("\0").update(String(requireIndex(eventIndex, "event index"))).digest("base64url")}`;
|
|
397
|
+
}
|
|
398
|
+
const RECEIPT_KINDS = /* @__PURE__ */ new Set([
|
|
399
|
+
"text",
|
|
400
|
+
"media",
|
|
401
|
+
"voice",
|
|
402
|
+
"poll",
|
|
403
|
+
"card",
|
|
404
|
+
"preview",
|
|
405
|
+
"unknown"
|
|
406
|
+
]);
|
|
407
|
+
function isPlan(value) {
|
|
408
|
+
if (!value || typeof value !== "object") return false;
|
|
409
|
+
const plan = value;
|
|
410
|
+
return plan.version === DELIVERY_PLAN_VERSION && typeof plan.queueId === "string" && Boolean(plan.queueId.trim()) && typeof plan.accountId === "string" && typeof plan.roomId === "string" && Boolean(plan.roomId.trim()) && (plan.wireEventType === "m.room.message" || plan.wireEventType === "m.room.encrypted") && typeof plan.endpointPrefix === "string" && Boolean(plan.endpointPrefix.trim()) && typeof plan.transactionScopeId === "string" && Boolean(plan.transactionScopeId.trim()) && Number.isSafeInteger(plan.partIndex) && (plan.partIndex ?? -1) >= 0 && Number.isSafeInteger(plan.partCount) && (plan.partCount ?? 0) > 0 && (plan.partIndex ?? -1) < (plan.partCount ?? 0) && Array.isArray(plan.events) && plan.events.length > 0 && plan.events.every((event) => event && typeof event === "object" && typeof event.transactionId === "string" && Boolean(event.transactionId.trim()) && RECEIPT_KINDS.has(event.receiptKind) && Boolean(event.content) && typeof event.content === "object");
|
|
411
|
+
}
|
|
412
|
+
function decodePlan(bytes) {
|
|
413
|
+
let value;
|
|
414
|
+
try {
|
|
415
|
+
value = JSON.parse(new TextDecoder().decode(bytes));
|
|
416
|
+
} catch {
|
|
417
|
+
throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery plan is invalid JSON");
|
|
418
|
+
}
|
|
419
|
+
if (!isPlan(value)) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery plan is invalid");
|
|
420
|
+
return value;
|
|
421
|
+
}
|
|
422
|
+
function assertPlanIdentity(plan, params) {
|
|
423
|
+
if (plan.queueId !== params.identity.queueId || plan.partIndex !== params.identity.partIndex || plan.partCount !== params.identity.partCount || plan.accountId !== (params.accountId ?? "") || plan.roomId !== params.roomId || plan.transactionScopeId !== params.transactionScopeId || plan.wireEventType !== params.wireEventType) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery plan no longer matches the active delivery target");
|
|
424
|
+
}
|
|
425
|
+
function endpointPrefix(dispatch) {
|
|
426
|
+
const encodedTransactionId = encodeURIComponent(dispatch.transactionId);
|
|
427
|
+
if (!dispatch.requestPath.endsWith(encodedTransactionId)) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery transaction does not match its request path");
|
|
428
|
+
return dispatch.requestPath.slice(0, -encodedTransactionId.length);
|
|
429
|
+
}
|
|
430
|
+
function createMatrixPlannedEvents(params) {
|
|
431
|
+
return params.events.map((event, index) => ({
|
|
432
|
+
...structuredClone(event),
|
|
433
|
+
transactionId: transactionId(params.identity, index)
|
|
207
434
|
}));
|
|
208
|
-
|
|
209
|
-
|
|
435
|
+
}
|
|
436
|
+
function resolveMatrixDurableDeliveryIdentity(params) {
|
|
437
|
+
if (params.queueId === void 0) return null;
|
|
438
|
+
if (params.partIndex === void 0 || params.partCount === void 0) throw new Error("Matrix durable delivery requires stable part topology");
|
|
439
|
+
const partIndex = requireIndex(params.partIndex, "part index");
|
|
440
|
+
const partCount = requirePartCount(params.partCount);
|
|
441
|
+
if (partIndex >= partCount) throw new Error("Matrix durable delivery part index must be below the part count");
|
|
210
442
|
return {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
max_selections: normalized.maxSelections,
|
|
215
|
-
answers
|
|
216
|
-
},
|
|
217
|
-
"m.text": fallbackText,
|
|
218
|
-
"org.matrix.msc1767.text": fallbackText
|
|
443
|
+
queueId: params.queueId,
|
|
444
|
+
partIndex,
|
|
445
|
+
partCount
|
|
219
446
|
};
|
|
220
447
|
}
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
448
|
+
async function loadMatrixDeliveryPlan(params) {
|
|
449
|
+
const entry = await createDeliveryPlanStore().lookup(planKey(params.identity));
|
|
450
|
+
if (!entry) return null;
|
|
451
|
+
const plan = decodePlan(entry.bytes);
|
|
452
|
+
if (planKey(plan) !== planKey(params.identity)) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery plan key is invalid");
|
|
453
|
+
assertPlanIdentity(plan, params);
|
|
454
|
+
return structuredClone(plan);
|
|
455
|
+
}
|
|
456
|
+
async function persistMatrixDeliveryPlan(params) {
|
|
457
|
+
if (params.events.length === 0) throw new Error("Matrix durable delivery plan must contain at least one event");
|
|
458
|
+
if (params.dispatch.roomId !== params.roomId || params.dispatch.eventType !== params.wireEventType || !params.events.some((event) => event.transactionId === params.dispatch.transactionId)) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery was dispatched to an unexpected endpoint");
|
|
459
|
+
const partCount = requirePartCount(params.identity.partCount);
|
|
460
|
+
const events = params.events.map((event, index) => {
|
|
461
|
+
if (event.transactionId !== transactionId(params.identity, index)) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery plan has an invalid transaction identifier");
|
|
462
|
+
return structuredClone(event);
|
|
463
|
+
});
|
|
464
|
+
const plan = {
|
|
465
|
+
version: DELIVERY_PLAN_VERSION,
|
|
466
|
+
queueId: params.identity.queueId,
|
|
467
|
+
accountId: params.accountId ?? "",
|
|
468
|
+
roomId: params.roomId,
|
|
469
|
+
wireEventType: params.wireEventType,
|
|
470
|
+
endpointPrefix: endpointPrefix(params.dispatch),
|
|
471
|
+
transactionScopeId: params.transactionScopeId,
|
|
472
|
+
partIndex: requireIndex(params.identity.partIndex, "part index"),
|
|
473
|
+
partCount,
|
|
474
|
+
events
|
|
229
475
|
};
|
|
476
|
+
const store = createDeliveryPlanStore();
|
|
477
|
+
await store.deleteExpired();
|
|
478
|
+
const bytes = new TextEncoder().encode(JSON.stringify(plan));
|
|
479
|
+
if (await store.registerIfAbsent(planKey(params.identity), bytes, {})) return plan;
|
|
480
|
+
const existing = await loadMatrixDeliveryPlan(params);
|
|
481
|
+
if (!existing || JSON.stringify(existing) !== JSON.stringify(plan)) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery plan no longer matches the prepared event batch");
|
|
482
|
+
return existing;
|
|
483
|
+
}
|
|
484
|
+
async function loadQueuePlans(queueId) {
|
|
485
|
+
const store = createDeliveryPlanStore();
|
|
486
|
+
const keys = (await store.entries()).filter((entry) => entry.key.startsWith(queuePrefix(queueId))).map((entry) => entry.key);
|
|
487
|
+
return await Promise.all(keys.map(async (key) => {
|
|
488
|
+
const entry = await store.lookup(key);
|
|
489
|
+
if (!entry) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery plan disappeared during reconciliation");
|
|
490
|
+
const plan = decodePlan(entry.bytes);
|
|
491
|
+
if (key !== planKey(plan)) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery plan key is invalid");
|
|
492
|
+
return plan;
|
|
493
|
+
}));
|
|
230
494
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
495
|
+
function assertCompletePartTopology(plans) {
|
|
496
|
+
const partCount = plans[0]?.partCount;
|
|
497
|
+
if (!partCount) throw new MatrixDeliveryPlanInvariantError("Matrix ambiguous delivery has no event plan");
|
|
498
|
+
if (plans.some((plan) => plan.partCount !== partCount)) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery plan part topology is inconsistent");
|
|
499
|
+
const storedParts = new Set(plans.map((plan) => plan.partIndex));
|
|
500
|
+
if (storedParts.size !== partCount || Array.from({ length: partCount }, (_, partIndex) => partIndex).some((partIndex) => !storedParts.has(partIndex))) throw new MatrixDeliveryPlanInvariantError("Matrix ambiguous delivery has an incomplete event plan");
|
|
501
|
+
}
|
|
502
|
+
function createReconciledMatrixReceipt(params) {
|
|
503
|
+
const uniqueResults = params.results.filter((result, index, results) => results.findIndex((entry) => entry.eventId === result.eventId) === index);
|
|
504
|
+
const platformMessageIds = uniqueResults.map((result) => result.eventId);
|
|
505
|
+
return {
|
|
506
|
+
...platformMessageIds[0] ? { primaryPlatformMessageId: platformMessageIds[0] } : {},
|
|
507
|
+
platformMessageIds,
|
|
508
|
+
parts: uniqueResults.map((result, index) => {
|
|
509
|
+
const part = {
|
|
510
|
+
platformMessageId: result.eventId,
|
|
511
|
+
kind: result.receiptKind,
|
|
512
|
+
index
|
|
513
|
+
};
|
|
514
|
+
if (params.replyToId) part.replyToId = params.replyToId;
|
|
515
|
+
if (params.threadId) part.threadId = params.threadId;
|
|
516
|
+
return part;
|
|
517
|
+
}),
|
|
518
|
+
...params.replyToId ? { replyToId: params.replyToId } : {},
|
|
519
|
+
...params.threadId ? { threadId: params.threadId } : {},
|
|
520
|
+
sentAt: Date.now()
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
async function requireTransactionScope(client) {
|
|
524
|
+
const scope = (await client.getTransactionScopeId()).trim();
|
|
525
|
+
if (!scope) throw new MatrixDeliveryPlanInvariantError("Matrix durable delivery requires a stable transaction scope");
|
|
526
|
+
return scope;
|
|
527
|
+
}
|
|
528
|
+
async function reconcileMatrixUnknownSend(ctx) {
|
|
529
|
+
try {
|
|
530
|
+
if (ctx.payloads.length !== 1) throw new MatrixDeliveryPlanInvariantError("Matrix reconciliation requires exactly one prepared payload");
|
|
531
|
+
const plans = await loadQueuePlans(ctx.queueId);
|
|
532
|
+
if (plans.length === 0) throw new MatrixDeliveryPlanInvariantError("Matrix ambiguous delivery has no persisted event plan");
|
|
533
|
+
assertCompletePartTopology(plans);
|
|
534
|
+
return await withResolvedMatrixSendClient({
|
|
535
|
+
cfg: ctx.cfg,
|
|
536
|
+
accountId: ctx.accountId
|
|
537
|
+
}, async (client) => {
|
|
538
|
+
const transactionScopeId = await requireTransactionScope(client);
|
|
539
|
+
const roomId = await resolveMatrixRoomId(client, ctx.to);
|
|
540
|
+
const wireEventType = await client.getMessageWireEventType(roomId);
|
|
541
|
+
const orderedPlans = [...plans].toSorted((left, right) => left.partIndex - right.partIndex);
|
|
542
|
+
const results = [];
|
|
543
|
+
for (const plan of orderedPlans) {
|
|
544
|
+
assertPlanIdentity(plan, {
|
|
545
|
+
identity: plan,
|
|
546
|
+
accountId: ctx.accountId,
|
|
547
|
+
roomId,
|
|
548
|
+
transactionScopeId,
|
|
549
|
+
wireEventType
|
|
550
|
+
});
|
|
551
|
+
for (const event of plan.events) results.push({
|
|
552
|
+
eventId: await client.sendMessage(roomId, event.content, event.transactionId, async (dispatch) => {
|
|
553
|
+
await persistMatrixDeliveryPlan({
|
|
554
|
+
identity: plan,
|
|
555
|
+
accountId: ctx.accountId,
|
|
556
|
+
roomId,
|
|
557
|
+
transactionScopeId,
|
|
558
|
+
wireEventType,
|
|
559
|
+
events: plan.events,
|
|
560
|
+
dispatch
|
|
561
|
+
});
|
|
562
|
+
}),
|
|
563
|
+
receiptKind: event.receiptKind
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
const replyToId = ctx.effectiveReplyToId !== void 0 ? ctx.effectiveReplyToId : ctx.replyToMode === "off" ? void 0 : ctx.replyToId;
|
|
567
|
+
const threadId = ctx.threadId == null ? void 0 : String(ctx.threadId);
|
|
568
|
+
const receipt = createReconciledMatrixReceipt({
|
|
569
|
+
results,
|
|
570
|
+
...replyToId ? { replyToId } : {},
|
|
571
|
+
...threadId ? { threadId } : {}
|
|
572
|
+
});
|
|
573
|
+
return {
|
|
574
|
+
status: "sent",
|
|
575
|
+
messageId: receipt.platformMessageIds.at(-1),
|
|
576
|
+
receipt
|
|
577
|
+
};
|
|
578
|
+
});
|
|
579
|
+
} catch (error) {
|
|
580
|
+
const retryable = !(error instanceof MatrixDeliveryPlanInvariantError);
|
|
581
|
+
let cleanupError;
|
|
582
|
+
if (!retryable) try {
|
|
583
|
+
await cleanupMatrixDeliveryPlans({ queueId: ctx.queueId });
|
|
584
|
+
} catch (cleanupFailure) {
|
|
585
|
+
cleanupError = cleanupFailure;
|
|
586
|
+
}
|
|
587
|
+
const errorMessage = formatErrorMessage(error instanceof Error || typeof error === "string" ? error : "unknown error");
|
|
588
|
+
return {
|
|
589
|
+
status: "unresolved",
|
|
590
|
+
error: cleanupError === void 0 ? errorMessage : `${errorMessage}; Matrix delivery-plan cleanup failed: ${formatErrorMessage(cleanupError instanceof Error || typeof cleanupError === "string" ? cleanupError : "unknown error")}`,
|
|
591
|
+
retryable
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
async function cleanupMatrixDeliveryPlans(ctx) {
|
|
596
|
+
const store = createDeliveryPlanStore();
|
|
597
|
+
await store.deleteExpired();
|
|
598
|
+
const keys = (await store.entries()).filter((entry) => entry.key.startsWith(queuePrefix(ctx.queueId))).map((entry) => entry.key);
|
|
599
|
+
await Promise.all(keys.map(async (key) => await store.delete(key)));
|
|
600
|
+
}
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region extensions/matrix/src/matrix/poll-types.ts
|
|
603
|
+
/**
|
|
604
|
+
* Matrix Poll Types (MSC3381)
|
|
605
|
+
*
|
|
606
|
+
* Defines types for Matrix poll events:
|
|
607
|
+
* - m.poll.start - Creates a new poll
|
|
608
|
+
* - m.poll.response - Records a vote
|
|
609
|
+
* - m.poll.end - Closes a poll
|
|
610
|
+
*/
|
|
611
|
+
const M_POLL_START = "m.poll.start";
|
|
612
|
+
const M_POLL_RESPONSE = "m.poll.response";
|
|
613
|
+
const M_POLL_END = "m.poll.end";
|
|
614
|
+
const ORG_POLL_START = "org.matrix.msc3381.poll.start";
|
|
615
|
+
const ORG_POLL_RESPONSE = "org.matrix.msc3381.poll.response";
|
|
616
|
+
const ORG_POLL_END = "org.matrix.msc3381.poll.end";
|
|
617
|
+
const POLL_EVENT_TYPES = [
|
|
618
|
+
M_POLL_START,
|
|
619
|
+
M_POLL_RESPONSE,
|
|
620
|
+
M_POLL_END,
|
|
621
|
+
ORG_POLL_START,
|
|
622
|
+
ORG_POLL_RESPONSE,
|
|
623
|
+
ORG_POLL_END
|
|
624
|
+
];
|
|
625
|
+
const POLL_START_TYPES = [M_POLL_START, ORG_POLL_START];
|
|
626
|
+
const POLL_RESPONSE_TYPES = [M_POLL_RESPONSE, ORG_POLL_RESPONSE];
|
|
627
|
+
const POLL_END_TYPES = [M_POLL_END, ORG_POLL_END];
|
|
628
|
+
function isPollStartType(eventType) {
|
|
629
|
+
return POLL_START_TYPES.includes(eventType);
|
|
630
|
+
}
|
|
631
|
+
function isPollResponseType(eventType) {
|
|
632
|
+
return POLL_RESPONSE_TYPES.includes(eventType);
|
|
633
|
+
}
|
|
634
|
+
function isPollEndType(eventType) {
|
|
635
|
+
return POLL_END_TYPES.includes(eventType);
|
|
636
|
+
}
|
|
637
|
+
function isPollEventType(eventType) {
|
|
638
|
+
return POLL_EVENT_TYPES.includes(eventType);
|
|
639
|
+
}
|
|
640
|
+
function getTextContent(text) {
|
|
641
|
+
if (!text) return "";
|
|
642
|
+
return text["m.text"] ?? text["org.matrix.msc1767.text"] ?? text.body ?? "";
|
|
643
|
+
}
|
|
644
|
+
function parsePollStart(content) {
|
|
645
|
+
const poll = content["m.poll.start"] ?? content[ORG_POLL_START] ?? content["m.poll"];
|
|
646
|
+
if (!poll) return null;
|
|
647
|
+
const question = getTextContent(poll.question).trim();
|
|
648
|
+
if (!question) return null;
|
|
649
|
+
const answers = poll.answers.map((answer) => ({
|
|
650
|
+
id: answer.id,
|
|
651
|
+
text: getTextContent(answer).trim()
|
|
652
|
+
})).filter((answer) => answer.id.trim().length > 0 && answer.text.length > 0);
|
|
653
|
+
if (answers.length === 0) return null;
|
|
654
|
+
const maxSelectionsRaw = poll.max_selections;
|
|
655
|
+
const maxSelections = typeof maxSelectionsRaw === "number" && Number.isFinite(maxSelectionsRaw) ? Math.floor(maxSelectionsRaw) : 1;
|
|
656
|
+
return {
|
|
657
|
+
question,
|
|
658
|
+
answers,
|
|
659
|
+
kind: poll.kind ?? "m.poll.disclosed",
|
|
660
|
+
maxSelections: Math.min(Math.max(maxSelections, 1), answers.length)
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
function parsePollStartContent(content) {
|
|
664
|
+
const parsed = parsePollStart(content);
|
|
665
|
+
if (!parsed) return null;
|
|
666
|
+
return {
|
|
667
|
+
eventId: "",
|
|
668
|
+
roomId: "",
|
|
669
|
+
sender: "",
|
|
670
|
+
senderName: "",
|
|
671
|
+
question: parsed.question,
|
|
672
|
+
answers: parsed.answers.map((answer) => answer.text),
|
|
673
|
+
kind: parsed.kind,
|
|
674
|
+
maxSelections: parsed.maxSelections
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function formatPollAsText(summary) {
|
|
678
|
+
return [
|
|
679
|
+
"[Poll]",
|
|
680
|
+
summary.question,
|
|
681
|
+
"",
|
|
682
|
+
...summary.answers.map((answer, idx) => `${idx + 1}. ${answer}`)
|
|
683
|
+
].join("\n");
|
|
684
|
+
}
|
|
685
|
+
function resolvePollReferenceEventId(content) {
|
|
686
|
+
if (!content || typeof content !== "object") return null;
|
|
687
|
+
const relates = content["m.relates_to"];
|
|
688
|
+
if (!relates || typeof relates.event_id !== "string") return null;
|
|
689
|
+
const eventId = relates.event_id.trim();
|
|
690
|
+
return eventId.length > 0 ? eventId : null;
|
|
691
|
+
}
|
|
692
|
+
function parsePollResponseAnswerIds(content) {
|
|
693
|
+
if (!content || typeof content !== "object") return null;
|
|
694
|
+
const response = content[M_POLL_RESPONSE] ?? content[ORG_POLL_RESPONSE];
|
|
695
|
+
if (!response || !Array.isArray(response.answers)) return null;
|
|
696
|
+
return response.answers.filter((answer) => typeof answer === "string");
|
|
697
|
+
}
|
|
698
|
+
function buildPollResultsSummary(params) {
|
|
699
|
+
const parsed = parsePollStart(params.content);
|
|
700
|
+
if (!parsed) return null;
|
|
701
|
+
let pollClosedAt = Number.POSITIVE_INFINITY;
|
|
702
|
+
for (const event of params.relationEvents) {
|
|
703
|
+
if (event.unsigned?.redacted_because) continue;
|
|
704
|
+
if (!isPollEndType(typeof event.type === "string" ? event.type : "")) continue;
|
|
705
|
+
if (event.sender !== params.sender) continue;
|
|
706
|
+
const ts = typeof event.origin_server_ts === "number" && Number.isFinite(event.origin_server_ts) ? event.origin_server_ts : Number.POSITIVE_INFINITY;
|
|
707
|
+
if (ts < pollClosedAt) pollClosedAt = ts;
|
|
708
|
+
}
|
|
709
|
+
const answerIds = new Set(parsed.answers.map((answer) => answer.id));
|
|
710
|
+
const latestVoteBySender = /* @__PURE__ */ new Map();
|
|
711
|
+
const orderedRelationEvents = [...params.relationEvents].toSorted((left, right) => {
|
|
712
|
+
const leftTs = typeof left.origin_server_ts === "number" && Number.isFinite(left.origin_server_ts) ? left.origin_server_ts : Number.POSITIVE_INFINITY;
|
|
713
|
+
const rightTs = typeof right.origin_server_ts === "number" && Number.isFinite(right.origin_server_ts) ? right.origin_server_ts : Number.POSITIVE_INFINITY;
|
|
714
|
+
if (leftTs !== rightTs) return leftTs - rightTs;
|
|
715
|
+
return (left.event_id ?? "").localeCompare(right.event_id ?? "");
|
|
716
|
+
});
|
|
717
|
+
for (const event of orderedRelationEvents) {
|
|
718
|
+
if (event.unsigned?.redacted_because) continue;
|
|
719
|
+
if (!isPollResponseType(typeof event.type === "string" ? event.type : "")) continue;
|
|
720
|
+
const senderId = normalizeOptionalString(event.sender) ?? "";
|
|
721
|
+
if (!senderId) continue;
|
|
722
|
+
const eventTs = typeof event.origin_server_ts === "number" && Number.isFinite(event.origin_server_ts) ? event.origin_server_ts : Number.POSITIVE_INFINITY;
|
|
723
|
+
if (eventTs > pollClosedAt) continue;
|
|
724
|
+
const rawAnswers = parsePollResponseAnswerIds(event.content) ?? [];
|
|
725
|
+
const normalizedAnswers = Array.from(new Set(rawAnswers.map((answerId) => normalizeOptionalString(answerId) ?? "").filter((answerId) => answerIds.has(answerId)).slice(0, parsed.maxSelections)));
|
|
726
|
+
latestVoteBySender.set(senderId, {
|
|
727
|
+
ts: eventTs,
|
|
728
|
+
eventId: typeof event.event_id === "string" ? event.event_id : "",
|
|
729
|
+
answerIds: normalizedAnswers
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
const voteCounts = new Map(parsed.answers.map((answer) => [answer.id, 0]));
|
|
733
|
+
let totalVotes = 0;
|
|
734
|
+
for (const latestVote of latestVoteBySender.values()) {
|
|
735
|
+
if (latestVote.answerIds.length === 0) continue;
|
|
736
|
+
totalVotes += 1;
|
|
737
|
+
for (const answerId of latestVote.answerIds) voteCounts.set(answerId, (voteCounts.get(answerId) ?? 0) + 1);
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
eventId: params.pollEventId,
|
|
741
|
+
roomId: params.roomId,
|
|
742
|
+
sender: params.sender,
|
|
743
|
+
senderName: params.senderName,
|
|
744
|
+
question: parsed.question,
|
|
745
|
+
answers: parsed.answers.map((answer) => answer.text),
|
|
746
|
+
kind: parsed.kind,
|
|
747
|
+
maxSelections: parsed.maxSelections,
|
|
748
|
+
entries: parsed.answers.map((answer) => ({
|
|
749
|
+
id: answer.id,
|
|
750
|
+
text: answer.text,
|
|
751
|
+
votes: voteCounts.get(answer.id) ?? 0
|
|
752
|
+
})),
|
|
753
|
+
totalVotes,
|
|
754
|
+
closed: Number.isFinite(pollClosedAt)
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function formatPollResultsAsText(summary) {
|
|
758
|
+
const lines = [
|
|
759
|
+
summary.closed ? "[Poll closed]" : "[Poll]",
|
|
760
|
+
summary.question,
|
|
761
|
+
""
|
|
762
|
+
];
|
|
763
|
+
const revealResults = summary.kind === "m.poll.disclosed" || summary.closed;
|
|
764
|
+
for (const [index, entry] of summary.entries.entries()) {
|
|
765
|
+
if (!revealResults) {
|
|
766
|
+
lines.push(`${index + 1}. ${entry.text}`);
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
lines.push(`${index + 1}. ${entry.text} (${entry.votes} vote${entry.votes === 1 ? "" : "s"})`);
|
|
770
|
+
}
|
|
771
|
+
lines.push("");
|
|
772
|
+
if (!revealResults) lines.push("Responses are hidden until the poll closes.");
|
|
773
|
+
else lines.push(`Total voters: ${summary.totalVotes}`);
|
|
774
|
+
return lines.join("\n");
|
|
775
|
+
}
|
|
776
|
+
function buildTextContent$1(body) {
|
|
777
|
+
return {
|
|
778
|
+
"m.text": body,
|
|
779
|
+
"org.matrix.msc1767.text": body
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function buildPollFallbackText(question, answers) {
|
|
783
|
+
if (answers.length === 0) return question;
|
|
784
|
+
return `${question}\n${answers.map((answer, idx) => `${idx + 1}. ${answer}`).join("\n")}`;
|
|
785
|
+
}
|
|
786
|
+
function buildPollStartContent(poll) {
|
|
787
|
+
const normalized = normalizePollInput(poll);
|
|
788
|
+
const answers = normalized.options.map((option, idx) => ({
|
|
789
|
+
id: `answer${idx + 1}`,
|
|
790
|
+
...buildTextContent$1(option)
|
|
791
|
+
}));
|
|
792
|
+
const isMultiple = normalized.maxSelections > 1;
|
|
793
|
+
const fallbackText = buildPollFallbackText(normalized.question, answers.map((answer) => getTextContent(answer)));
|
|
794
|
+
return {
|
|
795
|
+
[M_POLL_START]: {
|
|
796
|
+
question: buildTextContent$1(normalized.question),
|
|
797
|
+
kind: isMultiple ? "m.poll.undisclosed" : "m.poll.disclosed",
|
|
798
|
+
max_selections: normalized.maxSelections,
|
|
799
|
+
answers
|
|
800
|
+
},
|
|
801
|
+
"m.text": fallbackText,
|
|
802
|
+
"org.matrix.msc1767.text": fallbackText
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
function buildPollResponseContent(pollEventId, answerIds) {
|
|
806
|
+
return {
|
|
807
|
+
[M_POLL_RESPONSE]: { answers: answerIds },
|
|
808
|
+
[ORG_POLL_RESPONSE]: { answers: answerIds },
|
|
809
|
+
"m.relates_to": {
|
|
810
|
+
rel_type: "m.reference",
|
|
811
|
+
event_id: pollEventId
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
//#endregion
|
|
816
|
+
//#region extensions/matrix/src/matrix/format-profile.ts
|
|
817
|
+
function createMatrixPrivateMarkers(markdown, exhaustedMessage) {
|
|
234
818
|
const used = new Set(Array.from(markdown, (character) => character.charCodeAt(0)));
|
|
235
819
|
for (const match of markdown.matchAll(/&#(?:x([0-9a-f]+)|(\d+));/giu)) {
|
|
236
820
|
const radix = match[1] ? 16 : 10;
|
|
@@ -759,937 +1343,559 @@ function mutateInlineTokensWithMentions(params) {
|
|
|
759
1343
|
roomMentioned
|
|
760
1344
|
};
|
|
761
1345
|
}
|
|
762
|
-
function compactLooseListTokens(tokens) {
|
|
763
|
-
const listItemStack = [];
|
|
764
|
-
for (const [index, token] of tokens.entries()) {
|
|
765
|
-
if (token.type === "list_item_open") {
|
|
766
|
-
listItemStack.push({
|
|
767
|
-
level: token.level,
|
|
768
|
-
immediateParagraphOpenIndexes: [],
|
|
769
|
-
immediateParagraphCloseIndexes: []
|
|
770
|
-
});
|
|
771
|
-
continue;
|
|
772
|
-
}
|
|
773
|
-
if (token.type === "list_item_close") {
|
|
774
|
-
const item = listItemStack.pop();
|
|
775
|
-
if (item && item.immediateParagraphOpenIndexes.length === 1 && item.immediateParagraphCloseIndexes.length === 1) {
|
|
776
|
-
const openIndex = item.immediateParagraphOpenIndexes[0];
|
|
777
|
-
const closeIndex = item.immediateParagraphCloseIndexes[0];
|
|
778
|
-
const openToken = openIndex === void 0 ? void 0 : tokens[openIndex];
|
|
779
|
-
const closeToken = closeIndex === void 0 ? void 0 : tokens[closeIndex];
|
|
780
|
-
if (openToken && closeToken) {
|
|
781
|
-
openToken.hidden = true;
|
|
782
|
-
closeToken.hidden = true;
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
const currentItem = listItemStack.at(-1);
|
|
788
|
-
if (!currentItem || token.level !== currentItem.level + 1) continue;
|
|
789
|
-
if (token.type === "paragraph_open") currentItem.immediateParagraphOpenIndexes.push(index);
|
|
790
|
-
else if (token.type === "paragraph_close") currentItem.immediateParagraphCloseIndexes.push(index);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
function markdownToMatrixHtml(markdown, options = {}) {
|
|
794
|
-
if (hasMatrixSpoilerMetadataCollision(markdown)) return renderMatrixFallbackHtml(markdown);
|
|
795
|
-
const tokens = parseMatrixMarkdown(projectMatrixMarkdown(markdown), options.tableMode);
|
|
796
|
-
compactLooseListTokens(tokens);
|
|
797
|
-
return md.renderer.render(tokens, md.options, {}).trimEnd();
|
|
798
|
-
}
|
|
799
|
-
function protectMatrixSpoilerDelimiters(markdown) {
|
|
800
|
-
const offsets = findMatrixSpoilerDelimiterOffsets(markdown);
|
|
801
|
-
if (offsets.length === 0) return { markdown };
|
|
802
|
-
const markers = createMatrixPrivateMarkers(markdown, "Matrix spoiler formatting exhausted its private marker pool");
|
|
803
|
-
let protectedMarkdown = "";
|
|
804
|
-
let cursor = 0;
|
|
805
|
-
for (const [index, offset] of offsets.entries()) {
|
|
806
|
-
const marker = index % 2 === 0 ? markers.open : markers.close;
|
|
807
|
-
protectedMarkdown += `${markdown.slice(cursor, offset)}${marker}${markers.padding}`;
|
|
808
|
-
cursor = offset + 2;
|
|
809
|
-
}
|
|
810
|
-
protectedMarkdown += markdown.slice(cursor);
|
|
811
|
-
return {
|
|
812
|
-
markdown: protectedMarkdown,
|
|
813
|
-
markers
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
function parseMatrixMarkdown(markdown, tableMode) {
|
|
817
|
-
const protectedSpoilers = protectMatrixSpoilerDelimiters(markdown);
|
|
818
|
-
if (tableMode === "off") md.disable("table");
|
|
819
|
-
try {
|
|
820
|
-
return md.parse(protectedSpoilers.markdown, { matrixSpoilerMarkers: protectedSpoilers.markers });
|
|
821
|
-
} finally {
|
|
822
|
-
if (tableMode === "off") md.enable("table");
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
function markdownToMatrixBody(markdown) {
|
|
826
|
-
const projected = projectMatrixMarkdown(markdown);
|
|
827
|
-
const offsets = findMatrixSpoilerDelimiterOffsets(projected);
|
|
828
|
-
const metadataCollision = hasMatrixSpoilerMetadataCollision(projected);
|
|
829
|
-
if (offsets.length === 0 && !metadataCollision) return projected;
|
|
830
|
-
let body = projected;
|
|
831
|
-
if (metadataCollision) body = "[Spoiler]";
|
|
832
|
-
else for (let index = offsets.length - 2; index >= 0; index -= 2) {
|
|
833
|
-
const open = offsets[index];
|
|
834
|
-
const close = offsets[index + 1];
|
|
835
|
-
if (open !== void 0 && close !== void 0) body = `${body.slice(0, open)}[Spoiler]${body.slice(close + 2)}`;
|
|
836
|
-
}
|
|
837
|
-
return renderMarkdownWithMarkers(markdownToIR(body, {
|
|
838
|
-
enableHtmlUnderline: true,
|
|
839
|
-
headingStyle: "rich",
|
|
840
|
-
linkify: true
|
|
841
|
-
}), {
|
|
842
|
-
styleMarkers: {},
|
|
843
|
-
escapeText: (text) => text
|
|
844
|
-
}, MATRIX_FORMAT_PROFILE);
|
|
845
|
-
}
|
|
846
|
-
function renderMatrixFallbackHtml(markdown) {
|
|
847
|
-
return `<p>${escapeHtml(markdownToMatrixBody(markdown)).replaceAll("\n", "<br>\n")}</p>`;
|
|
848
|
-
}
|
|
849
|
-
async function resolveMarkdownMentionState(params) {
|
|
850
|
-
const tokens = parseMatrixMarkdown(maskEscapedMentions(projectMatrixMarkdown(params.markdown)), params.tableMode);
|
|
851
|
-
restoreEscapedMentionsInBlockTokens(tokens);
|
|
852
|
-
const selfUserId = await resolveMatrixSelfUserId(params.client);
|
|
853
|
-
const userIds = [];
|
|
854
|
-
const seenUserIds = /* @__PURE__ */ new Set();
|
|
855
|
-
let roomMentioned = false;
|
|
856
|
-
for (const token of tokens) {
|
|
857
|
-
if (!token.children?.length) continue;
|
|
858
|
-
const mutated = mutateInlineTokensWithMentions({
|
|
859
|
-
children: token.children,
|
|
860
|
-
userIds,
|
|
861
|
-
seenUserIds,
|
|
862
|
-
selfUserId
|
|
863
|
-
});
|
|
864
|
-
token.children = mutated.children;
|
|
865
|
-
roomMentioned ||= mutated.roomMentioned;
|
|
866
|
-
}
|
|
867
|
-
const mentions = {};
|
|
868
|
-
if (userIds.length > 0) mentions.user_ids = userIds;
|
|
869
|
-
if (roomMentioned) mentions.room = true;
|
|
870
|
-
return {
|
|
871
|
-
tokens,
|
|
872
|
-
mentions
|
|
873
|
-
};
|
|
874
|
-
}
|
|
875
|
-
async function resolveMatrixMentionsInMarkdown(params) {
|
|
876
|
-
return (await resolveMarkdownMentionState(params)).mentions;
|
|
877
|
-
}
|
|
878
|
-
async function renderMarkdownToMatrixHtmlWithMentions(params) {
|
|
879
|
-
const state = await resolveMarkdownMentionState(params);
|
|
880
|
-
if (hasMatrixSpoilerMetadataCollision(params.markdown)) {
|
|
881
|
-
const redacted = markdownToMatrixBody(params.markdown);
|
|
882
|
-
const redactedState = await resolveMarkdownMentionState({
|
|
883
|
-
...params,
|
|
884
|
-
markdown: redacted
|
|
885
|
-
});
|
|
886
|
-
return {
|
|
887
|
-
html: renderMatrixFallbackHtml(params.markdown),
|
|
888
|
-
mentions: redactedState.mentions
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
compactLooseListTokens(state.tokens);
|
|
892
|
-
return {
|
|
893
|
-
html: md.renderer.render(state.tokens, md.options, {}).trimEnd() || void 0,
|
|
894
|
-
mentions: state.mentions
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
//#endregion
|
|
898
|
-
//#region extensions/matrix/src/matrix/send/chunking.ts
|
|
899
|
-
const getCore$2 = () => getMatrixRuntime();
|
|
900
|
-
function protectMatrixUnderlineTags(markdown) {
|
|
901
|
-
const codeRegions = findCodeRegions(markdown);
|
|
902
|
-
const metadataRanges = findMatrixMarkdownMetadataRanges(markdown);
|
|
903
|
-
const tags = [...tokenizeHtmlTags(markdown)].filter((tag) => (tag.name === "u" || tag.name === "ins") && !tag.selfClosing && !isInsideCode(tag.start, codeRegions) && !isMarkdownEscaped(markdown, tag.start) && !metadataRanges.some((range) => tag.start >= range.start && tag.start < range.end));
|
|
904
|
-
if (tags.length === 0) return { markdown };
|
|
905
|
-
const markers = createMatrixPrivateMarkers(markdown, "Matrix underline chunking exhausted its private marker pool");
|
|
906
|
-
let depth = 0;
|
|
907
|
-
const replacements = tags.flatMap((tag) => {
|
|
908
|
-
if (!tag.closing) {
|
|
909
|
-
depth += 1;
|
|
910
|
-
return [{
|
|
911
|
-
tag,
|
|
912
|
-
marker: depth === 1 ? markers.open : ""
|
|
913
|
-
}];
|
|
914
|
-
}
|
|
915
|
-
if (depth === 0) return [];
|
|
916
|
-
depth -= 1;
|
|
917
|
-
return [{
|
|
918
|
-
tag,
|
|
919
|
-
marker: depth === 0 ? markers.close : ""
|
|
920
|
-
}];
|
|
921
|
-
});
|
|
922
|
-
let protectedMarkdown = markdown;
|
|
923
|
-
for (const { tag, marker } of replacements.toReversed()) protectedMarkdown = `${protectedMarkdown.slice(0, tag.start)}${marker}${markers.padding.repeat(tag.raw.length - marker.length)}${protectedMarkdown.slice(tag.end)}`;
|
|
924
|
-
return {
|
|
925
|
-
markdown: protectedMarkdown,
|
|
926
|
-
markers
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
function restoreMatrixStyleChunks(chunks, spoiler, underline) {
|
|
930
|
-
const stack = [];
|
|
931
|
-
const syntax = {
|
|
932
|
-
spoiler: {
|
|
933
|
-
open: "||",
|
|
934
|
-
close: "||",
|
|
935
|
-
markers: spoiler
|
|
936
|
-
},
|
|
937
|
-
underline: {
|
|
938
|
-
open: "<u>",
|
|
939
|
-
close: "</u>",
|
|
940
|
-
markers: underline
|
|
941
|
-
}
|
|
942
|
-
};
|
|
943
|
-
return chunks.map((chunk) => {
|
|
944
|
-
let restored = stack.map((style) => syntax[style].open).join("");
|
|
945
|
-
for (const character of chunk) {
|
|
946
|
-
const opening = Object.keys(syntax).find((style) => character === syntax[style].markers?.open);
|
|
947
|
-
const closing = Object.keys(syntax).find((style) => character === syntax[style].markers?.close);
|
|
948
|
-
if (opening) {
|
|
949
|
-
stack.push(opening);
|
|
950
|
-
restored += syntax[opening].open;
|
|
951
|
-
} else if (closing) {
|
|
952
|
-
const stackIndex = stack.lastIndexOf(closing);
|
|
953
|
-
if (stackIndex >= 0) {
|
|
954
|
-
const above = stack.slice(stackIndex + 1);
|
|
955
|
-
restored += above.toReversed().map((style) => syntax[style].close).join("");
|
|
956
|
-
restored += syntax[closing].close;
|
|
957
|
-
stack.splice(stackIndex, 1);
|
|
958
|
-
restored += above.map((style) => syntax[style].open).join("");
|
|
959
|
-
}
|
|
960
|
-
} else if (character !== spoiler?.padding && character !== underline?.padding) restored += character;
|
|
961
|
-
}
|
|
962
|
-
return restored + stack.toReversed().map((style) => syntax[style].close).join("");
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
function splitMatrixTableSegments(markdown) {
|
|
966
|
-
const segments = [];
|
|
967
|
-
let cursor = 0;
|
|
968
|
-
for (const range of findMatrixTableSourceRanges(markdown)) {
|
|
969
|
-
const plain = markdown.slice(cursor, range.start).replace(/(?:[ \t]*\n)+$/u, "");
|
|
970
|
-
if (plain.trim()) segments.push({
|
|
971
|
-
table: false,
|
|
972
|
-
text: plain
|
|
973
|
-
});
|
|
974
|
-
const rawTable = markdown.slice(range.start, range.end).trimEnd();
|
|
975
|
-
const indent = /^ +/u.exec(rawTable)?.[0] ?? "";
|
|
976
|
-
const table = indent ? rawTable.split("\n").map((line) => line.startsWith(indent) ? line.slice(indent.length) : line).join("\n") : rawTable;
|
|
977
|
-
segments.push({
|
|
978
|
-
table: true,
|
|
979
|
-
text: table
|
|
980
|
-
});
|
|
981
|
-
cursor = range.end;
|
|
982
|
-
}
|
|
983
|
-
const tail = markdown.slice(cursor).replace(/^(?:[ \t]*\n)+/u, "");
|
|
984
|
-
if (tail.trim()) segments.push({
|
|
985
|
-
table: false,
|
|
986
|
-
text: tail
|
|
987
|
-
});
|
|
988
|
-
return segments;
|
|
989
|
-
}
|
|
990
|
-
function prepareMatrixSingleText(text, opts) {
|
|
991
|
-
const normalizedText = text.replace(/\r\n?/gu, "\n");
|
|
992
|
-
const trimmedText = opts.preserveWhitespace ? normalizedText : normalizedText.trim();
|
|
993
|
-
const cfg = requireRuntimeConfig(opts.cfg, "Matrix text preparation");
|
|
994
|
-
const tableMode = opts.tableMode ?? getCore$2().channel.text.resolveMarkdownTableMode({
|
|
995
|
-
cfg,
|
|
996
|
-
channel: "matrix",
|
|
997
|
-
accountId: opts.accountId,
|
|
998
|
-
supportsBlockTables: MATRIX_FORMAT_PROFILE.constructs.table === "native"
|
|
999
|
-
});
|
|
1000
|
-
const singleEventLimit = Math.min(getCore$2().channel.text.resolveTextChunkLimit(cfg, "matrix", opts.accountId), MATRIX_FORMAT_PROFILE.chunk.limit);
|
|
1001
|
-
const convertedText = renderMatrixMarkdownTables(trimmedText, tableMode);
|
|
1002
|
-
const eventTextLength = Math.max(convertedText.length, markdownToMatrixBody(convertedText).length);
|
|
1003
|
-
return {
|
|
1004
|
-
trimmedText,
|
|
1005
|
-
convertedText,
|
|
1006
|
-
singleEventLimit,
|
|
1007
|
-
eventTextLength,
|
|
1008
|
-
fitsInSingleEvent: eventTextLength <= singleEventLimit,
|
|
1009
|
-
tableMode
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
function chunkMatrixText(text, opts) {
|
|
1013
|
-
const preparedText = prepareMatrixSingleText(text, opts);
|
|
1014
|
-
if (preparedText.fitsInSingleEvent) return {
|
|
1015
|
-
...preparedText,
|
|
1016
|
-
chunks: preparedText.convertedText ? [preparedText.convertedText] : []
|
|
1017
|
-
};
|
|
1018
|
-
const cfg = requireRuntimeConfig(opts.cfg, "Matrix text chunking");
|
|
1019
|
-
const chunkMode = getCore$2().channel.text.resolveChunkMode(cfg, "matrix", opts.accountId);
|
|
1020
|
-
const collisionRedacted = hasMatrixSpoilerMetadataCollision(preparedText.convertedText) ? markdownToMatrixBody(preparedText.convertedText) : void 0;
|
|
1021
|
-
const chunkSegment = (segmentText) => {
|
|
1022
|
-
const protectedUnderline = protectMatrixUnderlineTags(hasMatrixSpoilerMetadataCollision(segmentText) ? markdownToMatrixBody(segmentText) : segmentText);
|
|
1023
|
-
const protectedSpoilers = protectMatrixSpoilerDelimiters(protectedUnderline.markdown);
|
|
1024
|
-
const wrapperReserve = (protectedSpoilers.markers ? 4 : 0) + (protectedUnderline.markers ? 7 : 0);
|
|
1025
|
-
const privateMarkers = [protectedSpoilers.markers, protectedUnderline.markers].flatMap((markers) => markers ? [
|
|
1026
|
-
markers.open,
|
|
1027
|
-
markers.close,
|
|
1028
|
-
markers.padding
|
|
1029
|
-
] : []);
|
|
1030
|
-
let reserve = wrapperReserve;
|
|
1031
|
-
while (reserve < preparedText.singleEventLimit) {
|
|
1032
|
-
const protectedChunks = getCore$2().channel.text.chunkMarkdownTextWithMode(protectedSpoilers.markdown, preparedText.singleEventLimit - reserve, chunkMode);
|
|
1033
|
-
const restored = restoreMatrixStyleChunks(protectedChunks, protectedSpoilers.markers, protectedUnderline.markers).filter((_, index) => {
|
|
1034
|
-
return privateMarkers.reduce((value, marker) => value.replaceAll(marker, ""), protectedChunks[index] ?? "").length > 0;
|
|
1035
|
-
});
|
|
1036
|
-
const overflow = Math.max(0, ...restored.map((chunk) => Math.max(chunk.length, markdownToMatrixBody(chunk).length) - preparedText.singleEventLimit));
|
|
1037
|
-
if (overflow === 0) return restored;
|
|
1038
|
-
reserve += overflow;
|
|
1039
|
-
}
|
|
1040
|
-
throw new Error("Matrix text chunk limit is too small for formatted content");
|
|
1041
|
-
};
|
|
1042
|
-
const chunks = collisionRedacted !== void 0 ? chunkSegment(collisionRedacted) : preparedText.tableMode === "block" ? splitMatrixTableSegments(preparedText.convertedText).flatMap((segment) => {
|
|
1043
|
-
if (!segment.table) return chunkSegment(segment.text);
|
|
1044
|
-
return segment.text.length <= preparedText.singleEventLimit ? [segment.text] : chunkSegment(renderMatrixMarkdownTables(segment.text, "bullets"));
|
|
1045
|
-
}) : chunkSegment(preparedText.convertedText);
|
|
1046
|
-
return {
|
|
1047
|
-
...preparedText,
|
|
1048
|
-
chunks
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
//#endregion
|
|
1052
|
-
//#region extensions/matrix/src/matrix/send/client.ts
|
|
1053
|
-
const loadMatrixSendClientRuntime = createLazyRuntimeModule(() => import("./client-bootstrap-XsIkHNt0.js").then((n) => n.t));
|
|
1054
|
-
function resolveMediaMaxBytes(accountId, cfg) {
|
|
1055
|
-
if (!cfg) throw new Error("Matrix media limits requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.");
|
|
1056
|
-
const matrixCfg = resolveMatrixAccountConfig({
|
|
1057
|
-
cfg: requireRuntimeConfig(cfg, "Matrix media limits"),
|
|
1058
|
-
accountId
|
|
1059
|
-
});
|
|
1060
|
-
const mediaMaxMb = typeof matrixCfg.mediaMaxMb === "number" ? matrixCfg.mediaMaxMb : void 0;
|
|
1061
|
-
if (typeof mediaMaxMb === "number") return mediaMaxMb * 1024 * 1024;
|
|
1062
|
-
}
|
|
1063
|
-
async function withResolvedMatrixSendClient(opts, run) {
|
|
1064
|
-
return await withResolvedMatrixClient({
|
|
1065
|
-
...opts,
|
|
1066
|
-
readiness: "started"
|
|
1067
|
-
}, run, "persist");
|
|
1068
|
-
}
|
|
1069
|
-
async function withResolvedMatrixControlClient(opts, run) {
|
|
1070
|
-
return await withResolvedMatrixClient({
|
|
1071
|
-
...opts,
|
|
1072
|
-
readiness: "none"
|
|
1073
|
-
}, run);
|
|
1074
|
-
}
|
|
1075
|
-
async function withResolvedMatrixClient(opts, run, shutdownBehavior) {
|
|
1076
|
-
if (opts.client) return await run(opts.client);
|
|
1077
|
-
const { withResolvedRuntimeMatrixClient } = await loadMatrixSendClientRuntime();
|
|
1078
|
-
return await withResolvedRuntimeMatrixClient(opts, run, shutdownBehavior);
|
|
1079
|
-
}
|
|
1080
|
-
//#endregion
|
|
1081
|
-
//#region extensions/matrix/src/matrix/send/types.ts
|
|
1082
|
-
const MsgType = {
|
|
1083
|
-
Text: "m.text",
|
|
1084
|
-
Image: "m.image",
|
|
1085
|
-
Audio: "m.audio",
|
|
1086
|
-
Video: "m.video",
|
|
1087
|
-
File: "m.file",
|
|
1088
|
-
Notice: "m.notice"
|
|
1089
|
-
};
|
|
1090
|
-
const RelationType = {
|
|
1091
|
-
Annotation: MATRIX_ANNOTATION_RELATION_TYPE,
|
|
1092
|
-
Replace: "m.replace",
|
|
1093
|
-
Thread: "m.thread"
|
|
1094
|
-
};
|
|
1095
|
-
const EventType = {
|
|
1096
|
-
Direct: "m.direct",
|
|
1097
|
-
Reaction: MATRIX_REACTION_EVENT_TYPE,
|
|
1098
|
-
RoomMessage: "m.room.message"
|
|
1099
|
-
};
|
|
1100
|
-
const MATRIX_OPENCLAW_FINALIZED_PREVIEW_KEY = "com.openclaw.finalized_preview";
|
|
1101
|
-
/**
|
|
1102
|
-
* MSC4357 live marker key.
|
|
1103
|
-
* When present on event content, signals that the message is still being
|
|
1104
|
-
* streamed (e.g. an LLM generating a response). Supporting clients render
|
|
1105
|
-
* the message with a streaming animation until an edit without this marker
|
|
1106
|
-
* arrives, indicating the stream is complete.
|
|
1107
|
-
* @see https://github.com/matrix-org/matrix-spec-proposals/pull/4357
|
|
1108
|
-
*/
|
|
1109
|
-
const MSC4357_LIVE_KEY = "org.matrix.msc4357.live";
|
|
1110
|
-
//#endregion
|
|
1111
|
-
//#region extensions/matrix/src/matrix/send/formatting.ts
|
|
1112
|
-
const getCore$1 = () => getMatrixRuntime();
|
|
1113
|
-
async function renderMatrixFormattedContent(params) {
|
|
1114
|
-
const markdown = params.markdown ?? "";
|
|
1115
|
-
const body = markdownToMatrixBody(markdown);
|
|
1116
|
-
if (params.includeMentions === false) return {
|
|
1117
|
-
body,
|
|
1118
|
-
html: markdownToMatrixHtml(markdown, { tableMode: params.tableMode }).trimEnd() || void 0
|
|
1119
|
-
};
|
|
1120
|
-
const { html, mentions } = await renderMarkdownToMatrixHtmlWithMentions({
|
|
1121
|
-
markdown,
|
|
1122
|
-
client: params.client,
|
|
1123
|
-
tableMode: params.tableMode
|
|
1124
|
-
});
|
|
1125
|
-
return {
|
|
1126
|
-
body,
|
|
1127
|
-
html,
|
|
1128
|
-
mentions
|
|
1129
|
-
};
|
|
1130
|
-
}
|
|
1131
|
-
function buildTextContent(body, relation, opts = {}) {
|
|
1132
|
-
const msgtype = opts.msgtype ?? MsgType.Text;
|
|
1133
|
-
return relation ? {
|
|
1134
|
-
msgtype,
|
|
1135
|
-
body,
|
|
1136
|
-
"m.relates_to": relation
|
|
1137
|
-
} : {
|
|
1138
|
-
msgtype,
|
|
1139
|
-
body
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
async function enrichMatrixFormattedContent(params) {
|
|
1143
|
-
const { body, html, mentions } = await renderMatrixFormattedContent({
|
|
1144
|
-
client: params.client,
|
|
1145
|
-
markdown: params.markdown,
|
|
1146
|
-
includeMentions: params.includeMentions,
|
|
1147
|
-
tableMode: params.tableMode
|
|
1148
|
-
});
|
|
1149
|
-
params.content.body = body || params.content.body;
|
|
1150
|
-
if (mentions) params.content["m.mentions"] = mentions;
|
|
1151
|
-
else delete params.content["m.mentions"];
|
|
1152
|
-
if (!html) {
|
|
1153
|
-
delete params.content.format;
|
|
1154
|
-
delete params.content.formatted_body;
|
|
1155
|
-
return;
|
|
1156
|
-
}
|
|
1157
|
-
params.content.format = "org.matrix.custom.html";
|
|
1158
|
-
params.content.formatted_body = html;
|
|
1159
|
-
}
|
|
1160
|
-
async function resolveMatrixMentionsForBody(params) {
|
|
1161
|
-
return await resolveMatrixMentionsInMarkdown({
|
|
1162
|
-
markdown: params.body ?? "",
|
|
1163
|
-
client: params.client
|
|
1164
|
-
});
|
|
1165
|
-
}
|
|
1166
|
-
function normalizeMentionUserIds(value) {
|
|
1167
|
-
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
|
|
1168
|
-
}
|
|
1169
|
-
function extractMatrixMentions(content) {
|
|
1170
|
-
const rawMentions = content?.["m.mentions"];
|
|
1171
|
-
if (!rawMentions || typeof rawMentions !== "object") return {};
|
|
1172
|
-
const mentions = rawMentions;
|
|
1173
|
-
const normalized = {};
|
|
1174
|
-
const userIds = normalizeMentionUserIds(mentions.user_ids);
|
|
1175
|
-
if (userIds.length > 0) normalized.user_ids = userIds;
|
|
1176
|
-
if (mentions.room === true) normalized.room = true;
|
|
1177
|
-
return normalized;
|
|
1178
|
-
}
|
|
1179
|
-
function diffMatrixMentions(current, previous) {
|
|
1180
|
-
const previousUserIds = new Set(previous.user_ids ?? []);
|
|
1181
|
-
const newUserIds = (current.user_ids ?? []).filter((userId) => !previousUserIds.has(userId));
|
|
1182
|
-
const delta = {};
|
|
1183
|
-
if (newUserIds.length > 0) delta.user_ids = newUserIds;
|
|
1184
|
-
if (current.room && !previous.room) delta.room = true;
|
|
1185
|
-
return delta;
|
|
1186
|
-
}
|
|
1187
|
-
function buildReplyRelation(replyToId) {
|
|
1188
|
-
const trimmed = replyToId?.trim();
|
|
1189
|
-
if (!trimmed) return;
|
|
1190
|
-
return { "m.in_reply_to": { event_id: trimmed } };
|
|
1191
|
-
}
|
|
1192
|
-
function buildThreadRelation(threadId, replyToId) {
|
|
1193
|
-
const trimmed = threadId.trim();
|
|
1194
|
-
const relation = {
|
|
1195
|
-
rel_type: RelationType.Thread,
|
|
1196
|
-
event_id: trimmed
|
|
1197
|
-
};
|
|
1198
|
-
const fallbackReplyToId = replyToId?.trim();
|
|
1199
|
-
if (fallbackReplyToId) {
|
|
1200
|
-
relation.is_falling_back = true;
|
|
1201
|
-
relation["m.in_reply_to"] = { event_id: fallbackReplyToId };
|
|
1202
|
-
}
|
|
1203
|
-
return relation;
|
|
1204
|
-
}
|
|
1205
|
-
function resolveMatrixMsgType(contentType, _fileName) {
|
|
1206
|
-
switch (getCore$1().media.mediaKindFromMime(contentType ?? "")) {
|
|
1207
|
-
case "image": return MsgType.Image;
|
|
1208
|
-
case "audio": return MsgType.Audio;
|
|
1209
|
-
case "video": return MsgType.Video;
|
|
1210
|
-
default: return MsgType.File;
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
function resolveMatrixVoiceDecision(opts) {
|
|
1214
|
-
if (!opts.wantsVoice) return { useVoice: false };
|
|
1215
|
-
if (isMatrixVoiceCompatibleAudio(opts)) return { useVoice: true };
|
|
1216
|
-
return { useVoice: false };
|
|
1217
|
-
}
|
|
1218
|
-
function isMatrixVoiceCompatibleAudio(opts) {
|
|
1219
|
-
return isVoiceMessageCompatibleAudio({
|
|
1220
|
-
contentType: opts.contentType,
|
|
1221
|
-
fileName: opts.fileName
|
|
1222
|
-
});
|
|
1223
|
-
}
|
|
1224
|
-
//#endregion
|
|
1225
|
-
//#region extensions/matrix/src/matrix/send/media.ts
|
|
1226
|
-
const getCore = () => getMatrixRuntime();
|
|
1227
|
-
function buildMatrixMediaInfo(params) {
|
|
1228
|
-
const base = {};
|
|
1229
|
-
if (Number.isFinite(params.size)) base.size = params.size;
|
|
1230
|
-
if (params.mimetype) base.mimetype = params.mimetype;
|
|
1231
|
-
if (params.imageInfo) {
|
|
1232
|
-
const dimensional = {
|
|
1233
|
-
...base,
|
|
1234
|
-
...params.imageInfo
|
|
1235
|
-
};
|
|
1236
|
-
if (typeof params.durationMs === "number") return {
|
|
1237
|
-
...dimensional,
|
|
1238
|
-
duration: params.durationMs
|
|
1239
|
-
};
|
|
1240
|
-
return dimensional;
|
|
1241
|
-
}
|
|
1242
|
-
if (typeof params.durationMs === "number") return {
|
|
1243
|
-
...base,
|
|
1244
|
-
duration: params.durationMs
|
|
1245
|
-
};
|
|
1246
|
-
if (Object.keys(base).length === 0) return;
|
|
1247
|
-
return base;
|
|
1248
|
-
}
|
|
1249
|
-
function buildMediaContent(params) {
|
|
1250
|
-
const info = buildMatrixMediaInfo({
|
|
1251
|
-
size: params.size,
|
|
1252
|
-
mimetype: params.mimetype,
|
|
1253
|
-
durationMs: params.durationMs,
|
|
1254
|
-
imageInfo: params.imageInfo
|
|
1255
|
-
});
|
|
1256
|
-
const base = {
|
|
1257
|
-
msgtype: params.msgtype,
|
|
1258
|
-
body: params.body,
|
|
1259
|
-
filename: params.filename,
|
|
1260
|
-
info: info ?? void 0
|
|
1261
|
-
};
|
|
1262
|
-
if (!params.file && params.url) base.url = params.url;
|
|
1263
|
-
if (params.file) base.file = params.file;
|
|
1264
|
-
if (params.isVoice) {
|
|
1265
|
-
base["org.matrix.msc3245.voice"] = {};
|
|
1266
|
-
if (typeof params.durationMs === "number") base["org.matrix.msc1767.audio"] = { duration: params.durationMs };
|
|
1267
|
-
}
|
|
1268
|
-
if (params.relation) base["m.relates_to"] = params.relation;
|
|
1269
|
-
return base;
|
|
1270
|
-
}
|
|
1271
|
-
const THUMBNAIL_MAX_SIDE = 800;
|
|
1272
|
-
const THUMBNAIL_QUALITY = 80;
|
|
1273
|
-
async function prepareImageInfo(params) {
|
|
1274
|
-
const meta = await getCore().media.getImageMetadata(params.buffer).catch(() => null);
|
|
1275
|
-
if (!meta) return;
|
|
1276
|
-
const imageInfo = {
|
|
1277
|
-
w: meta.width,
|
|
1278
|
-
h: meta.height
|
|
1279
|
-
};
|
|
1280
|
-
if (Math.max(meta.width, meta.height) > THUMBNAIL_MAX_SIDE) try {
|
|
1281
|
-
const thumbBuffer = await getCore().media.resizeToJpeg({
|
|
1282
|
-
buffer: params.buffer,
|
|
1283
|
-
maxSide: THUMBNAIL_MAX_SIDE,
|
|
1284
|
-
quality: THUMBNAIL_QUALITY,
|
|
1285
|
-
withoutEnlargement: true
|
|
1286
|
-
});
|
|
1287
|
-
const thumbMeta = await getCore().media.getImageMetadata(thumbBuffer).catch(() => null);
|
|
1288
|
-
const result = await uploadMediaWithEncryption(params.client, thumbBuffer, {
|
|
1289
|
-
contentType: "image/jpeg",
|
|
1290
|
-
filename: "thumbnail.jpg",
|
|
1291
|
-
encrypted: params.encrypted === true
|
|
1292
|
-
});
|
|
1293
|
-
if (result.file) imageInfo.thumbnail_file = result.file;
|
|
1294
|
-
else imageInfo.thumbnail_url = result.url;
|
|
1295
|
-
if (thumbMeta) imageInfo.thumbnail_info = {
|
|
1296
|
-
w: thumbMeta.width,
|
|
1297
|
-
h: thumbMeta.height,
|
|
1298
|
-
mimetype: "image/jpeg",
|
|
1299
|
-
size: thumbBuffer.byteLength
|
|
1300
|
-
};
|
|
1301
|
-
} catch {}
|
|
1302
|
-
return imageInfo;
|
|
1303
|
-
}
|
|
1304
|
-
async function resolveMediaDurationMs(params) {
|
|
1305
|
-
if (params.kind !== "audio" && params.kind !== "video") return;
|
|
1306
|
-
try {
|
|
1307
|
-
const fileInfo = params.contentType || params.fileName ? {
|
|
1308
|
-
mimeType: params.contentType,
|
|
1309
|
-
size: params.buffer.byteLength,
|
|
1310
|
-
path: params.fileName
|
|
1311
|
-
} : void 0;
|
|
1312
|
-
const durationSeconds = (await parseBuffer(params.buffer, fileInfo, {
|
|
1313
|
-
duration: true,
|
|
1314
|
-
skipCovers: true
|
|
1315
|
-
})).format.duration;
|
|
1316
|
-
if (typeof durationSeconds === "number" && Number.isFinite(durationSeconds)) return Math.max(0, Math.round(durationSeconds * 1e3));
|
|
1317
|
-
} catch {}
|
|
1318
|
-
}
|
|
1319
|
-
async function uploadFile(client, file, params) {
|
|
1320
|
-
return await client.uploadContent(file, params.contentType, params.filename);
|
|
1321
|
-
}
|
|
1322
|
-
async function uploadMediaWithEncryption(client, buffer, params) {
|
|
1323
|
-
if (params.encrypted && client.crypto) {
|
|
1324
|
-
const encrypted = await client.crypto.encryptMedia(buffer);
|
|
1325
|
-
const mxc = await client.uploadContent(encrypted.buffer, params.contentType, params.filename);
|
|
1326
|
-
return {
|
|
1327
|
-
url: mxc,
|
|
1328
|
-
file: {
|
|
1329
|
-
url: mxc,
|
|
1330
|
-
...encrypted.file
|
|
1346
|
+
function compactLooseListTokens(tokens) {
|
|
1347
|
+
const listItemStack = [];
|
|
1348
|
+
for (const [index, token] of tokens.entries()) {
|
|
1349
|
+
if (token.type === "list_item_open") {
|
|
1350
|
+
listItemStack.push({
|
|
1351
|
+
level: token.level,
|
|
1352
|
+
immediateParagraphOpenIndexes: [],
|
|
1353
|
+
immediateParagraphCloseIndexes: []
|
|
1354
|
+
});
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
if (token.type === "list_item_close") {
|
|
1358
|
+
const item = listItemStack.pop();
|
|
1359
|
+
if (item && item.immediateParagraphOpenIndexes.length === 1 && item.immediateParagraphCloseIndexes.length === 1) {
|
|
1360
|
+
const openIndex = item.immediateParagraphOpenIndexes[0];
|
|
1361
|
+
const closeIndex = item.immediateParagraphCloseIndexes[0];
|
|
1362
|
+
const openToken = openIndex === void 0 ? void 0 : tokens[openIndex];
|
|
1363
|
+
const closeToken = closeIndex === void 0 ? void 0 : tokens[closeIndex];
|
|
1364
|
+
if (openToken && closeToken) {
|
|
1365
|
+
openToken.hidden = true;
|
|
1366
|
+
closeToken.hidden = true;
|
|
1367
|
+
}
|
|
1331
1368
|
}
|
|
1332
|
-
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
const currentItem = listItemStack.at(-1);
|
|
1372
|
+
if (!currentItem || token.level !== currentItem.level + 1) continue;
|
|
1373
|
+
if (token.type === "paragraph_open") currentItem.immediateParagraphOpenIndexes.push(index);
|
|
1374
|
+
else if (token.type === "paragraph_close") currentItem.immediateParagraphCloseIndexes.push(index);
|
|
1333
1375
|
}
|
|
1334
|
-
return { url: await uploadFile(client, buffer, params) };
|
|
1335
|
-
}
|
|
1336
|
-
/**
|
|
1337
|
-
* Upload media with optional encryption for E2EE rooms.
|
|
1338
|
-
*/
|
|
1339
|
-
async function uploadMediaMaybeEncrypted(client, roomId, buffer, params) {
|
|
1340
|
-
const isEncrypted = Boolean(client.crypto && await client.crypto.isRoomEncrypted(roomId));
|
|
1341
|
-
return await uploadMediaWithEncryption(client, buffer, {
|
|
1342
|
-
...params,
|
|
1343
|
-
encrypted: isEncrypted
|
|
1344
|
-
});
|
|
1345
1376
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
isStrictDirectMembership: () => isStrictDirectMembership,
|
|
1352
|
-
isStrictDirectRoom: () => isStrictDirectRoom,
|
|
1353
|
-
readJoinedMatrixMembers: () => readJoinedMatrixMembers
|
|
1354
|
-
});
|
|
1355
|
-
function normalizeJoinedMatrixMembers(joinedMembers) {
|
|
1356
|
-
if (!Array.isArray(joinedMembers)) return [];
|
|
1357
|
-
return joinedMembers.map((entry) => normalizeNullableString(entry)).filter((entry) => Boolean(entry));
|
|
1377
|
+
function markdownToMatrixHtml(markdown, options = {}) {
|
|
1378
|
+
if (hasMatrixSpoilerMetadataCollision(markdown)) return renderMatrixFallbackHtml(markdown);
|
|
1379
|
+
const tokens = parseMatrixMarkdown(projectMatrixMarkdown(markdown), options.tableMode);
|
|
1380
|
+
compactLooseListTokens(tokens);
|
|
1381
|
+
return md.renderer.render(tokens, md.options, {}).trimEnd();
|
|
1358
1382
|
}
|
|
1359
|
-
function
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1383
|
+
function protectMatrixSpoilerDelimiters(markdown) {
|
|
1384
|
+
const offsets = findMatrixSpoilerDelimiterOffsets(markdown);
|
|
1385
|
+
if (offsets.length === 0) return { markdown };
|
|
1386
|
+
const markers = createMatrixPrivateMarkers(markdown, "Matrix spoiler formatting exhausted its private marker pool");
|
|
1387
|
+
let protectedMarkdown = "";
|
|
1388
|
+
let cursor = 0;
|
|
1389
|
+
for (const [index, offset] of offsets.entries()) {
|
|
1390
|
+
const marker = index % 2 === 0 ? markers.open : markers.close;
|
|
1391
|
+
protectedMarkdown += `${markdown.slice(cursor, offset)}${marker}${markers.padding}`;
|
|
1392
|
+
cursor = offset + 2;
|
|
1393
|
+
}
|
|
1394
|
+
protectedMarkdown += markdown.slice(cursor);
|
|
1395
|
+
return {
|
|
1396
|
+
markdown: protectedMarkdown,
|
|
1397
|
+
markers
|
|
1398
|
+
};
|
|
1364
1399
|
}
|
|
1365
|
-
|
|
1400
|
+
function parseMatrixMarkdown(markdown, tableMode) {
|
|
1401
|
+
const protectedSpoilers = protectMatrixSpoilerDelimiters(markdown);
|
|
1402
|
+
if (tableMode === "off") md.disable("table");
|
|
1366
1403
|
try {
|
|
1367
|
-
return
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1404
|
+
return md.parse(protectedSpoilers.markdown, { matrixSpoilerMarkers: protectedSpoilers.markers });
|
|
1405
|
+
} finally {
|
|
1406
|
+
if (tableMode === "off") md.enable("table");
|
|
1370
1407
|
}
|
|
1371
1408
|
}
|
|
1372
|
-
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1409
|
+
function markdownToMatrixBody(markdown) {
|
|
1410
|
+
const projected = projectMatrixMarkdown(markdown);
|
|
1411
|
+
const offsets = findMatrixSpoilerDelimiterOffsets(projected);
|
|
1412
|
+
const metadataCollision = hasMatrixSpoilerMetadataCollision(projected);
|
|
1413
|
+
if (offsets.length === 0 && !metadataCollision) return projected;
|
|
1414
|
+
let body = projected;
|
|
1415
|
+
if (metadataCollision) body = "[Spoiler]";
|
|
1416
|
+
else for (let index = offsets.length - 2; index >= 0; index -= 2) {
|
|
1417
|
+
const open = offsets[index];
|
|
1418
|
+
const close = offsets[index + 1];
|
|
1419
|
+
if (open !== void 0 && close !== void 0) body = `${body.slice(0, open)}[Spoiler]${body.slice(close + 2)}`;
|
|
1382
1420
|
}
|
|
1421
|
+
return renderMarkdownWithMarkers(markdownToIR(body, {
|
|
1422
|
+
enableHtmlUnderline: true,
|
|
1423
|
+
headingStyle: "rich",
|
|
1424
|
+
linkify: true
|
|
1425
|
+
}), {
|
|
1426
|
+
styleMarkers: {},
|
|
1427
|
+
escapeText: (text) => text
|
|
1428
|
+
}, MATRIX_FORMAT_PROFILE);
|
|
1383
1429
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1430
|
+
function renderMatrixFallbackHtml(markdown) {
|
|
1431
|
+
return `<p>${escapeHtml(markdownToMatrixBody(markdown)).replaceAll("\n", "<br>\n")}</p>`;
|
|
1432
|
+
}
|
|
1433
|
+
async function resolveMarkdownMentionState(params) {
|
|
1434
|
+
const tokens = parseMatrixMarkdown(maskEscapedMentions(projectMatrixMarkdown(params.markdown)), params.tableMode);
|
|
1435
|
+
restoreEscapedMentionsInBlockTokens(tokens);
|
|
1436
|
+
const selfUserId = await resolveMatrixSelfUserId(params.client);
|
|
1437
|
+
const userIds = [];
|
|
1438
|
+
const seenUserIds = /* @__PURE__ */ new Set();
|
|
1439
|
+
let roomMentioned = false;
|
|
1440
|
+
for (const token of tokens) {
|
|
1441
|
+
if (!token.children?.length) continue;
|
|
1442
|
+
const mutated = mutateInlineTokensWithMentions({
|
|
1443
|
+
children: token.children,
|
|
1444
|
+
userIds,
|
|
1445
|
+
seenUserIds,
|
|
1446
|
+
selfUserId
|
|
1447
|
+
});
|
|
1448
|
+
token.children = mutated.children;
|
|
1449
|
+
roomMentioned ||= mutated.roomMentioned;
|
|
1450
|
+
}
|
|
1451
|
+
const mentions = {};
|
|
1452
|
+
if (userIds.length > 0) mentions.user_ids = userIds;
|
|
1453
|
+
if (roomMentioned) mentions.room = true;
|
|
1399
1454
|
return {
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
viaMemberState: memberStateFlag === true,
|
|
1403
|
-
memberStateFlag
|
|
1455
|
+
tokens,
|
|
1456
|
+
mentions
|
|
1404
1457
|
};
|
|
1405
1458
|
}
|
|
1406
|
-
async function
|
|
1407
|
-
return (await
|
|
1408
|
-
client: params.client,
|
|
1409
|
-
roomId: params.roomId,
|
|
1410
|
-
remoteUserId: params.remoteUserId,
|
|
1411
|
-
selfUserId: params.selfUserId
|
|
1412
|
-
})).strict;
|
|
1459
|
+
async function resolveMatrixMentionsInMarkdown(params) {
|
|
1460
|
+
return (await resolveMarkdownMentionState(params)).mentions;
|
|
1413
1461
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
});
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
const direct = await client.getAccountData(EventType.Direct);
|
|
1427
|
-
return direct && typeof direct === "object" && !Array.isArray(direct) ? direct : {};
|
|
1428
|
-
} catch {
|
|
1429
|
-
return {};
|
|
1462
|
+
async function renderMarkdownToMatrixHtmlWithMentions(params) {
|
|
1463
|
+
const state = await resolveMarkdownMentionState(params);
|
|
1464
|
+
if (hasMatrixSpoilerMetadataCollision(params.markdown)) {
|
|
1465
|
+
const redacted = markdownToMatrixBody(params.markdown);
|
|
1466
|
+
const redactedState = await resolveMarkdownMentionState({
|
|
1467
|
+
...params,
|
|
1468
|
+
markdown: redacted
|
|
1469
|
+
});
|
|
1470
|
+
return {
|
|
1471
|
+
html: renderMatrixFallbackHtml(params.markdown),
|
|
1472
|
+
mentions: redactedState.mentions
|
|
1473
|
+
};
|
|
1430
1474
|
}
|
|
1475
|
+
compactLooseListTokens(state.tokens);
|
|
1476
|
+
return {
|
|
1477
|
+
html: md.renderer.render(state.tokens, md.options, {}).trimEnd() || void 0,
|
|
1478
|
+
mentions: state.mentions
|
|
1479
|
+
};
|
|
1431
1480
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
const current = direct[remoteUserId];
|
|
1439
|
-
if (!Array.isArray(current)) return [];
|
|
1440
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1441
|
-
const normalized = [];
|
|
1442
|
-
for (const value of current) {
|
|
1443
|
-
const roomId = normalizeOptionalString(value) ?? "";
|
|
1444
|
-
if (!roomId || seen.has(roomId)) continue;
|
|
1445
|
-
seen.add(roomId);
|
|
1446
|
-
normalized.push(roomId);
|
|
1447
|
-
}
|
|
1448
|
-
return normalized;
|
|
1481
|
+
//#endregion
|
|
1482
|
+
//#region extensions/matrix/src/matrix/send/chunking.ts
|
|
1483
|
+
const getCore$2 = () => getMatrixRuntime();
|
|
1484
|
+
function normalizeMatrixEventLimit(limit) {
|
|
1485
|
+
if (!Number.isFinite(limit) || limit <= 0) return limit;
|
|
1486
|
+
return Math.max(1, Math.floor(limit));
|
|
1449
1487
|
}
|
|
1450
|
-
function
|
|
1451
|
-
const
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
if (!roomId || seen.has(roomId)) continue;
|
|
1456
|
-
seen.add(roomId);
|
|
1457
|
-
normalized.push(roomId);
|
|
1458
|
-
}
|
|
1459
|
-
return normalized;
|
|
1488
|
+
function resolveMatrixChunkOverflow(chunk, limit) {
|
|
1489
|
+
const body = markdownToMatrixBody(chunk);
|
|
1490
|
+
const renderedLength = Math.max(chunk.length, body.length);
|
|
1491
|
+
if (limit === 1 && Array.from(chunk).length === 1 && Array.from(body).length === 1) return 0;
|
|
1492
|
+
return Math.max(0, renderedLength - limit);
|
|
1460
1493
|
}
|
|
1461
|
-
function
|
|
1462
|
-
const
|
|
1463
|
-
const
|
|
1464
|
-
|
|
1494
|
+
function protectMatrixUnderlineTags(markdown) {
|
|
1495
|
+
const codeRegions = findCodeRegions(markdown);
|
|
1496
|
+
const metadataRanges = findMatrixMarkdownMetadataRanges(markdown);
|
|
1497
|
+
const tags = [...tokenizeHtmlTags(markdown)].filter((tag) => (tag.name === "u" || tag.name === "ins") && !tag.selfClosing && !isInsideCode(tag.start, codeRegions) && !isMarkdownEscaped(markdown, tag.start) && !metadataRanges.some((range) => tag.start >= range.start && tag.start < range.end));
|
|
1498
|
+
if (tags.length === 0) return { markdown };
|
|
1499
|
+
const markers = createMatrixPrivateMarkers(markdown, "Matrix underline chunking exhausted its private marker pool");
|
|
1500
|
+
let depth = 0;
|
|
1501
|
+
const replacements = tags.flatMap((tag) => {
|
|
1502
|
+
if (!tag.closing) {
|
|
1503
|
+
depth += 1;
|
|
1504
|
+
return [{
|
|
1505
|
+
tag,
|
|
1506
|
+
marker: depth === 1 ? markers.open : ""
|
|
1507
|
+
}];
|
|
1508
|
+
}
|
|
1509
|
+
if (depth === 0) return [];
|
|
1510
|
+
depth -= 1;
|
|
1511
|
+
return [{
|
|
1512
|
+
tag,
|
|
1513
|
+
marker: depth === 0 ? markers.close : ""
|
|
1514
|
+
}];
|
|
1515
|
+
});
|
|
1516
|
+
let protectedMarkdown = markdown;
|
|
1517
|
+
for (const { tag, marker } of replacements.toReversed()) protectedMarkdown = `${protectedMarkdown.slice(0, tag.start)}${marker}${markers.padding.repeat(tag.raw.length - marker.length)}${protectedMarkdown.slice(tag.end)}`;
|
|
1518
|
+
return {
|
|
1519
|
+
markdown: protectedMarkdown,
|
|
1520
|
+
markers
|
|
1521
|
+
};
|
|
1465
1522
|
}
|
|
1466
|
-
function
|
|
1467
|
-
const
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1523
|
+
function restoreMatrixStyleChunks(chunks, spoiler, underline) {
|
|
1524
|
+
const stack = [];
|
|
1525
|
+
const syntax = {
|
|
1526
|
+
spoiler: {
|
|
1527
|
+
open: "||",
|
|
1528
|
+
close: "||",
|
|
1529
|
+
markers: spoiler
|
|
1530
|
+
},
|
|
1531
|
+
underline: {
|
|
1532
|
+
open: "<u>",
|
|
1533
|
+
close: "</u>",
|
|
1534
|
+
markers: underline
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
1537
|
+
return chunks.map((chunk) => {
|
|
1538
|
+
let restored = stack.map((style) => syntax[style].open).join("");
|
|
1539
|
+
for (const character of chunk) {
|
|
1540
|
+
const opening = Object.keys(syntax).find((style) => character === syntax[style].markers?.open);
|
|
1541
|
+
const closing = Object.keys(syntax).find((style) => character === syntax[style].markers?.close);
|
|
1542
|
+
if (opening) {
|
|
1543
|
+
stack.push(opening);
|
|
1544
|
+
restored += syntax[opening].open;
|
|
1545
|
+
} else if (closing) {
|
|
1546
|
+
const stackIndex = stack.lastIndexOf(closing);
|
|
1547
|
+
if (stackIndex >= 0) {
|
|
1548
|
+
const above = stack.slice(stackIndex + 1);
|
|
1549
|
+
restored += above.toReversed().map((style) => syntax[style].close).join("");
|
|
1550
|
+
restored += syntax[closing].close;
|
|
1551
|
+
stack.splice(stackIndex, 1);
|
|
1552
|
+
restored += above.map((style) => syntax[style].open).join("");
|
|
1553
|
+
}
|
|
1554
|
+
} else if (character !== spoiler?.padding && character !== underline?.padding) restored += character;
|
|
1555
|
+
}
|
|
1556
|
+
return restored + stack.toReversed().map((style) => syntax[style].close).join("");
|
|
1557
|
+
});
|
|
1472
1558
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1559
|
+
function splitMatrixTableSegments(markdown) {
|
|
1560
|
+
const segments = [];
|
|
1561
|
+
let cursor = 0;
|
|
1562
|
+
for (const range of findMatrixTableSourceRanges(markdown)) {
|
|
1563
|
+
const plain = markdown.slice(cursor, range.start).replace(/(?:[ \t]*\n)+$/u, "");
|
|
1564
|
+
if (plain.trim()) segments.push({
|
|
1565
|
+
table: false,
|
|
1566
|
+
text: plain
|
|
1480
1567
|
});
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1568
|
+
const rawTable = markdown.slice(range.start, range.end).trimEnd();
|
|
1569
|
+
const indent = /^ +/u.exec(rawTable)?.[0] ?? "";
|
|
1570
|
+
const table = indent ? rawTable.split("\n").map((line) => line.startsWith(indent) ? line.slice(indent.length) : line).join("\n") : rawTable;
|
|
1571
|
+
segments.push({
|
|
1572
|
+
table: true,
|
|
1573
|
+
text: table
|
|
1485
1574
|
});
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1575
|
+
cursor = range.end;
|
|
1576
|
+
}
|
|
1577
|
+
const tail = markdown.slice(cursor).replace(/^(?:[ \t]*\n)+/u, "");
|
|
1578
|
+
if (tail.trim()) segments.push({
|
|
1579
|
+
table: false,
|
|
1580
|
+
text: tail
|
|
1492
1581
|
});
|
|
1582
|
+
return segments;
|
|
1493
1583
|
}
|
|
1494
|
-
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1584
|
+
function prepareMatrixSingleText(text, opts) {
|
|
1585
|
+
const normalizedText = text.replace(/\r\n?/gu, "\n");
|
|
1586
|
+
const trimmedText = opts.preserveWhitespace ? normalizedText : normalizedText.trim();
|
|
1587
|
+
const cfg = requireRuntimeConfig(opts.cfg, "Matrix text preparation");
|
|
1588
|
+
const tableMode = opts.tableMode ?? getCore$2().channel.text.resolveMarkdownTableMode({
|
|
1589
|
+
cfg,
|
|
1590
|
+
channel: "matrix",
|
|
1591
|
+
accountId: opts.accountId,
|
|
1592
|
+
supportsBlockTables: MATRIX_FORMAT_PROFILE.constructs.table === "native"
|
|
1500
1593
|
});
|
|
1594
|
+
const convertedText = renderMatrixMarkdownTables(trimmedText, tableMode);
|
|
1595
|
+
const singleEventLimit = normalizeMatrixEventLimit(Math.min(getCore$2().channel.text.resolveTextChunkLimit(cfg, "matrix", opts.accountId), MATRIX_FORMAT_PROFILE.chunk.limit));
|
|
1596
|
+
const eventTextLength = Math.max(convertedText.length, markdownToMatrixBody(convertedText).length);
|
|
1501
1597
|
return {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1598
|
+
trimmedText,
|
|
1599
|
+
convertedText,
|
|
1600
|
+
singleEventLimit,
|
|
1601
|
+
eventTextLength,
|
|
1602
|
+
fitsInSingleEvent: eventTextLength <= singleEventLimit,
|
|
1603
|
+
tableMode
|
|
1507
1604
|
};
|
|
1508
1605
|
}
|
|
1509
|
-
function
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1606
|
+
function chunkMatrixText(text, opts) {
|
|
1607
|
+
const preparedText = prepareMatrixSingleText(text, opts);
|
|
1608
|
+
if (preparedText.fitsInSingleEvent) return {
|
|
1609
|
+
...preparedText,
|
|
1610
|
+
chunks: preparedText.convertedText ? [preparedText.convertedText] : []
|
|
1611
|
+
};
|
|
1612
|
+
const cfg = requireRuntimeConfig(opts.cfg, "Matrix text chunking");
|
|
1613
|
+
const chunkMode = getCore$2().channel.text.resolveChunkMode(cfg, "matrix", opts.accountId);
|
|
1614
|
+
const collisionRedacted = hasMatrixSpoilerMetadataCollision(preparedText.convertedText) ? markdownToMatrixBody(preparedText.convertedText) : void 0;
|
|
1615
|
+
const chunkSegment = (segmentText) => {
|
|
1616
|
+
const protectedUnderline = protectMatrixUnderlineTags(hasMatrixSpoilerMetadataCollision(segmentText) ? markdownToMatrixBody(segmentText) : segmentText);
|
|
1617
|
+
const protectedSpoilers = protectMatrixSpoilerDelimiters(protectedUnderline.markdown);
|
|
1618
|
+
const wrapperReserve = (protectedSpoilers.markers ? 4 : 0) + (protectedUnderline.markers ? 7 : 0);
|
|
1619
|
+
const privateMarkers = [protectedSpoilers.markers, protectedUnderline.markers].flatMap((markers) => markers ? [
|
|
1620
|
+
markers.open,
|
|
1621
|
+
markers.close,
|
|
1622
|
+
markers.padding
|
|
1623
|
+
] : []);
|
|
1624
|
+
let reserve = wrapperReserve;
|
|
1625
|
+
while (reserve < preparedText.singleEventLimit) {
|
|
1626
|
+
const protectedChunks = getCore$2().channel.text.chunkMarkdownTextWithMode(protectedSpoilers.markdown, preparedText.singleEventLimit - reserve, chunkMode);
|
|
1627
|
+
const restored = restoreMatrixStyleChunks(protectedChunks, protectedSpoilers.markers, protectedUnderline.markers).filter((_, index) => {
|
|
1628
|
+
return privateMarkers.reduce((value, marker) => value.replaceAll(marker, ""), protectedChunks[index] ?? "").length > 0;
|
|
1629
|
+
});
|
|
1630
|
+
const overflow = Math.max(0, ...restored.map((chunk) => resolveMatrixChunkOverflow(chunk, preparedText.singleEventLimit)));
|
|
1631
|
+
if (overflow === 0) return restored;
|
|
1632
|
+
reserve += overflow;
|
|
1633
|
+
}
|
|
1634
|
+
throw new Error("Matrix text chunk limit is too small for formatted content");
|
|
1635
|
+
};
|
|
1636
|
+
const chunks = collisionRedacted !== void 0 ? chunkSegment(collisionRedacted) : preparedText.tableMode === "block" ? splitMatrixTableSegments(preparedText.convertedText).flatMap((segment) => {
|
|
1637
|
+
if (!segment.table) return chunkSegment(segment.text);
|
|
1638
|
+
return segment.text.length <= preparedText.singleEventLimit ? [segment.text] : chunkSegment(renderMatrixMarkdownTables(segment.text, "bullets"));
|
|
1639
|
+
}) : chunkSegment(preparedText.convertedText);
|
|
1512
1640
|
return {
|
|
1513
|
-
...
|
|
1514
|
-
|
|
1641
|
+
...preparedText,
|
|
1642
|
+
chunks
|
|
1515
1643
|
};
|
|
1516
1644
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
const
|
|
1645
|
+
//#endregion
|
|
1646
|
+
//#region extensions/matrix/src/matrix/send/formatting.ts
|
|
1647
|
+
const getCore$1 = () => getMatrixRuntime();
|
|
1648
|
+
async function renderMatrixFormattedContent(params) {
|
|
1649
|
+
const markdown = params.markdown ?? "";
|
|
1650
|
+
const body = markdownToMatrixBody(markdown);
|
|
1651
|
+
if (params.includeMentions === false) return {
|
|
1652
|
+
body,
|
|
1653
|
+
html: markdownToMatrixHtml(markdown, { tableMode: params.tableMode }).trimEnd() || void 0
|
|
1654
|
+
};
|
|
1655
|
+
const { html, mentions } = await renderMarkdownToMatrixHtmlWithMentions({
|
|
1656
|
+
markdown,
|
|
1528
1657
|
client: params.client,
|
|
1529
|
-
|
|
1530
|
-
remoteUserId,
|
|
1531
|
-
selfUserId: params.selfUserId
|
|
1658
|
+
tableMode: params.tableMode
|
|
1532
1659
|
});
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1660
|
+
return {
|
|
1661
|
+
body,
|
|
1662
|
+
html,
|
|
1663
|
+
mentions
|
|
1537
1664
|
};
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1665
|
+
}
|
|
1666
|
+
function buildTextContent(body, relation, opts = {}) {
|
|
1667
|
+
const msgtype = opts.msgtype ?? MsgType.Text;
|
|
1668
|
+
return relation ? {
|
|
1669
|
+
msgtype,
|
|
1670
|
+
body,
|
|
1671
|
+
"m.relates_to": relation
|
|
1672
|
+
} : {
|
|
1673
|
+
msgtype,
|
|
1674
|
+
body
|
|
1542
1675
|
};
|
|
1543
|
-
try {
|
|
1544
|
-
const repaired = await persistMatrixDirectRoomMapping({
|
|
1545
|
-
client: params.client,
|
|
1546
|
-
remoteUserId,
|
|
1547
|
-
roomId: params.roomId
|
|
1548
|
-
});
|
|
1549
|
-
return {
|
|
1550
|
-
classifyAsDirect: true,
|
|
1551
|
-
repaired,
|
|
1552
|
-
roomId: params.roomId,
|
|
1553
|
-
reason: repaired ? "promoted" : "already-mapped"
|
|
1554
|
-
};
|
|
1555
|
-
} catch {
|
|
1556
|
-
return {
|
|
1557
|
-
classifyAsDirect: true,
|
|
1558
|
-
repaired: false,
|
|
1559
|
-
roomId: params.roomId,
|
|
1560
|
-
reason: "repair-failed"
|
|
1561
|
-
};
|
|
1562
|
-
}
|
|
1563
1676
|
}
|
|
1564
|
-
async function
|
|
1565
|
-
const
|
|
1566
|
-
const selfUserId = normalizeOptionalString(await params.client.getUserId().catch(() => null)) ?? null;
|
|
1567
|
-
const mappedRoomIds = normalizeMappedRoomIds(await readMatrixDirectAccountData(params.client), remoteUserId);
|
|
1568
|
-
const mappedRooms = await Promise.all(mappedRoomIds.map(async (roomId) => await classifyDirectRoomCandidate({
|
|
1677
|
+
async function enrichMatrixFormattedContent(params) {
|
|
1678
|
+
const { body, html, mentions } = await renderMatrixFormattedContent({
|
|
1569
1679
|
client: params.client,
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
if (
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
joinedRooms = [];
|
|
1582
|
-
}
|
|
1583
|
-
const discoveredStrictRooms = [];
|
|
1584
|
-
for (const roomId of normalizeRoomIdList(joinedRooms)) {
|
|
1585
|
-
if (mappedRoomIds.includes(roomId)) continue;
|
|
1586
|
-
const candidate = await classifyDirectRoomCandidate({
|
|
1587
|
-
client: params.client,
|
|
1588
|
-
roomId,
|
|
1589
|
-
remoteUserId,
|
|
1590
|
-
selfUserId,
|
|
1591
|
-
source: "joined"
|
|
1592
|
-
});
|
|
1593
|
-
if (candidate.strict) discoveredStrictRooms.push(candidate);
|
|
1680
|
+
markdown: params.markdown,
|
|
1681
|
+
includeMentions: params.includeMentions,
|
|
1682
|
+
tableMode: params.tableMode
|
|
1683
|
+
});
|
|
1684
|
+
params.content.body = body || params.content.body;
|
|
1685
|
+
if (mentions) params.content["m.mentions"] = mentions;
|
|
1686
|
+
else delete params.content["m.mentions"];
|
|
1687
|
+
if (!html) {
|
|
1688
|
+
delete params.content.format;
|
|
1689
|
+
delete params.content.formatted_body;
|
|
1690
|
+
return;
|
|
1594
1691
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1692
|
+
params.content.format = "org.matrix.custom.html";
|
|
1693
|
+
params.content.formatted_body = html;
|
|
1694
|
+
}
|
|
1695
|
+
async function resolveMatrixMentionsForBody(params) {
|
|
1696
|
+
return await resolveMatrixMentionsInMarkdown({
|
|
1697
|
+
markdown: params.body ?? "",
|
|
1698
|
+
client: params.client
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
function normalizeMentionUserIds(value) {
|
|
1702
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
|
|
1703
|
+
}
|
|
1704
|
+
function extractMatrixMentions(content) {
|
|
1705
|
+
const rawMentions = content?.["m.mentions"];
|
|
1706
|
+
if (!rawMentions || typeof rawMentions !== "object") return {};
|
|
1707
|
+
const mentions = rawMentions;
|
|
1708
|
+
const normalized = {};
|
|
1709
|
+
const userIds = normalizeMentionUserIds(mentions.user_ids);
|
|
1710
|
+
if (userIds.length > 0) normalized.user_ids = userIds;
|
|
1711
|
+
if (mentions.room === true) normalized.room = true;
|
|
1712
|
+
return normalized;
|
|
1713
|
+
}
|
|
1714
|
+
function diffMatrixMentions(current, previous) {
|
|
1715
|
+
const previousUserIds = new Set(previous.user_ids ?? []);
|
|
1716
|
+
const newUserIds = (current.user_ids ?? []).filter((userId) => !previousUserIds.has(userId));
|
|
1717
|
+
const delta = {};
|
|
1718
|
+
if (newUserIds.length > 0) delta.user_ids = newUserIds;
|
|
1719
|
+
if (current.room && !previous.room) delta.room = true;
|
|
1720
|
+
return delta;
|
|
1605
1721
|
}
|
|
1606
|
-
|
|
1607
|
-
const
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
const
|
|
1613
|
-
const
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
remoteUserId,
|
|
1617
|
-
roomIds: [activeRoomId, ...inspected.discoveredStrictRoomIds]
|
|
1618
|
-
});
|
|
1619
|
-
return {
|
|
1620
|
-
...inspected,
|
|
1621
|
-
activeRoomId,
|
|
1622
|
-
createdRoomId,
|
|
1623
|
-
changed: mappingWrite.changed,
|
|
1624
|
-
directContentBefore: mappingWrite.directContentBefore,
|
|
1625
|
-
directContentAfter: mappingWrite.directContentAfter
|
|
1722
|
+
function buildReplyRelation(replyToId) {
|
|
1723
|
+
const trimmed = replyToId?.trim();
|
|
1724
|
+
if (!trimmed) return;
|
|
1725
|
+
return { "m.in_reply_to": { event_id: trimmed } };
|
|
1726
|
+
}
|
|
1727
|
+
function buildThreadRelation(threadId, replyToId) {
|
|
1728
|
+
const trimmed = threadId.trim();
|
|
1729
|
+
const relation = {
|
|
1730
|
+
rel_type: RelationType.Thread,
|
|
1731
|
+
event_id: trimmed
|
|
1626
1732
|
};
|
|
1733
|
+
const fallbackReplyToId = replyToId?.trim();
|
|
1734
|
+
if (fallbackReplyToId) {
|
|
1735
|
+
relation.is_falling_back = true;
|
|
1736
|
+
relation["m.in_reply_to"] = { event_id: fallbackReplyToId };
|
|
1737
|
+
}
|
|
1738
|
+
return relation;
|
|
1627
1739
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1740
|
+
function resolveMatrixMsgType(contentType, _fileName) {
|
|
1741
|
+
switch (getCore$1().media.mediaKindFromMime(contentType ?? "")) {
|
|
1742
|
+
case "image": return MsgType.Image;
|
|
1743
|
+
case "audio": return MsgType.Audio;
|
|
1744
|
+
case "video": return MsgType.Video;
|
|
1745
|
+
default: return MsgType.File;
|
|
1746
|
+
}
|
|
1634
1747
|
}
|
|
1635
|
-
function
|
|
1636
|
-
|
|
1748
|
+
function resolveMatrixVoiceDecision(opts) {
|
|
1749
|
+
if (!opts.wantsVoice) return { useVoice: false };
|
|
1750
|
+
if (isMatrixVoiceCompatibleAudio(opts)) return { useVoice: true };
|
|
1751
|
+
return { useVoice: false };
|
|
1637
1752
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
const created = /* @__PURE__ */ new Map();
|
|
1644
|
-
directRoomCacheByClient.set(client, created);
|
|
1645
|
-
return created;
|
|
1753
|
+
function isMatrixVoiceCompatibleAudio(opts) {
|
|
1754
|
+
return isVoiceMessageCompatibleAudio({
|
|
1755
|
+
contentType: opts.contentType,
|
|
1756
|
+
fileName: opts.fileName
|
|
1757
|
+
});
|
|
1646
1758
|
}
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1759
|
+
//#endregion
|
|
1760
|
+
//#region extensions/matrix/src/matrix/send/media.ts
|
|
1761
|
+
const getCore = () => getMatrixRuntime();
|
|
1762
|
+
function buildMatrixMediaInfo(params) {
|
|
1763
|
+
const base = {};
|
|
1764
|
+
if (Number.isFinite(params.size)) base.size = params.size;
|
|
1765
|
+
if (params.mimetype) base.mimetype = params.mimetype;
|
|
1766
|
+
if (params.imageInfo) {
|
|
1767
|
+
const dimensional = {
|
|
1768
|
+
...base,
|
|
1769
|
+
...params.imageInfo
|
|
1770
|
+
};
|
|
1771
|
+
if (typeof params.durationMs === "number") return {
|
|
1772
|
+
...dimensional,
|
|
1773
|
+
duration: params.durationMs
|
|
1774
|
+
};
|
|
1775
|
+
return dimensional;
|
|
1653
1776
|
}
|
|
1777
|
+
if (typeof params.durationMs === "number") return {
|
|
1778
|
+
...base,
|
|
1779
|
+
duration: params.durationMs
|
|
1780
|
+
};
|
|
1781
|
+
if (Object.keys(base).length === 0) return;
|
|
1782
|
+
return base;
|
|
1654
1783
|
}
|
|
1655
|
-
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
if (cached && await isStrictDirectRoom({
|
|
1662
|
-
client,
|
|
1663
|
-
roomId: cached,
|
|
1664
|
-
remoteUserId: trimmed,
|
|
1665
|
-
selfUserId
|
|
1666
|
-
})) return cached;
|
|
1667
|
-
if (cached) directRoomCache.delete(trimmed);
|
|
1668
|
-
const inspection = await inspectMatrixDirectRooms({
|
|
1669
|
-
client,
|
|
1670
|
-
remoteUserId: trimmed
|
|
1784
|
+
function buildMediaContent(params) {
|
|
1785
|
+
const info = buildMatrixMediaInfo({
|
|
1786
|
+
size: params.size,
|
|
1787
|
+
mimetype: params.mimetype,
|
|
1788
|
+
durationMs: params.durationMs,
|
|
1789
|
+
imageInfo: params.imageInfo
|
|
1671
1790
|
});
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1791
|
+
const base = {
|
|
1792
|
+
msgtype: params.msgtype,
|
|
1793
|
+
body: params.body,
|
|
1794
|
+
filename: params.filename,
|
|
1795
|
+
info: info ?? void 0
|
|
1796
|
+
};
|
|
1797
|
+
if (!params.file && params.url) base.url = params.url;
|
|
1798
|
+
if (params.file) base.file = params.file;
|
|
1799
|
+
if (params.isVoice) {
|
|
1800
|
+
base["org.matrix.msc3245.voice"] = {};
|
|
1801
|
+
if (typeof params.durationMs === "number") base["org.matrix.msc1767.audio"] = { duration: params.durationMs };
|
|
1680
1802
|
}
|
|
1681
|
-
|
|
1803
|
+
if (params.relation) base["m.relates_to"] = params.relation;
|
|
1804
|
+
return base;
|
|
1682
1805
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1806
|
+
const THUMBNAIL_MAX_SIDE = 800;
|
|
1807
|
+
const THUMBNAIL_QUALITY = 80;
|
|
1808
|
+
const AIFC_IMA4_BYTES_PER_CHANNEL_PACKET = 34;
|
|
1809
|
+
const AIFC_IMA4_FRAMES_PER_PACKET = 64;
|
|
1810
|
+
function resolveAifcIma4DurationSeconds(buffer, sampleRate) {
|
|
1811
|
+
if (!sampleRate || !Number.isFinite(sampleRate) || buffer.length < 12 || buffer.toString("ascii", 0, 4) !== "FORM" || buffer.toString("ascii", 8, 12) !== "AIFC") return;
|
|
1812
|
+
let channels;
|
|
1813
|
+
let declaredFrameCount;
|
|
1814
|
+
let soundDataBytes;
|
|
1815
|
+
for (let offset = 12; offset + 8 <= buffer.length;) {
|
|
1816
|
+
const chunkType = buffer.toString("ascii", offset, offset + 4);
|
|
1817
|
+
const chunkSize = buffer.readUInt32BE(offset + 4);
|
|
1818
|
+
const chunkStart = offset + 8;
|
|
1819
|
+
if (chunkSize > buffer.length - chunkStart) return;
|
|
1820
|
+
if (chunkType === "COMM") {
|
|
1821
|
+
if (chunkSize < 22 || buffer.toString("ascii", chunkStart + 18, chunkStart + 22) !== "ima4") return;
|
|
1822
|
+
channels = buffer.readUInt16BE(chunkStart);
|
|
1823
|
+
declaredFrameCount = buffer.readUInt32BE(chunkStart + 2);
|
|
1824
|
+
} else if (chunkType === "SSND") {
|
|
1825
|
+
if (chunkSize < 8) return;
|
|
1826
|
+
const soundDataOffset = buffer.readUInt32BE(chunkStart);
|
|
1827
|
+
if (soundDataOffset > chunkSize - 8) return;
|
|
1828
|
+
soundDataBytes = chunkSize - 8 - soundDataOffset;
|
|
1829
|
+
}
|
|
1830
|
+
offset = chunkStart + chunkSize + (chunkSize & 1);
|
|
1691
1831
|
}
|
|
1692
|
-
return
|
|
1832
|
+
if (!channels || !declaredFrameCount || !soundDataBytes) return;
|
|
1833
|
+
const packetSize = channels * AIFC_IMA4_BYTES_PER_CHANNEL_PACKET;
|
|
1834
|
+
if (soundDataBytes % packetSize !== 0) return;
|
|
1835
|
+
const packetCount = soundDataBytes / packetSize;
|
|
1836
|
+
if (packetCount !== declaredFrameCount) return;
|
|
1837
|
+
return packetCount * AIFC_IMA4_FRAMES_PER_PACKET / sampleRate;
|
|
1838
|
+
}
|
|
1839
|
+
async function prepareImageInfo(params) {
|
|
1840
|
+
const meta = await getCore().media.getImageMetadata(params.buffer).catch(() => null);
|
|
1841
|
+
if (!meta) return;
|
|
1842
|
+
const imageInfo = {
|
|
1843
|
+
w: meta.width,
|
|
1844
|
+
h: meta.height
|
|
1845
|
+
};
|
|
1846
|
+
if (Math.max(meta.width, meta.height) > THUMBNAIL_MAX_SIDE) try {
|
|
1847
|
+
const thumbBuffer = await getCore().media.resizeToJpeg({
|
|
1848
|
+
buffer: params.buffer,
|
|
1849
|
+
maxSide: THUMBNAIL_MAX_SIDE,
|
|
1850
|
+
quality: THUMBNAIL_QUALITY,
|
|
1851
|
+
withoutEnlargement: true
|
|
1852
|
+
});
|
|
1853
|
+
const thumbMeta = await getCore().media.getImageMetadata(thumbBuffer).catch(() => null);
|
|
1854
|
+
const result = await uploadMediaWithEncryption(params.client, params.roomId, thumbBuffer, {
|
|
1855
|
+
contentType: "image/jpeg",
|
|
1856
|
+
filename: "thumbnail.jpg"
|
|
1857
|
+
});
|
|
1858
|
+
if (result.file) imageInfo.thumbnail_file = result.file;
|
|
1859
|
+
else imageInfo.thumbnail_url = result.url;
|
|
1860
|
+
if (thumbMeta) imageInfo.thumbnail_info = {
|
|
1861
|
+
w: thumbMeta.width,
|
|
1862
|
+
h: thumbMeta.height,
|
|
1863
|
+
mimetype: "image/jpeg",
|
|
1864
|
+
size: thumbBuffer.byteLength
|
|
1865
|
+
};
|
|
1866
|
+
} catch {}
|
|
1867
|
+
return imageInfo;
|
|
1868
|
+
}
|
|
1869
|
+
async function resolveMediaDurationMs(params) {
|
|
1870
|
+
if (params.kind !== "audio" && params.kind !== "video") return;
|
|
1871
|
+
try {
|
|
1872
|
+
const fileInfo = params.contentType || params.fileName ? {
|
|
1873
|
+
mimeType: params.contentType,
|
|
1874
|
+
size: params.buffer.byteLength,
|
|
1875
|
+
path: params.fileName
|
|
1876
|
+
} : void 0;
|
|
1877
|
+
const metadata = await parseBuffer(params.buffer, fileInfo, {
|
|
1878
|
+
duration: true,
|
|
1879
|
+
skipCovers: true
|
|
1880
|
+
});
|
|
1881
|
+
const durationSeconds = resolveAifcIma4DurationSeconds(params.buffer, metadata.format.sampleRate) ?? metadata.format.duration;
|
|
1882
|
+
if (typeof durationSeconds === "number" && Number.isFinite(durationSeconds)) return Math.max(0, Math.round(durationSeconds * 1e3));
|
|
1883
|
+
} catch {}
|
|
1884
|
+
}
|
|
1885
|
+
async function uploadMediaWithEncryption(client, roomId, buffer, params) {
|
|
1886
|
+
if (await client.prepareRoomForMessageSend(roomId) === "m.room.encrypted") {
|
|
1887
|
+
if (!client.crypto) throw new Error("Encrypted Matrix room: enable encryption before uploading media");
|
|
1888
|
+
const encrypted = await client.crypto.encryptMedia(buffer);
|
|
1889
|
+
const mxc = await client.uploadContent(encrypted.buffer, "application/octet-stream");
|
|
1890
|
+
return {
|
|
1891
|
+
url: mxc,
|
|
1892
|
+
file: {
|
|
1893
|
+
url: mxc,
|
|
1894
|
+
...encrypted.file
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
return { url: await client.uploadContent(buffer, params.contentType, params.filename) };
|
|
1693
1899
|
}
|
|
1694
1900
|
//#endregion
|
|
1695
1901
|
//#region extensions/matrix/src/matrix/send.ts
|
|
@@ -1707,16 +1913,28 @@ var send_exports = /* @__PURE__ */ __exportAll({
|
|
|
1707
1913
|
sendTypingMatrix: () => sendTypingMatrix
|
|
1708
1914
|
});
|
|
1709
1915
|
function createMatrixSendReceipt(params) {
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1916
|
+
const firstEvent = params.events[0];
|
|
1917
|
+
const receipt = createMessageReceiptFromOutboundResults({
|
|
1918
|
+
kind: firstEvent?.kind ?? "text",
|
|
1919
|
+
...firstEvent?.replyToId ? { replyToId: firstEvent.replyToId } : {},
|
|
1713
1920
|
...params.threadId ? { threadId: params.threadId } : {},
|
|
1714
|
-
results: params.
|
|
1921
|
+
results: params.events.map(({ messageId }) => ({
|
|
1715
1922
|
channel: "matrix",
|
|
1716
1923
|
messageId,
|
|
1717
1924
|
roomId: params.roomId
|
|
1718
1925
|
}))
|
|
1719
1926
|
});
|
|
1927
|
+
receipt.parts = receipt.parts.map((part, index) => {
|
|
1928
|
+
const event = params.events[index];
|
|
1929
|
+
const actualPart = {
|
|
1930
|
+
...part,
|
|
1931
|
+
kind: event.kind
|
|
1932
|
+
};
|
|
1933
|
+
if (event.replyToId) actualPart.replyToId = event.replyToId;
|
|
1934
|
+
else delete actualPart.replyToId;
|
|
1935
|
+
return actualPart;
|
|
1936
|
+
});
|
|
1937
|
+
return receipt;
|
|
1720
1938
|
}
|
|
1721
1939
|
function isMatrixClient(value) {
|
|
1722
1940
|
return typeof value.sendEvent === "function";
|
|
@@ -1769,8 +1987,13 @@ async function resolvePreviousEditMentions(params) {
|
|
|
1769
1987
|
});
|
|
1770
1988
|
}
|
|
1771
1989
|
async function sendMessageMatrix(to, message, opts) {
|
|
1772
|
-
const
|
|
1773
|
-
if (!
|
|
1990
|
+
const messageText = message?.trimEnd() ?? "";
|
|
1991
|
+
if (!messageText.trim() && !opts.mediaUrl) throw new Error("Matrix send requires text or media");
|
|
1992
|
+
const durableIdentity = resolveMatrixDurableDeliveryIdentity({
|
|
1993
|
+
queueId: opts.deliveryQueueId,
|
|
1994
|
+
partIndex: opts.deliveryPartIndex,
|
|
1995
|
+
partCount: opts.deliveryPartCount
|
|
1996
|
+
});
|
|
1774
1997
|
return await withResolvedMatrixSendClient({
|
|
1775
1998
|
client: opts.client,
|
|
1776
1999
|
cfg: opts.cfg,
|
|
@@ -1778,135 +2001,174 @@ async function sendMessageMatrix(to, message, opts) {
|
|
|
1778
2001
|
accountId: opts.accountId
|
|
1779
2002
|
}, async (client) => {
|
|
1780
2003
|
const roomId = await resolveMatrixRoomId(client, to);
|
|
2004
|
+
const wireEventType = await client.prepareRoomForMessageSend(roomId);
|
|
1781
2005
|
const cfg = requireRuntimeConfig(opts.cfg, "Matrix send");
|
|
1782
|
-
const { chunks, tableMode } = chunkMatrixText(trimmedMessage, {
|
|
1783
|
-
cfg,
|
|
1784
|
-
accountId: opts.accountId
|
|
1785
|
-
});
|
|
1786
2006
|
const threadId = normalizeThreadId(opts.threadId);
|
|
1787
|
-
const
|
|
1788
|
-
let
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
receipt: createMatrixSendReceipt({
|
|
1801
|
-
roomId,
|
|
1802
|
-
platformMessageIds: [eventId],
|
|
1803
|
-
kind,
|
|
1804
|
-
replyToId: opts.replyToId,
|
|
1805
|
-
threadId
|
|
1806
|
-
}),
|
|
1807
|
-
content: visibleContent
|
|
1808
|
-
});
|
|
1809
|
-
}
|
|
1810
|
-
return eventId;
|
|
1811
|
-
};
|
|
1812
|
-
const platformMessageIds = [];
|
|
1813
|
-
const acceptedContents = [];
|
|
1814
|
-
let lastMessageId = "";
|
|
1815
|
-
let receiptKind = "text";
|
|
1816
|
-
if (opts.mediaUrl) {
|
|
1817
|
-
const maxBytes = resolveMediaMaxBytes(opts.accountId, cfg);
|
|
1818
|
-
const media = await loadOutboundMediaFromUrl(opts.mediaUrl, {
|
|
1819
|
-
maxBytes,
|
|
1820
|
-
mediaAccess: opts.mediaAccess,
|
|
1821
|
-
mediaLocalRoots: opts.mediaLocalRoots,
|
|
1822
|
-
mediaReadFile: opts.mediaReadFile
|
|
1823
|
-
});
|
|
1824
|
-
const uploaded = await uploadMediaMaybeEncrypted(client, roomId, media.buffer, {
|
|
1825
|
-
contentType: media.contentType,
|
|
1826
|
-
filename: media.fileName
|
|
1827
|
-
});
|
|
1828
|
-
const durationMs = await resolveMediaDurationMs({
|
|
1829
|
-
buffer: media.buffer,
|
|
1830
|
-
contentType: media.contentType,
|
|
1831
|
-
fileName: media.fileName,
|
|
1832
|
-
kind: media.kind === "sticker" ? "unknown" : media.kind ?? "unknown"
|
|
1833
|
-
});
|
|
1834
|
-
const baseMsgType = resolveMatrixMsgType(media.contentType, media.fileName);
|
|
1835
|
-
const { useVoice } = resolveMatrixVoiceDecision({
|
|
1836
|
-
wantsVoice: opts.audioAsVoice === true,
|
|
1837
|
-
contentType: media.contentType,
|
|
1838
|
-
fileName: media.fileName
|
|
1839
|
-
});
|
|
1840
|
-
const msgtype = useVoice ? MsgType.Audio : baseMsgType;
|
|
1841
|
-
receiptKind = useVoice ? "voice" : "media";
|
|
1842
|
-
const imageInfo = msgtype === MsgType.Image ? await prepareImageInfo({
|
|
1843
|
-
buffer: media.buffer,
|
|
1844
|
-
client,
|
|
1845
|
-
encrypted: Boolean(uploaded.file)
|
|
1846
|
-
}) : void 0;
|
|
1847
|
-
const [firstChunk, ...rest] = chunks;
|
|
1848
|
-
const captionMarkdown = useVoice ? "" : firstChunk ?? "";
|
|
1849
|
-
const content = buildMediaContent({
|
|
1850
|
-
msgtype,
|
|
1851
|
-
body: useVoice ? "Voice message" : captionMarkdown || media.fileName || "(file)",
|
|
1852
|
-
url: uploaded.url,
|
|
1853
|
-
file: uploaded.file,
|
|
1854
|
-
filename: media.fileName,
|
|
1855
|
-
mimetype: media.contentType,
|
|
1856
|
-
size: media.buffer.byteLength,
|
|
1857
|
-
durationMs,
|
|
1858
|
-
relation,
|
|
1859
|
-
isVoice: useVoice,
|
|
1860
|
-
imageInfo
|
|
1861
|
-
});
|
|
1862
|
-
await enrichMatrixFormattedContent({
|
|
1863
|
-
client,
|
|
1864
|
-
content,
|
|
1865
|
-
markdown: captionMarkdown,
|
|
1866
|
-
tableMode
|
|
2007
|
+
const transactionScopeId = durableIdentity ? await client.getTransactionScopeId() : void 0;
|
|
2008
|
+
let plannedEvents = (durableIdentity ? await loadMatrixDeliveryPlan({
|
|
2009
|
+
identity: durableIdentity,
|
|
2010
|
+
accountId: opts.accountId,
|
|
2011
|
+
roomId,
|
|
2012
|
+
transactionScopeId,
|
|
2013
|
+
wireEventType
|
|
2014
|
+
}) : null)?.events;
|
|
2015
|
+
if (!plannedEvents) {
|
|
2016
|
+
const { chunks, tableMode } = chunkMatrixText(messageText, {
|
|
2017
|
+
cfg,
|
|
2018
|
+
accountId: opts.accountId,
|
|
2019
|
+
preserveWhitespace: true
|
|
1867
2020
|
});
|
|
1868
|
-
const
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2021
|
+
const relation = threadId ? buildThreadRelation(threadId, opts.replyToId) : buildReplyRelation(opts.replyToId);
|
|
2022
|
+
let pendingExtraContent = opts.extraContent;
|
|
2023
|
+
const events = [];
|
|
2024
|
+
const prepareContent = (content, receiptKind) => {
|
|
2025
|
+
events.push({
|
|
2026
|
+
content: withMatrixExtraContentFields(content, pendingExtraContent),
|
|
2027
|
+
receiptKind
|
|
2028
|
+
});
|
|
2029
|
+
pendingExtraContent = void 0;
|
|
2030
|
+
};
|
|
2031
|
+
if (opts.mediaUrl) {
|
|
2032
|
+
const maxBytes = resolveMediaMaxBytes(opts.accountId, cfg);
|
|
2033
|
+
const media = await loadOutboundMediaFromUrl(opts.mediaUrl, {
|
|
2034
|
+
maxBytes,
|
|
2035
|
+
mediaAccess: opts.mediaAccess,
|
|
2036
|
+
mediaLocalRoots: opts.mediaLocalRoots,
|
|
2037
|
+
mediaReadFile: opts.mediaReadFile
|
|
2038
|
+
});
|
|
2039
|
+
const uploaded = await uploadMediaWithEncryption(client, roomId, media.buffer, {
|
|
2040
|
+
contentType: media.contentType,
|
|
2041
|
+
filename: media.fileName
|
|
2042
|
+
});
|
|
2043
|
+
const durationMs = await resolveMediaDurationMs({
|
|
2044
|
+
buffer: media.buffer,
|
|
2045
|
+
contentType: media.contentType,
|
|
2046
|
+
fileName: media.fileName,
|
|
2047
|
+
kind: media.kind === "sticker" ? "unknown" : media.kind ?? "unknown"
|
|
2048
|
+
});
|
|
2049
|
+
const baseMsgType = resolveMatrixMsgType(media.contentType, media.fileName);
|
|
2050
|
+
const { useVoice } = resolveMatrixVoiceDecision({
|
|
2051
|
+
wantsVoice: opts.audioAsVoice === true,
|
|
2052
|
+
contentType: media.contentType,
|
|
2053
|
+
fileName: media.fileName
|
|
2054
|
+
});
|
|
2055
|
+
const msgtype = useVoice ? MsgType.Audio : baseMsgType;
|
|
2056
|
+
const receiptKind = useVoice ? "voice" : "media";
|
|
2057
|
+
const imageInfo = msgtype === MsgType.Image ? await prepareImageInfo({
|
|
2058
|
+
buffer: media.buffer,
|
|
2059
|
+
client,
|
|
2060
|
+
roomId
|
|
2061
|
+
}) : void 0;
|
|
2062
|
+
const [firstChunk, ...rest] = chunks;
|
|
2063
|
+
const captionMarkdown = useVoice ? "" : firstChunk ?? "";
|
|
2064
|
+
const content = buildMediaContent({
|
|
2065
|
+
msgtype,
|
|
2066
|
+
body: useVoice ? "Voice message" : captionMarkdown || media.fileName || "(file)",
|
|
2067
|
+
url: uploaded.url,
|
|
2068
|
+
file: uploaded.file,
|
|
2069
|
+
filename: media.fileName,
|
|
2070
|
+
mimetype: media.contentType,
|
|
2071
|
+
size: media.buffer.byteLength,
|
|
2072
|
+
durationMs,
|
|
2073
|
+
relation,
|
|
2074
|
+
isVoice: useVoice,
|
|
2075
|
+
imageInfo
|
|
2076
|
+
});
|
|
2077
|
+
await enrichMatrixFormattedContent({
|
|
2078
|
+
client,
|
|
2079
|
+
content,
|
|
2080
|
+
markdown: captionMarkdown,
|
|
2081
|
+
tableMode
|
|
2082
|
+
});
|
|
2083
|
+
prepareContent(content, receiptKind);
|
|
2084
|
+
const textChunks = useVoice ? chunks : rest;
|
|
2085
|
+
const followupRelation = useVoice || threadId ? relation : void 0;
|
|
2086
|
+
for (const chunk of textChunks) {
|
|
2087
|
+
if (!chunk.trim()) continue;
|
|
2088
|
+
const followup = buildTextContent(chunk, followupRelation);
|
|
2089
|
+
await enrichMatrixFormattedContent({
|
|
2090
|
+
client,
|
|
2091
|
+
content: followup,
|
|
2092
|
+
markdown: chunk,
|
|
2093
|
+
tableMode
|
|
2094
|
+
});
|
|
2095
|
+
prepareContent(followup, "text");
|
|
2096
|
+
}
|
|
2097
|
+
} else for (const chunk of chunks.length ? chunks : [""]) {
|
|
2098
|
+
if (!chunk.trim()) continue;
|
|
2099
|
+
const content = buildTextContent(chunk, relation);
|
|
1877
2100
|
await enrichMatrixFormattedContent({
|
|
1878
2101
|
client,
|
|
1879
|
-
content
|
|
1880
|
-
markdown:
|
|
2102
|
+
content,
|
|
2103
|
+
markdown: chunk,
|
|
1881
2104
|
tableMode
|
|
1882
2105
|
});
|
|
1883
|
-
|
|
1884
|
-
lastMessageId = followupEventId ?? lastMessageId;
|
|
1885
|
-
if (followupEventId) platformMessageIds.push(followupEventId);
|
|
2106
|
+
prepareContent(content, "text");
|
|
1886
2107
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2108
|
+
plannedEvents = durableIdentity ? createMatrixPlannedEvents({
|
|
2109
|
+
identity: durableIdentity,
|
|
2110
|
+
events
|
|
2111
|
+
}) : events.map((event) => ({
|
|
2112
|
+
content: event.content,
|
|
2113
|
+
receiptKind: event.receiptKind,
|
|
2114
|
+
transactionId: ""
|
|
2115
|
+
}));
|
|
2116
|
+
}
|
|
2117
|
+
if (opts.mediaUrl) await client.prepareRoomForMessageSend(roomId, plannedEvents[0]?.content);
|
|
2118
|
+
let platformDispatchStarted = false;
|
|
2119
|
+
if (!durableIdentity) {
|
|
2120
|
+
await opts.onPlatformSendDispatch?.();
|
|
2121
|
+
platformDispatchStarted = true;
|
|
2122
|
+
}
|
|
2123
|
+
const acceptedEvents = [];
|
|
2124
|
+
const acceptedContents = [];
|
|
2125
|
+
let lastMessageId = "";
|
|
2126
|
+
for (const planned of plannedEvents) {
|
|
2127
|
+
const eventId = await client.sendMessage(roomId, planned.content, planned.transactionId || void 0, durableIdentity ? async (dispatch) => {
|
|
2128
|
+
await persistMatrixDeliveryPlan({
|
|
2129
|
+
identity: durableIdentity,
|
|
2130
|
+
accountId: opts.accountId,
|
|
2131
|
+
roomId,
|
|
2132
|
+
transactionScopeId,
|
|
2133
|
+
wireEventType: dispatch.eventType,
|
|
2134
|
+
events: plannedEvents,
|
|
2135
|
+
dispatch
|
|
2136
|
+
});
|
|
2137
|
+
if (!platformDispatchStarted) {
|
|
2138
|
+
await opts.onPlatformSendDispatch?.();
|
|
2139
|
+
platformDispatchStarted = true;
|
|
2140
|
+
}
|
|
2141
|
+
} : void 0);
|
|
2142
|
+
lastMessageId = eventId || lastMessageId;
|
|
2143
|
+
if (!eventId) continue;
|
|
2144
|
+
const eventReplyToId = planned.content["m.relates_to"]?.["m.in_reply_to"]?.event_id;
|
|
2145
|
+
const acceptedEvent = {
|
|
2146
|
+
messageId: eventId,
|
|
2147
|
+
kind: planned.receiptKind,
|
|
2148
|
+
...eventReplyToId ? { replyToId: eventReplyToId } : {}
|
|
2149
|
+
};
|
|
2150
|
+
acceptedEvents.push(acceptedEvent);
|
|
2151
|
+
const visibleContent = planned.content.body ?? "";
|
|
2152
|
+
acceptedContents.push(visibleContent);
|
|
2153
|
+
await opts.onDeliveryResult?.({
|
|
2154
|
+
messageId: eventId,
|
|
2155
|
+
roomId,
|
|
2156
|
+
primaryMessageId: eventId,
|
|
2157
|
+
receipt: createMatrixSendReceipt({
|
|
2158
|
+
roomId,
|
|
2159
|
+
events: [acceptedEvent],
|
|
2160
|
+
threadId
|
|
2161
|
+
}),
|
|
2162
|
+
content: visibleContent
|
|
1896
2163
|
});
|
|
1897
|
-
const eventId = await sendContent(content, "text");
|
|
1898
|
-
lastMessageId = eventId ?? lastMessageId;
|
|
1899
|
-
if (eventId) platformMessageIds.push(eventId);
|
|
1900
2164
|
}
|
|
1901
2165
|
return {
|
|
1902
2166
|
messageId: lastMessageId || "unknown",
|
|
1903
2167
|
roomId,
|
|
1904
|
-
primaryMessageId:
|
|
2168
|
+
primaryMessageId: acceptedEvents[0]?.messageId ?? (lastMessageId || "unknown"),
|
|
1905
2169
|
receipt: createMatrixSendReceipt({
|
|
1906
2170
|
roomId,
|
|
1907
|
-
|
|
1908
|
-
kind: receiptKind,
|
|
1909
|
-
replyToId: opts.replyToId,
|
|
2171
|
+
events: acceptedEvents,
|
|
1910
2172
|
threadId
|
|
1911
2173
|
}),
|
|
1912
2174
|
content: acceptedContents.join("\n")
|
|
@@ -1967,11 +2229,12 @@ async function sendReadReceiptMatrix(roomId, eventId, client) {
|
|
|
1967
2229
|
});
|
|
1968
2230
|
}
|
|
1969
2231
|
async function sendSingleTextMessageMatrix(roomId, text, opts) {
|
|
1970
|
-
const { trimmedText, convertedText, singleEventLimit, eventTextLength, fitsInSingleEvent, tableMode } = prepareMatrixSingleText(text, {
|
|
2232
|
+
const { trimmedText, convertedText, singleEventLimit, eventTextLength, fitsInSingleEvent, tableMode } = prepareMatrixSingleText(text.trimEnd(), {
|
|
1971
2233
|
cfg: opts.cfg,
|
|
1972
|
-
accountId: opts.accountId
|
|
2234
|
+
accountId: opts.accountId,
|
|
2235
|
+
preserveWhitespace: true
|
|
1973
2236
|
});
|
|
1974
|
-
if (!trimmedText) throw new Error("Matrix single-message send requires text");
|
|
2237
|
+
if (!trimmedText.trim()) throw new Error("Matrix single-message send requires text");
|
|
1975
2238
|
if (!fitsInSingleEvent) throw new Error(`Matrix single-message text exceeds limit (${eventTextLength} > ${singleEventLimit})`);
|
|
1976
2239
|
return await withResolvedMatrixSendClient({
|
|
1977
2240
|
client: opts.client,
|
|
@@ -1991,15 +2254,18 @@ async function sendSingleTextMessageMatrix(roomId, text, opts) {
|
|
|
1991
2254
|
});
|
|
1992
2255
|
if (opts.live) content[MSC4357_LIVE_KEY] = {};
|
|
1993
2256
|
const eventId = await client.sendMessage(resolvedRoom, content);
|
|
2257
|
+
const replyToId = content["m.relates_to"]?.["m.in_reply_to"]?.event_id;
|
|
1994
2258
|
return {
|
|
1995
2259
|
messageId: eventId ?? "unknown",
|
|
1996
2260
|
roomId: resolvedRoom,
|
|
1997
2261
|
primaryMessageId: eventId ?? "unknown",
|
|
1998
2262
|
receipt: createMatrixSendReceipt({
|
|
1999
2263
|
roomId: resolvedRoom,
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2264
|
+
events: eventId ? [{
|
|
2265
|
+
messageId: eventId,
|
|
2266
|
+
kind: "text",
|
|
2267
|
+
...replyToId ? { replyToId } : {}
|
|
2268
|
+
}] : [],
|
|
2003
2269
|
threadId: normalizedThreadId
|
|
2004
2270
|
}),
|
|
2005
2271
|
content: content.body
|
|
@@ -2074,4 +2340,4 @@ async function reactMatrixMessage(roomId, messageId, emoji, opts) {
|
|
|
2074
2340
|
});
|
|
2075
2341
|
}
|
|
2076
2342
|
//#endregion
|
|
2077
|
-
export {
|
|
2343
|
+
export { promoteMatrixDirectRoomCandidate as C, MsgType as E, direct_management_exports as S, MATRIX_OPENCLAW_FINALIZED_PREVIEW_KEY as T, parsePollStartContent as _, sendSingleTextMessageMatrix as a, reconcileMatrixUnknownSend as b, chunkMatrixText as c, buildPollResultsSummary as d, formatPollAsText as f, parsePollStart as g, isPollStartType as h, sendPollMatrix as i, prepareMatrixSingleText as l, isPollEventType as m, reactMatrixMessage as n, sendTypingMatrix as o, formatPollResultsAsText as p, sendMessageMatrix as r, send_exports as s, editMessageMatrix as t, buildPollResponseContent as u, resolvePollReferenceEventId as v, repairMatrixDirectRooms as w, resolveMatrixRoomId as x, cleanupMatrixDeliveryPlans as y };
|