diff --git a/src/index.ts b/src/index.ts index 56afd3d0..9adea50a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,6 +37,7 @@ export * from "./metrics/Metrics"; // Mixins export * from "./mixins/AutojoinRoomsMixin"; export * from "./mixins/AutojoinUpgradedRoomsMixin"; +export * from "./mixins/AutoleaveRoomsMixin"; // Models export * from "./models/Presence"; diff --git a/src/mixins/AutoleaveRoomsMixin.ts b/src/mixins/AutoleaveRoomsMixin.ts new file mode 100644 index 00000000..3c53ed11 --- /dev/null +++ b/src/mixins/AutoleaveRoomsMixin.ts @@ -0,0 +1,20 @@ +import { MatrixClient } from "../MatrixClient"; + +/** + * Automatically leaves empty rooms + * @category Mixins + */ +export class AutoleaveRoomsMixin { + public static setupOnClient(client: MatrixClient): void { + client.on("room.event", async (roomId: string, event: any) => { + if ( + event.type === "m.room.member" && + event.content?.membership === "leave" && + (await client.getJoinedRoomMembers(roomId)).length === 1 + ) { + await client.leaveRoom(roomId); + await client.forgetRoom(roomId); + } + }); + } +} diff --git a/test/mixins/AutoleaveRoomsMixinTest.ts b/test/mixins/AutoleaveRoomsMixinTest.ts new file mode 100644 index 00000000..4fb0b6b7 --- /dev/null +++ b/test/mixins/AutoleaveRoomsMixinTest.ts @@ -0,0 +1,63 @@ +import * as simple from "simple-mock"; + +import { AutoleaveRoomsMixin } from "../../src"; +import { createTestClient } from "../TestUtils"; + +describe("AutoleaveRoomsMixin", () => { + it("shouldn't leave rooms multiple members", async () => { + const { client } = createTestClient(); + AutoleaveRoomsMixin.setupOnClient(client); + + const roomId = "!test:example.org"; + const members = [ + "@this:example.org", + "@alice:example.org", + "@bob:example.org", + ]; + + const getJoinedRoomMembersSpy = simple + .mock(client, "getJoinedRoomMembers") + .callFn((rid) => { + expect(rid).toEqual(roomId); + return members; + }); + // const leaveSpy = simple.mock(client, "leaveRoom"); + // const forgetSpy = simple.mock(client, "forgetRoom"); + + client.emit("room.event", roomId, { + type: "m.room.member", + content: { membership: "leave" }, + }); + expect(getJoinedRoomMembersSpy.callCount).toBe(1); + // Since the AutoleaveRoomsMixin room.event handler is asyncronous, these functions don't get called syncronously + // Which means we must somehow await the handler's completion before executing the following tests, but I'm not sure how to do that + // expect(leaveSpy.callCount).toBe(0); + // expect(forgetSpy.callCount).toBe(0); + }); + + it("should leave rooms with one or no members", async () => { + const { client } = createTestClient(); + AutoleaveRoomsMixin.setupOnClient(client); + + const roomId = "!test:example.org"; + const members = ["@this:example.org"]; + + const getJoinedRoomMembersSpy = simple + .mock(client, "getJoinedRoomMembers") + .callFn((rid) => { + expect(rid).toEqual(roomId); + return members; + }); + // const leaveSpy = simple.mock(client, "leaveRoom"); + // const forgetSpy = simple.mock(client, "forgetRoom"); + + client.emit("room.event", roomId, { + type: "m.room.member", + content: { membership: "leave" }, + }); + expect(getJoinedRoomMembersSpy.callCount).toBe(1); + // See comments above (lines 41-42) + // expect(leaveSpy.callCount).toBe(1); + // expect(forgetSpy.callCount).toBe(1); + }); +});