@retensy/mcp 0.11.0
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.
- package/.claude-plugin/marketplace.json +12 -0
- package/.claude-plugin/plugin.json +12 -0
- package/.mcp.json +12 -0
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/package.json +21 -0
- package/skills/build-bot-funnel/SKILL.md +70 -0
- package/skills/build-bot-funnel/reference/schema.md +193 -0
- package/skills/build-bot-funnel/reference/validation.md +107 -0
- package/skills/build-bot-funnel/validate.mjs +398 -0
- package/src/index.mjs +424 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "retensy",
|
|
3
|
+
"owner": { "name": "retensy", "url": "https://bots.retensy.com" },
|
|
4
|
+
"description": "Плагины Retensy для Claude Code.",
|
|
5
|
+
"plugins": [
|
|
6
|
+
{
|
|
7
|
+
"name": "retensy-mcp",
|
|
8
|
+
"source": "./",
|
|
9
|
+
"description": "Сборка и публикация воронок ботов (Telegram/MAX/Instagram, Retensy Bots) из описания + публикация статей блога в Markdown — через MCP + скилл."
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "retensy-mcp",
|
|
3
|
+
"displayName": "Retensy MCP",
|
|
4
|
+
"version": "0.11.0",
|
|
5
|
+
"description": "MCP-сервер + скилл для Retensy Bots: сборка и публикация воронок ботов (Telegram/MAX/Instagram) и публикация статей блога в Markdown — всё одним токеном zmcp_.",
|
|
6
|
+
"author": { "name": "retensy", "url": "https://bots.retensy.com" },
|
|
7
|
+
"homepage": "https://bots.retensy.com/bots",
|
|
8
|
+
"repository": "https://github.com/retensy/retensy-mcp",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": ["mcp", "telegram", "bot", "funnel", "retensy", "no-code", "claude-code"],
|
|
11
|
+
"mcpServers": "./.mcp.json"
|
|
12
|
+
}
|
package/.mcp.json
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 retensy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# retensy-mcp
|
|
2
|
+
|
|
3
|
+
[](https://github.com/retensy/retensy-mcp/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@retensy/mcp)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
MCP-сервер (+ скилл для Claude Code) для **сборки и публикации воронок/автоматизаций ботов (Telegram, MAX и Instagram)** в сервисе [retensy `/bots`](https://bots.retensy.com/bots): из текстового описания → валидный граф сценария → заливка и публикация через API.
|
|
9
|
+
|
|
10
|
+
- 🤖 **30 инструментов сборки/публикации**: `list_bots`, `list_graphs`, `list_channels`, `get_graph`, `create_graph`, `update_graph`, `edit_graph_live`, `patch_graph`, `dry_run`, `publish_graph`, `import_funnel`, `list_templates`, `create_graph_from_template`, `clone_graph`, `copy_graph`, `rename_graph`, `set_active_graph`, `delete_graph`, `upload_file`, `list_files`, `delete_file`, `graph_analytics`, `list_bot_users`, `list_links` (+ `setup`/`set_token`).
|
|
11
|
+
- 📝 **Статьи блога** (тот же токен `zmcp_…`): `article_publish`, `article_update`, `article_list`, `article_get` — публикация статей в Markdown (как README на GitHub) в раздел **/articles**.
|
|
12
|
+
- 📎 **Медиа**: `upload_file` грузит фото/видео/документы в библиотеку **/bots/files** (до 50 МБ) и возвращает публичный URL — его вставляешь в медиа-карточку сценария.
|
|
13
|
+
- 🧠 **Скилл `build-bot-funnel`**: учит агента собирать корректный граф (типы узлов, ветки, кнопки, задержки) и проверять его перед публикацией. Поддерживает Telegram, MAX и Instagram.
|
|
14
|
+
- 📦 **Без зависимостей** — чистый Node ≥18, ставится и запускается сразу.
|
|
15
|
+
|
|
16
|
+
### Поддерживаемые платформы
|
|
17
|
+
|
|
18
|
+
| Платформа | Онбординг | Триггеры входа | Ограничения |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| **Telegram** | Токен бота (BotFather) | `/start`, команды, callback, текст, рассылки | Полный функционал |
|
|
21
|
+
| **MAX** | Токен бота (MAX Developer) | Команды, callback, текст | Без SUBSCRIBED/reply-клавиатур (мягкие предупреждения) |
|
|
22
|
+
| **Instagram** | OAuth в `/growth` (без токена) | Комментарий/Direct/Ответ на историю/Упоминание | Ограниченный набор узлов; DELAY ≤ 24ч; ASK_QUESTION только TEXT/EMAIL/PHONE/NUMBER/CONTACT (CONTACT = ручной ввод номера); без рассылок |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Установка
|
|
27
|
+
|
|
28
|
+
### Вариант A — как плагин Claude Code (рекомендуется)
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
/plugin marketplace add retensy/retensy-mcp
|
|
32
|
+
/plugin install retensy-mcp@retensy
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Подтянутся и MCP-сервер `bot-graph`, и скилл `build-bot-funnel`. Проверить: `/mcp` и `/plugin`.
|
|
36
|
+
|
|
37
|
+
### Вариант B — как обычный MCP-сервер (Claude Code / Cursor / Windsurf / любой MCP-клиент)
|
|
38
|
+
|
|
39
|
+
Через `npx` без установки. Пример конфига (`.mcp.json` / настройки клиента):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"retensy-mcp": {
|
|
45
|
+
"command": "npx",
|
|
46
|
+
"args": ["-y", "@retensy/mcp"],
|
|
47
|
+
"env": {
|
|
48
|
+
"RETENSY_BASE_URL": "https://bots.retensy.com",
|
|
49
|
+
"RETENSY_MCP_TOKEN": "zmcp_ваш_токен"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
См. также [`examples/.mcp.json`](examples/.mcp.json).
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Авторизация — персональный токен
|
|
61
|
+
|
|
62
|
+
Токен даёт **полный доступ** к управлению твоими ботами (как вход в аккаунт).
|
|
63
|
+
|
|
64
|
+
1. Залогинься на https://bots.retensy.com → открой **`/bots/mcp-tokens`**.
|
|
65
|
+
2. Создай токен → скопируй секрет `zmcp_...` (показывается один раз).
|
|
66
|
+
3. Передай токен любым способом:
|
|
67
|
+
- **просто пришли его агенту в чат** — он вызовет инструмент `set_token` и сохранит токен в `~/.retensy-bot-graph/token` (применяется сразу, без рестарта), **или**
|
|
68
|
+
- `env` в `.mcp.json` (Вариант B), **или**
|
|
69
|
+
- переменной окружения: PowerShell `setx RETENSY_MCP_TOKEN "zmcp_..."`, bash `export RETENSY_MCP_TOKEN="zmcp_..."`.
|
|
70
|
+
|
|
71
|
+
Отозвать токен можно там же — доступ блокируется мгновенно.
|
|
72
|
+
|
|
73
|
+
> **Не знаешь, что делать?** Скажи агенту «настрой подключение» — он вызовет `setup`, объяснит шаги и попросит токен. Любой инструмент при отсутствии токена тоже вернёт пошаговую инструкцию.
|
|
74
|
+
|
|
75
|
+
> Дев-окружение: `RETENSY_BASE_URL=http://localhost:8066`.
|
|
76
|
+
> Fallback без токена: `RETENSY_SESSION_COOKIE` = значение куки `SESSION` из браузера.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Использование
|
|
81
|
+
|
|
82
|
+
Опиши воронку словами — агент соберёт граф и (через MCP) опубликует:
|
|
83
|
+
|
|
84
|
+
> «Собери бота: `/start` → приветствие с кнопкой подписки на канал → вопрос с 3 кнопками (бизнес / эксперт / просто смотрю) → для каждой свою цепочку из 2 сообщений с задержкой 1 день → финал с регистрацией на вебинар. Залей в бота и опубликуй.»
|
|
85
|
+
|
|
86
|
+
Под капотом скилл соберёт `nodes/edges`, прогонит локальную проверку и вызовет `import_funnel` → создаст граф, зальёт узлы, прогонит `dry-run /start`, опубликует. При ошибках публикации — разберёт по `code`/`nodeId`, починит, повторит.
|
|
87
|
+
|
|
88
|
+
### Инструменты
|
|
89
|
+
|
|
90
|
+
| Tool | Назначение |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `setup` | статус авторизации + пошаговая инструкция подключения |
|
|
93
|
+
| `set_token` | сохранить присланный токен `zmcp_…` (без env/рестарта) |
|
|
94
|
+
| `list_bots` | список ботов |
|
|
95
|
+
| `list_graphs(botId)` | графы (сценарии) бота |
|
|
96
|
+
| `list_channels(botId)` | каналы/группы, подключённые к боту (chatId для условия SUBSCRIBED) |
|
|
97
|
+
| `get_graph(graphId, [summary], [saveToFile])` | получить граф; `summary:true` — компактная сводка (id/type/title + рёбра), `saveToFile` — записать полный JSON на диск (для больших графов, чтобы не упереться в лимит токенов) |
|
|
98
|
+
| `create_graph(botId, name)` | создать пустой граф (DRAFT) |
|
|
99
|
+
| `update_graph(graphId, graphFile\|graph\|nodes,edges)` | залить узлы/рёбра (PUT); `graphFile` — путь к локальному JSON, граф не нужно слать инлайном |
|
|
100
|
+
| `edit_graph_live(graphId, graphFile\|graph\|nodes,edges)` | правка живого графа НА МЕСТЕ + авто-бэкап (рекомендуется для прода) |
|
|
101
|
+
| `patch_graph(graphId, replacements)` | строковые замены в JSON графа на сервере (для больших/живых графов) |
|
|
102
|
+
| `dry_run(graphId, kind, value)` | прогон без публикации |
|
|
103
|
+
| `publish_graph(graphId)` | публикация (вернёт `errors[]` при провале) |
|
|
104
|
+
| `import_funnel(botId, name, graphFile\|graph)` | всё за раз: create → update → dry-run → publish |
|
|
105
|
+
| `list_templates()` | готовые шаблоны воронок |
|
|
106
|
+
| `create_graph_from_template(botId, templateId, name)` | граф из шаблона (DRAFT) |
|
|
107
|
+
| `clone_graph(graphId)` | копия графа в новый DRAFT |
|
|
108
|
+
| `rename_graph(graphId, name)` | переименовать сценарий |
|
|
109
|
+
| `set_active_graph(botId, graphId)` | переключить активный (живой) граф бота |
|
|
110
|
+
| `delete_graph(graphId)` | удалить граф (активный — нельзя, 409) |
|
|
111
|
+
| `upload_file(path\|url)` | загрузить файл в /bots/files → публичный `url` для медиа-карточки |
|
|
112
|
+
| `list_files()` | файлы библиотеки /bots/files + использовано/лимит байт |
|
|
113
|
+
| `delete_file(id)` | удалить файл из /bots/files |
|
|
114
|
+
| `graph_analytics(graphId)` | прохождение сценария по узлам (где отваливается воронка) |
|
|
115
|
+
| `list_bot_users(botId)` | подписчики/лиды бота (постранично, поиск `query`) |
|
|
116
|
+
| `list_links(botId)` | стартовые трекинговые ссылки бота с UTM |
|
|
117
|
+
| `article_list()` | свои статьи блога (id, slug, title, просмотры) |
|
|
118
|
+
| `article_get(slug)` | статья по slug (Markdown content, excerpt, обложка) |
|
|
119
|
+
| `article_publish(content, title?, cover?, excerpt?)` | новая статья (Markdown; title из `# ...`, если не задан; обложка из `cover`-URL или 1-й картинки → OG; `excerpt` явно или авто) → id, slug, URL |
|
|
120
|
+
| `article_update(id, content, title?)` | обновить свою статью по id |
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Формат графа и проверка
|
|
125
|
+
|
|
126
|
+
Граф — контейнер `retensy-bot-graph` (`nodes[]` + `edges[]`). Полная схема узлов/хэндлов и правила валидатора — в скилле:
|
|
127
|
+
- [`skills/build-bot-funnel/reference/schema.md`](skills/build-bot-funnel/reference/schema.md)
|
|
128
|
+
- [`skills/build-bot-funnel/reference/validation.md`](skills/build-bot-funnel/reference/validation.md)
|
|
129
|
+
|
|
130
|
+
Локальная проверка графа перед заливкой:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Telegram (по умолчанию)
|
|
134
|
+
node skills/build-bot-funnel/validate.mjs path/to/import.json
|
|
135
|
+
# Instagram-бот
|
|
136
|
+
node skills/build-bot-funnel/validate.mjs path/to/import.json --platform=INSTAGRAM
|
|
137
|
+
# MAX-бот
|
|
138
|
+
node skills/build-bot-funnel/validate.mjs path/to/import.json --platform=MAX
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Разработка
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
git clone https://github.com/retensy/retensy-mcp
|
|
147
|
+
cd retensy-mcp
|
|
148
|
+
RETENSY_MCP_TOKEN=zmcp_... node src/index.mjs # стартует stdio MCP-сервер
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Зависимостей нет — это голый JSON-RPC по stdio (протокол MCP `2024-11-05`).
|
|
152
|
+
|
|
153
|
+
## Безопасность
|
|
154
|
+
|
|
155
|
+
Токен = доступ к аккаунту по API. Не коммить его; держи в `env`. В конфигах храни ссылку `${RETENSY_MCP_TOKEN}`, не само значение.
|
|
156
|
+
|
|
157
|
+
## Лицензия
|
|
158
|
+
|
|
159
|
+
MIT — см. [LICENSE](LICENSE).
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@retensy/mcp",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "MCP server to build and publish Telegram, MAX and Instagram bot funnels/automations — and publish Markdown blog articles — in the Retensy Bots service. Zero dependencies.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": { "retensy-mcp": "src/index.mjs" },
|
|
7
|
+
"main": "src/index.mjs",
|
|
8
|
+
"publishConfig": { "access": "public" },
|
|
9
|
+
"files": ["src", "skills", ".mcp.json", ".claude-plugin", "README.md", "LICENSE"],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.mjs",
|
|
12
|
+
"check": "node --check src/index.mjs",
|
|
13
|
+
"test": "node scripts/smoke.mjs",
|
|
14
|
+
"validate": "node skills/build-bot-funnel/validate.mjs"
|
|
15
|
+
},
|
|
16
|
+
"engines": { "node": ">=18" },
|
|
17
|
+
"keywords": ["mcp", "telegram", "max", "instagram", "bot", "funnel", "automation", "articles", "blog", "markdown", "retensy", "claude-code", "model-context-protocol"],
|
|
18
|
+
"repository": { "type": "git", "url": "git+https://github.com/retensy/retensy-mcp.git" },
|
|
19
|
+
"homepage": "https://bots.retensy.com/bots",
|
|
20
|
+
"license": "MIT"
|
|
21
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-bot-funnel
|
|
3
|
+
description: Собрать воронку (сценарий) бота для сервиса /bots из текстового описания — сгенерировать валидный граф формата retensy-bot-graph, проверить его и (через MCP bot-graph) залить в бота, прогнать dry-run и опубликовать. Поддерживает Telegram, MAX и Instagram. Использовать, когда пользователь описывает воронку/прогрев/сценарий бота словами и просит «собрать», «сделать граф», «залить в бота», «опубликовать сценарий».
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Сборка воронки бота (retensy /bots)
|
|
7
|
+
|
|
8
|
+
Превращает **текстовое описание воронки** в валидный граф `retensy-bot-graph` и (опционально) публикует его через MCP-сервер `bot-graph`. Поддерживаются платформы **Telegram, MAX и Instagram** — у каждой свои триггеры и ограничения, описанные в schema.md.
|
|
9
|
+
|
|
10
|
+
**Различия платформ (кратко):**
|
|
11
|
+
- **Telegram** — полный функционал: `/start` и другие команды, все типы узлов, SUBSCRIBED, кнопки-контакты, рассылки.
|
|
12
|
+
- **MAX** — те же узлы, но без SUBSCRIBED/reply-клавиатур (мягкие предупреждения при публикации, не блокируют).
|
|
13
|
+
- **Instagram** — ограниченный набор узлов; вход только через комментарий/директ/историю (нет `/start`); нет рассылок; DELAY не более 24ч; ASK_QUESTION только TEXT/EMAIL/PHONE/NUMBER/CONTACT (CONTACT = ручной ввод номера, кнопки «Поделиться номером» в IG нет); онбординг через OAuth в разделе «Инструменты роста» (/growth), без токена бота.
|
|
14
|
+
|
|
15
|
+
## Когда применять
|
|
16
|
+
Пользователь описывает сценарий бота словами: приветствие → подписка → вопрос с кнопками → ветки → задержки → вебинар и т.п. Либо просит залить/опубликовать готовую воронку.
|
|
17
|
+
|
|
18
|
+
## Рабочий процесс (делай по шагам)
|
|
19
|
+
|
|
20
|
+
1. **Уточни вход.** Если описание неполное, спроси кратко: точка(и) входа (команда `/start` и т.п.), тексты сообщений, кнопки и куда они ведут, развилки/условия, задержки, финал. Не выдумывай маркетинговые тексты — проси у пользователя или помечай `[ВСТАВЬ ТЕКСТ]` только если он сам разрешил.
|
|
21
|
+
|
|
22
|
+
2. **Спланируй граф.** Нарисуй в голове дерево: триггеры → сообщения → ветки. Каждой кнопке-выбору — свой выходной хэндл (`btn_0`, `btn_1`…). Подробности типов узлов и хэндлов: [reference/schema.md](reference/schema.md).
|
|
23
|
+
|
|
24
|
+
3. **Сгенерируй JSON** формата `retensy-bot-graph` (см. схему). Требования к корректности (иначе не опубликуется) — в [reference/validation.md](reference/validation.md). Главное:
|
|
25
|
+
- У каждого `SEND_MESSAGE` непустой `config.text` (и продублируй в `cards[0].text`).
|
|
26
|
+
- id узлов и рёбер — валидные UUID, уникальные.
|
|
27
|
+
- Есть хотя бы один корневой `TRIGGER_*`; все нелистовые узлы достижимы от триггера; синхронных циклов нет (цикл только через `ASK_QUESTION`/`DELAY`/`SCHEDULE`).
|
|
28
|
+
- Кнопки-выборы разведены по хэндлам `btn_N` — у такой `CALLBACK`-кнопки `value` ОБЯЗАТЕЛЬНО пустой (`""`): бот сам сгенерит `callback_data`, непустой `value` ломает переход (`NO_MATCH`). Цвет кнопки — только `""`/`#34C759`/`#FF3B30`. Условия `CONDITION` — `yes`/`no`; вопрос (`ASK_QUESTION` или `SEND_MESSAGE` с `awaitReply:true`) — `valid`/`invalid`.
|
|
29
|
+
- **Не добавляй узлы `END`** — ветка завершается сама на узле без исходящих рёбер; явный «конец сценария» убран из редактора.
|
|
30
|
+
- `SEND_MESSAGE` умеет быть и сообщением, и **вопросом** (`awaitReply:true` + `saveTo`/`inputKind`/`validator`). У кнопок-ссылок (`kind:"URL"`) есть флаг `track:true` — на такой шаг ссылается условие `LINK_CLICKED`.
|
|
31
|
+
- **Медиа** (фото/видео/аудио/документ/кружок/галерея): загрузи файл инструментом `upload_file` (локальный `path` или `url` для перезаливки) → получишь публичный URL → вставь его в медиа-карточку `SEND_MESSAGE` (`url`, у `gallery` — `urls[]`) или в `SEND_PHOTO.photoUrl`. Уже загруженное — `list_files`. Бинарь в графе не хранится, только ссылки. Подробности — в schema.md.
|
|
32
|
+
- `CONDITION` умеет: теги, переменные, UTM-метки, имя/email/телефон из профиля, @username, подписку на канал (`SUBSCRIBED`), клик по ссылке шага (`LINK_CLICKED`), дату/время/день недели. Полная таблица `kind`/`op` — в schema.md.
|
|
33
|
+
- Пакет `ACTIONS` — до 30 действий (метки, профиль, HTTP, уведомления, интеграции GetCourse/amoCRM/Google Sheets/Я.Метрика, модерация группы). Список — в schema.md.
|
|
34
|
+
|
|
35
|
+
4. **Проверь локально** перед заливкой:
|
|
36
|
+
```bash
|
|
37
|
+
node "<путь к скиллу>/validate.mjs" <путь к import.json>
|
|
38
|
+
# Для IG-бота:
|
|
39
|
+
node "<путь к скиллу>/validate.mjs" <путь к import.json> --platform=INSTAGRAM
|
|
40
|
+
# Для MAX-бота:
|
|
41
|
+
node "<путь к скиллу>/validate.mjs" <путь к import.json> --platform=MAX
|
|
42
|
+
```
|
|
43
|
+
Скрипт ловит пустые сообщения, висячие рёбра, дубли и не-UUID id узлов/рёбер, недостижимые узлы, превышение 4096, кривые DELAY (вкл. `duration`+`unit`), непустой `value` у кнопок-выборов с ребром `btn_N`, неподдерживаемые цвета. При `--platform=INSTAGRAM` дополнительно: IG-allowlist узлов (`IG_NODE_UNSUPPORTED`), DELAY > 24ч (`IG_DELAY_OVER_24H`), неподдерживаемый inputKind (`IG_INPUT_UNSUPPORTED`). Исправь всё, что он покажет.
|
|
44
|
+
|
|
45
|
+
5. **Залей и опубликуй через MCP `bot-graph`** (если он подключён и пользователь просит публикацию):
|
|
46
|
+
- `list_bots` → выбрать `botId` (или `create_graph` в существующем боте).
|
|
47
|
+
- `create_graph(botId, name)` → получить `graphId`. Либо стартуй с готовой основы: `list_templates` → `create_graph_from_template(botId, templateId, name)`.
|
|
48
|
+
- `update_graph(graphId, nodes, edges, canvasMeta)` → залить узлы/рёбра.
|
|
49
|
+
- `dry_run(graphId, kind:"command", value:"start")` → прогнать стартовую ветку, проверить `runStatus`.
|
|
50
|
+
- `publish_graph(graphId)` → если вернулись `errors[]`, разобрать по `code`/`nodeId`, починить узлы, обновить, опубликовать снова.
|
|
51
|
+
- Управление сценариями: `clone_graph`, `rename_graph`, `set_active_graph` (переключить живой граф), `delete_graph` (активный нельзя — сначала переключи).
|
|
52
|
+
Если MCP не подключён — отдай готовый `import.json` и подскажи: /bots → граф → **Импорт**.
|
|
53
|
+
|
|
54
|
+
5b. **Правка СУЩЕСТВУЮЩЕГО / живого сценария — по умолчанию `edit_graph_live`, а НЕ clone+publish.**
|
|
55
|
+
- Когда пользователь просит «поправь сценарий X» (особенно если он уже открыт в редакторе или опубликован) — правь **ТОТ ЖЕ `graphId`** через **`edit_graph_live(graphId, nodes, edges)`**. Он сам снимает авто-бэкап предыдущего состояния (один rolling-граф «🔙 Авто-бэкап») и делает PUT на месте — **id не меняется**.
|
|
56
|
+
- Почему так: бэкенд при PUT/публикации шлёт `external_update` в WS-комнату → открытые редакторы перечитывают граф **вживую** (юзеру не надо перезаходить). Бот читает активный граф **заново из БД на каждое сообщение** → правка живого PUBLISHED-графа применяется **сразу, без отдельной публикации**.
|
|
57
|
+
- `clone_graph`+`publish_graph` каждый раз плодят НОВЫЙ id и переключают активный → юзер вынужден открывать новый граф. Так делай только для крупного рискованного рефактора, где нужна изолированная песочница.
|
|
58
|
+
- ⚠️ PUT не валидирует (валидирует только `publish`) → перед `edit_graph_live` живого графа **обязательно** прогони `validate.mjs` + `dry_run`. Откат: опубликовать граф «🔙 Авто-бэкап» (или скопировать его содержимое обратно).
|
|
59
|
+
|
|
60
|
+
6. **Отчитайся**: сколько узлов/веток, какие тексты помечены на проверку, ссылка/ id графа.
|
|
61
|
+
|
|
62
|
+
## Важные ограничения
|
|
63
|
+
- **Источник = текст** (этот режим). Если просят распознать с приватной Miro-доски — самый надёжный путь: CSV-экспорт из Miro; либо запуск залогиненного Chrome пользователя и съёмка экрана (headless WebGL-холст Miro не отдаёт). Это отдельный сценарий, не основной для этого скилла.
|
|
64
|
+
- **Авторизация MCP** — персональный токен (создаётся в вебе на `/bots/mcp-tokens`, формат `zmcp_…`, полный доступ). Если инструмент вернул «нет токена» или ошибку доступа — **вызови `setup`**, объясни пользователю шаги, попроси прислать токен и сохрани его через **`set_token`** (применяется сразу, без env/рестарта). Также работают env `RETENSY_MCP_TOKEN` и session-cookie.
|
|
65
|
+
- Бэкенд читает плоские поля `config.text`/`config.photoUrl`; редактор берёт текст из первой карточки `type:"text"`. Поэтому **всегда заполняй и `text`, и `cards`**.
|
|
66
|
+
|
|
67
|
+
## Файлы скилла
|
|
68
|
+
- [reference/schema.md](reference/schema.md) — формат графа, типы узлов, конфиги, хэндлы.
|
|
69
|
+
- [reference/validation.md](reference/validation.md) — правила валидатора бэкенда (коды ошибок) и `cardsToLegacy`.
|
|
70
|
+
- `validate.mjs` — оффлайн-проверка графа перед заливкой.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Формат графа `retensy-bot-graph`
|
|
2
|
+
|
|
3
|
+
## Контейнер импорта
|
|
4
|
+
```json
|
|
5
|
+
{
|
|
6
|
+
"format": "retensy-bot-graph",
|
|
7
|
+
"version": 1,
|
|
8
|
+
"name": "Название воронки",
|
|
9
|
+
"nodes": [ /* TgNode[] */ ],
|
|
10
|
+
"edges": [ /* TgEdge[] */ ],
|
|
11
|
+
"canvasMeta": {}
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
При заливке через MCP в `update_graph` передаются только `nodes`, `edges`, `canvasMeta`, `name`.
|
|
15
|
+
|
|
16
|
+
## Узел (TgNode)
|
|
17
|
+
```json
|
|
18
|
+
{ "id": "<uuid>", "type": "<NodeType>", "config": { ... }, "position": { "x": 0, "y": 0 } }
|
|
19
|
+
```
|
|
20
|
+
- `id` — валидный UUID (8-4-4-4-12), уникальный.
|
|
21
|
+
- `position` — раскладка на холсте (слева направо: шаг x ≈ 420; ветки разносим по y).
|
|
22
|
+
|
|
23
|
+
## Ребро (TgEdge) — «стрелка»
|
|
24
|
+
```json
|
|
25
|
+
{ "id": "<uuid>", "sourceNodeId": "<uuid>", "sourceHandle": "next", "targetNodeId": "<uuid>" }
|
|
26
|
+
```
|
|
27
|
+
`sourceHandle` — какой выход узла используется (см. ниже).
|
|
28
|
+
- `id`, `sourceNodeId`, `targetNodeId` — **валидные UUID** (бэкенд десериализует их как `java.util.UUID`; короткая строка вроде `"m1"` → HTTP 400 при заливке). `id` уникален среди рёбер.
|
|
29
|
+
|
|
30
|
+
## Типы узлов (NodeType) и их config
|
|
31
|
+
|
|
32
|
+
### Триггеры (точки входа, корневые)
|
|
33
|
+
|
|
34
|
+
#### Telegram / MAX
|
|
35
|
+
- `TRIGGER_COMMAND` — `{ "isRoot": true, "command": "start" }` (команда без `/`). Первый — с `isRoot:true`.
|
|
36
|
+
- `TRIGGER_CALLBACK` — `{ "matchMode": "EQUALS"|"STARTS_WITH", "value": "<callback_data>" }`
|
|
37
|
+
- `TRIGGER_TEXT` — `{ "matchMode": "ANY"|"EQUALS"|"CONTAINS"|"REGEX", "value": "..." }`
|
|
38
|
+
- `BROADCAST_FILTER` — режим рассылки (если есть — единственный триггер).
|
|
39
|
+
|
|
40
|
+
#### Instagram (только для IG-ботов)
|
|
41
|
+
IG-боты не поддерживают команды (`/start`). Вход — через взаимодействие с контентом или директ:
|
|
42
|
+
- `TRIGGER_IG_DM` — `{ "isRoot": true, "keywords": "хочу, каталог" }` — входящее сообщение в Instagram Direct. Это дефолтный триггер нового IG-графа (бэкенд сеет его при создании). `keywords` (опц.) — список через запятую или с новой строки; **регистронезависимо, совпадение по вхождению (contains)**; пусто = любое сообщение.
|
|
43
|
+
- `TRIGGER_IG_COMMENT` — `{ "isRoot": true, "keywords": "купить, цена" }` — комментарий к посту или Reel бота. `keywords` (опц.) — список через запятую или с новой строки; регистронезависимый contains; пусто = любой комментарий.
|
|
44
|
+
- `TRIGGER_IG_STORY_REPLY` — `{ "isRoot": true, "keywords": "хочу, вопрос" }` — ответ на историю бота. `keywords` (опц.) — список через запятую или с новой строки; регистронезависимый contains; пусто = любой ответ.
|
|
45
|
+
- `TRIGGER_IG_STORY_MENTION` — `{ "isRoot": true }` — упоминание бота в истории подписчика. **Текста нет → фильтрация по ключевым словам не применяется**; срабатывает на каждое упоминание независимо от содержимого истории.
|
|
46
|
+
|
|
47
|
+
> **Разные слова — разные сценарии:** добавь **несколько триггеров одного типа** с разными `keywords`; бэкенд берёт **первый совпавший по порядку узлов**. Триггер-«ловушку» с пустыми `keywords` (ловит всё) ставь **последним** — иначе он заблокирует все нижестоящие ключевые слова.
|
|
48
|
+
|
|
49
|
+
Для всех IG-триггеров реакция бота отправляется через Instagram Messaging API в течение **24-часового окна** после последнего входящего действия пользователя.
|
|
50
|
+
|
|
51
|
+
### Сообщения
|
|
52
|
+
- `SEND_MESSAGE` —
|
|
53
|
+
```json
|
|
54
|
+
{ "_title": "Заголовок узла", "parseMode": "PLAIN"|"HTML"|"MARKDOWN",
|
|
55
|
+
"text": "Текст сообщения",
|
|
56
|
+
"cards": [ { "id": "c1", "type": "text", "text": "Текст сообщения" } ],
|
|
57
|
+
"buttons": [ [ { "text": "Кнопка", "kind": "CALLBACK"|"URL", "value": "<url для URL; ПУСТО для CALLBACK>", "color": "", "track": true } ] ] }
|
|
58
|
+
```
|
|
59
|
+
ВСЕГДА заполняй и `text`, и `cards[0].text` одинаково. `buttons` — массив рядов (каждый ряд — массив кнопок).
|
|
60
|
+
- **Медиа-карточки** (`cards[].type`): `text`, `image`, `video`, `audio`, `file`, `voice`, `videonote` (кружок), `gallery` (альбом 2–10 фото), `question` (вопрос). У медиа-карточки ссылка на файл лежит в поле `url` (у `gallery` — массив `urls`). Сам файл (фото/видео/документ) в графе НЕ хранится — только URL. Чтобы получить URL, сперва загрузи файл инструментом **`upload_file`** (`path` локального файла или `url` для перезаливки) — он кладёт файл в библиотеку **/bots/files** и возвращает публичный `url`; его и вставляй в карточку. Примеры: `{ "id":"c2","type":"image","url":"https://bots.retensy.com/media/botmedia/<id>.jpg","text":"подпись (опц.)" }`, видео/аудио/файл/`voice`/`videonote` — так же с `url`; галерея — `{ "type":"gallery","urls":["https://…","https://…"] }`. Уже загруженные файлы — `list_files`, удалить — `delete_file`. Лимит 50 МБ; типы: image/video/audio/pdf/zip/doc(x)/xlsx/pptx/txt (SVG нельзя).
|
|
61
|
+
- `parseMode:"HTML"` (дефолт редактора) — текст должен быть **безопасным Telegram-HTML**: разрешены только `b,strong,i,em,u,ins,s,strike,del,code,pre,a[href],tg-spoiler,br`. Любой другой тег/атрибут → ошибка публикации `HTML_NOT_SAFE`. Не уверен — ставь `PLAIN`.
|
|
62
|
+
- **Кнопки-выборы (`kind:"CALLBACK"`)**: `value` ОСТАВЛЯЙ ПУСТЫМ (`""`). Бот сам сгенерит `callback_data` вида `n:<id узла>:<индекс>`, а переход задаётся ребром `btn_N` от кнопки. **Непустой `value`** трактуется как legacy-`callback_data` для отдельного узла `TRIGGER_CALLBACK` (у такой кнопки ребра `btn_N` быть не должно) — если поставить `value` обычной кнопке-выбору, переход по `btn_N` **сломается** (нажатие → `NO_MATCH`).
|
|
63
|
+
- **Кнопки-ссылки (`kind:"URL"`)**: `value` = URL. Могут иметь `"track": true` — клики считаются, и на такой шаг можно сослаться из условия `LINK_CLICKED` (см. ниже).
|
|
64
|
+
- **`color`** (опционально, и у CALLBACK, и у URL) — только стили, которые рендерит Telegram (как в основном боте / pengrad `ButtonStyle`): `""`=по умолчанию, `"#2EA6FF"`=primary (синий), `"#34C759"`=success (зелёный), `"#FF3B30"`=danger (красный). Других цветов нет.
|
|
65
|
+
- **Режим «Вопрос» (`awaitReply: true`)** — сообщение задаёт вопрос и ждёт ответ (паркуется как `ASK_QUESTION`). Доп. поля: `"saveTo":"name"` (обязателен, `[a-z_][a-z0-9_]{0,63}`), `"inputKind":"TEXT"|"PHOTO"|"DOCUMENT"|"CONTACT"|"LOCATION"`, `"validator":"ANY"|"PHONE"|"EMAIL"|"REGEX"`, `"regex":"..."`, `"retryText":"..."`, `"maxAttempts":3`. Выходы — `valid` / `invalid` (как у `ASK_QUESTION`), плюс `btn_N` для кнопок.
|
|
66
|
+
- **Канал ответа Instagram (`igReplyChannel`, только для IG-ботов)** — `"comment"` (по умолчанию) — публичный ответ под комментарием; `"dm"` — личное сообщение автору комментария (Private Reply). `"dm"` срабатывает только в графе с `TRIGGER_IG_COMMENT` (нужен `comment_id`): Meta разрешает **1 ЛС на комментарий**, 24-часовое окно на private reply не распространяется. Чтобы ответить и публично, и в ЛС — поставь два шага: один с `"igReplyChannel":"comment"`, второй с `"igReplyChannel":"dm"`.
|
|
67
|
+
- `SEND_PHOTO` — `{ "photoUrl": "https://...", "caption": "подпись (необязательно, ≤1024)", "igReplyChannel": "comment"|"dm" }` (`igReplyChannel` — только IG, см. `SEND_MESSAGE`). `photoUrl` можно получить через `upload_file`.
|
|
68
|
+
|
|
69
|
+
### Логика / ветвление
|
|
70
|
+
- `CONDITION` — проверка условий, выходы `yes` / `no`. `{ "match":"ALL"|"ANY", "conditions":[ { "kind":"...", "op":"...", "key":"...", "value":"..." } ] }`. `match:"ALL"` — все условия истинны; `"ANY"` — хотя бы одно. Полный список `kind`/`op`/полей — в разделе [«Условия CONDITION»](#условия-condition).
|
|
71
|
+
- `BRANCH` — `{ "cases":[ {"id":"c1","label":"...","expression":"var.x=='a'"} ], "hasDefault": false, "abTest": false }`. Выходы: `case_<id>` (+ `default`).
|
|
72
|
+
- `ASK_QUESTION` — вопрос со сбором ответа. `{ "promptText":"...","saveTo":"name","inputKind":"TEXT"|"PHOTO"|"DOCUMENT"|"CONTACT"|"LOCATION","validator":"ANY"|"PHONE"|"EMAIL"|"REGEX","regex":"...","retryText":"...","maxAttempts":3 }`. `inputKind` (по умолчанию `TEXT`) — что ждём в ответ (`CONTACT` → телефон: в Telegram показывается кнопка «Поделиться номером», в MAX/Instagram кнопки нет — номер вводится вручную и принимается как телефон на всех платформах; `LOCATION` → `lat,lon`, `PHOTO`/`DOCUMENT` → file_id). Выходы `valid` / `invalid`.
|
|
73
|
+
- `END` — `{}` (конец ветки). **Не добавляй `END`**: ветка и так завершается на узле без исходящих рёбер; явный «конец сценария» бесполезен и убран из палитры редактора. Тип оставлен лишь для совместимости со старыми графами.
|
|
74
|
+
|
|
75
|
+
### Тайминги
|
|
76
|
+
- `DELAY` — пауза. Три вида (`kind`):
|
|
77
|
+
- **`FIXED`** («Отправить через»): `{ "kind":"FIXED", "durationSec": 86400 }` — `durationSec` в **секундах** (60 = 1 мин, 3600 = 1 час, 86400 = 1 сутки). Редактор также пишет `{ "kind":"FIXED", "duration": 24, "unit":"MINUTES"|"HOURS"|"DAYS" }` (минуты/часы/дни — чтобы не вбивать большие числа). Для генерации проще `durationSec`.
|
|
78
|
+
- **`TOMORROW`** («Отправить завтра»): `{ "kind":"TOMORROW", "time":"18:00" }` — завтра в указанное время `HH:mm` (МСК), относительно момента, когда пользователь дошёл до узла.
|
|
79
|
+
- **`UNTIL`** («Отправить в»): `{ "kind":"UNTIL", "isoTimestamp":"2026-06-25T15:00:00Z" }` — конкретный момент в ISO-8601 (UTC). ⚠️ Рантайм читает только `isoTimestamp`; пары `isoDate`+`time` НЕ работают.
|
|
80
|
+
- `SCHEDULE` — `{ "isoDate":"2026-06-25", "time":"18:00", "timezone":"Europe/Moscow" }`. Выходы `scheduled` / `past`.
|
|
81
|
+
|
|
82
|
+
### Состояние / действия
|
|
83
|
+
- `SET_VARIABLE` (`{ "key":"name", "value":"..." }`), `ADD_TAG`/`REMOVE_TAG` (`{ "tag":"lead" }`), `FORMULA` (`{ "expression":"...", "saveTo":"name" }`)
|
|
84
|
+
- `ACTIONS` — непустой пакет действий `{ "actions":[ { "kind":"...", ...поля } ] }`. Допустимые `kind` (иначе ошибка `ACTION_UNKNOWN_KIND`):
|
|
85
|
+
- **метки/автоворонки**: `add_tag`, `remove_tag`, `autoflow_add`, `autoflow_remove` — поле `tag` (`[a-z0-9_-]{1,64}`)
|
|
86
|
+
- **профиль**: `set_field` — `key` (`[a-z_][a-z0-9_]{0,63}`) + `value`; `subscribe`, `unsubscribe`
|
|
87
|
+
- **HTTP**: `external_request`, `subscriber_webhook` — `url` (http/https) + `method`/`headersJson`/`bodyTemplate`
|
|
88
|
+
- **уведомления**: `notify` (`text`), `subscriber_email` (`email`,`text`), `agent_chat`
|
|
89
|
+
- **бот/шаг**: `stop_bot`, `delete_step_message`, `cancel_payment_subscription`
|
|
90
|
+
- **Google Таблицы (работает)**: `gsheets_send` — дописать строку-заявку в таблицу: `{ "kind":"gsheets_send", "googleEmail":"me@gmail.com", "spreadsheetId":"<id таблицы>", "sheetName":"Лист1", "cells":["{{from.first_name}}","{{var.phone}}","{{var.email}}"] }`. `cells` — значения по порядку (шаблоны), бот дописывает их строкой в конец листа. Google-аккаунт подключается В ВЕБЕ (`/bots` → у действия кнопка «Подключить Google»), НЕ через MCP — у пользователя уже должен быть подключён `googleEmail`. Нужны `googleEmail` + `spreadsheetId` + непустой `cells[]` (иначе `ACTION_GSHEETS_INCOMPLETE`).
|
|
91
|
+
- **интеграции (пока заглушки, no-op `integration_not_connected`)**: `getcourse_send`, `getcourse_order`, `amocrm_send`, `amocrm_update`, `yametrika_event`, `gsheets_get`, `gsheets_update`, `gsheets_write_cell`, `gsheets_read_cell`
|
|
92
|
+
- **модерация группы**: `group_unban`, `group_kick`, `group_approve`, `group_decline`
|
|
93
|
+
|
|
94
|
+
### Внешнее / прочее
|
|
95
|
+
- `CALL_WEBHOOK` — `{ "url":"https://...", "method":"POST", "bodyTemplate":"{...}", "timeoutMs":5000 }`. Выходы `ok` / `error`.
|
|
96
|
+
- `AI_REPLY`, `PAYMENT_LINK`.
|
|
97
|
+
|
|
98
|
+
## Условия CONDITION
|
|
99
|
+
|
|
100
|
+
Каждый элемент `conditions[]` — `{ "kind", "op", ...поля }`. Любая внутренняя ошибка условия = `false` (узел уходит в `no`).
|
|
101
|
+
|
|
102
|
+
| `kind` | `op` (допустимые) | Поля | Что проверяет |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| `TAG` | `HAS`, `NOT_HAS` | `value` — метка `[a-z0-9_-]{1,64}` | есть ли у пользователя тег |
|
|
105
|
+
| `VARIABLE` | `EQUALS`, `NOT_EQUALS`, `CONTAINS`, `NOT_EMPTY`, `EMPTY`, `GT`, `LT` | `key` — имя переменной `[a-z_][a-z0-9_]{0,63}`, `value` | значение переменной (`GT`/`LT` — числовое сравнение) |
|
|
106
|
+
| `UTM` | `EQUALS`, `CONTAINS`, `NOT_EMPTY`, `EMPTY` | `key` ∈ `source`/`medium`/`campaign`/`content`/`term`, `value` | UTM-метку клика (`utm_<key>`), регистронезависимо |
|
|
107
|
+
| `NAME` | `EQUALS`, `CONTAINS`, `NOT_EMPTY`, `EMPTY` | `value` | имя из профиля, регистронезависимо |
|
|
108
|
+
| `EMAIL` | `EQUALS`, `CONTAINS`, `NOT_EMPTY`, `EMPTY` | `value` | email из профиля |
|
|
109
|
+
| `PHONE` | `EQUALS`, `CONTAINS`, `NOT_EMPTY`, `EMPTY` | `value` | телефон из профиля |
|
|
110
|
+
| `USERNAME` | `EQUALS`, `CONTAINS` | `value` | @username пользователя Telegram |
|
|
111
|
+
| `SUBSCRIBED` | `SUBSCRIBED`, `NOT_SUBSCRIBED` | `key` — **числовой** id канала/группы (напр. `-1001234567890`); узнать числовой id подключённых каналов: `list_channels(botId)` | подписан ли пользователь на канал бота |
|
|
112
|
+
| `LINK_CLICKED` | `CLICKED`, `NOT_CLICKED` | `key` — **`id` узла-шага** с отслеживаемой URL-кнопкой (тот же UUID, что у `SEND_MESSAGE`) | кликал ли пользователь по ссылке этого шага |
|
|
113
|
+
| `CURRENT_DATE` | `BEFORE`, `AFTER`, `EQUALS` | `value` — дата `YYYY-MM-DD` | сегодняшнюю дату (МСК) |
|
|
114
|
+
| `CURRENT_TIME` | `BETWEEN` | `value`, `value2` — время `HH:mm` | текущее время в интервале (через полночь — если `value`>`value2`) |
|
|
115
|
+
| `DAY_OF_WEEK` | `IN` | `days` — массив из `MON`,`TUE`,`WED`,`THU`,`FRI`,`SAT`,`SUN` | день недели (МСК) |
|
|
116
|
+
|
|
117
|
+
Для `NOT_EMPTY`/`EMPTY` поле `value` не нужно. `UTM` без `key` всегда `false`.
|
|
118
|
+
|
|
119
|
+
**`SUBSCRIBED`** работает только если бот **админ** в канале/группе и канал «привязан» (бот узнаёт о членстве через хук `my_chat_member` — добавь бота в канал админом). `key` должен парситься в число, иначе условие = `false`. Профильные поля (`NAME`/`EMAIL`/`PHONE`) и UTM заполняются по ходу воронки (`ASK_QUESTION`→`saveTo`, диплинк-клик с UTM).
|
|
120
|
+
|
|
121
|
+
**`LINK_CLICKED`** проверяет факт клика по URL-кнопке конкретного шага. Чтобы условие работало: у нужного `SEND_MESSAGE` хотя бы одна кнопка `kind:"URL"` с `"track": true`, а в условии `key` = `id` этого узла-шага. Клик фиксируется через публичный редирект бота, поэтому условие имеет смысл ставить **после** `DELAY`/`ASK_QUESTION` (дай пользователю время кликнуть).
|
|
122
|
+
|
|
123
|
+
> **Платформа MAX.** Боты конструктора умеют работать и в мессенджере MAX. Там не поддерживаются `SUBSCRIBED`/`NOT_SUBSCRIBED` (нет членства в каналах) и reply-клавиатуры; публикация такого графа на MAX-бот вернёт **мягкие предупреждения** (не блокирует). Для Telegram-ботов всё работает как описано.
|
|
124
|
+
|
|
125
|
+
## Платформа Instagram
|
|
126
|
+
|
|
127
|
+
IG-боты подключаются через OAuth в разделе **«Инструменты роста»** (`/growth`) — **без вставки токена вручную**; у IG нет персонального бот-токена. После OAuth бот получает доступ к Messaging API через привязанный Instagram Business/Creator-аккаунт.
|
|
128
|
+
|
|
129
|
+
### Разрешённые типы узлов для IG-ботов
|
|
130
|
+
|
|
131
|
+
Только следующие (всё остальное — ошибка `IG_NODE_UNSUPPORTED` при публикации):
|
|
132
|
+
|
|
133
|
+
| Тип | Доступен в IG |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `TRIGGER_IG_COMMENT`, `TRIGGER_IG_DM`, `TRIGGER_IG_STORY_REPLY`, `TRIGGER_IG_STORY_MENTION` | ✅ (триггеры входа) |
|
|
136
|
+
| `SEND_MESSAGE`, `SEND_PHOTO` | ✅ |
|
|
137
|
+
| `BRANCH`, `CONDITION` | ✅ |
|
|
138
|
+
| `SET_VARIABLE`, `ADD_TAG`, `REMOVE_TAG`, `FORMULA` | ✅ |
|
|
139
|
+
| `ASK_QUESTION` | ✅ (с ограничениями — см. ниже) |
|
|
140
|
+
| `DELAY` | ✅ (не более 24ч — см. ниже) |
|
|
141
|
+
| `END` | ✅ |
|
|
142
|
+
| `TRIGGER_COMMAND`, `TRIGGER_CALLBACK`, `TRIGGER_TEXT` | ❌ |
|
|
143
|
+
| `BROADCAST_FILTER` (рассылки) | ❌ |
|
|
144
|
+
| `SCHEDULE`, `ACTIONS`, `CALL_WEBHOOK`, `AI_REPLY`, `PAYMENT_LINK` | ❌ |
|
|
145
|
+
|
|
146
|
+
### Ограничения IG-ботов
|
|
147
|
+
|
|
148
|
+
- **Нет команд** — вход только через `TRIGGER_IG_COMMENT` / `TRIGGER_IG_DM` / `TRIGGER_IG_STORY_REPLY` / `TRIGGER_IG_STORY_MENTION`. `/start` и другие команды не поддерживаются.
|
|
149
|
+
- **Нет рассылок** — `BROADCAST_FILTER` недоступен.
|
|
150
|
+
- **DELAY не более 24 часов** — Instagram доставляет сообщения только в течение 24-часового окна после последнего входящего действия (`IG_DELAY_OVER_24H`). `DELAY` с `kind=TOMORROW` или `kind=UNTIL` блокируются (они заведомо > 24ч). `kind=FIXED` с `durationSec > 86400` тоже блокируется.
|
|
151
|
+
- **`ASK_QUESTION`** — `inputKind`: `TEXT`, `EMAIL`, `PHONE`, `NUMBER`, `CONTACT` (`IG_INPUT_UNSUPPORTED` для остальных). `CONTACT` допустим: кнопки «Поделиться номером» в IG нет, поэтому бот отправляет текст-инструкцию и принимает номер, набранный вручную (как телефон). Нельзя запрашивать `LOCATION`, `PHOTO`, `DOCUMENT`.
|
|
152
|
+
- **Нет reply-клавиатур** — кнопки IG работают как inline (URL или Deep Link); стиль кнопок ограничен возможностями IG Messaging API.
|
|
153
|
+
- **Нет SUBSCRIBED** — условие «подписан на канал» недоступно.
|
|
154
|
+
- **Коммент → ЛС (Private Reply)** — на `SEND_MESSAGE`/`SEND_PHOTO` в графе с `TRIGGER_IG_COMMENT` укажи `"igReplyChannel": "dm"`, чтобы ответить автору комментария в Direct (а не публично под постом). По умолчанию (`"comment"`) — публичный ответ. Подробнее — `igReplyChannel` в разделе «Сообщения». **`"comment"` тоже требует, чтобы шаг был достижим от `TRIGGER_IG_COMMENT`** — иначе ошибка валидации `IG_COMMENT_REPLY_NO_COMMENT_TRIGGER` (в DM/story-флоу отвечать в комментарии некуда).
|
|
155
|
+
|
|
156
|
+
### Оффлайн-проверка IG-графа
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
node validate.mjs graph.json --platform=INSTAGRAM
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Ловит `IG_NODE_UNSUPPORTED`, `IG_DELAY_OVER_24H`, `IG_INPUT_UNSUPPORTED` — в дополнение ко всем обычным структурным проверкам.
|
|
163
|
+
|
|
164
|
+
## Кросс-платформенное копирование (Telegram ⇄ MAX)
|
|
165
|
+
|
|
166
|
+
Инструмент `copy_graph` копирует граф в **другого бота** пользователя (`targetBotId`), в т.ч. на другую платформу. Формат графа один и тот же; различается лишь платформа бота-получателя. При копировании в MAX-бот несовместимые узлы **адаптируются**, отчёт — в `notes[]`:
|
|
167
|
+
|
|
168
|
+
| code | severity | что значит |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `MAX_CONTACT_AS_TEXT` | TRANSFORM | вопрос с `inputKind=CONTACT` переписан в `inputKind=TEXT` + `validator=PHONE` (в MAX нет кнопки «поделиться контактом»; иначе узел стал бы тупиком) |
|
|
171
|
+
| `MAX_SUBSCRIBED_ALWAYS_NO` | MANUAL | условие `SUBSCRIBED` оставлено как есть, но в MAX всегда «не подписан» — проверьте ветвление вручную |
|
|
172
|
+
| `MAX_VOICE_AS_AUDIO` | INFO | голосовое уйдёт обычным аудио |
|
|
173
|
+
| `MAX_VIDEO_NOTE_AS_VIDEO` | INFO | кружок уйдёт обычным видео |
|
|
174
|
+
| `MAX_GALLERY_AS_ATTACHMENTS` | INFO | галерея уйдёт одним сообщением с вложениями |
|
|
175
|
+
| `MAX_DELETE_NOOP` | INFO | действие «удалить сообщение» в MAX игнорируется |
|
|
176
|
+
|
|
177
|
+
`preview: true` возвращает только `notes[]` (без копирования). Копирование в Telegram-бота (или в бота той же платформы) — точная копия, `notes[]` пустой. Новый граф создаётся как `DRAFT` с именем «… (copy)». Копировать в тот же бот нельзя (для дублирования — `clone_graph`).
|
|
178
|
+
|
|
179
|
+
## Выходные хэндлы (`sourceHandle`) — шпаргалка
|
|
180
|
+
| Узел | Хэндлы |
|
|
181
|
+
|---|---|
|
|
182
|
+
| обычный поток | `next` |
|
|
183
|
+
| кнопки сообщения (`buttons`) | `btn_0`, `btn_1`, … (по индексу кнопки, плоско по всем рядам) |
|
|
184
|
+
| `CONDITION` | `yes`, `no` |
|
|
185
|
+
| `BRANCH` | `case_<id>`, `default` |
|
|
186
|
+
| `ASK_QUESTION` | `valid`, `invalid` |
|
|
187
|
+
| `SEND_MESSAGE` с `awaitReply:true` | `valid`, `invalid` (+ `btn_N` для кнопок) |
|
|
188
|
+
| `CALL_WEBHOOK` | `ok`, `error` |
|
|
189
|
+
| `SCHEDULE` | `scheduled`, `past` |
|
|
190
|
+
| `DELAY` | `next` |
|
|
191
|
+
|
|
192
|
+
## Подстановки в тексте
|
|
193
|
+
`{{from.first_name}}`, `{{from.username}}`, `{{var.<имя>}}`, либо `{Имя}` как плейсхолдер. Имена переменных/меток: `[a-z_][a-z0-9_]{0,63}` (var) и `[a-z0-9_-]{1,64}` (tag).
|