diff --git a/src/commands/CommandHandler.ts b/src/commands/CommandHandler.ts index 4eb7ec11..5fd9f22d 100644 --- a/src/commands/CommandHandler.ts +++ b/src/commands/CommandHandler.ts @@ -27,7 +27,6 @@ limitations under the License. import { Mjolnir } from "../Mjolnir"; import { showJoinsStatus } from "./JoinsCommand"; -import { execDumpRulesCommand, execRulesMatchingCommand } from "./DumpRulesCommand"; import { LogService, RichReply } from "matrix-bot-sdk"; import { htmlEscape } from "../utils"; import { execSyncCommand } from "./SyncCommand"; @@ -71,6 +70,7 @@ import "./HijackRoomCommand"; import "./Ban"; import "./Unban"; import "./StatusCommand"; +import "./Rules"; export const COMMAND_PREFIX = "!mjolnir"; @@ -85,10 +85,6 @@ export async function handleCommand(roomId: string, event: { content: { body: st try { if (parts.length === 1 || parts[1] === 'joins') { return await showJoinsStatus(roomId, event, mjolnir, parts.slice(/* ["joins"] */ 2)); - } else if (parts[1] === 'rules' && parts.length === 4 && parts[2] === 'matching') { - return await execRulesMatchingCommand(roomId, event, mjolnir, parts[3]) - } else if (parts[1] === 'rules') { - return await execDumpRulesCommand(roomId, event, mjolnir); } else if (parts[1] === 'sync') { return await execSyncCommand(roomId, event, mjolnir); } else if (parts[1] === 'verify') { diff --git a/src/commands/DumpRulesCommand.ts b/src/commands/DumpRulesCommand.ts deleted file mode 100644 index b7f13827..00000000 --- a/src/commands/DumpRulesCommand.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright (C) 2022 Gnuxie - * All rights reserved. - * - * This file is modified and is NOT licensed under the Apache License. - * This modified file incorperates work from mjolnir - * https://github.com/matrix-org/mjolnir - * which included the following license notice: - -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. - * - * However, this file is modified and the modifications in this file - * are NOT distributed, contributed, committed, or licensed under the Apache License. - */ - -import { RichReply } from "matrix-bot-sdk"; -import { Mjolnir } from "../Mjolnir"; -import { EntityType } from "../models/ListRule"; -import { htmlEscape } from "../utils"; - -/** - * List all of the rules that match a given entity. - * The reason why you want to test against all rules and not just e.g. user or server is because - * there are situations where rules of different types can ban other entities e.g. server ACL can cause users to be banned. - * @param roomId The room the command is from. - * @param event The event containing the command. - * @param mjolnir A mjolnir to fetch the watched lists from. - * @param entity a user, room id or server. - * @returns When a response has been sent to the command. - */ -export async function execRulesMatchingCommand(roomId: string, event: any, mjolnir: Mjolnir, entity: string) { - let html = ""; - let text = ""; - for (const list of mjolnir.policyListManager.lists) { - const matches = list.rulesMatchingEntity(entity) - - if (matches.length === 0) { - continue; - } - - const matchesInfo = `Found ${matches.length} ` + (matches.length === 1 ? 'match:' : 'matches:'); - const shortcodeInfo = list.listShortcode ? ` (shortcode: ${htmlEscape(list.listShortcode)})` : ''; - - html += `${htmlEscape(list.roomId)}${shortcodeInfo} ${matchesInfo}
"; - } - - if (text.length === 0) { - html += `No results for ${htmlEscape(entity)}`; - text += `No results for ${entity}`; - } - const reply = RichReply.createFor(roomId, event, text, html); - reply["msgtype"] = "m.notice"; - return mjolnir.client.sendMessage(roomId, reply); -} - -// !mjolnir rules -export async function execDumpRulesCommand(roomId: string, event: any, mjolnir: Mjolnir) { - let html = "Rules currently in use:
"; - let text = "Rules currently in use:\n"; - - let hasLists = false; - for (const list of mjolnir.policyListManager.lists) { - hasLists = true; - let hasRules = false; - - const shortcodeInfo = list.listShortcode ? ` (shortcode: ${list.listShortcode})` : ''; - - html += `${list.roomId}${shortcodeInfo}:
"; - text += "\n"; - } - - if (!hasLists) { - html = "No ban lists configured"; - text = "No ban lists configured"; - } - - const reply = RichReply.createFor(roomId, event, text, html); - reply["msgtype"] = "m.notice"; - return mjolnir.client.sendMessage(roomId, reply); -} diff --git a/src/commands/Rules.tsx b/src/commands/Rules.tsx new file mode 100644 index 00000000..5a630f75 --- /dev/null +++ b/src/commands/Rules.tsx @@ -0,0 +1,147 @@ +/** + * Copyright (C) 2022 Gnuxie + * All rights reserved. + * + * This file is modified and is NOT licensed under the Apache License. + * This modified file incorperates work from mjolnir + * https://github.com/matrix-org/mjolnir + * which included the following license notice: + +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. + * + * However, this file is modified and the modifications in this file + * are NOT distributed, contributed, committed, or licensed under the Apache License. + */ + +import { MjolnirContext } from "./CommandHandler"; +import { renderMatrixAndSend } from "./interface-manager/DeadDocumentMatrix"; +import { defineInterfaceCommand, findTableCommand } from "./interface-manager/InterfaceCommand"; +import { JSXFactory } from "./interface-manager/JSXFactory"; +import { tickCrossRenderer } from "./interface-manager/MatrixHelpRenderer"; +import { defineMatrixInterfaceAdaptor } from "./interface-manager/MatrixInterfaceAdaptor"; +import { MatrixRoomReference } from "./interface-manager/MatrixRoomReference"; +import { findPresentationType, parameters, union } from "./interface-manager/ParameterParsing"; +import { CommandResult } from "./interface-manager/Validation"; +import { UserID } from "matrix-bot-sdk"; +import { MatrixSendClient } from "../MatrixEmitter"; +import { ListRule } from "../models/ListRule"; + +async function renderListMatches( + client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult +) { + if (result.isErr()) { + return await tickCrossRenderer.call(this, ...arguments); + } + const lists = result.ok; + if (lists.length === 0) { + return await renderMatrixAndSend( + No policy lists configured, + commandRoomId, event, client + ) + } + return await renderMatrixAndSend( + + Rules currently in use:
+ {lists.map(list => renderListRules(list))} +
, + commandRoomId, event, client + ) +} + +function renderListRules(list: ListMatches) { + const renderRuleSummary = (rule: ListRule, entityDescription: string) => { + return
  • + {entityDescription} ({rule.recommendation}): {rule.entity} ({rule.reason}) +
  • + }; + return + {list.roomId} + {list.shortcode ? `(shortcode: ${list.shortcode})` : ''}:
    +
      + {list.matches.length === 0 + ?
    • No rules
    • + : list.matches.map(rule => renderRuleSummary(rule, rule.kind))} +
    +
    +} + +interface ListMatches { + shortcode: string, + roomRef: string, + roomId: string, + matches: ListRule[] +} + +defineInterfaceCommand({ + designator: ["rules"], + table: "mjolnir", + parameters: parameters([]), + command: async function (this: MjolnirContext) { + return CommandResult.Ok( + this.mjolnir.policyListManager.lists + .map(list => { + return { + shortcode: list.listShortcode, + roomRef: list.roomRef, + roomId: list.roomId, + matches: list.allRules + } + }) + ); + }, + summary: "Lists the rules currently in use by Mjolnir." +}) + +defineMatrixInterfaceAdaptor({ + interfaceCommand: findTableCommand("mjolnir", "rules"), + renderer: renderListMatches +}) + +defineInterfaceCommand({ + designator: ["rules", "matching"], + table: "mjolnir", + parameters: parameters([ + { + name: "entity", + acceptor: union( + findPresentationType("UserID"), + findPresentationType("MatrixRoomReference"), + findPresentationType("string"), + ) + } + ]), + command: async function ( + this: MjolnirContext, _keywords, entity: string|UserID|MatrixRoomReference + ): Promise> { + return CommandResult.Ok( + this.mjolnir.policyListManager.lists + .map(list => { + return { + shortcode: list.listShortcode, + roomRef: list.roomRef, + roomId: list.roomId, + matches: list.rulesMatchingEntity(entity.toString()) + } + }) + ); + }, + summary: "Lists the rules in use that will match this entity e.g. `!rules matching @foo:example.com` will show all the user and server rules, including globs, that match this user" +}) + +// I'm pretty sure that both commands could merge and use the same rendeer. +defineMatrixInterfaceAdaptor({ + interfaceCommand: findTableCommand("mjolnir", "rules", "matching"), + renderer: renderListMatches +})