From 62b390b7b9c0e8b8b4437941dd60e769881fd9aa Mon Sep 17 00:00:00 2001 From: Davte Date: Fri, 26 Jul 2019 23:37:23 +0200 Subject: [PATCH] Multilanguage support for reply keyboard buttons --- davtelepot/__init__.py | 2 +- davtelepot/bot.py | 112 ++++++++++++++++++++++------------------ davtelepot/languages.py | 34 +++++++++--- 3 files changed, 89 insertions(+), 59 deletions(-) diff --git a/davtelepot/__init__.py b/davtelepot/__init__.py index 5a07362..7592216 100644 --- a/davtelepot/__init__.py +++ b/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" diff --git a/davtelepot/bot.py b/davtelepot/bot.py index d426e02..652bf1f 100644 --- a/davtelepot/bot.py +++ b/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: diff --git a/davtelepot/languages.py b/davtelepot/languages.py index d479028..87166b2 100644 --- a/davtelepot/languages.py +++ b/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'