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)
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, )
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)
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
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!")
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()
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, )
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
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
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)
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}")
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!")
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>!" )
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