smart-bot-factory 0.3.7__py3-none-any.whl → 0.3.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of smart-bot-factory might be problematic. Click here for more details.

Files changed (45) hide show
  1. smart_bot_factory/admin/__init__.py +7 -7
  2. smart_bot_factory/admin/admin_events.py +483 -383
  3. smart_bot_factory/admin/admin_logic.py +234 -158
  4. smart_bot_factory/admin/admin_manager.py +68 -53
  5. smart_bot_factory/admin/admin_tester.py +46 -40
  6. smart_bot_factory/admin/timeout_checker.py +201 -153
  7. smart_bot_factory/aiogram_calendar/__init__.py +11 -3
  8. smart_bot_factory/aiogram_calendar/common.py +12 -18
  9. smart_bot_factory/aiogram_calendar/dialog_calendar.py +126 -64
  10. smart_bot_factory/aiogram_calendar/schemas.py +49 -28
  11. smart_bot_factory/aiogram_calendar/simple_calendar.py +94 -50
  12. smart_bot_factory/analytics/analytics_manager.py +414 -392
  13. smart_bot_factory/cli.py +204 -148
  14. smart_bot_factory/config.py +123 -102
  15. smart_bot_factory/core/bot_utils.py +474 -332
  16. smart_bot_factory/core/conversation_manager.py +287 -200
  17. smart_bot_factory/core/decorators.py +1129 -749
  18. smart_bot_factory/core/message_sender.py +287 -266
  19. smart_bot_factory/core/router.py +170 -100
  20. smart_bot_factory/core/router_manager.py +121 -83
  21. smart_bot_factory/core/states.py +4 -3
  22. smart_bot_factory/creation/__init__.py +1 -1
  23. smart_bot_factory/creation/bot_builder.py +320 -242
  24. smart_bot_factory/creation/bot_testing.py +440 -365
  25. smart_bot_factory/dashboard/__init__.py +1 -3
  26. smart_bot_factory/event/__init__.py +2 -7
  27. smart_bot_factory/handlers/handlers.py +676 -472
  28. smart_bot_factory/integrations/openai_client.py +218 -168
  29. smart_bot_factory/integrations/supabase_client.py +928 -637
  30. smart_bot_factory/message/__init__.py +18 -22
  31. smart_bot_factory/router/__init__.py +2 -2
  32. smart_bot_factory/setup_checker.py +162 -126
  33. smart_bot_factory/supabase/__init__.py +1 -1
  34. smart_bot_factory/supabase/client.py +631 -515
  35. smart_bot_factory/utils/__init__.py +2 -3
  36. smart_bot_factory/utils/debug_routing.py +38 -27
  37. smart_bot_factory/utils/prompt_loader.py +153 -120
  38. smart_bot_factory/utils/user_prompt_loader.py +55 -56
  39. smart_bot_factory/utm_link_generator.py +123 -116
  40. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/METADATA +3 -1
  41. smart_bot_factory-0.3.8.dist-info/RECORD +59 -0
  42. smart_bot_factory-0.3.7.dist-info/RECORD +0 -59
  43. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/WHEEL +0 -0
  44. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/entry_points.txt +0 -0
  45. {smart_bot_factory-0.3.7.dist-info → smart_bot_factory-0.3.8.dist-info}/licenses/LICENSE +0 -0
@@ -2,40 +2,41 @@
2
2
  Строитель ботов для Smart Bot Factory
3
3
  """
4
4
 
5
- import os
6
5
  import logging
6
+ import os
7
7
  from pathlib import Path
8
- from typing import Optional, Dict, Any, List
8
+ from typing import Any, Dict, List, Optional
9
9
 
10
- from ..config import Config
11
- from ..integrations.openai_client import OpenAIClient
12
- from ..integrations.supabase_client import SupabaseClient
13
- from ..core.conversation_manager import ConversationManager
14
10
  from ..admin.admin_manager import AdminManager
15
11
  from ..analytics.analytics_manager import AnalyticsManager
16
- from ..utils.prompt_loader import PromptLoader
12
+ from ..config import Config
13
+ from ..core.conversation_manager import ConversationManager
17
14
  from ..core.decorators import get_handlers_for_prompt
18
15
  from ..core.router_manager import RouterManager
16
+ from ..integrations.openai_client import OpenAIClient
17
+ from ..integrations.supabase_client import SupabaseClient
18
+ from ..utils.prompt_loader import PromptLoader
19
19
 
20
20
  logger = logging.getLogger(__name__)
21
21
 
22
+
22
23
  class BotBuilder:
23
24
  """
24
25
  Строитель ботов, который использует существующие файлы проекта
25
26
  и добавляет новые возможности через декораторы
26
27
  """
27
-
28
+
28
29
  def __init__(self, bot_id: str, config_dir: Optional[Path] = None):
29
30
  """
30
31
  Инициализация строителя бота
31
-
32
+
32
33
  Args:
33
34
  bot_id: Идентификатор бота
34
35
  config_dir: Путь к директории конфигурации (по умолчанию configs/bot_id)
35
36
  """
36
37
  self.bot_id = bot_id
37
38
  self.config_dir = config_dir or Path("bots") / bot_id
38
-
39
+
39
40
  # Компоненты бота
40
41
  self.config: Optional[Config] = None
41
42
  self.openai_client: Optional[OpenAIClient] = None
@@ -47,180 +48,190 @@ class BotBuilder:
47
48
  self.router_manager: Optional[RouterManager] = None
48
49
  self._telegram_routers: List = [] # Список Telegram роутеров
49
50
  self._start_handlers: List = [] # Список обработчиков on_start
50
-
51
+
51
52
  # Хуки для кастомизации process_user_message
52
53
  self._message_validators: List = [] # Валидация ДО обработки
53
- self._prompt_enrichers: List = [] # Обогащение системного промпта
54
- self._context_enrichers: List = [] # Обогащение контекста для AI
55
- self._response_processors: List = [] # Обработка ответа AI
56
- self._send_filters: List = [] # Фильтры перед отправкой пользователю
57
-
54
+ self._prompt_enrichers: List = [] # Обогащение системного промпта
55
+ self._context_enrichers: List = [] # Обогащение контекста для AI
56
+ self._response_processors: List = [] # Обработка ответа AI
57
+ self._send_filters: List = [] # Фильтры перед отправкой пользователю
58
+
58
59
  # Кастомный PromptLoader
59
60
  self._custom_prompt_loader = None
60
-
61
+
61
62
  # Кастомный процессор событий
62
63
  self._custom_event_processor = None
63
-
64
+
64
65
  # Флаги инициализации
65
66
  self._initialized = False
66
-
67
+
67
68
  logger.info(f"🏗️ Создан BotBuilder для бота: {bot_id}")
68
-
69
- async def build(self) -> 'BotBuilder':
69
+
70
+ async def build(self) -> "BotBuilder":
70
71
  """
71
72
  Строит и инициализирует все компоненты бота
72
-
73
+
73
74
  Returns:
74
75
  BotBuilder: Возвращает self для цепочки вызовов
75
76
  """
76
77
  if self._initialized:
77
78
  logger.warning(f"⚠️ Бот {self.bot_id} уже инициализирован")
78
79
  return self
79
-
80
+
80
81
  try:
81
82
  logger.info(f"🚀 Начинаем сборку бота {self.bot_id}")
82
-
83
+
83
84
  # 1. Инициализируем конфигурацию
84
85
  await self._init_config()
85
-
86
+
86
87
  # 2. Инициализируем клиенты
87
88
  await self._init_clients()
88
-
89
+
89
90
  # 3. Инициализируем менеджеры
90
91
  await self._init_managers()
91
-
92
+
92
93
  # 4. Обновляем промпты с информацией о доступных инструментах
93
94
  await self._update_prompts_with_tools()
94
-
95
+
95
96
  self._initialized = True
96
97
  logger.info(f"✅ Бот {self.bot_id} успешно собран и готов к работе")
97
-
98
+
98
99
  return self
99
-
100
+
100
101
  except Exception as e:
101
102
  logger.error(f"❌ Ошибка при сборке бота {self.bot_id}: {e}")
102
103
  raise
103
-
104
+
104
105
  async def _init_config(self):
105
106
  """Инициализация конфигурации"""
106
107
  logger.info(f"⚙️ Инициализация конфигурации для {self.bot_id}")
107
-
108
+
108
109
  # Устанавливаем BOT_ID в переменные окружения
109
- os.environ['BOT_ID'] = self.bot_id
110
-
110
+ os.environ["BOT_ID"] = self.bot_id
111
+
111
112
  # Загружаем .env файл если существует
112
113
  env_file = self.config_dir / ".env"
113
114
  if env_file.exists():
114
115
  from dotenv import load_dotenv
116
+
115
117
  load_dotenv(env_file)
116
118
  logger.info(f"📄 Загружен .env файл: {env_file}")
117
-
119
+
118
120
  # Устанавливаем путь к промптам относительно папки бота
119
121
  prompts_subdir = os.environ.get("PROMT_FILES_DIR", "prompts")
120
122
  logger.info(f"🔍 PROMT_FILES_DIR из .env: {prompts_subdir}")
121
-
123
+
122
124
  prompts_dir = self.config_dir / prompts_subdir
123
125
  logger.info(f"🔍 Путь к промптам: {prompts_dir}")
124
126
  logger.info(f"🔍 Существует ли папка: {prompts_dir.exists()}")
125
-
127
+
126
128
  # ВАЖНО: Устанавливаем правильный путь ДО создания Config
127
129
  os.environ["PROMT_FILES_DIR"] = str(prompts_dir)
128
130
  logger.info(f"📁 Установлен путь к промптам: {prompts_dir}")
129
-
131
+
130
132
  # Создаем конфигурацию
131
- logger.info(f"🔍 PROMT_FILES_DIR перед созданием Config: {os.environ.get('PROMT_FILES_DIR')}")
133
+ logger.info(
134
+ f"🔍 PROMT_FILES_DIR перед созданием Config: {os.environ.get('PROMT_FILES_DIR')}"
135
+ )
132
136
  self.config = Config()
133
- logger.info(f"✅ Конфигурация инициализирована")
134
-
137
+ logger.info("✅ Конфигурация инициализирована")
138
+
135
139
  async def _init_clients(self):
136
140
  """Инициализация клиентов"""
137
141
  logger.info(f"🔌 Инициализация клиентов для {self.bot_id}")
138
-
142
+
139
143
  # OpenAI клиент
140
144
  self.openai_client = OpenAIClient(
141
145
  api_key=self.config.OPENAI_API_KEY,
142
146
  model=self.config.OPENAI_MODEL,
143
147
  max_tokens=self.config.OPENAI_MAX_TOKENS,
144
- temperature=self.config.OPENAI_TEMPERATURE
148
+ temperature=self.config.OPENAI_TEMPERATURE,
145
149
  )
146
- logger.info(f"✅ OpenAI клиент инициализирован")
147
-
150
+ logger.info("✅ OpenAI клиент инициализирован")
151
+
148
152
  # Supabase клиент
149
153
  self.supabase_client = SupabaseClient(
150
154
  url=self.config.SUPABASE_URL,
151
155
  key=self.config.SUPABASE_KEY,
152
- bot_id=self.bot_id
156
+ bot_id=self.bot_id,
153
157
  )
154
158
  await self.supabase_client.initialize()
155
- logger.info(f"✅ Supabase клиент инициализирован")
156
-
159
+ logger.info("✅ Supabase клиент инициализирован")
160
+
157
161
  async def _init_managers(self):
158
162
  """Инициализация менеджеров"""
159
163
  logger.info(f"👥 Инициализация менеджеров для {self.bot_id}")
160
-
164
+
161
165
  # Admin Manager
162
166
  self.admin_manager = AdminManager(self.config, self.supabase_client)
163
167
  await self.admin_manager.sync_admins_from_config()
164
- logger.info(f"✅ Admin Manager инициализирован")
165
-
168
+ logger.info("✅ Admin Manager инициализирован")
169
+
166
170
  # Analytics Manager
167
171
  self.analytics_manager = AnalyticsManager(self.supabase_client)
168
- logger.info(f"✅ Analytics Manager инициализирован")
169
-
172
+ logger.info("✅ Analytics Manager инициализирован")
173
+
170
174
  # Conversation Manager
171
- parse_mode = os.environ.get('MESSAGE_PARSE_MODE', 'Markdown')
172
- admin_session_timeout_minutes = int(os.environ.get('ADMIN_SESSION_TIMEOUT_MINUTES', '30'))
173
-
174
- self.conversation_manager = ConversationManager(self.supabase_client, self.admin_manager, parse_mode, admin_session_timeout_minutes)
175
- logger.info(f"✅ Conversation Manager инициализирован")
176
-
175
+ parse_mode = os.environ.get("MESSAGE_PARSE_MODE", "Markdown")
176
+ admin_session_timeout_minutes = int(
177
+ os.environ.get("ADMIN_SESSION_TIMEOUT_MINUTES", "30")
178
+ )
179
+
180
+ self.conversation_manager = ConversationManager(
181
+ self.supabase_client,
182
+ self.admin_manager,
183
+ parse_mode,
184
+ admin_session_timeout_minutes,
185
+ )
186
+ logger.info("✅ Conversation Manager инициализирован")
187
+
177
188
  # Router Manager (создаем только если еще не создан)
178
189
  if not self.router_manager:
179
190
  self.router_manager = RouterManager()
180
- logger.info(f"✅ Router Manager инициализирован")
191
+ logger.info("✅ Router Manager инициализирован")
181
192
  else:
182
- logger.info(f"✅ Router Manager уже был создан ранее")
183
-
193
+ logger.info("✅ Router Manager уже был создан ранее")
194
+
184
195
  # Prompt Loader (используем кастомный если установлен)
185
196
  if self._custom_prompt_loader:
186
197
  self.prompt_loader = self._custom_prompt_loader
187
- logger.info(f"✅ Используется кастомный Prompt Loader: {type(self.prompt_loader).__name__}")
188
- else:
189
- self.prompt_loader = PromptLoader(
190
- prompts_dir=self.config.PROMT_FILES_DIR
198
+ logger.info(
199
+ f"✅ Используется кастомный Prompt Loader: {type(self.prompt_loader).__name__}"
191
200
  )
192
- logger.info(f"✅ Используется стандартный Prompt Loader")
193
-
201
+ else:
202
+ self.prompt_loader = PromptLoader(prompts_dir=self.config.PROMT_FILES_DIR)
203
+ logger.info("✅ Используется стандартный Prompt Loader")
204
+
194
205
  await self.prompt_loader.validate_prompts()
195
- logger.info(f"✅ Prompt Loader инициализирован")
196
-
206
+ logger.info("✅ Prompt Loader инициализирован")
207
+
197
208
  async def _update_prompts_with_tools(self):
198
209
  """
199
210
  Обновляет промпты информацией о доступных обработчиках событий
200
211
  """
201
- logger.info(f"🔧 Обновление промптов с информацией об обработчиках")
202
-
212
+ logger.info("🔧 Обновление промптов с информацией об обработчиках")
213
+
203
214
  # Получаем информацию о доступных обработчиках
204
215
  # Сначала пробуем получить из роутеров, если нет - из старых декораторов
205
216
  if self.router_manager:
206
217
  event_handlers_info = self.router_manager.get_handlers_for_prompt()
207
218
  else:
208
219
  event_handlers_info = get_handlers_for_prompt()
209
-
220
+
210
221
  # Если есть обработчики, добавляем их в системный промпт
211
222
  if event_handlers_info:
212
223
  # Сохраняем информацию о обработчиках для использования в handlers.py
213
224
  self._tools_prompt = event_handlers_info
214
-
215
- logger.info(f"✅ Промпты обновлены с информацией об обработчиках")
225
+
226
+ logger.info("✅ Промпты обновлены с информацией об обработчиках")
216
227
  else:
217
228
  self._tools_prompt = ""
218
- logger.info(f"ℹ️ Нет зарегистрированных обработчиков")
219
-
229
+ logger.info("ℹ️ Нет зарегистрированных обработчиков")
230
+
220
231
  def get_tools_prompt(self) -> str:
221
232
  """Возвращает промпт с информацией об инструментах"""
222
- return getattr(self, '_tools_prompt', '')
223
-
233
+ return getattr(self, "_tools_prompt", "")
234
+
224
235
  def get_status(self) -> Dict[str, Any]:
225
236
  """Возвращает статус бота"""
226
237
  return {
@@ -234,35 +245,43 @@ class BotBuilder:
234
245
  "conversation_manager": self.conversation_manager is not None,
235
246
  "admin_manager": self.admin_manager is not None,
236
247
  "analytics_manager": self.analytics_manager is not None,
237
- "prompt_loader": self.prompt_loader is not None
248
+ "prompt_loader": self.prompt_loader is not None,
238
249
  },
239
250
  "tools": {
240
- "event_handlers": len(get_handlers_for_prompt().split('\n')) if get_handlers_for_prompt() else 0
241
- }
251
+ "event_handlers": (
252
+ len(get_handlers_for_prompt().split("\n"))
253
+ if get_handlers_for_prompt()
254
+ else 0
255
+ )
256
+ },
242
257
  }
243
-
258
+
244
259
  def set_global_vars_in_module(self, module_name: str):
245
260
  """
246
261
  Устанавливает глобальные переменные в указанном модуле для удобного доступа
247
-
262
+
248
263
  Args:
249
264
  module_name: Имя модуля (например, 'valera', 'my_bot')
250
265
  """
251
266
  try:
252
- import sys
253
267
  import importlib
254
-
268
+ import sys
269
+
255
270
  # Получаем модуль бота
256
271
  bot_module = sys.modules.get(module_name)
257
272
  if not bot_module:
258
273
  # Пытаемся импортировать модуль, если он не загружен
259
274
  try:
260
275
  bot_module = importlib.import_module(module_name)
261
- logger.info(f"📦 Модуль '{module_name}' импортирован для установки глобальных переменных")
276
+ logger.info(
277
+ f"📦 Модуль '{module_name}' импортирован для установки глобальных переменных"
278
+ )
262
279
  except ImportError as ie:
263
- logger.warning(f"⚠️ Не удалось импортировать модуль '{module_name}': {ie}")
280
+ logger.warning(
281
+ f"⚠️ Не удалось импортировать модуль '{module_name}': {ie}"
282
+ )
264
283
  return
265
-
284
+
266
285
  # Устанавливаем глобальные переменные
267
286
  bot_module.supabase_client = self.supabase_client
268
287
  bot_module.openai_client = self.openai_client
@@ -271,115 +290,128 @@ class BotBuilder:
271
290
  bot_module.analytics_manager = self.analytics_manager
272
291
  bot_module.conversation_manager = self.conversation_manager
273
292
  bot_module.prompt_loader = self.prompt_loader
274
-
275
- logger.info(f"✅ Глобальные переменные установлены в модуле '{module_name}'")
276
-
293
+
294
+ logger.info(
295
+ f"✅ Глобальные переменные установлены в модуле '{module_name}'"
296
+ )
297
+
277
298
  except Exception as e:
278
- logger.warning(f"⚠️ Не удалось установить глобальные переменные в модуле '{module_name}': {e}")
279
-
299
+ logger.warning(
300
+ f"⚠️ Не удалось установить глобальные переменные в модуле '{module_name}': {e}"
301
+ )
302
+
280
303
  def register_router(self, router):
281
304
  """
282
305
  Регистрирует роутер событий в менеджере роутеров
283
-
306
+
284
307
  Args:
285
308
  router: EventRouter для регистрации
286
309
  """
287
310
  # Если RouterManager еще не инициализирован, создаем его
288
311
  if not self.router_manager:
289
312
  from ..core.router_manager import RouterManager
313
+
290
314
  self.router_manager = RouterManager()
291
- logger.info(f"✅ Router Manager создан для регистрации роутера '{router.name}'")
292
-
315
+ logger.info(
316
+ f"✅ Router Manager создан для регистрации роутера '{router.name}'"
317
+ )
318
+
293
319
  self.router_manager.register_router(router)
294
- logger.info(f"✅ Роутер событий '{router.name}' зарегистрирован в боте {self.bot_id}")
295
-
320
+ logger.info(
321
+ f"✅ Роутер событий '{router.name}' зарегистрирован в боте {self.bot_id}"
322
+ )
323
+
296
324
  def register_routers(self, *event_routers):
297
325
  """
298
326
  Регистрирует несколько роутеров событий одновременно
299
-
327
+
300
328
  Args:
301
329
  *event_routers: Произвольное количество EventRouter
302
-
330
+
303
331
  Example:
304
332
  bot_builder.register_routers(event_router1, event_router2, event_router3)
305
333
  """
306
334
  if not event_routers:
307
335
  logger.warning("⚠️ register_routers вызван без аргументов")
308
336
  return
309
-
337
+
310
338
  for router in event_routers:
311
339
  self.register_router(router)
312
-
340
+
313
341
  logger.info(f"✅ Зарегистрировано {len(event_routers)} роутеров событий")
314
-
342
+
315
343
  def register_telegram_router(self, telegram_router):
316
344
  """
317
345
  Регистрирует Telegram роутер для обработки команд и сообщений
318
-
346
+
319
347
  Args:
320
348
  telegram_router: aiogram.Router для регистрации
321
-
349
+
322
350
  Example:
323
351
  from aiogram import Router
324
352
  from aiogram.filters import Command
325
-
353
+
326
354
  # Создаем обычный aiogram Router
327
355
  my_router = Router(name="my_commands")
328
-
356
+
329
357
  @my_router.message(Command("price"))
330
358
  async def price_handler(message: Message):
331
359
  await message.answer("Наши цены...")
332
-
360
+
333
361
  # Регистрируем в боте
334
362
  bot_builder.register_telegram_router(my_router)
335
363
  """
336
364
  from aiogram import Router as AiogramRouter
337
-
365
+
338
366
  if not isinstance(telegram_router, AiogramRouter):
339
- raise TypeError(f"Ожидается aiogram.Router, получен {type(telegram_router)}")
340
-
367
+ raise TypeError(
368
+ f"Ожидается aiogram.Router, получен {type(telegram_router)}"
369
+ )
370
+
341
371
  self._telegram_routers.append(telegram_router)
342
- router_name = getattr(telegram_router, 'name', 'unnamed')
343
- logger.info(f"✅ Telegram роутер '{router_name}' зарегистрирован в боте {self.bot_id}")
344
-
372
+ router_name = getattr(telegram_router, "name", "unnamed")
373
+ logger.info(
374
+ f"✅ Telegram роутер '{router_name}' зарегистрирован в боте {self.bot_id}"
375
+ )
376
+
345
377
  def register_telegram_routers(self, *telegram_routers):
346
378
  """
347
379
  Регистрирует несколько Telegram роутеров одновременно
348
-
380
+
349
381
  Args:
350
382
  *telegram_routers: Произвольное количество aiogram.Router
351
-
383
+
352
384
  Example:
353
385
  from aiogram import Router
354
-
386
+
355
387
  router1 = Router(name="commands")
356
388
  router2 = Router(name="callbacks")
357
-
389
+
358
390
  bot_builder.register_telegram_routers(router1, router2)
359
391
  """
360
392
  if not telegram_routers:
361
393
  logger.warning("⚠️ register_telegram_routers вызван без аргументов")
362
394
  return
363
-
395
+
364
396
  for router in telegram_routers:
365
397
  self.register_telegram_router(router)
366
-
398
+
367
399
  logger.info(f"✅ Зарегистрировано {len(telegram_routers)} Telegram роутеров")
368
-
400
+
369
401
  def on_start(self, handler):
370
402
  """
371
403
  Регистрирует обработчик, который вызывается после стандартной логики /start
372
-
404
+
373
405
  Обработчик получает доступ к:
374
406
  - user_id: int - ID пользователя Telegram
375
407
  - session_id: str - ID созданной сессии
376
408
  - message: Message - Объект сообщения от aiogram
377
409
  - state: FSMContext - Контекст состояния
378
-
410
+
379
411
  Args:
380
412
  handler: Async функция с сигнатурой:
381
413
  async def handler(user_id: int, session_id: str, message: Message, state: FSMContext)
382
-
414
+
383
415
  Example:
384
416
  @bot_builder.on_start
385
417
  async def my_start_handler(user_id, session_id, message, state):
@@ -388,64 +420,66 @@ class BotBuilder:
388
420
  """
389
421
  if not callable(handler):
390
422
  raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
391
-
423
+
392
424
  self._start_handlers.append(handler)
393
425
  logger.info(f"✅ Зарегистрирован обработчик on_start: {handler.__name__}")
394
426
  return handler # Возвращаем handler для использования как декоратор
395
-
427
+
396
428
  def get_start_handlers(self) -> List:
397
429
  """Получает список обработчиков on_start"""
398
430
  return self._start_handlers.copy()
399
-
431
+
400
432
  def set_prompt_loader(self, prompt_loader):
401
433
  """
402
434
  Устанавливает кастомный PromptLoader
403
-
435
+
404
436
  Должен быть вызван ДО build()
405
-
437
+
406
438
  Args:
407
439
  prompt_loader: Экземпляр PromptLoader или его наследника (например UserPromptLoader)
408
-
440
+
409
441
  Example:
410
442
  from smart_bot_factory.utils import UserPromptLoader
411
-
443
+
412
444
  # Использовать UserPromptLoader с автопоиском prompts_dir
413
445
  custom_loader = UserPromptLoader("my-bot")
414
446
  bot_builder.set_prompt_loader(custom_loader)
415
-
447
+
416
448
  # Или кастомный наследник
417
449
  class MyPromptLoader(UserPromptLoader):
418
450
  def __init__(self, bot_id):
419
451
  super().__init__(bot_id)
420
452
  self.extra_file = self.prompts_dir / 'extra.txt'
421
-
453
+
422
454
  my_loader = MyPromptLoader("my-bot")
423
455
  bot_builder.set_prompt_loader(my_loader)
424
456
  """
425
457
  self._custom_prompt_loader = prompt_loader
426
- logger.info(f"✅ Установлен кастомный PromptLoader: {type(prompt_loader).__name__}")
427
-
458
+ logger.info(
459
+ f"✅ Установлен кастомный PromptLoader: {type(prompt_loader).__name__}"
460
+ )
461
+
428
462
  def set_event_processor(self, custom_processor):
429
463
  """
430
464
  Устанавливает кастомную функцию для обработки событий
431
-
465
+
432
466
  Полностью заменяет стандартную process_events из bot_utils
433
-
467
+
434
468
  Args:
435
469
  custom_processor: async def(session_id: str, events: list, user_id: int)
436
-
470
+
437
471
  Example:
438
472
  from smart_bot_factory.message import get_bot
439
473
  from smart_bot_factory.core.decorators import execute_event_handler
440
-
474
+
441
475
  async def my_process_events(session_id, events, user_id):
442
476
  '''Моя кастомная обработка событий'''
443
477
  bot = get_bot()
444
-
478
+
445
479
  for event in events:
446
480
  event_type = event.get('тип')
447
481
  event_info = event.get('инфо')
448
-
482
+
449
483
  if event_type == 'запись':
450
484
  # Кастомная логика для бронирования
451
485
  telegram_user = await bot.get_chat(user_id)
@@ -454,26 +488,30 @@ class BotBuilder:
454
488
  else:
455
489
  # Для остальных - стандартная обработка
456
490
  await execute_event_handler(event_type, user_id, event_info)
457
-
491
+
458
492
  bot_builder.set_event_processor(my_process_events)
459
493
  """
460
494
  if not callable(custom_processor):
461
- raise TypeError(f"Процессор должен быть callable, получен {type(custom_processor)}")
462
-
495
+ raise TypeError(
496
+ f"Процессор должен быть callable, получен {type(custom_processor)}"
497
+ )
498
+
463
499
  self._custom_event_processor = custom_processor
464
- logger.info(f"✅ Установлена кастомная функция обработки событий: {custom_processor.__name__}")
465
-
500
+ logger.info(
501
+ f"✅ Установлена кастомная функция обработки событий: {custom_processor.__name__}"
502
+ )
503
+
466
504
  # ========== ХУКИ ДЛЯ КАСТОМИЗАЦИИ ОБРАБОТКИ СООБЩЕНИЙ ==========
467
-
505
+
468
506
  def validate_message(self, handler):
469
507
  """
470
508
  Регистрирует валидатор сообщений (вызывается ДО обработки AI)
471
-
509
+
472
510
  Если валидатор возвращает False, обработка прерывается
473
-
511
+
474
512
  Args:
475
513
  handler: async def(message: Message, supabase_client) -> bool
476
-
514
+
477
515
  Example:
478
516
  @bot_builder.validate_message
479
517
  async def check_service_names(message, supabase_client):
@@ -484,18 +522,18 @@ class BotBuilder:
484
522
  """
485
523
  if not callable(handler):
486
524
  raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
487
-
525
+
488
526
  self._message_validators.append(handler)
489
527
  logger.info(f"✅ Зарегистрирован валидатор сообщений: {handler.__name__}")
490
528
  return handler
491
-
529
+
492
530
  def enrich_prompt(self, handler):
493
531
  """
494
532
  Регистрирует обогатитель системного промпта
495
-
533
+
496
534
  Args:
497
535
  handler: async def(system_prompt: str, user_id: int, session_id: str, supabase_client) -> str
498
-
536
+
499
537
  Example:
500
538
  @bot_builder.enrich_prompt
501
539
  async def add_client_info(system_prompt, user_id, session_id, supabase_client):
@@ -507,42 +545,42 @@ class BotBuilder:
507
545
  """
508
546
  if not callable(handler):
509
547
  raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
510
-
548
+
511
549
  self._prompt_enrichers.append(handler)
512
550
  logger.info(f"✅ Зарегистрирован обогатитель промпта: {handler.__name__}")
513
551
  return handler
514
-
552
+
515
553
  def enrich_context(self, handler):
516
554
  """
517
555
  Регистрирует обогатитель контекста для AI (messages array)
518
-
556
+
519
557
  Args:
520
558
  handler: async def(messages: List[dict], user_id: int, session_id: str) -> List[dict]
521
-
559
+
522
560
  Example:
523
561
  @bot_builder.enrich_context
524
562
  async def add_external_data(messages, user_id, session_id):
525
563
  # Добавляем данные из внешнего API
526
564
  messages.append({
527
- "role": "system",
565
+ "role": "system",
528
566
  "content": "Дополнительная информация..."
529
567
  })
530
568
  return messages
531
569
  """
532
570
  if not callable(handler):
533
571
  raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
534
-
572
+
535
573
  self._context_enrichers.append(handler)
536
574
  logger.info(f"✅ Зарегистрирован обогатитель контекста: {handler.__name__}")
537
575
  return handler
538
-
576
+
539
577
  def process_response(self, handler):
540
578
  """
541
579
  Регистрирует обработчик ответа AI (ПОСЛЕ получения ответа)
542
-
580
+
543
581
  Args:
544
582
  handler: async def(response_text: str, ai_metadata: dict, user_id: int) -> tuple[str, dict]
545
-
583
+
546
584
  Example:
547
585
  @bot_builder.process_response
548
586
  async def modify_response(response_text, ai_metadata, user_id):
@@ -553,27 +591,27 @@ class BotBuilder:
553
591
  """
554
592
  if not callable(handler):
555
593
  raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
556
-
594
+
557
595
  self._response_processors.append(handler)
558
596
  logger.info(f"✅ Зарегистрирован обработчик ответа: {handler.__name__}")
559
597
  return handler
560
-
598
+
561
599
  def filter_send(self, handler):
562
600
  """
563
601
  Регистрирует фильтр отправки (может блокировать отправку пользователю)
564
-
602
+
565
603
  Если фильтр возвращает True, сообщение НЕ отправляется
566
-
604
+
567
605
  Args:
568
606
  handler: async def(user_id: int) -> bool
569
-
607
+
570
608
  Example:
571
609
  @bot_builder.filter_send
572
610
  async def block_during_process(user_id):
573
611
  if is_processing(user_id):
574
612
  return True # Блокируем отправку
575
613
  return False # Разрешаем отправку
576
-
614
+
577
615
  # Или совместимый с should_block_ai_response
578
616
  @bot_builder.filter_send
579
617
  async def should_block_ai_response(user_id):
@@ -582,45 +620,48 @@ class BotBuilder:
582
620
  """
583
621
  if not callable(handler):
584
622
  raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
585
-
623
+
586
624
  self._send_filters.append(handler)
587
625
  logger.info(f"✅ Зарегистрирован фильтр отправки: {handler.__name__}")
588
626
  return handler
589
-
627
+
590
628
  def get_message_hooks(self) -> Dict[str, List]:
591
629
  """Получает все хуки для обработки сообщений"""
592
630
  return {
593
- 'validators': self._message_validators.copy(),
594
- 'prompt_enrichers': self._prompt_enrichers.copy(),
595
- 'context_enrichers': self._context_enrichers.copy(),
596
- 'response_processors': self._response_processors.copy(),
597
- 'send_filters': self._send_filters.copy()
631
+ "validators": self._message_validators.copy(),
632
+ "prompt_enrichers": self._prompt_enrichers.copy(),
633
+ "context_enrichers": self._context_enrichers.copy(),
634
+ "response_processors": self._response_processors.copy(),
635
+ "send_filters": self._send_filters.copy(),
598
636
  }
599
-
637
+
600
638
  def get_router_manager(self) -> RouterManager:
601
639
  """Получает менеджер роутеров событий"""
602
640
  return self.router_manager
603
-
641
+
604
642
  async def _setup_bot_commands(self, bot):
605
643
  """Устанавливает меню команд для бота (разные для админов и пользователей)"""
606
- from aiogram.types import BotCommand, BotCommandScopeDefault, BotCommandScopeChat
607
-
644
+ from aiogram.types import (BotCommand, BotCommandScopeChat,
645
+ BotCommandScopeDefault)
646
+
608
647
  try:
609
648
  # Команды для обычных пользователей
610
649
  user_commands = [
611
650
  BotCommand(command="start", description="🚀 Начать/перезапустить бота"),
612
651
  BotCommand(command="help", description="❓ Помощь"),
613
652
  ]
614
-
653
+
615
654
  # Устанавливаем для всех пользователей по умолчанию
616
655
  await bot.set_my_commands(user_commands, scope=BotCommandScopeDefault())
617
656
  logger.info("✅ Установлены команды для обычных пользователей")
618
-
657
+
619
658
  # Команды для админов (включая команды пользователей + админские)
620
659
  admin_commands = [
621
660
  BotCommand(command="start", description="🚀 Начать/перезапустить бота"),
622
661
  BotCommand(command="help", description="❓ Помощь"),
623
- BotCommand(command="cancel", description="❌ Отменить текущее действие"),
662
+ BotCommand(
663
+ command="cancel", description="❌ Отменить текущее действие"
664
+ ),
624
665
  BotCommand(command="admin", description="👑 Админ панель"),
625
666
  BotCommand(command="stats", description="📊 Статистика"),
626
667
  BotCommand(command="chat", description="💬 Начать чат с пользователем"),
@@ -631,61 +672,67 @@ class BotBuilder:
631
672
  BotCommand(command="list_events", description="📋 Список событий"),
632
673
  BotCommand(command="delete_event", description="🗑️ Удалить событие"),
633
674
  ]
634
-
675
+
635
676
  # Устанавливаем для каждого админа персональные команды
636
677
  for admin_id in self.config.ADMIN_TELEGRAM_IDS:
637
678
  try:
638
679
  await bot.set_my_commands(
639
- admin_commands,
640
- scope=BotCommandScopeChat(chat_id=admin_id)
680
+ admin_commands, scope=BotCommandScopeChat(chat_id=admin_id)
641
681
  )
642
682
  logger.info(f"✅ Установлены админские команды для {admin_id}")
643
683
  except Exception as e:
644
- logger.warning(f"⚠️ Не удалось установить команды для админа {admin_id}: {e}")
645
-
646
- logger.info(f"✅ Меню команд настроено ({len(self.config.ADMIN_TELEGRAM_IDS)} админов)")
647
-
684
+ logger.warning(
685
+ f"⚠️ Не удалось установить команды для админа {admin_id}: {e}"
686
+ )
687
+
688
+ logger.info(
689
+ f"✅ Меню команд настроено ({len(self.config.ADMIN_TELEGRAM_IDS)} админов)"
690
+ )
691
+
648
692
  except Exception as e:
649
693
  logger.error(f"❌ Ошибка установки команд бота: {e}")
650
-
694
+
651
695
  async def start(self):
652
696
  """
653
697
  Запускает бота (аналог main.py)
654
698
  """
655
699
  if not self._initialized:
656
- raise RuntimeError(f"Бот {self.bot_id} не инициализирован. Вызовите build() сначала")
657
-
700
+ raise RuntimeError(
701
+ f"Бот {self.bot_id} не инициализирован. Вызовите build() сначала"
702
+ )
703
+
658
704
  logger.info(f"🚀 Запускаем бота {self.bot_id}")
659
-
705
+
660
706
  try:
661
707
  # Импортируем необходимые компоненты
662
708
  from aiogram import Bot, Dispatcher
663
709
  from aiogram.fsm.storage.memory import MemoryStorage
664
-
710
+
665
711
  # Создаем бота и диспетчер
666
712
  bot = Bot(token=self.config.TELEGRAM_BOT_TOKEN)
667
713
  storage = MemoryStorage()
668
714
  dp = Dispatcher(storage=storage)
669
-
715
+
670
716
  # Устанавливаем меню команд для бота
671
717
  await self._setup_bot_commands(bot)
672
-
718
+
673
719
  # Инициализируем базу данных
674
720
  await self.supabase_client.initialize()
675
-
721
+
676
722
  # Синхронизируем админов из конфигурации
677
723
  await self.admin_manager.sync_admins_from_config()
678
-
724
+
679
725
  # Проверяем доступность промптов
680
726
  prompts_status = await self.prompt_loader.validate_prompts()
681
727
  logger.info(f"Статус промптов: {prompts_status}")
682
-
683
-
728
+
684
729
  import importlib
685
-
730
+
686
731
  # Устанавливаем глобальные переменные в модулях handlers и admin_logic
687
732
  try:
688
- handlers_module = importlib.import_module('smart_bot_factory.handlers.handlers')
733
+ handlers_module = importlib.import_module(
734
+ "smart_bot_factory.handlers.handlers"
735
+ )
689
736
  handlers_module.config = self.config
690
737
  handlers_module.bot = bot
691
738
  handlers_module.dp = dp
@@ -695,15 +742,25 @@ class BotBuilder:
695
742
  handlers_module.admin_manager = self.admin_manager
696
743
  handlers_module.analytics_manager = self.analytics_manager
697
744
  handlers_module.conversation_manager = self.conversation_manager
698
- handlers_module.start_handlers = self._start_handlers # Передаем обработчики on_start
699
- handlers_module.message_hooks = self.get_message_hooks() # Передаем хуки для обработки сообщений
700
- handlers_module.custom_event_processor = self._custom_event_processor # Передаем кастомный процессор событий
745
+ handlers_module.start_handlers = (
746
+ self._start_handlers
747
+ ) # Передаем обработчики on_start
748
+ handlers_module.message_hooks = (
749
+ self.get_message_hooks()
750
+ ) # Передаем хуки для обработки сообщений
751
+ handlers_module.custom_event_processor = (
752
+ self._custom_event_processor
753
+ ) # Передаем кастомный процессор событий
701
754
  logger.info("✅ Глобальные переменные установлены в handlers")
702
755
  except Exception as e:
703
- logger.warning(f"⚠️ Не удалось установить глобальные переменные в handlers: {e}")
704
-
756
+ logger.warning(
757
+ f"⚠️ Не удалось установить глобальные переменные в handlers: {e}"
758
+ )
759
+
705
760
  try:
706
- admin_logic_module = importlib.import_module('smart_bot_factory.admin.admin_logic')
761
+ admin_logic_module = importlib.import_module(
762
+ "smart_bot_factory.admin.admin_logic"
763
+ )
707
764
  admin_logic_module.config = self.config
708
765
  admin_logic_module.bot = bot
709
766
  admin_logic_module.dp = dp
@@ -715,11 +772,15 @@ class BotBuilder:
715
772
  admin_logic_module.conversation_manager = self.conversation_manager
716
773
  logger.info("✅ Глобальные переменные установлены в admin_logic")
717
774
  except Exception as e:
718
- logger.warning(f"⚠️ Не удалось установить глобальные переменные в admin_logic: {e}")
719
-
775
+ logger.warning(
776
+ f"⚠️ Не удалось установить глобальные переменные в admin_logic: {e}"
777
+ )
778
+
720
779
  # Также устанавливаем в bot_utils
721
780
  try:
722
- bot_utils_module = importlib.import_module('smart_bot_factory.core.bot_utils')
781
+ bot_utils_module = importlib.import_module(
782
+ "smart_bot_factory.core.bot_utils"
783
+ )
723
784
  bot_utils_module.config = self.config
724
785
  bot_utils_module.bot = bot
725
786
  bot_utils_module.dp = dp
@@ -729,14 +790,19 @@ class BotBuilder:
729
790
  bot_utils_module.admin_manager = self.admin_manager
730
791
  bot_utils_module.analytics_manager = self.analytics_manager
731
792
  bot_utils_module.conversation_manager = self.conversation_manager
732
- bot_utils_module.custom_event_processor = self._custom_event_processor # Передаем кастомный процессор событий
793
+ bot_utils_module.custom_event_processor = (
794
+ self._custom_event_processor
795
+ ) # Передаем кастомный процессор событий
733
796
  logger.info("✅ Глобальные переменные установлены в bot_utils")
734
797
  except Exception as e:
735
- logger.warning(f"⚠️ Не удалось установить глобальные переменные в bot_utils: {e}")
736
-
798
+ logger.warning(
799
+ f"⚠️ Не удалось установить глобальные переменные в bot_utils: {e}"
800
+ )
801
+
737
802
  # Также устанавливаем в debug_routing
738
803
  try:
739
804
  from ..utils import debug_routing
805
+
740
806
  debug_routing.config = self.config
741
807
  debug_routing.bot = bot
742
808
  debug_routing.dp = dp
@@ -747,73 +813,85 @@ class BotBuilder:
747
813
  debug_routing.conversation_manager = self.conversation_manager
748
814
  logger.info("✅ Глобальные переменные установлены в debug_routing")
749
815
  except Exception as e:
750
- logger.warning(f"⚠️ Не удалось установить глобальные переменные в debug_routing: {e}")
751
-
816
+ logger.warning(
817
+ f"⚠️ Не удалось установить глобальные переменные в debug_routing: {e}"
818
+ )
819
+
752
820
  # Теперь импортируем и настраиваем обработчики
753
- from ..handlers.handlers import setup_handlers
754
- from ..admin.admin_logic import setup_admin_handlers
755
821
  from ..admin.admin_events import setup_admin_events_handlers
822
+ from ..admin.admin_logic import setup_admin_handlers
756
823
  from ..core.bot_utils import setup_utils_handlers
757
-
824
+ from ..handlers.handlers import setup_handlers
825
+
758
826
  # Подключаем пользовательские Telegram роутеры ПЕРВЫМИ (высший приоритет)
759
827
  if self._telegram_routers:
760
- logger.info(f"🔗 Подключаем {len(self._telegram_routers)} пользовательских Telegram роутеров")
828
+ logger.info(
829
+ f"🔗 Подключаем {len(self._telegram_routers)} пользовательских Telegram роутеров"
830
+ )
761
831
  for telegram_router in self._telegram_routers:
762
832
  dp.include_router(telegram_router)
763
- router_name = getattr(telegram_router, 'name', 'unnamed')
833
+ router_name = getattr(telegram_router, "name", "unnamed")
764
834
  logger.info(f"✅ Подключен Telegram роутер: {router_name}")
765
-
835
+
766
836
  # Настраиваем стандартные обработчики (меньший приоритет)
767
- setup_utils_handlers(dp) # Утилитарные команды (/status, /help)
768
- setup_admin_handlers(dp) # Админские команды (/админ, /стат, /чат)
837
+ setup_utils_handlers(dp) # Утилитарные команды (/status, /help)
838
+ setup_admin_handlers(dp) # Админские команды (/админ, /стат, /чат)
769
839
  setup_admin_events_handlers(dp) # Админские события (/создать_событие)
770
- setup_handlers(dp) # Основные пользовательские обработчики
771
-
840
+ setup_handlers(dp) # Основные пользовательские обработчики
841
+
772
842
  # Устанавливаем глобальные переменные в модуле бота для удобного доступа
773
843
  self.set_global_vars_in_module(self.bot_id)
774
-
844
+
775
845
  # Устанавливаем роутер-менеджер в декораторы ПЕРЕД настройкой обработчиков
776
846
  if self.router_manager:
777
847
  from ..core.decorators import set_router_manager
848
+
778
849
  set_router_manager(self.router_manager)
779
850
  logger.info("✅ RouterManager установлен в decorators")
780
-
851
+
781
852
  # Обновляем обработчики после установки RouterManager
782
853
  # (на случай если декораторы выполнялись после добавления роутера)
783
854
  self.router_manager._update_combined_handlers()
784
855
  logger.info("✅ RouterManager обработчики обновлены")
785
-
856
+
786
857
  # Фоновые задачи выполняются через asyncio.create_task в decorators.py
787
-
858
+
788
859
  # Логируем информацию о запуске
789
860
  logger.info(f"✅ Бот {self.bot_id} запущен и готов к работе!")
790
861
  logger.info(f" 📊 Изоляция данных: bot_id = {self.config.BOT_ID}")
791
- logger.info(f" 👑 Админов настроено: {len(self.config.ADMIN_TELEGRAM_IDS)}")
862
+ logger.info(
863
+ f" 👑 Админов настроено: {len(self.config.ADMIN_TELEGRAM_IDS)}"
864
+ )
792
865
  logger.info(f" 📝 Загружено промптов: {len(self.config.PROMPT_FILES)}")
793
-
866
+
794
867
  # Запускаем единый фоновый процессор для всех событий
795
- from ..core.decorators import background_event_processor
796
868
  import asyncio
869
+
870
+ from ..core.decorators import background_event_processor
871
+
797
872
  asyncio.create_task(background_event_processor())
798
- logger.info("✅ Фоновый процессор событий запущен (user_event, scheduled_task, global_handler, admin_event)")
799
-
873
+ logger.info(
874
+ "✅ Фоновый процессор событий запущен (user_event, scheduled_task, global_handler, admin_event)"
875
+ )
876
+
800
877
  # Четкое сообщение о запуске
801
878
  print(f"\n🤖 БОТ {self.bot_id.upper()} УСПЕШНО ЗАПУЩЕН!")
802
879
  print(f"📱 Telegram Bot ID: {self.config.BOT_ID}")
803
880
  print(f"👑 Админов: {len(self.config.ADMIN_TELEGRAM_IDS)}")
804
881
  print(f"📝 Промптов: {len(self.config.PROMPT_FILES)}")
805
- print(f"⏳ Ожидание сообщений...")
806
- print(f"⏹️ Для остановки нажмите Ctrl+C\n")
807
-
882
+ print("⏳ Ожидание сообщений...")
883
+ print("⏹️ Для остановки нажмите Ctrl+C\n")
884
+
808
885
  # Запуск polling (бесконечная обработка сообщений)
809
886
  await dp.start_polling(bot)
810
-
887
+
811
888
  except Exception as e:
812
889
  logger.error(f"❌ Ошибка при запуске бота {self.bot_id}: {e}")
813
890
  import traceback
891
+
814
892
  logger.error(f"Стек ошибки: {traceback.format_exc()}")
815
893
  raise
816
894
  finally:
817
895
  # Очистка ресурсов при завершении
818
- if 'bot' in locals():
896
+ if "bot" in locals():
819
897
  await bot.session.close()