mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 01:36:06 -04:00
Add commands for managing a personal ban list
This commit is contained in:
parent
41cc36e4c6
commit
39b59dbee1
@ -8,8 +8,8 @@ TODO: Describe what all this means.
|
|||||||
|
|
||||||
Phase 1:
|
Phase 1:
|
||||||
* [ ] Ban users
|
* [ ] Ban users
|
||||||
* [ ] ACL servers
|
* [x] ACL servers
|
||||||
* [ ] Update lists with new bans/ACLs
|
* [x] Update lists with new bans/ACLs
|
||||||
* [ ] "Ban on sight" mode (rather than proactive)
|
* [ ] "Ban on sight" mode (rather than proactive)
|
||||||
|
|
||||||
Phase 2:
|
Phase 2:
|
||||||
|
@ -15,6 +15,12 @@ autojoin: true
|
|||||||
# This should be a room alias or room ID - not a matrix.to URL.
|
# This should be a room alias or room ID - not a matrix.to URL.
|
||||||
managementRoom: "#moderators:example.org"
|
managementRoom: "#moderators:example.org"
|
||||||
|
|
||||||
|
# The room ID or alias where the bot's own personal ban list is kept. This is
|
||||||
|
# where the commands to manage a ban list end up being routed to. Note that
|
||||||
|
# this room is NOT automatically added to the banLists list below - you will
|
||||||
|
# need to add it yourself!
|
||||||
|
publishedBanListRoom: "#banlist:example.org"
|
||||||
|
|
||||||
# A list of rooms to protect (matrix.to URLs)
|
# A list of rooms to protect (matrix.to URLs)
|
||||||
protectedRooms:
|
protectedRooms:
|
||||||
- "https://matrix.to/#/#yourroom:example.org"
|
- "https://matrix.to/#/#yourroom:example.org"
|
||||||
|
@ -23,7 +23,8 @@ import { COMMAND_PREFIX, handleCommand } from "./commands/CommandHandler";
|
|||||||
export class Mjolnir {
|
export class Mjolnir {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly client: MatrixClient,
|
public readonly client: MatrixClient,
|
||||||
private managementRoomId: string,
|
public readonly managementRoomId: string,
|
||||||
|
public readonly publishedBanListRoomId: string,
|
||||||
public readonly protectedRooms: { [roomId: string]: string },
|
public readonly protectedRooms: { [roomId: string]: string },
|
||||||
public readonly banLists: BanList[],
|
public readonly banLists: BanList[],
|
||||||
) {
|
) {
|
||||||
@ -49,10 +50,13 @@ export class Mjolnir {
|
|||||||
if (!event['state_key']) return; // we also don't do anything with state events that have no state key
|
if (!event['state_key']) return; // we also don't do anything with state events that have no state key
|
||||||
|
|
||||||
if (ALL_RULE_TYPES.includes(event['type'])) {
|
if (ALL_RULE_TYPES.includes(event['type'])) {
|
||||||
|
let updated = false;
|
||||||
for (const list of this.banLists) {
|
for (const list of this.banLists) {
|
||||||
if (list.roomId !== roomId) continue;
|
if (list.roomId !== roomId) continue;
|
||||||
await list.updateList();
|
await list.updateList();
|
||||||
|
updated = true;
|
||||||
}
|
}
|
||||||
|
if (!updated) return;
|
||||||
|
|
||||||
const errors = await applyServerAcls(this.banLists, Object.keys(this.protectedRooms), this.client);
|
const errors = await applyServerAcls(this.banLists, Object.keys(this.protectedRooms), this.client);
|
||||||
return this.printActionResult(errors);
|
return this.printActionResult(errors);
|
||||||
|
@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixClient } from "matrix-bot-sdk";
|
|
||||||
import { Mjolnir } from "../Mjolnir";
|
import { Mjolnir } from "../Mjolnir";
|
||||||
import { execStatusCommand } from "./StatusCommand";
|
import { execStatusCommand } from "./StatusCommand";
|
||||||
|
import { execBanCommand, execUnbanCommand } from "./UnbanBanCommand";
|
||||||
|
|
||||||
export const COMMAND_PREFIX = "!mjolnir";
|
export const COMMAND_PREFIX = "!mjolnir";
|
||||||
|
|
||||||
@ -26,6 +26,10 @@ export function handleCommand(roomId: string, event: any, mjolnir: Mjolnir) {
|
|||||||
|
|
||||||
if (parts.length === 1) {
|
if (parts.length === 1) {
|
||||||
return execStatusCommand(roomId, event, mjolnir);
|
return execStatusCommand(roomId, event, mjolnir);
|
||||||
|
} else if (parts[1] === 'ban' && parts.length > 3) {
|
||||||
|
return execBanCommand(roomId, event, mjolnir, parts);
|
||||||
|
} else if (parts[1] === 'unban' && parts.length > 3) {
|
||||||
|
return execUnbanCommand(roomId, event, mjolnir, parts);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Help menu
|
// TODO: Help menu
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import { Mjolnir } from "../Mjolnir";
|
import { Mjolnir } from "../Mjolnir";
|
||||||
|
|
||||||
|
// !mjolnir
|
||||||
export async function execStatusCommand(roomId: string, event: any, mjolnir: Mjolnir) {
|
export async function execStatusCommand(roomId: string, event: any, mjolnir: Mjolnir) {
|
||||||
let html = "";
|
let html = "";
|
||||||
let text = "";
|
let text = "";
|
||||||
|
81
src/commands/UnbanBanCommand.ts
Normal file
81
src/commands/UnbanBanCommand.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
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 { RULE_ROOM, RULE_SERVER, RULE_USER, ruleTypeToStable } from "../models/BanList";
|
||||||
|
import { RichReply } from "matrix-bot-sdk";
|
||||||
|
import { RECOMMENDATION_BAN, recommendationToStable } from "../models/ListRule";
|
||||||
|
|
||||||
|
function parseBits(parts: string[]): { entityType: string, ruleType: string, glob: string, reason: string } {
|
||||||
|
const entityType = parts[2].toLowerCase();
|
||||||
|
const glob = parts[3];
|
||||||
|
const reason = parts.slice(4).join(' ') || "<no reason>";
|
||||||
|
|
||||||
|
let rule = null;
|
||||||
|
if (entityType === "user") {
|
||||||
|
rule = RULE_USER;
|
||||||
|
} else if (entityType === "room") {
|
||||||
|
rule = RULE_ROOM;
|
||||||
|
} else if (entityType === "server") {
|
||||||
|
rule = RULE_SERVER;
|
||||||
|
}
|
||||||
|
if (!rule) {
|
||||||
|
return {entityType, ruleType: null, glob, reason};
|
||||||
|
}
|
||||||
|
rule = ruleTypeToStable(rule);
|
||||||
|
|
||||||
|
return {entityType, ruleType: rule, glob, reason};
|
||||||
|
}
|
||||||
|
|
||||||
|
// !mjolnir ban <user|server|room> <glob> [reason]
|
||||||
|
export async function execBanCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
|
||||||
|
const bits = parseBits(parts);
|
||||||
|
if (!bits.ruleType) {
|
||||||
|
const replyText = "Unknown entity type '" + bits.entityType + "' - try one of user, room, or server";
|
||||||
|
const reply = RichReply.createFor(roomId, event, replyText, replyText);
|
||||||
|
reply["msgtype"] = "m.notice";
|
||||||
|
return mjolnir.client.sendMessage(roomId, reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recommendation = recommendationToStable(RECOMMENDATION_BAN);
|
||||||
|
const ruleContent = {
|
||||||
|
entity: bits.glob,
|
||||||
|
recommendation,
|
||||||
|
reason: bits.reason,
|
||||||
|
};
|
||||||
|
const stateKey = `rule:${bits.glob}`;
|
||||||
|
|
||||||
|
await mjolnir.client.sendStateEvent(mjolnir.publishedBanListRoomId, bits.ruleType, stateKey, ruleContent);
|
||||||
|
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// !mjolnir unban <user|server|room> <glob>
|
||||||
|
export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
|
||||||
|
const bits = parseBits(parts);
|
||||||
|
if (!bits.ruleType) {
|
||||||
|
const replyText = "Unknown entity type '" + bits.entityType + "' - try one of user, room, or server";
|
||||||
|
const reply = RichReply.createFor(roomId, event, replyText, replyText);
|
||||||
|
reply["msgtype"] = "m.notice";
|
||||||
|
return mjolnir.client.sendMessage(roomId, reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ruleContent = {}; // empty == clear/unban
|
||||||
|
const stateKey = `rule:${bits.glob}`;
|
||||||
|
|
||||||
|
await mjolnir.client.sendStateEvent(mjolnir.publishedBanListRoomId, bits.ruleType, stateKey, ruleContent);
|
||||||
|
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
|
||||||
|
}
|
@ -22,6 +22,7 @@ interface IConfig {
|
|||||||
dataPath: string;
|
dataPath: string;
|
||||||
autojoin: boolean;
|
autojoin: boolean;
|
||||||
managementRoom: string;
|
managementRoom: string;
|
||||||
|
publishedBanListRoom: string;
|
||||||
protectedRooms: string[]; // matrix.to urls
|
protectedRooms: string[]; // matrix.to urls
|
||||||
banLists: string[]; // matrix.to urls
|
banLists: string[]; // matrix.to urls
|
||||||
}
|
}
|
||||||
|
14
src/index.ts
14
src/index.ts
@ -24,9 +24,7 @@ import {
|
|||||||
SimpleFsStorageProvider
|
SimpleFsStorageProvider
|
||||||
} from "matrix-bot-sdk";
|
} from "matrix-bot-sdk";
|
||||||
import config from "./config";
|
import config from "./config";
|
||||||
import BanList, { ALL_RULE_TYPES } from "./models/BanList";
|
import BanList from "./models/BanList";
|
||||||
import { applyServerAcls } from "./actions/ApplyAcl";
|
|
||||||
import { RoomUpdateError } from "./models/RoomUpdateError";
|
|
||||||
import { Mjolnir } from "./Mjolnir";
|
import { Mjolnir } from "./Mjolnir";
|
||||||
|
|
||||||
LogService.setLogger(new RichConsoleLogger());
|
LogService.setLogger(new RichConsoleLogger());
|
||||||
@ -40,7 +38,7 @@ if (config.autojoin) {
|
|||||||
|
|
||||||
(async function () {
|
(async function () {
|
||||||
const banLists: BanList[] = [];
|
const banLists: BanList[] = [];
|
||||||
const protectedRooms:{[roomId: string]: string} = {};
|
const protectedRooms: { [roomId: string]: string } = {};
|
||||||
|
|
||||||
// Ensure we're in all the rooms we expect to be in
|
// Ensure we're in all the rooms we expect to be in
|
||||||
const joinedRooms = await client.getJoinedRooms();
|
const joinedRooms = await client.getJoinedRooms();
|
||||||
@ -71,11 +69,17 @@ if (config.autojoin) {
|
|||||||
protectedRooms[roomId] = roomRef;
|
protectedRooms[roomId] = roomRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we've joined the ban list we're publishing too
|
||||||
|
let banListRoomId = await client.resolveRoom(config.publishedBanListRoom);
|
||||||
|
if (!joinedRooms.includes(banListRoomId)) {
|
||||||
|
banListRoomId = await client.joinRoom(config.publishedBanListRoom);
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure we're also in the management room
|
// Ensure we're also in the management room
|
||||||
const managementRoomId = await client.joinRoom(config.managementRoom);
|
const managementRoomId = await client.joinRoom(config.managementRoom);
|
||||||
await client.sendNotice(managementRoomId, "Mjolnir is starting up. Use !mjolnir to query status.");
|
await client.sendNotice(managementRoomId, "Mjolnir is starting up. Use !mjolnir to query status.");
|
||||||
|
|
||||||
const bot = new Mjolnir(client, managementRoomId, protectedRooms, banLists);
|
const bot = new Mjolnir(client, managementRoomId, banListRoomId, protectedRooms, banLists);
|
||||||
await bot.start();
|
await bot.start();
|
||||||
|
|
||||||
// TODO: Check permissions for mjolnir in protected rooms
|
// TODO: Check permissions for mjolnir in protected rooms
|
||||||
|
@ -26,6 +26,13 @@ export const ROOM_RULE_TYPES = [RULE_ROOM, "org.matrix.mjolnir.rule.room"];
|
|||||||
export const SERVER_RULE_TYPES = [RULE_SERVER, "org.matrix.mjolnir.rule.server"];
|
export const SERVER_RULE_TYPES = [RULE_SERVER, "org.matrix.mjolnir.rule.server"];
|
||||||
export const ALL_RULE_TYPES = [...USER_RULE_TYPES, ...ROOM_RULE_TYPES, ...SERVER_RULE_TYPES];
|
export const ALL_RULE_TYPES = [...USER_RULE_TYPES, ...ROOM_RULE_TYPES, ...SERVER_RULE_TYPES];
|
||||||
|
|
||||||
|
export function ruleTypeToStable(rule: string, unstable = true): string {
|
||||||
|
if (USER_RULE_TYPES.includes(rule)) return unstable ? USER_RULE_TYPES[USER_RULE_TYPES.length - 1] : RULE_USER;
|
||||||
|
if (ROOM_RULE_TYPES.includes(rule)) return unstable ? ROOM_RULE_TYPES[ROOM_RULE_TYPES.length - 1] : RULE_ROOM;
|
||||||
|
if (SERVER_RULE_TYPES.includes(rule)) return unstable ? SERVER_RULE_TYPES[SERVER_RULE_TYPES.length - 1] : RULE_SERVER;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export default class BanList {
|
export default class BanList {
|
||||||
private rules: ListRule[] = [];
|
private rules: ListRule[] = [];
|
||||||
|
|
||||||
|
@ -19,6 +19,11 @@ import { MatrixGlob } from "matrix-bot-sdk/lib/MatrixGlob";
|
|||||||
export const RECOMMENDATION_BAN = "m.ban";
|
export const RECOMMENDATION_BAN = "m.ban";
|
||||||
export const RECOMMENDATION_BAN_TYPES = [RECOMMENDATION_BAN, "org.matrix.mjolnir.ban"];
|
export const RECOMMENDATION_BAN_TYPES = [RECOMMENDATION_BAN, "org.matrix.mjolnir.ban"];
|
||||||
|
|
||||||
|
export function recommendationToStable(recommendation: string, unstable = true): string {
|
||||||
|
if (RECOMMENDATION_BAN_TYPES.includes(recommendation)) return unstable ? RECOMMENDATION_BAN_TYPES[RECOMMENDATION_BAN_TYPES.length - 1] : RECOMMENDATION_BAN;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export class ListRule {
|
export class ListRule {
|
||||||
|
|
||||||
private glob: MatrixGlob;
|
private glob: MatrixGlob;
|
||||||
|
Loading…
Reference in New Issue
Block a user