matrix-nodejs-homeserver 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/LICENSE +75 -0
  2. package/README.md +457 -2
  3. package/dist/adapters/auth.d.ts +50 -0
  4. package/dist/adapters/auth.js +18 -0
  5. package/dist/adapters/cluster.d.ts +55 -0
  6. package/dist/adapters/cluster.js +249 -0
  7. package/dist/adapters/media.d.ts +50 -0
  8. package/dist/adapters/media.js +63 -0
  9. package/dist/adapters/s3.d.ts +33 -0
  10. package/dist/adapters/s3.js +95 -0
  11. package/dist/api/account.js +560 -0
  12. package/dist/api/admin.js +251 -0
  13. package/dist/api/appservice.js +33 -0
  14. package/dist/api/backup.js +269 -0
  15. package/dist/api/federation.js +792 -0
  16. package/dist/api/filters.js +87 -0
  17. package/dist/api/index.js +47 -0
  18. package/dist/api/legacy.js +170 -0
  19. package/dist/api/media.js +255 -0
  20. package/dist/api/misc.js +86 -0
  21. package/dist/api/preview.js +200 -0
  22. package/dist/api/profile.js +165 -0
  23. package/dist/api/push.js +187 -0
  24. package/dist/api/relationships.js +264 -0
  25. package/dist/api/rooms.js +1364 -0
  26. package/dist/api/rtc.d.ts +24 -0
  27. package/dist/api/rtc.js +52 -0
  28. package/dist/api/search.js +155 -0
  29. package/dist/api/slidingsync.js +464 -0
  30. package/dist/api/spaces.js +172 -0
  31. package/dist/api/sync.js +600 -0
  32. package/dist/api/threadsubs.js +129 -0
  33. package/dist/api/threepid.js +122 -0
  34. package/dist/api/uia.js +126 -0
  35. package/dist/api/user.js +286 -0
  36. package/dist/core/admin.d.ts +85 -0
  37. package/dist/core/admin.js +307 -0
  38. package/dist/core/adminroom.d.ts +49 -0
  39. package/dist/core/adminroom.js +482 -0
  40. package/dist/core/appservices.d.ts +73 -0
  41. package/dist/core/appservices.js +368 -0
  42. package/dist/core/audit.d.ts +51 -0
  43. package/dist/core/audit.js +86 -0
  44. package/dist/core/auth.d.ts +48 -0
  45. package/dist/core/auth.js +237 -0
  46. package/dist/core/authrules.d.ts +4 -0
  47. package/dist/core/authrules.js +347 -0
  48. package/dist/core/cluster.d.ts +66 -0
  49. package/dist/core/cluster.js +191 -0
  50. package/dist/core/crypto.d.ts +10 -0
  51. package/dist/core/crypto.js +100 -0
  52. package/dist/core/delayed.d.ts +39 -0
  53. package/dist/core/delayed.js +159 -0
  54. package/dist/core/errors.d.ts +9 -0
  55. package/dist/core/errors.js +45 -0
  56. package/dist/core/events.d.ts +32 -0
  57. package/dist/core/events.js +185 -0
  58. package/dist/core/glob.js +66 -0
  59. package/dist/core/identity.d.ts +24 -0
  60. package/dist/core/identity.js +96 -0
  61. package/dist/core/ids.js +36 -0
  62. package/dist/core/invitefilter.js +35 -0
  63. package/dist/core/json.js +16 -0
  64. package/dist/core/keys.d.ts +36 -0
  65. package/dist/core/keys.js +449 -0
  66. package/dist/core/notifier.d.ts +17 -0
  67. package/dist/core/notifier.js +54 -0
  68. package/dist/core/oauth.d.ts +38 -0
  69. package/dist/core/oauth.js +108 -0
  70. package/dist/core/partialstate.d.ts +32 -0
  71. package/dist/core/partialstate.js +197 -0
  72. package/dist/core/phone.js +268 -0
  73. package/dist/core/policy.d.ts +29 -0
  74. package/dist/core/policy.js +96 -0
  75. package/dist/core/profile.js +39 -0
  76. package/dist/core/push.d.ts +47 -0
  77. package/dist/core/push.js +306 -0
  78. package/dist/core/pushrules.d.ts +1 -0
  79. package/dist/core/pushrules.js +228 -0
  80. package/dist/core/ratelimit.d.ts +23 -0
  81. package/dist/core/ratelimit.js +77 -0
  82. package/dist/core/rooms.d.ts +215 -0
  83. package/dist/core/rooms.js +1729 -0
  84. package/dist/core/settings.d.ts +11 -0
  85. package/dist/core/settings.js +29 -0
  86. package/dist/core/sso.d.ts +46 -0
  87. package/dist/core/sso.js +165 -0
  88. package/dist/core/stateres.d.ts +4 -0
  89. package/dist/core/stateres.js +208 -0
  90. package/dist/core/threepid.d.ts +80 -0
  91. package/dist/core/threepid.js +275 -0
  92. package/dist/core/typing.d.ts +16 -0
  93. package/dist/core/typing.js +60 -0
  94. package/dist/federation/client.d.ts +51 -0
  95. package/dist/federation/client.js +278 -0
  96. package/dist/federation/index.d.ts +153 -0
  97. package/dist/federation/index.js +1058 -0
  98. package/dist/federation/keyring.d.ts +26 -0
  99. package/dist/federation/keyring.js +122 -0
  100. package/dist/federation/sender.d.ts +26 -0
  101. package/dist/federation/sender.js +164 -0
  102. package/dist/homeserver.d.ts +223 -0
  103. package/dist/homeserver.js +301 -0
  104. package/dist/http/adapters.d.ts +40 -0
  105. package/dist/http/adapters.js +130 -0
  106. package/dist/http/handler.d.ts +8 -0
  107. package/dist/http/handler.js +202 -0
  108. package/dist/http/input.js +24 -0
  109. package/dist/http/router.d.ts +60 -0
  110. package/dist/http/router.js +76 -0
  111. package/dist/index.d.ts +25 -0
  112. package/dist/index.js +14 -0
  113. package/dist/storage/memory.d.ts +5 -0
  114. package/dist/storage/memory.js +122 -0
  115. package/dist/storage/schema.d.ts +4 -0
  116. package/dist/storage/schema.js +664 -0
  117. package/dist/storage/sql.d.ts +72 -0
  118. package/dist/storage/sql.js +274 -0
  119. package/dist/storage/types.d.ts +55 -0
  120. package/package.json +67 -4
@@ -0,0 +1,85 @@
1
+ import { Row } from "../storage/types.js";
2
+ import { Homeserver } from "../homeserver.js";
3
+ import "../index.js";
4
+ //#region src/core/admin.d.ts
5
+ export interface ServerNoticesOptions {
6
+ /** Localpart of the user that sends server notices. Default: "server". */
7
+ localpart?: string;
8
+ displayName?: string;
9
+ roomName?: string;
10
+ }
11
+ /** Programmatic administration, for embedding applications and the admin HTTP API. */
12
+ export declare class AdminService {
13
+ private hs;
14
+ constructor(hs: Homeserver);
15
+ createUser(localpart: string, opts?: {
16
+ password?: string;
17
+ admin?: boolean;
18
+ displayname?: string;
19
+ }): Promise<string>;
20
+ getUser(userId: string): Promise<Record<string, any> | null>;
21
+ listUsers(opts?: {
22
+ from?: number;
23
+ limit?: number;
24
+ search?: string;
25
+ includeDeactivated?: boolean;
26
+ }): Promise<{
27
+ users: {
28
+ [x: string]: any;
29
+ }[];
30
+ total: number;
31
+ next: number | null;
32
+ }>;
33
+ setAdmin(userId: string, admin: boolean): Promise<void>;
34
+ /** Lock or unlock an account (reversible; sessions are kept but unusable while locked). */
35
+ setLocked(userId: string, locked: boolean, actor?: string): Promise<void>;
36
+ /** Suspend or unsuspend an account (reversible; the user can still read, leave and log out). */
37
+ setSuspended(userId: string, suspended: boolean, actor?: string): Promise<void>;
38
+ resetPassword(userId: string, password: string, logoutDevices?: boolean): Promise<void>;
39
+ /** Deactivate an account: leave all rooms, log out everywhere and (optionally) erase the profile. */
40
+ deactivateUser(userId: string, opts?: {
41
+ erase?: boolean;
42
+ }): Promise<void>;
43
+ createRegistrationToken(opts?: {
44
+ token?: string;
45
+ usesAllowed?: number | null;
46
+ expiryTime?: number | null;
47
+ length?: number;
48
+ }): Promise<{
49
+ token: string;
50
+ uses_allowed: number | null;
51
+ pending: number;
52
+ completed: number;
53
+ expiry_time: number | null;
54
+ }>;
55
+ listRegistrationTokens(): Promise<Row[]>;
56
+ deleteRegistrationToken(token: string): Promise<void>;
57
+ listRooms(opts?: {
58
+ from?: number;
59
+ limit?: number;
60
+ }): Promise<{
61
+ rooms: Record<string, any>[];
62
+ total: number;
63
+ }>;
64
+ /** Remove everyone from a room, delist it and drop its aliases. With `purge`, also delete its history. */
65
+ deleteRoom(roomId: string, opts?: {
66
+ actor?: string;
67
+ reason?: string;
68
+ purge?: boolean;
69
+ }): Promise<{
70
+ kicked: string[];
71
+ }>;
72
+ deleteMedia(mediaId: string): Promise<void>;
73
+ /** Delete local media uploaded before `beforeTs`. Returns the deleted media IDs. */
74
+ purgeMediaBefore(beforeTs: number): Promise<string[]>;
75
+ get noticeUserId(): string;
76
+ /** Whether `roomId` is a server notices room (members may not reject or leave its invites). */
77
+ isNoticeRoom(roomId: string): Promise<boolean>;
78
+ /** Send a message to a user in their server notices room, creating or re-inviting as needed. */
79
+ sendServerNotice(userId: string, content: Record<string, any>, opts?: {
80
+ type?: string;
81
+ txnId?: string;
82
+ }): Promise<string>;
83
+ private noticeRoomFor;
84
+ }
85
+ //#endregion
@@ -0,0 +1,307 @@
1
+ import { randomString } from "./crypto.js";
2
+ import { errors } from "./errors.js";
3
+ import { LOCALPART_RE, isValidUserId } from "./ids.js";
4
+ import { createUser } from "../api/account.js";
5
+ //#region src/core/admin.ts
6
+ /** Programmatic administration, for embedding applications and the admin HTTP API. */
7
+ var AdminService = class {
8
+ hs;
9
+ constructor(hs) {
10
+ this.hs = hs;
11
+ }
12
+ async createUser(localpart, opts = {}) {
13
+ const lp = localpart.toLowerCase();
14
+ if (!LOCALPART_RE.test(lp)) throw errors.invalidUsername();
15
+ if (await this.getUser(`@${lp}:${this.hs.serverName}`)) throw errors.userInUse();
16
+ return createUser(this.hs, lp, opts);
17
+ }
18
+ async getUser(userId) {
19
+ const row = await this.hs.db.findOne("users", [{
20
+ field: "user_id",
21
+ value: userId
22
+ }]);
23
+ if (!row) return null;
24
+ const { password_hash, ...rest } = row;
25
+ return rest;
26
+ }
27
+ async listUsers(opts = {}) {
28
+ let rows = await this.hs.db.findMany("users", { sortBy: {
29
+ field: "created_ts",
30
+ direction: "asc"
31
+ } });
32
+ if (!opts.includeDeactivated) rows = rows.filter((r) => !r.deactivated);
33
+ if (opts.search) {
34
+ const term = opts.search.toLowerCase();
35
+ rows = rows.filter((r) => r.user_id.toLowerCase().includes(term) || (r.displayname ?? "").toLowerCase().includes(term));
36
+ }
37
+ const from = opts.from ?? 0;
38
+ const limit = opts.limit ?? 100;
39
+ return {
40
+ users: rows.slice(from, from + limit).map(({ password_hash, ...u }) => u),
41
+ total: rows.length,
42
+ next: from + limit < rows.length ? from + limit : null
43
+ };
44
+ }
45
+ async setAdmin(userId, admin) {
46
+ if (!await this.hs.db.update("users", [{
47
+ field: "user_id",
48
+ value: userId
49
+ }], { is_admin: admin })) throw errors.notFound("Unknown user");
50
+ await this.hs.auth.invalidateUser(userId);
51
+ if (admin) await this.hs.adminRoom?.addAdmin(userId);
52
+ else await this.hs.adminRoom?.removeAdmin(userId);
53
+ }
54
+ /** Lock or unlock an account (reversible; sessions are kept but unusable while locked). */
55
+ async setLocked(userId, locked, actor = "server") {
56
+ if (!await this.hs.db.update("users", [{
57
+ field: "user_id",
58
+ value: userId
59
+ }], { locked })) throw errors.notFound("Unknown user");
60
+ await this.hs.auth.invalidateUser(userId);
61
+ await this.hs.audit.record({
62
+ action: locked ? "user_lock" : "user_unlock",
63
+ actor,
64
+ target: userId
65
+ });
66
+ }
67
+ /** Suspend or unsuspend an account (reversible; the user can still read, leave and log out). */
68
+ async setSuspended(userId, suspended, actor = "server") {
69
+ if (!await this.hs.db.update("users", [{
70
+ field: "user_id",
71
+ value: userId
72
+ }], { suspended })) throw errors.notFound("Unknown user");
73
+ await this.hs.auth.invalidateUser(userId);
74
+ await this.hs.audit.record({
75
+ action: suspended ? "user_suspend" : "user_unsuspend",
76
+ actor,
77
+ target: userId
78
+ });
79
+ }
80
+ async resetPassword(userId, password, logoutDevices = true) {
81
+ const hash = await this.hs.userAuth.hashPassword(password);
82
+ if (!await this.hs.db.update("users", [{
83
+ field: "user_id",
84
+ value: userId
85
+ }], { password_hash: hash })) throw errors.notFound("Unknown user");
86
+ if (logoutDevices) await this.hs.auth.deleteAllDevices(userId);
87
+ }
88
+ /** Deactivate an account: leave all rooms, log out everywhere and (optionally) erase the profile. */
89
+ async deactivateUser(userId, opts = {}) {
90
+ if (!await this.hs.db.findOne("users", [{
91
+ field: "user_id",
92
+ value: userId
93
+ }])) throw errors.notFound("Unknown user");
94
+ for (const membership of [
95
+ "join",
96
+ "invite",
97
+ "knock"
98
+ ]) for (const roomId of await this.hs.rooms.roomsForUser(userId, membership)) try {
99
+ await this.hs.rooms.setMembership(roomId, userId, userId, "leave");
100
+ } catch {}
101
+ await this.hs.auth.deleteAllDevices(userId);
102
+ await this.hs.threepids.removeAll(userId);
103
+ await this.hs.db.delete("pushers", [{
104
+ field: "user_id",
105
+ value: userId
106
+ }]);
107
+ const update = {
108
+ deactivated: true,
109
+ password_hash: null
110
+ };
111
+ if (opts.erase !== false) Object.assign(update, {
112
+ displayname: null,
113
+ avatar_url: null
114
+ });
115
+ await this.hs.db.update("users", [{
116
+ field: "user_id",
117
+ value: userId
118
+ }], update);
119
+ await this.hs.auth.invalidateUser(userId);
120
+ }
121
+ async createRegistrationToken(opts = {}) {
122
+ const token = opts.token ?? randomString(opts.length ?? 16);
123
+ if (!/^[A-Za-z0-9._~-]{1,64}$/.test(token)) throw errors.invalidParam("Invalid token");
124
+ if (await this.hs.db.findOne("registration_tokens", [{
125
+ field: "token",
126
+ value: token
127
+ }])) throw errors.invalidParam("Token already exists");
128
+ const row = {
129
+ token,
130
+ uses_allowed: opts.usesAllowed ?? null,
131
+ pending: 0,
132
+ completed: 0,
133
+ expiry_time: opts.expiryTime ?? null
134
+ };
135
+ await this.hs.db.create("registration_tokens", row);
136
+ return row;
137
+ }
138
+ listRegistrationTokens() {
139
+ return this.hs.db.findMany("registration_tokens");
140
+ }
141
+ async deleteRegistrationToken(token) {
142
+ if (!await this.hs.db.delete("registration_tokens", [{
143
+ field: "token",
144
+ value: token
145
+ }])) throw errors.notFound("Unknown token");
146
+ }
147
+ async listRooms(opts = {}) {
148
+ const rows = await this.hs.db.findMany("rooms", { sortBy: {
149
+ field: "created_ts",
150
+ direction: "asc"
151
+ } });
152
+ const from = opts.from ?? 0;
153
+ const page = rows.slice(from, from + (opts.limit ?? 100));
154
+ return {
155
+ rooms: await Promise.all(page.map(async (r) => ({
156
+ ...r,
157
+ joined_members: (await this.hs.rooms.joinedMembers(r.room_id)).length
158
+ }))),
159
+ total: rows.length
160
+ };
161
+ }
162
+ /** Remove everyone from a room, delist it and drop its aliases. With `purge`, also delete its history. */
163
+ async deleteRoom(roomId, opts = {}) {
164
+ if (!await this.hs.rooms.exists(roomId)) throw errors.notFound("Unknown room");
165
+ const kicked = [];
166
+ for (const membership of [
167
+ "join",
168
+ "invite",
169
+ "knock"
170
+ ]) for (const row of await this.hs.db.findMany("memberships", { where: [{
171
+ field: "room_id",
172
+ value: roomId
173
+ }, {
174
+ field: "membership",
175
+ value: membership
176
+ }] })) {
177
+ if (!this.hs.isLocalUser(row.user_id)) continue;
178
+ try {
179
+ await this.hs.rooms.setMembership(roomId, row.user_id, row.user_id, "leave", opts.reason);
180
+ kicked.push(row.user_id);
181
+ } catch (err) {
182
+ this.hs.logger.warn("Could not remove", row.user_id, "from", roomId, err);
183
+ }
184
+ }
185
+ await this.hs.db.update("rooms", [{
186
+ field: "room_id",
187
+ value: roomId
188
+ }], { is_public: false });
189
+ await this.hs.db.delete("room_aliases", [{
190
+ field: "room_id",
191
+ value: roomId
192
+ }]);
193
+ if (opts.purge) {
194
+ const where = [{
195
+ field: "room_id",
196
+ value: roomId
197
+ }];
198
+ await this.hs.transaction(async (db) => {
199
+ for (const table of [
200
+ "events",
201
+ "state_snapshots",
202
+ "current_state",
203
+ "memberships",
204
+ "receipts",
205
+ "relations",
206
+ "notifications",
207
+ "rooms"
208
+ ]) await db.delete(table, where);
209
+ });
210
+ this.hs.rooms.forgetRoom(roomId);
211
+ }
212
+ return { kicked };
213
+ }
214
+ async deleteMedia(mediaId) {
215
+ await this.hs.media.delete?.(mediaId);
216
+ await this.hs.db.delete("media", [{
217
+ field: "media_id",
218
+ value: mediaId
219
+ }]);
220
+ }
221
+ /** Delete local media uploaded before `beforeTs`. Returns the deleted media IDs. */
222
+ async purgeMediaBefore(beforeTs) {
223
+ const rows = await this.hs.db.findMany("media", { where: [{
224
+ field: "created_ts",
225
+ operator: "lt",
226
+ value: beforeTs
227
+ }] });
228
+ for (const r of rows) await this.deleteMedia(r.media_id);
229
+ return rows.map((r) => r.media_id);
230
+ }
231
+ get noticeUserId() {
232
+ return `@${this.hs.config.serverNotices.localpart}:${this.hs.serverName}`;
233
+ }
234
+ /** Whether `roomId` is a server notices room (members may not reject or leave its invites). */
235
+ async isNoticeRoom(roomId) {
236
+ return (await this.hs.db.findOne("rooms", [{
237
+ field: "room_id",
238
+ value: roomId
239
+ }]))?.creator === this.noticeUserId;
240
+ }
241
+ /** Send a message to a user in their server notices room, creating or re-inviting as needed. */
242
+ async sendServerNotice(userId, content, opts = {}) {
243
+ if (!isValidUserId(userId) || !this.hs.isLocalUser(userId)) throw errors.invalidParam("Server notices can only be sent to local users");
244
+ if (!await this.hs.db.findOne("users", [{
245
+ field: "user_id",
246
+ value: userId
247
+ }])) throw errors.notFound("Unknown user");
248
+ const txnKey = opts.txnId ? `notice-txn:${userId}|${opts.txnId}` : null;
249
+ if (txnKey) {
250
+ const existing = await this.hs.cluster.get(txnKey);
251
+ if (existing) return existing;
252
+ }
253
+ const noticeUser = this.noticeUserId;
254
+ if (!await this.hs.db.findOne("users", [{
255
+ field: "user_id",
256
+ value: noticeUser
257
+ }])) await createUser(this.hs, noticeUser.slice(1).split(":")[0], { displayname: this.hs.config.serverNotices.displayName });
258
+ const roomId = await this.noticeRoomFor(userId);
259
+ const membership = await this.hs.rooms.membership(roomId, userId);
260
+ if (membership !== "join" && membership !== "invite") await this.hs.rooms.invite(roomId, noticeUser, userId);
261
+ const eventId = await this.hs.rooms.sendEvent(roomId, noticeUser, {
262
+ type: opts.type ?? "m.room.message",
263
+ content
264
+ });
265
+ if (txnKey) await this.hs.cluster.set(txnKey, eventId, 864e5);
266
+ return eventId;
267
+ }
268
+ async noticeRoomFor(userId) {
269
+ const noticeUser = this.noticeUserId;
270
+ const rooms = await this.hs.db.findMany("rooms", { where: [{
271
+ field: "creator",
272
+ value: noticeUser
273
+ }] });
274
+ for (const room of rooms) if ((await this.hs.db.findOne("account_data", [
275
+ {
276
+ field: "user_id",
277
+ value: userId
278
+ },
279
+ {
280
+ field: "room_id",
281
+ value: room.room_id
282
+ },
283
+ {
284
+ field: "type",
285
+ value: "m.tag"
286
+ }
287
+ ]))?.content?.tags?.["m.server_notice"]) return room.room_id;
288
+ const { room_id } = await this.hs.rooms.createRoom(noticeUser, {
289
+ preset: "private_chat",
290
+ name: this.hs.config.serverNotices.roomName,
291
+ power_level_content_override: {
292
+ users: { [noticeUser]: 100 },
293
+ events_default: 100
294
+ }
295
+ });
296
+ await this.hs.streams.write((stream) => this.hs.db.upsert("account_data", {
297
+ user_id: userId,
298
+ room_id,
299
+ type: "m.tag",
300
+ content: { tags: { "m.server_notice": { order: .5 } } },
301
+ stream
302
+ }));
303
+ return room_id;
304
+ }
305
+ };
306
+ //#endregion
307
+ export { AdminService };
@@ -0,0 +1,49 @@
1
+ import { PDU } from "./events.js";
2
+ import { Homeserver } from "../homeserver.js";
3
+ //#region src/core/adminroom.d.ts
4
+ export interface AdminRoomOptions {
5
+ /** Enable the admin room and admin bot. Default: true when the option object is given. */
6
+ enabled?: boolean;
7
+ /** The first account registered on the server becomes a server admin. Default: true. */
8
+ firstUserIsAdmin?: boolean;
9
+ /** Let the very first account register even when registration is closed. Default: true. */
10
+ firstUserCanRegister?: boolean;
11
+ /** Localpart of the admin bot. Default: "admin". */
12
+ botLocalpart?: string;
13
+ botDisplayName?: string;
14
+ roomName?: string;
15
+ /** Localpart of the admin room alias (#admins:server). Default: "admins". */
16
+ aliasLocalpart?: string;
17
+ }
18
+ /**
19
+ * A tuwunel-style admin room: server admins chat with an admin bot to manage the
20
+ * server. The first registered account becomes an admin and is put in the room.
21
+ */
22
+ export declare class AdminRoomService {
23
+ private hs;
24
+ readonly options: Required<AdminRoomOptions>;
25
+ private roomIdCache;
26
+ constructor(hs: Homeserver, options: AdminRoomOptions);
27
+ get botUserId(): string;
28
+ get alias(): string;
29
+ /** Whether no real (non-bot) account exists yet. */
30
+ noUsersYet(): Promise<boolean>;
31
+ roomId(): Promise<string | null>;
32
+ /** Create the admin bot and room if needed. */
33
+ ensureRoom(): Promise<string>;
34
+ /** Called after an account was created. */
35
+ onUserCreated(userId: string): Promise<void>;
36
+ /** Put an admin into the admin room. */
37
+ addAdmin(userId: string): Promise<void>;
38
+ /** Remove a former admin from the admin room. */
39
+ removeAdmin(userId: string): Promise<void>;
40
+ /** Post a message from the admin bot. */
41
+ say(text: string, replyTo?: string): Promise<void>;
42
+ /** Called for every new event; handles commands posted in the admin room. */
43
+ onEvent(event: PDU): Promise<void>;
44
+ private resolveUser;
45
+ private resolveRoom;
46
+ /** Run one admin command; returns the reply text. */
47
+ execute(actor: string, args: string[]): Promise<string>;
48
+ }
49
+ //#endregion