Example #1
0
    def handle_command(self, event):
        """Handle command messages"""

        # is commands_enabled?

        config_commands_enabled = self.bot.get_config_suboption(event.conv_id, 'commands_enabled')
        tagged_ignore = "ignore" in self.bot.tags.useractive(event.user_id.chat_id, event.conv_id)

        if not config_commands_enabled or tagged_ignore:
            admins_list = self.bot.get_config_suboption(event.conv_id, 'admins') or []
            # admins always have commands enabled
            if event.user_id.chat_id not in admins_list:
                return

        # ensure bot alias is always a list
        if not isinstance(self.bot_command, list):
            self.bot_command = [self.bot_command]

        # check that a bot alias is used e.g. /syrupbot
        if not event.text.split()[0].lower() in self.bot_command:
            return

        # Parse message
        event.text = event.text.replace(u'\xa0', u' ') # convert non-breaking space in Latin1 (ISO 8859-1)
        try:
            line_args = shlex.split(event.text, posix=False)
        except Exception as e:
            logger.exception(e)
            yield from self.bot.coro_send_message(event.conv, _("{}: {}").format(
                event.user.full_name, str(e)))
            return

        # Test if command length is sufficient
        if len(line_args) < 2:
            yield from self.bot.coro_send_message(event.conv, _('{}: Missing parameter(s)').format(
                event.user.full_name))
            return

        commands = command.get_available_commands(self.bot, event.user.id_.chat_id, event.conv_id)

        supplied_command = line_args[1].lower()
        if supplied_command in commands["user"]:
            pass
        elif supplied_command in commands["admin"]:
            pass
        elif supplied_command in command.commands:
            yield from command.blocked_command(self.bot, event, *line_args[1:])
            return
        else:
            yield from command.unknown_command(self.bot, event, *line_args[1:])
            return

        # Run command
        results = yield from command.run(self.bot, event, *line_args[1:])

        if "acknowledge" in dir(event):
            for id in event.acknowledge:
                yield from self.run_reprocessor(id, event, results)
Example #2
0
def help(bot, event, cmd=None, *args):
    """list supported commands, /devilbot help <command> will show additional details"""
    help_lines = []
    link_to_guide = bot.get_config_suboption(event.conv_id, 'link_to_guide')
    admins_list = bot.get_config_suboption(event.conv_id, 'admins')

    if not cmd or (cmd=="impersonate" and event.user.id_.chat_id in admins_list):

        help_chat_id = event.user.id_.chat_id
        help_conv_id = event.conv_id

        if cmd == "impersonate":
            if len(args) == 1:
                [help_chat_id] = args
            elif len(args) == 2:
                [help_chat_id, help_conv_id] = args
            else:
                raise ValueError("impersonation: supply chat id and optional conversation id")

            help_lines.append(_('<b>Impersonation:</b><br />'
                                '<b><pre>{}</pre></b><br />'
                                '<b><pre>{}</pre></b><br />').format( help_chat_id,
                                                                      help_conv_id ))

        commands = command.get_available_commands(bot, help_chat_id, help_conv_id)
        commands_admin = commands["admin"]
        commands_nonadmin = commands["user"]

        if len(commands_nonadmin) > 0:
            help_lines.append(_('<b>User commands:</b>'))
            help_lines.append(', '.join(sorted(commands_nonadmin)))

        if link_to_guide:
            help_lines.append('')
            help_lines.append(_('<i>For more information, please see: {}</i>').format(link_to_guide))

        if len(commands_admin) > 0:
            help_lines.append('')
            help_lines.append(_('<b>Admin commands:</b>'))
            help_lines.append(', '.join(sorted(commands_admin)))
    else:
        if cmd in command.commands:
            command_fn = command.commands[cmd]
        elif cmd.lower() in command.commands:
            command_fn = command.commands[cmd.lower()]
        else:
            yield from command.unknown_command(bot, event)
            return

        help_lines.append("<b>{}</b>: {}".format(command_fn.__name__, command_fn.__doc__))

    yield from bot.coro_send_to_user_and_conversation(
        event.user.id_.chat_id,
        event.conv_id,
        "<br />".join(help_lines), # via private message
        _("<i>{}, I've sent you some help ;)</i>") # public message
            .format(event.user.full_name))
Example #3
0
def help(bot, event, cmd=None, *args):
    """list supported commands, /bot help <command> will show additional details"""
    help_lines = []
    link_to_guide = bot.get_config_suboption(event.conv_id, 'link_to_guide')
    admins_list = bot.get_config_suboption(event.conv_id, 'admins')

    if not cmd or (cmd=="impersonate" and event.user.id_.chat_id in admins_list):

        help_chat_id = event.user.id_.chat_id
        help_conv_id = event.conv_id

        if cmd == "impersonate":
            if len(args) == 1:
                [help_chat_id] = args
            elif len(args) == 2:
                [help_chat_id, help_conv_id] = args
            else:
                raise ValueError("impersonation: supply chat id and optional conversation id")

            help_lines.append(_('<b>Impersonation:</b><br />'
                                '<b><pre>{}</pre></b><br />'
                                '<b><pre>{}</pre></b><br />').format( help_chat_id,
                                                                      help_conv_id ))

        commands = command.get_available_commands(bot, help_chat_id, help_conv_id)
        commands_admin = commands["admin"]
        commands_nonadmin = commands["user"]

        if len(commands_nonadmin) > 0:
            help_lines.append(_('<b>User commands:</b>'))
            help_lines.append(', '.join(sorted(commands_nonadmin)))

        if link_to_guide:
            help_lines.append('')
            help_lines.append(_('<i>For more information, please see: {}</i>').format(link_to_guide))

        if len(commands_admin) > 0:
            help_lines.append('')
            help_lines.append(_('<b>Admin commands:</b>'))
            help_lines.append(', '.join(sorted(commands_admin)))
    else:
        if cmd in command.commands:
            command_fn = command.commands[cmd]
        elif cmd.lower() in command.commands:
            command_fn = command.commands[cmd.lower()]
        else:
            yield from command.unknown_command(bot, event)
            return

        help_lines.append("<b>{}</b>: {}".format(command_fn.__name__, command_fn.__doc__))

    yield from bot.coro_send_to_user_and_conversation(
        event.user.id_.chat_id,
        event.conv_id,
        "<br />".join(help_lines), # via private message
        _("<i>{}, I've sent you some help ;)</i>") # public message
            .format(event.user.full_name))
Example #4
0
def help(bot, event, cmd=None, *args):
    """list supported commands, /bot help <command> will show additional details"""
    help_lines = []
    link_to_guide = bot.get_config_suboption(event.conv_id, 'link_to_guide')
    admins_list = bot.get_config_suboption(event.conv_id, 'admins')

    help_chat_id = event.user.id_.chat_id
    help_conv_id = event.conv_id
    commands = command.get_available_commands(bot, help_chat_id, help_conv_id)
    commands_admin = commands["admin"]
    commands_nonadmin = commands["user"]

    if not cmd or (cmd == "impersonate"
                   and event.user.id_.chat_id in admins_list):

        if cmd == "impersonate":
            if len(args) == 1:
                [help_chat_id] = args
            elif len(args) == 2:
                [help_chat_id, help_conv_id] = args
            else:
                raise ValueError(
                    "impersonation: supply chat id and optional conversation id"
                )

            help_lines.append(
                _('<b>Impersonation:</b><br />'
                  '<b><pre>{}</pre></b><br />'
                  '<b><pre>{}</pre></b><br />').format(help_chat_id,
                                                       help_conv_id))

        if len(commands_nonadmin) > 0:
            help_lines.append(_('<b>User commands:</b>'))
            help_lines.append(', '.join(sorted(commands_nonadmin)))

        if link_to_guide:
            help_lines.append('')
            help_lines.append(
                _('<i>For more information, please see: {}</i>').format(
                    link_to_guide))

        if len(commands_admin) > 0:
            help_lines.append('')
            help_lines.append(_('<b>Admin commands:</b>'))
            help_lines.append(', '.join(sorted(commands_admin)))

        help_lines.append("")
        help_lines.append("<b>Command-specific help:</b>")
        help_lines.append("/bot help <command name>")

        bot_aliases = [
            _alias for _alias in bot._handlers.bot_command if len(_alias) < 9
        ]
        if len(bot_aliases) > 1:
            help_lines.append("")
            help_lines.append("<b>My short-hand names:</b>")
            help_lines.append(', '.join(sorted(bot_aliases)))

    else:
        if cmd in command.commands and (cmd in commands_admin
                                        or cmd in commands_nonadmin):
            command_fn = command.commands[cmd]
        elif cmd.lower() in command.commands and (cmd in commands_admin
                                                  or cmd in commands_nonadmin):
            command_fn = command.commands[cmd.lower()]
        else:
            yield from command.unknown_command(bot, event)
            return

        if "__doc__" in dir(command_fn) and command_fn.__doc__:
            _docstring = command_fn.__doc__.strip()
        else:
            _docstring = "_{}_".format(_("command help not available"))
        """docstrings: apply (very) limited markdown-like formatting to command help"""

        # simple bullet lists
        _docstring = re.sub(r'\n +\* +', '\n* ', _docstring)
        """docstrings: handle generic whitespace
            manually parse line-breaks: single break -> space; multiple breaks -> paragraph
            XXX: the markdown parser is iffy on line-break processing"""

        # turn standalone linebreaks into space, preserves multiple linebreaks
        _docstring = re.sub(r"(?<!\n)\n(?= *[^ \t\n\r\f\v\*])", " ",
                            _docstring)
        # convert multiple consecutive spaces into single space
        _docstring = re.sub(r" +", " ", _docstring)
        # convert consecutive linebreaks into double linebreak (pseudo-paragraph)
        _docstring = re.sub(r" *\n\n+ *(?!\*)", "\n\n", _docstring)

        help_lines.append("<b>{}</b>: {}".format(command_fn.__name__,
                                                 _docstring))

    # replace /bot with the first alias in the command handler
    # XXX: [botalias] maintained backward compatibility, please avoid using it
    help_lines = [
        re.sub(r"(?<!\S)\/bot(?!\S)", bot._handlers.bot_command[0], _line)
        for _line in help_lines
    ]

    yield from bot.coro_send_to_user_and_conversation(
        event.user.id_.chat_id,
        event.conv_id,
        "<br />".join(help_lines),  # via private message
        _("<i>{}, I've sent you some help ;), however if we aren't already chatting, we should probably start, go ahead and accept my chat request</i>"
          )  # public message
        .format(event.user.full_name))
Example #5
0
def help(bot, event, cmd=None, *args):
    """list supported commands, /bot help <command> will show additional details"""
    help_lines = []
    link_to_guide = bot.get_config_suboption(event.conv_id, 'link_to_guide')
    admins_list = bot.get_config_suboption(event.conv_id, 'admins')

    help_chat_id = event.user.id_.chat_id
    help_conv_id = event.conv_id
    commands = command.get_available_commands(bot, help_chat_id, help_conv_id)
    commands_admin = commands["admin"]
    commands_nonadmin = commands["user"]

    if not cmd or (cmd=="impersonate" and event.user.id_.chat_id in admins_list):

        if cmd == "impersonate":
            if len(args) == 1:
                [help_chat_id] = args
            elif len(args) == 2:
                [help_chat_id, help_conv_id] = args
            else:
                raise ValueError("impersonation: supply chat id and optional conversation id")

            help_lines.append(_('<b>Impersonation:</b><br />'
                                '<b><pre>{}</pre></b><br />'
                                '<b><pre>{}</pre></b><br />').format( help_chat_id,
                                                                      help_conv_id ))

        if len(commands_nonadmin) > 0:
            help_lines.append(_('<b>User commands:</b>'))
            help_lines.append(', '.join(sorted(commands_nonadmin)))

        if link_to_guide:
            help_lines.append('')
            help_lines.append(_('<i>For more information, please see: {}</i>').format(link_to_guide))

        if len(commands_admin) > 0:
            help_lines.append('')
            help_lines.append(_('<b>Admin commands:</b>'))
            help_lines.append(', '.join(sorted(commands_admin)))

        help_lines.append("")
        help_lines.append("<b>Command-specific help:</b>")
        help_lines.append("/bot help <command name>")

        bot_aliases = [ _alias for _alias in bot._handlers.bot_command if len(_alias) < 9 ]
        if len(bot_aliases) > 1:
            help_lines.append("")
            help_lines.append("<b>My short-hand names:</b>")
            help_lines.append(', '.join(sorted(bot_aliases)))

    else:
        if cmd in command.commands and (cmd in commands_admin or cmd in commands_nonadmin):
            command_fn = command.commands[cmd]
        elif cmd.lower() in command.commands and (cmd in commands_admin or cmd in commands_nonadmin):
            command_fn = command.commands[cmd.lower()]
        else:
            yield from command.unknown_command(bot, event)
            return

        if "__doc__" in dir(command_fn) and command_fn.__doc__:
            _docstring = command_fn.__doc__.strip()
        else:
            _docstring = "_{}_".format(_("command help not available"))

        """docstrings: apply (very) limited markdown-like formatting to command help"""

        # simple bullet lists
        _docstring = re.sub(r'\n +\* +', '\n* ', _docstring)

        """docstrings: handle generic whitespace
            manually parse line-breaks: single break -> space; multiple breaks -> paragraph
            XXX: the markdown parser is iffy on line-break processing"""

        # turn standalone linebreaks into space, preserves multiple linebreaks
        _docstring = re.sub(r"(?<!\n)\n(?= *[^ \t\n\r\f\v\*])", " ", _docstring)
        # convert multiple consecutive spaces into single space
        _docstring = re.sub(r" +", " ", _docstring)
        # convert consecutive linebreaks into double linebreak (pseudo-paragraph)
        _docstring = re.sub(r" *\n\n+ *(?!\*)", "\n\n", _docstring)

        help_lines.append("<b>{}</b>: {}".format(command_fn.__name__, _docstring))

    # replace /bot with the first alias in the command handler
    # XXX: [botalias] maintained backward compatibility, please avoid using it
    help_lines = [ re.sub(r"(?<!\S)\/bot(?!\S)", bot._handlers.bot_command[0], _line)
                   for _line in help_lines ]

    yield from bot.coro_send_to_user_and_conversation(
        event.user.id_.chat_id,
        event.conv_id,
        "<br />".join(help_lines), # via private message
        _("<i>{}, I've sent you some help ;), however if we aren't already chatting, we should probably start, go ahead and accept my chat request</i>") # public message
            .format(event.user.full_name))
Example #6
0
def config(bot, event, cmd=None, *args):
    """displays or modifies the configuration
        Parameters: /bot config get [key] [subkey] [...]
                    /bot config set [key] [subkey] [...] [value]
                    /bot config append [key] [subkey] [...] [value]
                    /bot config remove [key] [subkey] [...] [value]"""

    # consume arguments and differentiate beginning of a json array or object
    tokens = list(args)
    parameters = []
    value = []
    state = "key"
    for token in tokens:
        if token.startswith(("{", "[", '"', "'")):
            # apparent start of json array/object, consume into a single list item
            state = "json"
        if state == "key":
            parameters.append(token)
        elif state == "json":
            value.append(token)
        else:
            raise ValueError("unknown state")
    if value:
        parameters.append(" ".join(value))

    if cmd == 'get' or cmd is None:
        config_args = list(parameters)
        value = bot.config.get_by_path(config_args) if config_args else dict(bot.config)

    elif cmd == 'test':
        num_parameters = len(parameters)
        text_parameters = []
        last = num_parameters - 1
        for num, token in enumerate(parameters):
            if num == last:
                try:
                    json.loads(token)
                    token += " <b>(valid json)</b>"
                except ValueError:
                    token += " <em>(INVALID)</em>"
            text_parameters.append(str(num + 1) + ": " + token)
        text_parameters.insert(0, "<b>config test</b>")

        if num_parameters == 1:
            text_parameters.append(_("<em>note: testing single parameter as json</em>"))
        elif num_parameters < 1:
            yield from command.unknown_command(bot, event)
            return

        yield from bot.coro_send_message(event.conv, "<br />".join(text_parameters))
        return

    elif cmd == 'set':
        config_args = list(parameters[:-1])
        if len(parameters) >= 2:
            bot.config.set_by_path(config_args, json.loads(parameters[-1]))
            bot.config.save()
            value = bot.config.get_by_path(config_args)
        else:
            yield from command.unknown_command(bot, event)
            return

    elif cmd == 'append':
        config_args = list(parameters[:-1])
        if len(parameters) >= 2:
            value = bot.config.get_by_path(config_args)
            if isinstance(value, list):
                value.append(json.loads(parameters[-1]))
                bot.config.set_by_path(config_args, value)
                bot.config.save()
            else:
                value = _('append failed on non-list')
        else:
            yield from command.unknown_command(bot, event)
            return

    elif cmd == 'remove':
        config_args = list(parameters[:-1])
        if len(parameters) >= 2:
            value = bot.config.get_by_path(config_args)
            if isinstance(value, list):
                value.remove(json.loads(parameters[-1]))
                bot.config.set_by_path(config_args, value)
                bot.config.save()
            else:
                value = _('remove failed on non-list')
        else:
            yield from command.unknown_command(bot, event)
            return

    else:
        yield from command.unknown_command(bot, event)
        return

    if value is None:
        value = _('Parameter does not exist!')

    config_path = ' '.join(k for k in ['config'] + config_args)
    segments = [hangups.ChatMessageSegment('{}:'.format(config_path),
                                           is_bold=True),
                hangups.ChatMessageSegment('\n', hangups.SegmentType.LINE_BREAK)]
    segments.extend(text_to_segments(json.dumps(value, indent=2, sort_keys=True)))
    yield from bot.coro_send_message(event.conv, segments)
Example #7
0
    def handle_command(self, event):
        """Handle command messages"""

        # is commands_enabled?
        if not self.bot.get_config_suboption(event.conv_id,
                                             'commands_enabled'):
            admins_list = self.bot.get_config_suboption(
                event.conv_id, 'admins') or []
            # admins always have commands enabled
            if event.user_id.chat_id not in admins_list:
                return

        # ensure bot alias is always a list
        if not isinstance(self.bot_command, list):
            self.bot_command = [self.bot_command]

        # check that a bot alias is used e.g. /bot
        if not event.text.split()[0].lower() in self.bot_command:
            return

        # Parse message
        event.text = event.text.replace(
            u'\xa0', u' ')  # convert non-breaking space in Latin1 (ISO 8859-1)
        try:
            line_args = shlex.split(event.text, posix=False)
        except Exception as e:
            logger.exception(e)
            yield from self.bot.coro_send_message(
                event.conv,
                _("{}: {}").format(event.user.full_name, str(e)))
            return

        # Test if command length is sufficient
        if len(line_args) < 2:
            yield from self.bot.coro_send_message(
                event.conv,
                _('{}: Missing parameter(s)').format(event.user.full_name))
            return

        commands = command.get_available_commands(self.bot,
                                                  event.user.id_.chat_id,
                                                  event.conv_id)

        supplied_command = line_args[1].lower()
        if supplied_command in commands["user"]:
            pass
        elif supplied_command in commands["admin"]:
            pass
        elif supplied_command in command.commands:
            yield from command.blocked_command(self.bot, event, *line_args[1:])
            return
        else:
            yield from command.unknown_command(self.bot, event, *line_args[1:])
            return

        # Run command
        results = yield from command.run(self.bot, event, *line_args[1:])

        if "acknowledge" in dir(event):
            for id in event.acknowledge:
                yield from self.run_reprocessor(id, event, results)
Example #8
0
    def handle_command(self, event):
        """Handle command messages"""

        # is commands_enabled?

        config_commands_enabled = self.bot.get_config_suboption(
            event.conv_id, 'commands_enabled')
        tagged_ignore = "ignore" in self.bot.tags.useractive(
            event.user_id.chat_id, event.conv_id)

        if not config_commands_enabled or tagged_ignore:
            admins_list = self.bot.get_config_suboption(
                event.conv_id, 'admins') or []
            # admins always have commands enabled
            if event.user_id.chat_id not in admins_list:
                return

        # ensure bot alias is always a list
        if not isinstance(self.bot_command, list):
            self.bot_command = [self.bot_command]

        # check that a bot alias is used e.g. /bot
        if not event.text.split()[0].lower() in self.bot_command:
            if self.bot.conversations.catalog[event.conv_id][
                    "type"] == "ONE_TO_ONE" and self.bot.get_config_option(
                        'auto_alias_one_to_one'):
                event.text = u" ".join((
                    self.bot_command[0],
                    event.text))  # Insert default alias if not already present
            else:
                return

        # Parse message
        event.text = event.text.replace(
            u'\xa0', u' ')  # convert non-breaking space in Latin1 (ISO 8859-1)
        try:
            line_args = shlex.split(event.text, posix=False)
        except Exception as e:
            logger.exception(e)
            yield from self.bot.coro_send_message(
                event.conv,
                _("{}: {}").format(event.user.full_name, str(e)))
            return

        # Test if command length is sufficient
        if len(line_args) < 2:
            config_silent = bot.get_config_suboption(event.conv.id_,
                                                     'silentmode')
            tagged_silent = "silent" in bot.tags.useractive(
                event.user_id.chat_id, event.conv.id_)
            if not (config_silent or tagged_silent):
                yield from self.bot.coro_send_message(
                    event.conv,
                    _('{}: Missing parameter(s)').format(event.user.full_name))
            return

        commands = command.get_available_commands(self.bot,
                                                  event.user.id_.chat_id,
                                                  event.conv_id)

        supplied_command = line_args[1].lower()
        if supplied_command in commands["user"]:
            pass
        elif supplied_command in commands["admin"]:
            pass
        elif supplied_command in command.commands:
            yield from command.blocked_command(self.bot, event, *line_args[1:])
            return
        else:
            yield from command.unknown_command(self.bot, event, *line_args[1:])
            return

        # Run command
        results = yield from command.run(self.bot, event, *line_args[1:])

        if "acknowledge" in dir(event):
            for id in event.acknowledge:
                yield from self.run_reprocessor(id, event, results)
Example #9
0
def config(bot, event, cmd=None, *args):
    """displays or modifies the configuration
        Parameters: /devilbot config get [key] [subkey] [...]
                    /devilbot config set [key] [subkey] [...] [value]
                    /devilbot config append [key] [subkey] [...] [value]
                    /devilbot config remove [key] [subkey] [...] [value]"""

    # consume arguments and differentiate beginning of a json array or object
    tokens = list(args)
    parameters = []
    value = []
    state = "key"
    for token in tokens:
        if token.startswith(("{", "[", '"', "'")):
            # apparent start of json array/object, consume into a single list item
            state = "json"
        if state == "key":
            parameters.append(token)
        elif state == "json":
            value.append(token)
        else:
            raise ValueError("unknown state")
    if value:
        parameters.append(" ".join(value))

    if cmd == 'get' or cmd is None:
        config_args = list(parameters)
        value = bot.config.get_by_path(config_args) if config_args else dict(
            bot.config)

    elif cmd == 'test':
        num_parameters = len(parameters)
        text_parameters = []
        last = num_parameters - 1
        for num, token in enumerate(parameters):
            if num == last:
                try:
                    json.loads(token)
                    token += " <b>(valid json)</b>"
                except ValueError:
                    token += " <em>(INVALID)</em>"
            text_parameters.append(str(num + 1) + ": " + token)
        text_parameters.insert(0, "<b>config test</b>")

        if num_parameters == 1:
            text_parameters.append(
                _("<em>note: testing single parameter as json</em>"))
        elif num_parameters < 1:
            yield from command.unknown_command(bot, event)
            return

        yield from bot.coro_send_message(event.conv,
                                         "<br />".join(text_parameters))
        return

    elif cmd == 'set':
        config_args = list(parameters[:-1])
        if len(parameters) >= 2:
            bot.config.set_by_path(config_args, json.loads(parameters[-1]))
            bot.config.save()
            value = bot.config.get_by_path(config_args)
        else:
            yield from command.unknown_command(bot, event)
            return

    elif cmd == 'append':
        config_args = list(parameters[:-1])
        if len(parameters) >= 2:
            value = bot.config.get_by_path(config_args)
            if isinstance(value, list):
                value.append(json.loads(parameters[-1]))
                bot.config.set_by_path(config_args, value)
                bot.config.save()
            else:
                value = _('append failed on non-list')
        else:
            yield from command.unknown_command(bot, event)
            return

    elif cmd == 'remove':
        config_args = list(parameters[:-1])
        if len(parameters) >= 2:
            value = bot.config.get_by_path(config_args)
            if isinstance(value, list):
                value.remove(json.loads(parameters[-1]))
                bot.config.set_by_path(config_args, value)
                bot.config.save()
            else:
                value = _('remove failed on non-list')
        else:
            yield from command.unknown_command(bot, event)
            return

    else:
        yield from command.unknown_command(bot, event)
        return

    if value is None:
        value = _('Parameter does not exist!')

    config_path = ' '.join(k for k in ['config'] + config_args)
    segments = [
        hangups.ChatMessageSegment('{}:'.format(config_path), is_bold=True),
        hangups.ChatMessageSegment('\n', hangups.SegmentType.LINE_BREAK)
    ]
    segments.extend(
        text_to_segments(json.dumps(value, indent=2, sort_keys=True)))
    yield from bot.coro_send_message(event.conv, segments)