smart-bot-factory 0.3.8__tar.gz → 0.3.10__tar.gz

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 (80) hide show
  1. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.gitignore +5 -1
  2. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/PKG-INFO +1 -1
  3. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/pyproject.toml +1 -1
  4. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/admin_manager.py +2 -2
  5. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/bot_utils.py +54 -24
  6. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/conversation_manager.py +36 -13
  7. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/decorators.py +125 -39
  8. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/message_sender.py +3 -2
  9. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/router.py +9 -0
  10. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/handlers/handlers.py +19 -24
  11. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/integrations/supabase_client.py +60 -17
  12. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/uv.lock +1 -1
  13. smart_bot_factory-0.3.8/.pre-commit-config.yaml +0 -23
  14. smart_bot_factory-0.3.8/publish.py +0 -152
  15. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +0 -16
  16. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +0 -582
  17. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +0 -66
  18. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +0 -212
  19. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +0 -28
  20. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +0 -8
  21. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +0 -133
  22. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +0 -108
  23. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +0 -46
  24. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +0 -16
  25. smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/welcome_file//320/247/320/265/320/272 /320/273/320/270/321/201/321/202 /320/277/320/276 152/320/244/320/227 /320/270 323/320/244/320/227 /320/264/320/273/321/217 /320/274/320/265/320/264/320/270/321/206/320/270/320/275/321/213.pdf +0 -0
  26. smart_bot_factory-0.3.8/valera.py +0 -56
  27. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.github/ISSUE_TEMPLATE//342/234/250-/320/267/320/260/320/277/321/200/320/276/321/201-/321/204/321/203/320/275/320/272/321/206/320/270/320/270.md" +0 -0
  28. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.github/ISSUE_TEMPLATE//360/237/220/233-/320/261/320/260/320/263-/321/200/320/265/320/277/320/276/321/200/321/202.md" +0 -0
  29. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.github/workflows/ci.yml +0 -0
  30. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.github/workflows/publish-private.yml +0 -0
  31. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.github/workflows/publish.yml +0 -0
  32. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.python-version +0 -0
  33. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/LICENSE +0 -0
  34. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/README.md +0 -0
  35. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/__init__.py +0 -0
  36. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/__init__.py +0 -0
  37. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/admin_events.py +0 -0
  38. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/admin_logic.py +0 -0
  39. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/admin_tester.py +0 -0
  40. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/timeout_checker.py +0 -0
  41. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/aiogram_calendar/__init__.py +0 -0
  42. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/aiogram_calendar/common.py +0 -0
  43. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/aiogram_calendar/dialog_calendar.py +0 -0
  44. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/aiogram_calendar/schemas.py +0 -0
  45. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/aiogram_calendar/simple_calendar.py +0 -0
  46. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/analytics/analytics_manager.py +0 -0
  47. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/cli.py +0 -0
  48. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/config.py +0 -0
  49. {smart_bot_factory-0.3.8/bots/valera → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24}/prompts/1sales_context.txt +0 -0
  50. {smart_bot_factory-0.3.8/bots/valera → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24}/prompts/2product_info.txt +0 -0
  51. {smart_bot_factory-0.3.8/bots/valera → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24}/prompts/3objection_handling.txt +0 -0
  52. {smart_bot_factory-0.3.8/bots/valera → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24}/prompts/final_instructions.txt +0 -0
  53. {smart_bot_factory-0.3.8/bots/valera → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24}/prompts/help_message.txt +0 -0
  54. {smart_bot_factory-0.3.8/bots/valera → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24}/prompts/welcome_message.txt +0 -0
  55. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +0 -0
  56. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +0 -0
  57. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +0 -0
  58. {smart_bot_factory-0.3.8/bots/valera → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24}/tests/quick_scenarios.yaml +0 -0
  59. {smart_bot_factory-0.3.8/bots/valera → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24}/tests/realistic_scenarios.yaml +0 -0
  60. {smart_bot_factory-0.3.8/bots/valera → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24}/tests/scenario_examples.yaml +0 -0
  61. {smart_bot_factory-0.3.8/bots/valera/welcome_files → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24/welcome_file}/welcome_file_msg.txt +0 -0
  62. {smart_bot_factory-0.3.8/bots/valera/welcome_files → smart_bot_factory-0.3.10/smart_bot_factory/configs/growthmed-october-24/welcome_file}//320/247/320/265/320/272 /320/273/320/270/321/201/321/202 /320/277/320/276 152/320/244/320/227 /320/270 323/320/244/320/227 /320/264/320/273/321/217 /320/274/320/265/320/264/320/270/321/206/320/270/320/275/321/213.pdf" +0 -0
  63. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/router_manager.py +0 -0
  64. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/states.py +0 -0
  65. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/creation/__init__.py +0 -0
  66. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/creation/bot_builder.py +0 -0
  67. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/creation/bot_testing.py +0 -0
  68. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/dashboard/__init__.py +0 -0
  69. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/event/__init__.py +0 -0
  70. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/integrations/openai_client.py +0 -0
  71. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/message/__init__.py +0 -0
  72. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/router/__init__.py +0 -0
  73. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/setup_checker.py +0 -0
  74. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/supabase/__init__.py +0 -0
  75. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/supabase/client.py +0 -0
  76. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/utils/__init__.py +0 -0
  77. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/utils/debug_routing.py +0 -0
  78. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/utils/prompt_loader.py +0 -0
  79. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/utils/user_prompt_loader.py +0 -0
  80. {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/utm_link_generator.py +0 -0
@@ -1,5 +1,9 @@
1
+ bots/*
2
+
3
+ valera.py
4
+ publish.py
1
5
  # Byte-compiled / optimized / DLL files
2
- __pycache__/
6
+ __pycache__/s
3
7
  *.py[codz]
4
8
  *$py.class
5
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smart-bot-factory
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Summary: Библиотека для создания умных чат-ботов
5
5
  Author-email: Kopatych <eserov73@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "smart-bot-factory"
3
- version = "0.3.8"
3
+ version = "0.3.10"
4
4
  description = "Библиотека для создания умных чат-ботов"
5
5
  authors = [
6
6
  {name = "Kopatych", email = "eserov73@gmail.com"}
@@ -122,8 +122,8 @@ class AdminManager:
122
122
 
123
123
  async def notify_admins(self, message: str, exclude_admin: int = None):
124
124
  """Отправляет уведомление всем активным админам"""
125
- from main import \
126
- bot # Импорт здесь чтобы избежать циклических импортов
125
+ from ..handlers.handlers import get_global_var
126
+ bot = get_global_var("bot")
127
127
 
128
128
  active_admins = await self.get_active_admins()
129
129
 
@@ -245,25 +245,33 @@ async def process_events(session_id: str, events: list, user_id: int) -> bool:
245
245
  f"🔍 Старые scheduled_tasks: {list(scheduled_tasks.keys())}"
246
246
  )
247
247
 
248
- # Сначала пробуем как обычное событие
248
+ # Сначала пробуем как обычное событие или scheduled task
249
+ handler_info = None
250
+ handler_type = None
251
+
249
252
  if event_type in event_handlers:
253
+ handler_info = event_handlers.get(event_type, {})
254
+ handler_type = "event"
255
+ elif event_type in scheduled_tasks:
256
+ handler_info = scheduled_tasks.get(event_type, {})
257
+ handler_type = "task"
258
+
259
+ if handler_info:
250
260
  from ..core.decorators import execute_event_handler
251
261
 
252
- event_handler_info = event_handlers.get(event_type, {})
253
- once_only = event_handler_info.get("once_only", True)
254
- send_ai_response_flag = event_handler_info.get(
255
- "send_ai_response", True
256
- )
262
+ once_only = handler_info.get("once_only", True)
263
+ send_ai_response_flag = handler_info.get("send_ai_response", True)
264
+ should_notify = handler_info.get("notify", False) # Получаем notify из handler_info
257
265
 
258
266
  logger.info(
259
- f" 🔍 Обработчик '{event_type}': once_only={once_only}, send_ai_response={send_ai_response_flag}"
267
+ f" 🔍 {handler_type.title()} '{event_type}': once_only={once_only}, send_ai_response={send_ai_response_flag}, notify={should_notify}"
260
268
  )
261
269
 
262
270
  # Проверяем флаг send_ai_response ИЗ ДЕКОРАТОРА
263
271
  if not send_ai_response_flag:
264
272
  should_send_ai_response = False
265
273
  logger.warning(
266
- f" 🔇🔇🔇 ОБРАБОТЧИК '{event_type}' ЗАПРЕТИЛ ОТПРАВКУ СООБЩЕНИЯ ОТ ИИ (send_ai_response=False) 🔇🔇🔇"
274
+ f" 🔇🔇🔇 {handler_type.upper()} '{event_type}' ЗАПРЕТИЛ ОТПРАВКУ СООБЩЕНИЯ ОТ ИИ (send_ai_response=False) 🔇🔇🔇"
267
275
  )
268
276
 
269
277
  # Если once_only=True - проверяем в БД наличие выполненных событий
@@ -301,14 +309,21 @@ async def process_events(session_id: str, events: list, user_id: int) -> bool:
301
309
 
302
310
  # Немедленно выполняем событие
303
311
  logger.info(
304
- f" 🎯 Немедленно выполняем user_event: '{event_type}'"
312
+ f" 🎯 Немедленно выполняем {handler_type}: '{event_type}'"
305
313
  )
306
314
 
307
315
  try:
308
- # Выполняем событие
309
- result = await execute_event_handler(
310
- event_type, user_id, event_info
311
- )
316
+ # Выполняем обработчик в зависимости от типа
317
+ if handler_type == "event":
318
+ result = await execute_event_handler(
319
+ event_type, user_id, event_info
320
+ )
321
+ elif handler_type == "task":
322
+ result = await execute_scheduled_task_from_event(
323
+ user_id, event_type, event_info, session_id
324
+ )
325
+ else:
326
+ raise ValueError(f"Неизвестный тип обработчика: {handler_type}")
312
327
 
313
328
  # Проверяем наличие поля 'info' для дашборда
314
329
  import json
@@ -353,14 +368,14 @@ async def process_events(session_id: str, events: list, user_id: int) -> bool:
353
368
  )
354
369
  event_id = response.data[0]["id"]
355
370
 
356
- should_notify = event_handler_info.get("notify", False)
357
-
371
+ # should_notify уже получен из handler_info выше
358
372
  logger.info(
359
373
  f" ✅ Событие {event_id} выполнено и сохранено как completed"
360
374
  )
361
375
 
362
376
  except Exception as e:
363
377
  logger.error(f" ❌ Ошибка выполнения события: {e}")
378
+
364
379
  # Сохраняем ошибку в БД
365
380
  event_record = {
366
381
  "event_type": event_type,
@@ -377,10 +392,15 @@ async def process_events(session_id: str, events: list, user_id: int) -> bool:
377
392
  if supabase_client.bot_id:
378
393
  event_record["bot_id"] = supabase_client.bot_id
379
394
 
380
- supabase_client.client.table("scheduled_events").insert(
381
- event_record
382
- ).execute()
383
- raise
395
+ try:
396
+ supabase_client.client.table("scheduled_events").insert(
397
+ event_record
398
+ ).execute()
399
+ logger.info(f" 💾 Ошибка сохранена в БД")
400
+ except Exception as db_error:
401
+ logger.error(f" ❌ Не удалось сохранить ошибку в БД: {db_error}")
402
+
403
+ continue # Переходим к следующему событию после сохранения ошибки
384
404
 
385
405
  # Если не user_event, пробуем как запланированную задачу
386
406
  elif event_type in scheduled_tasks:
@@ -469,12 +489,22 @@ async def process_events(session_id: str, events: list, user_id: int) -> bool:
469
489
  logger.error(f" ❌ Ошибка в обработчике/задаче: {e}")
470
490
  logger.exception(" Стек ошибки:")
471
491
 
472
- # Уведомляем админов только если result.notify = True
473
- if should_notify:
474
- await notify_admins_about_event(user_id, event)
475
- logger.info(" ✅ Админы уведомлены")
492
+ # Проверяем notify_time для scheduled_task
493
+ if handler_type == "task":
494
+ notify_time = handler_info.get("notify_time", "after")
495
+ # Для 'before' уведомляем сразу при создании
496
+ if notify_time == "before" and should_notify:
497
+ await notify_admins_about_event(user_id, event)
498
+ logger.info(" ✅ Админы уведомлены (notify_time=before)")
499
+ elif notify_time == "after":
500
+ logger.info(" ⏳ Уведомление будет отправлено после выполнения задачи (notify_time=after)")
476
501
  else:
477
- logger.info(f" 🔕 Уведомления админам отключены для '{event_type}'")
502
+ # Для обычных событий уведомляем сразу
503
+ if should_notify:
504
+ await notify_admins_about_event(user_id, event)
505
+ logger.info(" ✅ Админы уведомлены")
506
+ else:
507
+ logger.info(f" 🔕 Уведомления админам отключены для '{event_type}'")
478
508
 
479
509
  except Exception as e:
480
510
  logger.error(f"❌ Ошибка обработки события {event}: {e}")
@@ -72,15 +72,20 @@ class ConversationManager:
72
72
 
73
73
  try:
74
74
  # Получаем последние 5 сообщений (сортируем по убыванию и берем первые 5)
75
- response = (
75
+ query = (
76
76
  self.supabase.client.table("sales_messages")
77
77
  .select("role", "content", "created_at")
78
78
  .eq("session_id", session_id)
79
79
  .order("created_at", desc=True)
80
80
  .limit(5)
81
- .execute()
82
81
  )
83
82
 
83
+ # Добавляем фильтр по bot_id если он указан
84
+ if self.supabase.bot_id:
85
+ query = query.eq("bot_id", self.supabase.bot_id)
86
+
87
+ response = query.execute()
88
+
84
89
  recent_messages = response.data if response.data else []
85
90
 
86
91
  if not recent_messages:
@@ -122,12 +127,17 @@ class ConversationManager:
122
127
  async def get_user_display_name(self, user_id: int) -> str:
123
128
  """Получает красивое отображение пользователя с username"""
124
129
  try:
125
- response = (
130
+ query = (
126
131
  self.supabase.client.table("sales_users")
127
132
  .select("first_name", "last_name", "username")
128
133
  .eq("telegram_id", user_id)
129
- .execute()
130
134
  )
135
+
136
+ # Добавляем фильтр по bot_id если он указан
137
+ if self.supabase.bot_id:
138
+ query = query.eq("bot_id", self.supabase.bot_id)
139
+
140
+ response = query.execute()
131
141
 
132
142
  if response.data:
133
143
  user_info = response.data[0]
@@ -316,8 +326,7 @@ class ConversationManager:
316
326
 
317
327
  try:
318
328
  # Отправляем сообщение как от бота
319
- parse_mode = self.parse_mode if self.parse_mode != "None" else None
320
- await send_message_by_human(user_id, message.text, parse_mode=parse_mode)
329
+ await send_message_by_human(user_id, message.text)
321
330
 
322
331
  # Сохраняем в БД как сообщение ассистента
323
332
  session_info = await supabase_client.get_active_session(user_id)
@@ -406,27 +415,36 @@ class ConversationManager:
406
415
  logger.info("🔍 Ищем активные диалоги админов...")
407
416
 
408
417
  # Получаем все активные диалоги
409
- response = (
418
+ query = (
410
419
  self.supabase.client.table("admin_user_conversations")
411
420
  .select("id", "admin_id", "user_id", "started_at", "auto_end_at")
412
421
  .eq("status", "active")
413
- .order("started_at", desc=True)
414
- .execute()
415
422
  )
416
423
 
424
+ # Добавляем фильтр по bot_id если он указан
425
+ if self.supabase.bot_id:
426
+ query = query.eq("bot_id", self.supabase.bot_id)
427
+
428
+ response = query.order("started_at", desc=True).execute()
429
+
417
430
  logger.info(f"📊 Найдено {len(response.data)} активных диалогов в БД")
418
431
 
419
432
  conversations = []
420
433
  for conv in response.data:
421
434
  # Получаем информацию о пользователе
422
435
  try:
423
- user_response = (
436
+ user_query = (
424
437
  self.supabase.client.table("sales_users")
425
438
  .select("first_name", "last_name", "username")
426
439
  .eq("telegram_id", conv["user_id"])
427
- .execute()
428
440
  )
429
441
 
442
+ # Добавляем фильтр по bot_id если он указан
443
+ if self.supabase.bot_id:
444
+ user_query = user_query.eq("bot_id", self.supabase.bot_id)
445
+
446
+ user_response = user_query.execute()
447
+
430
448
  user_info = user_response.data[0] if user_response.data else {}
431
449
  except Exception as e:
432
450
  logger.error(
@@ -436,13 +454,18 @@ class ConversationManager:
436
454
 
437
455
  # Получаем информацию об админе
438
456
  try:
439
- admin_response = (
457
+ admin_query = (
440
458
  self.supabase.client.table("sales_admins")
441
459
  .select("first_name", "last_name", "username")
442
460
  .eq("telegram_id", conv["admin_id"])
443
- .execute()
444
461
  )
445
462
 
463
+ # Добавляем фильтр по bot_id если он указан
464
+ if self.supabase.bot_id:
465
+ admin_query = admin_query.eq("bot_id", self.supabase.bot_id)
466
+
467
+ admin_response = admin_query.execute()
468
+
446
469
  admin_info = admin_response.data[0] if admin_response.data else {}
447
470
  except Exception as e:
448
471
  logger.error(
@@ -476,6 +476,7 @@ def event_handler(
476
476
  def schedule_task(
477
477
  task_name: str,
478
478
  notify: bool = False,
479
+ notify_time: str = "after", # 'after' или 'before'
479
480
  smart_check: bool = True,
480
481
  once_only: bool = True,
481
482
  delay: Union[str, int] = None,
@@ -1310,6 +1311,11 @@ async def save_immediate_event(
1310
1311
  f"Событие '{event_type}' уже обрабатывалось (once_only=True)"
1311
1312
  )
1312
1313
 
1314
+ # Получаем bot_id
1315
+ bot_id = supabase_client.bot_id
1316
+ if not bot_id:
1317
+ logger.warning("⚠️ bot_id не указан при создании immediate_event")
1318
+
1313
1319
  event_record = {
1314
1320
  "event_type": event_type,
1315
1321
  "event_category": "user_event",
@@ -1318,12 +1324,9 @@ async def save_immediate_event(
1318
1324
  "scheduled_at": None, # Немедленное выполнение
1319
1325
  "status": "immediate",
1320
1326
  "session_id": session_id,
1327
+ "bot_id": bot_id, # Всегда добавляем bot_id
1321
1328
  }
1322
1329
 
1323
- # 🆕 Добавляем bot_id если указан
1324
- if supabase_client.bot_id:
1325
- event_record["bot_id"] = supabase_client.bot_id
1326
-
1327
1330
  try:
1328
1331
  response = (
1329
1332
  supabase_client.client.table("scheduled_events")
@@ -1375,6 +1378,11 @@ async def save_scheduled_task(
1375
1378
 
1376
1379
  scheduled_at = datetime.now(timezone.utc) + timedelta(seconds=delay_seconds)
1377
1380
 
1381
+ # Получаем bot_id
1382
+ bot_id = supabase_client.bot_id
1383
+ if not bot_id:
1384
+ logger.warning("⚠️ bot_id не указан при создании scheduled_task")
1385
+
1378
1386
  event_record = {
1379
1387
  "event_type": task_name,
1380
1388
  "event_category": "scheduled_task",
@@ -1383,12 +1391,9 @@ async def save_scheduled_task(
1383
1391
  "scheduled_at": scheduled_at.isoformat(),
1384
1392
  "status": "pending",
1385
1393
  "session_id": session_id,
1394
+ "bot_id": bot_id, # Всегда добавляем bot_id
1386
1395
  }
1387
1396
 
1388
- # 🆕 Добавляем bot_id если указан
1389
- if supabase_client.bot_id:
1390
- event_record["bot_id"] = supabase_client.bot_id
1391
-
1392
1397
  try:
1393
1398
  response = (
1394
1399
  supabase_client.client.table("scheduled_events")
@@ -1445,6 +1450,11 @@ async def save_global_event(
1445
1450
  scheduled_at = datetime.now(timezone.utc) + timedelta(seconds=delay_seconds)
1446
1451
  status = "pending"
1447
1452
 
1453
+ # Получаем bot_id
1454
+ bot_id = supabase_client.bot_id
1455
+ if not bot_id:
1456
+ logger.warning("⚠️ bot_id не указан при создании global_event")
1457
+
1448
1458
  event_record = {
1449
1459
  "event_type": handler_type,
1450
1460
  "event_category": "global_handler",
@@ -1452,12 +1462,9 @@ async def save_global_event(
1452
1462
  "event_data": handler_data,
1453
1463
  "scheduled_at": scheduled_at.isoformat() if scheduled_at else None,
1454
1464
  "status": status,
1465
+ "bot_id": bot_id, # Всегда добавляем bot_id (глобальные события тоже привязаны к боту)
1455
1466
  }
1456
1467
 
1457
- # 🆕 Добавляем bot_id если указан (глобальные события тоже привязаны к боту)
1458
- if supabase_client.bot_id:
1459
- event_record["bot_id"] = supabase_client.bot_id
1460
-
1461
1468
  try:
1462
1469
  response = (
1463
1470
  supabase_client.client.table("scheduled_events")
@@ -1503,22 +1510,32 @@ async def update_event_result(
1503
1510
  update_data["last_error"] = error_message
1504
1511
  # Получаем текущее количество попыток
1505
1512
  try:
1506
- current_retry = (
1513
+ query = (
1507
1514
  supabase_client.client.table("scheduled_events")
1508
1515
  .select("retry_count")
1509
1516
  .eq("id", event_id)
1510
- .execute()
1511
- .data[0]["retry_count"]
1512
1517
  )
1518
+
1519
+ # Добавляем фильтр по bot_id если указан
1520
+ if supabase_client.bot_id:
1521
+ query = query.eq("bot_id", supabase_client.bot_id)
1522
+
1523
+ current_retry = query.execute().data[0]["retry_count"]
1513
1524
  update_data["retry_count"] = current_retry + 1
1514
1525
  except Exception:
1515
1526
  logger.debug("Не удалось получить текущее количество попыток, устанавливаем 1")
1516
1527
  update_data["retry_count"] = 1
1517
1528
 
1518
1529
  try:
1519
- supabase_client.client.table("scheduled_events").update(update_data).eq(
1530
+ query = supabase_client.client.table("scheduled_events").update(update_data).eq(
1520
1531
  "id", event_id
1521
- ).execute()
1532
+ )
1533
+
1534
+ # Добавляем фильтр по bot_id если указан
1535
+ if supabase_client.bot_id:
1536
+ query = query.eq("bot_id", supabase_client.bot_id)
1537
+
1538
+ query.execute()
1522
1539
  logger.info(f"📝 Результат события {event_id} обновлен: {status}")
1523
1540
  except Exception as e:
1524
1541
  logger.error(f"❌ Ошибка обновления результата события {event_id}: {e}")
@@ -1613,7 +1630,14 @@ async def background_event_processor():
1613
1630
 
1614
1631
  # ========== ОБРАБОТКА АДМИНСКИХ СОБЫТИЙ ==========
1615
1632
  if event_category == "admin_event":
1633
+ # Проверяем bot_id
1634
+ if not event.get("bot_id"):
1635
+ logger.warning(f"⚠️ Админское событие {event['id']} не имеет bot_id")
1636
+
1616
1637
  try:
1638
+ logger.info(f"🔄 Начало обработки админского события {event['id']}")
1639
+ logger.info(f"📝 Данные события: {event}")
1640
+
1617
1641
  # Обрабатываем и получаем результат
1618
1642
  result = await process_admin_event(event)
1619
1643
 
@@ -1621,22 +1645,37 @@ async def background_event_processor():
1621
1645
  import json
1622
1646
 
1623
1647
  supabase_client = get_supabase_client()
1624
- supabase_client.client.table("scheduled_events").update(
1625
- {
1626
- "status": "completed",
1627
- "executed_at": datetime.now(
1628
- timezone.utc
1629
- ).isoformat(),
1630
- "result_data": (
1631
- json.dumps(result, ensure_ascii=False)
1632
- if result
1633
- else None
1634
- ),
1635
- }
1636
- ).eq("id", event["id"]).execute()
1648
+ if not supabase_client:
1649
+ raise RuntimeError("Не найден supabase_client")
1650
+
1651
+ # Готовим данные для обновления
1652
+ update_data = {
1653
+ "status": "completed",
1654
+ "executed_at": datetime.now(timezone.utc).isoformat(),
1655
+ "result_data": json.dumps(result, ensure_ascii=False) if result else None,
1656
+ }
1657
+
1658
+ # Если у события нет bot_id, но он есть в клиенте - добавляем
1659
+ if not event.get("bot_id") and supabase_client.bot_id:
1660
+ update_data["bot_id"] = supabase_client.bot_id
1661
+ logger.info(f"📝 Добавлен bot_id: {supabase_client.bot_id}")
1662
+
1663
+ # Строим запрос
1664
+ query = (
1665
+ supabase_client.client.table("scheduled_events")
1666
+ .update(update_data)
1667
+ .eq("id", event["id"])
1668
+ )
1669
+
1670
+ # Добавляем фильтр по bot_id если он был в событии
1671
+ if event.get("bot_id"):
1672
+ query = query.eq("bot_id", event["bot_id"])
1673
+
1674
+ # Выполняем обновление
1675
+ query.execute()
1637
1676
 
1638
1677
  logger.info(
1639
- f"✅ Админское событие {event['id']} выполнено"
1678
+ f"✅ Админское событие {event['id']} выполнено и обновлено в БД"
1640
1679
  )
1641
1680
  continue
1642
1681
 
@@ -1644,18 +1683,45 @@ async def background_event_processor():
1644
1683
  logger.error(
1645
1684
  f"❌ Ошибка обработки админского события {event['id']}: {e}"
1646
1685
  )
1686
+ logger.exception("Стек ошибки:")
1647
1687
 
1648
- # Обновляем статус на failed
1649
- supabase_client = get_supabase_client()
1650
- supabase_client.client.table("scheduled_events").update(
1651
- {
1688
+ try:
1689
+ # Обновляем статус на failed
1690
+ supabase_client = get_supabase_client()
1691
+ if not supabase_client:
1692
+ raise RuntimeError("Не найден supabase_client")
1693
+
1694
+ # Готовим данные для обновления
1695
+ update_data = {
1652
1696
  "status": "failed",
1653
1697
  "last_error": str(e),
1654
- "executed_at": datetime.now(
1655
- timezone.utc
1656
- ).isoformat(),
1698
+ "executed_at": datetime.now(timezone.utc).isoformat(),
1657
1699
  }
1658
- ).eq("id", event["id"]).execute()
1700
+
1701
+ # Если у события нет bot_id, но он есть в клиенте - добавляем
1702
+ if not event.get("bot_id") and supabase_client.bot_id:
1703
+ update_data["bot_id"] = supabase_client.bot_id
1704
+ logger.info(f"📝 Добавлен bot_id: {supabase_client.bot_id}")
1705
+
1706
+ # Строим запрос
1707
+ query = (
1708
+ supabase_client.client.table("scheduled_events")
1709
+ .update(update_data)
1710
+ .eq("id", event["id"])
1711
+ )
1712
+
1713
+ # Добавляем фильтр по bot_id если он был в событии
1714
+ if event.get("bot_id"):
1715
+ query = query.eq("bot_id", event["bot_id"])
1716
+
1717
+ # Выполняем обновление
1718
+ query.execute()
1719
+ logger.info(f"✅ Статус события {event['id']} обновлен на failed")
1720
+
1721
+ except Exception as update_error:
1722
+ logger.error(f"❌ Ошибка обновления статуса события: {update_error}")
1723
+ logger.exception("Стек ошибки обновления:")
1724
+
1659
1725
  continue
1660
1726
 
1661
1727
  # ========== ОБРАБОТКА USER СОБЫТИЙ ==========
@@ -1820,7 +1886,27 @@ async def process_scheduled_event(event: Dict):
1820
1886
 
1821
1887
  result = None
1822
1888
  if event_category == "scheduled_task":
1889
+ # Получаем информацию о задаче
1890
+ router_manager = get_router_manager()
1891
+ if router_manager:
1892
+ scheduled_tasks = router_manager.get_scheduled_tasks()
1893
+ else:
1894
+ scheduled_tasks = _scheduled_tasks
1895
+
1896
+ task_info = scheduled_tasks.get(event_type, {})
1897
+ notify = task_info.get("notify", False)
1898
+ notify_time = task_info.get("notify_time", "after")
1899
+
1900
+ # Выполняем задачу
1823
1901
  result = await execute_scheduled_task(event_type, user_id, event_data)
1902
+
1903
+ # Отправляем уведомление после выполнения если notify=True и notify_time="after"
1904
+ if notify and notify_time == "after":
1905
+ from ..core.bot_utils import notify_admins_about_event
1906
+ event_for_notify = {"тип": event_type, "инфо": event_data}
1907
+ await notify_admins_about_event(user_id, event_for_notify)
1908
+ logger.info(f" ✅ Админы уведомлены после выполнения задачи '{event_type}'")
1909
+
1824
1910
  elif event_category == "global_handler":
1825
1911
  result = await execute_global_handler(event_type, event_data)
1826
1912
  elif event_category == "user_event":
@@ -7,6 +7,7 @@ import time
7
7
  from datetime import datetime
8
8
  from typing import Any, Dict, Optional
9
9
 
10
+ from aiogram.types import InlineKeyboardMarkup
10
11
  import pytz
11
12
 
12
13
  logger = logging.getLogger(__name__)
@@ -193,7 +194,7 @@ async def send_message_by_ai(
193
194
 
194
195
 
195
196
  async def send_message_by_human(
196
- user_id: int, message_text: str, session_id: Optional[str] = None
197
+ user_id: int, message_text: str, session_id: Optional[str] = None, parse_mode: str = "Markdown", reply_markup: Optional[InlineKeyboardMarkup] = None
197
198
  ) -> Dict[str, Any]:
198
199
  """
199
200
  Отправляет сообщение пользователю от имени человека (готовый текст)
@@ -214,7 +215,7 @@ async def send_message_by_human(
214
215
  supabase_client = get_global_var("supabase_client")
215
216
 
216
217
  # Отправляем сообщение пользователю
217
- message = await bot.send_message(chat_id=user_id, text=message_text)
218
+ message = await bot.send_message(chat_id=user_id, text=message_text, parse_mode=parse_mode, reply_markup=reply_markup)
218
219
 
219
220
  # Если указана сессия, сохраняем сообщение в БД
220
221
  if session_id:
@@ -93,6 +93,7 @@ class EventRouter:
93
93
  self,
94
94
  task_name: str,
95
95
  notify: bool = False,
96
+ notify_time: str = "after", # 'after' или 'before'
96
97
  smart_check: bool = True,
97
98
  once_only: bool = True,
98
99
  delay: Union[str, int] = None,
@@ -105,6 +106,9 @@ class EventRouter:
105
106
  Args:
106
107
  task_name: Название задачи
107
108
  notify: Уведомлять ли админов
109
+ notify_time: Когда отправлять уведомление админам:
110
+ - 'before': при создании задачи
111
+ - 'after': после успешного выполнения (по умолчанию)
108
112
  smart_check: Использовать ли умную проверку
109
113
  once_only: Выполнять ли только один раз
110
114
  delay: Время задержки в удобном формате (например, "1h 30m", "45m", 3600) - ОБЯЗАТЕЛЬНО
@@ -141,10 +145,15 @@ class EventRouter:
141
145
  )
142
146
  raise
143
147
 
148
+ # Проверяем корректность notify_time
149
+ if notify_time not in ["before", "after"]:
150
+ raise ValueError(f"notify_time должен быть 'before' или 'after', получено: {notify_time}")
151
+
144
152
  self._scheduled_tasks[task_name] = {
145
153
  "handler": func,
146
154
  "name": func.__name__,
147
155
  "notify": notify,
156
+ "notify_time": notify_time, # Когда отправлять уведомление
148
157
  "smart_check": smart_check,
149
158
  "once_only": once_only,
150
159
  "router": self.name,