Files
arestools/README.md
T

130 lines
11 KiB
Markdown
Raw Normal View History

# telegrambusiness
Telegram-бот на Node.js, работающий через **официальное бизнес-подключение** Telegram (Business API). Бот **доступен всем**: любой человек подключает его к **своему** бизнес-аккаунту как чат-бота и получает сохранение того, что собеседник удалил:
- **antidelete** — сообщения (и медиа), которые собеседник удалил в личке.
Бот многопользовательский. При подключении он присылает владельцу **красочное приветствие** с описанием функций, а все перехваты приходят этому же владельцу в личку. Данные строго изолированы: **каждый видит только свои** перехваты. Наружу бот ничего не пишет, слеш-команд в чужих чатах нет.
> ✅ **Без сессии, по правилам Telegram.** Проект **не хранит строку сессии** и не использует MTProto-юзербот. Работает исключительно через штатную функцию «Telegram для бизнеса» — обычный бот от [@BotFather](https://t.me/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`).
```bash
npm install
cp .env.example .env
```
Заполните `.env`:
1. **BOT_TOKEN** — новый бот у [@BotFather](https://t.me/BotFather).
2. **ENCRYPTION_KEY** — сгенерируйте: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`.
3. **OWNER_ID** — необязательно, только для служебных логов оператора. Доступа к чужим данным не даёт (изоляция по владельцу), можно не задавать.
Включите боту режим бизнеса — этого достаточно, дальше его подключит каждый пользователь сам:
1. [@BotFather](https://t.me/BotFather) → `/mybots` → выберите бота → **Bot Settings****Business Mode****Enable**.
Запуск:
```bash
npm run dev
```
Дальше это делает **любой пользователь** для своего аккаунта: **Настройки → Telegram для бизнеса → Чат-боты** → добавить бота и разрешить управление сообщениями. Сразу после подключения бот пришлёт в личку приветствие. Когда собеседник удалит сообщение, придёт уведомление с кнопкой «Открыть». Команда **/start** в любой момент откроет приветствие и личную ленту перехватов.
`npm run dev` — с автоперезапуском, `npm start` — обычный запуск.
## Как добавить модуль
Модуль — файл в `src/modules/`. Загрузчик обходит папку рекурсивно; файлы на `_` игнорируются. Объявляйте только нужные хуки:
```js
// 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](src/core/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](src/business/connections.js)). Если владельца распознать не удалось, сообщение не кэшируется.
- Перехватываются только **входящие в личных диалогах** (см. `isPrivateIncoming` в [events.js](src/business/events.js)).
- Id сообщения в Bot API уникален **только внутри чата**, поэтому в БД составной ключ `(conn_id, chat_id, msg_id)`.
- Если владелец отключит бизнес-подключение в настройках Telegram, перехват для него приостановится (бот залогирует это по апдейту `business_connection`).
- `CACHE_MEDIA=1` качает медиа каждого входящего, чтобы восстанавливать удалённые картинки. Это ест диск — при `=0` останется только текст и пометка о типе медиа.
- Ключ шифрования — в `.env`, **один на все подключения**. Потеряете ключ — расшифровать хранилище нельзя.
- Хранилище локальное; `data/` в `.gitignore`. Схема БД сменилась на мультитенантную — при обновлении со старой версии удалите прежнюю `data/` перед первым запуском.