smart-bot-factory 0.3.6__py3-none-any.whl → 0.3.8__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/admin/__init__.py +7 -7
- smart_bot_factory/admin/admin_events.py +483 -383
- smart_bot_factory/admin/admin_logic.py +234 -158
- smart_bot_factory/admin/admin_manager.py +68 -53
- smart_bot_factory/admin/admin_tester.py +46 -40
- smart_bot_factory/admin/timeout_checker.py +201 -153
- smart_bot_factory/aiogram_calendar/__init__.py +11 -3
- smart_bot_factory/aiogram_calendar/common.py +12 -18
- smart_bot_factory/aiogram_calendar/dialog_calendar.py +126 -64
- smart_bot_factory/aiogram_calendar/schemas.py +49 -28
- smart_bot_factory/aiogram_calendar/simple_calendar.py +94 -50
- smart_bot_factory/analytics/analytics_manager.py +414 -392
- smart_bot_factory/cli.py +204 -148
- smart_bot_factory/config.py +123 -102
- smart_bot_factory/core/bot_utils.py +480 -324
- smart_bot_factory/core/conversation_manager.py +287 -200
- smart_bot_factory/core/decorators.py +1145 -739
- smart_bot_factory/core/message_sender.py +287 -266
- smart_bot_factory/core/router.py +170 -100
- smart_bot_factory/core/router_manager.py +121 -83
- smart_bot_factory/core/states.py +4 -3
- smart_bot_factory/creation/__init__.py +1 -1
- smart_bot_factory/creation/bot_builder.py +320 -242
- smart_bot_factory/creation/bot_testing.py +440 -365
- smart_bot_factory/dashboard/__init__.py +1 -3
- smart_bot_factory/event/__init__.py +2 -7
- smart_bot_factory/handlers/handlers.py +682 -466
- smart_bot_factory/integrations/openai_client.py +218 -168
- smart_bot_factory/integrations/supabase_client.py +928 -637
- smart_bot_factory/message/__init__.py +18 -22
- smart_bot_factory/router/__init__.py +2 -2
- smart_bot_factory/setup_checker.py +162 -126
- smart_bot_factory/supabase/__init__.py +1 -1
- smart_bot_factory/supabase/client.py +631 -515
- smart_bot_factory/utils/__init__.py +2 -3
- smart_bot_factory/utils/debug_routing.py +38 -27
- smart_bot_factory/utils/prompt_loader.py +153 -120
- smart_bot_factory/utils/user_prompt_loader.py +55 -56
- smart_bot_factory/utm_link_generator.py +123 -116
- {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/METADATA +3 -1
- smart_bot_factory-0.3.8.dist-info/RECORD +59 -0
- smart_bot_factory-0.3.6.dist-info/RECORD +0 -59
- {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.3.6.dist-info → smart_bot_factory-0.3.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,141 +1,156 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Dict, List, Set
|
|
3
|
+
|
|
3
4
|
from aiogram.types import User
|
|
4
5
|
|
|
5
6
|
logger = logging.getLogger(__name__)
|
|
6
7
|
|
|
8
|
+
|
|
7
9
|
class AdminManager:
|
|
8
10
|
"""Управление администраторами бота"""
|
|
9
|
-
|
|
11
|
+
|
|
10
12
|
def __init__(self, config, supabase_client):
|
|
11
13
|
self.config = config
|
|
12
14
|
self.supabase = supabase_client
|
|
13
15
|
self.admin_ids: Set[int] = set(config.ADMIN_TELEGRAM_IDS)
|
|
14
16
|
self.admin_modes: Dict[int, bool] = {} # admin_id -> is_in_admin_mode
|
|
15
|
-
|
|
17
|
+
|
|
16
18
|
logger.info(f"Инициализирован менеджер админов: {len(self.admin_ids)} админов")
|
|
17
|
-
|
|
19
|
+
|
|
18
20
|
async def sync_admins_from_config(self):
|
|
19
21
|
"""Синхронизирует админов из конфига с базой данных"""
|
|
20
22
|
if not self.admin_ids:
|
|
21
23
|
logger.warning("Нет админов в конфигурации")
|
|
22
24
|
return
|
|
23
|
-
|
|
25
|
+
|
|
24
26
|
try:
|
|
25
27
|
for admin_id in self.admin_ids:
|
|
26
|
-
await self.supabase.sync_admin(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
await self.supabase.sync_admin(
|
|
29
|
+
{
|
|
30
|
+
"telegram_id": admin_id,
|
|
31
|
+
"username": None, # будет обновлено при первом сообщении
|
|
32
|
+
"first_name": None,
|
|
33
|
+
"last_name": None,
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
33
37
|
# Устанавливаем режим администратора по умолчанию
|
|
34
38
|
if admin_id not in self.admin_modes:
|
|
35
39
|
self.admin_modes[admin_id] = True
|
|
36
|
-
|
|
40
|
+
|
|
37
41
|
logger.info(f"Синхронизированы админы: {self.admin_ids}")
|
|
38
|
-
|
|
42
|
+
|
|
39
43
|
except Exception as e:
|
|
40
44
|
logger.error(f"Ошибка синхронизации админов: {e}")
|
|
41
45
|
raise
|
|
42
|
-
|
|
46
|
+
|
|
43
47
|
async def update_admin_info(self, user: User):
|
|
44
48
|
"""Обновляет информацию об админе"""
|
|
45
49
|
if not self.is_admin(user.id):
|
|
46
50
|
return
|
|
47
|
-
|
|
51
|
+
|
|
48
52
|
try:
|
|
49
|
-
await self.supabase.sync_admin(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
await self.supabase.sync_admin(
|
|
54
|
+
{
|
|
55
|
+
"telegram_id": user.id,
|
|
56
|
+
"username": user.username,
|
|
57
|
+
"first_name": user.first_name,
|
|
58
|
+
"last_name": user.last_name,
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
56
62
|
except Exception as e:
|
|
57
63
|
logger.error(f"Ошибка обновления информации админа {user.id}: {e}")
|
|
58
|
-
|
|
64
|
+
|
|
59
65
|
def is_admin(self, telegram_id: int) -> bool:
|
|
60
66
|
"""Проверяет, является ли пользователь админом"""
|
|
61
67
|
return telegram_id in self.admin_ids
|
|
62
|
-
|
|
68
|
+
|
|
63
69
|
def is_in_admin_mode(self, telegram_id: int) -> bool:
|
|
64
70
|
"""Проверяет, находится ли админ в режиме администратора"""
|
|
65
71
|
if not self.is_admin(telegram_id):
|
|
66
72
|
return False
|
|
67
73
|
return self.admin_modes.get(telegram_id, True)
|
|
68
|
-
|
|
74
|
+
|
|
69
75
|
def toggle_admin_mode(self, telegram_id: int) -> bool:
|
|
70
76
|
"""Переключает режим админа. Возвращает новое состояние"""
|
|
71
77
|
if not self.is_admin(telegram_id):
|
|
72
78
|
return False
|
|
73
|
-
|
|
79
|
+
|
|
74
80
|
current_mode = self.admin_modes.get(telegram_id, True)
|
|
75
81
|
new_mode = not current_mode
|
|
76
82
|
self.admin_modes[telegram_id] = new_mode
|
|
77
|
-
|
|
78
|
-
logger.info(
|
|
83
|
+
|
|
84
|
+
logger.info(
|
|
85
|
+
f"Админ {telegram_id} переключен в режим: {'администратор' if new_mode else 'пользователь'}"
|
|
86
|
+
)
|
|
79
87
|
return new_mode
|
|
80
|
-
|
|
88
|
+
|
|
81
89
|
def set_admin_mode(self, telegram_id: int, is_admin_mode: bool):
|
|
82
90
|
"""Устанавливает режим админа"""
|
|
83
91
|
if not self.is_admin(telegram_id):
|
|
84
92
|
return
|
|
85
|
-
|
|
93
|
+
|
|
86
94
|
self.admin_modes[telegram_id] = is_admin_mode
|
|
87
|
-
logger.info(
|
|
88
|
-
|
|
95
|
+
logger.info(
|
|
96
|
+
f"Режим админа {telegram_id} установлен: {'администратор' if is_admin_mode else 'пользователь'}"
|
|
97
|
+
)
|
|
98
|
+
|
|
89
99
|
async def get_active_admins(self) -> List[int]:
|
|
90
100
|
"""Возвращает список активных админов в режиме администратора"""
|
|
91
|
-
return [
|
|
92
|
-
|
|
101
|
+
return [
|
|
102
|
+
admin_id for admin_id in self.admin_ids if self.is_in_admin_mode(admin_id)
|
|
103
|
+
]
|
|
104
|
+
|
|
93
105
|
def get_admin_mode_text(self, telegram_id: int) -> str:
|
|
94
106
|
"""Возвращает текстовое описание режима админа"""
|
|
95
107
|
if not self.is_admin(telegram_id):
|
|
96
108
|
return "Не администратор"
|
|
97
|
-
|
|
109
|
+
|
|
98
110
|
if self.is_in_admin_mode(telegram_id):
|
|
99
111
|
return "👑 Режим администратора"
|
|
100
112
|
else:
|
|
101
113
|
return "👤 Режим пользователя"
|
|
102
|
-
|
|
114
|
+
|
|
103
115
|
def format_admin_status(self, telegram_id: int) -> str:
|
|
104
116
|
"""Форматирует статус админа для отображения"""
|
|
105
117
|
if not self.is_admin(telegram_id):
|
|
106
118
|
return ""
|
|
107
|
-
|
|
119
|
+
|
|
108
120
|
mode = "👑 АДМИН" if self.is_in_admin_mode(telegram_id) else "👤 ПОЛЬЗ"
|
|
109
121
|
return f"[{mode}]"
|
|
110
|
-
|
|
122
|
+
|
|
111
123
|
async def notify_admins(self, message: str, exclude_admin: int = None):
|
|
112
124
|
"""Отправляет уведомление всем активным админам"""
|
|
113
|
-
from main import
|
|
114
|
-
|
|
125
|
+
from main import \
|
|
126
|
+
bot # Импорт здесь чтобы избежать циклических импортов
|
|
127
|
+
|
|
115
128
|
active_admins = await self.get_active_admins()
|
|
116
|
-
|
|
129
|
+
|
|
117
130
|
if exclude_admin:
|
|
118
131
|
active_admins = [aid for aid in active_admins if aid != exclude_admin]
|
|
119
|
-
|
|
132
|
+
|
|
120
133
|
sent_count = 0
|
|
121
134
|
for admin_id in active_admins:
|
|
122
135
|
try:
|
|
123
|
-
await bot.send_message(admin_id, message, parse_mode=
|
|
136
|
+
await bot.send_message(admin_id, message, parse_mode="Markdown")
|
|
124
137
|
sent_count += 1
|
|
125
138
|
except Exception as e:
|
|
126
139
|
logger.error(f"Ошибка отправки уведомления админу {admin_id}: {e}")
|
|
127
|
-
|
|
140
|
+
|
|
128
141
|
logger.info(f"Уведомление отправлено {sent_count} админам")
|
|
129
142
|
return sent_count
|
|
130
|
-
|
|
143
|
+
|
|
131
144
|
def get_stats(self) -> Dict[str, any]:
|
|
132
145
|
"""Возвращает статистику по админам"""
|
|
133
146
|
total_admins = len(self.admin_ids)
|
|
134
|
-
active_admins = len(
|
|
135
|
-
|
|
147
|
+
active_admins = len(
|
|
148
|
+
[aid for aid in self.admin_ids if self.is_in_admin_mode(aid)]
|
|
149
|
+
)
|
|
150
|
+
|
|
136
151
|
return {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
152
|
+
"total_admins": total_admins,
|
|
153
|
+
"active_admins": active_admins,
|
|
154
|
+
"admin_ids": list(self.admin_ids),
|
|
155
|
+
"modes": dict(self.admin_modes),
|
|
156
|
+
}
|
|
@@ -4,138 +4,143 @@
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import logging
|
|
7
|
-
import json
|
|
8
|
-
import re
|
|
9
7
|
import sys
|
|
10
|
-
import os
|
|
11
8
|
|
|
9
|
+
from ..analytics.analytics_manager import AnalyticsManager
|
|
12
10
|
from ..config import Config
|
|
11
|
+
from ..core.conversation_manager import ConversationManager
|
|
13
12
|
from ..integrations.supabase_client import SupabaseClient
|
|
14
13
|
from .admin_manager import AdminManager
|
|
15
|
-
from ..core.conversation_manager import ConversationManager
|
|
16
|
-
from ..analytics.analytics_manager import AnalyticsManager
|
|
17
14
|
from .timeout_checker import setup_bot_environment
|
|
18
15
|
|
|
19
16
|
logger = logging.getLogger(__name__)
|
|
20
17
|
|
|
18
|
+
|
|
21
19
|
async def test_admin_system(bot_name: str = "growthmed-october-24") -> bool:
|
|
22
20
|
"""
|
|
23
21
|
Тестирует систему администрирования бота
|
|
24
|
-
|
|
22
|
+
|
|
25
23
|
Args:
|
|
26
24
|
bot_name: Имя бота для тестирования
|
|
27
|
-
|
|
25
|
+
|
|
28
26
|
Returns:
|
|
29
27
|
bool: True если все тесты пройдены, False если найдены проблемы
|
|
30
28
|
"""
|
|
31
29
|
logger.info(f"🚀 Тестирование системы администрирования: {bot_name}")
|
|
32
30
|
logger.info(f"🤖 Bot ID будет автоопределен как: {bot_name}\n")
|
|
33
|
-
|
|
31
|
+
|
|
34
32
|
# Настраиваем окружение для бота (автоматически устанавливает BOT_ID)
|
|
35
33
|
config_dir = setup_bot_environment(bot_name)
|
|
36
34
|
if not config_dir:
|
|
37
35
|
return False
|
|
38
|
-
|
|
36
|
+
|
|
39
37
|
# Инициализируем конфигурацию
|
|
40
38
|
config = Config()
|
|
41
|
-
logger.info(
|
|
39
|
+
logger.info("📋 Конфигурация:")
|
|
42
40
|
logger.info(f" BOT_ID: {config.BOT_ID}")
|
|
43
|
-
logger.info(
|
|
41
|
+
logger.info(
|
|
42
|
+
f" ADMIN_SESSION_TIMEOUT_MINUTES: {config.ADMIN_SESSION_TIMEOUT_MINUTES}"
|
|
43
|
+
)
|
|
44
44
|
logger.info(f" PROMT_FILES_DIR: {config.PROMT_FILES_DIR}")
|
|
45
45
|
logger.info(f" Найдено промпт-файлов: {len(config.PROMPT_FILES)}")
|
|
46
46
|
logger.info("")
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
# Проверяем админов
|
|
49
49
|
if not config.ADMIN_TELEGRAM_IDS:
|
|
50
50
|
logger.warning("⚠️ Админы не настроены (ADMIN_TELEGRAM_IDS пуст)")
|
|
51
51
|
return False
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
logger.info(f"👑 Админы: {config.ADMIN_TELEGRAM_IDS}")
|
|
54
54
|
logger.info("")
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
# Проверяем подключение к БД
|
|
57
57
|
try:
|
|
58
58
|
supabase_client = SupabaseClient(config.SUPABASE_URL, config.SUPABASE_KEY)
|
|
59
59
|
await supabase_client.initialize()
|
|
60
60
|
logger.info("✅ Подключение к Supabase успешно")
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
# Проверяем таблицы
|
|
63
63
|
tables = [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
"sales_admins",
|
|
65
|
+
"admin_user_conversations",
|
|
66
|
+
"session_events",
|
|
67
|
+
"sales_chat_sessions",
|
|
68
|
+
"sales_messages",
|
|
69
69
|
]
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
logger.info("\n📊 Проверка таблиц:")
|
|
72
72
|
for table in tables:
|
|
73
73
|
try:
|
|
74
|
-
|
|
74
|
+
supabase_client.client.table(table).select("*").limit(1).execute()
|
|
75
75
|
logger.info(f" ✅ {table}")
|
|
76
76
|
except Exception as e:
|
|
77
77
|
logger.error(f" ❌ {table}: {e}")
|
|
78
78
|
return False
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
# Проверяем AdminManager
|
|
81
81
|
admin_manager = AdminManager(config, supabase_client)
|
|
82
|
-
logger.info(
|
|
83
|
-
|
|
82
|
+
logger.info(
|
|
83
|
+
f"\n👑 AdminManager инициализирован ({len(admin_manager.admin_ids)} админов)"
|
|
84
|
+
)
|
|
85
|
+
|
|
84
86
|
# Проверяем ConversationManager
|
|
85
87
|
conversation_manager = ConversationManager(supabase_client, admin_manager)
|
|
86
88
|
logger.info("✅ ConversationManager инициализирован")
|
|
87
|
-
|
|
89
|
+
|
|
88
90
|
# Проверяем AnalyticsManager
|
|
89
91
|
analytics_manager = AnalyticsManager(supabase_client)
|
|
90
|
-
|
|
92
|
+
|
|
91
93
|
# Тестируем получение статистики
|
|
92
|
-
|
|
94
|
+
await analytics_manager.get_funnel_stats(1)
|
|
93
95
|
logger.info("✅ AnalyticsManager работает")
|
|
94
|
-
|
|
96
|
+
|
|
95
97
|
# Проверяем активные диалоги
|
|
96
98
|
conversations = await conversation_manager.get_active_conversations()
|
|
97
99
|
logger.info(f"\n💬 Активные диалоги: {len(conversations)}")
|
|
98
|
-
|
|
100
|
+
|
|
99
101
|
if conversations:
|
|
100
102
|
for conv in conversations:
|
|
101
|
-
logger.info(
|
|
103
|
+
logger.info(
|
|
104
|
+
f" • Диалог {conv['id']}: админ {conv['admin_id']} с пользователем {conv['user_id']}"
|
|
105
|
+
)
|
|
102
106
|
else:
|
|
103
107
|
logger.info(" Нет активных диалогов")
|
|
104
108
|
logger.info(" 💡 Создайте диалог командой /чат USER_ID для тестирования")
|
|
105
|
-
|
|
109
|
+
|
|
106
110
|
# Проверяем форматирование диалогов
|
|
107
111
|
if conversations:
|
|
108
112
|
formatted = conversation_manager.format_active_conversations(conversations)
|
|
109
113
|
logger.info("\n📝 Форматирование диалогов:")
|
|
110
114
|
logger.info(formatted)
|
|
111
|
-
|
|
115
|
+
|
|
112
116
|
logger.info("\n✅ Админская система готова к работе")
|
|
113
117
|
return True
|
|
114
|
-
|
|
118
|
+
|
|
115
119
|
except Exception as e:
|
|
116
120
|
logger.error(f"❌ Ошибка тестирования: {e}")
|
|
117
121
|
logger.exception("Стек ошибки:")
|
|
118
122
|
return False
|
|
119
123
|
|
|
124
|
+
|
|
120
125
|
def main():
|
|
121
126
|
"""Точка входа для запуска из командной строки"""
|
|
122
127
|
# Настройка логирования
|
|
123
|
-
logging.basicConfig(level=logging.INFO, format=
|
|
124
|
-
|
|
128
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
129
|
+
|
|
125
130
|
logger.info("🔍 Утилита тестирования админской системы")
|
|
126
131
|
logger.info("Использование:")
|
|
127
132
|
logger.info(" python -m smart_bot_factory.admin_tester [bot_name]")
|
|
128
133
|
logger.info(" python -m smart_bot_factory.admin_tester growthmed-october-24")
|
|
129
134
|
logger.info("")
|
|
130
|
-
|
|
131
|
-
if len(sys.argv) > 1 and sys.argv[1] in [
|
|
135
|
+
|
|
136
|
+
if len(sys.argv) > 1 and sys.argv[1] in ["-h", "--help", "help"]:
|
|
132
137
|
return
|
|
133
|
-
|
|
138
|
+
|
|
134
139
|
# Определяем какого бота тестировать
|
|
135
140
|
bot_name = "growthmed-october-24" # по умолчанию
|
|
136
141
|
if len(sys.argv) > 1:
|
|
137
142
|
bot_name = sys.argv[1]
|
|
138
|
-
|
|
143
|
+
|
|
139
144
|
try:
|
|
140
145
|
success = asyncio.run(test_admin_system(bot_name))
|
|
141
146
|
if not success:
|
|
@@ -147,5 +152,6 @@ def main():
|
|
|
147
152
|
logger.exception("Стек ошибки:")
|
|
148
153
|
sys.exit(1)
|
|
149
154
|
|
|
155
|
+
|
|
150
156
|
if __name__ == "__main__":
|
|
151
157
|
main()
|