diff --git a/config/config.sample.yaml b/config/config.sample.yaml index 925df2bbadf7195dd2049b2c01c901c61620e0fa..146668def098cd7a639b253fb4cf49030c6ea3b3 100644 --- a/config/config.sample.yaml +++ b/config/config.sample.yaml @@ -19,20 +19,11 @@ bridge: disablePresence: false # Disable sending typing notifications when somebody on Discord types. disableTypingNotifications: false - # Disable parsing discord usernames out of matrix messages so - # that it highlights discord users. - # WARNING: Not always 100% accurate, but close enough usually. - disableDiscordMentions: false # Disable deleting messages on Discord if a message is redacted on Matrix. disableDeletionForwarding: false # Enable users to bridge rooms using !discord commands. See # https://t2bot.io/discord for instructions. enableSelfServiceBridging: false - # For both below, a space is inserted after @ to stop the mentions working. - # Disable relaying @everyone to Discord. Non-puppeted users can abuse this. - disableEveryoneMention: false - # Disable relaying @here to Discord. Non-puppeted users can abuse this. - disableHereMention: false # Authentication configuration for the discord bot. auth: clientID: "12345" diff --git a/config/config.schema.yaml b/config/config.schema.yaml index 4f832fa6ce069b1a2560c318a806e09c9127b00a..17b816ca6060f16f7bd0cd4e6872ce2c9d102a08 100644 --- a/config/config.schema.yaml +++ b/config/config.schema.yaml @@ -16,16 +16,10 @@ properties: type: "boolean" disableTypingNotifications: type: "boolean" - disableDiscordMentions: - type: "boolean" disableDeletionForwarding: type: "boolean" enableSelfServiceBridging: type: "boolean" - disableEveryoneMention: - type: "boolean" - disableHereMention: - type: "boolean" auth: type: "object" required: ["botToken", "clientID"] diff --git a/src/matrixeventprocessor.ts b/src/matrixeventprocessor.ts index d0128e257e0274c1c99a85dacb20021a1a722ad3..2ad0107368de6ebf72a54f592d7917a9a968eca2 100644 --- a/src/matrixeventprocessor.ts +++ b/src/matrixeventprocessor.ts @@ -8,7 +8,7 @@ import * as mime from "mime"; import { MatrixUser, Bridge } from "matrix-appservice-bridge"; import { Client as MatrixClient } from "matrix-js-sdk"; import { IMatrixEvent, IMatrixEventContent, IMatrixMessage } from "./matrixtypes"; -import { MatrixMessageProcessor, MatrixMessageProcessorOpts } from "./matrixmessageprocessor"; +import { MatrixMessageProcessor, IMatrixMessageProcessorParams } from "./matrixmessageprocessor"; import { Log } from "./log"; const log = new Log("MatrixEventProcessor"); @@ -44,13 +44,7 @@ export class MatrixEventProcessor { this.config = opts.config; this.bridge = opts.bridge; this.discord = opts.discord; - this.matrixMsgProcessor = new MatrixMessageProcessor( - this.discord, - new MatrixMessageProcessorOpts( - this.config.bridge.disableEveryoneMention, - this.config.bridge.disableHereMention, - ), - ); + this.matrixMsgProcessor = new MatrixMessageProcessor(this.discord); } public StateEventToMessage(event: IMatrixEvent, channel: Discord.TextChannel): string | undefined { @@ -108,9 +102,18 @@ export class MatrixEventProcessor { log.warn(`Trying to fetch member state or profile for ${event.sender} failed`, err); } + const params = { + mxClient, + roomId: event.room_id, + userId: event.sender, + } as IMatrixMessageProcessorParams; + if (profile) { + params.displayname = profile.displayname; + } + let body: string = ""; if (event.type !== "m.sticker") { - body = await this.matrixMsgProcessor.FormatMessage(event.content as IMatrixMessage, channel.guild, profile); + body = await this.matrixMsgProcessor.FormatMessage(event.content as IMatrixMessage, channel.guild, params); } const messageEmbed = new Discord.RichEmbed(); diff --git a/src/matrixmessageprocessor.ts b/src/matrixmessageprocessor.ts index 190a372a9651adcf3c7a81655940b850c3111a30..27dee33618da60db6507c1c0897888b2b22694a7 100644 --- a/src/matrixmessageprocessor.ts +++ b/src/matrixmessageprocessor.ts @@ -3,27 +3,33 @@ import { IMatrixMessage, IMatrixEvent } from "./matrixtypes"; import * as Parser from "node-html-parser"; import { Util } from "./util"; import { DiscordBot } from "./bot"; +import { Client as MatrixClient } from "matrix-js-sdk"; const MIN_NAME_LENGTH = 2; const MAX_NAME_LENGTH = 32; const MATRIX_TO_LINK = "https://matrix.to/#/"; -export class MatrixMessageProcessorOpts { - constructor(readonly disableEveryone: boolean = true, readonly disableHere: boolean = true) { } +export interface IMatrixMessageProcessorParams { + displayname?: string; + mxClient?: MatrixClient; + roomId?: string; + userId?: string; } export class MatrixMessageProcessor { private guild: Discord.Guild; private listDepth: number = 0; private listBulletPoints: string[] = ["●", "○", "■", "‣"]; - constructor(public bot: DiscordBot, public opts: MatrixMessageProcessorOpts) { } + private params?: IMatrixMessageProcessorParams; + constructor(public bot: DiscordBot) { } public async FormatMessage( msg: IMatrixMessage, guild: Discord.Guild, - profile?: IMatrixEvent | null, + params?: IMatrixMessageProcessorParams, ): Promise<string> { this.guild = guild; this.listDepth = 0; + this.params = params; let reply = ""; if (msg.formatted_body) { // parser needs everything wrapped in html elements @@ -37,15 +43,15 @@ export class MatrixMessageProcessor { reply = await this.walkNode(parsed); reply = reply.replace(/\s*$/, ""); // trim off whitespace at end } else { - reply = this.escapeDiscord(msg.body); + reply = await this.escapeDiscord(msg.body); } if (msg.msgtype === "m.emote") { - if (profile && - profile.displayname && - profile.displayname.length >= MIN_NAME_LENGTH && - profile.displayname.length <= MAX_NAME_LENGTH) { - reply = `_${profile.displayname} ${reply}_`; + if (params && + params.displayname && + params.displayname.length >= MIN_NAME_LENGTH && + params.displayname.length <= MAX_NAME_LENGTH) { + reply = `_${params.displayname} ${reply}_`; } else { reply = `_${reply}_`; } @@ -53,14 +59,24 @@ export class MatrixMessageProcessor { return reply; } - private escapeDiscord(msg: string): string { - if (this.opts.disableEveryone) { - msg = msg.replace(/@everyone/g, "@ everyone"); - } - if (this.opts.disableHere) { - msg = msg.replace(/@here/g, "@ here"); + private async escapeDiscord(msg: string): Promise<string> { + // \u200B is the zero-width space --> they still look the same but don't mention + msg = msg.replace(/@everyone/g, "@\u200Beveryone"); + msg = msg.replace(/@here/g, "@\u200Bhere"); + + if (msg.includes("@room") && this.params && this.params.mxClient && this.params.roomId && this.params.userId) { + // let's check for more complex logic if @room should be replaced + const res: IMatrixEvent = await this.params.mxClient.getStateEvent(this.params.roomId, "m.room.power_levels"); + if ( + res && res.users + && res.users[this.params.userId] !== undefined + && res.notifications + && res.notifications.room !== undefined + && res.users[this.params.userId] >= res.notifications.room + ) { + msg = msg.replace(/@room/g, "@here"); + } } - msg = msg.replace(/@room/g, "@here"); const escapeChars = ["\\", "*", "_", "~", "`"]; msg = msg.split(" ").map((s) => { if (s.match(/^https?:\/\//)) { @@ -173,7 +189,7 @@ export class MatrixMessageProcessor { } if (!emoji) { - return this.escapeDiscord(name); + return await this.escapeDiscord(name); } return `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}>`; } @@ -253,7 +269,7 @@ export class MatrixMessageProcessor { if ((node as Parser.TextNode).text === "\n") { return ""; } - return this.escapeDiscord((node as Parser.TextNode).text); + return await this.escapeDiscord((node as Parser.TextNode).text); } else if (node.nodeType === Parser.NodeType.ELEMENT_NODE) { const nodeHtml = node as Parser.HTMLElement; switch (nodeHtml.tagName) { diff --git a/src/matrixtypes.ts b/src/matrixtypes.ts index 22a876945404b4499cbae7b7c78a3eba65e85760..9e72460f93ae884d79ca4f4e37fd196813939661 100644 --- a/src/matrixtypes.ts +++ b/src/matrixtypes.ts @@ -23,6 +23,8 @@ export interface IMatrixEvent { content?: IMatrixEventContent; unsigned?: any; // tslint:disable-line no-any origin_server_ts?: number; + users?: any; // tslint:disable-line no-any + notifications?: any; // tslint:disable-line no-any } export interface IMatrixMessage { diff --git a/test/test_matrixeventprocessor.ts b/test/test_matrixeventprocessor.ts index 2f2a8bff85ddb91f42083bb76372c4825420e3bc..3d61f0ee22444532ac03cb2aaa214b07d313a770 100644 --- a/test/test_matrixeventprocessor.ts +++ b/test/test_matrixeventprocessor.ts @@ -59,11 +59,7 @@ const mxClient = { }, }; -function createMatrixEventProcessor( - disableMentions: boolean = false, - disableEveryone = false, - disableHere = false, -): MatrixEventProcessor { +function createMatrixEventProcessor(): MatrixEventProcessor { const bridge = { getBot: () => { return { @@ -134,9 +130,6 @@ function createMatrixEventProcessor( }, }; const config = new DiscordBridgeConfig(); - config.bridge.disableDiscordMentions = disableMentions; - config.bridge.disableEveryoneMention = disableEveryone; - config.bridge.disableHereMention = disableHere; const Util = Object.assign(require("../src/util").Util, { DownloadFile: (name: string) => { @@ -375,26 +368,26 @@ describe("MatrixEventProcessor", () => { Chai.assert.equal(author!.url, "https://matrix.to/#/@test:localhost"); }); - it("Should remove everyone mentions if configured.", async () => { - const processor = createMatrixEventProcessor(false, true); + it("Should remove everyone mentions.", async () => { + const processor = createMatrixEventProcessor(); const embeds = await processor.EventToEmbed({ content: { body: "@everyone Hello!", }, sender: "@test:localhost", } as IMatrixEvent, mockChannel as any); - Chai.assert.equal(embeds.messageEmbed.description, "@ everyone Hello!"); + Chai.assert.equal(embeds.messageEmbed.description, "@\u200Beveryone Hello!"); }); - it("Should remove here mentions if configured.", async () => { - const processor = createMatrixEventProcessor(false, false, true); + it("Should remove here mentions.", async () => { + const processor = createMatrixEventProcessor(); const embeds = await processor.EventToEmbed({ content: { body: "@here Hello!", }, sender: "@test:localhost", } as IMatrixEvent, mockChannel as any); - Chai.assert.equal(embeds.messageEmbed.description, "@ here Hello!"); + Chai.assert.equal(embeds.messageEmbed.description, "@\u200Bhere Hello!"); }); it("Should replace /me with * displayname, and italicize message", async () => { diff --git a/test/test_matrixmessageprocessor.ts b/test/test_matrixmessageprocessor.ts index 46667dc426550fefb911e0ee9bd0926fbc05959a..90587095f58d03ed1b58e0c4b44149524865f0e4 100644 --- a/test/test_matrixmessageprocessor.ts +++ b/test/test_matrixmessageprocessor.ts @@ -6,14 +6,30 @@ import { MockChannel } from "./mocks/channel"; import { MockEmoji } from "./mocks/emoji"; import { DiscordBot } from "../src/bot"; import { DbEmoji } from "../src/db/dbdataemoji"; -import { MatrixMessageProcessor, MatrixMessageProcessorOpts } from "../src/matrixmessageprocessor"; +import { MatrixMessageProcessor } from "../src/matrixmessageprocessor"; // we are a test file and thus need those /* tslint:disable:no-unused-expression max-file-line-count no-any */ const expect = Chai.expect; -const opts = new MatrixMessageProcessorOpts(); +const mxClient = { + getStateEvent: async (roomId, stateType, _) => { + if (stateType === "m.room.power_levels") { + return { + notifications: { + room: 50, + }, + users: { + "@nopower:localhost": 0, + "@power:localhost": 100, + }, + }; + } + return null; + } +}; + const bot = { GetEmojiByMxc: async (mxc: string): Promise<DbEmoji> => { if (mxc === "mxc://real_emote:localhost") { @@ -46,100 +62,86 @@ function getHtmlMessage(msg: string, msgtype: string = "m.text") { describe("MatrixMessageProcessor", () => { describe("FormatMessage / body / simple", () => { it("leaves blank stuff untouched", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getPlainMessage("hello world!"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("hello world!"); }); it("escapes simple stuff", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getPlainMessage("hello *world* how __are__ you?"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("hello \\*world\\* how \\_\\_are\\_\\_ you?"); }); it("escapes more complex stuff", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getPlainMessage("wow \\*this\\* is cool"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("wow \\\\\\*this\\\\\\* is cool"); }); - it("Converts @room to @here", async () => { - const mp = new MatrixMessageProcessor(bot, opts); - const guild = new MockGuild("1234"); - const msg = getPlainMessage("hey @room"); - const result = await mp.FormatMessage(msg, guild as any); - expect(result).is.equal("hey @here"); - }); - it("doesn't discord-escape links", async () => { - const mp = new MatrixMessageProcessor(bot, opts); - const guild = new MockGuild("1234"); - const msg = getPlainMessage("http://example.com/_test_/"); - const result = await mp.FormatMessage(msg, guild as any); - expect(result).is.equal("http://example.com/_test_/"); - }); }); describe("FormatMessage / formatted_body / simple", () => { it("leaves blank stuff untouched", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("hello world!"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("hello world!"); }); it("un-escapes simple stuff", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("foxes & foxes"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("foxes & foxes"); }); it("converts italic formatting", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("this text is <em>italic</em> and so is <i>this one</i>"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("this text is *italic* and so is *this one*"); }); it("converts bold formatting", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("wow some <b>bold</b> and <strong>more</strong> boldness!"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("wow some **bold** and **more** boldness!"); }); it("converts underline formatting", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("to be <u>underlined</u> or not to be?"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("to be __underlined__ or not to be?"); }); it("converts strike formatting", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("does <del>this text</del> exist?"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("does ~~this text~~ exist?"); }); it("converts code", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("WOW this is <code>some awesome</code> code"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("WOW this is `some awesome` code"); }); it("converts multiline-code", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<p>here</p><pre><code>is\ncode\n</code></pre><p>yay</p>"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("here```\nis\ncode\n```yay"); }); it("converts multiline language code", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage(`<p>here</p> <pre><code class="language-js">is @@ -150,14 +152,14 @@ code expect(result).is.equal("here```js\nis\ncode\n```yay"); }); it("handles linebreaks", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("line<br>break"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("line\nbreak"); }); it("handles <hr>", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("test<hr>foxes"); const result = await mp.FormatMessage(msg, guild as any); @@ -166,42 +168,42 @@ code }); describe("FormatMessage / formatted_body / complex", () => { it("html unescapes stuff inside of code", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<code>is <em>italic</em>?</code>"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("`is <em>italic</em>?`"); }); it("html unescapes inside of pre", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<pre><code>wow &</code></pre>"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("```\nwow &```"); }); it("doesn't parse inside of code", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<code>*yay*</code>"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("`*yay*`"); }); it("doesn't parse inside of pre", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<pre><code>*yay*</code></pre>"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("```\n*yay*```"); }); it("parses new lines", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<em>test</em><br><strong>ing</strong>"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("*test*\n**ing**"); }); it("drops mx-reply", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<mx-reply><blockquote>message</blockquote></mx-reply>test reply"); const result = await mp.FormatMessage(msg, guild as any); @@ -238,7 +240,7 @@ code }); describe("FormatMessage / formatted_body / discord", () => { it("Parses user pills", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const member = new MockMember("12345", "TestUsername", guild); guild.members.set("12345", member); @@ -247,7 +249,7 @@ code expect(result).is.equal("<@12345>"); }); it("Ignores invalid user pills", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const member = new MockMember("12345", "TestUsername", guild); guild.members.set("12345", member); @@ -256,7 +258,7 @@ code expect(result).is.equal("[TestUsername](https://matrix.to/#/@_discord_789:localhost)"); }); it("Parses channel pills", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const channel = new MockChannel("12345", guild, "text", "SomeChannel"); guild.channels.set("12345", channel as any); @@ -266,7 +268,7 @@ code expect(result).is.equal("<#12345>"); }); it("Handles invalid channel pills", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const channel = new MockChannel("12345", guild, "text", "SomeChannel"); guild.channels.set("12345", channel as any); @@ -275,37 +277,30 @@ code expect(result).is.equal("https://matrix.to/#/#_discord_1234_789:localhost"); }); it("Handles external channel pills", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<a href=\"https://matrix.to/#/#matrix:matrix.org\">#SomeChannel</a>"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("https://matrix.to/#/#matrix:matrix.org"); }); it("Ignores links without href", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<a><em>yay?</em></a>"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("*yay?*"); }); it("Ignores links with non-matrix href", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<a href=\"http://example.com\"><em>yay?</em></a>"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("[*yay?*](http://example.com)"); }); - it("Converts @room to @here", async () => { - const mp = new MatrixMessageProcessor(bot, opts); - const guild = new MockGuild("1234"); - const msg = getHtmlMessage("hey @room"); - const result = await mp.FormatMessage(msg, guild as any); - expect(result).is.equal("hey @here"); - }); }); describe("FormatMessage / formatted_body / emoji", () => { it("Inserts emoji by name", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const emoji = new MockEmoji("123456", "test_emoji"); guild.emojis.set("123456", emoji); @@ -314,7 +309,7 @@ code expect(result).is.equal("<:test_emoji:123456>"); }); it("Inserts emojis by mxc url", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const emoji = new MockEmoji("123456", "test_emoji"); guild.emojis.set("123456", emoji); @@ -323,7 +318,7 @@ code expect(result).is.equal("<:test_emoji:123456>"); }); it("ignores unknown mxc urls", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const emoji = new MockEmoji("123456", "test_emoji"); guild.emojis.set("123456", emoji); @@ -332,7 +327,7 @@ code expect(result).is.equal("yay"); }); it("ignores with no alt / title, too", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const emoji = new MockEmoji("123456", "test_emoji"); guild.emojis.set("123456", emoji); @@ -341,30 +336,100 @@ code expect(result).is.equal(""); }); }); + describe("FormatMessage / formatted_body / matrix", () => { + it("escapes @everyone", async () => { + const mp = new MatrixMessageProcessor(bot); + const guild = new MockGuild("1234"); + const msg = getPlainMessage("hey @everyone"); + const result = await mp.FormatMessage(msg, guild as any); + expect(result).is.equal("hey @\u200Beveryone"); + }); + it("escapes @here", async () => { + const mp = new MatrixMessageProcessor(bot); + const guild = new MockGuild("1234"); + const msg = getPlainMessage("hey @here"); + const result = await mp.FormatMessage(msg, guild as any); + expect(result).is.equal("hey @\u200Bhere"); + }); + it("converts @room to @here, if sufficient power", async () => { + const mp = new MatrixMessageProcessor(bot); + const guild = new MockGuild("1234"); + const msg = getPlainMessage("hey @room"); + const params = { + mxClient, + roomId: "!123456:localhost", + userId: "@power:localhost", + }; + const result = await mp.FormatMessage(msg, guild as any, params as any); + expect(result).is.equal("hey @here"); + }); + it("ignores @room to @here conversion, if insufficient power", async () => { + const mp = new MatrixMessageProcessor(bot); + const guild = new MockGuild("1234"); + const msg = getPlainMessage("hey @room"); + const params = { + mxClient, + roomId: "!123456:localhost", + userId: "@nopower:localhost", + }; + const result = await mp.FormatMessage(msg, guild as any, params as any); + expect(result).is.equal("hey @room"); + }); + it("handles /me for normal names", async () => { + const mp = new MatrixMessageProcessor(bot); + const guild = new MockGuild("1234"); + const msg = getPlainMessage("floofs", "m.emote"); + const params = { + displayname: "fox", + }; + const result = await mp.FormatMessage(msg, guild as any, params as any); + expect(result).is.equal("_fox floofs_"); + }); + it("handles /me for short names", async () => { + const mp = new MatrixMessageProcessor(bot); + const guild = new MockGuild("1234"); + const msg = getPlainMessage("floofs", "m.emote"); + const params = { + displayname: "f", + }; + const result = await mp.FormatMessage(msg, guild as any, params as any); + expect(result).is.equal("_floofs_"); + }); + it("handles /me for long names", async () => { + const mp = new MatrixMessageProcessor(bot); + const guild = new MockGuild("1234"); + const msg = getPlainMessage("floofs", "m.emote"); + const params = { + displayname: "foxfoxfoxfoxfoxfoxfoxfoxfoxfoxfoxfox", + }; + const result = await mp.FormatMessage(msg, guild as any, params as any); + expect(result).is.equal("_floofs_"); + }); + }); describe("FormatMessage / formatted_body / blockquotes", () => { it("parses single blockquotes", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<blockquote>hey</blockquote>there"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("> hey\n\nthere"); }); it("parses double blockquotes", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<blockquote><blockquote>hey</blockquote>you</blockquote>there"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("> > hey\n> \n> you\n\nthere"); }); it("parses blockquotes with <p>", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage("<blockquote>\n<p>spoky</p>\n</blockquote>\n<p>test</p>\n"); const result = await mp.FormatMessage(msg, guild as any); expect(result).is.equal("> spoky\n\ntest"); }); it("parses double blockquotes with <p>", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage(`<blockquote> <blockquote> @@ -380,7 +445,7 @@ code }); describe("FormatMessage / formatted_body / lists", () => { it("parses simple unordered lists", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage(`<p>soru</p> <ul> @@ -393,7 +458,7 @@ code expect(result).is.equal("soru\n● test\n● ing\n\nmore"); }); it("parses nested unordered lists", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage(`<p>foxes</p> <ul> @@ -411,7 +476,7 @@ code expect(result).is.equal("foxes\n● awesome\n● floofy\n ○ fur\n ○ tail\n\nyay!"); }); it("parses more nested unordered lists", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage(`<p>foxes</p> <ul> @@ -431,7 +496,7 @@ code }); }); it("parses simple ordered lists", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage(`<p>oookay</p> <ol> @@ -444,7 +509,7 @@ code expect(result).is.equal("oookay\n1. test\n2. test more\n\nok?"); }); it("parses nested ordered lists", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage(`<p>and now</p> <ol> @@ -463,7 +528,7 @@ code expect(result).is.equal("and now\n1. test\n2. test more\n 1. and more\n 2. more?\n3. done!\n\nok?"); }); it("parses ordered lists with different start", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage(`<ol start="5"> <li>test</li> @@ -473,7 +538,7 @@ code expect(result).is.equal("\n5. test\n6. test more"); }); it("parses ul in ol", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage(`<ol> <li>test</li> @@ -488,7 +553,7 @@ code expect(result).is.equal("\n1. test\n2. test more\n ○ asdf\n ○ jklö"); }); it("parses ol in ul", async () => { - const mp = new MatrixMessageProcessor(bot, opts); + const mp = new MatrixMessageProcessor(bot); const guild = new MockGuild("1234"); const msg = getHtmlMessage(`<ul> <li>test</li>