mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-09-29 20:56:23 +00:00
WIP
This commit is contained in:
parent
77784a88b1
commit
df63b608e7
@ -192,3 +192,42 @@ web:
|
|||||||
abuseReporting:
|
abuseReporting:
|
||||||
# Whether to enable this feature.
|
# Whether to enable this feature.
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
# Uncomment to enable support for undoable actions.
|
||||||
|
#
|
||||||
|
# As of this writing, the only undoable action is `softredact`.
|
||||||
|
#
|
||||||
|
#trashcan:
|
||||||
|
# # The room in which we store undoable actions for review.
|
||||||
|
# # Mjölnir will not create it. This can be the same as the
|
||||||
|
# # management room but it doesn't have to.
|
||||||
|
# roomAliasOrId: "#trashcan:localhost"
|
||||||
|
#
|
||||||
|
# # Support for soft redactions, i.e. using Mjölnir to mark
|
||||||
|
# # messages as hidden pending moderation.
|
||||||
|
# #
|
||||||
|
# # As of this writing, this feature only works with develop
|
||||||
|
# # versions of Element Web, with feature
|
||||||
|
# # `feature_msc3531_hide_messages_pending_moderation`
|
||||||
|
# # enabled.
|
||||||
|
# #
|
||||||
|
# # Comment this out if you wish to disable soft redactions.
|
||||||
|
# softRedact:
|
||||||
|
# # After `goodAfterMs` milliseconds, any message hidden
|
||||||
|
# # pending moderation by Mjölnir will revert to visible
|
||||||
|
# # and vanish from the trashcan.
|
||||||
|
# # Ignored if `goodAfterMs` is >= `badAfterMs` or `retainMs`.
|
||||||
|
# goodAfterMs: .inf
|
||||||
|
#
|
||||||
|
# # After `badAfterMs` milliseconds, any message hidden
|
||||||
|
# # pending moderation by Mjölnir will be irreversibly
|
||||||
|
# # deleted from homeservers and up-to-date clients and
|
||||||
|
# # vanish from the trashcan.
|
||||||
|
# # Ignored if `badAfterMs` is > `goodAfterMs` or `retainMs`.
|
||||||
|
# badAfterMs: .inf
|
||||||
|
#
|
||||||
|
# # After `retainMs` milliseconds, any message hidden
|
||||||
|
# # pending moderation will vanish from the trashcan, without
|
||||||
|
# # further action.
|
||||||
|
# # Ignored if `retainMs` is > `goodAfterMs` or `badAfterMs`.
|
||||||
|
# retainMs: 604800000 # Approximately one week.
|
||||||
|
@ -42,6 +42,7 @@ import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQu
|
|||||||
import * as htmlEscape from "escape-html";
|
import * as htmlEscape from "escape-html";
|
||||||
import { ReportManager } from "./report/ReportManager";
|
import { ReportManager } from "./report/ReportManager";
|
||||||
import { WebAPIs } from "./webapis/WebAPIs";
|
import { WebAPIs } from "./webapis/WebAPIs";
|
||||||
|
import { TrashManager } from "./trashcan/TrashManager";
|
||||||
|
|
||||||
export const STATE_NOT_STARTED = "not_started";
|
export const STATE_NOT_STARTED = "not_started";
|
||||||
export const STATE_CHECKING_PERMISSIONS = "checking_permissions";
|
export const STATE_CHECKING_PERMISSIONS = "checking_permissions";
|
||||||
@ -73,6 +74,8 @@ export class Mjolnir {
|
|||||||
private explicitlyProtectedRoomIds: string[] = [];
|
private explicitlyProtectedRoomIds: string[] = [];
|
||||||
private knownUnprotectedRooms: string[] = [];
|
private knownUnprotectedRooms: string[] = [];
|
||||||
private webapis: WebAPIs;
|
private webapis: WebAPIs;
|
||||||
|
public trashcanManager?: TrashManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a listener to the client that will automatically accept invitations.
|
* Adds a listener to the client that will automatically accept invitations.
|
||||||
* @param {MatrixClient} client
|
* @param {MatrixClient} client
|
||||||
@ -146,15 +149,25 @@ export class Mjolnir {
|
|||||||
} else {
|
} else {
|
||||||
config.managementRoom = managementRoomId;
|
config.managementRoom = managementRoomId;
|
||||||
}
|
}
|
||||||
await logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status.");
|
|
||||||
|
|
||||||
return new Mjolnir(client, protectedRooms, banLists);
|
let trashcanRoomId;
|
||||||
|
if (config.trashcan) {
|
||||||
|
trashcanRoomId = await client.resolveRoom(config.trashcan.roomAliasOrId);
|
||||||
|
const joinedRooms = await client.getJoinedRooms();
|
||||||
|
if (!joinedRooms.includes(trashcanRoomId)) {
|
||||||
|
await client.joinRoom(trashcanRoomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status.");
|
||||||
|
return new Mjolnir(client, protectedRooms, banLists, trashcanRoomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly client: MatrixClient,
|
public readonly client: MatrixClient,
|
||||||
public readonly protectedRooms: { [roomId: string]: string },
|
public readonly protectedRooms: { [roomId: string]: string },
|
||||||
private banLists: BanList[],
|
private banLists: BanList[],
|
||||||
|
trashcanRoomId?: string,
|
||||||
) {
|
) {
|
||||||
this.explicitlyProtectedRoomIds = Object.keys(this.protectedRooms);
|
this.explicitlyProtectedRoomIds = Object.keys(this.protectedRooms);
|
||||||
|
|
||||||
@ -222,6 +235,12 @@ export class Mjolnir {
|
|||||||
// Setup Web APIs
|
// Setup Web APIs
|
||||||
console.log("Creating Web APIs");
|
console.log("Creating Web APIs");
|
||||||
this.webapis = new WebAPIs(new ReportManager(this));
|
this.webapis = new WebAPIs(new ReportManager(this));
|
||||||
|
|
||||||
|
// Setup trashcan.
|
||||||
|
console.log("Creating Trashcan Manager");
|
||||||
|
if (trashcanRoomId) {
|
||||||
|
this.trashcanManager = new TrashManager(this, trashcanRoomId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get lists(): BanList[] {
|
public get lists(): BanList[] {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
|
Copyright 2019-2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -24,7 +24,8 @@ import { execSyncCommand } from "./SyncCommand";
|
|||||||
import { execPermissionCheckCommand } from "./PermissionCheckCommand";
|
import { execPermissionCheckCommand } from "./PermissionCheckCommand";
|
||||||
import { execCreateListCommand } from "./CreateBanListCommand";
|
import { execCreateListCommand } from "./CreateBanListCommand";
|
||||||
import { execUnwatchCommand, execWatchCommand } from "./WatchUnwatchCommand";
|
import { execUnwatchCommand, execWatchCommand } from "./WatchUnwatchCommand";
|
||||||
import { execRedactCommand } from "./RedactCommand";
|
import { execHardRedactCommand } from "./HardRedactCommand";
|
||||||
|
import { execSoftRedactCommand } from "./SoftRedactCommand";
|
||||||
import { execImportCommand } from "./ImportCommand";
|
import { execImportCommand } from "./ImportCommand";
|
||||||
import { execSetDefaultListCommand } from "./SetDefaultBanListCommand";
|
import { execSetDefaultListCommand } from "./SetDefaultBanListCommand";
|
||||||
import { execDeactivateCommand } from "./DeactivateCommand";
|
import { execDeactivateCommand } from "./DeactivateCommand";
|
||||||
@ -63,7 +64,9 @@ export async function handleCommand(roomId: string, event: any, mjolnir: Mjolnir
|
|||||||
} else if (parts[1] === 'unwatch' && parts.length > 1) {
|
} else if (parts[1] === 'unwatch' && parts.length > 1) {
|
||||||
return await execUnwatchCommand(roomId, event, mjolnir, parts);
|
return await execUnwatchCommand(roomId, event, mjolnir, parts);
|
||||||
} else if (parts[1] === 'redact' && parts.length > 1) {
|
} else if (parts[1] === 'redact' && parts.length > 1) {
|
||||||
return await execRedactCommand(roomId, event, mjolnir, parts);
|
return await execHardRedactCommand(roomId, event, mjolnir, parts);
|
||||||
|
} else if (parts[1] === 'hide' && parts.length > 1) {
|
||||||
|
return await execSoftRedactCommand(roomId, event, mjolnir, parts);
|
||||||
} else if (parts[1] === 'import' && parts.length > 2) {
|
} else if (parts[1] === 'import' && parts.length > 2) {
|
||||||
return await execImportCommand(roomId, event, mjolnir, parts);
|
return await execImportCommand(roomId, event, mjolnir, parts);
|
||||||
} else if (parts[1] === 'default' && parts.length > 2) {
|
} else if (parts[1] === 'default' && parts.length > 2) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019-2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -18,8 +18,16 @@ import { Mjolnir } from "../Mjolnir";
|
|||||||
import { redactUserMessagesIn } from "../utils";
|
import { redactUserMessagesIn } from "../utils";
|
||||||
import { Permalinks } from "matrix-bot-sdk";
|
import { Permalinks } from "matrix-bot-sdk";
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* "Hard redaction", cannot be undone.
|
||||||
|
*
|
||||||
|
* Instruct all participant homeservers to permanently erase the
|
||||||
|
* contents of the message from the timeline and to request the
|
||||||
|
* same from all clients.
|
||||||
|
*/
|
||||||
|
|
||||||
// !mjolnir redact <user ID> [room alias] [limit]
|
// !mjolnir redact <user ID> [room alias] [limit]
|
||||||
export async function execRedactCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
|
export async function execHardRedactCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
|
||||||
const userId = parts[2];
|
const userId = parts[2];
|
||||||
let roomAlias: string|null = null;
|
let roomAlias: string|null = null;
|
||||||
let limit = Number.parseInt(parts.length > 3 ? parts[3] : "", 10); // default to NaN for later
|
let limit = Number.parseInt(parts.length > 3 ? parts[3] : "", 10); // default to NaN for later
|
66
src/commands/SoftRedactCommand.ts
Normal file
66
src/commands/SoftRedactCommand.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 { redactUserMessagesIn } from "../utils";
|
||||||
|
import { Permalinks } from "matrix-bot-sdk";
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* "Soft redaction", aka "undoable redaction".
|
||||||
|
*
|
||||||
|
* 1. Instruct all clients to mark the message as hidden
|
||||||
|
* pending moderation. Only moderators and the author
|
||||||
|
* of the message may still see it.
|
||||||
|
* 2. Copy the message to the trashcan room.
|
||||||
|
* 3. In the trashcan room, moderators have the ability
|
||||||
|
* to either restore the message or trash (i.e. redact)
|
||||||
|
* it permanently.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// !mjolnir hide <user ID> [room alias] [limit]
|
||||||
|
export async function execSoftRedactCommand(roomId: string, instructionEvent: any, mjolnir: Mjolnir, parts: string[]) {
|
||||||
|
// This could either be an eventId or a userId.
|
||||||
|
const targetId = parts[2];
|
||||||
|
let roomAlias: string | null = null;
|
||||||
|
let limit = Number.parseInt(parts.length > 3 ? parts[3] : "", 10); // 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], 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we always have a limit set
|
||||||
|
if (isNaN(limit)) limit = 1000;
|
||||||
|
|
||||||
|
const processingReactionId = await mjolnir.client.unstableApis.addReactionToEvent(roomId, instructionEvent['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, instructionEvent['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, limit);
|
||||||
|
|
||||||
|
await mjolnir.client.unstableApis.addReactionToEvent(roomId, instructionEvent['event_id'], '✅');
|
||||||
|
await mjolnir.client.redactEvent(roomId, processingReactionId, 'done processing');
|
||||||
|
}
|
@ -76,7 +76,15 @@ interface IConfig {
|
|||||||
abuseReporting: {
|
abuseReporting: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
trashcan?: {
|
||||||
|
roomAliasOrId: string;
|
||||||
|
softRedact?: {
|
||||||
|
goodAfterMs: number;
|
||||||
|
badAfterMs: number;
|
||||||
|
retainMs: number;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config options only set at runtime. Try to avoid using the objects
|
* Config options only set at runtime. Try to avoid using the objects
|
||||||
|
Loading…
Reference in New Issue
Block a user