Queer European MD passionate about IT
Browse Source

Multilanguage support for reply keyboard buttons

Davte 5 years ago
parent
commit
62b390b7b9
3 changed files with 89 additions and 59 deletions
  1. 1 1
      davtelepot/__init__.py
  2. 62 50
      davtelepot/bot.py
  3. 26 8
      davtelepot/languages.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.1.36"
+__version__ = "2.2.0"
 __maintainer__ = "Davide Testa"
 __contact__ = "t.me/davte"
 

+ 62 - 50
davtelepot/bot.py

@@ -176,6 +176,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
         self.commands = OrderedDict()
         self.command_aliases = OrderedDict()
         self.messages['commands'] = dict()
+        self.messages['reply_keyboard_buttons'] = dict()
         self._unknown_command_message = None
         self.text_message_parsers = OrderedDict()
         # Handle location messages
@@ -206,7 +207,6 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
             lambda update, user_record=None, authorization_level='user': True
         )
         self.default_reply_keyboard_elements = []
-        self._default_keyboard = dict()
         self.recent_users = OrderedDict()
         self._log_file_name = None
         self._errors_file_name = None
@@ -411,13 +411,32 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
             return self._authorization_denied_message
         return self.__class__._authorization_denied_message
 
-    @property
-    def default_keyboard(self):
-        """Get the default keyboard.
-
-        It is sent when reply_markup is left blank and chat is private.
-        """
-        return self._default_keyboard
+    def get_keyboard(self, user_record=dict(), update=dict(),
+                     telegram_id=None):
+        """Return a reply keyboard translated into user language."""
+        if (not user_record) and telegram_id:
+            with self.db as db:
+                user_record = db['users'].find_one(telegram_id=telegram_id)
+        buttons = [
+            dict(
+                text=self.get_message(
+                    'reply_keyboard_buttons', command,
+                    user_record=user_record, update=update,
+                    default_message=element['reply_keyboard_button']
+                )
+            )
+            for command, element in self.commands.items()
+            if 'reply_keyboard_button' in element
+        ]
+        if len(buttons) == 0:
+            return
+        return dict(
+            keyboard=make_lines_of_buttons(
+                buttons,
+                (2 if len(buttons) < 4 else 3)  # Row length
+            ),
+            resize_keyboard=True
+        )
 
     @property
     def unknown_command_message(self):
@@ -1025,7 +1044,10 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
             and chat_id > 0
             and text != self.authorization_denied_message
         ):
-            reply_markup = self.default_keyboard
+            reply_markup = self.get_keyboard(
+                update=update,
+                telegram_id=chat_id
+            )
         if not text:
             return
         parse_mode = str(parse_mode)
@@ -1176,7 +1198,10 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
             and chat_id > 0
             and caption != self.authorization_denied_message
         ):
-            reply_markup = self.default_keyboard
+            reply_markup = self.get_keyboard(
+                update=update,
+                telegram_id=chat_id
+            )
         if type(photo) is str:
             photo_path = photo
             with self.db as db:
@@ -1296,7 +1321,10 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
             and chat_id > 0
             and caption != self.authorization_denied_message
         ):
-            reply_markup = self.default_keyboard
+            reply_markup = self.get_keyboard(
+                update=update,
+                telegram_id=chat_id,
+            )
         if document_path is not None:
             with self.db as db:
                 already_sent = db['sent_documents'].find_one(
@@ -1544,8 +1572,9 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
         """
         self._unknown_command_message = unknown_command_message
 
-    def command(self, command, aliases=None, show_in_keyboard=False,
-                description="", authorization_level='admin'):
+    def command(self, command, aliases=None, reply_keyboard_button=None,
+                show_in_keyboard=False, description="",
+                authorization_level='admin'):
         """Associate a bot command with a custom handler function.
 
         Decorate command handlers like this:
@@ -1559,9 +1588,11 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
         `command` is the command name (with or without /).
         `aliases` is a list of aliases; each will call the command handler
             function; the first alias will appear as button in
-            default_keyboard.
-        `show_in_keyboard`, if True, makes first alias appear in
-            default_keyboard.
+            reply keyboard if `reply_keyboard_button` is not set.
+        `reply_keyboard_button` is a str or better dict of language-specific
+            strings to be shown in default keyboard.
+        `show_in_keyboard`, if True, makes a button for this command appear in
+            default keyboard.
         `description` can be used to help users understand what `/command`
             does.
         `authorization_level` is the lowest authorization level needed to run
@@ -1623,8 +1654,13 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
             if aliases:
                 for alias in aliases:
                     self.command_aliases[alias] = decorated_command_handler
-                if show_in_keyboard:
-                    self.default_reply_keyboard_elements.append(aliases[0])
+            if show_in_keyboard and (aliases or reply_keyboard_button):
+                _reply_keyboard_button = reply_keyboard_button or aliases[0]
+                self.messages[
+                    'reply_keyboard_buttons'][
+                    command] = _reply_keyboard_button
+                self.commands[command][
+                    'reply_keyboard_button'] = _reply_keyboard_button
         return command_decorator
 
     def parser(self, condition, description='', authorization_level='admin',
@@ -1690,7 +1726,8 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
         return parser_decorator
 
     def set_command(self, command, handler, aliases=None,
-                    show_in_keyboard=False, description="",
+                    reply_keyboard_button=None, show_in_keyboard=False,
+                    description="",
                     authorization_level='admin'):
         """Associate a `command` with a `handler`.
 
@@ -1700,9 +1737,11 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
         `handler` is the function to be called on update objects.
         `aliases` is a list of aliases; each will call the command handler
             function; the first alias will appear as button in
-            default_keyboard.
-        `show_in_keyboard`, if True, makes first alias appear in
-            default_keyboard.
+            reply keyboard if `reply_keyboard_button` is not set.
+        `reply_keyboard_button` is a str or better dict of language-specific
+            strings to be shown in default keyboard.
+        `show_in_keyboard`, if True, makes a button for this command appear in
+            default keyboard.
         `description` is a description and can be used to help users understand
             what `/command` does.
         `authorization_level` is the lowest authorization level needed to run
@@ -1712,6 +1751,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
             raise TypeError(f'Handler `{handler}` is not callable.')
         return self.command(
             command=command, aliases=aliases,
+            reply_keyboard_button=reply_keyboard_button,
             show_in_keyboard=show_in_keyboard, description=description,
             authorization_level=authorization_level
         )(handler)
@@ -1944,33 +1984,6 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
             del self.individual_location_handlers[identifier]
         return
 
-    def set_default_keyboard(self, keyboard='set_default'):
-        """Set a default keyboard for the bot.
-
-        If a keyboard is not passed as argument, a default one is generated,
-            based on aliases of commands.
-        """
-        if keyboard == 'set_default':
-            buttons = [
-                dict(
-                    text=x
-                )
-                for x in self.default_reply_keyboard_elements
-            ]
-            if len(buttons) == 0:
-                self._default_keyboard = None
-            else:
-                self._default_keyboard = dict(
-                    keyboard=make_lines_of_buttons(
-                        buttons,
-                        (2 if len(buttons) < 4 else 3)  # Row length
-                    ),
-                    resize_keyboard=True
-                )
-        else:
-            self._default_keyboard = keyboard
-        return
-
     async def webhook_feeder(self, request):
         """Handle incoming HTTP `request`s.
 
@@ -2012,7 +2025,6 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject):
 
     def setup(self):
         """Make bot ask for updates and handle responses."""
-        self.set_default_keyboard()
         if not self.webhook_url:
             asyncio.ensure_future(self.get_updates())
         else:

+ 26 - 8
davtelepot/languages.py

@@ -1,6 +1,7 @@
 """Bot support for multiple languages."""
 
 # Standard library modules
+import asyncio
 from collections import OrderedDict
 import logging
 
@@ -17,6 +18,10 @@ default_language_messages = {
             'en': "Language 🗣",
             'it': "Lingua 🗣"
         },
+        'reply_keyboard_button': {
+            'en': "Language 🗣",
+            'it': "Lingua 🗣"
+        },
         'description': {
             'en': "Change language settings",
             'it': "Cambia le impostazioni della lingua"
@@ -26,6 +31,10 @@ default_language_messages = {
         'description': {
             'en': "Change language settings",
             'it': "Cambia le impostazioni della lingua"
+        },
+        'language_set': {
+            'en': "Selected language: English 🇬🇧",
+            'it': "Lingua selezionata: Italiano 🇮🇹"
         }
     },
     'language_panel': {
@@ -265,6 +274,16 @@ async def _language_button(bot, update, user_record, data):
                     ensure=True
                 )
                 user_record['selected_language_code'] = data[1]
+        if 'chat' in update['message'] and update['message']['chat']['id'] > 0:
+            asyncio.ensure_future(
+                bot.send_message(
+                    text=bot.get_message(
+                        'language', 'language_button', 'language_set',
+                        update=update['message'], user_record=user_record
+                    ),
+                    chat_id=update['message']['chat']['id']
+                )
+            )
     if len(data) == 0 or data[0] in ('show', 'set'):
         text, reply_markup = get_language_panel(bot, user_record)
     if text:
@@ -292,18 +311,17 @@ def init(
     bot.messages['language'] = language_messages
     bot.add_supported_languages(supported_languages)
 
-    language_command_alias = bot.get_message(
-        'language', 'language_command', 'alias',
-        language='en', default_message=None
-    )
-    if language_command_alias is None:
-        aliases = []
-    else:
-        aliases = [language_command_alias]
+    aliases = [
+        alias
+        for alias in language_messages[
+            'language_command']['alias'].values()
+    ]
 
     @bot.command(
         command='/language',
         aliases=aliases,
+        reply_keyboard_button=language_messages['language_command'][
+            'reply_keyboard_button'],
         show_in_keyboard=show_in_keyboard,
         description=language_messages['language_command']['description'],
         authorization_level='everybody'