move LogProxy.logMessage to Mjolnir.logMessage (#194)

This commit is contained in:
Jess Porter 2022-02-15 15:44:41 +00:00 committed by GitHub
parent e9dff8fd5a
commit a58c7d3f1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 129 additions and 155 deletions

View File

@ -1,55 +0,0 @@
/*
Copyright 2019, 2020 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 { LogLevel, LogService, TextualMessageEventContent } from "matrix-bot-sdk";
import config from "./config";
import { htmlEscape, replaceRoomIdsWithPills } from "./utils";
const levelToFn = {
[LogLevel.DEBUG.toString()]: LogService.debug,
[LogLevel.INFO.toString()]: LogService.info,
[LogLevel.WARN.toString()]: LogService.warn,
[LogLevel.ERROR.toString()]: LogService.error,
};
export async function logMessage(level: LogLevel, module: string, message: string | any, additionalRoomIds: string[] | string | null = null, isRecursive = false) {
if (!additionalRoomIds) additionalRoomIds = [];
if (!Array.isArray(additionalRoomIds)) additionalRoomIds = [additionalRoomIds];
if (config.RUNTIME.client && (config.verboseLogging || LogLevel.INFO.includes(level))) {
let clientMessage = message;
if (level === LogLevel.WARN) clientMessage = `⚠ | ${message}`;
if (level === LogLevel.ERROR) clientMessage = `‼ | ${message}`;
const client = config.RUNTIME.client;
const managementRoomId = await client.resolveRoom(config.managementRoom);
const roomIds = new Set([managementRoomId, ...additionalRoomIds]);
let evContent: TextualMessageEventContent = {
body: message,
formatted_body: htmlEscape(message),
msgtype: "m.notice",
format: "org.matrix.custom.html",
};
if (!isRecursive) {
evContent = await replaceRoomIdsWithPills(client, clientMessage, roomIds, "m.notice");
}
await client.sendMessage(managementRoomId, evContent);
}
levelToFn[level.toString()](module, message);
}

View File

@ -23,7 +23,8 @@ import {
MatrixGlob,
MembershipEvent,
Permalinks,
UserID
UserID,
TextualMessageEventContent
} from "matrix-bot-sdk";
import BanList, { ALL_RULE_TYPES as ALL_BAN_LIST_RULE_TYPES, ListRuleChange, RULE_ROOM, RULE_SERVER, RULE_USER } from "./models/BanList";
@ -32,7 +33,6 @@ import { RoomUpdateError } from "./models/RoomUpdateError";
import { COMMAND_PREFIX, handleCommand } from "./commands/CommandHandler";
import { applyUserBans } from "./actions/ApplyBan";
import config from "./config";
import { logMessage } from "./LogProxy";
import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache";
import { IProtection } from "./protections/IProtection";
import { PROTECTIONS } from "./protections/protections";
@ -43,8 +43,16 @@ import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQu
import { htmlEscape } from "./utils";
import { ReportManager } from "./report/ReportManager";
import { WebAPIs } from "./webapis/WebAPIs";
import { replaceRoomIdsWithPills } from "./utils";
import RuleServer from "./models/RuleServer";
const levelToFn = {
[LogLevel.DEBUG.toString()]: LogService.debug,
[LogLevel.INFO.toString()]: LogService.info,
[LogLevel.WARN.toString()]: LogService.warn,
[LogLevel.ERROR.toString()]: LogService.error,
};
export const STATE_NOT_STARTED = "not_started";
export const STATE_CHECKING_PERMISSIONS = "checking_permissions";
export const STATE_SYNCING = "syncing";
@ -144,10 +152,10 @@ export class Mjolnir {
if (!joinedRooms.includes(managementRoomId)) {
await client.joinRoom(config.managementRoom);
}
await logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status.");
const ruleServer = config.web.ruleServer ? new RuleServer() : null;
const mjolnir = new Mjolnir(client, managementRoomId, protectedRooms, banLists, ruleServer);
await mjolnir.logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status.");
Mjolnir.addJoinOnInviteListener(mjolnir, client, config);
return mjolnir;
}
@ -270,7 +278,7 @@ export class Mjolnir {
// Load the state.
this.currentState = STATE_CHECKING_PERMISSIONS;
await logMessage(LogLevel.DEBUG, "Mjolnir@startup", "Loading protected rooms...");
await this.logMessage(LogLevel.DEBUG, "Mjolnir@startup", "Loading protected rooms...");
await this.resyncJoinedRooms(false);
try {
const data: { rooms?: string[] } | null = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE);
@ -287,25 +295,25 @@ export class Mjolnir {
this.applyUnprotectedRooms();
if (config.verifyPermissionsOnStartup) {
await logMessage(LogLevel.INFO, "Mjolnir@startup", "Checking permissions...");
await this.logMessage(LogLevel.INFO, "Mjolnir@startup", "Checking permissions...");
await this.verifyPermissions(config.verboseLogging);
}
this.currentState = STATE_SYNCING;
if (config.syncOnStartup) {
await logMessage(LogLevel.INFO, "Mjolnir@startup", "Syncing lists...");
await this.logMessage(LogLevel.INFO, "Mjolnir@startup", "Syncing lists...");
await this.syncLists(config.verboseLogging);
await this.registerProtections();
}
this.currentState = STATE_RUNNING;
Healthz.isHealthy = true;
await logMessage(LogLevel.INFO, "Mjolnir@startup", "Startup complete. Now monitoring rooms.");
await this.logMessage(LogLevel.INFO, "Mjolnir@startup", "Startup complete. Now monitoring rooms.");
} catch (err) {
try {
LogService.error("Mjolnir", "Error during startup:");
LogService.error("Mjolnir", extractRequestError(err));
await logMessage(LogLevel.ERROR, "Mjolnir@startup", "Startup failed due to error - see console");
await this.logMessage(LogLevel.ERROR, "Mjolnir@startup", "Startup failed due to error - see console");
} catch (e) {
// If we failed to handle the error, just crash
console.error(e);
@ -323,6 +331,36 @@ export class Mjolnir {
this.webapis.stop();
}
public async logMessage(level: LogLevel, module: string, message: string | any, additionalRoomIds: string[] | string | null = null, isRecursive = false): Promise<any> {
if (!additionalRoomIds) additionalRoomIds = [];
if (!Array.isArray(additionalRoomIds)) additionalRoomIds = [additionalRoomIds];
if (config.RUNTIME.client && (config.verboseLogging || LogLevel.INFO.includes(level))) {
let clientMessage = message;
if (level === LogLevel.WARN) clientMessage = `⚠ | ${message}`;
if (level === LogLevel.ERROR) clientMessage = `‼ | ${message}`;
const client = config.RUNTIME.client;
const managementRoomId = await client.resolveRoom(config.managementRoom);
const roomIds = [managementRoomId, ...additionalRoomIds];
let evContent: TextualMessageEventContent = {
body: message,
formatted_body: htmlEscape(message),
msgtype: "m.notice",
format: "org.matrix.custom.html",
};
if (!isRecursive) {
evContent = await replaceRoomIdsWithPills(this, clientMessage, new Set(roomIds), "m.notice");
}
await client.sendMessage(managementRoomId, evContent);
}
levelToFn[level.toString()](module, message);
}
public async addProtectedRoom(roomId: string) {
this.protectedRooms[roomId] = Permalinks.forRoom(roomId);
@ -455,7 +493,7 @@ export class Mjolnir {
) {
validatedSettings[key] = value;
} else {
await logMessage(
await this.logMessage(
LogLevel.WARN,
"getProtectionSetting",
`Tried to read ${protectionName}.${key} and got invalid value ${value}`
@ -606,7 +644,7 @@ export class Mjolnir {
// Ignore - probably haven't warned about it yet
}
await logMessage(LogLevel.WARN, "Mjolnir", `Not protecting ${roomId} - it is a ban list that this bot did not create. Add the room as protected if it is supposed to be protected. This warning will not appear again.`, roomId);
await this.logMessage(LogLevel.WARN, "Mjolnir", `Not protecting ${roomId} - it is a ban list that this bot did not create. Add the room as protected if it is supposed to be protected. This warning will not appear again.`, roomId);
await this.client.setAccountData(WARN_UNPROTECTED_ROOM_EVENT_PREFIX + roomId, { warned: true });
}
@ -834,16 +872,16 @@ export class Mjolnir {
// Run the event handlers - we always run this after protections so that the protections
// can flag the event for redaction.
await this.unlistedUserRedactionHandler.handleEvent(roomId, event, this.client);
await this.unlistedUserRedactionHandler.handleEvent(roomId, event, this);
if (event['type'] === 'm.room.power_levels' && event['state_key'] === '') {
// power levels were updated - recheck permissions
ErrorCache.resetError(roomId, ERROR_KIND_PERMISSION);
await logMessage(LogLevel.DEBUG, "Mjolnir", `Power levels changed in ${roomId} - checking permissions...`, roomId);
await this.logMessage(LogLevel.DEBUG, "Mjolnir", `Power levels changed in ${roomId} - checking permissions...`, roomId);
const errors = await this.verifyPermissionsIn(roomId);
const hadErrors = await this.printActionResult(errors);
if (!hadErrors) {
await logMessage(LogLevel.DEBUG, "Mjolnir", `All permissions look OK.`);
await this.logMessage(LogLevel.DEBUG, "Mjolnir", `All permissions look OK.`);
}
return;
} else if (event['type'] === "m.room.member") {
@ -982,7 +1020,7 @@ export class Mjolnir {
* @returns The list of errors encountered, for reporting to the management room.
*/
public async processRedactionQueue(roomId?: string): Promise<RoomUpdateError[]> {
return await this.eventRedactionQueue.process(this.client, roomId);
return await this.eventRedactionQueue.process(this, roomId);
}
private async handleReport(roomId: string, reporterId: string, event: any, reason?: string) {

View File

@ -20,7 +20,6 @@ import { RoomUpdateError } from "../models/RoomUpdateError";
import { Mjolnir } from "../Mjolnir";
import config from "../config";
import { LogLevel, UserID } from "matrix-bot-sdk";
import { logMessage } from "../LogProxy";
import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";
/**
@ -45,7 +44,7 @@ export async function applyServerAcls(lists: BanList[], roomIds: string[], mjoln
const finalAcl = acl.safeAclContent();
if (finalAcl.deny.length !== acl.literalAclContent().deny.length) {
logMessage(LogLevel.WARN, "ApplyAcl", `Mjölnir has detected and removed an ACL that would exclude itself. Please check the ACL lists.`);
mjolnir.logMessage(LogLevel.WARN, "ApplyAcl", `Mjölnir has detected and removed an ACL that would exclude itself. Please check the ACL lists.`);
}
if (config.verboseLogging) {
@ -56,12 +55,12 @@ export async function applyServerAcls(lists: BanList[], roomIds: string[], mjoln
const errors: RoomUpdateError[] = [];
for (const roomId of roomIds) {
try {
await logMessage(LogLevel.DEBUG, "ApplyAcl", `Checking ACLs for ${roomId}`, roomId);
await mjolnir.logMessage(LogLevel.DEBUG, "ApplyAcl", `Checking ACLs for ${roomId}`, roomId);
try {
const currentAcl = await mjolnir.client.getRoomStateEvent(roomId, "m.room.server_acl", "");
if (acl.matches(currentAcl)) {
await logMessage(LogLevel.DEBUG, "ApplyAcl", `Skipping ACLs for ${roomId} because they are already the right ones`, roomId);
await mjolnir.logMessage(LogLevel.DEBUG, "ApplyAcl", `Skipping ACLs for ${roomId} because they are already the right ones`, roomId);
continue;
}
} catch (e) {
@ -69,12 +68,12 @@ export async function applyServerAcls(lists: BanList[], roomIds: string[], mjoln
}
// We specifically use sendNotice to avoid having to escape HTML
await logMessage(LogLevel.DEBUG, "ApplyAcl", `Applying ACL in ${roomId}`, roomId);
await mjolnir.logMessage(LogLevel.DEBUG, "ApplyAcl", `Applying ACL in ${roomId}`, roomId);
if (!config.noop) {
await mjolnir.client.sendStateEvent(roomId, "m.room.server_acl", "", finalAcl);
} else {
await logMessage(LogLevel.WARN, "ApplyAcl", `Tried to apply ACL in ${roomId} but Mjolnir is running in no-op mode`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "ApplyAcl", `Tried to apply ACL in ${roomId} but Mjolnir is running in no-op mode`, roomId);
}
} catch (e) {
const message = e.message || (e.body ? e.body.error : '<no message>');

View File

@ -18,7 +18,6 @@ import BanList from "../models/BanList";
import { RoomUpdateError } from "../models/RoomUpdateError";
import { Mjolnir } from "../Mjolnir";
import config from "../config";
import { logMessage } from "../LogProxy";
import { LogLevel } from "matrix-bot-sdk";
import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";
@ -35,7 +34,7 @@ export async function applyUserBans(lists: BanList[], roomIds: string[], mjolnir
for (const roomId of roomIds) {
try {
// We specifically use sendNotice to avoid having to escape HTML
await logMessage(LogLevel.DEBUG, "ApplyBan", `Updating member bans in ${roomId}`, roomId);
await mjolnir.logMessage(LogLevel.DEBUG, "ApplyBan", `Updating member bans in ${roomId}`, roomId);
let members: { userId: string, membership: string }[];
@ -63,7 +62,7 @@ export async function applyUserBans(lists: BanList[], roomIds: string[], mjolnir
// User needs to be banned
// We specifically use sendNotice to avoid having to escape HTML
await logMessage(LogLevel.INFO, "ApplyBan", `Banning ${member.userId} in ${roomId} for: ${userRule.reason}`, roomId);
await mjolnir.logMessage(LogLevel.INFO, "ApplyBan", `Banning ${member.userId} in ${roomId} for: ${userRule.reason}`, roomId);
if (!config.noop) {
await mjolnir.client.banUser(member.userId, roomId, userRule.reason);
@ -71,7 +70,7 @@ export async function applyUserBans(lists: BanList[], roomIds: string[], mjolnir
mjolnir.queueRedactUserMessagesIn(member.userId, roomId);
}
} else {
await logMessage(LogLevel.WARN, "ApplyBan", `Tried to ban ${member.userId} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "ApplyBan", `Tried to ban ${member.userId} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
}
banned = true;

View File

@ -16,7 +16,6 @@ limitations under the License.
import { Mjolnir } from "../Mjolnir";
import { extractRequestError, LogLevel, LogService } from "matrix-bot-sdk";
import { logMessage } from "../LogProxy";
// !mjolnir rooms add <room alias/ID>
export async function execAddProtectedRoom(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
@ -33,7 +32,7 @@ export async function execRemoveProtectedRoom(roomId: string, event: any, mjolni
await mjolnir.client.leaveRoom(protectedRoomId);
} catch (e) {
LogService.warn("AddRemoveProtectedRoomsCommand", extractRequestError(e));
await logMessage(LogLevel.WARN, "AddRemoveProtectedRoomsCommand", `Failed to leave ${protectedRoomId} - the room is no longer being protected, but the bot could not leave`, protectedRoomId);
await mjolnir.logMessage(LogLevel.WARN, "AddRemoveProtectedRoomsCommand", `Failed to leave ${protectedRoomId} - the room is no longer being protected, but the bot could not leave`, protectedRoomId);
}
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
}

View File

@ -17,7 +17,6 @@ limitations under the License.
import { Mjolnir } from "../Mjolnir";
import { LogLevel } from "matrix-bot-sdk";
import config from "../config";
import { logMessage } from "../LogProxy";
// !mjolnir kick <user> [room] [reason]
export async function execKickCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
@ -39,11 +38,11 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln
const joinedUsers = await mjolnir.client.getJoinedRoomMembers(targetRoomId);
if (!joinedUsers.includes(userId)) continue; // skip
await logMessage(LogLevel.INFO, "KickCommand", `Kicking ${userId} in ${targetRoomId} for ${reason}`, targetRoomId);
await mjolnir.logMessage(LogLevel.INFO, "KickCommand", `Kicking ${userId} in ${targetRoomId} for ${reason}`, targetRoomId);
if (!config.noop) {
await mjolnir.client.kickUser(userId, targetRoomId, reason);
} else {
await logMessage(LogLevel.WARN, "KickCommand", `Tried to kick ${userId} in ${targetRoomId} but the bot is running in no-op mode.`, targetRoomId);
await mjolnir.logMessage(LogLevel.WARN, "KickCommand", `Tried to kick ${userId} in ${targetRoomId} but the bot is running in no-op mode.`, targetRoomId);
}
}

View File

@ -46,7 +46,7 @@ export async function execRedactCommand(roomId: string, event: any, mjolnir: Mjo
}
const targetRoomIds = roomAlias ? [roomAlias] : Object.keys(mjolnir.protectedRooms);
await redactUserMessagesIn(mjolnir.client, userId, targetRoomIds, limit);
await redactUserMessagesIn(mjolnir, userId, targetRoomIds, limit);
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
await mjolnir.client.redactEvent(roomId, processingReactionId, 'done processing');

View File

@ -16,7 +16,6 @@ limitations under the License.
import { Mjolnir } from "../Mjolnir";
import { extractRequestError, LogLevel, LogService } from "matrix-bot-sdk";
import { logMessage } from "../LogProxy";
// !mjolnir powerlevel <user ID> <level> [room]
export async function execSetPowerLevelCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
@ -31,7 +30,7 @@ export async function execSetPowerLevelCommand(roomId: string, event: any, mjoln
await mjolnir.client.setUserPowerLevel(victim, targetRoomId, level);
} catch (e) {
const message = e.message || (e.body ? e.body.error : '<no message>');
await logMessage(LogLevel.ERROR, "SetPowerLevelCommand", `Failed to set power level of ${victim} to ${level} in ${targetRoomId}: ${message}`, targetRoomId);
await mjolnir.logMessage(LogLevel.ERROR, "SetPowerLevelCommand", `Failed to set power level of ${victim} to ${level} in ${targetRoomId}: ${message}`, targetRoomId);
LogService.error("SetPowerLevelCommand", extractRequestError(e));
}
}

View File

@ -19,7 +19,6 @@ import BanList, { RULE_ROOM, RULE_SERVER, RULE_USER, USER_RULE_TYPES } from "../
import { extractRequestError, LogLevel, LogService, MatrixGlob, RichReply } from "matrix-bot-sdk";
import { RECOMMENDATION_BAN, recommendationToStable } from "../models/ListRule";
import config from "../config";
import { logMessage } from "../LogProxy";
import { DEFAULT_LIST_EVENT_TYPE } from "./SetDefaultBanListCommand";
interface Arguments {
@ -144,21 +143,21 @@ export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjol
if (USER_RULE_TYPES.includes(bits.ruleType!) && bits.reason === 'true') {
const rule = new MatrixGlob(bits.entity);
await logMessage(LogLevel.INFO, "UnbanBanCommand", "Unbanning users that match glob: " + bits.entity);
await mjolnir.logMessage(LogLevel.INFO, "UnbanBanCommand", "Unbanning users that match glob: " + bits.entity);
let unbannedSomeone = false;
for (const protectedRoomId of Object.keys(mjolnir.protectedRooms)) {
const members = await mjolnir.client.getRoomMembers(protectedRoomId, undefined, ['ban'], undefined);
await logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Found ${members.length} banned user(s)`);
await mjolnir.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Found ${members.length} banned user(s)`);
for (const member of members) {
const victim = member.membershipFor;
if (member.membership !== 'ban') continue;
if (rule.test(victim)) {
await logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Unbanning ${victim} in ${protectedRoomId}`, protectedRoomId);
await mjolnir.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Unbanning ${victim} in ${protectedRoomId}`, protectedRoomId);
if (!config.noop) {
await mjolnir.client.unbanUser(victim, protectedRoomId);
} else {
await logMessage(LogLevel.WARN, "UnbanBanCommand", `Attempted to unban ${victim} in ${protectedRoomId} but Mjolnir is running in no-op mode`, protectedRoomId);
await mjolnir.logMessage(LogLevel.WARN, "UnbanBanCommand", `Attempted to unban ${victim} in ${protectedRoomId} but Mjolnir is running in no-op mode`, protectedRoomId);
}
unbannedSomeone = true;
@ -167,7 +166,7 @@ export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjol
}
if (unbannedSomeone) {
await logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Syncing lists to ensure no users were accidentally unbanned`);
await mjolnir.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Syncing lists to ensure no users were accidentally unbanned`);
await mjolnir.syncLists(config.verboseLogging);
}
}

View File

@ -24,7 +24,6 @@ import {
SimpleFsStorageProvider
} from "matrix-bot-sdk";
import config from "./config";
import { logMessage } from "./LogProxy";
import { Healthz } from "./health/healthz";
import { Mjolnir } from "./Mjolnir";
import { patchMatrixClient } from "./utils";
@ -42,22 +41,25 @@ if (config.health.healthz.enabled) {
}
(async function () {
const storagePath = path.isAbsolute(config.dataPath) ? config.dataPath : path.join(__dirname, '../', config.dataPath);
const storage = new SimpleFsStorageProvider(path.join(storagePath, "bot.json"));
let bot: Mjolnir | null = null;
try {
const storagePath = path.isAbsolute(config.dataPath) ? config.dataPath : path.join(__dirname, '../', config.dataPath);
const storage = new SimpleFsStorageProvider(path.join(storagePath, "bot.json"));
let client: MatrixClient;
if (config.pantalaimon.use) {
const pantalaimon = new PantalaimonClient(config.homeserverUrl, storage);
client = await pantalaimon.createClientWithCredentials(config.pantalaimon.username, config.pantalaimon.password);
} else {
client = new MatrixClient(config.homeserverUrl, config.accessToken, storage);
let client: MatrixClient;
if (config.pantalaimon.use) {
const pantalaimon = new PantalaimonClient(config.homeserverUrl, storage);
client = await pantalaimon.createClientWithCredentials(config.pantalaimon.username, config.pantalaimon.password);
} else {
client = new MatrixClient(config.homeserverUrl, config.accessToken, storage);
}
patchMatrixClient();
config.RUNTIME.client = client;
bot = await Mjolnir.setupMjolnirFromConfig(client);
await bot.start();
} catch (err) {
bot?.logMessage(LogLevel.ERROR, "index", err);
process.exit(1);
}
patchMatrixClient();
config.RUNTIME.client = client;
let bot = await Mjolnir.setupMjolnirFromConfig(client);
await bot.start();
})().catch(err => {
logMessage(LogLevel.ERROR, "index", err);
process.exit(1);
});
})();

View File

@ -18,7 +18,6 @@ import { Protection } from "./IProtection";
import { NumberProtectionSetting } from "./ProtectionSettings";
import { Mjolnir } from "../Mjolnir";
import { LogLevel, LogService } from "matrix-bot-sdk";
import { logMessage } from "../LogProxy";
import config from "../config";
// if this is exceeded, we'll ban the user for spam and redact their messages
@ -64,11 +63,11 @@ export class BasicFlooding extends Protection {
}
if (messageCount >= this.settings.maxPerMinute.value) {
await logMessage(LogLevel.WARN, "BasicFlooding", `Banning ${event['sender']} in ${roomId} for flooding (${messageCount} messages in the last minute)`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "BasicFlooding", `Banning ${event['sender']} in ${roomId} for flooding (${messageCount} messages in the last minute)`, roomId);
if (!config.noop) {
await mjolnir.client.banUser(event['sender'], roomId, "spam");
} else {
await logMessage(LogLevel.WARN, "BasicFlooding", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "BasicFlooding", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
}
if (this.recentlyBanned.includes(event['sender'])) return; // already handled (will be redacted)
@ -81,7 +80,7 @@ export class BasicFlooding extends Protection {
await mjolnir.client.redactEvent(roomId, eventId, "spam");
}
} else {
await logMessage(LogLevel.WARN, "BasicFlooding", `Tried to redact messages for ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "BasicFlooding", `Tried to redact messages for ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
}
// Free up some memory now that we're ready to handle it elsewhere

View File

@ -17,7 +17,6 @@ limitations under the License.
import { Protection } from "./IProtection";
import { Mjolnir } from "../Mjolnir";
import { LogLevel, LogService } from "matrix-bot-sdk";
import { logMessage } from "../LogProxy";
import config from "../config";
import { isTrueJoinEvent } from "../utils";
@ -58,11 +57,11 @@ export class FirstMessageIsImage extends Protection {
const formattedBody = content['formatted_body'] || '';
const isMedia = msgtype === 'm.image' || msgtype === 'm.video' || formattedBody.toLowerCase().includes('<img');
if (isMedia && this.justJoined[roomId].includes(event['sender'])) {
await logMessage(LogLevel.WARN, "FirstMessageIsImage", `Banning ${event['sender']} for posting an image as the first thing after joining in ${roomId}.`);
await mjolnir.logMessage(LogLevel.WARN, "FirstMessageIsImage", `Banning ${event['sender']} for posting an image as the first thing after joining in ${roomId}.`);
if (!config.noop) {
await mjolnir.client.banUser(event['sender'], roomId, "spam");
} else {
await logMessage(LogLevel.WARN, "FirstMessageIsImage", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "FirstMessageIsImage", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
}
if (this.recentlyBanned.includes(event['sender'])) return; // already handled (will be redacted)
@ -73,7 +72,7 @@ export class FirstMessageIsImage extends Protection {
if (!config.noop) {
await mjolnir.client.redactEvent(roomId, event['event_id'], "spam");
} else {
await logMessage(LogLevel.WARN, "FirstMessageIsImage", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "FirstMessageIsImage", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
}
}
}

View File

@ -17,7 +17,6 @@ limitations under the License.
import { Protection } from "./IProtection";
import { Mjolnir } from "../Mjolnir";
import { LogLevel, Permalinks, UserID } from "matrix-bot-sdk";
import { logMessage } from "../LogProxy";
import config from "../config";
export class MessageIsMedia extends Protection {
@ -42,12 +41,12 @@ export class MessageIsMedia extends Protection {
const formattedBody = content['formatted_body'] || '';
const isMedia = msgtype === 'm.image' || msgtype === 'm.video' || formattedBody.toLowerCase().includes('<img');
if (isMedia) {
await logMessage(LogLevel.WARN, "MessageIsMedia", `Redacting event from ${event['sender']} for posting an image/video. ${Permalinks.forEvent(roomId, event['event_id'], [new UserID(await mjolnir.client.getUserId()).domain])}`);
await mjolnir.logMessage(LogLevel.WARN, "MessageIsMedia", `Redacting event from ${event['sender']} for posting an image/video. ${Permalinks.forEvent(roomId, event['event_id'], [new UserID(await mjolnir.client.getUserId()).domain])}`);
// Redact the event
if (!config.noop) {
await mjolnir.client.redactEvent(roomId, event['event_id'], "Images/videos are not permitted here");
} else {
await logMessage(LogLevel.WARN, "MessageIsMedia", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "MessageIsMedia", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
}
}
}

View File

@ -17,7 +17,6 @@ limitations under the License.
import { Protection } from "./IProtection";
import { Mjolnir } from "../Mjolnir";
import { LogLevel, Permalinks, UserID } from "matrix-bot-sdk";
import { logMessage } from "../LogProxy";
import config from "../config";
export class MessageIsVoice extends Protection {
@ -39,12 +38,12 @@ export class MessageIsVoice extends Protection {
if (event['type'] === 'm.room.message' && event['content']) {
if (event['content']['msgtype'] !== 'm.audio') return;
if (event['content']['org.matrix.msc3245.voice'] === undefined) return;
await logMessage(LogLevel.INFO, "MessageIsVoice", `Redacting event from ${event['sender']} for posting a voice message. ${Permalinks.forEvent(roomId, event['event_id'], [new UserID(await mjolnir.client.getUserId()).domain])}`);
await mjolnir.logMessage(LogLevel.INFO, "MessageIsVoice", `Redacting event from ${event['sender']} for posting a voice message. ${Permalinks.forEvent(roomId, event['event_id'], [new UserID(await mjolnir.client.getUserId()).domain])}`);
// Redact the event
if (!config.noop) {
await mjolnir.client.redactEvent(roomId, event['event_id'], "Voice messages are not permitted here");
} else {
await logMessage(LogLevel.WARN, "MessageIsVoice", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "MessageIsVoice", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
}
}
}

View File

@ -17,7 +17,6 @@ limitations under the License.
import { Protection } from "./IProtection";
import { Mjolnir } from "../Mjolnir";
import { LogLevel, LogService } from "matrix-bot-sdk";
import { logMessage } from "../LogProxy";
import config from "../config";
import { isTrueJoinEvent } from "../utils";
@ -94,18 +93,18 @@ export class WordList extends Protection {
// Perform the test
if (message && this.badWords.test(message)) {
await logMessage(LogLevel.WARN, "WordList", `Banning ${event['sender']} for word list violation in ${roomId}.`);
await mjolnir.logMessage(LogLevel.WARN, "WordList", `Banning ${event['sender']} for word list violation in ${roomId}.`);
if (!config.noop) {
await mjolnir.client.banUser(event['sender'], roomId, "Word list violation");
} else {
await logMessage(LogLevel.WARN, "WordList", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "WordList", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
}
// Redact the event
if (!config.noop) {
await mjolnir.client.redactEvent(roomId, event['event_id'], "spam");
} else {
await logMessage(LogLevel.WARN, "WordList", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
await mjolnir.logMessage(LogLevel.WARN, "WordList", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
}
}
}

View File

@ -13,11 +13,11 @@ 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 { LogLevel, MatrixClient } from "matrix-bot-sdk"
import { LogLevel } from "matrix-bot-sdk"
import { ERROR_KIND_FATAL } from "../ErrorCache";
import { logMessage } from "../LogProxy";
import { RoomUpdateError } from "../models/RoomUpdateError";
import { redactUserMessagesIn } from "../utils";
import { Mjolnir } from "../Mjolnir";
export interface QueuedRedaction {
/** The room which the redaction will take place in. */
@ -27,7 +27,7 @@ export interface QueuedRedaction {
* Called by the EventRedactionQueue.
* @param client A MatrixClient to use to carry out the redaction.
*/
redact(client: MatrixClient): Promise<void>
redact(mjolnir: Mjolnir): Promise<void>
/**
* Used to test whether the redaction is the equivalent to another redaction.
* @param redaction Another QueuedRedaction to test if this redaction is an equivalent to.
@ -47,9 +47,9 @@ export class RedactUserInRoom implements QueuedRedaction {
this.roomId = roomId;
}
public async redact(client: MatrixClient) {
await logMessage(LogLevel.DEBUG, "Mjolnir", `Redacting events from ${this.userId} in room ${this.roomId}.`);
await redactUserMessagesIn(client, this.userId, [this.roomId]);
public async redact(mjolnir: Mjolnir) {
await mjolnir.logMessage(LogLevel.DEBUG, "Mjolnir", `Redacting events from ${this.userId} in room ${this.roomId}.`);
await redactUserMessagesIn(mjolnir, this.userId, [this.roomId]);
}
public redactionEqual(redaction: QueuedRedaction): boolean {
@ -107,12 +107,12 @@ export class EventRedactionQueue {
* @param limitToRoomId If the roomId is provided, only redactions for that room will be processed.
* @returns A description of any errors encountered by each QueuedRedaction that was processed.
*/
public async process(client: MatrixClient, limitToRoomId?: string): Promise<RoomUpdateError[]> {
public async process(mjolnir: Mjolnir, limitToRoomId?: string): Promise<RoomUpdateError[]> {
const errors: RoomUpdateError[] = [];
const redact = async (currentBatch: QueuedRedaction[]) => {
for (const redaction of currentBatch) {
try {
await redaction.redact(client);
await redaction.redact(mjolnir);
} catch (e) {
let roomError: RoomUpdateError;
if (e.roomId && e.errorMessage && e.errorKind) {

View File

@ -13,9 +13,9 @@ 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 { extractRequestError, LogLevel, LogService, MatrixClient, Permalinks } from "matrix-bot-sdk";
import { logMessage } from "../LogProxy";
import { extractRequestError, LogLevel, LogService, Permalinks } from "matrix-bot-sdk";
import config from "../config";
import { Mjolnir } from "../Mjolnir";
/**
* A queue of users who have been flagged for redaction typically by the flooding or image protection.
@ -38,18 +38,18 @@ export class UnlistedUserRedactionQueue {
return this.usersToRedact.has(userId);
}
public async handleEvent(roomId: string, event: any, mjolnirClient: MatrixClient) {
public async handleEvent(roomId: string, event: any, mjolnir: Mjolnir) {
if (this.isUserQueued(event['sender'])) {
const permalink = Permalinks.forEvent(roomId, event['event_id']);
try {
LogService.info("AutomaticRedactionQueue", `Redacting event because the user is listed as bad: ${permalink}`)
if (!config.noop) {
await mjolnirClient.redactEvent(roomId, event['event_id']);
await mjolnir.client.redactEvent(roomId, event['event_id']);
} else {
await logMessage(LogLevel.WARN, "AutomaticRedactionQueue", `Tried to redact ${permalink} but Mjolnir is running in no-op mode`);
await mjolnir.logMessage(LogLevel.WARN, "AutomaticRedactionQueue", `Tried to redact ${permalink} but Mjolnir is running in no-op mode`);
}
} catch (e) {
logMessage(LogLevel.WARN, "AutomaticRedactionQueue", `Unable to redact message: ${permalink}`);
mjolnir.logMessage(LogLevel.WARN, "AutomaticRedactionQueue", `Unable to redact message: ${permalink}`);
LogService.warn("AutomaticRedactionQueue", extractRequestError(e));
}
}

View File

@ -27,7 +27,7 @@ import {
getRequestFn,
setRequestFn,
} from "matrix-bot-sdk";
import { logMessage } from "./LogProxy";
import { Mjolnir } from "./Mjolnir";
import config from "./config";
import { ClientRequest, IncomingMessage } from "http";
@ -59,17 +59,17 @@ export function isTrueJoinEvent(event: any): boolean {
return membership === 'join' && prevMembership !== "join";
}
export async function redactUserMessagesIn(client: MatrixClient, userIdOrGlob: string, targetRoomIds: string[], limit = 1000) {
export async function redactUserMessagesIn(mjolnir: Mjolnir, userIdOrGlob: string, targetRoomIds: string[], limit = 1000) {
for (const targetRoomId of targetRoomIds) {
await logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Fetching sent messages for ${userIdOrGlob} in ${targetRoomId} to redact...`, targetRoomId);
await mjolnir.logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Fetching sent messages for ${userIdOrGlob} in ${targetRoomId} to redact...`, targetRoomId);
await getMessagesByUserIn(client, userIdOrGlob, targetRoomId, limit, async (eventsToRedact) => {
await getMessagesByUserIn(mjolnir.client, userIdOrGlob, targetRoomId, limit, async (eventsToRedact) => {
for (const victimEvent of eventsToRedact) {
await logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Redacting ${victimEvent['event_id']} in ${targetRoomId}`, targetRoomId);
await mjolnir.logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Redacting ${victimEvent['event_id']} in ${targetRoomId}`, targetRoomId);
if (!config.noop) {
await client.redactEvent(targetRoomId, victimEvent['event_id']);
await mjolnir.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`, targetRoomId);
await mjolnir.logMessage(LogLevel.WARN, "utils#redactUserMessagesIn", `Tried to redact ${victimEvent['event_id']} in ${targetRoomId} but Mjolnir is running in no-op mode`, targetRoomId);
}
}
});
@ -191,7 +191,7 @@ export async function getMessagesByUserIn(client: MatrixClient, sender: string,
* @param msgtype The desired message type of the returned TextualMessageEventContent
* @returns A TextualMessageEventContent with replaced room IDs
*/
export async function replaceRoomIdsWithPills(client: MatrixClient, text: string, roomIds: Set<string>, msgtype: MessageType = "m.text"): Promise<TextualMessageEventContent> {
export async function replaceRoomIdsWithPills(mjolnir: Mjolnir, text: string, roomIds: Set<string>, msgtype: MessageType = "m.text"): Promise<TextualMessageEventContent> {
const content: TextualMessageEventContent = {
body: text,
formatted_body: htmlEscape(text),
@ -203,14 +203,14 @@ export async function replaceRoomIdsWithPills(client: MatrixClient, text: string
return v.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
};
const viaServers = [(new UserID(await client.getUserId())).domain];
const viaServers = [(new UserID(await mjolnir.client.getUserId())).domain];
for (const roomId of roomIds) {
let alias = roomId;
try {
alias = (await client.getPublishedAlias(roomId)) || roomId;
alias = (await mjolnir.client.getPublishedAlias(roomId)) || roomId;
} catch (e) {
// This is a recursive call, so tell the function not to try and call us
await logMessage(LogLevel.WARN, "utils", `Failed to resolve room alias for ${roomId} - see console for details`, null, true);
await mjolnir.logMessage(LogLevel.WARN, "utils", `Failed to resolve room alias for ${roomId} - see console for details`, null, true);
LogService.warn("utils", extractRequestError(e));
}
const regexRoomId = new RegExp(escapeRegex(roomId), "g");

View File

@ -16,7 +16,7 @@ describe("Test: utils", function() {
);
const out = await replaceRoomIdsWithPills(
this.mjolnir.client,
this.mjolnir,
`it's fun here in ${this.mjolnir.managementRoomId}`,
new Set([this.mjolnir.managementRoomId])
);