smart-bot-factory 0.3.8__py3-none-any.whl → 0.3.10__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.

@@ -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,
@@ -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,25 @@ async def timeup_handler(message: Message, state: FSMContext):
155
148
  )
156
149
 
157
150
  # Выполняем событие
158
- await process_scheduled_event(event)
151
+ if event_category != "admin_event":
152
+ await process_scheduled_event(event)
159
153
 
160
154
  # Помечаем как выполненное
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} успешно выполнено")
155
+ if event_category != "admin_event" or event_category != "global_handler":
156
+ await update_event_result(
157
+ event_id,
158
+ "completed",
159
+ {
160
+ "executed": True,
161
+ "test_mode": True,
162
+ "tested_by_user": message.from_user.id,
163
+ "tested_at": datetime.now().isoformat(),
164
+ },
165
+ )
166
+ if event_category != "admin_event":
167
+ success_count += 1
168
+ results.append(f"✅ {event_label}")
169
+ logger.info(f"✅ Событие {event_id} успешно выполнено")
175
170
 
176
171
  except Exception as e:
177
172
  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
 
@@ -1515,6 +1538,7 @@ class SupabaseClient:
1515
1538
 
1516
1539
  scheduled_datetime = scheduled_datetime.replace(tzinfo=timezone.utc)
1517
1540
 
1541
+ # Готовим данные для записи
1518
1542
  event_record = {
1519
1543
  "event_type": event_name,
1520
1544
  "event_category": "admin_event",
@@ -1524,6 +1548,11 @@ class SupabaseClient:
1524
1548
  "status": "pending",
1525
1549
  }
1526
1550
 
1551
+ # Добавляем bot_id если он указан
1552
+ if self.bot_id:
1553
+ event_record["bot_id"] = self.bot_id
1554
+ logger.info(f"📝 Добавлен bot_id: {self.bot_id} для админского события")
1555
+
1527
1556
  response = (
1528
1557
  self.client.table("scheduled_events").insert(event_record).execute()
1529
1558
  )
@@ -1549,15 +1578,22 @@ class SupabaseClient:
1549
1578
  List[Dict]: Список админских событий
1550
1579
  """
1551
1580
  try:
1581
+ # Строим базовый запрос
1552
1582
  query = (
1553
1583
  self.client.table("scheduled_events")
1554
1584
  .select("*")
1555
1585
  .eq("event_category", "admin_event")
1556
1586
  )
1557
1587
 
1588
+ # Добавляем фильтры
1558
1589
  if status:
1559
1590
  query = query.eq("status", status)
1560
1591
 
1592
+ # Фильтруем по bot_id если он указан
1593
+ if self.bot_id:
1594
+ query = query.eq("bot_id", self.bot_id)
1595
+ logger.info(f"🔍 Фильтруем админские события по bot_id: {self.bot_id}")
1596
+
1561
1597
  response = query.order("scheduled_at", desc=False).execute()
1562
1598
 
1563
1599
  logger.info(f"Найдено {len(response.data)} админских событий")
@@ -1578,15 +1614,22 @@ class SupabaseClient:
1578
1614
  bool: True если активное событие с таким именем существует
1579
1615
  """
1580
1616
  try:
1581
- response = (
1617
+ # Строим запрос
1618
+ query = (
1582
1619
  self.client.table("scheduled_events")
1583
1620
  .select("id", "event_type", "status")
1584
1621
  .eq("event_category", "admin_event")
1585
1622
  .eq("event_type", event_name)
1586
1623
  .eq("status", "pending")
1587
- .execute()
1588
1624
  )
1589
1625
 
1626
+ # Фильтруем по bot_id если он указан
1627
+ if self.bot_id:
1628
+ query = query.eq("bot_id", self.bot_id)
1629
+ logger.info(f"🔍 Проверка названия события с фильтром по bot_id: {self.bot_id}")
1630
+
1631
+ response = query.execute()
1632
+
1590
1633
  exists = len(response.data) > 0
1591
1634
 
1592
1635
  if exists:
@@ -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
@@ -6,7 +6,7 @@ smart_bot_factory/utm_link_generator.py,sha256=eA3Eic4Wvs0QpMdAFPJgYJyzv-_pSQWZ5
6
6
  smart_bot_factory/admin/__init__.py,sha256=xHUtMLeQDDQEXZrg4WAGpt-TWp8adjCC0-mYUBuino4,489
7
7
  smart_bot_factory/admin/admin_events.py,sha256=FkiD0FtnlgpxLCoN9KXR61VCn8OsI0JCWm9RjVVa6tk,43903
8
8
  smart_bot_factory/admin/admin_logic.py,sha256=TiXhZIXiXK1YQkrueY_zPFYL3_sJzoAvd100AP-ZCWU,23136
9
- smart_bot_factory/admin/admin_manager.py,sha256=hjk4XI5prdlE6KMm64DjzadujGu1DmXWkeTQLweWF54,6406
9
+ smart_bot_factory/admin/admin_manager.py,sha256=u22j1xy2zLTw0kejgzWF5m657_IyMDKnDlM9kBzbjh4,6359
10
10
  smart_bot_factory/admin/admin_tester.py,sha256=YwovS51chPJoQ33pIiug-vS3augQzc1hHfbma48HtDk,6352
11
11
  smart_bot_factory/admin/timeout_checker.py,sha256=afWsg39X6M5SaRKMk7ptNod8Z0StIa2o8H5Lx8y1ZE0,24438
12
12
  smart_bot_factory/aiogram_calendar/__init__.py,sha256=UCoae3mXXDgYqqSQPotEpUxzAsZ_e62FuW9MWl1sG34,410
@@ -29,11 +29,11 @@ smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml,sh
29
29
  smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml,sha256=bzDulOU4a2LyWlcHzlQU8GYhOky2WTfyizGfjX4ioMY,2436
30
30
  smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt,sha256=Db21Mm0r8SBWFdX9EeIF2FZtLQ2cvuwVlSRJd2KEYCg,922
31
31
  smart_bot_factory/configs/growthmed-october-24/welcome_file/Чек лист по 152ФЗ и 323ФЗ для медицины.pdf,sha256=BiAiQHNnQXJPMsks9AeL6s0beEjRFkRMJLMlAn4WorA,5284954
32
- smart_bot_factory/core/bot_utils.py,sha256=WlJ3QMIO96-n6-KyjQrGm2PR8Z1U-lvIGtSSXSSaLuE,49874
33
- smart_bot_factory/core/conversation_manager.py,sha256=zduDEaTr7iQoVvbcMdmqBsyN4x1ZaOpTnUt0JsAgQ7I,28497
34
- smart_bot_factory/core/decorators.py,sha256=9zBlgKZb0fnClZ0dVULNJKd9Fdl7VRDnVX07LmLU85I,103031
35
- smart_bot_factory/core/message_sender.py,sha256=VjOLZUhw0Lkz_IOh4p9J_L5DkEkaURFRRU-qrIsG2qc,32338
36
- smart_bot_factory/core/router.py,sha256=GdsSZxeuYICyzXNFeJnS4CnbaXGxojg-c9JK7z2OFiM,15592
32
+ smart_bot_factory/core/bot_utils.py,sha256=vbWb4wUkG-FlQIGTQ6RLoSSVg_LRVTzpnX3tyowsbqY,52032
33
+ smart_bot_factory/core/conversation_manager.py,sha256=ga0ThoDpvov97x5KLcHpL8aRlEZB1XRw3JPrexIb-jk,29506
34
+ smart_bot_factory/core/decorators.py,sha256=1FwJMjSM-QEgX3hX_LEW0A6VS3TWXBRIwwpPxK2pZuA,108247
35
+ smart_bot_factory/core/message_sender.py,sha256=gK0HbjLumAu5_Vr29DnOQkwbPGGmQrtkgW1dVt_9Bb8,32518
36
+ smart_bot_factory/core/router.py,sha256=66sCp7Okyu1ceIQPpxFF88-NizTD1N7rR4ZAdwDYmi8,16285
37
37
  smart_bot_factory/core/router_manager.py,sha256=x-cc4zhZUVPCTUH980GdlE_-JjHRtYnPdEBiWnf3ka8,10156
38
38
  smart_bot_factory/core/states.py,sha256=mFyQ-oxIjTX03Wy5NHZUgYgh8fsJ5YmQU09_PhPAB04,889
39
39
  smart_bot_factory/creation/__init__.py,sha256=lHFgYIjOLvdsDAW8eLMp8zfFUCd-6wepvUaUMeJ7feU,128
@@ -41,9 +41,9 @@ smart_bot_factory/creation/bot_builder.py,sha256=-gF53eCaWBRKoUbRGR-PDHpqLWlceQ9
41
41
  smart_bot_factory/creation/bot_testing.py,sha256=aMPSgj2o2zl6yWiV1a6RMmSJfiwMYp-_0ZkmJ6aFZVs,54561
42
42
  smart_bot_factory/dashboard/__init__.py,sha256=Qie1pJgzprBlCPrZVQLhVs1JR5-YrpzfcBvHHD3OLJk,94
43
43
  smart_bot_factory/event/__init__.py,sha256=E5u8Pjul_T0E77Ftmj-mqrip05w7KwUXlF1WMc5_lIs,192
44
- smart_bot_factory/handlers/handlers.py,sha256=IELP8fH-hW8KhnTRKBvaO-2rnPmJRGmmcKN8acrvPe0,63656
44
+ smart_bot_factory/handlers/handlers.py,sha256=za41yxSfsSwARnuI-l57JO90HN2knOXIqkbgq_z-MOA,63623
45
45
  smart_bot_factory/integrations/openai_client.py,sha256=R04qglOWNu3yQ32QRMRMzO01T6luWTp83H3OJMvD-uM,24316
46
- smart_bot_factory/integrations/supabase_client.py,sha256=vBmwNt069GXk9qel6UvgudNyvOEsp_YCkOX6XCPlaCY,66158
46
+ smart_bot_factory/integrations/supabase_client.py,sha256=7Yip9din4PDqFD1X2pkKB4KYAhmf04LUzxo9SnOrVg4,68415
47
47
  smart_bot_factory/message/__init__.py,sha256=neUUYAs8ITz3cV1B_T0P9_3XZvvn8cZ2VDpDqrf6lrc,1940
48
48
  smart_bot_factory/router/__init__.py,sha256=ywywG6iFBLLkBvtZ-huPVetqjXGN4UiUzoK6lw46bMg,272
49
49
  smart_bot_factory/supabase/__init__.py,sha256=xa1xmHLaQVoghjD5cqguiL4yd25VYMvGd6_e87uc0tQ,181
@@ -52,8 +52,8 @@ smart_bot_factory/utils/__init__.py,sha256=RgP2IAMFsJF7fxhhg8JLfhbGg9mO63xQM-NEa
52
52
  smart_bot_factory/utils/debug_routing.py,sha256=wV9BFwNmjBbF3rNI7UBdGsTf1bIT5XVQIGnxwIxhNYA,4707
53
53
  smart_bot_factory/utils/prompt_loader.py,sha256=YClbQjvRgTWCD42Rr92g77zzVEVMUgrqStI6YETC0c4,20022
54
54
  smart_bot_factory/utils/user_prompt_loader.py,sha256=lq1eQ4Hb2qN22osOjaFtkGdEc4OgpFPrzPNpPhsm5kA,2353
55
- smart_bot_factory-0.3.8.dist-info/METADATA,sha256=3D53canLAlt3V72z7HX0EfZT9nSSl4VZVWYuNSFTSmI,40748
56
- smart_bot_factory-0.3.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
57
- smart_bot_factory-0.3.8.dist-info/entry_points.txt,sha256=ybKEAI0WSb7WoRiey7QE-HHfn88UGV7nxLDxXq7b7SU,50
58
- smart_bot_factory-0.3.8.dist-info/licenses/LICENSE,sha256=OrK3cwdUTzNzIhJvSPtJaVMoYIyC_sSx5EFE_FDMvGs,1092
59
- smart_bot_factory-0.3.8.dist-info/RECORD,,
55
+ smart_bot_factory-0.3.10.dist-info/METADATA,sha256=1rn8juhuHmHF0Vf9tmqucWYj0JW-acIjzvIMlvLfvFI,40749
56
+ smart_bot_factory-0.3.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
57
+ smart_bot_factory-0.3.10.dist-info/entry_points.txt,sha256=ybKEAI0WSb7WoRiey7QE-HHfn88UGV7nxLDxXq7b7SU,50
58
+ smart_bot_factory-0.3.10.dist-info/licenses/LICENSE,sha256=OrK3cwdUTzNzIhJvSPtJaVMoYIyC_sSx5EFE_FDMvGs,1092
59
+ smart_bot_factory-0.3.10.dist-info/RECORD,,