diff --git a/davtelepot/__init__.py b/davtelepot/__init__.py index 9f2a66a..7d166f8 100644 --- a/davtelepot/__init__.py +++ b/davtelepot/__init__.py @@ -11,7 +11,7 @@ __author__ = "Davide Testa" __email__ = "davide@davte.it" __credits__ = ["Marco Origlia", "Nick Lee @Nickoala"] __license__ = "GNU General Public License v3.0" -__version__ = "2.5.7" +__version__ = "2.5.8" __maintainer__ = "Davide Testa" __contact__ = "t.me/davte" diff --git a/davtelepot/administration_tools.py b/davtelepot/administration_tools.py index fbb58d9..7a0af55 100644 --- a/davtelepot/administration_tools.py +++ b/davtelepot/administration_tools.py @@ -13,9 +13,11 @@ import asyncio import datetime import json import logging +import re import types -from typing import Union, List +from collections import OrderedDict +from typing import Union, List, Tuple # Third party modules from sqlalchemy.exc import ResourceClosedError @@ -27,9 +29,14 @@ from .utilities import ( async_wrapper, CachedPage, Confirmator, extract, get_cleaned_text, get_user, escape_html_chars, line_drawing_unordered_list, make_button, make_inline_keyboard, remove_html_tags, send_part_of_text_file, - send_csv_file + send_csv_file, make_lines_of_buttons ) +# Use this parameter in SQL `LIMIT x OFFSET y` clauses +rows_number_limit = 10 + +command_description_parser = re.compile(r'(?P\w+)(\s?-\s?(?P.*))?') + async def _forward_to(update, bot: Bot, @@ -970,6 +977,729 @@ async def get_package_updates(bot: Bot, await asyncio.sleep(monitoring_interval) +async def _send_start_messages(bot: Bot): + """Send restart messages at restart.""" + for restart_message in bot.db['restart_messages'].find(sent=None): + asyncio.ensure_future( + bot.send_message( + **{ + key: val + for key, val in restart_message.items() + if key in ( + 'chat_id', + 'text', + 'parse_mode', + 'reply_to_message_id' + ) + } + ) + ) + bot.db['restart_messages'].update( + dict( + sent=datetime.datetime.now(), + id=restart_message['id'] + ), + ['id'], + ensure=True + ) + return + + +async def _load_talking_sessions(bot: Bot): + sessions = [] + for session in bot.db.query( + """SELECT * + FROM talking_sessions + WHERE NOT cancelled + """ + ): + sessions.append( + dict( + other_user_record=bot.db['users'].find_one( + id=session['user'] + ), + admin_record=bot.db['users'].find_one( + id=session['admin'] + ), + ) + ) + for session in sessions: + await start_session( + bot=bot, + other_user_record=session['other_user_record'], + admin_record=session['admin_record'] + ) + + +def get_current_commands(bot: Bot, language: str = None) -> List[dict]: + return sorted( + [ + { + 'command': name, + 'description': bot.get_message( + messages=information['description'], + language=language + ) + } + for name, information in bot.commands.items() + if 'description' in information + and information['description'] + and 'authorization_level' in information + and information['authorization_level'] in ('registered_user', 'everybody',) + ], + key=(lambda c: c['command']) + ) + + +def get_custom_commands(bot: Bot, language: str = None) -> List[dict]: + additional_commands = [ + { + 'command': record['command'], + 'description': record['description'] + } + for record in bot.db['bot_father_commands'].find( + cancelled=None, + hidden=False + ) + ] + hidden_commands_names = [ + record['command'] + for record in bot.db['bot_father_commands'].find( + cancelled=None, + hidden=True + ) + ] + return sorted( + [ + command + for command in (get_current_commands(bot=bot, language=language) + + additional_commands) + if command['command'] not in hidden_commands_names + ], + key=(lambda c: c['command']) + ) + + +async def _father_command(bot, language): + modes = [ + { + key: ( + bot.get_message(messages=val, + language=language) + if isinstance(val, dict) + else val + ) + for key, val in mode.items() + } + for mode in bot.messages['admin']['father_command']['modes'] + ] + text = "\n\n".join( + [ + bot.get_message( + 'admin', 'father_command', 'title', + language=language + ) + ] + [ + "{m[symbol]} {m[name]}\n{m[description]}".format(m=mode) + for mode in modes + ] + ) + reply_markup = make_inline_keyboard( + [ + make_button( + text="{m[symbol]} {m[name]}".format(m=mode), + prefix='father:///', + delimiter='|', + data=[mode['id']] + ) + for mode in modes + ], + 2 + ) + return dict( + text=text, + reply_markup=reply_markup + ) + + +def browse_bot_father_settings_records(bot: Bot, + language: str, + page: int = 0) -> Tuple[str, str, dict]: + """Return a reply keyboard to edit bot father settings records.""" + result, text, reply_markup = '', '', None + records = list( + bot.db['bot_father_commands'].find( + cancelled=None, + _limit=(rows_number_limit + 1), + _offset=(page * rows_number_limit) + ) + ) + for record in bot.db.query( + "SELECT COUNT(*) AS c " + "FROM bot_father_commands " + "WHERE cancelled IS NULL" + ): + records_count = record['c'] + break + else: + records_count = 0 + text = bot.get_message( + 'admin', 'father_command', 'settings', 'browse_records', + language=language, + record_interval=((page * rows_number_limit + 1) if records else 0, + min((page + 1) * rows_number_limit, len(records)), + records_count), + commands_list='\n'.join( + f"{'➖' if record['hidden'] else '➕'} {record['command']}" + for record in records[:rows_number_limit] + ) + ) + buttons = make_lines_of_buttons( + [ + make_button( + text=f"{'➖' if record['hidden'] else '➕'} {record['command']}", + prefix='father:///', + delimiter='|', + data=['settings', 'edit', 'select', record['id']] + ) + for record in records[:rows_number_limit] + ], + 3 + ) + buttons += make_lines_of_buttons( + ( + [ + make_button( + text='⬅', + prefix='father:///', + delimiter='|', + data=['settings', 'edit', 'go', page - 1] + ) + ] + if page > 0 + else [] + ) + [ + make_button( + text=bot.get_message('admin', 'father_command', 'back', + language=language), + prefix='father:///', + delimiter='|', + data=['settings'] + ) + ] + ( + [ + make_button( + text='️➡️', + prefix='father:///', + delimiter='|', + data=['settings', 'edit', 'go', page + 1] + ) + ] + if len(records) > rows_number_limit + else [] + ), + 3 + ) + reply_markup = dict( + inline_keyboard=buttons + ) + return result, text, reply_markup + + +def get_bot_father_settings_editor(mode: str, + record: OrderedDict = None): + """Get a coroutine to edit or create a record in bot father settings table. + + Modes: + - add + - hide + """ + async def bot_father_settings_editor(bot: Bot, update: dict, + language: str): + """Edit or create a record in bot father settings table.""" + nonlocal record + if record is not None: + record_id = record['id'] + else: + record_id = None + # Cancel if user used /cancel command, or remove trailing forward_slash + input_text = update['text'] + if input_text.startswith('/'): + if language not in bot.messages['admin']['cancel']['lower']: + language = bot.default_language + if input_text.lower().endswith(bot.messages['admin']['cancel']['lower'][language]): + return bot.get_message( + 'admin', 'cancel', 'done', + language=language + ) + else: + input_text = input_text[1:] + if record is None: + # Use regex compiled pattern to search for command and description + re_search = command_description_parser.search(input_text) + if re_search is None: + return bot.get_message( + 'admin', 'error', 'text', + language=language + ) + re_search = re_search.groupdict() + command = re_search['command'].lower() + description = re_search['description'] + else: + command = record['command'] + description = input_text + error = None + # A description (str 3-256) is required + if mode in ('add', 'edit'): + if description is None or len(description) < 3: + error = 'missing_description' + elif type(description) is str and len(description) > 255: + error = 'description_too_long' + elif mode == 'add': + duplicate = bot.db['bot_father_commands'].find_one( + command=command, + cancelled=None + ) + if duplicate: + error = 'duplicate_record' + if error: + text = bot.get_message( + 'admin', 'father_command', 'settings', 'modes', + 'add', 'error', error, + language=language + ) + reply_markup = make_inline_keyboard( + [ + make_button( + text=bot.get_message( + 'admin', 'father_command', 'back', + language=language + ), + prefix='father:///', + delimiter='|', + data=['settings'] + ) + ] + ) + else: + table = bot.db['bot_father_commands'] + new_record = dict( + command=command, + description=description, + hidden=(mode == 'hide'), + cancelled=None + ) + if record_id is None: + record_id = table.insert( + new_record + ) + else: + new_record['id'] = record_id + table.upsert( + new_record, + ['id'] + ) + text = bot.get_message( + 'admin', 'father_command', 'settings', 'modes', + mode, ('edit' if 'id' in new_record else 'add'), 'done', + command=command, + description=(description if description else '-'), + language=language + ) + reply_markup = make_inline_keyboard( + [ + make_button( + text=bot.get_message( + 'admin', 'father_command', 'settings', 'modes', + 'edit', 'button', + language=language + ), + prefix='father:///', + delimiter='|', + data=['settings', 'edit', 'select', record_id] + ), make_button( + text=bot.get_message( + 'admin', 'father_command', 'back', + language=language + ), + prefix='father:///', + delimiter='|', + data=['settings'] + ) + ], + 2 + ) + asyncio.ensure_future( + bot.delete_message(update=update) + ) + return dict( + text=text, + reply_markup=reply_markup + ) + return bot_father_settings_editor + + +async def edit_bot_father_settings_via_message(bot: Bot, + user_record: OrderedDict, + language: str, + mode: str, + record: OrderedDict = None): + result, text, reply_markup = '', '', None + modes = bot.messages['admin']['father_command']['settings']['modes'] + if mode not in modes: + result = bot.get_message( + 'admin', 'father_command', 'error', + language=language + ) + else: + result = bot.get_message( + ('add' if record is None else 'edit'), 'popup', + messages=modes[mode], + language=language, + command=(record['command'] if record is not None else None) + ) + text = bot.get_message( + ('add' if record is None else 'edit'), 'text', + messages=modes[mode], + language=language, + command=(record['command'] if record is not None else None) + ) + reply_markup = make_inline_keyboard( + [ + make_button( + text=bot.get_message( + 'admin', 'cancel', 'button', + language=language, + ), + prefix='father:///', + delimiter='|', + data=['cancel'] + ) + ] + ) + bot.set_individual_text_message_handler( + get_bot_father_settings_editor(mode=mode, record=record), + user_id=user_record['telegram_id'], + ) + return result, text, reply_markup + + +async def _father_button(bot: Bot, user_record: OrderedDict, + language: str, data: list): + """Handle BotFather button. + + Operational modes + - main: back to main page (see _father_command) + - get: show commands stored by @BotFather + - set: edit commands stored by @BotFather + """ + result, text, reply_markup = '', '', None + command, *data = data + if command == 'cancel': + bot.remove_individual_text_message_handler(user_id=user_record['telegram_id']) + result = text = bot.get_message( + 'admin', 'cancel', 'done', + language=language + ) + reply_markup = make_inline_keyboard( + [ + make_button( + text=bot.get_message('admin', 'father_command', 'back', + language=language), + prefix='father:///', + delimiter='|', + data=['main'] + ) + ] + ) + elif command == 'get': + commands = await bot.getMyCommands() + text = '' + '\n'.join( + "{c[command]} - {c[description]}".format(c=command) + for command in commands + ) + '' + reply_markup = make_inline_keyboard( + [ + make_button( + text=bot.get_message('admin', 'father_command', 'back', + language=language), + prefix='father:///', + delimiter='|', + data=['main'] + ) + ] + ) + elif command == 'main': + return dict( + text='', + edit=(await _father_command(bot=bot, language=language)) + ) + elif command == 'set': + stored_commands = await bot.getMyCommands() + current_commands = get_custom_commands(bot=bot, language=language) + if len(data) > 0 and data[0] == 'confirm': + if not Confirmator.get('set_bot_father_commands', + confirm_timedelta=3 + ).confirm(user_record['id']): + return bot.get_message( + 'admin', 'confirm', + language=language + ) + if stored_commands == current_commands: + text = bot.get_message( + 'admin', 'father_command', 'set', 'no_change', + language=language + ) + else: + if isinstance( + await bot.setMyCommands(current_commands), + Exception + ): + text = bot.get_message( + 'admin', 'father_command', 'set', 'error', + language=language + ) + else: + text = bot.get_message( + 'admin', 'father_command', 'set', 'done', + language=language + ) + reply_markup = make_inline_keyboard( + [ + make_button( + text=bot.get_message('admin', 'father_command', 'back', + language=language), + prefix='father:///', + delimiter='|', + data=['main'] + ) + ] + ) + else: + stored_commands_names = [c['command'] for c in stored_commands] + current_commands_names = [c['command'] for c in current_commands] + # Show preview of new, edited and removed commands + # See 'legend' in bot.messages['admin']['father_command']['set'] + text = bot.get_message( + 'admin', 'father_command', 'set', 'header', + language=language + ) + '\n\n' + '\n\n'.join([ + '\n'.join( + ('✅ ' if c in stored_commands + else '☑️ ' if c['command'] not in stored_commands_names + else '✏️') + c['command'] + for c in current_commands + ), + '\n'.join( + f'❌ {command}' + for command in stored_commands_names + if command not in current_commands_names + ), + bot.get_message( + 'admin', 'father_command', 'set', 'legend', + language=language + ) + ]) + reply_markup = make_inline_keyboard( + [ + make_button( + text=bot.get_message('admin', 'father_command', 'set', + 'button', + language=language), + prefix='father:///', + delimiter='|', + data=['set', 'confirm'] + ) + ] + [ + make_button( + text=bot.get_message('admin', 'father_command', 'back', + language=language), + prefix='father:///', + delimiter='|', + data=['main'] + ) + ], + 1 + ) + elif command == 'settings': + if len(data) == 0: + additional_commands = '\n'.join( + f"{record['command']} - {record['description']}" + for record in bot.db['bot_father_commands'].find( + cancelled=None, + hidden=False + ) + ) + if not additional_commands: + additional_commands = '-' + hidden_commands = '\n'.join( + f"{record['command']}" + for record in bot.db['bot_father_commands'].find( + cancelled=None, + hidden=True + ) + ) + if not hidden_commands: + hidden_commands = '-' + text = bot.get_message( + 'admin', 'father_command', 'settings', 'panel', + language=language, + additional_commands=additional_commands, + hidden_commands=hidden_commands + ) + modes = bot.messages['admin']['father_command']['settings']['modes'] + reply_markup = make_inline_keyboard( + [ + make_button( + text=modes[code]['symbol'] + ' ' + bot.get_message( + messages=modes[code]['name'], + language=language + ), + prefix='father:///', + delimiter='|', + data=['settings', code] + ) + for code, mode in modes.items() + ] + [ + make_button( + text=bot.get_message('admin', 'father_command', 'back', + language=language), + prefix='father:///', + delimiter='|', + data=['main'] + ) + ], + 2 + ) + elif data[0] in ('add', 'hide', ): + result, text, reply_markup = await edit_bot_father_settings_via_message( + bot=bot, + user_record=user_record, + language=language, + mode=data[0] + ) + elif data[0] == 'edit': + if len(data) > 2 and data[1] == 'select': + selected_record = bot.db['bot_father_commands'].find_one(id=data[2]) + if selected_record is None: + return bot.get_message( + 'admin', 'error', + language=language + ) + if len(data) == 3: + text = bot.get_message( + 'admin', 'father_command', 'settings', + 'modes', 'edit', 'panel', 'text', + language=language, + command=selected_record['command'], + description=selected_record['description'], + ) + reply_markup = make_inline_keyboard( + [ + make_button( + text=bot.get_message( + 'admin', 'father_command', 'settings', + 'modes', 'edit', 'panel', + 'edit_description', 'button', + language=language, + ), + prefix='father:///', + delimiter='|', + data=['settings', 'edit', 'select', + selected_record['id'], 'edit_descr'] + ), + make_button( + text=bot.get_message( + 'admin', 'father_command', 'settings', + 'modes', 'edit', 'panel', + 'delete', 'button', + language=language, + ), + prefix='father:///', + delimiter='|', + data=['settings', 'edit', 'select', + selected_record['id'], 'del'] + ), + make_button( + text=bot.get_message( + 'admin', 'father_command', 'back', + language=language, + ), + prefix='father:///', + delimiter='|', + data=['settings', 'edit'] + ) + ], + 2 + ) + elif len(data) > 3 and data[3] == 'edit_descr': + result, text, reply_markup = await edit_bot_father_settings_via_message( + bot=bot, + user_record=user_record, + language=language, + mode=data[0], + record=selected_record + ) + elif len(data) > 3 and data[3] == 'del': + if not Confirmator.get('set_bot_father_commands', + confirm_timedelta=3 + ).confirm(user_record['id']): + result = bot.get_message( + 'admin', 'confirm', + language=language + ) + else: + bot.db['bot_father_commands'].update( + dict( + id=selected_record['id'], + cancelled=True + ), + ['id'] + ) + result = bot.get_message( + 'admin', 'father_command', 'settings', + 'modes', 'edit', 'panel', 'delete', + 'done', 'popup', + language=language + ) + text = bot.get_message( + 'admin', 'father_command', 'settings', + 'modes', 'edit', 'panel', 'delete', + 'done', 'text', + language=language + ) + reply_markup = make_inline_keyboard( + [ + make_button( + text=bot.get_message( + 'admin', 'father_command', + 'back', + language=language + ), + prefix='father:///', + delimiter='|', + data=['settings'] + ) + ], + 1 + ) + elif len(data) == 1 or data[1] == 'go': + result, text, reply_markup = browse_bot_father_settings_records( + bot=bot, + language=language, + page=(data[2] if len(data) > 2 else 0) + ) + if text: + return dict( + text=result, + edit=dict( + text=text, + reply_markup=reply_markup + ) + ) + return result + + def init(telegram_bot: Bot, talk_messages: dict = None, admin_messages: dict = None, @@ -989,60 +1719,134 @@ def init(telegram_bot: Bot, admin_messages = messages.default_admin_messages telegram_bot.messages['admin'] = admin_messages db = telegram_bot.db - if 'talking_sessions' not in db.tables: - db['talking_sessions'].insert( - dict( - user=0, - admin=0, - cancelled=1 - ) + if 'bot_father_commands' not in db.tables: + table = db.create_table( + table_name='bot_father_commands' ) - - allowed_during_maintenance = [ + table.create_column( + 'command', + db.types.string + ) + table.create_column( + 'description', + db.types.string + ) + table.create_column( + 'hidden', + db.types.boolean + ) + table.create_column( + 'cancelled', + db.types.boolean + ) + if 'talking_sessions' not in db.tables: + table = db.create_table( + table_name='users' + ) + table.create_column( + 'user', + db.types.integer + ) + table.create_column( + 'admin', + db.types.integer + ) + table.create_column( + 'cancelled', + db.types.integer + ) + for exception in [ get_maintenance_exception_criterion(telegram_bot, command) for command in ['stop', 'restart', 'maintenance'] - ] + ]: + telegram_bot.allow_during_maintenance(exception) + # Tasks to complete before starting bot @telegram_bot.additional_task(when='BEFORE') async def load_talking_sessions(): - sessions = [] - for session in db.query( - """SELECT * - FROM talking_sessions - WHERE NOT cancelled - """ - ): - sessions.append( - dict( - other_user_record=db['users'].find_one( - id=session['user'] - ), - admin_record=db['users'].find_one( - id=session['admin'] - ), - ) - ) - for session in sessions: - await start_session( - bot=telegram_bot, - other_user_record=session['other_user_record'], - admin_record=session['admin_record'] - ) + return await _load_talking_sessions(bot=telegram_bot) - @telegram_bot.command(command='/talk', + @telegram_bot.additional_task(when='BEFORE', bot=telegram_bot) + async def notify_version(bot): + return await notify_new_version(bot=bot) + + @telegram_bot.additional_task('BEFORE') + async def send_restart_messages(): + return await _send_start_messages(bot=telegram_bot) + + # Administration commands + @telegram_bot.command(command='/db', aliases=[], show_in_keyboard=False, description=admin_messages[ - 'talk_command']['description'], + 'db_command']['description'], authorization_level='admin') - async def talk_command(bot, update, user_record): - return await _talk_command(bot, update, user_record) + async def send_bot_database(bot, update, user_record): + return await _send_bot_database(bot, update, user_record) - @telegram_bot.button(prefix='talk:///', + @telegram_bot.command(command='/errors', + aliases=[], + show_in_keyboard=False, + description=admin_messages[ + 'errors_command']['description'], + authorization_level='admin') + async def errors_command(bot, update, user_record): + return await _errors_command(bot, update, user_record) + + @telegram_bot.command(command='/father', + aliases=[], + show_in_keyboard=False, + **{ + key: value + for key, value in admin_messages['father_command'].items() + if key in ('description', ) + }, + authorization_level='admin') + async def father_command(bot, language): + return await _father_command(bot=bot, language=language) + + @telegram_bot.button(prefix='father:///', separator='|', authorization_level='admin') - async def talk_button(bot, update, user_record, data): - return await _talk_button(bot, update, user_record, data) + async def query_button(bot, user_record, language, data): + return await _father_button(bot=bot, + user_record=user_record, + language=language, + data=data) + + @telegram_bot.command(command='/log', + aliases=[], + show_in_keyboard=False, + description=admin_messages[ + 'log_command']['description'], + authorization_level='admin') + async def log_command(bot, update, user_record): + return await _log_command(bot, update, user_record) + + @telegram_bot.command(command='/maintenance', aliases=[], + show_in_keyboard=False, + description=admin_messages[ + 'maintenance_command']['description'], + authorization_level='admin') + async def maintenance_command(bot, update, user_record): + return await _maintenance_command(bot, update, user_record) + + @telegram_bot.command(command='/query', + aliases=[], + show_in_keyboard=False, + description=admin_messages[ + 'query_command']['description'], + authorization_level='admin') + async def query_command(bot, update, user_record): + return await _query_command(bot, update, user_record) + + @telegram_bot.button(prefix='db_query:///', + separator='|', + description=admin_messages[ + 'query_command']['description'], + authorization_level='admin') + async def query_button(bot, update, user_record, data): + return await _query_button(bot, update, user_record, data) @telegram_bot.command(command='/restart', aliases=[], @@ -1053,33 +1857,14 @@ def init(telegram_bot: Bot, async def restart_command(bot, update, user_record): return await _restart_command(bot, update, user_record) - @telegram_bot.additional_task('BEFORE') - async def send_restart_messages(): - """Send restart messages at restart.""" - for restart_message in db['restart_messages'].find(sent=None): - asyncio.ensure_future( - telegram_bot.send_message( - **{ - key: val - for key, val in restart_message.items() - if key in ( - 'chat_id', - 'text', - 'parse_mode', - 'reply_to_message_id' - ) - } - ) - ) - db['restart_messages'].update( - dict( - sent=datetime.datetime.now(), - id=restart_message['id'] - ), - ['id'], - ensure=True - ) - return + @telegram_bot.command(command='/select', + aliases=[], + show_in_keyboard=False, + description=admin_messages[ + 'select_command']['description'], + authorization_level='admin') + async def select_command(bot, update, user_record): + return await _query_command(bot, update, user_record) @telegram_bot.command(command='/stop', aliases=[], @@ -1098,69 +1883,20 @@ def init(telegram_bot: Bot, async def stop_button(bot, update, user_record, data): return await _stop_button(bot, update, user_record, data) - @telegram_bot.command(command='/db', + @telegram_bot.command(command='/talk', aliases=[], show_in_keyboard=False, description=admin_messages[ - 'db_command']['description'], + 'talk_command']['description'], authorization_level='admin') - async def send_bot_database(bot, update, user_record): - return await _send_bot_database(bot, update, user_record) + async def talk_command(bot, update, user_record): + return await _talk_command(bot, update, user_record) - @telegram_bot.command(command='/query', - aliases=[], - show_in_keyboard=False, - description=admin_messages[ - 'query_command']['description'], - authorization_level='admin') - async def query_command(bot, update, user_record): - return await _query_command(bot, update, user_record) - - @telegram_bot.command(command='/select', - aliases=[], - show_in_keyboard=False, - description=admin_messages[ - 'select_command']['description'], - authorization_level='admin') - async def select_command(bot, update, user_record): - return await _query_command(bot, update, user_record) - - @telegram_bot.button(prefix='db_query:///', + @telegram_bot.button(prefix='talk:///', separator='|', - description=admin_messages[ - 'query_command']['description'], authorization_level='admin') - async def query_button(bot, update, user_record, data): - return await _query_button(bot, update, user_record, data) - - @telegram_bot.command(command='/log', - aliases=[], - show_in_keyboard=False, - description=admin_messages[ - 'log_command']['description'], - authorization_level='admin') - async def log_command(bot, update, user_record): - return await _log_command(bot, update, user_record) - - @telegram_bot.command(command='/errors', - aliases=[], - show_in_keyboard=False, - description=admin_messages[ - 'errors_command']['description'], - authorization_level='admin') - async def errors_command(bot, update, user_record): - return await _errors_command(bot, update, user_record) - - for exception in allowed_during_maintenance: - telegram_bot.allow_during_maintenance(exception) - - @telegram_bot.command(command='/maintenance', aliases=[], - show_in_keyboard=False, - description=admin_messages[ - 'maintenance_command']['description'], - authorization_level='admin') - async def maintenance_command(bot, update, user_record): - return await _maintenance_command(bot, update, user_record) + async def talk_button(bot, update, user_record, data): + return await _talk_button(bot, update, user_record, data) @telegram_bot.command(command='/version', aliases=[], @@ -1175,7 +1911,3 @@ def init(telegram_bot: Bot, return await _version_command(bot=bot, update=update, user_record=user_record) - - @telegram_bot.additional_task(when='BEFORE', bot=telegram_bot) - async def notify_version(bot): - return await notify_new_version(bot=bot) diff --git a/davtelepot/api.py b/davtelepot/api.py index c265f0b..bd88fc7 100644 --- a/davtelepot/api.py +++ b/davtelepot/api.py @@ -664,18 +664,33 @@ class TelegramBot: parameters=locals() ) - async def sendPoll(self, chat_id, question, options, - dummy=None, - disable_notification=None, - reply_to_message_id=None, + async def sendPoll(self, + chat_id: Union[int, str], + question: str, + options: List[str], + is_anonymous: bool = True, + type_: str = 'regular', + allows_multiple_answers: bool = False, + correct_option_id: int = None, + explanation: str = None, + explanation_parse_mode: str = None, + open_period: int = None, + close_date: int = None, + is_closed: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, reply_markup=None): """Send a native poll in a group, a supergroup or channel. See https://core.telegram.org/bots/api#sendpoll for details. """ + # To avoid shadowing `type`, this workaround is required + parameters = locals().copy() + parameters['type'] = parameters['type_'] + del parameters['type_'] return await self.api_request( 'sendPoll', - parameters=locals() + parameters=parameters ) async def sendChatAction(self, chat_id, action): @@ -1409,7 +1424,7 @@ class TelegramBot: parameters=locals() ) - async def setMyCommands(self, commands: List[Command]): + async def setMyCommands(self, commands: List[Union[Command, dict]]): """Change the list of the bot's commands. Use this method to change the list of the bot's commands. diff --git a/davtelepot/api_helper.py b/davtelepot/api_helper.py index c2f98f7..1e63e4c 100644 --- a/davtelepot/api_helper.py +++ b/davtelepot/api_helper.py @@ -136,6 +136,7 @@ async def print_api_methods(loop=None, parameters_table = tag break # Stop searching in siblings if is found description += tag.get_text() + # Methods start with a lowercase letter if method_name and method_name[0] == method_name[0].lower(): methods.append( TelegramApiMethod( diff --git a/davtelepot/bot.py b/davtelepot/bot.py index 742f074..3fe3a37 100644 --- a/davtelepot/bot.py +++ b/davtelepot/bot.py @@ -17,11 +17,11 @@ Usage database_url='my_other_db') @long_polling_bot.command('/foo') - async def foo_command(bot, update, user_record): + async def foo_command(bot, update, user_record, language): return "Bar!" @webhook_bot.command('/bar') - async def bar_command(bot, update, user_record): + async def bar_command(bot, update, user_record, language): return "Foo!" exit_state = Bot.run( @@ -579,17 +579,19 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): def administrators(self): return self._get_administrators(self) - async def message_router(self, update, user_record): + async def message_router(self, update, user_record, language): """Route Telegram `message` update to appropriate message handler.""" for key, value in update.items(): if key in self.message_handlers: - return await self.message_handlers[key](update, user_record) + return await self.message_handlers[key](update=update, + user_record=user_record, + language=language) logging.error( f"The following message update was received: {update}\n" "However, this message type is unknown." ) - async def edited_message_handler(self, update, user_record): + async def edited_message_handler(self, update, user_record, language=None): """Handle Telegram `edited_message` update.""" logging.info( f"The following update was received: {update}\n" @@ -597,7 +599,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) return - async def channel_post_handler(self, update, user_record): + async def channel_post_handler(self, update, user_record, language=None): """Handle Telegram `channel_post` update.""" logging.info( f"The following update was received: {update}\n" @@ -605,7 +607,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) return - async def edited_channel_post_handler(self, update, user_record): + async def edited_channel_post_handler(self, update, user_record, language=None): """Handle Telegram `edited_channel_post` update.""" logging.info( f"The following update was received: {update}\n" @@ -613,7 +615,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) return - async def inline_query_handler(self, update, user_record): + async def inline_query_handler(self, update, user_record, language=None): """Handle Telegram `inline_query` update. Answer it with results or log errors. @@ -648,7 +650,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): logging.info("Error answering inline query\n{}".format(e)) return - async def chosen_inline_result_handler(self, update, user_record): + async def chosen_inline_result_handler(self, update, user_record, language=None): """Handle Telegram `chosen_inline_result` update.""" if user_record is not None: user_id = user_record['telegram_id'] @@ -678,7 +680,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): self.chosen_inline_result_handlers[user_id][result_id] = handler return - async def callback_query_handler(self, update, user_record): + async def callback_query_handler(self, update, user_record, language=None): """Handle Telegram `callback_query` update. A callback query is sent when users press inline keyboard buttons. @@ -699,7 +701,8 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): answer = await _function( bot=self, update=update, - user_record=user_record + user_record=user_record, + language=language ) break if answer is None: @@ -733,7 +736,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): logging.error(e) return - async def shipping_query_handler(self, update, user_record): + async def shipping_query_handler(self, update, user_record, language=None): """Handle Telegram `shipping_query` update.""" logging.info( f"The following update was received: {update}\n" @@ -741,7 +744,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) return - async def pre_checkout_query_handler(self, update, user_record): + async def pre_checkout_query_handler(self, update, user_record, language=None): """Handle Telegram `pre_checkout_query` update.""" logging.info( f"The following update was received: {update}\n" @@ -749,7 +752,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) return - async def poll_handler(self, update, user_record): + async def poll_handler(self, update, user_record, language=None): """Handle Telegram `poll` update.""" logging.info( f"The following update was received: {update}\n" @@ -757,7 +760,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) return - async def text_message_handler(self, update, user_record): + async def text_message_handler(self, update, user_record, language=None): """Handle `text` message update.""" replier, reply = None, None text = update['text'].lower() @@ -817,56 +820,56 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) return - async def audio_file_handler(self, update, user_record): + async def audio_file_handler(self, update, user_record, language=None): """Handle `audio` file update.""" logging.info( "A audio file update was received, " "but this handler does nothing yet." ) - async def document_message_handler(self, update, user_record): + async def document_message_handler(self, update, user_record, language=None): """Handle `document` message update.""" logging.info( "A document message update was received, " "but this handler does nothing yet." ) - async def animation_message_handler(self, update, user_record): + async def animation_message_handler(self, update, user_record, language=None): """Handle `animation` message update.""" logging.info( "A animation message update was received, " "but this handler does nothing yet." ) - async def game_message_handler(self, update, user_record): + async def game_message_handler(self, update, user_record, language=None): """Handle `game` message update.""" logging.info( "A game message update was received, " "but this handler does nothing yet." ) - async def photo_message_handler(self, update, user_record): + async def photo_message_handler(self, update, user_record, language=None): """Handle `photo` message update.""" logging.info( "A photo message update was received, " "but this handler does nothing yet." ) - async def sticker_message_handler(self, update, user_record): + async def sticker_message_handler(self, update, user_record, language=None): """Handle `sticker` message update.""" logging.info( "A sticker message update was received, " "but this handler does nothing yet." ) - async def video_message_handler(self, update, user_record): + async def video_message_handler(self, update, user_record, language=None): """Handle `video` message update.""" logging.info( "A video message update was received, " "but this handler does nothing yet." ) - async def voice_message_handler(self, update, user_record): + async def voice_message_handler(self, update, user_record, language=None): """Handle `voice` message update.""" replier, reply = None, None user_id = update['from']['id'] if 'from' in update else None @@ -896,21 +899,21 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) return - async def video_note_message_handler(self, update, user_record): + async def video_note_message_handler(self, update, user_record, language=None): """Handle `video_note` message update.""" logging.info( "A video_note message update was received, " "but this handler does nothing yet." ) - async def contact_message_handler(self, update, user_record): + async def contact_message_handler(self, update, user_record, language=None): """Handle `contact` message update.""" logging.info( "A contact message update was received, " "but this handler does nothing yet." ) - async def location_message_handler(self, update, user_record): + async def location_message_handler(self, update, user_record, language=None): """Handle `location` message update.""" replier, reply = None, None user_id = update['from']['id'] if 'from' in update else None @@ -940,56 +943,56 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) return - async def venue_message_handler(self, update, user_record): + async def venue_message_handler(self, update, user_record, language=None): """Handle `venue` message update.""" logging.info( "A venue message update was received, " "but this handler does nothing yet." ) - async def poll_message_handler(self, update, user_record): + async def poll_message_handler(self, update, user_record, language=None): """Handle `poll` message update.""" logging.info( "A poll message update was received, " "but this handler does nothing yet." ) - async def new_chat_members_message_handler(self, update, user_record): + async def new_chat_members_message_handler(self, update, user_record, language=None): """Handle `new_chat_members` message update.""" logging.info( "A new_chat_members message update was received, " "but this handler does nothing yet." ) - async def left_chat_member_message_handler(self, update, user_record): + async def left_chat_member_message_handler(self, update, user_record, language=None): """Handle `left_chat_member` message update.""" logging.info( "A left_chat_member message update was received, " "but this handler does nothing yet." ) - async def new_chat_title_message_handler(self, update, user_record): + async def new_chat_title_message_handler(self, update, user_record, language=None): """Handle `new_chat_title` message update.""" logging.info( "A new_chat_title message update was received, " "but this handler does nothing yet." ) - async def new_chat_photo_message_handler(self, update, user_record): + async def new_chat_photo_message_handler(self, update, user_record, language=None): """Handle `new_chat_photo` message update.""" logging.info( "A new_chat_photo message update was received, " "but this handler does nothing yet." ) - async def delete_chat_photo_message_handler(self, update, user_record): + async def delete_chat_photo_message_handler(self, update, user_record, language=None): """Handle `delete_chat_photo` message update.""" logging.info( "A delete_chat_photo message update was received, " "but this handler does nothing yet." ) - async def group_chat_created_message_handler(self, update, user_record): + async def group_chat_created_message_handler(self, update, user_record, language=None): """Handle `group_chat_created` message update.""" logging.info( "A group_chat_created message update was received, " @@ -1004,63 +1007,63 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): "but this handler does nothing yet." ) - async def channel_chat_created_message_handler(self, update, user_record): + async def channel_chat_created_message_handler(self, update, user_record, language=None): """Handle `channel_chat_created` message update.""" logging.info( "A channel_chat_created message update was received, " "but this handler does nothing yet." ) - async def migrate_to_chat_id_message_handler(self, update, user_record): + async def migrate_to_chat_id_message_handler(self, update, user_record, language=None): """Handle `migrate_to_chat_id` message update.""" logging.info( "A migrate_to_chat_id message update was received, " "but this handler does nothing yet." ) - async def migrate_from_chat_id_message_handler(self, update, user_record): + async def migrate_from_chat_id_message_handler(self, update, user_record, language=None): """Handle `migrate_from_chat_id` message update.""" logging.info( "A migrate_from_chat_id message update was received, " "but this handler does nothing yet." ) - async def pinned_message_message_handler(self, update, user_record): + async def pinned_message_message_handler(self, update, user_record, language=None): """Handle `pinned_message` message update.""" logging.info( "A pinned_message message update was received, " "but this handler does nothing yet." ) - async def invoice_message_handler(self, update, user_record): + async def invoice_message_handler(self, update, user_record, language=None): """Handle `invoice` message update.""" logging.info( "A invoice message update was received, " "but this handler does nothing yet." ) - async def successful_payment_message_handler(self, update, user_record): + async def successful_payment_message_handler(self, update, user_record, language=None): """Handle `successful_payment` message update.""" logging.info( "A successful_payment message update was received, " "but this handler does nothing yet." ) - async def connected_website_message_handler(self, update, user_record): + async def connected_website_message_handler(self, update, user_record, language=None): """Handle `connected_website` message update.""" logging.info( "A connected_website message update was received, " "but this handler does nothing yet." ) - async def passport_data_message_handler(self, update, user_record): + async def passport_data_message_handler(self, update, user_record, language=None): """Handle `passport_data` message update.""" logging.info( "A passport_data message update was received, " "but this handler does nothing yet." ) - async def dice_handler(self, update, user_record): + async def dice_handler(self, update, user_record, language=None): """Handle `dice` message update.""" logging.info( "A dice message update was received, " @@ -2013,7 +2016,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): """ self._allowed_during_maintenance.append(criterion) - async def handle_update_during_maintenance(self, update, user_record=None): + async def handle_update_during_maintenance(self, update, user_record=None, language=None): """Handle an update while bot is under maintenance. Handle all types of updates. @@ -2106,7 +2109,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): Decorate command handlers like this: ``` @bot.command('/my_command', ['Button'], True, "My command", 'user') - async def command_handler(bot, update, user_record): + async def command_handler(bot, update, user_record, language): return "Result" ``` When a message text starts with `/command[@bot_name]`, or with an @@ -2165,7 +2168,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): command = command.strip('/ ').lower() def command_decorator(command_handler): - async def decorated_command_handler(bot, update, user_record): + async def decorated_command_handler(bot, update, user_record, language=None): logging.info( f"Command `{command}@{bot.name}` called by " f"`{update['from'] if 'from' in update else update['chat']}`" @@ -2223,7 +2226,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): return 'from' in update @bot.parser(custom_criteria, authorization_level='user') - async def text_parser(bot, update, user_record): + async def text_parser(bot, update, user_record, language): return "Result" ``` If condition evaluates True when run on a message text @@ -2241,7 +2244,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) def parser_decorator(parser): - async def decorated_parser(bot, update, user_record): + async def decorated_parser(bot, update, user_record, language=None): logging.info( f"Text message update matching condition " f"`{condition.__name__}@{bot.name}` from " @@ -2310,7 +2313,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ``` @bot.button('a_prefix:///', description="A button", authorization_level='user') - async def button_handler(bot, update, user_record, data): + async def button_handler(bot, update, user_record, language, data): return "Result" ``` `separator` will be used to parse callback data received when a button @@ -2325,7 +2328,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) def button_decorator(handler): - async def decorated_button_handler(bot, update, user_record): + async def decorated_button_handler(bot, update, user_record, language=None): logging.info( f"Button `{update['data']}`@{bot.name} pressed by " f"`{update['from']}`" @@ -2382,7 +2385,7 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): ) def query_decorator(handler): - async def decorated_query_handler(bot, update, user_record): + async def decorated_query_handler(bot, update, user_record, language=None): logging.info( f"Inline query matching condition " f"`{condition.__name__}@{bot.name}` from " @@ -2881,9 +2884,12 @@ class Bot(TelegramBot, ObjectWithDatabase, MultiLanguageObject): for key, value in update.items(): if key in self.routing_table: user_record = self.get_user_record(update=value) + language = self.get_language(update=update, + user_record=user_record) return await self.routing_table[key]( update=value, - user_record=user_record + user_record=user_record, + language=language ) logging.error(f"Unknown type of update.\n{update}") diff --git a/davtelepot/messages.py b/davtelepot/messages.py index 3df1d7f..3b04159 100644 --- a/davtelepot/messages.py +++ b/davtelepot/messages.py @@ -1,6 +1,24 @@ """Default messages for bot functions.""" default_admin_messages = { + 'cancel': { + 'button': { + 'en': "↩️ Cancel", + 'it': "↩️ Annulla" + }, + 'done': { + 'en': "↩️ Operation cancelled", + 'it': "↩️ Operazione annullata", + }, + 'lower': { + 'en': "cancel", + 'it': "annulla", + }, + }, + 'confirm': { + 'en': "🔄 Click again to confirm", + 'it': "🔄 Clicka di nuovo per confermare", + }, 'db_command': { 'description': { 'en': "Ask for bot database via Telegram", @@ -23,6 +41,12 @@ default_admin_messages = { 'it': "Database inviato." } }, + 'error': { + 'text': { + 'en': "❌️ Error!", + 'it': "❌️ Errore!" + }, + }, 'errors_command': { 'description': { 'en': "Receive bot error log file, if set", @@ -66,6 +90,302 @@ default_admin_messages = { "L'ordine è cronologico, con i messaggi nuovi in alto." } }, + 'father_command': { + 'back': { + 'en': "↩️ Back", + 'it': "↩️ Indietro", + }, + 'description': { + 'en': "Edit the @BotFather commands", + 'it': "Modifica i comandi con @BotFather", + }, + 'error': { + 'en': "❌ Error! ❌", + 'it': "❌ Errore! ❌", + }, + 'modes': [ + { + 'id': "get", + 'name': { + 'en': "See", + 'it': "Consulta" + }, + 'symbol': "ℹ️", + 'description': { + 'en': "See commands stored by @BotFather", + 'it': "Consulta i comandi salvati su @BotFather" + }, + }, + { + 'id': "set", + 'name': { + 'en': "Change", + 'it': "Modifica" + }, + 'symbol': "✏️", + 'description': { + 'en': "Change commands stored by @BotFather", + 'it': "Modifica i comandi salvati su @BotFather" + }, + }, + { + 'id': "settings", + 'name': { + 'en': "Settings", + 'it': "Impostazioni" + }, + 'symbol': "⚙️", + 'description': { + 'en': "Set commands to hide or to add", + 'it': "Imposta comandi da nascondere o aggiungere" + }, + }, + ], + 'set': { + 'button': { + 'en': "⚠️ Set these commands 🔧", + 'it': "⚠️ Imposta questi comandi 🔧", + }, + 'done': { + 'en': "✅ Done!", + 'it': "✅ Fatto!", + }, + 'error': { + 'en': "Something went wrong 😕", + 'it': "Qualcosa è andato storto 😕", + }, + 'header': { + 'en': "✏️ Change commands stored by @BotFather 🤖", + 'it': "✏️ Modifica i comandi salvati su @BotFather 🤖", + }, + 'legend': { + 'en': "Legend\n" + "✅ Already stored\n" + "✏️ New description\n" + "☑ New command\n" + "❌ Will be removed", + 'it': "Legenda\n" + "✅ Già presente\n" + "✏️ Nuova descrizione\n" + "☑ Nuovo comando\n" + "❌ Comando da eliminare", + }, + 'no_change': { + 'en': "❌ No change detected", + 'it': "❌ Nessuna modifica", + }, + }, + 'settings': { + 'browse_records': { + 'en': "✏️ Edit BotFather settings ⚙️\n\n" + "Select a record to edit.\n\n" + "{commands_list}\n\n" + "Legend\n" + "➕ Added commands\n" + "➖ Hidden commands\n\n" + "Showing records from {record_interval[0]} to " + "{record_interval[1]} of {record_interval[2]}", + 'it': "✏️ Modifica impostazioni di BotFather ⚙\n\n️" + "Seleziona un'impostazione da modificare.\n\n" + "{commands_list}\n\n" + "Legenda\n" + "➕ Comandi aggiunti\n" + "➖ Comandi nascosti\n\n" + "Record da {record_interval[0]} a " + "{record_interval[1]} di {record_interval[2]}", + }, + 'modes': { + 'add': { + 'add': { + 'done': { + 'en': "➕️️ Added additional command\n\n" + "Command: {command}\n" + "Description: {description}", + 'it': "➕️️ Inserito comando aggiuntivo\n\n" + "Comando: {command}\n" + "Descrizione: {description}", + }, + 'popup': { + 'en': "Write the command to add", + 'it': "Scrivimi il comando da aggiungere", + }, + 'text': { + 'en': "Write the command to add or /cancel this operation", + 'it': "Scrivimi il comando da aggiungere o /annulla", + }, + }, + 'description': { + 'en': "Add command to default list", + 'it': "Aggiungi un comando dalla lista autogenerata" + }, + 'edit': { + 'done': { + 'en': "✏️ Edited additional command\n\n" + "Command: {command}\n" + "Description: {description}", + 'it': "✏️ Comando da nascondere modificato\n\n" + "Comando: {command}\n" + "Descrizione: {description}", + }, + }, + 'error': { + 'description_too_long': { + 'en': "Description is too long\n\n" + "Description length must be 3-256 chars.", + 'it': "Descrizione troppo lunga\n\n" + "La descrizione deve essere di 3-256 caratteri.", + }, + 'duplicate_record': { + 'en': "Duplicate record\n\n" + "Command is already being added to default " + "output. Edit that record if you need to.", + 'it': "Record già presente\n\n" + "Questo comando è già aggiunto a quelli di " + "default. Modifica il record già presente se " + "necessario.", + }, + 'missing_description': { + 'en': "Missing description\n\n" + "Additional commands must have a description " + "(3-256 chars).", + 'it': "Descrizione mancante\n\n" + "I comandi aggiuntivi devono avere una " + "descrizione di 3-256 caratteri.", + }, + 'unhandled_exception': { + 'en': "❌ Unhandled exception ⚠️", + 'it': "❌ Errore imprevisto ⚠️", + }, + }, + 'name': { + 'en': "Add", + 'it': "Aggiungi" + }, + 'symbol': "➕️", + }, + 'hide': { + 'add': { + 'done': { + 'en': "➖ Added hidden command\n\n" + "Command: {command}\n", + 'it': "➖ Comando da nascondere aggiunto" + "Comando: {command}\n", + }, + 'popup': { + 'en': "Write the command to hide", + 'it': "Scrivimi il comando da nascondere", + }, + 'text': { + 'en': "Write the command to hide or /cancel this operation", + 'it': "Scrivimi il comando da nascondere o /annulla", + } + }, + 'description': { + 'en': "Hide command from default list", + 'it': "Nascondi un comando dalla lista autogenerata" + }, + 'edit': { + 'done': { + 'en': "✏️ Edited hidden command\n\n" + "Command: {command}\n" + "Description: {description}", + 'it': "✏️ Comando da nascondere modificato\n\n" + "Comando: {command}\n" + "Descrizione: {description}", + }, + }, + 'name': { + 'en': "Hide", + 'it': "Nascondi" + }, + 'symbol': "➖️", + }, + 'edit': { + 'button': { + 'en': "✏️ Edit record", + 'it': "✏️ Modifica record" + }, + 'description': { + 'en': "Edit added or hidden commands", + 'it': "Modifica i comandi aggiunti o nascosti" + }, + 'edit': { + 'popup': { + 'en': "Write the new description", + 'it': "Scrivimi la nuova descrizione", + }, + 'text': { + 'en': "Write the new description for command " + "{command} or /cancel", + 'it': "Scrivimi la nuova descrizione per il " + "comando {command} o /annulla", + }, + 'done': { + 'en': "✏️ Edit succeeded ✅\n\n" + "Command: {command}\n""" + "Description: {description}", + 'it': "✏️ Modifica completata ✅\n\n" + "Comando: {command}\n""" + "Descrizione: {description}", + } + }, + 'name': { + 'en': "Edit", + 'it': "Modifica" + }, + 'panel': { + 'delete': { + 'button': { + 'en': "❌ Delete record", + 'it': "❌ Elimina record", + }, + 'done': { + 'popup': { + 'en': "Record deleted ✅", + 'it': "Record eliminato ✅", + }, + 'text': { + 'en': "Record deleted ✅", + 'it': "Record eliminato ✅", + }, + }, + }, + 'edit_description': { + 'button': { + 'en': "✏️ Edit description", + 'it': "✏️ Modifica descrizione", + }, + }, + 'text': { + 'en': "✏️ Edit record ✅\n\n" + "Command: {command}\n""" + "Description: {description}", + 'it': "✏️ Modifica record\n\n" + "Comando: {command}\n""" + "Descrizione: {description}", + }, + }, + 'symbol': "✏️", + }, + }, + 'panel': { + 'en': "🤖 @BotFather settings ⚙️\n\n" + "➕ Additional commands\n" + "{additional_commands}\n\n" + "➖ Hidden commands\n" + "{hidden_commands}", + 'it': "⚙️ Impostazioni di @BotFather 🤖\n\n" + "➕ Comandi aggiuntivi\n" + "{additional_commands}\n\n" + "➖ Comandi nascosti\n" + "{hidden_commands}", + }, + }, + 'title': { + 'en': "🤖 BotFather", + 'it': "🤖 BotFather", + }, + }, 'log_command': { 'description': { 'en': "Receive bot log file, if set", diff --git a/davtelepot/utilities.py b/davtelepot/utilities.py index f89315c..622cad8 100644 --- a/davtelepot/utilities.py +++ b/davtelepot/utilities.py @@ -18,6 +18,8 @@ import time from difflib import SequenceMatcher # Third party modules +from typing import Union + import aiohttp from aiohttp import web from bs4 import BeautifulSoup @@ -703,7 +705,7 @@ class Confirmable(): CONFIRM_TIMEDELTA = datetime.timedelta(seconds=10) - def __init__(self, confirm_timedelta=None): + def __init__(self, confirm_timedelta: Union[datetime.timedelta, int] = None): """Instantiate Confirmable instance. If `confirm_timedelta` is not passed, @@ -711,6 +713,8 @@ class Confirmable(): """ if confirm_timedelta is None: confirm_timedelta = self.__class__.CONFIRM_TIMEDELTA + elif type(confirm_timedelta) is int: + confirm_timedelta = datetime.timedelta(seconds=confirm_timedelta) self.set_confirm_timedelta(confirm_timedelta) self._confirm_datetimes = {}