From eb710edc955347e2f9b1a82be8d8eab399dbba1b Mon Sep 17 00:00:00 2001 From: gnuxie Date: Mon, 2 Jan 2023 19:29:42 +0000 Subject: [PATCH] Remove unused, obsolete MatrixInterfaceCommand. I'm not sure how it survived? probably f'd something up in a rebase. --- .../MatrixInterfaceCommand.ts | 231 ------------------ 1 file changed, 231 deletions(-) delete mode 100644 src/commands/interface-manager/MatrixInterfaceCommand.ts diff --git a/src/commands/interface-manager/MatrixInterfaceCommand.ts b/src/commands/interface-manager/MatrixInterfaceCommand.ts deleted file mode 100644 index cb271a13..00000000 --- a/src/commands/interface-manager/MatrixInterfaceCommand.ts +++ /dev/null @@ -1,231 +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 2022 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. - */ - -/** - * When we do move these components into their own library, - * I'd like to remove the dependency on matrix-bot-sdk. - */ - -import { ApplicationCommand, ApplicationFeature, getApplicationFeature } from "./ApplicationCommand"; -import { ValidationError, ValidationResult } from "./Validation"; -import { RichReply, LogService, MatrixClient } from "matrix-bot-sdk"; -import { ReadItem } from "./CommandReader"; -import { MatrixSendClient } from "../../MatrixEmitter"; - - -/** - * 💀 . o O ( at least I don't have to remember the types ) - * https://matrix-client.matrix.org/_matrix/media/r0/download/matrix.org/nbisOFhCcTzNicZrfixWMHZn - * Probably am "doing something wrong", and no, trying to make this protocol isn't it. - */ - -type CommandLookupEntry = Map>; - -type BaseFunction = (...args: any) => Promise; -const FLATTENED_MATRIX_COMMANDS = new Set>(); -const THIS_COMMAND_SYMBOL = Symbol("thisCommand"); - -export interface MatrixContext { - client: MatrixSendClient, - roomId: string, - event: any, -} - -type ParserSignature Promise> = ( - this: MatrixInterfaceCommand, - // The idea then is that this can be extended to include a Mjolnir or whatever. - context: C, - ...parts: ReadItem[]) => Promise>>; - -type RendererSignature = ( - this: MatrixInterfaceCommand, - client: MatrixClient, - commandRoomId: string, - event: any, - result: Awaited>) => Promise; - -/** - * A command that interfaces with a user via Matrix. - * The command wraps an `ApplicationCommand` to make it available to Matrix. - * To do this. A MatrixInterfaceCommand needs to parse an event and the context - * that it was received in with a `parser` and then render the result - * of an `ApplicationCommand` with a `renderer`, which really means - * rendering and sending a matrix event. - * - * Note, matrix interface command can be multi step ie ask for confirmation. - * From the perspective here, confirmation should be a distinct thing that happens - * before the interface command is invoked. - * - * When confirmation is required in the middle of a traditional command ie preview kick - * the preview command should be a distinct command. - */ -class MatrixInterfaceCommand Promise> { - constructor( - public readonly commandParts: string[], - private readonly parser: ParserSignature, - public readonly applicationCommand: ApplicationCommand, - private readonly renderer: RendererSignature, - private readonly validationErrorHandler?: (client: MatrixClient, roomId: string, event: any, validationError: ValidationError) => Promise - ) { - - } - - /** - * Parse the context required by the command, call the associated application command and then render the result to a Matrix room. - * The arguments to invoke will be given directly to the parser. - * The executor of the application command will then be applied to whatever is returned by the parser. - * Then the renderer will be applied to the same arguments given to the parser (so it knows which matrix room to respond to) - * along with the result of the executor. - * @param args These will be the arguments to the parser function. - */ - public async invoke(...args: Parameters>): Promise { - const parseResults = await this.parser(...args); - const matrixContext: MatrixContext = args.at(0) as MatrixContext; - if (parseResults.isErr()) { - this.reportValidationError.apply(this, [matrixContext.client, matrixContext.roomId, matrixContext.event, parseResults.err]); - return; - } - const executorResult: ReturnType = await this.applicationCommand.executor.apply(this, parseResults.ok); - // just give the renderer the MatrixContext. - - await this.renderer.apply(this, [matrixContext.client, matrixContext.roomId, matrixContext.event, executorResult]); - } - - private async reportValidationError(client: MatrixClient, roomId: string, event: any, validationError: ValidationError): Promise { - LogService.info("MatrixInterfaceCommand", `User input validation error when parsing command ${this.commandParts}: ${validationError.message}`); - if (this.validationErrorHandler) { - await this.validationErrorHandler.apply(this, arguments); - return; - } - const replyMessage = validationError.message; - const reply = RichReply.createFor(roomId, event, replyMessage, replyMessage); - reply["msgtype"] = "m.notice"; - await client.sendMessage(roomId, reply); - } -} - -/** - * Define a command to be interfaced via Matrix. - * @param commandParts constant parts used to discriminate the command e.g. "ban" or "config" "get" - * @param parser A function that parses a Matrix Event from a room to be able to invoke an ApplicationCommand. - * @param applicationCommmand The ApplicationCommand this is an interface wrapper for. - * @param renderer Render the result of the application command back to a room. - */ -export function defineMatrixInterfaceCommand Promise>( - commandParts: string[], - parser: ParserSignature, - applicationCommmand: ApplicationCommand, - renderer: RendererSignature) { - FLATTENED_MATRIX_COMMANDS.add( - new MatrixInterfaceCommand( - commandParts, - parser, - applicationCommmand, - renderer - ) - ); -} - - -/** - * This can be used by mjolnirs or an appservice bot. - */ -export class MatrixCommandTable { - public readonly features: ApplicationFeature[]; - private readonly flattenedCommands: Set>; - private readonly commands: CommandLookupEntry = new Map(); - - constructor(featureNames: string[]) { - this.features = featureNames.map(name => { - const feature = getApplicationFeature(name); - if (feature) { - return feature - } else { - throw new TypeError(`Couldn't find feature with name ${name}`) - } - }); - - const commandHasFeatures = (command: ApplicationCommand) => { - return command.requiredFeatures.every(feature => this.features.includes(feature)) - } - this.flattenedCommands = new Set([...FLATTENED_MATRIX_COMMANDS] - .filter(interfaceCommand => commandHasFeatures(interfaceCommand.applicationCommand))); - [...this.flattenedCommands].forEach(this.internCommand, this); - } - - public findAMatchingCommand(readItems: ReadItem[]) { - const getCommand = (table: CommandLookupEntry): undefined|MatrixInterfaceCommand => { - const command = table.get(THIS_COMMAND_SYMBOL); - if (command instanceof Map) { - throw new TypeError("There is an implementation bug, only commands should be stored under the command symbol"); - } - return command; - }; - const tableHelper = (table: CommandLookupEntry, nextParts: ReadItem[]): undefined|MatrixInterfaceCommand => { - if (nextParts.length === 0 || typeof nextParts.at(0) !== 'string') { - // Then they might be using something like "!mjolnir status" - return getCommand(table); - } - const entry = table.get(nextParts.shift()! as string); - if (!entry) { - // The reason there's no match is because this is the command arguments, rather than subcommand notation. - return getCommand(table); - } else { - if (!(entry instanceof Map)) { - throw new TypeError("There is an implementation bug, only maps should be stored under arbirtrary keys"); - } - return tableHelper(entry, nextParts); - } - }; - return tableHelper(this.commands, [...readItems]); - } - - public async invokeAMatchingCommand(context: C, readItems: ReadItem[]): Promise { - const command = this.findAMatchingCommand(readItems); - if (command) { - const itmesWithoutCommandDesignators = readItems.slice(command.commandParts.length) - await command.invoke(context, ...itmesWithoutCommandDesignators); - } - } - - private internCommand(command: MatrixInterfaceCommand) { - const internCommandHelper = (table: CommandLookupEntry, commandParts: string[]): void => { - if (commandParts.length === 0) { - if (table.has(THIS_COMMAND_SYMBOL)) { - throw new TypeError(`There is already a command for ${JSON.stringify(commandParts)}`) - } - table.set(THIS_COMMAND_SYMBOL, command); - } else { - const nextTable = new Map(); - table.set(commandParts.shift()!, nextTable); - internCommandHelper(nextTable, commandParts); - } - } - - internCommandHelper(this.commands, [...command.commandParts]); - } -}