diff --git a/src/bot.ts b/src/bot.ts index c6053d6cb45c86bfce83859ab31ef50801d5836f..44688901193671603f4b2513eaef93bd6b22fb3c 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -30,7 +30,7 @@ import { MatrixRoomHandler } from "./matrixroomhandler"; import { Log } from "./log"; import * as Discord from "discord.js"; import * as mime from "mime"; -import { IMatrixEvent, IMatrixMediaInfo } from "./matrixtypes"; +import { IMatrixEvent, IMatrixMediaInfo, IMatrixMessage } from "./matrixtypes"; import { Appservice, Intent } from "matrix-bot-sdk"; import { DiscordCommandHandler } from "./discordcommandhandler"; import { MetricPeg } from "./metrics"; @@ -388,6 +388,75 @@ export class DiscordBot { this.channelLock.release(channel.id); } + /** + * Edits an event on Discord. + * @throws {Unstable.ForeignNetworkError} + */ + public async edit( + embedSet: IMatrixEventProcessorResult, + opts: Discord.MessageOptions, + roomLookup: ChannelLookupResult, + event: IMatrixEvent, + editEventId: string, + ): Promise<void> { + const chan = roomLookup.channel; + const botUser = roomLookup.botUser; + const embed = embedSet.messageEmbed; + const oldMsg = await chan.fetchMessage(editEventId); + if (!oldMsg) { + // old message not found, just sending this normally + await this.send(embedSet, opts, roomLookup, event); + return; + } + if (!botUser) { + try { + if (!roomLookup.canSendEmbeds) { + await oldMsg.edit(this.prepareEmbedSetUserAccount(embedSet), opts); + } else { + opts.embed = this.prepareEmbedSetBotAccount(embedSet); + await oldMsg.edit(embed.description, opts); + } + return; + } catch (err) { + log.warning("Failed to edit discord message, falling back to delete and resend...", err); + } + } + try { + if (editEventId === this.lastEventIds[chan.id]) { + log.info("Immediate edit, deleting and re-sending"); + this.channelLock.set(chan.id); + // we need to delete the event off of the store + // else the delete bridges over back to matrix + const dbEvent = await this.store.Get(DbEvent, { discord_id: editEventId }); + log.verbose("Event to delete", dbEvent); + if (dbEvent && dbEvent.Next()) { + await this.store.Delete(dbEvent); + } + await oldMsg.delete(); + this.channelLock.release(chan.id); + const msg = await this.send(embedSet, opts, roomLookup, event, true); + // we re-insert the old matrix event with the new discord id + // to allow consecutive edits, as matrix edits are typically + // done on the original event + const dummyEvent = { + event_id: event.content!["m.relates_to"].event_id, + room_id: event.room_id, + } as IMatrixEvent; + this.StoreMessagesSent(msg, chan, dummyEvent).catch(() => { + log.warn("Failed to store edit sent message for ", event.event_id); + }); + return; + } + const link = `https://discordapp.com/channels/${chan.guild.id}/${chan.id}/${editEventId}`; + embedSet.messageEmbed.description = `[Edit](${link}): ${embedSet.messageEmbed.description}`; + await this.send(embedSet, opts, roomLookup, event); + } catch (err) { + // throw wrapError(err, Unstable.ForeignNetworkError, "Couldn't edit message"); + log.warn(`Failed to edit message ${event.event_id}`); + log.verbose(err); + } + } + /** * Sends an event to Discord. * @throws {Unstable.ForeignNetworkError} @@ -397,7 +466,8 @@ export class DiscordBot { opts: Discord.MessageOptions, roomLookup: ChannelLookupResult, event: IMatrixEvent, - ): Promise<void> { + awaitStore: boolean = false, + ): Promise<Discord.Message | null | (Discord.Message | null)[]> { const chan = roomLookup.channel; const botUser = roomLookup.botUser; const embed = embedSet.messageEmbed; @@ -424,43 +494,13 @@ export class DiscordBot { this.channelLock.set(chan.id); if (!roomLookup.canSendEmbeds) { // NOTE: Don't send replies to discord if we are a puppet user. - let addText = ""; - if (embedSet.replyEmbed) { - for (const line of embedSet.replyEmbed.description!.split("\n")) { - addText += "\n> " + line; - } - } - msg = await chan.send(embed.description + addText, opts); + msg = await chan.send(this.prepareEmbedSetUserAccount(embedSet), opts); } else if (!botUser) { - if (embedSet.imageEmbed || embedSet.replyEmbed) { - let sendEmbed = new Discord.RichEmbed(); - if (embedSet.imageEmbed) { - if (!embedSet.replyEmbed) { - sendEmbed = embedSet.imageEmbed; - } else { - sendEmbed.setImage(embedSet.imageEmbed.image!.url); - } - } - if (embedSet.replyEmbed) { - if (!embedSet.imageEmbed) { - sendEmbed = embedSet.replyEmbed; - } else { - sendEmbed.addField("Replying to", embedSet.replyEmbed!.author!.name); - sendEmbed.addField("Reply text", embedSet.replyEmbed.description); - } - } - opts.embed = sendEmbed; - } + opts.embed = this.prepareEmbedSetBotAccount(embedSet); msg = await chan.send(embed.description, opts); } else if (hook) { MetricPeg.get.remoteCall("hook.send"); - const embeds: Discord.RichEmbed[] = []; - if (embedSet.imageEmbed) { - embeds.push(embedSet.imageEmbed); - } - if (embedSet.replyEmbed) { - embeds.push(embedSet.replyEmbed); - } + const embeds = this.prepareEmbedSetWebhook(embedSet); msg = await hook.send(embed.description, { avatarURL: embed!.author!.icon_url, embeds, @@ -468,27 +508,24 @@ export class DiscordBot { username: embed!.author!.name, } as Discord.WebhookMessageOptions); } else { - if (embedSet.imageEmbed) { - embed.setImage(embedSet.imageEmbed.image!.url); - } - if (embedSet.replyEmbed) { - embed.addField("Replying to", embedSet.replyEmbed!.author!.name); - embed.addField("Reply text", embedSet.replyEmbed.description); - } - opts.embed = embed; + opts.embed = this.prepareEmbedSetBot(embedSet); msg = await chan.send("", opts); } // Don't block on this. - this.StoreMessagesSent(msg, chan, event).then(() => { + const storePromise = this.StoreMessagesSent(msg, chan, event).then(() => { this.channelLock.release(chan.id); }).catch(() => { log.warn("Failed to store sent message for ", event.event_id); }); + if (awaitStore) { + await storePromise; + } } catch (err) { // throw wrapError(err, Unstable.ForeignNetworkError, "Couldn't send message"); log.warn(`Failed to send message ${event.event_id}`); log.verbose(err); } + return msg; } public async ProcessMatrixRedact(event: IMatrixEvent) { @@ -720,6 +757,63 @@ export class DiscordBot { return dbEmoji; } + private prepareEmbedSetUserAccount(embedSet: IMatrixEventProcessorResult): string { + const embed = embedSet.messageEmbed; + let addText = ""; + if (embedSet.replyEmbed) { + for (const line of embedSet.replyEmbed.description!.split("\n")) { + addText += "\n> " + line; + } + } + return embed.description += addText; + } + + private prepareEmbedSetBotAccount(embedSet: IMatrixEventProcessorResult): Discord.RichEmbed | undefined { + if (!embedSet.imageEmbed && !embedSet.replyEmbed) { + return undefined; + } + let sendEmbed = new Discord.RichEmbed(); + if (embedSet.imageEmbed) { + if (!embedSet.replyEmbed) { + sendEmbed = embedSet.imageEmbed; + } else { + sendEmbed.setImage(embedSet.imageEmbed.image!.url); + } + } + if (embedSet.replyEmbed) { + if (!embedSet.imageEmbed) { + sendEmbed = embedSet.replyEmbed; + } else { + sendEmbed.addField("Replying to", embedSet.replyEmbed!.author!.name); + sendEmbed.addField("Reply text", embedSet.replyEmbed.description); + } + } + return sendEmbed; + } + + private prepareEmbedSetWebhook(embedSet: IMatrixEventProcessorResult): Discord.RichEmbed[] { + const embeds: Discord.RichEmbed[] = []; + if (embedSet.imageEmbed) { + embeds.push(embedSet.imageEmbed); + } + if (embedSet.replyEmbed) { + embeds.push(embedSet.replyEmbed); + } + return embeds; + } + + private prepareEmbedSetBot(embedSet: IMatrixEventProcessorResult): Discord.RichEmbed { + const embed = embedSet.messageEmbed; + if (embedSet.imageEmbed) { + embed.setImage(embedSet.imageEmbed.image!.url); + } + if (embedSet.replyEmbed) { + embed.addField("Replying to", embedSet.replyEmbed!.author!.name); + embed.addField("Reply text", embedSet.replyEmbed.description); + } + return embed; + } + private async SendMatrixMessage(matrixMsg: IDiscordMessageParserResult, chan: Discord.Channel, guild: Discord.Guild, author: Discord.User, msgID: string): Promise<boolean> { @@ -759,7 +853,7 @@ export class DiscordBot { } } - private async OnMessage(msg: Discord.Message) { + private async OnMessage(msg: Discord.Message, editEventId: string = "") { const indexOfMsg = this.sentMessages.indexOf(msg.id); if (indexOfMsg !== -1) { log.verbose("Got repeated message, ignoring."); @@ -810,49 +904,55 @@ export class DiscordBot { try { const intent = this.GetIntentFromDiscordMember(msg.author, msg.webhookID); // Check Attachements - await Util.AsyncForEach(msg.attachments.array(), async (attachment) => { - const content = await Util.DownloadFile(attachment.url); - const fileMime = content.mimeType || mime.getType(attachment.filename) || "application/octet-stream"; - const mxcUrl = await intent.underlyingClient.uploadContent( - content.buffer, - fileMime, - attachment.filename, - ); - const type = fileMime.split("/")[0]; - let msgtype = { - audio: "m.audio", - image: "m.image", - video: "m.video", - }[type]; - if (!msgtype) { - msgtype = "m.file"; - } - const info = { - mimetype: fileMime, - size: attachment.filesize, - } as IMatrixMediaInfo; - if (msgtype === "m.image" || msgtype === "m.video") { - info.w = attachment.width; - info.h = attachment.height; - } - await Util.AsyncForEach(rooms, async (room) => { - const eventId = await intent.sendEvent(room, { - body: attachment.filename, - external_url: attachment.url, - info, - msgtype, - url: mxcUrl, + if (!editEventId) { + // on discord you can't edit in images, you can only edit text + // so it is safe to only check image upload stuff if we don't have + // an edit + await Util.AsyncForEach(msg.attachments.array(), async (attachment) => { + const content = await Util.DownloadFile(attachment.url); + const fileMime = content.mimeType || mime.getType(attachment.filename) + || "application/octet-stream"; + const mxcUrl = await intent.underlyingClient.uploadContent( + content.buffer, + fileMime, + attachment.filename, + ); + const type = fileMime.split("/")[0]; + let msgtype = { + audio: "m.audio", + image: "m.image", + video: "m.video", + }[type]; + if (!msgtype) { + msgtype = "m.file"; + } + const info = { + mimetype: fileMime, + size: attachment.filesize, + } as IMatrixMediaInfo; + if (msgtype === "m.image" || msgtype === "m.video") { + info.w = attachment.width; + info.h = attachment.height; + } + await Util.AsyncForEach(rooms, async (room) => { + const eventId = await intent.sendEvent(room, { + body: attachment.filename, + external_url: attachment.url, + info, + msgtype, + url: mxcUrl, + }); + this.lastEventIds[room] = eventId; + const evt = new DbEvent(); + evt.MatrixId = `${eventId};${room}`; + evt.DiscordId = msg.id; + evt.ChannelId = msg.channel.id; + evt.GuildId = msg.guild.id; + await this.store.Insert(evt); }); - this.lastEventIds[room] = eventId; - const evt = new DbEvent(); - evt.MatrixId = `${eventId};${room}`; - evt.DiscordId = msg.id; - evt.ChannelId = msg.channel.id; - evt.GuildId = msg.guild.id; - await this.store.Insert(evt); }); - }); - if (msg.content === null) { + } + if (!msg.content && msg.embeds.length === 0) { return; } const result = await this.discordMsgProcessor.FormatMessage(msg); @@ -860,12 +960,27 @@ export class DiscordBot { return; } await Util.AsyncForEach(rooms, async (room) => { - const trySend = async () => intent.sendEvent(room, { + const sendContent: IMatrixMessage = { body: result.body, format: "org.matrix.custom.html", formatted_body: result.formattedBody, msgtype: result.msgtype, - }); + }; + if (editEventId) { + sendContent.body = `* ${result.body}`; + sendContent.formatted_body = `* ${result.formattedBody}`; + sendContent["m.new_content"] = { + body: result.body, + format: "org.matrix.custom.html", + formatted_body: result.formattedBody, + msgtype: result.msgtype, + }; + sendContent["m.relates_to"] = { + event_id: editEventId, + rel_type: "m.replace", + }; + } + const trySend = async () => intent.sendEvent(room, sendContent); const afterSend = async (eventId) => { this.lastEventIds[room] = eventId; const evt = new DbEvent(); @@ -906,28 +1021,16 @@ export class DiscordBot { return; } log.info(`Got edit event for ${newMsg.id}`); - let link = ""; const storeEvent = await this.store.Get(DbEvent, {discord_id: oldMsg.id}); if (storeEvent && storeEvent.Result) { while (storeEvent.Next()) { const matrixIds = storeEvent.MatrixId.split(";"); - if (matrixIds[0] === this.lastEventIds[matrixIds[1]]) { - log.info("Immediate edit, deleting and re-sending"); - await this.DeleteDiscordMessage(oldMsg); - await this.OnMessage(newMsg); - return; - } - link = `https://matrix.to/#/${matrixIds[1]}/${matrixIds[0]}`; + await this.OnMessage(newMsg, matrixIds[0]); + return; } } - - // Create a new edit message using the old and new message contents - const editedMsg = await this.discordMsgProcessor.FormatEdit(oldMsg, newMsg, link); - - // Send the message to all bridged matrix rooms - if (!await this.SendMatrixMessage(editedMsg, newMsg.channel, newMsg.guild, newMsg.author, newMsg.id)) { - log.error("Unable to announce message edit for msg id:", newMsg.id); - } + newMsg.content = `Edit: ${newMsg.content}`; + await this.OnMessage(newMsg); } private async DeleteDiscordMessage(msg: Discord.Message) { @@ -969,7 +1072,7 @@ export class DiscordBot { } log.verbose("Sent ", m.id); this.sentMessages.push(m.id); - this.lastEventIds[event.room_id] = event.event_id; + this.lastEventIds[chan.id] = m.id; try { const evt = new DbEvent(); evt.MatrixId = `${event.event_id};${event.room_id}`; diff --git a/src/matrixeventprocessor.ts b/src/matrixeventprocessor.ts index ae6eed4b0cab6bc68c2fb152fc7cbd41dbacf1db..388fe1521f6a78fce94bfc3ed43d2b1b40e8c8f7 100644 --- a/src/matrixeventprocessor.ts +++ b/src/matrixeventprocessor.ts @@ -23,6 +23,8 @@ import * as mime from "mime"; import { IMatrixEvent, IMatrixEventContent, IMatrixMessage } from "./matrixtypes"; import { MatrixMessageProcessor, IMatrixMessageProcessorParams } from "./matrixmessageprocessor"; import { MatrixCommandHandler } from "./matrixcommandhandler"; +import { DbEvent } from "./db/dbdataevent"; + import { Log } from "./log"; import { IRoomStoreEntry, RemoteStoreRoom } from "./db/roomstore"; import { Appservice, MatrixClient } from "matrix-bot-sdk"; @@ -69,6 +71,7 @@ export class MatrixEventProcessor { constructor(opts: MatrixEventProcessorOpts, cm?: MatrixCommandHandler) { this.config = opts.config; this.bridge = opts.bridge; + this.store = opts.store; this.discord = opts.discord; this.store = opts.store; this.matrixMsgProcessor = new MatrixMessageProcessor(this.discord, this.config); @@ -186,6 +189,15 @@ export class MatrixEventProcessor { const roomLookup = await this.discord.LookupRoom(guildId, channelId, event.sender); const chan = roomLookup.channel; + let editEventId = ""; + if (event.content && event.content["m.relates_to"] && event.content["m.relates_to"].rel_type === "m.replace") { + const editMatrixId = `${event.content["m.relates_to"].event_id};${event.room_id}`; + const storeEvent = await this.store.Get(DbEvent, {matrix_id: editMatrixId}); + if (storeEvent && storeEvent.Result && storeEvent.Next()) { + editEventId = storeEvent.DiscordId; + } + } + const embedSet = await this.EventToEmbed(event, chan); const opts: Discord.MessageOptions = {}; const file = await this.HandleAttachment(event, mxClient, roomLookup.canSendEmbeds); @@ -197,9 +209,13 @@ export class MatrixEventProcessor { embedSet.imageEmbed = file as Discord.RichEmbed; } - // Throws an `Unstable.ForeignNetworkError` when sending the message fails. - await this.discord.send(embedSet, opts, roomLookup, event); - + // Throws an `Unstable.ForeignNetworkError` when sending the message fails. + if (editEventId) { + await this.discord.edit(embedSet, opts, roomLookup, event, editEventId); + } else { + await this.discord.send(embedSet, opts, roomLookup, event); + } + // Don't await this. this.sendReadReceipt(event).catch((ex) => { log.verbose("Failed to send read reciept for ", event.event_id, ex); }); @@ -283,7 +299,8 @@ export class MatrixEventProcessor { let body: string = ""; if (event.type !== "m.sticker") { - body = await this.matrixMsgProcessor.FormatMessage(event.content as IMatrixMessage, channel.guild, params); + const content = event.content!["m.new_content"] ? event.content!["m.new_content"] : event.content; + body = await this.matrixMsgProcessor.FormatMessage(content as IMatrixMessage, channel.guild, params); } const messageEmbed = new Discord.RichEmbed(); diff --git a/src/matrixtypes.ts b/src/matrixtypes.ts index f08ae1301c571fdbc866e7aafff94eed21e8229f..439d73439f7f0de58dc3452f41edec098da3d171 100644 --- a/src/matrixtypes.ts +++ b/src/matrixtypes.ts @@ -52,6 +52,8 @@ export interface IMatrixMessage { msgtype: string; formatted_body?: string; format?: string; + "m.new_content"?: any; // tslint:disable-line no-any + "m.relates_to"?: any; // tslint:disable-line no-any } export interface IMatrixMediaInfo { diff --git a/test/mocks/appservicemock.ts b/test/mocks/appservicemock.ts index b832db55365c5c8b23e3ffa7ba3ae1cd38c14984..5aaf9ca2e873416efaaea1aea49e688d36a91dde 100644 --- a/test/mocks/appservicemock.ts +++ b/test/mocks/appservicemock.ts @@ -73,6 +73,10 @@ export class AppserviceMock extends AppserviceMockBase { constructor(private opts: IAppserviceMockOpts = {}) { super(); opts.roommembers = opts.roommembers || []; + this.cleanup(); + } + + public cleanup() { this.intents = {}; this.botIntent = new IntentMock(this.opts, "BOT"); this.botClient = this.botIntent.underlyingClient; @@ -163,8 +167,8 @@ class IntentMock extends AppserviceMockBase { this.funcCalled("sendText", roomId, body); } - public sendEvent(roomId: string, body: string) { - this.funcCalled("sendEvent", roomId, body); + public sendEvent(roomId: string, content: any) { + this.funcCalled("sendEvent", roomId, content); } public async ensureRegistered(): Promise<void> { diff --git a/test/test_discordbot.ts b/test/test_discordbot.ts index dd4a1a1250be7134911ecc3ba260efa7c10d41f6..f8e6e181660bf1e61a2364546582aecefb71b005 100644 --- a/test/test_discordbot.ts +++ b/test/test_discordbot.ts @@ -109,6 +109,7 @@ describe("DiscordBot", () => { let HANDLE_COMMAND = false; function getDiscordBot() { HANDLE_COMMAND = false; + mockBridge.cleanup(); const discord = new modDiscordBot.DiscordBot( config, mockBridge, @@ -160,6 +161,25 @@ describe("DiscordBot", () => { await discordBot.OnMessage(msg as any); mockBridge.getIntent(author.id).wasCalled("sendEvent"); }); + 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", { + "body": "* Foxies are super amazing!", + "format": "org.matrix.custom.html", + "formatted_body": "* Foxies are super amazing!", + "m.new_content": { + body: "Foxies are super amazing!", + format: "org.matrix.custom.html", + formatted_body: "Foxies are super amazing!", + msgtype: "m.text", + }, + "m.relates_to": { event_id: "editevent", rel_type: "m.replace" }, + "msgtype": "m.text", + }); + }); it("uploads images", async () => { discordBot = getDiscordBot(); msg.author = author; @@ -288,7 +308,7 @@ describe("DiscordBot", () => { await discordBot.OnMessageUpdate(oldMsg, newMsg); expect(checkMsgSent).to.be.false; }); - it("should send a matrix message on an edited discord message", async () => { + it("should send a matrix edit on an edited discord message", async () => { discordBot = new modDiscordBot.DiscordBot( config, mockBridge, @@ -308,14 +328,26 @@ describe("DiscordBot", () => { oldMsg.content = "a"; newMsg.content = "b"; - // Mock the SendMatrixMessage method to check if it is called - let checkMsgSent = false; - discordBot.SendMatrixMessage = (...args) => checkMsgSent = true; + let storeMockResults = 1; + discordBot.store = { + Get: (a, b) => { + return { + MatrixId: "editedid", + Next: () => storeMockResults--, + Result: true, + }; + }, + }; + + let checkEditEventSent = ""; + discordBot.OnMessage = (str, event) => { + checkEditEventSent = event; + }; await discordBot.OnMessageUpdate(oldMsg, newMsg); - expect(checkMsgSent).to.be.true; + expect(checkEditEventSent).to.equal("editedid"); }); - it("should delete and re-send if it is the newest message", async () => { + it("should send a new message if no store event found", async () => { discordBot = new modDiscordBot.DiscordBot( config, mockBridge, @@ -340,14 +372,24 @@ describe("DiscordBot", () => { oldMsg.content = "a"; newMsg.content = "b"; - let deletedMessage = false; - discordBot.DeleteDiscordMessage = async (_) => { deletedMessage = true; }; - let sentMessage = false; - discordBot.OnMessage = async (_) => { sentMessage = true; }; + let storeMockResults = 0; + discordBot.store = { + Get: (a, b) => { + return { + MatrixId: "editedid", + Next: () => storeMockResults--, + Result: true, + }; + }, + }; + + let checkEditEventSent = "wrong"; + discordBot.OnMessage = (str, event) => { + checkEditEventSent = event; + }; await discordBot.OnMessageUpdate(oldMsg, newMsg); - expect(deletedMessage).to.be.true; - expect(sentMessage).to.be.true; + expect(checkEditEventSent).to.be.undefined; }); }); describe("event:message", () => { diff --git a/test/test_matrixeventprocessor.ts b/test/test_matrixeventprocessor.ts index 3b3734bca21629e092569f3e4c9b4b82f8b8aaf3..b00248099467a3cb3b430ec02566cb5b5d9ef569 100644 --- a/test/test_matrixeventprocessor.ts +++ b/test/test_matrixeventprocessor.ts @@ -151,11 +151,15 @@ const profileFetcher = async (userId) => { let STATE_EVENT_MSG = ""; let MESSAGE_PROCCESS = ""; let KICKBAN_HANDLED = false; +let MESSAGE_SENT = false; +let MESSAGE_EDITED = false; -function createMatrixEventProcessor() { +function createMatrixEventProcessor(storeMockResults = 0) { STATE_EVENT_MSG = ""; MESSAGE_PROCCESS = ""; KICKBAN_HANDLED = false; + MESSAGE_SENT = false; + MESSAGE_EDITED = false; const bridge = new AppserviceMock({ botUserId: "@botuser:localhost", eventFetcher, @@ -170,6 +174,18 @@ function createMatrixEventProcessor() { }; const config = new DiscordBridgeConfig(); + const store = { + Get: (a, b) => { + return { + DiscordId: "123456", + MatrixId: "editedevent", + Next: () => storeMockResults--, + Result: true, + }; + }, + removeEntriesByMatrixRoomId: () => Promise.resolve(), + }; + const Util = Object.assign(require("../src/util").Util, { DownloadFile: (name: string) => { const size = parseInt(name.substring(name.lastIndexOf("/") + 1), undefined); @@ -187,10 +203,22 @@ function createMatrixEventProcessor() { HandleMatrixKickBan: () => { KICKBAN_HANDLED = true; }, + LookupRoom: async (guildId, chanId) => { + return { + botUser: true, + canSendEmbeds: true, + }; + }, ProcessMatrixRedact: async (evt) => { MESSAGE_PROCCESS = "redacted"; }, UserSyncroniser: us, + edit: async (embedSet, opts, roomLookup, event) => { + MESSAGE_EDITED = true; + }, + send: async (embedSet, opts, roomLookup, event) => { + MESSAGE_SENT = true; + }, sendAsBot: async (msg, channel, event) => { STATE_EVENT_MSG = msg; }, @@ -205,10 +233,6 @@ function createMatrixEventProcessor() { }, }); - const store = { - removeEntriesByMatrixRoomId: () => Promise.resolve(), - }; - const processor = new (Proxyquire("../src/matrixeventprocessor", { "./util": { Util, @@ -226,6 +250,66 @@ const mockChannel = new MockChannel(); mockChannel.members.set("12345", new MockMember("12345", "testuser2")); describe("MatrixEventProcessor", () => { + describe("ProcessMsgEvent", () => { + it("Should send messages", async () => { + const { processor } = createMatrixEventProcessor(); + const event = { + content: { + body: "blah", + msgtype: "m.text", + }, + room_id: "!someroom:localhost", + sender: "@user:localhost", + type: "m.room.message", + } as any; + processor.HandleAttachment = async () => ""; + processor.EventToEmbed = async (evt, chan) => { + return { + messageEmbed: new Discord.RichEmbed(), + }; + }; + const room = { data: { + discord_channel: "1234", + discord_guild: "1234", + }} as any; + await processor.ProcessMsgEvent(event, room); + expect(MESSAGE_SENT).to.be.true; + expect(MESSAGE_EDITED).to.be.false; + }); + it("Should eventually send edits", async () => { + const { processor } = createMatrixEventProcessor(1); + const event = { + content: { + "body": "* blah", + "m.new_content": { + body: "blah", + msgtype: "m.text", + }, + "m.relates_to": { + event_id: "editedevent", + rel_type: "m.replace", + }, + "msgtype": "m.text", + }, + room_id: "!someroom:localhost", + sender: "@user:localhost", + type: "m.room.message", + } as any; + processor.HandleAttachment = async () => ""; + processor.EventToEmbed = async (evt, chan) => { + return { + messageEmbed: new Discord.RichEmbed(), + }; + }; + const room = { data: { + discord_channel: "1234", + discord_guild: "1234", + }} as any; + await processor.ProcessMsgEvent(event, room); + expect(MESSAGE_SENT).to.be.false; + expect(MESSAGE_EDITED).to.be.true; + }); + }); describe("ProcessStateEvent", () => { it("Should ignore unhandled states", async () => { const {processor} = createMatrixEventProcessor();