From acf0a406de130dd3d60ea428495ceebe3c396626 Mon Sep 17 00:00:00 2001 From: Gnuxie <50846879+Gnuxie@users.noreply.github.com> Date: Sat, 1 Feb 2025 17:53:10 +0000 Subject: [PATCH] Add functionality for `config.protectAllJoinedRooms` via a protection + adjacent changes. (#711) * Initial ProtectAllJoinedRoomsProtection. We need something like this to implement `config.protectAllJoinedRooms`, we also need something to go alongside that removes rooms as they are added or removed. it will probably be a behaviour of the same protection that we will split out. * Move it cos i cba and we need to merge with a protection to unprotect on leave and ban. * IDK i keep getting distracted i need my notebook back aaaa. * Create RoomsSetBehaviourProtection out of redundant protections. * Add toggle for RoomsSetBehaviour. * Enable the RoomsSetBehaviour protection by default. * Add the behaviour protection to protections index. * Whoopsie unprotected parted rooms should actually call itself. * Fix bugs meow. * handleExternalInvite -> handleExternalMembership * Make sure leave events get propagated in bot mode. * Add batcher to ProtectJoinedRooms component. * Hook into handleExternalMembership for RoomsSetBehaviour. * Remove crap from fixtures.ts * leave all rooms when starting integration tests. * Change how protections are informed of membership. * Add test for Joinig and protecting rooms on invite. * Tidy up UnprotectPartedRooms rendering. * Update for MPS v2.7.0 Added - Generic item batching is now available for protections to use by using the `StandardBatcher`. Changed - `Task` has been improved to be more liberal in the closures it accepts. And `Task` now has more options for logging how tasks have failed. - The `Protection` callback `handleExternalInvite` has been renamed to `handleExternalMembership`. Fixed - An issue where adding rooms to the protected rooms set more than once could sometimes cause duplicate events to be propagated. * Fix typo mare. * Stop protecting rooms automatically when config.protectAllJoinedRooms is false. * Update CHANGELOG.md --- CHANGELOG.md | 16 ++ package.json | 4 +- src/Draupnir.ts | 10 +- src/DraupnirBotMode.ts | 4 + .../DefaultEnabledProtectionsMigration.ts | 22 +++ src/protections/DraupnirProtectionsIndex.ts | 1 + .../ProtectedRooms/ProtectJoinedRooms.tsx | 114 +++++++++++++++ .../RoomsSetBehaviourProtection.tsx | 99 +++++++++++++ .../ProtectedRooms/UnprotectPartedRooms.tsx | 99 +++++++++++++ .../JoinRoomsOnInviteProtection.tsx | 2 +- test/integration/fixtures.ts | 5 - test/integration/mjolnirSetupUtils.ts | 9 ++ .../protections/JoinRoomsOnInviteTest.ts | 138 ++++++++++++++++++ yarn.lock | 16 +- 14 files changed, 520 insertions(+), 19 deletions(-) create mode 100644 src/protections/ProtectedRooms/ProtectJoinedRooms.tsx create mode 100644 src/protections/ProtectedRooms/RoomsSetBehaviourProtection.tsx create mode 100644 src/protections/ProtectedRooms/UnprotectPartedRooms.tsx create mode 100644 test/integration/protections/JoinRoomsOnInviteTest.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a7b66d5..bd76fcb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,22 @@ and this project adheres to ## [Unreleased] - None +### Fixed + +- `config.protectAllJoinedRooms` was unimplemented in versions `v2.0.2` and + below. This went under the radar in the beta programme because it would have + only been detectable for first time testers migrating over. Reported by + @cremesk. + +- Draupnir will now automatically unprotect rooms when the bot is kicked, and + send an alert to the management room. + +### Added + +- `RoomSetBehaviourProtection` to add the + `config.protectAllJoinedRoomsFunctionality`. This is also responsible for + unprotecting rooms as the bot is removed from them. + ## [v2.0.2] 2025-01-24 ### Added diff --git a/package.json b/package.json index 721ce291..51fe6d48 100644 --- a/package.json +++ b/package.json @@ -63,8 +63,8 @@ "jsdom": "^24.0.0", "matrix-appservice-bridge": "^10.3.1", "matrix-bot-sdk": "npm:@vector-im/matrix-bot-sdk@^0.7.1-element.6", - "matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@2.6.0", - "matrix-protection-suite-for-matrix-bot-sdk": "npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@2.6.0", + "matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@2.7.0", + "matrix-protection-suite-for-matrix-bot-sdk": "npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@2.7.0", "pg": "^8.8.0", "yaml": "^2.3.2" }, diff --git a/src/Draupnir.ts b/src/Draupnir.ts index d78fb30e..6f4a4685 100644 --- a/src/Draupnir.ts +++ b/src/Draupnir.ts @@ -16,6 +16,7 @@ import { EventReport, LoggableConfigTracker, Logger, + Membership, MembershipEvent, Ok, PolicyRoomManager, @@ -50,7 +51,6 @@ import { RendererMessageCollector } from "./capabilities/RendererMessageCollecto import { DraupnirRendererMessageCollector } from "./capabilities/DraupnirRendererMessageCollector"; import { renderProtectionFailedToStart } from "./protections/ProtectedRoomsSetRenderers"; import { draupnirStatusInfo, renderStatusInfo } from "./commands/StatusCommand"; -import { isInvitationForUser } from "./protections/invitation/inviteCore"; import { StringRoomID, StringUserID, @@ -319,9 +319,13 @@ export class Draupnir implements Client, MatrixAdaptorContext { public handleTimelineEvent(roomID: StringRoomID, event: RoomEvent): void { if ( Value.Check(MembershipEvent, event) && - isInvitationForUser(event, this.clientUserID) + event.state_key === this.clientUserID && + // if the membership is join, make sure that we filter out protected rooms. + (event.content.membership === Membership.Join + ? !this.protectedRoomsSet.isProtectedRoom(roomID) + : true) ) { - this.protectedRoomsSet.handleExternalInvite(roomID, event); + this.protectedRoomsSet.handleExternalMembership(roomID, event); } this.managementRoomMessageListener(roomID, event); void Task( diff --git a/src/DraupnirBotMode.ts b/src/DraupnirBotMode.ts index 66ff32c8..953425a8 100644 --- a/src/DraupnirBotMode.ts +++ b/src/DraupnirBotMode.ts @@ -96,6 +96,10 @@ export class DraupnirBotModeToggle implements BotModeTogle { this.roomStateManagerFactory.handleTimelineEvent(roomID, event); this.clientsInRoomMap.handleTimelineEvent(roomID, event); }); + this.matrixEmitter.on("room.leave", (roomID, event) => { + this.roomStateManagerFactory.handleTimelineEvent(roomID, event); + this.clientsInRoomMap.handleTimelineEvent(roomID, event); + }); } public static async create( client: MatrixSendClient, diff --git a/src/protections/DefaultEnabledProtectionsMigration.ts b/src/protections/DefaultEnabledProtectionsMigration.ts index 3a098af0..bff0bed1 100644 --- a/src/protections/DefaultEnabledProtectionsMigration.ts +++ b/src/protections/DefaultEnabledProtectionsMigration.ts @@ -17,6 +17,7 @@ import { import { RedactionSynchronisationProtection } from "./RedactionSynchronisation"; import { PolicyChangeNotification } from "./PolicyChangeNotification"; import { JoinRoomsOnInviteProtection } from "./invitation/JoinRoomsOnInviteProtection"; +import { RoomsSetBehaviour } from "./ProtectedRooms/RoomsSetBehaviourProtection"; export const DefaultEnabledProtectionsMigration = new SchemedDataManager([ @@ -143,4 +144,25 @@ export const DefaultEnabledProtectionsMigration = [DRAUPNIR_SCHEMA_VERSION_KEY]: 5, }); }, + async function enableRoomsSetBehaviourProtection(input, toVersion) { + if (!Value.Check(MjolnirEnabledProtectionsEvent, input)) { + return ActionError.Result( + `The data for ${MjolnirEnabledProtectionsEventType} is corrupted.` + ); + } + const enabledProtections = new Set(input.enabled); + const protection = findProtection(RoomsSetBehaviour.name); + if (protection === undefined) { + const message = `Cannot find the ${RoomsSetBehaviour.name} protection`; + return ActionException.Result(message, { + exception: new TypeError(message), + exceptionKind: ActionExceptionKind.Unknown, + }); + } + enabledProtections.add(protection.name); + return Ok({ + enabled: [...enabledProtections], + [DRAUPNIR_SCHEMA_VERSION_KEY]: toVersion, + }); + }, ]); diff --git a/src/protections/DraupnirProtectionsIndex.ts b/src/protections/DraupnirProtectionsIndex.ts index bf7f80b3..658de39e 100644 --- a/src/protections/DraupnirProtectionsIndex.ts +++ b/src/protections/DraupnirProtectionsIndex.ts @@ -19,6 +19,7 @@ import "./MessageIsMedia"; import "./MessageIsVoice"; import "./NewJoinerProtection"; import "./PolicyChangeNotification"; +import "./ProtectedRooms/RoomsSetBehaviourProtection"; import "./TrustedReporters"; import "./WordList"; diff --git a/src/protections/ProtectedRooms/ProtectJoinedRooms.tsx b/src/protections/ProtectedRooms/ProtectJoinedRooms.tsx new file mode 100644 index 00000000..d8f2e1a2 --- /dev/null +++ b/src/protections/ProtectedRooms/ProtectJoinedRooms.tsx @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2025 Gnuxie +// +// SPDX-License-Identifier: AFL-3.0 + +import { + MatrixRoomReference, + StringRoomID, + StringUserID, +} from "@the-draupnir-project/matrix-basic-types"; +import { + ClientRooms, + ConstantPeriodItemBatch, + isError, + Logger, + MembershipChange, + MembershipChangeType, + MembershipEvent, + ProtectedRoomsSet, + RoomMessageSender, + RoomSetResultBuilder, + StandardBatcher, + Task, +} from "matrix-protection-suite"; +import { sendMatrixEventsFromDeadDocument } from "../../commands/interface-manager/MPSMatrixInterfaceAdaptor"; +import { renderRoomSetResult } from "../../capabilities/CommonRenderers"; +import { DeadDocumentJSX } from "@the-draupnir-project/interface-manager"; + +const log = new Logger("ProtectAllJoinedRooms"); + +export class ProtectedJoinedRooms { + private readonly batcher = new StandardBatcher( + () => + new ConstantPeriodItemBatch( + this.syncProtectedRooms.bind(this), + { waitPeriodMS: 1000 } + ) + ); + public constructor( + private readonly clientUserID: StringUserID, + private readonly managementRoomID: StringRoomID, + private readonly protectedRoomsSet: ProtectedRoomsSet, + private readonly clientRooms: ClientRooms, + private readonly roomMessageSender: RoomMessageSender + ) { + // nothing to do. + } + + handleMembershipChange(changes: MembershipChange[]): void { + for (const change of changes) { + if (change.userID === this.clientUserID) { + switch (change.membershipChangeType) { + case MembershipChangeType.NoChange: { + continue; + } + default: { + this.batcher.add(change.roomID); + return; + } + } + } + } + } + + handleExternalMembership( + roomID: StringRoomID, + _event: MembershipEvent + ): void { + this.batcher.add(roomID); + } + + public async syncProtectedRooms() { + const policyRooms = + this.protectedRoomsSet.issuerManager.allWatchedLists.map((profile) => + profile.room.toRoomIDOrAlias() + ); + const roomsToProtect = + this.clientRooms.currentRevision.allJoinedRooms.filter((roomID) => { + return ( + !policyRooms.includes(roomID) && + !this.protectedRoomsSet.isProtectedRoom(roomID) + ); + }); + const setResult = new RoomSetResultBuilder(); + for (const roomID of roomsToProtect) { + const protectResult = + await this.protectedRoomsSet.protectedRoomsManager.addRoom( + MatrixRoomReference.fromRoomID(roomID) + ); + if (isError(protectResult)) { + log.error("Unable to protect the room", roomID, protectResult.error); + } + setResult.addResult(roomID, protectResult); + } + if (setResult.getResult().map.size === 0) { + return; + } else { + void Task( + sendMatrixEventsFromDeadDocument( + this.roomMessageSender, + this.managementRoomID, + + {renderRoomSetResult(setResult.getResult(), { + summary:

Protecting new rooms.

, + })} +
, + {} + ), + { + description: "Report newly protected rooms to the managmeent room.", + } + ); + } + } +} diff --git a/src/protections/ProtectedRooms/RoomsSetBehaviourProtection.tsx b/src/protections/ProtectedRooms/RoomsSetBehaviourProtection.tsx new file mode 100644 index 00000000..14dba437 --- /dev/null +++ b/src/protections/ProtectedRooms/RoomsSetBehaviourProtection.tsx @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2025 Gnuxie +// +// SPDX-License-Identifier: AFL-3.0 + +import { + AbstractProtection, + describeProtection, + MembershipChange, + MembershipEvent, + ProtectedRoomsSet, + ProtectionDescription, + RoomMembershipRevision, + UnknownConfig, +} from "matrix-protection-suite"; +import { DraupnirProtection } from "../Protection"; +import { Draupnir } from "../../Draupnir"; +import { Ok, Result } from "@gnuxie/typescript-result"; +import { ProtectedJoinedRooms } from "./ProtectJoinedRooms"; +import { UnprotectPartedRooms } from "./UnprotectPartedRooms"; +import { StringRoomID } from "@the-draupnir-project/matrix-basic-types"; + +export type RoomsSetBehaviourCapabailities = Record; +export type RoomsSetBehaviourSettings = UnknownConfig; + +export type RoomsSetBehaviourDescription = ProtectionDescription< + Draupnir, + RoomsSetBehaviourSettings, + RoomsSetBehaviourCapabailities +>; + +export class RoomsSetBehaviour + extends AbstractProtection + implements DraupnirProtection +{ + private readonly protectJoinedRooms = new ProtectedJoinedRooms( + this.draupnir.clientUserID, + this.draupnir.managementRoomID, + this.protectedRoomsSet, + this.draupnir.clientRooms, + this.draupnir.clientPlatform.toRoomMessageSender() + ); + private readonly unprotectedPartedRooms = new UnprotectPartedRooms( + this.draupnir.clientUserID, + this.draupnir.managementRoomID, + this.protectedRoomsSet.protectedRoomsManager, + this.draupnir.clientPlatform.toRoomMessageSender() + ); + public constructor( + description: RoomsSetBehaviourDescription, + capabilities: RoomsSetBehaviourCapabailities, + protectedRoomsSet: ProtectedRoomsSet, + private readonly draupnir: Draupnir + ) { + super(description, capabilities, protectedRoomsSet, {}); + if (this.draupnir.config.protectAllJoinedRooms) { + void this.protectJoinedRooms.syncProtectedRooms(); + } + } + + public handleMembershipChange( + _revision: RoomMembershipRevision, + changes: MembershipChange[] + ): Promise> { + if (this.draupnir.config.protectAllJoinedRooms) { + this.protectJoinedRooms.handleMembershipChange(changes); + } + for (const change of changes) { + this.unprotectedPartedRooms.handleMembershipChange(change); + } + return Promise.resolve(Ok(undefined)); + } + + public handleExternalMembership( + roomID: StringRoomID, + event: MembershipEvent + ): void { + if (this.draupnir.config.protectAllJoinedRooms) { + this.protectJoinedRooms.handleExternalMembership(roomID, event); + } + } +} + +describeProtection({ + name: RoomsSetBehaviour.name, + description: + "Unprotects parted rooms and update the list of protected rooms.", + capabilityInterfaces: {}, + defaultCapabilities: {}, + factory(description, protectedRoomsSet, draupnir, capabilities, _settings) { + return Ok( + new RoomsSetBehaviour( + description, + capabilities, + protectedRoomsSet, + draupnir + ) + ); + }, +}); diff --git a/src/protections/ProtectedRooms/UnprotectPartedRooms.tsx b/src/protections/ProtectedRooms/UnprotectPartedRooms.tsx new file mode 100644 index 00000000..0dcca97a --- /dev/null +++ b/src/protections/ProtectedRooms/UnprotectPartedRooms.tsx @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2025 Gnuxie +// +// SPDX-License-Identifier: AFL-3.0 + +import { + MatrixRoomReference, + StringRoomID, + StringUserID, +} from "@the-draupnir-project/matrix-basic-types"; +import { + isOk, + MembershipChange, + MembershipChangeType, + ProtectedRoomsManager, + RoomMessageSender, + Task, +} from "matrix-protection-suite"; +import { sendMatrixEventsFromDeadDocument } from "../../commands/interface-manager/MPSMatrixInterfaceAdaptor"; +import { + renderMentionPill, + renderRoomPill, +} from "../../commands/interface-manager/MatrixHelpRenderer"; +import { DeadDocumentJSX } from "@the-draupnir-project/interface-manager"; + +export class UnprotectPartedRooms { + constructor( + private readonly clientUserID: StringUserID, + private readonly managementRoomID: StringRoomID, + private readonly protectedRoomsManager: ProtectedRoomsManager, + private readonly messageSender: RoomMessageSender + ) { + // nothing to do. + } + + public async handlePartedRoom(change: MembershipChange): Promise { + const room = MatrixRoomReference.fromRoomID(change.roomID); + const unprotectResult = await this.protectedRoomsManager.removeRoom(room); + const removalDescription = ( + + Draupnir has been removed from {renderRoomPill(room)} by{" "} + {renderMentionPill(change.sender, change.sender)} + {change.content.reason ? ( + + {" "} + for reason: {change.content.reason} + + ) : ( + "" + )} + . + + ); + if (isOk(unprotectResult)) { + void Task( + sendMatrixEventsFromDeadDocument( + this.messageSender, + this.managementRoomID, + {removalDescription} The room is now unprotected., + {} + ), + { + description: + "Report recently unprotected rooms to the management room.", + } + ); + } else { + void Task( + sendMatrixEventsFromDeadDocument( + this.messageSender, + this.managementRoomID, + + {removalDescription} Draupnir could not unprotect the room. Please + use !draupnir rooms remove {room.toRoomIDOrAlias()} if + the room is still marked as protected. + , + {} + ), + { + description: + "Report recently unprotected rooms to the management room.", + } + ); + } + } + + public handleMembershipChange(change: MembershipChange): void { + if (change.userID === this.clientUserID) { + if (!this.protectedRoomsManager.isProtectedRoom(change.roomID)) { + return; + } + switch (change.membershipChangeType) { + case MembershipChangeType.Banned: + case MembershipChangeType.Kicked: + case MembershipChangeType.Left: + void this.handlePartedRoom(change); + } + } + } +} diff --git a/src/protections/invitation/JoinRoomsOnInviteProtection.tsx b/src/protections/invitation/JoinRoomsOnInviteProtection.tsx index 678bc827..f7725398 100644 --- a/src/protections/invitation/JoinRoomsOnInviteProtection.tsx +++ b/src/protections/invitation/JoinRoomsOnInviteProtection.tsx @@ -83,7 +83,7 @@ export class JoinRoomsOnInviteProtection this.watchRoomsOnInvite.handleProtectionDisable(); } - handleExternalInvite(roomID: StringRoomID, event: MembershipEvent): void { + handleExternalMembership(roomID: StringRoomID, event: MembershipEvent): void { if (!isInvitationForUser(event, this.protectedRoomsSet.userID)) { return; } diff --git a/test/integration/fixtures.ts b/test/integration/fixtures.ts index 969c15e8..2b6dd066 100644 --- a/test/integration/fixtures.ts +++ b/test/integration/fixtures.ts @@ -21,7 +21,6 @@ import { makeBotModeToggle, teardownManagementRoom, } from "./mjolnirSetupUtils"; -import { MatrixRoomReference } from "@the-draupnir-project/matrix-basic-types"; patchMatrixClient(); @@ -38,10 +37,6 @@ export const mochaHooks = { JSON.stringify(this.currentTest?.title) ); // Makes MatrixClient error logs a bit easier to parse. console.log("mochaHooks.beforeEach"); - const test = MatrixRoomReference.fromPermalink( - "https://matrix.to/#/!JzRjamSLPHAikHkPab%3Alocalhost%3A9999?via=localhost:9999" - ); - console.log(test); // Sometimes it takes a little longer to register users. this.timeout(30000); const config = (this.config = configRead()); diff --git a/test/integration/mjolnirSetupUtils.ts b/test/integration/mjolnirSetupUtils.ts index 2bec3e08..509226d4 100644 --- a/test/integration/mjolnirSetupUtils.ts +++ b/test/integration/mjolnirSetupUtils.ts @@ -21,6 +21,7 @@ import { IConfig } from "../../src/config"; import { Draupnir } from "../../src/Draupnir"; import { DraupnirBotModeToggle } from "../../src/DraupnirBotMode"; import { + MatrixSendClient, SafeMatrixEmitter, SafeMatrixEmitterWrapper, } from "matrix-protection-suite-for-matrix-bot-sdk"; @@ -152,6 +153,7 @@ export async function makeBotModeToggle( client.setAccountData(MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE, { references: [], }), + leaveAllRooms(client), ]); } await overrideRatelimitForUser( @@ -197,3 +199,10 @@ export async function teardownManagementRoom( await client.deleteRoomAlias(alias); await client.leaveRoom(roomId); } + +export async function leaveAllRooms(client: MatrixSendClient): Promise { + const joinedRooms = await client.getJoinedRooms(); + await Promise.allSettled( + joinedRooms.map((roomID) => client.leaveRoom(roomID)) + ); +} diff --git a/test/integration/protections/JoinRoomsOnInviteTest.ts b/test/integration/protections/JoinRoomsOnInviteTest.ts new file mode 100644 index 00000000..e193909a --- /dev/null +++ b/test/integration/protections/JoinRoomsOnInviteTest.ts @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2025 Gnuxie +// +// SPDX-License-Identifier: AFL-3.0 + +import { + MatrixRoomReference, + StringRoomID, +} from "@the-draupnir-project/matrix-basic-types"; +import { newTestUser } from "../clientHelper"; +import { DraupnirTestContext } from "../mjolnirSetupUtils"; +import expect from "expect"; +import { MatrixSendClient } from "matrix-protection-suite-for-matrix-bot-sdk"; +import { Draupnir } from "../../../src/Draupnir"; + +async function setupProtectedRooms( + draupnir: Draupnir, + moderator: MatrixSendClient, + { numberOfRooms }: { numberOfRooms: number } +): Promise { + await moderator.joinRoom(draupnir.managementRoomID); + return await Promise.all( + [...Array(numberOfRooms)].map(async (_) => { + const room = await moderator.createRoom({ + invite: [draupnir.clientUserID], + }); + await moderator.setUserPowerLevel(draupnir.clientUserID, room, 90); + return room as StringRoomID; + }) + ); +} + +describe("JoinRoomsOnInvite", function () { + it( + "Should automatically protect and unrpotect rooms when joining and leaving.\ + The principle is that we add a bunch of rooms, and then kick the bot.\ + You can go to the management room in a client and see what the output looks like for this flow.", + async function (this: DraupnirTestContext) { + const draupnir = this.draupnir; + if (draupnir === undefined) { + throw new TypeError(`setup didn't run properly`); + } + const moderator = await newTestUser(this.config.homeserverUrl, { + name: { contains: "moderator" }, + }); + // Mutate the config which is a little naughty, but protections + // currently access it dynamically. + draupnir.config.protectAllJoinedRooms = true; + const protectedRooms = await setupProtectedRooms(draupnir, moderator, { + numberOfRooms: 5, + }); + await new Promise((resolve) => setTimeout(resolve, 2000)); + expect( + protectedRooms.every((roomID: StringRoomID) => + draupnir.protectedRoomsSet.isProtectedRoom(roomID) + ) + ).toBe(true); + // now test that kicking them works + await Promise.all( + protectedRooms.map((roomID, i) => + moderator.kickUser( + draupnir.clientUserID, + roomID, + i === 0 ? "don't want this bot" : undefined + ) + ) + ); + await new Promise((resolve) => setTimeout(resolve, 1000)); + expect( + protectedRooms.every( + (roomID: StringRoomID) => + !draupnir.protectedRoomsSet.isProtectedRoom(roomID) + ) + ).toBe(true); + // allow for messages to send to the mangement room. + await new Promise((resolve) => setTimeout(resolve, 1000)); + } as unknown as Mocha.AsyncFunc + ); + it( + "That rooms will automatically be unprotected when protectAllJoinedRooms is false", + async function (this: DraupnirTestContext) { + const draupnir = this.draupnir; + if (draupnir === undefined) { + throw new TypeError(`setup didn't run properly`); + } + const moderator = await newTestUser(this.config.homeserverUrl, { + name: { contains: "moderator" }, + }); + // Mutate the config which is a little naughty, but protections + // currently access it dynamically. + draupnir.config.protectAllJoinedRooms = false; + const protectedRooms = await setupProtectedRooms(draupnir, moderator, { + numberOfRooms: 5, + }); + await new Promise((resolve) => setTimeout(resolve, 2000)); + // we shouldn't be protecting rooms automatically + expect( + protectedRooms.every( + (roomID: StringRoomID) => + !draupnir.protectedRoomsSet.isProtectedRoom(roomID) + ) + ).toBe(true); + // protect the rooms manually + await Promise.all( + protectedRooms.map((roomID) => + draupnir.protectedRoomsSet.protectedRoomsManager.addRoom( + MatrixRoomReference.fromRoomID(roomID) + ) + ) + ); + expect( + protectedRooms.every((roomID: StringRoomID) => + draupnir.protectedRoomsSet.isProtectedRoom(roomID) + ) + ).toBe(true); + // now test that banning them works + await Promise.all( + protectedRooms.map((roomID, i) => + // I would have liked this to be ban, but for some reason bot-sdk + // doesn't allow informing of rooms you are banned from!!! + moderator.kickUser( + draupnir.clientUserID, + roomID, + i === 0 ? "don't want this bot" : undefined + ) + ) + ); + await new Promise((resolve) => setTimeout(resolve, 1000)); + expect( + protectedRooms.every( + (roomID: StringRoomID) => + !draupnir.protectedRoomsSet.isProtectedRoom(roomID) + ) + ).toBe(true); + // allow for messages to send to the mangement room. + await new Promise((resolve) => setTimeout(resolve, 1000)); + } as unknown as Mocha.AsyncFunc + ); +}); diff --git a/yarn.lock b/yarn.lock index 1a26fd29..162d1386 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2593,18 +2593,18 @@ matrix-appservice@^2.0.0: request-promise "^4.2.6" sanitize-html "^2.11.0" -"matrix-protection-suite-for-matrix-bot-sdk@npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@2.6.0": - version "2.6.0" - resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite-for-matrix-bot-sdk/-/matrix-protection-suite-for-matrix-bot-sdk-2.6.0.tgz#dd1ffbf5ea1cbeba891248d989701d9ba268d074" - integrity sha512-JH3FMJddRGSA5SKbnlT9ZRPJ8+/6VWWeAa9hGv+FHBn2FeyfndEKWChoFE8s6s1c06OJvjLg5o/efuVhZzKyEw== +"matrix-protection-suite-for-matrix-bot-sdk@npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite-for-matrix-bot-sdk/-/matrix-protection-suite-for-matrix-bot-sdk-2.7.0.tgz#82e3b053033d635ecdceab034c5548640bbe2332" + integrity sha512-Om+2CSojGJFqGnC9S7W9OXfccmI288nH93DACESRBjrlqVUO6mkdNSk789s1UTW5LIqZ3bjiC72F3STDASTdfg== dependencies: "@gnuxie/typescript-result" "^1.0.0" await-lock "^2.2.2" -"matrix-protection-suite@npm:@gnuxie/matrix-protection-suite@2.6.0": - version "2.6.0" - resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite/-/matrix-protection-suite-2.6.0.tgz#ce3dfecb72f5215467b74ed55f388043bbb2365f" - integrity sha512-MQxsFZQ7Xyx8GdpEcr4Pxg0XHA7Apn1aXPnTVM6FiEKTsWOyS0uYxuxfM0Q4kfYf5mVCdvCHB/xNJGqn6IV06A== +"matrix-protection-suite@npm:@gnuxie/matrix-protection-suite@2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite/-/matrix-protection-suite-2.7.0.tgz#953f89e3b02fd9b9edb21f0901f1ed22a2f41f48" + integrity sha512-ZTbz5t1Gh0Yxx+9+7EUp51HfBaccTazSpLKNPQDmjgjoVo6PT3da0CfTvGJR11EqLlBMey/D0zV418ajxjixrw== dependencies: "@gnuxie/typescript-result" "^1.0.0" await-lock "^2.2.2"