# 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` — сколько сообщений и участников бот помнит, ID чата и видит ли он всю переписку; `/clear` — забыть сохранённую переписку (с подтверждением, имена участников остаются); `/import` — загрузить старую переписку из экспорта (см. ниже). - **Личности**: задаются в коде, из чата не редактируются. Сейчас одна — `boltun` (Dirty D). - `/personas` — список и выбор кнопками - `/persona ` — выбрать ## Импорт старой переписки Telegram не отдаёт ботам сообщения, написанные до их добавления, — их можно загрузить из экспорта. 1. Telegram Desktop → меню чата → «Экспорт истории чата». Сними галочки с фото, видео и файлов, формат — **«Машиночитаемый JSON»**. 2. Пришли боту `result.json` с подписью `/import` — прямо в этот чат или боту в личку (в личке бот сам поймёт, какой это чат). Можно и ответить `/import` на уже отправленный файл. 3. Бот ответит, сколько сообщений и участников добавил. - Импорт идемпотентен: повторная загрузка ничего не дублирует, уже сохранённые ботом сообщения и ники не перезаписываются. - Хранится не больше `HISTORY_LIMIT` последних сообщений на чат — для большой истории подними лимит. - Ников в экспорте нет, только имена: участники из экспорта находятся по имени, ник подтянется, когда человек напишет при боте. - Файл больше 20 МБ бот скачать не может (ограничение Bot API). Тогда положи его в volume (`/app/data`) и запусти в контейнере `node dist/scripts/import-history.js /app/data/result.json` (локально — `pnpm import-history:dev result.json`). ## Промпты и личности - `prompts/system.md` — общие правила бота (формат ответа, инструменты). Читается при старте. - `prompts/personas/.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.