smart-bot-factory 0.3.9__tar.gz → 1.0.0__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.9 → smart_bot_factory-1.0.0}/.gitignore +5 -1
  2. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/PKG-INFO +1 -1
  3. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/pyproject.toml +1 -1
  4. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/admin/admin_manager.py +2 -2
  5. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/core/bot_utils.py +54 -24
  6. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/core/conversation_manager.py +36 -13
  7. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/core/decorators.py +38 -13
  8. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/core/message_sender.py +3 -2
  9. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/core/router.py +9 -0
  10. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/event/__init__.py +1 -1
  11. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/handlers/handlers.py +27 -27
  12. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/integrations/supabase_client.py +38 -15
  13. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/uv.lock +1 -1
  14. smart_bot_factory-0.3.9/.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 -20
  15. smart_bot_factory-0.3.9/.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 -26
  16. smart_bot_factory-0.3.9/.github/workflows/ci.yml +0 -59
  17. smart_bot_factory-0.3.9/.github/workflows/publish-private.yml +0 -34
  18. smart_bot_factory-0.3.9/.github/workflows/publish.yml +0 -31
  19. smart_bot_factory-0.3.9/.pre-commit-config.yaml +0 -23
  20. smart_bot_factory-0.3.9/publish.py +0 -152
  21. smart_bot_factory-0.3.9/smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +0 -16
  22. smart_bot_factory-0.3.9/smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +0 -582
  23. smart_bot_factory-0.3.9/smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +0 -66
  24. smart_bot_factory-0.3.9/smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +0 -212
  25. smart_bot_factory-0.3.9/smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +0 -28
  26. smart_bot_factory-0.3.9/smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +0 -8
  27. smart_bot_factory-0.3.9/smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +0 -133
  28. smart_bot_factory-0.3.9/smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +0 -108
  29. smart_bot_factory-0.3.9/smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +0 -46
  30. smart_bot_factory-0.3.9/smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +0 -16
  31. smart_bot_factory-0.3.9/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
  32. smart_bot_factory-0.3.9/valera.py +0 -56
  33. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/.python-version +0 -0
  34. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/LICENSE +0 -0
  35. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/README.md +0 -0
  36. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/__init__.py +0 -0
  37. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/admin/__init__.py +0 -0
  38. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/admin/admin_events.py +0 -0
  39. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/admin/admin_logic.py +0 -0
  40. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/admin/admin_tester.py +0 -0
  41. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/admin/timeout_checker.py +0 -0
  42. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/aiogram_calendar/__init__.py +0 -0
  43. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/aiogram_calendar/common.py +0 -0
  44. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/aiogram_calendar/dialog_calendar.py +0 -0
  45. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/aiogram_calendar/schemas.py +0 -0
  46. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/aiogram_calendar/simple_calendar.py +0 -0
  47. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/analytics/analytics_manager.py +0 -0
  48. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/cli.py +0 -0
  49. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/config.py +0 -0
  50. {smart_bot_factory-0.3.9/bots/valera → smart_bot_factory-1.0.0/smart_bot_factory/configs/growthmed-october-24}/prompts/1sales_context.txt +0 -0
  51. {smart_bot_factory-0.3.9/bots/valera → smart_bot_factory-1.0.0/smart_bot_factory/configs/growthmed-october-24}/prompts/2product_info.txt +0 -0
  52. {smart_bot_factory-0.3.9/bots/valera → smart_bot_factory-1.0.0/smart_bot_factory/configs/growthmed-october-24}/prompts/3objection_handling.txt +0 -0
  53. {smart_bot_factory-0.3.9/bots/valera → smart_bot_factory-1.0.0/smart_bot_factory/configs/growthmed-october-24}/prompts/final_instructions.txt +0 -0
  54. {smart_bot_factory-0.3.9/bots/valera → smart_bot_factory-1.0.0/smart_bot_factory/configs/growthmed-october-24}/prompts/help_message.txt +0 -0
  55. {smart_bot_factory-0.3.9/bots/valera → smart_bot_factory-1.0.0/smart_bot_factory/configs/growthmed-october-24}/prompts/welcome_message.txt +0 -0
  56. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +0 -0
  57. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +0 -0
  58. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +0 -0
  59. {smart_bot_factory-0.3.9/bots/valera → smart_bot_factory-1.0.0/smart_bot_factory/configs/growthmed-october-24}/tests/quick_scenarios.yaml +0 -0
  60. {smart_bot_factory-0.3.9/bots/valera → smart_bot_factory-1.0.0/smart_bot_factory/configs/growthmed-october-24}/tests/realistic_scenarios.yaml +0 -0
  61. {smart_bot_factory-0.3.9/bots/valera → smart_bot_factory-1.0.0/smart_bot_factory/configs/growthmed-october-24}/tests/scenario_examples.yaml +0 -0
  62. {smart_bot_factory-0.3.9/bots/valera/welcome_files → smart_bot_factory-1.0.0/smart_bot_factory/configs/growthmed-october-24/welcome_file}/welcome_file_msg.txt +0 -0
  63. {smart_bot_factory-0.3.9/bots/valera/welcome_files → smart_bot_factory-1.0.0/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
  64. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/core/router_manager.py +0 -0
  65. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/core/states.py +0 -0
  66. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/creation/__init__.py +0 -0
  67. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/creation/bot_builder.py +0 -0
  68. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/creation/bot_testing.py +0 -0
  69. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/dashboard/__init__.py +0 -0
  70. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/integrations/openai_client.py +0 -0
  71. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/message/__init__.py +0 -0
  72. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/router/__init__.py +0 -0
  73. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/setup_checker.py +0 -0
  74. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/supabase/__init__.py +0 -0
  75. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/supabase/client.py +0 -0
  76. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/utils/__init__.py +0 -0
  77. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/utils/debug_routing.py +0 -0
  78. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/utils/prompt_loader.py +0 -0
  79. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/smart_bot_factory/utils/user_prompt_loader.py +0 -0
  80. {smart_bot_factory-0.3.9 → smart_bot_factory-1.0.0}/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.9
3
+ Version: 1.0.0
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.9"
3
+ version = "1.0.0"
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(
@@ -7,7 +7,7 @@ import logging
7
7
  import re
8
8
  from datetime import datetime, timedelta, timezone
9
9
  from functools import wraps
10
- from typing import Any, Callable, Dict, Union
10
+ from typing import Any, Callable, Dict, Optional, Union
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
@@ -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,
@@ -1885,7 +1886,27 @@ async def process_scheduled_event(event: Dict):
1885
1886
 
1886
1887
  result = None
1887
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
+ # Выполняем задачу
1888
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
+
1889
1910
  elif event_category == "global_handler":
1890
1911
  result = await execute_global_handler(event_type, event_data)
1891
1912
  elif event_category == "user_event":
@@ -2155,12 +2176,13 @@ async def check_event_already_processed(
2155
2176
  return False
2156
2177
 
2157
2178
 
2158
- async def process_admin_event(event: Dict):
2179
+ async def process_admin_event(event: Dict, single_user_id: Optional[int] = None):
2159
2180
  """
2160
2181
  Обрабатывает одно админское событие - скачивает файлы из Storage и отправляет пользователям
2161
2182
 
2162
2183
  Args:
2163
2184
  event: Событие из БД с данными для отправки
2185
+ single_user_id: ID пользователя для тестовой отправки. Если указан, сообщение будет отправлено только ему
2164
2186
  """
2165
2187
  import json
2166
2188
  import shutil
@@ -2244,17 +2266,20 @@ async def process_admin_event(event: Dict):
2244
2266
  raise
2245
2267
 
2246
2268
  # 2. Получаем пользователей
2247
- users = await supabase_client.get_users_by_segment(segment)
2248
-
2249
- if not users:
2250
- logger.warning(f"⚠️ Нет пользователей для сегмента '{segment}'")
2251
- return {
2252
- "success_count": 0,
2253
- "failed_count": 0,
2254
- "total_users": 0,
2255
- "segment": segment or "Все",
2256
- "warning": "Нет пользователей",
2257
- }
2269
+ if single_user_id:
2270
+ users = [{"telegram_id": single_user_id}]
2271
+ logger.info(f"🔍 Тестовая отправка для пользователя {single_user_id}")
2272
+ else:
2273
+ users = await supabase_client.get_users_by_segment(segment)
2274
+ if not users:
2275
+ logger.warning(f"⚠️ Нет пользователей для сегмента '{segment}'")
2276
+ return {
2277
+ "success_count": 0,
2278
+ "failed_count": 0,
2279
+ "total_users": 0,
2280
+ "segment": segment or "Все",
2281
+ "warning": "Нет пользователей",
2282
+ }
2258
2283
 
2259
2284
  success_count = 0
2260
2285
  failed_count = 0
@@ -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,
@@ -4,4 +4,4 @@ Event модули smart_bot_factory
4
4
 
5
5
  from ..core.decorators import event_handler, global_handler, schedule_task
6
6
 
7
- __all__ = ["event_handler", "schedule_task", "global_handler"]
7
+ __all__ = ["event_handler", "schedule_task", "global_handler"]
@@ -79,7 +79,7 @@ async def timeup_handler(message: Message, state: FSMContext):
79
79
  """Обработчик команды /timeup (или /вперед) - тестирование запланированных событий"""
80
80
  from datetime import datetime
81
81
 
82
- from ..core.decorators import process_scheduled_event, update_event_result
82
+ from ..core.decorators import process_scheduled_event, update_event_result, process_admin_event
83
83
 
84
84
  supabase_client = get_global_var("supabase_client")
85
85
 
@@ -95,9 +95,7 @@ async def timeup_handler(message: Message, state: FSMContext):
95
95
  .in_("status", ["pending", "immediate"])
96
96
  )
97
97
 
98
- # 🆕 Фильтруем по bot_id если указан
99
- if supabase_client.bot_id:
100
- user_events_query = user_events_query.eq("bot_id", supabase_client.bot_id)
98
+ user_events_query = user_events_query.eq("bot_id", supabase_client.bot_id)
101
99
 
102
100
  user_events = user_events_query.execute()
103
101
 
@@ -107,14 +105,9 @@ async def timeup_handler(message: Message, state: FSMContext):
107
105
  .select("*")
108
106
  .is_("user_id", "null")
109
107
  .in_("status", ["pending", "immediate"])
108
+ .eq("bot_id", supabase_client.bot_id)
110
109
  )
111
110
 
112
- # 🆕 Фильтруем по bot_id если указан
113
- if supabase_client.bot_id:
114
- global_events_query = global_events_query.eq(
115
- "bot_id", supabase_client.bot_id
116
- )
117
-
118
111
  global_events = global_events_query.execute()
119
112
 
120
113
  # Объединяем события
@@ -155,23 +148,30 @@ async def timeup_handler(message: Message, state: FSMContext):
155
148
  )
156
149
 
157
150
  # Выполняем событие
158
- await process_scheduled_event(event)
159
-
160
- # Помечаем как выполненное
161
- await update_event_result(
162
- event_id,
163
- "completed",
164
- {
165
- "executed": True,
166
- "test_mode": True,
167
- "tested_by_user": message.from_user.id,
168
- "tested_at": datetime.now().isoformat(),
169
- },
170
- )
171
-
172
- success_count += 1
173
- results.append(f" {event_label}")
174
- logger.info(f" Событие {event_id} успешно выполнено")
151
+ if event_category == "admin_event":
152
+ # Для админских событий используем тестовую отправку только текущему пользователю
153
+ await process_admin_event(event, single_user_id=message.from_user.id)
154
+ # Не отмечаем админское событие как выполненное при тестовой отправке
155
+ success_count += 1
156
+ results.append(f"✅ {event_label} (тестовая отправка)")
157
+ logger.info(f"✅ Событие {event_id} протестировано для пользователя {message.from_user.id}")
158
+ else:
159
+ await process_scheduled_event(event)
160
+ # Помечаем как выполненное только не-админские события
161
+ if event_category != "global_handler":
162
+ await update_event_result(
163
+ event_id,
164
+ "completed",
165
+ {
166
+ "executed": True,
167
+ "test_mode": True,
168
+ "tested_by_user": message.from_user.id,
169
+ "tested_at": datetime.now().isoformat(),
170
+ },
171
+ )
172
+ success_count += 1
173
+ results.append(f"✅ {event_label}")
174
+ logger.info(f"✅ Событие {event_id} успешно выполнено")
175
175
 
176
176
  except Exception as e:
177
177
  failed_count += 1
@@ -570,22 +570,37 @@ class SupabaseClient:
570
570
  ) -> int:
571
571
  """Начинает диалог между админом и пользователем"""
572
572
  try:
573
+ # Проверяем существование пользователя с правильным bot_id
574
+ user_query = self.client.table("sales_users").select("telegram_id").eq("telegram_id", user_id)
575
+ if self.bot_id:
576
+ user_query = user_query.eq("bot_id", self.bot_id)
577
+
578
+ user_response = user_query.execute()
579
+ if not user_response.data:
580
+ logger.error(f"❌ Пользователь {user_id} не найден в sales_users{f' для bot_id {self.bot_id}' if self.bot_id else ''}")
581
+ raise APIError("User not found in sales_users")
582
+
573
583
  # Завершаем активные диалоги этого админа
574
584
  await self.end_admin_conversations(admin_id)
575
585
 
586
+ # Готовим данные для записи
587
+ conversation_data = {
588
+ "admin_id": admin_id,
589
+ "user_id": user_id,
590
+ "session_id": session_id,
591
+ "status": "active",
592
+ "auto_end_at": (
593
+ datetime.now(timezone.utc) + timedelta(minutes=30)
594
+ ).isoformat(),
595
+ }
596
+
597
+ # Добавляем bot_id если он указан
598
+ if self.bot_id:
599
+ conversation_data["bot_id"] = self.bot_id
600
+
576
601
  response = (
577
602
  self.client.table("admin_user_conversations")
578
- .insert(
579
- {
580
- "admin_id": admin_id,
581
- "user_id": user_id,
582
- "session_id": session_id,
583
- "status": "active",
584
- "auto_end_at": (
585
- datetime.now(timezone.utc) + timedelta(minutes=30)
586
- ).isoformat(),
587
- }
588
- )
603
+ .insert(conversation_data)
589
604
  .execute()
590
605
  )
591
606
 
@@ -608,7 +623,7 @@ class SupabaseClient:
608
623
  self.client.table("admin_user_conversations")
609
624
  .update(
610
625
  {
611
- "status": "ended",
626
+ "status": "completed", # Используем 'completed' вместо 'ended'
612
627
  "ended_at": datetime.now(timezone.utc).isoformat(),
613
628
  }
614
629
  )
@@ -619,6 +634,9 @@ class SupabaseClient:
619
634
  query = query.eq("admin_id", admin_id)
620
635
  if user_id:
621
636
  query = query.eq("user_id", user_id)
637
+ # Добавляем фильтр по bot_id если он указан
638
+ if self.bot_id:
639
+ query = query.eq("bot_id", self.bot_id)
622
640
 
623
641
  response = query.execute()
624
642
  ended_count = len(response.data)
@@ -1193,15 +1211,20 @@ class SupabaseClient:
1193
1211
  """Проверяет, изменился ли этап пользователя с момента планирования события"""
1194
1212
  try:
1195
1213
  # Получаем текущую информацию о сессии
1196
- current_response = (
1214
+ query = (
1197
1215
  self.client.table("sales_chat_sessions")
1198
1216
  .select("id", "current_stage")
1199
- .eq("user_telegram_id", user_id)
1217
+ .eq("user_id", user_id)
1200
1218
  .order("created_at", desc=True)
1201
1219
  .limit(1)
1202
- .execute()
1203
1220
  )
1204
1221
 
1222
+ # Добавляем фильтр по bot_id если он указан
1223
+ if self.bot_id:
1224
+ query = query.eq("bot_id", self.bot_id)
1225
+
1226
+ current_response = query.execute()
1227
+
1205
1228
  if not current_response.data:
1206
1229
  return False
1207
1230
 
@@ -1670,7 +1670,7 @@ wheels = [
1670
1670
 
1671
1671
  [[package]]
1672
1672
  name = "smart-bot-factory"
1673
- version = "0.3.8"
1673
+ version = "0.3.11.dev2"
1674
1674
  source = { editable = "." }
1675
1675
  dependencies = [
1676
1676
  { name = "aiofiles" },