mirror of
https://github.com/the-draupnir-project/Draupnir.git
synced 2026-10-22 17:11:17 +00:00
Merge pull request #54 from Gnuxie/gnuxie/rooms-command-upgrade
Use interface manager for "rooms", "rooms add" and "rooms remove" commands.
This commit is contained in:
6 files changed
+191
-122
No files matched your search
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2022 Gnuxie <Gnuxie@protonmail.com>
|
||||
* 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 2020-2021 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 { Mjolnir } from "../Mjolnir";
|
||||
import { LogLevel, LogService } from "matrix-bot-sdk";
|
||||
|
||||
// !mjolnir rooms add <room alias/ID>
|
||||
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 <room alias/ID>
|
||||
export async function execRemoveProtectedRoom(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
|
||||
const protectedRoomId = await mjolnir.client.resolveRoom(parts[3]);
|
||||
await mjolnir.removeProtectedRoom(protectedRoomId);
|
||||
try {
|
||||
await mjolnir.client.leaveRoom(protectedRoomId);
|
||||
} catch (e) {
|
||||
LogService.warn("AddRemoveProtectedRoomsCommand", e);
|
||||
await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "AddRemoveProtectedRoomsCommand", `Failed to leave ${protectedRoomId} - the room is no longer being protected, but the bot could not leave`, protectedRoomId);
|
||||
}
|
||||
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
|
||||
}
|
||||
@@ -38,8 +38,6 @@ import {
|
||||
execDisableProtection, execEnableProtection, execListProtections, execConfigGetProtection,
|
||||
execConfigSetProtection, execConfigAddProtection, execConfigRemoveProtection
|
||||
} from "./ProtectionsCommands";
|
||||
import { execListProtectedRooms } from "./ListProtectedRoomsCommand";
|
||||
import { execAddProtectedRoom, execRemoveProtectedRoom } from "./AddRemoveProtectedRoomsCommand";
|
||||
import { execSetPowerLevelCommand } from "./SetPowerLevelCommand";
|
||||
import { execResolveCommand } from "./ResolveAlias";
|
||||
import { execKickCommand } from "./KickCommand";
|
||||
@@ -64,13 +62,13 @@ defineCommandTable("synapse admin");
|
||||
import "./HijackRoomCommand";
|
||||
import "./ShutdownRoomCommand";
|
||||
import "./DeactivateCommand";
|
||||
import "./AddRemoveProtectedRoomsCommand";
|
||||
import "./AliasCommands";
|
||||
|
||||
defineCommandTable("mjolnir").importTable(findCommandTable("synapse admin"));
|
||||
import "./Ban";
|
||||
import "./Unban";
|
||||
import "./StatusCommand";
|
||||
import "./Rooms";
|
||||
import "./Rules";
|
||||
import "./WatchUnwatchCommand";
|
||||
import "./Help";
|
||||
@@ -114,12 +112,6 @@ export async function handleCommand(roomId: string, event: { content: { body: st
|
||||
return await execConfigRemoveProtection(roomId, event, mjolnir, parts.slice(3))
|
||||
} else if (parts[1] === 'config' && parts[2] === 'get') {
|
||||
return await execConfigGetProtection(roomId, event, mjolnir, parts.slice(3))
|
||||
} 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 if (parts[1] === 'resolve' && parts.length > 2) {
|
||||
return await execResolveCommand(roomId, event, mjolnir, parts);
|
||||
} else if (parts[1] === 'powerlevel' && parts.length > 3) {
|
||||
@@ -141,7 +133,6 @@ export async function handleCommand(roomId: string, event: { content: { body: st
|
||||
return await adaptor.invoke(mjolnirContext, mjolnirContext, ...stream.rest());
|
||||
} catch (e) {
|
||||
const commandError = new CommandException(e, 'Unknown Unexpected Error');
|
||||
LogService.error("CommandHandler", commandError.uuid, commandError.message, e);
|
||||
await tickCrossRenderer.call(mjolnirContext, mjolnir.client, roomId, event, CommandResult.Err(commandError));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2022 Gnuxie <Gnuxie@protonmail.com>
|
||||
* 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 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.
|
||||
*
|
||||
* However, this file is modified and the modifications in this file
|
||||
* are NOT distributed, contributed, committed, or licensed under the Apache License.
|
||||
*/
|
||||
|
||||
import { Mjolnir } from "../Mjolnir";
|
||||
import { RichReply } from "matrix-bot-sdk";
|
||||
import { Permalinks } from "./interface-manager/Permalinks";
|
||||
|
||||
// !mjolnir rooms
|
||||
export async function execListProtectedRooms(roomId: string, event: any, mjolnir: Mjolnir) {
|
||||
const rooms = mjolnir.protectedRoomsTracker.getProtectedRooms();
|
||||
let html = `<b>Protected rooms (${rooms.length}):</b><br/><ul>`;
|
||||
let text = `Protected rooms (${rooms.length}):\n`;
|
||||
|
||||
let hasRooms = false;
|
||||
for (const protectedRoomId of rooms) {
|
||||
hasRooms = true;
|
||||
|
||||
const roomUrl = Permalinks.forRoom(protectedRoomId);
|
||||
html += `<li><a href="${roomUrl}">${protectedRoomId}</a></li>`;
|
||||
text += `* ${roomUrl}\n`;
|
||||
}
|
||||
|
||||
html += "</ul>";
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright (C) 2022 Gnuxie <Gnuxie@protonmail.com>
|
||||
* 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 2020-2021 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 { defineInterfaceCommand, findTableCommand } from "./interface-manager/InterfaceCommand";
|
||||
import { findPresentationType, parameters } from "./interface-manager/ParameterParsing";
|
||||
import { MjolnirContext } from "./CommandHandler";
|
||||
import { MatrixRoomID, MatrixRoomReference } from "./interface-manager/MatrixRoomReference";
|
||||
import { CommandResult } from "./interface-manager/Validation";
|
||||
import { CommandException } from "./interface-manager/CommandException";
|
||||
import { defineMatrixInterfaceAdaptor } from "./interface-manager/MatrixInterfaceAdaptor";
|
||||
import { tickCrossRenderer } from "./interface-manager/MatrixHelpRenderer";
|
||||
import { DocumentNode } from "./interface-manager/DeadDocument";
|
||||
import { JSXFactory } from "./interface-manager/JSXFactory";
|
||||
import { renderMatrixAndSend } from "./interface-manager/DeadDocumentMatrix";
|
||||
import { Permalinks } from "./interface-manager/Permalinks";
|
||||
|
||||
defineInterfaceCommand({
|
||||
table: "mjolnir",
|
||||
designator: ["rooms"],
|
||||
summary: "List all of the protected rooms.",
|
||||
parameters: parameters([]),
|
||||
command: async function (this: MjolnirContext, _keywrods): Promise<CommandResult<string[]>> {
|
||||
return CommandResult.Ok(this.mjolnir.protectedRoomsTracker.getProtectedRooms());
|
||||
}
|
||||
})
|
||||
|
||||
function renderProtectedRooms(rooms: string[]): DocumentNode {
|
||||
return <root>
|
||||
<details>
|
||||
<summary><b>Protected Rooms ({rooms.length}):</b></summary>
|
||||
<ul>
|
||||
{rooms.map(r => <li><a href={Permalinks.forRoom(r)}>{r}</a></li>)}
|
||||
</ul>
|
||||
</details>
|
||||
</root>
|
||||
}
|
||||
|
||||
defineMatrixInterfaceAdaptor({
|
||||
interfaceCommand: findTableCommand("mjolnir", "rooms"),
|
||||
renderer: async function (client, commandRoomId, event, result) {
|
||||
tickCrossRenderer.call(this, ...arguments);
|
||||
if (result.isErr()) {
|
||||
return; // tickCrossRenderer will handle it.
|
||||
}
|
||||
await renderMatrixAndSend(
|
||||
renderProtectedRooms(result.ok),
|
||||
commandRoomId, event, client
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
defineInterfaceCommand({
|
||||
table: "mjolnir",
|
||||
designator: ["rooms", "add"],
|
||||
summary: "Protect the room using the watched policy lists, banning users and synchronizing server ACL.",
|
||||
parameters: parameters([
|
||||
{
|
||||
name: 'room',
|
||||
acceptor: findPresentationType("MatrixRoomReference"),
|
||||
description: 'The room to protect.'
|
||||
}
|
||||
]),
|
||||
command: async function (this: MjolnirContext, _keywords, roomRef: MatrixRoomReference): Promise<CommandResult<void>> {
|
||||
const roomIDOrError = await (async () => {
|
||||
try {
|
||||
return CommandResult.Ok(await roomRef.joinClient(this.mjolnir.client));
|
||||
} catch (e) {
|
||||
return CommandException.Result<MatrixRoomID>(
|
||||
`The homeserver that Draupnir is hosted on cannot join this room using the room reference provided.\
|
||||
Try an alias or the "share room" button in your client to obtain a valid reference to the room.`,
|
||||
{ exception: e }
|
||||
);
|
||||
}
|
||||
})();
|
||||
if (roomIDOrError.isErr()) {
|
||||
return CommandResult.Err(roomIDOrError.err);
|
||||
}
|
||||
await this.mjolnir.addProtectedRoom(roomIDOrError.ok.toRoomIdOrAlias());
|
||||
return CommandResult.Ok(undefined);
|
||||
},
|
||||
})
|
||||
|
||||
defineInterfaceCommand({
|
||||
table: "mjolnir",
|
||||
designator: ["rooms", "remove"],
|
||||
summary: "Stop protecting the room and leave.",
|
||||
parameters: parameters([
|
||||
{
|
||||
name: 'room',
|
||||
acceptor: findPresentationType("MatrixRoomReference"),
|
||||
description: 'The room to stop protecting.'
|
||||
}
|
||||
]),
|
||||
command: async function (this: MjolnirContext, _keywords, roomRef: MatrixRoomReference): Promise<CommandResult<void>> {
|
||||
const roomID = await roomRef.resolve(this.mjolnir.client);
|
||||
try {
|
||||
await this.mjolnir.client.leaveRoom(roomID.toRoomIdOrAlias());
|
||||
} catch (exception) {
|
||||
return CommandException.Result(`Failed to leave ${roomRef.toPermalink()} - the room is no longer being protected, but the bot could not leave.`, { exception });
|
||||
}
|
||||
return CommandResult.Ok(undefined);
|
||||
},
|
||||
})
|
||||
|
||||
for (const designator of [["rooms", "add"], ["rooms", "remove"]]) {
|
||||
defineMatrixInterfaceAdaptor({
|
||||
interfaceCommand: findTableCommand("mjolnir", ...designator),
|
||||
renderer: tickCrossRenderer,
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { UserID } from "matrix-bot-sdk";
|
||||
import { MatrixRoomReference } from "./MatrixRoomReference";
|
||||
import { Permalinks } from "./Permalinks";
|
||||
|
||||
export interface ISuperCoolStream<T> {
|
||||
readonly source: T
|
||||
@@ -91,15 +92,15 @@ const WHITESPACE = [' ', '\r', '\f', '\v', '\n', '\t'];
|
||||
* @returns ReadItems that have been read from this command.
|
||||
*/
|
||||
export function readCommand(string: string): ReadItem[] {
|
||||
return readCommandFromStream(new StringStream(string));
|
||||
return readCommandFromStream(new StringStream(string))
|
||||
}
|
||||
|
||||
function readCommandFromStream(stream: StringStream): ReadItem[] {
|
||||
const words: any[] = [];
|
||||
const words: ReadItem[] = [];
|
||||
while (stream.peekChar() !== undefined && (eatWhitespace(stream), true) && stream.peekChar() !== undefined) {
|
||||
words.push(readItem(stream));
|
||||
}
|
||||
return words;
|
||||
return words.map(applyPostReadTransformersToReadItem);
|
||||
}
|
||||
|
||||
function eatWhitespace(stream: StringStream): void {
|
||||
@@ -152,6 +153,37 @@ function defineReadItem(dispatchCharacter: string, macro: ReadMacro) {
|
||||
WORD_DISPATCH_CHARACTERS.set(dispatchCharacter, macro);
|
||||
}
|
||||
|
||||
type PostReadStringReplaceTransformer = (item: string) => ReadItem|string;
|
||||
type TransformerEntry = { regex: RegExp, transformer: PostReadStringReplaceTransformer };
|
||||
const POST_READ_TRANSFORMERS = new Map<string, TransformerEntry>();
|
||||
|
||||
|
||||
/**
|
||||
* Define a function that will be applied to ReadItem's that are strings that
|
||||
* also match the regex.
|
||||
* If the regex matches, the transformer function will be called with the read item
|
||||
* and given the oppertunity to return a new version of the item.
|
||||
*
|
||||
* This is mainly used to transform URLs into a MatrixRoomReference.
|
||||
*/
|
||||
function definePostReadReplace(regex: RegExp, transformer: PostReadStringReplaceTransformer) {
|
||||
if (POST_READ_TRANSFORMERS.has(regex.source)) {
|
||||
throw new TypeError(`A transformer has already been defined for the regexp ${regex.source}`);
|
||||
}
|
||||
POST_READ_TRANSFORMERS.set(regex.source, { regex, transformer })
|
||||
}
|
||||
|
||||
function applyPostReadTransformersToReadItem(item: ReadItem): ReadItem {
|
||||
if (typeof item === 'string') {
|
||||
for (const [_key, { regex, transformer }] of POST_READ_TRANSFORMERS) {
|
||||
if (regex.test(item)) {
|
||||
return transformer(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper that consumes from `stream` and appends to `output` until a character is peeked matching `regex`.
|
||||
* @param regex A regex for a character to stop at.
|
||||
@@ -233,3 +265,13 @@ function readKeyword(stream: StringStream): Keyword {
|
||||
|
||||
defineReadItem('-', readKeyword);
|
||||
defineReadItem(':', readKeyword);
|
||||
|
||||
definePostReadReplace(/^https:\/\/matrix\.to/, input => {
|
||||
const url = Permalinks.parseUrl(input);
|
||||
if (url.eventId !== undefined) {
|
||||
// don't know what to turn event references into yet.
|
||||
return input;
|
||||
} else {
|
||||
return MatrixRoomReference.fromPermalink(input);
|
||||
}
|
||||
})
|
||||
@@ -11,6 +11,7 @@ import { JSXFactory } from "./JSXFactory";
|
||||
import { DocumentNode } from "./DeadDocument";
|
||||
import { renderMatrixAndSend } from "./DeadDocumentMatrix";
|
||||
import { CommandException } from "./CommandException";
|
||||
import { LogService } from "matrix-bot-sdk";
|
||||
|
||||
function requiredArgument(argumentName: string): string {
|
||||
return `<${argumentName}>`;
|
||||
@@ -73,12 +74,15 @@ export async function renderHelp(client: MatrixSendClient, commandRoomId: string
|
||||
|
||||
export async function tickCrossRenderer(this: MatrixInterfaceAdaptor<MatrixContext, BaseFunction>, client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult<unknown, CommandError>): Promise<void> {
|
||||
const react = async (emote: string) => {
|
||||
await client.unstableApis.addReactionToEvent(commandRoomId, event['event_id'], emote);
|
||||
try {
|
||||
await client.unstableApis.addReactionToEvent(commandRoomId, event['event_id'], emote);
|
||||
} catch (e) {
|
||||
LogService.error("tickCrossRenderer", "Couldn't react to the event", event['event_id'], e);
|
||||
}
|
||||
}
|
||||
if (result.isOk()) {
|
||||
await react('✅')
|
||||
} else {
|
||||
await react('❌');
|
||||
if (result.err instanceof ArgumentParseError) {
|
||||
await renderMatrixAndSend(
|
||||
renderArgumentParseError(this.interfaceCommand, result.err),
|
||||
@@ -86,6 +90,8 @@ export async function tickCrossRenderer(this: MatrixInterfaceAdaptor<MatrixConte
|
||||
event,
|
||||
client);
|
||||
} else if (result.err instanceof CommandException) {
|
||||
const commandError = result.err;
|
||||
LogService.error("CommandException", commandError.uuid, commandError.message, commandError.exception);
|
||||
await renderMatrixAndSend(
|
||||
renderCommandException(this.interfaceCommand, result.err),
|
||||
commandRoomId,
|
||||
@@ -94,6 +100,8 @@ export async function tickCrossRenderer(this: MatrixInterfaceAdaptor<MatrixConte
|
||||
} else {
|
||||
await client.replyNotice(commandRoomId, event, result.err.message);
|
||||
}
|
||||
// reacting is way less important than communicating what happened, do it last.
|
||||
await react('❌');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,4 +141,4 @@ function renderCommandException(command: InterfaceCommand<BaseFunction>, error:
|
||||
export function renderMentionPill(mxid: string, displayName: string): DocumentNode {
|
||||
const url = `https://matrix.to/#/${mxid}`;
|
||||
return <a href={url}>{displayName}</a>
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user