diff --git a/config/default.yaml b/config/default.yaml index d46e24c..8229c1a 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -26,10 +26,10 @@ idServerDomain: "vector.im" # result in emails or other communications to the user. idServerBrand: "vector-im" -# This is the user ID of the moderator for all public-facing rooms the bot creates. -# Note that this user will be granted power level 100 (the highest) in every room +# These are the user IDs of the moderators for all public-facing rooms the bot creates. +# Note that these users will be granted power level 100 (the highest) in every room # and be invited. -moderatorUserId: "@moderator:example.org" +moderatorUserIds: ["@moderator:example.org"] # Settings for how the bot should represent livestreams. livestream: @@ -337,4 +337,4 @@ metrics: address: 127.0.0.1 # if set to `true` will prevent the conf-bot from sending live invites to email/matrix_ids -dry_run_enabled: false \ No newline at end of file +dry_run_enabled: false diff --git a/spec/util/e2e-test.ts b/spec/util/e2e-test.ts index b5b3745..55ee625 100644 --- a/spec/util/e2e-test.ts +++ b/spec/util/e2e-test.ts @@ -203,7 +203,7 @@ export class E2ETestEnv { } }, }, - moderatorUserId: `@modbot:${homeserver.domain}`, + moderatorUserId: [`@modbot:${homeserver.domain}`], webserver: { address: '0.0.0.0', port: 0, diff --git a/src/Conference.ts b/src/Conference.ts index 5d5db10..025730f 100644 --- a/src/Conference.ts +++ b/src/Conference.ts @@ -394,7 +394,7 @@ export class Conference { this.client, mergeWithCreationTemplate(AUDITORIUM_BACKSTAGE_CREATION_TEMPLATE, { room_alias_name: (new RoomAlias(alias)).localpart, - invite: [this.config.moderatorUserId], + invite: this.config.moderatorUserIds, }), ); await rootSpace.addChildRoom(roomId); @@ -433,7 +433,7 @@ export class Conference { subspace = await this.client.createSpace({ isPublic: true, name: name, - invites: [this.config.moderatorUserId], + invites: this.config.moderatorUserIds, }); this.subspaces[subspaceId] = subspace; @@ -448,9 +448,11 @@ export class Conference { roomId: subspace.roomId, } as IStoredSubspace); - // Grants PL100 to the moderator in the subspace. + // Grants PL100 to the moderators in the subspace. // We can't do this directly with `createSpace` unfortunately, as we could for plain rooms. - await this.client.setUserPowerLevel(this.config.moderatorUserId, subspace.roomId, 100); + for (let moderator of this.config.moderatorUserIds) { + await this.client.setUserPowerLevel(moderator, subspace.roomId, 100); + } } else { subspace = this.subspaces[subspaceId]; } @@ -481,7 +483,7 @@ export class Conference { ); } else { // Create a new interest room. - roomId = await safeCreateRoom(this.client, mergeWithCreationTemplate(SPECIAL_INTEREST_CREATION_TEMPLATE(this.config.moderatorUserId), { + roomId = await safeCreateRoom(this.client, mergeWithCreationTemplate(SPECIAL_INTEREST_CREATION_TEMPLATE(this.config.moderatorUserIds), { creation_content: { [RSC_CONFERENCE_ID]: this.id, [RSC_SPECIAL_INTEREST_ID]: interestRoom.id, @@ -571,7 +573,7 @@ export class Conference { await parentSpace.addChildSpace(audSpace, { order: `auditorium-${auditorium.id}` }); - const roomId = await safeCreateRoom(this.client, mergeWithCreationTemplate(AUDITORIUM_CREATION_TEMPLATE(this.config.moderatorUserId), { + const roomId = await safeCreateRoom(this.client, mergeWithCreationTemplate(AUDITORIUM_CREATION_TEMPLATE(this.config.moderatorUserIds), { creation_content: { [RSC_CONFERENCE_ID]: this.id, [RSC_AUDITORIUM_ID]: auditorium.id, @@ -629,7 +631,7 @@ export class Conference { } if (!this.talks[talk.id]) { - roomId = await safeCreateRoom(this.client, mergeWithCreationTemplate(TALK_CREATION_TEMPLATE(this.config.moderatorUserId), { + roomId = await safeCreateRoom(this.client, mergeWithCreationTemplate(TALK_CREATION_TEMPLATE(this.config.moderatorUserIds), { name: talk.title, creation_content: { [RSC_CONFERENCE_ID]: this.id, @@ -848,7 +850,9 @@ export class Conference { // we'll be unable to do promotions/demotions in the future. const pls = await this.client.getRoomStateEvent(roomId, "m.room.power_levels", ""); pls['users'][await this.client.getUserId()] = 100; - pls['users'][this.config.moderatorUserId] = 100; + for (let moderator of this.config.moderatorUserIds) { + pls['users'][moderator] = 100; + } for (const userId of mxids) { if (pls['users'][userId]) continue; pls['users'][userId] = 50; @@ -916,7 +920,9 @@ export class Conference { this.membersInRooms[roomId] = joinedOrLeftMembers; const total = new Set(Object.values(this.membersInRooms).flat()); total.delete(myUserId); - total.delete(this.config.moderatorUserId); + for (let moderator of this.config.moderatorUserIds) { + total.delete(moderator); + } attendeeTotalGauge.set(total.size); } catch (ex) { LogService.warn("Conference", `Failed to recalculate room membership for ${roomId}`, ex); diff --git a/src/config.ts b/src/config.ts index f98e017..c193a92 100644 --- a/src/config.ts +++ b/src/config.ts @@ -30,7 +30,7 @@ export interface IConfig { managementRoom: string; idServerDomain?: string; idServerBrand?: string; - moderatorUserId: string; + moderatorUserIds: string[]; livestream: { auditoriumUrl: string; talkUrl: string; diff --git a/src/models/room_kinds.ts b/src/models/room_kinds.ts index 4914dd0..368cd8c 100644 --- a/src/models/room_kinds.ts +++ b/src/models/room_kinds.ts @@ -19,7 +19,7 @@ import { RoomCreateOptions } from "matrix-bot-sdk"; export const KickPowerLevel = 50; -export const PUBLIC_ROOM_POWER_LEVELS_TEMPLATE = (moderatorUserId: string) => ({ +export const PUBLIC_ROOM_POWER_LEVELS_TEMPLATE = (moderatorUserIds: string[]) => ({ ban: 50, events_default: 0, invite: 50, @@ -40,10 +40,7 @@ export const PUBLIC_ROOM_POWER_LEVELS_TEMPLATE = (moderatorUserId: string) => ({ "m.space.parent": 100, "m.space.child": 100, }, - users: { - [moderatorUserId]: 100, - // should be populated with the creator - }, + users: Object.fromEntries(moderatorUserIds.map(moderator => [moderator, 100])), }); export const PRIVATE_ROOM_POWER_LEVELS_TEMPLATE = { @@ -91,7 +88,7 @@ export const CONFERENCE_ROOM_CREATION_TEMPLATE: RoomCreateOptions = { }, }; -export const AUDITORIUM_CREATION_TEMPLATE = (moderatorUserId: string) => ({ +export const AUDITORIUM_CREATION_TEMPLATE = (moderatorUserIds: string[]) => ({ preset: 'public_chat', visibility: 'public', initial_state: [ @@ -101,8 +98,8 @@ export const AUDITORIUM_CREATION_TEMPLATE = (moderatorUserId: string) => ({ creation_content: { [RSC_ROOM_KIND_FLAG]: RoomKind.Auditorium, }, - power_level_content_override: PUBLIC_ROOM_POWER_LEVELS_TEMPLATE(moderatorUserId), - invite: [moderatorUserId], + power_level_content_override: PUBLIC_ROOM_POWER_LEVELS_TEMPLATE(moderatorUserIds), + invite: moderatorUserIds, } satisfies RoomCreateOptions); export const AUDITORIUM_BACKSTAGE_CREATION_TEMPLATE: RoomCreateOptions = { @@ -118,7 +115,7 @@ export const AUDITORIUM_BACKSTAGE_CREATION_TEMPLATE: RoomCreateOptions = { power_level_content_override: PRIVATE_ROOM_POWER_LEVELS_TEMPLATE, }; -export const TALK_CREATION_TEMPLATE = (moderatorUserId: string) => ({ // before being opened up to the public +export const TALK_CREATION_TEMPLATE = (moderatorUserIds: string[]) => ({ // before being opened up to the public preset: 'private_chat', visibility: 'private', initial_state: [ @@ -128,11 +125,11 @@ export const TALK_CREATION_TEMPLATE = (moderatorUserId: string) => ({ // before creation_content: { [RSC_ROOM_KIND_FLAG]: RoomKind.Talk, }, - power_level_content_override: PUBLIC_ROOM_POWER_LEVELS_TEMPLATE(moderatorUserId), - invite: [moderatorUserId], + power_level_content_override: PUBLIC_ROOM_POWER_LEVELS_TEMPLATE(moderatorUserIds), + invite: moderatorUserIds, } satisfies RoomCreateOptions); -export const SPECIAL_INTEREST_CREATION_TEMPLATE = (moderatorUserId: string) => ({ +export const SPECIAL_INTEREST_CREATION_TEMPLATE = (moderatorUserIds: string[]) => ({ preset: 'public_chat', visibility: 'public', initial_state: [ @@ -149,7 +146,7 @@ export const SPECIAL_INTEREST_CREATION_TEMPLATE = (moderatorUserId: string) => ( "m.room.power_levels": 50, }, }, - invite: [moderatorUserId], + invite: moderatorUserIds, } satisfies RoomCreateOptions); export function mergeWithCreationTemplate(template: RoomCreateOptions, addlProps: any): any {