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.

Files changed (61) hide show
  1. smart_bot_factory/__init__.py +51 -0
  2. smart_bot_factory/admin/__init__.py +16 -0
  3. smart_bot_factory/admin/admin_logic.py +430 -0
  4. smart_bot_factory/admin/admin_manager.py +141 -0
  5. smart_bot_factory/admin/admin_migration.sql +136 -0
  6. smart_bot_factory/admin/admin_tester.py +151 -0
  7. smart_bot_factory/admin/timeout_checker.py +499 -0
  8. smart_bot_factory/analytics/__init__.py +7 -0
  9. smart_bot_factory/analytics/analytics_manager.py +355 -0
  10. smart_bot_factory/cli.py +642 -0
  11. smart_bot_factory/config.py +235 -0
  12. smart_bot_factory/configs/growthmed-helper/env_example.txt +1 -0
  13. smart_bot_factory/configs/growthmed-helper/prompts/1sales_context.txt +9 -0
  14. smart_bot_factory/configs/growthmed-helper/prompts/2product_info.txt +582 -0
  15. smart_bot_factory/configs/growthmed-helper/prompts/3objection_handling.txt +66 -0
  16. smart_bot_factory/configs/growthmed-helper/prompts/final_instructions.txt +232 -0
  17. smart_bot_factory/configs/growthmed-helper/prompts/help_message.txt +28 -0
  18. smart_bot_factory/configs/growthmed-helper/prompts/welcome_message.txt +7 -0
  19. smart_bot_factory/configs/growthmed-helper/welcome_file/welcome_file_msg.txt +16 -0
  20. 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
  21. smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +16 -0
  22. smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +582 -0
  23. smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +66 -0
  24. smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +212 -0
  25. smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +28 -0
  26. smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +8 -0
  27. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +818 -0
  28. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +32 -0
  29. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +35 -0
  30. smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +66 -0
  31. smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +108 -0
  32. smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +46 -0
  33. smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +16 -0
  34. 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
  35. smart_bot_factory/core/__init__.py +22 -0
  36. smart_bot_factory/core/bot_utils.py +693 -0
  37. smart_bot_factory/core/conversation_manager.py +536 -0
  38. smart_bot_factory/core/decorators.py +229 -0
  39. smart_bot_factory/core/message_sender.py +249 -0
  40. smart_bot_factory/core/states.py +14 -0
  41. smart_bot_factory/creation/__init__.py +8 -0
  42. smart_bot_factory/creation/bot_builder.py +329 -0
  43. smart_bot_factory/creation/bot_testing.py +986 -0
  44. smart_bot_factory/database/database_structure.sql +57 -0
  45. smart_bot_factory/database/schema.sql +1094 -0
  46. smart_bot_factory/handlers/handlers.py +583 -0
  47. smart_bot_factory/integrations/__init__.py +9 -0
  48. smart_bot_factory/integrations/openai_client.py +435 -0
  49. smart_bot_factory/integrations/supabase_client.py +592 -0
  50. smart_bot_factory/setup_checker.py +476 -0
  51. smart_bot_factory/utils/__init__.py +9 -0
  52. smart_bot_factory/utils/debug_routing.py +103 -0
  53. smart_bot_factory/utils/prompt_loader.py +427 -0
  54. smart_bot_factory/uv.lock +2004 -0
  55. smart_bot_factory-0.1.3.dist-info/METADATA +126 -0
  56. smart_bot_factory-0.1.3.dist-info/RECORD +59 -0
  57. smart_bot_factory-0.1.3.dist-info/licenses/LICENSE +24 -0
  58. smart_bot_factory-0.1.2.dist-info/METADATA +0 -31
  59. smart_bot_factory-0.1.2.dist-info/RECORD +0 -4
  60. {smart_bot_factory-0.1.2.dist-info → smart_bot_factory-0.1.3.dist-info}/WHEEL +0 -0
  61. {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
+ }