smart-bot-factory 1.1.1__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.
Files changed (73) hide show
  1. smart_bot_factory/__init__.py +3 -0
  2. smart_bot_factory/admin/__init__.py +18 -0
  3. smart_bot_factory/admin/admin_events.py +1223 -0
  4. smart_bot_factory/admin/admin_logic.py +553 -0
  5. smart_bot_factory/admin/admin_manager.py +156 -0
  6. smart_bot_factory/admin/admin_tester.py +157 -0
  7. smart_bot_factory/admin/timeout_checker.py +547 -0
  8. smart_bot_factory/aiogram_calendar/__init__.py +14 -0
  9. smart_bot_factory/aiogram_calendar/common.py +64 -0
  10. smart_bot_factory/aiogram_calendar/dialog_calendar.py +259 -0
  11. smart_bot_factory/aiogram_calendar/schemas.py +99 -0
  12. smart_bot_factory/aiogram_calendar/simple_calendar.py +224 -0
  13. smart_bot_factory/analytics/analytics_manager.py +414 -0
  14. smart_bot_factory/cli.py +806 -0
  15. smart_bot_factory/config.py +258 -0
  16. smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +16 -0
  17. smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +582 -0
  18. smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +66 -0
  19. smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +212 -0
  20. smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +28 -0
  21. smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +8 -0
  22. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +818 -0
  23. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +32 -0
  24. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +35 -0
  25. smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +133 -0
  26. smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +108 -0
  27. smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +46 -0
  28. smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +16 -0
  29. 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
  30. smart_bot_factory/core/bot_utils.py +1108 -0
  31. smart_bot_factory/core/conversation_manager.py +653 -0
  32. smart_bot_factory/core/decorators.py +2464 -0
  33. smart_bot_factory/core/message_sender.py +729 -0
  34. smart_bot_factory/core/router.py +347 -0
  35. smart_bot_factory/core/router_manager.py +218 -0
  36. smart_bot_factory/core/states.py +27 -0
  37. smart_bot_factory/creation/__init__.py +7 -0
  38. smart_bot_factory/creation/bot_builder.py +1093 -0
  39. smart_bot_factory/creation/bot_testing.py +1122 -0
  40. smart_bot_factory/dashboard/__init__.py +3 -0
  41. smart_bot_factory/event/__init__.py +7 -0
  42. smart_bot_factory/handlers/handlers.py +2013 -0
  43. smart_bot_factory/integrations/langchain_openai.py +542 -0
  44. smart_bot_factory/integrations/openai_client.py +513 -0
  45. smart_bot_factory/integrations/supabase_client.py +1678 -0
  46. smart_bot_factory/memory/__init__.py +8 -0
  47. smart_bot_factory/memory/memory_manager.py +299 -0
  48. smart_bot_factory/memory/static_memory.py +214 -0
  49. smart_bot_factory/message/__init__.py +56 -0
  50. smart_bot_factory/rag/__init__.py +5 -0
  51. smart_bot_factory/rag/decorators.py +29 -0
  52. smart_bot_factory/rag/router.py +54 -0
  53. smart_bot_factory/rag/templates/__init__.py +3 -0
  54. smart_bot_factory/rag/templates/create_table.sql +7 -0
  55. smart_bot_factory/rag/templates/create_table_and_function_template.py +94 -0
  56. smart_bot_factory/rag/templates/match_function.sql +61 -0
  57. smart_bot_factory/rag/templates/match_services_template.py +82 -0
  58. smart_bot_factory/rag/vectorstore.py +449 -0
  59. smart_bot_factory/router/__init__.py +10 -0
  60. smart_bot_factory/setup_checker.py +512 -0
  61. smart_bot_factory/supabase/__init__.py +7 -0
  62. smart_bot_factory/supabase/client.py +631 -0
  63. smart_bot_factory/utils/__init__.py +11 -0
  64. smart_bot_factory/utils/debug_routing.py +114 -0
  65. smart_bot_factory/utils/prompt_loader.py +529 -0
  66. smart_bot_factory/utils/tool_router.py +68 -0
  67. smart_bot_factory/utils/user_prompt_loader.py +55 -0
  68. smart_bot_factory/utm_link_generator.py +123 -0
  69. smart_bot_factory-1.1.1.dist-info/METADATA +1135 -0
  70. smart_bot_factory-1.1.1.dist-info/RECORD +73 -0
  71. smart_bot_factory-1.1.1.dist-info/WHEEL +4 -0
  72. smart_bot_factory-1.1.1.dist-info/entry_points.txt +2 -0
  73. smart_bot_factory-1.1.1.dist-info/licenses/LICENSE +24 -0
@@ -0,0 +1,1135 @@
1
+ Metadata-Version: 2.4
2
+ Name: smart-bot-factory
3
+ Version: 1.1.1
4
+ Summary: Библиотека для создания умных чат-ботов
5
+ Author-email: Kopatych <eserov73@gmail.com>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: chatbot,cli,openai,supabase,telegram
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Communications :: Chat
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.9
23
+ Requires-Dist: aiofiles>=23.0.0
24
+ Requires-Dist: aiogram-media-group>=0.5.1
25
+ Requires-Dist: aiogram>=3.4.1
26
+ Requires-Dist: beautifulsoup4>=4.14.2
27
+ Requires-Dist: click>=8.0.0
28
+ Requires-Dist: httpx>=0.28.1
29
+ Requires-Dist: langchain-community>=0.3.31
30
+ Requires-Dist: langchain-openai>=0.3.35
31
+ Requires-Dist: langchain>=0.3.27
32
+ Requires-Dist: openai>=1.12.0
33
+ Requires-Dist: playwright>=1.55.0
34
+ Requires-Dist: project-root-finder>=1.9
35
+ Requires-Dist: python-dateutil>=2.9.0.post0
36
+ Requires-Dist: python-dotenv>=1.0.1
37
+ Requires-Dist: python-slugify>=8.0.4
38
+ Requires-Dist: pytz>=2023.3
39
+ Requires-Dist: pyyaml>=6.0.2
40
+ Requires-Dist: supabase>=2.3.4
41
+ Description-Content-Type: text/markdown
42
+
43
+ # Smart Bot Factory
44
+
45
+ ## Библиотека для создания умных Telegram ботов с AI, аналитикой и гибкой архитектурой
46
+
47
+ ## 📋 Содержание
48
+
49
+ - [Установка](#-установка)
50
+ - [Быстрый старт](#-быстрый-старт)
51
+ - [CLI Команды](#-cli-команды)
52
+ - [Декораторы](#-декораторы)
53
+ - [event_handler](#event_handler---обработчики-событий)
54
+ - [schedule_task](#schedule_task---запланированные-задачи)
55
+ - [global_handler](#global_handler---глобальные-обработчики)
56
+ - [Dashboard Info](#-dashboard-info---отправка-данных-в-дашборд)
57
+ - [Хуки для кастомизации](#-хуки-для-кастомизации)
58
+ - [Telegram роутеры](#-telegram-роутеры)
59
+ - [Расширенные возможности](#-расширенные-возможности)
60
+
61
+ ---
62
+
63
+ ## 🚀 Установка
64
+
65
+ ```bash
66
+ pip install smart_bot_factory
67
+ ```
68
+
69
+ ## ⚡ Быстрый старт
70
+
71
+ ### 1. Создание бота через CLI
72
+
73
+ ```bash
74
+ # Создать структуру нового бота
75
+ sbf create my-bot
76
+
77
+ # Настроить .env файл
78
+ sbf config my-bot
79
+
80
+ # Запустить бота
81
+ sbf run my-bot
82
+ ```
83
+
84
+ ### 2. Минимальный код бота
85
+
86
+ ```python
87
+ """my-bot.py"""
88
+ import asyncio
89
+ from smart_bot_factory.router import EventRouter
90
+ from smart_bot_factory.message import send_message_by_human
91
+ from smart_bot_factory.creation import BotBuilder
92
+
93
+ # Инициализация
94
+ event_router = EventRouter("my-bot")
95
+ bot_builder = BotBuilder("my-bot")
96
+
97
+ @event_router.event_handler("collect_phone", once_only=True)
98
+ async def handle_phone(user_id: int, phone: str):
99
+ """ИИ создает: {"тип": "collect_phone", "инфо": "+79001234567"}"""
100
+ await send_message_by_human(
101
+ user_id=user_id,
102
+ message_text=f"✅ Телефон {phone} сохранен"
103
+ )
104
+ return {"status": "success"}
105
+
106
+ async def main():
107
+ bot_builder.register_routers(event_router)
108
+ await bot_builder.build()
109
+ await bot_builder.start()
110
+
111
+ if __name__ == "__main__":
112
+ asyncio.run(main())
113
+ ```
114
+
115
+ ---
116
+
117
+ ## 🎮 CLI Команды
118
+
119
+ ### Создание бота
120
+
121
+ ```bash
122
+ # Создать нового бота из базового шаблона
123
+ sbf create my-bot
124
+
125
+ # Скопировать существующего бота
126
+ sbf copy best-valera new-valera
127
+
128
+ ```
129
+
130
+ **💡 Команда `copy` - создает нового бота на основе существующего:**
131
+
132
+ - ✅ Копирует код бота с автозаменой `bot_id`
133
+ - ✅ Копирует все промпты
134
+ - ✅ Копирует тесты и файлы
135
+ - ✅ Создает новый `.env` (не копирует токены)
136
+
137
+ ### Управление ботами
138
+
139
+ ```bash
140
+ # Показать список ботов
141
+ sbf list
142
+
143
+ # Запустить бота
144
+ sbf run my-bot
145
+
146
+ # Удалить бота
147
+ sbf rm my-bot
148
+
149
+ # Удалить без подтверждения
150
+ sbf rm my-bot --force
151
+ ```
152
+
153
+ ### Настройка
154
+
155
+ ```bash
156
+ # Открыть .env файл в редакторе
157
+ sbf config my-bot
158
+
159
+ # Управление промптами
160
+ sbf prompts my-bot # Список промптов
161
+ sbf prompts my-bot --edit system # Редактировать промпт
162
+ sbf prompts my-bot --add custom # Добавить новый промпт
163
+ ```
164
+
165
+ ### Тестирование
166
+
167
+ ```bash
168
+ # Запустить все тесты
169
+ sbf test my-bot
170
+
171
+ # Запустить конкретный файл
172
+ sbf test my-bot --file test_booking.yaml
173
+
174
+ # Подробный вывод
175
+ sbf test my-bot -v
176
+ ```
177
+
178
+ ### Утилиты
179
+
180
+ ```bash
181
+ # Показать путь к проекту
182
+ sbf path
183
+
184
+ # Создать UTM ссылку
185
+ sbf link
186
+ ```
187
+
188
+ ---
189
+
190
+ ## 📦 Декораторы
191
+
192
+ ### `event_handler` - Обработчики событий
193
+
194
+ **Назначение:** Обрабатывают события от ИИ немедленно (как только ИИ создает событие).
195
+
196
+ **Сигнатура:**
197
+
198
+ ```python
199
+ @event_router.event_handler(
200
+ event_type: str, # Тип события
201
+ notify: bool = False, # Уведомлять админов
202
+ once_only: bool = True, # Выполнять только 1 раз
203
+ send_ai_response: bool = True # Отправлять ответ от ИИ
204
+ )
205
+ async def handler(user_id: int, event_data: str):
206
+ # Ваш код
207
+ return {"status": "success"}
208
+ ```
209
+
210
+ **Параметры:**
211
+
212
+ - **`event_type`** (обязательный) - Уникальное имя события
213
+ - **`notify`** (по умолчанию `False`) - Отправлять уведомление админам после выполнения
214
+ - **`once_only`** (по умолчанию `True`) - Если `True`, событие выполнится только 1 раз для пользователя
215
+ - **`send_ai_response`** (по умолчанию `True`) - Если `False`, ИИ НЕ отправит сообщение после выполнения обработчика
216
+
217
+ **Как работает:**
218
+
219
+ 1. ИИ создает событие в JSON: `{"тип": "collect_phone", "инфо": "+79001234567"}`
220
+ 2. Обработчик вызывается **немедленно**
221
+ 3. Событие сохраняется в БД со статусом `completed`
222
+ 4. Если `once_only=True` - повторные события блокируются
223
+
224
+ **Примеры:**
225
+
226
+ ```python
227
+ # Базовый пример
228
+ @event_router.event_handler("collect_phone")
229
+ async def save_phone(user_id: int, phone_number: str):
230
+ """Сохраняет телефон клиента"""
231
+ await send_message_by_human(
232
+ user_id=user_id,
233
+ message_text=f"✅ Телефон {phone_number} сохранен"
234
+ )
235
+ return {"status": "success", "phone": phone_number}
236
+
237
+ # С уведомлением админов
238
+ @event_router.event_handler("new_lead", notify=True, once_only=True)
239
+ async def process_lead(user_id: int, lead_info: str):
240
+ """Обрабатывает нового лида"""
241
+ # Админы получат уведомление автоматически
242
+ return {"status": "lead_created", "info": lead_info}
243
+
244
+ # Может выполняться многократно
245
+ @event_router.event_handler("ask_question", once_only=False)
246
+ async def handle_question(user_id: int, question: str):
247
+ """Обрабатывает вопросы (может быть много)"""
248
+ # Логика обработки
249
+ return {"status": "answered"}
250
+
251
+ # БЕЗ отправки ответа от ИИ
252
+ @event_router.event_handler("silent_event", send_ai_response=False)
253
+ async def handle_silent(user_id: int, event_data: str):
254
+ """
255
+ Выполняет логику БЕЗ отправки сообщения от ИИ
256
+ Используйте когда хотите только собрать данные без ответа пользователю
257
+ """
258
+ await send_message_by_human(user_id, "✅ Данные сохранены")
259
+ return {"status": "saved"}
260
+ ```
261
+
262
+ ---
263
+
264
+ ### `schedule_task` - Запланированные задачи
265
+
266
+ **Назначение:** Выполняются через заданное время после создания события.
267
+
268
+ **Сигнатура:**
269
+
270
+ ```python
271
+ @event_router.schedule_task(
272
+ task_name: str, # Название задачи
273
+ delay: Union[str, int], # Задержка: "1h 30m" или секунды
274
+ notify: bool = False, # Уведомлять админов
275
+ smart_check: bool = True, # Умная проверка активности
276
+ once_only: bool = True, # Выполнять только 1 раз
277
+ event_type: Union[str, Callable] = None, # Источник времени события
278
+ send_ai_response: bool = True # Отправлять ответ от ИИ
279
+ )
280
+ async def handler(user_id: int, user_data: str):
281
+ # Ваш код
282
+ return {"status": "sent"}
283
+ ```
284
+
285
+ **Параметры:**
286
+
287
+ - **`task_name`** (обязательный) - Уникальное имя задачи
288
+ - **`delay`** (обязательный) - Задержка выполнения:
289
+ - Строка: `"1h 30m"`, `"2h"`, `"45m"`, `"30s"`
290
+ - Число: `3600` (секунды)
291
+ - **`notify`** (по умолчанию `False`) - Уведомлять админов
292
+ - **`smart_check`** (по умолчанию `True`) - Умная проверка:
293
+ - Отменяет задачу если пользователь перешел на новый этап
294
+ - Переносит задачу если пользователь был активен
295
+ - **`once_only`** (по умолчанию `True`) - Выполнять только 1 раз для пользователя
296
+ - **`event_type`** (опционально) - Источник времени события:
297
+ - **Строка**: `"appointment_booking"` - ищет событие в БД и вычисляет время
298
+ - **Функция**: `async def(user_id, user_data) -> datetime` - кастомная логика
299
+ - **`send_ai_response`** (по умолчанию `True`) - Если `False`, ИИ НЕ отправит сообщение после выполнения задачи
300
+
301
+ **Формула времени с `event_type`:**
302
+
303
+ ```text
304
+ reminder_time = event_datetime - delay
305
+ ```
306
+
307
+ **Примеры:**
308
+
309
+ ```python
310
+ # Простое напоминание через 24 часа
311
+ @event_router.schedule_task("follow_up", delay="24h")
312
+ async def send_follow_up(user_id: int, reminder_text: str):
313
+ """
314
+ ИИ создает: {"тип": "follow_up", "инфо": "Не забудьте про запись"}
315
+ Выполнится через 24 часа
316
+ """
317
+ await send_message_by_human(
318
+ user_id=user_id,
319
+ message_text=f"👋 {reminder_text}"
320
+ )
321
+ return {"status": "sent"}
322
+
323
+ # Напоминание относительно события из БД
324
+ @event_router.schedule_task(
325
+ "booking_reminder",
326
+ delay="2h", # За 2 часа до записи
327
+ event_type="appointment_booking" # Ищет в БД событие типа "appointment_booking"
328
+ )
329
+ async def remind_booking(user_id: int, user_data: str):
330
+ """
331
+ ИИ создает событие: {"тип": "appointment_booking", "инфо": "дата: 2025-10-15, время: 19:00"}
332
+ Затем создает: {"тип": "booking_reminder", "инфо": ""}
333
+
334
+ Логика:
335
+ 1. Находит в БД последнее событие "appointment_booking" для user_id
336
+ 2. Парсит из него datetime: 2025-10-15 19:00
337
+ 3. Вычисляет: reminder_time = 19:00 - 2h = 17:00
338
+ 4. Отправляет напоминание в 17:00
339
+ """
340
+ await send_message_by_human(
341
+ user_id=user_id,
342
+ message_text="⏰ Напоминаю о записи через 2 часа!"
343
+ )
344
+ return {"status": "sent"}
345
+
346
+ # Напоминание с кастомной функцией получения времени
347
+ async def get_booking_from_api(user_id: int, user_data: str) -> datetime:
348
+ """Получает время записи из внешнего API"""
349
+ from yclients_api import get_next_booking
350
+ booking = await get_next_booking(user_id)
351
+ return booking['datetime'] # datetime объект
352
+
353
+ @event_router.schedule_task(
354
+ "api_reminder",
355
+ delay="1h",
356
+ event_type=get_booking_from_api # Функция вместо строки
357
+ )
358
+ async def send_api_reminder(user_id: int, user_data: str):
359
+ """
360
+ ИИ создает: {"тип": "api_reminder", "инфо": ""}
361
+
362
+ Логика:
363
+ 1. Вызывается get_booking_from_api(user_id, "")
364
+ 2. Функция возвращает datetime из API
365
+ 3. Вычисляется: reminder_time = api_datetime - 1h
366
+ 4. Отправляется в вычисленное время
367
+ """
368
+ await send_message_by_human(user_id, "⏰ Напоминание из API!")
369
+ return {"status": "sent"}
370
+
371
+ # Без smart_check (отправить в любом случае)
372
+ @event_router.schedule_task("important_reminder", delay="12h", smart_check=False)
373
+ async def important_reminder(user_id: int, text: str):
374
+ """Отправится в любом случае, даже если пользователь активен"""
375
+ await send_message_by_human(user_id, f"🔔 {text}")
376
+ return {"status": "sent"}
377
+ ```
378
+
379
+ ---
380
+
381
+ ### `global_handler` - Глобальные обработчики
382
+
383
+ **Назначение:** Выполняются для всех пользователей одновременно.
384
+
385
+ **Сигнатура:**
386
+
387
+ ```python
388
+ @event_router.global_handler(
389
+ handler_type: str, # Тип обработчика
390
+ delay: Union[str, int], # Задержка
391
+ notify: bool = False, # Уведомлять админов
392
+ once_only: bool = True, # Выполнять только 1 раз
393
+ event_type: Union[str, Callable] = None, # Источник времени
394
+ send_ai_response: bool = True # Отправлять ответ от ИИ
395
+ )
396
+ async def handler(handler_data: str):
397
+ # Ваш код
398
+ return {"status": "sent"}
399
+ ```
400
+
401
+ **Отличия от `schedule_task`:**
402
+
403
+ - **Нет `user_id`** - работает глобально
404
+ - **Нет `smart_check`** - не привязан к активности пользователя
405
+ - Одно выполнение = одна рассылка всем
406
+
407
+ **Примеры:**
408
+
409
+ ```python
410
+ # Рассылка всем через 2 часа
411
+ @event_router.global_handler("promo_announcement", delay="2h", notify=True)
412
+ async def send_promo(announcement_text: str):
413
+ """
414
+ ИИ создает: {"тип": "promo_announcement", "инфо": "Скидка 20%!"}
415
+ Отправится всем через 2 часа
416
+ """
417
+ await send_message_to_users_by_stage(
418
+ stage="all",
419
+ message_text=f"🎉 {announcement_text}",
420
+ bot_id="my-bot"
421
+ )
422
+ return {"status": "sent", "recipients": "all"}
423
+
424
+ # С кастомной функцией времени
425
+ async def get_promo_end_time(handler_data: str) -> datetime:
426
+ """Получает время окончания акции из CRM"""
427
+ from crm_api import get_active_promo
428
+ promo = await get_active_promo()
429
+ return promo['end_datetime']
430
+
431
+ @event_router.global_handler(
432
+ "promo_ending_notification",
433
+ delay="2h",
434
+ event_type=get_promo_end_time
435
+ )
436
+ async def notify_promo_ending(handler_data: str):
437
+ """Уведомление всем за 2 часа до окончания акции"""
438
+ await send_message_to_users_by_stage(
439
+ stage="all",
440
+ message_text="⏰ Акция заканчивается через 2 часа!",
441
+ bot_id="my-bot"
442
+ )
443
+ return {"status": "sent"}
444
+ ```
445
+
446
+ ---
447
+
448
+ ## 📊 Dashboard Info - Отправка данных в дашборд
449
+
450
+ **Назначение:** Позволяет отправлять информацию о событиях в дашборд (таблица `scheduled_events`, столбец `info_dashboard`) для аналитики и мониторинга.
451
+
452
+ ### Как работает
453
+
454
+ 1. Обработчик события возвращает результат с полем `'info'`
455
+ 2. Система автоматически извлекает это поле и записывает в `info_dashboard` таблицы
456
+ 3. Функция `prepare_dashboard_info` автоматически:
457
+ - Получает `username` из таблицы `sales_users`
458
+ - Форматирует строку с подстановкой данных
459
+ - Добавляет московское время (UTC+3)
460
+
461
+ ### Сигнатура
462
+
463
+ ```python
464
+ from smart_bot_factory.dashboard import prepare_dashboard_info
465
+
466
+ dashboard_data = await prepare_dashboard_info(
467
+ description_template: str, # Строка с {username}, например "{username} купил подписку"
468
+ title: str, # Заголовок для дашборда
469
+ user_id: int # Telegram ID пользователя
470
+ )
471
+ ```
472
+
473
+ **Возвращает:**
474
+
475
+ ```python
476
+ {
477
+ 'title': 'Заголовок',
478
+ 'description': '@username123 купил подписку', # С подстановкой реального username
479
+ 'created_at': '2025-10-18T15:30:45.123456+03:00' # Московское время
480
+ }
481
+ ```
482
+
483
+ ### Примеры использования
484
+
485
+ #### С event_handler
486
+
487
+ ```python
488
+ from smart_bot_factory.dashboard import prepare_dashboard_info
489
+
490
+ @event_router.event_handler("collect_phone", notify=True, once_only=True)
491
+ async def handle_phone_collection(user_id: int, phone_number: str):
492
+ """Сохраняет телефон клиента"""
493
+
494
+ # Ваша бизнес-логика
495
+ session = await supabase_client.get_active_session(user_id)
496
+ if session:
497
+ metadata = session.get('metadata', {})
498
+ metadata['phone'] = phone_number
499
+ await supabase_client.update_session_metadata(session['id'], metadata)
500
+
501
+ await send_message_by_human(
502
+ user_id=user_id,
503
+ message_text=f"✅ Спасибо! Ваш номер {phone_number} сохранен"
504
+ )
505
+
506
+ # 📊 Возвращаем результат С данными для дашборда
507
+ return {
508
+ "status": "success",
509
+ "phone": phone_number,
510
+ "info": await prepare_dashboard_info(
511
+ description_template="{username} оставил номер телефона",
512
+ title="Новый контакт",
513
+ user_id=user_id
514
+ )
515
+ }
516
+ ```
517
+
518
+ #### С schedule_task
519
+
520
+ ```python
521
+ @event_router.schedule_task("follow_up", delay="24h", smart_check=True)
522
+ async def send_follow_up(user_id: int, reminder_text: str):
523
+ """Напоминание через 24 часа"""
524
+
525
+ await send_message_by_human(
526
+ user_id=user_id,
527
+ message_text=f"👋 {reminder_text}"
528
+ )
529
+
530
+ # 📊 Работает и для задач!
531
+ return {
532
+ "status": "sent",
533
+ "type": "follow_up",
534
+ "info": await prepare_dashboard_info(
535
+ description_template="{username} получил напоминание",
536
+ title="Напоминание отправлено",
537
+ user_id=user_id
538
+ )
539
+ }
540
+ ```
541
+
542
+ #### БЕЗ дашборда
543
+
544
+ Если не нужно отправлять данные в дашборд - просто не добавляйте поле `'info'`:
545
+
546
+ ```python
547
+ @event_router.event_handler("collect_name", once_only=False)
548
+ async def handle_name_collection(user_id: int, client_name: str):
549
+ """БЕЗ дашборда - просто сохраняем имя"""
550
+
551
+ await send_message_by_human(
552
+ user_id=user_id,
553
+ message_text=f"✅ Спасибо! Ваше имя {client_name} сохранено"
554
+ )
555
+
556
+ # Возвращаем БЕЗ поля 'info' - дашборд останется пустым
557
+ return {"status": "success"}
558
+ ```
559
+
560
+ ### Что попадает в БД
561
+
562
+ **События С дашбордом:**
563
+
564
+ ```sql
565
+ SELECT * FROM scheduled_events WHERE id = '123';
566
+
567
+ id: 123
568
+ event_type: collect_phone
569
+ event_category: user_event
570
+ user_id: 12345
571
+ status: completed
572
+ result_data: {"status": "success", "phone": "+79001234567", "info": {...}}
573
+ info_dashboard: {
574
+ "title": "Новый контакт",
575
+ "description": "@username123 оставил номер телефона",
576
+ "created_at": "2025-10-18T15:30:45+03:00"
577
+ }
578
+ ```
579
+
580
+ **События БЕЗ дашборда:**
581
+
582
+ ```sql
583
+ SELECT * FROM scheduled_events WHERE id = '124';
584
+
585
+ id: 124
586
+ event_type: collect_name
587
+ event_category: user_event
588
+ user_id: 12345
589
+ status: completed
590
+ result_data: {"status": "success"}
591
+ info_dashboard: NULL ← Остается пустым
592
+ ```
593
+
594
+ ### Форматирование строк
595
+
596
+ Функция `prepare_dashboard_info` поддерживает подстановку `{username}`:
597
+
598
+ ```python
599
+ # Примеры шаблонов:
600
+ "{username} купил подписку на 1 год"
601
+ "{username} оставил контакт"
602
+ "{username} записался на консультацию"
603
+ "{username} задал вопрос о продукте"
604
+ "{username} завершил оплату"
605
+
606
+ # После подстановки:
607
+ "@user123 купил подписку на 1 год"
608
+ "@ivan_petrov оставил контакт"
609
+ ```
610
+
611
+ Если пользователь не найден в `sales_users` - будет использован fallback: `user_12345`
612
+
613
+ ---
614
+
615
+ ## 🎣 Хуки для кастомизации
616
+
617
+ Хуки позволяют внедрять свою логику в стандартную обработку сообщений без переписывания всей функции.
618
+
619
+ ### Доступные хуки
620
+
621
+ ```python
622
+ bot_builder = BotBuilder("my-bot")
623
+
624
+ # 1. Валидация сообщения (ДО обработки AI)
625
+ @bot_builder.validate_message
626
+ async def check_spam(message_text: str, message_obj):
627
+ if "спам" in message_text.lower():
628
+ await message_obj.answer("⛔ Спам запрещен")
629
+ return False # Блокировать обработку
630
+ return True # Продолжить
631
+
632
+ # 2. Обогащение системного промпта
633
+ @bot_builder.enrich_prompt
634
+ async def add_client_info(system_prompt: str, user_id: int):
635
+ session = await supabase_client.get_active_session(user_id)
636
+ phone = session.get('metadata', {}).get('phone')
637
+
638
+ if phone:
639
+ return f"{system_prompt}\n\nТелефон клиента: {phone}"
640
+ return system_prompt
641
+
642
+ # 3. Обогащение контекста для AI
643
+ @bot_builder.enrich_context
644
+ async def add_external_data(messages: list):
645
+ # Добавляем данные из внешнего API
646
+ messages.append({
647
+ "role": "system",
648
+ "content": "Дополнительная информация из CRM..."
649
+ })
650
+ return messages
651
+
652
+ # 4. Обработка ответа AI
653
+ @bot_builder.process_response
654
+ async def modify_response(response_text: str, ai_metadata: dict, user_id: int):
655
+ # Модифицируем ответ
656
+ if "цена" in response_text.lower():
657
+ response_text += "\n\n💰 Актуальные цены на сайте"
658
+ return response_text, ai_metadata
659
+
660
+ # 5. Фильтры отправки
661
+ @bot_builder.filter_send
662
+ async def block_during_booking(user_id: int):
663
+ if is_processing_booking(user_id):
664
+ return True # Блокировать отправку
665
+ return False # Разрешить
666
+
667
+ # 6. Кастомная логика после /start
668
+ @bot_builder.on_start
669
+ async def custom_start(user_id: int, session_id: str, message, state):
670
+ """Вызывается ПОСЛЕ стандартного /start"""
671
+ keyboard = InlineKeyboardMarkup(...)
672
+ await message.answer("Выберите действие:", reply_markup=keyboard)
673
+ ```
674
+
675
+ ---
676
+
677
+ ## 📱 Telegram роутеры
678
+
679
+ Подключайте чистые `aiogram.Router` для кастомных команд, callback'ов и фильтров.
680
+
681
+ ### Создание роутера
682
+
683
+ ```python
684
+ from aiogram import Router, F
685
+ from aiogram.filters import Command
686
+ from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
687
+
688
+ # Создаем aiogram Router
689
+ telegram_router = Router(name="my_commands")
690
+
691
+ @telegram_router.message(Command("price", "цена"))
692
+ async def price_handler(message: Message):
693
+ """Команда /price"""
694
+ await message.answer(
695
+ "💰 Наши цены:\n"
696
+ "• Услуга 1 - 1000₽\n"
697
+ "• Услуга 2 - 2000₽"
698
+ )
699
+
700
+ @telegram_router.message(Command("catalog"))
701
+ async def catalog_handler(message: Message):
702
+ """Команда /catalog с кнопками"""
703
+ keyboard = InlineKeyboardMarkup(inline_keyboard=[
704
+ [InlineKeyboardButton(text="🔥 Акции", callback_data="promo")],
705
+ [InlineKeyboardButton(text="📅 Записаться", callback_data="book")]
706
+ ])
707
+ await message.answer("Выберите:", reply_markup=keyboard)
708
+
709
+ @telegram_router.callback_query(F.data == "book")
710
+ async def handle_booking(callback: CallbackQuery):
711
+ """Обработка кнопки"""
712
+ await callback.answer("Записываю...")
713
+ await callback.message.answer("Напишите желаемую дату")
714
+
715
+ @telegram_router.message(F.text.lower().contains("помощь"))
716
+ async def help_handler(message: Message):
717
+ """Реагирует на слово 'помощь'"""
718
+ await message.answer("Чем могу помочь?")
719
+
720
+ # Регистрация в боте
721
+ bot_builder.register_telegram_router(telegram_router)
722
+ ```
723
+
724
+ ### Множественная регистрация
725
+
726
+ ```python
727
+ commands_router = Router(name="commands")
728
+ callbacks_router = Router(name="callbacks")
729
+ filters_router = Router(name="filters")
730
+
731
+ # Регистрируем все сразу
732
+ bot_builder.register_telegram_routers(
733
+ commands_router,
734
+ callbacks_router,
735
+ filters_router
736
+ )
737
+ ```
738
+
739
+ **⚠️ Важно:** Пользовательские роутеры подключаются **ПЕРВЫМИ** (высший приоритет), поэтому ваши команды обрабатываются раньше стандартных.
740
+
741
+ ---
742
+
743
+ ## 🔧 Расширенные возможности
744
+
745
+ ### Кастомный PromptLoader
746
+
747
+ Создайте свой загрузчик промптов с автоматическим определением пути:
748
+
749
+ ```python
750
+ from smart_bot_factory.utils import UserPromptLoader
751
+
752
+ # Автоматически найдет bots/my-bot/prompts
753
+ custom_loader = UserPromptLoader("my-bot")
754
+
755
+ # Или наследуйтесь для кастомизации
756
+ class MyPromptLoader(UserPromptLoader):
757
+ def __init__(self, bot_id):
758
+ super().__init__(bot_id)
759
+ self.extra_file = self.prompts_dir / 'extra.txt'
760
+
761
+ my_loader = MyPromptLoader("my-bot")
762
+
763
+ # Установите ДО build()
764
+ bot_builder.set_prompt_loader(my_loader)
765
+ ```
766
+
767
+ ### Полная замена обработки событий
768
+
769
+ Замените стандартную функцию `process_events`:
770
+
771
+ ```python
772
+ from smart_bot_factory.message import get_bot
773
+ from smart_bot_factory.core.decorators import execute_event_handler
774
+
775
+ async def my_process_events(session_id, events, user_id):
776
+ """Моя кастомная обработка событий"""
777
+ bot = get_bot()
778
+
779
+ for event in events:
780
+ event_type = event.get('тип')
781
+
782
+ if event_type == 'booking':
783
+ # Ваша кастомная логика
784
+ telegram_user = await bot.get_chat(user_id)
785
+ name = telegram_user.first_name
786
+ # ... обработка
787
+ else:
788
+ # Стандартная обработка остальных
789
+ await execute_event_handler(event_type, user_id, event.get('инфо'))
790
+
791
+ # Установите ДО build()
792
+ bot_builder.set_event_processor(my_process_events)
793
+ ```
794
+
795
+ ### Доступ к aiogram Bot
796
+
797
+ Получите прямой доступ к `aiogram.Bot`:
798
+
799
+ ```python
800
+ from smart_bot_factory.message import get_bot
801
+
802
+ @event_router.event_handler("check_user")
803
+ async def get_user_info(user_id: int, event_data: str):
804
+ """Получает информацию из Telegram"""
805
+ bot = get_bot()
806
+
807
+ # Используем любые методы aiogram Bot
808
+ telegram_user = await bot.get_chat(user_id)
809
+ name = telegram_user.first_name
810
+ username = telegram_user.username
811
+
812
+ await bot.send_message(user_id, f"Привет, {name}!")
813
+ return {"name": name, "username": username}
814
+ ```
815
+
816
+ ### Отправка сообщений с файлами
817
+
818
+ ```python
819
+ from smart_bot_factory.message import send_message
820
+
821
+ @event_router.event_handler("send_catalog")
822
+ async def send_catalog(user_id: int, event_data: str):
823
+ """Отправляет каталог с файлами"""
824
+ from smart_bot_factory.message import get_bot
825
+ from smart_bot_factory.supabase import SupabaseClient
826
+
827
+ bot = get_bot()
828
+ supabase_client = SupabaseClient("my-bot")
829
+
830
+ # Получаем message объект (для ответа)
831
+ # В реальности используйте message из контекста
832
+
833
+ await send_message(
834
+ message=message, # aiogram Message объект
835
+ text="📁 Вот наш каталог:",
836
+ supabase_client=supabase_client,
837
+ files_list=["catalog.pdf", "price_list.pdf"],
838
+ parse_mode="Markdown"
839
+ )
840
+
841
+ return {"status": "sent"}
842
+ ```
843
+
844
+ ---
845
+
846
+ ## 📚 Полный пример
847
+
848
+ ```python
849
+ """advanced-bot.py - Продвинутый пример"""
850
+
851
+ import asyncio
852
+ from datetime import datetime, timedelta
853
+
854
+ from smart_bot_factory.router import EventRouter
855
+ from smart_bot_factory.message import send_message_by_human, get_bot
856
+ from smart_bot_factory.creation import BotBuilder
857
+ from smart_bot_factory.supabase import SupabaseClient
858
+
859
+ from aiogram import Router, F
860
+ from aiogram.filters import Command
861
+ from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
862
+
863
+ # Инициализация
864
+ event_router = EventRouter("advanced-bot")
865
+ telegram_router = Router(name="commands")
866
+ bot_builder = BotBuilder("advanced-bot")
867
+ supabase_client = SupabaseClient("advanced-bot")
868
+
869
+ # ========== СОБЫТИЯ ==========
870
+
871
+ @event_router.event_handler("collect_phone", notify=True, once_only=True)
872
+ async def save_phone(user_id: int, phone: str):
873
+ session = await supabase_client.get_active_session(user_id)
874
+ if session:
875
+ metadata = session.get('metadata', {})
876
+ metadata['phone'] = phone
877
+ await supabase_client.update_session_metadata(session['id'], metadata)
878
+
879
+ await send_message_by_human(user_id, f"✅ Телефон {phone} сохранен")
880
+ return {"status": "success"}
881
+
882
+ # ========== ЗАДАЧИ ==========
883
+
884
+ async def get_appointment_time(user_id: int, user_data: str) -> datetime:
885
+ """Получает время из YClients API"""
886
+ # Ваша интеграция с YClients
887
+ return datetime.now() + timedelta(hours=24)
888
+
889
+ @event_router.schedule_task(
890
+ "appointment_reminder",
891
+ delay="2h",
892
+ event_type=get_appointment_time,
893
+ smart_check=False
894
+ )
895
+ async def remind_appointment(user_id: int, user_data: str):
896
+ await send_message_by_human(user_id, "⏰ Запись через 2 часа!")
897
+ return {"status": "sent"}
898
+
899
+ # ========== ГЛОБАЛЬНЫЕ ==========
900
+
901
+ @event_router.global_handler("daily_promo", delay="24h", once_only=False)
902
+ async def daily_promo(text: str):
903
+ await send_message_to_users_by_stage(
904
+ stage="all",
905
+ message_text=f"🎉 {text}",
906
+ bot_id="advanced-bot"
907
+ )
908
+ return {"status": "sent"}
909
+
910
+ # ========== TELEGRAM КОМАНДЫ ==========
911
+
912
+ @telegram_router.message(Command("price"))
913
+ async def price_cmd(message: Message):
914
+ await message.answer("💰 Цены: ...")
915
+
916
+ @telegram_router.callback_query(F.data == "book")
917
+ async def booking_callback(callback):
918
+ await callback.answer("Записываю...")
919
+
920
+ # ========== ХУКИ ==========
921
+
922
+ @bot_builder.validate_message
923
+ async def check_business_hours(message_text: str, message_obj):
924
+ """Проверка рабочих часов"""
925
+ hour = datetime.now().hour
926
+ if hour < 9 or hour > 21:
927
+ await message_obj.answer("Мы работаем с 9:00 до 21:00")
928
+ return False
929
+ return True
930
+
931
+ @bot_builder.enrich_prompt
932
+ async def add_client_data(system_prompt: str, user_id: int):
933
+ """Добавляет данные клиента в промпт"""
934
+ session = await supabase_client.get_active_session(user_id)
935
+ phone = session.get('metadata', {}).get('phone')
936
+
937
+ if phone:
938
+ return f"{system_prompt}\n\nТелефон клиента: {phone}"
939
+ return system_prompt
940
+
941
+ # ========== ЗАПУСК ==========
942
+
943
+ async def main():
944
+ # Регистрация
945
+ bot_builder.register_routers(event_router)
946
+ bot_builder.register_telegram_router(telegram_router)
947
+
948
+ # Кастомизация (опционально)
949
+ # from smart_bot_factory.utils import UserPromptLoader
950
+ # bot_builder.set_prompt_loader(UserPromptLoader("advanced-bot"))
951
+
952
+ # Сборка и запуск
953
+ await bot_builder.build()
954
+ await bot_builder.start()
955
+
956
+ if __name__ == "__main__":
957
+ asyncio.run(main())
958
+ ```
959
+
960
+ ---
961
+
962
+ ## 📖 Структура проекта
963
+
964
+ ```text
965
+ project/
966
+ ├── bots/
967
+ │ └── my-bot/
968
+ │ ├── prompts/ # Промпты для AI
969
+ │ │ ├── system_prompt.txt
970
+ │ │ ├── welcome_message.txt
971
+ │ │ └── final_instructions.txt
972
+ │ ├── tests/ # YAML тесты
973
+ │ ├── welcome_files/ # Файлы приветствия
974
+ │ ├── files/ # Файлы для отправки
975
+ │ └── .env # Конфигурация
976
+ ├── my-bot.py # Код бота
977
+ └── .env # Глобальная конфигурация (опционально)
978
+ ```
979
+
980
+ ---
981
+
982
+ ## ⚙️ Конфигурация (.env)
983
+
984
+ ```bash
985
+ # Telegram
986
+ TELEGRAM_BOT_TOKEN=your_token_here
987
+
988
+ # Supabase
989
+ SUPABASE_URL=https://your-project.supabase.co
990
+ SUPABASE_KEY=your_key_here
991
+
992
+ # OpenAI
993
+ OPENAI_API_KEY=sk-your-key
994
+ OPENAI_MODEL=gpt-4o-mini
995
+ OPENAI_MAX_TOKENS=1500
996
+
997
+ # Администраторы (Telegram ID через запятую)
998
+ ADMIN_TELEGRAM_IDS=123456789,987654321
999
+
1000
+ # Режим отладки (показывать JSON)
1001
+ DEBUG_MODE=false
1002
+ ```
1003
+
1004
+ ---
1005
+
1006
+ ## 🎯 Сравнение декораторов
1007
+
1008
+ | Декоратор | Когда выполняется | Для кого | Ключевые параметры |
1009
+ |-----------|-------------------|----------|--------------------|
1010
+ | `@event_handler` | Немедленно | 1 пользователь | `event_type`, `notify`, `once_only`, `send_ai_response` |
1011
+ | `@schedule_task` | Через время | 1 пользователь | `task_name`, `delay`, `event_type`, `smart_check`, `once_only`, `notify`, `send_ai_response` |
1012
+ | `@global_handler` | Через время | Все пользователи | `handler_type`, `delay`, `event_type`, `once_only`, `notify`, `send_ai_response` |
1013
+
1014
+ ---
1015
+
1016
+ ## 🔑 Ключевые концепции
1017
+
1018
+ ### `send_ai_response=True`
1019
+
1020
+ Контролирует отправку сообщения от ИИ после выполнения обработчика:
1021
+
1022
+ - **`True`** (по умолчанию) - ИИ отправит сообщение пользователю после выполнения обработчика
1023
+ - **`False`** - ИИ НЕ отправит сообщение (используйте когда нужна только фоновая обработка или когда отправляете сообщение вручную)
1024
+
1025
+ **Когда использовать `send_ai_response=False`:**
1026
+
1027
+ - Когда нужно только собрать данные без ответа пользователю
1028
+ - Когда вы сами отправляете сообщение через `send_message_by_human()`
1029
+ - Для фоновых задач без взаимодействия с пользователем
1030
+
1031
+ ```python
1032
+ # ИИ отправит сообщение (по умолчанию)
1033
+ @event_router.event_handler("collect_phone")
1034
+ async def save_phone(user_id: int, phone: str):
1035
+ # Сохраняем телефон
1036
+ # ИИ автоматически отправит сообщение после выполнения
1037
+ return {"status": "success"}
1038
+
1039
+ # ИИ НЕ отправит сообщение
1040
+ @event_router.event_handler("collect_name", send_ai_response=False)
1041
+ async def save_name(user_id: int, name: str):
1042
+ # Сохраняем имя
1043
+ await send_message_by_human(user_id, f"✅ Имя {name} сохранено")
1044
+ # ИИ не будет отправлять свое сообщение
1045
+ return {"status": "success"}
1046
+ ```
1047
+
1048
+ ### `once_only=True`
1049
+
1050
+ Гарантирует выполнение события только 1 раз для пользователя:
1051
+
1052
+ - **При сохранении**: Проверяет БД, если есть - не сохраняет
1053
+ - **При выполнении**: Проверяет БД, если есть `completed` - отменяет
1054
+
1055
+ ```python
1056
+ @event_router.event_handler("welcome_bonus", once_only=True)
1057
+ async def give_bonus(user_id: int, bonus_info: str):
1058
+ # Выполнится только 1 раз, даже если пользователь сделает /start заново
1059
+ return {"status": "bonus_given"}
1060
+ ```
1061
+
1062
+ ### `smart_check=True`
1063
+
1064
+ Умная проверка для запланированных задач:
1065
+
1066
+ - **Отменяет** задачу если пользователь перешел на новый этап
1067
+ - **Переносит** задачу если пользователь был недавно активен
1068
+
1069
+ ```python
1070
+ @event_router.schedule_task("follow_up", delay="24h", smart_check=True)
1071
+ async def follow_up(user_id: int, text: str):
1072
+ # Не отправится если пользователь уже был активен
1073
+ return {"status": "sent"}
1074
+ ```
1075
+
1076
+ ### `event_type` - Привязка ко времени события
1077
+
1078
+ Планирует задачу относительно времени события:
1079
+
1080
+ **Строка** - ищет в БД:
1081
+
1082
+ ```python
1083
+ @event_router.schedule_task("reminder", delay="2h", event_type="appointment")
1084
+ async def remind(user_id: int, text: str):
1085
+ # 1. ИИ создает событие: {"тип": "appointment", "инфо": "дата: 2025-10-15, время: 19:00"}
1086
+ # 2. ИИ создает задачу: {"тип": "reminder", "инфо": ""}
1087
+ # 3. Ищется в БД событие "appointment" для user_id
1088
+ # 4. Парсится datetime: 2025-10-15 19:00
1089
+ # 5. Вычисляется: 19:00 - 2h = 17:00
1090
+ # 6. Задача выполняется в 17:00
1091
+ pass
1092
+ ```
1093
+
1094
+ **Функция** - кастомная логика:
1095
+
1096
+ ```python
1097
+ async def get_time_from_api(user_id: int, user_data: str) -> datetime:
1098
+ booking = await external_api.get_booking(user_id)
1099
+ return booking['datetime']
1100
+
1101
+ @event_router.schedule_task("api_reminder", delay="1h", event_type=get_time_from_api)
1102
+ async def remind(user_id: int, text: str):
1103
+ # 1. ИИ создает: {"тип": "api_reminder", "инфо": ""}
1104
+ # 2. Вызывается get_time_from_api(user_id, "")
1105
+ # 3. Функция возвращает datetime из API
1106
+ # 4. Вычисляется: api_datetime - 1h
1107
+ # 5. Задача выполняется в вычисленное время
1108
+ pass
1109
+ ```
1110
+
1111
+ ---
1112
+
1113
+ ## 🚀 Публикация изменений
1114
+
1115
+ Если вы разработчик библиотеки:
1116
+
1117
+ ```bash
1118
+ # Автоматически увеличивает версию и публикует в PyPI
1119
+ uv run publish.py
1120
+
1121
+ # Требует PYPI_API_TOKEN в .env файле
1122
+ ```
1123
+
1124
+ ---
1125
+
1126
+ ## 📞 Поддержка
1127
+
1128
+ - Документация: [GitHub](https://github.com/your-repo)
1129
+ - Issues: [GitHub Issues](https://github.com/your-repo/issues)
1130
+
1131
+ ---
1132
+
1133
+ ## 📄 Лицензия
1134
+
1135
+ MIT