Queer European MD passionate about IT
Browse Source

Working on /father command

Davte 5 years ago
parent
commit
9072c3abe5
5 changed files with 565 additions and 12 deletions
  1. 411 6
      davtelepot/administration_tools.py
  2. 1 1
      davtelepot/api.py
  3. 2 1
      davtelepot/bot.py
  4. 146 3
      davtelepot/messages.py
  5. 5 1
      davtelepot/utilities.py

+ 411 - 6
davtelepot/administration_tools.py

@@ -15,7 +15,8 @@ import json
 import logging
 import logging
 import types
 import types
 
 
-from typing import Union, List
+from collections import OrderedDict
+from typing import Union, List, Tuple
 
 
 # Third party modules
 # Third party modules
 from sqlalchemy.exc import ResourceClosedError
 from sqlalchemy.exc import ResourceClosedError
@@ -27,9 +28,12 @@ from .utilities import (
     async_wrapper, CachedPage, Confirmator, extract, get_cleaned_text,
     async_wrapper, CachedPage, Confirmator, extract, get_cleaned_text,
     get_user, escape_html_chars, line_drawing_unordered_list, make_button,
     get_user, escape_html_chars, line_drawing_unordered_list, make_button,
     make_inline_keyboard, remove_html_tags, send_part_of_text_file,
     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,
 async def _forward_to(update,
                       bot: Bot,
                       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,
 def init(telegram_bot: Bot,
@@ -1048,6 +1424,26 @@ def init(telegram_bot: Bot,
         admin_messages = messages.default_admin_messages
         admin_messages = messages.default_admin_messages
     telegram_bot.messages['admin'] = admin_messages
     telegram_bot.messages['admin'] = admin_messages
     db = telegram_bot.db
     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:
     if 'talking_sessions' not in db.tables:
         table = db.create_table(
         table = db.create_table(
             table_name='users'
             table_name='users'
@@ -1111,8 +1507,17 @@ def init(telegram_bot: Bot,
                               if key in ('description', )
                               if key in ('description', )
                           },
                           },
                           authorization_level='admin')
                           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',
     @telegram_bot.command(command='/log',
                           aliases=[],
                           aliases=[],

+ 1 - 1
davtelepot/api.py

@@ -1424,7 +1424,7 @@ class TelegramBot:
             parameters=locals()
             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.
         """Change the list of the bot's commands.
 
 
         Use this method to 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(
                 answer = await _function(
                     bot=self,
                     bot=self,
                     update=update,
                     update=update,
-                    user_record=user_record
+                    user_record=user_record,
+                    language=language
                 )
                 )
                 break
                 break
         if answer is None:
         if answer is None:

+ 146 - 3
davtelepot/messages.py

@@ -67,14 +67,157 @@ default_admin_messages = {
         }
         }
     },
     },
     'father_command': {
     'father_command': {
+        'back': {
+            'en': "↩️ Back",
+            'it': "↩️ Indietro",
+        },
+        'confirm': {
+            'en': "🔄 Click again to confirm",
+            'it': "🔄 Clicka di nuovo per confermare",
+        },
         'description': {
         'description': {
             'en': "Edit the @BotFather commands",
             'en': "Edit the @BotFather commands",
             'it': "Modifica i comandi con @BotFather",
             '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': {
         'title': {
-            'en': "🤖 BotFather commands\n\n",
-            'it': "🤖 Comandi su BotFather\n\n",
-        }
+            'en': "🤖 <b>BotFather</b>",
+            'it': "🤖 <b>BotFather</b>",
+        },
     },
     },
     'log_command': {
     'log_command': {
         'description': {
         'description': {

+ 5 - 1
davtelepot/utilities.py

@@ -18,6 +18,8 @@ import time
 from difflib import SequenceMatcher
 from difflib import SequenceMatcher
 
 
 # Third party modules
 # Third party modules
+from typing import Union
+
 import aiohttp
 import aiohttp
 from aiohttp import web
 from aiohttp import web
 from bs4 import BeautifulSoup
 from bs4 import BeautifulSoup
@@ -703,7 +705,7 @@ class Confirmable():
 
 
     CONFIRM_TIMEDELTA = datetime.timedelta(seconds=10)
     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.
         """Instantiate Confirmable instance.
 
 
         If `confirm_timedelta` is not passed,
         If `confirm_timedelta` is not passed,
@@ -711,6 +713,8 @@ class Confirmable():
         """
         """
         if confirm_timedelta is None:
         if confirm_timedelta is None:
             confirm_timedelta = self.__class__.CONFIRM_TIMEDELTA
             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.set_confirm_timedelta(confirm_timedelta)
         self._confirm_datetimes = {}
         self._confirm_datetimes = {}