Exemple #1
0
async def fill_shortcut(bot, shortcut, parameters, message):
    parameters = split_parameters(parameters, include_quotes=True)
    stripped_parameters = parameters[::2]
    arguments_dictionary = {}
    current_index = -1
    for current_index, current in enumerate(stripped_parameters):
        if current_index >= len(shortcut.args):
            invoker = utilities.get_invoker(bot, guild=message.guild)
            raise CBException(
                "Too many arguments.", embed_fields=shortcut.help_embed_fields,
                embed_format={'invoker': invoker})
        else:
            arg = shortcut.args[current_index]
            if arg.argtype in (ArgTypes.SINGLE, ArgTypes.OPTIONAL):
                arguments_dictionary[arg.name] = current
            else:  # Instant finish grouped arguments
                if arg.argtype in (ArgTypes.SPLIT, ArgTypes.SPLIT_OPTIONAL):
                    arguments_dictionary[arg.name] = ''.join(stripped_parameters[current_index:])
                else:  # Merged
                    arguments_dictionary[arg.name] = ''.join(parameters[current_index * 2:])
                break
    # TODO: TEST THIS!
    logger.debug("Finished shortcut loop. %s", arguments_dictionary)
    if current_index < len(shortcut.args) - 1:  # Check for optional arguments
        arg = shortcut.args[current_index + 1]
        if arg.argtype is ArgTypes.OPTIONAL:
            while (arg and arg.argtype is ArgTypes.OPTIONAL and
                    current_index < len(shortcut.args)):
                arguments_dictionary[arg.name] = '' if arg.default is None else arg.default
                current_index += 1
                try:
                    arg = shortcut.args[current_index]
                except:
                    arg = None
        if arg and arg.argtype in (ArgTypes.SPLIT_OPTIONAL, ArgTypes.MERGED_OPTIONAL):
            arguments_dictionary[arg.name] = '' if arg.default is None else arg.default
        elif arg:
            invoker = utilities.get_invoker(bot, guild=message.guild)
            raise CBException(
                "Not enough arguments.", embed_fields=shortcut.help_embed_fields,
                embed_format={'invoker': invoker})
    logger.debug("Finished checking for optional arguments. %s", arguments_dictionary)
    for arg in shortcut.args:
        value = arguments_dictionary[arg.name]
        if value is not None:
            new_value = await arg.convert_and_check(bot, message, value)
            arguments_dictionary[arg.name] = new_value
    return shortcut.replacement.format(**arguments_dictionary).strip()
Exemple #2
0
async def role_list(bot, context):
    """Lists the available roles for self-assignment."""
    if context.arguments[0]:  # List members that have this role
        role = context.arguments[0]
        if role.is_default():
            raise CBException("Cannot list members with the @everyone role.")
        if not role.members:
            raise CBException("No members have the role {}.".format(role.mention))
        elif len(role.members) > 80:
            name_file = discord.File(
                utilities.get_text_as_file('\n'.join(str(it) for it in role.members)),
                filename='members.txt')
            embed = discord.Embed(
                description='{} members have this role.'.format(len(role.members)))
            return Response(file=name_file, embed=embed)
        else:
            plural = len(role.members) > 1
            return Response(embed=discord.Embed(
                title='{} member{} {} this role:'.format(
                    len(role.members), 's' if plural else '', 'have' if plural else 'has'),
                description=', '.join(it.mention for it in role.members)))

    else:  # List self-assignable roles
        available_roles = _check_roles(bot, context.guild)
        if not available_roles:
            raise CBException("There are no self-assignable roles available.")

        embed = discord.Embed(
            title='Self-assignable roles:',
            description='\n'.join(it.mention for it in available_roles))
        embed.set_footer(text='To join a role, use {}role join'.format(
            utilities.get_invoker(bot, guild=context.guild)))
        return Response(embed=embed)
Exemple #3
0
def get_manual(bot, subject, entry, guild=None):
    """Gets the given manual entry as a tuple: (topic, [text, text2])."""
    invoker = utilities.get_invoker(bot, guild=guild)
    base_invoker = utilities.get_invoker(bot)
    if entry <= 0:
        raise CBException("Invalid manual entry.")
    for manual in bot.manuals:
        manual_length = len(manual[1]['order'])
        if manual_length >= entry:
            entry_title = manual[1]['order'][entry - 1]
            found_entry = manual[1]['entries'][entry_title]
            response = '***`{0}`*** -- {1}\n\n'.format(manual[0], entry_title)
            return response + found_entry.format(invoker=invoker, base_invoker=base_invoker)
        else:
            entry -= manual_length
    raise CBException("Invalid manual entry.")
Exemple #4
0
def get_general_help(bot, guild=None, is_owner=False):
    """Gets the general help. Lists all base commands that aren't shortcuts."""
    response = "Here is a list of commands by group:\n"
    invoker = utilities.get_invoker(bot, guild=guild)

    group_dictionary = {}

    for plugin_name, plugin in bot.plugins.items():
        visible_commands = []
        for command in plugin[1]:
            level = command.elevated_level
            hidden = command.hidden
            group = command.group
            if (((level < 3 and not hidden) or is_owner) and
                    command not in visible_commands):
                if command.description:
                    description = command.description
                else:
                    description = '[Description not provided]'
                if group_dictionary.get(group) is None:
                    group_dictionary[group] = []
                group_dictionary[group].append('**`{0}`** -- {1}'.format(
                    command.base, description))

    listing = []
    for group, entries in sorted(list(group_dictionary.items())):
        listing.append('\n***`{0}`***\n\t{1}'.format(
            group, '\n\t'.join(sorted(entries))))
    response += '\n'.join(listing) + '\n'

    response += ("\nGet help on a command with `{0}help <command>`\n"
                 "Confused by the syntax? See `{0}manual 3`").format(invoker)
    return response
Exemple #5
0
async def on_server_join(bot, server):
    # Add server to the list
    logging.debug("Joining server")
    data.add_server(bot, server)
    if bot.selfbot:  # Don't send DMs if in selfbot mode
        return
    invoker = utilities.get_invoker(bot)
    text = (
        "Hello! You are receiving this notification because this bot was "
        "added to one of your servers, specifically '{0.name}' (ID: {0.id}). "
        "If you are aware of this and approve of the addition, feel free to "
        "continue and use the bot. However, if you did not approve of this "
        "addition, it is highly recommended you kick or ban this bot as there "
        "may be potential for users to use the bot to spam. Only users that "
        "have the administrator permission can add bots to servers. "
        "Unfortunately, there is no way to track who added the bot.\n\n"
        "To read more about the functionality and usage of the bot, type "
        "`{1}manual` to see a list of topics, and `{1}help` to see a list of "
        "commands. **As a server owner, it is highly recommended that you "
        "read `{1}manual 5` and `{1}manual 4` for moderating and configuring "
        "the bot.**\n\nThat's all for now. If you have any questions, please "
        "refer to the manual, or send the bot owners a message using "
        "`{1}owner feedback <message>`.\n\nCheck out the Wiki for more: "
        "https://github.com/jkchen2/JshBot/wiki").format(server, invoker)
    await bot.send_message(server.owner, text)
Exemple #6
0
def get_manual(bot, subject, entry, guild=None):
    """Gets the given manual entry as a tuple: (topic, [text, text2])."""
    invoker = utilities.get_invoker(bot, guild=guild)
    base_invoker = utilities.get_invoker(bot)
    if entry <= 0:
        raise CBException("Invalid manual entry.")
    for manual in bot.manuals:
        manual_length = len(manual[1]['order'])
        if manual_length >= entry:
            entry_title = manual[1]['order'][entry - 1]
            found_entry = manual[1]['entries'][entry_title]
            response = '***`{0}`*** -- {1}\n\n'.format(manual[0], entry_title)
            return response + found_entry.format(invoker=invoker,
                                                 base_invoker=base_invoker)
        else:
            entry -= manual_length
    raise CBException("Invalid manual entry.")
Exemple #7
0
def get_manual(bot, entry, server=None):
    """Gets the given manual entry."""
    invoker = utilities.get_invoker(bot, server=server)
    base_invoker = utilities.get_invoker(bot)
    if entry <= 0:
        raise BotException(EXCEPTION, "Invalid manual entry.")
    for manual in bot.manuals:
        manual_length = len(manual[1]['order'])
        if manual_length >= entry:
            entry_title = manual[1]['order'][entry - 1]
            found_entry = manual[1]['entries'][entry_title]
            response = '***`{0}`*** -- {1}\n\n'.format(manual[0], entry_title)
            return response + found_entry.format(invoker=invoker,
                                                 base_invoker=base_invoker)
        else:
            entry -= manual_length
    raise BotException(EXCEPTION, "Invalid manual entry.")
Exemple #8
0
def get_help(bot, base, topic=None, is_owner=False, guild=None):
    """Gets the help of the base command, or the topic of a help command."""
    try:
        base = base.lower()
        command = bot.commands[base]
    except KeyError:
        raise CBException(
            "Invalid command base. Ensure sure you are not including the command invoker."
        )

    if command.hidden and not is_owner:
        return '```\nCommand is hidden.```'
    if command.shortcut and base in command.shortcut.bases:
        shortcut_index = command.shortcut.bases.index(base)
        return usage_reminder(bot,
                              base,
                              index=shortcut_index,
                              shortcut=True,
                              is_owner=is_owner,
                              guild=guild)

    # Handle specific topic help
    if topic is not None:
        try:
            topic_index = int(topic)
        except:  # Guess the help index
            guess = parser.guess_index(bot, '{0} {1}'.format(base, topic))
            topic_index = None if guess[1] == -1 else guess[1] + 1
            return get_help(bot,
                            base,
                            topic=topic_index,
                            is_owner=is_owner,
                            guild=guild)
        else:  # Proper index given
            return usage_reminder(bot, base, index=topic_index, guild=guild)

    response = ''
    invoker = utilities.get_invoker(bot, guild=guild)
    if command.description:
        response += '**Description**:\n{}\n\n'.format(
            command.description.format(invoker=invoker))
    response += usage_reminder(bot, base, is_owner=is_owner,
                               guild=guild) + '\n'
    if command.shortcut:
        response += usage_reminder(
            bot, base, shortcut=True, is_owner=is_owner, guild=guild) + '\n'
    if command.other:
        response += '**Other information**:\n{}'.format(
            command.other.format(invoker=invoker))

    return response.rstrip()
Exemple #9
0
def get_general_manual(bot, guild=None):
    """Lists available manual entries and assigns each one an index."""
    response = "Here is a list of manual entries by plugin:\n"
    invoker = utilities.get_invoker(bot, guild=guild)
    counter = 1
    for manual in bot.manuals:
        response += '\n***`{}`***\n'.format(manual[0])
        for entry in manual[1]['order']:
            response += '\t**`[{0: <2}]`** {1}\n'.format(counter, entry)
            counter += 1
    response += (
        '\nRead an entry with `{}manual <entry number>`\nNew? It is '
        'recommended that you read manual entries 1, 2, and 3.').format(invoker)
    return response
Exemple #10
0
def usage_reminder(bot,
                   base,
                   index=None,
                   shortcut=False,
                   guild=None,
                   is_owner=False):
    """Returns the usage syntax for the base command (simple format).

    Keyword arguments:
    index -- gets that specific index's help entry
    guild_id -- used to check the invoker
    is_mod -- used to check for private command visibility
    """
    command = bot.commands[base]
    if command.hidden and not is_owner:
        return "`\nCommand is hidden.`"
    if shortcut:
        if base in command.shortcut.bases:
            base = command.base
        command = command.shortcut
    invoker = utilities.get_invoker(bot, guild=guild)

    if index is None:  # List all commands or shortcuts
        if shortcut:
            response = '**Shortcuts**:\n'
        else:
            response = '**Usage**:\n'
        for topic_index, (syntax, details) in enumerate(command.help):
            details = details if details else '[Details not provided]'
            if shortcut:
                response += '`{0}{1} {2}`\t→\t`{0}{3} {4}`\n'.format(
                    invoker, command.bases[topic_index], syntax, base, details)
            else:
                syntax = syntax if syntax else '[Syntax not provided]'
                response += '`{0}{1} {2}`\n'.format(invoker, base, syntax)
    else:  # Help on a specific command
        if shortcut:
            syntax, result = command.help[index]
            response = '`{0}{1} {2}`\t→\t`{0}{3} {4}`'.format(
                invoker, command.bases[index], syntax, base, result)
        elif 0 < index <= len(command.help):
            syntax, details = command.help[index - 1]
            syntax = syntax if syntax else '[Syntax not provided]'
            details = details if details else '[Details not provided]'
            response = '`{0}{1} {2}`\n{3}'.format(invoker, base, syntax,
                                                  details)
        else:
            raise CBException("Invalid help index.")

    return response
Exemple #11
0
def get_general_manual(bot, guild=None):
    """Lists available manual entries and assigns each one an index."""
    response = "Here is a list of manual entries by plugin:\n"
    invoker = utilities.get_invoker(bot, guild=guild)
    counter = 1
    for manual in bot.manuals:
        response += '\n***`{}`***\n'.format(manual[0])
        for entry in manual[1]['order']:
            response += '\t**`[{0: <2}]`** {1}\n'.format(counter, entry)
            counter += 1
    response += ('\nRead an entry with `{}manual <entry number>`\nNew? It is '
                 'recommended that you read manual entries 1, 2, and 3.'
                 ).format(invoker)
    return response
Exemple #12
0
def _user_character_search(bot, command_author, owner=None, character_search=None):
    """Finds characters under the given owner, search, or both."""
    # Setup select arguments
    where_args, input_args = [], []
    if not (owner or character_search):
        where_args.append('owner_id=%s')
        input_args.append(command_author.id)
    if owner:
        where_args.append('owner_id=%s')
        input_args.append(owner.id)
    if character_search:
        where_args.append('clean_name=%s')
        input_args.append(character_search)

    # Get character list
    cursor = data.db_select(
        bot, from_arg='characters', where_arg=' AND '.join(where_args), input_args=input_args)
    characters = cursor.fetchall() if cursor else []
    if not characters:
        if owner:
            cursor = data.db_select(
                bot, from_arg='characters', where_arg='owner_id=%s', input_args=[owner.id])
            owner_characters = cursor.fetchall() if cursor else []
            if owner_characters:
                raise CBException(
                    "{} has no character named \"{}\".".format(
                        owner.mention, character_search))
            else:
                raise CBException("{} has no character entries.".format(owner.mention))
        elif character_search:
            raise CBException("No character named \"{}\" was found.".format(character_search))
        else:
            raise CBException(
                "You have no character entries!\n"
                "You can create one with `{}character create`".format(
                    utilities.get_invoker(bot, getattr(command_author, 'guild', None))))

    # Check if character list contains characters made by the command author
    character_index = 0
    if not owner:
        for index, character in enumerate(characters):
            if character.owner_id == command_author.id:
                character_index = index
                break

    return [character_index, characters]
Exemple #13
0
def usage_reminder(
        bot, base, index=None, shortcut=False, guild=None, is_owner=False):
    """Returns the usage syntax for the base command (simple format).

    Keyword arguments:
    index -- gets that specific index's help entry
    guild_id -- used to check the invoker
    is_mod -- used to check for private command visibility
    """
    command = bot.commands[base]
    if command.hidden and not is_owner:
        return "`\nCommand is hidden.`"
    if shortcut:
        if base in command.shortcut.bases:
            base = command.base
        command = command.shortcut
    invoker = utilities.get_invoker(bot, guild=guild)

    if index is None:  # List all commands or shortcuts
        if shortcut:
            response = '**Shortcuts**:\n'
        else:
            response = '**Usage**:\n'
        for topic_index, (syntax, details) in enumerate(command.help):
            details = details if details else '[Details not provided]'
            if shortcut:
                response += '`{0}{1} {2}`\t→\t`{0}{3} {4}`\n'.format(
                    invoker, command.bases[topic_index], syntax, base, details)
            else:
                syntax = syntax if syntax else '[Syntax not provided]'
                response += '`{0}{1} {2}`\n'.format(invoker, base, syntax)
    else:  # Help on a specific command
        if shortcut:
            syntax, result = command.help[index]
            response = '`{0}{1} {2}`\t→\t`{0}{3} {4}`'.format(
                invoker, command.bases[index], syntax, base, result)
        elif 0 < index <= len(command.help):
            syntax, details = command.help[index - 1]
            syntax = syntax if syntax else '[Syntax not provided]'
            details = details if details else '[Details not provided]'
            response = '`{0}{1} {2}`\n{3}'.format(
                invoker, base, syntax, details)
        else:
            raise CBException("Invalid help index.")

    return response
Exemple #14
0
async def tagremote(bot, context):
    """Gets the current session data as a link."""
    session_data = data.get(bot, __name__, 'data', guild_id=context.guild.id)
    if not session_data:
        raise CBException(
            "No session available.\nStart one with `{}tagremote start`".format(
                utilities.get_invoker(bot, guild=context.guild)))

    channel_id, session_code = session_data['channel'], session_data['session']
    voice_channel_id = session_data['voice_channel']
    channel_mention = data.get_channel(bot, channel_id, guild=context.guild).mention
    voice_channel_mention = data.get_channel(bot, voice_channel_id, guild=context.guild).mention
    description = 'The session code is:\n`{}`\nThe session is attached to {} and {}'.format(
        session_code, channel_mention, voice_channel_mention)
    return Response(embed=discord.Embed(
        title='Tap here on your phone to use the tag remote',
        url='https://jkchen2.github.io/tag-remote/#{}'.format(session_code),
        description=description))
Exemple #15
0
def get_help(bot, base, topic=None, is_owner=False, guild=None):
    """Gets the help of the base command, or the topic of a help command."""
    try:
        base = base.lower()
        command = bot.commands[base]
    except KeyError:
        raise CBException(
            "Invalid command base. Ensure sure you are not including the command invoker.")

    if command.hidden and not is_owner:
        return '```\nCommand is hidden.```'
    if command.shortcut and base in command.shortcut.bases:
        shortcut_index = command.shortcut.bases.index(base)
        return usage_reminder(
            bot, base, index=shortcut_index, shortcut=True,
            is_owner=is_owner, guild=guild)

    # Handle specific topic help
    if topic is not None:
        try:
            topic_index = int(topic)
        except:  # Guess the help index
            guess = parser.guess_index(bot, '{0} {1}'.format(base, topic))
            topic_index = None if guess[1] == -1 else guess[1] + 1
            return get_help(
                bot, base, topic=topic_index, is_owner=is_owner, guild=guild)
        else:  # Proper index given
            return usage_reminder(bot, base, index=topic_index, guild=guild)

    response = ''
    invoker = utilities.get_invoker(bot, guild=guild)
    if command.description:
        response += '**Description**:\n{}\n\n'.format(
            command.description.format(invoker=invoker))
    response += usage_reminder(
        bot, base, is_owner=is_owner, guild=guild) + '\n'
    if command.shortcut:
        response += usage_reminder(
            bot, base, shortcut=True, is_owner=is_owner, guild=guild) + '\n'
    if command.other:
        response += '**Other information**:\n{}'.format(
            command.other.format(invoker=invoker))

    return response.rstrip()
Exemple #16
0
async def guess_command(
        bot, text, message, safe=True, substitute_shortcuts=True, suggest_help=True):
    """Guesses the closest command or subcommand.
    
    Keyword arguments:
    safe -- Returns None if no command was guessed
    substitute_shortcuts -- Fills in the shortcut (if found) and guesses a command from that
    suggest_help -- Suggests that the user run the regular help command
    """
    if not text:
        if safe:
            return None
        else:
            raise CBException("No guess text.")
    text = text.strip()
    split_content = text.split(' ', 1)
    if len(split_content) == 1:
        split_content.append('')
    base, parameters = split_content
    base = base.lower()
    try:
        command = bot.commands[base]
    except KeyError:
        if safe:
            return None
        else:
            if suggest_help:
                invoker = utilities.get_invoker(bot, message=message)
                additional = ' To see the menu, type `{}help`'.format(invoker)
            else:
                additional = ''
            raise CBException("Invalid base.{}".format(additional))
    if isinstance(command, commands.Shortcut) and substitute_shortcuts:
        try:
            parameters = await fill_shortcut(bot, command, parameters, message)
            command = command.command
        except BotException:
            return command.command
    if not parameters or isinstance(command, commands.Shortcut):
        return command
    else:
        return await match_subcommand(bot, command, parameters, message, match_closest=True)
Exemple #17
0
def get_general_help(bot, guild=None, is_owner=False):
    """Gets the general help. Lists all base commands that aren't shortcuts."""
    response = "Here is a list of commands by group:\n"
    invoker = utilities.get_invoker(bot, guild=guild)
    '''
    plugin_pairs = []
    for plugin_name, plugin in bot.plugins.items():
        plugin_pairs.append((plugin_name, plugin[1]))
    plugin_pairs.sort()
    '''
    group_dictionary = {}

    for plugin_name, plugin in bot.plugins.items():
        visible_commands = []
        for command in plugin[1]:
            level = command.elevated_level
            hidden = command.hidden
            group = command.group
            if (((level < 3 and not hidden) or is_owner)
                    and command not in visible_commands):
                if command.description:
                    description = command.description
                else:
                    description = '[Description not provided]'
                if group_dictionary.get(group) is None:
                    group_dictionary[group] = []
                group_dictionary[group].append('**`{0}`** -- {1}'.format(
                    command.base, description))

    listing = []
    for group, entries in sorted(list(group_dictionary.items())):
        listing.append('\n***`{0}`***\n\t{1}'.format(
            group, '\n\t'.join(sorted(entries))))
    response += '\n'.join(listing) + '\n'

    response += ("\nGet help on a command with `{0}help <command>`\n"
                 "Confused by the syntax? See `{0}manual 3`").format(invoker)
    return response
Exemple #18
0
async def get_response(bot, context):
    """Gets a response given the parsed input.

    context attributes:
    bot -- A reference to the bot itself.
    message -- The discord.message object obtained from on_message.
    base -- The base command name that immediately follows the invoker.
    subcommand -- The subcommand that matched the parameters.
    index -- The index of the found subcommand.
    options -- A dictionary representing the options and potential positional
        arguments that are attached to them.
    arguments -- A list of strings that follow the syntax of the blueprint
        index for arguments following the options.
    keywords -- Another list of strings that holds all option keywords. These
        can be used to prevent database conflicts with user commands.
    cleaned_content -- Simply the message content without the invoker.
    """

    # This is what the bot will say when it returns from this function.
    # The response object can be manipulated in many ways. The attributes of
    #   the response will be passed into the send function.
    response = Response()
    response.content = ''  # Default

    # Set to True if you want your message read with /tts (not recommended).
    response.tts = False  # Default

    # The message type dictates how the bot handles your returned message.
    #
    # NORMAL - Normal. The issuing command can be edited.
    # PERMANENT - Message is not added to the edit dictionary.
    # REPLACE - Deletes the issuing command after 'extra' seconds. Defaults
    #   to 0 seconds if 'extra' is not given.
    # ACTIVE - The message reference is passed back to the function defined
    #   with 'extra_function'. If 'extra_function' is not defined, it will call
    #   plugin.handle_active_message.
    # INTERACTIVE - Assembles reaction buttons given by extra['buttons'] and
    #   calls 'extra_function' whenever one is pressed.
    # WAIT - Wait for event. Calls 'extra_function' with the result, or None
    #   if the wait timed out.
    #
    # Only the NORMAL message type can be edited.
    response.message_type = MessageTypes.NORMAL  # Default

    # The extra variable is used for some message types.
    response.extra = None  # Default

    # Initially, check to make sure that you've matched the proper command.
    # If there is only one command specified, this may not be necessary.
    index, options, arguments = context.index, context.options, context.arguments
    if context.base == 'mycommand':

        # Then, the subcommand index will tell you which command syntax was
        #   satisfied. The order is the same as was specified initially.
        if index == 0:  # myoption
            response.content = "You called the first subcommand!"
            # Do other stuff...

        elif index == 1:  # custom/attached
            # To see if an optional option was included in the command, use:
            if 'custom' in options:
                response.content += "You included the \"custom\" flag!\n"
                # Do stuff relevant to this flag here...

            # To get the parameter attached to an option, simply access it from
            #   the options dictionary.
            if 'attached' in options:
                response.content += "The attached parmeter: {}\n".format(
                    options['attached'])

            # In case somebody was looking for the help...
            if len(options) == 0:
                invoker = utilities.get_invoker(bot, guild=context.guild)
                response.content += (
                    "You didn't use either flag...\n"
                    "For help, try `{}help mycommand`".format(invoker))

        elif index == 2:  # trailing arguments
            # If arguments are specified as trailing, they will be in a list.
            response.content += "The list of trailing arguments: {}".format(
                arguments)

        elif index == 3:  # grouped arguments
            # All arguments are grouped together as the first element
            response.message_type = MessageTypes.PERMANENT
            response.content = ("You can't edit your command here.\n"
                                "Single grouped argument: {}").format(
                                    arguments[0])

        elif index == 4:  # complex
            # This mixes elements of both examples seen above.
            response.content = ("The argument attached to the complex "
                                "option: {}").format(options['complex'])
            if 'other' in options:
                response.content += "\nThe other option has attached: {}".format(
                    options['other'])
            response.content += "\nLastly, the trailing arguments: {}".format(
                arguments)

        elif index == 5:  # (Very slow) marquee
            # This demonstrates the active message type.
            # Check active_marquee to see how it works.
            response.message_type = MessageTypes.ACTIVE
            response.extra_function = active_marquee  # The function to call
            response.extra = arguments[0]  # The text to use
            response.content = "Setting up marquee..."  # This will be shown first

    # Here's another command base.
    elif context.base == 'myothercommand':

        if index == 0:  # keyword checker
            text = arguments[0]
            if not text:
                response.content = "You didn't say anything...\n"
            else:
                response.content = "This is your input: {}\n".format(text)
                if text in context.keywords:
                    response.content += "Your input was in the list of keywords!\n"
                else:
                    response.content += (
                        "Your input was not in the list of keywords. "
                        "They are: {}\n").format(context.keywords)
            response.message_type = MessageTypes.PERMANENT
            response.delete_after = 15
            response.content += "This message will self destruct in 15 seconds."

        else:  # impossible command???
            raise CBException(
                "This is a bug! You should never see this message.")

    elif context.base == 'wait':
        response.message_type = MessageTypes.WAIT
        # The extra attribute should consist of a dictionary containing the
        #   event and any other kwargs. Most notably, you will likely want to
        #   define the check used in wait_for.
        response.extra_function = custom_interaction
        response.extra = {
            'event': 'message',
            'kwargs': {
                'timeout': 30,  # Default 300
                'check': lambda m: m.author == context.author,
            }
        }
        response.content = "Say something, {}.".format(context.author)

    return response
Exemple #19
0
async def get_response(bot, context):
    """Gets a response given the parsed input.

    context attributes:
    bot -- A reference to the bot itself.
    message -- The discord.message object obtained from on_message.
    base -- The base command name that immediately follows the invoker.
    subcommand -- The subcommand that matched the parameters.
    index -- The index of the found subcommand.
    options -- A dictionary representing the options and potential positional
        arguments that are attached to them.
    arguments -- A list of strings that follow the syntax of the blueprint
        index for arguments following the options.
    keywords -- Another list of strings that holds all option keywords. These
        can be used to prevent database conflicts with user commands.
    cleaned_content -- Simply the message content without the invoker.
    """

    # This is what the bot will say when it returns from this function.
    # The response object can be manipulated in many ways. The attributes of
    #   the response will be passed into the send function.
    response = Response()
    response.content = ''  # Default

    # Set to True if you want your message read with /tts (not recommended).
    response.tts = False  # Default

    # The message type dictates how the bot handles your returned message.
    #
    # NORMAL - Normal. The issuing command can be edited.
    # PERMANENT - Message is not added to the edit dictionary.
    # REPLACE - Deletes the issuing command after 'extra' seconds. Defaults
    #   to 0 seconds if 'extra' is not given.
    # ACTIVE - The message reference is passed back to the function defined
    #   with 'extra_function'. If 'extra_function' is not defined, it will call
    #   plugin.handle_active_message.
    # INTERACTIVE - Assembles reaction buttons given by extra['buttons'] and
    #   calls 'extra_function' whenever one is pressed.
    # WAIT - Wait for event. Calls 'extra_function' with the result, or None
    #   if the wait timed out.
    #
    # Only the NORMAL message type can be edited.
    response.message_type = MessageTypes.NORMAL  # Default

    # The extra variable is used for some message types.
    response.extra = None  # Default

    # Initially, check to make sure that you've matched the proper command.
    # If there is only one command specified, this may not be necessary.
    index, options, arguments = context.index, context.options, context.arguments
    if context.base == 'mycommand':

        # Then, the subcommand index will tell you which command syntax was
        #   satisfied. The order is the same as was specified initially.
        if index == 0:  # myoption
            response.content = "You called the first subcommand!"
            # Do other stuff...

        elif index == 1:  # custom/attached
            # To see if an optional option was included in the command, use:
            if 'custom' in options:
                response.content += "You included the \"custom\" flag!\n"
                # Do stuff relevant to this flag here...

            # To get the parameter attached to an option, simply access it from
            #   the options dictionary.
            if 'attached' in options:
                response.content += "The attached parmeter: {}\n".format(options['attached'])

            # In case somebody was looking for the help...
            if len(options) == 0:
                invoker = utilities.get_invoker(bot, guild=context.guild)
                response.content += ("You didn't use either flag...\n"
                                     "For help, try `{}help mycommand`".format(invoker))

        elif index == 2:  # trailing arguments
            # If arguments are specified as trailing, they will be in a list.
            response.content += "The list of trailing arguments: {}".format(arguments)

        elif index == 3:  # grouped arguments
            # All arguments are grouped together as the first element
            response.message_type = MessageTypes.PERMANENT
            response.content = ("You can't edit your command here.\n"
                                "Single grouped argument: {}").format(arguments[0])

        elif index == 4:  # complex
            # This mixes elements of both examples seen above.
            response.content = ("The argument attached to the complex "
                                "option: {}").format(options['complex'])
            if 'other' in options:
                response.content += "\nThe other option has attached: {}".format(options['other'])
            response.content += "\nLastly, the trailing arguments: {}".format(arguments)

        elif index == 5:  # (Very slow) marquee
            # This demonstrates the active message type.
            # Check active_marquee to see how it works.
            response.message_type = MessageTypes.ACTIVE
            response.extra_function = active_marquee  # The function to call
            response.extra = arguments[0]  # The text to use
            response.content = "Setting up marquee..."  # This will be shown first

    # Here's another command base.
    elif context.base == 'myothercommand':

        if index == 0:  # keyword checker
            text = arguments[0]
            if not text:
                response.content = "You didn't say anything...\n"
            else:
                response.content = "This is your input: {}\n".format(text)
                if text in context.keywords:
                    response.content += "Your input was in the list of keywords!\n"
                else:
                    response.content += ("Your input was not in the list of keywords. "
                                         "They are: {}\n").format(context.keywords)
            response.message_type = MessageTypes.PERMANENT
            response.delete_after = 15
            response.content += "This message will self destruct in 15 seconds."

        else:  # impossible command???
            raise CBException("This is a bug! You should never see this message.")

    elif context.base == 'wait':
        response.message_type = MessageTypes.WAIT
        # The extra attribute should consist of a dictionary containing the
        #   event and any other kwargs. Most notably, you will likely want to
        #   define the check used in wait_for.
        response.extra_function = custom_interaction
        response.extra = {
            'event': 'message',
            'kwargs': {
                'timeout': 30,  # Default 300
                'check': lambda m: m.author == context.author,
            }
        }
        response.content = "Say something, {}.".format(context.author)

    return response
Exemple #20
0
def get_manual(bot, subject_id=None, topic_index=None, page=None, guild=None, safe=True):
    """
    Gets the given manual entry depending on depth.

    If subject is not provided: returns a tuple: ('Manual menu', subject_listing)
    If subject is provided: return a tuple: (subject_name, topic_listing)
    If topic is provivded: return a tuple: (topic_name, text)
    All return values also include (1 indexed): (page_number, total_pages, crumbs)
    These return values are to be used as embed fields.
    """
    MAX_E = 5
    invoker = utilities.get_invoker(bot, guild=guild)
    base_invoker = utilities.get_invoker(bot)
    crumbs = 'Manual menu'
    if page is None:
        page = 0

    subjects = list(bot.manuals.values())

    if subject_id is not None:  # Subject selected; browsing topics
        try:
            subject_index = int(subject_id)
            assert subject_index >= 0  # TODO: Better checking
        except ValueError:  # Subject text given
            subject_name = subject_id.lower()
            if subject_name not in bot.manuals:
                if safe:
                    return
                raise CBException("Invalid subject.")
            subject = bot.manuals[subject_name]
        else:  # Subject index given
            try:
                subject = subjects[subject_index]
            except IndexError:
                if safe:
                    return
                raise CBException(
                    "Invalid subject index. Must be between 1 and {}.".format(len(bot.manuals)))
        topics = subject['topics']
        crumbs += '\t→\t{}'.format(subject['subject'])

        if topic_index is not None:  # Topic selected; browsing text pages
            try:
                topic = topics[topic_index]
            except:
                if safe:
                    return
                raise CBException(
                    "Invalid topic index. Must be between 1 and {}.".format(len(topics)))
            if not 0 <= page < len(topic[1]):
                if safe:
                    return
                raise CBException(
                    "Invalid page number. Must be between 1 and {}.".format(len(topic[1])))
            text = topic[1][page].format(invoker=invoker, base_invoker=base_invoker)
            crumbs += '\t→\t{}'.format(topic[0])
            return crumbs, text, page, len(topic[1]) - 1

        else:  # Subject selected; browsing topics
            total_topic_pages = int((len(topics)-1) / MAX_E)
            if not 0 <= page <= total_topic_pages:
                if safe:
                    return
                raise CBException(
                    "Invalid topic page number. Must be between 1 and {}.".format(
                        total_topic_pages+1))
            topics = topics[page*MAX_E:(page + 1)*MAX_E]
            raw_list = []
            for index, topic in enumerate(topics):
                raw_list.append('{} {}'.format(numeric_words[index + 1], topic[0]))
            topic_listing = '\n'.join(raw_list)
            return crumbs, topic_listing, page, total_topic_pages

    else:  # No subject_id given; get general subject listing
        total_subject_pages = int((len(subjects)-1) / MAX_E)
        if not 0 <= page <= total_subject_pages:
            if safe:
                return
            raise CBException(
                "Invalid subject page. Must be between 1 and {}.".format(total_subject_pages+1))
        subjects = subjects[page*MAX_E:(page + 1)*MAX_E]
        raw_list = []
        for index, subject_pair in enumerate(subjects):
            raw_list.append('{} {}'.format(numeric_words[index + 1], subject_pair['subject']))
        subject_listing = '\n'.join(raw_list)
        return crumbs, subject_listing, page, total_subject_pages
Exemple #21
0
def get_help(
        bot, category_id=None, command_index=None, subcommand_index=None,
        page=None, guild=None, safe=True, using_menu=True, elevation=0):
    """
    Gets the help entry depending on depth. Help me.
    """
    MAX_E = 5
    invoker = utilities.get_invoker(bot, guild=guild)
    base_invoker = utilities.get_invoker(bot)
    crumbs = 'Help menu'
    if page is None:
        page = 0

    categories = OrderedDict([('Core', [])])
    for command in bot.commands.values():
        if isinstance(command, commands.Command):
            if not command.hidden or elevation >= 3:
                if command.category in categories:
                    categories[command.category].append(command)
                else:
                    categories[command.category] = [command]
    categories = OrderedDict(sorted(list(categories.items())))

    if category_id is not None:  # Category selected; browsing commands listing
        try:
            category_index = int(category_id)
            assert category_index >= 0  # TODO: Better checking
        except ValueError:  # Category text given
            category_name = category_id.title()
            if category_name not in categories:
                if safe:
                    return
                raise CBException("Invalid category")
            commands_listing = sorted(categories[category_name])
        else:
            categories_listing = list(categories.values())
            try:
                commands_listing = sorted(categories_listing[category_index])
            except:
                if safe:
                    return
                raise CBException(
                    "Invalid category index. Must be between 1 and {}".format(
                        len(categories_listing)))
            category_name = list(categories)[category_index]
        crumbs += '\t→\t{}'.format(category_name)

        if command_index is not None:  # Command selected; browsing subcommands
            try:
                command = commands_listing[command_index]
            except:
                if safe:
                    return
                raise CBException(
                    "Invalid command index. Must be between 1 and {}.".format(
                        len(commands_listing)))
            crumbs += '\t→\t{}'.format(command.base)
            subcommands = command.subcommands

            if subcommand_index is not None:  # Subcommand selected; browsing detailed help
                try:
                    subcommand = subcommands[subcommand_index]
                except:
                    if safe:
                        return
                    raise CBException(
                        "Invalid subcommand index. Must be between 1 and {}.".format(
                            len(subcommands)))
                crumbs += '\t→\tSubcommand {}'.format(subcommand.index + 1)
                if using_menu:  # Add crumbs
                    embed_fields = [(crumbs, '\u200b')] + subcommand.help_embed_fields
                else:
                    embed_fields = subcommand.help_embed_fields
                return embed_fields, 0, 0

            else:  # No subcommand selected; browsing command still
                total_pages = int((len(command.help_lines)-1) / MAX_E)
                if not 0 <= page <= total_pages:
                    if safe:
                        return
                    raise CBException(
                            "Invalid subcommand page. Must be between 1 and {}".format(
                                total_pages+1))
                if using_menu:  # Add numbers
                    help_lines = command.help_lines[page*MAX_E:(page + 1)*MAX_E]
                    raw_list = []
                    for index, entry in enumerate(help_lines):
                        raw_list.append('{} {}'.format(numeric_words[index + 1], entry))
                    embed_fields = [(crumbs, '\u200b')] + command.help_embed_fields
                    embed_fields[command.usage_embed_index + 1] = ('Usage:', '\n'.join(raw_list))
                else:
                    embed_fields = command.help_embed_fields
                return embed_fields, page, total_pages

        else:  # No command selected; browsing commands listing still
            total_pages = int((len(commands_listing)-1) / MAX_E)
            if not 0 <= page <= total_pages:
                if safe:
                    return
                raise CBException(
                    "Invalid command page. Must be between 1 and {}".format(total_pages+1))
            commands_listing = commands_listing[page*MAX_E:(page + 1)*MAX_E]
            raw_list = []
            for index, command in enumerate(commands_listing):
                raw_list.append('{} **`{}`** -- {}'.format(
                    numeric_words[index + 1], command.base, command.description))
            embed_fields = [(crumbs, '\n'.join(raw_list))]
            return embed_fields, page, total_pages

    else:  # Nothing selected. Get category listing
        total_pages = int((len(categories)-1) / MAX_E)
        if not 0 <= page <= total_pages:
            if safe:
                return
            raise CBException(
                "Invalid category page. Must be between 1 and {}".format(total_pages+1))
        category_pairs = list(categories.items())[page*MAX_E:(page + 1)*MAX_E]
        raw_list = []
        for index, category_pair in enumerate(category_pairs):
            category_name, category_commands = category_pair
            peek_commands = [command.base for command in category_commands[:3]]
            if len(category_commands) > 3:
                peek_commands.append('...')
            peek = '[`{}`]'.format('`, `'.join(peek_commands))
            raw_list.append('{} **{}**\n\t\t{}'.format(
                numeric_words[index + 1], category_name, peek))
        embed_fields = [(crumbs, '\n'.join(raw_list))]
        return embed_fields, page, total_pages

    pass
Exemple #22
0
def get_manual(bot,
               subject_id=None,
               topic_index=None,
               page=None,
               guild=None,
               safe=True):
    """
    Gets the given manual entry depending on depth.

    If subject is not provided: returns a tuple: ('Manual menu', subject_listing)
    If subject is provided: return a tuple: (subject_name, topic_listing)
    If topic is provivded: return a tuple: (topic_name, text)
    All return values also include (1 indexed): (page_number, total_pages, crumbs)
    These return values are to be used as embed fields.
    """
    MAX_E = 5
    invoker = utilities.get_invoker(bot, guild=guild)
    base_invoker = utilities.get_invoker(bot)
    crumbs = 'Manual menu'
    if page is None:
        page = 0

    subjects = list(bot.manuals.values())

    if subject_id is not None:  # Subject selected; browsing topics
        try:
            subject_index = int(subject_id)
            assert subject_index >= 0  # TODO: Better checking
        except ValueError:  # Subject text given
            subject_name = subject_id.lower()
            if subject_name not in bot.manuals:
                if safe:
                    return
                raise CBException("Invalid subject.")
            subject = bot.manuals[subject_name]
        else:  # Subject index given
            try:
                subject = subjects[subject_index]
            except IndexError:
                if safe:
                    return
                raise CBException(
                    "Invalid subject index. Must be between 1 and {}.".format(
                        len(bot.manuals)))
        topics = subject['topics']
        crumbs += '\t→\t{}'.format(subject['subject'])

        if topic_index is not None:  # Topic selected; browsing text pages
            try:
                topic = topics[topic_index]
            except:
                if safe:
                    return
                raise CBException(
                    "Invalid topic index. Must be between 1 and {}.".format(
                        len(topics)))
            if not 0 <= page < len(topic[1]):
                if safe:
                    return
                raise CBException(
                    "Invalid page number. Must be between 1 and {}.".format(
                        len(topic[1])))
            text = topic[1][page].format(invoker=invoker,
                                         base_invoker=base_invoker)
            crumbs += '\t→\t{}'.format(topic[0])
            return crumbs, text, page, len(topic[1]) - 1

        else:  # Subject selected; browsing topics
            total_topic_pages = int((len(topics) - 1) / MAX_E)
            if not 0 <= page <= total_topic_pages:
                if safe:
                    return
                raise CBException(
                    "Invalid topic page number. Must be between 1 and {}.".
                    format(total_topic_pages + 1))
            topics = topics[page * MAX_E:(page + 1) * MAX_E]
            raw_list = []
            for index, topic in enumerate(topics):
                raw_list.append('{} {}'.format(numeric_words[index + 1],
                                               topic[0]))
            topic_listing = '\n'.join(raw_list)
            return crumbs, topic_listing, page, total_topic_pages

    else:  # No subject_id given; get general subject listing
        total_subject_pages = int((len(subjects) - 1) / MAX_E)
        if not 0 <= page <= total_subject_pages:
            if safe:
                return
            raise CBException(
                "Invalid subject page. Must be between 1 and {}.".format(
                    total_subject_pages + 1))
        subjects = subjects[page * MAX_E:(page + 1) * MAX_E]
        raw_list = []
        for index, subject_pair in enumerate(subjects):
            raw_list.append('{} {}'.format(numeric_words[index + 1],
                                           subject_pair['subject']))
        subject_listing = '\n'.join(raw_list)
        return crumbs, subject_listing, page, total_subject_pages
Exemple #23
0
async def match_subcommand(bot, command, parameters, message, match_closest=False):
    """Matches the given parameters to a valid subcommand from the command.
    Returns a tuple of the subcommand, options, and arguments.

    If match_closest is True, returns the closest matching subcommand or None.
    No processing (conversion, checking) is done, and returns only the subcommand or None.
    """

    parameters, quoted_indices = split_parameters(parameters, quote_list=True)
    closest_index = -1
    closest_index_matches = 0
    closest_index_error = None
    stripped_parameters = parameters[::2]
    for subcommand in command.subcommands:

        current_index = 0
        matches = 0
        options = {}
        arguments = []
        last_opt_index = -1
        arg_index = -1
        used_opts = []
        exhausted_opts = len(subcommand.opts) == 0
        not_found_error = None

        while current_index < len(stripped_parameters):
            current = stripped_parameters[current_index]

            if not exhausted_opts:  # Check opts
                if current_index * 2 in quoted_indices:  # Quoted elements are always arguments
                    exhausted_opts = True

                found_opt = subcommand.opts.get(current.lower(), None)
                if not exhausted_opts and found_opt:

                    if subcommand.strict_syntax:  # Check strict syntax
                        if found_opt.index < last_opt_index:  # Syntax out of order
                            exhausted_opts = True
                        else:
                            last_opt_index = found_opt.index

                    if not exhausted_opts:
                        if found_opt.name in options:  # Duplicate. Skip to args
                            exhausted_opts = True
                        else:  # Check for attached argument
                            if found_opt.attached:  # Required attached argument
                                if current_index + 1 >= len(stripped_parameters):
                                    not_found_error = (
                                        'Option {opt.name_string} requires an attached parameter, '
                                        '{opt.attached_string}.'.format(opt=found_opt))
                                    matches += 3
                                else:
                                    current_index += 1
                                    options[found_opt.name] = stripped_parameters[current_index]
                                    matches += 6
                            else:  # No attached argument required
                                options[found_opt.name] = None
                                matches += 5
                            used_opts.append(found_opt)

                else:  # Option not found. Skip to args
                    exhausted_opts = True

                if exhausted_opts:  # No more matching opts - check for optional opts
                    current_index -= 1  # Search args where we left off
                    remaining_opts = [o for o in subcommand.opts.values() if o not in used_opts]
                    for opt in remaining_opts:
                        if opt.optional:
                            matches += 1
                            if opt.always_include:
                                options[opt.name] = opt.default
                        else:  # Not optional. Unfit subcommand
                            not_found_error = 'Option {} is required.'.format(opt.name_string)
                            break

            else:  # Check args
                arg_index += 1
                if arg_index >= len(subcommand.args):  # Too many arguments
                    not_found_error = 'Too many arguments.'
                else:
                    matches += 1
                    arg = subcommand.args[arg_index]
                    if arg.argtype in (ArgTypes.SINGLE, ArgTypes.OPTIONAL):
                        arguments.append(current)
                    else:  # Instant finish grouped arguments
                        if arg.argtype in (ArgTypes.SPLIT, ArgTypes.SPLIT_OPTIONAL):
                            arguments += stripped_parameters[current_index:]
                        else:  # Merged
                            split_arguments = []
                            quote_index = current_index * 2
                            for segment in parameters[current_index * 2:]:
                                if quote_index in quoted_indices:  # Add quotes back in
                                    split_arguments.append('"{}"'.format(segment))
                                else:
                                    split_arguments.append(segment)
                                quote_index += 1
                            arguments += [''.join(split_arguments)]
                        break

            if not_found_error:  # Skip rest of loop and evaluate matches
                break

            current_index += 1

        # Finished opt/arg while loop
        if not not_found_error and not exhausted_opts:  # Opts remain
            remaining_opts = [o for o in subcommand.opts.values() if o not in used_opts]
            for opt in remaining_opts:
                if opt.optional:
                    matches += 1
                    if opt.always_include:
                        options[opt.name] = opt.default
                else:  # Not optional. Unfit subcommand
                    not_found_error = 'Option {} is required.'.format(opt.name_string)
                    break
        if not not_found_error and arg_index < len(subcommand.args) - 1:  # Optional arguments
            arg_index += 1
            arg = subcommand.args[arg_index]
            if arg.argtype is ArgTypes.OPTIONAL:
                matches += 1
                while (arg and arg.argtype is ArgTypes.OPTIONAL and
                        arg_index < len(subcommand.args)):
                    arguments.append(arg.default)
                    arg_index += 1
                    try:
                        arg = subcommand.args[arg_index]
                    except:
                        arg = None
            if arg and arg.argtype in (ArgTypes.SPLIT_OPTIONAL, ArgTypes.MERGED_OPTIONAL):
                matches += 1
                arguments.append(arg.default)
            elif arg:
                not_found_error = 'No value given for argument {}.'.format(arg.help_string)

        if not not_found_error:  # Check for message attachment
            if subcommand.attaches:
                if message.attachments or subcommand.attaches.optional:
                    matches += 6
                else:
                    not_found_error = 'Missing attachment **__`{name}`__**'.format(
                        name=subcommand.attaches.name)
            elif message.attachments:  # No attachment argument, but attachment was provided
                not_found_error = 'No attachment required, but one was given.'

        if not_found_error:  # Find closest subcommand
            if matches > closest_index_matches:
                closest_index = subcommand.index
                closest_index_matches = matches
                closest_index_error = not_found_error
        else:  # Subcommand found. Convert and check
            if subcommand.confidence_threshold is not None:  # Confidence threshold
                if closest_index_matches >= subcommand.confidence_threshold:
                    continue  # Skip valid match due to low confidence

            # No additional processing
            if match_closest:
                if matches <= 1 and matches < closest_index_matches:  # No confidence
                    continue
                else:
                    return subcommand

            # Cannot match parameters in a direct message if disabled
            elif not subcommand.allow_direct and isinstance(message.channel, PrivateChannel):
                return subcommand, {}, []

            # Fill in options and arguments
            else:
                for option_name, value in options.items():  # Check options
                    current_opt = subcommand.opts[option_name]
                    if value is not None:
                        new_value = await current_opt.convert_and_check(bot, message, value)
                        if new_value is not None:
                            options[option_name] = new_value
                for index, pair in enumerate(zip(subcommand.args, arguments)):  # Check arguments
                    arg, value = pair
                    if (value is not None
                            or arg.argtype in (ArgTypes.SINGLE, ArgTypes.SPLIT, ArgTypes.MERGED)):
                        if arg.argtype not in (ArgTypes.SINGLE, ArgTypes.OPTIONAL):
                            new_values = await arg.convert_and_check(
                                bot, message, arguments[index:])
                            arguments = arguments[:index] + new_values
                            break
                        else:
                            new_value = await arg.convert_and_check(bot, message, value)
                            arguments[index] = new_value

                return subcommand, options, arguments

    # Looped through all subcommands. Not found
    if closest_index == -1 or closest_index_matches <= 1:  # Low confidence
        guess = command
    else:
        guess = command.subcommands[closest_index]
    if match_closest:
        return guess
    else:
        if isinstance(guess, commands.SubCommand):
            syntax_error = 'Invalid syntax: {}'.format(closest_index_error)
        else:
            guess = command
            syntax_error = 'Invalid syntax.'
        invoker = utilities.get_invoker(bot, guild=message.guild)
        raise CBException(
            syntax_error, embed_fields=guess.help_embed_fields, embed_format={'invoker': invoker})
Exemple #24
0
def get_help(bot,
             category_id=None,
             command_index=None,
             subcommand_index=None,
             page=None,
             guild=None,
             safe=True,
             using_menu=True,
             elevation=0):
    """
    Gets the help entry depending on depth. Help me.
    """
    MAX_E = 5
    invoker = utilities.get_invoker(bot, guild=guild)
    base_invoker = utilities.get_invoker(bot)
    crumbs = 'Help menu'
    if page is None:
        page = 0

    categories = OrderedDict([('Core', [])])
    for command in bot.commands.values():
        if isinstance(command, commands.Command):
            if not command.hidden or elevation >= 3:
                if command.category in categories:
                    categories[command.category].append(command)
                else:
                    categories[command.category] = [command]
    categories = OrderedDict(sorted(list(categories.items())))

    if category_id is not None:  # Category selected; browsing commands listing
        try:
            category_index = int(category_id)
            assert category_index >= 0  # TODO: Better checking
        except ValueError:  # Category text given
            category_name = subject_id.title()
            if category_name not in categories:
                if safe:
                    return
                raise CBException("Invalid category")
            commands_listing = sorted(categories[category_name])
        else:
            categories_listing = list(categories.values())
            try:
                commands_listing = sorted(categories_listing[category_index])
            except:
                if safe:
                    return
                raise CBException(
                    "Invalid category index. Must be between 1 and {}".format(
                        len(categories_listing)))
            category_name = list(categories)[category_index]
        crumbs += '\t→\t{}'.format(category_name)

        if command_index is not None:  # Command selected; browsing subcommands
            try:
                command = commands_listing[command_index]
            except:
                if safe:
                    return
                raise CBException(
                    "Invalid command index. Must be between 1 and {}.".format(
                        len(commands_listing)))
            crumbs += '\t→\t{}'.format(command.base)
            subcommands = command.subcommands

            if subcommand_index is not None:  # Subcommand selected; browsing detailed help
                try:
                    subcommand = subcommands[subcommand_index]
                except:
                    if safe:
                        return
                    raise CBException(
                        "Invalid subcommand index. Must be between 1 and {}.".
                        format(len(subcommands)))
                crumbs += '\t→\tSubcommand {}'.format(subcommand.index + 1)
                if using_menu:  # Add crumbs
                    embed_fields = [(crumbs, '\u200b')
                                    ] + subcommand.help_embed_fields
                else:
                    embed_fields = subcommand.help_embed_fields
                return embed_fields, 0, 0

            else:  # No subcommand selected; browsing command still
                total_pages = int((len(command.help_lines) - 1) / MAX_E)
                if not 0 <= page <= total_pages:
                    if safe:
                        return
                    raise CBException(
                        "Invalid subcommand page. Must be between 1 and {}".
                        format(total_pages + 1))
                if using_menu:  # Add numbers
                    help_lines = command.help_lines[page * MAX_E:(page + 1) *
                                                    MAX_E]
                    raw_list = []
                    for index, entry in enumerate(help_lines):
                        raw_list.append('{} {}'.format(
                            numeric_words[index + 1], entry))
                    embed_fields = [(crumbs, '\u200b')
                                    ] + command.help_embed_fields
                    embed_fields[command.usage_embed_index +
                                 1] = ('Usage:', '\n'.join(raw_list))
                else:
                    embed_fields = command.help_embed_fields
                return embed_fields, page, total_pages

        else:  # No command selected; browsing commands listing still
            total_pages = int((len(commands_listing) - 1) / MAX_E)
            if not 0 <= page <= total_pages:
                if safe:
                    return
                raise CBException(
                    "Invalid command page. Must be between 1 and {}".format(
                        total_pages + 1))
            commands_listing = commands_listing[page * MAX_E:(page + 1) *
                                                MAX_E]
            raw_list = []
            for index, command in enumerate(commands_listing):
                raw_list.append('{} **`{}`** -- {}'.format(
                    numeric_words[index + 1], command.base,
                    command.description))
            embed_fields = [(crumbs, '\n'.join(raw_list))]
            return embed_fields, page, total_pages

    else:  # Nothing selected. Get category listing
        total_pages = int((len(categories) - 1) / MAX_E)
        if not 0 <= page <= total_pages:
            if safe:
                return
            raise CBException(
                "Invalid category page. Must be between 1 and {}".format(
                    total_pages + 1))
        category_pairs = list(categories.items())[page * MAX_E:(page + 1) *
                                                  MAX_E]
        raw_list = []
        for index, category_pair in enumerate(category_pairs):
            category_name, category_commands = category_pair
            peek_commands = [command.base for command in category_commands[:3]]
            if len(category_commands) > 3:
                peek_commands.append('...')
            peek = '[`{}`]'.format('`, `'.join(peek_commands))
            raw_list.append('{} **{}**\n\t\t{}'.format(
                numeric_words[index + 1], category_name, peek))
        embed_fields = [(crumbs, '\n'.join(raw_list))]
        return embed_fields, page, total_pages

    pass