Advertisement
Guest User

Untitled

a guest
May 24th, 2018
93
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.69 KB | None | 0 0
  1. """Overrides the built-in help formatter.
  2. All help messages will be embed and pretty.
  3. Most of the code stolen from
  4. discord.ext.commands.formatter.py and
  5. converted into embeds instead of codeblocks.
  6. Docstr on cog class becomes category.
  7. Docstr on command definition becomes command
  8. summary and usage.
  9. Use [p] in command docstr for bot prefix.
  10. See [p]help here for example.
  11. await bot.formatter.format_help_for(ctx, command)
  12. to send help page for command. Optionally pass a
  13. string as third arg to add a more descriptive
  14. message to help page.
  15. e.g. format_help_for(ctx, ctx.command, "Missing required arguments")
  16. discord.py 1.0.0a
  17. Experimental: compatibility with 0.16.8
  18. Copyrights to logic of code belong to Rapptz (Danny)
  19. Everything else credit to SirThane#1780
  20. Pagination added by runew0lf#0001"""
  21.  
  22. import discord
  23. from discord.ext import commands
  24. from discord.ext.commands import formatter
  25. from discord import Member, Reaction
  26. import asyncio
  27. import re
  28. import itertools
  29.  
  30.  
  31. MAXLEN = 700
  32.  
  33. empty = u'\u200b'
  34.  
  35.  
  36. _mentions_transforms = {
  37.     '@everyone': '@\u200beveryone',
  38.     '@here': '@\u200bhere'
  39. }
  40.  
  41. LEFT_EMOJI = "\u2B05"
  42. RIGHT_EMOJI = "\u27A1"
  43. DELETE_EMOJI = "\u274c"
  44. FIRST_EMOJI = "\u23EE"
  45. LAST_EMOJI = "\u23ED"
  46.  
  47. PAGINATION_EMOJI = [FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI]
  48.  
  49. _mention_pattern = re.compile('|'.join(_mentions_transforms.keys()))
  50.  
  51.  
  52. orig_help = None
  53.  
  54. class Help(formatter.HelpFormatter):
  55.     """Formats help for commands."""
  56.  
  57.     def __init__(self, bot, *args, **kwargs):
  58.         self.bot = bot
  59.         global orig_help
  60.         orig_help = bot.get_command('help')
  61.         self.bot.remove_command('help')
  62.         self.bot.formatter = self
  63.         self.bot.help_formatter = self
  64.         super().__init__(*args, **kwargs)
  65.  
  66.     # Shortcuts that allow cog to run on 0.16.8 and 1.0.0a
  67.  
  68.     def pm_check(self, ctx):
  69.         return isinstance(ctx.channel, discord.abc.PrivateChannel)
  70.  
  71.     @property
  72.     def me(self):
  73.         return self.context.me
  74.  
  75.     @property
  76.     def bot_all_commands(self):
  77.         return self.bot.all_commands
  78.  
  79.     @property
  80.     def avatar(self):
  81.         return self.bot.user.avatar_url_as(format='png')
  82.  
  83.     @property
  84.     def color(self):
  85.         if self.pm_check(self.context):
  86.             return 0
  87.         else:
  88.             return self.me.color
  89.  
  90.     async def send(self, dest, content=None, embeds=None):
  91.         if len(embeds) == 1:
  92.             embed = embeds[0]
  93.             embed.set_author(name='{0} Help Manual'.format(self.bot.user.name), icon_url=self.avatar)
  94.             return await dest.send(content=content, embed=embed)
  95.  
  96.         help_msg = await dest.send(content=content, embed=embeds[0])
  97.         message = help_msg
  98.  
  99.         for emoji in PAGINATION_EMOJI:
  100.             # Add all the applicable emoji to the message
  101.             await message.add_reaction(emoji)
  102.  
  103.         def event_check(reaction_: Reaction, user_: Member):
  104.             """
  105.            Make sure that this reaction is what we want to operate on
  106.            """
  107.  
  108.             return (
  109.                     reaction_.message.id == message.id and  # Reaction on this specific message
  110.                     reaction_.emoji in PAGINATION_EMOJI and  # One of the reactions we handle
  111.                     user_.id != self.bot.user.id
  112.                     )
  113.  
  114.         current_page = 0
  115.         while True:
  116.             try:
  117.                 reaction, user = await self.bot.wait_for("reaction_add", timeout=300, check=event_check)
  118.             except asyncio.TimeoutError:
  119.                 break  # We're done, no reactions for the last 5 minutes
  120.  
  121.             if reaction.emoji == DELETE_EMOJI:
  122.                 break
  123.  
  124.             if reaction.emoji == FIRST_EMOJI:
  125.                 await message.remove_reaction(reaction.emoji, user)
  126.                 current_page = 0
  127.                 await help_msg.edit(content=content, embed=embeds[current_page])
  128.  
  129.             if reaction.emoji == LAST_EMOJI:
  130.                 await message.remove_reaction(reaction.emoji, user)
  131.                 current_page = len(embeds) - 1
  132.                 await help_msg.edit(content=content, embed=embeds[current_page])
  133.  
  134.             if reaction.emoji == LEFT_EMOJI:
  135.                 await message.remove_reaction(reaction.emoji, user)
  136.                 if current_page <= 0:
  137.                     continue
  138.                 current_page -= 1
  139.                 await help_msg.edit(content=content, embed=embeds[current_page])
  140.  
  141.             if reaction.emoji == RIGHT_EMOJI:
  142.                 await message.remove_reaction(reaction.emoji, user)
  143.                 if current_page >= len(embeds) - 1:
  144.                     continue
  145.                 current_page += 1
  146.                 await help_msg.edit(content=content, embed=embeds[current_page])
  147.  
  148.         await message.clear_reactions()
  149.  
  150.     @property
  151.     def author(self):
  152.         # Get author dict with username if PM and display name in guild
  153.         if self.pm_check(self.context):
  154.             name = self.bot.user.name
  155.         else:
  156.             name = self.me.display_name if not '' else self.bot.user.name
  157.         author = {
  158.                 'name': '{0} Help Manual'.format(name),
  159.                 'icon_url': self.avatar
  160.             }
  161.         return author
  162.  
  163.     @property
  164.     def destination(self):
  165.         return self.context.message.author if self.bot.pm_help else self.context.message.channel
  166.  
  167.     def _add_subcommands(self, cmds):
  168.         list_entries = []
  169.         entries = ''
  170.         for name, command in cmds:
  171.             if name in command.aliases:
  172.                 # skip aliases
  173.                 continue
  174.  
  175.             new_short_doc = command.short_doc.replace('[p]', self.clean_prefix)
  176.  
  177.             if self.is_cog() or self.is_bot():
  178.                 name = '{0}{1}'.format(self.clean_prefix, name)
  179.  
  180.             if len(entries + '**{0}**  -  {1}\n'.format(name, new_short_doc)) > MAXLEN:
  181.                 list_entries.append(entries)
  182.                 entries = ''
  183.             entries += '**{0}**  -  {1}\n'.format(name, new_short_doc)
  184.         list_entries.append(entries)
  185.         return list_entries
  186.  
  187.     def get_ending_note(self):
  188.         # command_name = self.context.invoked_with
  189.         return "Type {0}help <command> for more info on a command.\n" \
  190.                "You can also type {0}help <category> for more info on a category.".format(self.clean_prefix)
  191.  
  192.     async def format(self, ctx, command):
  193.         """Formats command for output.
  194.        Returns a dict used to build embed"""
  195.  
  196.         # All default values for embed dict
  197.         self.command = command
  198.         self.context = ctx
  199.         emb = {
  200.             'embed': {
  201.                 'title': '',
  202.                 'description': '',
  203.             },
  204.             'footer': {
  205.                 'text': self.get_ending_note()
  206.             },
  207.             'fields': []
  208.         }
  209.  
  210.         if isinstance(command, discord.ext.commands.core.Command):
  211.             # <signature portion>
  212.             # emb['embed']['title'] = emb['embed']['description']
  213.             emb['embed']['description'] = '`Syntax: {0}`'.format(self.get_command_signature())
  214.  
  215.             # <long doc> section
  216.             if command.help:
  217.                 name = '{0}'.format(command.help.split('\n\n')[0])
  218.                 name_length = len(name)
  219.                 name = name.replace('[p]', self.clean_prefix)
  220.                 value = command.help[name_length:].replace('[p]', self.clean_prefix)
  221.                 if value == '':
  222.                      name = '{0}'.format(command.help.split('\n')[0])
  223.                      name_length = len(name)
  224.                      value = command.help[name_length:].replace('[p]', self.clean_prefix)
  225.                 if value == '':
  226.                     value = empty
  227.                 if len(value) > 1024:
  228.                     first = value[:1024].rsplit('\n', 1)[0]
  229.                     list_values = [first, value[len(first):]]
  230.                     while len(list_values[-1]) > 1024:
  231.                         next_val = list_values[-1][:1024].rsplit('\n', 1)[0]
  232.                         remaining = [next_val, list_values[-1][len(next_val):]]
  233.                         list_values = list_values[:-1] + remaining
  234.                     for new_val in list_values:
  235.                         field = {
  236.                             'name': name,
  237.                             'value': new_val,
  238.                             'inline': False
  239.                         }
  240.                         emb['fields'].append(field)
  241.                 else:
  242.                     field = {
  243.                         'name': name,
  244.                         'value': value,
  245.                         'inline': False
  246.                     }
  247.                     emb['fields'].append(field)
  248.  
  249.             # end it here if it's just a regular command
  250.             if not self.has_subcommands():
  251.                 return emb
  252.  
  253.         def category(tup):
  254.             # Turn get cog (Category) name from cog/list tuples
  255.             cog = tup[1].cog_name
  256.             return '**__{0}:__**'.format(cog) if cog is not None else '**__\u200bNo Category:__**'
  257.  
  258.         # Get subcommands for bot or category
  259.         filtered = await self.filter_command_list()
  260.  
  261.         if self.is_bot():
  262.             # Get list of non-hidden commands for bot.
  263.             data = sorted(filtered, key=category)
  264.             for category, commands in itertools.groupby(data, key=category):
  265.                 # there simply is no prettier way of doing this.
  266.  
  267.                 commands = sorted(commands)
  268.                 if len(commands) > 0:
  269.                     for count, subcommands in enumerate(self._add_subcommands(commands)):
  270.                         field = {
  271.                             'inline': False
  272.                         }
  273.                         if count > 0:
  274.                             field['name'] = category + ' pt. {}'.format(count+1)
  275.                         else:
  276.                             field['name'] = category
  277.                         field['value'] = subcommands  # May need paginated
  278.                         emb['fields'].append(field)
  279.  
  280.         else:
  281.             # Get list of commands for category
  282.             filtered = sorted(filtered)
  283.             if filtered:
  284.                 for subcommands in self._add_subcommands(filtered):
  285.                     field = {
  286.                         'name': '**__Commands:__**' if not self.is_bot() and self.is_cog() else '**__Subcommands:__**',
  287.                         'value': subcommands,  # May need paginated
  288.                         'inline': False
  289.                     }
  290.  
  291.                     emb['fields'].append(field)
  292.  
  293.         return emb
  294.  
  295.     async def format_help_for(self, ctx, command_or_bot, reason: str=None):
  296.         """Formats the help page and handles the actual heavy lifting of how  ### WTF HAPPENED?
  297.        the help command looks like. To change the behaviour, override the
  298.        :meth:`~.HelpFormatter.format` method.
  299.        Parameters
  300.        -----------
  301.        ctx: :class:`.Context`
  302.            The context of the invoked help command.
  303.        command_or_bot: :class:`.Command` or :class:`.Bot`
  304.            The bot or command that we are getting the help of.
  305.        Returns
  306.        --------
  307.        list
  308.            A paginated output of the help command.
  309.        """
  310.         self.context = ctx
  311.         self.command = command_or_bot
  312.         emb = await self.format(ctx, command_or_bot)
  313.  
  314.         if reason:
  315.             emb['embed']['title'] = "{0}".format(reason)
  316.  
  317.         embeds = []
  318.         embed = discord.Embed(color=self.color, **emb['embed'])
  319.         embed.set_author(name='{0} Help Manual Page 1'.format(self.bot.user.name), icon_url=self.avatar)
  320.         embed.set_footer(**emb['footer'])
  321.         txt = ""
  322.         for field in emb['fields']:
  323.             txt += field["name"] + field["value"]
  324.             if len(txt) > MAXLEN:
  325.                 embeds.append(embed)
  326.                 txt = field["name"] + field["value"]
  327.                 del embed
  328.                 embed = discord.Embed(color=self.color, **emb['embed'])
  329.                 embed.set_author(name=f'{self.bot.user.name} Help Manual Page {len(embeds)+1}', icon_url=self.avatar)
  330.                 embed.set_footer(**emb['footer'])
  331.             embed.add_field(**field)
  332.         embeds.append(embed)
  333.  
  334.         embed.set_footer(**emb['footer'])
  335.         await self.send(self.destination, embeds=embeds)
  336.  
  337.     def simple_embed(self, title=None, description=None, color=None, author=None):
  338.         # Shortcut
  339.         embed = discord.Embed(title=title, description=description, color=color)
  340.         embed.set_footer(text=self.bot.formatter.get_ending_note())
  341.         if author:
  342.             embed.set_author(**author)
  343.         return embed
  344.  
  345.     def cmd_not_found(self, cmd, color=0):
  346.         # Shortcut for a shortcut. Sue me
  347.         embed = self.simple_embed(title=self.bot.command_not_found.format(cmd),
  348.                                   description='Commands are case sensitive. Please check your spelling and try again',
  349.                                   color=color, author=self.author)
  350.         return embed
  351.  
  352.     @commands.command(name='help', pass_context=True, hidden=True)
  353.     async def help(self, ctx, *cmds: str):
  354.         if not ctx.message.author.permissions_in(ctx.channel).embed_links:
  355.             return await ctx.send(content="You don't have permissions to send embeds here. Find a different server/channel where you can embed links and try the help command there.")
  356.  
  357.         """Shows help documentation.
  358.        [p]**help**: Shows the help manual.
  359.        [p]**help** command: Show help for a command
  360.        [p]**help** Category: Show commands and description for a category"""
  361.         self.context = ctx
  362.  
  363.         def repl(obj):
  364.             return _mentions_transforms.get(obj.group(0), '')
  365.  
  366.         # help by itself just lists our own commands.
  367.         if len(cmds) == 0:
  368.             await self.bot.formatter.format_help_for(ctx, self.bot)
  369.             return
  370.  
  371.         elif len(cmds) == 1:
  372.             # try to see if it is a cog name
  373.             name = _mention_pattern.sub(repl, cmds[0])
  374.             command = None
  375.             if name in self.bot.cogs:
  376.                 command = self.bot.cogs[name]
  377.             else:
  378.                 command = self.bot_all_commands.get(name)
  379.                 if command is None:
  380.                     await self.send(self.destination, embeds=[self.cmd_not_found(name, self.color)])
  381.                     return
  382.  
  383.             await self.bot.formatter.format_help_for(ctx, command)
  384.         else:
  385.             name = _mention_pattern.sub(repl, cmds[0])
  386.             command = self.bot_all_commands.get(name)
  387.             if command is None:
  388.                 await self.send(self.destination, embeds=[self.cmd_not_found(name, self.color)])
  389.                 return
  390.  
  391.             for key in cmds[1:]:
  392.                 try:
  393.                     key = _mention_pattern.sub(repl, key)
  394.                     command = command.all_commands.get(key)
  395.                     if command is None:
  396.                         await self.send(self.destination, embeds=[self.cmd_not_found(key, self.color)])
  397.                         return
  398.                 except AttributeError:
  399.                     await self.send(self.destination,
  400.                                     embeds=[self.simple_embed(title=
  401.                                                             'Command "{0.name}" has no subcommands.'.format(command),
  402.                                                             color=self.color,
  403.                                                             author=self.author)])
  404.                     return
  405.  
  406.             await self.bot.formatter.format_help_for(ctx, command)
  407.  
  408.     def __unload(self):
  409.         self.bot.formatter = formatter.HelpFormatter()
  410.         self.bot.add_command(orig_help)
  411.  
  412.  
  413. def setup(bot):
  414.     bot.add_cog(Help(bot))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement