async def post_reaction(self, ctx: neko.Context, *, react_name=''): """ Posts a reaction. Run without any commands to see a list of reactions. Run `mewd` to destroy the calling message. """ with ctx.typing(): react_name = react_name.lower() # If the react is there, then send it! if react_name and react_name in self.images: try: if ctx.invoked_with == 'mewd': await ctx.message.delete() file_name = random.choice(self.images[react_name]) await ctx.send(file=discord.File(file_name)) except discord.Forbidden: ctx.command.reset_cooldown(ctx) except FileNotFoundError: ctx.command.reset_cooldown(ctx) traceback.print_exc() raise neko.NekoCommandError( 'Something broke and the dev ' 'was shot. Please try again later ^w^') # Otherwise, if the react doesn't exist, or wasn't specified, then # list the reacts available. else: book = neko.PaginatedBook(title='Available reactions', ctx=ctx) book.add_line(', '.join( map(lambda n: f'`{n}`', sorted(self.images)))) await book.send() # Reset the cool down. ctx.command.reset_cooldown(ctx)
async def list_commands(self, ctx): """ Lists loaded commands and aliases. """ book = neko.PaginatedBook( ctx=ctx, title='Loaded commands', max_lines=15, max_size=500 ) command_lines = [] for command in sorted({*ctx.bot.walk_commands()}, key=lambda c: c.qualified_name): line = f'**{command.qualified_name}** ' if command.aliases: line += f'\n\t({", ".join(command.aliases)}) ' if not command.enabled: line += '\n\tdisabled ' cn = command.cog_name if cn: line += f'\n\tcog: `{cn}` ' line += f'\n\tmodule: `{command.module}`' command_lines.append(line) book.add_lines(command_lines) await book.send()
async def tag_list(self, ctx): """ This lists bot local and global tags. """ book = neko.PaginatedBook(ctx=ctx, title='Tags', max_lines=15) async with ctx.typing(): await self._add_tag_list_to_pag(ctx, book) await book.send()
async def group(self, ctx): """ A command group holding a collection of commands for querying the status of various online services. """ book = neko.PaginatedBook(title='Available sub-commands', ctx=ctx) for command in sorted(self.group.commands, key=lambda c: c.name): book.add_line(f'**{command.name}** - {command.brief}') await book.send()
async def command_grp(self, ctx): """ Run without any arguments to show a list of available commands. """ book = neko.PaginatedBook(title='Available commands', ctx=ctx) for command in self.command_grp.commands: book.add_line(command.name) await book.send()
async def git_pull(self, ctx): """ Attempts to stash any local changes, and then git-pull from the remote. """ def _sp_call(): return subprocess.check_output( './update', stderr=subprocess.STDOUT, universal_newlines=True) result = await ctx.bot.do_job_in_pool(_sp_call) book = neko.PaginatedBook(title='>>>UPDATE<<<', ctx=ctx) book.add_lines(result) await book.send()
async def __get_tb(ctx): book = neko.PaginatedBook( title=f'Last traceback ({ctx.bot.last_error.date})', ctx=ctx, prefix='```python', suffix='```') if ctx.bot.last_error.value: book.add_line( ''.join( traceback.format_exception( ctx.bot.last_error.type, ctx.bot.last_error.value, ctx.bot.last_error.traceback))) else: book.add_lines('Nothing has broken \N{THINKING FACE}') await book.send()
async def list_cogs(self, ctx): """ Lists loaded cogs, along with their base-types, and the file they are defined in. """ book = neko.PaginatedBook( ctx=ctx, title='Loaded cogs', max_lines=15, max_size=800 ) for name, _cog in sorted(ctx.bot.cogs.items(), key=lambda k: k[0]): try: file = os.path.relpath(inspect.getsourcefile(_cog.__class__)) except BaseException: continue line = ( f'**{name}** in `{_cog.__module__}` ' f'(`{file}`).' 'With bases ' f'`{"`, `".join(b.__name__ for b in type(_cog).__bases__)}`.' ) if hasattr(_cog, 'events'): events = list(_cog.events()) if events: line += ( f'Defines events: ' f'`{"`, `".join(event.__name__ for event in events)}`' ) if hasattr(_cog, 'commands'): cmds = list(_cog.commands()) if cmds: line += ( f'Defines commands: ' f'`{"`, `".join(cmd.name for cmd in cmds)}`' ) book.add_line(line) await book.send()
async def host_health(self, ctx): """Gets the host health and resource utilisation.""" if os.name == 'nt': raise NotImplementedError( 'Here\'s a quarter, kid. Go get yourself a real ' 'operating system.' ) up_fut = asyncio.create_subprocess_exec( 'uptime', stdout=asyncio.subprocess.PIPE, encoding='ascii' ) # Gets the username user = getpass.getuser() ps_fut = asyncio.create_subprocess_exec( 'ps', '-U', user, '-f', '-o', 'pid,comm,%cpu,%mem,cputime,start', stdout=asyncio.subprocess.PIPE, encoding='ascii' ) up_res = await up_fut ps_res = await ps_fut up_stdout = [await up_res.stdout.read()] ps_stdout = [await ps_res.stdout.read()] up_out = b''.join(up_stdout).decode('ascii') ps_out = b''.join(ps_stdout).decode('ascii') book = neko.PaginatedBook( title=up_out, ctx=ctx, prefix='```', suffix='```' ) book.add_lines(ps_out) await book.send()
async def list_extensions(self, ctx): """ Lists loaded extensions. These are modules imported that contain a setup() function, and often consist of groups of commands, listeners or cogs. """ book = neko.PaginatedBook( ctx=ctx, title='Loaded extensions', max_lines=15, max_size=800 ) for name, module in sorted(ctx.bot.extensions.items(), key=lambda e: e[0]): file = os.path.relpath(inspect.getfile(module)) line = f'**{name}** `({file})`' if hasattr(module, '__all__'): line += 'Exposed via `__all__`: ' line += ', '.join( sorted( f'`{imp}`' for imp in getattr(module, '__all__') ) ) line += '. Exposes: ' line += ', '.join( sorted( f'`{member[0]}`' for member in inspect.getmembers(module) if not member[0].startswith('_'), ) ) book.add_line(line + '.') await book.send()
async def tag_my(self, ctx): """Shows tags you own globally and in this guild.""" async with ctx.bot.postgres_pool.acquire() as conn: async with ctx.typing(): results = await conn.fetch( ''' SELECT name, is_nsfw, guild FROM nekozilla.tags WHERE author = ($1) AND (guild IS NULL OR guild = ($2)) ORDER BY name, created; ''', ctx.author.id, ctx.guild.id) book = neko.PaginatedBook(ctx=ctx, title='My Tags', max_lines=10) for result in results: name = f'`{result["name"]}`' if result['is_nsfw']: name = f'_{name}_' name += ' in this guild' if result['guild'] else ' globally' book.add_line(name) await book.send()
async def tag_group(self, ctx: neko.Context, tag_name=None, *args): """ Displays the tag if it can be found. The local tags are searched first, and then the global tags. If we start the tag with an "!", then we try the global tags list first instead. **22nd Jan 2018**: I have added a few meta-commands into the mix. From now on, the following can be added into a tag, and it will be resolved when the tag is retrieved: ${args} -> any text you put after the tag name.\r ${channel} -> the channel name.\r ${channel_mention} -> the channel name, but as a mention.\r ${channel_id} -> the channel snowflake ID.\r ${author} -> the display name of whoever invoked the tag.\r ${author_mention} -> mentions whoever invoked the tag.\r ${author_discriminator} -> shows the discriminator of whoever invoked the tag.\r ${author_username} -> shows the username of whoever invoked the tag. ${author_id} -> shows the snowflake ID for the user who invoked the tag. ${guild} -> shows the name of the guild (server) the tag is called on. ${guild_id} -> shows the ID of the guild (server) the tag is called on. """ if tag_name is None: book = neko.PaginatedBook(ctx=ctx, title='Tags', max_lines=15) desc = f'Run {ctx.prefix}help tag <command> for more info.\n\n' page = neko.Page(title='Tag commands') cmds = {*self.tag_group.walk_commands()} for cmd in copy.copy(cmds): # Remove any commands we cannot run. if not await cmd.can_run(ctx) or cmd.hidden: cmds.remove(cmd) # Generate strings. cmds = { '**' + ' '.join(cmd.qualified_name.split(' ')[1:]) + '** - ' + cmd.brief for cmd in cmds } for line in sorted(cmds): desc += f'{line}\n' desc += '\nThe following pages will list the available tags.' page.description = desc book += page async with ctx.typing(): await self._add_tag_list_to_pag(ctx, book) await book.send() return if tag_name.startswith('!'): tag_name = tag_name[1:] local_first = True else: local_first = False async with self.bot.postgres_pool.acquire() as conn: with ctx.channel.typing(): results = await conn.fetch( ''' SELECT content, file_name, b64data FROM nekozilla.tags LEFT OUTER JOIN nekozilla.tags_attach ON pk = tag_pk WHERE name = ($1) AND (guild = ($2) OR guild IS NULL) AND -- This will be TRUE OR FALSE -> TRUE if chan is NSFW -- and FALSE OR FALSE -> FALSE if chan is NOT NSFW. is_nsfw = (($3) OR FALSE) ORDER BY guild NULLS LAST; ''', tag_name, ctx.guild.id, ctx.channel.nsfw) if not results: raise neko.NekoCommandError('No tag found with that name.') else: first = results.pop(0 if local_first else -1) content = first['content'] attachment_name = first['file_name'] attachment_data = first['b64data'] if attachment_name is not None and attachment_data is not None: # Decode attachment data. attachment_data = base64.b64decode(attachment_data) # We allow a few dynamic bits and pieces. # TODO: document this. replacements = { ('${args}', ' '.join(str(arg) for arg in args)), ('${channel}', str(ctx.channel.name)), ('${channel_mention}', f'<#{ctx.channel.id}>'), ('${channel_id}', str(ctx.channel.id)), ('${author}', str(ctx.author.display_name)), ('${author_mention}', str(ctx.author.mention)), ('${author_discriminator}', str(ctx.author.discriminator)), ('${author_username}', str(ctx.author.name)), ('${author_id}', str(ctx.author.id)), ('${guild}', str(ctx.guild.name)), ('${guild_id}', str(ctx.guild.id)) } for replacement in replacements: content = content.replace(*replacement) if attachment_name is not None and attachment_data is not None: with io.BytesIO( initial_bytes=attachment_data) as att_bytes: att_bytes.seek(0) file = discord.File(att_bytes, filename=attachment_name) await ctx.send(content, file=file) else: await ctx.send(content)