smart-bot-factory 0.1.4__py3-none-any.whl → 0.1.6__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.
- smart_bot_factory/__init__.py +0 -30
- smart_bot_factory/admin/admin_logic.py +11 -11
- smart_bot_factory/cli.py +147 -85
- smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +2 -0
- smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +95 -28
- smart_bot_factory/core/bot_utils.py +224 -88
- smart_bot_factory/core/conversation_manager.py +542 -535
- smart_bot_factory/core/decorators.py +927 -230
- smart_bot_factory/core/message_sender.py +2 -7
- smart_bot_factory/core/router.py +173 -0
- smart_bot_factory/core/router_manager.py +166 -0
- smart_bot_factory/creation/__init__.py +1 -2
- smart_bot_factory/creation/bot_builder.py +103 -13
- smart_bot_factory/creation/bot_testing.py +74 -13
- smart_bot_factory/event/__init__.py +12 -0
- smart_bot_factory/handlers/handlers.py +10 -2
- smart_bot_factory/integrations/supabase_client.py +272 -2
- smart_bot_factory/message/__init__.py +12 -0
- smart_bot_factory/router/__init__.py +9 -0
- smart_bot_factory/supabase/__init__.py +7 -0
- smart_bot_factory-0.1.6.dist-info/METADATA +466 -0
- {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.6.dist-info}/RECORD +26 -31
- smart_bot_factory/analytics/__init__.py +0 -7
- smart_bot_factory/configs/growthmed-helper/prompts/1sales_context.txt +0 -9
- smart_bot_factory/configs/growthmed-helper/prompts/2product_info.txt +0 -582
- smart_bot_factory/configs/growthmed-helper/prompts/3objection_handling.txt +0 -66
- smart_bot_factory/configs/growthmed-helper/prompts/final_instructions.txt +0 -232
- smart_bot_factory/configs/growthmed-helper/prompts/help_message.txt +0 -28
- smart_bot_factory/configs/growthmed-helper/prompts/welcome_message.txt +0 -7
- smart_bot_factory/configs/growthmed-helper/welcome_file/welcome_file_msg.txt +0 -16
- smart_bot_factory/configs/growthmed-helper/welcome_file//342/225/250/320/267/342/225/250/342/225/241/342/225/250/342/225/221 /342/225/250/342/225/227/342/225/250/342/225/225/342/225/244/320/221/342/225/244/320/222 /342/225/250/342/224/220/342/225/250/342/225/233 152/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/225/225 323/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/224/244/342/225/250/342/225/227/342/225/244/320/237 /342/225/250/342/225/235/342/225/250/342/225/241/342/225/250/342/224/244/342/225/250/342/225/225/342/225/244/320/226/342/225/250/342/225/225/342/225/250/342/225/234/342/225/244/320/233.pdf +0 -0
- smart_bot_factory/core/__init__.py +0 -22
- smart_bot_factory/integrations/__init__.py +0 -9
- smart_bot_factory-0.1.4.dist-info/METADATA +0 -126
- /smart_bot_factory/{configs/growthmed-helper/env_example.txt → supabase/example_usage.py} +0 -0
- {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.6.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.6.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,65 +2,132 @@ scenarios:
|
|
|
2
2
|
- name: "Быстрое решение по участию"
|
|
3
3
|
steps:
|
|
4
4
|
- user_input: "Привет, срочно нужна информация о конференции"
|
|
5
|
-
expected_keywords:
|
|
6
|
-
|
|
5
|
+
expected_keywords:
|
|
6
|
+
- ["привет", "здравствуйте", "добро пожаловать", "добрый день"]
|
|
7
|
+
- ["информация", "данные", "подробности", "детали"]
|
|
8
|
+
- ["конференци", "мероприятие", "событие"]
|
|
9
|
+
forbidden_keywords: ["не спешите", "позже", "не торопитесь"]
|
|
7
10
|
|
|
8
11
|
- user_input: "Завтра улетаю в командировку, успею зарегистрироваться?"
|
|
9
|
-
expected_keywords:
|
|
10
|
-
|
|
12
|
+
expected_keywords:
|
|
13
|
+
- ["успеете", "успеете зарегистрироваться", "все получится"]
|
|
14
|
+
- ["регистрация", "запись", "участие"]
|
|
15
|
+
- ["быстро", "срочно", "экспресс", "ускоренно"]
|
|
16
|
+
forbidden_keywords: ["поздно", "не успеете", "время вышло"]
|
|
11
17
|
|
|
12
18
|
- name: "Студент медвуза интересуется будущим"
|
|
13
19
|
steps:
|
|
14
20
|
- user_input: "Здравствуйте, я студент 5 курса медуниверситета"
|
|
15
|
-
expected_keywords:
|
|
16
|
-
|
|
21
|
+
expected_keywords:
|
|
22
|
+
- ["здравствуйте", "привет", "добро пожаловать"]
|
|
23
|
+
- ["студент", "обучающийся", "учащийся"]
|
|
24
|
+
- ["медицин", "медвуз", "университет", "медицинский"]
|
|
25
|
+
forbidden_keywords: ["рано для вас", "еще рано", "не подходит"]
|
|
17
26
|
|
|
18
27
|
- user_input: "Стоит ли уже сейчас думать о бизнесе в медицине?"
|
|
19
|
-
expected_keywords:
|
|
20
|
-
|
|
28
|
+
expected_keywords:
|
|
29
|
+
- ["стоит", "правильно", "хорошая идея", "отличный подход"]
|
|
30
|
+
- ["перспектив", "возможности", "будущее", "карьера"]
|
|
31
|
+
- ["бизнес", "предпринимательство", "дело"]
|
|
32
|
+
forbidden_keywords: ["рано думать", "не стоит", "пока рано"]
|
|
21
33
|
|
|
22
34
|
- user_input: "Есть ли льготы для студентов?"
|
|
23
|
-
expected_keywords:
|
|
24
|
-
|
|
35
|
+
expected_keywords:
|
|
36
|
+
- ["льготы", "скидки", "преференции", "льготные условия"]
|
|
37
|
+
- ["студент", "учащимся", "обучающимся"]
|
|
38
|
+
- ["специальные условия", "уменьшенная стоимость"]
|
|
39
|
+
forbidden_keywords: ["полная стоимость", "без скидок", "обычная цена"]
|
|
25
40
|
|
|
26
41
|
- name: "Иностранный врач в России"
|
|
27
42
|
steps:
|
|
28
43
|
- user_input: "Hello, I am foreign doctor working in Moscow"
|
|
29
|
-
expected_keywords:
|
|
30
|
-
|
|
44
|
+
expected_keywords:
|
|
45
|
+
- ["hello", "hi", "добро пожаловать", "привет"]
|
|
46
|
+
- ["foreign", "иностранный", "зарубежный"]
|
|
47
|
+
- ["moscow", "москва", "россия"]
|
|
48
|
+
forbidden_keywords: ["только на русском", "only russian", "русский язык"]
|
|
31
49
|
|
|
32
50
|
- user_input: "Is conference in English or Russian language?"
|
|
33
|
-
expected_keywords:
|
|
34
|
-
|
|
51
|
+
expected_keywords:
|
|
52
|
+
- ["language", "язык", "english", "английский"]
|
|
53
|
+
- ["translation", "перевод", "переводим", "синхронный перевод"]
|
|
54
|
+
- ["both", "оба языка", "многоязычный"]
|
|
55
|
+
forbidden_keywords: ["только русский", "only russian", "без перевода"]
|
|
35
56
|
|
|
36
57
|
- user_input: "I want to understand Russian medical business"
|
|
37
|
-
expected_keywords:
|
|
38
|
-
|
|
58
|
+
expected_keywords:
|
|
59
|
+
- ["business", "бизнес", "предпринимательство"]
|
|
60
|
+
- ["russian", "российский", "россии"]
|
|
61
|
+
- ["understand", "понять", "изучить", "разобраться"]
|
|
62
|
+
forbidden_keywords: ["сложно будет", "difficult", "не получится"]
|
|
39
63
|
|
|
40
64
|
- name: "Пенсионер-врач ищет активность"
|
|
41
65
|
steps:
|
|
42
66
|
- user_input: "Добрый день, я врач на пенсии уже 3 года"
|
|
43
|
-
expected_keywords:
|
|
44
|
-
|
|
67
|
+
expected_keywords:
|
|
68
|
+
- ["добрый день", "здравствуйте", "привет"]
|
|
69
|
+
- ["пенси", "пенсионер", "на пенсии", "отставной"]
|
|
70
|
+
- ["врач", "доктор", "медицинский работник"]
|
|
71
|
+
forbidden_keywords: ["не актуально", "не подходит", "слишком поздно"]
|
|
45
72
|
|
|
46
73
|
- user_input: "Хочу быть в курсе современных тенденций"
|
|
47
|
-
expected_keywords:
|
|
48
|
-
|
|
74
|
+
expected_keywords:
|
|
75
|
+
- ["современн", "актуальн", "новейш", "передов"]
|
|
76
|
+
- ["тенденци", "тренды", "направления", "развитие"]
|
|
77
|
+
- ["курс", "информирован", "знания", "обучение"]
|
|
78
|
+
forbidden_keywords: ["устарело", "не актуально", "пропустили время"]
|
|
49
79
|
|
|
50
80
|
- user_input: "Подходит ли конференция для неработающих врачей?"
|
|
51
|
-
expected_keywords:
|
|
52
|
-
|
|
81
|
+
expected_keywords:
|
|
82
|
+
- ["подходит", "идеально", "отлично", "рекомендуем"]
|
|
83
|
+
- ["для всех", "всем", "любому", "каждому"]
|
|
84
|
+
- ["врачей", "специалистов", "медиков", "докторов"]
|
|
85
|
+
forbidden_keywords: ["только для практикующих", "не подходит", "не рекомендуется"]
|
|
53
86
|
|
|
54
87
|
- name: "Технический директор медцентра"
|
|
55
88
|
steps:
|
|
56
89
|
- user_input: "Здравствуйте, я отвечаю за IT в медицинском центре"
|
|
57
|
-
expected_keywords:
|
|
58
|
-
|
|
90
|
+
expected_keywords:
|
|
91
|
+
- ["здравствуйте", "привет", "добро пожаловать"]
|
|
92
|
+
- ["it", "информационные технологии", "технологии", "айти"]
|
|
93
|
+
- ["медицинск", "медицинский", "клиника", "медцентр"]
|
|
94
|
+
forbidden_keywords: ["не ваша тема", "не подходит", "не актуально"]
|
|
59
95
|
|
|
60
96
|
- user_input: "Есть ли секции по цифровизации медицины?"
|
|
61
|
-
expected_keywords:
|
|
62
|
-
|
|
97
|
+
expected_keywords:
|
|
98
|
+
- ["цифровизация", "цифровые технологии", "информатизация"]
|
|
99
|
+
- ["технологи", "инновации", "современные решения"]
|
|
100
|
+
- ["it", "программирование", "автоматизация"]
|
|
101
|
+
forbidden_keywords: ["только бизнес", "без технологий", "не рассматриваем"]
|
|
63
102
|
|
|
64
103
|
- user_input: "Интересуют телемедицина и электронные карты"
|
|
65
|
-
expected_keywords:
|
|
66
|
-
|
|
104
|
+
expected_keywords:
|
|
105
|
+
- ["телемедицина", "удаленная медицина", "дистанционные консультации"]
|
|
106
|
+
- ["электронн", "цифровые", "компьютерные"]
|
|
107
|
+
- ["карты", "медицинские карты", "история болезни"]
|
|
108
|
+
forbidden_keywords: ["не рассматриваем", "не актуально", "не обсуждаем"]
|
|
109
|
+
|
|
110
|
+
- name: "Смешанный формат синонимов - демонстрация новых возможностей"
|
|
111
|
+
steps:
|
|
112
|
+
- user_input: "Здравствуйте, хочу узнать подробности о мероприятии"
|
|
113
|
+
expected_keywords:
|
|
114
|
+
- "здравствуйте" # Одиночное слово
|
|
115
|
+
- ["хочу", "интересуюсь", "планирую"] # Группа синонимов
|
|
116
|
+
- ["подробности", "детали", "информация"] # Еще одна группа синонимов
|
|
117
|
+
- "мероприятие" # Еще одно одиночное слово
|
|
118
|
+
forbidden_keywords: []
|
|
119
|
+
|
|
120
|
+
- user_input: "Когда будет конференция и сколько стоит участие?"
|
|
121
|
+
expected_keywords:
|
|
122
|
+
- ["когда", "дата", "время", "расписание"] # Группа синонимов
|
|
123
|
+
- "конференция" # Одиночное слово
|
|
124
|
+
- ["стоит", "цена", "стоимость", "сколько"] # Группа синонимов
|
|
125
|
+
- ["участие", "регистрация", "вход"] # Группа синонимов
|
|
126
|
+
forbidden_keywords: ["не знаю", "неизвестно", "не определились"]
|
|
127
|
+
|
|
128
|
+
- user_input: "Есть ли скидки для студентов и врачей?"
|
|
129
|
+
expected_keywords:
|
|
130
|
+
- ["скидки", "льготы", "преференции"] # Группа синонимов
|
|
131
|
+
- "студентов" # Одиночное слово
|
|
132
|
+
- ["врачей", "докторов", "медиков"] # Группа синонимов
|
|
133
|
+
forbidden_keywords: ["полная стоимость", "без скидок"]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
+
import re
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from aiogram import Router
|
|
6
7
|
from aiogram.filters import Command
|
|
@@ -13,7 +14,13 @@ from aiogram.types import (
|
|
|
13
14
|
from aiogram.utils.media_group import MediaGroupBuilder
|
|
14
15
|
|
|
15
16
|
from pathlib import Path
|
|
16
|
-
from ..core.decorators import
|
|
17
|
+
from ..core.decorators import (
|
|
18
|
+
execute_event_handler,
|
|
19
|
+
save_immediate_event,
|
|
20
|
+
schedule_task_for_later_with_db,
|
|
21
|
+
schedule_global_handler_for_later_with_db,
|
|
22
|
+
update_event_result
|
|
23
|
+
)
|
|
17
24
|
|
|
18
25
|
# Функция для получения глобальных переменных
|
|
19
26
|
def get_global_var(var_name):
|
|
@@ -24,6 +31,7 @@ def get_global_var(var_name):
|
|
|
24
31
|
|
|
25
32
|
logger = logging.getLogger(__name__)
|
|
26
33
|
|
|
34
|
+
|
|
27
35
|
# Создаем роутер для общих команд
|
|
28
36
|
utils_router = Router()
|
|
29
37
|
|
|
@@ -182,28 +190,109 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
182
190
|
logger.info(f" 📝 Тип: {event_type}")
|
|
183
191
|
logger.info(f" 📄 Данные: {event_info}")
|
|
184
192
|
|
|
185
|
-
#
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
# Вызываем зарегистрированный обработчик события или задачи
|
|
193
|
+
# Определяем категорию события и сохраняем в БД
|
|
194
|
+
event_id = None
|
|
195
|
+
should_execute_immediately = False
|
|
190
196
|
should_notify = False
|
|
197
|
+
|
|
191
198
|
try:
|
|
199
|
+
# Проверяем зарегистрированные обработчики через роутер-менеджер
|
|
200
|
+
from ..core.decorators import get_router_manager, _event_handlers, _scheduled_tasks, _global_handlers
|
|
201
|
+
|
|
202
|
+
# Получаем обработчики из роутеров или fallback к старым декораторам
|
|
203
|
+
router_manager = get_router_manager()
|
|
204
|
+
if router_manager:
|
|
205
|
+
event_handlers = router_manager.get_event_handlers()
|
|
206
|
+
scheduled_tasks = router_manager.get_scheduled_tasks()
|
|
207
|
+
global_handlers = router_manager.get_global_handlers()
|
|
208
|
+
logger.debug(f"🔍 RouterManager найден: {len(global_handlers)} глобальных обработчиков")
|
|
209
|
+
else:
|
|
210
|
+
event_handlers = _event_handlers
|
|
211
|
+
scheduled_tasks = _scheduled_tasks
|
|
212
|
+
global_handlers = _global_handlers
|
|
213
|
+
logger.warning("⚠️ RouterManager не найден, используем старые декораторы")
|
|
214
|
+
|
|
192
215
|
# Сначала пробуем как обычное событие
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
216
|
+
if event_type in event_handlers:
|
|
217
|
+
try:
|
|
218
|
+
logger.info(f" 🎯 Сохраняем как user_event: '{event_type}'")
|
|
219
|
+
event_id = await save_immediate_event(event_type, user_id, event_info, session_id)
|
|
220
|
+
should_execute_immediately = True
|
|
221
|
+
logger.info(f" 💾 Событие сохранено в БД: {event_id}")
|
|
222
|
+
except ValueError as e:
|
|
223
|
+
if "once_only=True" in str(e):
|
|
224
|
+
logger.info(f" 🔄 Событие '{event_type}' уже обрабатывалось, пропускаем")
|
|
225
|
+
continue
|
|
226
|
+
else:
|
|
227
|
+
raise
|
|
228
|
+
|
|
229
|
+
# Если не user_event, пробуем как запланированную задачу
|
|
230
|
+
elif event_type in scheduled_tasks:
|
|
231
|
+
try:
|
|
232
|
+
# Извлекаем время из event_info (AI должен передавать число секунд)
|
|
233
|
+
try:
|
|
234
|
+
delay_seconds = int(event_info)
|
|
235
|
+
except ValueError:
|
|
236
|
+
# Если не число, используем значение по умолчанию
|
|
237
|
+
delay_seconds = 3600
|
|
238
|
+
logger.warning(f" ⚠️ Не удалось извлечь время из '{event_info}', используем 3600с")
|
|
199
239
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
240
|
+
logger.info(f" ⏰ Сохраняем как scheduled_task: '{event_type}' через {delay_seconds}с")
|
|
241
|
+
result = await schedule_task_for_later_with_db(event_type, user_id, event_info, delay_seconds, session_id)
|
|
242
|
+
event_id = result['event_id']
|
|
243
|
+
should_notify = result.get('notify', False)
|
|
244
|
+
logger.info(f" 💾 Задача сохранена в БД: {event_id}")
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
if "once_only=True" in str(e):
|
|
248
|
+
logger.info(f" 🔄 Задача '{event_type}' уже запланирована, пропускаем")
|
|
249
|
+
continue
|
|
250
|
+
else:
|
|
251
|
+
logger.error(f" ❌ Ошибка планирования scheduled_task '{event_type}': {e}")
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
# Если не scheduled_task, пробуем как глобальный обработчик
|
|
255
|
+
elif event_type in global_handlers:
|
|
256
|
+
try:
|
|
257
|
+
# Извлекаем время из event_info (AI должен передавать число секунд)
|
|
258
|
+
try:
|
|
259
|
+
delay_seconds = int(event_info)
|
|
260
|
+
except ValueError:
|
|
261
|
+
# Если не число, используем значение по умолчанию
|
|
262
|
+
delay_seconds = 3600
|
|
263
|
+
logger.warning(f" ⚠️ Не удалось извлечь время из '{event_info}', используем 3600с")
|
|
264
|
+
|
|
265
|
+
logger.info(f" 🌍 Сохраняем как global_handler: '{event_type}' через {delay_seconds}с")
|
|
266
|
+
result = await schedule_global_handler_for_later_with_db(event_type, delay_seconds, event_info)
|
|
267
|
+
event_id = result['event_id']
|
|
268
|
+
should_notify = result.get('notify', False)
|
|
269
|
+
logger.info(f" 💾 Глобальное событие сохранено в БД: {event_id}")
|
|
270
|
+
|
|
271
|
+
except Exception as e:
|
|
272
|
+
if "once_only=True" in str(e):
|
|
273
|
+
logger.info(f" 🔄 Глобальное событие '{event_type}' уже запланировано, пропускаем")
|
|
274
|
+
continue
|
|
275
|
+
else:
|
|
276
|
+
logger.error(f" ❌ Ошибка планирования global_handler '{event_type}': {e}")
|
|
277
|
+
continue
|
|
278
|
+
|
|
279
|
+
else:
|
|
280
|
+
logger.warning(f" ⚠️ Обработчик '{event_type}' не найден среди зарегистрированных")
|
|
281
|
+
logger.debug(f" 🔍 Доступные обработчики:")
|
|
282
|
+
logger.debug(f" - event_handlers: {list(event_handlers.keys())}")
|
|
283
|
+
logger.debug(f" - scheduled_tasks: {list(scheduled_tasks.keys())}")
|
|
284
|
+
logger.debug(f" - global_handlers: {list(global_handlers.keys())}")
|
|
285
|
+
|
|
286
|
+
# Выполняем немедленные события
|
|
287
|
+
if should_execute_immediately and event_id:
|
|
288
|
+
try:
|
|
289
|
+
result = await execute_event_handler(event_type, user_id, event_info)
|
|
290
|
+
should_notify = result.get('notify', False)
|
|
291
|
+
await update_event_result(event_id, 'completed', result)
|
|
292
|
+
logger.info(f" ✅ Событие выполнено: {result}")
|
|
293
|
+
except Exception as e:
|
|
294
|
+
await update_event_result(event_id, 'failed', None, str(e))
|
|
295
|
+
logger.error(f" ❌ Ошибка выполнения события: {e}")
|
|
207
296
|
|
|
208
297
|
except ValueError as e:
|
|
209
298
|
logger.warning(f" ⚠️ Обработчик/задача не найдены: {e}")
|
|
@@ -312,34 +401,30 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
312
401
|
parse_mode = config.MESSAGE_PARSE_MODE if config.MESSAGE_PARSE_MODE != 'None' else None
|
|
313
402
|
logger.info(f" 🔧 Parse mode: {parse_mode}")
|
|
314
403
|
|
|
315
|
-
#
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if files_info:
|
|
341
|
-
final_text = final_text.strip() + "".join(files_info)
|
|
342
|
-
logger.info(f" ✨ Добавлена информация о {len(files_list)} файлах и {len(directories_list)} каталогах")
|
|
404
|
+
# Получаем user_id и импортируем supabase_client
|
|
405
|
+
user_id = message.from_user.id
|
|
406
|
+
supabase_client = get_global_var('supabase_client')
|
|
407
|
+
|
|
408
|
+
# Текст уже готов, используем как есть
|
|
409
|
+
final_text = text
|
|
410
|
+
|
|
411
|
+
# Работаем с переданными файлами и каталогами
|
|
412
|
+
logger.info(f" 📦 Передано файлов: {files_list}")
|
|
413
|
+
logger.info(f" 📂 Передано каталогов: {directories_list}")
|
|
414
|
+
|
|
415
|
+
# Получаем список уже отправленных файлов и каталогов
|
|
416
|
+
sent_files = await supabase_client.get_sent_files(user_id)
|
|
417
|
+
sent_directories = await supabase_client.get_sent_directories(user_id)
|
|
418
|
+
|
|
419
|
+
logger.info(f" 📋 Уже отправлено файлов: {sent_files}")
|
|
420
|
+
logger.info(f" 📋 Уже отправлено каталогов: {sent_directories}")
|
|
421
|
+
|
|
422
|
+
# Фильтруем файлы и каталоги, которые уже отправлялись
|
|
423
|
+
actual_files_list = [f for f in files_list if f not in sent_files]
|
|
424
|
+
actual_directories_list = [d for d in directories_list if str(d) not in sent_directories]
|
|
425
|
+
|
|
426
|
+
logger.info(f" 🆕 После фильтрации файлов: {actual_files_list}")
|
|
427
|
+
logger.info(f" 🆕 После фильтрации каталогов: {actual_directories_list}")
|
|
343
428
|
|
|
344
429
|
|
|
345
430
|
# Проверяем, что есть что отправлять
|
|
@@ -349,30 +434,36 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
349
434
|
final_text = "Ошибка формирования ответа. Попробуйте еще раз."
|
|
350
435
|
|
|
351
436
|
logger.info(f"📱 Подготовка сообщения: {len(final_text)} символов")
|
|
437
|
+
logger.info(f" 📦 Файлов для обработки: {actual_files_list}")
|
|
438
|
+
logger.info(f" 📂 Каталогов для обработки: {actual_directories_list}")
|
|
352
439
|
|
|
353
440
|
# Проверяем наличие файлов для отправки
|
|
354
|
-
if
|
|
441
|
+
if actual_files_list or actual_directories_list:
|
|
355
442
|
# Функция определения типа медиа по расширению
|
|
356
443
|
def get_media_type(file_path: str) -> str:
|
|
357
444
|
ext = Path(file_path).suffix.lower()
|
|
358
445
|
if ext in {'.jpg', '.jpeg', '.png'}:
|
|
359
446
|
return 'photo'
|
|
360
|
-
elif ext in {'.mp4'}:
|
|
447
|
+
elif ext in {'.mp4', '.mov'}:
|
|
361
448
|
return 'video'
|
|
362
449
|
else:
|
|
363
450
|
return 'document'
|
|
364
451
|
|
|
365
452
|
# Создаем списки для разных типов файлов
|
|
366
|
-
|
|
453
|
+
video_files = [] # для видео
|
|
454
|
+
photo_files = [] # для фото
|
|
367
455
|
document_files = [] # для документов
|
|
368
456
|
|
|
369
457
|
# Функция обработки файла
|
|
370
458
|
def process_file(file_path: Path, source: str = ""):
|
|
371
459
|
if file_path.is_file():
|
|
372
460
|
media_type = get_media_type(str(file_path))
|
|
373
|
-
if media_type
|
|
374
|
-
|
|
375
|
-
logger.info(f"
|
|
461
|
+
if media_type == 'video':
|
|
462
|
+
video_files.append(file_path)
|
|
463
|
+
logger.info(f" 🎥 Добавлено видео{f' из {source}' if source else ''}: {file_path.name}")
|
|
464
|
+
elif media_type == 'photo':
|
|
465
|
+
photo_files.append(file_path)
|
|
466
|
+
logger.info(f" 📸 Добавлено фото{f' из {source}' if source else ''}: {file_path.name}")
|
|
376
467
|
else:
|
|
377
468
|
document_files.append(file_path)
|
|
378
469
|
logger.info(f" 📄 Добавлен документ{f' из {source}' if source else ''}: {file_path.name}")
|
|
@@ -380,54 +471,58 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
380
471
|
logger.warning(f" ⚠️ Файл не найден: {file_path}")
|
|
381
472
|
|
|
382
473
|
# Обрабатываем прямые файлы
|
|
383
|
-
for file_name in
|
|
474
|
+
for file_name in actual_files_list:
|
|
384
475
|
try:
|
|
385
|
-
|
|
386
|
-
config = get_global_var('config')
|
|
387
|
-
bot_id = config.BOT_ID if config else "unknown"
|
|
388
|
-
file_path = Path(f"bots/{bot_id}/files/{file_name}")
|
|
389
|
-
process_file(file_path)
|
|
476
|
+
process_file(Path(f"files/{file_name}"))
|
|
390
477
|
except Exception as e:
|
|
391
478
|
logger.error(f" ❌ Ошибка обработки файла {file_name}: {e}")
|
|
392
479
|
|
|
393
480
|
# Обрабатываем файлы из каталогов
|
|
394
|
-
for dir_name in
|
|
395
|
-
|
|
396
|
-
config = get_global_var('config')
|
|
397
|
-
bot_id = config.BOT_ID if config else "unknown"
|
|
398
|
-
dir_path = Path(f"bots/{bot_id}/{dir_name}")
|
|
481
|
+
for dir_name in actual_directories_list:
|
|
482
|
+
dir_name = Path(dir_name)
|
|
399
483
|
try:
|
|
400
|
-
if
|
|
401
|
-
for file_path in
|
|
484
|
+
if dir_name.is_dir():
|
|
485
|
+
for file_path in dir_name.iterdir():
|
|
402
486
|
try:
|
|
403
|
-
process_file(file_path,
|
|
487
|
+
process_file(file_path, dir_name)
|
|
404
488
|
except Exception as e:
|
|
405
489
|
logger.error(f" ❌ Ошибка обработки файла {file_path}: {e}")
|
|
406
490
|
else:
|
|
407
|
-
logger.warning(f" ⚠️ Каталог не найден: {
|
|
491
|
+
logger.warning(f" ⚠️ Каталог не найден: {dir_name}")
|
|
408
492
|
except Exception as e:
|
|
409
|
-
logger.error(f" ❌ Ошибка обработки каталога {
|
|
493
|
+
logger.error(f" ❌ Ошибка обработки каталога {dir_name}: {e}")
|
|
494
|
+
|
|
495
|
+
# Списки для отслеживания реально отправленных файлов
|
|
496
|
+
sent_files_to_save = []
|
|
497
|
+
sent_dirs_to_save = []
|
|
410
498
|
|
|
411
|
-
# Отправляем
|
|
412
|
-
if
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if media_type == 'photo':
|
|
417
|
-
media_group.add_photo(media=FSInputFile(str(file_path)))
|
|
418
|
-
else: # video
|
|
419
|
-
media_group.add_video(media=FSInputFile(str(file_path)))
|
|
499
|
+
# 1. Отправляем видео (если есть)
|
|
500
|
+
if video_files:
|
|
501
|
+
video_group = MediaGroupBuilder()
|
|
502
|
+
for file_path in video_files:
|
|
503
|
+
video_group.add_video(media=FSInputFile(str(file_path)))
|
|
420
504
|
|
|
421
|
-
|
|
422
|
-
if
|
|
423
|
-
|
|
424
|
-
logger.info(f" ✅ Отправлено
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
505
|
+
videos = video_group.build()
|
|
506
|
+
if videos:
|
|
507
|
+
await message.answer_media_group(media=videos)
|
|
508
|
+
logger.info(f" ✅ Отправлено {len(videos)} видео")
|
|
509
|
+
|
|
510
|
+
# 2. Отправляем фото (если есть)
|
|
511
|
+
if photo_files:
|
|
512
|
+
photo_group = MediaGroupBuilder()
|
|
513
|
+
for file_path in photo_files:
|
|
514
|
+
photo_group.add_photo(media=FSInputFile(str(file_path)))
|
|
515
|
+
|
|
516
|
+
photos = photo_group.build()
|
|
517
|
+
if photos:
|
|
518
|
+
await message.answer_media_group(media=photos)
|
|
519
|
+
logger.info(f" ✅ Отправлено {len(photos)} фото")
|
|
520
|
+
|
|
521
|
+
# 3. Отправляем текст
|
|
522
|
+
result = await message.answer(final_text, parse_mode=parse_mode)
|
|
523
|
+
logger.info(f" ✅ Отправлен текст сообщения")
|
|
429
524
|
|
|
430
|
-
# Отправляем документы
|
|
525
|
+
# 4. Отправляем документы (если есть)
|
|
431
526
|
if document_files:
|
|
432
527
|
doc_group = MediaGroupBuilder()
|
|
433
528
|
for file_path in document_files:
|
|
@@ -436,7 +531,32 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
436
531
|
docs = doc_group.build()
|
|
437
532
|
if docs:
|
|
438
533
|
await message.answer_media_group(media=docs)
|
|
439
|
-
logger.info(f" ✅
|
|
534
|
+
logger.info(f" ✅ Отправлено {len(docs)} документов")
|
|
535
|
+
|
|
536
|
+
# 5. Собираем список реально отправленных файлов и каталогов
|
|
537
|
+
# Если были отправлены файлы из actual_files_list - сохраняем их
|
|
538
|
+
if video_files or photo_files or document_files:
|
|
539
|
+
# Сохраняем прямые файлы из actual_files_list (если отправлены)
|
|
540
|
+
sent_files_to_save.extend(actual_files_list)
|
|
541
|
+
logger.info(f" 📝 Добавляем в список для сохранения файлы: {actual_files_list}")
|
|
542
|
+
# Сохраняем каталоги из actual_directories_list (если отправлены файлы из них)
|
|
543
|
+
sent_dirs_to_save.extend([str(d) for d in actual_directories_list])
|
|
544
|
+
logger.info(f" 📝 Добавляем в список для сохранения каталоги: {actual_directories_list}")
|
|
545
|
+
|
|
546
|
+
# 6. Обновляем информацию в БД
|
|
547
|
+
if sent_files_to_save or sent_dirs_to_save:
|
|
548
|
+
try:
|
|
549
|
+
if sent_files_to_save:
|
|
550
|
+
logger.info(f" 💾 Сохраняем файлы в БД: {sent_files_to_save}")
|
|
551
|
+
await supabase_client.add_sent_files(user_id, sent_files_to_save)
|
|
552
|
+
if sent_dirs_to_save:
|
|
553
|
+
logger.info(f" 💾 Сохраняем каталоги в БД: {sent_dirs_to_save}")
|
|
554
|
+
await supabase_client.add_sent_directories(user_id, sent_dirs_to_save)
|
|
555
|
+
logger.info(f" ✅ Обновлена информация о отправленных файлах в БД")
|
|
556
|
+
except Exception as e:
|
|
557
|
+
logger.error(f" ❌ Ошибка обновления информации о файлах в БД: {e}")
|
|
558
|
+
else:
|
|
559
|
+
logger.info(f" ℹ️ Нет новых файлов для сохранения в БД")
|
|
440
560
|
|
|
441
561
|
return result
|
|
442
562
|
else:
|
|
@@ -446,6 +566,14 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
446
566
|
return result
|
|
447
567
|
|
|
448
568
|
except Exception as e:
|
|
569
|
+
# Проверяем, является ли ошибка блокировкой бота
|
|
570
|
+
if "Forbidden: bot was blocked by the user" in str(e):
|
|
571
|
+
logger.warning(f"🚫 Бот заблокирован пользователем {user_id}")
|
|
572
|
+
return None
|
|
573
|
+
elif "TelegramForbiddenError" in str(type(e).__name__):
|
|
574
|
+
logger.warning(f"🚫 Бот заблокирован пользователем {user_id}")
|
|
575
|
+
return None
|
|
576
|
+
|
|
449
577
|
logger.error(f"❌ ОШИБКА в send_message: {e}")
|
|
450
578
|
logger.exception("Полный стек ошибки send_message:")
|
|
451
579
|
|
|
@@ -456,6 +584,14 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
456
584
|
logger.info(f"✅ Запасное сообщение отправлено")
|
|
457
585
|
return result
|
|
458
586
|
except Exception as e2:
|
|
587
|
+
# Проверяем и здесь блокировку бота
|
|
588
|
+
if "Forbidden: bot was blocked by the user" in str(e2):
|
|
589
|
+
logger.warning(f"🚫 Бот заблокирован пользователем {user_id} (fallback)")
|
|
590
|
+
return None
|
|
591
|
+
elif "TelegramForbiddenError" in str(type(e2).__name__):
|
|
592
|
+
logger.warning(f"🚫 Бот заблокирован пользователем {user_id} (fallback)")
|
|
593
|
+
return None
|
|
594
|
+
|
|
459
595
|
logger.error(f"❌ Даже запасное сообщение не отправилось: {e2}")
|
|
460
596
|
raise
|
|
461
597
|
|