Смена архитектуры с MTProto-юзербота (GramJS + строка сессии) на официальное бизнес-подключение Telegram (Bot API). Проект больше НЕ хранит сессию — работает только через бота с включённым Business Mode, подключённого в Настройках → Telegram для бизнеса → Чат-боты. Это соответствует ToS. Изменения: - удалено: scripts/login.js, src/userbot/client.js, модуль onetime - src/userbot/ → src/business/: events.js подписывает business_message / edited / deleted / connection и нормализует сообщение; context.js даёт ctx.api = bot.api - core/media.js: скачивание через getFile + fetch вместо GramJS - core/db.js: составной ключ PRIMARY KEY (chat_id, msg_id) — в Bot API message_id уникален только внутри чата - bot/panel.js: барьер владельца пропускает бизнес-апдейты (у них ctx.from это собеседник); убран UI одноразовых - index.js: сборка только на grammY, явный allowed_updates с бизнес-типами - config.js: убраны API_ID/API_HASH/SESSION, проверки на старте почищены - package.json: убраны зависимости telegram и input, скрипт login - README и .env.example переписаны под бизнес-подключение Границы: onetime невозможен через Business API (сервер отбрасывает self-destruct до доставки боту), медиа >~20 МБ не выгружаются (лимит getFile), перехват — только личные 1-на-1 чаты. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8.5 KiB
telegrambusiness
Личный Telegram-бот на Node.js, работающий через официальное бизнес-подключение Telegram (Business API). Бот подключается к вашему аккаунту как чат-бот для бизнеса и сохраняет то, что собеседник удалил:
- antidelete — сообщения (и медиа), которые собеседник удалил в личке.
Всё управление и все перехваты приходят в этого же бота — в личку владельца. Наружу бот ничего не пишет, слеш-команд в чатах нет.
✅ Без сессии, по правилам Telegram. Проект не хранит строку сессии и не использует MTProto-юзербот. Работает исключительно через штатную функцию «Telegram для бизнеса» — обычный бот от @BotFather с включённым Business Mode. Это соответствует ToS.
⚠️ Дисклеймер. Инструмент для личного использования на своём аккаунте и своей переписке. Не применяйте против других людей.
Как устроено (важно понимать до запуска)
Telegram в бизнес-событии удаления (deleted_business_messages) присылает только id сообщений — без текста и без медиа. Поэтому «сохранить удалённое» реактивно невозможно: бот заранее кэширует каждое входящее в личке, а при удалении достаёт его из своего кэша. То же с медиа — файл качается в момент прихода, после удаления ссылка мертва.
Отсюда следствия:
- тексты и метаданные пишутся в локальную БД (
node:sqlite), зашифрованы (AES-256-GCM); - медиа лежат отдельными зашифрованными файлами в
data/media/; - всё чистится по
RETENTION_DAYS.
Чего этот подход НЕ умеет
- Одноразовые медиа («просмотр один раз», самоуничтожающиеся) перехватить невозможно: сервер Bot API отбрасывает такие сообщения ещё до доставки боту. Это ограничение платформы, обойти его без юзербота нельзя — а от юзербота мы отказались сознательно.
- Медиа больше ~20 МБ не скачиваются: у Bot API (
getFile) лимит на размер файла. Такое медиа сохранится только как пометка о типе.
Установка
Нужен Node.js ≥ 22.5 (для встроенного node:sqlite).
npm install
cp .env.example .env
Заполните .env:
- BOT_TOKEN — новый бот у @BotFather.
- OWNER_ID — ваш id (напишите @userinfobot).
- ENCRYPTION_KEY — сгенерируйте:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))".
Включите боту режим бизнеса и подключите его к аккаунту:
- @BotFather →
/mybots→ выберите бота → Bot Settings → Business Mode → Enable. - В приложении Telegram: Настройки → Telegram для бизнеса → Чат-боты → добавьте своего бота. Дайте ему право читать/управлять сообщениями.
Запуск:
npm run dev
Откройте своего бота, нажмите Start — появится панель. Когда собеседник удалит сообщение, бот пришлёт уведомление с кнопкой «Открыть».
npm run dev — с автоперезапуском, npm start — обычный запуск.
Как добавить модуль
Модуль — файл в src/modules/. Загрузчик обходит папку рекурсивно; файлы на _ игнорируются. Объявляйте только нужные хуки:
// src/modules/keywords.js
export default {
name: 'keywords',
description: 'Уведомлять, если во входящем есть ключевое слово',
async onMessage(msg, ctx) {
if (!msg.isPrivateIncoming) return;
if (/срочно/i.test(msg.text ?? '')) {
await ctx.capture('deleted', { // переиспользуем ленту перехватов
chatId: msg.chatId,
senderId: msg.senderId,
sender: msg.sender,
text: msg.text,
});
}
},
};
Хук получает нормализованное сообщение: { chatId, msgId, senderId, sender, text, media, isPrivateIncoming }.
| Хук | Когда вызывается |
|---|---|
setup(_, ctx) |
Один раз при старте |
onMessage(msg, ctx) |
Новое бизнес-сообщение (business_message) |
onEdited(msg, ctx) |
Правка (edited_business_message) |
onDeleted(event, ctx) |
Удаление; event.chatId, event.msgIds |
ctx даёт: api (grammY bot.api), registry и capture(kind, data) — сохранить перехват и уведомить владельца. Ошибка в одном модуле не роняет остальные (registry.js — runHook).
Устройство
src/
index.js сборка: бот + модули + чистка
config.js .env, проверка на старте
core/
crypto.js AES-256-GCM (шифр текста и медиа)
db.js node:sqlite: кэш входящих + лента перехватов
media.js тип медиа, скачать через getFile + шифровать
loader.js обход src/modules
registry.js реестр модулей, runHook
logger.js
business/
events.js подписка business_message/edited/deleted → модули
context.js ctx.capture — мост модуль↔БД↔бот
modules/
cache.js кэширует входящие (основа antidelete)
antidelete.js удаление → достать из кэша → перехват
bot/
panel.js бот: инлайн-панель, лента, выдача медиа
Поток: входящее (business_message) → cache кэширует текст и качает медиа → при удалении (deleted_business_messages) antidelete поднимает из кэша → ctx.capture пишет в БД и шлёт уведомление → панель показывает и отдаёт расшифрованный файл.
Что стоит учесть
- Перехватываются только входящие в личных диалогах. Свои сообщения — нет (см.
isPrivateIncomingв events.js). - Id сообщения в Bot API уникален только внутри чата, поэтому в БД составной ключ
(chat_id, msg_id). - Если вы отключите бизнес-подключение в настройках Telegram, перехват приостановится (бот залогирует это по апдейту
business_connection). CACHE_MEDIA=1качает медиа каждого входящего, чтобы восстанавливать удалённые картинки. Это ест диск — при=0останется только текст и пометка о типе медиа.- Ключ шифрования — в
.env. Потеряете ключ — расшифровать хранилище нельзя. - Хранилище локальное;
data/в.gitignore.