From caeb3554e51d52bfa2218b0a3b3525f76789dc07 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Fri, 29 Sep 2023 16:35:48 +0400 Subject: [PATCH 01/30] notify us admins in admin room about a deleted slack channel --- src/TeamSyncer.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index 44869c85..f2c1c149 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -55,12 +55,14 @@ const TEAM_SYNC_FAILSAFE = 10; */ export class TeamSyncer { private teamConfigs: {[teamId: string]: ITeamSyncConfig} = {}; + private readonly adminRoom?: string; constructor(private main: Main) { const config = main.config; if (!config.team_sync) { throw Error("team_sync is not defined in the config"); } // Apply defaults to configs + this.adminRoom = config.matrix_admin_room; this.teamConfigs = config.team_sync; for (const teamConfig of Object.values(this.teamConfigs)) { if (teamConfig.channels?.enabled) { @@ -368,6 +370,14 @@ export class TeamSyncer { return; } + // notify admins + if (this.adminRoom) { + await this.main.botIntent.sendMessage(this.adminRoom, { + msgtype: "m.notice", + body: `${teamId} removed channel ${channelId}`, + }); + } + try { await this.main.botIntent.sendMessage(room.MatrixRoomId, { msgtype: "m.notice", From 7b831ab19ae4109821dae3f4f1ef7ca18c511092 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Fri, 29 Sep 2023 17:17:59 +0400 Subject: [PATCH 02/30] notify us admins in admin room when bridge boots up --- src/Main.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Main.ts b/src/Main.ts index 629e3048..2be270e6 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -1289,6 +1289,14 @@ export class Main { } log.info("Bridge initialised"); + // notify admins + if (this.config.matrix_admin_room) { + void this.botIntent.sendMessage(this.config.matrix_admin_room,{ + msgtype: "m.notice", + body: "Bridge initialised", + }); + } + this.ready = true; return port; } From 224fdb87699e643d9b9ca890340fb88e51afc955 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Mon, 9 Oct 2023 16:34:12 +0400 Subject: [PATCH 03/30] add bridged room id to notification message upon slack channel deletion --- src/TeamSyncer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index f2c1c149..2b600a86 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -374,7 +374,7 @@ export class TeamSyncer { if (this.adminRoom) { await this.main.botIntent.sendMessage(this.adminRoom, { msgtype: "m.notice", - body: `${teamId} removed channel ${channelId}`, + body: `${teamId} removed channel ${channelId}, bridged room: ${room.MatrixRoomId}`, }); } From 556b146ff5145bd9d6a56f8d8ad481ffc40ea099 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Mon, 9 Oct 2023 20:35:38 +0400 Subject: [PATCH 04/30] ensure rooms are created by super admin user, creator is only a mod along with a list of users who should be moderators in these rooms --- src/IConfig.ts | 1 + src/TeamSyncer.ts | 34 +++++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/IConfig.ts b/src/IConfig.ts index c91bfc3b..08c5daad 100644 --- a/src/IConfig.ts +++ b/src/IConfig.ts @@ -32,6 +32,7 @@ export interface IConfig { inbound_uri_prefix?: string; username_prefix: string; + super_admin_user?: string; matrix_admin_room?: string; rmau_limit?: number; diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index 2b600a86..f7f9362a 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -40,6 +40,7 @@ export interface ITeamSyncConfig { }; users?: { enabled: boolean; + moderators?: string[]; // promote these users as moderators }; } @@ -55,6 +56,7 @@ const TEAM_SYNC_FAILSAFE = 10; */ export class TeamSyncer { private teamConfigs: {[teamId: string]: ITeamSyncConfig} = {}; + private readonly superAdminUser?: string; private readonly adminRoom?: string; constructor(private main: Main) { const config = main.config; @@ -62,6 +64,7 @@ export class TeamSyncer { throw Error("team_sync is not defined in the config"); } // Apply defaults to configs + this.superAdminUser = config.super_admin_user; this.adminRoom = config.matrix_admin_room; this.teamConfigs = config.team_sync; for (const teamConfig of Object.values(this.teamConfigs)) { @@ -508,12 +511,16 @@ export class TeamSyncer { isPublic = true, inviteList: string[] = []): Promise { let intent: Intent; let creatorUserId: string|undefined; - try { - creatorUserId = (await this.main.ghostStore.get(creator, undefined, teamId)).matrixUserId; - intent = this.main.getIntent(creatorUserId); - } catch (ex) { - // Couldn't get the creator's mxid, using the bot. - intent = this.main.botIntent; + if (this.superAdminUser) { + intent = this.main.getIntent(this.superAdminUser); + } else { + try { + creatorUserId = (await this.main.ghostStore.get(creator, undefined, teamId)).matrixUserId; + intent = this.main.getIntent(creatorUserId); + } catch (ex) { + // Couldn't get the creator's mxid, using the bot. + intent = this.main.botIntent; + } } const aliasPrefix = this.getAliasPrefix(teamId); const alias = aliasPrefix ? `${aliasPrefix}${channel.name.toLowerCase()}` : undefined; @@ -524,8 +531,21 @@ export class TeamSyncer { log.debug("Creating new room for channel", channel.name, topic, alias); const plUsers = {}; plUsers[this.main.botUserId] = 100; + if (this.superAdminUser) { + plUsers[this.superAdminUser] = 100; + } if (creatorUserId) { - plUsers[creatorUserId] = 100; + plUsers[creatorUserId] = 50; + } + for (const team in this.teamConfigs) { + if (team === "all" || team === teamId ) { + const mods = this.teamConfigs[team]?.users?.moderators; + if (mods && Array.isArray(mods)) { + for (const mod of mods) { + plUsers[mod] = 50; + } + } + } } inviteList = inviteList.filter((s) => s !== creatorUserId || s !== this.main.botUserId); inviteList.push(this.main.botUserId); From bdff14afff5d6f20abe02d462e5613bf7316a3f7 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 10 Oct 2023 13:40:10 +0400 Subject: [PATCH 05/30] remove super_admin_user field in config and add rooms field under teamsync --- src/IConfig.ts | 1 - src/TeamSyncer.ts | 52 +++++++++++++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/IConfig.ts b/src/IConfig.ts index 08c5daad..c91bfc3b 100644 --- a/src/IConfig.ts +++ b/src/IConfig.ts @@ -32,7 +32,6 @@ export interface IConfig { inbound_uri_prefix?: string; username_prefix: string; - super_admin_user?: string; matrix_admin_room?: string; rmau_limit?: number; diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index f7f9362a..ffd0547d 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -40,8 +40,12 @@ export interface ITeamSyncConfig { }; users?: { enabled: boolean; - moderators?: string[]; // promote these users as moderators }; + rooms?: { + creator?: string; + administrators?: string[]; // promote these users as admins + moderators?: string[]; // promote these users as moderators + } } const TEAM_SYNC_CONCURRENCY = 1; @@ -56,7 +60,6 @@ const TEAM_SYNC_FAILSAFE = 10; */ export class TeamSyncer { private teamConfigs: {[teamId: string]: ITeamSyncConfig} = {}; - private readonly superAdminUser?: string; private readonly adminRoom?: string; constructor(private main: Main) { const config = main.config; @@ -64,7 +67,6 @@ export class TeamSyncer { throw Error("team_sync is not defined in the config"); } // Apply defaults to configs - this.superAdminUser = config.super_admin_user; this.adminRoom = config.matrix_admin_room; this.teamConfigs = config.team_sync; for (const teamConfig of Object.values(this.teamConfigs)) { @@ -511,12 +513,33 @@ export class TeamSyncer { isPublic = true, inviteList: string[] = []): Promise { let intent: Intent; let creatorUserId: string|undefined; - if (this.superAdminUser) { - intent = this.main.getIntent(this.superAdminUser); + + let admins: string[] | undefined; + let mods: string[] | undefined; + let matrixCreator: string | undefined; + + for (const team in this.teamConfigs) { + if (team === "all" || team === teamId ) { + const teamConfig = this.teamConfigs[team]; + mods = teamConfig?.rooms?.moderators; + admins = teamConfig?.rooms?.administrators; + matrixCreator = teamConfig?.rooms?.creator; + } + } + + admins?.push(this.main.botUserId); + if (matrixCreator) { + admins?.push(matrixCreator); + } + + if (matrixCreator) { + intent = this.main.getIntent(matrixCreator); } else { try { creatorUserId = (await this.main.ghostStore.get(creator, undefined, teamId)).matrixUserId; intent = this.main.getIntent(creatorUserId); + + mods?.push(creatorUserId); // make slack channel creator (user) a mod as well } catch (ex) { // Couldn't get the creator's mxid, using the bot. intent = this.main.botIntent; @@ -530,22 +553,11 @@ export class TeamSyncer { } log.debug("Creating new room for channel", channel.name, topic, alias); const plUsers = {}; - plUsers[this.main.botUserId] = 100; - if (this.superAdminUser) { - plUsers[this.superAdminUser] = 100; - } - if (creatorUserId) { - plUsers[creatorUserId] = 50; + for (const mod of mods ?? []) { + plUsers[mod] = 50; } - for (const team in this.teamConfigs) { - if (team === "all" || team === teamId ) { - const mods = this.teamConfigs[team]?.users?.moderators; - if (mods && Array.isArray(mods)) { - for (const mod of mods) { - plUsers[mod] = 50; - } - } - } + for (const admin of admins ?? []) { + plUsers[admin] = 100; } inviteList = inviteList.filter((s) => s !== creatorUserId || s !== this.main.botUserId); inviteList.push(this.main.botUserId); From 9f3f6c0f6276315ecade7141a9c74516824b3a25 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 10 Oct 2023 14:02:31 +0400 Subject: [PATCH 06/30] change hint message when new slack channel is created --- src/TeamSyncer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index ffd0547d..6b7bff39 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -484,7 +484,8 @@ export class TeamSyncer { try { await client.chat.postEphemeral({ user: channelItem.creator, - text: `Hint: To bridge to Matrix, run the \`/invite @${user.name}\` command in this channel.`, + text: `Please invite \`@${user.name}\` app to this channel (\`/invite @${user.name}\`) so that ` + + `this channel is also available on Matrix side.`, channel: channelItem.id, }); } catch (error) { From c274bee585a125c77835d029ba2e195d53faca4f Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 10 Oct 2023 17:42:34 +0400 Subject: [PATCH 07/30] fix bug with membership sync logic - logic for collecting users which are to leave the room was incorrect - additionally, now we just work with matrix user ids for both leaving/joining of new users, as opposed to working with SlackGhost object --- src/TeamSyncer.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index 6b7bff39..dd1d9a9a 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -418,28 +418,36 @@ export class TeamSyncer { // Ghosts will exist already: We joined them in the user sync. const ghosts = await Promise.all(members.members.map(async(slackUserId) => this.main.ghostStore.get(slackUserId, teamInfo.domain, teamId))); - const joinedUsers = ghosts.filter((g) => !existingGhosts.includes(g.matrixUserId)); // Skip users that are joined. - const leftUsers = existingGhosts.map((userId) => ghosts.find((g) => g.matrixUserId === userId )).filter(g => !!g) as SlackGhost[]; - log.info(`Joining ${joinedUsers.length} ghosts to ${roomId}`); - log.info(`Leaving ${leftUsers.length} ghosts to ${roomId}`); + const joinedUsers = ghosts.filter((g) => !existingGhosts.includes(g.matrixUserId)).map(g => g.matrixUserId); + const leftUsers = existingGhosts.map((userId, index) => { + const ghost = ghosts.find((g) => g.matrixUserId === userId); + return ghost === undefined ? index : null; + }).filter(index => index !== null).map(index => + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + existingGhosts[index] + ); + + log.info(`Joining ${joinedUsers.length} ghosts to ${roomId}`,joinedUsers); + log.info(`Leaving ${leftUsers.length} ghosts to ${roomId}`,leftUsers); const queue = new PQueue({concurrency: JOIN_CONCURRENCY}); // Join users who aren't joined - queue.addAll(joinedUsers.map((ghost) => async () => { + queue.addAll(joinedUsers.map((userId) => async () => { try { - await this.main.membershipQueue.join(roomId, ghost.matrixUserId, { getId: () => ghost.matrixUserId }); + await this.main.membershipQueue.join(roomId, userId, { getId: () => userId }); } catch (ex) { - log.warn(`Failed to join ${ghost.matrixUserId} to ${roomId}`); + log.warn(`Failed to join ${userId} to ${roomId}`); } })).catch((ex) => log.error(`queue.addAll(joinedUsers) rejected with an error:`, ex)); // Leave users who are joined - queue.addAll(leftUsers.map((ghost) => async () => { + queue.addAll(leftUsers.map((userId) => async () => { try { - await this.main.membershipQueue.leave(roomId, ghost.matrixUserId, { getId: () => ghost.matrixUserId }); + await this.main.membershipQueue.leave(roomId, userId, { getId: () => userId }); } catch (ex) { - log.warn(`Failed to leave ${ghost.matrixUserId} from ${roomId}`); + log.warn(`Failed to leave ${userId} from ${roomId}`); } })).catch((ex) => log.error(`queue.addAll(leftUsers) rejected with an error:`, ex)); From 17505eab22d57cfafe4524df15d6a61f17feca21 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 10 Oct 2023 19:21:53 +0400 Subject: [PATCH 08/30] handle channel_archive event --- src/SlackEventHandler.ts | 3 +++ src/TeamSyncer.ts | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/SlackEventHandler.ts b/src/SlackEventHandler.ts index 4e15d5d7..77726614 100644 --- a/src/SlackEventHandler.ts +++ b/src/SlackEventHandler.ts @@ -211,6 +211,7 @@ export class SlackEventHandler extends BaseSlackHandler { break; case "channel_created": case "channel_deleted": + case "channel_archived": case "user_change": case "team_join": await this.handleTeamSyncEvent(event as ISlackTeamSyncEvent, teamId); @@ -367,6 +368,8 @@ export class SlackEventHandler extends BaseSlackHandler { await this.main.teamSyncer.onChannelAdded(teamId, eventDetails.channel.id, eventDetails.channel.name, eventDetails.channel.creator); } else if (event.type === "channel_deleted") { await this.main.teamSyncer.onChannelDeleted(teamId, event.channel); + } else if (event.type === "channel_archive") { + await this.main.teamSyncer.onChannelArchived(teamId, event.channel); } else if (event.type === "team_join" || event.type === "user_change") { const user = event.user!; const domain = (await this.main.datastore.getTeam(teamId))!.domain; diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index dd1d9a9a..4a2e30f3 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -406,6 +406,48 @@ export class TeamSyncer { } } + public async onChannelArchived(teamId: string, channelId: string): Promise { + log.info(`${teamId} archived channel ${channelId}`); + if (!this.getTeamSyncConfig(teamId, "channel", channelId)) { + return; + } + const room = this.main.rooms.getBySlackChannelId(channelId); + if (!room) { + log.warn("Not unlinking channel, no room found"); + return; + } + + // notify admins + if (this.adminRoom) { + await this.main.botIntent.sendMessage(this.adminRoom, { + msgtype: "m.notice", + body: `${teamId} archived channel ${channelId}, bridged room: ${room.MatrixRoomId}`, + }); + } + + try { + await this.main.botIntent.sendMessage(room.MatrixRoomId, { + msgtype: "m.notice", + body: "The Slack channel bridged to this room has been archived.", + }); + } catch (ex) { + log.warn("Failed to send archived notice into the room:", ex); + } + + // Hide deleted channels in the room directory. + try { + await this.main.botIntent.setRoomDirectoryVisibility(room.MatrixRoomId, "private"); + } catch (ex) { + log.warn("Failed to hide room from the room directory:", ex); + } + + try { + await this.main.actionUnlink({ matrix_room_id: room.MatrixRoomId }); + } catch (ex) { + log.warn("Tried to unlink room but failed:", ex); + } + } + public async syncMembershipForRoom(roomId: string, channelId: string, teamId: string, client: WebClient): Promise { const existingGhosts = await this.main.listGhostUsers(roomId); // We assume that we have this From fe1627b7d4cca082403ee93d28238d7d224562e6 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 10 Oct 2023 19:34:57 +0400 Subject: [PATCH 09/30] dedupe code from archive/deletion channel handlers --- src/TeamSyncer.ts | 53 ++++++++++++++--------------------------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index 4a2e30f3..f5d348a6 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -375,35 +375,8 @@ export class TeamSyncer { return; } - // notify admins - if (this.adminRoom) { - await this.main.botIntent.sendMessage(this.adminRoom, { - msgtype: "m.notice", - body: `${teamId} removed channel ${channelId}, bridged room: ${room.MatrixRoomId}`, - }); - } - - try { - await this.main.botIntent.sendMessage(room.MatrixRoomId, { - msgtype: "m.notice", - body: "The Slack channel bridged to this room has been deleted.", - }); - } catch (ex) { - log.warn("Failed to send deletion notice into the room:", ex); - } - - // Hide deleted channels in the room directory. - try { - await this.main.botIntent.setRoomDirectoryVisibility(room.MatrixRoomId, "private"); - } catch (ex) { - log.warn("Failed to hide room from the room directory:", ex); - } - - try { - await this.main.actionUnlink({ matrix_room_id: room.MatrixRoomId }); - } catch (ex) { - log.warn("Tried to unlink room but failed:", ex); - } + await this.notifyAdmins(`${teamId} removed channel ${channelId}, bridged room: ${room.MatrixRoomId}`); + await this.shutDownBridgedRoom("deleted", room.MatrixRoomId); } public async onChannelArchived(teamId: string, channelId: string): Promise { @@ -417,32 +390,38 @@ export class TeamSyncer { return; } - // notify admins + await this.notifyAdmins(`${teamId} archived channel ${channelId}, bridged room: ${room.MatrixRoomId}`); + await this.shutDownBridgedRoom("archived", room.MatrixRoomId); + } + + public async notifyAdmins(message: string) { if (this.adminRoom) { await this.main.botIntent.sendMessage(this.adminRoom, { msgtype: "m.notice", - body: `${teamId} archived channel ${channelId}, bridged room: ${room.MatrixRoomId}`, + body: message, }); } + } + public async shutDownBridgedRoom(reason: string, roomId: string) { try { - await this.main.botIntent.sendMessage(room.MatrixRoomId, { + await this.main.botIntent.sendMessage(roomId, { msgtype: "m.notice", - body: "The Slack channel bridged to this room has been archived.", + body: `The Slack channel bridged to this room has been \`${reason}\`.`, }); } catch (ex) { - log.warn("Failed to send archived notice into the room:", ex); + log.warn(`Failed to send \`${reason}\` notice into the room:`, ex); } - // Hide deleted channels in the room directory. + // Hide from room directory. try { - await this.main.botIntent.setRoomDirectoryVisibility(room.MatrixRoomId, "private"); + await this.main.botIntent.setRoomDirectoryVisibility(roomId, "private"); } catch (ex) { log.warn("Failed to hide room from the room directory:", ex); } try { - await this.main.actionUnlink({ matrix_room_id: room.MatrixRoomId }); + await this.main.actionUnlink({ matrix_room_id: roomId }); } catch (ex) { log.warn("Tried to unlink room but failed:", ex); } From f77f12a826326de1aeb254c73de5d8e8aa366007 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Wed, 11 Oct 2023 14:11:14 +0400 Subject: [PATCH 10/30] account for inconsistent event handling for channel_archive event --- src/SlackEventHandler.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/SlackEventHandler.ts b/src/SlackEventHandler.ts index 77726614..2dcc604f 100644 --- a/src/SlackEventHandler.ts +++ b/src/SlackEventHandler.ts @@ -211,7 +211,6 @@ export class SlackEventHandler extends BaseSlackHandler { break; case "channel_created": case "channel_deleted": - case "channel_archived": case "user_change": case "team_join": await this.handleTeamSyncEvent(event as ISlackTeamSyncEvent, teamId); @@ -244,6 +243,13 @@ export class SlackEventHandler extends BaseSlackHandler { return; } + if (event.subtype === "channel_archive") { + if (this.main.teamSyncer) { + await this.main.teamSyncer.onChannelArchived(teamId, event.channel); + return; + } + } + if (event.subtype !== "message_deleted" && event.message && event.message.subtype === "tombstone") { // Filter out tombstones early, we only care about them on deletion. throw Error("ignored"); @@ -368,8 +374,6 @@ export class SlackEventHandler extends BaseSlackHandler { await this.main.teamSyncer.onChannelAdded(teamId, eventDetails.channel.id, eventDetails.channel.name, eventDetails.channel.creator); } else if (event.type === "channel_deleted") { await this.main.teamSyncer.onChannelDeleted(teamId, event.channel); - } else if (event.type === "channel_archive") { - await this.main.teamSyncer.onChannelArchived(teamId, event.channel); } else if (event.type === "team_join" || event.type === "user_change") { const user = event.user!; const domain = (await this.main.datastore.getTeam(teamId))!.domain; From 65704854e459ccb62a2244daba8420952ec04057 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Wed, 11 Oct 2023 22:17:56 +0400 Subject: [PATCH 11/30] subscribe to member_left_channel event --- src/SlackEventHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SlackEventHandler.ts b/src/SlackEventHandler.ts index 2dcc604f..2cbcf270 100644 --- a/src/SlackEventHandler.ts +++ b/src/SlackEventHandler.ts @@ -113,8 +113,8 @@ export class SlackEventHandler extends BaseSlackHandler { * to events in order to handle them. */ protected static SUPPORTED_EVENTS: string[] = ["message", "reaction_added", "reaction_removed", - "team_domain_change", "channel_rename", "user_change", "user_typing", "member_joined_channel", "channel_created", "channel_deleted", "team_join"]; + "team_domain_change", "channel_rename", "user_change", "user_typing", "member_joined_channel", "member_left_channel", constructor(main: Main) { super(main); } From e184ded0477651bdde4acfcaa19e1f940d24241b Mon Sep 17 00:00:00 2001 From: Ashfame Date: Wed, 11 Oct 2023 22:20:55 +0400 Subject: [PATCH 12/30] subscribe to channel_archive event as well --- src/SlackEventHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SlackEventHandler.ts b/src/SlackEventHandler.ts index 2cbcf270..3c51f10a 100644 --- a/src/SlackEventHandler.ts +++ b/src/SlackEventHandler.ts @@ -113,8 +113,8 @@ export class SlackEventHandler extends BaseSlackHandler { * to events in order to handle them. */ protected static SUPPORTED_EVENTS: string[] = ["message", "reaction_added", "reaction_removed", - "channel_created", "channel_deleted", "team_join"]; "team_domain_change", "channel_rename", "user_change", "user_typing", "member_joined_channel", "member_left_channel", + "channel_created", "channel_deleted", "channel_archive", "team_join"]; constructor(main: Main) { super(main); } From d902cdde69c397a10e78254f0dec6f3a411e22ac Mon Sep 17 00:00:00 2001 From: Ashfame Date: Wed, 11 Oct 2023 22:20:58 +0400 Subject: [PATCH 13/30] Revert "account for inconsistent event handling for channel_archive event" This reverts commit f77f12a826326de1aeb254c73de5d8e8aa366007. we had to explicitly subscribe a handler to channel_archive event in RTM API --- src/SlackEventHandler.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/SlackEventHandler.ts b/src/SlackEventHandler.ts index 3c51f10a..f1b93e7e 100644 --- a/src/SlackEventHandler.ts +++ b/src/SlackEventHandler.ts @@ -211,6 +211,7 @@ export class SlackEventHandler extends BaseSlackHandler { break; case "channel_created": case "channel_deleted": + case "channel_archived": case "user_change": case "team_join": await this.handleTeamSyncEvent(event as ISlackTeamSyncEvent, teamId); @@ -243,13 +244,6 @@ export class SlackEventHandler extends BaseSlackHandler { return; } - if (event.subtype === "channel_archive") { - if (this.main.teamSyncer) { - await this.main.teamSyncer.onChannelArchived(teamId, event.channel); - return; - } - } - if (event.subtype !== "message_deleted" && event.message && event.message.subtype === "tombstone") { // Filter out tombstones early, we only care about them on deletion. throw Error("ignored"); @@ -374,6 +368,8 @@ export class SlackEventHandler extends BaseSlackHandler { await this.main.teamSyncer.onChannelAdded(teamId, eventDetails.channel.id, eventDetails.channel.name, eventDetails.channel.creator); } else if (event.type === "channel_deleted") { await this.main.teamSyncer.onChannelDeleted(teamId, event.channel); + } else if (event.type === "channel_archive") { + await this.main.teamSyncer.onChannelArchived(teamId, event.channel); } else if (event.type === "team_join" || event.type === "user_change") { const user = event.user!; const domain = (await this.main.datastore.getTeam(teamId))!.domain; From d2fb55d1ec6c8622fa33c75bb988cd223eecf777 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Wed, 11 Oct 2023 22:42:15 +0400 Subject: [PATCH 14/30] fix event name --- src/SlackEventHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SlackEventHandler.ts b/src/SlackEventHandler.ts index f1b93e7e..82dbeceb 100644 --- a/src/SlackEventHandler.ts +++ b/src/SlackEventHandler.ts @@ -211,7 +211,7 @@ export class SlackEventHandler extends BaseSlackHandler { break; case "channel_created": case "channel_deleted": - case "channel_archived": + case "channel_archive": case "user_change": case "team_join": await this.handleTeamSyncEvent(event as ISlackTeamSyncEvent, teamId); From e520141aa57955a4c7217331ebca7ee353c2e995 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Thu, 12 Oct 2023 13:47:14 +0400 Subject: [PATCH 15/30] notify us when a new bridged room is created upon a new slack channel as well --- src/TeamSyncer.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index f5d348a6..9ede98f5 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -231,6 +231,14 @@ export class TeamSyncer { const client = await this.main.clientFactory.getTeamClient(teamId); const { channel } = (await client.conversations.info({ channel: channelId })) as ConversationsInfoResponse; await this.syncChannel(teamId, channel); + const room = this.main.rooms.getBySlackChannelId(channelId); + if (!room) { + log.warn(`No bridged room found for new channel (${channelId}) after sync`); + await this.notifyAdmins(`${teamId} created channel ${channelId} but problem creating a bridge`); + return; + } + + await this.notifyAdmins(`${teamId} created channel ${channelId}, bridged room: ${room.MatrixRoomId}`); } public async onDiscoveredPrivateChannel(teamId: string, client: WebClient, chanInfo: ConversationsInfoResponse): Promise { From e8035fbbe21fe43895d14118f1a4dbf8bb4ccce3 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Fri, 13 Oct 2023 15:38:18 +0400 Subject: [PATCH 16/30] disable updating ghost users upon each slack message when team sync is enabled as user_change events are handled in realtime under team sync --- src/BridgedRoom.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/BridgedRoom.ts b/src/BridgedRoom.ts index db9f8e48..3abb8914 100644 --- a/src/BridgedRoom.ts +++ b/src/BridgedRoom.ts @@ -718,11 +718,23 @@ export class BridgedRoom { } try { const ghost = await this.main.ghostStore.getForSlackMessage(message, this.slackTeamId); - const ghostChanged = await ghost.update(message, this.SlackClient); await ghost.cancelTyping(this.MatrixRoomId); // If they were typing, stop them from doing that. - if (ghostChanged) { - await this.main.fixDMMetadata(this, ghost); + + let isTeamSyncEnabled = false; + for (const team in this.main.config.team_sync) { + if (team === "all" || team === this.slackTeamId ) { + if (this.main.config.team_sync[team].users?.enabled) { + isTeamSyncEnabled = true; + } + } } + if (!isTeamSyncEnabled) { + const ghostChanged = await ghost.update(message, this.SlackClient); + if (ghostChanged) { + await this.main.fixDMMetadata(this, ghost); + } + } + this.slackSendLock = this.slackSendLock.then(() => { // Check again if (!isMessageChangedEvent && this.recentSlackMessages.includes(message.ts)) { From 053c816fd31790f77b40874075acc8d78c7521e8 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 17 Oct 2023 12:12:41 +0400 Subject: [PATCH 17/30] better var name --- src/BridgedRoom.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BridgedRoom.ts b/src/BridgedRoom.ts index 3abb8914..e1bda58c 100644 --- a/src/BridgedRoom.ts +++ b/src/BridgedRoom.ts @@ -720,15 +720,15 @@ export class BridgedRoom { const ghost = await this.main.ghostStore.getForSlackMessage(message, this.slackTeamId); await ghost.cancelTyping(this.MatrixRoomId); // If they were typing, stop them from doing that. - let isTeamSyncEnabled = false; + let isTeamSyncEnabledForUsers = false; for (const team in this.main.config.team_sync) { if (team === "all" || team === this.slackTeamId ) { if (this.main.config.team_sync[team].users?.enabled) { - isTeamSyncEnabled = true; + isTeamSyncEnabledForUsers = true; } } } - if (!isTeamSyncEnabled) { + if (!isTeamSyncEnabledForUsers) { const ghostChanged = await ghost.update(message, this.SlackClient); if (ghostChanged) { await this.main.fixDMMetadata(this, ghost); From afb7a9d35fab19e4b361e877d635afe07c6bac15 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 17 Oct 2023 12:50:55 +0400 Subject: [PATCH 18/30] move notifyAdmins func to Main --- src/Main.ts | 16 +++++++++------- src/TeamSyncer.ts | 17 ++++------------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/Main.ts b/src/Main.ts index 2be270e6..32965668 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -1289,13 +1289,7 @@ export class Main { } log.info("Bridge initialised"); - // notify admins - if (this.config.matrix_admin_room) { - void this.botIntent.sendMessage(this.config.matrix_admin_room,{ - msgtype: "m.notice", - body: "Bridge initialised", - }); - } + await this.notifyAdmins("Bridge initialised"); this.ready = true; return port; @@ -1779,4 +1773,12 @@ export class Main { log.info("Enabled RTM"); } + public async notifyAdmins(message: string) { + if (this.config.matrix_admin_room) { + await this.botIntent.sendMessage(this.config.matrix_admin_room, { + msgtype: "m.notice", + body: message, + }); + } + } } diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index 9ede98f5..55879740 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -234,11 +234,11 @@ export class TeamSyncer { const room = this.main.rooms.getBySlackChannelId(channelId); if (!room) { log.warn(`No bridged room found for new channel (${channelId}) after sync`); - await this.notifyAdmins(`${teamId} created channel ${channelId} but problem creating a bridge`); + await this.main.notifyAdmins(`${teamId} created channel ${channelId} but problem creating a bridge`); return; } - await this.notifyAdmins(`${teamId} created channel ${channelId}, bridged room: ${room.MatrixRoomId}`); + await this.main.notifyAdmins(`${teamId} created channel ${channelId}, bridged room: ${room.MatrixRoomId}`); } public async onDiscoveredPrivateChannel(teamId: string, client: WebClient, chanInfo: ConversationsInfoResponse): Promise { @@ -383,7 +383,7 @@ export class TeamSyncer { return; } - await this.notifyAdmins(`${teamId} removed channel ${channelId}, bridged room: ${room.MatrixRoomId}`); + await this.main.notifyAdmins(`${teamId} removed channel ${channelId}, bridged room: ${room.MatrixRoomId}`); await this.shutDownBridgedRoom("deleted", room.MatrixRoomId); } @@ -398,19 +398,10 @@ export class TeamSyncer { return; } - await this.notifyAdmins(`${teamId} archived channel ${channelId}, bridged room: ${room.MatrixRoomId}`); + await this.main.notifyAdmins(`${teamId} archived channel ${channelId}, bridged room: ${room.MatrixRoomId}`); await this.shutDownBridgedRoom("archived", room.MatrixRoomId); } - public async notifyAdmins(message: string) { - if (this.adminRoom) { - await this.main.botIntent.sendMessage(this.adminRoom, { - msgtype: "m.notice", - body: message, - }); - } - } - public async shutDownBridgedRoom(reason: string, roomId: string) { try { await this.main.botIntent.sendMessage(roomId, { From c0874165948d2385f57c8efa11e5542d21382a84 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Date: Tue, 17 Oct 2023 12:52:05 +0400 Subject: [PATCH 19/30] Update src/TeamSyncer.ts Co-authored-by: Paulo Pinto --- src/TeamSyncer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index 55879740..cfc80a50 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -406,7 +406,7 @@ export class TeamSyncer { try { await this.main.botIntent.sendMessage(roomId, { msgtype: "m.notice", - body: `The Slack channel bridged to this room has been \`${reason}\`.`, + body: `The Slack channel bridged to this room has been '${reason}'.`, }); } catch (ex) { log.warn(`Failed to send \`${reason}\` notice into the room:`, ex); From bf0c565d6b9d500f0f7d130ea4f1740e4c37e3dc Mon Sep 17 00:00:00 2001 From: Ashish Kumar Date: Tue, 17 Oct 2023 12:52:21 +0400 Subject: [PATCH 20/30] Update src/TeamSyncer.ts Co-authored-by: Paulo Pinto --- src/TeamSyncer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index cfc80a50..2d6345d4 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -409,7 +409,7 @@ export class TeamSyncer { body: `The Slack channel bridged to this room has been '${reason}'.`, }); } catch (ex) { - log.warn(`Failed to send \`${reason}\` notice into the room:`, ex); + log.warn(`Failed to send '${reason}' notice into the room:`, ex); } // Hide from room directory. From 7bee73b8fcf9a1a5ea412eb738bef12f9335417f Mon Sep 17 00:00:00 2001 From: Ashish Kumar Date: Tue, 17 Oct 2023 12:53:02 +0400 Subject: [PATCH 21/30] Update src/TeamSyncer.ts Co-authored-by: Paulo Pinto --- src/TeamSyncer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index 2d6345d4..b068823a 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -512,8 +512,8 @@ export class TeamSyncer { try { await client.chat.postEphemeral({ user: channelItem.creator, - text: `Please invite \`@${user.name}\` app to this channel (\`/invite @${user.name}\`) so that ` + - `this channel is also available on Matrix side.`, + text: `Please invite \`@${user.name}\` to this channel (\`/invite @${user.name}\`) so that ` + + `the channel is also available on Matrix.`, channel: channelItem.id, }); } catch (error) { From c4f22ac2a7d7defe95278ad81c3354549d17b554 Mon Sep 17 00:00:00 2001 From: Ashish Kumar Date: Tue, 17 Oct 2023 12:55:57 +0400 Subject: [PATCH 22/30] Update src/TeamSyncer.ts Co-authored-by: Paulo Pinto --- src/TeamSyncer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index b068823a..e1e82284 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -553,6 +553,7 @@ export class TeamSyncer { mods = teamConfig?.rooms?.moderators; admins = teamConfig?.rooms?.administrators; matrixCreator = teamConfig?.rooms?.creator; + break; } } From 0dfbb4f7a000df65ceba14483af4d449ba8b4675 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 17 Oct 2023 17:46:24 +0400 Subject: [PATCH 23/30] improve performance of membership sync at boot --- src/Main.ts | 13 +++++++++ src/TeamSyncer.ts | 41 +++++++++++++++++---------- src/datastore/Models.ts | 1 + src/datastore/NedbDatastore.ts | 4 +++ src/datastore/postgres/PgDatastore.ts | 7 +++++ 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/Main.ts b/src/Main.ts index 32965668..eb1e6874 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -640,6 +640,19 @@ export class Main { return userIds.filter((i) => i.match(regexp)); } + public async listGhostAndMappedUsers(roomId: string): Promise { + const userIds = await this.listAllUsers(roomId); + const mappedUsernames = await this.datastore.getAllMatrixUsernames(); + + const mappedUsersSet = new Set(); + for (const mappedUsername of mappedUsernames ?? []) { + mappedUsersSet.add("@" + mappedUsername + ":" + this.config.homeserver.server_name); + } + + const regexp = new RegExp("^@" + this.config.username_prefix); + return userIds.filter((userId) => mappedUsersSet.has(userId) || userId.match(regexp)); + } + public async drainAndLeaveMatrixRoom(roomId: string): Promise { const userIds = await this.listGhostUsers(roomId); log.info(`Draining ${userIds.length} ghosts from ${roomId}`); diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index e1e82284..5c83e84a 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -427,27 +427,38 @@ export class TeamSyncer { } public async syncMembershipForRoom(roomId: string, channelId: string, teamId: string, client: WebClient): Promise { - const existingGhosts = await this.main.listGhostUsers(roomId); - // We assume that we have this const teamInfo = (await this.main.datastore.getTeam(teamId)); if (!teamInfo) { throw Error("Could not find team"); } - // Finally, sync membership for the channel. - const members = await client.conversations.members({channel: channelId}) as ConversationsMembersResponse; - // Ghosts will exist already: We joined them in the user sync. - const ghosts = await Promise.all(members.members.map(async(slackUserId) => this.main.ghostStore.get(slackUserId, teamInfo.domain, teamId))); - - const joinedUsers = ghosts.filter((g) => !existingGhosts.includes(g.matrixUserId)).map(g => g.matrixUserId); - const leftUsers = existingGhosts.map((userId, index) => { - const ghost = ghosts.find((g) => g.matrixUserId === userId); - return ghost === undefined ? index : null; - }).filter(index => index !== null).map(index => - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - existingGhosts[index] + + // create Set for both matrix membership state and slack membership state + // compare them to figure out who all needs to join the matrix room and leave the matrix room + // this obviously assumes we treat slack as the source of truth for membership + const existingMatrixUsersSet = new Set(); + const slackUsersSet = new Set(); + + const existingMatrixUsers = await this.main.listGhostAndMappedUsers(roomId); + for (const u of existingMatrixUsers) { + existingMatrixUsersSet.add(u); + } + + const slackUsers = await client.conversations.members({channel: channelId}) as ConversationsMembersResponse; + await Promise.all( + slackUsers.members.map(async(slackUserId) => { + const ghost = await this.main.ghostStore.get(slackUserId, teamInfo.domain, teamId); + slackUsersSet.add(ghost.matrixUserId); + }) ); + const joinedUsers: string[] = []; + slackUsersSet.forEach((u) => { + if (!existingMatrixUsersSet.has(u)) { + joinedUsers.push(u); + } + }); + const leftUsers = existingMatrixUsers.filter((userId) => !slackUsersSet.has(userId)); + log.info(`Joining ${joinedUsers.length} ghosts to ${roomId}`,joinedUsers); log.info(`Leaving ${leftUsers.length} ghosts to ${roomId}`,leftUsers); diff --git a/src/datastore/Models.ts b/src/datastore/Models.ts index 27152d3d..5565d09e 100644 --- a/src/datastore/Models.ts +++ b/src/datastore/Models.ts @@ -98,6 +98,7 @@ export interface Datastore extends ProvisioningStore { getMatrixUser(userId: string): Promise; getMatrixUsername(slackUserId: string): Promise; setMatrixUsername(slackUserId: string, matrixUsername: string): Promise; + getAllMatrixUsernames(): Promise; storeMatrixUser(user: MatrixUser): Promise; getAllUsersForTeam(teamId: string): Promise; diff --git a/src/datastore/NedbDatastore.ts b/src/datastore/NedbDatastore.ts index 30344db6..17d0ab79 100644 --- a/src/datastore/NedbDatastore.ts +++ b/src/datastore/NedbDatastore.ts @@ -172,6 +172,10 @@ export class NedbDatastore implements Datastore { throw Error("method not implemented"); } + public async getAllMatrixUsernames(): Promise { + throw Error("method not implemented"); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public async setMatrixUsername(slackUserId: string, matrixUsername: string): Promise { throw Error("method not implemented"); diff --git a/src/datastore/postgres/PgDatastore.ts b/src/datastore/postgres/PgDatastore.ts index 4e5802db..f7afff20 100644 --- a/src/datastore/postgres/PgDatastore.ts +++ b/src/datastore/postgres/PgDatastore.ts @@ -113,6 +113,13 @@ export class PgDatastore implements Datastore, ClientEncryptionStore, Provisioni ); } + public async getAllMatrixUsernames(): Promise { + const all = await this.postgresDb.manyOrNone( + "SELECT matrix_username FROM matrix_usernames" + ); + return all.map((dbEntry) => dbEntry ? dbEntry.matrix_username : null); + } + public async getAllUsersForTeam(teamId: string): Promise { const users = await this.postgresDb.manyOrNone("SELECT json FROM users WHERE json::json->>'team_id' = ${teamId}", { teamId, From fb909d7989473098a71f0c47c27fdb3b7f7186a4 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 17 Oct 2023 17:57:35 +0400 Subject: [PATCH 24/30] move power levels code a bit above --- src/TeamSyncer.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index 5c83e84a..cf5f62ed 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -586,13 +586,7 @@ export class TeamSyncer { intent = this.main.botIntent; } } - const aliasPrefix = this.getAliasPrefix(teamId); - const alias = aliasPrefix ? `${aliasPrefix}${channel.name.toLowerCase()}` : undefined; - let topic: undefined|string; - if (channel.purpose) { - topic = channel.purpose.value; - } - log.debug("Creating new room for channel", channel.name, topic, alias); + const plUsers = {}; for (const mod of mods ?? []) { plUsers[mod] = 50; @@ -600,6 +594,15 @@ export class TeamSyncer { for (const admin of admins ?? []) { plUsers[admin] = 100; } + + const aliasPrefix = this.getAliasPrefix(teamId); + const alias = aliasPrefix ? `${aliasPrefix}${channel.name.toLowerCase()}` : undefined; + let topic: undefined|string; + if (channel.purpose) { + topic = channel.purpose.value; + } + + log.debug("Creating new room for channel", channel.name, topic, alias); inviteList = inviteList.filter((s) => s !== creatorUserId || s !== this.main.botUserId); inviteList.push(this.main.botUserId); const extraContent: Record[] = []; From fcbe71bcecde5e2a6f16eb3ad0b053c004e25d31 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 17 Oct 2023 18:33:43 +0400 Subject: [PATCH 25/30] improve code organisation in createRoomForChannel --- src/TeamSyncer.ts | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index cf5f62ed..75a1e333 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -552,41 +552,46 @@ export class TeamSyncer { private async createRoomForChannel(teamId: string, creator: string, channel: ConversationsInfo, isPublic = true, inviteList: string[] = []): Promise { let intent: Intent; - let creatorUserId: string|undefined; let admins: string[] | undefined; let mods: string[] | undefined; - let matrixCreator: string | undefined; + let creatorFromConfig: string | undefined; for (const team in this.teamConfigs) { if (team === "all" || team === teamId ) { const teamConfig = this.teamConfigs[team]; mods = teamConfig?.rooms?.moderators; admins = teamConfig?.rooms?.administrators; - matrixCreator = teamConfig?.rooms?.creator; + creatorFromConfig = teamConfig?.rooms?.creator; break; } } + // default behavior of bot user being admin on room admins?.push(this.main.botUserId); - if (matrixCreator) { - admins?.push(matrixCreator); + + // creator specified in config should be an admin + if (creatorFromConfig) { + admins?.push(creatorFromConfig); } - if (matrixCreator) { - intent = this.main.getIntent(matrixCreator); - } else { + let creatorUserId = creatorFromConfig; + if (!creatorUserId) { try { creatorUserId = (await this.main.ghostStore.get(creator, undefined, teamId)).matrixUserId; - intent = this.main.getIntent(creatorUserId); - - mods?.push(creatorUserId); // make slack channel creator (user) a mod as well + mods?.push(creatorUserId); // make Slack channel creator (user) a mod as well } catch (ex) { - // Couldn't get the creator's mxid, using the bot. - intent = this.main.botIntent; + // Couldn't get the creator's mxid, will default to the bot below. } } + if (creatorUserId) { + intent = this.main.getIntent(creatorUserId); + } else { + intent = this.main.botIntent; + } + + // power levels const plUsers = {}; for (const mod of mods ?? []) { plUsers[mod] = 50; From 12c63a0f47929e4ff755cdc2f36f95192ae9faa3 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 17 Oct 2023 18:49:12 +0400 Subject: [PATCH 26/30] notify us if unlinking fails, so that we can manually act --- src/TeamSyncer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TeamSyncer.ts b/src/TeamSyncer.ts index 75a1e333..3d55eb97 100644 --- a/src/TeamSyncer.ts +++ b/src/TeamSyncer.ts @@ -423,6 +423,7 @@ export class TeamSyncer { await this.main.actionUnlink({ matrix_room_id: roomId }); } catch (ex) { log.warn("Tried to unlink room but failed:", ex); + await this.main.notifyAdmins(`failed to unlink bridge on ${roomId}`); } } From 1db0e5afd036dfadaaa7c75ea09c7911ff0bda45 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 17 Oct 2023 18:52:54 +0400 Subject: [PATCH 27/30] define new method on fakeDatastore for tests to pass --- tests/utils/fakeDatastore.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/utils/fakeDatastore.ts b/tests/utils/fakeDatastore.ts index 08077d42..2d524ca4 100644 --- a/tests/utils/fakeDatastore.ts +++ b/tests/utils/fakeDatastore.ts @@ -214,4 +214,8 @@ export class FakeDatastore implements Datastore { async setMatrixUsername(slackUserId: string, matrixUsername: string): Promise { throw new Error("Method not implemented."); } + + async getAllMatrixUsernames(): Promise { + throw new Error("Method not implemented."); + } } From e002c3baf6bce1217a74cf54910e3b10b5e0d3d3 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Tue, 17 Oct 2023 18:59:23 +0400 Subject: [PATCH 28/30] log if fails to notify admins, makes integration test pass --- src/Main.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Main.ts b/src/Main.ts index eb1e6874..edb5168f 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -1788,10 +1788,14 @@ export class Main { public async notifyAdmins(message: string) { if (this.config.matrix_admin_room) { - await this.botIntent.sendMessage(this.config.matrix_admin_room, { - msgtype: "m.notice", - body: message, - }); + try { + await this.botIntent.sendMessage(this.config.matrix_admin_room, { + msgtype: "m.notice", + body: message, + }); + } catch(ex) { + log.warn("failed to notify admins", message); + } } } } From 88963fefc5b724e158a272352ce34ef5983034b0 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Wed, 18 Oct 2023 17:02:47 +0400 Subject: [PATCH 29/30] document team sync modifications in readme --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 957d0ad6..087cfd48 100644 --- a/README.md +++ b/README.md @@ -158,3 +158,12 @@ The endpoint should respond with `401` when the secret doesn't match, `404` when "matrix": "janedoe" } ``` + +## Team Sync Modifications + +- Don't update Slack ghost users upon every message as they are already handled in realtime under team sync +- Fix bug in membership change calculation when syncing channels upon boot +- Handle `channel_archive` slack event +- Define `rooms` field under teamsync config for who should be the creator, mods and admins in new rooms created by team sync +- Tweak message that gets posted in a new channel to suggest inviting `matrixbridge` Slack app +- Notify admins in admin room for bridge when bridge initialises upon boot, when a Slack channel is created/archived/deleted and unlinking of bridge fails upon channel archive/delete event. From 28f6b855d9c80baadc2577d945934acc83fce642 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Wed, 18 Oct 2023 18:00:24 +0400 Subject: [PATCH 30/30] move readme section --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 087cfd48..7ccf340c 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,15 @@ In addition to all features of the upstream bridge, this fork adds the following - Fix issue that caused files sent to a thread on Slack to be posted on the main timeline on Matrix [[#20](https://github.com/Automattic/matrix-appservice-slack/pull/20)] [[upstream issue #671](https://github.com/matrix-org/matrix-appservice-slack/issues/671)] - Fix issue that caused channel name to not be displayed in the output of the `link` and `list` admin commands [[upstream fix #756](https://github.com/matrix-org/matrix-appservice-slack/pull/756)] +## Team Sync Modifications + +- Don't update Slack ghost users upon every message as they are already handled in realtime under team sync. +- Fix bug in membership change calculation when syncing channels upon boot. +- Handle `channel_archive` slack event. +- Define `rooms` field under teamsync config for who should be the creator, mods and admins in new rooms created by team sync. +- Tweak message that gets posted in a new channel to suggest inviting `matrixbridge` Slack app. +- Notify admins in admin room for bridge when bridge initialises upon boot, when a Slack channel is created/archived/deleted and unlinking of bridge fails upon channel archive/delete event. + ## Usage This fork is a drop-in replacement for the upstream bridge, so the setup instructions are the same as upstream. The only difference is of course that you need to get the code from this fork: @@ -158,12 +167,3 @@ The endpoint should respond with `401` when the secret doesn't match, `404` when "matrix": "janedoe" } ``` - -## Team Sync Modifications - -- Don't update Slack ghost users upon every message as they are already handled in realtime under team sync -- Fix bug in membership change calculation when syncing channels upon boot -- Handle `channel_archive` slack event -- Define `rooms` field under teamsync config for who should be the creator, mods and admins in new rooms created by team sync -- Tweak message that gets posted in a new channel to suggest inviting `matrixbridge` Slack app -- Notify admins in admin room for bridge when bridge initialises upon boot, when a Slack channel is created/archived/deleted and unlinking of bridge fails upon channel archive/delete event.