Queer European MD passionate about IT
Browse Source

Support for multilanguage /help command

A bot guide may be built automatically using @bot.command decorator.
Davte 5 years ago
parent
commit
e94794c988
5 changed files with 291 additions and 1 deletions
  1. 1 1
      davtelepot/__init__.py
  2. 23 0
      davtelepot/bot.py
  3. 2 0
      davtelepot/custombot.py
  4. 226 0
      davtelepot/helper.py
  5. 39 0
      davtelepot/messages.py

+ 1 - 1
davtelepot/__init__.py

@@ -14,7 +14,7 @@ __author__ = "Davide Testa"
 __email__ = "davide@davte.it"
 __credits__ = ["Marco Origlia", "Nick Lee @Nickoala"]
 __license__ = "GNU General Public License v3.0"
-__version__ = "2.2.9"
+__version__ = "2.3.0"
 __maintainer__ = "Davide Testa"
 __contact__ = "t.me/davte"
 

+ 23 - 0
davtelepot/bot.py

@@ -181,6 +181,8 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
         self.messages['reply_keyboard_buttons'] = dict()
         self._unknown_command_message = None
         self.text_message_parsers = OrderedDict()
+        # Support for /help command
+        self.messages['help_sections'] = OrderedDict()
         # Handle location messages
         self.individual_location_handlers = dict()
         # Callback query-related properties
@@ -1577,6 +1579,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
 
     def command(self, command, aliases=None, reply_keyboard_button=None,
                 show_in_keyboard=False, description="",
+                help_section=None,
                 authorization_level='admin'):
         """Associate a bot command with a custom handler function.
 
@@ -1598,6 +1601,22 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
             default keyboard.
         `description` can be used to help users understand what `/command`
             does.
+        `help_section` is a dict on which the corresponding help section is
+            built. It may provide multilanguage support and should be
+            structured as follows:
+            {
+              "label": {  # It will be displayed as button label
+                'en': "Label",
+                ...
+              },
+              "name": "section_name",
+              # If missing, `authorization_level` is used
+              "authorization_level": "everybody",
+              "description": {
+                'en': "Description in English",
+                ...
+              },
+          }
         `authorization_level` is the lowest authorization level needed to run
             the command.
         """
@@ -1619,6 +1638,10 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
                 raise TypeError(
                     f'Aliases {aliases} is not a list of strings string'
                 )
+        if isinstance(help_section, dict):
+            if 'authorization_level' not in help_section:
+                help_section['authorization_level'] = authorization_level
+            self.messages['help_sections'][help_section['name']] = help_section
         command = command.strip('/ ').lower()
 
         def command_decorator(command_handler):

+ 2 - 0
davtelepot/custombot.py

@@ -373,6 +373,7 @@ class Bot(davtelepot.bot.Bot):
     def command(self, command, aliases=None, show_in_keyboard=False,
                 reply_keyboard_button=None, descr="", auth='admin',
                 description=None,
+                help_section=None,
                 authorization_level=None):
         """Define a bot command.
 
@@ -387,6 +388,7 @@ class Bot(davtelepot.bot.Bot):
             reply_keyboard_button=reply_keyboard_button,
             show_in_keyboard=show_in_keyboard,
             description=description,
+            help_section=help_section,
             authorization_level=authorization_level
         )
 

+ 226 - 0
davtelepot/helper.py

@@ -0,0 +1,226 @@
+"""Make a self-consistent bot help section."""
+
+# Third party modules
+from davtelepot.utilities import (
+    get_cleaned_text, make_inline_keyboard,
+    make_lines_of_buttons, make_button
+)
+
+# Project modules
+from .messages import default_help_messages
+
+
+def get_command_description(bot, update, user_record):
+    """Get a string description of `bot` commands.
+
+    Show only commands available for `update` sender.
+    """
+    user_role = bot.Role.get_user_role(
+        user_record=user_record
+    )
+    return "\n".join(
+        [
+            "/{}: {}".format(
+                command,
+                bot.get_message(
+                    'commands', command, 'description',
+                    user_record=user_record, update=update,
+                    default_message=(
+                        details['description']
+                        if type(details['description']) is str
+                        else ''
+                    )
+                )
+            )
+            for command, details in sorted(
+                bot.commands.items(),
+                key=lambda x:x[0]
+                )
+            if details['description']
+            and user_role.code <= bot.Role.get_user_role(
+                user_role_id=details['authorization_level']
+            ).code
+        ]
+    )
+
+
+def _make_button(text=None, callback_data='',
+                 prefix='help:///', delimiter='|', data=[]):
+    return make_button(text=text, callback_data=callback_data,
+                       prefix=prefix, delimiter=delimiter, data=data)
+
+
+def get_back_to_help_menu_keyboard(bot, update, user_record):
+    """Return a keyboard to let user come back to help menu."""
+    return make_inline_keyboard(
+        [
+            _make_button(
+                text=bot.get_message(
+                    'help', 'help_command', 'back_to_help_menu',
+                    update=update, user_record=user_record
+                ),
+                data=['menu']
+            )
+        ],
+        1
+    )
+
+
+def get_help_buttons(bot, update, user_record):
+    """Get `bot` help menu inline keyboard.
+
+    Show only buttons available for `update` sender.
+    """
+    user_role = bot.Role.get_user_role(
+        user_record=user_record
+    )
+    buttons_list = [
+        _make_button(
+            text=bot.get_message(
+                'help_sections', section['name'], 'label',
+                update=update, user_record=user_record,
+            ),
+            data=['section', name]
+        )
+        for name, section in bot.messages['help_sections'].items()
+        if 'authorization_level' in section
+        and user_role.code <= bot.Role.get_user_role(
+            user_role_id=section['authorization_level']
+        ).code
+    ]
+    return dict(
+        inline_keyboard=(
+            make_lines_of_buttons(buttons_list, 3)
+            + make_lines_of_buttons(
+                [
+                    _make_button(
+                        text=bot.get_message(
+                            'help', 'commands_button_label',
+                            update=update, user_record=user_record,
+                        ),
+                        data=['commands']
+                    )
+                ],
+                1
+            )
+        )
+    )
+
+
+async def _help_command(bot, update, user_record):
+    if not bot.authorization_function(update=update,
+                                      authorization_level='everybody'):
+        return bot.get_message(
+            'help', 'help_command', 'access_denied_message',
+            update=update, user_record=user_record
+        )
+    reply_markup = get_help_buttons(bot, update, user_record)
+    return dict(
+        text=bot.get_message(
+            'help', 'help_command', 'text',
+            update=update, user_record=user_record,
+            bot=bot
+        ),
+        parse_mode='HTML',
+        reply_markup=reply_markup,
+        disable_web_page_preview=True
+    )
+
+
+async def _help_button(bot, update, user_record, data):
+    result, text, reply_markup = '', '', None
+    if data[0] == 'commands':
+        text = bot.get_message(
+            'help', 'help_command', 'header',
+            update=update, user_record=user_record,
+            bot=bot,
+            commands=get_command_description(bot, update, user_record)
+        )
+        reply_markup = get_back_to_help_menu_keyboard(
+            bot=bot, update=update, user_record=user_record
+        )
+    elif data[0] == 'menu':
+        text = bot.get_message(
+            'help', 'help_command', 'text',
+            update=update, user_record=user_record,
+            bot=bot
+        )
+        reply_markup = get_help_buttons(bot, update, user_record)
+    elif (
+        data[0] == 'section'
+        and len(data) > 1
+        and data[1] in bot.messages['help_sections']
+    ):
+        section = bot.messages['help_sections'][data[1]]
+        if bot.authorization_function(
+            update=update,
+            authorization_level=section['authorization_level']
+        ):
+            text = (
+                "<b>{label}</b>\n\n"
+                "{description}"
+            ).format(
+                label=bot.get_message(
+                    'help_sections', section['name'], 'label',
+                    update=update, user_record=user_record,
+                ),
+                description=bot.get_message(
+                    'help_sections', section['name'], 'description',
+                    update=update, user_record=user_record,
+                    bot=bot
+                ),
+            )
+        else:
+            text = bot.authorization_denied_message
+        reply_markup = get_back_to_help_menu_keyboard(
+            bot=bot, update=update, user_record=user_record
+        )
+    if text or reply_markup:
+        return dict(
+            text=result,
+            edit=dict(
+                text=text,
+                parse_mode='HTML',
+                reply_markup=reply_markup,
+                disable_web_page_preview=True
+            )
+        )
+    return result
+
+
+async def _start_command(bot, update, user_record):
+    text = get_cleaned_text(update=update, bot=bot, replace=['start'])
+    if not text:
+        return await _help_command(bot, update, user_record)
+    update['text'] = text
+    await bot.text_message_handler(
+        update=update,
+        user_record=None
+    )
+    return
+
+
+def init(bot, help_messages=None):
+    """Assign parsers, commands, buttons and queries to given `bot`."""
+    if help_messages is None:
+        help_messages = default_help_messages
+    bot.messages['help'] = help_messages
+
+    @bot.command("/start", authorization_level='everybody')
+    async def start_command(bot, update, user_record):
+        return await _start_command(bot, update, user_record)
+
+    @bot.command(command='/help', aliases=['00help'],
+                 reply_keyboard_button=help_messages['help_command'][
+                    'reply_keyboard_button'],
+                 show_in_keyboard=True,
+                 description=help_messages['help_command']['description'],
+                 authorization_level='everybody')
+    async def help_command(bot, update, user_record):
+        result = await _help_command(bot, update, user_record)
+        return result
+
+    @bot.button(prefix='help:///', separator='|',
+                authorization_level='everybody')
+    async def help_button(bot, update, user_record, data):
+        return await _help_button(bot, update, user_record, data)

+ 39 - 0
davtelepot/messages.py

@@ -0,0 +1,39 @@
+"""Default messages for bot functions."""
+
+default_help_messages = {
+    'help_command': {
+        'header': {
+            'en': "<b>{bot.name} commands</b>\n\n"
+                  "{commands}",
+            'it': "<b>Comandi di {bot.name}</b>\n\n"
+                  "{commands}",
+        },
+        'text': {
+            'en': "<b>Guide</b>",
+            'it': "<b>Guida</b>"
+        },
+        'reply_keyboard_button': {
+            'en': "Help 📖",
+            'it': "Guida 📖"
+        },
+        'description': {
+            'en': "Help",
+            'it': "Aiuto"
+        },
+        'access_denied_message': {
+            'en': "Ask for authorization. If your request is accepted, send "
+                  "/help command again to read the guide.",
+            'it': "Chiedi di essere autorizzato: se la tua richiesta "
+                  "verrà accolta, ripeti il comando /help per leggere "
+                  "il messaggio di aiuto."
+        },
+        'back_to_help_menu': {
+            'en': "Back to guide menu 📖",
+            'it': "Torna al menu Guida 📖",
+        },
+    },
+    'commands_button_label': {
+            'en': "Commands 🤖",
+            'it': "Comandi 🤖",
+    },
+}