async def send_profile_verification( self, user_profile: localutils.UserProfile, target_user: discord.Member) -> typing.Optional[discord.Message]: """ Sends a profile in to the template's verification channel. Args: user_profile (localutils.UserProfile): The profile to be submitted. target_user (discord.Member): The owner of the profile. Returns: typing.Optional[discord.Message]: The message that was sent into the verification channel by the bot. Raises: localutils.errors.TemplateVerificationChannelError: The bot encountered an error sending a message to the verification channel. """ # Grab the verification channel ID template: localutils.Template = user_profile.template verification_channel_id: typing.Optional[int] = template.get_verification_channel_id(target_user) # this may raise InvalidCommandText # Check if there's a verification channel if verification_channel_id is None: return None # Get the channel try: channel: discord.TextChannel = self.bot.get_channel(verification_channel_id) or await self.bot.fetch_channel(verification_channel_id) if channel is None: raise localutils.errors.TemplateVerificationChannelError(f"I can't reach a channel with the ID `{verification_channel_id}`.") except discord.HTTPException: raise localutils.errors.TemplateVerificationChannelError(f"I can't reach a channel with the ID `{verification_channel_id}`.") # Send the data embed: utils.Embed = user_profile.build_embed(self.bot, target_user) embed.set_footer(text=f'{template.name} // Verification Check') try: components = utils.MessageComponents.add_buttons_with_rows( utils.Button("Approve", style=utils.ButtonStyle.SUCCESS, custom_id="VERIFY PROFILE YES"), utils.Button("Decline", style=utils.ButtonStyle.DANGER, custom_id="VERIFY PROFILE NO"), ) v = await channel.send( f"New **{template.name}** submission from <@{user_profile.user_id}>\n{user_profile.user_id}/" f"{template.template_id}/{user_profile.name}", embed=embed, components=components ) except discord.HTTPException: raise localutils.errors.TemplateVerificationChannelError(f"I can't send messages to {channel.mention}.") # Wew nice we're done return v
async def update_components(self, payload: vbu.ComponentInteractionPayload): """ Update the components on a message to show the user click count. """ # Get the current components components = payload.message.components # Add the "first clicked by" button if len(components.components[0].components) == 1: username = self.first_button_click.get(payload.message.id) if username: components.components[0].add_component( vbu.Button(label=username, custom_id="BONG MESSAGE FIRST CLICKED", disabled=True, style=vbu.ButtonStyle.SECONDARY)) # Update the bong button bong_button = components.get_component("BONG MESSAGE BUTTON") button_clicks = len(self.bong_button_clicks[payload.message.id]) if button_clicks > 1: bong_button.label = f"{button_clicks} clicks" else: bong_button.label = f"{button_clicks} click" # Edit the message edit_url = self.bot.guild_settings[ payload.guild.id]['bong_channel_webhook'].rstrip( "/") + f"/messages/{payload.message.id}" edit_url = edit_url.replace("/api/", "/api/v9/") r = await self.bot.session.patch( edit_url, json={ "content": payload.message.content, "components": components.to_dict(), }, ) # d = await r.text() self.logger.info( f"Tried to update components on message {payload.message.id} - {r.status}" )
async def on_component_interaction( self, payload: vbu.ComponentInteractionPayload): """ Handle the "handle report" button being clicked. """ # See if we care about this button if payload.component.custom_id != "HANDLE_REPORT": return self.logger.info("Received report interaction") # Sick components = payload.message.components report_button = components.get_component("HANDLE_REPORT") report_button.label = "Report handled" report_button.disabled = True components.components[0].add_component( vbu.Button(str(payload.user), "REPORT_HANDLED_BY", disabled=True), ) # Update message await payload.update_message(components=components) self.logger.info("Updated report message 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, )
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
def make_button(i): return vbu.Button("X", f"DISABLE_BUTTON_COMMAND {i}", style=vbu.ButtonStyle(random.randint(1, 4)))
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 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 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 edittemplate(self, ctx: utils.Context, template: localutils.Template): """ Edits a template for your guild. """ # See if they're already editing that template if self.template_editing_locks[ctx.guild.id].locked(): return await ctx.send("You're already editing a template.") # See if they're bot support is_bot_support = await self.user_is_bot_support(ctx) # Grab the template edit lock async with self.template_editing_locks[ctx.guild.id]: # Get the template fields async with self.bot.database() as db: await template.fetch_fields(db) guild_settings_rows = await db( """SELECT * FROM guild_settings WHERE guild_id=$1 OR guild_id=0 ORDER BY guild_id DESC""", ctx.guild.id, ) perks = await localutils.get_perks_for_guild(db, ctx.guild.id) ctx.guild_perks = perks guild_settings = guild_settings_rows[0] # Set up our initial vars so we can edit them later template_display_edit_message = await ctx.send( "Loading template...") # The message with the template components = utils.MessageComponents.add_buttons_with_rows( utils.Button("Template name", custom_id="1\N{COMBINING ENCLOSING KEYCAP}"), utils.Button("Profile verification channel", custom_id="2\N{COMBINING ENCLOSING KEYCAP}"), utils.Button("Profile archive channel", custom_id="3\N{COMBINING ENCLOSING KEYCAP}"), utils.Button("Profile completion role", custom_id="4\N{COMBINING ENCLOSING KEYCAP}"), utils.Button("Template fields", custom_id="5\N{COMBINING ENCLOSING KEYCAP}"), utils.Button("Profile count per user", custom_id="6\N{COMBINING ENCLOSING KEYCAP}"), utils.Button("Done", custom_id="DONE", style=utils.ButtonStyle.SUCCESS), ) template_options_edit_message = None should_edit = True # Whether or not the template display message should be edited # Start our edit loop while True: # Ask what they want to edit if should_edit: try: await template_display_edit_message.edit( content=None, embed=template.build_embed(self.bot, brief=True), allowed_mentions=discord.AllowedMentions( roles=False), ) except discord.HTTPException: return should_edit = False # Wait for a response from the user try: if template_options_edit_message: await template_options_edit_message.edit( components=components.enable_components()) else: template_options_edit_message = await ctx.send( "What would you like to edit?", components=components) payload = await template_options_edit_message.wait_for_button_click( check=lambda p: p.user.id == ctx.author.id, timeout=120) await payload.ack() reaction = payload.component.custom_id except asyncio.TimeoutError: try: await template_options_edit_message.edit( content="Timed out waiting for edit response.", components=None) except discord.HTTPException: pass return # See what they reacted with try: available_reactions = { "1\N{COMBINING ENCLOSING KEYCAP}": ("name", str), "2\N{COMBINING ENCLOSING KEYCAP}": ("verification_channel_id", commands.TextChannelConverter()), "3\N{COMBINING ENCLOSING KEYCAP}": ("archive_channel_id", commands.TextChannelConverter()), "4\N{COMBINING ENCLOSING KEYCAP}": ("role_id", commands.RoleConverter()), "5\N{COMBINING ENCLOSING KEYCAP}": (None, self.edit_field(ctx, template, guild_settings, is_bot_support)), "6\N{COMBINING ENCLOSING KEYCAP}": ("max_profile_count", int), "DONE": None, } attr, converter = available_reactions[reaction] except TypeError: break # They're done # Disable the components await template_options_edit_message.edit( components=components.disable_components()) # If they want to edit a field, we go through this section if attr is None: # Let them change the fields fields_have_changed = await converter # If the fields have changed then we should update the message if fields_have_changed: async with self.bot.database() as db: await template.fetch_fields(db) should_edit = True # And we're done with this round, so continue upwards continue # Change the given attribute should_edit = await self.change_template_attribute( ctx, template, guild_settings, is_bot_support, attr, converter) # Tell them it's done await template_options_edit_message.edit( content= (f"Finished editing template. Users can create profiles with `{ctx.clean_prefix}{template.name.lower()} set`, " f"edit with `{ctx.clean_prefix}{template.name.lower()} edit`, and show them with " f"`{ctx.clean_prefix}{template.name.lower()} get`."), components=None, )
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 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)
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 issue_create(self, ctx: utils.Context, repo: GitRepo, *, title: 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`.")) # Ask if we want to do this embed = utils.Embed(title=title, use_random_colour=True).set_footer(text=str(repo)) components = utils.MessageComponents.boolean_buttons() components.components[0].components.insert( 1, utils.Button("Set body", "BODY")) m = await ctx.send("Are you sure you want to create this issue?", embed=embed, components=components) body = "" while True: # See if we want to update the body if body: embed = utils.Embed( title=title, description=body, use_random_colour=True).set_footer(text=str(repo)) try: payload = await m.wait_for_button_click( check=lambda p: p.user.id == ctx.author.id, timeout=120) await payload.ack() except asyncio.TimeoutError: return await ctx.send( "Timed out asking for issue create confirmation.") # Get the body if payload.component.custom_id == "BODY": # Wait for their body message await payload.message.edit( components=components.disable_components()) 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 await payload.message.edit( components=components.enable_components()) # 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" # Edit the message embed = utils.Embed( title=title, description=body, use_random_colour=True).set_footer(text=str(repo)) await m.edit(embed=embed) # Check the reaction if payload.component.custom_id == "NO": await payload.message.edit( components=components.disable_components()) return await ctx.send("Alright, cancelling issue add.") if payload.component.custom_id == "YES": await payload.message.edit( components=components.disable_components()) break # Work out our args if repo.host == "Github": json = {'title': title, 'body': body.strip()} 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()} 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}`." ) await ctx.send( f"Your issue has been created - <{data.get('html_url') or data.get('web_url')}>." )
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)
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 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