Sélectionner une révision Git
matrixcommandhandler.ts
matrixcommandhandler.ts 10,08 Kio
import { DiscordBot } from "./bot";
import { Log } from "./log";
import { DiscordBridgeConfig } from "./config";
import { Bridge, BridgeContext } from "matrix-appservice-bridge";
import { IMatrixEvent } from "./matrixtypes";
import { Provisioner } from "./provisioner";
import { Util } from "./util";
import * as Discord from "discord.js";
const log = new Log("MatrixCommandHandler");
/* tslint:disable:no-magic-numbers */
const PROVISIONING_DEFAULT_POWER_LEVEL = 50;
const PROVISIONING_DEFAULT_USER_POWER_LEVEL = 0;
const ROOM_CACHE_MAXAGE_MS = 15 * 60 * 1000;
/* tslint:enable:no-magic-numbers */
export class MatrixCommandHandler {
private botJoinedRooms: Set<string> = new Set(); // roomids
private botJoinedRoomsCacheUpdatedAt = 0;
private provisioner: Provisioner;
constructor(
private discord: DiscordBot,
private bridge: Bridge,
private config: DiscordBridgeConfig,
) {
this.provisioner = this.discord.Provisioner;
}
public async HandleInvite(event: IMatrixEvent) {
log.info(`Received invite for ${event.state_key} in room ${event.room_id}`);
if (event.state_key === this.discord.GetBotId()) {
log.info("Accepting invite for bridge bot");
await this.bridge.getIntent().joinRoom(event.room_id);
this.botJoinedRooms.add(event.room_id);
}
}
public async Process(event: IMatrixEvent, context: BridgeContext) {
const intent = this.bridge.getIntent();
if (!(await this.isBotInRoom(event.room_id))) {
log.warn(`Bot is not in ${event.room_id}. Ignoring command`);
return;
}
if (!this.config.bridge.enableSelfServiceBridging) {
// We can do this here because the only commands we support are self-service bridging
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "The owner of this bridge does not permit self-service bridging.",
msgtype: "m.notice",
});
}
// Check to make sure the user has permission to do anything in the room. We can do this here
// because the only commands we support are self-service commands (which therefore require some
// level of permissions)
const plEvent = await this.bridge.getIntent().getClient()
.getStateEvent(event.room_id, "m.room.power_levels", "");
let userLevel = PROVISIONING_DEFAULT_USER_POWER_LEVEL;
let requiredLevel = PROVISIONING_DEFAULT_POWER_LEVEL;
if (plEvent && plEvent.state_default) {
requiredLevel = plEvent.state_default;
}
if (plEvent && plEvent.users_default) {
userLevel = plEvent.users_default;
}
if (plEvent && plEvent.users && plEvent.users[event.sender]) {
userLevel = plEvent.users[event.sender];
}
if (userLevel < requiredLevel) {
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "You do not have the required power level in this room to create a bridge to a Discord channel.",
msgtype: "m.notice",
});
}
const {command, args} = Util.MsgToArgs(event.content!.body as string, "!discord");
if (command === "help" && args[0] === "bridge") {
const link = Util.GetBotLink(this.config);
// tslint:disable prefer-template
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "How to bridge a Discord guild:\n" +
"1. Invite the bot to your Discord guild using this link: " + link + "\n" +
"2. Invite me to the matrix room you'd like to bridge\n" +
"3. Open the Discord channel you'd like to bridge in a web browser\n" +
"4. In the matrix room, send the message `!discord bridge <guild id> <channel id>` " +
"(without the backticks)\n" +
" Note: The Guild ID and Channel ID can be retrieved from the URL in your web browser.\n" +
" The URL is formatted as https://discordapp.com/channels/GUILD_ID/CHANNEL_ID\n" +
"5. Enjoy your new bridge!",
msgtype: "m.notice",
});
// tslint:enable prefer-template
} else if (command === "bridge") {
if (context.rooms.remote) {
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "This room is already bridged to a Discord guild.",
msgtype: "m.notice",
});
}
const MAXARGS = 2;
if (args.length > MAXARGS || args.length < 1) {
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "Invalid syntax. For more information try !discord help bridge",
msgtype: "m.notice",
});
}
let guildId: string;
let channelId: string;
const AMOUNT_OF_IDS_DISCORD_IDENTIFIES_ROOMS_BY = 2;
if (args.length === AMOUNT_OF_IDS_DISCORD_IDENTIFIES_ROOMS_BY) { // "x y" syntax
guildId = args[0];
channelId = args[1];
} else if (args.length === 1 && args[0].includes("/")) { // "x/y" syntax
const split = args[0].split("/");
guildId = split[0];
channelId = split[1];
} else {
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "Invalid syntax: See `!discord help`",
formatted_body: "Invalid syntax: See <code>!discord help</code>",
msgtype: "m.notice",
});
}
try {
const discordResult = await this.discord.LookupRoom(guildId, channelId);
const channel = discordResult.channel as Discord.TextChannel;
log.info(`Bridging matrix room ${event.room_id} to ${guildId}/${channelId}`);
this.bridge.getIntent().sendMessage(event.room_id, {
body: "I'm asking permission from the guild administrators to make this bridge.",
msgtype: "m.notice",
});
await this.provisioner.AskBridgePermission(channel, event.sender);
await this.provisioner.BridgeMatrixRoom(channel, event.room_id);
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "I have bridged this room to your channel",
msgtype: "m.notice",
});
} catch (err) {
if (err.message === "Timed out waiting for a response from the Discord owners"
|| err.message === "The bridge has been declined by the Discord guild") {
return this.bridge.getIntent().sendMessage(event.room_id, {
body: err.message,
msgtype: "m.notice",
});
}
log.error(`Error bridging ${event.room_id} to ${guildId}/${channelId}`);
log.error(err);
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "There was a problem bridging that channel - has the guild owner approved the bridge?",
msgtype: "m.notice",
});
}
} else if (command === "unbridge") {
const remoteRoom = context.rooms.remote;
if (!remoteRoom) {
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "This room is not bridged.",
msgtype: "m.notice",
});
}
if (!remoteRoom.data.plumbed) {
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "This room cannot be unbridged.",
msgtype: "m.notice",
});
}
try {
await this.provisioner.UnbridgeRoom(remoteRoom);
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "This room has been unbridged",
msgtype: "m.notice",
});
} catch (err) {
log.error("Error while unbridging room " + event.room_id);
log.error(err);
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "There was an error unbridging this room. " +
"Please try again later or contact the bridge operator.",
msgtype: "m.notice",
});
}
} else if (command === "help") {
// Unknown command or no command given to get help on, so we'll just give them the help
// tslint:disable prefer-template
return this.bridge.getIntent().sendMessage(event.room_id, {
body: "Available commands:\n" +
"!discord bridge <guild id> <channel id> - Bridges this room to a Discord channel\n" +
"!discord unbridge - Unbridges a Discord channel from this room\n" +
"!discord help <command> - Help menu for another command. Eg: !discord help bridge\n",
msgtype: "m.notice",
});
// tslint:enable prefer-template
}
}
private async isBotInRoom(roomId: string): Promise<boolean> {
// Update the room cache, if not done already.
if (Date.now () - this.botJoinedRoomsCacheUpdatedAt > ROOM_CACHE_MAXAGE_MS) {
log.verbose("Updating room cache for bot...");
try {
log.verbose("Got new room cache for bot");
this.botJoinedRoomsCacheUpdatedAt = Date.now();
const rooms = (await this.bridge.getBot().getJoinedRooms()) as string[];
this.botJoinedRooms = new Set(rooms);
} catch (e) {
log.error("Failed to get room cache for bot, ", e);
return false;
}
}
return this.botJoinedRooms.has(roomId);
}
}