Support automatically redacting users for certain ban reasons

This commit is contained in:
Travis Ralston 2019-12-09 19:56:12 -07:00
parent 6753e7f780
commit 959162c4a3
6 changed files with 46 additions and 19 deletions

View File

@ -57,6 +57,14 @@ noop: false
# server struggles with /state requests then set this to true.
fasterMembershipChecks: false
# A case-insensitive list of ban reasons to automatically redact a user's
# messages for. Typically this is useful to avoid having to type two commands
# to the bot. Use asterisks to represent globs (ie: "spam*testing" would match
# "spam for testing" as well as "spamtesting").
automaticallyRedactForReasons:
- "spam"
- "advertising"
# A list of rooms to protect (matrix.to URLs)
protectedRooms:
- "https://matrix.to/#/#yourroom:example.org"

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { LogLevel, LogService, MatrixClient, Permalinks } from "matrix-bot-sdk";
import { LogLevel, LogService, MatrixClient, MatrixGlob, Permalinks } from "matrix-bot-sdk";
import BanList, { ALL_RULE_TYPES } from "./models/BanList";
import { applyServerAcls } from "./actions/ApplyAcl";
import { RoomUpdateError } from "./models/RoomUpdateError";
@ -42,12 +42,17 @@ export class Mjolnir {
private currentState: string = STATE_NOT_STARTED;
private protections: IProtection[] = [];
private redactionQueue = new AutomaticRedactionQueue();
private automaticRedactionReasons: MatrixGlob[] = [];
constructor(
public readonly client: MatrixClient,
public readonly protectedRooms: { [roomId: string]: string },
private banLists: BanList[],
) {
for (const reason of config.automaticallyRedactForReasons) {
this.automaticRedactionReasons.push(new MatrixGlob(reason.toLowerCase()));
}
client.on("room.event", this.handleEvent.bind(this));
client.on("room.message", async (roomId, event) => {
@ -95,6 +100,10 @@ export class Mjolnir {
return this.redactionQueue;
}
public get automaticRedactGlobs(): MatrixGlob[] {
return this.automaticRedactionReasons;
}
public start() {
return this.client.start().then(async () => {
this.currentState = STATE_CHECKING_PERMISSIONS;

View File

@ -21,6 +21,7 @@ import config from "../config";
import { logMessage } from "../LogProxy";
import { LogLevel } from "matrix-bot-sdk";
import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";
import { redactUserMessagesIn } from "../utils";
/**
* Applies the member bans represented by the ban lists to the provided rooms, returning the
@ -67,6 +68,11 @@ export async function applyUserBans(lists: BanList[], roomIds: string[], mjolnir
await logMessage(LogLevel.DEBUG, "ApplyBan", `Banning ${member.userId} in ${roomId} for: ${userRule.reason}`);
if (!config.noop) {
// Always prioritize redactions above bans
if (mjolnir.automaticRedactGlobs.find(g => g.test(userRule.reason.toLowerCase()))) {
await redactUserMessagesIn(mjolnir.client, member.userId, [roomId]);
}
await mjolnir.client.banUser(member.userId, roomId, userRule.reason);
} else {
await logMessage(LogLevel.WARN, "ApplyBan", `Tried to ban ${member.userId} in ${roomId} but Mjolnir is running in no-op mode`);

View File

@ -15,10 +15,7 @@ limitations under the License.
*/
import { Mjolnir } from "../Mjolnir";
import { getMessagesByUserSinceLastJoin } from "../utils";
import config from "../config";
import { logMessage } from "../LogProxy";
import { LogLevel } from "matrix-bot-sdk";
import { redactUserMessagesIn } from "../utils";
// !mjolnir redact <user ID> [room alias]
export async function execRedactCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
@ -29,19 +26,7 @@ export async function execRedactCommand(roomId: string, event: any, mjolnir: Mjo
}
const targetRoomIds = roomAlias ? [roomAlias] : Object.keys(mjolnir.protectedRooms);
for (const targetRoomId of targetRoomIds) {
await logMessage(LogLevel.DEBUG, "RedactCommand", `Fetching sent messages for ${userId} in ${targetRoomId} to redact...`);
const eventsToRedact = await getMessagesByUserSinceLastJoin(mjolnir.client, userId, targetRoomId);
for (const victimEvent of eventsToRedact) {
await logMessage(LogLevel.DEBUG, "RedactCommand", `Redacting ${victimEvent['event_id']} in ${targetRoomId}`);
if (!config.noop) {
await mjolnir.client.redactEvent(targetRoomId, victimEvent['event_id']);
} else {
await logMessage(LogLevel.WARN, "RedactCommand", `Tried to redact ${victimEvent['event_id']} in ${targetRoomId} but Mjolnir is running in no-op mode`);
}
}
}
await redactUserMessagesIn(mjolnir.client, userId, targetRoomIds);
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
}

View File

@ -35,6 +35,7 @@ interface IConfig {
noop: boolean;
protectedRooms: string[]; // matrix.to urls
fasterMembershipChecks: boolean;
automaticallyRedactForReasons: string[]; // case-insensitive globs
/**
* Config options only set at runtime. Try to avoid using the objects

View File

@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { LogService, MatrixClient, MatrixGlob } from "matrix-bot-sdk";
import { LogLevel, LogService, MatrixClient, MatrixGlob } from "matrix-bot-sdk";
import { logMessage } from "./LogProxy";
import config from "./config";
export function setToArray<T>(set: Set<T>): T[] {
const arr: T[] = [];
@ -35,6 +37,22 @@ export function isTrueJoinEvent(event: any): boolean {
return membership === 'join' && prevMembership !== "join";
}
export async function redactUserMessagesIn(client: MatrixClient, userIdOrGlob: string, targetRoomIds: string[]) {
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);
for (const victimEvent of eventsToRedact) {
await logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Redacting ${victimEvent['event_id']} in ${targetRoomId}`);
if (!config.noop) {
await client.redactEvent(targetRoomId, victimEvent['event_id']);
} else {
await logMessage(LogLevel.WARN, "utils#redactUserMessagesIn", `Tried to redact ${victimEvent['event_id']} in ${targetRoomId} but Mjolnir is running in no-op mode`);
}
}
}
}
/**
* Gets all the events sent by a user (or users if using wildcards) in a given room ID, since
* the time they joined.