smart-bot-factory 0.2.10__py3-none-any.whl → 0.3.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.
Potentially problematic release.
This version of smart-bot-factory might be problematic. Click here for more details.
- smart_bot_factory/cli.py +35 -64
- smart_bot_factory/core/bot_utils.py +69 -21
- smart_bot_factory/core/decorators.py +377 -143
- smart_bot_factory/core/router.py +7 -4
- smart_bot_factory/creation/bot_builder.py +47 -14
- smart_bot_factory/handlers/handlers.py +5 -4
- smart_bot_factory/router/__init__.py +2 -3
- smart_bot_factory-0.3.1.dist-info/METADATA +905 -0
- {smart_bot_factory-0.2.10.dist-info → smart_bot_factory-0.3.1.dist-info}/RECORD +12 -13
- smart_bot_factory/core/telegram_router.py +0 -58
- smart_bot_factory-0.2.10.dist-info/METADATA +0 -789
- {smart_bot_factory-0.2.10.dist-info → smart_bot_factory-0.3.1.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.2.10.dist-info → smart_bot_factory-0.3.1.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.2.10.dist-info → smart_bot_factory-0.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smart-bot-factory
|
|
3
|
+
Version: 0.3.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>=3.4.1
|
|
25
|
+
Requires-Dist: click>=8.0.0
|
|
26
|
+
Requires-Dist: openai>=1.12.0
|
|
27
|
+
Requires-Dist: project-root-finder>=1.9
|
|
28
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
29
|
+
Requires-Dist: pytz>=2023.3
|
|
30
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
31
|
+
Requires-Dist: supabase>=2.3.4
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Smart Bot Factory
|
|
35
|
+
|
|
36
|
+
**Библиотека для создания умных Telegram ботов с AI, аналитикой и гибкой архитектурой**
|
|
37
|
+
|
|
38
|
+
## 📋 Содержание
|
|
39
|
+
|
|
40
|
+
- [Установка](#установка)
|
|
41
|
+
- [Быстрый старт](#быстрый-старт)
|
|
42
|
+
- [CLI Команды](#cli-команды)
|
|
43
|
+
- [Декораторы](#декораторы)
|
|
44
|
+
- [event_handler](#event_handler---обработчики-событий)
|
|
45
|
+
- [schedule_task](#schedule_task---запланированные-задачи)
|
|
46
|
+
- [global_handler](#global_handler---глобальные-обработчики)
|
|
47
|
+
- [Хуки для кастомизации](#хуки-для-кастомизации)
|
|
48
|
+
- [Telegram роутеры](#telegram-роутеры)
|
|
49
|
+
- [Расширенные возможности](#расширенные-возможности)
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 🚀 Установка
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install smart-bot-factory
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## ⚡ Быстрый старт
|
|
60
|
+
|
|
61
|
+
### 1. Создание бота через CLI
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Создать структуру нового бота
|
|
65
|
+
sbf create my-bot
|
|
66
|
+
|
|
67
|
+
# Настроить .env файл
|
|
68
|
+
sbf config my-bot
|
|
69
|
+
|
|
70
|
+
# Запустить бота
|
|
71
|
+
sbf run my-bot
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. Минимальный код бота
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
"""my-bot.py"""
|
|
78
|
+
import asyncio
|
|
79
|
+
from smart_bot_factory.router import EventRouter
|
|
80
|
+
from smart_bot_factory.message import send_message_by_human
|
|
81
|
+
from smart_bot_factory.creation import BotBuilder
|
|
82
|
+
|
|
83
|
+
# Инициализация
|
|
84
|
+
event_router = EventRouter("my-bot")
|
|
85
|
+
bot_builder = BotBuilder("my-bot")
|
|
86
|
+
|
|
87
|
+
@event_router.event_handler("collect_phone", once_only=True)
|
|
88
|
+
async def handle_phone(user_id: int, phone: str):
|
|
89
|
+
"""ИИ создает: {"тип": "collect_phone", "инфо": "+79001234567"}"""
|
|
90
|
+
await send_message_by_human(
|
|
91
|
+
user_id=user_id,
|
|
92
|
+
message_text=f"✅ Телефон {phone} сохранен"
|
|
93
|
+
)
|
|
94
|
+
return {"status": "success"}
|
|
95
|
+
|
|
96
|
+
async def main():
|
|
97
|
+
bot_builder.register_routers(event_router)
|
|
98
|
+
await bot_builder.build()
|
|
99
|
+
await bot_builder.start()
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
asyncio.run(main())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 🎮 CLI Команды
|
|
108
|
+
|
|
109
|
+
### Создание бота
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Создать нового бота из базового шаблона
|
|
113
|
+
sbf create my-bot
|
|
114
|
+
|
|
115
|
+
# Создать из кастомного шаблона
|
|
116
|
+
sbf create new-bot existing-bot
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Управление ботами
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Показать список ботов
|
|
123
|
+
sbf list
|
|
124
|
+
|
|
125
|
+
# Запустить бота
|
|
126
|
+
sbf run my-bot
|
|
127
|
+
|
|
128
|
+
# Удалить бота
|
|
129
|
+
sbf rm my-bot
|
|
130
|
+
|
|
131
|
+
# Удалить без подтверждения
|
|
132
|
+
sbf rm my-bot --force
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Настройка
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Открыть .env файл в редакторе
|
|
139
|
+
sbf config my-bot
|
|
140
|
+
|
|
141
|
+
# Управление промптами
|
|
142
|
+
sbf prompts my-bot # Список промптов
|
|
143
|
+
sbf prompts my-bot --edit system # Редактировать промпт
|
|
144
|
+
sbf prompts my-bot --add custom # Добавить новый промпт
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Тестирование
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Запустить все тесты
|
|
151
|
+
sbf test my-bot
|
|
152
|
+
|
|
153
|
+
# Запустить конкретный файл
|
|
154
|
+
sbf test my-bot --file test_booking.yaml
|
|
155
|
+
|
|
156
|
+
# Подробный вывод
|
|
157
|
+
sbf test my-bot -v
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Утилиты
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Показать путь к проекту
|
|
164
|
+
sbf path
|
|
165
|
+
|
|
166
|
+
# Создать UTM ссылку
|
|
167
|
+
sbf link
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 📦 Декораторы
|
|
173
|
+
|
|
174
|
+
### `event_handler` - Обработчики событий
|
|
175
|
+
|
|
176
|
+
**Назначение:** Обрабатывают события от ИИ немедленно (как только ИИ создает событие).
|
|
177
|
+
|
|
178
|
+
**Сигнатура:**
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
@event_router.event_handler(
|
|
182
|
+
event_type: str, # Тип события
|
|
183
|
+
notify: bool = False, # Уведомлять админов
|
|
184
|
+
once_only: bool = True # Выполнять только 1 раз
|
|
185
|
+
)
|
|
186
|
+
async def handler(user_id: int, event_data: str):
|
|
187
|
+
# Ваш код
|
|
188
|
+
return {"status": "success"}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Параметры:**
|
|
192
|
+
|
|
193
|
+
- **`event_type`** (обязательный) - Уникальное имя события
|
|
194
|
+
- **`notify`** (по умолчанию `False`) - Отправлять уведомление админам после выполнения
|
|
195
|
+
- **`once_only`** (по умолчанию `True`) - Если `True`, событие выполнится только 1 раз для пользователя
|
|
196
|
+
|
|
197
|
+
**Как работает:**
|
|
198
|
+
|
|
199
|
+
1. ИИ создает событие в JSON: `{"тип": "collect_phone", "инфо": "+79001234567"}`
|
|
200
|
+
2. Обработчик вызывается **немедленно**
|
|
201
|
+
3. Событие сохраняется в БД со статусом `completed`
|
|
202
|
+
4. Если `once_only=True` - повторные события блокируются
|
|
203
|
+
|
|
204
|
+
**Примеры:**
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
# Базовый пример
|
|
208
|
+
@event_router.event_handler("collect_phone")
|
|
209
|
+
async def save_phone(user_id: int, phone_number: str):
|
|
210
|
+
"""Сохраняет телефон клиента"""
|
|
211
|
+
await send_message_by_human(
|
|
212
|
+
user_id=user_id,
|
|
213
|
+
message_text=f"✅ Телефон {phone_number} сохранен"
|
|
214
|
+
)
|
|
215
|
+
return {"status": "success", "phone": phone_number}
|
|
216
|
+
|
|
217
|
+
# С уведомлением админов
|
|
218
|
+
@event_router.event_handler("new_lead", notify=True, once_only=True)
|
|
219
|
+
async def process_lead(user_id: int, lead_info: str):
|
|
220
|
+
"""Обрабатывает нового лида"""
|
|
221
|
+
# Админы получат уведомление автоматически
|
|
222
|
+
return {"status": "lead_created", "info": lead_info}
|
|
223
|
+
|
|
224
|
+
# Может выполняться многократно
|
|
225
|
+
@event_router.event_handler("ask_question", once_only=False)
|
|
226
|
+
async def handle_question(user_id: int, question: str):
|
|
227
|
+
"""Обрабатывает вопросы (может быть много)"""
|
|
228
|
+
# Логика обработки
|
|
229
|
+
return {"status": "answered"}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### `schedule_task` - Запланированные задачи
|
|
235
|
+
|
|
236
|
+
**Назначение:** Выполняются через заданное время после создания события.
|
|
237
|
+
|
|
238
|
+
**Сигнатура:**
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
@event_router.schedule_task(
|
|
242
|
+
task_name: str, # Название задачи
|
|
243
|
+
delay: Union[str, int], # Задержка: "1h 30m" или секунды
|
|
244
|
+
notify: bool = False, # Уведомлять админов
|
|
245
|
+
smart_check: bool = True, # Умная проверка активности
|
|
246
|
+
once_only: bool = True, # Выполнять только 1 раз
|
|
247
|
+
event_type: Union[str, Callable] = None # Источник времени события
|
|
248
|
+
)
|
|
249
|
+
async def handler(user_id: int, user_data: str):
|
|
250
|
+
# Ваш код
|
|
251
|
+
return {"status": "sent"}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Параметры:**
|
|
255
|
+
|
|
256
|
+
- **`task_name`** (обязательный) - Уникальное имя задачи
|
|
257
|
+
- **`delay`** (обязательный) - Задержка выполнения:
|
|
258
|
+
- Строка: `"1h 30m"`, `"2h"`, `"45m"`, `"30s"`
|
|
259
|
+
- Число: `3600` (секунды)
|
|
260
|
+
- **`notify`** (по умолчанию `False`) - Уведомлять админов
|
|
261
|
+
- **`smart_check`** (по умолчанию `True`) - Умная проверка:
|
|
262
|
+
- Отменяет задачу если пользователь перешел на новый этап
|
|
263
|
+
- Переносит задачу если пользователь был активен
|
|
264
|
+
- **`once_only`** (по умолчанию `True`) - Выполнять только 1 раз для пользователя
|
|
265
|
+
- **`event_type`** (опционально) - Источник времени события:
|
|
266
|
+
- **Строка**: `"appointment_booking"` - ищет событие в БД и вычисляет время
|
|
267
|
+
- **Функция**: `async def(user_id, user_data) -> datetime` - кастомная логика
|
|
268
|
+
|
|
269
|
+
**Формула времени с `event_type`:**
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
reminder_time = event_datetime - delay
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Примеры:**
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
# Простое напоминание через 24 часа
|
|
279
|
+
@event_router.schedule_task("follow_up", delay="24h")
|
|
280
|
+
async def send_follow_up(user_id: int, reminder_text: str):
|
|
281
|
+
"""
|
|
282
|
+
ИИ создает: {"тип": "follow_up", "инфо": "Не забудьте про запись"}
|
|
283
|
+
Выполнится через 24 часа
|
|
284
|
+
"""
|
|
285
|
+
await send_message_by_human(
|
|
286
|
+
user_id=user_id,
|
|
287
|
+
message_text=f"👋 {reminder_text}"
|
|
288
|
+
)
|
|
289
|
+
return {"status": "sent"}
|
|
290
|
+
|
|
291
|
+
# Напоминание относительно события из БД
|
|
292
|
+
@event_router.schedule_task(
|
|
293
|
+
"booking_reminder",
|
|
294
|
+
delay="2h", # За 2 часа до записи
|
|
295
|
+
event_type="appointment_booking" # Ищет в БД событие типа "appointment_booking"
|
|
296
|
+
)
|
|
297
|
+
async def remind_booking(user_id: int, user_data: str):
|
|
298
|
+
"""
|
|
299
|
+
ИИ создает событие: {"тип": "appointment_booking", "инфо": "дата: 2025-10-15, время: 19:00"}
|
|
300
|
+
Затем создает: {"тип": "booking_reminder", "инфо": ""}
|
|
301
|
+
|
|
302
|
+
Логика:
|
|
303
|
+
1. Находит в БД последнее событие "appointment_booking" для user_id
|
|
304
|
+
2. Парсит из него datetime: 2025-10-15 19:00
|
|
305
|
+
3. Вычисляет: reminder_time = 19:00 - 2h = 17:00
|
|
306
|
+
4. Отправляет напоминание в 17:00
|
|
307
|
+
"""
|
|
308
|
+
await send_message_by_human(
|
|
309
|
+
user_id=user_id,
|
|
310
|
+
message_text="⏰ Напоминаю о записи через 2 часа!"
|
|
311
|
+
)
|
|
312
|
+
return {"status": "sent"}
|
|
313
|
+
|
|
314
|
+
# Напоминание с кастомной функцией получения времени
|
|
315
|
+
async def get_booking_from_api(user_id: int, user_data: str) -> datetime:
|
|
316
|
+
"""Получает время записи из внешнего API"""
|
|
317
|
+
from yclients_api import get_next_booking
|
|
318
|
+
booking = await get_next_booking(user_id)
|
|
319
|
+
return booking['datetime'] # datetime объект
|
|
320
|
+
|
|
321
|
+
@event_router.schedule_task(
|
|
322
|
+
"api_reminder",
|
|
323
|
+
delay="1h",
|
|
324
|
+
event_type=get_booking_from_api # Функция вместо строки
|
|
325
|
+
)
|
|
326
|
+
async def send_api_reminder(user_id: int, user_data: str):
|
|
327
|
+
"""
|
|
328
|
+
ИИ создает: {"тип": "api_reminder", "инфо": ""}
|
|
329
|
+
|
|
330
|
+
Логика:
|
|
331
|
+
1. Вызывается get_booking_from_api(user_id, "")
|
|
332
|
+
2. Функция возвращает datetime из API
|
|
333
|
+
3. Вычисляется: reminder_time = api_datetime - 1h
|
|
334
|
+
4. Отправляется в вычисленное время
|
|
335
|
+
"""
|
|
336
|
+
await send_message_by_human(user_id, "⏰ Напоминание из API!")
|
|
337
|
+
return {"status": "sent"}
|
|
338
|
+
|
|
339
|
+
# Без smart_check (отправить в любом случае)
|
|
340
|
+
@event_router.schedule_task("important_reminder", delay="12h", smart_check=False)
|
|
341
|
+
async def important_reminder(user_id: int, text: str):
|
|
342
|
+
"""Отправится в любом случае, даже если пользователь активен"""
|
|
343
|
+
await send_message_by_human(user_id, f"🔔 {text}")
|
|
344
|
+
return {"status": "sent"}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
### `global_handler` - Глобальные обработчики
|
|
350
|
+
|
|
351
|
+
**Назначение:** Выполняются для всех пользователей одновременно.
|
|
352
|
+
|
|
353
|
+
**Сигнатура:**
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
@event_router.global_handler(
|
|
357
|
+
handler_type: str, # Тип обработчика
|
|
358
|
+
delay: Union[str, int], # Задержка
|
|
359
|
+
notify: bool = False, # Уведомлять админов
|
|
360
|
+
once_only: bool = True, # Выполнять только 1 раз
|
|
361
|
+
event_type: Union[str, Callable] = None # Источник времени
|
|
362
|
+
)
|
|
363
|
+
async def handler(handler_data: str):
|
|
364
|
+
# Ваш код
|
|
365
|
+
return {"status": "sent"}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Отличия от `schedule_task`:**
|
|
369
|
+
|
|
370
|
+
- **Нет `user_id`** - работает глобально
|
|
371
|
+
- **Нет `smart_check`** - не привязан к активности пользователя
|
|
372
|
+
- Одно выполнение = одна рассылка всем
|
|
373
|
+
|
|
374
|
+
**Примеры:**
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
# Рассылка всем через 2 часа
|
|
378
|
+
@event_router.global_handler("promo_announcement", delay="2h", notify=True)
|
|
379
|
+
async def send_promo(announcement_text: str):
|
|
380
|
+
"""
|
|
381
|
+
ИИ создает: {"тип": "promo_announcement", "инфо": "Скидка 20%!"}
|
|
382
|
+
Отправится всем через 2 часа
|
|
383
|
+
"""
|
|
384
|
+
await send_message_to_users_by_stage(
|
|
385
|
+
stage="all",
|
|
386
|
+
message_text=f"🎉 {announcement_text}",
|
|
387
|
+
bot_id="my-bot"
|
|
388
|
+
)
|
|
389
|
+
return {"status": "sent", "recipients": "all"}
|
|
390
|
+
|
|
391
|
+
# С кастомной функцией времени
|
|
392
|
+
async def get_promo_end_time(handler_data: str) -> datetime:
|
|
393
|
+
"""Получает время окончания акции из CRM"""
|
|
394
|
+
from crm_api import get_active_promo
|
|
395
|
+
promo = await get_active_promo()
|
|
396
|
+
return promo['end_datetime']
|
|
397
|
+
|
|
398
|
+
@event_router.global_handler(
|
|
399
|
+
"promo_ending_notification",
|
|
400
|
+
delay="2h",
|
|
401
|
+
event_type=get_promo_end_time
|
|
402
|
+
)
|
|
403
|
+
async def notify_promo_ending(handler_data: str):
|
|
404
|
+
"""Уведомление всем за 2 часа до окончания акции"""
|
|
405
|
+
await send_message_to_users_by_stage(
|
|
406
|
+
stage="all",
|
|
407
|
+
message_text="⏰ Акция заканчивается через 2 часа!",
|
|
408
|
+
bot_id="my-bot"
|
|
409
|
+
)
|
|
410
|
+
return {"status": "sent"}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## 🎣 Хуки для кастомизации
|
|
416
|
+
|
|
417
|
+
Хуки позволяют внедрять свою логику в стандартную обработку сообщений без переписывания всей функции.
|
|
418
|
+
|
|
419
|
+
### Доступные хуки
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
bot_builder = BotBuilder("my-bot")
|
|
423
|
+
|
|
424
|
+
# 1. Валидация сообщения (ДО обработки AI)
|
|
425
|
+
@bot_builder.validate_message
|
|
426
|
+
async def check_spam(message_text: str, message_obj):
|
|
427
|
+
if "спам" in message_text.lower():
|
|
428
|
+
await message_obj.answer("⛔ Спам запрещен")
|
|
429
|
+
return False # Блокировать обработку
|
|
430
|
+
return True # Продолжить
|
|
431
|
+
|
|
432
|
+
# 2. Обогащение системного промпта
|
|
433
|
+
@bot_builder.enrich_prompt
|
|
434
|
+
async def add_client_info(system_prompt: str, user_id: int):
|
|
435
|
+
session = await supabase_client.get_active_session(user_id)
|
|
436
|
+
phone = session.get('metadata', {}).get('phone')
|
|
437
|
+
|
|
438
|
+
if phone:
|
|
439
|
+
return f"{system_prompt}\n\nТелефон клиента: {phone}"
|
|
440
|
+
return system_prompt
|
|
441
|
+
|
|
442
|
+
# 3. Обогащение контекста для AI
|
|
443
|
+
@bot_builder.enrich_context
|
|
444
|
+
async def add_external_data(messages: list):
|
|
445
|
+
# Добавляем данные из внешнего API
|
|
446
|
+
messages.append({
|
|
447
|
+
"role": "system",
|
|
448
|
+
"content": "Дополнительная информация из CRM..."
|
|
449
|
+
})
|
|
450
|
+
return messages
|
|
451
|
+
|
|
452
|
+
# 4. Обработка ответа AI
|
|
453
|
+
@bot_builder.process_response
|
|
454
|
+
async def modify_response(response_text: str, ai_metadata: dict, user_id: int):
|
|
455
|
+
# Модифицируем ответ
|
|
456
|
+
if "цена" in response_text.lower():
|
|
457
|
+
response_text += "\n\n💰 Актуальные цены на сайте"
|
|
458
|
+
return response_text, ai_metadata
|
|
459
|
+
|
|
460
|
+
# 5. Фильтры отправки
|
|
461
|
+
@bot_builder.filter_send
|
|
462
|
+
async def block_during_booking(user_id: int):
|
|
463
|
+
if is_processing_booking(user_id):
|
|
464
|
+
return True # Блокировать отправку
|
|
465
|
+
return False # Разрешить
|
|
466
|
+
|
|
467
|
+
# 6. Кастомная логика после /start
|
|
468
|
+
@bot_builder.on_start
|
|
469
|
+
async def custom_start(user_id: int, session_id: str, message, state):
|
|
470
|
+
"""Вызывается ПОСЛЕ стандартного /start"""
|
|
471
|
+
keyboard = InlineKeyboardMarkup(...)
|
|
472
|
+
await message.answer("Выберите действие:", reply_markup=keyboard)
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## 📱 Telegram роутеры
|
|
478
|
+
|
|
479
|
+
Подключайте чистые `aiogram.Router` для кастомных команд, callback'ов и фильтров.
|
|
480
|
+
|
|
481
|
+
### Создание роутера
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
from aiogram import Router, F
|
|
485
|
+
from aiogram.filters import Command
|
|
486
|
+
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
|
487
|
+
|
|
488
|
+
# Создаем aiogram Router
|
|
489
|
+
telegram_router = Router(name="my_commands")
|
|
490
|
+
|
|
491
|
+
@telegram_router.message(Command("price", "цена"))
|
|
492
|
+
async def price_handler(message: Message):
|
|
493
|
+
"""Команда /price"""
|
|
494
|
+
await message.answer(
|
|
495
|
+
"💰 Наши цены:\n"
|
|
496
|
+
"• Услуга 1 - 1000₽\n"
|
|
497
|
+
"• Услуга 2 - 2000₽"
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
@telegram_router.message(Command("catalog"))
|
|
501
|
+
async def catalog_handler(message: Message):
|
|
502
|
+
"""Команда /catalog с кнопками"""
|
|
503
|
+
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
504
|
+
[InlineKeyboardButton(text="🔥 Акции", callback_data="promo")],
|
|
505
|
+
[InlineKeyboardButton(text="📅 Записаться", callback_data="book")]
|
|
506
|
+
])
|
|
507
|
+
await message.answer("Выберите:", reply_markup=keyboard)
|
|
508
|
+
|
|
509
|
+
@telegram_router.callback_query(F.data == "book")
|
|
510
|
+
async def handle_booking(callback: CallbackQuery):
|
|
511
|
+
"""Обработка кнопки"""
|
|
512
|
+
await callback.answer("Записываю...")
|
|
513
|
+
await callback.message.answer("Напишите желаемую дату")
|
|
514
|
+
|
|
515
|
+
@telegram_router.message(F.text.lower().contains("помощь"))
|
|
516
|
+
async def help_handler(message: Message):
|
|
517
|
+
"""Реагирует на слово 'помощь'"""
|
|
518
|
+
await message.answer("Чем могу помочь?")
|
|
519
|
+
|
|
520
|
+
# Регистрация в боте
|
|
521
|
+
bot_builder.register_telegram_router(telegram_router)
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### Множественная регистрация
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
commands_router = Router(name="commands")
|
|
528
|
+
callbacks_router = Router(name="callbacks")
|
|
529
|
+
filters_router = Router(name="filters")
|
|
530
|
+
|
|
531
|
+
# Регистрируем все сразу
|
|
532
|
+
bot_builder.register_telegram_routers(
|
|
533
|
+
commands_router,
|
|
534
|
+
callbacks_router,
|
|
535
|
+
filters_router
|
|
536
|
+
)
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**⚠️ Важно:** Пользовательские роутеры подключаются **ПЕРВЫМИ** (высший приоритет), поэтому ваши команды обрабатываются раньше стандартных.
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
## 🔧 Расширенные возможности
|
|
544
|
+
|
|
545
|
+
### Кастомный PromptLoader
|
|
546
|
+
|
|
547
|
+
Создайте свой загрузчик промптов с автоматическим определением пути:
|
|
548
|
+
|
|
549
|
+
```python
|
|
550
|
+
from smart_bot_factory.utils import UserPromptLoader
|
|
551
|
+
|
|
552
|
+
# Автоматически найдет bots/my-bot/prompts
|
|
553
|
+
custom_loader = UserPromptLoader("my-bot")
|
|
554
|
+
|
|
555
|
+
# Или наследуйтесь для кастомизации
|
|
556
|
+
class MyPromptLoader(UserPromptLoader):
|
|
557
|
+
def __init__(self, bot_id):
|
|
558
|
+
super().__init__(bot_id)
|
|
559
|
+
self.extra_file = self.prompts_dir / 'extra.txt'
|
|
560
|
+
|
|
561
|
+
my_loader = MyPromptLoader("my-bot")
|
|
562
|
+
|
|
563
|
+
# Установите ДО build()
|
|
564
|
+
bot_builder.set_prompt_loader(my_loader)
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Полная замена обработки событий
|
|
568
|
+
|
|
569
|
+
Замените стандартную функцию `process_events`:
|
|
570
|
+
|
|
571
|
+
```python
|
|
572
|
+
from smart_bot_factory.message import get_bot
|
|
573
|
+
from smart_bot_factory.core.decorators import execute_event_handler
|
|
574
|
+
|
|
575
|
+
async def my_process_events(session_id, events, user_id):
|
|
576
|
+
"""Моя кастомная обработка событий"""
|
|
577
|
+
bot = get_bot()
|
|
578
|
+
|
|
579
|
+
for event in events:
|
|
580
|
+
event_type = event.get('тип')
|
|
581
|
+
|
|
582
|
+
if event_type == 'booking':
|
|
583
|
+
# Ваша кастомная логика
|
|
584
|
+
telegram_user = await bot.get_chat(user_id)
|
|
585
|
+
name = telegram_user.first_name
|
|
586
|
+
# ... обработка
|
|
587
|
+
else:
|
|
588
|
+
# Стандартная обработка остальных
|
|
589
|
+
await execute_event_handler(event_type, user_id, event.get('инфо'))
|
|
590
|
+
|
|
591
|
+
# Установите ДО build()
|
|
592
|
+
bot_builder.set_event_processor(my_process_events)
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Доступ к aiogram Bot
|
|
596
|
+
|
|
597
|
+
Получите прямой доступ к `aiogram.Bot`:
|
|
598
|
+
|
|
599
|
+
```python
|
|
600
|
+
from smart_bot_factory.message import get_bot
|
|
601
|
+
|
|
602
|
+
@event_router.event_handler("check_user")
|
|
603
|
+
async def get_user_info(user_id: int, event_data: str):
|
|
604
|
+
"""Получает информацию из Telegram"""
|
|
605
|
+
bot = get_bot()
|
|
606
|
+
|
|
607
|
+
# Используем любые методы aiogram Bot
|
|
608
|
+
telegram_user = await bot.get_chat(user_id)
|
|
609
|
+
name = telegram_user.first_name
|
|
610
|
+
username = telegram_user.username
|
|
611
|
+
|
|
612
|
+
await bot.send_message(user_id, f"Привет, {name}!")
|
|
613
|
+
return {"name": name, "username": username}
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### Отправка сообщений с файлами
|
|
617
|
+
|
|
618
|
+
```python
|
|
619
|
+
from smart_bot_factory.message import send_message
|
|
620
|
+
|
|
621
|
+
@event_router.event_handler("send_catalog")
|
|
622
|
+
async def send_catalog(user_id: int, event_data: str):
|
|
623
|
+
"""Отправляет каталог с файлами"""
|
|
624
|
+
from smart_bot_factory.message import get_bot
|
|
625
|
+
from smart_bot_factory.supabase import SupabaseClient
|
|
626
|
+
|
|
627
|
+
bot = get_bot()
|
|
628
|
+
supabase_client = SupabaseClient("my-bot")
|
|
629
|
+
|
|
630
|
+
# Получаем message объект (для ответа)
|
|
631
|
+
# В реальности используйте message из контекста
|
|
632
|
+
|
|
633
|
+
await send_message(
|
|
634
|
+
message=message, # aiogram Message объект
|
|
635
|
+
text="📁 Вот наш каталог:",
|
|
636
|
+
supabase_client=supabase_client,
|
|
637
|
+
files_list=["catalog.pdf", "price_list.pdf"],
|
|
638
|
+
parse_mode="Markdown"
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
return {"status": "sent"}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## 📚 Полный пример
|
|
647
|
+
|
|
648
|
+
```python
|
|
649
|
+
"""advanced-bot.py - Продвинутый пример"""
|
|
650
|
+
|
|
651
|
+
import asyncio
|
|
652
|
+
from datetime import datetime, timedelta
|
|
653
|
+
|
|
654
|
+
from smart_bot_factory.router import EventRouter
|
|
655
|
+
from smart_bot_factory.message import send_message_by_human, get_bot
|
|
656
|
+
from smart_bot_factory.creation import BotBuilder
|
|
657
|
+
from smart_bot_factory.supabase import SupabaseClient
|
|
658
|
+
|
|
659
|
+
from aiogram import Router, F
|
|
660
|
+
from aiogram.filters import Command
|
|
661
|
+
from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
|
|
662
|
+
|
|
663
|
+
# Инициализация
|
|
664
|
+
event_router = EventRouter("advanced-bot")
|
|
665
|
+
telegram_router = Router(name="commands")
|
|
666
|
+
bot_builder = BotBuilder("advanced-bot")
|
|
667
|
+
supabase_client = SupabaseClient("advanced-bot")
|
|
668
|
+
|
|
669
|
+
# ========== СОБЫТИЯ ==========
|
|
670
|
+
|
|
671
|
+
@event_router.event_handler("collect_phone", notify=True, once_only=True)
|
|
672
|
+
async def save_phone(user_id: int, phone: str):
|
|
673
|
+
session = await supabase_client.get_active_session(user_id)
|
|
674
|
+
if session:
|
|
675
|
+
metadata = session.get('metadata', {})
|
|
676
|
+
metadata['phone'] = phone
|
|
677
|
+
await supabase_client.update_session_metadata(session['id'], metadata)
|
|
678
|
+
|
|
679
|
+
await send_message_by_human(user_id, f"✅ Телефон {phone} сохранен")
|
|
680
|
+
return {"status": "success"}
|
|
681
|
+
|
|
682
|
+
# ========== ЗАДАЧИ ==========
|
|
683
|
+
|
|
684
|
+
async def get_appointment_time(user_id: int, user_data: str) -> datetime:
|
|
685
|
+
"""Получает время из YClients API"""
|
|
686
|
+
# Ваша интеграция с YClients
|
|
687
|
+
return datetime.now() + timedelta(hours=24)
|
|
688
|
+
|
|
689
|
+
@event_router.schedule_task(
|
|
690
|
+
"appointment_reminder",
|
|
691
|
+
delay="2h",
|
|
692
|
+
event_type=get_appointment_time,
|
|
693
|
+
smart_check=False
|
|
694
|
+
)
|
|
695
|
+
async def remind_appointment(user_id: int, user_data: str):
|
|
696
|
+
await send_message_by_human(user_id, "⏰ Запись через 2 часа!")
|
|
697
|
+
return {"status": "sent"}
|
|
698
|
+
|
|
699
|
+
# ========== ГЛОБАЛЬНЫЕ ==========
|
|
700
|
+
|
|
701
|
+
@event_router.global_handler("daily_promo", delay="24h", once_only=False)
|
|
702
|
+
async def daily_promo(text: str):
|
|
703
|
+
await send_message_to_users_by_stage(
|
|
704
|
+
stage="all",
|
|
705
|
+
message_text=f"🎉 {text}",
|
|
706
|
+
bot_id="advanced-bot"
|
|
707
|
+
)
|
|
708
|
+
return {"status": "sent"}
|
|
709
|
+
|
|
710
|
+
# ========== TELEGRAM КОМАНДЫ ==========
|
|
711
|
+
|
|
712
|
+
@telegram_router.message(Command("price"))
|
|
713
|
+
async def price_cmd(message: Message):
|
|
714
|
+
await message.answer("💰 Цены: ...")
|
|
715
|
+
|
|
716
|
+
@telegram_router.callback_query(F.data == "book")
|
|
717
|
+
async def booking_callback(callback):
|
|
718
|
+
await callback.answer("Записываю...")
|
|
719
|
+
|
|
720
|
+
# ========== ХУКИ ==========
|
|
721
|
+
|
|
722
|
+
@bot_builder.validate_message
|
|
723
|
+
async def check_business_hours(message_text: str, message_obj):
|
|
724
|
+
"""Проверка рабочих часов"""
|
|
725
|
+
hour = datetime.now().hour
|
|
726
|
+
if hour < 9 or hour > 21:
|
|
727
|
+
await message_obj.answer("Мы работаем с 9:00 до 21:00")
|
|
728
|
+
return False
|
|
729
|
+
return True
|
|
730
|
+
|
|
731
|
+
@bot_builder.enrich_prompt
|
|
732
|
+
async def add_client_data(system_prompt: str, user_id: int):
|
|
733
|
+
"""Добавляет данные клиента в промпт"""
|
|
734
|
+
session = await supabase_client.get_active_session(user_id)
|
|
735
|
+
phone = session.get('metadata', {}).get('phone')
|
|
736
|
+
|
|
737
|
+
if phone:
|
|
738
|
+
return f"{system_prompt}\n\nТелефон клиента: {phone}"
|
|
739
|
+
return system_prompt
|
|
740
|
+
|
|
741
|
+
# ========== ЗАПУСК ==========
|
|
742
|
+
|
|
743
|
+
async def main():
|
|
744
|
+
# Регистрация
|
|
745
|
+
bot_builder.register_routers(event_router)
|
|
746
|
+
bot_builder.register_telegram_router(telegram_router)
|
|
747
|
+
|
|
748
|
+
# Кастомизация (опционально)
|
|
749
|
+
# from smart_bot_factory.utils import UserPromptLoader
|
|
750
|
+
# bot_builder.set_prompt_loader(UserPromptLoader("advanced-bot"))
|
|
751
|
+
|
|
752
|
+
# Сборка и запуск
|
|
753
|
+
await bot_builder.build()
|
|
754
|
+
await bot_builder.start()
|
|
755
|
+
|
|
756
|
+
if __name__ == "__main__":
|
|
757
|
+
asyncio.run(main())
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
---
|
|
761
|
+
|
|
762
|
+
## 📖 Структура проекта
|
|
763
|
+
|
|
764
|
+
```
|
|
765
|
+
project/
|
|
766
|
+
├── bots/
|
|
767
|
+
│ └── my-bot/
|
|
768
|
+
│ ├── prompts/ # Промпты для AI
|
|
769
|
+
│ │ ├── system_prompt.txt
|
|
770
|
+
│ │ ├── welcome_message.txt
|
|
771
|
+
│ │ └── final_instructions.txt
|
|
772
|
+
│ ├── tests/ # YAML тесты
|
|
773
|
+
│ ├── welcome_files/ # Файлы приветствия
|
|
774
|
+
│ ├── files/ # Файлы для отправки
|
|
775
|
+
│ └── .env # Конфигурация
|
|
776
|
+
├── my-bot.py # Код бота
|
|
777
|
+
└── .env # Глобальная конфигурация (опционально)
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## ⚙️ Конфигурация (.env)
|
|
783
|
+
|
|
784
|
+
```env
|
|
785
|
+
# Telegram
|
|
786
|
+
TELEGRAM_BOT_TOKEN=your_token_here
|
|
787
|
+
|
|
788
|
+
# Supabase
|
|
789
|
+
SUPABASE_URL=https://your-project.supabase.co
|
|
790
|
+
SUPABASE_KEY=your_key_here
|
|
791
|
+
|
|
792
|
+
# OpenAI
|
|
793
|
+
OPENAI_API_KEY=sk-your-key
|
|
794
|
+
OPENAI_MODEL=gpt-4o-mini
|
|
795
|
+
OPENAI_MAX_TOKENS=1500
|
|
796
|
+
|
|
797
|
+
# Администраторы (Telegram ID через запятую)
|
|
798
|
+
ADMIN_TELEGRAM_IDS=123456789,987654321
|
|
799
|
+
|
|
800
|
+
# Режим отладки (показывать JSON)
|
|
801
|
+
DEBUG_MODE=false
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
---
|
|
805
|
+
|
|
806
|
+
## 🎯 Сравнение декораторов
|
|
807
|
+
|
|
808
|
+
| Декоратор | Когда выполняется | Для кого | Параметры |
|
|
809
|
+
|-----------|-------------------|----------|-----------|
|
|
810
|
+
| `@event_handler` | Немедленно | 1 пользователь | `event_type`, `notify`, `once_only` |
|
|
811
|
+
| `@schedule_task` | Через время | 1 пользователь | `task_name`, `delay`, `event_type`, `smart_check`, `once_only`, `notify` |
|
|
812
|
+
| `@global_handler` | Через время | Все пользователи | `handler_type`, `delay`, `event_type`, `once_only`, `notify` |
|
|
813
|
+
|
|
814
|
+
---
|
|
815
|
+
|
|
816
|
+
## 🔑 Ключевые концепции
|
|
817
|
+
|
|
818
|
+
### `once_only=True`
|
|
819
|
+
|
|
820
|
+
Гарантирует выполнение события только 1 раз для пользователя:
|
|
821
|
+
|
|
822
|
+
- **При сохранении**: Проверяет БД, если есть - не сохраняет
|
|
823
|
+
- **При выполнении**: Проверяет БД, если есть `completed` - отменяет
|
|
824
|
+
|
|
825
|
+
```python
|
|
826
|
+
@event_router.event_handler("welcome_bonus", once_only=True)
|
|
827
|
+
async def give_bonus(user_id: int, bonus_info: str):
|
|
828
|
+
# Выполнится только 1 раз, даже если пользователь сделает /start заново
|
|
829
|
+
return {"status": "bonus_given"}
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
### `smart_check=True`
|
|
833
|
+
|
|
834
|
+
Умная проверка для запланированных задач:
|
|
835
|
+
|
|
836
|
+
- **Отменяет** задачу если пользователь перешел на новый этап
|
|
837
|
+
- **Переносит** задачу если пользователь был недавно активен
|
|
838
|
+
|
|
839
|
+
```python
|
|
840
|
+
@event_router.schedule_task("follow_up", delay="24h", smart_check=True)
|
|
841
|
+
async def follow_up(user_id: int, text: str):
|
|
842
|
+
# Не отправится если пользователь уже был активен
|
|
843
|
+
return {"status": "sent"}
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
### `event_type` - Привязка ко времени события
|
|
847
|
+
|
|
848
|
+
Планирует задачу относительно времени события:
|
|
849
|
+
|
|
850
|
+
**Строка** - ищет в БД:
|
|
851
|
+
|
|
852
|
+
```python
|
|
853
|
+
@event_router.schedule_task("reminder", delay="2h", event_type="appointment")
|
|
854
|
+
async def remind(user_id: int, text: str):
|
|
855
|
+
# 1. ИИ создает событие: {"тип": "appointment", "инфо": "дата: 2025-10-15, время: 19:00"}
|
|
856
|
+
# 2. ИИ создает задачу: {"тип": "reminder", "инфо": ""}
|
|
857
|
+
# 3. Ищется в БД событие "appointment" для user_id
|
|
858
|
+
# 4. Парсится datetime: 2025-10-15 19:00
|
|
859
|
+
# 5. Вычисляется: 19:00 - 2h = 17:00
|
|
860
|
+
# 6. Задача выполняется в 17:00
|
|
861
|
+
pass
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
**Функция** - кастомная логика:
|
|
865
|
+
|
|
866
|
+
```python
|
|
867
|
+
async def get_time_from_api(user_id: int, user_data: str) -> datetime:
|
|
868
|
+
booking = await external_api.get_booking(user_id)
|
|
869
|
+
return booking['datetime']
|
|
870
|
+
|
|
871
|
+
@event_router.schedule_task("api_reminder", delay="1h", event_type=get_time_from_api)
|
|
872
|
+
async def remind(user_id: int, text: str):
|
|
873
|
+
# 1. ИИ создает: {"тип": "api_reminder", "инфо": ""}
|
|
874
|
+
# 2. Вызывается get_time_from_api(user_id, "")
|
|
875
|
+
# 3. Функция возвращает datetime из API
|
|
876
|
+
# 4. Вычисляется: api_datetime - 1h
|
|
877
|
+
# 5. Задача выполняется в вычисленное время
|
|
878
|
+
pass
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
## 🚀 Публикация изменений
|
|
884
|
+
|
|
885
|
+
Если вы разработчик библиотеки:
|
|
886
|
+
|
|
887
|
+
```bash
|
|
888
|
+
# Автоматически увеличивает версию и публикует в PyPI
|
|
889
|
+
uv run publish.py
|
|
890
|
+
|
|
891
|
+
# Требует PYPI_API_TOKEN в .env файле
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
---
|
|
895
|
+
|
|
896
|
+
## 📞 Поддержка
|
|
897
|
+
|
|
898
|
+
- Документация: [GitHub](https://github.com/your-repo)
|
|
899
|
+
- Issues: [GitHub Issues](https://github.com/your-repo/issues)
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
## 📄 Лицензия
|
|
904
|
+
|
|
905
|
+
MIT
|