diff --git a/src/bot.py b/src/bot.py index 0495ec1..c0ac228 100644 --- a/src/bot.py +++ b/src/bot.py @@ -25,6 +25,7 @@ ) from .jobs.utils import get_job_runnable from .tg import handlers, sender +from .tg.handler_registry import HANDLER_REGISTRY from .tg.handlers.utils import admin_only, direct_message_only, manager_only logging.addLevelName(USAGE_LOG_LEVEL, "NOTICE") @@ -39,6 +40,17 @@ def usage(self, message, *args, **kws): class SysBlokBot: + """ + Main Bot class responsible for initialization and handler registration. + + Attributes: + config_manager: Manages configuration settings. + application: Telegram Application instance. + telegram_sender: wrapper for sending messages. + app_context: Application context state. + handlers_info: Dictionary to store handler descriptions for /help command. + """ + def __init__( self, config_manager: ConfigManager, @@ -92,351 +104,52 @@ def __init__( logger.info("SysBlokBot successfully initialized") def init_handlers(self): - # all command handlers defined here - # business logic cmds - self.add_admin_handler( - "send_trello_board_state", - CommandCategories.BROADCAST, - self.admin_broadcast_handler("trello_board_state_job"), - "рассылка сводки о состоянии доски", - ) - self.add_manager_handler( - "get_trello_board_state", - CommandCategories.SUMMARY, - self.manager_reply_handler("trello_board_state_job"), - "получить сводку о состоянии доски", - ) - self.add_manager_handler( - "get_publication_plans", - CommandCategories.SUMMARY, - self.manager_reply_handler("publication_plans_job"), - "получить сводку о публикуемыми на неделе постами", - ) - self.add_admin_handler( - "send_publication_plans", - CommandCategories.BROADCAST, - self.admin_broadcast_handler("publication_plans_job"), - "рассылка сводки о публикуемых на неделе постах", - ) - self.add_manager_handler( - "get_manager_status", - CommandCategories.SUMMARY, - direct_message_only( - self.manager_reply_handler("board_my_cards_razvitie_job") - ), - "получить мои карточки из доски Развитие", - ) - self.add_manager_handler( - "fill_posts_list", - CommandCategories.DEBUG, - direct_message_only(self.manager_reply_handler("fill_posts_list_job")), - "заполнить реестр постов (пока не работает)", - ) - self.add_manager_handler( - "fill_posts_list_focalboard", - CommandCategories.DEBUG, - direct_message_only( - self.manager_reply_handler("fill_posts_list_focalboard_job") - ), - "заполнить реестр постов из Focalboard (пока не работает)", - ) - self.add_admin_handler( - "hr_acquisition", - CommandCategories.HR, - self.manager_reply_handler("hr_acquisition_job"), - "обработать новые анкеты", - ) - self.add_admin_handler( - "hr_acquisition_pt", - CommandCategories.HR, - self.manager_reply_handler("hr_acquisition_pt_job"), - "обработать новые анкеты Пишу Тебе", - ) - self.add_manager_handler( - "get_hr_status", - CommandCategories.HR, - self.manager_reply_handler("hr_status_job"), - "получить статус по работе hr (по новичкам и участникам на испытательном)", - ) - self.add_admin_handler( - "send_hr_status", - CommandCategories.BROADCAST, - self.admin_broadcast_handler("hr_status_job"), - "разослать статус по работе hr (по новичкам и участинкам на испытательном)", - ) - self.add_manager_handler( - "create_folders_for_illustrators", - CommandCategories.REGISTRY, - self.manager_reply_handler("create_folders_for_illustrators_job"), - "создать папки для иллюстраторов", - ) - self.add_manager_handler( - "get_tasks_report_focalboard", - CommandCategories.MOST_USED, - # CommandCategories.SUMMARY, - direct_message_only(handlers.get_tasks_report_focalboard), - "получить список задач из Focalboard", - ) - - self.add_manager_handler( - "get_rubrics", - CommandCategories.MOST_USED, - direct_message_only(handlers.get_rubrics), - "получить рубрики из доски Редакция", - ) - - self.add_manager_handler( - "get_articles_rubric", - CommandCategories.DEBUG, # used to be SUMMARY but hiding for now - self.manager_reply_handler("trello_get_articles_rubric_job"), - "получить карточки по названию рубрики в трелло", - ) - self.add_manager_handler( - "get_chat_id", - CommandCategories.MOST_USED, - handlers.get_chat_id, - "получить chat_id (свой или группы)", - ) - self.add_manager_handler( - "manage_reminders", - CommandCategories.MOST_USED, - handlers.manage_reminders, - "настроить напоминания", - ) - self.add_manager_handler( - "get_fb_analytics_report", - CommandCategories.STATS, - self.manager_reply_handler("fb_analytics_report_job"), - "получить статистику facebook страницы за неделю", - ) - self.add_manager_handler( - "get_ig_analytics_report", - CommandCategories.STATS, - self.manager_reply_handler("ig_analytics_report_job"), - "получить статистику instagram страницы за неделю", - ) - self.add_manager_handler( - "get_tg_analytics_report", - CommandCategories.STATS, - self.manager_reply_handler("tg_analytics_report_job"), - "получить статистику telegram канала за неделю", - ) - self.add_manager_handler( - "get_report_from_sheet", - CommandCategories.SUMMARY, - self.manager_reply_handler("sheet_report_job"), - "получить статистику по табличке (например, оцифровка открыток)", - ) - # hidden from /help command for curator enrollment - self.add_manager_handler( - "enroll_curator", CommandCategories.HR, handlers.enroll_curator - ) - - # admin-only technical cmds - self.add_admin_handler( - "update_config", - CommandCategories.CONFIG, - self.admin_reply_handler("config_updater_job"), - "обновить конфиг вне расписания", - ) - self.add_admin_handler( - "list_jobs", - CommandCategories.CONFIG, - handlers.list_jobs, - "показать статус асинхронных задач", - ) - self.add_admin_handler( - "get_usage_list", - CommandCategories.CONFIG, - handlers.list_chats, - "показать места использование бота: пользователи и чаты", - ) - self.add_admin_handler( - "set_log_level", - CommandCategories.LOGGING, - handlers.set_log_level, - "изменить уровень логирования (info / debug)", - ) - self.add_admin_handler( - "mute_errors", - CommandCategories.LOGGING, - handlers.mute_errors, - "отключить логирование ошибок в телеграм", - ) - self.add_admin_handler( - "unmute_errors", - CommandCategories.LOGGING, - handlers.unmute_errors, - "включить логирование ошибок в телеграм", - ) - self.add_admin_handler( - "get_config", - CommandCategories.CONFIG, - handlers.get_config, - "получить текущий конфиг (частично или полностью)", - ) - self.add_admin_handler( - "get_config_jobs", - CommandCategories.CONFIG, - handlers.get_config_jobs, - "получить текущий конфиг джобов (частично или полностью)", - ) - self.add_admin_handler( - "reload_config_jobs", - CommandCategories.CONFIG, - handlers.reload_config_jobs, - "обновить конфиг джобов с Google-диска", - ) - self.add_admin_handler( - "set_config", - CommandCategories.CONFIG, - handlers.set_config, - "установить новое значение в конфиге", - ) - self.add_admin_handler( - "add_manager", - CommandCategories.MOST_USED, - handlers.add_manager, - "добавить менеджера в список", - ) - self.add_admin_handler( - "change_board", - CommandCategories.CONFIG, - handlers.change_board, - "изменить Trello board_id", - ) - self.add_admin_handler( - "send_reminders", - CommandCategories.BROADCAST, - self.admin_reply_handler("send_reminders_job"), - "отослать напоминания вне расписания", - ) - self.add_admin_handler( - "send_trello_curator_notification", - CommandCategories.BROADCAST, - self.admin_reply_handler("trello_board_state_notifications_job"), - "разослать кураторам состояние их карточек вне расписания", - ) - self.add_admin_handler( - "manage_all_reminders", - CommandCategories.MOST_USED, - handlers.manage_all_reminders, - "настроить все напоминания", - ) - self.add_admin_handler( - "get_roles_for_member", - # CommandCategories.HR, - CommandCategories.DEBUG, - handlers.get_roles_for_member, - "показать роли для участника", - ) - self.add_admin_handler( - "get_members_for_role", - # CommandCategories.HR, - CommandCategories.DEBUG, - handlers.get_members_for_role, - "показать участников для роли", - ) - self.add_admin_handler( - "check_chat_consistency", - CommandCategories.HR, - self.admin_reply_handler("hr_check_chat_consistency_job"), - "консистентность чата редакции", - ) - self.add_admin_handler( - "check_chat_consistency_frozen", - CommandCategories.HR, - self.admin_reply_handler("hr_check_chat_consistency_frozen_job"), - "консистентность чата редакции (замороженные участники)", - ) - self.add_admin_handler( - "get_members_without_telegram", - CommandCategories.HR, - self.admin_reply_handler("hr_get_members_without_telegram_job"), - ( - "активные участники без указанного телеграма" - "(телефон это 10+ цифр+-(), отсутствие включает #N/A и кириллицу)" - ), - ) - self.add_admin_handler( - "check_site_health", - CommandCategories.DATA_SYNC, - self.admin_reply_handler("site_health_check_job"), - "проверка статуса сайта", - ) - self.add_admin_handler( - "get_chat_data", - CommandCategories.DEBUG, - handlers.get_chat_data, - "get_chat_data", - ) - self.add_admin_handler( - "clean_chat_data", - CommandCategories.DEBUG, - handlers.clean_chat_data, - "clean_chat_data", - ) - self.add_admin_handler( - "get_managers", - CommandCategories.MOST_USED, - handlers.get_managers, - "get_managers", - ) - - # sample handler - self.add_handler( - "sample_handler", - self.admin_reply_handler("sample_job"), - ) - - # db commands hidden from /help command - self.add_handler( - "db_fetch_authors_sheet", - self.admin_reply_handler("db_fetch_authors_sheet_job"), - ) - self.add_handler( - "db_fetch_curators_sheet", - self.admin_reply_handler("db_fetch_curators_sheet_job"), - ) - self.add_handler( - "db_fetch_team_sheet", - self.admin_reply_handler("db_fetch_team_sheet_job"), - ) - self.add_handler( - "db_fetch_strings_sheet", - self.admin_reply_handler("db_fetch_strings_sheet_job"), - ) - self.add_admin_handler( - "db_fetch_all_team_members", - CommandCategories.MOST_USED, - self.admin_reply_handler("db_fetch_all_team_members_job"), - "db_fetch_all_team_members", - ) - self.add_admin_handler( - "backfill_telegram_user_ids", - CommandCategories.DATA_SYNC, - self.admin_reply_handler("backfill_telegram_user_ids_job"), - "backfill Telegram user IDs from team member usernames", - ) + """ + Initializes Telegram handlers based on the configuration in `HANDLER_REGISTRY`. + Iterates over the registry, resolves handlers (direct or job-based), + applies wrappers (e.g. direct_only), and registers them with the application. + """ + # Register handlers from registry + for config in HANDLER_REGISTRY: + # Resolve handler + handler = config.handler_func + if config.job_name: + if config.job_type == "admin_broadcast": + handler = self.admin_broadcast_handler(config.job_name) + elif config.job_type == "admin_reply": + handler = self.admin_reply_handler(config.job_name) + elif config.job_type == "manager_reply": + handler = self.manager_reply_handler(config.job_name) + elif config.job_type == "user_reply": + handler = self.user_handler(config.job_name) + + # Apply modifiers + if config.direct_only: + handler = direct_message_only(handler) + + # Register + if config.access_level == "admin": + self.add_admin_handler( + config.command, config.category, handler, config.description + ) + elif config.access_level == "manager": + self.add_manager_handler( + config.command, config.category, handler, config.description + ) + elif config.access_level == "user": + self.add_user_handler( + config.command, config.category, handler, config.description + ) + else: # hidden + self.add_handler(config.command, handler) - # general purpose cmds - self.add_admin_handler( - "start", CommandCategories.DEBUG, handlers.start, "начать чат с ботом" - ) + # Special case: Help handler (requires dynamic self.handlers_info) self.add_admin_handler( "help", CommandCategories.DEBUG, - # CommandCategories.MOST_USED, lambda update, context: handlers.help(update, context, self.handlers_info), "получить список доступных команд", ) - self.add_admin_handler( - "shrug", - CommandCategories.DEBUG, - # CommandCategories.MOST_USED, - self.admin_reply_handler("shrug_job"), - "¯\\_(ツ)_/¯", - ) # on non-command user message diff --git a/src/tg/handler_registry.py b/src/tg/handler_registry.py new file mode 100644 index 0000000..dfd92a2 --- /dev/null +++ b/src/tg/handler_registry.py @@ -0,0 +1,444 @@ +from dataclasses import dataclass +from typing import Callable, Optional, Literal + +from ...consts import CommandCategories +from . import handlers + + +@dataclass +class HandlerConfig: + command: str + description: str = "" + category: Optional[CommandCategories] = None + access_level: Literal["admin", "manager", "user", "hidden"] = "hidden" + + # Logic configuration + handler_func: Optional[Callable] = None + job_name: Optional[str] = None + job_type: Literal[ + "admin_broadcast", "admin_reply", "manager_reply", "user_reply" + ] = "manager_reply" + + # Modifiers + direct_only: bool = False + + +HANDLER_REGISTRY = [ + # Business logic cmds + HandlerConfig( + command="send_trello_board_state", + category=CommandCategories.BROADCAST, + access_level="admin", + job_name="trello_board_state_job", + job_type="admin_broadcast", + description="рассылка сводки о состоянии доски", + ), + HandlerConfig( + command="get_trello_board_state", + category=CommandCategories.SUMMARY, + access_level="manager", + job_name="trello_board_state_job", + job_type="manager_reply", + description="получить сводку о состоянии доски", + ), + HandlerConfig( + command="get_publication_plans", + category=CommandCategories.SUMMARY, + access_level="manager", + job_name="publication_plans_job", + job_type="manager_reply", + description="получить сводку о публикуемыми на неделе постами", + ), + HandlerConfig( + command="send_publication_plans", + category=CommandCategories.BROADCAST, + access_level="admin", + job_name="publication_plans_job", + job_type="admin_broadcast", + description="рассылка сводки о публикуемых на неделе постах", + ), + HandlerConfig( + command="get_manager_status", + category=CommandCategories.SUMMARY, + access_level="manager", + job_name="board_my_cards_razvitie_job", + job_type="manager_reply", + direct_only=True, + description="получить мои карточки из доски Развитие", + ), + HandlerConfig( + command="fill_posts_list", + category=CommandCategories.DEBUG, + access_level="manager", + job_name="fill_posts_list_job", + job_type="manager_reply", + direct_only=True, + description="заполнить реестр постов (пока не работает)", + ), + HandlerConfig( + command="fill_posts_list_focalboard", + category=CommandCategories.DEBUG, + access_level="manager", + job_name="fill_posts_list_focalboard_job", + job_type="manager_reply", + direct_only=True, + description="заполнить реестр постов из Focalboard (пока не работает)", + ), + HandlerConfig( + command="hr_acquisition", + category=CommandCategories.HR, + access_level="admin", + job_name="hr_acquisition_job", + job_type="manager_reply", + description="обработать новые анкеты", + ), + HandlerConfig( + command="hr_acquisition_pt", + category=CommandCategories.HR, + access_level="admin", + job_name="hr_acquisition_pt_job", + job_type="manager_reply", + description="обработать новые анкеты Пишу Тебе", + ), + HandlerConfig( + command="get_hr_status", + category=CommandCategories.HR, + access_level="manager", + job_name="hr_status_job", + job_type="manager_reply", + description="получить статус по работе hr (по новичкам и участникам на испытательном)", + ), + HandlerConfig( + command="send_hr_status", + category=CommandCategories.BROADCAST, + access_level="admin", + job_name="hr_status_job", + job_type="admin_broadcast", + description="разослать статус по работе hr (по новичкам и участинкам на испытательном)", + ), + HandlerConfig( + command="create_folders_for_illustrators", + category=CommandCategories.REGISTRY, + access_level="manager", + job_name="create_folders_for_illustrators_job", + job_type="manager_reply", + description="создать папки для иллюстраторов", + ), + HandlerConfig( + command="get_tasks_report_focalboard", + category=CommandCategories.MOST_USED, + access_level="manager", + handler_func=handlers.get_tasks_report_focalboard, + direct_only=True, + description="получить список задач из Focalboard", + ), + HandlerConfig( + command="get_rubrics", + category=CommandCategories.MOST_USED, + access_level="manager", + handler_func=handlers.get_rubrics, + direct_only=True, + description="получить рубрики из доски Редакция", + ), + HandlerConfig( + command="get_articles_rubric", + category=CommandCategories.DEBUG, + access_level="manager", + job_name="trello_get_articles_rubric_job", + job_type="manager_reply", + description="получить карточки по названию рубрики в трелло", + ), + HandlerConfig( + command="get_chat_id", + category=CommandCategories.MOST_USED, + access_level="manager", + handler_func=handlers.get_chat_id, + description="получить chat_id (свой или группы)", + ), + HandlerConfig( + command="manage_reminders", + category=CommandCategories.MOST_USED, + access_level="manager", + handler_func=handlers.manage_reminders, + description="настроить напоминания", + ), + HandlerConfig( + command="get_fb_analytics_report", + category=CommandCategories.STATS, + access_level="manager", + job_name="fb_analytics_report_job", + job_type="manager_reply", + description="получить статистику facebook страницы за неделю", + ), + HandlerConfig( + command="get_ig_analytics_report", + category=CommandCategories.STATS, + access_level="manager", + job_name="ig_analytics_report_job", + job_type="manager_reply", + description="получить статистику instagram страницы за неделю", + ), + HandlerConfig( + command="get_tg_analytics_report", + category=CommandCategories.STATS, + access_level="manager", + job_name="tg_analytics_report_job", + job_type="manager_reply", + description="получить статистику telegram канала за неделю", + ), + HandlerConfig( + command="get_report_from_sheet", + category=CommandCategories.SUMMARY, + access_level="manager", + job_name="sheet_report_job", + job_type="manager_reply", + description="получить статистику по табличке (например, оцифровка открыток)", + ), + HandlerConfig( + command="enroll_curator", + category=CommandCategories.HR, + access_level="manager", + handler_func=handlers.enroll_curator, + description="", # hidden from help + ), + # Admin-only technical cmds + HandlerConfig( + command="update_config", + category=CommandCategories.CONFIG, + access_level="admin", + job_name="config_updater_job", + job_type="admin_reply", + description="обновить конфиг вне расписания", + ), + HandlerConfig( + command="list_jobs", + category=CommandCategories.CONFIG, + access_level="admin", + handler_func=handlers.list_jobs, + description="показать статус асинхронных задач", + ), + HandlerConfig( + command="get_usage_list", + category=CommandCategories.CONFIG, + access_level="admin", + handler_func=handlers.list_chats, + description="показать места использование бота: пользователи и чаты", + ), + HandlerConfig( + command="set_log_level", + category=CommandCategories.LOGGING, + access_level="admin", + handler_func=handlers.set_log_level, + description="изменить уровень логирования (info / debug)", + ), + HandlerConfig( + command="mute_errors", + category=CommandCategories.LOGGING, + access_level="admin", + handler_func=handlers.mute_errors, + description="отключить логирование ошибок в телеграм", + ), + HandlerConfig( + command="unmute_errors", + category=CommandCategories.LOGGING, + access_level="admin", + handler_func=handlers.unmute_errors, + description="включить логирование ошибок в телеграм", + ), + HandlerConfig( + command="get_config", + category=CommandCategories.CONFIG, + access_level="admin", + handler_func=handlers.get_config, + description="получить текущий конфиг (частично или полностью)", + ), + HandlerConfig( + command="get_config_jobs", + category=CommandCategories.CONFIG, + access_level="admin", + handler_func=handlers.get_config_jobs, + description="получить текущий конфиг джобов (частично или полностью)", + ), + HandlerConfig( + command="reload_config_jobs", + category=CommandCategories.CONFIG, + access_level="admin", + handler_func=handlers.reload_config_jobs, + description="обновить конфиг джобов с Google-диска", + ), + HandlerConfig( + command="set_config", + category=CommandCategories.CONFIG, + access_level="admin", + handler_func=handlers.set_config, + description="установить новое значение в конфиге", + ), + HandlerConfig( + command="add_manager", + category=CommandCategories.MOST_USED, + access_level="admin", + handler_func=handlers.add_manager, + description="добавить менеджера в список", + ), + HandlerConfig( + command="change_board", + category=CommandCategories.CONFIG, + access_level="admin", + handler_func=handlers.change_board, + description="изменить Trello board_id", + ), + HandlerConfig( + command="send_reminders", + category=CommandCategories.BROADCAST, + access_level="admin", + job_name="send_reminders_job", + job_type="admin_reply", + description="отослать напоминания вне расписания", + ), + HandlerConfig( + command="send_trello_curator_notification", + category=CommandCategories.BROADCAST, + access_level="admin", + job_name="trello_board_state_notifications_job", + job_type="admin_reply", + description="разослать кураторам состояние их карточек вне расписания", + ), + HandlerConfig( + command="manage_all_reminders", + category=CommandCategories.MOST_USED, + access_level="admin", + handler_func=handlers.manage_all_reminders, + description="настроить все напоминания", + ), + HandlerConfig( + command="get_roles_for_member", + category=CommandCategories.DEBUG, + access_level="admin", + handler_func=handlers.get_roles_for_member, + description="показать роли для участника", + ), + HandlerConfig( + command="get_members_for_role", + category=CommandCategories.DEBUG, + access_level="admin", + handler_func=handlers.get_members_for_role, + description="показать участников для роли", + ), + HandlerConfig( + command="check_chat_consistency", + category=CommandCategories.HR, + access_level="admin", + job_name="hr_check_chat_consistency_job", + job_type="admin_reply", + description="консистентность чата редакции", + ), + HandlerConfig( + command="check_chat_consistency_frozen", + category=CommandCategories.HR, + access_level="admin", + job_name="hr_check_chat_consistency_frozen_job", + job_type="admin_reply", + description="консистентность чата редакции (замороженные участники)", + ), + HandlerConfig( + command="get_members_without_telegram", + category=CommandCategories.HR, + access_level="admin", + job_name="hr_get_members_without_telegram_job", + job_type="admin_reply", + description="активные участники без указанного телеграма(телефон это 10+ цифр+-(), отсутствие включает #N/A и кириллицу)", + ), + HandlerConfig( + command="check_site_health", + category=CommandCategories.DATA_SYNC, + access_level="admin", + job_name="site_health_check_job", + job_type="admin_reply", + description="проверка статуса сайта", + ), + HandlerConfig( + command="get_chat_data", + category=CommandCategories.DEBUG, + access_level="admin", + handler_func=handlers.get_chat_data, + description="get_chat_data", + ), + HandlerConfig( + command="clean_chat_data", + category=CommandCategories.DEBUG, + access_level="admin", + handler_func=handlers.clean_chat_data, + description="clean_chat_data", + ), + HandlerConfig( + command="get_managers", + category=CommandCategories.MOST_USED, + access_level="admin", + handler_func=handlers.get_managers, + description="get_managers", + ), + # Sample and DB commands + HandlerConfig( + command="sample_handler", + access_level="hidden", + job_name="sample_job", + job_type="admin_reply", + ), + HandlerConfig( + command="db_fetch_authors_sheet", + access_level="hidden", + job_name="db_fetch_authors_sheet_job", + job_type="admin_reply", + ), + HandlerConfig( + command="db_fetch_curators_sheet", + access_level="hidden", + job_name="db_fetch_curators_sheet_job", + job_type="admin_reply", + ), + HandlerConfig( + command="db_fetch_team_sheet", + access_level="hidden", + job_name="db_fetch_team_sheet_job", + job_type="admin_reply", + ), + HandlerConfig( + command="db_fetch_strings_sheet", + access_level="hidden", + job_name="db_fetch_strings_sheet_job", + job_type="admin_reply", + ), + HandlerConfig( + command="db_fetch_all_team_members", + category=CommandCategories.MOST_USED, + access_level="admin", + job_name="db_fetch_all_team_members_job", + job_type="admin_reply", + description="db_fetch_all_team_members", + ), + HandlerConfig( + command="backfill_telegram_user_ids", + category=CommandCategories.DATA_SYNC, + access_level="admin", + job_name="backfill_telegram_user_ids_job", + job_type="admin_reply", + description="backfill Telegram user IDs from team member usernames", + ), + # General purpose + HandlerConfig( + command="start", + category=CommandCategories.DEBUG, + access_level="admin", + handler_func=handlers.start, + description="начать чат с ботом", + ), + HandlerConfig( + command="shrug", + category=CommandCategories.DEBUG, + access_level="admin", + job_name="shrug_job", + job_type="admin_reply", + description="¯\\_(ツ)_/¯", + ), + # HELP is handled specially or requires lambda +]