diff --git a/src/m2d/converters/event-to-message.js b/src/m2d/converters/event-to-message.js index ebbe9e1..b52717d 100644 --- a/src/m2d/converters/event-to-message.js +++ b/src/m2d/converters/event-to-message.js @@ -330,9 +330,9 @@ async function uploadEndOfMessageSpriteSheet(content, attachments, pendingFiles, */ async function handleRoomOrMessageLinks(input, di) { let offset = 0 - for (const match of [...input.matchAll(/("?https:\/\/matrix.to\/#\/(![^"/, ?)]+)(?:\/(\$[^"/ ?)]+))?(?:\?[^",:!? )]*?)?)(">|[,<\n )]|$)/g)]) { + for (const match of [...input.matchAll(/("?https:\/\/matrix.to\/#\/((?:#|%23|!)[^"/, ?)]+)(?:\/(\$[^"/ ?)]+))?(?:\?[^",:!? )]*?)?)(">|[,<\n )]|$)/g)]) { assert(typeof match.index === "number") - const [_, attributeValue, roomID, eventID, endMarker] = match + let [_, attributeValue, roomID, eventID, endMarker] = match let result const resultType = endMarker === '">' ? "html" : "plain" @@ -350,6 +350,16 @@ async function handleRoomOrMessageLinks(input, di) { // Don't process links that are part of the reply fallback, they'll be removed entirely by turndown if (input.slice(match.index + match[0].length + offset).startsWith("In reply to")) continue + // Resolve room alias to room ID if needed + roomID = decodeURIComponent(roomID) + if (roomID[0] === "#") { + try { + roomID = await di.api.getAlias(roomID) + } catch (e) { + continue // Room alias is unresolvable, so it can't be converted + } + } + const channelID = select("channel_room", "channel_id", {room_id: roomID}).pluck().get() if (!channelID) continue if (!eventID) { diff --git a/src/m2d/converters/event-to-message.test.js b/src/m2d/converters/event-to-message.test.js index a97fd26..145e9ec 100644 --- a/src/m2d/converters/event-to-message.test.js +++ b/src/m2d/converters/event-to-message.test.js @@ -2957,6 +2957,133 @@ test("event2message: mentioning bridged rooms works (plaintext body)", async t = ) }) +test("event2message: mentioning bridged rooms by alias works", async t => { + let called = 0 + t.deepEqual( + await eventToMessage({ + content: { + msgtype: "m.text", + body: "wrong body", + format: "org.matrix.custom.html", + formatted_body: `I'm just worm-farm testing channel mentions` + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }, {}, { + api: { + async getAlias(alias) { + called++ + t.equal(alias, "#worm-farm:cadence.moe") + return "!BnKuBPCvyfOkhcUjEu:cadence.moe" + } + } + }), + { + ensureJoined: [], + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "cadence [they]", + content: "I'm just <#1100319550446252084> testing channel mentions", + avatar_url: undefined, + allowed_mentions: { + parse: ["users", "roles"] + } + }] + } + ) + t.equal(called, 1) +}) + +test("event2message: mentioning bridged rooms by alias works (plaintext body)", async t => { + let called = 0 + t.deepEqual( + await eventToMessage({ + content: { + msgtype: "m.text", + body: `I'm just https://matrix.to/#/#worm-farm:cadence.moe?via=cadence.moe testing channel mentions` + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }, {}, { + api: { + async getAlias(alias) { + called++ + t.equal(alias, "#worm-farm:cadence.moe") + return "!BnKuBPCvyfOkhcUjEu:cadence.moe" + } + } + }), + { + ensureJoined: [], + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "cadence [they]", + content: "I'm just <#1100319550446252084> testing channel mentions", + avatar_url: undefined, + allowed_mentions: { + parse: ["users", "roles"] + } + }] + } + ) + t.equal(called, 1) +}) + +test("event2message: mentioning bridged rooms by alias skips the link when alias is unresolvable", async t => { + let called = 0 + t.deepEqual( + await eventToMessage({ + content: { + msgtype: "m.text", + body: `I'm just https://matrix.to/#/#worm-farm:cadence.moe?via=cadence.moe and https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe?via=cadence.moe testing channel mentions` + }, + event_id: "$g07oYSZFWBkxohNEfywldwgcWj1hbhDzQ1sBAKvqOOU", + origin_server_ts: 1688301929913, + room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe", + sender: "@cadence:cadence.moe", + type: "m.room.message", + unsigned: { + age: 405299 + } + }, {}, { + api: { + async getAlias(alias) { + called++ + throw new MatrixServerError("Alias doesn't exist or something") + } + } + }), + { + ensureJoined: [], + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "cadence [they]", + content: "I'm just and <#1100319550446252084> testing channel mentions", + avatar_url: undefined, + allowed_mentions: { + parse: ["users", "roles"] + } + }] + } + ) + t.equal(called, 1) +}) + test("event2message: mentioning known bridged events works (plaintext body)", async t => { t.deepEqual( await eventToMessage({ diff --git a/src/matrix/api.js b/src/matrix/api.js index f9d3e08..c2b2384 100644 --- a/src/matrix/api.js +++ b/src/matrix/api.js @@ -367,6 +367,16 @@ async function ackEvent(event, mxid) { await sendReadReceipt(event.room_id, event.event_id, mxid) } +/** + * Resolve a room alias to a room ID. + * @param {string} alias + */ +async function getAlias(alias) { + /** @type {Ty.R.ResolvedRoom} */ + const root = await mreq.mreq("GET", `/client/v3/directory/room/${encodeURIComponent(alias)}`) + return root.room_id +} + module.exports.path = path module.exports.register = register module.exports.createRoom = createRoom @@ -395,3 +405,4 @@ module.exports.ping = ping module.exports.getMedia = getMedia module.exports.sendReadReceipt = sendReadReceipt module.exports.ackEvent = ackEvent +module.exports.getAlias = getAlias diff --git a/src/types.d.ts b/src/types.d.ts index b99046b..3298c40 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -336,6 +336,11 @@ export namespace R { room_id: string room_type?: string } + + export type ResolvedRoom = { + room_id: string + servers: string[] + } } export type Pagination = {