async def send_command_help(self, command): '''Help interface for the commands themselves (Overridden)''' ## Initial setup self.paginator = Paginator() clip_groups = self.context.bot.get_cog("Clips").clip_groups ## Is the help command a category? If so only dump the relv command_str = command.__str__() if (command.name in clip_groups): await self.send_clip_category_help(command) return # <signature> section signature = self.get_command_signature(command) self.paginator.add_line(signature, empty=True) # <long doc> section help_section = command.help if help_section: if (len(help_section) > self.paginator.max_size): for line in help_section.splitlines(): self.paginator.add_line(line) else: self.paginator.add_line(help_section, empty=True) self.paginator.close_page() await self.send_pages()
def __init__(self, ctx: Context, base_title: str, color=None): self.ctx = ctx self.title = base_title self.attrs = {} if color: self.attrs['color'] = color self.paginator = Paginator(prefix='', suffix='', max_size=2048)
async def show_off(self, ctx: CommandContext, name: str): paginator = Paginator(prefix="```py") for line in inspect.getsource( self.bot.get_command(name).callback).split('\n'): paginator.add_line(line.replace("`", "`\u200B")) for page in paginator.pages: await ctx.send(page)
async def source(self, ctx: Context, *, command_name: str) -> None: """Get the source code of a command.""" command = self.bot.get_command(command_name) if not command: await ctx.send(f"No command named `{command_name}` found.") return try: source_lines, _ = inspect.getsourcelines(command.callback) except (TypeError, OSError): await ctx.send( f"I was unable to retrieve the source for `{command_name}` for some reason." ) return source_lines = textwrap.dedent("".join(source_lines).replace( "```", f"`{ZWS}`{ZWS}`")).split("\n") paginator = Paginator( prefix="```py", suffix="```", max_size=2048, ) for line in source_lines: paginator.add_line(line) pages = menus.MenuPages( source=SauceSource( paginator.pages, per_page=1, ), clear_reactions_after=True, ) await pages.start(ctx)
def __init__(self, ctx, text, *, prefix='```', suffix='```', max_size=2000): paginator = CommandPaginator( prefix=prefix, suffix=suffix, max_size=max_size - 200) for line in text.split('\n'): paginator.add_line(line) super().__init__(ctx, entries=paginator.pages, per_page=1, show_entry_count=False)
async def reload(self, ctx: Context): """Reloads all cogs.""" # make a copy of the extension list, because we will be iterating over it and modifying it due to the # load and unload calls extensions = self.bot.extensions.copy() log = Paginator() # unload and load everything for extension_name, _module in extensions.items(): try: self.log.info('reloading "%s"', extension_name) self.bot.unload_extension(extension_name) self.bot.load_extension(extension_name) except Exception: self.log.exception('failed extension load (%s):', extension_name) log.add_line( f'\N{CROSS MARK} Failed to reload extension `{extension_name}`.' ) if not log.pages: await ctx.send('\N{OK HAND SIGN}') else: for page in log.pages: await ctx.send(page)
async def send_bot_help(self, mapping): '''The main bot help command (overridden)''' ## Initial setup self.paginator = Paginator() phrase_cog = self.context.bot.get_cog("Phrases") phrase_groups = None if (phrase_cog != None): phrase_groups = phrase_cog.phrase_groups self.dump_header_boilerplate() ## Dump the non-phrase commands self.dump_commands() ## Dump the base phrase commands if (phrase_groups != None): phrases_group = phrase_groups["phrases"] if (phrases_group): self.dump_phrase_group(phrases_group) ## Dump the names of the additional phrases. Don't print their commands because that's too much info. ## This is a help interface, not a CVS receipt self.dump_phrase_categories(phrase_groups) self.dump_footer_boilerplate(list(phrase_groups.keys())) await self.send_pages()
async def format(self): """Handles the actual behaviour involved with formatting. To change the behaviour, this method should be overridden. Returns -------- list A paginated output of the help command. """ self._paginator = Paginator() # we need a padding of ~80 or so description = self.command.description if not self.is_cog() else inspect.getdoc(self.command) if description: # <description> portion self._paginator.add_line(description, empty=True) if isinstance(self.command, Command): # <signature portion> signature = self.get_command_signature() self._paginator.add_line(signature, empty=True) # <long doc> section if self.command.help: self._paginator.add_line(self.command.help, empty=True) # end it here if it's just a regular command if not self.has_subcommands(): self._paginator.close_page() return self._paginator.pages max_width = self.max_name_size def category(tup): cog = tup[1].cog_name # we insert the zero width space there to give it approximate # last place sorting position. return cog.lower() + ':' if cog is not None else '\u200bcommands:' filtered = await self.filter_command_list() if self.is_bot(): data = sorted(filtered, key=category) for category, commands in itertools.groupby(data, key=category): # there simply is no prettier way of doing this. commands = sorted(commands) if len(commands) > 0: self._paginator.add_line(category) self._add_subcommands_to_page(max_width, commands) else: filtered = sorted(filtered) if filtered: self._paginator.add_line('commands:') self._add_subcommands_to_page(max_width, filtered) return self._paginator.pages
def __init__(self, **options): self.asciidoc_prefix = "```asciidoc" self.paginator = Paginator(prefix=self.asciidoc_prefix) self.commands_heading = "Commands\n--------" super().__init__(paginator=self.paginator, commands_heading=self.commands_heading, **options)
def __init__(self, text, *, prefix="```", suffix="```", max_size=2000): pages = CommandPaginator(prefix=prefix, suffix=suffix, max_size=max_size - 200) for line in text.split("\n"): pages.add_line(line) super().__init__(entries=pages, per_page=1)
async def roles(self, ctx: CommandContext): guild: Guild = ctx.guild paginator = Paginator() for role in guild.roles: paginator.add_line(role.name + ' ' + str(role.id)) for page in paginator.pages: await ctx.send(embed=Embed(color=Color.blurple(), description=page) )
async def format(self): """A modified copy of Discord.py rewrite's vanilla HelpFormatter.format().""" self._paginator = Paginator() # we need a padding of ~80 or so description = self.command.description if not self.is_cog( ) else inspect.getdoc(self.command) if description: # <description> portion self._paginator.add_line(description, empty=True) if isinstance(self.command, Command): # <signature portion> signature = self.get_command_signature() self._paginator.add_line(signature, empty=True) # <long doc> section if self.command.help: self._paginator.add_line(self.format_help(self.command.help), empty=True) # end it here if it's just a regular command if not self.has_subcommands(): self._paginator.close_page() return self._paginator.pages max_width = self.max_name_size def category(tup): cog = tup[1].cog_name # we insert the zero width space there to give it approximate # last place sorting position. return cog + ':' if cog is not None else '\u200bCommands:' filtered = await self.filter_command_list() if self.is_bot(): data = sorted(filtered, key=category) for category, commands in itertools.groupby(data, key=category): # there simply is no prettier way of doing this. commands = sorted(commands) if len(commands) > 0: self._paginator.add_line(category) self._add_subcommands_to_page(max_width, commands) else: filtered = sorted(filtered) if filtered: self._paginator.add_line('Commands:') self._add_subcommands_to_page(max_width, filtered) # add the ending note self._paginator.add_line() ending_note = self.get_ending_note() self._paginator.add_line(ending_note) return self._paginator.pages
async def ext_list(self, ctx: Context): """List all loaded extensions.""" paginator = Paginator(prefix='```\n') for name in sorted(self.bot.extensions): paginator.add_line(name) for page in paginator.pages: await ctx.send(page)
def __init__(self, entries: typing.List[str], *, per_page=None): self.initial_page = True if per_page is None: pages = CommandPaginator(prefix="", suffix="", max_size=1800) for line in entries: pages.add_line(line) super().__init__(pages.pages, per_page=1) else: super().__init__(entries, per_page=per_page)
def __init__(self, **options): self.sort_commands = options.pop('sort_commands', True) self.commands_heading = options.pop('commands_heading', "Commands") self.dm_help = options.pop('dm_help', False) self.dm_help_threshold = options.pop('dm_help_threshold', 1000) self.aliases_heading = options.pop('aliases_heading', "Aliases:") self.no_category = options.pop('no_category', '') self.paginator = options.pop('paginator', None) if self.paginator is None: self.paginator = Paginator(suffix=None, prefix=None) super().__init__(**options)
async def paginate(message: str, pag: disextc.Paginator = None) -> disextc.Paginator: """ Helper to use the Paginator. Given a line of text it will format it and return the paginator to add more lines. :param message -> str type with message to send :param pag -> Pagenator to add to, or none to create a new. :return -> Paginator containing line of text. """ if pag is None: pag = disextc.Paginator() pag.add_line(message) return pag
def run(): """This command runs the bot.""" paginator = Paginator(max_size=1336) bot = CrimsoBOT(help_command=PaginatedHelpCommand(paginator=paginator)) bot.load_extensions() bot.run(TOKEN)
async def raw(self, ctx: Context, *, message: Message, json: bool = False) -> None: """Shows information about the raw API response.""" # I *guess* it could be deleted right as the command is invoked but I felt like it wasn't worth handling # doing this extra request is also much easier than trying to convert everything back into a dictionary again raw_data = await ctx.bot.http.get_message(message.channel.id, message.id) paginator = Paginator() def add_content(title: str, content: str) -> None: paginator.add_line(f'== {title} ==\n') # replace backticks as it breaks out of code blocks. Spaces seemed to be the most reasonable solution. # we hope it's not close to 2000 paginator.add_line(content.replace('```', '`` `')) paginator.close_page() if message.content: add_content('Raw message', message.content) transformer = pprint.pformat if json else self.format_fields for field_name in ('embeds', 'attachments'): data = raw_data[field_name] if not data: continue total = len(data) for current, item in enumerate(data, start=1): title = f'Raw {field_name} ({current}/{total})' add_content(title, transformer(item)) for page in paginator.pages: await ctx.send(page)
def paginate_to_embeds( description: str, title: Optional[str] = None, max_size: int = 2000, prefix: Optional[str] = "", suffix: Optional[str] = "", color: Union[discord.Color, int, None] = None) -> List[Embed]: """ Facilitates sequential embed menus. Returned embeds share title, have description split at :max_length: and are added index/pages at footer to keep track of pages. Parameters ---------- description: :class:`str` String to be split at :max_length: per embed. title: :class:`str` Shared by all embeds max_size: :class:`int` Maximum amount of characters per embed. Discord's limit is 2000. prefix: :class:`str` Defaults to "" it will be appended at the start of the description of each embed. Useful for codeblocks (use triple back quotes). suffix: :class:`str` Same as :prefix: but at the end of the text. color: :class:`Union[discord.Color, int, None]` color to use for the embed. Accepts int (decimal or hex) or discord.Color/discord.Colour. Returns ------- The rendered list of embeds :class:`List[Embed]` """ embeds = [] to_list = description.split("\n") paginator = Paginator(prefix=prefix, suffix=suffix, max_size=max_size) for line in to_list: paginator.add_line(line) for i, page in enumerate(paginator.pages): embed = Embed(description=page).set_footer( text=f"page: {i + 1}/{len(paginator.pages)}") d = {'title': title, 'colour': color} for attr in d: if value := d[attr]: setattr(embed, attr, value) embeds.append(embed)
async def _dm_me_error(*, bot, cog, ctx, error, event_method): embed = discord.Embed( title=f"An error occurred: `{type(error).__qualname__}`", description=f'Supplied error message: `{error or "—"}`', colour=0xFF0000, ) trace = traceback.format_tb(error.__traceback__) trace = "".join(trace) should_pag = len(trace) > 1010 if not should_pag: trace = f"```\n{trace}\n```" embed.add_field(name="Traceback", value=trace, inline=False) if ctx: whom_info = ( f"{ctx.command.qualified_name} invoked as {ctx.invoked_with}\n" f"Invoked by: {ctx.author} (`{ctx.author.id}`)\n" f'{"Guild: " + str(ctx.guild) if ctx.guild else "in DMs"}\n' f"Channel: #{ctx.channel}\n" f"When: {ctx.message.created_at}" ) body = ctx.message.content body = body.replace("`", "’") if len(body) > 1000: body = f"{body[:997]}..." body = f"```\n{body}\n```" embed.add_field(name="Command info", value=whom_info) embed.add_field(name="Command body", value=body) if cog: embed.add_field(name="Cog", value=str(cog)) if event_method: embed.add_field(name="Event method", value=str(event_method)) owner = bot.get_user(bot.owner_id) await owner.send(embed=embed) if should_pag: p = Paginator() for line in trace.split("\n"): p.add_line(line) for page in p.pages: # Send the last 15 only. Prevents a hell of a lot # of spam if the bot hits a stack overflow. await owner.send(page[-15:])
async def send_initial_message(self, ctx, channel): query = '''SELECT name FROM tags ORDER BY name ''' res = await ctx.bot.db.fetch(query) if not res: await ctx.send('No tags found.') self.stop() paginator = Paginator(prefix=None, suffix=None, max_size=420) i = 1 for name in [row['name'] for row in res]: paginator.add_line(f'**{i}.** {name}') i += 1 self.pages = paginator.pages return await ctx.send(embed=self.embed)
async def guild_role(self, text: str, check=lambda role: True, list_ids=False) -> Role: async def converter(mes: Message): return discord.utils.get(self.guild.roles, id=int( mes.content.translate(digit_keeper))) if list_ids: guild: Guild = self.ctx.guild paginator = Paginator() for role in guild.roles: paginator.add_line(role.name + ' ' + str(role.id)) for page in paginator.pages: await self.ctx.send( embed=Embed(color=Color.blurple(), description=page)) return await self.by_converter(text, check=check, converter=converter)
async def code(self, ctx: Context) -> None: """Return stats about the bot's code.""" total = 0 file_amount = 0 list_of_files = [] for filepath, _, files in os.walk("bot"): for name in files: if name.endswith(".py"): file_lines = 0 file_amount += 1 with codecs.open( "./" + str(pathlib.PurePath(filepath, name)), "r", "utf-8") as file: for _, line in enumerate(file): if line.strip().startswith("#") or len( line.strip()) == 0: pass else: total += 1 file_lines += 1 final_path = filepath + os.path.sep + name list_of_files.append( final_path.split("." + os.path.sep)[-1] + f" : {file_lines} lines") paginator = Paginator(max_size=2048, ) for line in list_of_files: paginator.add_line(line) pages = menus.MenuPages( source=CodeInfoSource( f"{self.bot.user.name}'s structure", f"I am made of {total} lines of Python, spread across {file_amount} files !", paginator.pages, per_page=1, ), clear_reactions_after=True, ) await pages.start(ctx)
async def eval(self, ctx: Context, *, code: str): """Owner evaluation. With great power comes great responsibility.""" bot = self.bot # noqa: F841 eval_paginator = Paginator(prefix='```\n') try: out = str(eval(code)) if not out: await ctx.send('\u200b') return for line in itertools.zip_longest(*([iter(out)] * self.MAX_LINE_SIZE)): eval_paginator.add_line(''.join(c for c in line if c).replace( '```', '\\`\\`\\`')) except Exception as exception: eval_paginator.add_line(f"{type(exception).__name__}: {exception}") for page in eval_paginator.pages: await ctx.send(page)
def paginate_to_embeds( title: str, description: str, max_size: int = 2000, prefix: Optional[str] = "", suffix: Optional[str] = "", color: Union[discord.Color, int, None] = None) -> List[Embed]: """ Facilitates sequential embed menus. Returned embeds share title, have description split at :max_length: and are added index/pages at footer to keep track of pages. Args: title: Shared by all embeds description: String to be split at :max_length: per embed. max_size: Maximum amount of characters per embed. Discord's limit is 2000. prefix: Defaults to "" it will be appended at the start of the description of each embed. Useful for codeblocks (use triple back quotes). suffix: Same as :prefix: but at the end of the text. color: color to use for the embed. Accepts int (decimal or hex) or discord.Color/discord.Colour. Returns: List[Embed] """ embeds = [] to_list = description.split("\n") paginator = Paginator(prefix=prefix, suffix=suffix, max_size=max_size) for line in to_list: paginator.add_line(line) for i, page in enumerate(paginator.pages): embeds.append( Embed( title=title, description=page, color=color, ).set_footer(text=f"page: {i + 1}/{len(paginator.pages)}")) return embeds
async def list_users(ctx: CommandContext, *, role: Role): author: Member = ctx.author roles: List[Role] = author.roles role_ids: Set[int] = set([role.id for role in roles]) admin: bool = len(role_ids.intersection(config.admin_roles)) if not admin and not check_is_mentor(author, role): return await ctx.send( embed=Embed( title='Missing permissions', color=Color.red(), description='You are neither an admin nor a mentor for this role.')) paginator = Paginator(prefix='', suffix='') for user in get_members_with_role(role): paginator.add_line(f'{user} - {user.mention}') for page in paginator.pages: await ctx.send( embed=Embed( title=f'Users with the role @{role.name}', description=page)) if len(paginator.pages) == 0: await ctx.send( embed=Embed( title=f'Users with the role @{role.name}', description='No users found.'))
async def send_bot_help(self, mapping): '''The main bot help command (overridden)''' ## Initial setup self.paginator = Paginator() clip_groups = self.context.bot.get_cog("Clips").clip_groups self.dump_header_boilerplate() ## Dump the non-clip commands self.dump_commands() ## Dump the base clip commands clips_group = clip_groups["internet"] if (clips_group): self.dump_clip_group(clips_group) ## Dump the names of the additional clips. Don't print their commands because that's too much info. ## This is a help interface, not a CVS receipt self.dump_clip_categories(clip_groups) self.dump_footer_boilerplate(list(clip_groups.keys())) await self.send_pages()
async def paginate(ctx, i, start='', prefix='', kprefix='`', ksuffix='`\n', eprefix='```\n', ejoin=' ', esuffix='\n```', suffix='', end=''): paginator = Paginator(prefix=prefix, suffix=suffix) messages = [] i = copy.deepcopy(i) if start: paginator.add_line(start + ('' if type(i) is not dict else '\n')) if type(i) in (tuple, list, set): if not i: i = (' ') paginator.add_line(eprefix + f'{ejoin}'.join(sorted(i)) + esuffix) elif type(i) is dict: if not i: i = {' ': ' '} for k, e in sorted(i.items()): paginator.add_line(kprefix + k + ksuffix + eprefix + f'{ejoin}'.join(e) + esuffix) if end: paginator.add_line(end) for page in paginator.pages: messages.append(await ctx.send(page)) return messages
async def send_raw_content(self, ctx: Context, message: Message, json: bool = False) -> None: """ Send information about the raw API response for a `discord.Message`. If `json` is True, send the information in a copy-pasteable Python format. """ if ctx.author not in message.channel.members: await ctx.send( ":x: You do not have permissions to see the channel this message is in." ) return # I *guess* it could be deleted right as the command is invoked but I felt like it wasn't worth handling # doing this extra request is also much easier than trying to convert everything back into a dictionary again raw_data = await ctx.bot.http.get_message(message.channel.id, message.id) paginator = Paginator() def add_content(title: str, content: str) -> None: paginator.add_line(f'== {title} ==\n') # Replace backticks as it breaks out of code blocks. # An invisible character seemed to be the most reasonable solution. We hope it's not close to 2000. paginator.add_line(content.replace('`', '`\u200b')) paginator.close_page() if message.content: add_content('Raw message', message.content) transformer = pprint.pformat if json else self.format_fields for field_name in ('embeds', 'attachments'): data = raw_data[field_name] if not data: continue total = len(data) for current, item in enumerate(data, start=1): title = f'Raw {field_name} ({current}/{total})' add_content(title, transformer(item)) for page in paginator.pages: await ctx.send(page, allowed_mentions=AllowedMentions.none())
def assemblePage(self): p = Paginator() for line in self.grid: p.add_line(''.join(line)) return p.pages
async def format(self): """Handles the actual behaviour involved with formatting. To change the behaviour, this method should be overridden. Returns -------- list A paginated output of the help command. """ self._paginator = Paginator() # we need a padding of ~80 or so description = self.command.description if not self.is_cog() else inspect.getdoc(self.command) if description: # <description> portion self._paginator.add_line(description, empty=True) if isinstance(self.command, Command): # <signature portion> if self.command.params.get("args", None) and type(self.command.params['args'].annotation) == ArgPC: self.command.usage = create_help(self.command, self.command.params['args'].annotation.parser) signature = self.get_command_signature() self._paginator.add_line(signature, empty=True) # <long doc> section if self.command.help: self._paginator.add_line(self.command.help, empty=True) # end it here if it's just a regular command if not self.has_subcommands(): self._paginator.close_page() return self._paginator.pages max_width = self.max_name_size def category(tup): """Splits the help command into categories for easier readability""" cog = tup[1].cog_name # we insert the zero width space there to give it approximate # last place sorting position. return cog + ':' if cog is not None else '\u200bNo Category:' filtered = await self.filter_command_list() if self.is_bot(): data = sorted(filtered, key=category) for category, commands in itertools.groupby(data, key=category): # there simply is no prettier way of doing this. commands = sorted(commands) if len(commands) > 0: self._paginator.add_line(category) self._add_subcommands_to_page(max_width, commands) else: filtered = sorted(filtered) if filtered: self._paginator.add_line('Commands:') self._add_subcommands_to_page(max_width, filtered) # add the ending note self._paginator.add_line() ending_note = self.get_ending_note() self._paginator.add_line(ending_note) return self._paginator.pages