The bot is now public: anyone connects it to their own Telegram Business account and gets antidelete for their own private chats. On connection (business_connection enabled) and on /start it sends a colorful welcome describing features, how to connect, and limitations. - connections.js: resolve a connection's owner from business_connection_id (memo -> DB -> getBusinessConnection). Business messages have no outgoing flag, so the owner's own messages are filtered by comparing from.id; if the owner can't be resolved the message is not cached. - Strict per-owner isolation: captures scoped by owner_id; the panel shows each user only their own feed; notifications go to the owner's chat. - db.js: multi-tenant schema (connections table; messages keyed by (conn_id, chat_id, msg_id); captures/counts scoped by owner_id). - media.js: key cached-media filenames by (connId, chatId, msgId) to prevent one tenant overwriting another's encrypted media. - panel.js: drop the owner-only barrier; /start sends welcome + own feed. - config.js: OWNER_ID is now optional (service logs only, grants no access). - Docs: README/.env.example rewritten for the multi-tenant model and the shared-key privacy caveat. - Stop tracking .claude/settings.local.json; restore the Hcrgram/ ignore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11 KiB
telegrambusiness
Telegram-бот на Node.js, работающий через официальное бизнес-подключение Telegram (Business API). Бот доступен всем: любой человек подключает его к своему бизнес-аккаунту как чат-бота и получает сохранение того, что собеседник удалил:
- antidelete — сообщения (и медиа), которые собеседник удалил в личке.
Бот многопользовательский. При подключении он присылает владельцу красочное приветствие с описанием функций, а все перехваты приходят этому же владельцу в личку. Данные строго изолированы: каждый видит только свои перехваты. Наружу бот ничего не пишет, слеш-команд в чужих чатах нет.
✅ Без сессии, по правилам Telegram. Проект не хранит строку сессии и не использует MTProto-юзербот. Работает исключительно через штатную функцию «Telegram для бизнеса» — обычный бот от @BotFather с включённым Business Mode. Это соответствует ToS.
⚠️ Дисклеймер. Инструмент сохраняет входящие в личке того, кто подключил бота к своему аккаунту. Оператор бота, у которого лежат
data/иENCRYPTION_KEY, технически имеет доступ к расшифровке. Разворачивайте и используйте ответственно, уважая приватность собеседников.
Как устроено (важно понимать до запуска)
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.
- ENCRYPTION_KEY — сгенерируйте:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))". - OWNER_ID — необязательно, только для служебных логов оператора. Доступа к чужим данным не даёт (изоляция по владельцу), можно не задавать.
Включите боту режим бизнеса — этого достаточно, дальше его подключит каждый пользователь сам:
- @BotFather →
/mybots→ выберите бота → Bot Settings → Business Mode → Enable.
Запуск:
npm run dev
Дальше это делает любой пользователь для своего аккаунта: Настройки → Telegram для бизнеса → Чат-боты → добавить бота и разрешить управление сообщениями. Сразу после подключения бот пришлёт в личку приветствие. Когда собеседник удалит сообщение, придёт уведомление с кнопкой «Открыть». Команда /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', { // переиспользуем ленту перехватов
ownerId: msg.ownerId, // чья это лента (изоляция)
ownerChatId: msg.ownerChatId, // куда уведомлять
chatId: msg.chatId,
senderId: msg.senderId,
sender: msg.sender,
text: msg.text,
});
}
},
};
Хук получает нормализованное сообщение с уже разрешённым владельцем подключения: { connId, ownerId, ownerChatId, chatId, msgId, senderId, sender, text, media, isPrivateIncoming }.
| Хук | Когда вызывается |
|---|---|
setup(_, ctx) |
Один раз при старте |
onMessage(msg, ctx) |
Новое бизнес-сообщение (business_message) |
onEdited(msg, ctx) |
Правка (edited_business_message) |
onDeleted(event, ctx) |
Удаление; event.connId, event.ownerId, event.ownerChatId, event.chatId, event.msgIds |
ctx даёт: api (grammY bot.api), registry и capture(kind, data) — сохранить перехват под data.ownerId и уведомить владельца в data.ownerChatId. Ошибка в одном модуле не роняет остальные (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/connection → модули
connections.js резолвер владельца по business_connection_id
context.js ctx.capture — мост модуль↔БД↔бот
modules/
cache.js кэширует входящие (основа antidelete)
antidelete.js удаление → достать из кэша → перехват
bot/
panel.js бот: приветствие, инлайн-панель, лента, выдача медиа
welcome.js текст красочного приветствия (/start и подключение)
Поток: подключение (business_connection) → запоминаем владельца и шлём приветствие → входящее (business_message) → cache кэширует текст и качает медиа под этим подключением → при удалении (deleted_business_messages) antidelete поднимает из кэша по (connId, chatId, msgId) → ctx.capture пишет в БД под ownerId и шлёт уведомление в ownerChatId → панель показывает владельцу только его записи и отдаёт расшифрованный файл.
Что стоит учесть
- Многопользовательский режим. Бота подключают к себе разные люди; данные строго изолированы по владельцу (
owner_id), в панели каждый видит только своё. - У бизнес-сообщений нет флага «исходящее», поэтому своё сообщение владельца отсекаем сравнением
from.idс владельцем подключения (business_connection_id→ владелец, см. connections.js). Если владельца распознать не удалось, сообщение не кэшируется. - Перехватываются только входящие в личных диалогах (см.
isPrivateIncomingв events.js). - Id сообщения в Bot API уникален только внутри чата, поэтому в БД составной ключ
(conn_id, chat_id, msg_id). - Если владелец отключит бизнес-подключение в настройках Telegram, перехват для него приостановится (бот залогирует это по апдейту
business_connection). CACHE_MEDIA=1качает медиа каждого входящего, чтобы восстанавливать удалённые картинки. Это ест диск — при=0останется только текст и пометка о типе медиа.- Ключ шифрования — в
.env, один на все подключения. Потеряете ключ — расшифровать хранилище нельзя. - Хранилище локальное;
data/в.gitignore. Схема БД сменилась на мультитенантную — при обновлении со старой версии удалите прежнююdata/перед первым запуском.