smart-bot-factory 0.1.4__py3-none-any.whl → 0.1.5__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 +138 -71
- smart_bot_factory/clients/__init__.py +33 -0
- 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/__init__.py +43 -22
- smart_bot_factory/core/bot_utils.py +251 -88
- smart_bot_factory/core/conversation_manager.py +542 -535
- smart_bot_factory/core/decorators.py +943 -230
- smart_bot_factory/core/globals.py +68 -0
- smart_bot_factory/core/message_sender.py +6 -6
- smart_bot_factory/core/router.py +172 -0
- smart_bot_factory/core/router_manager.py +165 -0
- smart_bot_factory/creation/__init__.py +1 -2
- smart_bot_factory/creation/bot_builder.py +116 -8
- smart_bot_factory/creation/bot_testing.py +74 -13
- smart_bot_factory/handlers/handlers.py +10 -2
- smart_bot_factory/integrations/__init__.py +1 -0
- smart_bot_factory/integrations/supabase_client.py +272 -2
- smart_bot_factory-0.1.5.dist-info/METADATA +466 -0
- {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.5.dist-info}/RECORD +25 -30
- smart_bot_factory/configs/growthmed-helper/env_example.txt +0 -1
- 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-0.1.4.dist-info/METADATA +0 -126
- {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.5.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.5.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.1.4.dist-info → smart_bot_factory-0.1.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -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,46 @@ def get_global_var(var_name):
|
|
|
24
31
|
|
|
25
32
|
logger = logging.getLogger(__name__)
|
|
26
33
|
|
|
34
|
+
def parse_time_expression(time_text: str) -> int:
|
|
35
|
+
"""
|
|
36
|
+
Парсит временные выражения и возвращает количество секунд
|
|
37
|
+
|
|
38
|
+
Поддерживаемые форматы:
|
|
39
|
+
- "30" -> 30 секунд (основной формат)
|
|
40
|
+
- "Запуск через 30 секунд" -> 30 секунд
|
|
41
|
+
- "через 5 минут" -> 300 секунд
|
|
42
|
+
- "через 1 час" -> 3600 секунд
|
|
43
|
+
- "через 2 часа" -> 7200 секунд
|
|
44
|
+
"""
|
|
45
|
+
# Сначала пробуем простое число (основной формат)
|
|
46
|
+
try:
|
|
47
|
+
return int(time_text)
|
|
48
|
+
except ValueError:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
# Убираем лишние символы и приводим к нижнему регистру
|
|
52
|
+
text = re.sub(r'[^\w\s\d]', '', time_text.lower())
|
|
53
|
+
|
|
54
|
+
# Ищем числа в тексте
|
|
55
|
+
numbers = re.findall(r'\d+', text)
|
|
56
|
+
if not numbers:
|
|
57
|
+
raise ValueError(f"Не найдено число в выражении: {time_text}")
|
|
58
|
+
|
|
59
|
+
number = int(numbers[0])
|
|
60
|
+
|
|
61
|
+
# Определяем единицу времени
|
|
62
|
+
if 'секунд' in text:
|
|
63
|
+
return number
|
|
64
|
+
elif 'минут' in text:
|
|
65
|
+
return number * 60
|
|
66
|
+
elif 'час' in text:
|
|
67
|
+
return number * 3600
|
|
68
|
+
elif 'день' in text or 'дней' in text:
|
|
69
|
+
return number * 86400
|
|
70
|
+
else:
|
|
71
|
+
# По умолчанию считаем секундами
|
|
72
|
+
return number
|
|
73
|
+
|
|
27
74
|
# Создаем роутер для общих команд
|
|
28
75
|
utils_router = Router()
|
|
29
76
|
|
|
@@ -182,28 +229,97 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
182
229
|
logger.info(f" 📝 Тип: {event_type}")
|
|
183
230
|
logger.info(f" 📄 Данные: {event_info}")
|
|
184
231
|
|
|
185
|
-
#
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
# Вызываем зарегистрированный обработчик события или задачи
|
|
232
|
+
# Определяем категорию события и сохраняем в БД
|
|
233
|
+
event_id = None
|
|
234
|
+
should_execute_immediately = False
|
|
190
235
|
should_notify = False
|
|
236
|
+
|
|
191
237
|
try:
|
|
238
|
+
# Проверяем зарегистрированные обработчики через роутер-менеджер
|
|
239
|
+
from ..core.decorators import get_router_manager, _event_handlers, _scheduled_tasks, _global_handlers
|
|
240
|
+
|
|
241
|
+
# Получаем обработчики из роутеров или fallback к старым декораторам
|
|
242
|
+
router_manager = get_router_manager()
|
|
243
|
+
if router_manager:
|
|
244
|
+
event_handlers = router_manager.get_event_handlers()
|
|
245
|
+
scheduled_tasks = router_manager.get_scheduled_tasks()
|
|
246
|
+
global_handlers = router_manager.get_global_handlers()
|
|
247
|
+
else:
|
|
248
|
+
event_handlers = _event_handlers
|
|
249
|
+
scheduled_tasks = _scheduled_tasks
|
|
250
|
+
global_handlers = _global_handlers
|
|
251
|
+
|
|
192
252
|
# Сначала пробуем как обычное событие
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
253
|
+
if event_type in event_handlers:
|
|
254
|
+
try:
|
|
255
|
+
logger.info(f" 🎯 Сохраняем как user_event: '{event_type}'")
|
|
256
|
+
event_id = await save_immediate_event(event_type, user_id, event_info, session_id)
|
|
257
|
+
should_execute_immediately = True
|
|
258
|
+
logger.info(f" 💾 Событие сохранено в БД: {event_id}")
|
|
259
|
+
except ValueError as e:
|
|
260
|
+
if "once_only=True" in str(e):
|
|
261
|
+
logger.info(f" 🔄 Событие '{event_type}' уже обрабатывалось, пропускаем")
|
|
262
|
+
continue
|
|
263
|
+
else:
|
|
264
|
+
raise
|
|
265
|
+
|
|
266
|
+
# Если не user_event, пробуем как запланированную задачу
|
|
267
|
+
elif event_type in scheduled_tasks:
|
|
268
|
+
try:
|
|
269
|
+
delay_seconds = parse_time_expression(event_info)
|
|
270
|
+
logger.info(f" ⏰ Сохраняем как scheduled_task: '{event_type}' через {delay_seconds}с")
|
|
271
|
+
result = await schedule_task_for_later_with_db(event_type, user_id, event_info, delay_seconds, session_id)
|
|
272
|
+
event_id = result['event_id']
|
|
273
|
+
should_notify = result.get('notify', False)
|
|
274
|
+
logger.info(f" 💾 Задача сохранена в БД: {event_id}")
|
|
199
275
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
276
|
+
except ValueError as e:
|
|
277
|
+
if "once_only=True" in str(e):
|
|
278
|
+
logger.info(f" 🔄 Задача '{event_type}' уже запланирована, пропускаем")
|
|
279
|
+
continue
|
|
280
|
+
else:
|
|
281
|
+
logger.error(f" ❌ Ошибка парсинга времени для scheduled_task '{event_type}': {e}")
|
|
282
|
+
# Fallback - планируем через 1 час
|
|
283
|
+
result = await schedule_task_for_later_with_db(event_type, user_id, event_info, 3600, session_id)
|
|
284
|
+
event_id = result['event_id']
|
|
285
|
+
should_notify = result.get('notify', False)
|
|
286
|
+
logger.info(f" ⏰ Задача сохранена с fallback временем (1 час): {event_id}")
|
|
287
|
+
|
|
288
|
+
# Если не scheduled_task, пробуем как глобальный обработчик
|
|
289
|
+
elif event_type in global_handlers:
|
|
290
|
+
try:
|
|
291
|
+
delay_seconds = parse_time_expression(event_info)
|
|
292
|
+
logger.info(f" 🌍 Сохраняем как global_handler: '{event_type}' через {delay_seconds}с")
|
|
293
|
+
result = await schedule_global_handler_for_later_with_db(event_type, delay_seconds, event_info)
|
|
294
|
+
event_id = result['event_id']
|
|
295
|
+
should_notify = result.get('notify', False)
|
|
296
|
+
logger.info(f" 💾 Глобальное событие сохранено в БД: {event_id}")
|
|
297
|
+
|
|
298
|
+
except ValueError as e:
|
|
299
|
+
if "once_only=True" in str(e):
|
|
300
|
+
logger.info(f" 🔄 Глобальное событие '{event_type}' уже запланировано, пропускаем")
|
|
301
|
+
continue
|
|
302
|
+
else:
|
|
303
|
+
logger.error(f" ❌ Ошибка парсинга времени для global_handler '{event_type}': {e}")
|
|
304
|
+
# Fallback - планируем через 1 час
|
|
305
|
+
result = await schedule_global_handler_for_later_with_db(event_type, 3600, event_info)
|
|
306
|
+
event_id = result['event_id']
|
|
307
|
+
should_notify = result.get('notify', False)
|
|
308
|
+
logger.info(f" 🌍 Глобальное событие сохранено с fallback временем (1 час): {event_id}")
|
|
309
|
+
|
|
310
|
+
else:
|
|
311
|
+
logger.warning(f" ⚠️ Обработчик '{event_type}' не найден среди зарегистрированных")
|
|
312
|
+
|
|
313
|
+
# Выполняем немедленные события
|
|
314
|
+
if should_execute_immediately and event_id:
|
|
315
|
+
try:
|
|
316
|
+
result = await execute_event_handler(event_type, user_id, event_info)
|
|
317
|
+
should_notify = result.get('notify', False)
|
|
318
|
+
await update_event_result(event_id, 'completed', result)
|
|
319
|
+
logger.info(f" ✅ Событие выполнено: {result}")
|
|
320
|
+
except Exception as e:
|
|
321
|
+
await update_event_result(event_id, 'failed', None, str(e))
|
|
322
|
+
logger.error(f" ❌ Ошибка выполнения события: {e}")
|
|
207
323
|
|
|
208
324
|
except ValueError as e:
|
|
209
325
|
logger.warning(f" ⚠️ Обработчик/задача не найдены: {e}")
|
|
@@ -312,34 +428,30 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
312
428
|
parse_mode = config.MESSAGE_PARSE_MODE if config.MESSAGE_PARSE_MODE != 'None' else None
|
|
313
429
|
logger.info(f" 🔧 Parse mode: {parse_mode}")
|
|
314
430
|
|
|
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)} каталогах")
|
|
431
|
+
# Получаем user_id и импортируем supabase_client
|
|
432
|
+
user_id = message.from_user.id
|
|
433
|
+
supabase_client = get_global_var('supabase_client')
|
|
434
|
+
|
|
435
|
+
# Текст уже готов, используем как есть
|
|
436
|
+
final_text = text
|
|
437
|
+
|
|
438
|
+
# Работаем с переданными файлами и каталогами
|
|
439
|
+
logger.info(f" 📦 Передано файлов: {files_list}")
|
|
440
|
+
logger.info(f" 📂 Передано каталогов: {directories_list}")
|
|
441
|
+
|
|
442
|
+
# Получаем список уже отправленных файлов и каталогов
|
|
443
|
+
sent_files = await supabase_client.get_sent_files(user_id)
|
|
444
|
+
sent_directories = await supabase_client.get_sent_directories(user_id)
|
|
445
|
+
|
|
446
|
+
logger.info(f" 📋 Уже отправлено файлов: {sent_files}")
|
|
447
|
+
logger.info(f" 📋 Уже отправлено каталогов: {sent_directories}")
|
|
448
|
+
|
|
449
|
+
# Фильтруем файлы и каталоги, которые уже отправлялись
|
|
450
|
+
actual_files_list = [f for f in files_list if f not in sent_files]
|
|
451
|
+
actual_directories_list = [d for d in directories_list if str(d) not in sent_directories]
|
|
452
|
+
|
|
453
|
+
logger.info(f" 🆕 После фильтрации файлов: {actual_files_list}")
|
|
454
|
+
logger.info(f" 🆕 После фильтрации каталогов: {actual_directories_list}")
|
|
343
455
|
|
|
344
456
|
|
|
345
457
|
# Проверяем, что есть что отправлять
|
|
@@ -349,30 +461,36 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
349
461
|
final_text = "Ошибка формирования ответа. Попробуйте еще раз."
|
|
350
462
|
|
|
351
463
|
logger.info(f"📱 Подготовка сообщения: {len(final_text)} символов")
|
|
464
|
+
logger.info(f" 📦 Файлов для обработки: {actual_files_list}")
|
|
465
|
+
logger.info(f" 📂 Каталогов для обработки: {actual_directories_list}")
|
|
352
466
|
|
|
353
467
|
# Проверяем наличие файлов для отправки
|
|
354
|
-
if
|
|
468
|
+
if actual_files_list or actual_directories_list:
|
|
355
469
|
# Функция определения типа медиа по расширению
|
|
356
470
|
def get_media_type(file_path: str) -> str:
|
|
357
471
|
ext = Path(file_path).suffix.lower()
|
|
358
472
|
if ext in {'.jpg', '.jpeg', '.png'}:
|
|
359
473
|
return 'photo'
|
|
360
|
-
elif ext in {'.mp4'}:
|
|
474
|
+
elif ext in {'.mp4', '.mov'}:
|
|
361
475
|
return 'video'
|
|
362
476
|
else:
|
|
363
477
|
return 'document'
|
|
364
478
|
|
|
365
479
|
# Создаем списки для разных типов файлов
|
|
366
|
-
|
|
480
|
+
video_files = [] # для видео
|
|
481
|
+
photo_files = [] # для фото
|
|
367
482
|
document_files = [] # для документов
|
|
368
483
|
|
|
369
484
|
# Функция обработки файла
|
|
370
485
|
def process_file(file_path: Path, source: str = ""):
|
|
371
486
|
if file_path.is_file():
|
|
372
487
|
media_type = get_media_type(str(file_path))
|
|
373
|
-
if media_type
|
|
374
|
-
|
|
375
|
-
logger.info(f"
|
|
488
|
+
if media_type == 'video':
|
|
489
|
+
video_files.append(file_path)
|
|
490
|
+
logger.info(f" 🎥 Добавлено видео{f' из {source}' if source else ''}: {file_path.name}")
|
|
491
|
+
elif media_type == 'photo':
|
|
492
|
+
photo_files.append(file_path)
|
|
493
|
+
logger.info(f" 📸 Добавлено фото{f' из {source}' if source else ''}: {file_path.name}")
|
|
376
494
|
else:
|
|
377
495
|
document_files.append(file_path)
|
|
378
496
|
logger.info(f" 📄 Добавлен документ{f' из {source}' if source else ''}: {file_path.name}")
|
|
@@ -380,54 +498,58 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
380
498
|
logger.warning(f" ⚠️ Файл не найден: {file_path}")
|
|
381
499
|
|
|
382
500
|
# Обрабатываем прямые файлы
|
|
383
|
-
for file_name in
|
|
501
|
+
for file_name in actual_files_list:
|
|
384
502
|
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)
|
|
503
|
+
process_file(Path(f"files/{file_name}"))
|
|
390
504
|
except Exception as e:
|
|
391
505
|
logger.error(f" ❌ Ошибка обработки файла {file_name}: {e}")
|
|
392
506
|
|
|
393
507
|
# Обрабатываем файлы из каталогов
|
|
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}")
|
|
508
|
+
for dir_name in actual_directories_list:
|
|
509
|
+
dir_name = Path(dir_name)
|
|
399
510
|
try:
|
|
400
|
-
if
|
|
401
|
-
for file_path in
|
|
511
|
+
if dir_name.is_dir():
|
|
512
|
+
for file_path in dir_name.iterdir():
|
|
402
513
|
try:
|
|
403
|
-
process_file(file_path,
|
|
514
|
+
process_file(file_path, dir_name)
|
|
404
515
|
except Exception as e:
|
|
405
516
|
logger.error(f" ❌ Ошибка обработки файла {file_path}: {e}")
|
|
406
517
|
else:
|
|
407
|
-
logger.warning(f" ⚠️ Каталог не найден: {
|
|
518
|
+
logger.warning(f" ⚠️ Каталог не найден: {dir_name}")
|
|
408
519
|
except Exception as e:
|
|
409
|
-
logger.error(f" ❌ Ошибка обработки каталога {
|
|
520
|
+
logger.error(f" ❌ Ошибка обработки каталога {dir_name}: {e}")
|
|
410
521
|
|
|
411
|
-
#
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
522
|
+
# Списки для отслеживания реально отправленных файлов
|
|
523
|
+
sent_files_to_save = []
|
|
524
|
+
sent_dirs_to_save = []
|
|
525
|
+
|
|
526
|
+
# 1. Отправляем видео (если есть)
|
|
527
|
+
if video_files:
|
|
528
|
+
video_group = MediaGroupBuilder()
|
|
529
|
+
for file_path in video_files:
|
|
530
|
+
video_group.add_video(media=FSInputFile(str(file_path)))
|
|
420
531
|
|
|
421
|
-
|
|
422
|
-
if
|
|
423
|
-
|
|
424
|
-
logger.info(f" ✅ Отправлено
|
|
425
|
-
else:
|
|
426
|
-
# Если нет медиа, отправляем просто текст
|
|
427
|
-
result = await message.answer(final_text, parse_mode=parse_mode, **kwargs)
|
|
428
|
-
logger.info(f" ✅ Отправлен текст сообщения")
|
|
532
|
+
videos = video_group.build()
|
|
533
|
+
if videos:
|
|
534
|
+
await message.answer_media_group(media=videos)
|
|
535
|
+
logger.info(f" ✅ Отправлено {len(videos)} видео")
|
|
429
536
|
|
|
430
|
-
# Отправляем
|
|
537
|
+
# 2. Отправляем фото (если есть)
|
|
538
|
+
if photo_files:
|
|
539
|
+
photo_group = MediaGroupBuilder()
|
|
540
|
+
for file_path in photo_files:
|
|
541
|
+
photo_group.add_photo(media=FSInputFile(str(file_path)))
|
|
542
|
+
|
|
543
|
+
photos = photo_group.build()
|
|
544
|
+
if photos:
|
|
545
|
+
await message.answer_media_group(media=photos)
|
|
546
|
+
logger.info(f" ✅ Отправлено {len(photos)} фото")
|
|
547
|
+
|
|
548
|
+
# 3. Отправляем текст
|
|
549
|
+
result = await message.answer(final_text, parse_mode=parse_mode)
|
|
550
|
+
logger.info(f" ✅ Отправлен текст сообщения")
|
|
551
|
+
|
|
552
|
+
# 4. Отправляем документы (если есть)
|
|
431
553
|
if document_files:
|
|
432
554
|
doc_group = MediaGroupBuilder()
|
|
433
555
|
for file_path in document_files:
|
|
@@ -436,7 +558,32 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
436
558
|
docs = doc_group.build()
|
|
437
559
|
if docs:
|
|
438
560
|
await message.answer_media_group(media=docs)
|
|
439
|
-
logger.info(f" ✅
|
|
561
|
+
logger.info(f" ✅ Отправлено {len(docs)} документов")
|
|
562
|
+
|
|
563
|
+
# 5. Собираем список реально отправленных файлов и каталогов
|
|
564
|
+
# Если были отправлены файлы из actual_files_list - сохраняем их
|
|
565
|
+
if video_files or photo_files or document_files:
|
|
566
|
+
# Сохраняем прямые файлы из actual_files_list (если отправлены)
|
|
567
|
+
sent_files_to_save.extend(actual_files_list)
|
|
568
|
+
logger.info(f" 📝 Добавляем в список для сохранения файлы: {actual_files_list}")
|
|
569
|
+
# Сохраняем каталоги из actual_directories_list (если отправлены файлы из них)
|
|
570
|
+
sent_dirs_to_save.extend([str(d) for d in actual_directories_list])
|
|
571
|
+
logger.info(f" 📝 Добавляем в список для сохранения каталоги: {actual_directories_list}")
|
|
572
|
+
|
|
573
|
+
# 6. Обновляем информацию в БД
|
|
574
|
+
if sent_files_to_save or sent_dirs_to_save:
|
|
575
|
+
try:
|
|
576
|
+
if sent_files_to_save:
|
|
577
|
+
logger.info(f" 💾 Сохраняем файлы в БД: {sent_files_to_save}")
|
|
578
|
+
await supabase_client.add_sent_files(user_id, sent_files_to_save)
|
|
579
|
+
if sent_dirs_to_save:
|
|
580
|
+
logger.info(f" 💾 Сохраняем каталоги в БД: {sent_dirs_to_save}")
|
|
581
|
+
await supabase_client.add_sent_directories(user_id, sent_dirs_to_save)
|
|
582
|
+
logger.info(f" ✅ Обновлена информация о отправленных файлах в БД")
|
|
583
|
+
except Exception as e:
|
|
584
|
+
logger.error(f" ❌ Ошибка обновления информации о файлах в БД: {e}")
|
|
585
|
+
else:
|
|
586
|
+
logger.info(f" ℹ️ Нет новых файлов для сохранения в БД")
|
|
440
587
|
|
|
441
588
|
return result
|
|
442
589
|
else:
|
|
@@ -446,6 +593,14 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
446
593
|
return result
|
|
447
594
|
|
|
448
595
|
except Exception as e:
|
|
596
|
+
# Проверяем, является ли ошибка блокировкой бота
|
|
597
|
+
if "Forbidden: bot was blocked by the user" in str(e):
|
|
598
|
+
logger.warning(f"🚫 Бот заблокирован пользователем {user_id}")
|
|
599
|
+
return None
|
|
600
|
+
elif "TelegramForbiddenError" in str(type(e).__name__):
|
|
601
|
+
logger.warning(f"🚫 Бот заблокирован пользователем {user_id}")
|
|
602
|
+
return None
|
|
603
|
+
|
|
449
604
|
logger.error(f"❌ ОШИБКА в send_message: {e}")
|
|
450
605
|
logger.exception("Полный стек ошибки send_message:")
|
|
451
606
|
|
|
@@ -456,6 +611,14 @@ async def send_message(message: Message, text: str, files_list: list = [], direc
|
|
|
456
611
|
logger.info(f"✅ Запасное сообщение отправлено")
|
|
457
612
|
return result
|
|
458
613
|
except Exception as e2:
|
|
614
|
+
# Проверяем и здесь блокировку бота
|
|
615
|
+
if "Forbidden: bot was blocked by the user" in str(e2):
|
|
616
|
+
logger.warning(f"🚫 Бот заблокирован пользователем {user_id} (fallback)")
|
|
617
|
+
return None
|
|
618
|
+
elif "TelegramForbiddenError" in str(type(e2).__name__):
|
|
619
|
+
logger.warning(f"🚫 Бот заблокирован пользователем {user_id} (fallback)")
|
|
620
|
+
return None
|
|
621
|
+
|
|
459
622
|
logger.error(f"❌ Даже запасное сообщение не отправилось: {e2}")
|
|
460
623
|
raise
|
|
461
624
|
|