Queer European MD passionate about IT
Procházet zdrojové kódy

Working on /father command

Davte před 5 roky
rodič
revize
9072c3abe5

+ 411 - 6
davtelepot/administration_tools.py

@@ -15,7 +15,8 @@ import json
 import logging
 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 +28,12 @@ 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
+
 
 async def _forward_to(update,
                       bot: Bot,
@@ -1024,9 +1028,381 @@ async def _load_talking_sessions(bot: Bot):
         )
 
 
-async def _father_command(bot, update):
+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'])
+    )
+
 
-    return
+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 = [
+        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)
+            if command['command'] not in hidden_commands
+        ] + additional_commands,
+        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)
+    )
+    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
+        ],
+        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
+
+
+async def edit_bot_father_settings_via_message(bot: Bot,
+                                               user_record: OrderedDict,
+                                               language: str,
+                                               mode: str):
+    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 = "🚧 Not implemented yet!"
+        # TODO: write messages, set individual text message parser to create records
+        # result = bot.get_message(
+        #     messages=modes[mode],
+        #     language=language
+        # )
+        # bot.set_individual_text_message_handler()
+    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 == 'get':
+        commands = await bot.getMyCommands()
+        text = '<code>' + '\n'.join(
+            "{c[command]} - {c[description]}".format(c=command)
+            for command in commands
+        ) + '</code>'
+        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_current_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', 'father_command', 'confirm',
+                    language=language
+                )
+            if stored_commands == current_commands:
+                text = bot.get_message(
+                    'admin', 'father_command', 'set', 'no_change',
+                    language=language
+                )
+            else:
+                await bot.setMyCommands(
+                    current_commands
+                )
+                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:
+            text = bot.get_message(
+                    'admin', 'father_command', 'set', 'header',
+                    language=language
+            ) + '\n\n' + '\n\n'.join([
+                '\n'.join(
+                    ('✅ ' if c in stored_commands else '☑️ ') + c['command']
+                    for c in current_commands
+                ),
+                '\n'.join(
+                    f'❌ {c["command"]}'
+                    for c in stored_commands if c not in current_commands
+                ),
+                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])
+                # TODO: show selected record and actions on it
+            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,
@@ -1048,6 +1424,26 @@ def init(telegram_bot: Bot,
         admin_messages = messages.default_admin_messages
     telegram_bot.messages['admin'] = admin_messages
     db = telegram_bot.db
+    if 'bot_father_commands' not in db.tables:
+        table = db.create_table(
+            table_name='bot_father_commands'
+        )
+        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'
@@ -1111,8 +1507,17 @@ def init(telegram_bot: Bot,
                               if key in ('description', )
                           },
                           authorization_level='admin')
-    async def father_command(bot, update):
-        return await _father_command(bot=bot, update=update)
+    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 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=[],

+ 1 - 1
davtelepot/api.py

@@ -1424,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.

+ 2 - 1
davtelepot/bot.py

@@ -701,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:

+ 146 - 3
davtelepot/messages.py

@@ -67,14 +67,157 @@ default_admin_messages = {
         }
     },
     'father_command': {
+        'back': {
+            'en': "↩️ Back",
+            'it': "↩️ Indietro",
+        },
+        'confirm': {
+            'en': "🔄 Click again to confirm",
+            'it': "🔄 Clicka di nuovo per confermare",
+        },
         '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!",
+            },
+            'header': {
+                'en': "✏️ <b>Change commands stored by @BotFather 🤖</b>",
+                'it': "✏️ <b>Modifica i comandi salvati su @BotFather 🤖</b>",
+            },
+            'legend': {
+                'en': "<b>Legend</b>\n"
+                      "✅ <i>Already stored</i>\n"
+                      "☑ <i>New command</i>\n"
+                      "❌ <i>Will be removed</i>",
+                'it': "<b>Legenda</b>\n"
+                      "✅ <i>Già presente</i>\n"
+                      "☑ <i>Nuovo comando</i>\n"
+                      "❌ <i>Comando eliminato</i>",
+            },
+            'no_change': {
+                'en': "❌ No change detected",
+                'it': "❌ Nessuna modifica",
+            },
+        },
+        'settings': {
+            'browse_records': {
+                'en': "✏️ <b>Edit BotFather settings</b> ⚙️\n\n"
+                      "Select a record to edit.\n"
+                      "Showing records from {record_interval[0]} to "
+                      "{record_interval[1]} of {record_interval[2]}\n\n"
+                      "<i>Legend</i>\n"
+                      "➕ Added commands\n"
+                      "➖ Hidden commands\n",
+                'it': "✏️ <b>Modifica impostazioni di BotFather</b> ⚙\n\n️"
+                      "Seleziona un'impostazione da modificare.\n"
+                      "Record da {record_interval[0]} a "
+                      "{record_interval[1]} di {record_interval[2]}\n\n"
+                      "<i>Legenda</i>\n"
+                      "➕ Comandi aggiunti\n"
+                      "➖ Comandi nascosti\n",
+            },
+            'modes': {
+                'add': {
+                    'symbol': "➕️",
+                    'name': {
+                        'en': "Add",
+                        'it': "Aggiungi"
+                    },
+                    'description': {
+                        'en': "Add command to default list",
+                        'it': "Aggiungi un comando dalla lista autogenerata"
+                    }
+                },
+                'hide': {
+                    'symbol': "➖️",
+                    'name': {
+                        'en': "Hide",
+                        'it': "Nascondi"
+                    },
+                    'description': {
+                        'en': "Hide command from default list",
+                        'it': "Nascondi un comando dalla lista autogenerata"
+                    }
+                },
+                'edit': {
+                    'symbol': "✏️",
+                    'name': {
+                        'en': "Edit",
+                        'it': "Modifica"
+                    },
+                    'description': {
+                        'en': "Edit added or hidden commands",
+                        'it': "Modifica i comandi aggiunti o nascosti"
+                    }
+                },
+            },
+            'panel': {
+                'en': "🤖 <b>@BotFather settings</b> ⚙️\n\n"
+                      "➕ <i>Additional commands</i>\n"
+                      "{additional_commands}\n\n"
+                      "➖ <i>Hidden commands</i>\n"
+                      "{hidden_commands}",
+                'it': "⚙️ <b>Impostazioni di @BotFather</b> 🤖\n\n"
+                      "➕ <i>Comandi aggiuntivi</i>\n"
+                      "{additional_commands}\n\n"
+                      "➖ <i>Comandi nascosti</i>\n"
+                      "{hidden_commands}",
+            },
+        },
         'title': {
-            'en': "🤖 BotFather commands\n\n",
-            'it': "🤖 Comandi su BotFather\n\n",
-        }
+            'en': "🤖 <b>BotFather</b>",
+            'it': "🤖 <b>BotFather</b>",
+        },
     },
     'log_command': {
         'description': {

+ 5 - 1
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 = {}