From a58c7d3f1aa6a1259675ce6c37d411328185db6c Mon Sep 17 00:00:00 2001 From: Jess Porter Date: Tue, 15 Feb 2022 15:44:41 +0000 Subject: [PATCH] move LogProxy.logMessage to Mjolnir.logMessage (#194) --- src/LogProxy.ts | 55 ---------------- src/Mjolnir.ts | 66 +++++++++++++++---- src/actions/ApplyAcl.ts | 11 ++-- src/actions/ApplyBan.ts | 7 +- .../AddRemoveProtectedRoomsCommand.ts | 3 +- src/commands/KickCommand.ts | 5 +- src/commands/RedactCommand.ts | 2 +- src/commands/SetPowerLevelCommand.ts | 3 +- src/commands/UnbanBanCommand.ts | 11 ++-- src/index.ts | 38 ++++++----- src/protections/BasicFlooding.ts | 7 +- src/protections/FirstMessageIsImage.ts | 7 +- src/protections/MessageIsMedia.ts | 5 +- src/protections/MessageIsVoice.ts | 5 +- src/protections/WordList.ts | 7 +- src/queues/EventRedactionQueue.ts | 16 ++--- src/queues/UnlistedUserRedactionQueue.ts | 12 ++-- src/utils.ts | 22 +++---- test/integration/utilsTest.ts | 2 +- 19 files changed, 129 insertions(+), 155 deletions(-) delete mode 100644 src/LogProxy.ts diff --git a/src/LogProxy.ts b/src/LogProxy.ts deleted file mode 100644 index d4d4fb5..0000000 --- a/src/LogProxy.ts +++ /dev/null @@ -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); -} diff --git a/src/Mjolnir.ts b/src/Mjolnir.ts index 6c85880..168d5bc 100644 --- a/src/Mjolnir.ts +++ b/src/Mjolnir.ts @@ -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 { + 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 { - 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) { diff --git a/src/actions/ApplyAcl.ts b/src/actions/ApplyAcl.ts index b813f0f..41f2ee9 100644 --- a/src/actions/ApplyAcl.ts +++ b/src/actions/ApplyAcl.ts @@ -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 : ''); diff --git a/src/actions/ApplyBan.ts b/src/actions/ApplyBan.ts index b7922ad..b735a1b 100644 --- a/src/actions/ApplyBan.ts +++ b/src/actions/ApplyBan.ts @@ -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; diff --git a/src/commands/AddRemoveProtectedRoomsCommand.ts b/src/commands/AddRemoveProtectedRoomsCommand.ts index d569e84..d8d5832 100644 --- a/src/commands/AddRemoveProtectedRoomsCommand.ts +++ b/src/commands/AddRemoveProtectedRoomsCommand.ts @@ -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 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'], '✅'); } diff --git a/src/commands/KickCommand.ts b/src/commands/KickCommand.ts index ead0bcc..65fe484 100644 --- a/src/commands/KickCommand.ts +++ b/src/commands/KickCommand.ts @@ -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 [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); } } diff --git a/src/commands/RedactCommand.ts b/src/commands/RedactCommand.ts index 4896758..bfc980e 100644 --- a/src/commands/RedactCommand.ts +++ b/src/commands/RedactCommand.ts @@ -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'); diff --git a/src/commands/SetPowerLevelCommand.ts b/src/commands/SetPowerLevelCommand.ts index 074ec0d..5c46f49 100644 --- a/src/commands/SetPowerLevelCommand.ts +++ b/src/commands/SetPowerLevelCommand.ts @@ -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 [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 : ''); - 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)); } } diff --git a/src/commands/UnbanBanCommand.ts b/src/commands/UnbanBanCommand.ts index c2f126c..7cdf47d 100644 --- a/src/commands/UnbanBanCommand.ts +++ b/src/commands/UnbanBanCommand.ts @@ -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); } } diff --git a/src/index.ts b/src/index.ts index 1434225..e6896a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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); -}); +})(); diff --git a/src/protections/BasicFlooding.ts b/src/protections/BasicFlooding.ts index 5af0299..323c739 100644 --- a/src/protections/BasicFlooding.ts +++ b/src/protections/BasicFlooding.ts @@ -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 diff --git a/src/protections/FirstMessageIsImage.ts b/src/protections/FirstMessageIsImage.ts index 1258e42..4f3c168 100644 --- a/src/protections/FirstMessageIsImage.ts +++ b/src/protections/FirstMessageIsImage.ts @@ -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(' + redact(mjolnir: Mjolnir): Promise /** * 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 { + public async process(mjolnir: Mjolnir, limitToRoomId?: string): Promise { 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) { diff --git a/src/queues/UnlistedUserRedactionQueue.ts b/src/queues/UnlistedUserRedactionQueue.ts index 1d3cbd6..f774459 100644 --- a/src/queues/UnlistedUserRedactionQueue.ts +++ b/src/queues/UnlistedUserRedactionQueue.ts @@ -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)); } } diff --git a/src/utils.ts b/src/utils.ts index 29eba22..c61c269 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -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, msgtype: MessageType = "m.text"): Promise { +export async function replaceRoomIdsWithPills(mjolnir: Mjolnir, text: string, roomIds: Set, msgtype: MessageType = "m.text"): Promise { 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"); diff --git a/test/integration/utilsTest.ts b/test/integration/utilsTest.ts index 22f853b..bc9b2db 100644 --- a/test/integration/utilsTest.ts +++ b/test/integration/utilsTest.ts @@ -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]) );