diff --git a/src/bot.ts b/src/bot.ts index daaceb10d245fb2fd9f88ccd453f313c510424a0..107fad21f993ca86f0925e29abab2b77b0171dde 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -446,19 +446,23 @@ export class DiscordBot { } } - public async GetChannelFromRoomId(roomId: string): Promise<Discord.Channel> { + public async GetChannelFromRoomId(roomId: string, client?: Discord.Client): Promise<Discord.Channel> { const entries = await this.bridge.getRoomStore().getEntriesByMatrixId( roomId, ); + if (!client) { + client = this.bot; + } + if (entries.length === 0) { log.verbose(`Couldn"t find channel for roomId ${roomId}.`); throw Error("Room(s) not found."); } const entry = entries[0]; - const guild = this.bot.guilds.get(entry.remote.get("discord_guild")); + const guild = client.guilds.get(entry.remote.get("discord_guild")); if (guild) { - const channel = this.bot.channels.get(entry.remote.get("discord_channel")); + const channel = client.channels.get(entry.remote.get("discord_channel")); if (channel) { return channel; } @@ -499,6 +503,60 @@ export class DiscordBot { return rooms.map((room) => room.matrix.getId()); } + public async handleMatrixKickBan(roomId: string, kickeeUserId: string, kicker: string, kickban: "kick"|"ban", reason?: string) { + const kickeeUser = (await this.GetDiscordUserOrMember( + new MatrixUser(kickeeUserId.replace("@", "")).localpart.substring("_discord".length) + ))!; + if (!kickeeUser || kickeeUser instanceof Discord.User) { + log.error("Could not find discord user for", kicker); + return; + } + const kickee = kickeeUser as Discord.GuildMember; + const client = await this.clientFactory.getClient(kicker); + let channel: Discord.Channel; + try { + channel = await this.GetChannelFromRoomId(roomId, client); + } catch (ex) { + log.error("Failed to get channel for ", roomId, ex); + return; + } + if (channel.type !== "text") { + log.warn("Channel was not a text channel"); + return; + } + const tchan = (channel as Discord.TextChannel); + const existingPerms = tchan.memberPermissions(kickee); + if (existingPerms && existingPerms.has(Discord.Permissions.FLAGS.VIEW_CHANNEL as number) === false ) { + log.warn("User isn't allowed to read anyway."); + return; + } + await tchan.send( + `${kickee} was ${kickban === "ban" ? "banned" : "kicked"} from this channel by ${kickeeUserId}.` + + (reason ? ` Reason: ${reason}` : "") + ); + log.info(`${kickban === "ban" ? "Banning" : "Kicking"} ${kickee}`); + + await tchan.overwritePermissions(kickee, + { + VIEW_CHANNEL: false, + SEND_MESSAGES: false, + }, + `Matrix user was ${kickban} by ${kicker}`); + if (kickban === "kick") { + // Kicks will let the user back in after ~30 seconds. + setTimeout(async () => { + log.info(`Kick was lifted for ${kickee.displayName}`); + await tchan.overwritePermissions(kickee, + { + VIEW_CHANNEL: null, + SEND_MESSAGES: null, + } as any, // XXX: Discord.js typings are wrong. + `Lifting kick for since duration expired.`); + }, this.config.room.kickFor); + } + + } + private async SendMatrixMessage(matrixMsg: MessageProcessorMatrixResult, chan: Discord.Channel, guild: Discord.Guild, author: Discord.User, msgID: string): Promise<boolean> { diff --git a/src/clientfactory.ts b/src/clientfactory.ts index b3b9a907447397b054bdbfd6e01ef65c62c96e26..fe4828254917734d12b4db8fb63feeb45983e317 100644 --- a/src/clientfactory.ts +++ b/src/clientfactory.ts @@ -3,7 +3,6 @@ import { DiscordStore } from "./store"; import { Client as DiscordClient } from "discord.js"; import * as Bluebird from "bluebird"; import { Log } from "./log"; -import { Client as MatrixClient } from "matrix-js-sdk"; const log = new Log("ClientFactory"); @@ -12,8 +11,8 @@ const READY_TIMEOUT = 5000; export class DiscordClientFactory { private config: DiscordBridgeConfigAuth; private store: DiscordStore; - private botClient: MatrixClient; - private clients: Map<string, MatrixClient>; + private botClient: DiscordClient; + private clients: Map<string, DiscordClient>; constructor(store: DiscordStore, config?: DiscordBridgeConfigAuth) { this.config = config!; this.clients = new Map(); @@ -62,13 +61,13 @@ export class DiscordClientFactory { }); } - public async getClient(userId: string | null = null): Promise<MatrixClient> { + public async getClient(userId: string | null = null): Promise<DiscordClient> { if (userId === null) { return this.botClient; } if (this.clients.has(userId)) { log.verbose("Returning cached user client for", userId); - return this.clients.get(userId); + return this.clients.get(userId) as DiscordClient; } const discordIds = await this.store.get_user_discord_ids(userId); if (discordIds.length === 0) { @@ -76,11 +75,11 @@ export class DiscordClientFactory { } // TODO: Select a profile based on preference, not the first one. const token = await this.store.get_token(discordIds[0]); - const client = Bluebird.promisifyAll(new DiscordClient({ + const client = new DiscordClient({ fetchAllMembers: true, messageCacheLifetime: 5, sync: true, - })); + }); const jsLog = new Log("discord.js-ppt"); client.on("debug", (msg) => { jsLog.verbose(msg); }); client.on("error", (msg) => { jsLog.error(msg); }); diff --git a/src/config.ts b/src/config.ts index c5dfb88b5bba1b70d81cd647da0e8f8923381211..e9cec8ac0ef78f63d2e545e5beeec64e2141de2a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -59,6 +59,7 @@ export class DiscordBridgeConfigLogging { class DiscordBridgeConfigRoom { public defaultVisibility: string; + public kickFor:number = 30000; } class DiscordBridgeConfigChannel { diff --git a/src/matrixroomhandler.ts b/src/matrixroomhandler.ts index 885c687b1c86a816ecf2a5b8153ee6d12fc662f0..8df1d5f4b4d364955f5171f92e45e6cd8f3881bf 100644 --- a/src/matrixroomhandler.ts +++ b/src/matrixroomhandler.ts @@ -123,11 +123,17 @@ export class MatrixRoomHandler { if (event.type === "m.room.member" && event.content!.membership === "invite") { await this.HandleInvite(event); return; - } else if (event.type === "m.room.member" && event.content!.membership === "join") { - if (this.bridge.getBot().isRemoteUser(event.state_key)) { + } else if (event.type === "m.room.member" && this.bridge.getBot().isRemoteUser(event.state_key)) { + if (event.content!.membership !== undefined && event.content!.membership === "join") { await this.discord.UserSyncroniser.OnMemberState(event, USERSYNC_STATE_DELAY_MS); - } else { - await this.discord.ProcessMatrixStateEvent(event); + } else if (["kick", "ban"].includes(event.content!.membership!)) { + // Kick/Ban handling + await this.discord.handleMatrixKickBan( + event.room_id, + event.state_key, + event.sender, + event.content!.membership as "kick"|"ban", + ); } return; } else if (["m.room.member", "m.room.name", "m.room.topic"].includes(event.type)) { diff --git a/src/matrixtypes.ts b/src/matrixtypes.ts index 4efbf8395084b61ef852d59886b6eb5e9cb5058e..3e678dcd477b2a7668b72761f5fd7409bd341885 100644 --- a/src/matrixtypes.ts +++ b/src/matrixtypes.ts @@ -7,6 +7,7 @@ export interface IMatrixEventContent { msgtype?: string; url?: string; displayname?: string; + reason?: string; "m.relates_to"?: any; // tslint:disable-line no-any }