async def plants(self, ctx: utils.Context, user: typing.Optional[discord.User]): """ Shows you all the plants that a given user has. """ # Grab the plant data user = user or ctx.author async with self.bot.database() as db: user_rows = await db( "SELECT * FROM plant_levels WHERE user_id=$1 ORDER BY plant_name DESC", user.id) # See if they have anything available plant_data = sorted([ (i['plant_name'], i['plant_type'], i['plant_nourishment'], i['last_water_time'], i['plant_adoption_time']) for i in user_rows ]) if not plant_data: embed = utils.Embed(use_random_colour=True, description=f"<@{user.id}> has no plants :c") return await ctx.send(embed=embed) # Add the plant information embed = utils.Embed(use_random_colour=True, description=f"<@{user.id}>'s plants") ctx._set_footer(embed) for plant_name, plant_type, plant_nourishment, last_water_time, plant_adoption_time in plant_data: plant_type_display = plant_type.replace('_', ' ').capitalize() plant_death_time = last_water_time + timedelta( **self.bot.config.get('plants', {}).get( 'death_timeout', {'days': 3})) plant_death_humanize_time = utils.TimeValue( (plant_death_time - dt.utcnow()).total_seconds()).clean_full plant_life_humanize_time = utils.TimeValue( (dt.utcnow() - plant_adoption_time).total_seconds()).clean_full if plant_nourishment == 0: text = f"{plant_type_display}, nourishment level {plant_nourishment}/{self.bot.plants[plant_type].max_nourishment_level}." elif plant_nourishment > 0: text = ( f"**{plant_type_display}**, nourishment level {plant_nourishment}/{self.bot.plants[plant_type].max_nourishment_level}.\n" f"If not watered, this plant will die in **{plant_death_humanize_time}**.\n" f"This plant has been alive for **{plant_life_humanize_time}**.\n" ) else: text = f"{plant_type_display}, dead :c" embed.add_field(plant_name, text, inline=False) # Return to user v = await ctx.send(embed=embed) try: await self.bot.add_delete_button(v, ( ctx.author, user, ), wait=False) except discord.HTTPException: pass
async def reminder(self, ctx: utils.Context): """ Shows you your reminders. """ # Get the guild ID try: guild_id = ctx.guild.id except AttributeError: guild_id = 0 # Grab their remidners async with self.bot.database() as db: rows = await db( "SELECT * FROM reminders WHERE user_id=$1 and guild_id=$2", ctx.author.id, guild_id) # Format an output string reminders = "" for reminder in rows: expiry = utils.TimeValue( (reminder['timestamp'] - dt.utcnow()).total_seconds()).clean_spaced or 'now' reminders += f"\n`{reminder['reminder_id']}` - {reminder['message'][:70]} ({expiry})" message = f"Your reminders: {reminders}" # Send to the user await ctx.send(message or "You have no reminders.", allowed_mentions=discord.AllowedMentions.none())
async def daily(self, ctx: utils.Context): """ Get money on the daily. """ async with self.bot.database() as db: # See which currencies allow faily command all_guild_currencies = await db( """SELECT currency_name FROM guild_currencies WHERE guild_id=$1 AND allow_daily_command=true""", ctx.guild.id, ) # Check out last run commands allowed_daily_currencies = await db( """SELECT currency_name, last_daily_command FROM user_money WHERE user_money.guild_id=$1 AND user_money.user_id=$2 AND currency_name=ANY($3::TEXT[])""", ctx.guild.id, ctx.author.id, [i['currency_name'] for i in all_guild_currencies], ) # Work out when each thing was last run allowed_daily_dict = {} for row in all_guild_currencies: allowed_daily_dict[row['currency_name']] = dt(2000, 1, 1) for row in allowed_daily_currencies: allowed_daily_dict[row['currency_name']] = row['last_daily_command'] if not allowed_daily_dict: return await ctx.send("There's nothing available for use with the daily command right now.") # Work out how much we're adding changed_daily = {} for currency_name, last_run_time in allowed_daily_dict.items(): if last_run_time > dt.utcnow() - self.DAILY_COMMAND_TIMEOUT: continue amount = random.randint(9_000, 13_000) await db( """INSERT INTO user_money (user_id, guild_id, currency_name, money_amount, last_daily_command) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (user_id, guild_id, currency_name) DO UPDATE SET money_amount=user_money.money_amount+excluded.money_amount, last_daily_command=excluded.last_daily_command""", ctx.author.id, ctx.guild.id, currency_name, amount, ctx.message.created_at, ) self.bot.dispatch("transaction", ctx.author, currency_name, amount, "DAILY_COMMAND") changed_daily[row['currency_name']] = amount # Make them into an embed if not changed_daily: soonest_allow_daily = max(allowed_daily_dict.values()) soonest_tv = utils.TimeValue((soonest_allow_daily - (dt.utcnow() - self.DAILY_COMMAND_TIMEOUT)).total_seconds()) return await ctx.send(f"You can't get anything with the daily command for another **{soonest_tv.clean_full}**.") embed = utils.Embed(use_random_colour=True) description_list = [] for currency, amount in changed_daily.items(): currency_name = currency.title() if currency.lower() == currency else currency description_list.append(f"**{currency_name}** - {amount}") embed.description = "\n".join(description_list) return await ctx.send(embed=embed)
async def whois(self, ctx: utils.Context, user: discord.Member = None): """ Give you some information about a user. """ user = user or ctx.author with utils.Embed(use_random_colour=True) as embed: embed.set_author_to_user(user) account_creation_time_humanized = utils.TimeValue( (dt.utcnow() - user.created_at).total_seconds()).clean_full create_value = f"{user.created_at.strftime('%A %B %d %Y %I:%M:%S%p')}\n{account_creation_time_humanized} ago" embed.add_field("Account Creation Time", create_value, inline=False) guild_join_time_humanized = utils.TimeValue( (dt.utcnow() - user.joined_at).total_seconds()).clean_full join_value = f"{user.joined_at.strftime('%A %B %d %Y %I:%M:%S%p')}\n{guild_join_time_humanized} ago" embed.add_field("Guild Join Time", join_value, inline=False) embed.set_thumbnail(user.avatar_url_as(size=1024)) return await ctx.send(embed=embed)
async def format_page(self, menu, entries): text = "" for row in entries: total_points = row[1] + (row[3] // 5) vc_time = utils.TimeValue(row[3] * 60).clean or '0m' text += f"**<@{row[0]}>** - `{total_points:,}` (`{row[2]:,}` text, `{vc_time}` VC)\n" max_page = math.ceil(len(self.entries) / self.per_page) return { "content": f"""__{self.header}:__\n{text}\n\nPage {menu.current_page + 1} of {max_page}""", "allowed_mentions": discord.AllowedMentions.none() }
async def partner(self, ctx: utils.Context, user: utils.converters.UserID = None): """ Tells you who a user is married to. """ # Get the user's info user_id = user or ctx.author.id user_name = await localutils.DiscordNameManager.fetch_name_by_id( self.bot, user_id) user_info = localutils.FamilyTreeMember.get( user_id, localutils.get_family_guild_id(ctx)) # Check they have a partner if user_info._partner is None: if user_id == ctx.author.id: return await ctx.send( f"You're not currently married.", allowed_mentions=discord.AllowedMentions.none()) return await ctx.send( f"**{localutils.escape_markdown(user_name)}** is not currently married.", allowed_mentions=discord.AllowedMentions.none(), ) partner_name = await localutils.DiscordNameManager.fetch_name_by_id( self.bot, user_info._partner) # Get timestamp async with self.bot.database() as db: if self.bot.config.get('is_server_specific', False): data = await db( "SELECT * FROM marriages WHERE user_id=$1 AND guild_id=$2", user_id, user_info._guild_id) else: data = await db( "SELECT * FROM marriages WHERE user_id=$1 AND guild_id=0", user_id) try: timestamp = data[0]['timestamp'] except Exception: timestamp = None # Output text = f"**{localutils.escape_markdown(user_name)}** is currently married to **{localutils.escape_markdown(partner_name)}** (`{user_info._partner}`). " if user_id == ctx.author.id: text = f"You're currently married to **{localutils.escape_markdown(partner_name)}** (`{user_info._partner}`). " if timestamp: duration = utils.TimeValue( (dt.utcnow() - timestamp).total_seconds()) text += f"{'You' if user_id == ctx.author.id else 'They'}'ve been married for {duration.clean_days}." await ctx.send(text, allowed_mentions=discord.AllowedMentions.none())
async def getitem(self, ctx: utils.Context, *, item_name: commands.clean_content): """ Gets you an item from the server. """ # Get the item from the db item_name = item_name.lower() db = await self.bot.database.get_connection() acquire_information = await db( "SELECT * FROM guild_item_acquire_methods WHERE guild_id=$1 AND item_name=$2 AND acquired_by='Command'", ctx.guild.id, item_name) if not acquire_information: await db.disconnect() return await ctx.send( f"You can't acquire **{item_name}** items via the `getitem` command." ) acquire_information = acquire_information[0] # See if they hit the timeout last_run = self.last_command_run[(ctx.guild.id, ctx.author.id, item_name)] if last_run + timedelta( seconds=acquire_information['acquire_per']) > dt.utcnow(): cooldown_seconds = ( (last_run + timedelta(seconds=acquire_information['acquire_per'])) - dt.utcnow()).total_seconds() cooldown_timevalue = utils.TimeValue(cooldown_seconds) await db.disconnect() return await ctx.send( f"You can't run this command again for another `{cooldown_timevalue.clean_spaced}`." ) self.last_command_run[(ctx.guild.id, ctx.author.id, item_name)] = dt.utcnow() # Add to database amount = random.randint(acquire_information['min_acquired'], acquire_information['max_acquired']) await db( """INSERT INTO user_inventories (guild_id, user_id, item_name, amount) VALUES ($1, $2, $3, $4) ON CONFLICT (guild_id, user_id, item_name) DO UPDATE SET amount=user_inventories.amount+excluded.amount""", ctx.guild.id, ctx.author.id, item_name, amount, ) await db.disconnect() return await ctx.send(f"You've received `{amount:,}x {item_name}`.")
async def plants(self, ctx: utils.Context, user: typing.Optional[discord.User]): """ Shows you all the plants that a given user has. """ # Grab the plant data user = user or ctx.author async with self.bot.database() as db: plant_data = await db( """SELECT * FROM plant_levels WHERE user_id=$1 ORDER BY plant_name DESC, plant_type DESC, plant_nourishment DESC, last_water_time DESC, plant_adoption_time DESC""", user.id, ) # See if they have anything available if not plant_data: embed = utils.Embed(use_random_colour=True, description=f"<@{user.id}> has no plants :c") return await ctx.send(embed=embed) # Add the plant information embed = utils.Embed(use_random_colour=True, description=f"<@{user.id}>'s plants") ctx.bot.set_footer_from_config(embed) for plant in plant_data: plant_type_display = plant['plant_type'].replace('_', ' ').capitalize() # Get the time when the plant will die if plant['immortal']: plant_death_time, plant_death_humanize_time = None, None else: death_timeout = timedelta( **self.bot.config['plants']['death_timeout']) plant_death_time = plant['last_water_time'] + death_timeout plant_death_humanize_time = utils.TimeValue( (plant_death_time - dt.utcnow()).total_seconds()).clean_full # See how long the plant has been alive plant_life_humanize_time = utils.TimeValue( (dt.utcnow() - plant['plant_adoption_time']).total_seconds()).clean_full # Make the text to put in the embed if plant['plant_nourishment'] == 0 or plant['immortal']: text = f"{plant_type_display}, nourishment level {plant['plant_nourishment']}/{self.bot.plants[plant['plant_type']].max_nourishment_level}." elif plant['plant_nourishment'] > 0 or plant['immortal']: text = f"**{plant_type_display}**, nourishment level {plant['plant_nourishment']}/{self.bot.plants[plant['plant_type']].max_nourishment_level}.\n" if not plant['immortal']: text += f"If not watered, this plant will die in **{plant_death_humanize_time}**.\n" text += f"This plant has been alive for **{plant_life_humanize_time}**.\n" else: text = f"{plant_type_display}, dead :c" # And add the field embed.add_field(plant['plant_name'], text, inline=False) # Return to user return await ctx.send(embed=embed)
async def water_plant_backend(self, user_id: int, plant_name: str): """ Run the backend for the plant watering. Returns a sexy lil dictionary in format: { "text": str, "success": bool, "new_nourishment_level": int, "voted_on_topgg": bool, "new_user_experience": int, "multipliers": [ { "multiplier": float, "text": str }, ... ] } """ # Decide on our plant type - will be ignored if there's already a plant db = await self.bot.database.get_connection() # See if they have a plant available plant_level_row = await db( "SELECT * FROM plant_levels WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2)", user_id, plant_name) if not plant_level_row: await db.disconnect() return self.get_water_plant_dict( f"You don't have a plant with the name **{plant_name}**! Run the `shop` command to plant some new seeds, or `plants` to see the list of plants you have already!" ) plant_data = self.bot.plants[plant_level_row[0]['plant_type']] # See if they're allowed to water things if plant_level_row[0]['last_water_time'] + timedelta( **self.bot.config.get('plants', {}).get( 'water_cooldown', {'minutes': 15})) > dt.utcnow( ) and user_id not in self.bot.owner_ids: await db.disconnect() timeout = utils.TimeValue( ((plant_level_row[0]['last_water_time'] + timedelta(**self.bot.config.get('plants', {}).get( 'water_cooldown', {'minutes': 15}))) - dt.utcnow()).total_seconds()) return self.get_water_plant_dict( f"You need to wait another {timeout.clean_spaced} to be able water your {plant_level_row[0]['plant_type'].replace('_', ' ')}." ) last_water_time = plant_level_row[0]['last_water_time'] # See if the plant should be dead if plant_level_row[0]['plant_nourishment'] < 0: plant_level_row = await db( """UPDATE plant_levels SET plant_nourishment=LEAST(-plant_levels.plant_nourishment, plant_levels.plant_nourishment), last_water_time=$3 WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2) RETURNING *""", user_id, plant_name, dt.utcnow(), ) # Increase the nourishment otherwise else: plant_level_row = await db( """UPDATE plant_levels SET plant_nourishment=LEAST(plant_levels.plant_nourishment+1, $4), last_water_time=$3, notification_sent=FALSE WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2) RETURNING *""", user_id, plant_name, dt.utcnow(), plant_data.max_nourishment_level, ) # Add to the user exp if the plant is alive user_plant_data = plant_level_row[0] gained_experience = 0 original_gained_experience = 0 multipliers = [] # List[dict] additional_text = [] # List[str] voted_on_topgg = False # Disconnect from the database so we don't have hanging connections open while # making our Top.gg web request await db.disconnect() # And now let's water the damn thing if user_plant_data['plant_nourishment'] > 0: # Get the experience that they should have gained gained_experience = plant_data.get_experience() original_gained_experience = gained_experience # See if we want to give them a 30 second water-time bonus if dt.utcnow() - last_water_time - timedelta( **self.bot.config.get('plants', {}).get( 'water_cooldown', {'minutes': 15})) <= timedelta( seconds=30): multipliers.append({ "multiplier": 1.5, "text": "You watered within 30 seconds of your plant's cooldown resetting." }) # See if we want to give the new owner bonus if plant_level_row[0]['user_id'] != plant_level_row[0][ 'original_owner_id']: multipliers.append({ "multiplier": 1.05, "text": "You watered a plant that you got from a trade." }) # See if we want to give them the voter bonus user_voted_api_request = False try: user_voted_api_request = await asyncio.wait_for( self.get_user_voted(user_id), timeout=2.0) except asyncio.TimeoutError: pass if self.bot.config.get( 'bot_listing_api_keys', {}).get('topgg_token') and user_voted_api_request: multipliers.append({ "multiplier": 1.1, "text": f"You [voted for the bot](https://top.gg/bot/{self.bot.config['oauth']['client_id']}/vote) on Top.gg." }) voted_on_topgg = True # See if we want to give them the plant longevity bonus if user_plant_data['plant_adoption_time'] < dt.utcnow( ) - timedelta(days=7): multipliers.append({ "multiplier": 1.2, "text": "Your plant has been alive for longer than a week." }) # Add the actual multiplier values for obj in multipliers: gained_experience *= obj['multiplier'] # Update db gained_experience = int(gained_experience) async with self.bot.database() as db: user_experience_row = 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+$2 RETURNING *""", user_id, gained_experience, ) # Send an output if user_plant_data['plant_nourishment'] < 0: return self.get_water_plant_dict( "You sadly pour water into the dry soil of your silently wilting plant :c" ) # Set up our output text gained_exp_string = f"**{gained_experience}**" if gained_experience == original_gained_experience else f"~~{original_gained_experience}~~ **{gained_experience}**" output_lines = [] if plant_data.get_nourishment_display_level( user_plant_data['plant_nourishment'] ) > plant_data.get_nourishment_display_level( user_plant_data['plant_nourishment'] - 1): output_lines.append( f"You gently pour water into **{plant_level_row[0]['plant_name']}**'s soil, gaining you {gained_exp_string} experience, watching your plant grow!~" ) else: output_lines.append( f"You gently pour water into **{plant_level_row[0]['plant_name']}**'s soil, gaining you {gained_exp_string} experience~" ) for obj in multipliers: output_lines.append(f"**{obj['multiplier']}x**: {obj['text']}") for t in additional_text: output_lines.append(t) # And now we output ALL the information that we need for this to be an API route return self.get_water_plant_dict( text="\n".join(output_lines), success=True, gained_experience=gained_experience, new_nourishment_level=plant_level_row[0]['plant_nourishment'], new_user_experience=user_experience_row[0]['user_experience'], voted_on_topgg=voted_on_topgg, multipliers=multipliers, )
async def shop(self, ctx:utils.Context): """ Shows you the available plants. """ # Get data from the user 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'] plant_limit = user_rows[0]['plant_limit'] last_plant_shop_time = user_rows[0]['last_plant_shop_time'] or dt(2000, 1, 1) else: user_experience = 0 plant_limit = 1 last_plant_shop_time = dt(2000, 1, 1) can_purchase_new_plants = dt.utcnow() > last_plant_shop_time + timedelta(**self.bot.config.get('plants', {}).get('water_cooldown', {'minutes': 15})) buy_plant_cooldown_delta = None if can_purchase_new_plants is False: buy_plant_cooldown_delta = utils.TimeValue( ((last_plant_shop_time + timedelta(**self.bot.config.get('plants', {}).get('water_cooldown', {'minutes': 15}))) - dt.utcnow()).total_seconds() ) # Set up our initial items available_item_count = 0 # Used to make sure we can continue the command embed = utils.Embed(use_random_colour=True, description="") ctx._set_footer(embed) # See what we wanna get to doing 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 {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_delta.clean}**.\n" # Add plants to the embed plant_text = [] for plant in sorted(available_plants.values()): modifier = lambda x: x text = f"{plant.display_name.capitalize()} - `{plant.required_experience:,} exp`" if can_purchase_new_plants and plant.required_experience <= user_experience and len(plant_level_rows) < plant_limit: available_item_count += 1 else: modifier = strikethrough plant_text.append(modifier(text)) # 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()) plant_text.append(f"These plants will change in {remaining_time.clean_spaced}.") embed.add_field("Available Plants", '\n'.join(plant_text), inline=True) # Set up items to be added to the embed item_text = [] # Add pots modifier = lambda x: x text = f"Pot - `{self.get_points_for_plant_pot(plant_limit):,} exp`" if user_experience >= self.get_points_for_plant_pot(plant_limit) and plant_limit < self.bot.config.get('plants', {}).get('hard_plant_cap', 10): available_item_count += 1 else: modifier = strikethrough item_text.append(modifier(text)) # Add variable items for item in self.bot.items.values(): modifier = lambda x: x text = f"{item.display_name.capitalize()} - `{item.price:,} exp`" if user_experience >= item.price: available_item_count += 1 else: modifier = strikethrough item_text.append(modifier(text)) # Add all our items to the embed embed.add_field("Available Items", '\n'.join(item_text), inline=True) # Cancel if they don't have anything available if available_item_count == 0: embed.description += "\n**There is currently nothing available which you can purchase.**\n" return await ctx.send(embed=embed) else: embed.description += "\n**Say the name of the item you want to purchase, or type `cancel` to exit the shop with nothing.**\n" # Wait for them to respond shop_menu_message = await ctx.send(embed=embed) try: done, pending = await asyncio.wait([ self.bot.wait_for("message", check=lambda m: m.author.id == ctx.author.id and m.channel == ctx.channel and m.content), self.bot.wait_for("raw_message_delete", check=lambda m: m.message_id == shop_menu_message.id), ], timeout=120, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError: pass # 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 plant_type_message = done given_response = plant_type_message.content.lower().replace(' ', '_') # See if they want to cancel if given_response == "cancel": try: await plant_type_message.add_reaction("\N{OK HAND SIGN}") except discord.HTTPException: pass return # See if they want a plant pot if given_response == "pot": if plant_limit >= self.bot.config.get('plants', {}).get('hard_plant_cap', 10): return await ctx.send(f"You're already at the maximum amount of pots, {ctx.author.mention}! :c") if user_experience >= self.get_points_for_plant_pot(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(plant_limit) ) return await ctx.send(f"Given you another plant pot, {ctx.author.mention}!") else: return await ctx.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) 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 ctx.send(f"Given you a **{item_type.display_name}**, {ctx.author.mention}!") else: return await ctx.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] except KeyError: return await ctx.send(f"`{plant_type_message.content}` 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 ctx.send(f"You can't purchase new plants for another **{buy_plant_cooldown_delta.clean}**.") if plant_type not in available_plants.values(): return await ctx.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 ctx.send(f"You don't have the required experience to get a **{plant_type.display_name}**, {ctx.author.mention} (it requires {plant_type.required_experience}, you have {user_experience}) :c") if len(plant_level_rows) >= plant_limit: return await ctx.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 ctx.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 ctx.send(f"Timed out asking for plant name {ctx.author.mention}.") plant_name = self.bot.get_cog("PlantCareCommands").validate_name(plant_name_message.content) if len(plant_name) > 50 or len(plant_name) == 0: await ctx.send("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 ctx.send(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(everyone=False, users=False, roles=False)) 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::BIGINT, $2, $3, 0, $4, $1::BIGINT, TIMEZONE('UTC', NOW()), CAST($1::BIGINT % 360 AS SMALLINT)) 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), ) 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 ctx.send(f"Planted your **{plant_type.display_name}** seeds!")
async def leaderboard(self, ctx: utils.Context, days: int = None): """ Gives you the leaderboard users for the server. """ if days is None: days = self.bot.guild_settings[ ctx.guild.id]['activity_window_days'] elif days <= 0: days = 7 elif days > 365: days = 365 # This takes a while async with ctx.typing(): # Get all their valid user IDs async with self.bot.database() as db: message_rows = await db( """SELECT user_id, COUNT(timestamp) FROM user_messages WHERE guild_id=$1 AND timestamp > TIMEZONE('UTC', NOW()) - MAKE_INTERVAL(days => $2) GROUP BY user_id ORDER BY COUNT(timestamp) DESC;""", ctx.guild.id, days, ) vc_rows = await db( """SELECT user_id, COUNT(timestamp) FROM user_vc_activity WHERE guild_id=$1 AND timestamp > TIMEZONE('UTC', NOW()) - MAKE_INTERVAL(days => $2) GROUP BY user_id ORDER BY COUNT(timestamp) DESC;""", ctx.guild.id, days, ) if self.bot.guild_settings[ ctx.guild.id]['minecraft_srv_authorization']: minecraft_rows = await db( """SELECT user_id, COUNT(timestamp) FROM minecraft_server_activity WHERE guild_id=$1 AND timestamp > TIMEZONE('UTC', NOW()) - MAKE_INTERVAL(days => $2) GROUP BY user_id ORDER BY COUNT(timestamp) DESC;""", ctx.guild.id, days, ) else: minecraft_rows = [] # Sort that into more formattable data user_data_dict = collections.defaultdict({ 'message_count': 0, 'vc_minute_count': 0, 'minecraft_minute_count': 0 }.copy) # uid: {message_count: int, vc_minute_count: int} for row in message_rows: user_data_dict[row['user_id']]['message_count'] = row['count'] for row in vc_rows: user_data_dict[ row['user_id']]['vc_minute_count'] = row['count'] for row in minecraft_rows: user_data_dict[ row['user_id']]['minecraft_minute_count'] = row['count'] # And now make it into something we can sort valid_guild_user_data = [{ 'id': uid, 'm': d['message_count'], 'vc': d['vc_minute_count'], 'mc': d['minecraft_minute_count'] } for uid, d in user_data_dict.items() if ctx.guild.get_member(uid)] ordered_guild_user_data = sorted(valid_guild_user_data, key=lambda k: k['m'] + (k['vc'] // 5) + (k['mc'] // 5), reverse=True) # And now make it into strings ordered_guild_user_strings = [] for d in ordered_guild_user_data: total_points = d['m'] + (d['vc'] // 5) + (d['mc'] // 5) vc_time = utils.TimeValue(d['vc'] * 60).clean_spaced or '0m' if self.bot.guild_settings[ ctx.guild.id]['minecraft_srv_authorization']: ordered_guild_user_strings.append( f"**<@{d['id']}>** - **{total_points:,}** (**{d['m']:,}** text, **{vc_time}** VC, **{d['mc']:,}** Minecraft)" ) else: ordered_guild_user_strings.append( f"**<@{d['id']}>** - **{total_points:,}** (**{d['m']:,}** text, **{vc_time}** VC)" ) # Make menu return await utils.Paginator( ordered_guild_user_strings, formatter=utils.Paginator.default_ranked_list_formatter).start(ctx)
async def useitem(self, ctx: utils.Context, item_name: str, user: typing.Optional[discord.Member], *, args: str = None): """ Use an item that you purchased from the shop. """ # Get the items they own try: item_data = [ i for i in self.bot.get_cog("ShopHandler").get_shop_items( ctx.guild).values() if item_name.lower().replace(' ', '') == i['name'].lower( ).replace(' ', '') or item_name.lower().replace(' ', '') in [o.lower().replace(' ', '') for o in i['aliases']] ][0] except IndexError: return await ctx.send("That isn't an item that exists.") db = await self.bot.database.get_connection() rows = await db( "SELECT * FROM user_inventory WHERE guild_id=$1 AND user_id=$2 AND LOWER(item_name)=LOWER($3)", ctx.guild.id, ctx.author.id, item_data['name'], ) if (not rows or rows[0]['amount'] <= 0 ) and ctx.original_author_id not in self.bot.owner_ids: await ctx.send( f"You don't have any **{item_data['name']}** items in this server." ) await db.disconnect() return # Use the item user = user or ctx.author await ctx.trigger_typing() # Paint if item_data['name'] == 'Paintbrush': async with self.paintbrush_locks[ctx.guild.id]: data = await self.use_paintbrush(ctx, args, db=db, user=user) # Cooldown tokens elif item_data['name'] == 'Cooldown Token': data = await self.use_cooldown_token(ctx, db=db, user=user) # Cooldown tokens elif item_data['name'].startswith('Buyable Role'): role_search = re.search(r"<@&(?P<roleid>[0-9]{13,23})", item_data['description']) role_id = role_search.group("roleid") self.logger.info(role_id) try: await ctx.author.add_roles(ctx.guild.get_role(int(role_id)), reason="Role purchased") except Exception as e: await ctx.send(f"I couldn't add the role - {e}") data = False else: await ctx.send("Added role.") data = True # Cooldown tokens elif item_data['name'].startswith('Buyable Temporary Role'): role_search = re.search(r"<@&(?P<roleid>[0-9]{13,23})", item_data['description']) role_id = role_search.group("roleid") self.logger.info(role_id) try: await ctx.author.add_roles(ctx.guild.get_role(int(role_id)), reason="Role purchased") except Exception as e: await ctx.send(f"I couldn't add the role - {e}") data = False else: await ctx.send("Added role.") duration_rows = await db( "SELECT * FROM buyable_temporary_roles WHERE guild_id=$1 AND role_id=$2", ctx.guild.id, int(role_id)) await db( """INSERT INTO temporary_roles (guild_id, role_id, user_id, remove_timestamp, key, dm_user) VALUES ($1, $2, $3, $4, 'Buyable Temp Role', true) ON CONFLICT (guild_id, role_id, user_id) DO UPDATE SET remove_timestamp=excluded.remove_timestamp, key=excluded.key, dm_user=excluded.dm_user""", ctx.guild.id, int(role_id), ctx.author.id, dt.utcnow() + utils.TimeValue(duration_rows[0]['duration']).delta) data = True # It's nothing else else: await ctx.send("No use method set in the code for that item.") await db.disconnect() return # Unpack data try: success, amount = data except TypeError: success, amount = data, 1 # Disconnect from the DB if the item failed if success is False: await db.disconnect() return # Alter their inventory await db( "UPDATE user_inventory SET amount=user_inventory.amount - $4 WHERE guild_id=$1 AND user_id=$2 AND item_name=$3", ctx.guild.id, ctx.author.id, item_data['name'], amount) self.logger.info( f"Remove item ({item_data['name']}) from user (G{ctx.guild.id}/U{ctx.author.id})" ) await db.disconnect()
async def water_plant_backend(self, user_id: int, plant_name: str, waterer_id: int = None): """ Run the backend for the plant watering. Returns a sexy lil dictionary in format: { "text": str, "success": bool, "new_nourishment_level": int, "voted_on_topgg": bool, "new_user_experience": int, "multipliers": [ { "multiplier": float, "text": str }, ... ] } """ # Decide on our plant type - will be ignored if there's already a plant db = await self.bot.database.get_connection() # Get friend watering status waterer_id = waterer_id or user_id waterer_is_owner = user_id == waterer_id they_you = {True: "you", False: "they"}.get(waterer_is_owner) their_your = {True: "your", False: "their"}.get(waterer_is_owner) # See if they can water this person's plant if not waterer_is_owner: given_key = await db( """SELECT * FROM user_garden_access WHERE garden_owner=$1 AND garden_access=$2""", user_id, waterer_id, ) if not given_key: await db.disconnect() return self.get_water_plant_dict( f"You don't have access to <@{user_id}>'s garden!") # See if they have a plant available plant_level_row = await db( """SELECT * FROM plant_levels WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2)""", user_id, plant_name, ) if not plant_level_row: await db.disconnect() shop_note = ( "Run the `shop` command to plant some new seeds, or `plants` " "to see the list of plants you have already!") return self.get_water_plant_dict(( f"{they_you.capitalize()} don't have a plant with the name **{plant_name}**! " f"{shop_note if waterer_is_owner else ''}")) plant_data = self.bot.plants[plant_level_row[0]['plant_type']] # See if the user running the command is the owner of the plant and give a cooldown period properly if waterer_is_owner: water_cooldown_period = timedelta( **self.bot.config['plants']['water_cooldown']) else: water_cooldown_period = timedelta( **self.bot.config['plants']['guest_water_cooldown']) # See if they're allowed to water things last_water_time = plant_level_row[0]['last_water_time'] if (last_water_time + water_cooldown_period ) > dt.utcnow() and user_id not in self.bot.owner_ids: await db.disconnect() timeout = utils.TimeValue( ((plant_level_row[0]['last_water_time'] + water_cooldown_period) - dt.utcnow()).total_seconds()) return self.get_water_plant_dict(( f"You need to wait another {timeout.clean_spaced} to be able to water " f"{their_your} {plant_level_row[0]['plant_type'].replace('_', ' ')}." )) # See if the plant should be dead if plant_level_row[0]['plant_nourishment'] < 0: plant_level_row = await db( """UPDATE plant_levels SET plant_nourishment=LEAST(-plant_levels.plant_nourishment, plant_levels.plant_nourishment), last_water_time=$3 WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2) RETURNING *""", user_id, plant_name, dt.utcnow(), ) # Increase the nourishment otherwise else: plant_level_row = await db( """UPDATE plant_levels SET plant_nourishment=LEAST(plant_levels.plant_nourishment+1, $4), last_water_time=$3, notification_sent=FALSE WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2) RETURNING *""", user_id, plant_name, dt.utcnow(), plant_data.max_nourishment_level, ) # Add to the user exp if the plant is alive user_plant_data = plant_level_row[0] gained_experience = 0 original_gained_experience = 0 multipliers = [] # List[dict] additional_text = [] # List[str] voted_on_topgg = False user_is_premium = False # See if the user is premium try: await localutils.checks.has_premium().predicate( utils.web.WebContext(self.bot, waterer_id)) user_is_premium = True except commands.CheckFailure: pass # Disconnect from the database so we don't have hanging connections open while # making our Top.gg web request await db.disconnect() # And now let's water the damn thing if user_plant_data['plant_nourishment'] > 0: # Get the experience that they should have gained total_experience = plant_data.get_experience() original_gained_experience = total_experience # See if we want to give them a premium bonus if user_is_premium: multipliers.append({ "multiplier": 2.0, "text": f"You're subscribed to Flower Premium! :D", }) # See if we want to give them a 30 second water-time bonus if dt.utcnow( ) - last_water_time - water_cooldown_period <= timedelta( seconds=30): multipliers.append({ "multiplier": 1.5, "text": f"You watered within 30 seconds of {their_your} plant's cooldown resetting.", }) # See if we want to give the new owner bonus if plant_level_row[0]['user_id'] != plant_level_row[0][ 'original_owner_id']: multipliers.append({ "multiplier": 1.05, "text": f"You watered a plant that {they_you} got from a trade.", }) # See if we want to give them the voter bonus user_voted_api_request = False try: user_voted_api_request = await asyncio.wait_for( self.get_user_voted(waterer_id), timeout=2.0) except asyncio.TimeoutError: pass if self.bot.config.get( 'bot_listing_api_keys', {}).get('topgg_token') and user_voted_api_request: bot_client_id = self.bot.config['oauth']['client_id'] multipliers.append({ "multiplier": 1.1, "text": f"You [voted for the bot](https://top.gg/bot/{bot_client_id}/vote) on Top.gg.", }) voted_on_topgg = True # See if we want to give them the plant longevity bonus if user_plant_data['plant_adoption_time'] < dt.utcnow( ) - timedelta(days=7): multipliers.append({ "multiplier": 1.2, "text": f"{their_your.title()} plant has been alive for longer than a week.", }) # See if we want to give them the plant longevity bonus if user_plant_data['immortal']: multipliers.append({ "multiplier": 0.5, "text": f"{their_your} plant is immortal.", }) # Add the actual multiplier values for obj in multipliers: total_experience *= obj['multiplier'] # Update db total_experience = int(total_experience) async with self.bot.database() as db: # Give exp to everyone we care about if waterer_is_owner: gained_experience = total_experience user_experience_row = 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+$2 RETURNING *""", user_id, gained_experience, ) else: gained_experience = int(total_experience * 0.8) owner_gained_experience = int(total_experience - gained_experience) await db.start_transaction() user_experience_row = 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+$2 RETURNING *""", waterer_id, gained_experience, ) owner_experience_row = 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+$2 RETURNING *""", user_id, owner_gained_experience, ) await db.commit_transaction() # Update the user achievements await db( """INSERT INTO plant_achievement_counts (user_id, plant_type, max_plant_nourishment) VALUES ($1, $2, $3) ON CONFLICT (user_id, plant_type) DO UPDATE SET max_plant_nourishment=GREATEST(plant_achievement_counts.max_plant_nourishment, excluded.max_plant_nourishment)""", user_id, user_plant_data['plant_type'], user_plant_data['plant_nourishment']) # Send an output if user_plant_data['plant_nourishment'] < 0: return self.get_water_plant_dict( f"You sadly pour water into the dry soil of {their_your} silently wilting plant :c" ) # Set up our output text gained_exp_string = f"**{gained_experience}**" if gained_experience == original_gained_experience else f"~~{original_gained_experience}~~ **{gained_experience}**" output_lines = [] if plant_data.get_nourishment_display_level( user_plant_data['plant_nourishment'] ) > plant_data.get_nourishment_display_level( user_plant_data['plant_nourishment'] - 1): output_lines.append( f"You gently pour water into **{plant_level_row[0]['plant_name']}**'s soil, gaining you {gained_exp_string} experience, watching {their_your} plant grow!~" ) else: output_lines.append( f"You gently pour water into **{plant_level_row[0]['plant_name']}**'s soil, gaining you {gained_exp_string} experience~" ) for obj in multipliers: output_lines.append(f"**{obj['multiplier']}x**: {obj['text']}") for t in additional_text: output_lines.append(t) # And now we output ALL the information that we need for this to be an API route return self.get_water_plant_dict( text="\n".join(output_lines), success=True, gained_experience=gained_experience, new_nourishment_level=plant_level_row[0]['plant_nourishment'], new_user_experience=user_experience_row[0]['user_experience'], voted_on_topgg=voted_on_topgg, multipliers=multipliers, )
async def waterplant(self, ctx: utils.Context, *, plant_name: str): """ Increase the growth level of your plant. """ # Decide on our plant type - will be ignored if there's already a plant db = await self.bot.database.get_connection() # See if they have a plant available plant_level_row = await db( "SELECT * FROM plant_levels WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2)", ctx.author.id, plant_name) if not plant_level_row: await db.disconnect() return await ctx.send( f"You don't have a plant with the name **{plant_name}**! Run `{ctx.prefix}getplant` to plant some new seeds, or `{ctx.prefix}plants` to see the list of plants you have already!", allowed_mentions=discord.AllowedMentions(users=False, roles=False, everyone=False)) plant_data = self.bot.plants[plant_level_row[0]['plant_type']] # See if they're allowed to water things if plant_level_row[0]['last_water_time'] + timedelta( **self.bot.config.get('plants', {}).get( 'water_cooldown', {'minutes': 15})) > dt.utcnow( ) and ctx.author.id not in self.bot.owner_ids: await db.disconnect() timeout = utils.TimeValue( ((plant_level_row[0]['last_water_time'] + timedelta(**self.bot.config.get('plants', {}).get( 'water_cooldown', {'minutes': 15}))) - dt.utcnow()).total_seconds()) return await ctx.send( f"You need to wait another {timeout.clean_spaced} to be able water your {plant_level_row[0]['plant_type'].replace('_', ' ')}." ) last_water_time = plant_level_row[0]['last_water_time'] # See if the plant should be dead if plant_level_row[0]['plant_nourishment'] < 0: plant_level_row = await db( """UPDATE plant_levels SET plant_nourishment=LEAST(-plant_levels.plant_nourishment, plant_levels.plant_nourishment), last_water_time=$3 WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2) RETURNING *""", ctx.author.id, plant_name, dt.utcnow(), ) # Increase the nourishment otherwise else: plant_level_row = await db( """UPDATE plant_levels SET plant_nourishment=LEAST(plant_levels.plant_nourishment+1, $4), last_water_time=$3 WHERE user_id=$1 AND LOWER(plant_name)=LOWER($2) RETURNING *""", ctx.author.id, plant_name, dt.utcnow(), plant_data.max_nourishment_level, ) # Add to the user exp if the plant is alive user_plant_data = plant_level_row[0] gained_experience = 0 original_gained_experience = 0 multipliers = [] # List[Tuple[float, "reason"]] additional_text = [] # List[str] voted_on_topgg = False # And now let's water the damn thing if user_plant_data['plant_nourishment'] > 0: # Get the experience that they should have gained gained_experience = plant_data.get_experience() original_gained_experience = gained_experience # See if we want to give them a 30 second water-time bonus if dt.utcnow() - last_water_time - timedelta( **self.bot.config.get('plants', {}).get( 'water_cooldown', {'minutes': 15})) <= timedelta( seconds=30): multipliers.append(( 1.5, "You watered within 30 seconds of your plant's cooldown resetting." )) # See if we want to give the new owner bonus if plant_level_row[0]['user_id'] != plant_level_row[0][ 'original_owner_id']: multipliers.append( (1.05, "You watered a plant that you got from a trade.")) # See if we want to give them the voter bonus if self.bot.config.get( 'bot_listing_api_keys', {}).get('topgg_token') and await self.get_user_voted( ctx.author.id): multipliers.append(( 1.1, f"You [voted for the bot](https://top.gg/bot/{self.bot.user.id}/vote) on Top.gg." )) voted_on_topgg = True # See if we want to give them the plant longevity bonus if user_plant_data['plant_adoption_time'] < dt.utcnow( ) - timedelta(days=7): multipliers.append( (1.2, "Your plant has been alive for longer than a week.")) # Add the actual multiplier values for multiplier, _ in multipliers: gained_experience *= multiplier # Update db gained_experience = int(gained_experience) 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+$2""", ctx.author.id, gained_experience, ) # Send an output await db.disconnect() if user_plant_data['plant_nourishment'] < 0: return await ctx.send( "You sadly pour water into the dry soil of your silently wilting plant :c" ) # Set up our output text gained_exp_string = f"**{gained_experience}**" if gained_experience == original_gained_experience else f"~~{original_gained_experience}~~ **{gained_experience}**" output_lines = [] if plant_data.get_nourishment_display_level( user_plant_data['plant_nourishment'] ) > plant_data.get_nourishment_display_level( user_plant_data['plant_nourishment'] - 1): output_lines.append( f"You gently pour water into **{plant_level_row[0]['plant_name']}**'s soil, gaining you {gained_exp_string} experience, watching your plant grow!~" ) else: output_lines.append( f"You gently pour water into **{plant_level_row[0]['plant_name']}**'s soil, gaining you {gained_exp_string} experience~" ) for m, t in multipliers: output_lines.append(f"**{m}x**: {t}") for t in additional_text: output_lines.append(t) # Try and embed the message embed = None if ctx.guild is None or ctx.channel.permissions_for( ctx.guild.me).embed_links: # Make initial embed embed = utils.Embed(use_random_colour=True, description=output_lines[0]) # Add multipliers if len(output_lines) > 1: embed.add_field("Multipliers", "\n".join( [i.strip('') for i in output_lines[1:]]), inline=False) # Add "please vote for Flower" footer counter = 0 ctx._set_footer(embed) check = lambda text: 'vote' in text if voted_on_topgg else False # Return True to change again - force to "vote for flower" if they haven't voted, else anything but while counter < 100 and check(embed.footer.text.lower()): ctx._set_footer(embed) counter += 1 # Clear the text we would otherwise output output_lines.clear() # Send message return await ctx.send("\n".join(output_lines), embed=embed)
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!")