From 400af48e6e271d7ee1939398a7cff3111152ee66 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Mon, 27 Sep 2021 15:52:28 +0100 Subject: [PATCH] Move Mjolnir setup and configuration to a sensible place. --- src/Mjolnir.ts | 83 +++++++++++++++++++++++ src/index.ts | 4 +- src/setup.ts | 95 --------------------------- test/integration/mjolnirSetupUtils.ts | 4 +- 4 files changed, 87 insertions(+), 99 deletions(-) delete mode 100644 src/setup.ts diff --git a/src/Mjolnir.ts b/src/Mjolnir.ts index c5db3464..844dbdee 100644 --- a/src/Mjolnir.ts +++ b/src/Mjolnir.ts @@ -21,6 +21,7 @@ import { LogService, MatrixClient, MatrixGlob, + MembershipEvent, Permalinks, UserID } from "matrix-bot-sdk"; @@ -37,6 +38,7 @@ import { PROTECTIONS } from "./protections/protections"; import { UnlistedUserRedactionQueue } from "./queues/UnlistedUserRedactionQueue"; import { Healthz } from "./health/healthz"; import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQueue"; +import * as htmlEscape from "escape-html"; export const STATE_NOT_STARTED = "not_started"; export const STATE_CHECKING_PERMISSIONS = "checking_permissions"; @@ -69,6 +71,84 @@ export class Mjolnir { private explicitlyProtectedRoomIds: string[] = []; private knownUnprotectedRooms: string[] = []; + /** + * Adds a listener to the client that will automatically accept invitations. + * @param {MatrixClient} client + * @param options By default accepts invites from anyone. + * @param {string} options.managementRoom The room to report ignored invitations to if `recordIgnoredInvites` is true. + * @param {boolean} options.recordIgnoredInvites Whether to report invites that will be ignored to the `managementRoom`. + * @param {boolean} options.autojoinOnlyIfManager Whether to only accept an invitation by a user present in the `managementRoom`. + * @param {string} options.acceptInvitesFromGroup A group of users to accept invites from, ignores invites form users not in this group. + */ + private static addJoinOnInviteListener(client: MatrixClient, options) { + client.on("room.invite", async (roomId: string, inviteEvent: any) => { + const membershipEvent = new MembershipEvent(inviteEvent); + + const reportInvite = async () => { + if (!options.recordIgnoredInvites) return; // Nothing to do + + await client.sendMessage(options.managementRoom, { + msgtype: "m.text", + body: `${membershipEvent.sender} has invited me to ${roomId} but the config prevents me from accepting the invitation. ` + + `If you would like this room protected, use "!mjolnir rooms add ${roomId}" so I can accept the invite.`, + format: "org.matrix.custom.html", + formatted_body: `${htmlEscape(membershipEvent.sender)} has invited me to ${htmlEscape(roomId)} but the config prevents me from ` + + `accepting the invitation. If you would like this room protected, use !mjolnir rooms add ${htmlEscape(roomId)} ` + + `so I can accept the invite.`, + }); + }; + + if (options.autojoinOnlyIfManager) { + const managers = await client.getJoinedRoomMembers(options.managementRoom); + if (!managers.includes(membershipEvent.sender)) return reportInvite(); // ignore invite + } else { + const groupMembers = await client.unstableApis.getGroupUsers(options.acceptInvitesFromGroup); + const userIds = groupMembers.map(m => m.user_id); + if (!userIds.includes(membershipEvent.sender)) return reportInvite(); // ignore invite + } + + return client.joinRoom(roomId); + }); + } + + /** + * Create a new Mjolnir instance from a client and the options in the configuration file, ready to be started. + * @param {MatrixClient} client The client for Mjolnir to use. + * @returns A new Mjolnir instance that can be started without further setup. + */ + static async setupMjolnirFromConfig(client: MatrixClient): Promise { + Mjolnir.addJoinOnInviteListener(client, config); + + const banLists: BanList[] = []; + const protectedRooms: { [roomId: string]: string } = {}; + const joinedRooms = await client.getJoinedRooms(); + // Ensure we're also joined to the rooms we're protecting + LogService.info("index", "Resolving protected rooms..."); + for (const roomRef of config.protectedRooms) { + const permalink = Permalinks.parseUrl(roomRef); + if (!permalink.roomIdOrAlias) continue; + + let roomId = await client.resolveRoom(permalink.roomIdOrAlias); + if (!joinedRooms.includes(roomId)) { + roomId = await client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers); + } + + protectedRooms[roomId] = roomRef; + } + + // Ensure we're also in the management room + LogService.info("index", "Resolving management room..."); + const managementRoomId = await client.resolveRoom(config.managementRoom); + if (!joinedRooms.includes(managementRoomId)) { + config.managementRoom = await client.joinRoom(config.managementRoom); + } else { + config.managementRoom = managementRoomId; + } + await logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status."); + + return new Mjolnir(client, protectedRooms, banLists); + } + constructor( public readonly client: MatrixClient, public readonly protectedRooms: { [roomId: string]: string }, @@ -209,6 +289,9 @@ export class Mjolnir { }); } + /** + * Stop Mjolnir from syncing and processing commands. + */ public stop() { this.client.stop(); } diff --git a/src/index.ts b/src/index.ts index 987df082..11550b87 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,7 +26,7 @@ import { import config from "./config"; import { logMessage } from "./LogProxy"; import { Healthz } from "./health/healthz"; -import { setupMjolnir } from "./setup"; +import { Mjolnir } from "./Mjolnir"; config.RUNTIME = {}; @@ -54,7 +54,7 @@ if (config.health.healthz.enabled) { config.RUNTIME.client = client; - let bot = await setupMjolnir(client, config); + let bot = await Mjolnir.setupMjolnirFromConfig(client); await bot.start(); })().catch(err => { logMessage(LogLevel.ERROR, "index", err); diff --git a/src/setup.ts b/src/setup.ts deleted file mode 100644 index 3485592b..00000000 --- a/src/setup.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2019-2021 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { LogLevel, LogService, MatrixClient, Permalinks } from "matrix-bot-sdk"; -import { MembershipEvent } from "matrix-bot-sdk/lib/models/events/MembershipEvent"; -import * as htmlEscape from "escape-html"; -import BanList from "./models/BanList"; -import { logMessage } from "./LogProxy"; -import { Mjolnir } from "./Mjolnir"; - -/** - * Adds a listener to the client that will automatically accept invitations. - * @param {MatrixClient} client - * @param options By default accepts invites from anyone. - * @param {string} options.managementRoom The room to report ignored invitations to if `recordIgnoredInvites` is true. - * @param {boolean} options.recordIgnoredInvites Whether to report invites that will be ignored to the `managementRoom`. - * @param {boolean} options.autojoinOnlyIfManager Whether to only accept an invitation by a user present in the `managementRoom`. - * @param {string} options.acceptInvitesFromGroup A group of users to accept invites from, ignores invites form users not in this group. - */ -function addJoinOnInviteListener(client: MatrixClient, options) { - client.on("room.invite", async (roomId: string, inviteEvent: any) => { - const membershipEvent = new MembershipEvent(inviteEvent); - - const reportInvite = async () => { - if (!options.recordIgnoredInvites) return; // Nothing to do - - await client.sendMessage(options.managementRoom, { - msgtype: "m.text", - body: `${membershipEvent.sender} has invited me to ${roomId} but the config prevents me from accepting the invitation. ` - + `If you would like this room protected, use "!mjolnir rooms add ${roomId}" so I can accept the invite.`, - format: "org.matrix.custom.html", - formatted_body: `${htmlEscape(membershipEvent.sender)} has invited me to ${htmlEscape(roomId)} but the config prevents me from ` - + `accepting the invitation. If you would like this room protected, use !mjolnir rooms add ${htmlEscape(roomId)} ` - + `so I can accept the invite.`, - }); - }; - - if (options.autojoinOnlyIfManager) { - const managers = await client.getJoinedRoomMembers(options.managementRoom); - if (!managers.includes(membershipEvent.sender)) return reportInvite(); // ignore invite - } else { - const groupMembers = await client.unstableApis.getGroupUsers(options.acceptInvitesFromGroup); - const userIds = groupMembers.map(m => m.user_id); - if (!userIds.includes(membershipEvent.sender)) return reportInvite(); // ignore invite - } - - return client.joinRoom(roomId); - }); -} - -export async function setupMjolnir(client, config): Promise { - addJoinOnInviteListener(client, config); - - const banLists: BanList[] = []; - const protectedRooms: { [roomId: string]: string } = {}; - const joinedRooms = await client.getJoinedRooms(); - // Ensure we're also joined to the rooms we're protecting - LogService.info("index", "Resolving protected rooms..."); - for (const roomRef of config.protectedRooms) { - const permalink = Permalinks.parseUrl(roomRef); - if (!permalink.roomIdOrAlias) continue; - - let roomId = await client.resolveRoom(permalink.roomIdOrAlias); - if (!joinedRooms.includes(roomId)) { - roomId = await client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers); - } - - protectedRooms[roomId] = roomRef; - } - - // Ensure we're also in the management room - LogService.info("index", "Resolving management room..."); - const managementRoomId = await client.resolveRoom(config.managementRoom); - if (!joinedRooms.includes(managementRoomId)) { - config.managementRoom = await client.joinRoom(config.managementRoom); - } else { - config.managementRoom = managementRoomId; - } - await logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status."); - - return new Mjolnir(client, protectedRooms, banLists); -} diff --git a/test/integration/mjolnirSetupUtils.ts b/test/integration/mjolnirSetupUtils.ts index 4fd2059c..c297d189 100644 --- a/test/integration/mjolnirSetupUtils.ts +++ b/test/integration/mjolnirSetupUtils.ts @@ -18,10 +18,10 @@ import { PantalaimonClient, MemoryStorageProvider } from "matrix-bot-sdk"; +import { Mjolnir} from '../../src/Mjolnir'; import config from "../../src/config"; import * as path from 'path'; import * as fs from 'fs/promises'; -import { setupMjolnir } from '../../src/setup'; import { registerUser } from "./clientHelper"; /** @@ -61,7 +61,7 @@ export async function makeMjolnir() { const pantalaimon = new PantalaimonClient(config.homeserverUrl, new MemoryStorageProvider()); const client = await pantalaimon.createClientWithCredentials(config.pantalaimon.username, config.pantalaimon.password); await ensureAliasedRoomExists(client, config.managementRoom); - return await setupMjolnir(client, config); + return await Mjolnir.setupMjolnirFromConfig(client); } /**