diff --git a/src/Mjolnir.ts b/src/Mjolnir.ts
new file mode 100644
index 00000000..91219072
--- /dev/null
+++ b/src/Mjolnir.ts
@@ -0,0 +1,90 @@
+/*
+Copyright 2019 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 { MatrixClient } from "matrix-bot-sdk";
+import BanList, { ALL_RULE_TYPES } from "./models/BanList";
+import { applyServerAcls } from "./actions/ApplyAcl";
+import { RoomUpdateError } from "./models/RoomUpdateError";
+import { COMMAND_PREFIX, handleCommand } from "./commands/CommandHandler";
+
+export class Mjolnir {
+ constructor(
+ public readonly client: MatrixClient,
+ private managementRoomId: string,
+ public readonly protectedRooms: { [roomId: string]: string },
+ public readonly banLists: BanList[],
+ ) {
+ client.on("room.event", this.handleEvent.bind(this));
+
+ client.on("room.message", async (roomId, event) => {
+ if (roomId !== managementRoomId) return;
+ if (!event['content']) return;
+
+ const content = event['content'];
+ if (content['msgtype'] === "m.text" && content['body'] && content['body'].startsWith(COMMAND_PREFIX)) {
+ await client.sendReadReceipt(roomId, event['event_id']);
+ return handleCommand(roomId, event, this);
+ }
+ });
+ }
+
+ public start() {
+ return this.client.start();
+ }
+
+ private async handleEvent(roomId: string, event: any) {
+ if (!event['state_key']) return; // we also don't do anything with state events that have no state key
+
+ if (ALL_RULE_TYPES.includes(event['type'])) {
+ for (const list of this.banLists) {
+ if (list.roomId !== roomId) continue;
+ await list.updateList();
+ }
+
+ const errors = await applyServerAcls(this.banLists, Object.keys(this.protectedRooms), this.client);
+ return this.printActionResult(errors);
+ } else if (event['type'] === "m.room.member") {
+ // TODO: Check membership against ban banLists
+ }
+ }
+
+ private async printActionResult(errors: RoomUpdateError[]) {
+ let html = "";
+ let text = "";
+
+ if (errors.length > 0) {
+ html += `${errors.length} errors updating protected rooms!
`;
+ text += `${errors.length} errors updating protected rooms!\n`;
+ for (const error of errors) {
+ const url = this.protectedRooms[error.roomId] ? this.protectedRooms[error.roomId] : `https://matrix.to/#/${error.roomId}`;
+ html += `- ${error.roomId} - ${error.errorMessage}
`;
+ text += `${url} - ${error.errorMessage}\n`;
+ }
+ html += "
";
+ } else {
+ html += `Updated all protected rooms with new rules successfully.`;
+ text += "Updated all protected rooms with new rules successfully";
+ }
+
+ const message = {
+ msgtype: "m.notice",
+ body: text,
+ format: "org.matrix.custom.html",
+ formatted_body: html,
+ };
+ return this.client.sendMessage(this.managementRoomId, message);
+ }
+}
diff --git a/src/commands/CommandHandler.ts b/src/commands/CommandHandler.ts
new file mode 100644
index 00000000..f8fe788e
--- /dev/null
+++ b/src/commands/CommandHandler.ts
@@ -0,0 +1,32 @@
+/*
+Copyright 2019 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 { MatrixClient } from "matrix-bot-sdk";
+import { Mjolnir } from "../Mjolnir";
+import { execStatusCommand } from "./StatusCommand";
+
+export const COMMAND_PREFIX = "!mjolnir";
+
+export function handleCommand(roomId: string, event: any, mjolnir: Mjolnir) {
+ const cmd = event['content']['body'];
+ const parts = cmd.trim().split(' ');
+
+ if (parts.length === 1) {
+ return execStatusCommand(roomId, event, mjolnir);
+ } else {
+ // TODO: Help menu
+ }
+}
diff --git a/src/commands/StatusCommand.ts b/src/commands/StatusCommand.ts
new file mode 100644
index 00000000..4163bd21
--- /dev/null
+++ b/src/commands/StatusCommand.ts
@@ -0,0 +1,46 @@
+/*
+Copyright 2019 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 { Mjolnir } from "../Mjolnir";
+
+export async function execStatusCommand(roomId: string, event: any, mjolnir: Mjolnir) {
+ let html = "";
+ let text = "";
+
+ // Append header information first
+ html += "Running: ✅
";
+ text += "Running: ✅\n";
+ html += `Protected rooms: ${Object.keys(mjolnir.protectedRooms).length}
`;
+ text += `Protected rooms: ${mjolnir.protectedRooms.length}\n`;
+
+ // Append list information
+ html += "Subscribed ban lists:
";
+ text += "Subscribed ban lists:\n";
+ for (const list of mjolnir.banLists) {
+ const ruleInfo = `rules: ${list.serverRules.length} servers, ${list.userRules.length} users, ${list.roomRules.length} rooms`;
+ html += `- ${list.roomId} (${ruleInfo})
`;
+ text += `${list.roomRef} (${ruleInfo})\n`;
+ }
+ html += "
";
+
+ const message = {
+ msgtype: "m.notice",
+ body: text,
+ format: "org.matrix.custom.html",
+ formatted_body: html,
+ };
+ return mjolnir.client.sendMessage(roomId, message);
+}
diff --git a/src/index.ts b/src/index.ts
index b09f4421..9f5101c4 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -27,48 +27,21 @@ import config from "./config";
import BanList, { ALL_RULE_TYPES } from "./models/BanList";
import { applyServerAcls } from "./actions/ApplyAcl";
import { RoomUpdateError } from "./models/RoomUpdateError";
+import { Mjolnir } from "./Mjolnir";
LogService.setLogger(new RichConsoleLogger());
const storage = new SimpleFsStorageProvider(path.join(config.dataPath, "bot.json"));
const client = new MatrixClient(config.homeserverUrl, config.accessToken, storage);
-const lists: BanList[] = [];
-let managementRoomId = "";
-const protectedRooms: { [roomId: string]: string } = {};
if (config.autojoin) {
AutojoinRoomsMixin.setupOnClient(client);
}
-client.on("room.event", async (roomId, event) => {
- if (!event['state_key']) return; // we also don't do anything with state events that have no state key
-
- if (ALL_RULE_TYPES.includes(event['type'])) {
- for (const list of lists) {
- if (list.roomId !== roomId) continue;
- await list.updateList();
- }
-
- const errors = await applyServerAcls(lists, Object.keys(protectedRooms), client);
- return printActionResult(errors);
- } else if (event['type'] === "m.room.member") {
- // TODO: Check membership against ban lists
- }
-});
-
-client.on("room.message", async (roomId, event) => {
- if (roomId !== managementRoomId) return;
- if (!event['content']) return;
-
- const content = event['content'];
- if (content['msgtype'] === "m.text" && content['body'] === "!mjolnir") {
- await client.sendReadReceipt(roomId, event['event_id']);
- return printStatus(roomId);
- }
-});
-
-
(async function () {
+ const banLists: BanList[] = [];
+ const protectedRooms:{[roomId: string]: string} = {};
+
// Ensure we're in all the rooms we expect to be in
const joinedRooms = await client.getJoinedRooms();
for (const roomRef of config.banLists) {
@@ -82,7 +55,7 @@ client.on("room.message", async (roomId, event) => {
const list = new BanList(roomId, roomRef, client);
await list.updateList();
- lists.push(list);
+ banLists.push(list);
}
// Ensure we're also joined to the rooms we're protecting
@@ -99,70 +72,14 @@ client.on("room.message", async (roomId, event) => {
}
// Ensure we're also in the management room
- managementRoomId = await client.joinRoom(config.managementRoom);
+ const managementRoomId = await client.joinRoom(config.managementRoom);
await client.sendNotice(managementRoomId, "Mjolnir is starting up. Use !mjolnir to query status.");
+ const bot = new Mjolnir(client, managementRoomId, protectedRooms, banLists);
+ await bot.start();
+
// TODO: Check permissions for mjolnir in protected rooms
// TODO: Complain about permission changes in protected rooms (including after power levels change)
- await client.start();
LogService.info("index", "Bot started!")
})();
-
-async function printStatus(roomId: string) {
- const rooms = await client.getJoinedRooms();
-
- let html = "";
- let text = "";
-
- // Append header information first
- html += "Running: ✅
";
- text += "Running: ✅\n";
- html += `Protected rooms: ${Object.keys(protectedRooms).length}
`;
- text += `Protected rooms: ${rooms.length}\n`;
-
- // Append list information
- html += "Subscribed lists:
";
- text += "Subscribed lists:\n";
- for (const list of lists) {
- const ruleInfo = `rules: ${list.serverRules.length} servers, ${list.userRules.length} users, ${list.roomRules.length} rooms`;
- html += `- ${list.roomId} (${ruleInfo})
`;
- text += `${list.roomRef} (${ruleInfo})\n`;
- }
- html += "
";
-
- const message = {
- msgtype: "m.notice",
- body: text,
- format: "org.matrix.custom.html",
- formatted_body: html,
- };
- return client.sendMessage(roomId, message);
-}
-
-async function printActionResult(errors: RoomUpdateError[]) {
- let html = "";
- let text = "";
-
- if (errors.length > 0) {
- html += `${errors.length} errors updating protected rooms!
`;
- text += `${errors.length} errors updating protected rooms!\n`;
- for (const error of errors) {
- const url = protectedRooms[error.roomId] ? protectedRooms[error.roomId] : `https://matrix.to/#/${error.roomId}`;
- html += `- ${error.roomId} - ${error.errorMessage}
`;
- text += `${url} - ${error.errorMessage}\n`;
- }
- html += "
";
- } else {
- html += `Updated all protected rooms with new rules successfully.`;
- text += "Updated all protected rooms with new rules successfully";
- }
-
- const message = {
- msgtype: "m.notice",
- body: text,
- format: "org.matrix.custom.html",
- formatted_body: html,
- };
- return client.sendMessage(managementRoomId, message);
-}