Beispiel #1
0
    async def on_button_click(self, payload):
        """
        Disables a given button.
        """

        if not payload.component.custom_id.startswith(
                "DISABLE_BUTTON_COMMAND"):
            return
        async with self.button_message_locks[payload.message.id]:
            components = vbu.MessageComponents(*[
                vbu.ActionRow(
                    *[vbu.Button.from_dict(b) for b in r['components']])
                for r in payload.data['message']['components']
            ])
            b = components.get_component(payload.component.custom_id)
            b.disable()
            await payload.ack()
            await payload.message.edit(components=components)
Beispiel #2
0
    async def on_message(self, message):
        """
        Pings the staff members when a report comes through.
        """

        # Reports channel
        if message.channel.id != 709608383216353280:
            return

        # Yags
        if message.author.id != 204255221017214977:
            return

        # Get embeds
        embed = None
        if message.embeds:
            embed = message.embeds[0]
            original_description = embed.description
            match = CHANNEL_REGEX.search(original_description)
            report_channel_id = match.group(1)
            embed.description += (
                f"\n**Timestamp:** {vbu.TimeFormatter(message.created_at)!s}"
                f"\n**Jump URL:** [Click here](https://discord.com/channels/{message.guild.id}/{report_channel_id}/{message.id})"
            )

        # Make components
        components = vbu.MessageComponents(
            vbu.ActionRow(vbu.Button("Handle report", "HANDLE_REPORT"), ), )

        # Repost message
        await message.delete()
        await message.channel.send(
            f"{message.content} <@&480519382699868181> <@&713026585569263637>",
            # f"{message.content}",
            embeds=[embed],
            components=components,
        )
Beispiel #3
0
    async def gamble(self, ctx: commands.Context):
        """
        This command is similar to slots, but doesn't cost anything.
        """

        # Pick the rarity that the user rolled
        i = utils.rarity_percentage_finder(1)
        rarity = random.choices(*i)[0]
        if rarity in ["epic", "rare", "mythic"]:
            rarity = "uncommon"

        if ctx.author.id in utils.current_fishers:
            return await ctx.send(
                f"{ctx.author.display_name}, you're already fishing!")
        utils.current_fishers.append(ctx.author.id)

        async with self.bot.database() as db:
            # Achievements
            await db(
                """INSERT INTO user_achievements (user_id, times_gambled) VALUES ($1, 1)
                ON CONFLICT (user_id) DO UPDATE SET times_gambled = user_achievements.times_gambled + 1""",
                ctx.author.id)

        # Set up some vars for later
        fish_type = []  # The list of fish that they rolled
        emoji_id = []  # The list of fish emojis that they rolled
        emojis = [
            "<a:first_set_roll:875259843571748924>",
            "<a:first_set_roll:875259843571748924>",
            "<a:first_set_roll:875259843571748924>"
        ]
        picked_buttons = [False, False, False]

        # Pick three fish names from their rarity
        for i in range(3):
            fish_type.append(
                random.choice(list(utils.EMOJI_RARITIES_SET_ONE[rarity])))

        # Get the emojis for the fish they rolled
        emoji_id.append([
            utils.EMOJI_RARITIES_SET_ONE[rarity][fish_type_single]
            for fish_type_single in fish_type
        ])
        embed = vbu.Embed(title=f"{ctx.author.display_name}'s roll")
        embed.add_field(name="Click the buttons to stop the rolls!",
                        value="".join(emojis))

        # And send the message
        components = vbu.MessageComponents(
            vbu.ActionRow(
                vbu.Button(custom_id="one",
                           emoji="1\N{COMBINING ENCLOSING KEYCAP}"),
                vbu.Button(custom_id="two",
                           emoji="2\N{COMBINING ENCLOSING KEYCAP}"),
                vbu.Button(custom_id="three",
                           emoji="3\N{COMBINING ENCLOSING KEYCAP}"),
            ), )
        gamble_message = await ctx.send(embed=embed, components=components)

        # Make the button check
        def button_check(payload):
            if payload.message.id != gamble_message.id:
                return False
            self.bot.loop.create_task(payload.defer_update())
            return payload.user.id == ctx.author.id

        # Keep going...
        while True:

            # Wait for them to click a button
            try:
                chosen_button_payload = await self.bot.wait_for(
                    'component_interaction', timeout=60.0, check=button_check)
                chosen_button = chosen_button_payload.component.custom_id.lower(
                )
            except asyncio.TimeoutError:
                await gamble_message.edit(
                    components=components.disable_components())
                break

            # Update the displayed emoji
            if chosen_button == "one" and picked_buttons[0] is False:
                emojis[0] = emoji_id[0][0]
                picked_buttons[0] = True
            if chosen_button == "two" and picked_buttons[1] is False:
                emojis[1] = emoji_id[0][1]
                picked_buttons[1] = True
            if chosen_button == "three" and picked_buttons[2] is False:
                emojis[2] = emoji_id[0][2]
                picked_buttons[2] = True

            # Disable the given button
            components.get_component(chosen_button).disable()
            await gamble_message.edit(
                embed=create_bucket_embed(
                    ctx.author,
                    (
                        "Click the buttons to stop the rolls!",
                        "".join(list(emojis)),
                    ),
                    f"{ctx.author.display_name}'s roll",
                ),
                components=components,
            )

            # Break when they're done picking fish
            if "<a:first_set_roll:875259843571748924>" not in emojis:
                break

        # Sees if they won the fish they rolled
        if emojis[0] == emojis[1] == emojis[
                2] and "<a:first_set_roll:875259843571748924>" not in emojis:
            fish_won = fish_type[0]
            for rarity, fish_types in self.bot.fish.items():
                for fish_type, fish_info in fish_types.items():
                    if fish_info["raw_name"] == fish_won:
                        fish_won_info = fish_info
                        break
            embed = discord.Embed()
            embed.add_field(name=f"{ctx.author.display_name} has won:",
                            value=' '.join(fish_won.split('_')).title())
            async with self.bot.database() as db:
                # Achievements
                await db(
                    """INSERT INTO user_achievements (user_id, times_caught) VALUES ($1, 1)
                    ON CONFLICT (user_id) DO UPDATE SET times_caught = user_achievements.times_caught + 1""",
                    ctx.author.id)
            await utils.ask_to_sell_fish(self.bot,
                                         ctx,
                                         fish_won_info,
                                         embed=embed)
        else:
            await ctx.send(f"{ctx.author.mention} lost!")

        utils.current_fishers.remove(ctx.author.id)
Beispiel #4
0
import discord
import voxelbotutils as vbu

CSUPPORT_MESSAGE = ("\u200b\n" * 28) + """
Please give a detailed report of:
* What you thought would happen vs what actually happened
* How you cause the issue to happen
* Any extra details (like screenshots)

Ping `@Support Team` for a faster response
"""
CSUPPORT_COMPONENTS = vbu.MessageComponents(
    vbu.ActionRow(vbu.Button("See the FAQs", "FAQ")))
FAQ_COMPONENTS = vbu.MessageComponents(
    vbu.ActionRow(
        vbu.Button("MarriageBot FAQs ->", "_", disabled=True),
        vbu.Button("I can't disown my child",
                   "FAQ CANT_DISOWN",
                   style=vbu.ButtonStyle.SECONDARY),
        vbu.Button("None of the commands work",
                   "FAQ NO_COMMANDS_WORK",
                   style=vbu.ButtonStyle.SECONDARY),
        vbu.Button("Gold doesn't have my family tree",
                   "FAQ COPY_FAMILY_TO_GOLD",
                   style=vbu.ButtonStyle.SECONDARY),
    ), )


class FAQHandler(vbu.Cog):

    NO_COMMANDS_WORK = 729049284129062932  # setprefix
Beispiel #5
0
    async def shop(self, ctx: utils.Context):
        """
        Shows you the available plants.
        """

        # Get data from the user and set up our variables to be used later
        async with self.bot.database() as db:
            user_rows = await db(
                "SELECT * FROM user_settings WHERE user_id=$1", ctx.author.id)
            plant_level_rows = await db(
                "SELECT * FROM plant_levels WHERE user_id=$1", ctx.author.id)
        if user_rows:
            user_experience = user_rows[0]['user_experience']
            user_plant_limit = user_rows[0]['plant_limit']
            last_plant_shop_time = user_rows[0]['last_plant_shop_time'] or dt(
                2000, 1, 1)
            plant_pot_hue = user_rows[0]['plant_pot_hue'] or ctx.author.id % 360
        else:
            user_experience = 0
            user_plant_limit = 1
            last_plant_shop_time = dt(2000, 1, 1)
            plant_pot_hue = ctx.author.id % 360

        # Work out if the user's cooldown is expired for purchasing new plants
        water_cooldown = timedelta(
            **self.bot.config['plants']['water_cooldown'])
        can_purchase_new_plants = dt.utcnow(
        ) > last_plant_shop_time + water_cooldown
        can_purchase_new_plants = can_purchase_new_plants or ctx.author.id in self.bot.owner_ids
        buy_plant_cooldown = None
        if can_purchase_new_plants is False:
            buy_plant_cooldown = utils.TimeValue(
                ((last_plant_shop_time + water_cooldown) -
                 dt.utcnow()).total_seconds())

        # Set up our initial embed
        all_plants = []  # Used to make sure we can continue the command
        all_items = []  # Used to make sure we can continue the command
        embed = utils.Embed(use_random_colour=True, description="")
        self.bot.set_footer_from_config(embed)

        # Make the initial embed
        embed.description += (
            f"What would you like to spend your experience to buy, {ctx.author.mention}? "
            f"You currently have **{user_experience:,} exp**, and you're using {len(plant_level_rows):,} of your "
            f"{user_plant_limit:,} available plant pots.\n")
        available_plants = await self.get_available_plants(ctx.author.id)

        # Add "can't purchase new plant" to the embed
        if can_purchase_new_plants is False:
            embed.description += f"\nYou can't purchase new plants for another **{buy_plant_cooldown.clean}**.\n"

        # Add plants to the embed
        for plant in sorted(available_plants.values()):
            text = plant.display_name.capitalize()
            disabled = not all([
                can_purchase_new_plants,
                plant.required_experience <= user_experience,
                len(plant_level_rows) < user_plant_limit,
            ])
            all_plants.append({
                "label": text,
                "custom_id": plant.name,
                "disabled": disabled
            })

        # Say when the plants will change
        now = dt.utcnow()
        remaining_time = utils.TimeValue(
            (dt(now.year if now.month < 12 else now.year + 1, now.month +
                1 if now.month < 12 else 1, 1) - now).total_seconds())
        embed.description += f"Your plants will change in {remaining_time.clean_spaced}.\n"

        # See if the user has premium
        user_has_premium = False
        try:
            await localutils.checks.has_premium().predicate(ctx)
            user_has_premium = True
        except commands.CommandError:
            pass

        # See how many pots the user is allowed
        bot_plant_cap = self.bot.config['plants']['hard_plant_cap']
        non_subscriber_plant_cap = self.bot.config['plants'][
            'non_subscriber_plant_cap']
        user_plant_cap = bot_plant_cap if user_has_premium else non_subscriber_plant_cap

        # Add pots
        text = f"Pot ({self.get_points_for_plant_pot(user_plant_limit):,} exp)"
        if user_experience >= self.get_points_for_plant_pot(
                user_plant_limit) and user_plant_limit < user_plant_cap:
            all_items.append({
                "label": text,
                "custom_id": "pot",
                "disabled": False
            })
        elif user_plant_limit >= bot_plant_cap:
            all_items.append({
                "label": "Pot (maximum pots reached)",
                "custom_id": "pot",
                "disabled": True,
                "style": utils.ButtonStyle.SECONDARY
            })
        elif user_plant_limit >= user_plant_cap:
            all_items.append({
                "label": "Pot (maximum free pots reached)",
                "custom_id": "pot_free",
                "disabled": False,
                "style": utils.ButtonStyle.SECONDARY
            })
        else:
            all_items.append({
                "label": text,
                "custom_id": "pot",
                "disabled": True,
                "style": utils.ButtonStyle.SECONDARY
            })

        # Add variable items
        for item in self.bot.items.values():
            text = f"{item.display_name.capitalize()} ({item.price:,} exp)"
            if user_experience >= item.price:
                all_items.append({
                    "label": text,
                    "custom_id": item.name,
                    "disabled": False,
                    "style": utils.ButtonStyle.SECONDARY
                })
            else:
                all_items.append({
                    "label": text,
                    "custom_id": item.name,
                    "disabled": True,
                    "style": utils.ButtonStyle.SECONDARY
                })

        # Add all our items to the embed
        components = utils.MessageComponents(
            utils.ActionRow(*[utils.Button(**i) for i in all_plants]),
            utils.ActionRow(*[utils.Button(**i) for i in all_items]),
        )

        # Cancel if they don't have anything available
        if not [i for i in all_plants + all_items if not i['disabled']]:
            embed.description += "\n**There is currently nothing available which you can purchase.**\n"
            return await ctx.send(ctx.author.mention,
                                  embed=embed,
                                  components=components)
        components.components.append(
            utils.ActionRow(
                utils.Button("Cancel",
                             "cancel",
                             style=utils.ButtonStyle.DANGER)))

        # Wait for them to respond
        shop_menu_message = await ctx.send(
            ctx.author.mention,
            embed=embed,
            components=components,
        )
        try:
            done, pending = await asyncio.wait(
                [
                    self.bot.wait_for(
                        "raw_message_delete",
                        check=lambda m: m.message_id == shop_menu_message.id,
                    ),
                    self.bot.wait_for(
                        "component_interaction",
                        check=lambda p: p.message.id == shop_menu_message.id
                        and ctx.author.id == p.user.id,
                    ),
                ],
                timeout=120,
                return_when=asyncio.FIRST_COMPLETED)
        except asyncio.TimeoutError:
            await shop_menu_message.edit(components=None)
            return await ctx.send(
                f"Timed out asking for plant type {ctx.author.mention}.")

        # See how they responded
        for future in pending:
            future.cancel()
        try:
            done = done.pop().result()
        except KeyError:
            return await ctx.send(
                f"Timed out asking for plant type {ctx.author.mention}.")
        if isinstance(done, discord.RawMessageDeleteEvent):
            return
        payload = done
        given_response = payload.component.custom_id.lower(
        )  # .replace(' ', '_')
        await payload.ack()

        # See if they want to cancel
        if given_response == "cancel":
            return await payload.message.delete()
        await payload.message.edit(components=components.disable_components())

        # See if they want a pot but they're not subbed
        if given_response == "pot_free":
            return await payload.send((
                f"You're already at the maximum amount of pots you can have without "
                f"subscribing - see `{ctx.clean_prefix}donate` for more information."
            ))

        # See if they want a plant pot
        if given_response == "pot":
            if user_plant_limit >= self.bot.config['plants']['hard_plant_cap']:
                return await payload.send(
                    f"You're already at the maximum amount of pots, {ctx.author.mention}! :c"
                )
            if user_experience >= self.get_points_for_plant_pot(
                    user_plant_limit):
                async with self.bot.database() as db:
                    await db(
                        """INSERT INTO user_settings (user_id, plant_limit, user_experience) VALUES ($1, 2, $2)
                        ON CONFLICT (user_id) DO UPDATE SET plant_limit=user_settings.plant_limit+1,
                        user_experience=user_settings.user_experience-excluded.user_experience""",
                        ctx.author.id,
                        self.get_points_for_plant_pot(user_plant_limit))
                return await payload.send(
                    f"Given you another plant pot, {ctx.author.mention}!")
            else:
                return await payload.send(
                    f"You don't have the required experience to get a new plant pot, {ctx.author.mention} :c"
                )

        # See if they want a revival token
        item_type = self.bot.items.get(given_response.replace(' ', '_'))
        if item_type is None:
            try:
                item_type = [
                    i for i in self.bot.items.values()
                    if i.display_name == given_response
                ][0]
            except IndexError:
                item_type = None
        if item_type is not None:
            if user_experience >= item_type.price:
                async with self.bot.database() as db:
                    await db.start_transaction()
                    await db(
                        """INSERT INTO user_settings (user_id, user_experience) VALUES ($1, $2) ON CONFLICT (user_id) DO UPDATE
                        SET user_experience=user_settings.user_experience-excluded.user_experience""",
                        ctx.author.id, item_type.price)
                    await db(
                        """INSERT INTO user_inventory (user_id, item_name, amount) VALUES ($1, $2, 1)
                        ON CONFLICT (user_id, item_name) DO UPDATE SET amount=user_inventory.amount+excluded.amount""",
                        ctx.author.id, item_type.name)
                    await db.commit_transaction()
                return await payload.send((
                    f"Given you a **{item_type.display_name}**, {ctx.author.mention}! You can use it "
                    f"with `{item_type.usage.format(ctx=ctx)}`."))
            else:
                return await payload.send(
                    f"You don't have the required experience to get a **{item_type.display_name}**, {ctx.author.mention} :c",
                )

        # See if they want a plant
        try:
            plant_type = self.bot.plants[given_response.replace(' ', '_')]
        except KeyError:
            return await payload.send(
                f"`{given_response}` isn't an available plant name, {ctx.author.mention}!",
                allowed_mentions=discord.AllowedMentions(users=[ctx.author],
                                                         roles=False,
                                                         everyone=False),
            )
        if can_purchase_new_plants is False:
            return await payload.send(
                f"You can't purchase new plants for another **{buy_plant_cooldown.clean}**.",
            )
        if plant_type not in available_plants.values():
            return await payload.send(
                f"**{plant_type.display_name.capitalize()}** isn't available in your shop this month, {ctx.author.mention} :c",
            )
        if plant_type.required_experience > user_experience:
            return await payload.send((
                f"You don't have the required experience to get a **{plant_type.display_name}**, {ctx.author.mention} "
                f"(it requires {plant_type.required_experience}, you have {user_experience}) :c"
            ))
        if len(plant_level_rows) >= user_plant_limit:
            return await payload.send(
                f"You don't have enough plant pots to be able to get a **{plant_type.display_name}**, {ctx.author.mention} :c",
            )

        # Get a name for the plant
        await payload.send("What name do you want to give your plant?")
        while True:
            try:
                plant_name_message = await self.bot.wait_for(
                    "message",
                    check=lambda m: m.author.id == ctx.author.id and m.channel
                    == ctx.channel and m.content,
                    timeout=120,
                )
            except asyncio.TimeoutError:
                return await payload.send(
                    f"Timed out asking for plant name {ctx.author.mention}.")
            plant_name = localutils.PlantType.validate_name(
                plant_name_message.content)
            if len(plant_name) > 50 or len(plant_name) == 0:
                await plant_name_message.reply(
                    "That name is too long! Please give another one instead!")
            else:
                break

        # Save the enw plant to database
        async with self.bot.database() as db:
            plant_name_exists = await db(
                "SELECT * FROM plant_levels WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2)",
                ctx.author.id, plant_name)
            if plant_name_exists:
                return await plant_name_message.reply(
                    (f"You've already used the name `{plant_name}` for one of your other plants - "
                     "please run this command again to give a new one!"),
                    allowed_mentions=discord.AllowedMentions.none(),
                )
            await db(
                """INSERT INTO plant_levels (user_id, plant_name, plant_type, plant_nourishment,
                last_water_time, original_owner_id, plant_adoption_time, plant_pot_hue)
                VALUES ($1, $2, $3, 0, $4, $1, TIMEZONE('UTC', NOW()), $5) ON
                CONFLICT (user_id, plant_name) DO UPDATE
                SET plant_nourishment=0, last_water_time=$4""",
                ctx.author.id,
                plant_name,
                plant_type.name,
                dt(2000, 1, 1),
                plant_pot_hue,
            )
            await db(
                """UPDATE user_settings SET user_experience=user_settings.user_experience-$2,
                last_plant_shop_time=TIMEZONE('UTC', NOW()) WHERE user_id=$1""",
                ctx.author.id,
                plant_type.required_experience,
            )
            await db(
                """INSERT INTO plant_achievement_counts (user_id, plant_type, plant_count) VALUES ($1, $2, 1)
                ON CONFLICT (user_id, plant_type) DO UPDATE SET
                plant_count=plant_achievement_counts.plant_count+excluded.plant_count""",
                ctx.author.id,
                plant_type.name,
            )
        await plant_name_message.reply(
            f"Planted your **{plant_type.display_name}** seeds!")
Beispiel #6
0
async def paginate(ctx, fields, user, custom_str=None):
    bot = ctx.bot
    curr_index = 1
    curr_field = fields[curr_index - 1]
    embed = create_bucket_embed(user, curr_field, custom_str)

    # Set up the buttons
    left = vbu.Button(custom_id="left",
                      emoji="◀�",
                      style=vbu.ButtonStyle.PRIMARY)
    right = vbu.Button(custom_id="right",
                       emoji="▶�",
                       style=vbu.ButtonStyle.PRIMARY)
    stop = vbu.Button(custom_id="stop",
                      emoji="��",
                      style=vbu.ButtonStyle.DANGER)
    numbers = vbu.Button(custom_id="numbers",
                         emoji="🔢",
                         style=vbu.ButtonStyle.PRIMARY)

    valid_buttons = [left, right, stop]
    if len(fields) > 1:
        valid_buttons.append(numbers)

    # Put the buttons together
    components = vbu.MessageComponents(vbu.ActionRow(*valid_buttons))

    fish_message = await ctx.send(embed=embed, components=components)

    def button_check(payload):

        if payload.message.id != fish_message.id:
            return False

        if payload.component.custom_id in [
                left.custom_id, right.custom_id, stop.custom_id,
                numbers.custom_id
        ]:
            bot.loop.create_task(payload.ack())

        return payload.user.id == ctx.author.id

    while True:  # Keep paginating until the user clicks stop
        try:
            chosen_button_payload = await bot.wait_for('component_interaction',
                                                       timeout=60.0,
                                                       check=button_check)
            chosen_button = chosen_button_payload.component.custom_id.lower()
        except asyncio.TimeoutError:
            chosen_button = "stop"

        index_chooser = {
            'left': max(1, curr_index - 1),
            'right': min(len(fields), curr_index + 1)
        }

        if chosen_button in index_chooser.keys():
            curr_index = index_chooser[
                chosen_button]  # Keep the index in bounds
            curr_field = fields[curr_index - 1]

            await fish_message.edit(
                embed=create_bucket_embed(user, curr_field, custom_str))

        elif chosen_button == "stop":
            await fish_message.edit(components=components.disable_components())
            break  # End the while loop

        elif chosen_button == "numbers" and len(fields) > 1:
            number_message = await ctx.send(
                f"What page would you like to go to? (1-{len(fields)}) ")

            # Check for custom message
            def message_check(message):
                return message.author == ctx.author and message.channel == fish_message.channel and message.content.isdigit(
                )

            user_message = await bot.wait_for('message', check=message_check)
            user_input = int(user_message.content)

            curr_index = min(len(fields), max(1, user_input))
            curr_field = fields[curr_index - 1]

            await fish_message.edit(
                embed=create_bucket_embed(user, curr_field, custom_str))
            await number_message.delete()
            await user_message.delete()
Beispiel #7
0
    async def disown(self,
                     ctx: vbu.Context,
                     *,
                     target: utils.ChildIDConverter = None):
        """
        Lets you remove a user from being your child.
        """

        # Get the user family tree member
        family_guild_id = utils.get_family_guild_id(ctx)
        user_tree = utils.FamilyTreeMember.get(ctx.author.id,
                                               guild_id=family_guild_id)

        # If they didn't give a child, give them a dropdown
        if target is None:

            # Make a list of options
            child_options = []
            for index, child_tree in enumerate(user_tree.children):
                child_name = await utils.DiscordNameManager.fetch_name_by_id(
                    self.bot, child_tree.id)
                child_options.append(
                    vbu.SelectOption(label=child_name,
                                     value=f"DISOWN {child_tree.id}"))
                if index >= 25:
                    return await ctx.send(
                        ("I couldn't work out which of your children you wanted to disown. "
                         "You can ping or use their ID to disown them."),
                        wait=False,
                    )

            # See if they don't have any children
            if not child_options:
                return await ctx.send("You don't have any children!",
                                      wait=False)

            # Wait for them to pick one
            components = vbu.MessageComponents(
                vbu.ActionRow(
                    vbu.SelectMenu(custom_id="DISOWN_USER",
                                   options=child_options), ))
            m = await ctx.send(
                "Which of your children would you like to disown?",
                components=components,
                wait=True,
            )

            # Make our check
            def check(payload: vbu.ComponentInteractionPayload):
                if payload.message.id != m.id:
                    return False
                if payload.user.id != ctx.author.id:
                    self.bot.loop.create_task(
                        payload.respond("You can't respond to this message!",
                                        wait=False,
                                        ephemeral=True))
                    return False
                return True

            try:
                payload = await self.bot.wait_for("component_interaction",
                                                  check=check,
                                                  timeout=60)
                await payload.defer_update()
                await payload.message.delete()
            except asyncio.TimeoutError:
                return await ctx.send(
                    "Timed out asking for which child you want to disown :<",
                    wait=False)

            # Get the child's ID that they selected
            target = int(payload.values[0][len("DISOWN "):])

        # Get the family tree member objects
        child_tree = utils.FamilyTreeMember.get(target,
                                                guild_id=family_guild_id)
        child_name = await utils.DiscordNameManager.fetch_name_by_id(
            self.bot, child_tree.id)

        # Make sure they're actually children
        if child_tree.id not in user_tree._children:
            return await ctx.send(
                f"It doesn't look like **{utils.escape_markdown(child_name)}** is one of your children!",
                allowed_mentions=discord.AllowedMentions.none(),
                wait=False,
            )

        # See if they're sure
        try:
            result = await utils.send_proposal_message(
                ctx,
                ctx.author,
                f"Are you sure you want to disown **{utils.escape_markdown(child_name)}**, {ctx.author.mention}?",
                timeout_message=
                f"Timed out making sure you want to disown, {ctx.author.mention} :<",
                cancel_message="Alright, I've cancelled your disown!",
            )
        except Exception:
            result = None
        if result is None:
            return

        # Remove from cache
        try:
            user_tree._children.remove(child_tree.id)
        except ValueError:
            pass
        child_tree._parent = None

        # Remove from redis
        async with self.bot.redis() as re:
            await re.publish('TreeMemberUpdate', user_tree.to_json())
            await re.publish('TreeMemberUpdate', child_tree.to_json())

        # Remove from database
        async with self.bot.database() as db:
            await db(
                """DELETE FROM parents WHERE child_id=$1 AND parent_id=$2 AND guild_id=$3""",
                child_tree.id,
                ctx.author.id,
                family_guild_id,
            )

        # And we're done
        await result.ctx.send(
            f"You've successfully disowned **{utils.escape_markdown(child_name)}** :c",
            allowed_mentions=discord.AllowedMentions.none(),
            wait=False,
        )
Beispiel #8
0
    async def create_new_field(
            self,
            ctx: utils.Context,
            template: localutils.Template,
            index: int,
            image_set: bool = False,
            prompt_for_creation: bool = True,
            delete_messages: bool = False
    ) -> typing.Optional[localutils.Field]:
        """
        Talk a user through creating a new field for their template.
        """

        # Here are some things we can use later
        def message_check(message):
            return all([
                message.author.id == ctx.author.id,
                message.channel.id == ctx.channel.id,
            ])

        messages_to_delete = []

        # Ask if they want a new field
        if prompt_for_creation:
            field_message = await ctx.send(
                "Do you want to make a new field for your profile?",
                embed=template.build_embed(self.bot),
                components=utils.MessageComponents.boolean_buttons(),
            )
            messages_to_delete.append(field_message)

            # Here's us waiting for the "do you want to make a new field" reaction
            try:
                payload = await field_message.wait_for_button_click(
                    check=lambda p: p.user.id == ctx.author.id, timeout=120)
                await payload.ack()
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being created with the fields currently added."
                    )
                except (discord.Forbidden, discord.NotFound):
                    pass
                return None

            # See if they don't wanna continue
            if payload.component.custom_id == "NO":
                return None
            await field_message.edit(content=field_message.content, embed=None)

        # Get a name for the new field
        field_name = await self.get_field_name(ctx, messages_to_delete)
        if field_name is None:
            return

        # Get a prompt for the field
        v = await ctx.send((
            "What message should I send when I'm asking people to fill out this field? "
            "This should be a question or prompt, eg 'What is your name/age/gender/etc'."
        ))
        messages_to_delete.append(v)
        while True:
            try:
                field_prompt_message = await self.bot.wait_for(
                    'message', check=message_check, timeout=120)
                messages_to_delete.append(field_prompt_message)
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being "
                        "created with the fields currently added.")
                except (discord.Forbidden, discord.NotFound):
                    pass
                return None

            if len(field_prompt_message.content) >= 1:
                break
            else:
                v = await ctx.send(
                    "You need to actually give text for the prompt :/")
                messages_to_delete.append(v)
        field_prompt = field_prompt_message.content
        prompt_is_command = bool(
            localutils.CommandProcessor.COMMAND_REGEX.search(field_prompt))

        # If it's a command, then we don't need to deal with this
        if not prompt_is_command:

            # Get field optional
            prompt_message = await ctx.send(
                "Is this field optional?",
                components=utils.MessageComponents.boolean_buttons(),
            )
            messages_to_delete.append(prompt_message)
            try:
                payload = await prompt_message.wait_for_button_click(
                    check=lambda p: p.user.id == ctx.author.id, timeout=120)
                await payload.ack()
                field_optional_emoji = payload.component.custom_id
            except asyncio.TimeoutError:
                field_optional_emoji = "NO"
            field_optional = field_optional_emoji == "YES"
            self.bot.loop.create_task(
                payload.message.edit(components=utils.MessageComponents.
                                     boolean_buttons().disable_components(), ))

            # Get timeout
            v = await ctx.send((
                "How many seconds should I wait for people to fill out this field (I recommend 120 - "
                "that's 2 minutes)? The minimum is 30, and the maximum is 600."
            ))
            messages_to_delete.append(v)
            while True:
                try:
                    field_timeout_message = await self.bot.wait_for(
                        'message', check=message_check, timeout=120)
                    messages_to_delete.append(field_timeout_message)
                except asyncio.TimeoutError:
                    await ctx.send(
                        "Creating a new field has timed out. The profile is being created with the fields currently added."
                    )
                    return None
                try:
                    timeout = int(field_timeout_message.content)
                    if timeout < 30:
                        raise ValueError()
                    break
                except ValueError:
                    v = await ctx.send(
                        "I couldn't convert your message into a valid number - the minimum is 30 seconds. Please try again."
                    )
                    messages_to_delete.append(v)
            field_timeout = min([timeout, 600])

            # Ask for a field type
            action_row = utils.ActionRow()
            action_row.add_component(utils.Button("Text", custom_id="TEXT"))
            action_row.add_component(
                utils.Button("Numbers", custom_id="NUMBERS"))
            if not image_set:
                action_row.add_component(
                    utils.Button("Image", custom_id="IMAGE"))
            components = utils.MessageComponents(action_row)
            field_type_message = await ctx.send("What type is this field?",
                                                components=components)
            messages_to_delete.append(field_type_message)

            # See what they said
            try:
                payload = await field_type_message.wait_for_button_click(
                    check=lambda p: p.user.id == ctx.author.id, timeout=120)
                await payload.ack()
                key = payload.component.custom_id
            except asyncio.TimeoutError:
                try:
                    await ctx.send(
                        "Picking a field type has timed out - defaulting to text."
                    )
                except (discord.Forbidden, discord.NotFound):
                    pass
                key = "TEXT"

            # Change that emoji into a datatype
            field_type = {
                "NUMBERS": localutils.NumberField,
                "TEXT": localutils.TextField,
                "IMAGE": localutils.ImageField,
            }[key]
            if isinstance(field_type, localutils.ImageField) and image_set:
                raise Exception(
                    "You shouldn't be able to set two image fields.")

        # Set some defaults for the field stuff
        else:
            field_optional = False
            field_timeout = 15
            field_type = localutils.TextField

        # Make the field object
        field = localutils.Field(
            field_id=uuid.uuid4(),
            name=field_name,
            index=index,
            prompt=field_prompt,
            timeout=field_timeout,
            field_type=field_type,
            template_id=template.template_id,
            optional=field_optional,
            deleted=False,
        )

        # See if we need to delete things
        if delete_messages:
            self.purge_message_list(ctx.channel, messages_to_delete)

        # And we done
        return field
Beispiel #9
0
async def send_proposal_message(
        ctx,
        user: discord.Member,
        text: str,
        *,
        timeout_message: str = None,
        cancel_message: str = None,
        allow_bots: bool = False) -> TickPayloadCheckResult:
    """
    Send a proposal message out to the user to see if they want to say yes or no.

    Args:
        ctx (utils.Context): The context object for the called command.
        user (discord.Member): The user who the calling user wants to ask out.
        text (str): The text to be sent when the user's proposal is started.

    Returns:
        TickPayloadCheckResult: The resulting reaction that either the user or the author gave.
    """

    timeout_message = timeout_message or f"Sorry, {ctx.author.mention}; your request to {user.mention} timed out - they didn't respond in time :<"
    cancel_message = cancel_message or f"Alright, {ctx.author.mention}; your request to {user.mention} has been cancelled."

    # Reply yes if we allow bots
    if allow_bots and user.bot:
        return TickPayloadCheckResult(
            ctx, TickPayloadCheckResult.BOOLEAN_EMOJIS["TICK"][0])

    # See if they want to say yes
    components = utils.ActionRow(
        utils.Button(
            "Yes",
            emoji=TickPayloadCheckResult.BOOLEAN_EMOJIS["TICK"][0],
            style=utils.ButtonStyle.SUCCESS,
        ),
        utils.Button(
            "No",
            emoji=TickPayloadCheckResult.BOOLEAN_EMOJIS["CROSS"][0],
            style=utils.ButtonStyle.DANGER,
        ),
    )
    message = await ctx.send(
        text, components=components
    )  # f"Hey, {user.mention}, do you want to adopt {ctx.author.mention}?"
    try:

        def check(p):
            if p.message.id != message.id:
                return False
            if p.user.id not in [user.id, ctx.author.id]:
                return False
            result = TickPayloadCheckResult.from_payload(p)
            if p.user.id == user.id:
                return result
            if p.user.id == ctx.author.id:
                return str(p.button.emoji) in result.BOOLEAN_EMOJIS["CROSS"]
            return False

        button_event = await ctx.bot.wait_for("button_click",
                                              check=check,
                                              timeout=60)
    except asyncio.TimeoutError:
        for button in components.components:
            button.disabled = True
        ctx.bot.loop.create_task(message.edit(components=components))
        await ctx.send(timeout_message,
                       allowed_mentions=only_mention(ctx.author))
        return None

    # Check what they said
    for button in components.components:
        button.disabled = True
    ctx.bot.loop.create_task(message.edit(components=components))
    result = TickPayloadCheckResult.from_payload(button_event)
    if not result.is_tick:
        if button_event.user.id == ctx.author.id:
            await result.ctx.send(cancel_message,
                                  allowed_mentions=only_mention(ctx.author))
            return None
        await result.ctx.send(f"Sorry, {ctx.author.mention}; they said no :<")
        return None

    # Alright we done
    return result
    async def blackjack(self,
                        ctx: utils.Context,
                        *,
                        bet: localutils.BetAmount = None):
        """
        Lets you play a blackjack game against the bot.
        """

        # Create the deck and hands used for the game
        deck: localutils.Deck = localutils.Deck.create_deck(shuffle=True)
        dealer_hand: localutils.Hand = localutils.Hand(deck)
        dealer_hand.draw(2)
        user_hand: localutils.Hand = localutils.Hand(deck)
        user_hand.draw(2)
        bet = bet or localutils.CurrencyAmount()
        components = utils.MessageComponents(
            utils.ActionRow(
                utils.Button("HIT", "HIT"),
                utils.Button("STAND", "STAND"),
            ), )

        # Ask the user if they want to hit or stand
        message = None
        while True:

            # See if the user went bust
            if min(user_hand.get_values()) > 21:
                embed = utils.Embed(colour=discord.Colour.red())
                embed.add_field("Dealer Hand",
                                f"{dealer_hand.display(show_cards=1)} (??)",
                                inline=True)
                embed.add_field(
                    "Your Hand",
                    f"{user_hand.display()} ({user_hand.get_values()[-1]} - bust)",
                    inline=True)
                if bet.amount:
                    embed.add_field(
                        "Result",
                        f"You lost, removed **{bet.amount:,}** from your account :c",
                        inline=False)
                else:
                    embed.add_field("Result", "You lost :c", inline=False)
                self.bot.dispatch("transaction", ctx.author, bet.currency,
                                  -bet.amount, "BLACKJACK", False)
                return await message.edit(
                    embed=embed, components=components.disable_components())
            if max(user_hand.get_values(max_value=21)) == 21:
                break

            # Output the hands to be used
            embed = utils.Embed(colour=0xfffffe)
            embed.add_field("Dealer Hand",
                            f"{dealer_hand.display(show_cards=1)} (??)",
                            inline=True)
            embed.add_field(
                "Your Hand",
                f"{user_hand.display()} ({', '.join(user_hand.get_values(cast=str, max_value=21))})",
                inline=True)
            if message is None:
                message = await ctx.send(embed=embed, components=components)
            else:
                await message.edit(embed=embed)

            # See what the user wants to do
            def check(payload):
                return all([
                    payload.message.id == message.id,
                    payload.user.id == ctx.author.id,
                ])

            try:
                payload = await self.bot.wait_for("component_interaction",
                                                  check=check,
                                                  timeout=120)
                await payload.ack()
            except asyncio.TimeoutError:
                return await ctx.send("Timed out waiting for your response.")

            # See if they want to stand
            clicked = payload.component
            if clicked.custom_id == "STAND":
                break

            # See if they want to hit
            user_hand.draw()
            await asyncio.sleep(0.2)

        # Let's draw until we get higher than the user
        user_max_value = max(user_hand.get_values(max_value=21))
        user_has_won = None
        while True:
            try:
                max_dealer_value = max(dealer_hand.get_values(max_value=21))
                if max_dealer_value >= user_max_value:
                    user_has_won = False  # Dealer wins
                    break
            except ValueError:
                user_has_won = True  # Dealer went bust
                break
            dealer_hand.draw()

        # Make sure we got a value and I didn't mess anything up
        if user_has_won is None:
            raise Exception("Failed to run the command properly.")

        # Don't error if the user got a blackjack
        if message:
            send_method = message.edit
            components = components.disable_components()
        else:
            send_method = ctx.send
            components = None

        # Output something for the user winning
        if user_has_won:
            embed = utils.Embed(colour=discord.Colour.green())
            if min(dealer_hand.get_values()) > 21:
                embed.add_field(
                    "Dealer Hand",
                    f"{dealer_hand.display()} ({dealer_hand.get_values()[-1]} - bust)",
                    inline=True)
            else:
                embed.add_field(
                    "Dealer Hand",
                    f"{dealer_hand.display()} ({dealer_hand.get_values()[0]})",
                    inline=True)
            embed.add_field(
                "Your Hand",
                f"{user_hand.display()} ({user_hand.get_values(max_value=21)[0]})",
                inline=True)
            if bet.amount:
                embed.add_field(
                    "Result",
                    f"You won! Added **{bet.amount:,}** to your account! :D",
                    inline=False)
            else:
                embed.add_field("Result", "You won! :D", inline=False)
            self.bot.dispatch("transaction", ctx.author, bet.currency,
                              bet.amount, "BLACKJACK", True)
            return await send_method(embed=embed, components=components)

        # Output something for the dealer winning
        embed = utils.Embed(colour=discord.Colour.red())
        embed.add_field(
            "Dealer Hand",
            f"{dealer_hand.display()} ({dealer_hand.get_values(max_value=21)[0]})",
            inline=True)
        embed.add_field(
            "Your Hand",
            f"{user_hand.display()} ({user_hand.get_values(max_value=21)[0]})",
            inline=True)
        if max_dealer_value > user_max_value:
            if bet.amount:
                embed.add_field(
                    "Result",
                    f"You lost, removed **{bet.amount:,}** from your account :c",
                    inline=False)
            else:
                embed.add_field("Result", "You lost :c", inline=False)
        else:
            if bet.amount:
                embed.add_field(
                    "Result",
                    f"You tied, returned **{bet.amount:,}** to your account :<",
                    inline=False)
            else:
                embed.add_field("Result", "You tied :<", inline=False)
        if max_dealer_value > user_max_value:
            self.bot.dispatch("transaction", ctx.author, bet.currency,
                              -bet.amount, "BLACKJACK", False)
        else:
            self.bot.dispatch("transaction", ctx.author, bet.currency, 0,
                              "BLACKJACK", False)
        return await send_method(embed=embed, components=components)
Beispiel #11
0
    async def edit_field(self, ctx: utils.Context,
                         template: localutils.Template, guild_settings: dict,
                         is_bot_support: bool) -> bool:
        """
        Talk the user through editing a field of a template.
        Returns whether or not the template display needs to be updated, or None for an error (like a timeout).
        """

        # Create some components to add to the message
        if len(template.fields) == 0:
            components = None
        else:
            field_name_buttons = [
                utils.Button(field.name[:25],
                             style=utils.ButtonStyle.SECONDARY,
                             custom_id=field.field_id)
                for field in template.field_list
            ]
            max_field_count = max(guild_settings['max_template_field_count'],
                                  ctx.guild_perks.max_field_count)
            if len(template.fields) < max_field_count or is_bot_support:
                components = utils.MessageComponents.add_buttons_with_rows(
                    utils.Button("New", custom_id="NEW"), *field_name_buttons)
            else:
                components = utils.MessageComponents.add_buttons_with_rows(
                    utils.Button("New (unavailable)", custom_id="NEW"),
                    *field_name_buttons)

        # Send a message asking what they want to edit
        if components:
            ask_field_edit_message: discord.Message = await ctx.send(
                "Which field do you want to edit?", components=components)
            messages_to_delete = [ask_field_edit_message]
        else:
            ask_field_edit_message = None
            messages_to_delete = []

        # Get field index message
        field_to_edit: localutils.Field = await self.get_field_to_edit(
            ctx,
            template,
            ask_field_edit_message,
            guild_settings,
            is_bot_support,
        )
        if not isinstance(field_to_edit, localutils.Field):
            self.purge_message_list(ctx.channel, messages_to_delete)
            return field_to_edit  # A new field was created - let's just exit here
        await ask_field_edit_message.edit(
            components=components.disable_components())

        # Ask what part of it they want to edit
        components = utils.MessageComponents(
            utils.ActionRow(
                utils.Button("Field name", custom_id="NAME"),
                utils.Button("Field prompt", custom_id="PROMPT"),
                utils.Button("Field being optional", custom_id="OPTIONAL"),
                utils.Button("Field type", custom_id="TYPE"),
            ),
            utils.ActionRow(
                utils.Button("Delete field",
                             style=utils.ButtonStyle.DANGER,
                             custom_id="DELETE"),
                utils.Button("Cancel",
                             style=utils.ButtonStyle.SECONDARY,
                             custom_id="CANCEL"),
            ),
        )
        attribute_message: discord.Message = await ctx.send(
            f"Editing the field **{field_to_edit.name}**. Which part would you like to edit?",
            allowed_mentions=discord.AllowedMentions.none(),
            components=components)
        messages_to_delete.append(attribute_message)

        # Wait for them to say what they want to edit
        try:
            payload = await attribute_message.wait_for_button_click(
                check=lambda p: p.user.id == ctx.author.id, timeout=120)
            await payload.ack()
            payload_id = payload.component.custom_id
        except asyncio.TimeoutError:
            try:
                await attribute_message.edit(
                    content="Timed out waiting for field attribute.",
                    components=None)
            except discord.HTTPException:
                pass
            return None

        # See if they want to cancel
        if payload_id == "CANCEL":
            self.purge_message_list(ctx.channel, messages_to_delete)
            return False
        await attribute_message.edit(
            components=components.disable_components())

        # Let's set up our validity converters for each of the fields
        def name_validity_checker(given_value):
            if len(given_value) > 256 or len(given_value) <= 0:
                return "Your given field name is too long. Please provide another."
            return True

        # See what they reacted with
        try:
            available_reactions = {
                "NAME": (
                    "name",
                    "What do you want to set the name of this field to?",
                    lambda given:
                    "Your given field name is too long. Please provide another."
                    if 0 >= len(given) > 256 else True,
                    None,
                ),
                "PROMPT": (
                    "prompt",
                    "What do you want to set the prompt for this field to?",
                    lambda given:
                    "Your given field prompt is too short. Please provide another."
                    if len(given) == 0 else True,
                    None,
                ),
                "OPTIONAL": (
                    "optional",
                    "Do you want this field to be optional?",
                    None,
                    utils.MessageComponents.boolean_buttons(),
                ),
                "TYPE": (
                    "field_type",
                    "What type do you want this field to have?",
                    None,
                    utils.MessageComponents(
                        utils.ActionRow(utils.Button("Text", "TEXT"),
                                        utils.Button("Numbers", "NUMBERS"))),
                ),
                "DELETE":
                None,
                # "CANCEL": None,
            }
            changed_attribute, prompt, value_check, components = available_reactions[
                payload_id]
        except TypeError:
            changed_attribute = None  # Delete field

        # Get the value they asked for
        field_value_message = None
        if changed_attribute:

            # Loop so we can deal with invalid values
            while True:

                # Send the prompt
                v = await ctx.send(prompt, components=components)
                messages_to_delete.append(v)

                # Ask the user for some content
                try:
                    if value_check:

                        def check(message):
                            return all([
                                message.author.id == ctx.author.id,
                                message.channel.id == ctx.channel.id,
                            ])

                        field_value_message = await self.bot.wait_for(
                            "message", check=check, timeout=120)
                        messages_to_delete.append(field_value_message)
                        field_value = str(field_value_message.content)
                    elif components:
                        payload = await v.wait_for_button_click(
                            check=lambda p: p.user.id == ctx.author.id,
                            timeout=120)
                        await payload.ack()
                        field_value = {
                            "YES": True,
                            "NO": False,
                            "TEXT": localutils.TextField.name,
                            "NUMBERS": localutils.NumberField.name,
                        }[payload.component.custom_id]
                    else:
                        raise Exception("You shouldn't be able to get here.")

                # Value failed to convert
                except ValueError:
                    v = await ctx.send(
                        "I couldn't convert your provided value properly. Please provide another."
                    )
                    messages_to_delete.append(v)
                    continue

                # Timed out
                except asyncio.TimeoutError:
                    try:
                        await ctx.send("Timed out waiting for field value.")
                    except discord.HTTPException:
                        pass
                    return None

                # Fix up the inputs
                if value_check:
                    value_is_valid = value_check(field_value)
                    if isinstance(value_is_valid, str) or isinstance(
                            value_is_valid, bool) and value_is_valid is False:
                        v = await ctx.send(
                            value_is_valid or
                            "Your provided value is invalid. Please provide another."
                        )
                        messages_to_delete.append(v)
                        continue
                break

        # Save the data
        async with self.bot.database() as db:
            if changed_attribute:
                await db(
                    """UPDATE field SET {0}=$2 WHERE field_id=$1""".format(
                        changed_attribute),
                    field_to_edit.field_id,
                    field_value,
                )
            else:
                await db(
                    """UPDATE field SET deleted=true WHERE field_id=$1""",
                    field_to_edit.field_id,
                )

        # And done
        self.purge_message_list(ctx.channel, messages_to_delete)
        return True
Beispiel #12
0
    async def issue_create(self,
                           ctx: vbu.Context,
                           repo: GitRepo,
                           *,
                           title: str,
                           body: str = ""):
        """
        Create a Github issue on a given repo.
        """

        # Get the database because whatever why not
        async with self.bot.database() as db:
            user_rows = await db(
                "SELECT * FROM user_settings WHERE user_id=$1", ctx.author.id)
            if not user_rows or not user_rows[0][
                    f'{repo.host.lower()}_username']:
                return await ctx.send(
                    (f"You need to link your {repo.host} account to Discord to run this "
                     f"command - see the website at `{ctx.clean_prefix}info`."
                     ),
                    wait=False,
                )

        # Work out what components we want to use
        embed = vbu.Embed(title=title,
                          description=body,
                          use_random_colour=True).set_footer(text=str(repo))
        components = vbu.MessageComponents.boolean_buttons()
        components.components[0].components.append(
            vbu.Button("Set title", "TITLE"))
        components.components[0].components.append(
            vbu.Button("Set body", "BODY"))
        components.components[0].components.append(
            vbu.Button("Set repo", "REPO"))
        options = [
            vbu.SelectOption(label=i.name,
                             value=i.name,
                             description=i.description)
            for i in await repo.get_labels(user_rows[0])
        ]
        components.add_component(
            vbu.ActionRow(
                vbu.SelectMenu(
                    custom_id="LABELS",
                    min_values=0,
                    max_values=len(options),
                    options=options,
                )))
        labels = []

        # Ask if we want to do this
        m = None
        while True:

            # See if we want to update the body
            embed = vbu.Embed(
                title=title,
                description=body or "...",
                use_random_colour=True,
            ).set_footer(text=str(repo), ).add_field(
                "Labels",
                ", ".join([f"`{i}`" for i in labels]) or "...",
            )
            if m is None:
                m = await ctx.send(
                    "Are you sure you want to create this issue?",
                    embed=embed,
                    components=components)
            else:
                await m.edit(embed=embed,
                             components=components.enable_components())
            try:
                payload = await m.wait_for_button_click(
                    check=lambda p: p.user.id == ctx.author.id, timeout=120)
            except asyncio.TimeoutError:
                return await ctx.send(
                    "Timed out asking for issue create confirmation.")

            # Disable components
            if payload.component.custom_id not in ["LABELS"]:
                await payload.update_message(
                    components=components.disable_components())

            # Get the body
            if payload.component.custom_id == "BODY":

                # Wait for their body message
                n = await payload.send(
                    "What body content do you want to be added to your issue?")
                try:
                    check = lambda n: n.author.id == ctx.author.id and n.channel.id == ctx.channel.id
                    body_message = await self.bot.wait_for("message",
                                                           check=check,
                                                           timeout=60 * 5)
                except asyncio.TimeoutError:
                    return await payload.send(
                        "Timed out asking for issue body text.")

                # Grab the attachments
                attachment_urls = []
                for i in body_message.attachments:
                    try:
                        async with self.bot.session.get(i.url) as r:
                            data = await r.read()
                        file = discord.File(io.BytesIO(data),
                                            filename=i.filename)
                        cache_message = await ctx.author.send(file=file)
                        attachment_urls.append(
                            (file.filename, cache_message.attachments[0].url))
                    except discord.HTTPException:
                        break

                # Delete their body message and our asking message
                try:
                    await n.delete()
                    await body_message.delete()
                except discord.HTTPException:
                    pass

                # Fix up the body
                body = body.strip() + "\n\n" + body_message.content + "\n\n"
                for name, url in attachment_urls:
                    body += f"![{name}]({url})\n"

            # Get the title
            elif payload.component.custom_id == "TITLE":

                # Wait for their body message
                n = await payload.send(
                    "What do you want to set the issue title to?")
                try:
                    check = lambda n: n.author.id == ctx.author.id and n.channel.id == ctx.channel.id
                    title_message = await self.bot.wait_for("message",
                                                            check=check,
                                                            timeout=60 * 5)
                except asyncio.TimeoutError:
                    return await payload.send(
                        "Timed out asking for issue title text.")

                # Delete their body message and our asking message
                try:
                    await n.delete()
                    await title_message.delete()
                except discord.HTTPException:
                    pass
                title = title_message.content

            # Get the repo
            elif payload.component.custom_id == "REPO":

                # Wait for their body message
                n = await payload.send("What do you want to set the repo to?")
                try:
                    check = lambda n: n.author.id == ctx.author.id and n.channel.id == ctx.channel.id
                    repo_message = await self.bot.wait_for("message",
                                                           check=check,
                                                           timeout=60 * 5)
                except asyncio.TimeoutError:
                    return await payload.send(
                        "Timed out asking for issue title text.")

                # Delete their body message and our asking message
                try:
                    await n.delete()
                    await repo_message.delete()
                except discord.HTTPException:
                    pass

                # Edit the message
                try:
                    repo = await GitRepo.convert(ctx, repo_message.content)
                except Exception:
                    await ctx.send(
                        f"That repo isn't valid, {ctx.author.mention}",
                        delete_after=3)

            # Get the labels
            elif payload.component.custom_id == "LABELS":
                await payload.defer_update()
                labels = payload.values

            # Check for exiting
            elif payload.component.custom_id == "NO":
                return await payload.send("Alright, cancelling issue add.",
                                          wait=False)
            elif payload.component.custom_id == "YES":
                break

        # Work out our args
        if repo.host == "Github":
            json = {'title': title, 'body': body.strip(), 'labels': labels}
            headers = {
                'Accept': 'application/vnd.github.v3+json',
                'Authorization': f"token {user_rows[0]['github_access_token']}"
            }
        elif repo.host == "Gitlab":
            json = {
                'title': title,
                'description': body.strip(),
                'labels': ",".join(labels)
            }
            headers = {
                'Authorization':
                f"Bearer {user_rows[0]['gitlab_bearer_token']}"
            }
        headers.update({'User-Agent': self.bot.user_agent})

        # Make the post request
        async with self.bot.session.post(repo.issue_api_url,
                                         json=json,
                                         headers=headers) as r:
            data = await r.json()
            self.logger.info(f"Received data from git {r.url!s} - {data!s}")
            if 200 <= r.status < 300:
                pass
            else:
                return await ctx.send(
                    f"I was unable to create an issue on that repository - `{data}`.",
                    wait=False)
        await ctx.send(
            f"Your issue has been created - <{data.get('html_url') or data.get('web_url')}>.",
            wait=False)
Beispiel #13
0
    async def send_guild_bong_message(self, text: str, now: dt, guild_id: int,
                                      settings: dict,
                                      channels_to_delete: list):
        """
        An async function that does the actual sending of the bong message.
        """

        avatar_url = f"https://raw.githubusercontent.com/Voxel-Fox-Ltd/BigBen/master/config/images/{now.hour % 12}.png"

        # Try for the guild
        try:

            # Lets set our channel ID here
            channel_id = settings['bong_channel_id']
            if channel_id is None:
                return

            # Can we hope we have a webhook?
            payload = {}
            if not settings.get("bong_channel_webhook"):
                return  # Death to channel sends

            # Grab webook
            webhook_url = settings.get("bong_channel_webhook")
            url = webhook_url + "?wait=1"
            payload.update({
                "wait": True,
                "username": self.bot.user.name,
                "avatar_url": avatar_url,
            })

            # Set up our emoji to be added
            emoji = settings['bong_emoji']
            if emoji is not None:
                if emoji.startswith("<"):
                    match = self.EMOJI_REGEX.search(emoji)
                    found = match.group("id")
                    if not self.bot.get_emoji(int(found)):
                        self.logger.info(
                            f"Add reaction cancelled - emoji with ID {found} not found"
                        )
                        emoji = None

            # See if we should get some other text
            override_text = settings.get('override_text',
                                         {}).get(f"{now.month}-{now.day}")
            payload['content'] = override_text or text

            # Set up the components to be added
            if emoji:
                payload['components'] = vbu.MessageComponents(
                    vbu.ActionRow(
                        vbu.Button(
                            custom_id="BONG MESSAGE BUTTON",
                            emoji=emoji,
                            style=vbu.ButtonStyle.SECONDARY,
                        ))).to_dict()

            # Send message
            try:
                site = await self.bot.session.post(url, json=payload)
                message_payload = await site.json()
            except (discord.Forbidden, discord.NotFound,
                    discord.HTTPException) as e:
                self.logger.info(
                    f"Send failed - {e} (G{guild_id}/C{channel_id})")
                return

            # Cache message
            self.bong_messages.add(int(message_payload['id']))
            self.logger.info(
                f"Sent bong message to channel (G{guild_id}/C{channel_id}/M{message_payload['id']})"
            )

        except Exception as e:
            self.logger.info(
                f"Failed sending message to guild (G{guild_id}) - {e}")
Beispiel #14
0
    async def achievements(self, ctx: commands.Context):
        # The milestones for each achievement type
        milestones = {
            'times_entertained':
            [5, 25, 100, 250, 500, 1000, 5000, 10000, 50000, 100000, 1000000],
            'times_fed':
            [5, 25, 100, 250, 500, 1000, 5000, 10000, 50000, 100000, 1000000],
            'times_cleaned':
            [5, 25, 100, 250, 500, 1000, 5000, 10000, 50000, 100000, 1000000],
            'times_caught':
            [5, 25, 100, 250, 500, 1000, 5000, 10000, 50000, 100000, 1000000],
            'tanks_owned': [1, 3, 5, 10],
            'times_gambled':
            [5, 25, 100, 250, 500, 1000, 5000, 10000, 50000, 100000, 1000000],
            'money_gained': [
                100, 250, 500, 1000, 5000, 10000, 50000, 100000, 1000000,
                10000000, 100000000
            ],
        }

        # Database variables
        async with self.bot.database() as db:
            achievement_data_milestones = await db(
                """SELECT * FROM user_achievements_milestones WHERE user_id = $1""",
                ctx.author.id)
            achievement_data = await db(
                """SELECT * FROM user_achievements WHERE user_id = $1""",
                ctx.author.id)
            tank_data = await db(
                """SELECT tank FROM user_tank_inventory WHERE user_id = $1""",
                ctx.author.id)
            if not achievement_data:
                achievement_data = await db(
                    """INSERT INTO user_achievements (user_id) VALUES ($1) RETURNING *""",
                    ctx.author.id)
            if not achievement_data_milestones:
                achievement_data_milestones = await db(
                    """INSERT INTO user_achievements_milestones (user_id) VALUES ($1) RETURNING *""",
                    ctx.author.id)

        # Getting the users data into a dictionary for the embed and ease of access
        data = {}
        for value_type, count in achievement_data[0].items():
            if value_type != "user_id":
                data[value_type] = count

        # Getting the users amount of tanks and adding that to the user data dictionary
        tanks = 0
        if not tank_data:
            tanks = 0
        else:
            for tank in tank_data[0]['tank']:
                if tank is True:
                    tanks += 1
        data["tanks_owned"] = tanks

        # Setting claimable to non as default
        claimable_nonclaimable = "nonclaimable"
        claimable_dict = {}
        claims = False

        # Creating the embed as well as checking if the achievement is claimable
        embed = discord.Embed(
            title=f"**{ctx.author.display_name}**'s achievements")
        for type, value in data.items():
            stars = []
            for data in milestones[type]:
                if data <= value:
                    stars.append("<:achievement_star:877646167087906816>")
                else:
                    stars.append("<:achievement_star_no:877646167222141008>")
            milestone = f"{type}_milestone"
            if value >= achievement_data_milestones[0][milestone]:
                if claims is False:
                    claims = True
                claimable_dict[type] = achievement_data_milestones[0][
                    milestone]
                claimable_nonclaimable = "claimable"
            embed.add_field(
                name=type.replace('_', ' ').title(),
                value=
                f"{value:,}/{achievement_data_milestones[0][milestone]:,} **{claimable_nonclaimable}** \n{''.join(stars)}"
            )

        if claims is True:
            components = vbu.MessageComponents(
                vbu.ActionRow(
                    vbu.Button(custom_id="claim_all",
                               emoji="1\N{COMBINING ENCLOSING KEYCAP}"), ), )
            claim_message = await ctx.send(embed=embed, components=components)
        else:
            return await ctx.send(embed=embed)

        # Make the button check
        def button_check(payload):
            if payload.message.id != claim_message.id:
                return False
            self.bot.loop.create_task(payload.defer_update())
            return payload.user.id == ctx.author.id

        pressed = False
        while True:

            # Wait for them to click a button
            try:
                chosen_button_payload = await self.bot.wait_for(
                    'component_interaction', timeout=60.0, check=button_check)
                chosen_button = chosen_button_payload.component.custom_id.lower(
                )
            except asyncio.TimeoutError:
                await claim_message.edit(
                    components=components.disable_components())
                break

            reward = 0
            # Update the displayed emoji
            if chosen_button == "claim_all":
                pressed = True
                for type, value in claimable_dict.items():
                    for count, data in enumerate(milestones[type]):
                        reward += (count + 1)
                        print(count)
                        print(reward)
                        print(data)
                        if data >= value:
                            new_milestone = milestones[type][(count + 1)]
                            print(new_milestone)
                            break
                    async with self.bot.database() as db:
                        await db(
                            """UPDATE user_achievements_milestones SET {0} = $1 WHERE user_id = $2"""
                            .format(f"{type}_milestone"), new_milestone,
                            ctx.author.id)
                async with self.bot.database() as db:
                    await db(
                        """INSERT INTO user_balance (user_id, doubloon) VALUES ($1, $2)
                        ON CONFLICT (user_id) DO UPDATE SET doubloon = user_balance.doubloon + $2""",
                        ctx.author.id, reward)
                components.get_component(chosen_button).disable()
            break
        if pressed is True:
            await ctx.send(f"Rewards claimed, you earned {reward} doubloons!")
Beispiel #15
0
    async def achievements(self, ctx: commands.Context):
        """
        Shows the achievements and lets the user claim them.
        """
        # The milestones for each achievement type
        milestones_dict_of_achievements = {
            'times_entertained':
            [5, 25, 100, 250, 500, 1000, 5000, 10000, 100000, 1000000],
            'times_fed':
            [5, 25, 25, 50, 100, 500, 1000, 10000, 100000, 1000000],
            'times_cleaned':
            [5, 25, 100, 250, 500, 1000, 5000, 10000, 100000, 1000000],
            'times_caught':
            [5, 25, 100, 250, 500, 1000, 5000, 10000, 100000, 1000000],
            'tanks_owned': [1, 3, 5, 10],
            'times_gambled':
            [5, 25, 100, 250, 500, 1000, 5000, 10000, 100000, 1000000],
            'money_gained': [
                100, 500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000,
                10000000
            ],
        }

        # Database variables
        async with self.bot.database() as db:
            user_achievement_milestone_data = await db(
                """SELECT * FROM user_achievements_milestones WHERE user_id = $1""",
                ctx.author.id)
            user_achievement_data = await db(
                """SELECT * FROM user_achievements WHERE user_id = $1""",
                ctx.author.id)
            tank_data = await db(
                """SELECT tank FROM user_tank_inventory WHERE user_id = $1""",
                ctx.author.id)
            if not user_achievement_data:
                user_achievement_data = await db(
                    """INSERT INTO user_achievements (user_id) VALUES ($1) RETURNING *""",
                    ctx.author.id)
            if not user_achievement_milestone_data:
                user_achievement_milestone_data = await db(
                    """INSERT INTO user_achievements_milestones (user_id) VALUES ($1) RETURNING *""",
                    ctx.author.id)

        # Getting the users data into a dictionary for the embed and ease of access
        user_achievement_data_dict = {}
        for achievement_type_database, achievement_amount_database in user_achievement_data[
                0].items():
            if achievement_type_database != "user_id":
                user_achievement_data_dict[
                    achievement_type_database] = achievement_amount_database

        # Getting the users amount of tanks and adding that to the user data dictionary
        tanks = 0
        if not tank_data:
            tanks = 0
        else:
            for tank in tank_data[0]['tank']:
                if tank is True:
                    tanks += 1
        user_achievement_data_dict["tanks_owned"] = tanks

        # Setting claimable to non as default
        Achievements_that_are_claimable = {}
        are_there_any_claimable_achievements_check = False

        # Creating the embed
        embed = discord.Embed(
            title=f"**{ctx.author.display_name}**'s achievements")

        # Set Variables for milestones, default to nonclaimable, and default stars to nothing
        for achievement, user_achievement_value in user_achievement_data_dict.items(
        ):
            milestone = f"{achievement}_milestone"
            is_achievement_claimable = "nonclaimable"
            list_of_stars_per_achievement = []
            # Checks what type of star to add
            for milestone_value in milestones_dict_of_achievements[
                    achievement]:
                if user_achievement_milestone_data[0][
                        f"{milestone}_done"] is True:
                    list_of_stars_per_achievement.append(
                        "<:achievement_star:877646167087906816>")
                elif milestone_value < user_achievement_milestone_data[0][
                        milestone]:
                    list_of_stars_per_achievement.append(
                        "<:achievement_star:877646167087906816>")
                elif milestone_value <= user_achievement_value:
                    list_of_stars_per_achievement.append(
                        "<:achievement_star_new:877737712046702592>")
                else:
                    list_of_stars_per_achievement.append(
                        "<:achievement_star_no:877646167222141008>")
            # Grammar stuff and the number of stars said
            next_unclaimable_star = 0
            st_nd_rd_th_grammar = 'th'
            for single_star_per_star_list in list_of_stars_per_achievement:
                if single_star_per_star_list != "<:achievement_star:877646167087906816>":
                    next_unclaimable_star += 1
                    break
                next_unclaimable_star += 1
            if next_unclaimable_star == 1:
                st_nd_rd_th_grammar = 'st'
            elif next_unclaimable_star == 2:
                st_nd_rd_th_grammar = 'nd'
            elif next_unclaimable_star == 3:
                st_nd_rd_th_grammar = 'rd'

            # Sets the milestonme to be claimable if it is
            if user_achievement_value >= user_achievement_milestone_data[0][
                    milestone] and user_achievement_milestone_data[0][
                        f'{milestone}_done'] is False:
                if are_there_any_claimable_achievements_check is False:
                    are_there_any_claimable_achievements_check = True
                Achievements_that_are_claimable[
                    achievement] = milestones_dict_of_achievements[
                        achievement].index(
                            user_achievement_milestone_data[0][milestone])
                is_achievement_claimable = "claimable"
            if user_achievement_milestone_data[0][f'{milestone}_done'] is True:
                value_data = 'All achievements have been claimed!'
                name_data = ''
            else:
                value_data = ''
                value_data = f"{(user_achievement_value/user_achievement_milestone_data[0][milestone])}% of **{next_unclaimable_star}**{st_nd_rd_th_grammar} star"
                name_data = f"{user_achievement_value:,}/{user_achievement_milestone_data[0][milestone]:,}"
            embed.add_field(
                name=f"{achievement.replace('_', ' ').title()} {name_data}",
                value=
                f"{value_data}\n{''.join(list_of_stars_per_achievement)} \n**{is_achievement_claimable}**"
            )

        # Adds a button to the message if there are any claimable achievements
        if are_there_any_claimable_achievements_check is True:
            components = vbu.MessageComponents(
                vbu.ActionRow(
                    vbu.Button(custom_id="claim_all",
                               emoji="1\N{COMBINING ENCLOSING KEYCAP}"), ), )
            claim_message = await ctx.send(embed=embed, components=components)
        else:
            # Doesnt add a button if theres no claimable achievements
            return await ctx.send(embed=embed)

        # Make the button check
        def button_check(payload):
            if payload.message.id != claim_message.id:
                return False
            self.bot.loop.create_task(payload.defer_update())
            return payload.user.id == ctx.author.id

        pressed = False
        while True:

            # Wait for them to click a button
            try:
                chosen_button_payload = await self.bot.wait_for(
                    'component_interaction', timeout=60.0, check=button_check)
                chosen_button = chosen_button_payload.component.custom_id.lower(
                )
            except asyncio.TimeoutError:
                await claim_message.edit(
                    components=components.disable_components())
                break

            # Sets reward and if the button is clicked...
            amount_of_doubloons_earned = 0
            if chosen_button == "claim_all":
                pressed = True
                for achievement_button, user_achievement_position_button in Achievements_that_are_claimable.items(
                ):
                    amount_per_achievement = user_achievement_position_button + 1
                    print(achievement_button)
                    print(user_achievement_position_button)
                    print(amount_per_achievement)
                    for x in range(amount_per_achievement):
                        print(x)
                        amount_of_doubloons_earned += x + 1
                        print(amount_of_doubloons_earned)
                    if achievement_button == 'tanks_owned' and user_achievement_position_button >= 3:
                        async with self.bot.database() as db:
                            await db(
                                """UPDATE user_achievements_milestones SET {0} = TRUE WHERE user_id = $1"""
                                .format(
                                    f"{achievement_button}_milestone_done"),
                                ctx.author.id)
                    elif user_achievement_position_button >= 9:
                        async with self.bot.database() as db:
                            await db(
                                """UPDATE user_achievements_milestones SET {0} = TRUE WHERE user_id = $1"""
                                .format(
                                    f"{achievement_button}_milestone_done"),
                                ctx.author.id)
                    else:
                        async with self.bot.database() as db:
                            await db(
                                """UPDATE user_achievements_milestones SET {0} = $1 WHERE user_id = $2"""
                                .format(f"{achievement_button}_milestone"),
                                milestones_dict_of_achievements[
                                    achievement_button][
                                        user_achievement_position_button + 1],
                                ctx.author.id)
                async with self.bot.database() as db:
                    await db(
                        """INSERT INTO user_balance (user_id, doubloon) VALUES ($1, $2)
                        ON CONFLICT (user_id) DO UPDATE SET doubloon = user_balance.doubloon + $2""",
                        ctx.author.id, amount_of_doubloons_earned)
                components.get_component(chosen_button).disable()
            break
        if pressed is True:
            await ctx.send(
                f"Rewards claimed, you earned {amount_of_doubloons_earned} <:doubloon:878297091057807400>!"
            )
Beispiel #16
0
async def ask_to_sell_fish(bot, ctx, new_fish: dict, embed, file=None):
    """
    Ask the user if they want to sell a fish they've been given.
    """

    # Add the buttons to the message
    components = vbu.MessageComponents(
        vbu.ActionRow(
            vbu.Button(custom_id="keep", emoji="<:keep:844594468580491264>"),
            vbu.Button(custom_id="sell", emoji="<:sell:844594478392147968>"),
        ), )
    message = await ctx.send(embed=embed, components=components, file=file)

    async with bot.database() as db:
        fish_rows = await db(
            """SELECT * FROM user_fish_inventory WHERE user_id=$1""",
            ctx.author.id)
        upgrades = await db(
            """SELECT rod_upgrade, bait_upgrade, weight_upgrade, line_upgrade, lure_upgrade FROM user_upgrades WHERE user_id = $1""",
            ctx.author.id)
        if not upgrades:
            await db("""INSERT INTO user_upgrades (user_id) VALUES ($1)""",
                     ctx.author.id)
            upgrades = await db(
                """SELECT rod_upgrade, bait_upgrade, weight_upgrade, line_upgrade, lure_upgrade FROM user_upgrades WHERE user_id = $1""",
                ctx.author.id)

    # See what reaction the user is adding to the message
    def button_check(payload):
        if payload.message.id != message.id:
            return False
        bot.loop.create_task(payload.defer_update())
        return payload.user.id == ctx.author.id
        # Keep going...

    while True:

        # Wait for them to click a button
        try:
            chosen_button_payload = await bot.wait_for('component_interaction',
                                                       timeout=60.0,
                                                       check=button_check)
            chosen_button = chosen_button_payload.component.custom_id.lower()
        except asyncio.TimeoutError:
            await message.edit(components=components.disable_components())
            await message.channel.send(
                "Did you forget about me? I've been waiting for a while now! I'll just assume you wanted to sell the fish."
            )
            # See if they want to sell the fish
            print("sell confirm")
            sell_multipliers = {1: 1, 2: 1.5, 3: 2, 4: 2.5, 5: 3}
            money_earned = math.ceil(
                (int(new_fish['cost']) / 2) *
                sell_multipliers[upgrades[0]['rod_upgrade']])
            async with bot.database() as db:
                await db(
                    """INSERT INTO user_balance (user_id, balance) VALUES ($1, $2)
                    ON CONFLICT (user_id) DO UPDATE SET balance = user_balance.balance + $2""",
                    ctx.author.id,
                    money_earned,
                )
                # Achievements
                await db(
                    """INSERT INTO user_achievements (user_id, money_gained) VALUES ($1, $2)
                    ON CONFLICT (user_id) DO UPDATE SET money_gained = user_achievements.money_gained + $2""",
                    ctx.author.id, money_earned)
            await message.channel.send(
                f"Sold your **{new_fish['name']}** for **{money_earned}** <:sand_dollar:877646167494762586>!"
            )
            # Disable the given button
            await message.edit(components=components.disable_components())
            return

        # Update the displayed emoji
        if chosen_button == "keep":
            print("keep")
            # Disable the given button
            await message.edit(components=components.disable_components())
            # Get their current fish names
            print("keep confirm")
            fish_names = []

            fish_names = [i['fish_name'] for i in fish_rows]
            fish_list = [(i['fish_name'], i['fish']) for i in fish_rows]
            fish_list = sorted(fish_list, key=lambda x: x[1])
            levels_start = {
                1: (1, 2),
                2: (1, 4),
                3: (2, 6),
                4: (2, 8),
                5: (3, 10)
            }
            level = random.randint(
                levels_start[upgrades[0]['weight_upgrade']][0],
                levels_start[upgrades[0]['weight_upgrade']][1])
            xp_max = math.floor(25 * level**1.5)
            sorted_fish = {
                "common": [],
                "uncommon": [],
                "rare": [],
                "epic": [],
                "legendary": [],
                "mythic": []
            }

            # Sorted Fish will become a dictionary of {rarity: [list of fish names of fish in that category]} if the fish is in the user's inventory
            for rarity, fish_types in bot.fish.items(
            ):  # For each rarity level
                for _, fish_detail in fish_types.items(
                ):  # For each fish in that level
                    raw_name = fish_detail["raw_name"]
                    for user_fish_name, user_fish in fish_list:
                        if raw_name == utils.get_normal_name(
                                user_fish
                        ):  # If the fish in the user's list matches the name of a fish in the rarity catgeory
                            sorted_fish[rarity].append(
                                (user_fish_name,
                                 user_fish))  # Append to the dictionary

            # They want to keep - ask what they want to name the fish
            await message.channel.send(
                "What do you want to name your new fish? (32 character limit and cannot be named the same as another fish you own)"
            )
            check = lambda m: m.author == ctx.author and m.channel == message.channel and len(
                m.content) > 1 and len(m.content
                                       ) <= 32 and m.content not in fish_names
            try:
                name_message = await bot.wait_for("message",
                                                  timeout=60.0,
                                                  check=check)
                name = name_message.content
                await message.channel.send(
                    f"Your new fish **{name}** (Lvl. {level}) has been added to your bucket!"
                )
            except asyncio.TimeoutError:
                name = f"{random.choice(['Captain', 'Mr.', 'Mrs.', 'Commander'])} {random.choice(['Nemo', 'Bubbles', 'Jack', 'Finley', 'Coral'])}"
                await message.channel.send(
                    f"Did you forget about me? I've been waiting for a while now! I'll name the fish for you. Let's call it **{name}** (Lvl. {level})"
                )

            # Save the fish name
            async with bot.database() as db:
                await db(
                    """INSERT INTO user_fish_inventory (user_id, fish, fish_name, fish_size, fish_level, fish_xp_max) VALUES ($1, $2, $3, $4, $5, $6)""",
                    ctx.author.id, new_fish["raw_name"], name,
                    new_fish["size"], level, xp_max)
            return
        if chosen_button == "sell":
            # See if they want to sell the fish
            print("sell confirm")
            sell_multipliers = {1: 1, 2: 1.5, 3: 2, 4: 2.5, 5: 3}
            money_earned = math.ceil(
                (int(new_fish['cost']) / 2) *
                sell_multipliers[upgrades[0]['rod_upgrade']])
            async with bot.database() as db:
                await db(
                    """INSERT INTO user_balance (user_id, balance) VALUES ($1, $2)
                    ON CONFLICT (user_id) DO UPDATE SET balance = user_balance.balance + $2""",
                    ctx.author.id,
                    money_earned,
                )
                # Achievements
                await db(
                    """INSERT INTO user_achievements (user_id, money_gained) VALUES ($1, $2)
                    ON CONFLICT (user_id) DO UPDATE SET money_gained = user_achievements.money_gained + $2""",
                    ctx.author.id, money_earned)
            await message.channel.send(
                f"Sold your **{new_fish['name']}** for **{money_earned}** <:sand_dollar:877646167494762586>!"
            )
            # Disable the given button
            await message.edit(components=components.disable_components())
            return