diff --git a/docs/moderators.md b/docs/moderators.md index a4f4d22..08d39ab 100644 --- a/docs/moderators.md +++ b/docs/moderators.md @@ -83,3 +83,8 @@ you want to redact events by that person from all protected rooms, don't specify Sometimes you might want to see what Mjolnir is up to. There's some commands in `!mjolnir help` that could be of use to you, such as `!mjolnir rules` to see what rules it is actually enforcing and `!mjolnir status` to see if Mjolnir is even running where you expect it to. + +Adding protected rooms on the fly is as easy as `!mjolnir rooms add `. You can see all the rooms +which are protected with `!mjolnir rooms`, and remove a room with `!mjolnir rooms remove `. Note +that rooms which are listed in the config may be protected again when the bot restarts - to remove these rooms +permanently from protection, remove them from the config. diff --git a/src/Mjolnir.ts b/src/Mjolnir.ts index af04ea1..5cccc48 100644 --- a/src/Mjolnir.ts +++ b/src/Mjolnir.ts @@ -34,6 +34,7 @@ export const STATE_RUNNING = "running"; const WATCHED_LISTS_EVENT_TYPE = "org.matrix.mjolnir.watched_lists"; const ENABLED_PROTECTIONS_EVENT_TYPE = "org.matrix.mjolnir.enabled_protections"; +const PROTECTED_ROOMS_EVENT_TYPE = "org.matrix.mjolnir.protected_rooms"; export class Mjolnir { @@ -81,7 +82,7 @@ export class Mjolnir { if (profile['displayname']) { this.displayName = profile['displayname']; } - }) + }); } public get lists(): BanList[] { @@ -113,6 +114,17 @@ export class Mjolnir { } }).then(async () => { this.currentState = STATE_SYNCING; + await logMessage(LogLevel.DEBUG, "Mjolnir@startup", "Loading protected rooms..."); + try { + const data = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE); + if (data && data['rooms']) { + for (const roomId of data['rooms']) { + this.protectedRooms[roomId] = Permalinks.forRoom(roomId); + } + } + } catch (e) { + LogService.warn("Mjolnir", e); + } if (config.syncOnStartup) { await logMessage(LogLevel.INFO, "Mjolnir@startup", "Syncing lists..."); await this.buildWatchedBanLists(); @@ -125,6 +137,36 @@ export class Mjolnir { }); } + public async addProtectedRoom(roomId: string) { + const permalink = Permalinks.forRoom(roomId); + this.protectedRooms[roomId] = permalink; + + let additionalProtectedRooms; + try { + additionalProtectedRooms = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE); + } catch (e) { + LogService.warn("Mjolnir", e); + } + if (!additionalProtectedRooms || !additionalProtectedRooms['rooms']) additionalProtectedRooms = {rooms: []}; + additionalProtectedRooms.rooms.push(roomId); + await this.client.setAccountData(PROTECTED_ROOMS_EVENT_TYPE, additionalProtectedRooms); + await this.syncLists(config.verboseLogging); + } + + public async removeProtectedRoom(roomId: string) { + delete this.protectedRooms[roomId]; + + let additionalProtectedRooms; + try { + additionalProtectedRooms = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE); + } catch (e) { + LogService.warn("Mjolnir", e); + } + if (!additionalProtectedRooms || !additionalProtectedRooms['rooms']) additionalProtectedRooms = {rooms: []}; + additionalProtectedRooms.rooms = additionalProtectedRooms.rooms.filter(r => r !== roomId); + await this.client.setAccountData(PROTECTED_ROOMS_EVENT_TYPE, additionalProtectedRooms); + } + private async getEnabledProtections() { let enabled: string[] = []; try { diff --git a/src/commands/AddRemoveProtectedRoomsCommand.ts b/src/commands/AddRemoveProtectedRoomsCommand.ts new file mode 100644 index 0000000..0dd70d5 --- /dev/null +++ b/src/commands/AddRemoveProtectedRoomsCommand.ts @@ -0,0 +1,33 @@ +/* +Copyright 2020 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"; +import { RichReply } from "matrix-bot-sdk"; + +// !mjolnir rooms add +export async function execAddProtectedRoom(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { + const protectedRoomId = await mjolnir.client.joinRoom(parts[3]); + await mjolnir.addProtectedRoom(protectedRoomId); + await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅'); +} + +// !mjolnir rooms remove +export async function execRemoveProtectedRoom(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { + const protectedRoomId = await mjolnir.client.resolveRoom(parts[3]); + await mjolnir.removeProtectedRoom(protectedRoomId); + await mjolnir.client.leaveRoom(protectedRoomId); + await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅'); +} diff --git a/src/commands/CommandHandler.ts b/src/commands/CommandHandler.ts index 6385671..7b8316a 100644 --- a/src/commands/CommandHandler.ts +++ b/src/commands/CommandHandler.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 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. @@ -29,6 +29,8 @@ import { execImportCommand } from "./ImportCommand"; import { execSetDefaultListCommand } from "./SetDefaultBanListCommand"; import { execDeactivateCommand } from "./DeactivateCommand"; import { execDisableProtection, execEnableProtection, execListProtections } from "./ProtectionsCommands"; +import { execListProtectedRooms } from "./ListProtectedRoomsCommand"; +import { execAddProtectedRoom, execRemoveProtectedRoom } from "./AddRemoveProtectedRoomsCommand"; export const COMMAND_PREFIX = "!mjolnir"; @@ -69,6 +71,12 @@ export async function handleCommand(roomId: string, event: any, mjolnir: Mjolnir return await execEnableProtection(roomId, event, mjolnir, parts); } else if (parts[1] === 'disable' && parts.length > 1) { return await execDisableProtection(roomId, event, mjolnir, parts); + } else if (parts[1] === 'rooms' && parts.length > 3 && parts[2] === 'add') { + return await execAddProtectedRoom(roomId, event, mjolnir, parts); + } else if (parts[1] === 'rooms' && parts.length > 3 && parts[2] === 'remove') { + return await execRemoveProtectedRoom(roomId, event, mjolnir, parts); + } else if (parts[1] === 'rooms' && parts.length === 2) { + return await execListProtectedRooms(roomId, event, mjolnir); } else { // Help menu const menu = "" + @@ -89,6 +97,9 @@ export async function handleCommand(roomId: string, event: any, mjolnir: Mjolnir "!mjolnir protections - List all available protections\n" + "!mjolnir enable - Enables a particular protection\n" + "!mjolnir disable - Disables a particular protection\n" + + "!mjolnir rooms - Lists all the protected rooms\n" + + "!mjolnir rooms add - Adds a protected room (may cause high server load)\n" + + "!mjolnir rooms remove - Removes a protected room\n" + "!mjolnir help - This menu\n"; const html = `Mjolnir help:
${htmlEscape(menu)}
`; const text = `Mjolnir help:\n${menu}`; diff --git a/src/commands/ListProtectedRoomsCommand.ts b/src/commands/ListProtectedRoomsCommand.ts new file mode 100644 index 0000000..007683a --- /dev/null +++ b/src/commands/ListProtectedRoomsCommand.ts @@ -0,0 +1,44 @@ +/* +Copyright 2020 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"; +import { RichReply } from "matrix-bot-sdk"; + +// !mjolnir rooms +export async function execListProtectedRooms(roomId: string, event: any, mjolnir: Mjolnir) { + let html = "Protected rooms:
    "; + let text = "Protected rooms:\n"; + + let hasRooms = false; + for (const roomId in mjolnir.protectedRooms) { + hasRooms = true; + + const roomUrl = mjolnir.protectedRooms[roomId]; + html += `
  • ${roomId}
  • `; + text += `* ${roomUrl}\n`; + } + + html += "
"; + + if (!hasRooms) { + html = "No protected rooms"; + text = "No protected rooms"; + } + + const reply = RichReply.createFor(roomId, event, text, html); + reply["msgtype"] = "m.notice"; + return mjolnir.client.sendMessage(roomId, reply); +}