From e0f43adf83b4c764e153ce9c2820d604f2fd633b Mon Sep 17 00:00:00 2001
From: Will Hunt <will@half-shot.uk>
Date: Sat, 11 May 2019 18:11:41 +0100
Subject: [PATCH] Leave users from rooms when unbridging

---
 src/channelsyncroniser.ts    | 37 +++++++++++++++++++++++++-----------
 src/config.ts                |  2 +-
 src/discordcommandhandler.ts |  2 +-
 src/matrixcommandhandler.ts  |  2 +-
 src/provisioner.ts           | 17 ++++++++++++++++-
 5 files changed, 45 insertions(+), 15 deletions(-)

diff --git a/src/channelsyncroniser.ts b/src/channelsyncroniser.ts
index c948f35..e2aa65e 100644
--- a/src/channelsyncroniser.ts
+++ b/src/channelsyncroniser.ts
@@ -17,7 +17,7 @@ limitations under the License.
 import * as Discord from "discord.js";
 import { DiscordBot } from "./bot";
 import { Util } from "./util";
-import { DiscordBridgeConfig } from "./config";
+import { DiscordBridgeConfig, DiscordBridgeConfigChannelDeleteOptions } from "./config";
 import { Bridge } from "matrix-appservice-bridge";
 import { Log } from "./log";
 import { DbRoomStore, IRoomStoreEntry } from "./db/roomstore";
@@ -105,6 +105,20 @@ export class ChannelSyncroniser {
         }
     }
 
+    public async OnUnbridge(channel: Discord.Channel, roomId: string) {
+        try {
+            const entry = (await this.roomStore.getEntriesByMatrixId(roomId))[0];
+            const opts = new DiscordBridgeConfigChannelDeleteOptions();
+            opts.namePrefix = null;
+            opts.topicPrefix = null;
+            opts.ghostsLeave = true;
+            await this.handleChannelDeletionForRoom(channel as Discord.TextChannel, roomId, entry);
+            log.info(`Channel ${channel.id} has been unbridged.`);
+        } catch (e) {
+            log.error(`Failed to unbridge channel from room: ${e}`);
+        }
+    }
+
     public async OnDelete(channel: Discord.Channel) {
         if (channel.type !== "text") {
             log.info(`Channel ${channel.id} was deleted but isn't a text channel, so ignoring.`);
@@ -269,22 +283,24 @@ export class ChannelSyncroniser {
     private async handleChannelDeletionForRoom(
         channel: Discord.TextChannel,
         roomId: string,
-        entry: IRoomStoreEntry): Promise<void> {
+        entry: IRoomStoreEntry,
+        overrideOptions?: DiscordBridgeConfigChannelDeleteOptions): Promise<void> {
         log.info(`Deleting ${channel.id} from ${roomId}.`);
         const intent = await this.bridge.getIntent();
-        const options = this.config.channel.deleteOptions;
+        const options = overrideOptions || this.config.channel.deleteOptions;
         const plumbed = entry.remote!.get("plumbed");
+        // tslint:disable-next-line: no-any
 
         await this.roomStore.upsertEntry(entry);
         if (options.ghostsLeave) {
             for (const member of channel.members.array()) {
-                try {
-                    const mIntent = await this.bot.GetIntentFromDiscordMember(member);
-                    mIntent.leave(roomId);
-                    log.info(`${member.id} left ${roomId}.`);
-                } catch (e) {
-                    log.warn(`Failed to make ${member.id} leave `);
-                }
+                const mIntent = await this.bot.GetIntentFromDiscordMember(member);
+                // Not awaiting this because we want to do this in the background.
+                mIntent.leave(roomId).then(() => {
+                    log.verbose(`${member.id} left ${roomId}.`);
+                }).catch(() => {
+                    log.warn(`Failed to make ${member.id} leave.`);
+                });
             }
         }
         if (options.namePrefix) {
@@ -347,7 +363,6 @@ export class ChannelSyncroniser {
                 }
             }
         }
-        // Unlist
 
         // Remove entry
         await this.roomStore.removeEntriesByMatrixRoomId(roomId);
diff --git a/src/config.ts b/src/config.ts
index 637bc55..1666043 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -86,7 +86,7 @@ class DiscordBridgeConfigChannel {
     public deleteOptions = new DiscordBridgeConfigChannelDeleteOptions();
 }
 
-class DiscordBridgeConfigChannelDeleteOptions {
+export class DiscordBridgeConfigChannelDeleteOptions {
     public namePrefix: string | null = null;
     public topicPrefix: string | null = null;
     public disableMessaging: boolean = false;
diff --git a/src/discordcommandhandler.ts b/src/discordcommandhandler.ts
index 99dfc62..04401b4 100644
--- a/src/discordcommandhandler.ts
+++ b/src/discordcommandhandler.ts
@@ -20,7 +20,7 @@ import { Util, ICommandActions, ICommandParameters, CommandPermissonCheck } from
 import { Bridge } from "matrix-appservice-bridge";
 import { Log } from "./log";
 
-const log = new Log("MatrixCommandHandler");
+const log = new Log("DiscordCommandHandler");
 
 export class DiscordCommandHandler {
     constructor(
diff --git a/src/matrixcommandhandler.ts b/src/matrixcommandhandler.ts
index c91ccbc..d289b7a 100644
--- a/src/matrixcommandhandler.ts
+++ b/src/matrixcommandhandler.ts
@@ -134,7 +134,7 @@ export class MatrixCommandHandler {
                         remoteRoom.data.discord_channel,
                     );
                     try {
-                        await this.provisioner.UnbridgeChannel(res.channel);
+                        await this.provisioner.UnbridgeChannel(res.channel, event.room_id);
                         return "This room has been unbridged";
                     } catch (err) {
                         log.error("Error while unbridging room " + event.room_id);
diff --git a/src/provisioner.ts b/src/provisioner.ts
index 0b5ee74..4f92ca7 100644
--- a/src/provisioner.ts
+++ b/src/provisioner.ts
@@ -17,9 +17,12 @@ limitations under the License.
 import * as Discord from "discord.js";
 import { DbRoomStore, RemoteStoreRoom, MatrixStoreRoom } from "./db/roomstore";
 import { ChannelSyncroniser } from "./channelsyncroniser";
+import { Log } from "./log";
 
 const PERMISSION_REQUEST_TIMEOUT = 300000; // 5 minutes
 
+const log = new Log("Provisioner");
+
 export class Provisioner {
 
     private pendingRequests: Map<string, (approved: boolean) => void> = new Map(); // [channelId]: resolver fn
@@ -38,7 +41,7 @@ export class Provisioner {
         return this.roomStore.linkRooms(local, remote);
     }
 
-    public async UnbridgeChannel(channel: Discord.TextChannel) {
+    public async UnbridgeChannel(channel: Discord.TextChannel, rId?: string) {
         const roomsRes = await this.roomStore.getEntriesByRemoteRoomData({
             discord_channel: channel.id,
             discord_guild: channel.guild.id,
@@ -48,6 +51,18 @@ export class Provisioner {
             throw Error("Channel is not bridged");
         }
         const remoteRoom = roomsRes[0].remote as RemoteStoreRoom;
+        let roomsToUnbridge: string[] = [];
+        if (rId) {
+            roomsToUnbridge = [rId];
+        } else {
+            // Kill em all.
+            roomsToUnbridge = roomsRes.map((entry) => entry.matrix!.roomId);
+        }
+        await Promise.all(roomsToUnbridge.map( async (roomId) => {
+            return this.channelSync.OnUnbridge(channel, roomId).catch((err) => {
+                log.error(`Failed to cleanly unbridge ${channel.id} ${channel.guild} from ${roomId}`);
+            });
+        }));
         await this.roomStore.removeEntriesByRemoteRoomId(remoteRoom.getId());
     }
 
-- 
GitLab