diff --git a/README.md b/README.md index e8a7cfc..7081682 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Phase 2: * [x] No-op mode (for verifying behaviour) * [x] Redact messages on ban (optionally) * [x] More useful spam in management room -* [ ] Command to import ACLs, etc from rooms +* [x] Command to import ACLs, etc from rooms * [x] Vet rooms on startup option * [ ] Command to actually unban users (instead of leaving them stuck) * [x] Support multiple lists diff --git a/src/commands/CommandHandler.ts b/src/commands/CommandHandler.ts index d3fc620..50ad50d 100644 --- a/src/commands/CommandHandler.ts +++ b/src/commands/CommandHandler.ts @@ -25,6 +25,7 @@ import { execPermissionCheckCommand } from "./PermissionCheckCommand"; import { execCreateListCommand } from "./CreateBanListCommand"; import { execUnwatchCommand, execWatchCommand } from "./WatchUnwatchCommand"; import { execRedactCommand } from "./RedactCommand"; +import { execImportCommand } from "./ImportCommand"; export const COMMAND_PREFIX = "!mjolnir"; @@ -53,18 +54,21 @@ export async function handleCommand(roomId: string, event: any, mjolnir: Mjolnir return await execUnwatchCommand(roomId, event, mjolnir, parts); } else if (parts[1] === 'redact' && parts.length > 1) { return await execRedactCommand(roomId, event, mjolnir, parts); + } else if (parts[1] === 'import' && parts.length > 2) { + return await execImportCommand(roomId, event, mjolnir, parts); } else { // Help menu const menu = "" + "!mjolnir - Print status information\n" + "!mjolnir status - Print status information\n" + - "!mjolnir ban [reason] - Adds an entity to the ban list\n" + - "!mjolnir unban - Removes an entity from the ban list\n" + - "!mjolnir redact [room alias/ID] - Redacts messages by the sender in the target room (or all rooms)\n" + + "!mjolnir ban [reason] - Adds an entity to the ban list\n" + + "!mjolnir unban - Removes an entity from the ban list\n" + + "!mjolnir redact [room alias/ID] - Redacts messages by the sender in the target room (or all rooms)\n" + "!mjolnir rules - Lists the rules currently in use by Mjolnir\n" + "!mjolnir sync - Force updates of all lists and re-apply rules\n" + "!mjolnir verify - Ensures Mjolnir can moderate all your rooms\n" + - "!mjolnir list create - Creates a new ban list with the given shortcode and alias\n" + + "!mjolnir list create - Creates a new ban list with the given shortcode and alias\n" + + "!mjolnir import - Imports bans and ACLs into the given list\n" + "!mjolnir help - This menu\n"; const html = `Mjolnir help:
${htmlEscape(menu)}
`; const text = `Mjolnir help:\n${menu}`; diff --git a/src/commands/ImportCommand.ts b/src/commands/ImportCommand.ts new file mode 100644 index 0000000..cd6e0af --- /dev/null +++ b/src/commands/ImportCommand.ts @@ -0,0 +1,82 @@ +/* +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"; +import { RichReply } from "matrix-bot-sdk"; +import { RECOMMENDATION_BAN, recommendationToStable } from "../models/ListRule"; +import { RULE_SERVER, RULE_USER, ruleTypeToStable } from "../models/BanList"; + +// !mjolnir import +export async function execImportCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { + const importRoomId = await mjolnir.client.resolveRoom(parts[2]); + const list = mjolnir.lists.find(b => b.listShortcode === parts[3]); + if (!list) { + const message = "Unable to find list - check your shortcode."; + const reply = RichReply.createFor(roomId, event, message, message); + reply["msgtype"] = "m.notice"; + return mjolnir.client.sendMessage(roomId, reply); + } + + let importedRules = 0; + + const state = await mjolnir.client.getRoomState(importRoomId); + for (const stateEvent of state) { + const content = stateEvent['content'] || {}; + if (!content || Object.keys(content).length === 0) continue; + + if (stateEvent['type'] === 'm.room.member' && stateEvent['state_key'] !== '') { + // Member event - check for ban + if (content['membership'] === 'ban') { + const reason = content['reason'] || ''; + + await mjolnir.client.sendNotice(mjolnir.managementRoomId, `Adding user ${stateEvent['state_key']} to ban list`); + + const recommendation = recommendationToStable(RECOMMENDATION_BAN); + const ruleContent = { + entity: stateEvent['state_key'], + recommendation, + reason: reason, + }; + const stateKey = `rule:${ruleContent.entity}`; + await mjolnir.client.sendStateEvent(list.roomId, ruleTypeToStable(RULE_USER), stateKey, ruleContent); + importedRules++; + } + } else if (stateEvent['type'] === 'm.room.server_acl' && stateEvent['state_key'] === '') { + // ACL event - ban denied servers + if (!content['deny']) continue; + for (const server of content['deny']) { + const reason = ""; + + await mjolnir.client.sendNotice(mjolnir.managementRoomId, `Adding server ${server} to ban list`); + + const recommendation = recommendationToStable(RECOMMENDATION_BAN); + const ruleContent = { + entity: server, + recommendation, + reason: reason, + }; + const stateKey = `rule:${ruleContent.entity}`; + await mjolnir.client.sendStateEvent(list.roomId, ruleTypeToStable(RULE_SERVER), stateKey, ruleContent); + importedRules++; + } + } + } + + const message = `Imported ${importedRules} rules to ban list`; + const reply = RichReply.createFor(roomId, event, message, message); + reply['msgtype'] = "m.notice"; + await mjolnir.client.sendMessage(roomId, reply); +}