async def get_category_and_embed(ctx): current_cat: DMCategory = await DMCategory.from_ctx(ctx) embed = create_default_embed(ctx) if current_cat is None: embed.title = f'{ctx.author.display_name} does not have a DM Category!' embed.description = f'Create a DM Category with `{ctx.prefix}dm setup`' return current_cat, embed, (current_cat is not None)
async def change_prefix(self, ctx, to_change: str = None): """ Changes the prefix for the current guild Can only be ran in a guild. If no prefix is specified, will show the current prefix. """ embed = create_default_embed(ctx) guild_id = str(ctx.guild.id) if to_change is None: if guild_id in self.bot.prefixes: prefix = self.bot.prefixes.get(guild_id, self.bot.prefix) else: dbsearch = await self.bot.mdb['prefixes'].find_one({'guild_id': guild_id}) if dbsearch is not None: prefix = dbsearch.get('prefix', self.bot.prefix) else: prefix = self.bot.prefix self.bot.prefixes[guild_id] = prefix embed.title = f'Prefix for {ctx.guild.name}' embed.description = f'Current Prefix: `{prefix}`' return await ctx.send(embed=embed) else: await ctx.bot.mdb['prefixes'].update_one({'guild_id': guild_id}, {'$set': {'prefix': to_change}}, upsert=True) ctx.bot.prefixes[guild_id] = to_change embed.title = f'Prefix updated for {ctx.guild.name}!' embed.description = f'Server prefix updated to `{to_change}`' return await ctx.send(embed=embed)
async def new_sheet(self, ctx, *, content: str): """ Adds a sheet to be approved """ embed = create_default_embed(ctx) embed.title = f'Sheet Approval - {ctx.author.display_name}' embed.description = content if '(url)' in content: return await ctx.author.send( 'You must include your *actual* sheet URL in the command, not `(url)`' ) # If not character-submission or FrogBot dev if (ctx.channel.id != self.bot.personal_server['sheet_channel'] ) and not (ctx.guild.id == 755202524859859004): return await ctx.send( 'This channel is not valid for submitting sheets.') msg = await ctx.send(embed=embed) new_sheet = ToBeApproved(message_id=msg.id, approvals=[], channel_id=ctx.channel.id, owner_id=ctx.author.id) await self.bot.mdb['to_approve'].insert_one(new_sheet.to_dict())
async def debug(self, ctx): """ Debugging commands for FrogBot """ embed = create_default_embed(ctx) embed.title = 'FrogBot Debug' # -- Calculate Values -- proc = psutil.Process(os.getpid()) cpu = psutil.cpu_percent() mem = psutil.virtual_memory() mem_used = proc.memory_full_info().uss if self._command_count is None: self._command_count = len([ command for cog in self.bot.cogs for command in self.bot.get_cog(cog).walk_commands() ]) command_count = self._command_count # -- Add fields --- embed.add_field(name='Memory Usage', value=f'{round((mem_used / 1000000), 2)} ' f'/ {round((mem.total / 1000000), 2)} MB ' f'({round(100 * (mem_used / mem.total), 2)}%)') embed.add_field(name='CPU Usage', value=f'{round(cpu, 2)}%') embed.add_field(name='Commands', value=f'{command_count} total commands loaded.') await ctx.send(embed=embed)
async def server_info(self, ctx): """ Displays information about the current server. """ embed = create_default_embed(ctx) guild = ctx.guild embed.title = f'{guild.name} - Server Information' general_info = f'**ID:** {guild.id}\n' \ f'**Owner:** {guild.owner.mention}\n' \ f'Created: {guild.created_at.strftime(DATE_FORMAT)}' embed.add_field(name='General Info', value=general_info, inline=False) emoji_x = 0 emojis = [] for emoji in guild.emojis: emoji_x += 1 if emoji_x >= 10: break emojis.append(emoji) emoji_info = f'{len(guild.emojis)} emoji{"s" if len(guild.emojis) != 1 else ""}\n' \ f'{",".join([str(e) for e in emojis])} {"..." if emoji_x >= 10 else ""}' embed.add_field(name='Emojis', value=emoji_info, inline=False) bots = [member for member in guild.members if member.bot] member_stats = f'{guild.member_count - len(bots)} members ({len(bots)} bots)' embed.add_field(name='Member Info', value=member_stats) channels = f'{len([c for c in guild.categories])} categories, ' \ f'{len([c for c in guild.channels if isinstance(c, discord.TextChannel)])} text channels, ' \ f'{len([c for c in guild.channels if isinstance(c, discord.VoiceChannel)])} voice channels.' embed.add_field(name='Channel Info', value=channels) embed.set_thumbnail(url=str(guild.icon_url)) return await ctx.send(embed=embed, allowed_mentions=None)
async def format_page(self, menu, entries): offset = menu.current_page * self.per_page embed = create_default_embed(self.context) message = '\n'.join([f':white_small_square: `{cc.name}`' for _, cc in enumerate(entries, start=offset)]) if message == '': message = 'No custom commands found for this server' embed.description = '**Current Custom Commands for this Server**:\n' + message return embed
async def send_command_help(self, command): to_send = self.get_destination() embed = create_default_embed(self.context) embed.title = f'FrogBot Help - `{self.get_command_signature(command).strip()}`' embed.description = command.help or 'No help specified.' embed.set_footer(text=f'An underlined command has subcommands.\n' f'See {self.clean_prefix}help <command name> for more details ' f'on individual commands') await to_send.send(embed=embed)
async def source(self, ctx): """ Returns the link to the source code of the bot. """ embed = create_default_embed(ctx) embed.title = 'FrogBot Source' embed.description = '[Click here for the Source Code.](https://github.com/1drturtle/FrogBot)' embed.set_thumbnail(url=str(self.bot.user.avatar_url)) await ctx.send(embed=embed)
async def avatar(self, ctx, who: discord.Member = None): """ Gives you the avatar of whoever you specify, or yourself if you don't specify anyone. """ embed = create_default_embed(ctx) if not who: who = ctx.author embed.title = f'Avatar for {who.display_name}' embed.set_image(url=str(who.avatar_url_as(format='png'))) return await ctx.send(embed=embed)
async def format_page(self, menu, entries): offset = menu.current_page * self.per_page embed = create_default_embed(self.context) embed.title = self.embed_title embed.description = self.embed_desc embed.set_footer(text=self.embed_footer) message = '\n'.join([command for _, command in enumerate(entries, start=offset)]) message = message + f'\n\nPage {menu.current_page+1}/{self.get_max_pages()}' embed.add_field(name='Commands', value=message) return embed
async def format_page(self, menu, entries): offset = menu.current_page * self.per_page embed = create_default_embed(self.context) embed.title = self.embed_title embed.description = self.embed_desc # Entries = List[tuple(Cog, Commands)] for _, item in enumerate(entries, start=offset): command_list = generate_command_names(item[1], short_doc=True) output = '\n'.join(command_list) embed.add_field(name=item[0].qualified_name, value=output, inline=False) embed.set_footer(text=f'Page {menu.current_page+1}/{self.get_max_pages()}\n'+self.embed_footer) return embed
async def send_invite(self, ctx): """ Sends a link to invite FrogBot """ embed = create_default_embed(ctx) embed.title = 'FrogBot Invite Link' embed.description = '[Click here to invite FrogBot to your server!]' \ '(https://discord.com/api/oauth2/authorize' \ '?client_id=717467616700006482' \ '&permissions=470117462' \ '&scope=bot)' await ctx.send(embed=embed)
async def raw_message(self, ctx, message_id: int): """ Returns the escaped markdown for a message. The message must be in the same channel as this command. """ embed = create_default_embed(ctx) try: message = await ctx.channel.fetch_message(message_id) except discord.NotFound: await ctx.send(f'Could not find the message with ID `{message_id}`' ) embed.title = f'Escaped Markdown for Message with ID `{message_id}`' embed.description = discord.utils.escape_markdown(message.content) await ctx.send(embed=embed)
async def emoji_info(self, ctx, emoji_to_parse: discord.Emoji): """ Returns custom emoji information. """ embed = create_default_embed(ctx) embed.title = f'Emoji - :{emoji_to_parse.name}:' embed.add_field(name='Guild', value=emoji_to_parse.guild.name) embed.add_field(name='ID', value=emoji_to_parse.guild.id) embed.set_image(url=str(emoji_to_parse.url)) await ctx.send(embed=embed)
async def send_cog_help(self, cog): to_send = self.get_destination() embed = create_default_embed(self.context) title = f'FrogBot Help - `{cog.qualified_name}`'.strip() footer = f'An underlined command has sub-commands.\n' \ f'See {self.clean_prefix}help <command name> for more details on individual commands.' command_list = await self.filter_commands(cog.get_commands(), sort=True) embed.description = cog.description or 'No description specified.' out = generate_command_names(command_list) source = HelpCogMenu(data=out, ctx=self.context, embed_title=title, embed_footer=footer, embed_desc=cog.description or 'No description specified.') command_menu = menus.MenuPages(source=source, clear_reactions_after=True) await command_menu.start(self.context, channel=to_send)
async def uptime(self, ctx): """ Displays the current uptime of the bot. """ embed = create_default_embed(ctx) embed.title = 'Poddo Uptime' bot_up = time_to_readable(self.bot.uptime) embed.add_field(name='Bot Uptime', value=f'{bot_up}') if ctx.bot.is_ready(): embed.add_field( name='Ready Uptime', value= f'{time_to_readable(datetime.utcnow() - self.bot.ready_time)}') return await ctx.send(embed=embed)
async def show_perms(self, ctx, who: discord.Member = None): """ Shows the permissions for the user specified in the current channel. Will show permissions for yourself if nobody is specified. """ if who is None: who = ctx.author embed = create_default_embed(ctx) embed.title = f'Permissions for {who.display_name}' yes, no = "\U00002705", "\U0001f6ab" out = '' for perm, value in who.permissions_in(ctx.channel): out += f'{yes if value else no} | {perm.replace("_", " ").title()}\n' embed.description = out await ctx.send(embed=embed)
async def hexcolor(self, ctx, *, color: str): """ Takes a color name and converts it to a hex code. For possible color options, see [this link](https://gist.github.com/Soheab/d9cf3f40e34037cfa544f464fc7d919e) """ embed = create_default_embed(ctx) color_converter = commands.ColourConverter() try: color: discord.Colour = await color_converter.convert(ctx, color) except commands.BadArgument: return await ctx.send( 'You have provided an invalid color. See the link in the help page for a list of ' 'possible colors.') embed.title = str(hex(color.value)) embed.colour = color await ctx.send(embed=embed)
async def info(self, ctx): """ Displays some information about the bot. """ embed = create_default_embed(ctx) embed.title = 'FrogBot Information' embed.description = 'Bot built by Dr Turtle#1771 made for D&D and personal servers!' members = sum([guild.member_count for guild in self.bot.guilds]) embed.add_field(name='Guilds', value=f'{len(self.bot.guilds)}') embed.add_field(name='Members', value=f'{members}') embed.add_field( name='Concerns', value= 'Do you have any concerns/privacy issues/security issues?\nContact me by' ' [joining the support server!](https://discord.gg/nNutJ8PyFu)', inline=False) embed.url = 'https://github.com/1drturtle/FrogBot' await ctx.send(embed=embed)
async def dm_setup(self, ctx): """ Creates a new DM Category. Will also create one channel for you, this is supposed to be your hub channel, but you can use it for whatever. """ try: new_category = await DMCategory.new(self.bot, ctx.guild, ctx.author) except CategoryExists: return await ctx.send( f'You already have a DM Category in this server. If this is an error, ' f'run `{ctx.prefix}dm delete` and then run this command again.' ) embed = create_default_embed(ctx) embed.title = f'{ctx.author.display_name} creates their DM Category!' embed.description = f'Your DM Category has been created.\nThe default channel is ' \ f'<#{new_category.channels[0].channel.id}>' return await ctx.send(embed=embed)
async def prompt(self, title: str, description: str, timeout=30, sendable=None) -> str: """ Prompts the Context author for a question, and returns the result. Returns None if they do not respond. :param str title: The title of the prompt :param str description: The description of the prompt :param int timeout: :param discord.Messagable sendable: Where to send the message. (Optional, defaults to context channel.) :return: The response, or None :rtype: str or None """ embed = create_default_embed(self) embed.title = title or 'Question Prompt' if not description: raise Exception('Missing required argument Description on prompt.') embed.description = description if sendable: question = await sendable.send(embed=embed) else: question = await self.channel.send(embed=embed) def check(msg: discord.Message): return msg.author.id == self.author.id and msg.channel.id == self.channel.id try: result = await self.bot.wait_for('message', check=check, timeout=timeout) except asyncio.TimeoutError: return None content = result.content await try_delete(question) await try_delete(result) return content or None
async def personal_server(self, ctx): """ Base command for personal server commands. Displays information about currently set personal server. """ if self.bot.personal_server['server_id'] is None: return await ctx.send('Personal Server not set, no information available.') personal_server = self.bot.get_guild(self.bot.personal_server['server_id']) sheet_channel = channel_id_to_link(personal_server.get_channel(self.bot.personal_server['sheet_channel'])) \ if self.bot.personal_server['sheet_channel'] is not None else 'Not set.' general_channel = channel_id_to_link(personal_server.get_channel(self.bot.personal_server['general_channel'])) \ if self.bot.personal_server['general_channel'] is not None else 'Not set.' embed = create_default_embed(ctx) embed.title = 'FrogBot Personal Server Information' embed.add_field(name='Server Info', value=f'Server ID: {personal_server.id}\n' f'Server Name: {personal_server.name}') embed.add_field(name='Channel Info', value=f'Sheet Channel: {sheet_channel}\n' f'General Channel: {general_channel}') await ctx.send(embed=embed)
async def remove_sheets(self, ctx): """ Removes deleted sheets from database. Run every once and a while. """ db = self.bot.mdb['to_approve'] embed = create_default_embed(ctx) embed.title = f'Pruning Old Sheets from Database.' all_sheets = await db.find().to_list(None) count = 0 for sheet in all_sheets: sheet.pop('_id') sheet = ToBeApproved.from_dict(sheet) channel = ctx.guild.get_channel(sheet.channel_id) if channel is None: continue try: await channel.fetch_message(sheet.message_id) except discord.NotFound: count += 1 await db.delete_one({'message_id': sheet.message_id}) embed.description = f'Pruned {count} Sheet{"s" if count != 1 else ""} from the DB.' await ctx.send(embed=embed)
async def member_info(self, ctx, who: discord.Member = None): """ Shows information about a member in this server. """ if who is None: who = ctx.author embed = create_default_embed(ctx) badges = '' if who.id == self.bot.owner: badges += f'{BADGE_EMOJIS["bot_owner"]} ' if who.id == ctx.guild.owner.id: badges += f'{BADGE_EMOJIS["server_owner"]} ' support_server = self.bot.get_guild(SUPPORT_SERVER_ID) if support_server is not None and member_in_guild(who.id, support_server): badges += f'{BADGE_EMOJIS["support_server"]}' embed.title = f'Member Information - {who.display_name} {badges}' # -- Basics -- embed.add_field(name='Name', value=f'{who.mention}') embed.add_field(name='Username', value=f'{who.name}#{who.discriminator}') embed.add_field(name='ID', value=f'{who.id}') # -- Roles -- embed.add_field(name='Roles', value=f'{len(who.roles)} role(s)') embed.add_field(name='Top Role', value=f'{who.top_role.mention if who.top_role.name != "@everyone" else "Default Role"}' f' (Position {who.top_role.position}/{ctx.guild.roles[-1].position})') embed.add_field(name='Is Server Owner', value=f'{"True" if ctx.guild.owner.id == who.id else "False"}') # -- Date Information -- embed.add_field(name='Account Created At', value=who.created_at.strftime(DATE_FORMAT)) embed.add_field(name='Joined Server At', value=who.joined_at.strftime(DATE_FORMAT)) embed.set_thumbnail(url=who.avatar_url) await ctx.send(embed=embed)
async def create_quest_role(self, ctx): """ Creates a Role for Quests The role created will have the default permissions that \@everyone has at the time of creation. """ author = ctx.author channel = ctx.channel user_mention = discord.AllowedMentions(users=[ctx.author]) color_converter = commands.ColorConverter() def chk(m): return m.author == author and m.channel == channel async def prompt(message: discord.Message, ebd: discord.Embed, content: str, mentions: discord.AllowedMentions = None): if content: ebd.description = content await message.edit(embed=ebd, allowed_mentions=mentions) result = await self.bot.wait_for('message', check=chk, timeout=60) if result is None: return result content = result.content await try_delete(result) return content, ebd def check_stop(content): if content.lower() in ['stop', 'cancel', 'quit', 'exit']: return True else: return False async def stop(question_msg): await try_delete(question_msg) await ctx.send('Operation Cancelled, stopping.', delete_after=10) embed = create_default_embed(ctx) embed.title = 'Quest Role Creation' question_msg = await ctx.send(embed=embed) role_name, embed = await prompt( question_msg, embed, f'{ctx.author.mention}, what would you like this role to be called?', mentions=user_mention) if check_stop(role_name): return await stop(question_msg) role_color, embed = await prompt( question_msg, embed, f'Role Name: `{role_name}`\nWhat color would you like this role to be?' ) if check_stop(role_color): return await stop(question_msg) try: color = await color_converter.convert(ctx=ctx, argument=role_color.lower()) except (commands.CommandError, commands.BadColourArgument): await try_delete(question_msg) return await ctx.send('Invalid Color provided, exiting.', delete_after=10) embed.color = color confirm_content, embed = await prompt( question_msg, embed, f'Role `{role_name}` with the color of this embed ' f'will be created. Please confirm.') if get_positivity(confirm_content): new_role = await ctx.guild.create_role( name=role_name, color=color, reason='Quest Role Creation') await ctx.send( f'Role {new_role.mention} created.', delete_after=10, allowed_mentions=discord.AllowedMentions(roles=[new_role])) return await try_delete(question_msg) else: return await stop(question_msg)
async def on_command_error(self, ctx, error): """The event triggered when an error is raised while invoking a command. Parameters ------------ ctx: utils.Context.PoddoContext The context used for command invocation. error: commands.CommandError The Exception raised. """ # This prevents any commands with local handlers being handled here in on_command_error. if hasattr(ctx.command, 'on_error'): return # This prevents any cogs with an overwritten cog_command_error being handled here. cog = ctx.cog if cog: if cog._get_overridden_method(cog.cog_command_error) is not None: return ignored = (commands.CommandNotFound, ) # Allows us to check for original exceptions raised and sent to CommandInvokeError. # If nothing is found. We keep the exception passed to on_command_error. error = getattr(error, 'original', error) # Anything in ignored will return and prevent anything happening. if isinstance(error, ignored): return # Set up the Error Embed embed = create_default_embed(ctx, colour=discord.Colour.red()) if isinstance(error, commands.DisabledCommand): embed.title = 'Command Disabled!' embed.description = f'{ctx.command.qualified_name} has been disabled.' await ctx.send(embed=embed) elif isinstance(error, commands.MissingAnyRole): roles = ', '.join(error.missing_roles) embed.title = 'Missing Roles!' embed.description = f'Error: You must have any of the following roles to run this command: {roles}' return await ctx.send(embed) elif isinstance(error, commands.EmojiNotFound): return await ctx.send( 'I could not find the emoji that you provided. Either I do not have access to it, ' 'or it is a default emoji.') elif isinstance(error, commands.CheckFailure): embed.title = 'Permission Error!' msg = str(error) or 'You are not allowed to run this command.' embed.description = f'Error: {msg}' return await ctx.send(embed=embed) elif isinstance(error, commands.MissingRequiredArgument): embed.title = 'Missing Argument!' embed.description = 'Error: ' + str( error ) or "An error has occurred. Please contact the developer." return await ctx.send(embed=embed) elif isinstance(error, commands.BadArgument) or isinstance( error, commands.BadUnionArgument): embed.title = 'Invalid Argument!' embed.description = 'Error: ' + str( error) or "Unknown Bad Argument" return await ctx.send(embed=embed) elif isinstance(error, commands.ArgumentParsingError) or isinstance( error, commands.TooManyArguments): embed.title = 'Invalid Argument(s)!' embed.description = 'Error: ' + str( error) or "Unknown Argument Parsing Error" return await ctx.send(embed=embed) elif isinstance(error, commands.CommandOnCooldown): embed.title = 'Command on Cooldown!' cooldown = pendulum.duration(seconds=int(error.retry_after)) embed.description = f'`{ctx.prefix}{ctx.command.qualified_name}`' \ f' is on cooldown for {cooldown.in_words()}' return await ctx.send(embed=embed) elif isinstance(error, discord.Forbidden): embed.title = 'Forbidden!' embed.description = 'Error: ' + str( error) or "Not allowed to perform this action." return await ctx.send(embed=embed) elif isinstance(error, commands.NoPrivateMessage): try: await ctx.author.send( f'{ctx.command} can not be used in Private Messages.') except discord.HTTPException: pass else: # All other Errors not returned come here. And we can just print the default TraceBack. self.log_error(error, context=ctx) embed.title = 'Unknown Error!' embed.description = 'An unknown error has occurred! A notification has been sent to the bot developer.' embed.add_field(name='Error Type', value=f'{type(error)}') log.error('Ignoring exception in command {}:'.format(ctx.command)) traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)
async def eval(self, ctx, *, body: str): """ Evaluates input """ embed = create_default_embed(ctx) env = { 'bot': self.bot, 'ctx': ctx, 'channel': ctx.channel, 'author': ctx.author, 'guild': ctx.guild, 'message': ctx.message, '_': self._last_result } env.update(globals()) if not body.startswith('```'): body = f'```py\n' \ f'{body}\n' \ f'```' body = self.cleanup_code(body) stdout = io.StringIO() to_compile = f'async def func():\n{textwrap.indent(body, " ")}' try: exec(to_compile, env) print(repr(env['func'])) except Exception as e: return await ctx.send( embed=self.embed_split(embed, f'{e.__class__.__name__}: {str(e)}', discord.Colour.red(), title='Eval Compile Error')) func = env['func'] try: with redirect_stdout(stdout): ret = await func() except Exception as e: value = stdout.getvalue() return await ctx.send( embed=self.embed_split(embed, f'{value}{traceback.format_exc()}', discord.Colour.red(), title='Error during Eval')) else: value = stdout.getvalue() embed.title = 'Eval Result' embed.colour = discord.Colour.green() desc = '' if ret is None: if value: desc = value else: self._last_result = ret desc = f'{value}{ret}' return await ctx.send(embed=self.embed_split( embed, desc, discord.Colour.green(), title='Eval Result'))