From af20087a1eb4d2a91910c5ddeed2008c71df99a4 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Wed, 26 Jan 2022 12:31:18 +0000 Subject: [PATCH] Revert "runtime persistent settings system (#158)" This reverts commit 423a34bebec04513ee7232f2abf773276a00b78d. --- src/Mjolnir.ts | 79 ---------- src/commands/CommandHandler.ts | 15 +- src/commands/ProtectionsCommands.ts | 173 --------------------- src/protections/BasicFlooding.ts | 14 +- src/protections/FirstMessageIsImage.ts | 2 - src/protections/IProtection.ts | 2 - src/protections/MessageIsMedia.ts | 2 - src/protections/MessageIsVoice.ts | 2 - src/protections/ProtectionSettings.ts | 120 -------------- src/protections/WordList.ts | 2 - src/protections/protections.ts | 4 +- test/integration/protectionSettingsTest.ts | 152 ------------------ 12 files changed, 7 insertions(+), 560 deletions(-) delete mode 100644 src/protections/ProtectionSettings.ts delete mode 100644 test/integration/protectionSettingsTest.ts diff --git a/src/Mjolnir.ts b/src/Mjolnir.ts index 3015b70..82b3e3b 100644 --- a/src/Mjolnir.ts +++ b/src/Mjolnir.ts @@ -36,7 +36,6 @@ import { logMessage } from "./LogProxy"; import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache"; import { IProtection } from "./protections/IProtection"; import { PROTECTIONS } from "./protections/protections"; -import { ProtectionSettingValidationError } from "./protections/ProtectionSettings"; import { UnlistedUserRedactionQueue } from "./queues/UnlistedUserRedactionQueue"; import { Healthz } from "./health/healthz"; import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQueue"; @@ -402,78 +401,6 @@ export class Mjolnir { } } - /* - * Read org.matrix.mjolnir.setting state event, find any saved settings for - * the requested protectionName, then iterate and validate against their parser - * counterparts in IProtection.settings and return those which validate - * - * @param protectionName The name of the protection whose settings we're reading - * @returns Every saved setting for this protectionName that has a valid value - */ - public async getProtectionSettings(protectionName: string): Promise<{ [setting: string]: any }> { - let savedSettings: { [setting: string]: any } = {} - try { - savedSettings = await this.client.getRoomStateEvent( - this.managementRoomId, 'org.matrix.mjolnir.setting', protectionName - ); - } catch { - // setting does not exist, return empty object - return savedSettings; - } - - const settingDefinitions = PROTECTIONS[protectionName].factory().settings; - const validatedSettings: { [setting: string]: any } = {} - for (let [key, value] of Object.entries(savedSettings)) { - if ( - // is this a setting name with a known parser? - key in settingDefinitions - // is the datatype of this setting's value what we expect? - && typeof(settingDefinitions[key].value) === typeof(value) - // is this setting's value valid for the setting? - && settingDefinitions[key].validate(value) - ) { - validatedSettings[key] = value; - } else { - await logMessage( - LogLevel.WARN, - "getProtectionSetting", - `Tried to read ${protectionName}.${key} and got invalid value ${value}` - ); - } - } - return validatedSettings; - } - - /* - * Takes an object of settings we want to change and what their values should be, - * check that their values are valid, combine them with current saved settings, - * then save the amalgamation to a state event - * - * @param protectionName Which protection these settings belong to - * @param changedSettings The settings to change and their values - */ - public async setProtectionSettings(protectionName: string, changedSettings: { [setting: string]: any }): Promise { - const settingDefinitions = PROTECTIONS[protectionName].factory().settings; - const validatedSettings: { [setting: string]: any } = await this.getProtectionSettings(protectionName); - - for (let [key, value] of Object.entries(changedSettings)) { - if (!(key in settingDefinitions)) { - throw new ProtectionSettingValidationError(`Failed to find protection setting by name: ${key}`); - } - if (typeof(settingDefinitions[key].value) !== typeof(value)) { - throw new ProtectionSettingValidationError(`Invalid type for protection setting: ${key} (${typeof(value)})`); - } - if (!settingDefinitions[key].validate(value)) { - throw new ProtectionSettingValidationError(`Invalid value for protection setting: ${key} (${value})`); - } - validatedSettings[key] = value; - } - - await this.client.sendStateEvent( - this.managementRoomId, 'org.matrix.mjolnir.setting', protectionName, validatedSettings - ); - } - public async enableProtection(protectionName: string, persist = true): Promise { const definition = PROTECTIONS[protectionName]; if (!definition) throw new Error("Failed to find protection by name: " + protectionName); @@ -481,12 +408,6 @@ export class Mjolnir { const protection = definition.factory(); this.protections.push(protection); - const savedSettings = await this.getProtectionSettings(protectionName); - for (let [key, value] of Object.entries(savedSettings)) { - // this.getProtectionSettings() validates this data for us, so we don't need to - protection.settings[key].setValue(value); - } - if (persist) { const existing = this.protections.map(p => p.name); await this.client.setAccountData(ENABLED_PROTECTIONS_EVENT_TYPE, { enabled: existing }); diff --git a/src/commands/CommandHandler.ts b/src/commands/CommandHandler.ts index 97bde07..d26005f 100644 --- a/src/commands/CommandHandler.ts +++ b/src/commands/CommandHandler.ts @@ -28,8 +28,7 @@ import { execRedactCommand } from "./RedactCommand"; import { execImportCommand } from "./ImportCommand"; import { execSetDefaultListCommand } from "./SetDefaultBanListCommand"; import { execDeactivateCommand } from "./DeactivateCommand"; -import { execDisableProtection, execEnableProtection, execListProtections, execConfigGetProtection, - execConfigSetProtection, execConfigAddProtection, execConfigRemoveProtection } from "./ProtectionsCommands"; +import { execDisableProtection, execEnableProtection, execListProtections } from "./ProtectionsCommands"; import { execListProtectedRooms } from "./ListProtectedRoomsCommand"; import { execAddProtectedRoom, execRemoveProtectedRoom } from "./AddRemoveProtectedRoomsCommand"; import { execAddRoomToDirectoryCommand, execRemoveRoomFromDirectoryCommand } from "./AddRemoveRoomFromDirectoryCommand"; @@ -77,14 +76,6 @@ export async function handleCommand(roomId: string, event: any, mjolnir: Mjolnir return await execEnableProtection(roomId, event, mjolnir, parts); } else if (parts[1] === 'disable' && parts.length > 1) { return await execDisableProtection(roomId, event, mjolnir, parts); - } else if (parts[1] === 'config' && parts[2] === 'set' && parts.length > 3) { - return await execConfigSetProtection(roomId, event, mjolnir, parts.slice(3)) - } else if (parts[1] === 'config' && parts[2] === 'add' && parts.length > 3) { - return await execConfigAddProtection(roomId, event, mjolnir, parts.slice(3)) - } else if (parts[1] === 'config' && parts[2] === 'remove' && parts.length > 3) { - return await execConfigRemoveProtection(roomId, event, mjolnir, parts.slice(3)) - } else if (parts[1] === 'config' && parts[2] === 'get') { - return await execConfigGetProtection(roomId, event, mjolnir, parts.slice(3)) } else if (parts[1] === 'rooms' && parts.length > 3 && parts[2] === 'add') { return await execAddProtectedRoom(roomId, event, mjolnir, parts); } else if (parts[1] === 'rooms' && parts.length > 3 && parts[2] === 'remove') { @@ -131,10 +122,6 @@ export async function handleCommand(roomId: string, event: any, mjolnir: Mjolnir "!mjolnir protections - List all available protections\n" + "!mjolnir enable - Enables a particular protection\n" + "!mjolnir disable - Disables a particular protection\n" + - "!mjolnir config set . [value] - Change a projection setting\n" + - "!mjolnir config add . [value] - Add a value to a list protection setting\n" + - "!mjolnir config remove . [value] - Remove a value from a list protection setting\n" + - "!mjolnir config get [protection] - List protection settings\n" + "!mjolnir rooms - Lists all the protected rooms\n" + "!mjolnir rooms add - Adds a protected room (may cause high server load)\n" + "!mjolnir rooms remove - Removes a protected room\n" + diff --git a/src/commands/ProtectionsCommands.ts b/src/commands/ProtectionsCommands.ts index 601f2fb..15f2f58 100644 --- a/src/commands/ProtectionsCommands.ts +++ b/src/commands/ProtectionsCommands.ts @@ -14,11 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as htmlEscape from "escape-html"; import { Mjolnir } from "../Mjolnir"; import { extractRequestError, LogService, RichReply } from "matrix-bot-sdk"; import { PROTECTIONS } from "../protections/protections"; -import { isListSetting } from "../protections/ProtectionSettings"; // !mjolnir enable export async function execEnableProtection(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { @@ -35,177 +33,6 @@ export async function execEnableProtection(roomId: string, event: any, mjolnir: } } -enum ConfigAction { - Set, - Add, - Remove -} - -/* - * Process a given ConfigAction against a given protection setting - * - * @param mjolnir Current Mjolnir instance - * @param parts Arguments given to the command being processed - * @param action Which ConfigAction to do to the provided protection setting - * @returns Command success or failure message - */ -async function _execConfigChangeProtection(mjolnir: Mjolnir, parts: string[], action: ConfigAction): Promise { - const [protectionName, ...settingParts] = parts[0].split("."); - const protection = PROTECTIONS[protectionName]; - if (protection === undefined) { - return `Unknown protection ${protectionName}`; - } - - const defaultSettings = protection.factory().settings - const settingName = settingParts[0]; - const stringValue = parts[1]; - - if (!(settingName in defaultSettings)) { - return `Unknown setting ${settingName}`; - } - - const parser = defaultSettings[settingName]; - // we don't need to validate `value`, because mjolnir.setProtectionSettings does - // it for us (and raises an exception if there's a problem) - let value = parser.fromString(stringValue); - - if (action === ConfigAction.Add) { - if (!isListSetting(parser)) { - return `Setting ${settingName} isn't a list`; - } else { - value = parser.addValue(value); - } - } else if (action === ConfigAction.Remove) { - if (!isListSetting(parser)) { - return `Setting ${settingName} isn't a list`; - } else { - value = parser.removeValue(value); - } - } - - // we need this to show what the value used to be - const oldSettings = await mjolnir.getProtectionSettings(protectionName); - - try { - await mjolnir.setProtectionSettings(protectionName, { [settingName]: value }); - } catch (e) { - return `Failed to set setting: ${e.message}`; - } - - const enabledProtections = Object.fromEntries(mjolnir.enabledProtections.map(p => [p.name, p])); - if (protectionName in enabledProtections) { - // protection is currently loaded, so change the live setting value - enabledProtections[protectionName].settings[settingName].setValue(value); - } - - return `Changed ${protectionName}.${settingName} to ${value} (was ${oldSettings[settingName]})`; -} - -/* - * Change a protection setting - * - * !mjolnir set . - */ -export async function execConfigSetProtection(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { - const message = await _execConfigChangeProtection(mjolnir, parts, ConfigAction.Set); - - const reply = RichReply.createFor(roomId, event, message, message); - reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); -} - -/* - * Add a value to a protection list setting - * - * !mjolnir add . - */ -export async function execConfigAddProtection(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { - const message = await _execConfigChangeProtection(mjolnir, parts, ConfigAction.Add); - - const reply = RichReply.createFor(roomId, event, message, message); - reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); -} - -/* - * Remove a value from a protection list setting - * - * !mjolnir remove . - */ -export async function execConfigRemoveProtection(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { - const message = await _execConfigChangeProtection(mjolnir, parts, ConfigAction.Remove); - - const reply = RichReply.createFor(roomId, event, message, message); - reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); -} - -/* - * Get all protection settings or get all settings for a given protection - * - * !mjolnir get [protection name] - */ -export async function execConfigGetProtection(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { - let pickProtections = Object.keys(PROTECTIONS); - - if (parts.length < 3) { - // no specific protectionName provided, show all of them. - - // sort output by protection name - pickProtections.sort(); - } else { - if (!pickProtections.includes(parts[0])) { - const errMsg = `Unknown protection: ${parts[0]}`; - const errReply = RichReply.createFor(roomId, event, errMsg, errMsg); - errReply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, errReply); - return; - } - pickProtections = [parts[0]]; - } - - let text = "Protection settings\n"; - let html = "Protection settings
    "; - - let anySettings = false; - - for (const protectionName of pickProtections) { - // get all available settings, their default values, and their parsers - const availableSettings = PROTECTIONS[protectionName].factory().settings; - // get all saved non-default values - const savedSettings = await mjolnir.getProtectionSettings(protectionName); - - if (Object.keys(availableSettings).length === 0) continue; - - const settingNames = Object.keys(PROTECTIONS[protectionName].factory().settings); - // this means, within each protection name, setting names are sorted - settingNames.sort(); - for (const settingName of settingNames) { - anySettings = true; - - let value = availableSettings[settingName].value - if (settingName in savedSettings) - // we have a non-default value for this setting, use it - value = savedSettings[settingName] - - text += `* ${protectionName}.${settingName}: ${value}`; - // `protectionName` and `settingName` are user-provided but - // validated against the names of existing protections and their - // settings, so XSS is avoided for these already - html += `
  • ${protectionName}.${settingName}: ${htmlEscape(value)}
  • ` - } - } - - html += "
"; - - if (!anySettings) - html = text = "No settings found"; - - const reply = RichReply.createFor(roomId, event, text, html); - reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); -} - // !mjolnir disable export async function execDisableProtection(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { await mjolnir.disableProtection(parts[2]); diff --git a/src/protections/BasicFlooding.ts b/src/protections/BasicFlooding.ts index 3f9cb3f..017b568 100644 --- a/src/protections/BasicFlooding.ts +++ b/src/protections/BasicFlooding.ts @@ -15,14 +15,12 @@ limitations under the License. */ import { IProtection } 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 -export const DEFAULT_MAX_PER_MINUTE = 10; +export const MAX_PER_MINUTE = 10; // if this is exceeded, we'll ban the user for spam and redact their messages const TIMESTAMP_THRESHOLD = 30000; // 30s out of phase export class BasicFlooding implements IProtection { @@ -30,11 +28,7 @@ export class BasicFlooding implements IProtection { private lastEvents: { [roomId: string]: { [userId: string]: { originServerTs: number, eventId: string }[] } } = {}; private recentlyBanned: string[] = []; - maxPerMinute = new NumberProtectionSetting(DEFAULT_MAX_PER_MINUTE); - settings = {}; - constructor() { - this.settings['maxPerMinute'] = this.maxPerMinute; } public get name(): string { @@ -62,7 +56,7 @@ export class BasicFlooding implements IProtection { messageCount++; } - if (messageCount >= this.maxPerMinute.value) { + if (messageCount >= MAX_PER_MINUTE) { await 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"); @@ -88,8 +82,8 @@ export class BasicFlooding implements IProtection { } // Trim the oldest messages off the user's history if it's getting large - if (forUser.length > this.maxPerMinute.value * 2) { - forUser.splice(0, forUser.length - (this.maxPerMinute.value * 2) - 1); + if (forUser.length > MAX_PER_MINUTE * 2) { + forUser.splice(0, forUser.length - (MAX_PER_MINUTE * 2) - 1); } } } diff --git a/src/protections/FirstMessageIsImage.ts b/src/protections/FirstMessageIsImage.ts index 6c526d9..89a7865 100644 --- a/src/protections/FirstMessageIsImage.ts +++ b/src/protections/FirstMessageIsImage.ts @@ -26,8 +26,6 @@ export class FirstMessageIsImage implements IProtection { private justJoined: { [roomId: string]: string[] } = {}; private recentlyBanned: string[] = []; - settings = {}; - constructor() { } diff --git a/src/protections/IProtection.ts b/src/protections/IProtection.ts index 61a9a05..4d7d261 100644 --- a/src/protections/IProtection.ts +++ b/src/protections/IProtection.ts @@ -15,7 +15,6 @@ limitations under the License. */ import { Mjolnir } from "../Mjolnir"; -import { AbstractProtectionSetting } from "./ProtectionSettings"; /** * Represents a protection mechanism of sorts. Protections are intended to be @@ -25,6 +24,5 @@ import { AbstractProtectionSetting } from "./ProtectionSettings"; */ export interface IProtection { readonly name: string; - settings: { [setting: string]: AbstractProtectionSetting }; handleEvent(mjolnir: Mjolnir, roomId: string, event: any): Promise; } diff --git a/src/protections/MessageIsMedia.ts b/src/protections/MessageIsMedia.ts index 7979445..a26289e 100644 --- a/src/protections/MessageIsMedia.ts +++ b/src/protections/MessageIsMedia.ts @@ -22,8 +22,6 @@ import config from "../config"; export class MessageIsMedia implements IProtection { - settings = {}; - constructor() { } diff --git a/src/protections/MessageIsVoice.ts b/src/protections/MessageIsVoice.ts index 867d84f..2bf6f81 100644 --- a/src/protections/MessageIsVoice.ts +++ b/src/protections/MessageIsVoice.ts @@ -22,8 +22,6 @@ import config from "../config"; export class MessageIsVoice implements IProtection { - settings = {}; - constructor() { } diff --git a/src/protections/ProtectionSettings.ts b/src/protections/ProtectionSettings.ts deleted file mode 100644 index 3acde9f..0000000 --- a/src/protections/ProtectionSettings.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* -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. -*/ - -export class ProtectionSettingValidationError extends Error {}; - -export class AbstractProtectionSetting { - // the current value of this setting - value: TValue - - /* - * Deserialise a value for this setting type from a string - * - * @param data Serialised value - * @returns Deserialised value or undefined if deserialisation failed - */ - fromString(data: string): TChange | undefined { - throw new Error("not Implemented"); - } - - /* - * Check whether a given value is valid for this setting - * - * @param data Setting value - * @returns Validity of provided value - */ - validate(data: TChange): boolean { - throw new Error("not Implemented"); - } - - /* - * Store a value in this setting, only to be used after `validate()` - * @param data Validated setting value - */ - setValue(data: TValue) { - this.value = data; - } -} -export class AbstractProtectionListSetting extends AbstractProtectionSetting { - /* - * Add `data` to the current setting value, and return that new object - * - * @param data Value to add to the current setting value - * @returns The potential new value of this setting object - */ - addValue(data: TChange): TValue { - throw new Error("not Implemented"); - } - - /* - * Remove `data` from the current setting value, and return that new object - * - * @param data Value to remove from the current setting value - * @returns The potential new value of this setting object - */ - removeValue(data: TChange): TValue { - throw new Error("not Implemented"); - } -} -export function isListSetting(object: any): object is AbstractProtectionListSetting { - return object instanceof AbstractProtectionListSetting; -} - - -export class StringProtectionSetting extends AbstractProtectionSetting { - value = ""; - fromString = (data) => data; - validate = (data) => true; -} -export class StringListProtectionSetting extends AbstractProtectionListSetting { - value: string[] = []; - fromString = (data) => data; - validate = (data) => true; - addValue(data: string): string[] { - return [...this.value, data]; - } - removeValue(data: string): string[] { - const index = this.value.indexOf(data); - return this.value.splice(index, index + 1); - } -} - -export class NumberProtectionSetting extends AbstractProtectionSetting { - min: number|undefined; - max: number|undefined; - - constructor( - defaultValue: number, - min: number|undefined = undefined, - max: number|undefined = undefined - ) { - super(); - this.setValue(defaultValue); - this.min = min; - this.max = max; - } - - fromString(data) { - let number = Number(data); - return isNaN(number) ? undefined : number; - } - validate(data) { - return (!isNaN(data) - && (this.min === undefined || this.min <= data) - && (this.max === undefined || data <= this.max)) - } - -} diff --git a/src/protections/WordList.ts b/src/protections/WordList.ts index f3caad6..88015eb 100644 --- a/src/protections/WordList.ts +++ b/src/protections/WordList.ts @@ -23,8 +23,6 @@ import { isTrueJoinEvent } from "../utils"; export class WordList implements IProtection { - settings = {}; - private justJoined: { [roomId: string]: { [username: string]: Date} } = {}; private badWords: RegExp; diff --git a/src/protections/protections.ts b/src/protections/protections.ts index 356bc9e..cbd7ee8 100644 --- a/src/protections/protections.ts +++ b/src/protections/protections.ts @@ -16,7 +16,7 @@ limitations under the License. import { FirstMessageIsImage } from "./FirstMessageIsImage"; import { IProtection } from "./IProtection"; -import { BasicFlooding, DEFAULT_MAX_PER_MINUTE } from "./BasicFlooding"; +import { BasicFlooding, MAX_PER_MINUTE } from "./BasicFlooding"; import { WordList } from "./WordList"; import { MessageIsVoice } from "./MessageIsVoice"; import { MessageIsMedia } from "./MessageIsMedia"; @@ -28,7 +28,7 @@ export const PROTECTIONS: PossibleProtections = { factory: () => new FirstMessageIsImage(), }, [new BasicFlooding().name]: { - description: "If a user posts more than " + DEFAULT_MAX_PER_MINUTE + " messages in 60s they'll be " + + description: "If a user posts more than " + MAX_PER_MINUTE + " messages in 60s they'll be " + "banned for spam. This does not publish the ban to any of your ban lists.", factory: () => new BasicFlooding(), }, diff --git a/test/integration/protectionSettingsTest.ts b/test/integration/protectionSettingsTest.ts deleted file mode 100644 index 3b84511..0000000 --- a/test/integration/protectionSettingsTest.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { strict as assert } from "assert"; - -import config from "../../src/config"; -import { PROTECTIONS } from "../../src/protections/protections"; -import { ProtectionSettingValidationError } from "../../src/protections/ProtectionSettings"; -import { NumberProtectionSetting, StringProtectionSetting, StringListProtectionSetting } from "../../src/protections/ProtectionSettings"; -import { newTestUser, noticeListener } from "./clientHelper"; -import { matrixClient, mjolnir } from "./mjolnirSetupUtils"; - -describe("Test: Protection settings", function() { - let client; - this.beforeEach(async function () { - client = await newTestUser(true); - await client.start(); - }) - this.afterEach(async function () { - await client.stop(); - }) - it("Mjolnir refuses to save invalid protection setting values", async function() { - this.timeout(20000); - await assert.rejects( - async () => await this.mjolnir.setProtectionSettings("BasicFloodingProtection", {"maxPerMinute": "soup"}), - ProtectionSettingValidationError - ); - }); - it("Mjolnir successfully saves valid protection setting values", async function() { - this.timeout(20000); - - PROTECTIONS["05OVMS"] = { - description: "A test protection", - factory: () => new class implements IProtection { - name = "05OVMS"; - async handleEvent(mjolnir: Mjolnir, roomId: string, event: any) {}; - settings = { test: new NumberProtectionSetting(3) }; - } - }; - - await this.mjolnir.setProtectionSettings("05OVMS", { test: 123 }); - assert.equal( - (await this.mjolnir.getProtectionSettings("05OVMS"))["test"], - 123 - ); - }); - it("Mjolnir should accumulate changed settings", async function() { - this.timeout(20000); - - PROTECTIONS["HPUjKN"] = { - description: "A test protection", - factory: () => new class implements IProtection { - name = "HPUjKN"; - async handleEvent(mjolnir: Mjolnir, roomId: string, event: any) {}; - settings = { - test1: new NumberProtectionSetting(3), - test2: new NumberProtectionSetting(4) - }; - } - }; - - await this.mjolnir.setProtectionSettings("HPUjKN", { test1: 1 }); - await this.mjolnir.setProtectionSettings("HPUjKN", { test2: 2 }); - const settings = await this.mjolnir.getProtectionSettings("HPUjKN"); - assert.equal(settings["test1"], 1); - assert.equal(settings["test2"], 2); - }); - it("Mjolnir responds to !set correctly", async function() { - this.timeout(20000); - await client.joinRoom(config.managementRoom); - - PROTECTIONS["JY2TPN"] = { - description: "A test protection", - factory: () => new class implements IProtection { - name = "JY2TPN"; - async handleEvent(mjolnir: Mjolnir, roomId: string, event: any) {}; - settings = { test: new StringProtectionSetting() }; - } - }; - - - let reply = new Promise((resolve, reject) => { - client.on('room.message', noticeListener(this.mjolnir.managementRoomId, (event) => { - if (event.content.body.includes("Changed JY2TPN.test ")) { - resolve(event); - } - })) - }); - - await client.sendMessage(this.mjolnir.managementRoomId, {msgtype: "m.text", body: "!mjolnir config set JY2TPN.test asd"}) - await reply - - const settings = await this.mjolnir.getProtectionSettings("JY2TPN"); - assert.equal(settings["test"], "asd"); - }); - it("Mjolnir adds a value to a list setting", async function() { - this.timeout(20000); - await client.joinRoom(config.managementRoom); - - PROTECTIONS["r33XyT"] = { - description: "A test protection", - factory: () => new class implements IProtection { - name = "r33XyT"; - async handleEvent(mjolnir: Mjolnir, roomId: string, event: any) {}; - settings = { test: new StringListProtectionSetting() }; - } - }; - - - let reply = new Promise((resolve, reject) => { - client.on('room.message', noticeListener(this.mjolnir.managementRoomId, (event) => { - if (event.content.body.includes("Changed r33XyT.test ")) { - resolve(event); - } - })) - }); - - await client.sendMessage(this.mjolnir.managementRoomId, {msgtype: "m.text", body: "!mjolnir config add r33XyT.test asd"}) - await reply - - assert.deepEqual(await this.mjolnir.getProtectionSettings("r33XyT"), { "test": ["asd"] }); - }); - it("Mjolnir removes a value from a list setting", async function() { - this.timeout(20000); - await client.joinRoom(config.managementRoom); - - PROTECTIONS["oXzT0E"] = { - description: "A test protection", - factory: () => new class implements IProtection { - name = "oXzT0E"; - async handleEvent(mjolnir: Mjolnir, roomId: string, event: any) {}; - settings = { test: new StringListProtectionSetting() }; - } - }; - - - let reply = new Promise((resolve, reject) => { - let i = 0; - client.on('room.message', noticeListener(this.mjolnir.managementRoomId, (event) => { - if (event.content.body.includes("Changed oXzT0E.test ")) { - if (++i == 2) { - resolve(event); - } - } - })) - }); - - await client.sendMessage(this.mjolnir.managementRoomId, {msgtype: "m.text", body: "!mjolnir config add oXzT0E.test asd"}) - await client.sendMessage(this.mjolnir.managementRoomId, {msgtype: "m.text", body: "!mjolnir config remove oXzT0E.test asd"}) - await reply - - assert.deepEqual(await this.mjolnir.getProtectionSettings("oXzT0E"), { "test": [] }); - }); -}); -