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);
}
/**