smart-bot-factory 0.1.2__py3-none-any.whl → 0.1.3__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 +51 -0
- smart_bot_factory/admin/__init__.py +16 -0
- smart_bot_factory/admin/admin_logic.py +430 -0
- smart_bot_factory/admin/admin_manager.py +141 -0
- smart_bot_factory/admin/admin_migration.sql +136 -0
- smart_bot_factory/admin/admin_tester.py +151 -0
- smart_bot_factory/admin/timeout_checker.py +499 -0
- smart_bot_factory/analytics/__init__.py +7 -0
- smart_bot_factory/analytics/analytics_manager.py +355 -0
- smart_bot_factory/cli.py +642 -0
- smart_bot_factory/config.py +235 -0
- smart_bot_factory/configs/growthmed-helper/env_example.txt +1 -0
- smart_bot_factory/configs/growthmed-helper/prompts/1sales_context.txt +9 -0
- smart_bot_factory/configs/growthmed-helper/prompts/2product_info.txt +582 -0
- smart_bot_factory/configs/growthmed-helper/prompts/3objection_handling.txt +66 -0
- smart_bot_factory/configs/growthmed-helper/prompts/final_instructions.txt +232 -0
- smart_bot_factory/configs/growthmed-helper/prompts/help_message.txt +28 -0
- smart_bot_factory/configs/growthmed-helper/prompts/welcome_message.txt +7 -0
- smart_bot_factory/configs/growthmed-helper/welcome_file/welcome_file_msg.txt +16 -0
- 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/configs/growthmed-october-24/prompts/1sales_context.txt +16 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +582 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +66 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +212 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +28 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +8 -0
- smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +818 -0
- smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +32 -0
- smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +35 -0
- smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +66 -0
- smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +108 -0
- smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +46 -0
- smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +16 -0
- smart_bot_factory/configs/growthmed-october-24/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 +22 -0
- smart_bot_factory/core/bot_utils.py +693 -0
- smart_bot_factory/core/conversation_manager.py +536 -0
- smart_bot_factory/core/decorators.py +229 -0
- smart_bot_factory/core/message_sender.py +249 -0
- smart_bot_factory/core/states.py +14 -0
- smart_bot_factory/creation/__init__.py +8 -0
- smart_bot_factory/creation/bot_builder.py +329 -0
- smart_bot_factory/creation/bot_testing.py +986 -0
- smart_bot_factory/database/database_structure.sql +57 -0
- smart_bot_factory/database/schema.sql +1094 -0
- smart_bot_factory/handlers/handlers.py +583 -0
- smart_bot_factory/integrations/__init__.py +9 -0
- smart_bot_factory/integrations/openai_client.py +435 -0
- smart_bot_factory/integrations/supabase_client.py +592 -0
- smart_bot_factory/setup_checker.py +476 -0
- smart_bot_factory/utils/__init__.py +9 -0
- smart_bot_factory/utils/debug_routing.py +103 -0
- smart_bot_factory/utils/prompt_loader.py +427 -0
- smart_bot_factory/uv.lock +2004 -0
- smart_bot_factory-0.1.3.dist-info/METADATA +126 -0
- smart_bot_factory-0.1.3.dist-info/RECORD +59 -0
- smart_bot_factory-0.1.3.dist-info/licenses/LICENSE +24 -0
- smart_bot_factory-0.1.2.dist-info/METADATA +0 -31
- smart_bot_factory-0.1.2.dist-info/RECORD +0 -4
- {smart_bot_factory-0.1.2.dist-info → smart_bot_factory-0.1.3.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.1.2.dist-info → smart_bot_factory-0.1.3.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Smart Bot Factory - библиотека для создания умных чат-ботов
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .creation.bot_builder import BotBuilder
|
|
6
|
+
from .core.decorators import event_handler, schedule_task
|
|
7
|
+
from .core.message_sender import send_message_by_ai, send_message_by_human
|
|
8
|
+
from .config import Config
|
|
9
|
+
from .integrations.openai_client import OpenAIClient
|
|
10
|
+
from .integrations.supabase_client import SupabaseClient
|
|
11
|
+
from .core.conversation_manager import ConversationManager
|
|
12
|
+
from .admin.admin_manager import AdminManager
|
|
13
|
+
from .utils.prompt_loader import PromptLoader
|
|
14
|
+
from .handlers.handlers import setup_handlers
|
|
15
|
+
from .admin.admin_logic import setup_admin_handlers
|
|
16
|
+
from .core.bot_utils import setup_utils_handlers, parse_ai_response, process_events
|
|
17
|
+
from .utils.debug_routing import setup_debug_handlers
|
|
18
|
+
from .core.states import UserStates, AdminStates
|
|
19
|
+
from .creation.bot_testing import main as bot_testing_main
|
|
20
|
+
from .analytics.analytics_manager import AnalyticsManager
|
|
21
|
+
from .admin.timeout_checker import check_timeouts, setup_bot_environment
|
|
22
|
+
from .setup_checker import check_setup
|
|
23
|
+
from .admin.admin_tester import test_admin_system
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
'BotBuilder',
|
|
27
|
+
'event_handler',
|
|
28
|
+
'schedule_task',
|
|
29
|
+
'send_message_by_ai',
|
|
30
|
+
'send_message_by_human',
|
|
31
|
+
'Config',
|
|
32
|
+
'OpenAIClient',
|
|
33
|
+
'SupabaseClient',
|
|
34
|
+
'ConversationManager',
|
|
35
|
+
'AdminManager',
|
|
36
|
+
'PromptLoader',
|
|
37
|
+
'setup_handlers',
|
|
38
|
+
'setup_admin_handlers',
|
|
39
|
+
'setup_utils_handlers',
|
|
40
|
+
'parse_ai_response',
|
|
41
|
+
'process_events',
|
|
42
|
+
'setup_debug_handlers',
|
|
43
|
+
'UserStates',
|
|
44
|
+
'AdminStates',
|
|
45
|
+
'bot_testing_main',
|
|
46
|
+
'AnalyticsManager',
|
|
47
|
+
'check_timeouts',
|
|
48
|
+
'setup_bot_environment',
|
|
49
|
+
'check_setup',
|
|
50
|
+
'test_admin_system',
|
|
51
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Admin модули smart_bot_factory
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .admin_logic import setup_admin_handlers
|
|
6
|
+
from .admin_manager import AdminManager
|
|
7
|
+
from .admin_tester import test_admin_system
|
|
8
|
+
from .timeout_checker import check_timeouts, setup_bot_environment
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'setup_admin_handlers',
|
|
12
|
+
'AdminManager',
|
|
13
|
+
'test_admin_system',
|
|
14
|
+
'check_timeouts',
|
|
15
|
+
'setup_bot_environment'
|
|
16
|
+
]
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# Исправленный admin_logic.py с правильной обработкой диалогов
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from aiogram import Router, F
|
|
6
|
+
from aiogram.filters import Command, StateFilter
|
|
7
|
+
from aiogram.fsm.context import FSMContext
|
|
8
|
+
from aiogram.fsm.state import State, StatesGroup
|
|
9
|
+
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
# Импортируем состояния
|
|
14
|
+
from ..core.states import UserStates, AdminStates
|
|
15
|
+
|
|
16
|
+
# Создаем роутер для админских обработчиков
|
|
17
|
+
admin_router = Router()
|
|
18
|
+
|
|
19
|
+
def setup_admin_handlers(dp):
|
|
20
|
+
"""Настройка админских обработчиков"""
|
|
21
|
+
dp.include_router(admin_router)
|
|
22
|
+
|
|
23
|
+
async def admin_start_handler(message: Message, state: FSMContext):
|
|
24
|
+
"""Обработчик /start для админов в режиме администратора"""
|
|
25
|
+
from handlers import get_global_var
|
|
26
|
+
admin_manager = get_global_var('admin_manager')
|
|
27
|
+
|
|
28
|
+
await state.set_state(AdminStates.admin_mode)
|
|
29
|
+
|
|
30
|
+
admin_status = admin_manager.get_admin_mode_text(message.from_user.id)
|
|
31
|
+
|
|
32
|
+
# Основное меню админа
|
|
33
|
+
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
34
|
+
[InlineKeyboardButton(text="📊 Статистика", callback_data="admin_stats")],
|
|
35
|
+
[InlineKeyboardButton(text="💬 Активные чаты", callback_data="admin_active_chats")],
|
|
36
|
+
[InlineKeyboardButton(text="🔄 Режим польз.", callback_data="admin_toggle_mode")]
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
welcome_text = f"""
|
|
40
|
+
{admin_status}
|
|
41
|
+
|
|
42
|
+
🎛️ **Панель администратора**
|
|
43
|
+
|
|
44
|
+
Доступные команды:
|
|
45
|
+
• `/стат` - статистика воронки
|
|
46
|
+
• `/история <user_id>` - история пользователя
|
|
47
|
+
• `/чат <user_id>` - начать диалог
|
|
48
|
+
• `/чаты` - активные диалоги
|
|
49
|
+
• `/стоп` - завершить диалог
|
|
50
|
+
• `/админ` - переключить режим
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
await message.answer(welcome_text, reply_markup=keyboard, parse_mode='Markdown')
|
|
54
|
+
|
|
55
|
+
@admin_router.message(Command("стат"))
|
|
56
|
+
async def admin_stats_handler(message: Message, state: FSMContext):
|
|
57
|
+
"""Статистика воронки"""
|
|
58
|
+
from handlers import get_global_var
|
|
59
|
+
admin_manager = get_global_var('admin_manager')
|
|
60
|
+
analytics_manager = get_global_var('analytics_manager')
|
|
61
|
+
|
|
62
|
+
if not admin_manager.is_admin(message.from_user.id):
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Получаем статистику
|
|
67
|
+
funnel_stats = await analytics_manager.get_funnel_stats(7)
|
|
68
|
+
events_stats = await analytics_manager.get_events_stats(7)
|
|
69
|
+
|
|
70
|
+
# Форматируем ответ
|
|
71
|
+
funnel_text = analytics_manager.format_funnel_stats(funnel_stats)
|
|
72
|
+
events_text = analytics_manager.format_events_stats(events_stats)
|
|
73
|
+
|
|
74
|
+
full_text = f"{funnel_text}\n\n{events_text}"
|
|
75
|
+
|
|
76
|
+
await message.answer(full_text, parse_mode='Markdown')
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(f"Ошибка получения статистики: {e}")
|
|
80
|
+
await message.answer("❌ Ошибка получения статистики")
|
|
81
|
+
|
|
82
|
+
@admin_router.message(Command("история"))
|
|
83
|
+
async def admin_history_handler(message: Message, state: FSMContext):
|
|
84
|
+
"""История пользователя"""
|
|
85
|
+
from handlers import get_global_var
|
|
86
|
+
admin_manager = get_global_var('admin_manager')
|
|
87
|
+
analytics_manager = get_global_var('analytics_manager')
|
|
88
|
+
|
|
89
|
+
if not admin_manager.is_admin(message.from_user.id):
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
parts = message.text.split()
|
|
94
|
+
if len(parts) < 2:
|
|
95
|
+
await message.answer("Укажите ID пользователя: /история 123456789")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
user_id = int(parts[1])
|
|
99
|
+
|
|
100
|
+
# Получаем историю (та же функция что использует кнопка)
|
|
101
|
+
journey = await analytics_manager.get_user_journey(user_id)
|
|
102
|
+
|
|
103
|
+
if not journey:
|
|
104
|
+
await message.answer(f"❌ У пользователя {user_id} нет активной сессии")
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
# Используем ту же функцию форматирования что и кнопка
|
|
108
|
+
history_text = analytics_manager.format_user_journey(user_id, journey)
|
|
109
|
+
|
|
110
|
+
await message.answer(history_text)
|
|
111
|
+
|
|
112
|
+
except ValueError:
|
|
113
|
+
await message.answer("❌ Неверный формат ID пользователя")
|
|
114
|
+
except Exception as e:
|
|
115
|
+
logger.error(f"Ошибка получения истории: {e}")
|
|
116
|
+
await message.answer("❌ Ошибка получения истории")
|
|
117
|
+
|
|
118
|
+
@admin_router.message(Command("чат"))
|
|
119
|
+
async def admin_chat_handler(message: Message, state: FSMContext):
|
|
120
|
+
"""Начать диалог с пользователем"""
|
|
121
|
+
from handlers import get_global_var
|
|
122
|
+
|
|
123
|
+
admin_manager = get_global_var('admin_manager')
|
|
124
|
+
supabase_client = get_global_var('supabase_client')
|
|
125
|
+
conversation_manager = get_global_var('conversation_manager')
|
|
126
|
+
|
|
127
|
+
if not admin_manager.is_admin(message.from_user.id):
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
# Парсим user_id из команды
|
|
132
|
+
parts = message.text.split()
|
|
133
|
+
if len(parts) < 2:
|
|
134
|
+
await message.answer("Укажите ID пользователя: /чат 123456789")
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
user_id = int(parts[1])
|
|
138
|
+
admin_id = message.from_user.id
|
|
139
|
+
|
|
140
|
+
logger.info(f"👑 Админ {admin_id} хочет начать диалог с пользователем {user_id}")
|
|
141
|
+
|
|
142
|
+
# Проверяем, есть ли активная сессия у пользователя
|
|
143
|
+
session_info = await supabase_client.get_active_session(user_id)
|
|
144
|
+
if not session_info:
|
|
145
|
+
await message.answer(f"❌ У пользователя {user_id} нет активной сессии")
|
|
146
|
+
logger.warning(f"❌ У пользователя {user_id} нет активной сессии")
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
logger.info(f"✅ У пользователя {user_id} есть активная сессия: {session_info['id']}")
|
|
150
|
+
|
|
151
|
+
# Начинаем диалог
|
|
152
|
+
logger.info(f"🚀 Запускаем создание диалога...")
|
|
153
|
+
success = await conversation_manager.start_admin_conversation(admin_id, user_id)
|
|
154
|
+
|
|
155
|
+
if success:
|
|
156
|
+
# ✅ ИСПРАВЛЕНИЕ: Правильно переключаем состояние админа
|
|
157
|
+
await state.set_state(AdminStates.in_conversation)
|
|
158
|
+
await state.update_data(conversation_user_id=user_id)
|
|
159
|
+
|
|
160
|
+
await message.answer(f"✅ Диалог с пользователем {user_id} начат\n💬 Ваши сообщения будут переданы пользователю\n⏹️ Используйте /стоп для завершения")
|
|
161
|
+
logger.info(f"✅ Диалог успешно создан, админ переключен в состояние in_conversation")
|
|
162
|
+
else:
|
|
163
|
+
await message.answer(f"❌ Не удалось начать диалог с пользователем {user_id}")
|
|
164
|
+
logger.error(f"❌ Не удалось создать диалог")
|
|
165
|
+
|
|
166
|
+
except ValueError:
|
|
167
|
+
await message.answer("❌ Неверный формат ID пользователя")
|
|
168
|
+
logger.error(f"❌ Неверный формат ID пользователя: {message.text}")
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"❌ Ошибка начала диалога: {e}")
|
|
171
|
+
await message.answer("❌ Ошибка начала диалога")
|
|
172
|
+
|
|
173
|
+
@admin_router.message(Command("чаты"))
|
|
174
|
+
async def admin_active_chats_command(message: Message, state: FSMContext):
|
|
175
|
+
"""Показать активные диалоги админов"""
|
|
176
|
+
from handlers import get_global_var
|
|
177
|
+
admin_manager = get_global_var('admin_manager')
|
|
178
|
+
conversation_manager = get_global_var('conversation_manager')
|
|
179
|
+
|
|
180
|
+
if not admin_manager.is_admin(message.from_user.id):
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
conversations = await conversation_manager.get_active_conversations()
|
|
185
|
+
formatted_text = conversation_manager.format_active_conversations(conversations)
|
|
186
|
+
|
|
187
|
+
# ✅ ИСПРАВЛЕНИЕ: Убираем parse_mode='Markdown' чтобы избежать ошибок парсинга
|
|
188
|
+
await message.answer(formatted_text)
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
logger.error(f"Ошибка получения активных чатов: {e}")
|
|
192
|
+
await message.answer("❌ Ошибка получения активных диалогов")
|
|
193
|
+
|
|
194
|
+
@admin_router.message(Command("стоп"))
|
|
195
|
+
async def admin_stop_handler(message: Message, state: FSMContext):
|
|
196
|
+
"""Завершить диалог"""
|
|
197
|
+
from handlers import get_global_var
|
|
198
|
+
admin_manager = get_global_var('admin_manager')
|
|
199
|
+
conversation_manager = get_global_var('conversation_manager')
|
|
200
|
+
|
|
201
|
+
if not admin_manager.is_admin(message.from_user.id):
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
admin_id = message.from_user.id
|
|
206
|
+
|
|
207
|
+
# Проверяем есть ли активный диалог
|
|
208
|
+
conversation = await conversation_manager.get_admin_active_conversation(admin_id)
|
|
209
|
+
|
|
210
|
+
if conversation:
|
|
211
|
+
user_id = conversation['user_id']
|
|
212
|
+
logger.info(f"🛑 Завершаем диалог админа {admin_id} с пользователем {user_id}")
|
|
213
|
+
|
|
214
|
+
success = await conversation_manager.end_admin_conversation(admin_id)
|
|
215
|
+
|
|
216
|
+
if success:
|
|
217
|
+
# ✅ ИСПРАВЛЕНИЕ: Правильно переключаем состояние обратно
|
|
218
|
+
await state.set_state(AdminStates.admin_mode)
|
|
219
|
+
await state.update_data(conversation_user_id=None)
|
|
220
|
+
|
|
221
|
+
await message.answer(f"✅ Диалог с пользователем {user_id} завершен")
|
|
222
|
+
logger.info(f"✅ Диалог завершен, админ переключен в admin_mode")
|
|
223
|
+
else:
|
|
224
|
+
await message.answer("❌ Ошибка завершения диалога")
|
|
225
|
+
else:
|
|
226
|
+
await message.answer("❌ Нет активного диалога")
|
|
227
|
+
logger.info(f"❌ У админа {admin_id} нет активного диалога")
|
|
228
|
+
|
|
229
|
+
except Exception as e:
|
|
230
|
+
logger.error(f"Ошибка завершения диалога: {e}")
|
|
231
|
+
await message.answer("❌ Ошибка завершения диалога")
|
|
232
|
+
|
|
233
|
+
@admin_router.message(Command("админ"))
|
|
234
|
+
async def admin_toggle_handler(message: Message, state: FSMContext):
|
|
235
|
+
"""Переключение режима админа"""
|
|
236
|
+
from handlers import get_global_var
|
|
237
|
+
admin_manager = get_global_var('admin_manager')
|
|
238
|
+
from .handlers import user_start_handler
|
|
239
|
+
|
|
240
|
+
if not admin_manager.is_admin(message.from_user.id):
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
new_mode = admin_manager.toggle_admin_mode(message.from_user.id)
|
|
244
|
+
|
|
245
|
+
if new_mode:
|
|
246
|
+
# Переключились в режим админа
|
|
247
|
+
await admin_start_handler(message, state)
|
|
248
|
+
else:
|
|
249
|
+
# Переключились в режим пользователя
|
|
250
|
+
await state.clear()
|
|
251
|
+
await message.answer("🔄 Переключен в режим пользователя\nНапишите /start для начала диалога")
|
|
252
|
+
|
|
253
|
+
@admin_router.message(Command("debug_chat"))
|
|
254
|
+
async def debug_chat_handler(message: Message, state: FSMContext):
|
|
255
|
+
"""Отладка диалогов админов"""
|
|
256
|
+
from handlers import get_global_var
|
|
257
|
+
admin_manager = get_global_var('admin_manager')
|
|
258
|
+
conversation_manager = get_global_var('conversation_manager')
|
|
259
|
+
supabase_client = get_global_var('supabase_client')
|
|
260
|
+
|
|
261
|
+
if not admin_manager.is_admin(message.from_user.id):
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
parts = message.text.split()
|
|
265
|
+
if len(parts) < 2:
|
|
266
|
+
await message.answer("Использование: /debug_chat USER_ID")
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
user_id = int(parts[1])
|
|
271
|
+
|
|
272
|
+
# 1. Проверяем запись в БД
|
|
273
|
+
conversation = await conversation_manager.is_user_in_admin_chat(user_id)
|
|
274
|
+
|
|
275
|
+
debug_info = [
|
|
276
|
+
f"🔍 ОТЛАДКА ДИАЛОГА С {user_id}",
|
|
277
|
+
"",
|
|
278
|
+
f"📊 Диалог в БД: {'✅' if conversation else '❌'}",
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
if conversation:
|
|
282
|
+
debug_info.extend([
|
|
283
|
+
f"👑 Админ: {conversation['admin_id']}",
|
|
284
|
+
f"🕐 Начат: {conversation['started_at']}",
|
|
285
|
+
])
|
|
286
|
+
|
|
287
|
+
# 2. Проверяем активную сессию пользователя
|
|
288
|
+
session_info = await supabase_client.get_active_session(user_id)
|
|
289
|
+
debug_info.append(f"🎯 Активная сессия: {'✅' if session_info else '❌'}")
|
|
290
|
+
|
|
291
|
+
if session_info:
|
|
292
|
+
debug_info.append(f"📝 ID сессии: {session_info['id']}")
|
|
293
|
+
|
|
294
|
+
# 3. Проверяем состояние пользователя (если он онлайн)
|
|
295
|
+
debug_info.append(f"")
|
|
296
|
+
debug_info.append(f"ℹ️ Для проверки состояния пользователь должен написать что-то")
|
|
297
|
+
|
|
298
|
+
await message.answer("\n".join(debug_info))
|
|
299
|
+
|
|
300
|
+
except Exception as e:
|
|
301
|
+
await message.answer(f"❌ Ошибка: {e}")
|
|
302
|
+
logger.error(f"Ошибка отладки: {e}")
|
|
303
|
+
|
|
304
|
+
@admin_router.callback_query(F.data.startswith("admin_"))
|
|
305
|
+
async def admin_callback_handler(callback: CallbackQuery, state: FSMContext):
|
|
306
|
+
"""Обработчик callback кнопок админов"""
|
|
307
|
+
from handlers import get_global_var
|
|
308
|
+
admin_manager = get_global_var('admin_manager')
|
|
309
|
+
analytics_manager = get_global_var('analytics_manager')
|
|
310
|
+
conversation_manager = get_global_var('conversation_manager')
|
|
311
|
+
|
|
312
|
+
if not admin_manager.is_admin(callback.from_user.id):
|
|
313
|
+
await callback.answer("Нет доступа")
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
data = callback.data
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
if data == "admin_stats":
|
|
320
|
+
# Показываем статистику
|
|
321
|
+
funnel_stats = await analytics_manager.get_funnel_stats(7)
|
|
322
|
+
events_stats = await analytics_manager.get_events_stats(7)
|
|
323
|
+
|
|
324
|
+
funnel_text = analytics_manager.format_funnel_stats(funnel_stats)
|
|
325
|
+
events_text = analytics_manager.format_events_stats(events_stats)
|
|
326
|
+
|
|
327
|
+
await callback.message.answer(f"{funnel_text}\n\n{events_text}")
|
|
328
|
+
|
|
329
|
+
elif data == "admin_toggle_mode":
|
|
330
|
+
# Переключаем режим
|
|
331
|
+
new_mode = admin_manager.toggle_admin_mode(callback.from_user.id)
|
|
332
|
+
mode_text = "администратор" if new_mode else "пользователь"
|
|
333
|
+
await callback.answer(f"Режим переключен: {mode_text}")
|
|
334
|
+
|
|
335
|
+
if not new_mode:
|
|
336
|
+
await state.clear()
|
|
337
|
+
await callback.message.answer("🔄 Теперь вы в режиме пользователя")
|
|
338
|
+
|
|
339
|
+
elif data == "admin_active_chats":
|
|
340
|
+
# Показываем активные диалоги
|
|
341
|
+
conversations = await conversation_manager.get_active_conversations()
|
|
342
|
+
formatted_text = conversation_manager.format_active_conversations(conversations)
|
|
343
|
+
|
|
344
|
+
# ✅ ИСПРАВЛЕНИЕ: Убираем parse_mode='Markdown'
|
|
345
|
+
await callback.message.answer(formatted_text)
|
|
346
|
+
|
|
347
|
+
elif data.startswith("admin_history_"):
|
|
348
|
+
user_id = int(data.split("_")[2])
|
|
349
|
+
journey = await analytics_manager.get_user_journey(user_id)
|
|
350
|
+
history_text = analytics_manager.format_user_journey(user_id, journey)
|
|
351
|
+
await callback.message.answer(history_text)
|
|
352
|
+
|
|
353
|
+
elif data.startswith("admin_end_"):
|
|
354
|
+
user_id = int(data.split("_")[2])
|
|
355
|
+
|
|
356
|
+
# Проверяем есть ли активный диалог
|
|
357
|
+
conversation = await conversation_manager.get_admin_active_conversation(callback.from_user.id)
|
|
358
|
+
|
|
359
|
+
if conversation and conversation['user_id'] == user_id:
|
|
360
|
+
await conversation_manager.end_admin_conversation(callback.from_user.id)
|
|
361
|
+
|
|
362
|
+
# ✅ ИСПРАВЛЕНИЕ: Правильно переключаем состояние
|
|
363
|
+
await state.set_state(AdminStates.admin_mode)
|
|
364
|
+
await state.update_data(conversation_user_id=None)
|
|
365
|
+
|
|
366
|
+
await callback.answer("Диалог завершен")
|
|
367
|
+
await callback.message.answer(f"✅ Диалог с пользователем {user_id} завершен")
|
|
368
|
+
logger.info(f"✅ Диалог завершен через кнопку, админ переключен в admin_mode")
|
|
369
|
+
else:
|
|
370
|
+
await callback.answer("Диалог не найден")
|
|
371
|
+
|
|
372
|
+
elif data.startswith("admin_chat_"):
|
|
373
|
+
user_id = int(data.split("_")[2])
|
|
374
|
+
admin_id = callback.from_user.id
|
|
375
|
+
|
|
376
|
+
success = await conversation_manager.start_admin_conversation(admin_id, user_id)
|
|
377
|
+
if success:
|
|
378
|
+
# ✅ ИСПРАВЛЕНИЕ: Правильно переключаем состояние
|
|
379
|
+
await state.set_state(AdminStates.in_conversation)
|
|
380
|
+
await state.update_data(conversation_user_id=user_id)
|
|
381
|
+
|
|
382
|
+
await callback.answer("Диалог начат")
|
|
383
|
+
await callback.message.answer(f"✅ Диалог с пользователем {user_id} начат")
|
|
384
|
+
logger.info(f"✅ Диалог начат через кнопку, админ переключен в in_conversation")
|
|
385
|
+
else:
|
|
386
|
+
await callback.answer("Не удалось начать диалог")
|
|
387
|
+
|
|
388
|
+
await callback.answer()
|
|
389
|
+
|
|
390
|
+
except Exception as e:
|
|
391
|
+
logger.error(f"Ошибка обработки callback {data}: {e}")
|
|
392
|
+
await callback.answer("Ошибка")
|
|
393
|
+
|
|
394
|
+
@admin_router.message(StateFilter(AdminStates.admin_mode, AdminStates.in_conversation))
|
|
395
|
+
async def admin_message_handler(message: Message, state: FSMContext):
|
|
396
|
+
"""Обработчик сообщений админов"""
|
|
397
|
+
from handlers import get_global_var
|
|
398
|
+
admin_manager = get_global_var('admin_manager')
|
|
399
|
+
conversation_manager = get_global_var('conversation_manager')
|
|
400
|
+
|
|
401
|
+
if not admin_manager.is_admin(message.from_user.id):
|
|
402
|
+
return
|
|
403
|
+
|
|
404
|
+
try:
|
|
405
|
+
logger.info(f"👑 Получено сообщение от админа {message.from_user.id}: '{message.text}'")
|
|
406
|
+
|
|
407
|
+
# Пытаемся обработать как админское сообщение
|
|
408
|
+
handled = await conversation_manager.route_admin_message(message, state)
|
|
409
|
+
|
|
410
|
+
if handled:
|
|
411
|
+
logger.info(f"✅ Сообщение админа обработано и переслано пользователю")
|
|
412
|
+
else:
|
|
413
|
+
# Не админское сообщение - показываем справку
|
|
414
|
+
logger.info(f"❌ Сообщение админа не обработано, показываем справку")
|
|
415
|
+
await message.answer("""
|
|
416
|
+
👑 **Режим администратора**
|
|
417
|
+
|
|
418
|
+
Доступные команды:
|
|
419
|
+
• `/стат` - статистика воронки
|
|
420
|
+
• `/история <user_id>` - история пользователя
|
|
421
|
+
• `/чат <user_id>` - начать диалог
|
|
422
|
+
• `/стоп` - завершить диалог
|
|
423
|
+
• `/админ` - переключить режим
|
|
424
|
+
|
|
425
|
+
💡 Если вы в диалоге с пользователем, просто напишите сообщение - оно будет переслано пользователю.
|
|
426
|
+
""", parse_mode='Markdown')
|
|
427
|
+
|
|
428
|
+
except Exception as e:
|
|
429
|
+
logger.error(f"Ошибка обработки сообщения админа: {e}")
|
|
430
|
+
await message.answer("❌ Ошибка обработки команды")
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List, Dict, Set
|
|
3
|
+
from aiogram.types import User
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
class AdminManager:
|
|
8
|
+
"""Управление администраторами бота"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, config, supabase_client):
|
|
11
|
+
self.config = config
|
|
12
|
+
self.supabase = supabase_client
|
|
13
|
+
self.admin_ids: Set[int] = set(config.ADMIN_TELEGRAM_IDS)
|
|
14
|
+
self.admin_modes: Dict[int, bool] = {} # admin_id -> is_in_admin_mode
|
|
15
|
+
|
|
16
|
+
logger.info(f"Инициализирован менеджер админов: {len(self.admin_ids)} админов")
|
|
17
|
+
|
|
18
|
+
async def sync_admins_from_config(self):
|
|
19
|
+
"""Синхронизирует админов из конфига с базой данных"""
|
|
20
|
+
if not self.admin_ids:
|
|
21
|
+
logger.warning("Нет админов в конфигурации")
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
for admin_id in self.admin_ids:
|
|
26
|
+
await self.supabase.sync_admin({
|
|
27
|
+
'telegram_id': admin_id,
|
|
28
|
+
'username': None, # будет обновлено при первом сообщении
|
|
29
|
+
'first_name': None,
|
|
30
|
+
'last_name': None
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
# Устанавливаем режим администратора по умолчанию
|
|
34
|
+
if admin_id not in self.admin_modes:
|
|
35
|
+
self.admin_modes[admin_id] = True
|
|
36
|
+
|
|
37
|
+
logger.info(f"Синхронизированы админы: {self.admin_ids}")
|
|
38
|
+
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.error(f"Ошибка синхронизации админов: {e}")
|
|
41
|
+
raise
|
|
42
|
+
|
|
43
|
+
async def update_admin_info(self, user: User):
|
|
44
|
+
"""Обновляет информацию об админе"""
|
|
45
|
+
if not self.is_admin(user.id):
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
await self.supabase.sync_admin({
|
|
50
|
+
'telegram_id': user.id,
|
|
51
|
+
'username': user.username,
|
|
52
|
+
'first_name': user.first_name,
|
|
53
|
+
'last_name': user.last_name
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.error(f"Ошибка обновления информации админа {user.id}: {e}")
|
|
58
|
+
|
|
59
|
+
def is_admin(self, telegram_id: int) -> bool:
|
|
60
|
+
"""Проверяет, является ли пользователь админом"""
|
|
61
|
+
return telegram_id in self.admin_ids
|
|
62
|
+
|
|
63
|
+
def is_in_admin_mode(self, telegram_id: int) -> bool:
|
|
64
|
+
"""Проверяет, находится ли админ в режиме администратора"""
|
|
65
|
+
if not self.is_admin(telegram_id):
|
|
66
|
+
return False
|
|
67
|
+
return self.admin_modes.get(telegram_id, True)
|
|
68
|
+
|
|
69
|
+
def toggle_admin_mode(self, telegram_id: int) -> bool:
|
|
70
|
+
"""Переключает режим админа. Возвращает новое состояние"""
|
|
71
|
+
if not self.is_admin(telegram_id):
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
current_mode = self.admin_modes.get(telegram_id, True)
|
|
75
|
+
new_mode = not current_mode
|
|
76
|
+
self.admin_modes[telegram_id] = new_mode
|
|
77
|
+
|
|
78
|
+
logger.info(f"Админ {telegram_id} переключен в режим: {'администратор' if new_mode else 'пользователь'}")
|
|
79
|
+
return new_mode
|
|
80
|
+
|
|
81
|
+
def set_admin_mode(self, telegram_id: int, is_admin_mode: bool):
|
|
82
|
+
"""Устанавливает режим админа"""
|
|
83
|
+
if not self.is_admin(telegram_id):
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
self.admin_modes[telegram_id] = is_admin_mode
|
|
87
|
+
logger.info(f"Режим админа {telegram_id} установлен: {'администратор' if is_admin_mode else 'пользователь'}")
|
|
88
|
+
|
|
89
|
+
async def get_active_admins(self) -> List[int]:
|
|
90
|
+
"""Возвращает список активных админов в режиме администратора"""
|
|
91
|
+
return [admin_id for admin_id in self.admin_ids if self.is_in_admin_mode(admin_id)]
|
|
92
|
+
|
|
93
|
+
def get_admin_mode_text(self, telegram_id: int) -> str:
|
|
94
|
+
"""Возвращает текстовое описание режима админа"""
|
|
95
|
+
if not self.is_admin(telegram_id):
|
|
96
|
+
return "Не администратор"
|
|
97
|
+
|
|
98
|
+
if self.is_in_admin_mode(telegram_id):
|
|
99
|
+
return "👑 Режим администратора"
|
|
100
|
+
else:
|
|
101
|
+
return "👤 Режим пользователя"
|
|
102
|
+
|
|
103
|
+
def format_admin_status(self, telegram_id: int) -> str:
|
|
104
|
+
"""Форматирует статус админа для отображения"""
|
|
105
|
+
if not self.is_admin(telegram_id):
|
|
106
|
+
return ""
|
|
107
|
+
|
|
108
|
+
mode = "👑 АДМИН" if self.is_in_admin_mode(telegram_id) else "👤 ПОЛЬЗ"
|
|
109
|
+
return f"[{mode}]"
|
|
110
|
+
|
|
111
|
+
async def notify_admins(self, message: str, exclude_admin: int = None):
|
|
112
|
+
"""Отправляет уведомление всем активным админам"""
|
|
113
|
+
from main import bot # Импорт здесь чтобы избежать циклических импортов
|
|
114
|
+
|
|
115
|
+
active_admins = await self.get_active_admins()
|
|
116
|
+
|
|
117
|
+
if exclude_admin:
|
|
118
|
+
active_admins = [aid for aid in active_admins if aid != exclude_admin]
|
|
119
|
+
|
|
120
|
+
sent_count = 0
|
|
121
|
+
for admin_id in active_admins:
|
|
122
|
+
try:
|
|
123
|
+
await bot.send_message(admin_id, message, parse_mode='Markdown')
|
|
124
|
+
sent_count += 1
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Ошибка отправки уведомления админу {admin_id}: {e}")
|
|
127
|
+
|
|
128
|
+
logger.info(f"Уведомление отправлено {sent_count} админам")
|
|
129
|
+
return sent_count
|
|
130
|
+
|
|
131
|
+
def get_stats(self) -> Dict[str, any]:
|
|
132
|
+
"""Возвращает статистику по админам"""
|
|
133
|
+
total_admins = len(self.admin_ids)
|
|
134
|
+
active_admins = len([aid for aid in self.admin_ids if self.is_in_admin_mode(aid)])
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
'total_admins': total_admins,
|
|
138
|
+
'active_admins': active_admins,
|
|
139
|
+
'admin_ids': list(self.admin_ids),
|
|
140
|
+
'modes': dict(self.admin_modes)
|
|
141
|
+
}
|