From f2ca79ca0ec3b0db4b3d962bf82f79f20dbd11a2 Mon Sep 17 00:00:00 2001 From: Shay Date: Fri, 22 Nov 2024 14:18:14 -0800 Subject: [PATCH] Use spoilers when mentioning targets of a rule (#559) * make managment room id public so it can be used to send messages * use spoilers when mentioning targets of a rule * html escapse targets * lint --- src/ManagementRoomOutput.ts | 2 +- src/ProtectedRoomsSet.ts | 27 +++++++++++++------------- src/commands/KickCommand.ts | 24 ++++++++++++----------- src/commands/SuspendCommand.ts | 4 +++- src/protections/BasicFlooding.ts | 13 +++++++------ src/protections/FirstMessageIsImage.ts | 13 +++++++------ src/utils.ts | 25 ++++++++++++------------ 7 files changed, 57 insertions(+), 51 deletions(-) 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, ) {} diff --git a/src/ProtectedRoomsSet.ts b/src/ProtectedRoomsSet.ts index a88b414a..9b8b345b 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 ${htmlEscape(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 ${htmlEscape(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..c49b4281 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[]) { @@ -57,12 +58,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 ${htmlEscape(target)} in ${protectedRoomId}.`, + }); if (!mjolnir.config.noop) { try { @@ -70,11 +71,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 ${htmlEscape(target)}: ${e}.`, + }); } } else { await mjolnir.managementRoomOutput.logMessage( diff --git a/src/commands/SuspendCommand.ts b/src/commands/SuspendCommand.ts index 33bef2d7..5ab6dc3a 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,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 ${htmlEscape(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..0c52d2a8 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; @@ -68,12 +69,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 ${htmlEscape(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..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 { isTrueJoinEvent } from "../utils"; +import { htmlEscape, isTrueJoinEvent } from "../utils"; export class FirstMessageIsImage extends Protection { private justJoined: { [roomId: string]: string[] } = {}; @@ -58,11 +58,12 @@ export class FirstMessageIsImage extends Protection { const isMedia = msgtype === "m.image" || msgtype === "m.video" || formattedBody.toLowerCase().includes("${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"])) { await mjolnir.managementRoomOutput.logMessage( diff --git a/src/utils.ts b/src/utils.ts index 05893a34..eca9a2a7 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 ${htmlEscape(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 ${htmlEscape(userIdOrGlob)}, please check logs for more info - falling + back to non-admin redaction process.`, + }); await botRedactUserMessagesIn(client, managementRoom, userIdOrGlob, filteredRooms, limit, noop); } } else {