|  | @@ -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);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 |