diff --git a/src/commands/CommandHandler.ts b/src/commands/CommandHandler.ts index 07603c0..3075ffd 100644 --- a/src/commands/CommandHandler.ts +++ b/src/commands/CommandHandler.ts @@ -104,7 +104,7 @@ export async function handleCommand(roomId: string, event: any, mjolnir: Mjolnir "!mjolnir status - Print status information\n" + "!mjolnir ban [reason] - Adds an entity to the ban list\n" + "!mjolnir unban [apply] - Removes an entity from the ban list. If apply is 'true', the users matching the glob will actually be unbanned\n" + - "!mjolnir redact [room alias/ID] - Redacts messages by the sender in the target room (or all rooms)\n" + + "!mjolnir redact [room alias/ID] [limit] - Redacts messages by the sender in the target room (or all rooms), up to a maximum number of events in the backlog (default 1000)\n" + "!mjolnir redact - Redacts a message by permalink\n" + "!mjolnir rules - Lists the rules currently in use by Mjolnir\n" + "!mjolnir sync - Force updates of all lists and re-apply rules\n" + diff --git a/src/commands/RedactCommand.ts b/src/commands/RedactCommand.ts index aa4bc55..ac781a8 100644 --- a/src/commands/RedactCommand.ts +++ b/src/commands/RedactCommand.ts @@ -18,25 +18,36 @@ import { Mjolnir } from "../Mjolnir"; import { redactUserMessagesIn } from "../utils"; import { Permalinks } from "matrix-bot-sdk"; -// !mjolnir redact [room alias] +// !mjolnir redact [room alias] [limit] export async function execRedactCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { const userId = parts[2]; let roomAlias = null; - if (parts.length > 3) { + let limit = Number.parseInt(parts.length > 3 ? parts[3] : null); // default to NaN for later + if (parts.length > 3 && isNaN(limit)) { roomAlias = await mjolnir.client.resolveRoom(parts[3]); + if (parts.length > 4) { + limit = Number.parseInt(parts[4]); + } } + // Make sure we always have a limit set + if (isNaN(limit)) limit = 1000; + + const processingReactionId = await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'In Progress'); + if (userId[0] !== '@') { // Assume it's a permalink const parsed = Permalinks.parseUrl(parts[2]); const targetRoomId = await mjolnir.client.resolveRoom(parsed.roomIdOrAlias); await mjolnir.client.redactEvent(targetRoomId, parsed.eventId); await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅'); + await mjolnir.client.redactEvent(roomId, processingReactionId, 'done processing command'); return; } const targetRoomIds = roomAlias ? [roomAlias] : Object.keys(mjolnir.protectedRooms); - await redactUserMessagesIn(mjolnir.client, userId, targetRoomIds); + await redactUserMessagesIn(mjolnir.client, userId, targetRoomIds, limit); await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅'); + await mjolnir.client.redactEvent(roomId, processingReactionId, 'done processing'); } diff --git a/src/utils.ts b/src/utils.ts index 30df8d5..392f046 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -37,11 +37,11 @@ export function isTrueJoinEvent(event: any): boolean { return membership === 'join' && prevMembership !== "join"; } -export async function redactUserMessagesIn(client: MatrixClient, userIdOrGlob: string, targetRoomIds: string[]) { +export async function redactUserMessagesIn(client: MatrixClient, userIdOrGlob: string, targetRoomIds: string[], limit = 1000) { for (const targetRoomId of targetRoomIds) { await logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Fetching sent messages for ${userIdOrGlob} in ${targetRoomId} to redact...`); - const eventsToRedact = await getMessagesByUserSinceLastJoin(client, userIdOrGlob, targetRoomId); + const eventsToRedact = await getMessagesByUserIn(client, userIdOrGlob, targetRoomId, limit); for (const victimEvent of eventsToRedact) { await logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Redacting ${victimEvent['event_id']} in ${targetRoomId}`); if (!config.noop) { @@ -59,20 +59,20 @@ export async function redactUserMessagesIn(client: MatrixClient, userIdOrGlob: s * @param {MatrixClient} client The client to use. * @param {string} sender The sender. Can include wildcards to match multiple people. * @param {string} roomId The room ID to search in. + * @param {number} limit The maximum number of messages to search. Defaults to 1000. * @returns {Promise} Resolves to the events sent by the user(s) prior to join. */ -export async function getMessagesByUserSinceLastJoin(client: MatrixClient, sender: string, roomId: string): Promise { - const limit = 1000; // maximum number of events to process, regardless of outcome +export async function getMessagesByUserIn(client: MatrixClient, sender: string, roomId: string, limit: number): Promise { const filter = { room: { rooms: [roomId], state: { - types: ["m.room.member"], + // types: ["m.room.member"], // We'll redact all types of events rooms: [roomId], }, timeline: { rooms: [roomId], - types: ["m.room.message"], + // types: ["m.room.message"], // We'll redact all types of events }, ephemeral: { limit: 0, @@ -131,7 +131,6 @@ export async function getMessagesByUserSinceLastJoin(client: MatrixClient, sende if (!response) return []; const messages = []; - const stopProcessingMembers = []; let processed = 0; const timeline = (((response['rooms'] || {})['join'] || {})[roomId] || {})['timeline'] || {}; @@ -143,12 +142,7 @@ export async function getMessagesByUserSinceLastJoin(client: MatrixClient, sende if (processed >= limit) return messages; // we're done even if we don't want to be processed++; - if (stopProcessingMembers.includes(event['sender'])) continue; if (testUser(event['sender'])) messages.push(event); - if (event['type'] === 'm.room.member' && testUser(event['state_key']) && isTrueJoinEvent(event)) { - stopProcessingMembers.push(event['sender']); - if (!isGlob) return messages; // done! - } } if (token) {