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.
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.gitignore +5 -1
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/PKG-INFO +1 -1
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/pyproject.toml +1 -1
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/admin_manager.py +2 -2
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/bot_utils.py +54 -24
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/conversation_manager.py +36 -13
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/decorators.py +125 -39
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/message_sender.py +3 -2
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/router.py +9 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/handlers/handlers.py +19 -24
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/integrations/supabase_client.py +60 -17
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/uv.lock +1 -1
- smart_bot_factory-0.3.8/.pre-commit-config.yaml +0 -23
- smart_bot_factory-0.3.8/publish.py +0 -152
- smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +0 -16
- smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +0 -582
- smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +0 -66
- smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +0 -212
- smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +0 -28
- smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +0 -8
- smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +0 -133
- smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +0 -108
- smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +0 -46
- smart_bot_factory-0.3.8/smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +0 -16
- 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
- smart_bot_factory-0.3.8/valera.py +0 -56
- {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
- {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
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.github/workflows/ci.yml +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.github/workflows/publish-private.yml +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.github/workflows/publish.yml +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/.python-version +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/LICENSE +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/README.md +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/__init__.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/__init__.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/admin_events.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/admin_logic.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/admin_tester.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/timeout_checker.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/aiogram_calendar/__init__.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/aiogram_calendar/common.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/aiogram_calendar/dialog_calendar.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/aiogram_calendar/schemas.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/aiogram_calendar/simple_calendar.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/analytics/analytics_manager.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/cli.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/config.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/router_manager.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/states.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/creation/__init__.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/creation/bot_builder.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/creation/bot_testing.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/dashboard/__init__.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/event/__init__.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/integrations/openai_client.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/message/__init__.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/router/__init__.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/setup_checker.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/supabase/__init__.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/supabase/client.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/utils/__init__.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/utils/debug_routing.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/utils/prompt_loader.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/utils/user_prompt_loader.py +0 -0
- {smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/utm_link_generator.py +0 -0
{smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/admin/admin_manager.py
RENAMED
|
@@ -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
|
|
126
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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" 🔍
|
|
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" 🔇🔇🔇
|
|
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" 🎯 Немедленно выполняем
|
|
312
|
+
f" 🎯 Немедленно выполняем {handler_type}: '{event_type}'"
|
|
305
313
|
)
|
|
306
314
|
|
|
307
315
|
try:
|
|
308
|
-
# Выполняем
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
#
|
|
473
|
-
if
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
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}")
|
{smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/conversation_manager.py
RENAMED
|
@@ -72,15 +72,20 @@ class ConversationManager:
|
|
|
72
72
|
|
|
73
73
|
try:
|
|
74
74
|
# Получаем последние 5 сообщений (сортируем по убыванию и берем первые 5)
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
)
|
|
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
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
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
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
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
|
-
|
|
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":
|
{smart_bot_factory-0.3.8 → smart_bot_factory-0.3.10}/smart_bot_factory/core/message_sender.py
RENAMED
|
@@ -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,
|