|
@@ -0,0 +1,162 @@
|
|
|
|
+import { parseYaml } from "../util";
|
|
|
|
+import { Command, ICommandData, Plugin } from "src/model/plugin";
|
|
|
|
+import { tryDo } from "@shared/common/async_utils";
|
|
|
|
+import * as t from "io-ts";
|
|
|
|
+import { logger } from "src/logging";
|
|
|
|
+import { getRepository } from "typeorm";
|
|
|
|
+import { GiveRoleMessage } from "@shared/db/entity/GiveRoleMessage";
|
|
|
|
+import { Message, MessageReaction, ReactionCollector, ReactionEmoji, TextChannel, User } from "discord.js";
|
|
|
|
+import { client } from "src/client";
|
|
|
|
+
|
|
|
|
+const ReactRoleMessageParams = t.type({
|
|
|
|
+ message: t.string,
|
|
|
|
+ roleId: t.string
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const REACT_EMOTE = "⭐";
|
|
|
|
+const MSG_COLOR = 9830318;
|
|
|
|
+
|
|
|
|
+@Plugin
|
|
|
|
+export class GiveRoleForReact {
|
|
|
|
+ @Command({
|
|
|
|
+ type: "mention",
|
|
|
|
+ pattern: "add role message",
|
|
|
|
+ documentation: {
|
|
|
|
+ description: "Add role giver message",
|
|
|
|
+ example: "add role message { message: \"This is a role!\", roleId: \"0237894783782\" }"
|
|
|
|
+ },
|
|
|
|
+ allowDM: false,
|
|
|
|
+ auth: true
|
|
|
|
+ })
|
|
|
|
+ async makeRoleMessage({ message, contents }: ICommandData): Promise<void> {
|
|
|
|
+ const textContent = (contents as string).substring("add role message".length).trim();
|
|
|
|
+
|
|
|
|
+ tryDo(message.delete());
|
|
|
|
+
|
|
|
|
+ const opts = parseYaml(ReactRoleMessageParams, textContent);
|
|
|
|
+ if (!opts.ok) {
|
|
|
|
+ await this.sendDM(message.author, `Sorry, I don't understand the command! Got the following errors: ${opts.errors.join("\n")}`);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const params = opts.result;
|
|
|
|
+
|
|
|
|
+ if (!message.guild?.roles.cache.has(params.roleId)) {
|
|
|
|
+ await this.sendDM(message.author, "Sorry, the role ID is not a valid role on the server!");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const msgSendResult = await tryDo(message.channel.send({
|
|
|
|
+ embed: {
|
|
|
|
+ title: `React with ${REACT_EMOTE} to gain role`,
|
|
|
|
+ description: params.message,
|
|
|
|
+ color: MSG_COLOR
|
|
|
|
+ }
|
|
|
|
+ }));
|
|
|
|
+
|
|
|
|
+ if (!msgSendResult.ok) {
|
|
|
|
+ logger.error(`GiveRoleForReact: failed to create message because ${msgSendResult.error}`);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const msg = msgSendResult.result;
|
|
|
|
+ await msg.react(REACT_EMOTE);
|
|
|
|
+ const roleGiveMessageRepo = getRepository(GiveRoleMessage);
|
|
|
|
+
|
|
|
|
+ if (!msg.guild) {
|
|
|
|
+ logger.error("GiveRoleForReact: tried to set up role react for DMs (this should never happen!)");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ await roleGiveMessageRepo.save({
|
|
|
|
+ messageId: msg.id,
|
|
|
|
+ guildId: msg.guild.id,
|
|
|
|
+ channelId: msg.channel.id,
|
|
|
|
+ roleToGive: params.roleId
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.initReactCollector(msg, params.roleId);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private initReactCollector(msg: Message, role: string) {
|
|
|
|
+ const check = (reaction: MessageReaction) => {
|
|
|
|
+ return reaction.emoji.name == REACT_EMOTE;
|
|
|
|
+ };
|
|
|
|
+ const collector = new ReactionCollector(msg, check, {
|
|
|
|
+ dispose: true
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ const guild = msg.guild;
|
|
|
|
+ if (!guild) {
|
|
|
|
+ throw new Error("Tried to initialize role collector for non-guild channel.");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ collector.on("collect", async (_, user) => {
|
|
|
|
+ if (user.bot) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const gu = guild.member(user);
|
|
|
|
+ if (!gu) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const result = await tryDo(gu.roles.add(role));
|
|
|
|
+ if (!result.ok) {
|
|
|
|
+ logger.error("GiveRoleForReact: Can't add role %s to user %s: %s", role, gu.id, result.error);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ collector.on("remove", async (_, user) => {
|
|
|
|
+ if (user.bot) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const gu = guild.member(user);
|
|
|
|
+ if (!gu) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ const result = await tryDo(gu.roles.remove(role));
|
|
|
|
+ if (!result.ok) {
|
|
|
|
+ logger.error("GiveRoleForReact: Can't remove role %s to user %s: %s", role, gu.id, result.error);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private async sendDM(usr: User, messageText: string) {
|
|
|
|
+ const dmResult = await tryDo(usr.createDM());
|
|
|
|
+ if (dmResult.ok) {
|
|
|
|
+ tryDo(dmResult.result.send(messageText));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ async start(): Promise<void> {
|
|
|
|
+ logger.info("Initializing role give messages");
|
|
|
|
+ const roleGiveMessageRepo = getRepository(GiveRoleMessage);
|
|
|
|
+
|
|
|
|
+ const reactMessages = await roleGiveMessageRepo.find();
|
|
|
|
+
|
|
|
|
+ const staleEntities = [];
|
|
|
|
+ for (const reactMessage of reactMessages) {
|
|
|
|
+ const guildResult = await tryDo(client.bot.guilds.fetch(reactMessage.guildId));
|
|
|
|
+ if (!guildResult.ok) {
|
|
|
|
+ staleEntities.push(reactMessage);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const guild = guildResult.result;
|
|
|
|
+ const channel = guild.channels.resolve(reactMessage.channelId);
|
|
|
|
+ if (!channel || !(channel instanceof TextChannel)) {
|
|
|
|
+ staleEntities.push(reactMessage);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const msgResult = await tryDo(channel.messages.fetch(reactMessage.messageId));
|
|
|
|
+ if (!msgResult.ok) {
|
|
|
|
+ staleEntities.push(reactMessage);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.initReactCollector(msgResult.result, reactMessage.roleToGive);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ await roleGiveMessageRepo.remove(staleEntities);
|
|
|
|
+ }
|
|
|
|
+}
|