From b90f1a32cc3d2475644cb807dd4948e65193e55b Mon Sep 17 00:00:00 2001 From: Alexei Date: Sat, 13 May 2023 21:10:49 +0300 Subject: [PATCH] =?UTF-8?q?Start=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20?= =?UTF-8?q?=D0=B3=D0=BE=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot_modules/access_utils.py | 12 +++++ bot_modules/mod_agregator.py | 2 + bot_modules/mod_simple_message.py | 86 +++++++++++++++++++++++++++++++ bot_modules/start.py | 104 +++++++++++++------------------------- bot_sys/aiogram_bot.py | 8 ++- bot_sys/bot_messages.py | 68 +++++++++++++++++++++++++ bot_sys/interfaces.py | 5 ++ main.py | 49 ++++++++++-------- template/bd_item.py | 3 ++ template/simple_message.py | 14 ++--- 10 files changed, 254 insertions(+), 97 deletions(-) create mode 100644 bot_modules/access_utils.py create mode 100644 bot_modules/mod_simple_message.py create mode 100644 bot_sys/bot_messages.py diff --git a/bot_modules/access_utils.py b/bot_modules/access_utils.py new file mode 100644 index 0000000..abc3531 --- /dev/null +++ b/bot_modules/access_utils.py @@ -0,0 +1,12 @@ +# -*- coding: utf8 -*- +# Общественное достояние, 2023, Алексей Безбородов (Alexei Bezborodov) + +# Права пользователей. Утилиты + +table_name = 'module_access' +mod_name_field = 'modName' +moduleaccess_field = 'modAccess' +mod_default_access_field = 'itemDefaultAccess' + +def GetAccessForModuleRequest(module_name, access, default_access): + return f"INSERT OR IGNORE INTO {table_name} ({mod_name_field}, {moduleaccess_field}, {mod_default_access_field}) VALUES ('{module_name}', '{access}', '{default_access}');" diff --git a/bot_modules/mod_agregator.py b/bot_modules/mod_agregator.py index 1d7f8a2..715609a 100644 --- a/bot_modules/mod_agregator.py +++ b/bot_modules/mod_agregator.py @@ -11,3 +11,5 @@ class ModuleAgregator: def AddModule(a_Module): self.m_Modules[a_Module.GetName()] = a_Module + def GetModList(): + self.m_Modules.items() diff --git a/bot_modules/mod_simple_message.py b/bot_modules/mod_simple_message.py new file mode 100644 index 0000000..88b51c7 --- /dev/null +++ b/bot_modules/mod_simple_message.py @@ -0,0 +1,86 @@ +# -*- coding: utf8 -*- +# Общественное достояние, 2023, Алексей Безбородов (Alexei Bezborodov) + +# Простой модуль с одним сообщением + +from bot_sys import keyboard, user_access +from bot_modules import access_utils +from template import simple_message + +class SimpleMessageModule(mod_interface.IModule): + def __init__(self, a_StartMessage, a_StartButtonName, a_InitAccess, a_ChildModuleNameList, a_Bot, a_ModuleAgregator, a_BotMessages, a_BotButtons, a_Log): + self.m_ChildModuleNameList = a_ChildModuleNameList + self.m_InitAccess = a_InitAccess + self.m_Bot = a_Bot + self.m_ModuleAgregator = a_ModuleAgregator + self.m_BotMessages = a_BotMessages + self.m_BotButtons = a_BotButtons + self.m_Log = a_Log + + self.m_StartButtonName = CreateButton(f'{GetName()}_start', a_StartButtonName) + self.m_StartMessage = CreateMessage(f'{GetName()}_start', a_StartMessage) + + async def StartMessageHandler(a_Message, state = None): + return self.StartMessageHandler(a_Message, state) + self.m_StartMessageHandlerFunc = StartMessageHandler + + def GetAccess(): + return self.GetAccess() + self.m_GetAccessFunc = GetAccess + + def GetStartKeyboardButtons(a_Message, a_UserGroups): + return self.GetStartKeyboardButtons(a_Message, a_UserGroups) + self.m_GetStartKeyboardButtonsFunc = GetStartKeyboardButtons + + self.m_StartMessageHandler = simple_message.SimpleMessageTemplate( + self.m_Bot, + self.m_StartMessageHandlerFunc, + None, + self.m_GetStartKeyboardButtonsFunc, + self.m_GetAccessFunc + ) + + # Основной обработчик главного сообщения + async def StartMessageHandler(a_Message, state = None): + return simple_message.WorkFuncResult(self.m_StartMessage) + + def CreateMessage(a_MessageName, a_MessageDesc): + msg = self.m_BotMessages.CreateMessage(a_MessageName, a_MessageDesc, self.m_Log.GetTimeNow()) + return msg + + def CreateButton(a_ButtonName, a_ButtonDesc): + btn = self.m_BotButtons.CreateMessage(a_ButtonName, a_ButtonDesc, self.m_Log.GetTimeNow()) + return btn + + def GetStartKeyboardButtons(a_Message, a_UserGroups): + def GetButtons(a_ModNameList): + buttons = [] + for n in a_ModNameList: + m = self.m_ModuleAgregator.GetModule(n) + b = m.GetModuleButtons() + if not b is None or len(b) != 0: + buttons += b + return buttons + + buttons = GetButtons(self.m_ChildModuleNameList) + return MakeButtons(buttons, a_UserGroups) + + def GetInitBDCommands(): + return [ + access_utils.GetAccessForModuleRequest(GetName(), self.m_InitAccess, self.m_InitAccess), + ] + + def GetAccess(): + return self.m_Bot.GetAccessForModule(module_name) + + def GetModuleButtons(): + return [ + keyboard.ButtonWithAccess(self.m_StartButtonName, user_access.AccessMode.VIEW, GetAccess()), + ] + + def RegisterHandlers(): + self.m_Bot.RegisterMessageHandler( + self.m_StartMessageHandler, + bd_item.GetCheckForTextFunc(self.m_StartButtonName) + ) + diff --git a/bot_modules/start.py b/bot_modules/start.py index 5760b43..ef6ced9 100644 --- a/bot_modules/start.py +++ b/bot_modules/start.py @@ -3,76 +3,44 @@ # Стартовое меню -from bot_sys import log, config, keyboard, user_access, user_messages -from bot_modules import profile, projects, groups, access, backup, languages -from template import simple_message +from bot_sys import user_access +from bot_modules import mod_simple_message -from aiogram.dispatcher import Dispatcher - -def MSG(a_MessageName, a_MessageDesc): - def UpdateMSG(a_Message : user_messages.Message): - print(a_Message.m_MessageName, a_Message.m_MessageDesc) - globals()[a_Message.m_MessageName] = a_Message - user_messages.MSG(a_MessageName, a_MessageDesc, UpdateMSG, log.GetTimeNow()) - -# --------------------------------------------------------- -# БД -module_name = 'start' - -init_bd_cmds = [ -f"INSERT OR IGNORE INTO module_access (modName, modAccess, itemDefaultAccess) VALUES ('{module_name}', '{user_access.user_access_group_all}=+', '{user_access.user_access_group_all}=+');" -] - -# --------------------------------------------------------- -# Сообщения - -MSG('start_message', ''' +class ModuleStart(mod_simple_message.SimpleMessageModule): + def __init__(self, a_ChildModuleNameList, a_Bot, a_ModuleAgregator, a_BotMessages, a_BotButtons, a_Log): + msg = ''' Добро пожаловать! Выберите возможные действия на кнопках ниже ⌨''' -) - -start_menu_button_name = "☰ Главное меню" - -# --------------------------------------------------------- -# Работа с кнопками - -def GetStartKeyboardButtons(a_Message, a_UserGroups): - mods = [profile, projects, groups, access, backup, languages] - return keyboard.MakeKeyboardForMods(mods, a_UserGroups) - -# --------------------------------------------------------- -# Обработка сообщений - -# Первичное привестивие -async def StartMenu(a_Message, state = None): - user_id = str(a_Message.from_user.id) - user_name = str(a_Message.from_user.username) - first_name = str(a_Message.from_user.first_name) - last_name = str(a_Message.from_user.last_name) - is_bot = str(a_Message.from_user.is_bot) - language_code = str(a_Message.from_user.language_code) - profile.AddUser(user_id, user_name, first_name, last_name, is_bot, language_code) - log.Info(f'Пользователь {user_id} {user_name} авторизовался в боте. Полные данные {a_Message.from_user}.') - return simple_message.WorkFuncResult(start_message) - -# --------------------------------------------------------- -# API - -# Инициализация БД -def GetInitBDCommands(): - return init_bd_cmds - -def GetAccess(): - return access.GetAccessForModule(module_name) - -# Имена доступных кнопок -def GetModuleButtons(): - return [keyboard.ButtonWithAccess(start_menu_button_name, user_access.AccessMode.VIEW, GetAccess())] - -# Обработка кнопок -def RegisterHandlers(dp : Dispatcher): - dp.register_message_handler(simple_message.SimpleMessageTemplateLegacy(StartMenu, GetStartKeyboardButtons, GetAccess), commands = ['start']) - dp.register_message_handler(simple_message.SimpleMessageTemplateLegacy(StartMenu, GetStartKeyboardButtons, GetAccess), text = start_menu_button_name) - + start_menu_button_name = "☰ Главное меню" + a_InitAccess = f'{user_access.user_access_group_all}=+' + super().__init__(self, msg, start_menu_button_name, a_InitAccess, a_ChildModuleNameList, a_Bot, a_ModuleAgregator, a_BotMessages, a_BotButtons, a_Log) + + def GetName(): + return 'start' + + # Основной обработчик главного сообщения + async def StartMessageHandler(a_Message, state = None): + user_id = str(a_Message.from_user.id) + user_name = str(a_Message.from_user.username) + first_name = str(a_Message.from_user.first_name) + last_name = str(a_Message.from_user.last_name) + is_bot = str(a_Message.from_user.is_bot) + language_code = str(a_Message.from_user.language_code) + profile.AddUser(user_id, user_name, first_name, last_name, is_bot, language_code) + self.m_Log.Info(f'Пользователь {user_id} {user_name} авторизовался в боте. Полные данные {a_Message.from_user}.') + return super().StartMessageHandler(a_Message, state) + + def RegisterHandlers(): + super().RegisterHandlers() + self.m_Bot.RegisterMessageHandler( + self.m_StartMessageHandler, + bd_item.GetCheckForCommandsFunc(['start']) + ) + + + +#def GetStartKeyboardButtons(a_Message, a_UserGroups): +# mods = [profile, projects, groups, access, backup, languages] +# return keyboard.MakeKeyboardForMods(mods, a_UserGroups) diff --git a/bot_sys/aiogram_bot.py b/bot_sys/aiogram_bot.py index 9c3927d..2c49bd3 100644 --- a/bot_sys/aiogram_bot.py +++ b/bot_sys/aiogram_bot.py @@ -7,6 +7,7 @@ from aiogram import types from aiogram import Bot from aiogram.dispatcher import Dispatcher from aiogram.contrib.fsm_storage.memory import MemoryStorage +from aiogram.utils import executor class AiogramBot(interfaces.IBot): def __init__(self, a_TelegramBotApiToken, a_BDFileName, a_RootIDs, a_Log): @@ -74,8 +75,11 @@ class AiogramBot(interfaces.IBot): ) def RegisterMessageHandler(self, a_MessageHandler, a_CheckFunc): - self.m_Dispatcher.register_message_handler(a_MessageHandler, a_CheckFunc) + self.m_Dispatcher.register_message_handler(a_MessageHandler, a_CheckFunc) def RegisterCallbackHandler(self, a_CallbackHandler, a_CheckFunc): - self.m_Dispatcher.register_callback_query_handler(a_CallbackHandler, a_CheckFunc) + self.m_Dispatcher.register_callback_query_handler(a_CallbackHandler, a_CheckFunc) + + def StartPolling(self): + executor.start_polling(self.m_Dispatcher) diff --git a/bot_sys/bot_messages.py b/bot_sys/bot_messages.py new file mode 100644 index 0000000..c84b528 --- /dev/null +++ b/bot_sys/bot_messages.py @@ -0,0 +1,68 @@ +#-*-coding utf-8-*- +# Общественное достояние, 2023, Алексей Безбородов (Alexei Bezborodov) + +# Работа с сообщениями + +class BotMessage: + def __init__(self, a_BotMessages, a_MessageName : str, a_MessageDesc : str, a_Language : str, a_PhotoID : str, a_DateTime): + self.m_BotMessages = a_BotMessages + self.m_MessageName = a_MessageName + self.m_MessageDesc = a_MessageDesc + self.m_Language = a_Language + self.m_PhotoID = a_PhotoID + self.m_DateTime = a_DateTime + + def GetName(): + return self.m_MessageName + + def GetDesc(): + return self.m_MessageDesc + + def GetLanguage(): + return self.m_Language + + def GetPhotoID(): + return self.m_PhotoID + + def __str__(self): + msg = GetMessageForLang(self.m_Language) + return msg.GetDesc() + + def GetMessageForLang(self, a_Language): + last_update = self.m_BotMessages.m_LastUpdate + new_msg = self + if self.m_DateTime < last_update: + msg = self.m_BotMessages.GetMessages() + if not msg.get(a_Language, None): + a_Language = self.m_Language + if not msg.get(a_Language, None): + a_Language = self.m_BotMessages.a_DefaultLanguage + new_msg = msg[a_Language].get(self.m_MessageName, self) + if a_Language == self.m_Language: + self.m_MessageDesc = new_msg.m_MessageDesc + self.m_Language = new_msg.m_Language + self.m_PhotoID = new_msg.m_PhotoID + self.m_DateTime = new_msg.m_DateTime + return new_msg + +class BotMessages: + def __init__(self, a_DefaultLanguage): + self.a_DefaultLanguage = a_DefaultLanguage + self.m_Messages = {} + self.m_LastUpdate = None + + def GetMessages(): + return self.m_Messages + + def UpdateSignal(a_DateTime): + self.m_LastUpdate = a_DateTime + + def CreateMessage(a_MessageName, a_MessageDesc, a_DateTime): + cur_msg = BotMessage(a_MessageName, a_MessageDesc, self.a_DefaultLanguage, 0, a_DateTime) + msg = GetMessages() + if not msg.get(self.a_DefaultLanguage, None): + msg[self.a_DefaultLanguage] = {} + if not msg[self.a_DefaultLanguage].get(a_MessageName, None): + msg[self.a_DefaultLanguage][a_MessageName] = cur_msg + return cur_msg + diff --git a/bot_sys/interfaces.py b/bot_sys/interfaces.py index e39af40..3638f12 100644 --- a/bot_sys/interfaces.py +++ b/bot_sys/interfaces.py @@ -39,3 +39,8 @@ class IBot(ABC): @abstractmethod def RegisterCallbackHandler(self, a_CallbackHandler, a_CheckFunc): pass + + @abstractmethod + def StartPolling(self): + pass + diff --git a/main.py b/main.py index f91a227..dee9567 100644 --- a/main.py +++ b/main.py @@ -4,36 +4,43 @@ log_start_message = 'Бот успешно запущен!' import os -from aiogram import Bot, types -from aiogram.utils import executor -from aiogram.dispatcher import Dispatcher -from aiogram.contrib.fsm_storage.memory import MemoryStorage -import sqlite3 -from bot_sys import config, log, bot_bd, user_access, user_messages -from bot_modules import profile, start, projects, groups, access, backup, tasks, needs, comments, messages, languages - -storage = MemoryStorage() -bot = Bot(token = config.GetTelegramBotApiToken(), parse_mode = types.ParseMode.HTML) -dp = Dispatcher(bot, storage = storage) + +from bot_sys import config, log, bot_bd, user_access, aiogram_bot, bot_messages +from bot_modules import mod_agregator, start #, projects, groups, access, backup, tasks, needs, comments, messages, profile, languages + +g_Log = log +g_Bot = aiogram_bot.AiogramBot(config.GetTelegramBotApiToken(), bot_bd.GetBDFileName(), config.GetRootIDs(), g_Log): + +default_language = 'ru' + +g_BotMessages = bot_messages.BotMessages(default_language) +g_BotButtons = bot_messages.BotMessages(default_language) + +g_ModuleAgregator = mod_agregator.ModuleAgregator() + +mod_start = start.ModuleStart([], g_Bot, g_ModuleAgregator, g_BotMessages, g_BotButtons, g_Log) +g_ModuleAgregator.AddModule(mod_start) # Первичная инициализация модулей. Все модули должны быть прописаны в списке modules -modules = [tasks, access, profile, start, projects, groups, backup, needs, comments, messages, languages] +modules = g_ModuleAgregator.GetModList() # [start] #tasks, access, profile, projects, groups, backup, needs, comments, messages, languages] -init_bd_cmd = [] +init_bd_cmds = [] for m in modules: c = m.GetInitBDCommands() if not c is None: - init_bd_cmd += c + init_bd_cmds += c # Первичная инициализация базы данных -bot_bd.BDExecute(init_bd_cmd) +for c in init_bd_cmds: + g_Bot.SQLRequest(c, commit = True) -user_messages.UpdateSignal(log.GetTimeNow()) +g_BotMessages.UpdateSignal(g_Log.GetTimeNow()) +g_BotButtons.UpdateSignal(g_Log.GetTimeNow()) -languages.FlushLanguages() -messages.FlushMessages() +#languages.FlushLanguages() +#messages.FlushMessages() for m in modules: - m.RegisterHandlers(dp) + m.RegisterHandlers() # Юнит тесты модулей и файлов test_mods = [user_access] @@ -43,6 +50,6 @@ for m in test_mods: if __name__ == '__main__': # os.system('clear') # os.system('cls') - log.Success(log_start_message) + g_Log.Success(log_start_message) -executor.start_polling(dp) +g_Bot.StartPolling() diff --git a/template/bd_item.py b/template/bd_item.py index 1c6e1e0..610c04f 100644 --- a/template/bd_item.py +++ b/template/bd_item.py @@ -33,6 +33,9 @@ def GetCheckForPrefixFunc(a_Prefix): def GetCheckForTextFunc(a_Text): return lambda x: x.text == a_Text +def GetCheckForCommandsFunc(a_Commands): + return lambda x: x.commands == a_Commands + def GetKeyDataFromCallbackMessage(a_Message, a_Prefix): key_item_id = None if a_Prefix and hasattr(a_Message, 'data'): diff --git a/template/simple_message.py b/template/simple_message.py index 5f848b8..cfe8da3 100644 --- a/template/simple_message.py +++ b/template/simple_message.py @@ -8,9 +8,8 @@ from bot_modules import access from aiogram import types class WorkFuncResult(): - def __init__(self, a_StringMessage : str, photo_id = None, item_access = None): - self.string_message = a_StringMessage - self.photo_id = photo_id + def __init__(self, a_BotMessage, item_access = None): + self.m_BotMessage = a_BotMessage self.item_access = item_access def InfoMessageTemplate(a_Bot, a_HelpMessage, a_GetButtonsFunc, a_GetInlineButtonsFunc, a_AccessFunc, access_mode = user_access.AccessMode.VIEW): @@ -39,6 +38,7 @@ def SimpleMessageTemplate(a_Bot, a_WorkFunc, a_GetButtonsFunc, a_GetInlineButton async def SimpleMessage(a_Message : types.message, state = None): user_id = str(a_Message.from_user.id) + lang = str(a_Message.from_user.language_code) user_groups = a_Bot.GetUserGroupData(user_id) if not user_access.CheckAccess(a_Bot.GetRootIDs(), a_AccessFunc(), user_groups, access_mode): return await AccessDeniedMessage(user_id, a_Message, user_groups) @@ -47,17 +47,19 @@ def SimpleMessageTemplate(a_Bot, a_WorkFunc, a_GetButtonsFunc, a_GetInlineButton if res is None: return - msg = res.string_message + msg = res.m_BotMessage if msg is None: return if not res.item_access is None and not user_access.CheckAccess(a_Bot.GetRootIDs(), res.item_access, user_groups, access_mode): return await AccessDeniedMessage(user_id, a_Message, user_groups) + msg = msg.GetMessageForLang(lang) + await a_Bot.SendMessage( user_id, - msg, - res.photo_id, + msg.GetDesc(), + msg.GetPhotoID(), ProxyGetButtonsTemplate(a_GetInlineButtonsFunc)(a_Message, user_groups), ProxyGetButtonsTemplate(a_GetButtonsFunc)(a_Message, user_groups) )