From 76e20d35c8aded4fdf5cf6dd1951ba8201183a0c Mon Sep 17 00:00:00 2001
From: Sorunome <mail@sorunome.de>
Date: Thu, 25 Oct 2018 19:47:55 +0200
Subject: [PATCH] add chanfix

---
 package.json              |   3 +-
 src/channelsyncroniser.ts |  14 +++-
 tools/chanfix.ts          | 149 ++++++++++++++++++++++++++++++++++++++
 tools/ghostfix.ts         |   5 +-
 4 files changed, 165 insertions(+), 6 deletions(-)
 create mode 100644 tools/chanfix.ts

diff --git a/package.json b/package.json
index f3cff63..1d441da 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,8 @@
     "adminme": "node ./build/tools/adminme.js",
     "usertool": "node ./build/tools/userClientTools.js",
     "directoryfix": "node ./build/tools/addRoomsToDirectory.js",
-    "ghostfix": "node ./build/tools/ghostfix.js"
+    "ghostfix": "node ./build/tools/ghostfix.js",
+    "chanfix": "node ./build/tools/chanfix.js"
   },
   "repository": {
     "type": "git",
diff --git a/src/channelsyncroniser.ts b/src/channelsyncroniser.ts
index 5822814..3078a61 100644
--- a/src/channelsyncroniser.ts
+++ b/src/channelsyncroniser.ts
@@ -133,7 +133,7 @@ export class ChannelSyncroniser {
         return rooms.map((room) => room.matrix.getId() as string);
     }
 
-    public async GetChannelUpdateState(channel: Discord.TextChannel): Promise<IChannelState> {
+    public async GetChannelUpdateState(channel: Discord.TextChannel, forceUpdate = false): Promise<IChannelState> {
         log.verbose(`State update request for ${channel.id}`);
         const channelState = Object.assign({}, DEFAULT_CHANNEL_STATE, {
             id: channel.id,
@@ -167,19 +167,19 @@ export class ChannelSyncroniser {
             });
             
             const oldName = remoteRoom.remote.get("discord_name");
-            if (remoteRoom.remote.get("update_name") && oldName !== name) {
+            if (remoteRoom.remote.get("update_name") && (forceUpdate || oldName !== name)) {
                 log.verbose(`Channel ${mxid} name should be updated`);
                 singleChannelState.name = name;
             }
             
             const oldTopic = remoteRoom.remote.get("discord_topic");
-            if (remoteRoom.remote.get("update_topic") && oldTopic !== topic) {
+            if (remoteRoom.remote.get("update_topic") && (forceUpdate || oldTopic !== topic)) {
                 log.verbose(`Channel ${mxid} topic should be updated`);
                 singleChannelState.topic = topic;
             }
             
             const oldIconUrl = remoteRoom.remote.get("discord_iconurl");
-            if (remoteRoom.remote.get("update_icon") && oldIconUrl !== iconUrl) {
+            if (remoteRoom.remote.get("update_icon") && oldIconUrl !== iconUrl) { // no force on icon update as we don't want to duplicate ALL the icons
                 log.verbose(`Channel ${mxid} icon should be updated`);
                 if (iconUrl !== null) {
                     singleChannelState.iconUrl = iconUrl;
@@ -193,6 +193,12 @@ export class ChannelSyncroniser {
         return channelState;
     }
 
+    public async EnsureState(channel: Discord.TextChannel) {
+        const state = await this.GetChannelUpdateState(channel, true);
+        log.info(`Ensuring ${state.id} to be correct`);
+        await this.ApplyStateToChannel(state);
+    }
+
     private async ApplyStateToChannel(channelsState: IChannelState) {
         const intent = this.bridge.getIntent();
         for (const channelState of channelsState.mxChannels) {
diff --git a/tools/chanfix.ts b/tools/chanfix.ts
new file mode 100644
index 0000000..706446f
--- /dev/null
+++ b/tools/chanfix.ts
@@ -0,0 +1,149 @@
+import { AppServiceRegistration, ClientFactory, Bridge, Intent } from "matrix-appservice-bridge";
+import * as yaml from "js-yaml";
+import * as fs from "fs";
+import * as args from "command-line-args";
+import * as usage from "command-line-usage";
+import * as Bluebird from "bluebird";
+import { ChannelSyncroniser } from "../src/channelsyncroniser";
+import { DiscordBridgeConfig } from "../src/config";
+import { DiscordBot } from "../src/bot";
+import { DiscordStore } from "../src/store";
+import { Provisioner } from "../src/provisioner";
+import { Log } from "../src/log";
+import { Util } from "../src/util";
+
+const log = new Log("ChanFix");
+
+const optionDefinitions = [
+    {
+        name: "help",
+        alias: "h",
+        type: Boolean,
+        description: "Display this usage guide.",
+    },
+    {
+      name: "config",
+      alias: "c",
+      type: String,
+      defaultValue: "config.yaml",
+      description: "The AS config file.",
+      typeLabel: "<config.yaml>",
+    },
+];
+
+const options = args(optionDefinitions);
+
+if (options.help) {
+    /* tslint:disable:no-console */
+    console.log(usage([
+    {
+        header: "Fix bridged channels",
+        content: "A tool to fix channels of rooms already bridged " +
+            "to matrix, to make sure their names, icons etc. are correctly."},
+    {
+        header: "Options",
+        optionList: optionDefinitions,
+    },
+    ]));
+    process.exit(0);
+}
+
+const yamlConfig = yaml.safeLoad(fs.readFileSync("./discord-registration.yaml", "utf8"));
+const registration = AppServiceRegistration.fromObject(yamlConfig);
+const config = new DiscordBridgeConfig();
+config.ApplyConfig(yaml.safeLoad(fs.readFileSync(options.config, "utf8")) as DiscordBridgeConfig);
+
+if (registration === null) {
+    throw new Error("Failed to parse registration file");
+}
+
+const botUserId = "@" + registration.sender_localpart + ":" + config.bridge.domain;
+const clientFactory = new ClientFactory({
+    appServiceUserId: botUserId,
+    token: registration.as_token,
+    url: config.bridge.homeserverUrl,
+});
+const provisioner = new Provisioner();
+const discordstore = new DiscordStore(config.database ? config.database.filename : "discord.db");
+const discordbot = new DiscordBot(config, discordstore, provisioner);
+
+const bridge = new Bridge({
+    clientFactory,
+    controller: {
+        onEvent: () => { },
+    },
+    intentOptions: {
+        clients: {
+            dontJoin: true, // handled manually
+      },
+    },
+    domain: config.bridge.domain,
+    homeserverUrl: config.bridge.homeserverUrl,
+    registration,
+    userStore: config.database.userStorePath,
+    roomStore: config.database.roomStorePath,
+});
+
+provisioner.SetBridge(bridge);
+discordbot.setBridge(bridge);
+let chanSync;
+
+let client;
+bridge.loadDatabases().catch((e) => {
+    return discordstore.init();
+}).then(() => {
+    chanSync = new ChannelSyncroniser(bridge, config, discordbot);
+    bridge._clientFactory = clientFactory;
+    bridge._botClient = bridge._clientFactory.getClientAs();
+    bridge._botIntent = new Intent(bridge._botClient, bridge._botClient, { registered: true });
+    return discordbot.ClientFactory.init().then(() => {
+        return discordbot.ClientFactory.getClient();
+    });
+}).then((clientTmp: any) => {
+    client = clientTmp;
+    
+    // first set update_icon to true if needed
+    return bridge.getRoomStore().getEntriesByRemoteRoomData({
+        update_name: true,
+        update_topic: true,
+    });
+}).then((mxRoomEntries) => {
+    const promiseList = [];
+    
+    mxRoomEntries.forEach((entry) => {
+        if (entry.remote.get("plumbed")) {
+            return; // skipping plumbed rooms
+        }
+        const update_icon = entry.remote.get("update_icon");
+        if (update_icon !== undefined && update_icon !== null) {
+            return; // skipping because something was set manually
+        }
+        entry.remote.set("update_icon", true);
+        promiseList.push(bridge.getRoomStore().upsertEntry(entry));
+    });
+    return Promise.all(promiseList);
+}).then(() => {
+    // now it is time to actually run the updates
+    let promiseChain: Bluebird<any> = Bluebird.resolve();
+    
+    let delay = config.limits.roomGhostJoinDelay; // we'll just re-use this
+    client.guilds.forEach((guild) => {
+        guild.channels.forEach((channel) => {
+            if (channel.type !== "text") {
+                return;
+            }
+            
+            promiseChain = promiseChain.return(Bluebird.delay(delay).then(() => {
+                return chanSync.EnsureState(channel).catch((err) => {
+                    log.warn(`Couldn't update rooms for ${channel.id}`, err);
+                });
+            }));
+            delay += config.limits.roomGhostJoinDelay;
+        });
+    });
+    return promiseChain;
+}).catch((err) => {
+    log.error(err);
+}).then(() => {
+    process.exit(0);
+});
diff --git a/tools/ghostfix.ts b/tools/ghostfix.ts
index ab613be..139a104 100644
--- a/tools/ghostfix.ts
+++ b/tools/ghostfix.ts
@@ -4,6 +4,7 @@ import * as fs from "fs";
 import * as args from "command-line-args";
 import * as usage from "command-line-usage";
 import * as Bluebird from "bluebird";
+import { ChannelSyncroniser } from "../src/channelsyncroniser";
 import { DiscordBridgeConfig } from "../src/config";
 import { DiscordBot } from "../src/bot";
 import { DiscordStore } from "../src/store";
@@ -97,12 +98,14 @@ const bridge = new Bridge({
 
 provisioner.SetBridge(bridge);
 discordbot.setBridge(bridge);
+let chanSync;
 let userSync;
 
 let client;
 bridge.loadDatabases().catch((e) => {
     return discordstore.init();
 }).then(() => {
+    chanSync = new ChannelSyncroniser(bridge, config, discordbot);
     userSync = new UserSyncroniser(bridge, config, discordbot);
     bridge._clientFactory = clientFactory;
     return discordbot.ClientFactory.init().then(() => {
@@ -123,7 +126,7 @@ bridge.loadDatabases().catch((e) => {
                     return;
                 }
                 promiseChain = promiseChain.return(Bluebird.delay(delay).then(() => {
-                    return Bluebird.each(discordbot.ChannelSyncroniser.GetRoomIdsFromChannel(channel), (room) => {
+                    return Bluebird.each(chanSync.GetRoomIdsFromChannel(channel), (room) => {
                         let currentSchedule = JOIN_ROOM_SCHEDULE[0];
                         const doJoin = () => Util.DelayedPromise(currentSchedule).then(() => {
                             userSync.EnsureJoin(member, room);
-- 
GitLab