2019-09-27 17:15:10 -04:00
|
|
|
/*
|
|
|
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2019-11-06 20:46:49 -05:00
|
|
|
import { LogLevel, LogService, MatrixClient, Permalinks } from "matrix-bot-sdk";
|
2019-09-27 17:15:10 -04:00
|
|
|
import BanList, { ALL_RULE_TYPES } from "./models/BanList";
|
|
|
|
import { applyServerAcls } from "./actions/ApplyAcl";
|
|
|
|
import { RoomUpdateError } from "./models/RoomUpdateError";
|
|
|
|
import { COMMAND_PREFIX, handleCommand } from "./commands/CommandHandler";
|
2019-09-27 21:54:13 -04:00
|
|
|
import { applyUserBans } from "./actions/ApplyBan";
|
2019-10-04 23:02:37 -04:00
|
|
|
import config from "./config";
|
2019-11-06 20:46:49 -05:00
|
|
|
import { logMessage } from "./LogProxy";
|
2019-11-06 21:17:11 -05:00
|
|
|
import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache";
|
2019-09-27 17:15:10 -04:00
|
|
|
|
2019-10-04 23:22:18 -04:00
|
|
|
export const STATE_NOT_STARTED = "not_started";
|
|
|
|
export const STATE_CHECKING_PERMISSIONS = "checking_permissions";
|
|
|
|
export const STATE_SYNCING = "syncing";
|
|
|
|
export const STATE_RUNNING = "running";
|
|
|
|
|
2019-10-08 15:58:31 -04:00
|
|
|
const WATCHED_LISTS_EVENT_TYPE = "org.matrix.mjolnir.watched_lists";
|
|
|
|
|
2019-09-27 17:15:10 -04:00
|
|
|
export class Mjolnir {
|
2019-09-27 18:04:08 -04:00
|
|
|
|
|
|
|
private displayName: string;
|
|
|
|
private localpart: string;
|
2019-10-04 23:22:18 -04:00
|
|
|
private currentState: string = STATE_NOT_STARTED;
|
2019-09-27 18:04:08 -04:00
|
|
|
|
2019-09-27 17:15:10 -04:00
|
|
|
constructor(
|
|
|
|
public readonly client: MatrixClient,
|
|
|
|
public readonly protectedRooms: { [roomId: string]: string },
|
2019-10-08 13:25:57 -04:00
|
|
|
private banLists: BanList[],
|
2019-09-27 17:15:10 -04:00
|
|
|
) {
|
|
|
|
client.on("room.event", this.handleEvent.bind(this));
|
|
|
|
|
|
|
|
client.on("room.message", async (roomId, event) => {
|
2019-11-06 20:46:49 -05:00
|
|
|
if (roomId !== config.managementRoom) return;
|
2019-09-27 17:15:10 -04:00
|
|
|
if (!event['content']) return;
|
|
|
|
|
|
|
|
const content = event['content'];
|
2019-09-27 18:04:08 -04:00
|
|
|
if (content['msgtype'] === "m.text" && content['body']) {
|
|
|
|
const prefixes = [COMMAND_PREFIX, this.localpart + ":", this.displayName + ":", await client.getUserId() + ":"];
|
2019-09-27 22:07:16 -04:00
|
|
|
const prefixUsed = prefixes.find(p => content['body'].startsWith(p));
|
|
|
|
if (!prefixUsed) return;
|
|
|
|
|
|
|
|
// rewrite the event body to make the prefix uniform (in case the bot has spaces in its display name)
|
|
|
|
event['content']['body'] = COMMAND_PREFIX + content['body'].substring(prefixUsed.length);
|
2019-11-06 20:46:49 -05:00
|
|
|
LogService.info("Mjolnir", `Command being run by ${event['sender']}: ${event['content']['body']}`);
|
2019-09-27 18:04:08 -04:00
|
|
|
|
2019-09-27 17:15:10 -04:00
|
|
|
await client.sendReadReceipt(roomId, event['event_id']);
|
|
|
|
return handleCommand(roomId, event, this);
|
|
|
|
}
|
|
|
|
});
|
2019-09-27 18:04:08 -04:00
|
|
|
|
|
|
|
client.getUserId().then(userId => {
|
|
|
|
this.localpart = userId.split(':')[0].substring(1);
|
|
|
|
return client.getUserProfile(userId);
|
|
|
|
}).then(profile => {
|
|
|
|
if (profile['displayname']) {
|
|
|
|
this.displayName = profile['displayname'];
|
|
|
|
}
|
|
|
|
})
|
2019-09-27 17:15:10 -04:00
|
|
|
}
|
|
|
|
|
2019-10-08 13:25:57 -04:00
|
|
|
public get lists(): BanList[] {
|
|
|
|
return this.banLists;
|
|
|
|
}
|
|
|
|
|
2019-10-04 23:22:18 -04:00
|
|
|
public get state(): string {
|
|
|
|
return this.currentState;
|
|
|
|
}
|
|
|
|
|
2019-09-27 17:15:10 -04:00
|
|
|
public start() {
|
2019-10-04 23:22:34 -04:00
|
|
|
return this.client.start().then(async () => {
|
|
|
|
this.currentState = STATE_CHECKING_PERMISSIONS;
|
|
|
|
if (config.verifyPermissionsOnStartup) {
|
2019-11-06 20:46:49 -05:00
|
|
|
await logMessage(LogLevel.INFO, "Mjolnir@startup", "Checking permissions...");
|
2019-10-04 23:38:50 -04:00
|
|
|
await this.verifyPermissions(config.verboseLogging);
|
2019-10-04 23:22:34 -04:00
|
|
|
}
|
|
|
|
}).then(async () => {
|
|
|
|
this.currentState = STATE_SYNCING;
|
2019-10-04 23:02:37 -04:00
|
|
|
if (config.syncOnStartup) {
|
2019-11-06 20:46:49 -05:00
|
|
|
await logMessage(LogLevel.INFO, "Mjolnir@startup", "Syncing lists...");
|
2019-10-08 13:25:57 -04:00
|
|
|
await this.buildWatchedBanLists();
|
2019-10-04 23:38:50 -04:00
|
|
|
await this.syncLists(config.verboseLogging);
|
2019-10-04 23:02:37 -04:00
|
|
|
}
|
2019-10-04 23:22:34 -04:00
|
|
|
}).then(async () => {
|
|
|
|
this.currentState = STATE_RUNNING;
|
2019-11-06 20:46:49 -05:00
|
|
|
await logMessage(LogLevel.INFO, "Mjolnir@startup", "Startup complete. Now monitoring rooms.");
|
2019-10-04 23:02:37 -04:00
|
|
|
});
|
2019-09-27 17:15:10 -04:00
|
|
|
}
|
|
|
|
|
2019-10-08 15:58:31 -04:00
|
|
|
public async watchList(roomRef: string): Promise<BanList> {
|
2019-10-08 13:25:57 -04:00
|
|
|
const joinedRooms = await this.client.getJoinedRooms();
|
|
|
|
const permalink = Permalinks.parseUrl(roomRef);
|
2019-10-08 15:58:31 -04:00
|
|
|
if (!permalink.roomIdOrAlias) return null;
|
2019-10-08 13:25:57 -04:00
|
|
|
|
|
|
|
const roomId = await this.client.resolveRoom(permalink.roomIdOrAlias);
|
|
|
|
if (!joinedRooms.includes(roomId)) {
|
|
|
|
await this.client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers);
|
|
|
|
}
|
|
|
|
|
2019-10-08 15:58:31 -04:00
|
|
|
if (this.banLists.find(b => b.roomId === roomId)) return null;
|
2019-10-08 13:25:57 -04:00
|
|
|
|
|
|
|
const list = new BanList(roomId, roomRef, this.client);
|
|
|
|
await list.updateList();
|
|
|
|
this.banLists.push(list);
|
2019-10-08 15:58:31 -04:00
|
|
|
|
|
|
|
await this.client.setAccountData(WATCHED_LISTS_EVENT_TYPE, {
|
|
|
|
references: this.banLists.map(b => b.roomRef),
|
|
|
|
});
|
|
|
|
|
|
|
|
return list;
|
2019-10-08 13:25:57 -04:00
|
|
|
}
|
|
|
|
|
2019-10-08 15:58:31 -04:00
|
|
|
public async unwatchList(roomRef: string): Promise<BanList> {
|
2019-10-08 13:25:57 -04:00
|
|
|
const permalink = Permalinks.parseUrl(roomRef);
|
2019-10-08 15:58:31 -04:00
|
|
|
if (!permalink.roomIdOrAlias) return null;
|
2019-10-08 13:25:57 -04:00
|
|
|
|
|
|
|
const roomId = await this.client.resolveRoom(permalink.roomIdOrAlias);
|
|
|
|
const list = this.banLists.find(b => b.roomId === roomId);
|
|
|
|
if (list) this.banLists.splice(this.banLists.indexOf(list), 1);
|
2019-10-08 15:58:31 -04:00
|
|
|
|
|
|
|
await this.client.setAccountData(WATCHED_LISTS_EVENT_TYPE, {
|
|
|
|
references: this.banLists.map(b => b.roomRef),
|
|
|
|
});
|
|
|
|
|
|
|
|
return list;
|
2019-10-08 13:25:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
public async buildWatchedBanLists() {
|
|
|
|
const banLists: BanList[] = [];
|
|
|
|
const joinedRooms = await this.client.getJoinedRooms();
|
2019-10-08 15:58:31 -04:00
|
|
|
|
|
|
|
let watchedListsEvent = {};
|
|
|
|
try {
|
|
|
|
watchedListsEvent = await this.client.getAccountData(WATCHED_LISTS_EVENT_TYPE);
|
|
|
|
} catch (e) {
|
|
|
|
// ignore - not important
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const roomRef of (watchedListsEvent['references'] || [])) {
|
2019-10-08 13:25:57 -04:00
|
|
|
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 list = new BanList(roomId, roomRef, this.client);
|
|
|
|
await list.updateList();
|
|
|
|
banLists.push(list);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.banLists = banLists;
|
|
|
|
}
|
|
|
|
|
2019-11-06 21:17:11 -05:00
|
|
|
public async verifyPermissions(verbose = true, printRegardless = false) {
|
2019-10-04 23:22:34 -04:00
|
|
|
const errors: RoomUpdateError[] = [];
|
|
|
|
for (const roomId of Object.keys(this.protectedRooms)) {
|
2019-10-04 23:36:19 -04:00
|
|
|
errors.push(...(await this.verifyPermissionsIn(roomId)));
|
2019-10-04 23:22:34 -04:00
|
|
|
}
|
|
|
|
|
2019-11-06 21:17:11 -05:00
|
|
|
const hadErrors = await this.printActionResult(errors, "Permission errors in protected rooms:", printRegardless);
|
2019-10-04 23:38:50 -04:00
|
|
|
if (!hadErrors && verbose) {
|
2019-10-04 23:22:34 -04:00
|
|
|
const html = `<font color="#00cc00">All permissions look OK.</font>`;
|
|
|
|
const text = "All permissions look OK.";
|
2019-11-06 20:46:49 -05:00
|
|
|
await this.client.sendMessage(config.managementRoom, {
|
2019-10-04 23:22:34 -04:00
|
|
|
msgtype: "m.notice",
|
|
|
|
body: text,
|
|
|
|
format: "org.matrix.custom.html",
|
|
|
|
formatted_body: html,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-04 23:36:19 -04:00
|
|
|
private async verifyPermissionsIn(roomId: string): Promise<RoomUpdateError[]> {
|
|
|
|
const errors: RoomUpdateError[] = [];
|
|
|
|
|
|
|
|
try {
|
|
|
|
const ownUserId = await this.client.getUserId();
|
|
|
|
|
|
|
|
const powerLevels = await this.client.getRoomStateEvent(roomId, "m.room.power_levels", "");
|
|
|
|
if (!powerLevels) {
|
|
|
|
// noinspection ExceptionCaughtLocallyJS
|
|
|
|
throw new Error("Missing power levels state event");
|
|
|
|
}
|
|
|
|
|
|
|
|
function plDefault(val: number | undefined | null, def: number): number {
|
|
|
|
if (!val && val !== 0) return def;
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
const users = powerLevels['users'] || {};
|
|
|
|
const events = powerLevels['events'] || {};
|
|
|
|
const usersDefault = plDefault(powerLevels['users_default'], 0);
|
|
|
|
const stateDefault = plDefault(powerLevels['state_default'], 50);
|
|
|
|
const ban = plDefault(powerLevels['ban'], 50);
|
|
|
|
const kick = plDefault(powerLevels['kick'], 50);
|
|
|
|
const redact = plDefault(powerLevels['redact'], 50);
|
|
|
|
|
|
|
|
const userLevel = plDefault(users[ownUserId], usersDefault);
|
|
|
|
const aclLevel = plDefault(events["m.room.server_acl"], stateDefault);
|
|
|
|
|
|
|
|
// Wants: ban, kick, redact, m.room.server_acl
|
|
|
|
|
|
|
|
if (userLevel < ban) {
|
2019-11-06 21:17:11 -05:00
|
|
|
errors.push({
|
|
|
|
roomId,
|
|
|
|
errorMessage: `Missing power level for bans: ${userLevel} < ${ban}`,
|
|
|
|
errorKind: ERROR_KIND_PERMISSION,
|
|
|
|
});
|
2019-10-04 23:36:19 -04:00
|
|
|
}
|
|
|
|
if (userLevel < kick) {
|
2019-11-06 21:17:11 -05:00
|
|
|
errors.push({
|
|
|
|
roomId,
|
|
|
|
errorMessage: `Missing power level for kicks: ${userLevel} < ${kick}`,
|
|
|
|
errorKind: ERROR_KIND_PERMISSION,
|
|
|
|
});
|
2019-10-04 23:36:19 -04:00
|
|
|
}
|
|
|
|
if (userLevel < redact) {
|
2019-11-06 21:17:11 -05:00
|
|
|
errors.push({
|
|
|
|
roomId,
|
|
|
|
errorMessage: `Missing power level for redactions: ${userLevel} < ${redact}`,
|
|
|
|
errorKind: ERROR_KIND_PERMISSION,
|
|
|
|
});
|
2019-10-04 23:36:19 -04:00
|
|
|
}
|
|
|
|
if (userLevel < aclLevel) {
|
2019-11-06 21:17:11 -05:00
|
|
|
errors.push({
|
|
|
|
roomId,
|
|
|
|
errorMessage: `Missing power level for server ACLs: ${userLevel} < ${aclLevel}`,
|
|
|
|
errorKind: ERROR_KIND_PERMISSION,
|
|
|
|
});
|
2019-10-04 23:36:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise OK
|
|
|
|
} catch (e) {
|
|
|
|
LogService.error("Mjolnir", e);
|
2019-11-06 21:17:11 -05:00
|
|
|
errors.push({
|
|
|
|
roomId,
|
|
|
|
errorMessage: e.message || (e.body ? e.body.error : '<no message>'),
|
|
|
|
errorKind: ERROR_KIND_FATAL,
|
|
|
|
});
|
2019-10-04 23:36:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return errors;
|
|
|
|
}
|
|
|
|
|
2019-10-09 06:29:01 -04:00
|
|
|
public async syncLists(verbose = true) {
|
2019-09-27 22:02:03 -04:00
|
|
|
for (const list of this.banLists) {
|
|
|
|
await list.updateList();
|
|
|
|
}
|
|
|
|
|
2019-10-04 22:59:30 -04:00
|
|
|
let hadErrors = false;
|
|
|
|
|
|
|
|
const aclErrors = await applyServerAcls(this.banLists, Object.keys(this.protectedRooms), this);
|
|
|
|
const banErrors = await applyUserBans(this.banLists, Object.keys(this.protectedRooms), this);
|
|
|
|
hadErrors = hadErrors || await this.printActionResult(aclErrors, "Errors updating server ACLs:");
|
|
|
|
hadErrors = hadErrors || await this.printActionResult(banErrors, "Errors updating member bans:");
|
|
|
|
|
2019-10-04 23:38:50 -04:00
|
|
|
if (!hadErrors && verbose) {
|
2019-10-04 22:59:30 -04:00
|
|
|
const html = `<font color="#00cc00">Done updating rooms - no errors</font>`;
|
2019-10-04 23:38:50 -04:00
|
|
|
const text = "Done updating rooms - no errors";
|
2019-11-06 20:46:49 -05:00
|
|
|
await this.client.sendMessage(config.managementRoom, {
|
2019-10-04 22:59:30 -04:00
|
|
|
msgtype: "m.notice",
|
|
|
|
body: text,
|
|
|
|
format: "org.matrix.custom.html",
|
|
|
|
formatted_body: html,
|
|
|
|
});
|
|
|
|
}
|
2019-09-27 22:02:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
public async syncListForRoom(roomId: string) {
|
|
|
|
let updated = false;
|
|
|
|
for (const list of this.banLists) {
|
|
|
|
if (list.roomId !== roomId) continue;
|
|
|
|
await list.updateList();
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
if (!updated) return;
|
|
|
|
|
2019-10-04 22:59:30 -04:00
|
|
|
let hadErrors = false;
|
|
|
|
|
|
|
|
const aclErrors = await applyServerAcls(this.banLists, Object.keys(this.protectedRooms), this);
|
|
|
|
const banErrors = await applyUserBans(this.banLists, Object.keys(this.protectedRooms), this);
|
|
|
|
hadErrors = hadErrors || await this.printActionResult(aclErrors, "Errors updating server ACLs:");
|
|
|
|
hadErrors = hadErrors || await this.printActionResult(banErrors, "Errors updating member bans:");
|
|
|
|
|
|
|
|
if (!hadErrors) {
|
|
|
|
const html = `<font color="#00cc00"><b>Done updating rooms - no errors</b></font>`;
|
|
|
|
const text = "Done updating rooms - no errors";
|
2019-11-06 20:46:49 -05:00
|
|
|
await this.client.sendMessage(config.managementRoom, {
|
2019-10-04 22:59:30 -04:00
|
|
|
msgtype: "m.notice",
|
|
|
|
body: text,
|
|
|
|
format: "org.matrix.custom.html",
|
|
|
|
formatted_body: html,
|
|
|
|
});
|
|
|
|
}
|
2019-09-27 22:02:03 -04:00
|
|
|
}
|
|
|
|
|
2019-09-27 17:15:10 -04:00
|
|
|
private async handleEvent(roomId: string, event: any) {
|
2019-10-18 11:38:19 -04:00
|
|
|
if (Object.keys(this.protectedRooms).includes(roomId)) {
|
|
|
|
if (event['sender'] === await this.client.getUserId()) return; // Ignore ourselves
|
|
|
|
if (event['type'] === 'm.room.power_levels' && event['state_key'] === '') {
|
|
|
|
// power levels were updated - recheck permissions
|
2019-11-06 21:17:11 -05:00
|
|
|
ErrorCache.resetError(roomId, ERROR_KIND_PERMISSION);
|
2019-10-18 11:38:19 -04:00
|
|
|
const url = this.protectedRooms[roomId];
|
|
|
|
let html = `Power levels changed in <a href="${url}">${roomId}</a> - checking permissions...`;
|
|
|
|
let text = `Power levels changed in ${url} - checking permissions...`;
|
2019-11-06 20:46:49 -05:00
|
|
|
await this.client.sendMessage(config.managementRoom, {
|
2019-10-04 23:36:19 -04:00
|
|
|
msgtype: "m.notice",
|
|
|
|
body: text,
|
|
|
|
format: "org.matrix.custom.html",
|
|
|
|
formatted_body: html,
|
|
|
|
});
|
2019-10-18 11:38:19 -04:00
|
|
|
const errors = await this.verifyPermissionsIn(roomId);
|
|
|
|
const hadErrors = await this.printActionResult(errors);
|
|
|
|
if (!hadErrors) {
|
|
|
|
html = `<font color="#00cc00">All permissions look OK.</font>`;
|
|
|
|
text = "All permissions look OK.";
|
2019-11-06 20:46:49 -05:00
|
|
|
await this.client.sendMessage(config.managementRoom, {
|
2019-10-18 11:38:19 -04:00
|
|
|
msgtype: "m.notice",
|
|
|
|
body: text,
|
|
|
|
format: "org.matrix.custom.html",
|
|
|
|
formatted_body: html,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
} else if (event['type'] === "m.room.member") {
|
2019-11-07 13:00:41 -05:00
|
|
|
// Only apply bans in the room we're looking at.
|
|
|
|
const errors = await applyUserBans(this.banLists, [roomId], this);
|
2019-10-18 11:38:19 -04:00
|
|
|
await this.printActionResult(errors);
|
2019-10-04 23:36:19 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-18 11:38:19 -04:00
|
|
|
if (this.banLists.map(b => b.roomId).includes(roomId)) {
|
|
|
|
if (ALL_RULE_TYPES.includes(event['type'])) {
|
|
|
|
await this.syncListForRoom(roomId);
|
2019-10-04 22:59:30 -04:00
|
|
|
}
|
2019-10-18 11:38:19 -04:00
|
|
|
}
|
2019-09-27 17:15:10 -04:00
|
|
|
}
|
|
|
|
|
2019-11-06 21:17:11 -05:00
|
|
|
private async printActionResult(errors: RoomUpdateError[], title: string = null, logAnyways = false) {
|
2019-10-04 22:59:30 -04:00
|
|
|
if (errors.length <= 0) return false;
|
2019-09-27 21:54:13 -04:00
|
|
|
|
2019-11-06 21:17:11 -05:00
|
|
|
if (!logAnyways) {
|
|
|
|
errors = errors.filter(e => ErrorCache.triggerError(e.roomId, e.errorKind));
|
|
|
|
if (errors.length <= 0) {
|
|
|
|
LogService.warn("Mjolnir", "Multiple errors are happening, however they are muted. Please check the management room.");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-27 17:15:10 -04:00
|
|
|
let html = "";
|
|
|
|
let text = "";
|
|
|
|
|
2019-10-04 22:59:30 -04:00
|
|
|
const htmlTitle = title ? `${title}<br />` : '';
|
|
|
|
const textTitle = title ? `${title}\n` : '';
|
|
|
|
|
|
|
|
html += `<font color="#ff0000"><b>${htmlTitle}${errors.length} errors updating protected rooms!</b></font><br /><ul>`;
|
|
|
|
text += `${textTitle}${errors.length} errors updating protected rooms!\n`;
|
2019-09-27 21:54:13 -04:00
|
|
|
for (const error of errors) {
|
|
|
|
const url = this.protectedRooms[error.roomId] ? this.protectedRooms[error.roomId] : `https://matrix.to/#/${error.roomId}`;
|
|
|
|
html += `<li><a href="${url}">${error.roomId}</a> - ${error.errorMessage}</li>`;
|
|
|
|
text += `${url} - ${error.errorMessage}\n`;
|
2019-09-27 17:15:10 -04:00
|
|
|
}
|
2019-09-27 21:54:13 -04:00
|
|
|
html += "</ul>";
|
2019-09-27 17:15:10 -04:00
|
|
|
|
|
|
|
const message = {
|
|
|
|
msgtype: "m.notice",
|
|
|
|
body: text,
|
|
|
|
format: "org.matrix.custom.html",
|
|
|
|
formatted_body: html,
|
|
|
|
};
|
2019-11-06 20:46:49 -05:00
|
|
|
await this.client.sendMessage(config.managementRoom, message);
|
2019-10-04 22:59:30 -04:00
|
|
|
return true;
|
2019-09-27 17:15:10 -04:00
|
|
|
}
|
2019-11-14 17:44:13 -05:00
|
|
|
|
|
|
|
public async isSynapseAdmin(): Promise<boolean> {
|
|
|
|
try {
|
|
|
|
const endpoint = `/_synapse/admin/v1/users/${await this.client.getUserId()}/admin`;
|
|
|
|
const response = await this.client.doRequest("GET", endpoint);
|
|
|
|
return response['admin'];
|
|
|
|
} catch (e) {
|
|
|
|
LogService.error("Mjolnir", "Error determining if Mjolnir is a server admin:");
|
|
|
|
LogService.error("Mjolnir", e);
|
|
|
|
return false; // assume not
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async deactivateSynapseUser(userId: string): Promise<any> {
|
|
|
|
const endpoint = `/_synapse/admin/v1/deactivate/${userId}`;
|
|
|
|
return await this.client.doRequest("POST", endpoint);
|
|
|
|
}
|
2019-09-27 17:15:10 -04:00
|
|
|
}
|