diff --git a/src/CachingClient.ts b/src/CachingClient.ts new file mode 100644 index 00000000..257db562 --- /dev/null +++ b/src/CachingClient.ts @@ -0,0 +1,196 @@ +import { MatrixClient, UserID } from "matrix-bot-sdk"; + +/** + * A caching layer on top of MatrixClient. + * + * This layer is designed to speed up Mjâlnir and reduce the load on + * the homeserver by caching critical data. + * + * # Caching policy + * + * As an instance of Many-Mjâlnir can end up instantiating thousands of + * instances of `Mjolnir`, we do not wish to cache *everything*. Rather, + * we cache only data that we know we'll be using repeatedly. + */ +export class CachingClient { + // The user id. Initialized by `start()`. + private _userId?: string; + private _localpart?: string; + private _displayName?: string | null; + private _domain?: string; + + // The contents of account data. + private _accountData: Map = new Map(); + private _joinedRooms: Cache>; + + constructor( + /** + * The implementation of the MatrixClient + */ + public readonly uncached: MatrixClient + ) {} + + /** + * Initialize this client. + * + * This MUST be called once before using the `CachingClient`. + * + * Do not forget to call `stop()` at the end. + */ + public async start() { + this.uncached.on("account_data", event => { + if ("type" in event && typeof event.type === "string") { + this._accountData.set(event.type, event); + } + }); + + await this.uncached.start(); + if (this._userId) { + throw new TypeError("Already initialized"); + } + this._userId = await this.uncached.getUserId(); + let userID = new UserID(this._userId); + this._localpart = userID.localpart; + this._domain = userID.domain; + let profile = await this.uncached.getUserProfile(this._userId); + this._displayName = profile?.['displayname'] || null; + } + + /** + * Stop the client. + */ + public stop() { + this.uncached.stop(); + for (let cache of this._accountData.values()) { + cache.unregister(); + } + this._accountData.clear(); + } + + /** + * The user id for this client. + */ + public get userId(): string { + return this._userId!; + } + public get localpart(): string { + return this._localpart!; + } + public get domain(): string { + return this._domain!; + } + public get displayName(): string | null { + return this._displayName || null; + } + + /** + * Register for knowing about account data of a specific kind. + * @param type + */ + public async accountData(type: string): Promise> { + let cache = this._accountData.get(type); + if (!cache) { + let newCache = new AccountDataCache(this.uncached, type); + await newCache.init(); + this._accountData.set(type, newCache); + cache = newCache; + } + return cache; + } + + public joinedRooms(): Cache> { + if (!this._joinedRooms) { + this._joinedRooms = new JoinedRoomsCache(this.uncached); + } + return this._joinedRooms; + } +} + +/** + * A cached value. + * + * Call `get()` to obtain the latest value. + */ +export abstract class Cache { + _value: T | null; + public get(): Readonly | null { + return this._value; + } + cache(value: T) { + this._value = value; + } + abstract unregister(): void; +} + +export abstract class WritableCache extends Cache { + abstract send(value: T): Promise; + public async set(value: T): Promise { + let previous = this.get(); + await this.send(value); + return previous; + } +} + +/** + * A cache specialized to store the list of currently joined rooms. + * + * `get()` returns a `Set` of room ids for all the rooms currently joined. + */ +class JoinedRoomsCache extends Cache> { + constructor(private uncached: MatrixClient) { + super(); + } + async init() { + let data = await this.uncached.getJoinedRooms(); + this.cache(new Set(data)); + await this.uncached.on("room.join", this.onJoin); + await this.uncached.on("room.leave", this.onLeave); + } + onJoin = (roomId: string) => { + this._value?.add(roomId); + } + onLeave = (roomId: string) => { + this._value?.delete(roomId); + } + unregister() { + this.uncached.removeListener("room.join", this.onJoin); + this.uncached.removeListener("room.join", this.onLeave); + } +} + +/** + * A cache for account data. + * + * One instance of `AccountDataCache` for each `type` of account data watched. + */ +class AccountDataCache extends WritableCache { + public async send(value: any): Promise { + await this.uncached.setAccountData(this.type, value); + } + + constructor(private readonly uncached: MatrixClient, public readonly type: string) { + super(); + + } + async init() { + let data; + try { + data = await this.uncached.getAccountData(this.type); + } catch (ex) { + if (ex.statusCode != 404) { + throw ex; + } + // Otherwise, this is a "not found" exception, which means that the account doesn't contain any such data. + } + this.cache(data); + this.uncached.on("account_data", this.watch); + } + watch = (event: any) => { + if ("type" in event && event.type === this.type) { + this.cache(event); + } + } + unregister() { + this.uncached.removeListener("account_data", this.watch); + } +} diff --git a/src/ManagementRoomOutput.ts b/src/ManagementRoomOutput.ts index e47ba619..e3a4b22e 100644 --- a/src/ManagementRoomOutput.ts +++ b/src/ManagementRoomOutput.ts @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { extractRequestError, LogLevel, LogService, MatrixClient, MessageType, Permalinks, TextualMessageEventContent, UserID } from "matrix-bot-sdk"; +import { extractRequestError, LogLevel, LogService, MessageType, Permalinks, TextualMessageEventContent, UserID } from "matrix-bot-sdk"; +import { CachingClient } from "./CachingClient"; import { IConfig } from "./config"; import { htmlEscape } from "./utils"; @@ -32,7 +33,7 @@ export default class ManagementRoomOutput { constructor( private readonly managementRoomId: string, - private readonly client: MatrixClient, + private readonly client: CachingClient, private readonly config: IConfig, ) { @@ -63,11 +64,11 @@ export default class ManagementRoomOutput { return v.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); }; - const viaServers = [(new UserID(await this.client.getUserId())).domain]; + const viaServers = [(new UserID(this.client.userId)).domain]; for (const roomId of roomIds) { let alias = roomId; try { - alias = (await this.client.getPublishedAlias(roomId)) || roomId; + alias = (await this.client.uncached.getPublishedAlias(roomId)) || roomId; } catch (e) { // This is a recursive call, so tell the function not to try and call us await this.logMessage(LogLevel.WARN, "utils", `Failed to resolve room alias for ${roomId} - see console for details`, null, true); @@ -115,7 +116,7 @@ export default class ManagementRoomOutput { evContent = await this.replaceRoomIdsWithPills(clientMessage, new Set(roomIds), "m.notice"); } - await client.sendMessage(this.managementRoomId, evContent); + await client.uncached.sendMessage(this.managementRoomId, evContent); } levelToFn[level.toString()](module, message); diff --git a/src/Mjolnir.ts b/src/Mjolnir.ts index 0400a72a..e484d676 100644 --- a/src/Mjolnir.ts +++ b/src/Mjolnir.ts @@ -39,6 +39,8 @@ import { ProtectedRooms } from "./ProtectedRooms"; import ManagementRoomOutput from "./ManagementRoomOutput"; import { ProtectionManager } from "./protections/ProtectionManager"; import { RoomMemberManager } from "./RoomMembers"; +import { CachingClient, WritableCache } from "./CachingClient"; +import { DEFAULT_LIST_EVENT_TYPE } from "./commands/SetDefaultBanListCommand"; export const STATE_NOT_STARTED = "not_started"; export const STATE_CHECKING_PERMISSIONS = "checking_permissions"; @@ -49,15 +51,8 @@ const PROTECTED_ROOMS_EVENT_TYPE = "org.matrix.mjolnir.protected_rooms"; const WATCHED_LISTS_EVENT_TYPE = "org.matrix.mjolnir.watched_lists"; const WARN_UNPROTECTED_ROOM_EVENT_PREFIX = "org.matrix.mjolnir.unprotected_room_warning.for."; -/** - * Synapse will tell us where we last got to on polling reports, so we need - * to store that for pagination on further polls - */ -export const REPORT_POLL_EVENT_TYPE = "org.matrix.mjolnir.report_poll"; - export class Mjolnir { - private displayName: string; - private localpart: string; + public readonly client: CachingClient; private currentState: string = STATE_NOT_STARTED; public readonly roomJoins: RoomMemberManager; /** @@ -98,6 +93,15 @@ export class Mjolnir { */ public readonly reportManager: ReportManager; + /** + * The list of protected rooms, as specified in the account data. + */ + public readonly accountData: { + protectedRooms?: WritableCache, + watchedLists?: WritableCache, + defaultList?: WritableCache, + } = {}; + /** * Adds a listener to the client that will automatically accept invitations. * @param {MatrixClient} client @@ -185,7 +189,7 @@ export class Mjolnir { } constructor( - public readonly client: MatrixClient, + client: MatrixClient, private readonly clientUserId: string, public readonly managementRoomId: string, public readonly config: IConfig, @@ -198,6 +202,7 @@ export class Mjolnir { // Combines the rules from ban lists so they can be served to a homeserver module or another consumer. public readonly ruleServer: RuleServer | null, ) { + this.client = new CachingClient(client); this.explicitlyProtectedRoomIds = Object.keys(this.protectedRooms); // Setup bot. @@ -212,12 +217,12 @@ export class Mjolnir { if (content['msgtype'] === "m.text" && content['body']) { const prefixes = [ COMMAND_PREFIX, - this.localpart + ":", - this.displayName + ":", - await client.getUserId() + ":", - this.localpart + " ", - this.displayName + " ", - await client.getUserId() + " ", + this.client.localpart + ":", + this.client.displayName + ":", + this.client.userId + ":", + this.client.localpart + " ", + this.client.displayName + " ", + this.client.userId + " ", ...config.commands.additionalPrefixes.map(p => `!${p}`), ...config.commands.additionalPrefixes.map(p => `${p}:`), ...config.commands.additionalPrefixes.map(p => `${p} `), @@ -248,15 +253,6 @@ export class Mjolnir { return this.resyncJoinedRooms(); }); - client.getUserId().then(userId => { - this.localpart = userId.split(':')[0].substring(1); - return client.getUserProfile(userId); - }).then(profile => { - if (profile['displayname']) { - this.displayName = profile['displayname']; - } - }); - // Setup Web APIs console.log("Creating Web APIs"); this.reportManager = new ReportManager(this); @@ -265,14 +261,14 @@ export class Mjolnir { this.reportPoller = new ReportPoller(this, this.reportManager); } // Setup join/leave listener - this.roomJoins = new RoomMemberManager(this.client); + this.roomJoins = new RoomMemberManager(client); this.taskQueue = new ThrottlingQueue(this, config.backgroundDelayMS); this.protectionManager = new ProtectionManager(this); - this.managementRoomOutput = new ManagementRoomOutput(managementRoomId, client, config); + this.managementRoomOutput = new ManagementRoomOutput(managementRoomId, this.client, config); const protections = new ProtectionManager(this); - this.protectedRoomsTracker = new ProtectedRooms(client, clientUserId, managementRoomId, this.managementRoomOutput, protections, config); + this.protectedRoomsTracker = new ProtectedRooms(this.client, managementRoomId, this.managementRoomOutput, protections, config); } public get lists(): PolicyList[] { @@ -305,17 +301,7 @@ export class Mjolnir { await this.webapis.start(); if (this.reportPoller) { - let reportPollSetting: { from: number } = { from: 0 }; - try { - reportPollSetting = await this.client.getAccountData(REPORT_POLL_EVENT_TYPE); - } catch (err) { - if (err.body?.errcode !== "M_NOT_FOUND") { - throw err; - } else { - this.managementRoomOutput.logMessage(LogLevel.INFO, "Mjolnir@startup", "report poll setting does not exist yet"); - } - } - this.reportPoller.start(reportPollSetting.from); + await this.reportPoller.start(); } // Load the state. @@ -323,8 +309,9 @@ export class Mjolnir { await this.managementRoomOutput.logMessage(LogLevel.DEBUG, "Mjolnir@startup", "Loading protected rooms..."); await this.resyncJoinedRooms(false); + this.accountData.protectedRooms = await this.client.accountData(PROTECTED_ROOMS_EVENT_TYPE); try { - const data: { rooms?: string[] } | null = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE); + const data: { rooms?: string[] } | null = this.accountData.protectedRooms.get(); if (data && data['rooms']) { for (const roomId of data['rooms']) { this.protectedRooms[roomId] = Permalinks.forRoom(roomId); @@ -334,6 +321,8 @@ export class Mjolnir { } catch (e) { LogService.warn("Mjolnir", extractRequestError(e)); } + this.accountData.watchedLists = await this.client.accountData(WATCHED_LISTS_EVENT_TYPE); + this.accountData.defaultList = await this.client.accountData(DEFAULT_LIST_EVENT_TYPE); await this.buildWatchedPolicyLists(); this.applyUnprotectedRooms(); await this.protectionManager.start(); @@ -384,15 +373,10 @@ export class Mjolnir { if (unprotectedIdx >= 0) this.unprotectedWatchedListRooms.splice(unprotectedIdx, 1); this.explicitlyProtectedRoomIds.push(roomId); - let additionalProtectedRooms: { rooms?: string[] } | null = null; - try { - additionalProtectedRooms = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE); - } catch (e) { - LogService.warn("Mjolnir", extractRequestError(e)); - } + let additionalProtectedRooms: { rooms?: string[] } | null = this.accountData.protectedRooms!.get(); const rooms = (additionalProtectedRooms?.rooms ?? []); rooms.push(roomId); - await this.client.setAccountData(PROTECTED_ROOMS_EVENT_TYPE, { rooms: rooms }); + await this.accountData.protectedRooms!.set({ rooms: rooms }); } public async removeProtectedRoom(roomId: string) { @@ -403,21 +387,16 @@ export class Mjolnir { const idx = this.explicitlyProtectedRoomIds.indexOf(roomId); if (idx >= 0) this.explicitlyProtectedRoomIds.splice(idx, 1); - let additionalProtectedRooms: { rooms?: string[] } | null = null; - try { - additionalProtectedRooms = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE); - } catch (e) { - LogService.warn("Mjolnir", extractRequestError(e)); - } + let additionalProtectedRooms: { rooms?: string[] } | null = this.accountData.protectedRooms!.get(); additionalProtectedRooms = { rooms: additionalProtectedRooms?.rooms?.filter(r => r !== roomId) ?? [] }; - await this.client.setAccountData(PROTECTED_ROOMS_EVENT_TYPE, additionalProtectedRooms); + await this.accountData.protectedRooms!.set(additionalProtectedRooms); } // See https://github.com/matrix-org/mjolnir/issues/370. private async resyncJoinedRooms(withSync = true) { if (!this.config.protectAllJoinedRooms) return; - const joinedRoomIds = (await this.client.getJoinedRooms()) + const joinedRoomIds = [...this.client.joinedRooms().get()!.values()] .filter(r => r !== this.managementRoomId && !this.unprotectedWatchedListRooms.includes(r)); const oldRoomIdsSet = new Set(this.protectedJoinedRoomIds); const joinedRoomIdsSet = new Set(joinedRoomIds); @@ -464,20 +443,20 @@ export class Mjolnir { } public async watchList(roomRef: string): Promise { - const joinedRooms = await this.client.getJoinedRooms(); + const joinedRooms = this.client.joinedRooms().get()!; const permalink = Permalinks.parseUrl(roomRef); if (!permalink.roomIdOrAlias) return null; - const roomId = await this.client.resolveRoom(permalink.roomIdOrAlias); - if (!joinedRooms.includes(roomId)) { - await this.client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers); + const roomId = await this.client.uncached.resolveRoom(permalink.roomIdOrAlias); + if (!joinedRooms.has(roomId)) { + await this.client.uncached.joinRoom(permalink.roomIdOrAlias, permalink.viaServers); } if (this.policyLists.find(b => b.roomId === roomId)) return null; const list = await this.addPolicyList(roomId, roomRef); - await this.client.setAccountData(WATCHED_LISTS_EVENT_TYPE, { + await this.accountData.watchedLists!.set({ references: this.policyLists.map(b => b.roomRef), }); @@ -490,7 +469,7 @@ export class Mjolnir { const permalink = Permalinks.parseUrl(roomRef); if (!permalink.roomIdOrAlias) return null; - const roomId = await this.client.resolveRoom(permalink.roomIdOrAlias); + const roomId = await this.client.uncached.resolveRoom(permalink.roomIdOrAlias); const list = this.policyLists.find(b => b.roomId === roomId) || null; if (list) { this.policyLists.splice(this.policyLists.indexOf(list), 1); @@ -498,7 +477,7 @@ export class Mjolnir { this.protectedRoomsTracker.unwatchList(list); } - await this.client.setAccountData(WATCHED_LISTS_EVENT_TYPE, { + await this.accountData.watchedLists!.set({ references: this.policyLists.map(b => b.roomRef), }); return list; @@ -508,21 +487,21 @@ export class Mjolnir { if (!this.config.protectAllJoinedRooms) return; // doesn't matter if (this.explicitlyProtectedRoomIds.includes(roomId)) return; // explicitly protected - const createEvent = new CreateEvent(await this.client.getRoomStateEvent(roomId, "m.room.create", "")); - if (createEvent.creator === await this.client.getUserId()) return; // we created it + const createEvent = new CreateEvent(await this.client.uncached.getRoomStateEvent(roomId, "m.room.create", "")); + if (createEvent.creator === this.client.userId) return; // we created it if (!this.unprotectedWatchedListRooms.includes(roomId)) this.unprotectedWatchedListRooms.push(roomId); this.applyUnprotectedRooms(); try { - const accountData: { warned: boolean } | null = await this.client.getAccountData(WARN_UNPROTECTED_ROOM_EVENT_PREFIX + roomId); + const accountData: { warned: boolean } | null = await this.client.uncached.getAccountData(WARN_UNPROTECTED_ROOM_EVENT_PREFIX + roomId); if (accountData && accountData.warned) return; // already warned } catch (e) { // Ignore - probably haven't warned about it yet } await this.managementRoomOutput.logMessage(LogLevel.WARN, "Mjolnir", `Not protecting ${roomId} - it is a ban list that this bot did not create. Add the room as protected if it is supposed to be protected. This warning will not appear again.`, roomId); - await this.client.setAccountData(WARN_UNPROTECTED_ROOM_EVENT_PREFIX + roomId, { warned: true }); + await this.client.uncached.setAccountData(WARN_UNPROTECTED_ROOM_EVENT_PREFIX + roomId, { warned: true }); } /** @@ -538,22 +517,17 @@ export class Mjolnir { private async buildWatchedPolicyLists() { this.policyLists = []; - const joinedRooms = await this.client.getJoinedRooms(); + const joinedRooms = this.client.joinedRooms().get()!; - let watchedListsEvent: { references?: string[] } | null = null; - try { - watchedListsEvent = await this.client.getAccountData(WATCHED_LISTS_EVENT_TYPE); - } catch (e) { - // ignore - not important - } + let watchedListsEvent = this.accountData.watchedLists!.get(); for (const roomRef of (watchedListsEvent?.references || [])) { const permalink = Permalinks.parseUrl(roomRef); if (!permalink.roomIdOrAlias) continue; - const roomId = await this.client.resolveRoom(permalink.roomIdOrAlias); - if (!joinedRooms.includes(roomId)) { - await this.client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers); + const roomId = await this.client.uncached.resolveRoom(permalink.roomIdOrAlias); + if (!joinedRooms.has(roomId)) { + await this.client.uncached.joinRoom(permalink.roomIdOrAlias, permalink.viaServers); } await this.warnAboutUnprotectedPolicyListRoom(roomId); @@ -567,9 +541,9 @@ export class Mjolnir { if (event['type'] === 'm.room.message' && event['content'] && event['content']['body']) { if (event['content']['body'] === "** Unable to decrypt: The sender's device has not sent us the keys for this message. **") { // UISI - await this.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '⚠'); - await this.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'UISI'); - await this.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '🚨'); + await this.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], '⚠'); + await this.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'UISI'); + await this.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], '🚨'); } } } @@ -590,8 +564,8 @@ export class Mjolnir { public async isSynapseAdmin(): Promise { try { - const endpoint = `/_synapse/admin/v1/users/${await this.client.getUserId()}/admin`; - const response = await this.client.doRequest("GET", endpoint); + const endpoint = `/_synapse/admin/v1/users/${this.client.userId}/admin`; + const response = await this.client.uncached.doRequest("GET", endpoint); return response['admin']; } catch (e) { LogService.error("Mjolnir", "Error determining if Mjolnir is a server admin:"); @@ -602,13 +576,13 @@ export class Mjolnir { public async deactivateSynapseUser(userId: string): Promise { const endpoint = `/_synapse/admin/v1/deactivate/${userId}`; - return await this.client.doRequest("POST", endpoint); + return await this.client.uncached.doRequest("POST", endpoint); } public async shutdownSynapseRoom(roomId: string, message?: string): Promise { const endpoint = `/_synapse/admin/v1/rooms/${roomId}`; - return await this.client.doRequest("DELETE", endpoint, null, { - new_room_user_id: await this.client.getUserId(), + return await this.client.uncached.doRequest("DELETE", endpoint, null, { + new_room_user_id: this.client.userId, block: true, message: message /* If `undefined`, we'll use Synapse's default message. */ }); @@ -623,8 +597,8 @@ export class Mjolnir { public async makeUserRoomAdmin(roomId: string, userId?: string): Promise { try { const endpoint = `/_synapse/admin/v1/rooms/${roomId}/make_room_admin`; - return await this.client.doRequest("POST", endpoint, null, { - user_id: userId || await this.client.getUserId(), /* if not specified make the bot administrator */ + return await this.client.uncached.doRequest("POST", endpoint, null, { + user_id: userId || this.client.userId, /* if not specified make the bot administrator */ }); } catch (e) { return extractRequestError(e); diff --git a/src/ProtectedRooms.ts b/src/ProtectedRooms.ts index fee4230a..1ab07eeb 100644 --- a/src/ProtectedRooms.ts +++ b/src/ProtectedRooms.ts @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { LogLevel, LogService, MatrixClient, MatrixGlob, Permalinks, UserID } from "matrix-bot-sdk"; +import { LogLevel, LogService, MatrixGlob, Permalinks, UserID } from "matrix-bot-sdk"; +import { CachingClient } from "./CachingClient"; import { IConfig } from "./config"; import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache"; import ManagementRoomOutput from "./ManagementRoomOutput"; @@ -83,8 +84,7 @@ export class ProtectedRooms { private aclChain: Promise = Promise.resolve(); constructor( - private readonly client: MatrixClient, - private readonly clientUserId: string, + private readonly client: CachingClient, private readonly managementRoomId: string, private readonly managementRoomOutput: ManagementRoomOutput, /** @@ -163,7 +163,7 @@ export class ProtectedRooms { } public async handleEvent(roomId: string, event: any) { - if (event['sender'] === this.clientUserId) { + if (event['sender'] === this.client.userId) { throw new TypeError("`ProtectedRooms::handleEvent` should not be used to inform about events sent by mjolnir."); } this.protectedRoomActivityTracker.handleEvent(roomId, event); @@ -212,7 +212,7 @@ export class ProtectedRooms { if (!hadErrors && verbose) { const html = `Done updating rooms - no errors`; const text = "Done updating rooms - no errors"; - await this.client.sendMessage(this.managementRoomId, { + await this.client.uncached.sendMessage(this.managementRoomId, { msgtype: "m.notice", body: text, format: "org.matrix.custom.html", @@ -260,7 +260,7 @@ export class ProtectedRooms { if (!hadErrors) { const html = `Done updating rooms - no errors`; const text = "Done updating rooms - no errors"; - await this.client.sendMessage(this.managementRoomId, { + await this.client.uncached.sendMessage(this.managementRoomId, { msgtype: "m.notice", body: text, format: "org.matrix.custom.html", @@ -290,7 +290,7 @@ export class ProtectedRooms { } private async _applyServerAcls(lists: PolicyList[], roomIds: string[]): Promise { - const serverName: string = new UserID(await this.client.getUserId()).domain; + const serverName: string = new UserID(this.client.userId).domain; // Construct a server ACL first const acl = new ServerAcl(serverName).denyIpAddresses().allowServer("*"); @@ -308,7 +308,7 @@ export class ProtectedRooms { if (this.config.verboseLogging) { // We specifically use sendNotice to avoid having to escape HTML - await this.client.sendNotice(this.managementRoomId, `Constructed server ACL:\n${JSON.stringify(finalAcl, null, 2)}`); + await this.client.uncached.sendNotice(this.managementRoomId, `Constructed server ACL:\n${JSON.stringify(finalAcl, null, 2)}`); } const errors: RoomUpdateError[] = []; @@ -317,7 +317,7 @@ export class ProtectedRooms { await this.managementRoomOutput.logMessage(LogLevel.DEBUG, "ApplyAcl", `Checking ACLs for ${roomId}`, roomId); try { - const currentAcl = await this.client.getRoomStateEvent(roomId, "m.room.server_acl", ""); + const currentAcl = await this.client.uncached.getRoomStateEvent(roomId, "m.room.server_acl", ""); if (acl.matches(currentAcl)) { await this.managementRoomOutput.logMessage(LogLevel.DEBUG, "ApplyAcl", `Skipping ACLs for ${roomId} because they are already the right ones`, roomId); continue; @@ -330,7 +330,7 @@ export class ProtectedRooms { await this.managementRoomOutput.logMessage(LogLevel.DEBUG, "ApplyAcl", `Applying ACL in ${roomId}`, roomId); if (!this.config.noop) { - await this.client.sendStateEvent(roomId, "m.room.server_acl", "", finalAcl); + await this.client.uncached.sendStateEvent(roomId, "m.room.server_acl", "", finalAcl); } else { await this.managementRoomOutput.logMessage(LogLevel.WARN, "ApplyAcl", `Tried to apply ACL in ${roomId} but Mjolnir is running in no-op mode`, roomId); } @@ -361,12 +361,12 @@ export class ProtectedRooms { let members: { userId: string, membership: string }[]; if (this.config.fasterMembershipChecks) { - const memberIds = await this.client.getJoinedRoomMembers(roomId); + const memberIds = await this.client.uncached.getJoinedRoomMembers(roomId); members = memberIds.map(u => { return { userId: u, membership: "join" }; }); } else { - const state = await this.client.getRoomState(roomId); + const state = await this.client.uncached.getRoomState(roomId); members = state.filter(s => s['type'] === 'm.room.member' && !!s['state_key']).map(s => { return { userId: s['state_key'], membership: s['content'] ? s['content']['membership'] : 'leave' }; }); @@ -387,7 +387,7 @@ export class ProtectedRooms { await this.managementRoomOutput.logMessage(LogLevel.INFO, "ApplyBan", `Banning ${member.userId} in ${roomId} for: ${userRule.reason}`, roomId); if (!this.config.noop) { - await this.client.banUser(member.userId, roomId, userRule.reason); + await this.client.uncached.banUser(member.userId, roomId, userRule.reason); if (this.automaticRedactGlobs.find(g => g.test(userRule.reason.toLowerCase()))) { this.redactUser(member.userId, roomId); } @@ -423,7 +423,7 @@ export class ProtectedRooms { */ private async printBanlistChanges(changes: ListRuleChange[], list: PolicyList, ignoreSelf = false): Promise { if (ignoreSelf) { - const sender = await this.client.getUserId(); + const sender = this.client.userId; changes = changes.filter(change => change.sender !== sender); } if (changes.length <= 0) return false; @@ -457,7 +457,7 @@ export class ProtectedRooms { format: "org.matrix.custom.html", formatted_body: html, }; - await this.client.sendMessage(this.managementRoomId, message); + await this.client.uncached.sendMessage(this.managementRoomId, message); return true; } @@ -480,9 +480,9 @@ export class ProtectedRooms { html += `${htmlTitle}${errors.length} errors updating protected rooms!
    `; text += `${textTitle}${errors.length} errors updating protected rooms!\n`; - const viaServers = [(new UserID(await this.client.getUserId())).domain]; + const viaServers = [(new UserID(this.client.userId)).domain]; for (const error of errors) { - const alias = (await this.client.getPublishedAlias(error.roomId)) || error.roomId; + const alias = (await this.client.uncached.getPublishedAlias(error.roomId)) || error.roomId; const url = Permalinks.forRoom(alias, viaServers); html += `
  • ${alias} - ${error.errorMessage}
  • `; text += `${url} - ${error.errorMessage}\n`; @@ -495,7 +495,7 @@ export class ProtectedRooms { format: "org.matrix.custom.html", formatted_body: html, }; - await this.client.sendMessage(this.managementRoomId, message); + await this.client.uncached.sendMessage(this.managementRoomId, message); return true; } @@ -513,7 +513,7 @@ export class ProtectedRooms { if (!hadErrors && verbose) { const html = `All permissions look OK.`; const text = "All permissions look OK."; - await this.client.sendMessage(this.managementRoomId, { + await this.client.uncached.sendMessage(this.managementRoomId, { msgtype: "m.notice", body: text, format: "org.matrix.custom.html", diff --git a/src/commands/AddRemoveProtectedRoomsCommand.ts b/src/commands/AddRemoveProtectedRoomsCommand.ts index 978b9c8e..5c737e55 100644 --- a/src/commands/AddRemoveProtectedRoomsCommand.ts +++ b/src/commands/AddRemoveProtectedRoomsCommand.ts @@ -19,20 +19,20 @@ import { extractRequestError, LogLevel, LogService } from "matrix-bot-sdk"; // !mjolnir rooms add export async function execAddProtectedRoom(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { - const protectedRoomId = await mjolnir.client.joinRoom(parts[3]); + const protectedRoomId = await mjolnir.client.uncached.joinRoom(parts[3]); await mjolnir.addProtectedRoom(protectedRoomId); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } // !mjolnir rooms remove export async function execRemoveProtectedRoom(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { - const protectedRoomId = await mjolnir.client.resolveRoom(parts[3]); + const protectedRoomId = await mjolnir.client.uncached.resolveRoom(parts[3]); await mjolnir.removeProtectedRoom(protectedRoomId); try { - await mjolnir.client.leaveRoom(protectedRoomId); + await mjolnir.client.uncached.leaveRoom(protectedRoomId); } catch (e) { LogService.warn("AddRemoveProtectedRoomsCommand", extractRequestError(e)); await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "AddRemoveProtectedRoomsCommand", `Failed to leave ${protectedRoomId} - the room is no longer being protected, but the bot could not leave`, protectedRoomId); } - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } diff --git a/src/commands/AddRemoveRoomFromDirectoryCommand.ts b/src/commands/AddRemoveRoomFromDirectoryCommand.ts index 1dd14597..868e6576 100644 --- a/src/commands/AddRemoveRoomFromDirectoryCommand.ts +++ b/src/commands/AddRemoveRoomFromDirectoryCommand.ts @@ -23,14 +23,14 @@ async function addRemoveFromDirectory(inRoomId: string, event: any, mjolnir: Mjo const message = "I am not a Synapse administrator, or the endpoint is blocked"; const reply = RichReply.createFor(inRoomId, event, message, message); reply['msgtype'] = "m.notice"; - mjolnir.client.sendMessage(inRoomId, reply); + mjolnir.client.uncached.sendMessage(inRoomId, reply); return; } - const targetRoomId = await mjolnir.client.resolveRoom(roomRef); - await mjolnir.client.setDirectoryVisibility(targetRoomId, visibility); + const targetRoomId = await mjolnir.client.uncached.resolveRoom(roomRef); + await mjolnir.client.uncached.setDirectoryVisibility(targetRoomId, visibility); - await mjolnir.client.unstableApis.addReactionToEvent(inRoomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(inRoomId, event['event_id'], 'βœ…'); } // !mjolnir directory add diff --git a/src/commands/AliasCommands.ts b/src/commands/AliasCommands.ts index 59ff4ce0..b0658900 100644 --- a/src/commands/AliasCommands.ts +++ b/src/commands/AliasCommands.ts @@ -28,15 +28,15 @@ export async function execMoveAliasCommand(roomId: string, event: any, mjolnir: const message = "I am not a Synapse administrator, or the endpoint is blocked"; const reply = RichReply.createFor(roomId, event, message, message); reply['msgtype'] = "m.notice"; - mjolnir.client.sendMessage(roomId, reply); + mjolnir.client.uncached.sendMessage(roomId, reply); return; } - await mjolnir.client.deleteRoomAlias(movingAlias); - const newRoomId = await mjolnir.client.resolveRoom(targetRoom); - await mjolnir.client.createRoomAlias(movingAlias, newRoomId); + await mjolnir.client.uncached.deleteRoomAlias(movingAlias); + const newRoomId = await mjolnir.client.uncached.resolveRoom(targetRoom); + await mjolnir.client.uncached.createRoomAlias(movingAlias, newRoomId); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } // !mjolnir alias add @@ -49,14 +49,14 @@ export async function execAddAliasCommand(roomId: string, event: any, mjolnir: M const message = "I am not a Synapse administrator, or the endpoint is blocked"; const reply = RichReply.createFor(roomId, event, message, message); reply['msgtype'] = "m.notice"; - mjolnir.client.sendMessage(roomId, reply); + mjolnir.client.uncached.sendMessage(roomId, reply); return; } - const newRoomId = await mjolnir.client.resolveRoom(targetRoom); - await mjolnir.client.createRoomAlias(aliasToAdd, newRoomId); + const newRoomId = await mjolnir.client.uncached.resolveRoom(targetRoom); + await mjolnir.client.uncached.createRoomAlias(aliasToAdd, newRoomId); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } // !mjolnir alias remove @@ -68,24 +68,24 @@ export async function execRemoveAliasCommand(roomId: string, event: any, mjolnir const message = "I am not a Synapse administrator, or the endpoint is blocked"; const reply = RichReply.createFor(roomId, event, message, message); reply['msgtype'] = "m.notice"; - mjolnir.client.sendMessage(roomId, reply); + mjolnir.client.uncached.sendMessage(roomId, reply); return; } - await mjolnir.client.deleteRoomAlias(aliasToRemove); + await mjolnir.client.uncached.deleteRoomAlias(aliasToRemove); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } // !mjolnir resolve export async function execResolveCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { const toResolve = parts[2]; - const resolvedRoomId = await mjolnir.client.resolveRoom(toResolve); + const resolvedRoomId = await mjolnir.client.uncached.resolveRoom(toResolve); const message = `Room ID for ${toResolve} is ${resolvedRoomId}`; const html = `Room ID for ${htmlEscape(toResolve)} is ${htmlEscape(resolvedRoomId)}`; const reply = RichReply.createFor(roomId, event, message, html); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); } diff --git a/src/commands/CommandHandler.ts b/src/commands/CommandHandler.ts index 3fad2cc9..2eea1c22 100644 --- a/src/commands/CommandHandler.ts +++ b/src/commands/CommandHandler.ts @@ -171,13 +171,13 @@ export async function handleCommand(roomId: string, event: { content: { body: st const text = `Mjolnir help:\n${menu}`; const reply = RichReply.createFor(roomId, event, text, html); reply["msgtype"] = "m.notice"; - return await mjolnir.client.sendMessage(roomId, reply); + return await mjolnir.client.uncached.sendMessage(roomId, reply); } } catch (e) { LogService.error("CommandHandler", extractRequestError(e)); const text = "There was an error processing your command - see console/log for details"; const reply = RichReply.createFor(roomId, event, text, text); reply["msgtype"] = "m.notice"; - return await mjolnir.client.sendMessage(roomId, reply); + return await mjolnir.client.uncached.sendMessage(roomId, reply); } } diff --git a/src/commands/CreateBanListCommand.ts b/src/commands/CreateBanListCommand.ts index 34628869..6d87c116 100644 --- a/src/commands/CreateBanListCommand.ts +++ b/src/commands/CreateBanListCommand.ts @@ -38,13 +38,13 @@ export async function execCreateListCommand(roomId: string, event: any, mjolnir: "redact": 50, "state_default": 50, "users": { - [await mjolnir.client.getUserId()]: 100, + [mjolnir.client.userId]: 100, [event["sender"]]: 50 }, "users_default": 0, }; - const listRoomId = await mjolnir.client.createRoom({ + const listRoomId = await mjolnir.client.uncached.createRoom({ preset: "public_chat", room_alias_name: aliasLocalpart, invite: [event['sender']], @@ -59,5 +59,5 @@ export async function execCreateListCommand(roomId: string, event: any, mjolnir: const text = `Created new list (${roomRef}). This list is now being watched.`; const reply = RichReply.createFor(roomId, event, text, html); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); } diff --git a/src/commands/DeactivateCommand.ts b/src/commands/DeactivateCommand.ts index 39743f40..f32f1318 100644 --- a/src/commands/DeactivateCommand.ts +++ b/src/commands/DeactivateCommand.ts @@ -26,10 +26,10 @@ export async function execDeactivateCommand(roomId: string, event: any, mjolnir: const message = "I am not a Synapse administrator, or the endpoint is blocked"; const reply = RichReply.createFor(roomId, event, message, message); reply['msgtype'] = "m.notice"; - mjolnir.client.sendMessage(roomId, reply); + mjolnir.client.uncached.sendMessage(roomId, reply); return; } await mjolnir.deactivateSynapseUser(victim); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } diff --git a/src/commands/DumpRulesCommand.ts b/src/commands/DumpRulesCommand.ts index ca92e791..89dfba50 100644 --- a/src/commands/DumpRulesCommand.ts +++ b/src/commands/DumpRulesCommand.ts @@ -72,7 +72,7 @@ export async function execRulesMatchingCommand(roomId: string, event: any, mjoln } const reply = RichReply.createFor(roomId, event, text, html); reply["msgtype"] = "m.notice"; - return mjolnir.client.sendMessage(roomId, reply); + return mjolnir.client.uncached.sendMessage(roomId, reply); } // !mjolnir rules @@ -124,5 +124,5 @@ export async function execDumpRulesCommand(roomId: string, event: any, mjolnir: const reply = RichReply.createFor(roomId, event, text, html); reply["msgtype"] = "m.notice"; - return mjolnir.client.sendMessage(roomId, reply); + return mjolnir.client.uncached.sendMessage(roomId, reply); } diff --git a/src/commands/ImportCommand.ts b/src/commands/ImportCommand.ts index 0ac1ba53..49027d8e 100644 --- a/src/commands/ImportCommand.ts +++ b/src/commands/ImportCommand.ts @@ -21,19 +21,19 @@ import PolicyList from "../models/PolicyList"; // !mjolnir import export async function execImportCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { - const importRoomId = await mjolnir.client.resolveRoom(parts[2]); + const importRoomId = await mjolnir.client.uncached.resolveRoom(parts[2]); const list = mjolnir.lists.find(b => b.listShortcode === parts[3]) as PolicyList; if (!list) { const errMessage = "Unable to find list - check your shortcode."; const errReply = RichReply.createFor(roomId, event, errMessage, errMessage); errReply["msgtype"] = "m.notice"; - mjolnir.client.sendMessage(roomId, errReply); + mjolnir.client.uncached.sendMessage(roomId, errReply); return; } let importedRules = 0; - const state = await mjolnir.client.getRoomState(importRoomId); + const state = await mjolnir.client.uncached.getRoomState(importRoomId); for (const stateEvent of state) { const content = stateEvent['content'] || {}; if (!content || Object.keys(content).length === 0) continue; @@ -43,7 +43,7 @@ export async function execImportCommand(roomId: string, event: any, mjolnir: Mjo if (content['membership'] === 'ban') { const reason = content['reason'] || ''; - await mjolnir.client.sendNotice(mjolnir.managementRoomId, `Adding user ${stateEvent['state_key']} to ban list`); + await mjolnir.client.uncached.sendNotice(mjolnir.managementRoomId, `Adding user ${stateEvent['state_key']} to ban list`); await list.banEntity(EntityType.RULE_USER, stateEvent['state_key'], reason); importedRules++; } @@ -53,7 +53,7 @@ export async function execImportCommand(roomId: string, event: any, mjolnir: Mjo for (const server of content['deny']) { const reason = ""; - await mjolnir.client.sendNotice(mjolnir.managementRoomId, `Adding server ${server} to ban list`); + await mjolnir.client.uncached.sendNotice(mjolnir.managementRoomId, `Adding server ${server} to ban list`); await list.banEntity(EntityType.RULE_SERVER, server, reason); importedRules++; @@ -64,5 +64,5 @@ export async function execImportCommand(roomId: string, event: any, mjolnir: Mjo const message = `Imported ${importedRules} rules to ban list`; const reply = RichReply.createFor(roomId, event, message, message); reply['msgtype'] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); } diff --git a/src/commands/KickCommand.ts b/src/commands/KickCommand.ts index 8a5c85ee..b1518e59 100644 --- a/src/commands/KickCommand.ts +++ b/src/commands/KickCommand.ts @@ -33,7 +33,7 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln let replyMessage = "Wildcard bans require an addition `--force` argument to confirm"; const reply = RichReply.createFor(roomId, event, replyMessage, replyMessage); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); return; } @@ -43,7 +43,7 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln if (parts.length > 3) { let reasonIndex = 3; if (parts[3].startsWith("#") || parts[3].startsWith("!")) { - rooms = [await mjolnir.client.resolveRoom(parts[3])]; + rooms = [await mjolnir.client.uncached.resolveRoom(parts[3])]; reasonIndex = 4; } reason = parts.slice(reasonIndex).join(' ') || ''; @@ -51,7 +51,7 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln if (!reason) reason = ''; for (const protectedRoomId of rooms) { - const members = await mjolnir.client.getRoomMembers(protectedRoomId, undefined, ["join"], ["ban", "leave"]); + const members = await mjolnir.client.uncached.getRoomMembers(protectedRoomId, undefined, ["join"], ["ban", "leave"]); for (const member of members) { const victim = member.membershipFor; @@ -62,7 +62,7 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln if (!mjolnir.config.noop) { try { await mjolnir.taskQueue.push(async () => { - return mjolnir.client.kickUser(victim, protectedRoomId, reason); + return mjolnir.client.uncached.kickUser(victim, protectedRoomId, reason); }); } catch (e) { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "KickCommand", `An error happened while trying to kick ${victim}: ${e}`); @@ -74,5 +74,5 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln } } - return mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + return mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } diff --git a/src/commands/ListProtectedRoomsCommand.ts b/src/commands/ListProtectedRoomsCommand.ts index 66574ea6..15322ba8 100644 --- a/src/commands/ListProtectedRoomsCommand.ts +++ b/src/commands/ListProtectedRoomsCommand.ts @@ -41,5 +41,5 @@ export async function execListProtectedRooms(roomId: string, event: any, mjolnir const reply = RichReply.createFor(roomId, event, text, html); reply["msgtype"] = "m.notice"; - return mjolnir.client.sendMessage(roomId, reply); + return mjolnir.client.uncached.sendMessage(roomId, reply); } diff --git a/src/commands/MakeRoomAdminCommand.ts b/src/commands/MakeRoomAdminCommand.ts index ea748cfb..d34e7246 100644 --- a/src/commands/MakeRoomAdminCommand.ts +++ b/src/commands/MakeRoomAdminCommand.ts @@ -24,19 +24,19 @@ export async function execMakeRoomAdminCommand(roomId: string, event: any, mjoln const message = "Either the command is disabled or I am not running as homeserver administrator."; const reply = RichReply.createFor(roomId, event, message, message); reply['msgtype'] = "m.notice"; - mjolnir.client.sendMessage(roomId, reply); + mjolnir.client.uncached.sendMessage(roomId, reply); return; } - let err = await mjolnir.makeUserRoomAdmin(await mjolnir.client.resolveRoom(parts[3]), parts[4]); + let err = await mjolnir.makeUserRoomAdmin(await mjolnir.client.uncached.resolveRoom(parts[3]), parts[4]); if (err instanceof Error || typeof (err) === "string") { const errMsg = "Failed to process command:"; const message = typeof (err) === "string" ? `${errMsg}: ${err}` : `${errMsg}: ${err.message}`; const reply = RichReply.createFor(roomId, event, message, message); reply['msgtype'] = "m.notice"; - mjolnir.client.sendMessage(roomId, reply); + mjolnir.client.uncached.sendMessage(roomId, reply); return; } else { - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } } diff --git a/src/commands/ProtectionsCommands.ts b/src/commands/ProtectionsCommands.ts index 8df7ee93..2c4e878b 100644 --- a/src/commands/ProtectionsCommands.ts +++ b/src/commands/ProtectionsCommands.ts @@ -23,14 +23,14 @@ import { isListSetting } from "../protections/ProtectionSettings"; export async function execEnableProtection(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { try { await mjolnir.protectionManager.enableProtection(parts[2]); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } catch (e) { LogService.error("ProtectionsCommands", extractRequestError(e)); const message = `Error enabling protection '${parts[0]}' - check the name and try again.`; const reply = RichReply.createFor(roomId, event, message, message); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); } } @@ -104,7 +104,7 @@ export async function execConfigSetProtection(roomId: string, event: any, mjolni const reply = RichReply.createFor(roomId, event, message, message); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); } /* @@ -117,7 +117,7 @@ export async function execConfigAddProtection(roomId: string, event: any, mjolni const reply = RichReply.createFor(roomId, event, message, message); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); } /* @@ -130,7 +130,7 @@ export async function execConfigRemoveProtection(roomId: string, event: any, mjo const reply = RichReply.createFor(roomId, event, message, message); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); } /* @@ -151,7 +151,7 @@ export async function execConfigGetProtection(roomId: string, event: any, mjolni const errMsg = `Unknown protection: ${parts[0]}`; const errReply = RichReply.createFor(roomId, event, errMsg, errMsg); errReply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, errReply); + await mjolnir.client.uncached.sendMessage(roomId, errReply); return; } pickProtections = [parts[0]]; @@ -191,13 +191,13 @@ export async function execConfigGetProtection(roomId: string, event: any, mjolni const reply = RichReply.createFor(roomId, event, text, html); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); } // !mjolnir disable export async function execDisableProtection(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { await mjolnir.protectionManager.disableProtection(parts[2]); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } // !mjolnir protections @@ -217,5 +217,5 @@ export async function execListProtections(roomId: string, event: any, mjolnir: M const reply = RichReply.createFor(roomId, event, text, html); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); } diff --git a/src/commands/RedactCommand.ts b/src/commands/RedactCommand.ts index f809a60f..8107aaea 100644 --- a/src/commands/RedactCommand.ts +++ b/src/commands/RedactCommand.ts @@ -24,7 +24,7 @@ export async function execRedactCommand(roomId: string, event: any, mjolnir: Mjo let roomAlias: string|null = null; let limit = Number.parseInt(parts.length > 3 ? parts[3] : "", 10); // default to NaN for later if (parts.length > 3 && isNaN(limit)) { - roomAlias = await mjolnir.client.resolveRoom(parts[3]); + roomAlias = await mjolnir.client.uncached.resolveRoom(parts[3]); if (parts.length > 4) { limit = Number.parseInt(parts[4], 10); } @@ -33,21 +33,21 @@ export async function execRedactCommand(roomId: string, event: any, mjolnir: Mjo // Make sure we always have a limit set if (isNaN(limit)) limit = 1000; - const processingReactionId = await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'In Progress'); + const processingReactionId = await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'In Progress'); if (userId[0] !== '@') { // Assume it's a permalink const parsed = Permalinks.parseUrl(parts[2]); - const targetRoomId = await mjolnir.client.resolveRoom(parsed.roomIdOrAlias); - await mjolnir.client.redactEvent(targetRoomId, parsed.eventId); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); - await mjolnir.client.redactEvent(roomId, processingReactionId, 'done processing command'); + const targetRoomId = await mjolnir.client.uncached.resolveRoom(parsed.roomIdOrAlias); + await mjolnir.client.uncached.redactEvent(targetRoomId, parsed.eventId); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.redactEvent(roomId, processingReactionId, 'done processing command'); return; } const targetRoomIds = roomAlias ? [roomAlias] : mjolnir.protectedRoomsTracker.getProtectedRooms(); await redactUserMessagesIn(mjolnir.client, mjolnir.managementRoomOutput, userId, targetRoomIds, limit); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); - await mjolnir.client.redactEvent(roomId, processingReactionId, 'done processing'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.redactEvent(roomId, processingReactionId, 'done processing'); } diff --git a/src/commands/SetDefaultBanListCommand.ts b/src/commands/SetDefaultBanListCommand.ts index 2967c918..9d32d92f 100644 --- a/src/commands/SetDefaultBanListCommand.ts +++ b/src/commands/SetDefaultBanListCommand.ts @@ -27,10 +27,10 @@ export async function execSetDefaultListCommand(roomId: string, event: any, mjol const replyText = "No ban list with that shortcode was found."; const reply = RichReply.createFor(roomId, event, replyText, replyText); reply["msgtype"] = "m.notice"; - mjolnir.client.sendMessage(roomId, reply); + mjolnir.client.uncached.sendMessage(roomId, reply); return; } - await mjolnir.client.setAccountData(DEFAULT_LIST_EVENT_TYPE, { shortcode }); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.accountData.defaultList!.set({ shortcode }); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } diff --git a/src/commands/SetPowerLevelCommand.ts b/src/commands/SetPowerLevelCommand.ts index 58553ebd..aca3d48e 100644 --- a/src/commands/SetPowerLevelCommand.ts +++ b/src/commands/SetPowerLevelCommand.ts @@ -23,11 +23,11 @@ export async function execSetPowerLevelCommand(roomId: string, event: any, mjoln const level = Math.round(Number(parts[3])); const inRoom = parts[4]; - let targetRooms = inRoom ? [await mjolnir.client.resolveRoom(inRoom)] : mjolnir.protectedRoomsTracker.getProtectedRooms(); + let targetRooms = inRoom ? [await mjolnir.client.uncached.resolveRoom(inRoom)] : mjolnir.protectedRoomsTracker.getProtectedRooms(); for (const targetRoomId of targetRooms) { try { - await mjolnir.client.setUserPowerLevel(victim, targetRoomId, level); + await mjolnir.client.uncached.setUserPowerLevel(victim, targetRoomId, level); } catch (e) { const message = e.message || (e.body ? e.body.error : ''); await mjolnir.managementRoomOutput.logMessage(LogLevel.ERROR, "SetPowerLevelCommand", `Failed to set power level of ${victim} to ${level} in ${targetRoomId}: ${message}`, targetRoomId); @@ -35,5 +35,5 @@ export async function execSetPowerLevelCommand(roomId: string, event: any, mjoln } } - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } diff --git a/src/commands/ShutdownRoomCommand.ts b/src/commands/ShutdownRoomCommand.ts index edfb4243..56761be6 100644 --- a/src/commands/ShutdownRoomCommand.ts +++ b/src/commands/ShutdownRoomCommand.ts @@ -27,10 +27,10 @@ export async function execShutdownRoomCommand(roomId: string, event: any, mjolni const message = "I am not a Synapse administrator, or the endpoint is blocked"; const reply = RichReply.createFor(roomId, event, message, message); reply['msgtype'] = "m.notice"; - mjolnir.client.sendMessage(roomId, reply); + mjolnir.client.uncached.sendMessage(roomId, reply); return; } - await mjolnir.shutdownSynapseRoom(await mjolnir.client.resolveRoom(victim), reason); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.shutdownSynapseRoom(await mjolnir.client.uncached.resolveRoom(victim), reason); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } diff --git a/src/commands/SinceCommand.ts b/src/commands/SinceCommand.ts index daced1e3..84c99fb3 100644 --- a/src/commands/SinceCommand.ts +++ b/src/commands/SinceCommand.ts @@ -99,14 +99,14 @@ function getTokenAsString(name: string, token: ParseEntry): {error: string}|{ok: export async function execSinceCommand(destinationRoomId: string, event: any, mjolnir: Mjolnir, tokens: ParseEntry[]) { let result = await execSinceCommandAux(destinationRoomId, event, mjolnir, tokens); if ("error" in result) { - mjolnir.client.unstableApis.addReactionToEvent(destinationRoomId, event['event_id'], '❌'); + mjolnir.client.uncached.unstableApis.addReactionToEvent(destinationRoomId, event['event_id'], '❌'); mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "SinceCommand", result.error); const reply = RichReply.createFor(destinationRoomId, event, result.error, htmlEscape(result.error)); reply["msgtype"] = "m.notice"; - /* no need to await */ mjolnir.client.sendMessage(destinationRoomId, reply); + /* no need to await */ mjolnir.client.uncached.sendMessage(destinationRoomId, reply); } else { // Details have already been printed. - mjolnir.client.unstableApis.addReactionToEvent(destinationRoomId, event['event_id'], 'βœ…'); + mjolnir.client.uncached.unstableApis.addReactionToEvent(destinationRoomId, event['event_id'], 'βœ…'); } } @@ -200,7 +200,7 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni } continue; } else if (maybeRoom.startsWith("#") || maybeRoom.startsWith("!")) { - const roomId = await mjolnir.client.resolveRoom(maybeRoom); + const roomId = await mjolnir.client.uncached.resolveRoom(maybeRoom); if (!protectedRooms.has(roomId)) { return mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "SinceCommand", `This room is not protected: ${htmlEscape(roomId)}.`); } @@ -220,7 +220,7 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni }; } - const progressEventId = await mjolnir.client.unstableApis.addReactionToEvent(destinationRoomId, event['event_id'], '⏳'); + const progressEventId = await mjolnir.client.uncached.unstableApis.addReactionToEvent(destinationRoomId, event['event_id'], '⏳'); const reason: string | undefined = reasonParts?.join(" "); for (let targetRoomId of rooms) { @@ -235,7 +235,7 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni case Action.Kick: { for (let join of recentJoins) { try { - await mjolnir.client.kickUser(join.userId, targetRoomId, reason); + await mjolnir.client.uncached.kickUser(join.userId, targetRoomId, reason); results.succeeded.push(join.userId); } catch (ex) { LogService.warn("SinceCommand", "Error while attempting to kick user", ex); @@ -248,7 +248,7 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni case Action.Ban: { for (let join of recentJoins) { try { - await mjolnir.client.banUser(join.userId, targetRoomId, reason); + await mjolnir.client.uncached.banUser(join.userId, targetRoomId, reason); results.succeeded.push(join.userId); } catch (ex) { LogService.warn("SinceCommand", "Error while attempting to ban user", ex); @@ -259,13 +259,13 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni return formatResult("ban", targetRoomId, recentJoins, results); } case Action.Mute: { - const powerLevels = await mjolnir.client.getRoomStateEvent(targetRoomId, "m.room.power_levels", "") as {users: Record}; + const powerLevels = await mjolnir.client.uncached.getRoomStateEvent(targetRoomId, "m.room.power_levels", "") as {users: Record}; for (let join of recentJoins) { powerLevels.users[join.userId] = -1; } try { - await mjolnir.client.sendStateEvent(targetRoomId, "m.room.power_levels", "", powerLevels); + await mjolnir.client.uncached.sendStateEvent(targetRoomId, "m.room.power_levels", "", powerLevels); for (let join of recentJoins) { results.succeeded.push(join.userId); } @@ -279,13 +279,13 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni return formatResult("mute", targetRoomId, recentJoins, results); } case Action.Unmute: { - const powerLevels = await mjolnir.client.getRoomStateEvent(targetRoomId, "m.room.power_levels", "") as {users: Record, users_default?: number}; + const powerLevels = await mjolnir.client.uncached.getRoomStateEvent(targetRoomId, "m.room.power_levels", "") as {users: Record, users_default?: number}; for (let join of recentJoins) { // Restore default powerlevel. delete powerLevels.users[join.userId]; } try { - await mjolnir.client.sendStateEvent(targetRoomId, "m.room.power_levels", "", powerLevels); + await mjolnir.client.uncached.sendStateEvent(targetRoomId, "m.room.power_levels", "", powerLevels); for (let join of recentJoins) { results.succeeded.push(join.userId); } @@ -303,10 +303,10 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni const reply = RichReply.createFor(destinationRoomId, event, text, html); reply["msgtype"] = "m.notice"; - /* no need to await */ mjolnir.client.sendMessage(destinationRoomId, reply); + /* no need to await */ mjolnir.client.uncached.sendMessage(destinationRoomId, reply); } - await mjolnir.client.redactEvent(destinationRoomId, progressEventId); + await mjolnir.client.uncached.redactEvent(destinationRoomId, progressEventId); return {ok: undefined}; } diff --git a/src/commands/StatusCommand.ts b/src/commands/StatusCommand.ts index 6e597ec5..c628262f 100644 --- a/src/commands/StatusCommand.ts +++ b/src/commands/StatusCommand.ts @@ -86,7 +86,7 @@ async function showMjolnirStatus(roomId: string, event: any, mjolnir: Mjolnir) { const reply = RichReply.createFor(roomId, event, text, html); reply["msgtype"] = "m.notice"; - return mjolnir.client.sendMessage(roomId, reply); + return mjolnir.client.uncached.sendMessage(roomId, reply); } async function showProtectionStatus(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { @@ -108,7 +108,7 @@ async function showProtectionStatus(roomId: string, event: any, mjolnir: Mjolnir } const reply = RichReply.createFor(roomId, event, text, html); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); } /** @@ -149,7 +149,7 @@ async function showJoinsStatus(destinationRoomId: string, event: any, mjolnir: M const maxAgeHumanReadable = HUMANIZER.humanize(maxAgeMS, HUMANIZER_OPTIONS); let targetRoomId; try { - targetRoomId = await mjolnir.client.resolveRoom(targetRoomAliasOrId); + targetRoomId = await mjolnir.client.uncached.resolveRoom(targetRoomAliasOrId); } catch (ex) { return { html: `Cannot resolve room ${htmlEscape(targetRoomAliasOrId)}.`, @@ -171,6 +171,6 @@ async function showJoinsStatus(destinationRoomId: string, event: any, mjolnir: M })(); const reply = RichReply.createFor(destinationRoomId, event, text, html); reply["msgtype"] = "m.notice"; - return mjolnir.client.sendMessage(destinationRoomId, reply); + return mjolnir.client.uncached.sendMessage(destinationRoomId, reply); } diff --git a/src/commands/UnbanBanCommand.ts b/src/commands/UnbanBanCommand.ts index ad7d7c78..e60abaaa 100644 --- a/src/commands/UnbanBanCommand.ts +++ b/src/commands/UnbanBanCommand.ts @@ -16,9 +16,8 @@ limitations under the License. import { Mjolnir } from "../Mjolnir"; import PolicyList from "../models/PolicyList"; -import { extractRequestError, LogLevel, LogService, MatrixGlob, RichReply } from "matrix-bot-sdk"; +import { LogLevel, MatrixGlob, RichReply } from "matrix-bot-sdk"; import { RULE_ROOM, RULE_SERVER, RULE_USER, USER_RULE_TYPES } from "../models/ListRule"; -import { DEFAULT_LIST_EVENT_TYPE } from "./SetDefaultBanListCommand"; interface Arguments { list: PolicyList | null; @@ -29,16 +28,11 @@ interface Arguments { // Exported for tests export async function parseArguments(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]): Promise { - let defaultShortcode: string | null = null; - try { - const data: { shortcode: string } = await mjolnir.client.getAccountData(DEFAULT_LIST_EVENT_TYPE); - defaultShortcode = data['shortcode']; - } catch (e) { - LogService.warn("UnbanBanCommand", "Non-fatal error getting default ban list"); - LogService.warn("UnbanBanCommand", extractRequestError(e)); - - // Assume no default. + let data = mjolnir.accountData.defaultList!.get(); + if (!data || !("shortcode" in data) || typeof data["shortcode"] !== "string") { + data = { shortcode: null }; } + let defaultShortcode: string | null = data['shortcode']; let argumentIndex = 2; let ruleType: string | null = null; @@ -101,7 +95,7 @@ export async function parseArguments(roomId: string, event: any, mjolnir: Mjolni if (replyMessage) { const reply = RichReply.createFor(roomId, event, replyMessage, replyMessage); reply["msgtype"] = "m.notice"; - await mjolnir.client.sendMessage(roomId, reply); + await mjolnir.client.uncached.sendMessage(roomId, reply); return null; } @@ -119,7 +113,7 @@ export async function execBanCommand(roomId: string, event: any, mjolnir: Mjolni if (!bits) return; // error already handled await bits.list!.banEntity(bits.ruleType!, bits.entity, bits.reason); - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } // !mjolnir unban [apply:t/f] @@ -134,7 +128,7 @@ export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjol await mjolnir.managementRoomOutput.logMessage(LogLevel.INFO, "UnbanBanCommand", "Unbanning users that match glob: " + bits.entity); let unbannedSomeone = false; for (const protectedRoomId of mjolnir.protectedRoomsTracker.getProtectedRooms()) { - const members = await mjolnir.client.getRoomMembers(protectedRoomId, undefined, ['ban'], undefined); + const members = await mjolnir.client.uncached.getRoomMembers(protectedRoomId, undefined, ['ban'], undefined); await mjolnir.managementRoomOutput.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Found ${members.length} banned user(s)`); for (const member of members) { const victim = member.membershipFor; @@ -143,7 +137,7 @@ export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjol await mjolnir.managementRoomOutput.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Unbanning ${victim} in ${protectedRoomId}`, protectedRoomId); if (!mjolnir.config.noop) { - await mjolnir.client.unbanUser(victim, protectedRoomId); + await mjolnir.client.uncached.unbanUser(victim, protectedRoomId); } else { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "UnbanBanCommand", `Attempted to unban ${victim} in ${protectedRoomId} but Mjolnir is running in no-op mode`, protectedRoomId); } @@ -159,5 +153,5 @@ export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjol } } - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } diff --git a/src/commands/WatchUnwatchCommand.ts b/src/commands/WatchUnwatchCommand.ts index d0c695d6..a1fa1ffa 100644 --- a/src/commands/WatchUnwatchCommand.ts +++ b/src/commands/WatchUnwatchCommand.ts @@ -24,10 +24,10 @@ export async function execWatchCommand(roomId: string, event: any, mjolnir: Mjol const replyText = "Cannot watch list due to error - is that a valid room alias?"; const reply = RichReply.createFor(roomId, event, replyText, replyText); reply["msgtype"] = "m.notice"; - mjolnir.client.sendMessage(roomId, reply); + mjolnir.client.uncached.sendMessage(roomId, reply); return; } - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } // !mjolnir unwatch @@ -37,8 +37,8 @@ export async function execUnwatchCommand(roomId: string, event: any, mjolnir: Mj const replyText = "Cannot unwatch list due to error - is that a valid room alias?"; const reply = RichReply.createFor(roomId, event, replyText, replyText); reply["msgtype"] = "m.notice"; - mjolnir.client.sendMessage(roomId, reply); + mjolnir.client.uncached.sendMessage(roomId, reply); return; } - await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); + await mjolnir.client.uncached.unstableApis.addReactionToEvent(roomId, event['event_id'], 'βœ…'); } diff --git a/src/models/PolicyList.ts b/src/models/PolicyList.ts index 8fc4113a..7134d6e5 100644 --- a/src/models/PolicyList.ts +++ b/src/models/PolicyList.ts @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { extractRequestError, LogService, MatrixClient, UserID } from "matrix-bot-sdk"; +import { extractRequestError, LogService, UserID } from "matrix-bot-sdk"; import { EventEmitter } from "events"; import { ALL_RULE_TYPES, EntityType, ListRule, Recommendation, ROOM_RULE_TYPES, RULE_ROOM, RULE_SERVER, RULE_USER, SERVER_RULE_TYPES, USER_RULE_TYPES } from "./ListRule"; +import { CachingClient } from "../CachingClient"; export const SHORTCODE_EVENT_TYPE = "org.matrix.mjolnir.shortcode"; @@ -92,7 +93,7 @@ class PolicyList extends EventEmitter { * @param roomRef A sharable/clickable matrix URL that refers to the room. * @param client A matrix client that is used to read the state of the room when `updateList` is called. */ - constructor(public readonly roomId: string, public readonly roomRef: string, private client: MatrixClient) { + constructor(public readonly roomId: string, public readonly roomRef: string, private client: CachingClient) { super(); this.batcher = new UpdateBatcher(this); } @@ -156,7 +157,7 @@ class PolicyList extends EventEmitter { public set listShortcode(newShortcode: string) { const currentShortcode = this.shortcode; this.shortcode = newShortcode; - this.client.sendStateEvent(this.roomId, SHORTCODE_EVENT_TYPE, '', { shortcode: this.shortcode }).catch(err => { + this.client.uncached.sendStateEvent(this.roomId, SHORTCODE_EVENT_TYPE, '', { shortcode: this.shortcode }).catch(err => { LogService.error("PolicyList", extractRequestError(err)); if (this.shortcode === newShortcode) this.shortcode = currentShortcode; }); @@ -219,7 +220,7 @@ class PolicyList extends EventEmitter { public async banEntity(ruleType: string, entity: string, reason?: string): Promise { // '@' at the beginning of state keys is reserved. const stateKey = ruleType === RULE_USER ? '_' + entity.substring(1) : entity; - const event_id = await this.client.sendStateEvent(this.roomId, ruleType, stateKey, { + const event_id = await this.client.uncached.sendStateEvent(this.roomId, ruleType, stateKey, { entity, recommendation: Recommendation.Ban, reason: reason || '', @@ -248,14 +249,14 @@ class PolicyList extends EventEmitter { break; } const sendNullState = async (stateType: string, stateKey: string) => { - const event_id = await this.client.sendStateEvent(this.roomId, stateType, stateKey, {}); + const event_id = await this.client.uncached.sendStateEvent(this.roomId, stateType, stateKey, {}); this.updateForEvent(event_id); } const removeRule = async (rule: ListRule): Promise => { const stateKey = rule.sourceEvent.state_key; // We can't cheat and check our state cache because we normalize the event types to the most recent version. const typesToRemove = (await Promise.all( - typesToCheck.map(stateType => this.client.getRoomStateEvent(this.roomId, stateType, stateKey) + typesToCheck.map(stateType => this.client.uncached.getRoomStateEvent(this.roomId, stateType, stateKey) .then(_ => stateType) // We need the state type as getRoomState only returns the content, not the top level. .catch(e => e.statusCode === 404 ? null : Promise.reject(e)))) ).filter(e => e); // remove nulls. I don't know why TS still thinks there can be nulls after this?? @@ -277,7 +278,7 @@ class PolicyList extends EventEmitter { public async updateList(): Promise { let changes: ListRuleChange[] = []; - const state = await this.client.getRoomState(this.roomId); + const state = await this.client.uncached.getRoomState(this.roomId); for (const event of state) { if (event['state_key'] === '' && event['type'] === SHORTCODE_EVENT_TYPE) { this.shortcode = (event['content'] || {})['shortcode'] || null; diff --git a/src/protections/BasicFlooding.ts b/src/protections/BasicFlooding.ts index 10ef5827..fa618fed 100644 --- a/src/protections/BasicFlooding.ts +++ b/src/protections/BasicFlooding.ts @@ -64,7 +64,7 @@ export class BasicFlooding extends Protection { if (messageCount >= this.settings.maxPerMinute.value) { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "BasicFlooding", `Banning ${event['sender']} in ${roomId} for flooding (${messageCount} messages in the last minute)`, roomId); if (!mjolnir.config.noop) { - await mjolnir.client.banUser(event['sender'], roomId, "spam"); + await mjolnir.client.uncached.banUser(event['sender'], roomId, "spam"); } else { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "BasicFlooding", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId); } @@ -76,7 +76,7 @@ export class BasicFlooding extends Protection { // Redact all the things the user said too if (!mjolnir.config.noop) { for (const eventId of forUser.map(e => e.eventId)) { - await mjolnir.client.redactEvent(roomId, eventId, "spam"); + await mjolnir.client.uncached.redactEvent(roomId, eventId, "spam"); } } else { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "BasicFlooding", `Tried to redact messages for ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId); diff --git a/src/protections/DetectFederationLag.ts b/src/protections/DetectFederationLag.ts index 8ffb3f82..25a6ca43 100644 --- a/src/protections/DetectFederationLag.ts +++ b/src/protections/DetectFederationLag.ts @@ -563,7 +563,7 @@ export class DetectFederationLag extends Protection { // Ill-formed event. return; } - if (sender === await mjolnir.client.getUserId()) { + if (sender === mjolnir.client.userId) { // Let's not create loops. return; } @@ -591,8 +591,7 @@ export class DetectFederationLag extends Protection { this.lagPerRoom.set(roomId, roomInfo); } - const localDomain = new UserID(await mjolnir.client.getUserId()).domain - const isLocalDomain = domain === localDomain; + const isLocalDomain = domain === mjolnir.client.domain; const thresholds = isLocalDomain ? { @@ -617,7 +616,7 @@ export class DetectFederationLag extends Protection { } // Check whether an alarm needs to be raised! - const isLocalDomainOnAlert = roomInfo.isServerOnAlert(localDomain); + const isLocalDomainOnAlert = roomInfo.isServerOnAlert(mjolnir.client.domain); if (roomInfo.alerts > this.settings.numberOfLaggingFederatedHomeserversEnterWarningZone.value || isLocalDomainOnAlert) { // Raise the alarm! @@ -630,7 +629,7 @@ export class DetectFederationLag extends Protection { /* do not await */ mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "FederationLag", `Room ${roomId} is experiencing ${isLocalDomainOnAlert ? "LOCAL" : "federated"} lag since ${roomInfo.latestAlertStart}.\n${roomInfo.alerts} homeservers are lagging: ${[...roomInfo.serversOnAlert()].sort()} .\nRoom lag statistics: ${JSON.stringify(stats, null, 2)}.`); // Drop a state event, for the use of potential other bots. - const warnStateEventId = await mjolnir.client.sendStateEvent(mjolnir.managementRoomId, LAG_STATE_EVENT, roomId, { + const warnStateEventId = await mjolnir.client.uncached.sendStateEvent(mjolnir.managementRoomId, LAG_STATE_EVENT, roomId, { domains: [...roomInfo.serversOnAlert()], roomId, // We need to round the stats, as Matrix doesn't support floating-point @@ -648,7 +647,7 @@ export class DetectFederationLag extends Protection { if (roomInfo.warnStateEventId) { const warnStateEventId = roomInfo.warnStateEventId; roomInfo.warnStateEventId = null; - await mjolnir.client.redactEvent(mjolnir.managementRoomId, warnStateEventId, "Alert over"); + await mjolnir.client.uncached.redactEvent(mjolnir.managementRoomId, warnStateEventId, "Alert over"); } } } @@ -691,13 +690,12 @@ export class DetectFederationLag extends Protection { */ public async statusCommand(mjolnir: Mjolnir, subcommand: string[]): Promise<{html: string, text: string} | null> { const roomId = subcommand[0] || "*"; - const localDomain = new UserID(await mjolnir.client.getUserId()).domain; const annotatedStats = (roomInfo: RoomInfo) => { const stats = roomInfo.globalStats()?.round(); if (!stats) { return null; } - const isLocalDomainOnAlert = roomInfo.isServerOnAlert(localDomain); + const isLocalDomainOnAlert = roomInfo.isServerOnAlert(mjolnir.client.domain); const numberOfServersOnAlert = roomInfo.alerts; if (isLocalDomainOnAlert) { (stats as any)["warning"] = "Local homeserver is lagging"; @@ -713,7 +711,7 @@ export class DetectFederationLag extends Protection { const result: any = {}; for (const [perRoomId, perRoomInfo] of this.lagPerRoom.entries()) { - const key = await mjolnir.client.getPublishedAlias(perRoomId) || perRoomId; + const key = await mjolnir.client.uncached.getPublishedAlias(perRoomId) || perRoomId; result[key] = annotatedStats(perRoomInfo); } text = JSON.stringify(result, null, 2); diff --git a/src/protections/FirstMessageIsImage.ts b/src/protections/FirstMessageIsImage.ts index dd7dd419..bdc2b752 100644 --- a/src/protections/FirstMessageIsImage.ts +++ b/src/protections/FirstMessageIsImage.ts @@ -58,7 +58,7 @@ export class FirstMessageIsImage extends Protection { if (isMedia && this.justJoined[roomId].includes(event['sender'])) { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "FirstMessageIsImage", `Banning ${event['sender']} for posting an image as the first thing after joining in ${roomId}.`); if (!mjolnir.config.noop) { - await mjolnir.client.banUser(event['sender'], roomId, "spam"); + await mjolnir.client.uncached.banUser(event['sender'], roomId, "spam"); } else { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "FirstMessageIsImage", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId); } @@ -69,7 +69,7 @@ export class FirstMessageIsImage extends Protection { // Redact the event if (!mjolnir.config.noop) { - await mjolnir.client.redactEvent(roomId, event['event_id'], "spam"); + await mjolnir.client.uncached.redactEvent(roomId, event['event_id'], "spam"); } else { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "FirstMessageIsImage", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId); } diff --git a/src/protections/JoinWaveShortCircuit.ts b/src/protections/JoinWaveShortCircuit.ts index 3746a139..20a83365 100644 --- a/src/protections/JoinWaveShortCircuit.ts +++ b/src/protections/JoinWaveShortCircuit.ts @@ -89,7 +89,7 @@ export class JoinWaveShortCircuit extends Protection { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "JoinWaveShortCircuit", `Setting ${roomId} to invite-only as more than ${this.settings.maxPer.value} users have joined over the last ${this.settings.timescaleMinutes.value} minutes (since ${this.joinBuckets[roomId].lastBucketStart})`, roomId); if (!mjolnir.config.noop) { - await mjolnir.client.sendStateEvent(roomId, "m.room.join_rules", "", {"join_rule": "invite"}) + await mjolnir.client.uncached.sendStateEvent(roomId, "m.room.join_rules", "", {"join_rule": "invite"}) } else { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "JoinWaveShortCircuit", `Tried to set ${roomId} to invite-only, but Mjolnir is running in no-op mode`, roomId); } diff --git a/src/protections/MessageIsMedia.ts b/src/protections/MessageIsMedia.ts index 64b3728c..3fd3f0ce 100644 --- a/src/protections/MessageIsMedia.ts +++ b/src/protections/MessageIsMedia.ts @@ -16,7 +16,7 @@ limitations under the License. import { Protection } from "./IProtection"; import { Mjolnir } from "../Mjolnir"; -import { LogLevel, Permalinks, UserID } from "matrix-bot-sdk"; +import { LogLevel, Permalinks } from "matrix-bot-sdk"; export class MessageIsMedia extends Protection { @@ -40,10 +40,10 @@ export class MessageIsMedia extends Protection { const formattedBody = content['formatted_body'] || ''; const isMedia = msgtype === 'm.image' || msgtype === 'm.video' || formattedBody.toLowerCase().includes(' p.name); - await this.mjolnir.client.setAccountData(ENABLED_PROTECTIONS_EVENT_TYPE, { enabled: protections }); + await this.mjolnir.client.uncached.setAccountData(ENABLED_PROTECTIONS_EVENT_TYPE, { enabled: protections }); } /* * Enable a protection by name and persist its enable state in to a state event @@ -204,7 +204,7 @@ export class ProtectionManager { public async getProtectionSettings(protectionName: string): Promise<{ [setting: string]: any }> { let savedSettings: { [setting: string]: any } = {} try { - savedSettings = await this.mjolnir.client.getRoomStateEvent( + savedSettings = await this.mjolnir.client.uncached.getRoomStateEvent( this.mjolnir.managementRoomId, 'org.matrix.mjolnir.setting', protectionName ); } catch { @@ -241,9 +241,9 @@ export class ProtectionManager { if (consequence.name === "alert") { /* take no additional action, just print the below message to management room */ } else if (consequence.name === "ban") { - await this.mjolnir.client.banUser(sender, roomId, "abuse detected"); + await this.mjolnir.client.uncached.banUser(sender, roomId, "abuse detected"); } else if (consequence.name === "redact") { - await this.mjolnir.client.redactEvent(roomId, eventId, "abuse detected"); + await this.mjolnir.client.uncached.redactEvent(roomId, eventId, "abuse detected"); } else { throw new Error(`unknown consequence ${consequence.name}`); } @@ -253,7 +253,7 @@ export class ProtectionManager { + ` against ${htmlEscape(sender)}` + ` in ${htmlEscape(roomId)}` + ` (reason: ${htmlEscape(consequence.reason)})`; - await this.mjolnir.client.sendMessage(this.mjolnir.managementRoomId, { + await this.mjolnir.client.uncached.sendMessage(this.mjolnir.managementRoomId, { msgtype: "m.notice", body: message, [CONSEQUENCE_EVENT_DATA]: { @@ -270,7 +270,7 @@ export class ProtectionManager { private async handleEvent(roomId: string, event: any) { if (this.mjolnir.protectedRoomsTracker.getProtectedRooms().includes(roomId)) { - if (event['sender'] === await this.mjolnir.client.getUserId()) return; // Ignore ourselves + if (event['sender'] === this.mjolnir.client.userId) return; // Ignore ourselves // Iterate all the enabled protections for (const protection of this.enabledProtections) { @@ -282,7 +282,7 @@ export class ProtectionManager { LogService.error("ProtectionManager", "Error handling protection: " + protection.name); LogService.error("ProtectionManager", "Failed event: " + eventPermalink); LogService.error("ProtectionManager", extractRequestError(e)); - await this.mjolnir.client.sendNotice(this.mjolnir.managementRoomId, "There was an error processing an event through a protection - see log for details. Event: " + eventPermalink); + await this.mjolnir.client.uncached.sendNotice(this.mjolnir.managementRoomId, "There was an error processing an event through a protection - see log for details. Event: " + eventPermalink); continue; } @@ -307,9 +307,7 @@ export class ProtectionManager { const additionalPermissions = this.requiredProtectionPermissions(); try { - const ownUserId = await this.mjolnir.client.getUserId(); - - const powerLevels = await this.mjolnir.client.getRoomStateEvent(roomId, "m.room.power_levels", ""); + const powerLevels = await this.mjolnir.client.uncached.getRoomStateEvent(roomId, "m.room.power_levels", ""); if (!powerLevels) { // noinspection ExceptionCaughtLocallyJS throw new Error("Missing power levels state event"); @@ -328,7 +326,7 @@ export class ProtectionManager { const kick = plDefault(powerLevels['kick'], 50); const redact = plDefault(powerLevels['redact'], 50); - const userLevel = plDefault(users[ownUserId], usersDefault); + const userLevel = plDefault(users[this.mjolnir.client.userId], usersDefault); const aclLevel = plDefault(events["m.room.server_acl"], stateDefault); // Wants: ban, kick, redact, m.room.server_acl diff --git a/src/protections/TrustedReporters.ts b/src/protections/TrustedReporters.ts index 99c1a186..ab58541d 100644 --- a/src/protections/TrustedReporters.ts +++ b/src/protections/TrustedReporters.ts @@ -73,16 +73,16 @@ export class TrustedReporters extends Protection { } if (reporters.size === this.settings.redactThreshold.value) { met.push("redact"); - await mjolnir.client.redactEvent(roomId, event.id, "abuse detected"); + await mjolnir.client.uncached.redactEvent(roomId, event.id, "abuse detected"); } if (reporters.size === this.settings.banThreshold.value) { met.push("ban"); - await mjolnir.client.banUser(event.userId, roomId, "abuse detected"); + await mjolnir.client.uncached.banUser(event.userId, roomId, "abuse detected"); } if (met.length > 0) { - await mjolnir.client.sendMessage(mjolnir.config.managementRoom, { + await mjolnir.client.uncached.sendMessage(mjolnir.config.managementRoom, { msgtype: "m.notice", body: `message ${event.id} reported by ${[...reporters].join(', ')}. ` + `actions: ${met.join(', ')}` diff --git a/src/queues/EventRedactionQueue.ts b/src/queues/EventRedactionQueue.ts index 4e3d84c0..33080663 100644 --- a/src/queues/EventRedactionQueue.ts +++ b/src/queues/EventRedactionQueue.ts @@ -13,11 +13,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -import { LogLevel, MatrixClient } from "matrix-bot-sdk" +import { LogLevel } from "matrix-bot-sdk" import { ERROR_KIND_FATAL } from "../ErrorCache"; import { RoomUpdateError } from "../models/RoomUpdateError"; import { redactUserMessagesIn } from "../utils"; import ManagementRoomOutput from "../ManagementRoomOutput"; +import { CachingClient } from "../CachingClient"; export interface QueuedRedaction { /** The room which the redaction will take place in. */ @@ -27,7 +28,7 @@ export interface QueuedRedaction { * Called by the EventRedactionQueue. * @param client A MatrixClient to use to carry out the redaction. */ - redact(client: MatrixClient, managementRoom: ManagementRoomOutput): Promise + redact(client: CachingClient, managementRoom: ManagementRoomOutput): Promise /** * Used to test whether the redaction is the equivalent to another redaction. * @param redaction Another QueuedRedaction to test if this redaction is an equivalent to. @@ -47,7 +48,7 @@ export class RedactUserInRoom implements QueuedRedaction { this.roomId = roomId; } - public async redact(client: MatrixClient, managementRoom: ManagementRoomOutput) { + public async redact(client: CachingClient, managementRoom: ManagementRoomOutput) { await managementRoom.logMessage(LogLevel.DEBUG, "Mjolnir", `Redacting events from ${this.userId} in room ${this.roomId}.`); await redactUserMessagesIn(client, managementRoom, this.userId, [this.roomId]); } @@ -107,7 +108,7 @@ export class EventRedactionQueue { * @param limitToRoomId If the roomId is provided, only redactions for that room will be processed. * @returns A description of any errors encountered by each QueuedRedaction that was processed. */ - public async process(client: MatrixClient, managementRoom: ManagementRoomOutput, limitToRoomId?: string): Promise { + public async process(client: CachingClient, managementRoom: ManagementRoomOutput, limitToRoomId?: string): Promise { const errors: RoomUpdateError[] = []; const redact = async (currentBatch: QueuedRedaction[]) => { for (const redaction of currentBatch) { diff --git a/src/queues/UnlistedUserRedactionQueue.ts b/src/queues/UnlistedUserRedactionQueue.ts index 9c50baaa..5ff02a7b 100644 --- a/src/queues/UnlistedUserRedactionQueue.ts +++ b/src/queues/UnlistedUserRedactionQueue.ts @@ -43,7 +43,7 @@ export class UnlistedUserRedactionQueue { try { LogService.info("AutomaticRedactionQueue", `Redacting event because the user is listed as bad: ${permalink}`) if (!mjolnir.config.noop) { - await mjolnir.client.redactEvent(roomId, event['event_id']); + await mjolnir.client.uncached.redactEvent(roomId, event['event_id']); } else { await mjolnir.managementRoomOutput.logMessage(LogLevel.WARN, "AutomaticRedactionQueue", `Tried to redact ${permalink} but Mjolnir is running in no-op mode`); } diff --git a/src/report/ReportManager.ts b/src/report/ReportManager.ts index c75e636e..e06542e3 100644 --- a/src/report/ReportManager.ts +++ b/src/report/ReportManager.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { PowerLevelAction } from "matrix-bot-sdk/lib/models/PowerLevelAction"; -import { LogService, UserID } from "matrix-bot-sdk"; +import { LogService } from "matrix-bot-sdk"; import { htmlToText } from "html-to-text"; import { htmlEscape } from "../utils"; import { JSDOM } from 'jsdom'; @@ -79,7 +79,7 @@ export class ReportManager extends EventEmitter { constructor(public mjolnir: Mjolnir) { super(); // Configure bot interactions. - mjolnir.client.on("room.event", async (roomId, event) => { + mjolnir.client.uncached.on("room.event", async (roomId, event) => { try { switch (event["type"]) { case "m.reaction": { @@ -125,7 +125,7 @@ export class ReportManager extends EventEmitter { * @param event The reaction. */ public async handleReaction({ roomId, event }: { roomId: string, event: any }) { - if (event.sender === await this.mjolnir.client.getUserId()) { + if (event.sender === this.mjolnir.client.userId) { // Let's not react to our own reactions. return; } @@ -144,8 +144,8 @@ export class ReportManager extends EventEmitter { // Get the original event. let initialNoticeReport: IReport | undefined, confirmationReport: IReportWithAction | undefined; try { - let originalEvent = await this.mjolnir.client.getEvent(roomId, relation.event_id); - if (originalEvent.sender !== await this.mjolnir.client.getUserId()) { + let originalEvent = await this.mjolnir.client.uncached.getEvent(roomId, relation.event_id); + if (originalEvent.sender !== this.mjolnir.client.userId) { // Let's not handle reactions to events we didn't send as // some setups have two or more Mjolnir's in the same management room. return; @@ -208,7 +208,7 @@ export class ReportManager extends EventEmitter { }) } else { LogService.info("ReportManager::handleReaction", "User", event["sender"], "cancelled action", matches[1]); - this.mjolnir.client.redactEvent(this.mjolnir.managementRoomId, relation.event_id, "Action cancelled"); + this.mjolnir.client.uncached.redactEvent(this.mjolnir.managementRoomId, relation.event_id, "Action cancelled"); } return; @@ -242,15 +242,15 @@ export class ReportManager extends EventEmitter { [ABUSE_ACTION_CONFIRMATION_KEY]: confirmationReport }; - let requestConfirmationEventId = await this.mjolnir.client.sendMessage(this.mjolnir.managementRoomId, confirmation); - await this.mjolnir.client.sendEvent(this.mjolnir.managementRoomId, "m.reaction", { + let requestConfirmationEventId = await this.mjolnir.client.uncached.sendMessage(this.mjolnir.managementRoomId, confirmation); + await this.mjolnir.client.uncached.sendEvent(this.mjolnir.managementRoomId, "m.reaction", { "m.relates_to": { "rel_type": "m.annotation", "event_id": requestConfirmationEventId, "key": `πŸ†— ${action.emoji} ${await action.title(this, initialNoticeReport)} [${action.label}][${CONFIRM}]` } }); - await this.mjolnir.client.sendEvent(this.mjolnir.managementRoomId, "m.reaction", { + await this.mjolnir.client.uncached.sendEvent(this.mjolnir.managementRoomId, "m.reaction", { "m.relates_to": { "rel_type": "m.annotation", "event_id": requestConfirmationEventId, @@ -304,14 +304,14 @@ export class ReportManager extends EventEmitter { error = ex; } if (error) { - this.mjolnir.client.sendEvent(this.mjolnir.managementRoomId, "m.reaction", { + this.mjolnir.client.uncached.sendEvent(this.mjolnir.managementRoomId, "m.reaction", { "m.relates_to": { "rel_type": "m.annotation", "event_id": failureEventId, "key": `${action.emoji} ❌` } }); - this.mjolnir.client.sendEvent(this.mjolnir.managementRoomId, "m.notice", { + this.mjolnir.client.uncached.sendEvent(this.mjolnir.managementRoomId, "m.notice", { "body": error.message || "", "m.relationship": { "rel_type": "m.reference", @@ -319,7 +319,7 @@ export class ReportManager extends EventEmitter { } }) } else { - this.mjolnir.client.sendEvent(this.mjolnir.managementRoomId, "m.reaction", { + this.mjolnir.client.uncached.sendEvent(this.mjolnir.managementRoomId, "m.reaction", { "m.relates_to": { "rel_type": "m.annotation", "event_id": successEventId, @@ -327,10 +327,10 @@ export class ReportManager extends EventEmitter { } }); if (onSuccessRemoveEventId) { - this.mjolnir.client.redactEvent(this.mjolnir.managementRoomId, onSuccessRemoveEventId, "Action complete"); + this.mjolnir.client.uncached.redactEvent(this.mjolnir.managementRoomId, onSuccessRemoveEventId, "Action complete"); } if (response) { - this.mjolnir.client.sendMessage(this.mjolnir.managementRoomId, { + this.mjolnir.client.uncached.sendMessage(this.mjolnir.managementRoomId, { msgtype: "m.notice", "formatted_body": response, format: "org.matrix.custom.html", @@ -467,7 +467,7 @@ class IgnoreBadReport implements IUIAction { return "Ignore bad report"; } public async execute(manager: ReportManager, report: IReportWithAction): Promise { - await manager.mjolnir.client.sendEvent(manager.mjolnir.managementRoomId, "m.room.message", + await manager.mjolnir.client.uncached.sendEvent(manager.mjolnir.managementRoomId, "m.room.message", { msgtype: "m.notice", body: "Report classified as invalid", @@ -494,7 +494,7 @@ class RedactMessage implements IUIAction { public needsConfirmation = true; public async canExecute(manager: ReportManager, report: IReport): Promise { try { - return await manager.mjolnir.client.userHasPowerLevelForAction(await manager.mjolnir.client.getUserId(), report.room_id, PowerLevelAction.RedactEvents); + return await manager.mjolnir.client.uncached.userHasPowerLevelForAction(manager.mjolnir.client.userId, report.room_id, PowerLevelAction.RedactEvents); } catch (ex) { return false; } @@ -506,7 +506,7 @@ class RedactMessage implements IUIAction { return `Redact event ${report.event_id}`; } public async execute(manager: ReportManager, report: IReport, _moderationRoomId: string): Promise { - await manager.mjolnir.client.redactEvent(report.room_id, report.event_id); + await manager.mjolnir.client.uncached.redactEvent(report.room_id, report.event_id); return; } } @@ -520,7 +520,7 @@ class KickAccused implements IUIAction { public needsConfirmation = true; public async canExecute(manager: ReportManager, report: IReport): Promise { try { - return await manager.mjolnir.client.userHasPowerLevelForAction(await manager.mjolnir.client.getUserId(), report.room_id, PowerLevelAction.Kick); + return await manager.mjolnir.client.uncached.userHasPowerLevelForAction(await manager.mjolnir.client.userId, report.room_id, PowerLevelAction.Kick); } catch (ex) { return false; } @@ -532,7 +532,7 @@ class KickAccused implements IUIAction { return `Kick ${htmlEscape(report.accused_id)} from room ${htmlEscape(report.room_alias_or_id)}`; } public async execute(manager: ReportManager, report: IReport): Promise { - await manager.mjolnir.client.kickUser(report.accused_id, report.room_id); + await manager.mjolnir.client.uncached.kickUser(report.accused_id, report.room_id); return; } } @@ -546,7 +546,7 @@ class MuteAccused implements IUIAction { public needsConfirmation = true; public async canExecute(manager: ReportManager, report: IReport): Promise { try { - return await manager.mjolnir.client.userHasPowerLevelFor(await manager.mjolnir.client.getUserId(), report.room_id, "m.room.power_levels", true); + return await manager.mjolnir.client.uncached.userHasPowerLevelFor(manager.mjolnir.client.userId, report.room_id, "m.room.power_levels", true); } catch (ex) { return false; } @@ -558,7 +558,7 @@ class MuteAccused implements IUIAction { return `Mute ${htmlEscape(report.accused_id)} in room ${htmlEscape(report.room_alias_or_id)}`; } public async execute(manager: ReportManager, report: IReport): Promise { - await manager.mjolnir.client.setUserPowerLevel(report.accused_id, report.room_id, -1); + await manager.mjolnir.client.uncached.setUserPowerLevel(report.accused_id, report.room_id, -1); return; } } @@ -572,7 +572,7 @@ class BanAccused implements IUIAction { public needsConfirmation = true; public async canExecute(manager: ReportManager, report: IReport): Promise { try { - return await manager.mjolnir.client.userHasPowerLevelForAction(await manager.mjolnir.client.getUserId(), report.room_id, PowerLevelAction.Ban); + return await manager.mjolnir.client.uncached.userHasPowerLevelForAction(manager.mjolnir.client.userId, report.room_id, PowerLevelAction.Ban); } catch (ex) { return false; } @@ -584,7 +584,7 @@ class BanAccused implements IUIAction { return `Ban ${htmlEscape(report.accused_id)} from room ${htmlEscape(report.room_alias_or_id)}`; } public async execute(manager: ReportManager, report: IReport): Promise { - await manager.mjolnir.client.banUser(report.accused_id, report.room_id); + await manager.mjolnir.client.uncached.banUser(report.accused_id, report.room_id); return; } } @@ -634,7 +634,7 @@ class EscalateToServerModerationRoom implements IUIAction { return false; } try { - await manager.mjolnir.client.getEvent(report.room_id, report.event_id); + await manager.mjolnir.client.uncached.getEvent(report.room_id, report.event_id); } catch (ex) { // We can't fetch the event. return false; @@ -645,10 +645,10 @@ class EscalateToServerModerationRoom implements IUIAction { return "Escalate"; } public async help(manager: ReportManager, _report: IReport): Promise { - return `Escalate report to ${getHomeserver(await manager.mjolnir.client.getUserId())} server moderators`; + return `Escalate report to ${manager.mjolnir.client.domain} server moderators`; } public async execute(manager: ReportManager, report: IReport, _moderationRoomId: string, displayManager: DisplayManager): Promise { - let event = await manager.mjolnir.client.getEvent(report.room_id, report.event_id); + let event = await manager.mjolnir.client.uncached.getEvent(report.room_id, report.event_id); // Display the report and UI directly in the management room, as if it had been // received from /report. @@ -691,7 +691,7 @@ class DisplayManager { let roomAliasOrId = roomId; try { - roomAliasOrId = await this.owner.mjolnir.client.getPublishedAlias(roomId) || roomId; + roomAliasOrId = await this.owner.mjolnir.client.uncached.getPublishedAlias(roomId) || roomId; } catch (ex) { // Ignore. } @@ -721,12 +721,12 @@ class DisplayManager { let reporterDisplayName: string, accusedDisplayName: string; try { - reporterDisplayName = (await this.owner.mjolnir.client.getUserProfile(reporterId))["displayname"] || reporterId; + reporterDisplayName = (await this.owner.mjolnir.client.uncached.getUserProfile(reporterId))["displayname"] || reporterId; } catch (ex) { reporterDisplayName = ""; } try { - accusedDisplayName = (await this.owner.mjolnir.client.getUserProfile(accusedId))["displayname"] || accusedId; + accusedDisplayName = (await this.owner.mjolnir.client.uncached.getUserProfile(accusedId))["displayname"] || accusedId; } catch (ex) { accusedDisplayName = ""; } @@ -876,7 +876,7 @@ class DisplayManager { [ABUSE_REPORT_KEY]: report }; - let noticeEventId = await this.owner.mjolnir.client.sendMessage(this.owner.mjolnir.managementRoomId, notice); + let noticeEventId = await this.owner.mjolnir.client.uncached.sendMessage(this.owner.mjolnir.managementRoomId, notice); if (kind !== Kind.ERROR) { // Now let's display buttons. for (let [label, action] of ACTIONS) { @@ -884,7 +884,7 @@ class DisplayManager { if (!await action.canExecute(this.owner, report, moderationRoomId)) { continue; } - await this.owner.mjolnir.client.sendEvent(this.owner.mjolnir.managementRoomId, "m.reaction", { + await this.owner.mjolnir.client.uncached.sendEvent(this.owner.mjolnir.managementRoomId, "m.reaction", { "m.relates_to": { "rel_type": "m.annotation", "event_id": noticeEventId, @@ -945,7 +945,3 @@ const ACTION_LIST = [ * As a map of labels => actions. */ const ACTIONS = new Map(ACTION_LIST.map(action => [action.label, action])); - -function getHomeserver(userId: string): string { - return new UserID(userId).domain -} diff --git a/src/report/ReportPoller.ts b/src/report/ReportPoller.ts index d1a03c20..9397f4fe 100644 --- a/src/report/ReportPoller.ts +++ b/src/report/ReportPoller.ts @@ -14,12 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Mjolnir, REPORT_POLL_EVENT_TYPE } from "../Mjolnir"; +import { Mjolnir } from "../Mjolnir"; import { ReportManager } from './ReportManager'; import { LogLevel } from "matrix-bot-sdk"; class InvalidStateError extends Error { } +/** + * Synapse will tell us where we last got to on polling reports, so we need + * to store that for pagination on further polls + */ + export const REPORT_POLL_EVENT_TYPE = "org.matrix.mjolnir.report_poll"; + /** * A class to poll synapse's report endpoint, so we can act on new reports * @@ -65,7 +71,7 @@ export class ReportPoller { next_token: number | undefined } | undefined; try { - response_ = await this.mjolnir.client.doRequest( + response_ = await this.mjolnir.client.uncached.doRequest( "GET", "/_synapse/admin/v1/event_reports", { @@ -87,7 +93,7 @@ export class ReportPoller { let event: any; // `any` because `handleServerAbuseReport` uses `any` try { - event = (await this.mjolnir.client.doRequest( + event = (await this.mjolnir.client.uncached.doRequest( "GET", `/_synapse/admin/v1/rooms/${report.room_id}/context/${report.event_id}?limit=1` )).event; @@ -112,7 +118,7 @@ export class ReportPoller { if (response.next_token !== undefined) { this.from = response.next_token; try { - await this.mjolnir.client.setAccountData(REPORT_POLL_EVENT_TYPE, { from: response.next_token }); + await this.mjolnir.client.uncached.setAccountData(REPORT_POLL_EVENT_TYPE, { from: response.next_token }); } catch (ex) { await this.mjolnir.managementRoomOutput.logMessage(LogLevel.ERROR, "getAbuseReports", `failed to update progress: ${ex}`); } @@ -130,9 +136,19 @@ export class ReportPoller { this.schedulePoll(); } - public start(startFrom: number) { + public async start() { if (this.timeout === null) { - this.from = startFrom; + let reportPollSetting: { from: number } = { from: 0 }; + try { + reportPollSetting = await this.mjolnir.client.uncached.getAccountData(REPORT_POLL_EVENT_TYPE); + } catch (err) { + if (err.body?.errcode !== "M_NOT_FOUND") { + throw err; + } else { + this.mjolnir.managementRoomOutput.logMessage(LogLevel.INFO, "Mjolnir@startup", "report poll setting does not exist yet"); + } + } + this.from = reportPollSetting.from; this.schedulePoll(); } else { throw new InvalidStateError("cannot start an already started poll"); diff --git a/src/utils.ts b/src/utils.ts index 07009c6f..508de7cd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -17,7 +17,6 @@ limitations under the License. import { LogLevel, LogService, - MatrixClient, MatrixGlob, getRequestFn, setRequestFn, @@ -25,6 +24,7 @@ import { import { ClientRequest, IncomingMessage } from "http"; import { default as parseDuration } from "parse-duration"; import ManagementRoomOutput from "./ManagementRoomOutput"; +import { CachingClient } from "./CachingClient"; // Define a few aliases to simplify parsing durations. @@ -77,7 +77,7 @@ export function isTrueJoinEvent(event: any): boolean { * @param limit The number of messages to redact from most recent first. If the limit is reached then no further messages will be redacted. * @param noop Whether to operate in noop mode. */ -export async function redactUserMessagesIn(client: MatrixClient, managementRoom: ManagementRoomOutput, userIdOrGlob: string, targetRoomIds: string[], limit = 1000, noop = false) { +export async function redactUserMessagesIn(client: CachingClient, managementRoom: ManagementRoomOutput, userIdOrGlob: string, targetRoomIds: string[], limit = 1000, noop = false) { for (const targetRoomId of targetRoomIds) { await managementRoom.logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Fetching sent messages for ${userIdOrGlob} in ${targetRoomId} to redact...`, targetRoomId); @@ -85,7 +85,7 @@ export async function redactUserMessagesIn(client: MatrixClient, managementRoom: for (const victimEvent of eventsToRedact) { await managementRoom.logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Redacting ${victimEvent['event_id']} in ${targetRoomId}`, targetRoomId); if (!noop) { - await client.redactEvent(targetRoomId, victimEvent['event_id']); + await client.uncached.redactEvent(targetRoomId, victimEvent['event_id']); } else { await managementRoom.logMessage(LogLevel.WARN, "utils#redactUserMessagesIn", `Tried to redact ${victimEvent['event_id']} in ${targetRoomId} but Mjolnir is running in no-op mode`, targetRoomId); } @@ -110,7 +110,7 @@ export async function redactUserMessagesIn(client: MatrixClient, managementRoom: * The callback will only be called if there are any relevant events. * @returns {Promise} Resolves when either: the limit has been reached, no relevant events could be found or there is no more timeline to paginate. */ -export async function getMessagesByUserIn(client: MatrixClient, sender: string, roomId: string, limit: number, cb: (events: any[]) => void): Promise { +export async function getMessagesByUserIn(client: CachingClient, sender: string, roomId: string, limit: number, cb: (events: any[]) => void): Promise { const isGlob = sender.includes("*"); const roomEventFilter = { rooms: [roomId], @@ -154,7 +154,7 @@ export async function getMessagesByUserIn(client: MatrixClient, sender: string, ... from ? { from } : {} }; LogService.info("utils", "Backfilling with token: " + from); - return client.doRequest("GET", `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/messages`, qs); + return client.uncached.doRequest("GET", `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/messages`, qs); } let processed = 0; diff --git a/test/integration/banListTest.ts b/test/integration/banListTest.ts index 40add9bd..87abf140 100644 --- a/test/integration/banListTest.ts +++ b/test/integration/banListTest.ts @@ -7,6 +7,7 @@ import { getFirstReaction } from "./commands/commandUtils"; import { getMessagesByUserIn } from "../../src/utils"; import { Mjolnir } from "../../src/Mjolnir"; import { ALL_RULE_TYPES, RULE_SERVER, RULE_USER, SERVER_RULE_TYPES } from "../../src/models/ListRule"; +import { CachingClient } from "../../src/CachingClient"; /** * Create a policy rule in a policy room. @@ -30,32 +31,32 @@ describe("Test: Updating the PolicyList", function() { it("Calculates what has changed correctly.", async function() { this.timeout(10000); const mjolnir: Mjolnir = this.mjolnir! - const moderator = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } }); - const banListId = await mjolnir.client.createRoom({ invite: [await moderator.getUserId()] }); + const moderator: MatrixClient = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } }); + const banListId = await mjolnir.client.uncached.createRoom({ invite: [await moderator.getUserId()] }); const banList = new PolicyList(banListId, banListId, mjolnir.client); - await mjolnir.client.setUserPowerLevel(await moderator.getUserId(), banListId, 100); + await mjolnir.client.uncached.setUserPowerLevel(await moderator.getUserId(), banListId, 100); assert.equal(banList.allRules.length, 0); // Test adding a new rule - await createPolicyRule(mjolnir.client, banListId, RULE_USER, '@added:localhost:9999', ''); + await createPolicyRule(mjolnir.client.uncached, banListId, RULE_USER, '@added:localhost:9999', ''); let changes: ListRuleChange[] = await banList.updateList(); assert.equal(changes.length, 1, 'There should only be one change'); assert.equal(changes[0].changeType, ChangeType.Added); - assert.equal(changes[0].sender, await mjolnir.client.getUserId()); + assert.equal(changes[0].sender, await mjolnir.client.uncached.getUserId()); assert.equal(banList.userRules.length, 1); assert.equal(banList.allRules.length, 1); // Test modifiying a rule - let originalEventId = await createPolicyRule(mjolnir.client, banListId, RULE_USER, '@modified:localhost:9999', ''); + let originalEventId = await createPolicyRule(mjolnir.client.uncached, banListId, RULE_USER, '@modified:localhost:9999', ''); await banList.updateList(); - let modifyingEventId = await createPolicyRule(mjolnir.client, banListId, RULE_USER, '@modified:localhost:9999', 'modified reason'); + let modifyingEventId = await createPolicyRule(mjolnir.client.uncached, banListId, RULE_USER, '@modified:localhost:9999', 'modified reason'); changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Modified); assert.equal(changes[0].previousState['event_id'], originalEventId, 'There should be a previous state event for a modified rule'); assert.equal(changes[0].event['event_id'], modifyingEventId); - let modifyingAgainEventId = await createPolicyRule(mjolnir.client, banListId, RULE_USER, '@modified:localhost:9999', 'modified again'); + let modifyingAgainEventId = await createPolicyRule(mjolnir.client.uncached, banListId, RULE_USER, '@modified:localhost:9999', 'modified again'); changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Modified); @@ -64,10 +65,10 @@ describe("Test: Updating the PolicyList", function() { assert.equal(banList.userRules.length, 2, 'There should be two rules, one for @modified:localhost:9999 and one for @added:localhost:9999'); // Test redacting a rule - const redactThis = await createPolicyRule(mjolnir.client, banListId, RULE_USER, '@redacted:localhost:9999', ''); + const redactThis = await createPolicyRule(mjolnir.client.uncached, banListId, RULE_USER, '@redacted:localhost:9999', ''); await banList.updateList(); assert.equal(banList.userRules.filter(r => r.entity === '@redacted:localhost:9999').length, 1); - await mjolnir.client.redactEvent(banListId, redactThis); + await mjolnir.client.uncached.redactEvent(banListId, redactThis); changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Removed); @@ -79,10 +80,10 @@ describe("Test: Updating the PolicyList", function() { // Test soft redaction of a rule const softRedactedEntity = '@softredacted:localhost:9999' - await createPolicyRule(mjolnir.client, banListId, RULE_USER, softRedactedEntity, ''); + await createPolicyRule(mjolnir.client.uncached, banListId, RULE_USER, softRedactedEntity, ''); await banList.updateList(); assert.equal(banList.userRules.filter(r => r.entity === softRedactedEntity).length, 1); - await mjolnir.client.sendStateEvent(banListId, RULE_USER, `rule:${softRedactedEntity}`, {}); + await mjolnir.client.uncached.sendStateEvent(banListId, RULE_USER, `rule:${softRedactedEntity}`, {}); changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Removed); @@ -92,25 +93,25 @@ describe("Test: Updating the PolicyList", function() { assert.equal(banList.userRules.filter(r => r.entity === softRedactedEntity).length, 0, 'The rule should have been removed'); // Now test a double soft redaction just to make sure stuff doesn't explode - await mjolnir.client.sendStateEvent(banListId, RULE_USER, `rule:${softRedactedEntity}`, {}); + await mjolnir.client.uncached.sendStateEvent(banListId, RULE_USER, `rule:${softRedactedEntity}`, {}); changes = await banList.updateList(); assert.equal(changes.length, 0, "It shouldn't detect a double soft redaction as a change, it should be seen as adding an invalid rule."); assert.equal(banList.userRules.filter(r => r.entity === softRedactedEntity).length, 0, 'The rule should have been removed'); // Test that different (old) rule types will be modelled as the latest event type. - originalEventId = await createPolicyRule(mjolnir.client, banListId, 'org.matrix.mjolnir.rule.user', '@old:localhost:9999', ''); + originalEventId = await createPolicyRule(mjolnir.client.uncached, banListId, 'org.matrix.mjolnir.rule.user', '@old:localhost:9999', ''); changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Added); assert.equal(banList.userRules.filter(r => r.entity === '@old:localhost:9999').length, 1); - modifyingEventId = await createPolicyRule(mjolnir.client, banListId, 'm.room.rule.user', '@old:localhost:9999', 'modified reason'); + modifyingEventId = await createPolicyRule(mjolnir.client.uncached, banListId, 'm.room.rule.user', '@old:localhost:9999', 'modified reason'); changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Modified); assert.equal(changes[0].event['event_id'], modifyingEventId); assert.equal(changes[0].previousState['event_id'], originalEventId, 'There should be a previous state event for a modified rule'); assert.equal(banList.userRules.filter(r => r.entity === '@old:localhost:9999').length, 1); - modifyingAgainEventId = await createPolicyRule(mjolnir.client, banListId, RULE_USER, '@old:localhost:9999', 'changes again'); + modifyingAgainEventId = await createPolicyRule(mjolnir.client.uncached, banListId, RULE_USER, '@old:localhost:9999', 'changes again'); changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Modified); @@ -121,18 +122,18 @@ describe("Test: Updating the PolicyList", function() { it("Will remove rules with old types when they are 'soft redacted' with a different but more recent event type.", async function() { this.timeout(3000); const mjolnir: Mjolnir = this.mjolnir! - const moderator = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" }} ); - const banListId = await mjolnir.client.createRoom({ invite: [await moderator.getUserId()] }); + const moderator: MatrixClient = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" }} ); + const banListId = await mjolnir.client.uncached.createRoom({ invite: [await moderator.getUserId()] }); const banList = new PolicyList(banListId, banListId, mjolnir.client); - await mjolnir.client.setUserPowerLevel(await moderator.getUserId(), banListId, 100); + await mjolnir.client.uncached.setUserPowerLevel(await moderator.getUserId(), banListId, 100); const entity = '@old:localhost:9999'; - let originalEventId = await createPolicyRule(mjolnir.client, banListId, 'm.room.rule.user', entity, ''); + let originalEventId = await createPolicyRule(mjolnir.client.uncached, banListId, 'm.room.rule.user', entity, ''); let changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Added); assert.equal(banList.userRules.filter(rule => rule.entity === entity).length, 1, 'There should be a rule stored that we just added...') - let softRedactingEventId = await mjolnir.client.sendStateEvent(banListId, RULE_USER, `rule:${entity}`, {}); + let softRedactingEventId = await mjolnir.client.uncached.sendStateEvent(banListId, RULE_USER, `rule:${entity}`, {}); changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Removed); @@ -142,18 +143,18 @@ describe("Test: Updating the PolicyList", function() { }) it("A rule of the most recent type won't be deleted when an old rule is deleted for the same entity.", async function() { const mjolnir: Mjolnir = this.mjolnir! - const moderator = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } }); - const banListId = await mjolnir.client.createRoom({ invite: [await moderator.getUserId()] }); + const moderator: MatrixClient = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } }); + const banListId = await mjolnir.client.uncached.createRoom({ invite: [await moderator.getUserId()] }); const banList = new PolicyList(banListId, banListId, mjolnir.client); - await mjolnir.client.setUserPowerLevel(await moderator.getUserId(), banListId, 100); + await mjolnir.client.uncached.setUserPowerLevel(await moderator.getUserId(), banListId, 100); const entity = '@old:localhost:9999'; - let originalEventId = await createPolicyRule(mjolnir.client, banListId, 'm.room.rule.user', entity, ''); + let originalEventId = await createPolicyRule(mjolnir.client.uncached, banListId, 'm.room.rule.user', entity, ''); let changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Added); assert.equal(banList.userRules.filter(rule => rule.entity === entity).length, 1, 'There should be a rule stored that we just added...') - let updatedEventId = await createPolicyRule(mjolnir.client, banListId, RULE_USER, entity, ''); + let updatedEventId = await createPolicyRule(mjolnir.client.uncached, banListId, RULE_USER, entity, ''); changes = await banList.updateList(); // If in the future you change this and it fails, it's really subjective whether this constitutes a modification, since the only thing that has changed // is the rule type. The actual content is identical. @@ -164,13 +165,13 @@ describe("Test: Updating the PolicyList", function() { assert.equal(banList.userRules.filter(rule => rule.entity === entity).length, 1, 'Only the latest version of the rule gets returned.'); // Now we delete the old version of the rule without consequence. - await mjolnir.client.sendStateEvent(banListId, 'm.room.rule.user', `rule:${entity}`, {}); + await mjolnir.client.uncached.sendStateEvent(banListId, 'm.room.rule.user', `rule:${entity}`, {}); changes = await banList.updateList(); assert.equal(changes.length, 0); assert.equal(banList.userRules.filter(rule => rule.entity === entity).length, 1, 'The rule should still be active.'); // And we can still delete the new version of the rule. - let softRedactingEventId = await mjolnir.client.sendStateEvent(banListId, RULE_USER, `rule:${entity}`, {}); + let softRedactingEventId = await mjolnir.client.uncached.sendStateEvent(banListId, RULE_USER, `rule:${entity}`, {}); changes = await banList.updateList(); assert.equal(changes.length, 1); assert.equal(changes[0].changeType, ChangeType.Removed); @@ -180,10 +181,10 @@ describe("Test: Updating the PolicyList", function() { }) it('Test: PolicyList Supports all entity types.', async function () { const mjolnir: Mjolnir = this.mjolnir! - const banListId = await mjolnir.client.createRoom(); + const banListId = await mjolnir.client.uncached.createRoom(); const banList = new PolicyList(banListId, banListId, mjolnir.client); for (let i = 0; i < ALL_RULE_TYPES.length; i++) { - await createPolicyRule(mjolnir.client, banListId, ALL_RULE_TYPES[i], `*${i}*`, ''); + await createPolicyRule(mjolnir.client.uncached, banListId, ALL_RULE_TYPES[i], `*${i}*`, ''); } let changes: ListRuleChange[] = await banList.updateList(); assert.equal(changes.length, ALL_RULE_TYPES.length); @@ -194,13 +195,13 @@ describe("Test: Updating the PolicyList", function() { describe('Test: We do not respond to recommendations other than m.ban in the PolicyList', function() { it('Will not respond to a rule that has a different recommendation to m.ban (or the unstable equivalent).', async function() { const mjolnir: Mjolnir = this.mjolnir! - const banListId = await mjolnir.client.createRoom(); + const banListId = await mjolnir.client.uncached.createRoom(); const banList = new PolicyList(banListId, banListId, mjolnir.client); - await createPolicyRule(mjolnir.client, banListId, RULE_SERVER, 'exmaple.org', '', { recommendation: 'something that is not m.ban' }); + await createPolicyRule(mjolnir.client.uncached, banListId, RULE_SERVER, 'exmaple.org', '', { recommendation: 'something that is not m.ban' }); let changes: ListRuleChange[] = await banList.updateList(); assert.equal(changes.length, 1, 'There should only be one change'); assert.equal(changes[0].changeType, ChangeType.Added); - assert.equal(changes[0].sender, await mjolnir.client.getUserId()); + assert.equal(changes[0].sender, await mjolnir.client.uncached.getUserId()); // We really don't want things that aren't m.ban to end up being accessible in these APIs. assert.equal(banList.serverRules.length, 0, `We should have an empty serverRules, got ${JSON.stringify(banList.serverRules)}`); assert.equal(banList.allRules.length, 0, `We should have an empty allRules, got ${JSON.stringify(banList.allRules)}`); @@ -210,12 +211,12 @@ describe('Test: We do not respond to recommendations other than m.ban in the Pol describe('Test: We will not be able to ban ourselves via ACL.', function() { it('We do not ban ourselves when we put ourselves into the policy list.', async function() { const mjolnir: Mjolnir = this.mjolnir - const serverName = new UserID(await mjolnir.client.getUserId()).domain; - const banListId = await mjolnir.client.createRoom(); + const serverName = new UserID(await mjolnir.client.uncached.getUserId()).domain; + const banListId = await mjolnir.client.uncached.createRoom(); const banList = new PolicyList(banListId, banListId, mjolnir.client); - await createPolicyRule(mjolnir.client, banListId, RULE_SERVER, serverName, ''); - await createPolicyRule(mjolnir.client, banListId, RULE_SERVER, 'evil.com', ''); - await createPolicyRule(mjolnir.client, banListId, RULE_SERVER, '*', ''); + await createPolicyRule(mjolnir.client.uncached, banListId, RULE_SERVER, serverName, ''); + await createPolicyRule(mjolnir.client.uncached, banListId, RULE_SERVER, 'evil.com', ''); + await createPolicyRule(mjolnir.client.uncached, banListId, RULE_SERVER, '*', ''); // We should still intern the matching rules rule. let changes: ListRuleChange[] = await banList.updateList(); assert.equal(banList.serverRules.length, 3); @@ -231,16 +232,16 @@ describe('Test: We will not be able to ban ourselves via ACL.', function() { describe('Test: ACL updates will batch when rules are added in succession.', function() { it('Will batch ACL updates if we spam rules into a PolicyList', async function() { const mjolnir: Mjolnir = this.mjolnir! - const serverName: string = new UserID(await mjolnir.client.getUserId()).domain - const moderator = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } }); + const serverName: string = new UserID(await mjolnir.client.uncached.getUserId()).domain + const moderator: MatrixClient = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } }); await moderator.joinRoom(mjolnir.managementRoomId); - const mjolnirId = await mjolnir.client.getUserId(); + const mjolnirId = await mjolnir.client.uncached.getUserId(); // Setup some protected rooms so we can check their ACL state later. const protectedRooms: string[] = []; for (let i = 0; i < 5; i++) { const room = await moderator.createRoom({ invite: [mjolnirId] }); - await mjolnir.client.joinRoom(room); + await mjolnir.client.uncached.joinRoom(room); await moderator.setUserPowerLevel(mjolnirId, room, 100); await mjolnir.addProtectedRoom(room); protectedRooms.push(room); @@ -250,13 +251,13 @@ describe('Test: ACL updates will batch when rules are added in succession.', fun await mjolnir.protectedRoomsTracker.syncLists(mjolnir.config.verboseLogging); await Promise.all(protectedRooms.map(async room => { // We're going to need timeline pagination I'm afraid. - const roomAcl = await mjolnir.client.getRoomStateEvent(room, "m.room.server_acl", ""); + const roomAcl = await mjolnir.client.uncached.getRoomStateEvent(room, "m.room.server_acl", ""); assert.equal(roomAcl?.deny?.length ?? 0, 0, 'There should be no entries in the deny ACL.'); })); // Flood the watched list with banned servers, which should prompt Mjolnir to update server ACL in protected rooms. const banListId = await moderator.createRoom({ invite: [mjolnirId] }); - await mjolnir.client.joinRoom(banListId); + await mjolnir.client.uncached.joinRoom(banListId); await mjolnir.watchList(Permalinks.forRoom(banListId)); const acl = new ServerAcl(serverName).denyIpAddresses().allowServer("*"); const evilServerCount = 200; @@ -278,7 +279,7 @@ describe('Test: ACL updates will batch when rules are added in succession.', fun // Check each of the protected rooms for ACL events and make sure they were batched and are correct. await Promise.all(protectedRooms.map(async room => { - const roomAcl = await mjolnir.client.getRoomStateEvent(room, "m.room.server_acl", ""); + const roomAcl = await mjolnir.client.uncached.getRoomStateEvent(room, "m.room.server_acl", ""); if (!acl.matches(roomAcl)) { assert.fail(`Room ${room} doesn't have the correct ACL: ${JSON.stringify(roomAcl, null, 2)}`) } @@ -299,32 +300,32 @@ describe('Test: unbaning entities via the PolicyList.', function() { afterEach(function() { this.moderator?.stop(); }); it('Will remove rules that have legacy types', async function() { const mjolnir: Mjolnir = this.mjolnir! - const serverName: string = new UserID(await mjolnir.client.getUserId()).domain + const serverName: string = new UserID(await mjolnir.client.uncached.getUserId()).domain const moderator: MatrixClient = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } }); this.moderator = moderator; await moderator.joinRoom(mjolnir.managementRoomId); - const mjolnirId = await mjolnir.client.getUserId(); + const mjolnirId = await mjolnir.client.uncached.getUserId(); // We'll make 1 protected room to test ACLs in. const protectedRoom = await moderator.createRoom({ invite: [mjolnirId] }); - await mjolnir.client.joinRoom(protectedRoom); + await mjolnir.client.uncached.joinRoom(protectedRoom); await moderator.setUserPowerLevel(mjolnirId, protectedRoom, 100); await mjolnir.addProtectedRoom(protectedRoom); // If a previous test hasn't cleaned up properly, these rooms will be populated by bogus ACLs at this point. await mjolnir.protectedRoomsTracker.syncLists(mjolnir.config.verboseLogging); // If this is not present, then it means the room isn't being protected, which is really bad. - const roomAcl = await mjolnir.client.getRoomStateEvent(protectedRoom, "m.room.server_acl", ""); + const roomAcl = await mjolnir.client.uncached.getRoomStateEvent(protectedRoom, "m.room.server_acl", ""); assert.equal(roomAcl?.deny?.length ?? 0, 0, 'There should be no entries in the deny ACL.'); // Create some legacy rules on a PolicyList. const banListId = await moderator.createRoom({ invite: [mjolnirId] }); - await moderator.setUserPowerLevel(await mjolnir.client.getUserId(), banListId, 100); + await moderator.setUserPowerLevel(await mjolnir.client.uncached.getUserId(), banListId, 100); await moderator.sendStateEvent(banListId, 'org.matrix.mjolnir.shortcode', '', { shortcode: "unban-test" }); - await mjolnir.client.joinRoom(banListId); + await mjolnir.client.uncached.joinRoom(banListId); await mjolnir.watchList(Permalinks.forRoom(banListId)); // we use this to compare changes. - const banList = new PolicyList(banListId, banListId, moderator); + const banList = new PolicyList(banListId, banListId, new CachingClient(moderator)); // we need two because we need to test the case where an entity has all rule types in the list // and another one that only has one (so that we would hit 404 while looking up state) const olderBadServer = "old.evil.example" @@ -341,7 +342,7 @@ describe('Test: unbaning entities via the PolicyList.', function() { // Check that we have setup our test properly and therefore evil.example is banned. const acl = new ServerAcl(serverName).denyIpAddresses().allowServer("*").denyServer(olderBadServer).denyServer(newerBadServer); - const protectedAcl = await mjolnir.client.getRoomStateEvent(protectedRoom, "m.room.server_acl", ""); + const protectedAcl = await mjolnir.client.uncached.getRoomStateEvent(protectedRoom, "m.room.server_acl", ""); if (!acl.matches(protectedAcl)) { assert.fail(`Room ${protectedRoom} doesn't have the correct ACL: ${JSON.stringify(roomAcl, null, 2)}`); } @@ -363,7 +364,7 @@ describe('Test: unbaning entities via the PolicyList.', function() { // Confirm that the server is unbanned. await banList.updateList(); assert.equal(banList.allRules.length, 0); - const aclAfter = await mjolnir.client.getRoomStateEvent(protectedRoom, "m.room.server_acl", ""); + const aclAfter = await mjolnir.client.uncached.getRoomStateEvent(protectedRoom, "m.room.server_acl", ""); assert.equal(aclAfter.deny.length, 0, 'Should be no servers denied anymore'); }) }) @@ -372,16 +373,16 @@ describe('Test: should apply bans to the most recently active rooms first', func it('Applies bans to the most recently active rooms first', async function() { this.timeout(180000) const mjolnir: Mjolnir = this.mjolnir! - const serverName: string = new UserID(await mjolnir.client.getUserId()).domain - const moderator = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } }); + const serverName: string = new UserID(await mjolnir.client.uncached.getUserId()).domain + const moderator: MatrixClient = await newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } }); await moderator.joinRoom(mjolnir.managementRoomId); - const mjolnirId = await mjolnir.client.getUserId(); + const mjolnirId = await mjolnir.client.uncached.getUserId(); // Setup some protected rooms so we can check their ACL state later. const protectedRooms: string[] = []; for (let i = 0; i < 10; i++) { const room = await moderator.createRoom({ invite: [mjolnirId] }); - await mjolnir.client.joinRoom(room); + await mjolnir.client.uncached.joinRoom(room); await moderator.setUserPowerLevel(mjolnirId, room, 100); await mjolnir.addProtectedRoom(room); protectedRooms.push(room); @@ -390,13 +391,13 @@ describe('Test: should apply bans to the most recently active rooms first', func // If a previous test hasn't cleaned up properly, these rooms will be populated by bogus ACLs at this point. await mjolnir.protectedRoomsTracker.syncLists(mjolnir.config.verboseLogging); await Promise.all(protectedRooms.map(async room => { - const roomAcl = await mjolnir.client.getRoomStateEvent(room, "m.room.server_acl", "").catch(e => e.statusCode === 404 ? { deny: [] } : Promise.reject(e)); + const roomAcl = await mjolnir.client.uncached.getRoomStateEvent(room, "m.room.server_acl", "").catch(e => e.statusCode === 404 ? { deny: [] } : Promise.reject(e)); assert.equal(roomAcl?.deny?.length ?? 0, 0, 'There should be no entries in the deny ACL.'); })); // Flood the watched list with banned servers, which should prompt Mjolnir to update server ACL in protected rooms. const banListId = await moderator.createRoom({ invite: [mjolnirId] }); - await mjolnir.client.joinRoom(banListId); + await mjolnir.client.uncached.joinRoom(banListId); await mjolnir.watchList(Permalinks.forRoom(banListId)); await mjolnir.protectedRoomsTracker.syncLists(mjolnir.config.verboseLogging); @@ -423,7 +424,7 @@ describe('Test: should apply bans to the most recently active rooms first', func // collect all the rooms that received an ACL event. const aclRooms: any[] = await new Promise(async resolve => { const rooms: any[] = []; - this.mjolnir.client.on('room.event', (room: string, event: any) => { + this.mjolnir.client.uncached.on('room.event', (room: string, event: any) => { if (protectedRooms.includes(room)) { rooms.push(room); }