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