async def quote_random(self, ctx: vbu.Context, user: discord.Member = None): """ Gets a random quote for a given user. """ # Get quote from database user = user or ctx.author async with vbu.Database() as db: quote_rows = await db( """SELECT quote_id as quote_id, user_id, channel_id, message_id FROM user_quotes WHERE user_id=$1 AND guild_id=$2 ORDER BY RANDOM() LIMIT 1""", user.id, ctx.guild.id, ) if not quote_rows: return await ctx.send( f"{user.mention} has no available quotes.", allowed_mentions=discord.AllowedMentions.none()) # Get the message data = quote_rows[0] if data['channel_id'] is None: self.logger.info(f"Deleting legacy quote - {data['quote_id']}") async with vbu.Database() as db: await db("DELETE FROM user_quotes WHERE quote_id=$1", data['quote_id']) return await ctx.reinvoke() channel = self.bot.get_channel(data['channel_id']) if channel is None: self.logger.info( f"Deleting quote from deleted channel - {data['quote_id']}") async with vbu.Database() as db: await db("DELETE FROM user_quotes WHERE quote_id=$1", data['quote_id']) return await ctx.reinvoke() try: message = await channel.fetch_message(data['message_id']) assert message is not None except (AssertionError, discord.HTTPException): self.logger.info( f"Deleting quote from deleted message - {data['quote_id']}") async with vbu.Database() as db: await db("DELETE FROM user_quotes WHERE quote_id=$1", data['quote_id']) return await ctx.reinvoke() # Output to user quote_embed = message.embeds[0] quote_author = self.bot.get_user(data['user_id']) if quote_author: quote_embed.set_author(name=quote_author.display_name, icon_url=quote_author.display_avatar.url) return await ctx.send(embed=quote_embed)
async def on_member_join(self, member: discord.Member): """ Pings a member nickname update on member join. """ # Check if they are a bot if member.bot: return # See if they have a permanent nickname set async with vbu.Database() as db: data = await db( """SELECT nickname FROM permanent_nicknames WHERE guild_id=$1 AND user_id=$2""", member.guild.id, member.id) if data: try: await member.edit(nick=data[0]["nickname"], reason="Changed by Apple.Py automagically") self.logger.info( f"Set permanent nickname of {member.id} in {member.guild.id} from member join" ) except discord.Forbidden as e: self.logger.error( f"Couldn't set permanent nickname of {member.id} in {member.guild.id} - {e}" ) return # See if we want to fun their name if self.bot.guild_settings[ member.guild.id]['automatic_nickname_update']: self.logger.info( f"Pinging nickname update for member join (G{member.guild.id}/U{member.id})" ) await self.fix_user_nickname(member)
async def reminder_list(self, ctx: vbu.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 vbu.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 = discord.utils.format_dt(reminder['timestamp']) 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 points_leaderboard_show(self, ctx: vbu.Context): """ Show the points leaderboard without creating a new one. """ # Make some assertions assert ctx.guild # See if they're running a subcommand if ctx.invoked_subcommand is not None: return # Get data async with vbu.Database() as db: rows = await db( "SELECT * FROM user_points WHERE guild_id=$1 AND points > 0 ORDER BY points DESC", ctx.guild.id) # Format it into an embed valid_users = [] for i in rows: member = ctx.guild.get_member(i['user_id']) if member is None: continue valid_users.append(f"{member.mention}: {i['points']:,}") if len(valid_users) >= 30: break with vbu.Embed(use_random_colour=True) as embed: embed.description = '\n'.join( valid_users) or 'Nobody is on here :c' # Output await ctx.send(embed=embed)
async def on_command(self, ctx: commands.Context): """ Count every time a command is run uwu """ command = ctx.command if command is None: return command_name = command.name async with vbu.Database() as db: current_count = await db( "SELECT count FROM command_counter WHERE command_name=$1", command_name, ) # Make sure we get a current count if current_count: current_count = current_count[0]["count"] else: current_count = 0 await db( "INSERT INTO command_counter (command_name, count) VALUES ($1, $2) ON CONFLICT (command_name) DO UPDATE SET count = $2", command_name, current_count + 1, ) self.bot.logger.info(f"Logging command completion: {command.name}")
async def user_feed(self, ctx, tank_chosen): async with vbu.Database() as db: upgrades = await db( """SELECT feeding_upgrade, big_servings_upgrade FROM user_upgrades WHERE user_id = $1""", ctx.author.id, ) fish_rows = await db( """SELECT * FROM user_fish_inventory WHERE user_id = $1 AND tank_fish = $2""", ctx.author.id, tank_chosen ) tank_rows = await db( """SELECT * FROM user_tank_inventory WHERE user_id = $1""", ctx.author.id, ) item_rows = await db( """SELECT * FROM user_item_inventory WHERE user_id = $1""", ctx.author.id, ) # For each fish in the selected fish rows add them to a list if not fish_rows: return await ctx.send("No fish in that tank!") fish_in_tank = [] for fish in fish_rows: feed_time = dt(year=2005, month=5, day=1) if fish["fish_feed_time"]: feed_time = fish["fish_feed_time"] if fish["fish_alive"] == True and not (fish_feed_timeout := feed_time + FISH_FEED_COOLDOWN) > dt.utcnow(): fish_in_tank.append(fish["fish_name"])
async def rolepicker_name_autocomplete( self, ctx: vbu.SlashContext, interaction: discord.Interaction): """ Handle autocompletes for rolepicker names. """ async with vbu.Database() as db: role_picker_rows = await db.call( """ SELECT name FROM role_pickers WHERE guild_id = $1 AND name LIKE '%' || $2 || '%' """, interaction.guild_id, interaction.options[0].options[0].value, ) return await interaction.response.send_autocomplete([ discord.ApplicationCommandOptionChoice(name=i['name'], value=i['name']) for i in role_picker_rows ])
async def timezone_get(self, ctx: vbu.Context, target: typing.Union[discord.Member, str] = None): """ Get the current time for a given user. """ # Check if they are a bot target = target or ctx.author target_is_timezone = False if isinstance(target, str): target_is_timezone = True target = self.get_common_timezone(target) if isinstance(target, discord.Member) and target.bot: return await ctx.send("I don't think bots have timezones...") # See if they've set a timezone if not target_is_timezone: async with vbu.Database() as db: rows = await db("SELECT timezone_name, timezone_offset FROM user_settings WHERE user_id=$1", target.id) if not rows or (rows[0]['timezone_name'] is None and rows[0]['timezone_offset'] is None): return await ctx.send(f"{target.mention} hasn't set up their timezone information! They can set it by running `{ctx.clean_prefix}timezone set`.") # Grab their current time and output if target_is_timezone: try: formatted_time = (discord.utils.utcnow().astimezone(pytz.timezone(target))).strftime('%-I:%M %p') except pytz.UnknownTimeZoneError: return await ctx.send("That isn't a valid timezone.") return await ctx.send(f"The current time in **{target}** is estimated to be **{formatted_time}**.") elif rows: if rows[0]['timezone_name']: formatted_time = (discord.utils.utcnow().astimezone(pytz.timezone(rows[0]['timezone_name']))).strftime('%-I:%M %p') else: formatted_time = (discord.utils.utcnow() + timedelta(minutes=rows[0]['timezone_offset'])).strftime('%-I:%M %p') await ctx.send(f"The current time for {target.mention} is estimated to be **{formatted_time}**.", allowed_mentions=discord.AllowedMentions.none())
async def user_revive(self, ctx, tank): # Get database vars async with vbu.Database() as db: fish_rows = await db( """SELECT * FROM user_fish_inventory WHERE user_id = $1 AND fish_alive = FALSE AND tank_fish = $2""", ctx.author.id, tank ) revival_count = await db( """SELECT revival FROM user_item_inventory WHERE user_id = $1""", ctx.author.id, ) # Make a list of all their fish fish_in_tank = [] for fish in fish_rows: fish_in_tank.append(fish["fish_name"]) # Create a select menu with their fish being choices fish = await utils.create_select_menu( self.bot, ctx, fish_in_tank, "dead fish", "revive", True ) if revival_count[0]['revival'] <= 0: return await ctx.send("You have no revivals!") # If the fish isn't in a tank, it has no death timer, but if it is it's set to three days death_timer = dt.utcnow() + timedelta(days=3) message = f"{fish} is now alive, and will die {discord.utils.format_dt(death_timer, style='R')}!" # Set the database values async with vbu.Database() as db: await db( """UPDATE user_fish_inventory SET fish_alive = True, death_time = $3 WHERE user_id = $1 AND fish_name = $2""", ctx.author.id, fish, death_timer, ) await db( """UPDATE user_item_inventory SET revival = revival - 1 WHERE user_id = $1""", ctx.author.id, ) # Send message await ctx.send( message, allowed_mentions=discord.AllowedMentions.none() )
async def on_message(self, message: discord.Message): """ Sends GitHub/Lab links if a message sent in the server matches the format `gh/user/repo`. """ if message.author.bot: return if (await self.bot.get_context(message)).command is not None: return # Find matches in the message m = re.finditer( ( r'(?:\s|^)(?P<ident>g[hl])/(?P<url>(?P<user>[a-zA-Z0-9_-]{1,255})/(?P<repo>[a-zA-Z0-9_-]{1,255}))' r'(?:[#!]?(?P<issue>\d+?))?(?:\s|$)' ), message.content, ) n = re.finditer( r'(?:\s|^)(?P<ident>g[hl]) (?P<alias>\S{1,255})(?: [#!]?(?P<issue>\d+?))?(?:\s|$)', message.content, ) # Dictionary of possible Git() links git_dict = { "gh": "hub", "gl": "lab", } # Add the url of each matched link to the final output sendable = "" for i in m: url = i.group("url") ident = i.group("ident") issue = i.group("issue") url = f"https://git{git_dict[ident]}.com/{url}" if issue: if ident == "gh": url = f"{url}/issues/{issue}" elif ident == "gl": url = f"{url}/-/issues/{issue}" sendable += f"<{url}>\n" if n: async with vbu.Database() as db: for i in n: issue = i.group("issue") rows = await db("SELECT * FROM github_repo_aliases WHERE alias=$1", i.group("alias")) if rows: url = f"https://{rows[0]['host'].lower()}.com/{rows[0]['owner']}/{rows[0]['repo']}" if issue: if rows[0]['host'] == "Github": url = f"{url}/issues/{issue}" elif rows[0]['host'] == "Gitlab": url = f"{url}/-/issues/{issue}" sendable += f"<{url}>\n" # Send the GitHub links if there's any output if sendable: await message.channel.send(sendable, allowed_mentions=discord.AllowedMentions.none())
async def increase_repo_usage_counter(self, user: typing.Union[discord.User, discord.Member], repo: GitRepo): async with vbu.Database() as db: await db( """INSERT INTO github_repo_uses (user_id, owner, repo, host, uses) VALUES ($1, $2, $3, $4, 1) ON CONFLICT (user_id, owner, repo, host) DO UPDATE SET uses=github_repo_uses.uses+excluded.uses""", user.id, repo.owner, repo.repo, repo.host, )
async def enough_to_craft(crafted: str, user_id: int): for item, required in items_required[crafted][0].items(): async with vbu.Database() as db: amount = await db( f"""SELECT {item} FROM user_item_inventory WHERE user_id = $1""", user_id) if amount[0][item] < required: return False return True
async def rolepicker_add( self, ctx: vbu.SlashContext, name: str, role: discord.Role): """ Add a new role to a role picker. """ # Defer so we can fetch await ctx.interaction.response.defer(ephemeral=True) # Fetch some values from the API author: discord.Member author = await ctx.guild.fetch_member(ctx.author.id) # type: ignore - author will definitely exist guild: discord.Guild = ctx.guild guild_roles = guild.roles # Make sure the role they gave is lower than their top author_top_role = [i for i in guild_roles if i.id == author.roles[-1].id][0] role = [i for i in guild_roles if i.id == role.id][0] if author_top_role < role: return await ctx.interaction.followup.send( "Your top role is below the one you're trying to manage.", ephemeral=True, ) # Add that role to the database async with vbu.Database() as db: await db.call( """ INSERT INTO role_picker_role ( guild_id, name, role_id ) VALUES ( $1, -- guild_id $2, -- name $3 -- role_id ) ON CONFLICT (guild_id, name, role_id) DO NOTHING """, guild.id, name, role.id, ) # Tell the user it's done await ctx.interaction.followup.send( "Added role to role picker!~", ephemeral=True, ) self.bot.dispatch("role_picker_update", guild, name)
async def quote_force(self, ctx: vbu.Context, messages: commands.Greedy[discord.Message]): """ Quotes a user's message to the guild's quote channel. """ # Make sure no subcommand is passed if ctx.invoked_subcommand is not None: return response = await self.get_quote_messages(ctx, messages, allow_self_quote=True) # Make embed if response['success'] is False: return await ctx.send(response['message']) embed = response['message'] user = response['user'] timestamp = response['timestamp'] # See if they have a quotes channel quote_channel_id = self.bot.guild_settings[ctx.guild.id].get( 'quote_channel_id') quote_id = create_id() embed.set_footer(text=f"Quote ID {quote_id.upper()}") posted_message = None if quote_channel_id: channel = self.bot.get_channel(quote_channel_id) try: posted_message = await channel.send(embed=embed) except (discord.Forbidden, AttributeError): pass if quote_channel_id is None or posted_message is None: return await ctx.send( "I couldn't send your quote into the quote channel.") # And save it to the database async with vbu.Database() as db: await db( """INSERT INTO user_quotes (quote_id, guild_id, channel_id, message_id, user_id, timestamp, quoter_id) VALUES ($1, $2, $3, $4, $5, $6, $7)""", quote_id, ctx.guild.id, posted_message.channel.id, posted_message.id, user.id, timestamp.replace(tzinfo=None), ctx.author.id, ) # Output to user await ctx.send( f"{ctx.author.mention}'s quote saved with ID `{quote_id.upper()}`", embed=embed)
async def topic(self, ctx: vbu.Context): """ The parent group for the topic commands. """ async with vbu.Database() as db: rows = await db("SELECT * FROM topics ORDER BY RANDOM() LIMIT 1") if not rows: return await ctx.send( "There aren't any topics set up in the database for this bot :<" ) return await ctx.send(rows[0]['topic'])
async def fish_food_death_loop(self): # Get all the fish that are in a tank and if they have a time of death, check to see if its past that time, then kill them if need be async with vbu.Database() as db: fish_rows = await db( """SELECT * FROM user_fish_inventory WHERE tank_fish != ''""") for fish_row in fish_rows: if fish_row["death_time"]: if dt.utcnow() > fish_row["death_time"] and fish_row[ 'fish_alive'] is True: await db( """UPDATE user_fish_inventory SET fish_alive=FALSE WHERE fish_name = $1 AND user_id = $2""", fish_row["fish_name"], fish_row["user_id"])
async def commanddata(self, ctx: commands.Context): """ Send out the list of commands and their current count """ # Get info from database async with vbu.Database() as db: command_data = await db("SELECT * FROM command_counter") # Make sure we have data if not command_data: return await ctx.send("No command data was found in the database.") # Set up the command list sorted_commands_singlelist = [] commands_list = {} # List of strings "**command name**: command count" total_count = 0 # To count the total number of commands for command in command_data: count = command["count"] total_count += count for command in command_data: count = command["count"] commands_list[ f"**{command['command_name']}**: {count} times `({(count / total_count) * 100:.2f}%)`\n" ] = count # commands_list.append({count: f"**{command['command_name']}**: {count} times `({(count / total_count) * 100}%)`\n"}) sorted_commands = sorted( commands_list.items(), key=lambda x: x[1], reverse=True ) for i in sorted_commands: sorted_commands_singlelist.append(i[0]) # Paginate # Set up the paginator formatter def formatter(menu, items): # Create the embed commands_embed = vbu.Embed(title="Command Data (times run)") # Add the total count footer commands_embed.set_footer(text=f"Total: {total_count}") # Add the command list to the emebd commands_embed.description = "\n".join(items) # Return the embed return commands_embed # Begin paginating pagin = vbu.Paginator( sorted_commands_singlelist, formatter=formatter, per_page=10 ) await pagin.start(ctx)
async def issue_create_autocomplete(self, ctx: commands.SlashContext, interaction: discord.Interaction): """ Send the user's most frequently used repos. """ if not interaction.user: return await interaction.response.send_autocomplete(None) async with vbu.Database() as db: rows = await db( """SELECT * FROM github_repo_uses WHERE user_id=$1 ORDER BY uses DESC""", interaction.user.id, ) responses = [ discord.ApplicationCommandOptionChoice(name=(repo := str(GitRepo(r['host'], r['owner'], r['repo']))), value=repo) for r in rows ]
async def repoalias_remove(self, ctx: vbu.Context, alias: str): """ Removes a Github repo alias from the database. """ async with vbu.Database() as db: data = await db( "SELECT * FROM github_repo_aliases WHERE alias=LOWER($1) AND added_by=$2", alias, ctx.author.id, ) if not data: return await ctx.send( "You don't own that repo alias.", allowed_mentions=discord.AllowedMentions.none(), ) await db("DELETE FROM github_repo_aliases WHERE alias=LOWER($1)", alias) await ctx.send("Done.")
async def rolepicker_delete( self, ctx: vbu.SlashContext, name: str): """ Remove a role picker message. """ # Defer so we can database call await ctx.interaction.response.defer(ephemeral=True) # Delete stored info async with vbu.Database() as db: role_picker_rows = await db.call( """ DELETE FROM role_pickers WHERE guild_id = $1 AND name = $2 RETURNING * """, ctx.guild.id, name, ) # See if we can delete the message as well row = role_picker_rows[0] messageable = self.bot.get_partial_messageable( row['channel_id'], type=discord.ChannelType.text, ) message = messageable.get_partial_message(row['message_id']) try: await message.delete() except: pass # And tell them about it await ctx.interaction.followup.send( "Deleted role picker.", ephemeral=True, )
async def points_leaderboard_create(self, ctx: vbu.Context): """ Create a points leaderboard. """ # Make some assertions assert ctx.guild message = await ctx.send("Setting up leaderboard message...") async with vbu.Database() as db: await db( """INSERT INTO guild_settings (guild_id, leaderboard_message_url) VALUES ($1, $2) ON CONFLICT (guild_id) DO UPDATE SET leaderboard_message_url=excluded.leaderboard_message_url""", ctx.guild.id, message.jump_url, ) self.bot.guild_settings[ ctx.guild.id]['leaderboard_message_url'] = message.jump_url self.bot.dispatch("leaderboard_update", ctx.guild)
async def user_cast_loop(self): self.cast_time = dt.utcnow() async with vbu.Database() as db: casts = await db("""SELECT * FROM user_balance""") for x in casts: if x["casts"] >= 50: continue amount_of_crafted = await db( """SELECT fishing_boots FROM user_item_inventory WHERE user_id = $1""", x["user_id"]) if amount_of_crafted: boot_multiplier = amount_of_crafted[0]['fishing_boots'] else: boot_multiplier = 0 amount = random.choices([1, 2], [(1 - (.04 * boot_multiplier)), (.04 * boot_multiplier)])[0] await db( """UPDATE user_balance SET casts=casts+$2 WHERE user_id = $1""", x["user_id"], amount)
async def on_leaderboard_update(self, guild: discord.Guild): """ Update the leaderboard message. """ # See if we can get the leaderboard message class FakeContext: bot = self.bot leaderboard_message_url = self.bot.guild_settings[ guild.id]['leaderboard_message_url'] if not leaderboard_message_url: return try: message = await commands.MessageConverter().convert( FakeContext, leaderboard_message_url) except commands.BadArgument: return if message is None: return # Get data async with vbu.Database() as db: rows = await db( "SELECT * FROM user_points WHERE guild_id=$1 AND points > 0 ORDER BY points DESC", guild.id) # Format it into an embed valid_users = [] for i in rows: member = guild.get_member(i['user_id']) if member is None: continue valid_users.append(f"{member.mention}: {i['points']:,}") if len(valid_users) >= 30: break with vbu.Embed(use_random_colour=True) as embed: embed.description = '\n'.join( valid_users) or 'Nobody is on here :c' # Output await message.edit(content=None, embed=embed)
async def rolepicker_remove( self, ctx: vbu.SlashContext, name: str, role: discord.Role): """ Remove a role from one of your role pickers. """ # Defer so we can fetch await ctx.interaction.response.defer(ephemeral=True) # Remove that role from the database async with vbu.Database() as db: removed_role = await db.call( """ DELETE FROM role_picker_role WHERE guild_id = $1 AND name = $2 AND role_id = $3 RETURNING * """, ctx.guild.id, name, role.id, ) # See if anything was removed if removed_role: await ctx.interaction.followup.send( "Removed role from role picker.", ephemeral=True, ) else: await ctx.interaction.followup.send( "That role wasn't in the picker.", ephemeral=True, ) return self.bot.dispatch("role_picker_update", ctx.guild, name)
async def convert(cls, ctx: vbu.Context, value: str): """ Convert a string into an (host, owner, repo) string tuple. """ # Let's strip the thing value = value.rstrip('/') # See if it's in the form `gh/owner/repo` if value.startswith("gh/"): _, owner, repo = value.split('/') host = "Github" # See if it's a Github url elif "github.com" in value.lower(): match = re.search(r"(?:https?://)?github\.com/(?P<user>[a-zA-Z0-9_\-.]+)/(?P<repo>[a-zA-Z0-9_\-.]+)", value) assert match owner, repo = match.group("user"), match.group("repo") host = "Github" # See if it's in the form `gl/owner/repo` elif value.startswith("gl/"): _, owner, repo = value.split('/') host = "Gitlab" # See if it's a Gitlab url elif "gitlab.com" in value.lower(): match = re.search(r"(?:https?://)?gitlab\.com/(?P<user>[a-zA-Z0-9_\-.]+)/(?P<repo>[a-zA-Z0-9_\-.]+)", value) assert match owner, repo = match.group("user"), match.group("repo") host = "Gitlab" # See if it's a repo alias else: async with vbu.Database() as db: repo_rows = await db("SELECT * FROM github_repo_aliases WHERE alias=LOWER($1)", value) if not repo_rows: raise commands.BadArgument("I couldn't find that git repo.") owner, repo, host = repo_rows[0]['owner'], repo_rows[0]['repo'], repo_rows[0]['host'] # Sick we're done return cls(host, owner, repo)
async def quote_get(self, ctx: vbu.Context, identifier: str): """ Gets a quote from the guild's quote channel. """ # Get quote from database async with vbu.Database() as db: quote_rows = await db( """SELECT user_quotes.quote_id as quote_id, user_id, channel_id, message_id FROM user_quotes LEFT JOIN quote_aliases ON user_quotes.quote_id=quote_aliases.quote_id WHERE user_quotes.quote_id=$1 OR quote_aliases.alias=$1""", identifier.lower(), ) if not quote_rows: return await ctx.send( f"There's no quote with the identifier `{identifier.upper()}`.", allowed_mentions=discord.AllowedMentions.none(), ) # Get the message data = quote_rows[0] if data['channel_id'] is None: return await ctx.send( "There's no quote channel set for that quote.") channel = self.bot.get_channel(data['channel_id']) if channel is None: return await ctx.send("I wasn't able to get your quote channel.") try: message = await channel.fetch_message(data['message_id']) assert message is not None except (AssertionError, discord.HTTPException): return await ctx.send("I wasn't able to get your quote message.") # try to refresh the user name and icon of the embed by getting the user from the user ID in the DB quote_embed = message.embeds[0] quote_author = self.bot.get_user(data['user_id']) if quote_author: quote_embed.set_author(name=quote_author.display_name, icon_url=quote_author.display_avatar.url) # Output to user return await ctx.send(embed=quote_embed)
async def quote_delete(self, ctx: vbu.Context, quote_id: str): """ Deletes a quote from your server. """ # quote_ids = [i.lower() for i in quote_ids] quote_ids = [quote_id.lower()] quote_channel_id = self.bot.guild_settings[ctx.guild.id].get( 'quote_channel_id') if quote_channel_id: quote_channel = self.bot.get_channel(quote_channel_id) try: async for message in quote_channel.history(limit=150): if not message.author.id == ctx.guild.me.id: continue if not message.embeds: continue embed = message.embeds[0] if not embed.footer: continue footer_text = embed.footer.text if not footer_text: continue if not footer_text.startswith("Quote ID"): continue message_quote_id = footer_text.split(' ')[2].lower() if message_quote_id in quote_ids: try: await message.delete() except discord.HTTPException: pass except (discord.HTTPException, AttributeError) as e: await ctx.send(e) async with vbu.Database() as db: await db( "DELETE FROM user_quotes WHERE quote_id=ANY($1) AND guild_id=$2", quote_ids, ctx.guild.id) return await ctx.send("Deleted quote(s).")
async def repoalias_add(self, ctx: vbu.Context, alias: str, repo: GitRepo): """ Add a Github repo alias to the database. """ async with vbu.Database() as db: try: await db( """INSERT INTO github_repo_aliases (alias, owner, repo, host, added_by) VALUES (LOWER($1), $2, $3, $4, $5)""", alias, repo.owner, repo.repo, repo.host, ctx.author.id, ) except asyncpg.UniqueViolationError: data = await db("SELECT * FROM github_repo_aliases WHERE alias=LOWER($1) AND added_by=$2", alias, ctx.author.id) if not data: return await ctx.send( f"The alias `{alias.lower()}` is already in use.", allowed_mentions=discord.AllowedMentions.none(), ) await db("DELETE FROM github_repo_aliases WHERE alias=$1", alias) return await self.repoalias_add(ctx, alias, repo) await ctx.send("Done.")
async def points_remove(self, ctx: vbu.Context, user: typing.Optional[discord.Member], points: int = 1): """ Remove points from a user. """ # Make some assertions assert isinstance(ctx.author, discord.Member) assert ctx.guild # Alter data user = user or ctx.author async with vbu.Database() as db: await db( """INSERT INTO user_points VALUES ($1, $2, $3) ON CONFLICT (guild_id, user_id) DO UPDATE SET points=user_points.points+excluded.points""", ctx.guild.id, user.id, -points) # Send output await ctx.send(f"Removed {points} points from {user.mention}.", allowed_mentions=discord.AllowedMentions.none()) self.bot.dispatch("leaderboard_update", ctx.guild)
async def timezone_set(self, ctx: vbu.Context, *, offset: str = None): """ Sets and stores your UTC offset into the bot. """ # Ask them the question if offset is None: ask_message = await ctx.send(( f"Hey, {ctx.author.mention}, what timezone are you currently in? You can give its name (`EST`, `GMT`, etc) " "or you can give your continent and nearest large city (`Europe/Amsterdam`, `Australia/Sydney`, etc) - this is " "case sensitive." )) try: check = lambda m: m.author.id == ctx.author.id and m.channel.id == ctx.channel.id response_message = await self.bot.wait_for("message", check=check, timeout=30) offset = response_message.content except asyncio.TimeoutError: return await ask_message.delete() # See if it's one of the more common ones that I know don't actually exist offset = self.get_common_timezone(offset) # Try and parse the timezone name try: zone = pytz.timezone(offset) except pytz.UnknownTimeZoneError: return await ctx.send(f"I can't work out what timezone you're referring to - please run this command again to try later, or go to the website (`{ctx.clean_prefix}info`) and I can work it out automatically.") # Store it in the database async with vbu.Database() as db: await db( """INSERT INTO user_settings (user_id, timezone_name) VALUES ($1, $2) ON CONFLICT (user_id) DO UPDATE SET timezone_name=excluded.timezone_name""", ctx.author.id, zone.zone, ) await ctx.send(f"I think your current time is **{discord.utils.utcnow().astimezone(zone).strftime('%-I:%M %p')}** - I've stored this in the database.")