diff --git a/src/bot.ts b/src/bot.ts index 0a198470e591325f55891562160374c56d1504f4..207fc9b0b75b4dd7b0ce60af5f91c4317002a43f 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1123,14 +1123,24 @@ export class DiscordBot { msgtype: result.msgtype, }; if (msg.reference) { - const storeEvent = await this.store.Get(DbEvent, {discord_id: msg.reference?.messageID}) - if (storeEvent && storeEvent.Result) - { - while(storeEvent.Next()) - { + const storeEvent = await this.store.Get(DbEvent, { discord_id: msg.reference?.messageID }); + if (storeEvent && storeEvent.Result) { + let replyToEventId: string | undefined = undefined; + while (storeEvent.Next()) { + const [eventId] = storeEvent.MatrixId.split(";"); + // Try to get the "deepest" event ID if this event replaces another ID + // We need to do this since a m.in_reply_to relation requires the original event ID and not the replacement one + const { chunk } = await intent.underlyingClient.unstableApis.getRelationsForEvent(room, eventId, "m.replace"); + if (!!chunk?.length) { + replyToEventId = chunk[0].content['m.relates_to'].event_id; + } else { + replyToEventId ??= eventId; + } + } + if (replyToEventId) { sendContent["m.relates_to"] = { "m.in_reply_to": { - event_id: storeEvent.MatrixId.split(";")[0] + event_id: replyToEventId } }; } @@ -1150,7 +1160,7 @@ export class DiscordBot { rel_type: "m.replace", }; } - const trySend = async () => intent.sendEvent(room, sendContent); + const trySend = async () => intent.sendEvent(room, sendContent); const afterSend = async (eventId) => { this.lastEventIds[room] = eventId; const evt = new DbEvent(); diff --git a/test/mocks/message.ts b/test/mocks/message.ts index c9ec0cbbb60eaf4efa94e28d0fbc8129e61af44c..ff5406855fa64f303627ae65bb3eb1ad725e38fd 100644 --- a/test/mocks/message.ts +++ b/test/mocks/message.ts @@ -29,11 +29,13 @@ export class MockMessage { public guild: Discord.Guild | undefined; public author: MockUser; public mentions: any = {}; + public reference?: Discord.MessageReference = undefined; constructor( channel?: Discord.TextChannel, content: string = "", author: MockUser = new MockUser("123456"), + reference = undefined ) { this.mentions.everyone = false; this.channel = channel; @@ -42,5 +44,6 @@ export class MockMessage { } this.content = content; this.author = author; + this.reference = reference; } } diff --git a/test/test_discordbot.ts b/test/test_discordbot.ts index c89a0cd0dce04c38bc305ee548c0ca136ac226de..009157ded564bf3ec1e77a9d964fa8b27e7e495f 100644 --- a/test/test_discordbot.ts +++ b/test/test_discordbot.ts @@ -16,6 +16,7 @@ limitations under the License. import { expect } from "chai"; import * as Proxyquire from "proxyquire"; +import * as Discord from "better-discord.js"; import { MockGuild } from "./mocks/guild"; import { MockMember } from "./mocks/member"; @@ -107,8 +108,15 @@ describe("DiscordBot", () => { describe("OnMessage()", () => { const channel = new MockTextChannel(); const msg = new MockMessage(channel); - const author = new MockUser("11111"); + const msgReply: Discord.MessageReference = { + messageID: '111', + guildID: '111', + channelID: '111', + }; + const authorId = '11111'; + const author = new MockUser(authorId); let HANDLE_COMMAND = false; + const roomId = "!asdf:localhost"; function getDiscordBot() { HANDLE_COMMAND = false; mockBridge.cleanup(); @@ -122,7 +130,7 @@ describe("DiscordBot", () => { OnUpdateUser: async () => { }, }; discord.channelSync = { - GetRoomIdsFromChannel: async () => ["!asdf:localhost"], + GetRoomIdsFromChannel: async () => [roomId], }; discord.discordCommandHandler = { Process: async () => { HANDLE_COMMAND = true; }, @@ -163,12 +171,95 @@ describe("DiscordBot", () => { await discordBot.OnMessage(msg as any); mockBridge.getIntent(author.id).wasCalled("sendEvent"); }); + describe("sends replies", () => { + const originalMxId = "$mAKet_w5WYFCgh1WaHVOvyn9LJLbolFeuELTKVfm0Po"; + const replacementMxId = "$grdFSE_12LFSELOIFMSOEIJOIJ98kljnfIfESJOIFESO"; + msg.author = author; + msg.content = "Foxies are amazing!"; + msg.reference = msgReply; + it("to a Discord message", async () => { + discordBot = getDiscordBot(); + discordBot.store = { + Get: async (a, b) => { + let storeMockResults = 0; + return Promise.resolve({ + Result: true, + MatrixId: `${originalMxId};${roomId}`, + Next: () => storeMockResults-- >= 0 + }); + }, + Insert: async () => { }, + }; + const intent = mockBridge.getIntent(author.id); + intent.underlyingClient.unstableApis.getRelationsForEvent = async () => ({ + chunk: [] + }); + await discordBot.OnMessage(msg); + mockBridge.getIntent(author.id).wasCalled("sendEvent", true, roomId, { + body: msg.content, + format: "org.matrix.custom.html", + formatted_body: msg.content, + msgtype: 'm.text', + "m.relates_to": { + "m.in_reply_to": { + event_id: originalMxId + } + } + }); + }); + it("to an edited Discord message", async () => { + let storeEventData = { MatrixId: `${originalMxId};${roomId}` }; + discordBot = getDiscordBot(); + discordBot.store = { + Get: async (a, b) => { + let storeMockResults = 1; + return Promise.resolve({ + Result: true, + ...storeEventData, + Next: () => { + storeEventData = { + MatrixId: `${replacementMxId};${roomId}`, + } + return storeMockResults-- >= 0; + } + }) + }, + Insert: async () => { }, + } + const intent = mockBridge.getIntent(author.id); + intent.underlyingClient.unstableApis.getRelationsForEvent = async () => ({ + chunk: [{ + sender: "11111", + room_id: roomId, + event_id: originalMxId, + content: { + "m.relates_to": { + event_id: replacementMxId, + rel_type: 'm.replace' + } + } + }] + }); + await discordBot.OnMessage(msg); + mockBridge.getIntent(author.id).wasCalled("sendEvent", true, roomId, { + body: msg.content, + format: "org.matrix.custom.html", + formatted_body: msg.content, + msgtype: 'm.text', + "m.relates_to": { + "m.in_reply_to": { + event_id: replacementMxId + } + } + }); + }); + }); it("sends edit messages", async () => { discordBot = getDiscordBot(); msg.author = author; msg.content = "Foxies are super amazing!"; await discordBot.OnMessage(msg, "editevent"); - mockBridge.getIntent(author.id).wasCalled("sendEvent", true, "!asdf:localhost", { + mockBridge.getIntent(author.id).wasCalled("sendEvent", true, "!asdf:localhost", { "body": "* Foxies are super amazing!", "format": "org.matrix.custom.html", "formatted_body": "* Foxies are super amazing!",