From 9758928bd78940f5b9869325943ce520c5b1ab49 Mon Sep 17 00:00:00 2001
From: Will Hunt <will@half-shot.uk>
Date: Sat, 28 Apr 2018 17:31:04 +0100
Subject: [PATCH] Supress @here and @everyone based on config Move Mention
 parser to MatrixEventProcessor

---
 src/config.ts                     |  12 +-
 src/matrixeventprocessor.ts       |  39 +++++-
 src/messageprocessor.ts           |  17 +--
 test/mocks/channel.ts             |   9 ++
 test/mocks/guild.ts               |   3 +-
 test/mocks/member.ts              |   2 +
 test/test_matrixeventprocessor.ts | 189 ++++++++++++++++++++++++++----
 test/test_messageprocessor.ts     |  76 ------------
 8 files changed, 220 insertions(+), 127 deletions(-)
 create mode 100644 test/mocks/channel.ts

diff --git a/src/config.ts b/src/config.ts
index 12b8e73..b836c2b 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,11 +1,11 @@
 /** Type annotations for config/config.schema.yaml */
 
 export class DiscordBridgeConfig {
-  public bridge: DiscordBridgeConfigBridge;
-  public auth: DiscordBridgeConfigAuth;
-  public logging: DiscordBridgeConfigLogging;
-  public database: DiscordBridgeConfigDatabase;
-  public room: DiscordBridgeConfigRoom;
+  public bridge: DiscordBridgeConfigBridge = new DiscordBridgeConfigBridge();
+  public auth: DiscordBridgeConfigAuth = new DiscordBridgeConfigAuth();
+  public logging: DiscordBridgeConfigLogging = new DiscordBridgeConfigLogging();
+  public database: DiscordBridgeConfigDatabase = new DiscordBridgeConfigDatabase();
+  public room: DiscordBridgeConfigRoom = new DiscordBridgeConfigRoom();
 }
 
 class DiscordBridgeConfigBridge {
@@ -17,6 +17,8 @@ class DiscordBridgeConfigBridge {
   public disableDiscordMentions: boolean;
   public disableDeletionForwarding: boolean;
   public enableSelfServiceBridging: boolean;
+  public disableEveryoneMention: boolean = false;
+  public disableHereMention: boolean = false;
 }
 
 class DiscordBridgeConfigDatabase {
diff --git a/src/matrixeventprocessor.ts b/src/matrixeventprocessor.ts
index e07f922..f63b76b 100644
--- a/src/matrixeventprocessor.ts
+++ b/src/matrixeventprocessor.ts
@@ -2,12 +2,13 @@ import * as Discord from "discord.js";
 import {MessageProcessorOpts, MessageProcessor} from "./messageprocessor";
 import {DiscordBot} from "./bot";
 import {DiscordBridgeConfig} from "./config";
+import * as escapeStringRegexp from "escape-string-regexp";
 
 export class MatrixEventProcessorOpts {
     constructor(
         readonly config: DiscordBridgeConfig,
         readonly bridge: any,
-        readonly msgProcessor: MessageProcessor) {
+        ) {
 
     }
 }
@@ -15,20 +16,29 @@ export class MatrixEventProcessorOpts {
 export class MatrixEventProcessor {
     private config: DiscordBridgeConfig;
     private bridge: any;
-    private msgProcessor: MessageProcessor;
 
     constructor (opts: MatrixEventProcessorOpts) {
         this.config = opts.config;
-        this.msgProcessor = opts.msgProcessor;
         this.bridge = opts.bridge;
     }
 
-    public EventToEmbed(event: any, profile: any, channel: Discord.TextChannel): Discord.RichEmbed {
-        const body = this.config.bridge.disableDiscordMentions ? event.content.body :
-            this.msgProcessor.FindMentionsInPlainBody(
+    public EventToEmbed(event: any, profile: any|null, channel: Discord.TextChannel): Discord.RichEmbed {
+        let body = this.config.bridge.disableDiscordMentions ? event.content.body :
+            this.FindMentionsInPlainBody(
                 event.content.body,
                 channel.members.array(),
             );
+
+        // Replace @everyone
+        if (this.config.bridge.disableEveryoneMention) {
+            body = body.replace(new RegExp(`@everyone`, "g"), "@ everyone");
+        }
+
+        // Replace @here
+        if (this.config.bridge.disableHereMention) {
+            body = body.replace(new RegExp(`@here`, "g"), "@ here");
+        }
+
         if (profile) {
             profile.displayname = profile.displayname || event.sender;
             if (profile.avatar_url) {
@@ -54,7 +64,24 @@ export class MatrixEventProcessor {
             });
         }
         return new Discord.RichEmbed({
+            author: {
+                name: event.sender,
+                url: `https://matrix.to/#/${event.sender}`,
+            },
             description: body,
         });
     }
+
+    public FindMentionsInPlainBody(body: string, members: Discord.GuildMember[]): string {
+        for (const member of members) {
+            const matcher = escapeStringRegexp(member.user.username + "#" + member.user.discriminator) + "|" +
+                escapeStringRegexp(member.displayName);
+            body = body.replace(
+                new RegExp(
+                    `\\b(${matcher})(?=\\b)`
+                    , "mig"), `<@!${member.id}>`,
+            );
+        }
+        return body;
+    }
 }
diff --git a/src/messageprocessor.ts b/src/messageprocessor.ts
index 7be2a99..cac98a0 100644
--- a/src/messageprocessor.ts
+++ b/src/messageprocessor.ts
@@ -2,7 +2,6 @@ import * as Discord from "discord.js";
 import * as marked from "marked";
 import * as log from "npmlog";
 import { DiscordBot } from "./bot";
-import * as escapeStringRegexp from "escape-string-regexp";
 import * as escapeHtml from "escape-html";
 
 const USER_REGEX = /<@!?([0-9]*)>/g;
@@ -39,8 +38,9 @@ export class MessageProcessor {
         // Backwards compat
         if (bot != null) {
             this.opts = new MessageProcessorOpts(opts.domain, bot);
+        } else {
+            this.opts = opts;
         }
-        this.opts = opts;
     }
 
     public async FormatDiscordMessage(msg: Discord.Message): Promise<MessageProcessorMatrixResult> {
@@ -178,17 +178,4 @@ export class MessageProcessor {
         }
         return content;
     }
-
-    public FindMentionsInPlainBody(body: string, members: Discord.GuildMember[]): string {
-      for (const member of members) {
-        const matcher = escapeStringRegexp(member.user.username + "#" + member.user.discriminator) + "|" +
-                        escapeStringRegexp(member.displayName);
-        body = body.replace(
-            new RegExp(
-                `\\b(${matcher})(?=\\b)`
-                , "mig"), `<@!${member.id}>`,
-        );
-      }
-      return body;
-    }
 }
diff --git a/test/mocks/channel.ts b/test/mocks/channel.ts
new file mode 100644
index 0000000..c347cd5
--- /dev/null
+++ b/test/mocks/channel.ts
@@ -0,0 +1,9 @@
+import {MockUser} from "./user";
+import * as Discord from "discord.js";
+import {MockMember} from "./member";
+import {MockCollection} from "./collection";
+
+//Mocking TextChannel
+export class MockChannel {
+    public members = new MockCollection<string, MockMember>();
+}
diff --git a/test/mocks/guild.ts b/test/mocks/guild.ts
index d79fd88..30c85a2 100644
--- a/test/mocks/guild.ts
+++ b/test/mocks/guild.ts
@@ -1,8 +1,9 @@
 import {MockCollection} from "./collection";
 import {MockMember} from "./member";
+import {Channel} from "discord.js";
 
 export class MockGuild {
-  public channels = new MockCollection<string, any>();
+  public channels = new MockCollection<string, Channel>();
   public members = new MockCollection<string, MockMember>();
   public id: string;
   constructor(id: string, channels: any[]) {
diff --git a/test/mocks/member.ts b/test/mocks/member.ts
index b614c96..60227bf 100644
--- a/test/mocks/member.ts
+++ b/test/mocks/member.ts
@@ -5,10 +5,12 @@ export class MockMember {
   public id = "";
   public presence: Discord.Presence;
   public user: MockUser;
+  public displayName: string;
   constructor(id: string, username: string) {
     this.id = id;
     this.presence = new Discord.Presence({});
     this.user = new MockUser(this.id, username);
+    this.displayName = username;
   }
 
   public MockSetPresence(presence: Discord.Presence) {
diff --git a/test/test_matrixeventprocessor.ts b/test/test_matrixeventprocessor.ts
index 15013ec..c63cc27 100644
--- a/test/test_matrixeventprocessor.ts
+++ b/test/test_matrixeventprocessor.ts
@@ -12,6 +12,7 @@ import { MockMember } from "./mocks/member";
 import {MatrixEventProcessor, MatrixEventProcessorOpts} from "../src/matrixeventprocessor";
 import {DiscordBridgeConfig} from "../src/config";
 import {MessageProcessor, MessageProcessorOpts} from "../src/messageprocessor";
+import {MockChannel} from "./mocks/channel";
 
 Chai.use(ChaiAsPromised);
 const expect = Chai.expect;
@@ -28,28 +29,37 @@ const bot = {
     },
 };
 
-function createMatrixEventProcessor(disableMentions: boolean = false): MatrixEventProcessor {
+function createMatrixEventProcessor(disableMentions: boolean = false, disableEveryone = false, disableHere = false): MatrixEventProcessor {
     const bot = {
 
-    }
+    };
     const bridge = {
-
+        getClientFactory: () => {
+            return {
+                getClientAs: () => {
+                    return {
+                        mxcUrlToHttp: () => {
+                            return "https://madeup.com";
+                        },
+                    };
+                },
+            };
+        },
     }
     const config = new DiscordBridgeConfig();
     config.bridge.disableDiscordMentions = disableMentions;
-    const messageProcessor = new MessageProcessor(new MessageProcessorOpts(
-        "localhost",
-        <DiscordBot> bot,
-    ));
+    config.bridge.disableEveryoneMention = disableEveryone;
+    config.bridge.disableHereMention = disableHere;
     return new MatrixEventProcessor(
         new MatrixEventProcessorOpts(
             config,
             bridge,
-            messageProcessor,
     ));
 }
+const mockChannel = new MockChannel();
+mockChannel.members.set("12345", new MockMember("12345", "testuser2"));
 
-describe("MatrixEventPrcoessor", () => {
+describe("MatrixEventProcessor", () => {
     describe("EventToEmbed", () => {
         it("Should contain a profile.", () => {
             const processor = createMatrixEventProcessor();
@@ -61,39 +71,170 @@ describe("MatrixEventPrcoessor", () => {
             }, {
                 displayname: "Test User",
                 avatar_url: "mxc://localhost/avatarurl",
-            });
-        });
-
-        it("Should not contain a profile if one does not exist.", () => {
-
+            }, mockChannel as any);
+            Chai.assert.equal(evt.author.name, "Test User");
+            Chai.assert.equal(evt.author.icon_url, "https://madeup.com");
+            Chai.assert.equal(evt.author.url, "https://matrix.to/#/@test:localhost");
         });
 
         it("Should should contain the users displayname if it exists.", () => {
-
+            const processor = createMatrixEventProcessor();
+            const evt = processor.EventToEmbed({
+                sender: "@test:localhost",
+                content: {
+                    body: "testcontent",
+                },
+            }, {
+                displayname: "Test User"}, mockChannel as any);
+            Chai.assert.equal(evt.author.name, "Test User");
+            Chai.assert.isUndefined(evt.author.icon_url);
+            Chai.assert.equal(evt.author.url, "https://matrix.to/#/@test:localhost");
         });
 
         it("Should should contain the users userid if the displayname is not set.", () => {
-
+            const processor = createMatrixEventProcessor();
+            const evt = processor.EventToEmbed({
+                sender: "@test:localhost",
+                content: {
+                    body: "testcontent",
+                },
+            }, null, mockChannel as any);
+            Chai.assert.equal(evt.author.name, "@test:localhost");
+            Chai.assert.isUndefined(evt.author.icon_url);
+            Chai.assert.equal(evt.author.url, "https://matrix.to/#/@test:localhost");
         });
 
         it("Should should contain the users avatar if it exists.", () => {
-
-        });
-
-        it("Should should contain the users userid if the avatar is not set.", () => {
-
+            const processor = createMatrixEventProcessor();
+            const evt = processor.EventToEmbed({
+                sender: "@test:localhost",
+                content: {
+                    body: "testcontent",
+                },
+            }, {avatar_url: "test"}, mockChannel as any);
+            Chai.assert.equal(evt.author.name, "@test:localhost");
+            Chai.assert.equal(evt.author.icon_url, "https://madeup.com");
+            Chai.assert.equal(evt.author.url, "https://matrix.to/#/@test:localhost");
         });
 
         it("Should enable mentions if configured.", () => {
-
+            const processor = createMatrixEventProcessor();
+            const evt = processor.EventToEmbed({
+                sender: "@test:localhost",
+                content: {
+                    body: "@testuser2 Hello!",
+                },
+            }, {avatar_url: "test"}, mockChannel as any);
+            Chai.assert.equal(evt.description, "@<@!12345> Hello!");
         });
 
         it("Should disable mentions if configured.", () => {
-
+            const processor = createMatrixEventProcessor(true);
+            const evt = processor.EventToEmbed({
+                sender: "@test:localhost",
+                content: {
+                    body: "@testuser2 Hello!",
+                },
+            }, {avatar_url: "test"}, mockChannel as any);
+            Chai.assert.equal(evt.description, "@testuser2 Hello!");
         });
 
         it("Should remove everyone mentions if configured.", () => {
+            const processor = createMatrixEventProcessor(false, true);
+            const evt = processor.EventToEmbed({
+                sender: "@test:localhost",
+                content: {
+                    body: "@everyone Hello!",
+                },
+            }, {avatar_url: "test"}, mockChannel as any);
+            Chai.assert.equal(evt.description, "@ everyone Hello!");
+        });
 
+        it("Should remove here mentions if configured.", () => {
+            const processor = createMatrixEventProcessor(false, false, true);
+            const evt = processor.EventToEmbed({
+                sender: "@test:localhost",
+                content: {
+                    body: "@here Hello!",
+                },
+            }, {avatar_url: "test"}, mockChannel as any);
+            Chai.assert.equal(evt.description, "@ here Hello!");
         });
     });
-}
+    describe("FindMentionsInPlainBody", () => {
+        it("processes mentioned username correctly", async () => {
+            const processor = createMatrixEventProcessor();
+            const guild: any = new MockGuild("123", []);
+            const members: Discord.GuildMember[] = [new Discord.GuildMember(guild, {
+                user: {
+                    username: "TestUsername",
+                    id: "12345",
+                    discriminator: "54321",
+                },
+            })];
+            Chai.assert.equal(
+                processor.FindMentionsInPlainBody("Hello TestUsername", members),
+                "Hello <@!12345>",
+            );
+            Chai.assert.equal(
+                processor.FindMentionsInPlainBody("Hello TestUsername#54321", members),
+                "Hello <@!12345>",
+            );
+        });
+        it("processes mentioned nickname correctly", async () => {
+            const processor = createMatrixEventProcessor();
+            const guild: any = new MockGuild("123", []);
+            const members: Discord.GuildMember[] = [new Discord.GuildMember(guild, {
+                nick: "Test",
+                user: {
+                    username: "Test",
+                    id: "54321",
+                },
+            }), new Discord.GuildMember(guild, {
+                nick: "TestNickname",
+                user: {
+                    username: "TestUsername",
+                    id: "12345",
+                },
+            })];
+            Chai.assert.equal(processor.FindMentionsInPlainBody("Hello TestNickname", members), "Hello <@!12345>");
+            Chai.assert.equal(processor.FindMentionsInPlainBody("TestNickname: Hello", members), "<@!12345>: Hello");
+            Chai.assert.equal(processor.FindMentionsInPlainBody("TestNickname, Hello", members), "<@!12345>, Hello");
+            Chai.assert.equal(processor.FindMentionsInPlainBody("TestNickname Hello", members), "<@!12345> Hello");
+            Chai.assert.equal(processor.FindMentionsInPlainBody("testNicKName Hello", members), "<@!12345> Hello");
+            Chai.assert.equal(
+                processor.FindMentionsInPlainBody("I wish TestNickname was here", members),
+                "I wish <@!12345> was here",
+            );
+            Chai.assert.equal(
+                processor.FindMentionsInPlainBody("I wish TestNickname was here, TestNickname is cool", members),
+                "I wish <@!12345> was here, <@!12345> is cool",
+            );
+            Chai.assert.equal(
+                processor.FindMentionsInPlainBody("TestNickname was here with Test", members),
+                "<@!12345> was here with <@!54321>",
+            );
+        });
+        it("processes non-mentions correctly", async () => {
+            const processor = createMatrixEventProcessor();
+            const guild: any = new MockGuild("123", []);
+            const members: Discord.GuildMember[] = [new Discord.GuildMember(guild, {
+                nick: "that",
+                user: {
+                    username: "TestUsername",
+                    id: "12345",
+                },
+            }),
+                new Discord.GuildMember(guild, {
+                    nick: "testingstring",
+                    user: {
+                        username: "that",
+                        id: "12345",
+                    },
+                })];
+            const msg = "Welcome thatman";
+            const content = processor.FindMentionsInPlainBody(msg, members);
+            Chai.assert.equal(content, "Welcome thatman");
+        });
+    });
+});
\ No newline at end of file
diff --git a/test/test_messageprocessor.ts b/test/test_messageprocessor.ts
index 2dc07b8..9e3cf5d 100644
--- a/test/test_messageprocessor.ts
+++ b/test/test_messageprocessor.ts
@@ -181,82 +181,6 @@ describe("MessageProcessor", () => {
             Chai.assert.equal(content, "Hello <img alt=\"hello\" src=\"mxc://image\" style=\"height: 1em;\"/>");
         });
     });
-    describe("FindMentionsInPlainBody", () => {
-        it("processes mentioned username correctly", async () => {
-            const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot);
-            const guild: any = new MockGuild("123", []);
-            const members: Discord.GuildMember[] = [new Discord.GuildMember(guild, {
-                user: {
-                    username: "TestUsername",
-                    id: "12345",
-                    discriminator: "54321",
-                },
-            })];
-            Chai.assert.equal(
-                processor.FindMentionsInPlainBody("Hello TestUsername", members),
-                "Hello <@!12345>",
-            );
-            Chai.assert.equal(
-                processor.FindMentionsInPlainBody("Hello TestUsername#54321", members),
-                "Hello <@!12345>",
-            );
-        });
-        it("processes mentioned nickname correctly", async () => {
-            const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot);
-            const guild: any = new MockGuild("123", []);
-            const members: Discord.GuildMember[] = [new Discord.GuildMember(guild, {
-                nick: "Test",
-                user: {
-                    username: "Test",
-                    id: "54321",
-                },
-            }), new Discord.GuildMember(guild, {
-                nick: "TestNickname",
-                user: {
-                    username: "TestUsername",
-                    id: "12345",
-                },
-            })];
-            Chai.assert.equal(processor.FindMentionsInPlainBody("Hello TestNickname", members), "Hello <@!12345>");
-            Chai.assert.equal(processor.FindMentionsInPlainBody("TestNickname: Hello", members), "<@!12345>: Hello");
-            Chai.assert.equal(processor.FindMentionsInPlainBody("TestNickname, Hello", members), "<@!12345>, Hello");
-            Chai.assert.equal(processor.FindMentionsInPlainBody("TestNickname Hello", members), "<@!12345> Hello");
-            Chai.assert.equal(processor.FindMentionsInPlainBody("testNicKName Hello", members), "<@!12345> Hello");
-            Chai.assert.equal(
-                processor.FindMentionsInPlainBody("I wish TestNickname was here", members),
-                "I wish <@!12345> was here",
-            );
-            Chai.assert.equal(
-                processor.FindMentionsInPlainBody("I wish TestNickname was here, TestNickname is cool", members),
-                "I wish <@!12345> was here, <@!12345> is cool",
-            );
-            Chai.assert.equal(
-                processor.FindMentionsInPlainBody("TestNickname was here with Test", members),
-                "<@!12345> was here with <@!54321>",
-            );
-        });
-        it("processes non-mentions correctly", async () => {
-            const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot);
-            const guild: any = new MockGuild("123", []);
-            const members: Discord.GuildMember[] = [new Discord.GuildMember(guild, {
-                nick: "that",
-                user: {
-                    username: "TestUsername",
-                    id: "12345",
-                },
-            }),
-            new Discord.GuildMember(guild, {
-                nick: "testingstring",
-                user: {
-                    username: "that",
-                    id: "12345",
-                },
-            })];
-            const msg = "Welcome thatman";
-            const content = processor.FindMentionsInPlainBody(msg, members);
-            Chai.assert.equal(content, "Welcome thatman");
-        });
-    });
     describe("InsertEmbeds", () => {
         it("processes titleless embeds properly", () => {
             const processor = new MessageProcessor(new MessageProcessorOpts("localhost"), <DiscordBot> bot);
-- 
GitLab