async def on_command_error(self, ctx: utils.Context, error: commands.CommandError): """Listens for command not found errors and tries to run them as interactions""" if not isinstance(error, commands.CommandNotFound): return # Deal with common aliases command_name = ctx.invoked_with.lower() for aliases in COMMON_COMMAND_ALIASES: if command_name in aliases: command_name = aliases[0] # See if we wanna deal with it guild_ids = [0] if ctx.guild is None else [0, ctx.guild.id] async with self.bot.database() as db: rows = await db( "SELECT response FROM interaction_text WHERE interaction_name=$1 AND guild_id=ANY($2::BIGINT[]) ORDER BY RANDOM() LIMIT 1", command_name.lower(), guild_ids) if not rows: self.logger.info("Nothing found") return # No responses found # Create a command we can invoke ctx.interaction_response = rows[0]['response'] ctx.interaction_name = command_name ctx.invoke_meta = True ctx.command = self.bot.get_command("interaction_command_meta") await self.bot.invoke(ctx)
async def copymee6roles(self, ctx: utils.Context): """Copies the Mee6 roles into your static role handling""" async with ctx.typing(): # Get data from the Mee6 API base = "https://mee6.xyz/api/plugins/levels/leaderboard/" async with self.bot.session.get(base + str(ctx.guild.id)) as r: data = await r.json() if str(r.status) == '404': return await ctx.send( "The leaderboard page for this guild is either not public or not present - Mee6 must be on your server for this to work." ) await ctx.send( f"Grabbed {len(data['role_rewards'])} roles from Mee6 - now saving to database..." ) # Save to db role_rewards = data['role_rewards'] async with self.bot.database() as db: for role in role_rewards: await db( """INSERT INTO static_role_gain (guild_id, role_id, threshold) VALUES ($1, $2, $3) ON CONFLICT (role_id) DO NOTHING""", ctx.guild.id, int(role['role']['id']), self.get_messages_by_level(role['rank'])) # Remove cached roles for the guild cog = self.bot.get_cog("RoleHandler") if cog: cog.static_role_handles[ctx.guild.id] = None # Output to user return await ctx.send( f"Saved {len(role_rewards)} role rewards from Mee6.")
async def copymee6exp(self, ctx: utils.Context): """Copies the Mee6 exp into Cerberus""" # Check that they're not already copied async with self.bot.database() as db: data = await db("SELECT * FROM copied_mee6_exp WHERE guild_id=$1", ctx.guild.id) if data: return await ctx.send( "You've already copied over your exp from Mee6.") async with ctx.typing(): # Get data from the Mee6 API base = "https://mee6.xyz/api/plugins/levels/leaderboard/" user_data = [] i = 0 while True: async with self.bot.session.get(base + str(ctx.guild.id), params={ 'page': i, 'limit': 1000 }) as r: data = await r.json() if str(r.status) == '404': return await ctx.send( "The leaderboard page for this guild is either not public or not present - Mee6 must be on your server for this to work." ) elif str(r.status)[0] != '2': return await ctx.send(data) self.logger.info( f"Grabbed Mee6 leaderboard data for guild {ctx.guild.id} page {i}" ) if data['players']: user_data.extend(data['players']) else: break i += 1 await ctx.send( f"Grabbed data from Mee6, now putting {len(user_data)} fields into the database - this may take a few minutes." ) # Store in database async with self.bot.database() as db: await db( "INSERT INTO copied_mee6_exp VALUES ($1) ON CONFLICT (guild_id) DO NOTHING", ctx.guild.id) for user in user_data: self.bot.message_count[(int( user['id']), ctx.guild.id)] += user['message_count'] await db( """INSERT INTO static_user_messages (user_id, guild_id, message_count) VALUES ($1, $2, $3) ON CONFLICT (user_id, guild_id) DO UPDATE SET message_count=$3""", int(user['id']), ctx.guild.id, self.bot.message_count[(int(user['id']), ctx.guild.id)]) return await ctx.send( f"Copied over {len(user_data)} users' exp from Mee6.")
async def on_command_error(self, ctx: utils.Context, error: commands.CommandError): """CommandNotFound handler so the bot can search for that custom command""" # Handle commandnotfound which is really just handling the set/get/delete/etc commands if not isinstance(error, commands.CommandNotFound): return # Get the command and used template matches = self.COMMAND_REGEX.search( ctx.message.content[len(ctx.prefix):]) if not matches: return command_operator = matches.group("command") # get/get/delete/edit template_name = matches.group("template") # template name # Filter out DMs if isinstance(ctx.channel, discord.DMChannel): return # Fail silently on DM invocation # Find the template they asked for on their server async with self.bot.database() as db: template = await utils.Template.fetch_template_by_name( db, ctx.guild.id, template_name, fetch_fields=False) if not template: self.logger.info( f"Failed at getting template '{template_name}' in guild {ctx.guild.id}" ) return # Fail silently on template doesn't exist # Invoke command metacommand: utils.Command = self.bot.get_command( f'{command_operator.lower()}_profile_meta') ctx.command = metacommand ctx.template = template ctx.invoke_meta = True try: self.bot.dispatch("command", ctx) await metacommand.invoke( ctx) # This converts the args for me, which is nice except (commands.CommandInvokeError, commands.CommandError) as e: self.bot.dispatch( "command_error", ctx, e ) # Throw any errors we get in this command into its own error handler
async def on_command_error(self, ctx: utils.Context, error: commands.CommandError): """CommandNotFound handler so the bot can search for that custom command""" # Handle commandnotfound which is really just handling the set/get/delete/etc commands if not isinstance(error, commands.CommandNotFound): return # Get the command and used profile matches = self.COMMAND_REGEX.search( ctx.message.content[len(ctx.prefix):]) if not matches: return command_operator = matches.group(1) # get/get/delete/edit profile_name = matches.group(2) # profile name # Filter out DMs if isinstance(ctx.channel, discord.DMChannel): return # Fail silently on DM invocation # Find the profile they asked for on their server guild_commands = utils.Profile.all_guilds[ctx.guild.id] profile = guild_commands.get(profile_name) if not profile: self.logger.info( f"Failed at getting profile '{profile_name}' in guild {ctx.guild.id}" ) return # Fail silently on profile doesn't exist # Invoke command metacommand: utils.Command = self.bot.get_command( f'{command_operator.lower()}_profile_meta') ctx.command = metacommand ctx.profile = profile ctx.invoke_meta = True try: self.bot.dispatch("command", ctx) await metacommand.invoke( ctx) # This converts the args for me, which is nice except commands.CommandError as e: self.bot.dispatch( "command_error", ctx, e ) # Throw any errors we get in this command into its own error handler
async def custom_command_listener(self, ctx:utils.Context, error:commands.CommandError): """Catch command error, look to see if it's a custom, respond accordingly""" # Make sure we need to care if not isinstance(error, commands.CommandNotFound): return # Check responses command_name = ctx.invoked_with.lower() async with self.bot.database() as db: metadata = await db("SELECT * FROM command_names WHERE (command_name=$1 OR $1=ANY(aliases)) AND guild_id=$2 ORDER BY RANDOM() LIMIT 1", command_name, ctx.guild.id) if not metadata: return # Invoke command metacommand: utils.Command = self.bot.get_command('interaction_response_metacommand') ctx.command = metacommand ctx.response_metadata = metadata ctx.invoke_meta = True try: await ctx.command.invoke(ctx) # This converts the args for me, which is nice except commands.CommandError as e: self.bot.dispatch("command_error", ctx, e)
async def importsona(self, ctx: utils.Context): """Get your sona from another server""" # See if they're setting one up already if ctx.author.id in self.currently_setting_sonas: return await ctx.send( "You're already setting up a sona! Please finish that one off first!" ) # Try and send them an initial DM try: await ctx.author.send( f"Now taking you through importing your sona to **{ctx.guild.name}**!" ) except discord.Forbidden: return await ctx.send( "I couldn't send you a DM! Please open your DMs for this server and try again." ) self.currently_setting_sonas.add(ctx.author.id) await ctx.send("Sent you a DM!") # Get sona data async with self.bot.database() as db: database_rows = await db("SELECT * FROM fursonas WHERE user_id=$1", ctx.author.id) # Format that into a list all_user_sonas = [] for row in database_rows: guild = self.bot.get_guild( row['guild_id']) or await self.bot.fetch_guild(row['guild_id']) # user_sona_information[guild].append(row) # Add to the all sona list menu_data = dict(row) menu_data.update({"guild_name": guild.name}) all_user_sonas.append(menu_data) # Let's add our other servers via their APIs for api_data in self.OTHER_FURRY_GUILD_DATA[::-1]: # Format data url = api_data['url'] params = { i: o.format(user=ctx.author, guild=ctx.guild, bot=self.bot) for i, o in api_data.get('params', dict()).copy().items() } headers = { i: o.format(user=ctx.author, guild=ctx.guild, bot=self.bot) for i, o in api_data.get('headers', dict()).copy().items() } # Run request async with self.bot.session.get(url, params=params, headers=headers) as r: try: grabbed_sona_data = await r.json() except Exception: grabbed_sona_data = {'data': []} # Add to lists if grabbed_sona_data['data']: guild_id = api_data['guild_id'] guild_name = api_data['name'] # Add to the all sona list for sona in grabbed_sona_data['data']: menu_data = sona.copy() menu_data.update({ "guild_name": guild_name, "guild_id": guild_id }) all_user_sonas.append(menu_data) # Filter the list all_user_sonas = [ i for i in all_user_sonas if i['guild_id'] != ctx.guild.id ] if not self.bot.guild_settings[ctx.guild.id]["nsfw_is_allowed"]: all_user_sonas = [i for i in all_user_sonas if i['nsfw'] is False] if not all_user_sonas: self.currently_setting_sonas.remove(ctx.author.id) return await ctx.send( "You have no sonas available to import from other servers.") # Send it off to the user pages = menus.MenuPages( source=FursonaPageSource(all_user_sonas, per_page=1)) await pages.start(ctx, channel=ctx.author, wait=True) # Ask if the user wants to import the sona they stopped on sona_data = pages.raw_sona_data ask_import_message = await ctx.author.send( f"Do you want to import your sona from **{sona_data['guild_name']}**?" ) await ask_import_message.add_reaction(self.CHECK_MARK_EMOJI) await ask_import_message.add_reaction(self.CROSS_MARK_EMOJI) try: check = lambda r, u: r.message.id == ask_import_message.id and u.id == ctx.author.id reaction, _ = await self.bot.wait_for("reaction_add", check=check, timeout=120) except asyncio.TimeoutError: self.currently_setting_sonas.remove(ctx.author.id) return await ctx.author.send("Timed out asking about sona import.") # Import data self.currently_setting_sonas.remove(ctx.author.id) emoji = str(reaction.emoji) if emoji == self.CROSS_MARK_EMOJI: return await ctx.author.send( "Alright, cancelled importing your sona.") command = self.bot.get_command("setsonabyjson") ctx.information = sona_data return await command.invoke(ctx)
async def setsona(self, ctx: utils.Context): """Stores your fursona information in the bot""" # See if the user already has a fursona stored async with self.bot.database() as db: rows = await db( "SELECT * FROM fursonas WHERE guild_id=$1 AND user_id=$2", ctx.guild.id, ctx.author.id) current_sona_names = [row['name'].lower() for row in rows] ctx.current_sona_names = current_sona_names # See if they're at the limit try: sona_limit = max(o for i, o in self.bot.guild_settings[ ctx.guild.id]['role_sona_count'].items() if int(i) in ctx.author._roles) except ValueError: sona_limit = 1 if len(current_sona_names) >= sona_limit: return await ctx.send( "You're already at the sona limit - you have to delete one to be able to set another." ) # See if they're setting one up already if ctx.author.id in self.currently_setting_sonas: return await ctx.send( "You're already setting up a sona! Please finish that one off first!" ) # Try and send them an initial DM user = ctx.author start_message = f"Now taking you through setting up your sona on **{ctx.guild.name}**!\n" if not self.bot.guild_settings[ctx.guild.id]["nsfw_is_allowed"]: start_message += f"NSFW fursonas are not allowed for **{ctx.guild.name}** and will be automatically declined.\n" try: await user.send(start_message.strip()) except discord.Forbidden: return await ctx.send( "I couldn't send you a DM! Please open your DMs for this server and try again." ) self.currently_setting_sonas.add(user.id) await ctx.send("Sent you a DM!") # Ask about name name_message = await self.send_verification_message( user, "What is the name of your sona?") if name_message is None: return self.currently_setting_sonas.remove(user.id) if name_message.content.lower() in current_sona_names: self.currently_setting_sonas.remove(user.id) return await user.send( f"You already have a sona with the name `{name_message.content}`. Please start your setup again and provide a different name." ) # Ask about gender gender_message = await self.send_verification_message( user, "What's your sona's gender?") if gender_message is None: return self.currently_setting_sonas.remove(user.id) # Ask about age age_message = await self.send_verification_message( user, "How old is your sona?") if age_message is None: return self.currently_setting_sonas.remove(user.id) # Ask about species species_message = await self.send_verification_message( user, "What species is your sona?") if species_message is None: return self.currently_setting_sonas.remove(user.id) # Ask about orientation orientation_message = await self.send_verification_message( user, "What's your sona's orientation?") if orientation_message is None: return self.currently_setting_sonas.remove(user.id) # Ask about height height_message = await self.send_verification_message( user, "How tall is your sona?") if height_message is None: return self.currently_setting_sonas.remove(user.id) # Ask about weight weight_message = await self.send_verification_message( user, "What's the weight of your sona?") if weight_message is None: return self.currently_setting_sonas.remove(user.id) # Ask about bio bio_message = await self.send_verification_message( user, "What's the bio of your sona?", max_length=1000) if bio_message is None: return self.currently_setting_sonas.remove(user.id) # Ask about image def check(m) -> bool: return all([ isinstance(m.channel, discord.DMChannel), m.author.id == user.id, any([ m.content.lower() == "no", self.get_image_from_message(m) ]), ]) image_message = await self.send_verification_message( user, "Do you have an image for your sona? Please post it if you have one (as a link or an attachment), or say `no` to continue without.", check=check) if image_message is None: return self.currently_setting_sonas.remove(user.id) # Ask about NSFW if self.bot.guild_settings[ctx.guild.id]["nsfw_is_allowed"]: check = lambda m: isinstance( m.channel, discord.DMChannel ) and m.author.id == user.id and m.content.lower( ) in ["yes", "no"] nsfw_message = await self.send_verification_message( user, "Is your sona NSFW? Please either say `yes` or `no`.", check=check) if nsfw_message is None: return self.currently_setting_sonas.remove(user.id) else: nsfw_content = nsfw_message.content.lower() else: nsfw_content = "no" # Format that into data image_content = None if image_message.content.lower( ) == "no" else self.get_image_from_message(image_message) information = { 'name': name_message.content, 'gender': gender_message.content, 'age': age_message.content, 'species': species_message.content, 'orientation': orientation_message.content, 'height': height_message.content, 'weight': weight_message.content, 'bio': bio_message.content, 'image': image_content, 'nsfw': nsfw_content == "yes", } self.currently_setting_sonas.remove(user.id) ctx.information = information if information['nsfw'] and not self.bot.guild_settings[ ctx.guild.id]["nsfw_is_allowed"]: return await user.send( "Your fursona has been automatically declined as it is NSFW") await self.bot.get_command("setsonabyjson").invoke(ctx)