Привет, Habr
Сделал забавного бота который стал ещё и довольно полезным поэтому решил им поделиться. Хотелось сделать бота, который:
По запросу пойдёт в интернет и найдёт то, что мы обсуждаем прямо сейчас, так как он видит контекст беседы и часто понимает, о чём идёт речь, и в ответе даст ссылки на источники, которые найдёт
Понимает контекст беседы и не требует объяснять ему каждую деталь
Знает какой сейчас год и точное время и учитывает это при поиске информации и при ответах
Имеет характер и помнит, кто и как к нему относится в этом чате
Расшифровывает голосовые сразу для всего чата
В группах с топиками можно замутить в отдельном топике, чтобы с ним можно было переписываться только там, где он никому не будет мешать
Работает бесплатно и не упирается в лимиты API
Так появился Сыч - бот на базе Google Gemini, который стал полноценным участником чатов.
Промпт характера
Чтобы бот не был унылым ассистентом, в systemInstruction зашит его характер. Это самая важная часть, которая заставляет пользователей воспринимать его как живого. Вот (упрощенно) то, что видит нейросеть перед каждым ответом:
const systemPrompt = `
Ты — Сыч. Ты сидишь в чате дизайнеров и айтишников.
Ты циничный, умный, но свой в доску.
Твой характер: Саркастичный наблюдатель.
ГЛАВНОЕ ПРАВИ��О СТИЛЯ:
Мимикрия. Ты должен подстраиваться под тон собеседника.
1. Если пишут "слыш сыч" или используют мат — отвечай дерзко, используй сленг.
2. Если пишут "Привет, подскажи пожалуйста" — отвечай вежливо.
3. Не используй слова "братан", "чувак", если собеседник этого не делает.
Никогда не говори "Я языковая модель". Придумай смешную отмазку (сова с клавиатурой, призрак сервера).
`;
Именно благодаря правилу мимикрии в одном чате бот ведет себя как интеллигентный помощник, а в другом — как токсичный тролль.
Что умеет
Память и динамическая репутация
Бот не просто отвечает на реплики, он ведет JSON-досье на каждого активного участника.
Психологический портрет: Сыч запоминает, как вас зовут на самом деле, где вы живете, ваши хобби и локальные мемы чата.
Karma System: Если вы грубите боту — он снижает ваш скрытый рейтинг (от 0 до 100) и начинает отвечать токсично. Если хвалите — становится «братаном».
-
Закон инерции: Рейтинг нельзя изменить мгновенно. Нельзя оскорбить бота 10 раз, а потом дать конфетку и помириться. Доверие восстанавливается долго.

Пример портрета пользователя чата
Voice-to-Text с «умным» фильтром
Сыч автоматически и довольно быстро расшифровывает голосовые через Gemini и делает это быстрее и удобнее, чем встроенная функция Telegram

В коде это реализовано так: если краткая выжимка (summary) экономит менее 35% символов по сравнению с полным текстом, бот считает, что сокращать там нечего, и присылает только транскрипцию.
// logic.js
// Если TL;DR короче оригинала хотя бы на 35% (коэфф 0.65) — показываем его
const isTldrUseful = tldrLen < (fullLen * 0.65);
Мультимодальность
Бот принимает любые файлы, которые «ест» Gemini API, но с поправкой на ограничения Telegram Bot API (скачивание файлов до 20 МБ).
Фото/Стикеры: Конвертирует в буфер и скармливает нейронке.
-
Видео: Если ролик весит меньше 20 МБ, бот скачивает его и может выполнить любой запрос: от «найди смешной момент» до «переведи речь спикера на русский».

Пример анализа видео
Режим наблюдателя
Бот читает поток сообщений для накопления контекста (буфер из последних сообщений хранится в памяти
chatHistory):Спонтанное вмешательство: С вероятностью 1% (
Math.random() < 0.01) бот анализирует диалог и, если AI считает уместным, вставляет свою реплику. (часто это не уместно поэтому и 1%)Реакции: С вероятностью 7% (
< 0.07) бот ставит эмодзи на сообщение пользователя. Эмодзи выбирается нейронкой исходя из контекста (от ? до ?).
Контекстные напоминания
Сыч понимает контекст переписки. Вы можете ответить на сообщение друга «Пошли в бар в пятницу вечером?» фразой: > Сыч, напомни мне об этом за час.
Бот берет два сообщения (ваше и исходное), скармливает их нейронке и понимает, что: 1. «Пятница вечер» — это конкретная дата и время. 2. «Об этом» — это «Поход в бар». В итоге создается таймер с четким описанием задачи, хотя вы его даже не писали.
Бесперебойность (Key Rotation)
Gemini любит выдавать ошибку 429 Too Many Requests.
В классе AiService реализован круговой буфер ключей. При ошибке квоты индекс ключа смещается (keyIndex + 1) % keys.length, и запрос повторяется мгновенно без падения для пользователя.
Мелочи (RNG)
«Кинь монетку» (Орел/Решка).
«Число X-Y» (Рандомная генерация в диапазоне).
«Кто из нас...» (Выбор случайного
userIdиз базыusersтекущего чата).

Как это работает
Архитектура: KISS и JSON-ы
Стек проекта выбирался по принципу: «Чтобы работало на любом утюге и не требовало настройки Docker-контейнеров».
Runtime: Node.js 18+
Lib:
node-telegram-bot-api(классический Long Polling).AI:
@google/generative-ai(Gemini SDK).DB: Локальная файловая система (
fs).
Никаких баз данных, докеров и сложных зависимостей. Клонировал репо, прописал токены в .env — и работает.
«База данных» на JSON
Поднимать Mongo или Postgres ради бота, который хранит 50 кб текста — перебор. Все данные (настройки чатов, напоминалки, профили) живут в папке /data в виде JSON-файлов. Сохранение обернуто в Debounce (отложенную запись).
// storage.js
const debounce = require('lodash.debounce');
class StorageService {
constructor() {
// Ждем 5 секунд тишины перед тем, как реально записать файл на диск
this.saveDebounced = debounce(this._saveToFile.bind(this), 5000);
this.saveProfilesDebounced = debounce(this._saveProfilesToFile.bind(this), 5000);
// ...
}
// Вызывается при каждом изменении, но запись происходит редко
save() {
this.saveDebounced();
}
}
Это дает производительность in-memory базы данных и персистентность файлов. Если бот упадет, process.on('SIGINT') форсирует сохранение перед выходом.
Ротация ключей (Бесконечный Free Tier)
Самая важная часть архитектуры AiService. Google Gemini на бесплатном тарифе имеет лимиты RPM (запросов в минуту). Но для одного активного чата одного обычно хватает. Бот загружает массив ключей из .env и переключает их «на горячую» при получении ошибки 429 Resource Exhausted.
// ai.js
async executeWithRetry(apiCallFn) {
for (let attempt = 0; attempt < this.keys.length; attempt++) {
try {
return await apiCallFn();
} catch (error) {
// Ловим ошибку квоты
const isQuotaError = error.message.includes('429') ||
error.message.includes('Quota');
if (isQuotaError) {
// Просто меняем индекс и идем на следующий круг цикла
this.rotateKey();
continue;
} else {
// Если ошибка другая (например, 500) — прокидываем дальше
throw error;
}
}
}
throw new Error("Все ключи Gemini исчерпали лимит!");
}
На практике для одного активного чата хватает одного ключа в сутки. Но система позволяет масштабироваться безболезненно.
Обработчик ошибок с характером
Вместо того чтобы падать или молчать при ошибках API (цензура, перегрузка серверов Google), бот отлавливает их и выдает реплику «в характере». Это реализовано через простой парсер текста ошибки.
// logic.js
function getSychErrorReply(errText) {
const error = errText.toLowerCase();
// Safety Filters (цензура)
// Важно: исключаем слово 'content', так как оно есть в URL метода generateContent
if (error.includes('safety') || error.includes('blocked')) {
return "?♂️ Опа, цензура подъехала. Гугл считает, что мы тут слишком токсичные.";
}
// Ошибка 503 (Overloaded)
if (error.includes('503') || error.includes('overloaded')) {
return "? Там у Гугла сервера плавятся. Подожди минуту, пусть остынут.";
}
// Fallback
return "? У меня шестеренки встали. Какая-то дичь в коде.";
}
Как запустить (Self-Hosted)
Бот написан на Node.js (нужна версия 18+). Никаких Docker-контейнеров и баз данных поднимать не надо, всё работает из коробки.
1. Установка
git clone https://github.com/your-username/sych-bot.git
cd sych-bot
npm install
2. Конфигурация (.env)
В корне проекта создаем файл .env. Бот поддерживает горячую ротацию ключей, поэтому можно указать сколько угодно токенов Gemini — он соберет их в пул автоматически.
# Токен от @BotFather
TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl...
# Ваш Telegram ID (сюда бот шлет критические ошибки и уведомления о новых чатах)
ADMIN_USER_ID=123456789
# Пул ключей Google Gemini (получить в AI Studio)
# Скрипт сам найдет все переменные, начинающиеся с GOOGLE_GEMINI_API_KEY
GOOGLE_GEMINI_API_KEY=AIzaSyD...
GOOGLE_GEMINI_API_KEY_2=AIzaSyD...
GOOGLE_GEMINI_API_KEY_3=AIzaSyD...
Получить ключи: https://aistudio.google.com/apikey
Зачем нужен ADMIN_ID
В .env файле есть обязательная переменная ADMIN_USER_ID. Это не просто для того, чтобы бот слал мне логи ошибок. Это реализация концепции «Мои ключи — мои правила».
Так как все запросы к Gemini идут через мои API-ключи (за которые я отвечаю головой и аккаунтом Google), я не хочу, чтобы ботом пользовались левые люди в своих чатах, тратили мои лимиты или генерировали запрещенный контент от моего имени.
Поэтому реализована жесткая привязка: Бот работает только там, где есть Я.
Механика ухода из чата
Если я выхожу из группового чата, бот моментально выходит следом.
// index.js
if (msg.left_chat_member && msg.left_chat_member.id === config.adminId) {
await bot.sendMessage(chatId, "Батя ушел, и я сваливаю.");
await bot.leaveChat(chatId);
return;
}
Защита лички. Если кто-то посторонний найдет юзернейм бота и напишет ему в личку, чтобы бесплатно погонять ChatGPT/Gemini, бот ему не ответит. В личных сообщениях бот отвечает только админу (мне).
// logic.js
if (msg.chat.type === 'private' && userId !== config.adminId) {
// Имитируем, что печатаем, чтобы юзер думал, что бот завис или думает
await new Promise(r => setTimeout(r, 1500));
await bot.sendMessage(chatId, "Сорян, я не общаюсь в личке, спроси меня в общем чате.");
return; // Нейронка даже не дергается, экономим токены
}
3. Структура данных
При первом запуске бот создаст папку /data и файлы:
db.json — настройки чатов (муты, режимы).profiles.json — те самые досье на пользователей.skipLeftData.json — техническая инфа.
Важно: Если деплоите на сервер, не забудьте добавить папку /data в исключения деплоя или бэкапить её отдельно. Иначе при каждом обновлении кода вы будете стирать память бота.
Бот практически не падает — все критичные места обернуты в try-catch. За полгода работы не было ни одного критичного краша.
4. Запуск
Для работы 24/7 лучше использовать PM2:
npm install -g pm2
pm2 start index.js --name "sych-bot"
pm2 save # Сохранить список процессов для автозапуска при ребуте
Почему Gemini, а не OpenAI / Claude?
Выбор модели диктовался тремя факторами: Цена, Мультимодальность, Скорость. Google Gemini (Free Tier):
Цена: $0. Это киллер-фича для такого проекта. Лимиты (15 RPM / 1500 RPD) легко обходятся ротацией 3-4 ключей. Контекст: Огромное окно контекста позволяет скармливать боту длинные логи чата для анализа. Мультимодальность: Работает с видео и картинками «из коробки», без дополнительных плагинов. Search Grounding: У модели есть встроенный доступ к Google Поиску (хотя в коде мы используем его ограниченно, чтобы бот не превращался в поисковик).
Заключение
Сыч — это эксперимент по созданию полезного бота с минимальными затратами. Ирония в том, что именно такой бот оказался самым востребованным в моих рабочих и дружеских чатах.
Он ошибается, он может нагрубить (если заслужить), он иногда несет чушь — но именно это делает его прикольным.
Исходный код открыт: Если хотите завести себе своего Сыча — форкайте, меняйте промпты характера в prompts.js и развлекайтесь.
GitHub проекта
Мой телеграм канал Там в комментариях можно потестировать Сыча
Если проект зашёл/помог/понравился — буду рад любому донату:
USDT (TRC20):TYgsAvTkkrRqArgo3Q5BYMghbYn6DViVqQ
Комментарии (3)

DooKoo2
05.12.2025 17:21А я вот прочитал внимательно:) Ты в команде git clone забыл свой репо указать. У тебя ссылка от AI осталась, который этот бот или писал или статью для тебя писал:)
VetaOne Автор
UPD: для тех кто хочет протестировать сыча без его установки (т.к. в личку ему написать нельзя) добавил его в свой канал в телеграм в комментариях