UNPKG

@vector-im/matrix-bot-sdk

Version:

TypeScript/JavaScript SDK for Matrix bots and appservices

176 lines (151 loc) 4.88 kB
import { type MatrixClient, type PowerLevelsEventContent, UserID, } from ".."; const CREATOR_ROOM_VERSIONS = ["12", "org.matrix.hydra.11"]; interface CreateEventContentHydra { additional_creators?: string[]; room_version: "12" | "org.matrix.hydra.11"; } interface CreateEventContentLegacy { /** * The room version. "v1" rooms are possibly undefined. */ room_version?: string; } // Effective defaults for power levels // https://spec.matrix.org/v1.15/client-server-api/#mroompower_levels export const ROOM_MODERATOR_PL = 50; export const ROOM_ADMIN_PL = 100; // MAX_INT + 1, so it always trumps any PL in canonical JSON. export const ROOM_CREATOR_PL = 2 ** 53; const DEFAULT_PL: PowerLevelsEventContent = { ban: ROOM_MODERATOR_PL, invite: 0, state_default: ROOM_MODERATOR_PL, events_default: 0, }; /** * Handles calculating the true power levels for a given room, taking into * account any specific room version features. */ export class PLManager { public static createFromRoomState( roomState: Awaited<ReturnType<MatrixClient["getRoomState"]>>, ): PLManager { const createEvent = roomState.find( (s) => s.type === "m.room.create" && s.state_key === "", ); if (!createEvent) { throw Error("Could not find create event for room, cannot handle"); } // This is not required to exist. const plEvent = roomState.find( (s) => s.type === "m.room.power_levels" && s.state_key === "", ); return new PLManager(createEvent, plEvent?.content); } public readonly creators: Set<string>; public static areCreatorsPriviledged(roomVersion: string): boolean { return CREATOR_ROOM_VERSIONS.includes(roomVersion); } public get areCreatorsPriviledged(): boolean { return ( !!this.createEvent.content.room_version && CREATOR_ROOM_VERSIONS.includes( this.createEvent.content.room_version, ) ); } public get getFirstCreator(): string { // Creator field is never used. return this.createEvent.sender; } public get currentPL(): PowerLevelsEventContent { return { ...DEFAULT_PL, ...this.powerLevels, }; } public get defaultPL(): number { return this.powerLevels?.users_default ?? 0; } constructor( private readonly createEvent: { sender: string; content: CreateEventContentHydra | CreateEventContentLegacy; }, private powerLevels: PowerLevelsEventContent | undefined, ) { this.creators = new Set([new UserID(this.getFirstCreator).toString()]); if ( this.areCreatorsPriviledged && "additional_creators" in createEvent.content ) { for (const userId of createEvent.content.additional_creators ?? []) { // The server MUST validate these to be valid userIDs. this.creators.add(new UserID(userId).toString()); } } } public canAdjustUserPL(userId: string): boolean { if (this.areCreatorsPriviledged) { return !this.creators.has(userId); } return true; } public getUserPowerLevel(userId: string): number { if (this.areCreatorsPriviledged && this.creators.has(userId)) { return ROOM_CREATOR_PL; } return ( this.powerLevels?.users?.[userId] ?? this.powerLevels?.users_default ?? 0 ); } } /** * Information on the bounds of a power level change a user can apply. */ export interface PowerLevelBounds { /** * Whether or not the user can even modify the power level of the user. This * will be false if the user can't send power level events, or the user is * unobtainably high in power. */ canModify: boolean; /** * The maximum possible power level the user can set on the target user. */ maximumPossibleLevel: number; } /** * Actions that can be guarded by power levels. */ export enum PowerLevelAction { /** * Power level required to ban other users. */ Ban = "ban", /** * Power level required to kick other users. */ Kick = "kick", /** * Power level required to redact events sent by other users. Users can redact * their own messages regardless of this power level requirement, unless forbidden * by the `events` section of the power levels content. */ RedactEvents = "redact", /** * Power level required to invite other users. */ Invite = "invite", /** * Power level required to notify the whole room with "@room". */ NotifyRoom = "notifications.room", }