Files
boltun-ai/README.md
yunogasai e812f1b1e2 Упрощение: общие хелперы, команды и упоминания по entities, фото только для моделей со зрением
- Одна formatUtc и один displayName(profile) вместо копий в разных слоях
- commandOf/mentionsUser/textWithoutMention работают по entities для текста и подписей;
  убраны askInCaption, regex-поиск упоминаний и отдельный composer.command("ask")
- Фото скачиваются только если модель их видит; пустой вопрос с картинкой формулирует промпт
- Поддержка фото задаётся явно для моделей по умолчанию, для своей AI_MODEL — через AI_IMAGE_INPUT
- Активность участников считается по сохранённой истории, а не отдельными счётчиками;
  один хендлер для message и edited_message, упоминания не пишутся дважды
- SQLite: synchronous = NORMAL для WAL
- Кнопки не-админов отклоняются через bot.drop(isAdmin)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 20:10:56 +02:00

62 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Boltun AI
Telegram-бот для групповых чатов с AI-личностями. Стек: Node.js, TypeScript, [grammY](https://grammy.dev), [Mastra](https://mastra.ai) (OpenRouter или DeepSeek напрямую), SQLite.
## Возможности
- Отвечает **только администраторам** из `ADMIN_IDS`, остальных молча игнорирует.
- **Вопросы**: `/ask <вопрос>`, упоминание `@бота`, ответ на сообщение бота, в личке — любое сообщение.
- **Контекст из чата**: бот сохраняет сообщения чата в SQLite (последние `HISTORY_LIMIT` на чат). Попросите «посмотри последние 100 сообщений и перескажи» — агент сам вызовет инструмент `read_chat_messages` и загрузит переписку в контекст. Если задать `/ask` ответом на чьё-то сообщение, оно тоже попадёт в контекст.
- **Участники**: бот запоминает имя, фамилию и ник каждого, кто пишет в чат (и тех, кого упомянули по имени-ссылке). Можно спрашивать «что писал @ivan?», «что думает Иван про выручку?», «кто тут самый активный?» — агент найдёт человека по нику или имени (инструменты `list_chat_members`, `read_member_messages`) и обращается к людям по имени.
- **Фото**: пришли фото с подписью, где упомянут бот (в личке — просто фото), или ответь на фото через `/ask` / упоминание — модель посмотрит картинку. Нужна модель со зрением (Gemini через OpenRouter по умолчанию); DeepSeek фото не видит и честно об этом скажет.
- **Память чата**: `/context` — сколько сообщений и участников бот помнит и видит ли он всю переписку; `/clear` — забыть сохранённую переписку (с подтверждением, имена участников остаются).
- **Личности**: задаются в коде, из чата не редактируются. Сейчас одна — `boltun` (Dirty D).
- `/personas` — список и выбор кнопками
- `/persona <id>` — выбрать
## Промпты и личности
- `prompts/system.md` — общие правила бота (формат ответа, инструменты). Читается при старте.
- `prompts/personas/<id>.md` — промпт личности; список личностей (id и название) — в `src/constants/personas.ts`.
- `pnpm seed` синхронизирует личности с БД (добавляет, обновляет, удаляет лишние). В Docker seed выполняется при каждом старте контейнера, локально — в `pnpm dev`.
Поменял промпт — перезапусти бота (или `pnpm seed` + перезапуск).
> Telegram Bot API не отдаёт историю чата задним числом: бот видит только сообщения, пришедшие после его добавления. Чтобы он видел все сообщения группы, отключите privacy mode в @BotFather (`/setprivacy` → Disable) **до** добавления в группу или сделайте бота админом группы.
## Запуск локально
```bash
nvm use # Node 24
pnpm install
cp .env.example .env # заполнить BOT_TOKEN, ADMIN_IDS и OPENROUTER_API_KEY (или DEEPSEEK_API_KEY)
pnpm dev
```
Свой Telegram ID можно узнать, написав боту `/whoami` в личку.
## Переменные окружения
| Переменная | Обязательна | Описание |
| --- | --- | --- |
| `BOT_TOKEN` | да | токен от @BotFather |
| `ADMIN_IDS` | да | Telegram ID админов через запятую |
| `OPENROUTER_API_KEY` | да* | ключ OpenRouter; модель по умолчанию — `openrouter/google/gemini-3.8-flash` (видит фото) |
| `DEEPSEEK_API_KEY` | да* | ключ DeepSeek напрямую (api.deepseek.com); используется по умолчанию, если ключа OpenRouter нет — модель `deepseek/deepseek-flash`, только текст |
| `AI_MODEL` | нет | явная модель Mastra model router `<провайдер>/<модель>`, например `deepseek/deepseek-v4-pro` |
| `AI_IMAGE_INPUT` | нет | `true`/`false` — отправлять ли модели фото. Для моделей по умолчанию выставляется само (Gemini — да, DeepSeek — нет); для своей `AI_MODEL` по умолчанию `false` |
\* нужен ключ того провайдера, чья модель выбрана; бот проверяет это при старте.
| `DB_PATH` | нет | путь к SQLite, по умолчанию `data/bot.db` (в Docker — `/app/data/bot.db`) |
| `HISTORY_LIMIT` | нет | сколько сообщений на чат хранить, по умолчанию 2000 |
## Деплой (Dokploy)
Приложение собирается из `Dockerfile` (Build Type: Dockerfile). Бот работает через long polling — домен и порты не нужны, достаточно одной реплики.
1. Создать проект и приложение, источник — этот git-репозиторий, ветка `main`.
2. Build Type — `Dockerfile`.
3. Environment — переменные из таблицы выше.
4. Volumes — volume, смонтированный в `/app/data` (там SQLite-база, иначе история и личности пропадут при передеплое).
5. Deploy.