Add a redact command

This commit is contained in:
Travis Ralston 2019-10-09 15:53:37 +01:00
parent 28f739a3a2
commit 78b73153b7
4 changed files with 138 additions and 1 deletions

View File

@ -15,7 +15,7 @@ Phase 1:
Phase 2:
* [x] Pantalaimon support
* [x] No-op mode (for verifying behaviour)
* [ ] Redact messages on ban (optionally)
* [x] Redact messages on ban (optionally)
* [x] More useful spam in management room
* [ ] Command to import ACLs, etc from rooms
* [x] Vet rooms on startup option

View File

@ -24,6 +24,7 @@ import { execSyncCommand } from "./SyncCommand";
import { execPermissionCheckCommand } from "./PermissionCheckCommand";
import { execCreateListCommand } from "./CreateBanListCommand";
import { execUnwatchCommand, execWatchCommand } from "./WatchUnwatchCommand";
import { execRedactCommand } from "./RedactCommand";
export const COMMAND_PREFIX = "!mjolnir";
@ -50,6 +51,8 @@ export async function handleCommand(roomId: string, event: any, mjolnir: Mjolnir
return await execWatchCommand(roomId, event, mjolnir, parts);
} else if (parts[1] === 'unwatch' && parts.length > 1) {
return await execUnwatchCommand(roomId, event, mjolnir, parts);
} else if (parts[1] === 'redact' && parts.length > 1) {
return await execRedactCommand(roomId, event, mjolnir, parts);
} else {
// Help menu
const menu = "" +
@ -57,6 +60,7 @@ export async function handleCommand(roomId: string, event: any, mjolnir: Mjolnir
"!mjolnir status - Print status information\n" +
"!mjolnir ban <list_shortcode> <user|room|server> <glob> [reason] - Adds an entity to the ban list\n" +
"!mjolnir unban <list_shortcode> <user|room|server> <glob> - Removes an entity from the ban list\n" +
"!mjolnir redact <user_id> [room alias/ID] - Redacts messages by the sender in the target room (or all rooms)\n" +
"!mjolnir rules - Lists the rules currently in use by Mjolnir\n" +
"!mjolnir sync - Force updates of all lists and re-apply rules\n" +
"!mjolnir verify - Ensures Mjolnir can moderate all your rooms\n" +

View File

@ -0,0 +1,47 @@
/*
Copyright 2019 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.
*/
import { Mjolnir } from "../Mjolnir";
import { getMessagesByUserSinceLastJoin } from "../utils";
import config from "../config";
// !mjolnir redact <user ID> [room alias]
export async function execRedactCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const userId = parts[2];
let roomAlias = null;
if (parts.length > 3) {
roomAlias = await mjolnir.client.resolveRoom(parts[3]);
}
const targetRoomIds = roomAlias ? [roomAlias] : Object.keys(mjolnir.protectedRooms);
for (const targetRoomId of targetRoomIds) {
if (config.verboseLogging) {
await mjolnir.client.sendNotice(mjolnir.managementRoomId, `Fetching sent messages for ${userId} in ${targetRoomId} to redact...`);
}
const eventsToRedact = await getMessagesByUserSinceLastJoin(mjolnir.client, userId, targetRoomId);
for (const event of eventsToRedact) {
if (config.verboseLogging) {
await mjolnir.client.sendNotice(mjolnir.managementRoomId, `Redacting ${event['event_id']} in ${targetRoomId}`);
}
if (!config.noop) {
await mjolnir.client.redactEvent(targetRoomId, event['event_id']);
}
}
}
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
}

View File

@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient } from "matrix-bot-sdk";
export function setToArray<T>(set: Set<T>): T[] {
const arr: T[] = [];
for (const v of set) {
@ -21,3 +23,87 @@ export function setToArray<T>(set: Set<T>): T[] {
}
return arr;
}
export async function getMessagesByUserSinceLastJoin(client: MatrixClient, sender: string, roomId: string): Promise<any[]> {
const filter = {
room: {
rooms: [roomId],
state: {
types: ["m.room.member"],
rooms: [roomId],
},
timeline: {
senders: [sender],
rooms: [roomId],
},
ephemeral: {
limit: 0,
types: [],
},
account_data: {
limit: 0,
types: [],
},
},
presence: {
limit: 0,
types: [],
},
account_data: {
limit: 0,
types: [],
},
};
function initialSync() {
const qs = {
filter: JSON.stringify(filter),
};
return client.doRequest("GET", "/_matrix/client/r0/sync", qs);
}
function backfill(from: string) {
const qs = {
filter: JSON.stringify(filter),
from: from,
dir: "b",
};
return client.doRequest("GET", `/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/messages`, qs);
}
// Do an initial sync first to get the batch token
const response = await initialSync();
if (!response) return [];
let token = response['next_batch'];
const messages = [];
const timeline = (((response['rooms'] || {})['join'] || {})[roomId] || {})['timeline'] || {};
const syncedMessages = timeline['events'] || [];
token = timeline['prev_batch'] || token;
for (const event of syncedMessages) {
if (event['sender'] === sender) messages.push(event);
if (event['type'] === 'm.room.member' && event['state_key'] === sender) {
if (event['content'] && event['content']['membership'] === 'join') {
return messages; // we're done!
}
}
}
while (token) {
const bfMessages = await backfill(token);
token = bfMessages['end'];
for (const event of (bfMessages['chunk'] || [])) {
if (event['sender'] === sender) messages.push(event);
if (event['type'] === 'm.room.member' && event['state_key'] === sender) {
if (event['content'] && event['content']['membership'] === 'join') {
return messages; // we're done!
}
}
}
}
return messages;
}