From 9cfa1417a4efa334c3a6ee91c34fb6123a7f4a1a Mon Sep 17 00:00:00 2001 From: "H. Shay" Date: Wed, 30 Oct 2024 14:00:49 -0700 Subject: [PATCH 1/4] make managment room id public so it can be used to send messages --- src/ManagementRoomOutput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ManagementRoomOutput.ts b/src/ManagementRoomOutput.ts index edb25029..87d7b1cd 100644 --- a/src/ManagementRoomOutput.ts +++ b/src/ManagementRoomOutput.ts @@ -40,7 +40,7 @@ const levelToFn = { */ export default class ManagementRoomOutput { constructor( - private readonly managementRoomId: string, + public readonly managementRoomId: string, private readonly client: MatrixSendClient, private readonly config: IConfig, ) {} From 82d33414d490efb497c1c9f6f1a7d2cbb65bbed7 Mon Sep 17 00:00:00 2001 From: "H. Shay" Date: Wed, 30 Oct 2024 14:01:23 -0700 Subject: [PATCH 2/4] use spoilers when mentioning targets of a rule --- src/ProtectedRoomsSet.ts | 27 +++++++++++++------------- src/commands/KickCommand.ts | 23 +++++++++++----------- src/commands/SuspendCommand.ts | 3 ++- src/protections/BasicFlooding.ts | 12 ++++++------ src/protections/FirstMessageIsImage.ts | 11 ++++++----- src/utils.ts | 25 ++++++++++++------------ 6 files changed, 52 insertions(+), 49 deletions(-) diff --git a/src/ProtectedRoomsSet.ts b/src/ProtectedRoomsSet.ts index a88b414a..96d8a58e 100644 --- a/src/ProtectedRoomsSet.ts +++ b/src/ProtectedRoomsSet.ts @@ -362,13 +362,12 @@ export class ProtectedRoomsSet { // ignore - assume no ACL } - // We specifically use sendNotice to avoid having to escape HTML - await this.managementRoomOutput.logMessage( - LogLevel.DEBUG, - "ApplyAcl", - `Applying ACL in ${roomId}`, - roomId, - ); + await this.client.sendMessage(this.managementRoomId, { + msgtype: "m.text", + body: `Applying ACL in ${roomId}.`, + format: "org.matrix.custom.html", + formatted_body: `Applying ACL in ${roomId}.`, + }); if (!this.config.noop) { await this.client.sendStateEvent(roomId, "m.room.server_acl", "", finalAcl); @@ -439,13 +438,13 @@ export class ProtectedRoomsSet { const memberAccess = this.accessControlUnit.getAccessForUser(member.userId, "IGNORE_SERVER"); if (memberAccess.outcome === Access.Banned) { const reason = memberAccess.rule ? memberAccess.rule.reason : ""; - // We specifically use sendNotice to avoid having to escape HTML - await this.managementRoomOutput.logMessage( - LogLevel.INFO, - "ApplyBan", - `Banning ${member.userId} in ${roomId} for: ${reason}`, - roomId, - ); + + await this.client.sendMessage(this.managementRoomId, { + msgtype: "m.text", + body: `Banning ${member.userId} in ${roomId} for: ${reason}.`, + format: "org.matrix.custom.html", + formatted_body: `Banning ${member.userId} in ${roomId} for: ${reason}.`, + }); if (!this.config.noop) { if (this.moderators.checkMembership(member.userId)) { diff --git a/src/commands/KickCommand.ts b/src/commands/KickCommand.ts index 19857e6d..2fe58b79 100644 --- a/src/commands/KickCommand.ts +++ b/src/commands/KickCommand.ts @@ -57,12 +57,12 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln const target = member.membershipFor; if (kickRule.test(target)) { - await mjolnir.managementRoomOutput.logMessage( - LogLevel.DEBUG, - "KickCommand", - `Removing ${target} in ${protectedRoomId}`, - protectedRoomId, - ); + await mjolnir.client.sendMessage(mjolnir.managementRoomId, { + msgtype: "m.text", + body: `Removing ${target} in ${protectedRoomId}.`, + format: "org.matrix.custom.html", + formatted_body: `Removing ${target} in ${protectedRoomId}.`, + }); if (!mjolnir.config.noop) { try { @@ -70,11 +70,12 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln return mjolnir.client.kickUser(target, protectedRoomId, reason); }); } catch (e) { - await mjolnir.managementRoomOutput.logMessage( - LogLevel.WARN, - "KickCommand", - `An error happened while trying to kick ${target}: ${e}`, - ); + await mjolnir.client.sendMessage(mjolnir.managementRoomId, { + msgtype: "m.text", + body: `An error happened while trying to kick ${target}: ${e}`, + format: "org.matrix.custom.html", + formatted_body: `An error happened while trying to kick ${target}: ${e}.`, + }); } } else { await mjolnir.managementRoomOutput.logMessage( diff --git a/src/commands/SuspendCommand.ts b/src/commands/SuspendCommand.ts index 33bef2d7..44759430 100644 --- a/src/commands/SuspendCommand.ts +++ b/src/commands/SuspendCommand.ts @@ -31,7 +31,8 @@ export async function execSuspendCommand(roomId: string, event: any, mjolnir: Mj await mjolnir.suspendSynapseUser(target); const msg = `User ${target} has been suspended.`; - const confirmation = RichReply.createFor(roomId, event, msg, msg); + const htmlMsg = `User ${target} has been suspended.`; + const confirmation = RichReply.createFor(roomId, event, msg, htmlMsg); confirmation["msgtype"] = "m.notice"; await mjolnir.client.sendMessage(roomId, confirmation); await mjolnir.client.unstableApis.addReactionToEvent(roomId, event["event_id"], "✅"); diff --git a/src/protections/BasicFlooding.ts b/src/protections/BasicFlooding.ts index d6ddc451..82fcc68d 100644 --- a/src/protections/BasicFlooding.ts +++ b/src/protections/BasicFlooding.ts @@ -68,12 +68,12 @@ export class BasicFlooding extends Protection { } if (messageCount >= this.settings.maxPerMinute.value) { - await mjolnir.managementRoomOutput.logMessage( - LogLevel.WARN, - "BasicFlooding", - `Banning ${event["sender"]} in ${roomId} for flooding (${messageCount} messages in the last minute)`, - roomId, - ); + await mjolnir.client.sendMessage(mjolnir.managementRoomId, { + msgtype: "m.text", + body: `Banning ${event["sender"]} in ${roomId} for flooding (${messageCount} messages in the last minute)`, + format: "org.matrix.custom.html", + formatted_body: `Banning ${event["sender"]} in ${roomId} for flooding (${messageCount} messages in the last minute).`, + }); if (!mjolnir.config.noop) { if (mjolnir.moderators.checkMembership(event["sender"])) { mjolnir.managementRoomOutput.logMessage( diff --git a/src/protections/FirstMessageIsImage.ts b/src/protections/FirstMessageIsImage.ts index 51574f96..2f756f3d 100644 --- a/src/protections/FirstMessageIsImage.ts +++ b/src/protections/FirstMessageIsImage.ts @@ -58,11 +58,12 @@ export class FirstMessageIsImage extends Protection { const isMedia = msgtype === "m.image" || msgtype === "m.video" || formattedBody.toLowerCase().includes("${event["sender"]} for posting an image as the first thing after joining in ${roomId}.`, + }); if (!mjolnir.config.noop) { if (mjolnir.moderators.checkMembership(event["sender"])) { await mjolnir.managementRoomOutput.logMessage( diff --git a/src/utils.ts b/src/utils.ts index 05893a34..bab8542e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -130,12 +130,12 @@ async function botRedactUserMessagesIn( } }); } catch (error) { - await managementRoom.logMessage( - LogLevel.ERROR, - "utils#redactUserMessagesIn", - `Caught an error while trying to redact messages for ${userIdOrGlob} in ${targetRoomId}: ${error}`, - targetRoomId, - ); + await client.sendMessage(managementRoom.managementRoomId, { + msgtype: "m.text", + body: `Caught an error while trying to redact messages for ${userIdOrGlob} in ${targetRoomId}: ${error}`, + format: "org.matrix.custom.html", + formatted_body: `Caught an error while trying to redact messages for ${userIdOrGlob} in ${targetRoomId}: ${error}`, + }); } } } @@ -215,12 +215,13 @@ export async function redactUserMessagesIn( "utils#redactUserMessagesIn", `Error using admin API to redact messages: ${extractRequestError(e)}`, ); - await managementRoom.logMessage( - LogLevel.ERROR, - "utils#redactUserMessagesIn", - `Error using admin API to redact messages for user ${userIdOrGlob}, please check logs for more info - falling - back to non-admin redaction process.`, - ); + await client.sendMessage(managementRoom.managementRoomId, { + msgtype: "m.text", + body: `Error using admin API to redact messages for user ${userIdOrGlob}, please check logs for more info - falling back to non-admin redaction process.`, + format: "org.matrix.custom.html", + formatted_body: `Error using admin API to redact messages for user ${userIdOrGlob}, please check logs for more info - falling + back to non-admin redaction process.`, + }); await botRedactUserMessagesIn(client, managementRoom, userIdOrGlob, filteredRooms, limit, noop); } } else { From 8cd1726be9cf351512a6f18c71771752ec4e4e2a Mon Sep 17 00:00:00 2001 From: "H. Shay" Date: Fri, 22 Nov 2024 08:56:58 -0800 Subject: [PATCH 3/4] html escapse targets --- src/ProtectedRoomsSet.ts | 4 ++-- src/commands/KickCommand.ts | 5 +++-- src/commands/SuspendCommand.ts | 3 ++- src/protections/BasicFlooding.ts | 3 ++- src/protections/FirstMessageIsImage.ts | 4 ++-- src/utils.ts | 4 ++-- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/ProtectedRoomsSet.ts b/src/ProtectedRoomsSet.ts index 96d8a58e..9b8b345b 100644 --- a/src/ProtectedRoomsSet.ts +++ b/src/ProtectedRoomsSet.ts @@ -366,7 +366,7 @@ export class ProtectedRoomsSet { msgtype: "m.text", body: `Applying ACL in ${roomId}.`, format: "org.matrix.custom.html", - formatted_body: `Applying ACL in ${roomId}.`, + formatted_body: `Applying ACL in ${htmlEscape(roomId)}.`, }); if (!this.config.noop) { @@ -443,7 +443,7 @@ export class ProtectedRoomsSet { msgtype: "m.text", body: `Banning ${member.userId} in ${roomId} for: ${reason}.`, format: "org.matrix.custom.html", - formatted_body: `Banning ${member.userId} in ${roomId} for: ${reason}.`, + formatted_body: `Banning ${htmlEscape(member.userId)} in ${roomId} for: ${reason}.`, }); if (!this.config.noop) { diff --git a/src/commands/KickCommand.ts b/src/commands/KickCommand.ts index 2fe58b79..28eb0fa8 100644 --- a/src/commands/KickCommand.ts +++ b/src/commands/KickCommand.ts @@ -16,6 +16,7 @@ limitations under the License. import { Mjolnir } from "../Mjolnir"; import { LogLevel, MatrixGlob, RichReply } from "@vector-im/matrix-bot-sdk"; +import {htmlEscape} from "../utils"; // !mjolnir kick [room] [reason] export async function execKickCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { @@ -61,7 +62,7 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln msgtype: "m.text", body: `Removing ${target} in ${protectedRoomId}.`, format: "org.matrix.custom.html", - formatted_body: `Removing ${target} in ${protectedRoomId}.`, + formatted_body: `Removing ${htmlEscape(target)} in ${protectedRoomId}.`, }); if (!mjolnir.config.noop) { @@ -74,7 +75,7 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln msgtype: "m.text", body: `An error happened while trying to kick ${target}: ${e}`, format: "org.matrix.custom.html", - formatted_body: `An error happened while trying to kick ${target}: ${e}.`, + formatted_body: `An error happened while trying to kick ${htmlEscape(target)}: ${e}.`, }); } } else { diff --git a/src/commands/SuspendCommand.ts b/src/commands/SuspendCommand.ts index 44759430..85f8ec40 100644 --- a/src/commands/SuspendCommand.ts +++ b/src/commands/SuspendCommand.ts @@ -16,6 +16,7 @@ limitations under the License. import { Mjolnir } from "../Mjolnir"; import { RichReply } from "@vector-im/matrix-bot-sdk"; +import {htmlEscape} from "../utils"; export async function execSuspendCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { const target = parts[2]; @@ -31,7 +32,7 @@ export async function execSuspendCommand(roomId: string, event: any, mjolnir: Mj await mjolnir.suspendSynapseUser(target); const msg = `User ${target} has been suspended.`; - const htmlMsg = `User ${target} has been suspended.`; + const htmlMsg = `User ${htmlEscape(target)} has been suspended.`; const confirmation = RichReply.createFor(roomId, event, msg, htmlMsg); confirmation["msgtype"] = "m.notice"; await mjolnir.client.sendMessage(roomId, confirmation); diff --git a/src/protections/BasicFlooding.ts b/src/protections/BasicFlooding.ts index 82fcc68d..76651f37 100644 --- a/src/protections/BasicFlooding.ts +++ b/src/protections/BasicFlooding.ts @@ -18,6 +18,7 @@ import { Protection } from "./IProtection"; import { NumberProtectionSetting } from "./ProtectionSettings"; import { Mjolnir } from "../Mjolnir"; import { LogLevel, LogService } from "@vector-im/matrix-bot-sdk"; +import {htmlEscape} from "../utils"; // if this is exceeded, we'll ban the user for spam and redact their messages export const DEFAULT_MAX_PER_MINUTE = 10; @@ -72,7 +73,7 @@ export class BasicFlooding extends Protection { msgtype: "m.text", body: `Banning ${event["sender"]} in ${roomId} for flooding (${messageCount} messages in the last minute)`, format: "org.matrix.custom.html", - formatted_body: `Banning ${event["sender"]} in ${roomId} for flooding (${messageCount} messages in the last minute).`, + formatted_body: `Banning ${htmlEscape(event["sender"])} in ${roomId} for flooding (${messageCount} messages in the last minute).`, }); if (!mjolnir.config.noop) { if (mjolnir.moderators.checkMembership(event["sender"])) { diff --git a/src/protections/FirstMessageIsImage.ts b/src/protections/FirstMessageIsImage.ts index 2f756f3d..1fcc340f 100644 --- a/src/protections/FirstMessageIsImage.ts +++ b/src/protections/FirstMessageIsImage.ts @@ -17,7 +17,7 @@ limitations under the License. import { Protection } from "./IProtection"; import { Mjolnir } from "../Mjolnir"; import { LogLevel, LogService } from "@vector-im/matrix-bot-sdk"; -import { isTrueJoinEvent } from "../utils"; +import {htmlEscape, isTrueJoinEvent} from "../utils"; export class FirstMessageIsImage extends Protection { private justJoined: { [roomId: string]: string[] } = {}; @@ -62,7 +62,7 @@ export class FirstMessageIsImage extends Protection { msgtype: "m.text", body: `Banning ${event["sender"]} for posting an image as the first thing after joining in ${roomId}.`, format: "org.matrix.custom.html", - formatted_body: `Banning ${event["sender"]} for posting an image as the first thing after joining in ${roomId}.`, + formatted_body: `Banning ${htmlEscape(event["sender"])} for posting an image as the first thing after joining in ${roomId}.`, }); if (!mjolnir.config.noop) { if (mjolnir.moderators.checkMembership(event["sender"])) { diff --git a/src/utils.ts b/src/utils.ts index bab8542e..eca9a2a7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -134,7 +134,7 @@ async function botRedactUserMessagesIn( msgtype: "m.text", body: `Caught an error while trying to redact messages for ${userIdOrGlob} in ${targetRoomId}: ${error}`, format: "org.matrix.custom.html", - formatted_body: `Caught an error while trying to redact messages for ${userIdOrGlob} in ${targetRoomId}: ${error}`, + formatted_body: `Caught an error while trying to redact messages for ${htmlEscape(userIdOrGlob)} in ${targetRoomId}: ${error}`, }); } } @@ -219,7 +219,7 @@ export async function redactUserMessagesIn( msgtype: "m.text", body: `Error using admin API to redact messages for user ${userIdOrGlob}, please check logs for more info - falling back to non-admin redaction process.`, format: "org.matrix.custom.html", - formatted_body: `Error using admin API to redact messages for user ${userIdOrGlob}, please check logs for more info - falling + formatted_body: `Error using admin API to redact messages for user ${htmlEscape(userIdOrGlob)}, please check logs for more info - falling back to non-admin redaction process.`, }); await botRedactUserMessagesIn(client, managementRoom, userIdOrGlob, filteredRooms, limit, noop); From e284fcfb7addfdc1b0f0c4debb2674ed5c07a016 Mon Sep 17 00:00:00 2001 From: "H. Shay" Date: Fri, 22 Nov 2024 13:30:16 -0800 Subject: [PATCH 4/4] lint --- src/commands/KickCommand.ts | 2 +- src/commands/SuspendCommand.ts | 2 +- src/protections/BasicFlooding.ts | 2 +- src/protections/FirstMessageIsImage.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/KickCommand.ts b/src/commands/KickCommand.ts index 28eb0fa8..c49b4281 100644 --- a/src/commands/KickCommand.ts +++ b/src/commands/KickCommand.ts @@ -16,7 +16,7 @@ limitations under the License. import { Mjolnir } from "../Mjolnir"; import { LogLevel, MatrixGlob, RichReply } from "@vector-im/matrix-bot-sdk"; -import {htmlEscape} from "../utils"; +import { htmlEscape } from "../utils"; // !mjolnir kick [room] [reason] export async function execKickCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { diff --git a/src/commands/SuspendCommand.ts b/src/commands/SuspendCommand.ts index 85f8ec40..5ab6dc3a 100644 --- a/src/commands/SuspendCommand.ts +++ b/src/commands/SuspendCommand.ts @@ -16,7 +16,7 @@ limitations under the License. import { Mjolnir } from "../Mjolnir"; import { RichReply } from "@vector-im/matrix-bot-sdk"; -import {htmlEscape} from "../utils"; +import { htmlEscape } from "../utils"; export async function execSuspendCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { const target = parts[2]; diff --git a/src/protections/BasicFlooding.ts b/src/protections/BasicFlooding.ts index 76651f37..0c52d2a8 100644 --- a/src/protections/BasicFlooding.ts +++ b/src/protections/BasicFlooding.ts @@ -18,7 +18,7 @@ import { Protection } from "./IProtection"; import { NumberProtectionSetting } from "./ProtectionSettings"; import { Mjolnir } from "../Mjolnir"; import { LogLevel, LogService } from "@vector-im/matrix-bot-sdk"; -import {htmlEscape} from "../utils"; +import { htmlEscape } from "../utils"; // if this is exceeded, we'll ban the user for spam and redact their messages export const DEFAULT_MAX_PER_MINUTE = 10; diff --git a/src/protections/FirstMessageIsImage.ts b/src/protections/FirstMessageIsImage.ts index 1fcc340f..fe3d129a 100644 --- a/src/protections/FirstMessageIsImage.ts +++ b/src/protections/FirstMessageIsImage.ts @@ -17,7 +17,7 @@ limitations under the License. import { Protection } from "./IProtection"; import { Mjolnir } from "../Mjolnir"; import { LogLevel, LogService } from "@vector-im/matrix-bot-sdk"; -import {htmlEscape, isTrueJoinEvent} from "../utils"; +import { htmlEscape, isTrueJoinEvent } from "../utils"; export class FirstMessageIsImage extends Protection { private justJoined: { [roomId: string]: string[] } = {};