mx-puppet-discord/src/index.ts

251 lines
8.0 KiB
TypeScript

/* tslint:disable: no-any */
/*
Copyright 2019, 2020 mx-puppet-discord
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.
*/
import {
PuppetBridge,
IPuppetBridgeRegOpts,
Log,
IRetData,
IProtocolInformation,
} from "mx-puppet-bridge";
import * as commandLineArgs from "command-line-args";
import * as commandLineUsage from "command-line-usage";
import { App } from "./app";
const log = new Log("DiscordPuppet:index");
const commandOptions = [
{ name: "register", alias: "r", type: Boolean },
{ name: "registration-file", alias: "f", type: String },
{ name: "config", alias: "c", type: String },
{ name: "help", alias: "h", type: Boolean },
];
const options = Object.assign({
"register": false,
"registration-file": "discord-registration.yaml",
"config": "config.yaml",
"help": false,
}, commandLineArgs(commandOptions));
if (options.help) {
// tslint:disable-next-line:no-console
console.log(commandLineUsage([
{
header: "Matrix Discord Puppet Bridge",
content: "A matrix puppet bridge for discord",
},
{
header: "Options",
optionList: commandOptions,
},
]));
process.exit(0);
}
const protocol: IProtocolInformation = {
features: {
file: true,
presence: true,
edit: true,
reply: true,
advancedRelay: true,
globalNamespace: true,
typingTimeout: 10 * 1000, // tslint:disable-line no-magic-numbers
},
id: "discord",
displayname: "Discord",
externalUrl: "https://discordapp.com/",
namePatterns: {
user: ":name",
userOverride: ":displayname",
room: "[:guild?#:name - :guild,:name]",
group: ":name",
},
};
const puppet = new PuppetBridge(options["registration-file"], options.config, protocol);
if (options.register) {
// okay, all we have to do is generate a registration file
puppet.readConfig(false);
try {
puppet.generateRegistration({
prefix: "_discordpuppet_",
id: "discord-puppet",
url: `http://${puppet.Config.bridge.bindAddress}:${puppet.Config.bridge.port}`,
} as IPuppetBridgeRegOpts);
} catch (err) {
// tslint:disable-next-line:no-console
console.log("Couldn't generate registration file:", err);
}
process.exit(0);
}
async function run() {
await puppet.init();
const app = new App(puppet);
await app.init();
puppet.on("puppetNew", app.newPuppet.bind(app));
puppet.on("puppetDelete", app.deletePuppet.bind(app));
puppet.on("message", app.matrix.events.handleMatrixMessage.bind(app.matrix.events));
puppet.on("file", app.matrix.events.handleMatrixFile.bind(app.matrix.events));
puppet.on("redact", app.matrix.events.handleMatrixRedact.bind(app.matrix.events));
puppet.on("edit", app.matrix.events.handleMatrixEdit.bind(app.matrix.events));
puppet.on("reply", app.matrix.events.handleMatrixReply.bind(app.matrix.events));
puppet.on("reaction", app.matrix.events.handleMatrixReaction.bind(app.matrix.events));
puppet.on("removeReaction", app.matrix.events.handleMatrixRemoveReaction.bind(app.matrix.events));
puppet.on("typing", app.matrix.events.handleMatrixTyping.bind(app.matrix.events));
puppet.on("presence", app.matrix.events.handleMatrixPresence.bind(app.matrix.events));
puppet.on("puppetName", app.handlePuppetName.bind(app));
puppet.on("puppetAvatar", app.handlePuppetAvatar.bind(app));
puppet.setGetUserIdsInRoomHook(app.getUserIdsInRoom.bind(app));
puppet.setCreateRoomHook(app.matrix.createRoom.bind(app.matrix));
puppet.setCreateUserHook(app.matrix.createUser.bind(app.matrix));
puppet.setCreateGroupHook(app.matrix.createGroup.bind(app.matrix));
puppet.setGetDmRoomIdHook(app.matrix.getDmRoom.bind(app.matrix));
puppet.setListUsersHook(app.listUsers.bind(app));
puppet.setListRoomsHook(app.matrix.listRooms.bind(app.matrix));
puppet.setGetDescHook(async (puppetId: number, data: any): Promise<string> => {
let s = "Discord";
if (data.username) {
s += ` as \`${data.username}\``;
}
if (data.id) {
s += ` (\`${data.id}\`)`;
}
return s;
});
puppet.setGetDataFromStrHook(async (str: string): Promise<IRetData> => {
const retData = {
success: false,
} as IRetData;
if (!str) {
retData.error = "Please specify a token to link!";
return retData;
}
const parts = str.split(" ");
const PARTS_LENGTH = 2;
if (parts.length !== PARTS_LENGTH) {
retData.error = "Please specify if your token is a user or a bot token! `link <user|bot> token`";
return retData;
}
const type = parts[0].toLowerCase();
if (!["bot", "user"].includes(type)) {
retData.error = "Please specify if your token is a user or a bot token! `link <user|bot> token`";
return retData;
}
retData.success = true;
retData.data = {
token: parts[1].trim(),
bot: type === "bot",
};
return retData;
});
puppet.setBotHeaderMsgHook((): string => {
return "Discord Puppet Bridge";
});
puppet.setResolveRoomIdHook(async (ident: string): Promise<string | null> => {
if (ident.match(/^[0-9]+$/)) {
return ident;
}
const matches = ident.match(/^(?:https?:\/\/)?discordapp\.com\/channels\/[^\/]+\/([0-9]+)/);
if (matches) {
return matches[1];
}
return null;
});
puppet.registerCommand("syncprofile", {
fn: app.commands.commandSyncProfile.bind(app.commands),
help: `Enable/disable the syncing of the matrix profile to the discord one (name and avatar)
Usage: \`syncprofile <puppetId> <1/0>\``,
});
puppet.registerCommand("joinentireguild", {
fn: app.commands.commandJoinEntireGuild.bind(app.commands),
help: `Join all the channels in a guild, if it is bridged
Usage: \`joinentireguild <puppetId> <guildId>\``,
});
puppet.registerCommand("listguilds", {
fn: app.commands.commandListGuilds.bind(app.commands),
help: `List all guilds that can be bridged
Usage: \`listguilds <puppetId>\``,
});
puppet.registerCommand("acceptinvite", {
fn: app.commands.commandAcceptInvite.bind(app.commands),
help: `Accept a discord.gg invite
Usage: \`acceptinvite <puppetId> <inviteLink>\``,
});
puppet.registerCommand("bridgeguild", {
fn: app.commands.commandBridgeGuild.bind(app.commands),
help: `Bridge a guild
Usage: \`bridgeguild <puppetId> <guildId>\``,
});
puppet.registerCommand("unbridgeguild", {
fn: app.commands.commandUnbridgeGuild.bind(app.commands),
help: `Unbridge a guild
Usage: \`unbridgeguild <puppetId> <guildId>\``,
});
puppet.registerCommand("bridgechannel", {
fn: app.commands.commandBridgeChannel.bind(app.commands),
help: `Bridge a channel
Usage: \`bridgechannel <puppetId> <channelId>\``,
});
puppet.registerCommand("unbridgechannel", {
fn: app.commands.commandUnbridgeChannel.bind(app.commands),
help: `Unbridge a channel
Usage: \`unbridgechannel <puppetId> <channelId>\``,
});
puppet.registerCommand("bridgeall", {
fn: app.commands.commandBridgeAll.bind(app.commands),
help: `Bridge everything
Usage: \`bridgeall <puppetId> <1/0>\``,
});
puppet.registerCommand("enablefriendsmanagement", {
fn: app.commands.commandEnableFriendsManagement.bind(app.commands),
help: `Enables friends management on the discord account
Usage: \`enablefriendsmanagement <puppetId>\``,
});
puppet.registerCommand("listfriends", {
fn: app.commands.commandListFriends.bind(app.commands),
help: `List all your current friends
Usage: \`listfriends <puppetId>\``,
});
puppet.registerCommand("addfriend", {
fn: app.commands.commandAddFriend.bind(app.commands),
help: `Add a new friend
Usage: \`addfriend <puppetId> <friend>\`, friend can be either the full username or the user ID`,
});
puppet.registerCommand("removefriend", {
fn: app.commands.commandRemoveFriend.bind(app.commands),
help: `Remove a friend
Usage: \`removefriend <puppetId> <friend>\`, friend can be either the full username or the user ID`,
});
await puppet.start();
}
// tslint:disable-next-line:no-floating-promises
run(); // start the thing!