mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 01:36:06 -04:00
support compound consequences, switch WordList to consequences (#351)
This commit is contained in:
parent
89b7ec1a18
commit
f108935d07
@ -35,7 +35,7 @@ import { applyUserBans } from "./actions/ApplyBan";
|
||||
import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache";
|
||||
import { Protection } from "./protections/IProtection";
|
||||
import { PROTECTIONS } from "./protections/protections";
|
||||
import { ConsequenceType, Consequence } from "./protections/consequence";
|
||||
import { Consequence } from "./protections/consequence";
|
||||
import { ProtectionSettingValidationError } from "./protections/ProtectionSettings";
|
||||
import { UnlistedUserRedactionQueue } from "./queues/UnlistedUserRedactionQueue";
|
||||
import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQueue";
|
||||
@ -959,36 +959,37 @@ export class Mjolnir {
|
||||
await this.printBanlistChanges(changes, policyList, true);
|
||||
}
|
||||
|
||||
private async handleConsequence(protection: Protection, roomId: string, eventId: string, sender: string, consequence: Consequence) {
|
||||
switch (consequence.type) {
|
||||
case ConsequenceType.alert:
|
||||
break;
|
||||
case ConsequenceType.redact:
|
||||
await this.client.redactEvent(roomId, eventId, "abuse detected");
|
||||
break;
|
||||
case ConsequenceType.ban:
|
||||
await this.client.banUser(sender, roomId, "abuse detected");
|
||||
break;
|
||||
}
|
||||
private async handleConsequences(protection: Protection, roomId: string, eventId: string, sender: string, consequences: Consequence[]) {
|
||||
for (const consequence of consequences) {
|
||||
try {
|
||||
if (consequence.name === "alert") {
|
||||
/* take no additional action, just print the below message to management room */
|
||||
} else if (consequence.name === "ban") {
|
||||
await this.client.banUser(sender, roomId, "abuse detected");
|
||||
} else if (consequence.name === "redact") {
|
||||
await this.client.redactEvent(roomId, eventId, "abuse detected");
|
||||
} else {
|
||||
throw new Error(`unknown consequence ${consequence.name}`);
|
||||
}
|
||||
|
||||
let message = `protection ${protection.name} enacting ${ConsequenceType[consequence.type]}`
|
||||
+ ` against ${htmlEscape(sender)}`
|
||||
+ ` in ${htmlEscape(roomId)}`;
|
||||
if (consequence.reason !== undefined) {
|
||||
// even though internally-sourced, there's no promise that `consequence.reason`
|
||||
// will never have user-supplied information, so escape it
|
||||
message += ` (reason: ${htmlEscape(consequence.reason)})`;
|
||||
}
|
||||
|
||||
await this.client.sendMessage(this.managementRoomId, {
|
||||
msgtype: "m.notice",
|
||||
body: message,
|
||||
[CONSEQUENCE_EVENT_DATA]: {
|
||||
who: sender,
|
||||
room: roomId,
|
||||
type: ConsequenceType[consequence.type]
|
||||
let message = `protection ${protection.name} enacting`
|
||||
+ ` ${consequence.name}`
|
||||
+ ` against ${htmlEscape(sender)}`
|
||||
+ ` in ${htmlEscape(roomId)}`
|
||||
+ ` (reason: ${htmlEscape(consequence.reason)})`;
|
||||
await this.client.sendMessage(this.managementRoomId, {
|
||||
msgtype: "m.notice",
|
||||
body: message,
|
||||
[CONSEQUENCE_EVENT_DATA]: {
|
||||
who: sender,
|
||||
room: roomId,
|
||||
types: [consequence.name],
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
await this.logMessage(LogLevel.ERROR, "handleConsequences", `Failed to enact ${consequence.name} consequence: ${e}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async handleEvent(roomId: string, event: any) {
|
||||
@ -1018,9 +1019,9 @@ export class Mjolnir {
|
||||
|
||||
// Iterate all the enabled protections
|
||||
for (const protection of this.enabledProtections) {
|
||||
let consequence: Consequence | undefined = undefined;
|
||||
let consequences: Consequence[] | undefined = undefined;
|
||||
try {
|
||||
consequence = await protection.handleEvent(this, roomId, event);
|
||||
consequences = await protection.handleEvent(this, roomId, event);
|
||||
} catch (e) {
|
||||
const eventPermalink = Permalinks.forEvent(roomId, event['event_id']);
|
||||
LogService.error("Mjolnir", "Error handling protection: " + protection.name);
|
||||
@ -1030,8 +1031,8 @@ export class Mjolnir {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (consequence !== undefined) {
|
||||
await this.handleConsequence(protection, roomId, event["event_id"], event["sender"], consequence);
|
||||
if (consequences !== undefined) {
|
||||
await this.handleConsequences(protection, roomId, event["event_id"], event["sender"], consequences);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ export abstract class Protection {
|
||||
* Handle a single event from a protected room, to decide if we need to
|
||||
* respond to it
|
||||
*/
|
||||
async handleEvent(mjolnir: Mjolnir, roomId: string, event: any): Promise<Consequence | any> {
|
||||
async handleEvent(mjolnir: Mjolnir, roomId: string, event: any): Promise<Consequence[] | any> {
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { Protection } from "./IProtection";
|
||||
import { ConsequenceBan, ConsequenceRedact } from "./consequence";
|
||||
import { Mjolnir } from "../Mjolnir";
|
||||
import { LogLevel, LogService } from "matrix-bot-sdk";
|
||||
import { isTrueJoinEvent } from "../utils";
|
||||
@ -95,21 +96,14 @@ export class WordList extends Protection {
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the test
|
||||
if (message && this.badWords!.test(message)) {
|
||||
await mjolnir.logMessage(LogLevel.WARN, "WordList", `Banning ${event['sender']} for word list violation in ${roomId}.`);
|
||||
if (!mjolnir.config.noop) {
|
||||
await mjolnir.client.banUser(event['sender'], roomId, "Word list violation");
|
||||
} else {
|
||||
await mjolnir.logMessage(LogLevel.WARN, "WordList", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
|
||||
}
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Redact the event
|
||||
if (!mjolnir.config.noop) {
|
||||
await mjolnir.client.redactEvent(roomId, event['event_id'], "spam");
|
||||
} else {
|
||||
await mjolnir.logMessage(LogLevel.WARN, "WordList", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
|
||||
}
|
||||
const matches = message.match(this.badWords!);
|
||||
if (matches) {
|
||||
const reason = `bad word: ${matches[0]}`;
|
||||
return [new ConsequenceBan(reason), new ConsequenceRedact(reason)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,44 @@
|
||||
|
||||
/*
|
||||
* Distinct individual actions that can be caused as a result of detected abuse
|
||||
*/
|
||||
export enum ConsequenceType {
|
||||
// effectively a no-op. just tell the management room
|
||||
alert,
|
||||
// redact the event that triggered this consequence
|
||||
redact,
|
||||
// ban the user that sent the event that triggered this consequence
|
||||
ban
|
||||
}
|
||||
|
||||
export class Consequence {
|
||||
/*
|
||||
* Action to take upon detection of abuse and an optional explanation of the detection
|
||||
* A requested action to take against a user after detected abuse
|
||||
*
|
||||
* @param type Action to take
|
||||
* @param name The name of the consequence being requested
|
||||
* @param reason Brief explanation of why we're taking an action, printed to management room.
|
||||
* this will be HTML escaped before printing, just in case it has user-provided data
|
||||
*/
|
||||
constructor(public readonly type: ConsequenceType, public readonly reason?: string) {}
|
||||
constructor(public name: string, public reason: string) { }
|
||||
}
|
||||
|
||||
export class ConsequenceAlert extends Consequence {
|
||||
/*
|
||||
* Request an alert to be created after detected abuse
|
||||
*
|
||||
* @param reason Brief explanation of why we're taking an action, printed to management room.
|
||||
* this will be HTML escaped before printing, just in case it has user-provided data
|
||||
*/
|
||||
constructor(reason: string) {
|
||||
super("alert", reason);
|
||||
}
|
||||
}
|
||||
export class ConsequenceRedact extends Consequence {
|
||||
/*
|
||||
* Request a message redaction after detected abuse
|
||||
*
|
||||
* @param reason Brief explanation of why we're taking an action, printed to management room.
|
||||
* this will be HTML escaped before printing, just in case it has user-provided data
|
||||
*/
|
||||
constructor(reason: string) {
|
||||
super("redact", reason);
|
||||
}
|
||||
}
|
||||
export class ConsequenceBan extends Consequence {
|
||||
/*
|
||||
* Request a ban after detected abuse
|
||||
*
|
||||
* @param reason Brief explanation of why we're taking an action, printed to management room.
|
||||
* this will be HTML escaped before printing, just in case it has user-provided data
|
||||
*/
|
||||
constructor(reason: string) {
|
||||
super("ban", reason);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { Mjolnir } from "../../src/Mjolnir";
|
||||
import { IProtection } from "../../src/protections/IProtection";
|
||||
import { newTestUser, noticeListener } from "./clientHelper";
|
||||
import { matrixClient, mjolnir } from "./mjolnirSetupUtils";
|
||||
import { ConsequenceType, Consequence } from "../../src/protections/consequence";
|
||||
import { ConsequenceBan, ConsequenceRedact } from "../../src/protections/consequence";
|
||||
|
||||
describe("Test: standard consequences", function() {
|
||||
let badUser;
|
||||
@ -33,7 +33,7 @@ describe("Test: standard consequences", function() {
|
||||
settings = { };
|
||||
handleEvent = async (mjolnir: Mjolnir, roomId: string, event: any) => {
|
||||
if (event.content.body === "ngmWkF") {
|
||||
return new Consequence(ConsequenceType.redact, "asd");
|
||||
return [new ConsequenceRedact("asd")];
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -77,7 +77,7 @@ describe("Test: standard consequences", function() {
|
||||
settings = { };
|
||||
handleEvent = async (mjolnir: Mjolnir, roomId: string, event: any) => {
|
||||
if (event.content.body === "7Uga3d") {
|
||||
return new Consequence(ConsequenceType.ban, "asd");
|
||||
return [new ConsequenceBan("asd")];
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -124,7 +124,7 @@ describe("Test: standard consequences", function() {
|
||||
settings = { };
|
||||
handleEvent = async (mjolnir: Mjolnir, roomId: string, event: any) => {
|
||||
if (event.content.body === "8HUnwb") {
|
||||
return new Consequence(ConsequenceType.ban, "asd");
|
||||
return [new ConsequenceBan("asd")];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user