Перевести проект на Telegram Business (без хранения сессии)
Смена архитектуры с 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>
This commit is contained in:
@@ -1,23 +1,29 @@
|
||||
# telegrambusiness
|
||||
|
||||
Личный Telegram-**юзербот** на Node.js + MTProto (GramJS). Работает от вашего аккаунта и сохраняет то, что Bot API недоступно:
|
||||
Личный Telegram-бот на Node.js, работающий через **официальное бизнес-подключение** Telegram (Business API). Бот подключается к вашему аккаунту как чат-бот для бизнеса и сохраняет то, что собеседник удалил:
|
||||
|
||||
- **antidelete** — сообщения, которые собеседник удалил в личке;
|
||||
- **onetime** — одноразовые («просмотр один раз») фото и видео.
|
||||
- **antidelete** — сообщения (и медиа), которые собеседник удалил в личке.
|
||||
|
||||
Всё управление и все перехваты приходят в **компаньон-бота** — отдельного бота от [@BotFather](https://t.me/BotFather), в личку владельца. Наружу юзербот ничего не пишет, слеш-команд в чатах нет.
|
||||
Всё управление и все перехваты приходят в этого же бота — в личку владельца. Наружу бот ничего не пишет, слеш-команд в чатах нет.
|
||||
|
||||
> ⚠️ **Дисклеймер.** Инструмент для личного использования на **своём** аккаунте и своей переписке. Юзерботы нарушают ToS Telegram и аккаунт могут заблокировать — держите его на отдельном номере, не на основном. Одноразовые медиа отправляются с расчётом на удаление; сохраняя их, вы берёте ответственность на себя.
|
||||
> ✅ **Без сессии, по правилам Telegram.** Проект **не хранит строку сессии** и не использует MTProto-юзербот. Работает исключительно через штатную функцию «Telegram для бизнеса» — обычный бот от [@BotFather](https://t.me/BotFather) с включённым Business Mode. Это соответствует ToS.
|
||||
|
||||
> ⚠️ **Дисклеймер.** Инструмент для личного использования на **своём** аккаунте и своей переписке. Не применяйте против других людей.
|
||||
|
||||
## Как устроено (важно понимать до запуска)
|
||||
|
||||
MTProto в событии удаления присылает **только id сообщений** — без текста и без медиа. Поэтому «сохранить удалённое» реактивно невозможно: юзербот **заранее кэширует каждое входящее** в личке, а при удалении достаёт его из своего кэша. То же с медиа — файл качается **в момент прихода**, после удаления ссылка мертва.
|
||||
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`).
|
||||
@@ -29,24 +35,22 @@ cp .env.example .env
|
||||
|
||||
Заполните `.env`:
|
||||
|
||||
1. **API_ID / API_HASH** — [my.telegram.org](https://my.telegram.org) → API development tools.
|
||||
2. **BOT_TOKEN** — новый бот у [@BotFather](https://t.me/BotFather) (это и есть панель).
|
||||
3. **OWNER_ID** — ваш id (напишите [@userinfobot](https://t.me/userinfobot)).
|
||||
4. **ENCRYPTION_KEY** — сгенерируйте: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`.
|
||||
1. **BOT_TOKEN** — новый бот у [@BotFather](https://t.me/BotFather).
|
||||
2. **OWNER_ID** — ваш id (напишите [@userinfobot](https://t.me/userinfobot)).
|
||||
3. **ENCRYPTION_KEY** — сгенерируйте: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`.
|
||||
|
||||
Затем одноразовый вход в аккаунт (телефон, код, при наличии — пароль 2FA):
|
||||
Включите боту режим бизнеса и подключите его к аккаунту:
|
||||
|
||||
```bash
|
||||
npm run login
|
||||
```
|
||||
1. [@BotFather](https://t.me/BotFather) → `/mybots` → выберите бота → **Bot Settings** → **Business Mode** → **Enable**.
|
||||
2. В приложении Telegram: **Настройки → Telegram для бизнеса → Чат-боты** → добавьте своего бота. Дайте ему право читать/управлять сообщениями.
|
||||
|
||||
Скрипт напечатает строку сессии — вставьте её в `.env` как `SESSION=...` и запускайте:
|
||||
Запуск:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Откройте своего бота, нажмите **Start** — появится панель. При удалении сообщения собеседником или получении одноразового медиа бот пришлёт уведомление с кнопкой «Открыть».
|
||||
Откройте своего бота, нажмите **Start** — появится панель. Когда собеседник удалит сообщение, бот пришлёт уведомление с кнопкой «Открыть».
|
||||
|
||||
`npm run dev` — с автоперезапуском, `npm start` — обычный запуск.
|
||||
|
||||
@@ -60,63 +64,61 @@ export default {
|
||||
name: 'keywords',
|
||||
description: 'Уведомлять, если во входящем есть ключевое слово',
|
||||
|
||||
async onMessage(message, ctx) {
|
||||
if (message.out) return; // не своё
|
||||
if (/срочно/i.test(message.text ?? '')) {
|
||||
await ctx.capture('deleted', { // переиспользуем ленту перехватов
|
||||
chatId: message.chatId,
|
||||
senderId: message.senderId,
|
||||
sender: (await message.getSender().catch(() => null))?.username ?? '',
|
||||
text: message.text,
|
||||
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(message, ctx)` | Новое сообщение (`NewMessage`) |
|
||||
| `onEdited(message, ctx)` | Правка (`EditedMessage`) |
|
||||
| `onDeleted(event, ctx)` | Удаление; `event.deletedIds` — массив id |
|
||||
| `onMessage(msg, ctx)` | Новое бизнес-сообщение (`business_message`) |
|
||||
| `onEdited(msg, ctx)` | Правка (`edited_business_message`) |
|
||||
| `onDeleted(event, ctx)` | Удаление; `event.chatId`, `event.msgIds` |
|
||||
|
||||
`ctx` даёт: `client` (GramJS), `me`, `registry` и `capture(kind, data)` — сохранить перехват и уведомить владельца. Ошибка в одном модуле не роняет остальные ([registry.js](src/core/registry.js) — `runHook`).
|
||||
`ctx` даёт: `api` (grammY `bot.api`), `registry` и `capture(kind, data)` — сохранить перехват и уведомить владельца. Ошибка в одном модуле не роняет остальные ([registry.js](src/core/registry.js) — `runHook`).
|
||||
|
||||
## Устройство
|
||||
|
||||
```
|
||||
src/
|
||||
index.js сборка: юзербот + бот + модули + чистка
|
||||
index.js сборка: бот + модули + чистка
|
||||
config.js .env, проверка на старте
|
||||
core/
|
||||
crypto.js AES-256-GCM (шифр текста и медиа)
|
||||
db.js node:sqlite: кэш входящих + лента перехватов
|
||||
media.js тип медиа, детект one-time, скачать+шифровать
|
||||
media.js тип медиа, скачать через getFile + шифровать
|
||||
loader.js обход src/modules
|
||||
registry.js реестр модулей, runHook
|
||||
logger.js
|
||||
userbot/
|
||||
client.js MTProto-клиент из строки сессии
|
||||
events.js подписка NewMessage/Edited/Deleted → модули
|
||||
business/
|
||||
events.js подписка business_message/edited/deleted → модули
|
||||
context.js ctx.capture — мост модуль↔БД↔бот
|
||||
modules/
|
||||
cache.js кэширует входящие (основа antidelete)
|
||||
antidelete.js удаление → достать из кэша → перехват
|
||||
onetime.js one-time медиа → скачать → перехват
|
||||
bot/
|
||||
panel.js компаньон-бот: инлайн-панель, лента, выдача медиа
|
||||
scripts/
|
||||
login.js генерация строки сессии
|
||||
panel.js бот: инлайн-панель, лента, выдача медиа
|
||||
```
|
||||
|
||||
Поток: входящее → `cache`/`onetime` (кэш и/или скачивание) → при удалении `antidelete` поднимает из кэша → `ctx.capture` пишет в БД и шлёт уведомление → панель показывает и отдаёт расшифрованный файл.
|
||||
Поток: входящее (`business_message`) → `cache` кэширует текст и качает медиа → при удалении (`deleted_business_messages`) `antidelete` поднимает из кэша → `ctx.capture` пишет в БД и шлёт уведомление → панель показывает и отдаёт расшифрованный файл.
|
||||
|
||||
## Что стоит учесть
|
||||
|
||||
- Перехватываются только **входящие в личных диалогах**. Группы, каналы и свои сообщения — нет (см. `isPrivateIncoming` в [events.js](src/userbot/events.js)).
|
||||
- «Удалить у себя» на стороне собеседника юзерботу не приходит — ловится только «удалить у всех».
|
||||
- Перехватываются только **входящие в личных диалогах**. Свои сообщения — нет (см. `isPrivateIncoming` в [events.js](src/business/events.js)).
|
||||
- Id сообщения в Bot API уникален **только внутри чата**, поэтому в БД составной ключ `(chat_id, msg_id)`.
|
||||
- Если вы отключите бизнес-подключение в настройках Telegram, перехват приостановится (бот залогирует это по апдейту `business_connection`).
|
||||
- `CACHE_MEDIA=1` качает медиа каждого входящего, чтобы восстанавливать удалённые картинки. Это ест диск — при `=0` останется только текст и пометка о типе медиа.
|
||||
- Ключ шифрования — в `.env`. Потеряете ключ — расшифровать хранилище нельзя.
|
||||
- Хранилище локальное; `data/` в `.gitignore`.
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user