Source

commands/stamps/password-stamp.js

const { sendEmbedToMember, sendMessageToMember, deleteMessage } = require('../../discord-services');
const { MessageEmbed, Message, Snowflake, Collection } = require('discord.js');
const PermissionCommand = require('../../classes/permission-command');
const BotGuildModel = require('../../classes/bot-guild');
const StampsManager = require('../../classes/stamps-manager');
const { StringPrompt, ChannelPrompt } = require('advanced-discord.js-prompts');

/**
 * Sends a reaction collector for users to react, send a password and receive a stamp. Used to give out stamps for activities that don't have 
 * an activity instance. The user who starts the password stamp must give the activity name, password, and stop time defaults to 120 seconds. Users 
 * have 3 attempts to get the password right within the stop time.
 * @category Commands
 * @subcategory Stamps
 * @extends PermissionCommand
 */
class PasswordStamp extends PermissionCommand {
    constructor(client) {
        super(client, {
            name: 'password-stamp',
            group: 'stamps',
            memberName: 'gives stamps requiring passwords',
            description: 'gives a stamp to everyone who reacted and gave the correct password',
            args: [
                {   key: 'activityName',
                    prompt: 'the workshop/activity name',
                    type: 'string',
                    default: '',
                },
                {
                    key: 'password',
                    prompt: 'the password for hackers to use to get stamp',
                    type: 'string',
                    default: '',
                },
                {
                    key: 'stopTime',
                    prompt: 'time for stamp collector to be open for, in minutes.',
                    type: 'integer',
                    default: 120,
                }
            ],
        }, 
        {
            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
            channelMessage: 'This command is only available on the admin console!',
            role: PermissionCommand.FLAGS.STAFF_ROLE,
            roleMessage: 'This command can only available to staff!',
        });
    }

    /**
     * @param {BotGuildModel} botGuild
     * @param {Message} message
     * @param {Object} args
     * @param {String} args.activityName
     * @param {String} args.password
     * @param {Number} args.stopTime
     */
    async runCommand(botGuild, message, {activityName, password, stopTime}) {
        // helpful vars
        let channel = message.channel;
        let userId = message.author.id;
        // check if arguments have been given and prompt for the channel to use
        try {
            if (activityName === '') {
                activityName = await StringPrompt.single({prompt: 'Please respond with the workshop/activity name.', channel, userId, cancelable: true});
            }

            if(password === '') {
                password = await StringPrompt.single({prompt: 'Please respond with the password for hackers to use to get stamp.', channel, userId, cancelable: true});
            }

            var targetChannel = await ChannelPrompt.single({prompt: 'What channel do you want to send the stamp collector to? Users should have access to this channel!', channel, userId, cancelable: true});
        } catch (error) {
            channel.send('<@' + userId + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000}));
            return;
        }

        const qEmbed = new MessageEmbed()
            .setColor(botGuild.colors.embedColor)
            .setTitle('React with anything to claim a stamp for attending ' + activityName)
            .setDescription('Once you react to this message, check for a DM from this bot. **You can only emoji this message once!**')
            .addField('A Password Is Required!', 'Through the Bot\'s DM, you will have 3 attempts in the first 60 seconds to enter the correct password.');
        
        targetChannel.send(qEmbed).then((msg) => {

            let emoji = '👍';
            msg.react(emoji);
            
            /**
             * keeps track of which users have already reacted to the message so there are no duplicates
             * @type {Collection<Snowflake, String>} - <User.id, User.username>
             */
            var seenUsers = new Collection();

            // filter emoji reaction and collector
            const emojiFilter = (reaction, user) => !user.bot && !seenUsers.has(user.id);
            const collector = msg.createReactionCollector(emojiFilter, {time: (1000 * stopTime * 60)});  // stopTime is in minutes, multiply to get seconds, then milliseconds 

            //send hacker a dm upon reaction
            collector.on('collect', async(reaction, user) => {
                seenUsers.set(user.id, user.username);

                const member = message.guild.member(user);

                // prompt member for password
                var dmMessage = await sendEmbedToMember(user, {
                    description: 'You have 60 seconds and 3 attempts to type the password correctly to get the ' + activityName + ' stamp.\n' +
                    'Please enter the password (leave no stray spaces or anything):',
                    title: 'Stamp Collector For ' + activityName,
                    color: '#b2ff2e',
                });

                var correctPassword = false;
                var incorrectPasswords = 0;

                const filter = m => user.id === m.author.id;
                //message collector for the user's password attempts
                const pwdCollector = dmMessage.channel.createMessageCollector(filter,{time: 60000, max: 3});

                pwdCollector.on('collect', async m => {
                    //update role and stop collecting if password matches
                    if (m.content.toLowerCase() === password.toLowerCase()) {

                        StampsManager.parseRole(member, activityName, botGuild);
                        
                        correctPassword = true;
                        pwdCollector.stop();
                    } else if (incorrectPasswords < 2) {
                        //add 1 to number of incorrect guesses and prompts user to try again
                        await sendMessageToMember(user, 'Incorrect. Please try again.', true);
                    }
                    incorrectPasswords++;
                });
                pwdCollector.on('end', collected => {
                    deleteMessage(dmMessage);

                    //show different messages after password collection expires depending on circumstance
                    if (!correctPassword) {
                        if (incorrectPasswords < 3) {
                            sendEmbedToMember(user, {
                                title: 'Stamp Collector',
                                description: 'Time\'s up! You took too long to enter the password for the ' + activityName + ' stamp. If you have extenuating circumstances please contact an organizer.',
                            });
                        } else {
                            sendEmbedToMember(user, {
                                title: 'Stamp Collector',
                                description: 'Incorrect. You have no attempts left for the ' + activityName + ' stamp. If you have extenuating circumstances please contact an organizer.',
                            });
                        }
                    }
                });
            });

            //edits the embedded message to notify people when it stops collecting reacts
            collector.on('end', collected => {
                if (msg.guild.channels.cache.find(channel => channel.name === targetChannel.name)) {
                    msg.edit(qEmbed.setTitle('Time\'s up! No more responses are being collected. Thanks for participating in ' + activityName + '\'s booth!'));
                }
            });
        });
    }
}
module.exports = PasswordStamp;