From 2ea8ddf8641c7e1b703a712ab6bcc2fa7f13f9ad Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 4 Sep 2023 13:30:50 +0200 Subject: [PATCH 1/5] Use the new help style on the appservice commands and add missing descriptions for the arguments (#69) * Use the new help style on the appservice commands and add missing descriptions for the arguments * Make the first letter of the table name uppercase in the help table --- src/appservice/bot/AccessCommands.tsx | 2 + .../bot/AppserviceCommandHandler.ts | 14 +++--- src/appservice/bot/ListCommand.tsx | 9 ++-- .../interface-manager/MatrixHelpRenderer.tsx | 47 +++++++++++++------ 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/appservice/bot/AccessCommands.tsx b/src/appservice/bot/AccessCommands.tsx index 25f96d3a..6854c50a 100644 --- a/src/appservice/bot/AccessCommands.tsx +++ b/src/appservice/bot/AccessCommands.tsx @@ -18,6 +18,7 @@ defineInterfaceCommand({ { name: 'user', acceptor: findPresentationType('UserID'), + description: 'The user that should be allowed to provision a bot' } ]), command: async function (this: AppserviceContext, _keywords: ParsedKeywords, user: UserID): Promise> { @@ -39,6 +40,7 @@ defineInterfaceCommand({ { name: 'user', acceptor: findPresentationType('UserID'), + description: 'The user which shall not be allowed to provision bots anymore' } ]), command: async function (this: AppserviceContext, _keywords: ParsedKeywords, user: UserID): Promise> { diff --git a/src/appservice/bot/AppserviceCommandHandler.ts b/src/appservice/bot/AppserviceCommandHandler.ts index 2853a745..887c53fd 100644 --- a/src/appservice/bot/AppserviceCommandHandler.ts +++ b/src/appservice/bot/AppserviceCommandHandler.ts @@ -5,9 +5,9 @@ import { WeakEvent } from 'matrix-appservice-bridge'; import { readCommand } from '../../commands/interface-manager/CommandReader'; -import { defineCommandTable, defineInterfaceCommand, findCommandTable } from '../../commands/interface-manager/InterfaceCommand'; +import { defineCommandTable, defineInterfaceCommand, findCommandTable, findTableCommand } from '../../commands/interface-manager/InterfaceCommand'; import { defineMatrixInterfaceAdaptor, findMatrixInterfaceAdaptor, MatrixContext } from '../../commands/interface-manager/MatrixInterfaceAdaptor'; -import { ArgumentStream, parameters } from '../../commands/interface-manager/ParameterParsing'; +import { ArgumentStream, RestDescription, findPresentationType, parameters } from '../../commands/interface-manager/ParameterParsing'; import { MjolnirAppService } from '../AppService'; import { CommandResult } from '../../commands/interface-manager/Validation'; import { renderHelp } from '../../commands/interface-manager/MatrixHelpRenderer'; @@ -26,23 +26,23 @@ import './AccessCommands'; import { AppserviceBotEmitter } from './AppserviceBotEmitter'; -const helpCommand = defineInterfaceCommand({ - parameters: parameters([]), +defineInterfaceCommand({ + parameters: parameters([], new RestDescription('command parts', findPresentationType("any"))), table: "appservice bot", - command: async function() { return CommandResult.Ok(findCommandTable("appservice bot").getAllCommands()) }, + command: async function () { return CommandResult.Ok(findCommandTable("appservice bot")) }, designator: ["help"], summary: "Display this message" }) defineMatrixInterfaceAdaptor({ - interfaceCommand: helpCommand, + interfaceCommand: findTableCommand("appservice bot", "help"), renderer: renderHelp }) export class AppserviceCommandHandler { private readonly commandTable = findCommandTable("appservice bot"); - constructor ( + constructor( private readonly appservice: MjolnirAppService ) { diff --git a/src/appservice/bot/ListCommand.tsx b/src/appservice/bot/ListCommand.tsx index 10cb4191..1d6035ef 100644 --- a/src/appservice/bot/ListCommand.tsx +++ b/src/appservice/bot/ListCommand.tsx @@ -28,7 +28,7 @@ const listUnstarted = defineInterfaceCommand({ designator: ["list", "unstarted"], table: "appservice bot", parameters: parameters([]), - command: async function() { + command: async function () { return CommandResult.Ok(this.appservice.mjolnirManager.getUnstartedMjolnirs()); }, summary: "List any Mjolnir that failed to start." @@ -38,7 +38,7 @@ const listUnstarted = defineInterfaceCommand({ // and be used similar to like #=1 and #1. defineMatrixInterfaceAdaptor({ interfaceCommand: listUnstarted, - renderer: async function(this: MatrixInterfaceAdaptor, client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult) { + renderer: async function (this: MatrixInterfaceAdaptor, client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult) { tickCrossRenderer.call(this, client, commandRoomId, event, result); // don't await, it doesn't really matter. if (result.isErr()) { return; // just let the default handler deal with it. @@ -53,7 +53,7 @@ defineMatrixInterfaceAdaptor({ {mjolnir.mjolnirRecord.owner}, {mjolnir.mxid.toString()} {mjolnir.failCode}: -
+
{mjolnir.cause} })} @@ -77,9 +77,10 @@ const restart = defineInterfaceCommand({ { name: "mjolnir", acceptor: findPresentationType("UserID"), + description: 'The userid of the mjolnir to restart' } ]), - command: async(context, _keywords, mjolnirId: UserID): Promise> => { + command: async (context, _keywords, mjolnirId: UserID): Promise> => { const mjolnirManager = context.appservice.mjolnirManager; const mjolnir = mjolnirManager.findUnstartedMjolnir(mjolnirId.localpart); if (mjolnir?.mjolnirRecord === undefined) { diff --git a/src/commands/interface-manager/MatrixHelpRenderer.tsx b/src/commands/interface-manager/MatrixHelpRenderer.tsx index fe39b7d1..9a91cbc1 100644 --- a/src/commands/interface-manager/MatrixHelpRenderer.tsx +++ b/src/commands/interface-manager/MatrixHelpRenderer.tsx @@ -3,7 +3,7 @@ */ import { MatrixSendClient } from "../../MatrixEmitter"; -import { BaseFunction, InterfaceCommand } from "./InterfaceCommand"; +import { BaseFunction, CommandTable, InterfaceCommand } from "./InterfaceCommand"; import { MatrixContext, MatrixInterfaceAdaptor } from "./MatrixInterfaceAdaptor"; import { ArgumentParseError, ParameterDescription, RestDescription } from "./ParameterParsing"; import { CommandError, CommandResult } from "./Validation"; @@ -29,17 +29,17 @@ function restArgument(rest: RestDescription): string { export function renderParameterDescription(description: ParameterDescription): DocumentNode { return - {description.name} - {description.description ?? 'no description'}
+ {description.name} - {description.description ?? 'no description'}
} export function renderCommandSummary(command: InterfaceCommand): DocumentNode { return
- {renderCommandHelp(command)} - {command.summary} + {renderCommandHelp(command)} - {command.summary} {command.description - ? Description:
{command.description}
+ ? Description:
{command.description}
: [] } {command.argumentListParser.descriptions.length > 0 @@ -63,13 +63,30 @@ export function renderCommandHelp(command: InterfaceCommand): stri ].join(' '); } -export async function renderHelp(client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult[], CommandError>): Promise { - const commands = result.ok; - let text = '' - for (const command of commands) { - text += `${renderCommandHelp(command)}\n`; +function renderTableHelp(table: CommandTable): DocumentNode { + let tableName = table.name; + if (typeof table.name === 'string') { + tableName = table.name.charAt(0).toUpperCase() + table.name.slice(1); } - await client.replyNotice(commandRoomId, event, text); + return +
+ {tableName} commands: + {table.getExportedCommands().map(renderCommandSummary)} + {table.getImportedTables().map(renderTableHelp)} +
+
+} + +export async function renderHelp(client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult): Promise { + if (result.isErr()) { + throw new TypeError("This command isn't supposed to fail"); + } + await renderMatrixAndSend( + renderTableHelp(result.ok), + commandRoomId, + event, + client + ); } export async function tickCrossRenderer(this: MatrixInterfaceAdaptor, client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult): Promise { @@ -122,17 +139,17 @@ function formattedArgumentHint(command: InterfaceCommand, error: A function renderArgumentParseError(command: InterfaceCommand, error: ArgumentParseError): DocumentNode { return - There was a problem when parsing the {error.parameter.name} parameter for this command.
- {renderCommandHelp(command)}
- {error.message}
+ There was a problem when parsing the {error.parameter.name} parameter for this command.
+ {renderCommandHelp(command)}
+ {error.message}
{formattedArgumentHint(command, error)}
} function renderCommandException(command: InterfaceCommand, error: CommandException): DocumentNode { return - There was an unexpected error when processing this command:
- {error.message}
+ There was an unexpected error when processing this command:
+ {error.message}
Details can be found by providing the reference {error.uuid} to an administrator.
From f55d8a453d5a251caad2df68100b83cd571c0a1b Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 4 Sep 2023 13:32:26 +0200 Subject: [PATCH 2/5] Add health endpoint to appservice and add metrics via prometheus (#70) This adds a `/healthz` endpoint to the appservice which allows this to work more nicely in kubernetes. It also adds some metrics for tracking the provisioning state. Grafana result: ![image](https://github.com/Gnuxie/Draupnir/assets/1374914/9426c8e6-2c1c-469c-8902-1b9e2b6db529) Note: The ts-ignore are sadly required since the `_getValue` method is not public :/ I didnt find another solution apart from tracking it maybe elsewhere. * Add health endpoint to appservice and add metrics via prometheus * Ensure that we dont have duplicate metrics when the appservice is registered multiple times * Move gauge modifications to utils function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix typo --- src/appservice/AppService.ts | 38 ++++++++++++++++++---- src/appservice/MjolnirManager.ts | 27 +++++++++++----- src/utils.ts | 55 ++++++++++++++++++++++++++------ 3 files changed, 97 insertions(+), 23 deletions(-) diff --git a/src/appservice/AppService.ts b/src/appservice/AppService.ts index f2687cc8..51912d60 100644 --- a/src/appservice/AppService.ts +++ b/src/appservice/AppService.ts @@ -25,7 +25,7 @@ limitations under the License. * are NOT distributed, contributed, committed, or licensed under the Apache License. */ -import { AppServiceRegistration, Bridge, Request, WeakEvent, BridgeContext, MatrixUser, Logger } from "matrix-appservice-bridge"; +import { AppServiceRegistration, Bridge, Request, WeakEvent, BridgeContext, MatrixUser, Logger, setBridgeVersion, PrometheusMetrics } from "matrix-appservice-bridge"; import { MjolnirManager } from ".//MjolnirManager"; import { DataStore } from ".//datastore"; import { PgDataStore } from "./postgres/PgDataStore"; @@ -33,6 +33,8 @@ import { Api } from "./Api"; import { IConfig } from "./config/config"; import { AccessControl } from "./AccessControl"; import { AppserviceCommandHandler } from "./bot/AppserviceCommandHandler"; +import { SOFTWARE_VERSION } from "../config"; +import { Registry } from 'prom-client'; const log = new Logger("AppService"); /** @@ -54,6 +56,7 @@ export class MjolnirAppService { public readonly mjolnirManager: MjolnirManager, public readonly accessControl: AccessControl, private readonly dataStore: DataStore, + private readonly prometheusMetrics: PrometheusMetrics ) { this.api = new Api(config.homeserver.url, mjolnirManager); this.commands = new AppserviceCommandHandler(this); @@ -75,21 +78,35 @@ export class MjolnirAppService { // It also allows us to combine constructor/initialize logic // to make the code base much simpler. A small hack to pay for an overall less hacky code base. controller: { - onUserQuery: () => {throw new Error("Mjolnir uninitialized")}, - onEvent: () => {throw new Error("Mjolnir uninitialized")}, + onUserQuery: () => { throw new Error("Mjolnir uninitialized") }, + onEvent: () => { throw new Error("Mjolnir uninitialized") }, }, suppressEcho: false, }); await bridge.initialise(); const accessControlListId = await bridge.getBot().getClient().resolveRoom(config.adminRoom); const accessControl = await AccessControl.setupAccessControl(accessControlListId, bridge); - const mjolnirManager = await MjolnirManager.makeMjolnirManager(dataStore, bridge, accessControl); + // Activate /metrics endpoint for Prometheus + + // This should happen automatically but in testing this didn't happen in the docker image + setBridgeVersion(SOFTWARE_VERSION); + + // Due to the way the tests and this prom library works we need to explicitly create a new one each time. + const prometheus = bridge.getPrometheusMetrics(true, new Registry()); + const instanceCountGauge = prometheus.addGauge({ + name: "draupnir_instances", + help: "Count of Draupnir Instances", + labels: ["status", "uuid"], + }); + + const mjolnirManager = await MjolnirManager.makeMjolnirManager(dataStore, bridge, accessControl, instanceCountGauge); const appService = new MjolnirAppService( config, bridge, mjolnirManager, accessControl, - dataStore + dataStore, + prometheus ); bridge.opts.controller = { onUserQuery: appService.onUserQuery.bind(appService), @@ -114,7 +131,7 @@ export class MjolnirAppService { return service; } - public onUserQuery (queriedUser: MatrixUser) { + public onUserQuery(queriedUser: MatrixUser) { return {}; // auto-provision users with no additonal data } @@ -160,6 +177,15 @@ export class MjolnirAppService { log.info("Starting MjolnirAppService, Matrix-side to listen on port", port); this.api.start(this.config.webAPI.port); await this.bridge.listen(port); + this.prometheusMetrics.addAppServicePath(this.bridge); + this.bridge.addAppServicePath({ + method: "GET", + path: "/healthz", + authenticate: false, + handler: async (_req, res) => { + res.status(200).send('ok'); + } + }); log.info("MjolnirAppService started successfully"); } diff --git a/src/appservice/MjolnirManager.ts b/src/appservice/MjolnirManager.ts index 5f3d8137..a6a6645a 100644 --- a/src/appservice/MjolnirManager.ts +++ b/src/appservice/MjolnirManager.ts @@ -11,6 +11,8 @@ import EventEmitter from "events"; import { MatrixEmitter } from "../MatrixEmitter"; import { Permalinks } from "../commands/interface-manager/Permalinks"; import { MatrixRoomReference } from "../commands/interface-manager/MatrixRoomReference"; +import { Gauge } from "prom-client"; +import { decrementGaugeValue, incrementGaugeValue } from "../utils"; const log = new Logger('MjolnirManager'); @@ -30,7 +32,8 @@ export class MjolnirManager { private constructor( private readonly dataStore: DataStore, private readonly bridge: Bridge, - private readonly accessControl: AccessControl + private readonly accessControl: AccessControl, + private readonly instanceCountGauge: Gauge<"status" | "uuid"> ) { } @@ -42,8 +45,8 @@ export class MjolnirManager { * @param accessControl Who has access to the bridge. * @returns A new mjolnir manager. */ - public static async makeMjolnirManager(dataStore: DataStore, bridge: Bridge, accessControl: AccessControl): Promise { - const mjolnirManager = new MjolnirManager(dataStore, bridge, accessControl); + public static async makeMjolnirManager(dataStore: DataStore, bridge: Bridge, accessControl: AccessControl, instanceCountGauge: Gauge<"status" | "uuid">): Promise { + const mjolnirManager = new MjolnirManager(dataStore, bridge, accessControl, instanceCountGauge); await mjolnirManager.startMjolnirs(await dataStore.list()); return mjolnirManager; } @@ -55,7 +58,7 @@ export class MjolnirManager { * @param client A client for the appservice virtual user that the new mjolnir should use. * @returns A new managed mjolnir. */ - public async makeInstance(requestingUserId: string, managementRoomId: string, client: MatrixClient): Promise { + public async makeInstance(localPart: string, requestingUserId: string, managementRoomId: string, client: MatrixClient): Promise { const mxid = await client.getUserId(); const intentListener = new MatrixIntentListener(mxid); const managedMjolnir = new ManagedMjolnir( @@ -70,6 +73,9 @@ export class MjolnirManager { await managedMjolnir.start(); this.mjolnirs.set(mxid, managedMjolnir); this.unstartedMjolnirs.delete(mxid); + incrementGaugeValue(this.instanceCountGauge, "offline", localPart); + decrementGaugeValue(this.instanceCountGauge, "disabled", localPart); + incrementGaugeValue(this.instanceCountGauge, "online", localPart); return managedMjolnir; } @@ -79,7 +85,7 @@ export class MjolnirManager { * @param ownerId The owner of the mjolnir. We ask for it explicitly to not leak access to another user's mjolnir. * @returns The matching managed mjolnir instance. */ - public getMjolnir(mjolnirId: string, ownerId: string): ManagedMjolnir|undefined { + public getMjolnir(mjolnirId: string, ownerId: string): ManagedMjolnir | undefined { const mjolnir = this.mjolnirs.get(mjolnirId); if (mjolnir) { if (mjolnir.ownerId !== ownerId) { @@ -141,7 +147,7 @@ export class MjolnirManager { } }); - const mjolnir = await this.makeInstance(requestingUserId, managementRoomId, mjIntent.matrixClient); + const mjolnir = await this.makeInstance(mjolnirLocalPart, requestingUserId, managementRoomId, mjIntent.matrixClient); await mjolnir.createFirstList(requestingUserId, "list"); await this.dataStore.store({ @@ -164,7 +170,7 @@ export class MjolnirManager { return [...this.unstartedMjolnirs.values()]; } - public findUnstartedMjolnir(localPart: string): UnstartedMjolnir|undefined { + public findUnstartedMjolnir(localPart: string): UnstartedMjolnir | undefined { return [...this.unstartedMjolnirs.values()].find(unstarted => unstarted.mjolnirRecord.local_part === localPart); } @@ -195,8 +201,11 @@ export class MjolnirManager { // Don't await, we don't want to clobber initialization just because we can't tell someone they're no longer allowed. mjIntent.matrixClient.sendNotice(mjolnirRecord.management_room, `Your mjolnir has been disabled by the administrator: ${access.rule?.reason ?? "no reason supplied"}`); this.reportUnstartedMjolnir(UnstartedMjolnir.FailCode.Unauthorized, access.outcome, mjolnirRecord, mjIntent.userId); + decrementGaugeValue(this.instanceCountGauge, "online", mjolnirRecord.local_part); + incrementGaugeValue(this.instanceCountGauge, "disabled", mjolnirRecord.local_part); } else { await this.makeInstance( + mjolnirRecord.local_part, mjolnirRecord.owner, mjolnirRecord.management_room, mjIntent.matrixClient, @@ -205,6 +214,8 @@ export class MjolnirManager { // Don't await, we don't want to clobber initialization if this fails. mjIntent.matrixClient.sendNotice(mjolnirRecord.management_room, `Your mjolnir could not be started. Please alert the administrator`); this.reportUnstartedMjolnir(UnstartedMjolnir.FailCode.StartError, e, mjolnirRecord, mjIntent.userId); + decrementGaugeValue(this.instanceCountGauge, "online", mjolnirRecord.local_part); + incrementGaugeValue(this.instanceCountGauge, "offline", mjolnirRecord.local_part); }); } } @@ -279,7 +290,7 @@ export class MatrixIntentListener extends EventEmitter implements MatrixEmitter public handleEvent(mxEvent: WeakEvent) { // These are ordered to be the same as matrix-bot-sdk's MatrixClient // They shouldn't need to be, but they are just in case it matters. - if (mxEvent['type'] === 'm.room.member' && mxEvent.state_key === this.mjolnirId) { + if (mxEvent['type'] === 'm.room.member' && mxEvent.state_key === this.mjolnirId) { if (mxEvent['content']['membership'] === 'leave') { this.emit('room.leave', mxEvent.room_id, mxEvent); } diff --git a/src/utils.ts b/src/utils.ts index 46896010..5ec7c71a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -41,6 +41,7 @@ import * as _ from '@sentry/tracing'; // Performing the import activates tracing import ManagementRoomOutput from "./ManagementRoomOutput"; import { IConfig } from "./config"; import { MatrixSendClient } from "./MatrixEmitter"; +import { Gauge } from "prom-client"; // Define a few aliases to simplify parsing durations. @@ -70,6 +71,42 @@ export function setToArray(set: Set): T[] { return arr; } +/** + * This increments a prometheus gauge. Used in the Appservice MjolnirManager. + * + * The ts-ignore is mandatory since we access a private method due to lack of a public one. + * + * See https://github.com/Gnuxie/Draupnir/pull/70#discussion_r1299188922 + * + * @param gauge The Gauge to be modified + * @param status The status value that should be modified + * @param uuid The UUID of the instance. (Usually the localPart) + */ +export function incrementGaugeValue(gauge: Gauge<"status" | "uuid">, status: "offline" | "disabled" | "online", uuid: string) { + // @ts-ignore + if (!gauge._getValue({ status: status, uuid: uuid })) { + gauge.inc({ status: status, uuid: uuid }); + } +} + +/** + * This decrements a prometheus gauge. Used in the Appservice MjolnirManager. + * + * The ts-ignore is mandatory since we access a private method due to lack of a public one. + * + * See https://github.com/Gnuxie/Draupnir/pull/70#discussion_r1299188922 + * + * @param gauge The Gauge to be modified + * @param status The status value that should be modified + * @param uuid The UUID of the instance. (Usually the localPart) + */ +export function decrementGaugeValue(gauge: Gauge<"status" | "uuid">, status: "offline" | "disabled" | "online", uuid: string) { + // @ts-ignore + if (gauge._getValue({ status: status, uuid: uuid })) { + gauge.dec({ status: status, uuid: uuid }); + } +} + export function isTrueJoinEvent(event: any): boolean { const membership = event['content']['membership'] || 'join'; let prevMembership = "leave"; @@ -134,7 +171,7 @@ export async function getMessagesByUserIn(client: MatrixSendClient, sender: stri const isGlob = sender.includes("*"); const roomEventFilter = { rooms: [roomId], - ... isGlob ? {} : {senders: [sender]} + ...isGlob ? {} : { senders: [sender] } }; const matcher = new MatrixGlob(sender); @@ -167,11 +204,11 @@ export async function getMessagesByUserIn(client: MatrixSendClient, sender: stri * if `null`, start from the most recent point in the timeline. * @returns The response part of the `/messages` API, see `BackfillResponse`. */ - async function backfill(from: string|null): Promise { + async function backfill(from: string | null): Promise { const qs = { filter: JSON.stringify(roomEventFilter), dir: "b", - ... from ? { from } : {} + ...from ? { from } : {} }; LogService.info("utils", "Backfilling with token: " + from); return client.doRequest("GET", `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/messages`, qs); @@ -195,10 +232,10 @@ export async function getMessagesByUserIn(client: MatrixSendClient, sender: stri } // We check that we have the token because rooms/messages is not required to provide one // and will not provide one when there is no more history to paginate. - let token: string|null = null; + let token: string | null = null; do { const bfMessages: BackfillResponse = await backfill(token); - const previousToken: string|null = token; + const previousToken: string | null = token; token = bfMessages['end'] ?? null; const events = filterEvents(bfMessages['chunk'] || []); // If we are using a glob, there may be no relevant events in this chunk. @@ -287,13 +324,13 @@ function patchMatrixClientForConciseExceptions() { const method: string | undefined = err.method ? err.method : "req" in err && err.req instanceof ClientRequest - ? err.req.method - : params.method; + ? err.req.method + : params.method; const path: string = err.url ? err.url : "req" in err && err.req instanceof ClientRequest - ? err.req.path - : params.uri ?? ''; + ? err.req.path + : params.uri ?? ''; let body: unknown = null; if ("body" in err) { body = err.body; From 0de9ad3333da843295fafe546a03ec2c16d8e4db Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 4 Sep 2023 13:34:26 +0200 Subject: [PATCH 3/5] Add command that allows to easily set the Displayname of a bot (#91) This adds the `displayname` command which just sets the displayname of the user. ----- * Add command that allows to easily set the Displayname of a bot * Only set the displayname once * Add missing matrix interface adaptor and use CommandError * Make displaynames with spaces work --- src/commands/CommandHandler.ts | 3 ++- src/commands/SetDisplayNameCommand.ts | 39 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/commands/SetDisplayNameCommand.ts diff --git a/src/commands/CommandHandler.ts b/src/commands/CommandHandler.ts index fea8811d..df39c90c 100644 --- a/src/commands/CommandHandler.ts +++ b/src/commands/CommandHandler.ts @@ -72,6 +72,7 @@ import "./Rooms"; import "./Rules"; import "./WatchUnwatchCommand"; import "./Help"; +import "./SetDisplayNameCommand"; export const COMMAND_PREFIX = "!mjolnir"; @@ -124,7 +125,7 @@ export async function handleCommand(roomId: string, event: { content: { body: st const readItems = readCommand(cmd).slice(1); // remove "!mjolnir" const stream = new ArgumentStream(readItems); const command = commandTable.findAMatchingCommand(stream) - ?? findTableCommand("mjolnir", "help"); + ?? findTableCommand("mjolnir", "help"); const adaptor = findMatrixInterfaceAdaptor(command); const mjolnirContext: MjolnirContext = { mjolnir, roomId, event, client: mjolnir.client, emitter: mjolnir.matrixEmitter, diff --git a/src/commands/SetDisplayNameCommand.ts b/src/commands/SetDisplayNameCommand.ts new file mode 100644 index 00000000..ae6e905b --- /dev/null +++ b/src/commands/SetDisplayNameCommand.ts @@ -0,0 +1,39 @@ +import { defineInterfaceCommand, findTableCommand } from "./interface-manager/InterfaceCommand"; +import { ParsedKeywords, RestDescription, findPresentationType, parameters } from "./interface-manager/ParameterParsing"; +import { MjolnirContext } from "./CommandHandler"; +import { CommandError, CommandResult } from "./interface-manager/Validation"; +import { tickCrossRenderer } from "./interface-manager/MatrixHelpRenderer"; +import { defineMatrixInterfaceAdaptor } from "./interface-manager/MatrixInterfaceAdaptor"; + + +defineInterfaceCommand({ + table: "mjolnir", + designator: ["displayname"], + summary: "Sets the displayname of the draupnir instance to the specified value in all rooms.", + parameters: parameters( + [], + new RestDescription( + "displayname", + findPresentationType("string"), + ), + ), + command: execSetDisplayNameCommand +}) + +// !draupnir displayname +export async function execSetDisplayNameCommand(this: MjolnirContext, _keywords: ParsedKeywords, ...displaynameParts: string[]): Promise> { + const displayname = displaynameParts.join(' '); + try { + await this.client.setDisplayName(displayname); + } catch (e) { + const message = e.message || (e.body ? e.body.error : ''); + return CommandError.Result(`Failed to set displayname to ${displayname}: ${message}`) + } + + return CommandResult.Ok(undefined); +} + +defineMatrixInterfaceAdaptor({ + interfaceCommand: findTableCommand("mjolnir", "displayname"), + renderer: tickCrossRenderer +}) From c9cc5693ace93690698140769386f8cb0635b276 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 12:37:09 +0100 Subject: [PATCH 4/5] Bump matrix-appservice-bridge from 8.1.1 to 8.1.2 (#71) Bumps [matrix-appservice-bridge](https://github.com/matrix-org/matrix-appservice-bridge) from 8.1.1 to 8.1.2. - [Release notes](https://github.com/matrix-org/matrix-appservice-bridge/releases) - [Changelog](https://github.com/matrix-org/matrix-appservice-bridge/blob/8.1.2/CHANGELOG.md) - [Commits](https://github.com/matrix-org/matrix-appservice-bridge/compare/8.1.1...8.1.2) --- updated-dependencies: - dependency-name: matrix-appservice-bridge dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 155 ++++----------------------------------------------- 2 files changed, 11 insertions(+), 146 deletions(-) diff --git a/package.json b/package.json index ae17b525..919a46a1 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "humanize-duration-ts": "^2.1.1", "js-yaml": "^4.1.0", "jsdom": "^16.6.0", - "matrix-appservice-bridge": "8.1.1", + "matrix-appservice-bridge": "8.1.2", "parse-duration": "^1.0.2", "pg": "^8.8.0", "shell-quote": "^1.7.3", diff --git a/yarn.lock b/yarn.lock index f03eaec0..e4881940 100644 --- a/yarn.lock +++ b/yarn.lock @@ -597,22 +597,6 @@ bluebird@^3.5.0: resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@1.19.2: - version "1.19.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" - integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.8.1" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.9.7" - raw-body "2.4.3" - type-is "~1.6.18" - body-parser@1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" @@ -852,16 +836,16 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.4.2, cookie@^0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - cookie@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -958,21 +942,11 @@ depd@2.0.0, depd@~2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg== - diff-sequences@^27.0.6: version "27.5.1" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" @@ -1270,43 +1244,7 @@ express-rate-limit@^6.7.0: resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-6.7.0.tgz#6aa8a1bd63dfe79702267b3af1161a93afc1d3c2" integrity sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA== -express@^4.17: - version "4.17.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" - integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.19.2" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.4.2" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.9.7" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.17.2" - serve-static "1.14.2" - setprototypeof "1.2.0" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -express@^4.17.1, express@^4.18.1: +express@^4.17, express@^4.17.1, express@^4.18.1: version "4.18.1" resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== @@ -1412,19 +1350,6 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - find-up@5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" @@ -1719,17 +1644,6 @@ htmlparser2@^6.0.0, htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" -http-errors@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" - integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.1" - http-errors@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" @@ -2235,10 +2149,10 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -matrix-appservice-bridge@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/matrix-appservice-bridge/-/matrix-appservice-bridge-8.1.1.tgz#9bf37d39742aed276e01d01e5c28c75d7ba1fcf7" - integrity sha512-6QYvTxe8kLXa2u+npeFUJph0PVqaOK6BPyC2J4bM0iklS8wNyprwzpToxGUr0WZS6D8vRc8qbyxhN1JTUbu9kw== +matrix-appservice-bridge@8.1.2: + version "8.1.2" + resolved "https://registry.yarnpkg.com/matrix-appservice-bridge/-/matrix-appservice-bridge-8.1.2.tgz#30953a4599533fe61a0c37bd5500b654cd236e30" + integrity sha512-OTQVEuYgsnlg7fBFASCaiYHgwi5+I/+vxwjOmR4y9n5ESKXoqI465bN+6zvW8MazdNfBl6NgZVWSm4DZohz8Zw== dependencies: "@alloc/quick-lru" "^5.2.0" "@types/pkginfo" "^0.4.0" @@ -2865,11 +2779,6 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@6.9.7: - version "6.9.7" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" - integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== - qs@~6.5.2: version "6.5.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" @@ -2900,16 +2809,6 @@ range-parser@~1.2.1: resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" - integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== - dependencies: - bytes "3.1.2" - http-errors "1.8.1" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@2.5.1: version "2.5.1" resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz" @@ -3082,25 +2981,6 @@ semver@^7.2.1: dependencies: lru-cache "^6.0.0" -send@0.17.2: - version "0.17.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" - integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "1.8.1" - mime "1.6.0" - ms "2.1.3" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -3127,16 +3007,6 @@ serialize-javascript@6.0.0: dependencies: randombytes "^2.1.0" -serve-static@1.14.2: - version "1.14.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" - integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.2" - serve-static@1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" @@ -3269,11 +3139,6 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - stealthy-require@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" From bc0fc7c25a27e4c0a052b4fc87569a48f552f059 Mon Sep 17 00:00:00 2001 From: Aminda Suomalainen Date: Mon, 4 Sep 2023 14:38:07 +0300 Subject: [PATCH 5/5] package.json: refer to https clone url (#90) Signed-off-by: Aminda Suomalainen --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 919a46a1..507485ea 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.84.0", "description": "A moderation tool for Matrix", "main": "lib/index.js", - "repository": "git@github.com:Gnuxie/Draupnir.git", + "repository": "https://github.com/Gnuxie/Draupnir.git", "author": "Gnuxie", "license": "SEE LICENSE IN LICENSE", "private": true,