openclaw-matrix-multiaccounts/src/matrix/actions/messages.ts
Claudia 8790ba5dad feat: Add multi-account Matrix support
- Modified shared.ts to maintain a Map of clients per accountId
- Each account gets its own Matrix client instance
- Backwards compatible with single-account usage
- resolveMatrixAuth now accepts accountId parameter
- stopSharedClient can stop specific or all accounts

Files changed:
- src/matrix/client/shared.ts (main changes)
- src/matrix/client/config.ts (accountId support)
- src/matrix/accounts.ts (list enabled accounts)
- src/matrix/monitor/index.ts (pass accountId)
- src/types.ts (accounts config type)
2026-02-01 11:06:37 +01:00

120 lines
3.4 KiB
TypeScript

import {
EventType,
MsgType,
RelationType,
type MatrixActionClientOpts,
type MatrixMessageSummary,
type MatrixRawEvent,
type RoomMessageEventContent,
} from "./types.js";
import { resolveActionClient } from "./client.js";
import { summarizeMatrixRawEvent } from "./summary.js";
import { resolveMatrixRoomId, sendMessageMatrix } from "../send.js";
export async function sendMatrixMessage(
to: string,
content: string,
opts: MatrixActionClientOpts & {
mediaUrl?: string;
replyToId?: string;
threadId?: string;
} = {},
) {
return await sendMessageMatrix(to, content, {
mediaUrl: opts.mediaUrl,
replyToId: opts.replyToId,
threadId: opts.threadId,
client: opts.client,
timeoutMs: opts.timeoutMs,
});
}
export async function editMatrixMessage(
roomId: string,
messageId: string,
content: string,
opts: MatrixActionClientOpts = {},
) {
const trimmed = content.trim();
if (!trimmed) throw new Error("Matrix edit requires content");
const { client, stopOnDone } = await resolveActionClient(opts);
try {
const resolvedRoom = await resolveMatrixRoomId(client, roomId);
const newContent = {
msgtype: MsgType.Text,
body: trimmed,
} satisfies RoomMessageEventContent;
const payload: RoomMessageEventContent = {
msgtype: MsgType.Text,
body: `* ${trimmed}`,
"m.new_content": newContent,
"m.relates_to": {
rel_type: RelationType.Replace,
event_id: messageId,
},
};
const eventId = await client.sendMessage(resolvedRoom, payload);
return { eventId: eventId ?? null };
} finally {
if (stopOnDone) client.stop();
}
}
export async function deleteMatrixMessage(
roomId: string,
messageId: string,
opts: MatrixActionClientOpts & { reason?: string } = {},
) {
const { client, stopOnDone } = await resolveActionClient(opts);
try {
const resolvedRoom = await resolveMatrixRoomId(client, roomId);
await client.redactEvent(resolvedRoom, messageId, opts.reason);
} finally {
if (stopOnDone) client.stop();
}
}
export async function readMatrixMessages(
roomId: string,
opts: MatrixActionClientOpts & {
limit?: number;
before?: string;
after?: string;
} = {},
): Promise<{
messages: MatrixMessageSummary[];
nextBatch?: string | null;
prevBatch?: string | null;
}> {
const { client, stopOnDone } = await resolveActionClient(opts);
try {
const resolvedRoom = await resolveMatrixRoomId(client, roomId);
const limit =
typeof opts.limit === "number" && Number.isFinite(opts.limit)
? Math.max(1, Math.floor(opts.limit))
: 20;
const token = opts.before?.trim() || opts.after?.trim() || undefined;
const dir = opts.after ? "f" : "b";
// @vector-im/matrix-bot-sdk uses doRequest for room messages
const res = await client.doRequest(
"GET",
`/_matrix/client/v3/rooms/${encodeURIComponent(resolvedRoom)}/messages`,
{
dir,
limit,
from: token,
},
) as { chunk: MatrixRawEvent[]; start?: string; end?: string };
const messages = res.chunk
.filter((event) => event.type === EventType.RoomMessage)
.filter((event) => !event.unsigned?.redacted_because)
.map(summarizeMatrixRawEvent);
return {
messages,
nextBatch: res.end ?? null,
prevBatch: res.start ?? null,
};
} finally {
if (stopOnDone) client.stop();
}
}