ros 15a03a77fd Переписать проект в личный MTProto-юзербот
Смена архитектуры с бизнес-бота на Bot API (команды со слешем) на
личный юзербот на GramJS/MTProto с управлением через компаньон-бота.

Возможности:
- antidelete — кэш входящих в личке, восстановление удалённых «у всех»
- onetime — перехват «просмотр один раз» фото/видео до истечения ссылки
- локальное хранилище node:sqlite + шифрование AES-256-GCM, чистка по сроку
- компаньон-бот (grammY): инлайн-панель, лента с пагинацией, выдача медиа,
  доступ только владельцу по OWNER_ID

Устройство:
- core/ — crypto, db, media, loader, registry, logger
- userbot/ — MTProto-клиент из строки сессии, подписка на события
- modules/ — фичи как модули с хуками setup/onMessage/onEdited/onDeleted
- bot/ — компаньон-бот (единственный UI)
- scripts/login.js — генерация строки сессии

Удалён старый код Bot API (commands/, handlers/, middlewares/, dispatcher,
store). Обновлены README и .env.example. Hcrgram/ (сторонний референс)
добавлен в .gitignore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 19:16:52 +03:00

telegrambusiness

Личный Telegram-юзербот на Node.js + MTProto (GramJS). Работает от вашего аккаунта и сохраняет то, что Bot API недоступно:

  • antidelete — сообщения, которые собеседник удалил в личке;
  • onetime — одноразовые («просмотр один раз») фото и видео.

Всё управление и все перехваты приходят в компаньон-бота — отдельного бота от @BotFather, в личку владельца. Наружу юзербот ничего не пишет, слеш-команд в чатах нет.

⚠️ Дисклеймер. Инструмент для личного использования на своём аккаунте и своей переписке. Юзерботы нарушают ToS Telegram и аккаунт могут заблокировать — держите его на отдельном номере, не на основном. Одноразовые медиа отправляются с расчётом на удаление; сохраняя их, вы берёте ответственность на себя.

Как устроено (важно понимать до запуска)

MTProto в событии удаления присылает только id сообщений — без текста и без медиа. Поэтому «сохранить удалённое» реактивно невозможно: юзербот заранее кэширует каждое входящее в личке, а при удалении достаёт его из своего кэша. То же с медиа — файл качается в момент прихода, после удаления ссылка мертва.

Отсюда следствия:

  • тексты и метаданные пишутся в локальную БД (node:sqlite), зашифрованы (AES-256-GCM);
  • медиа лежат отдельными зашифрованными файлами в data/media/;
  • всё чистится по RETENTION_DAYS.

Установка

Нужен Node.js ≥ 22.5 (для встроенного node:sqlite).

npm install
cp .env.example .env

Заполните .env:

  1. API_ID / API_HASHmy.telegram.org → API development tools.
  2. BOT_TOKEN — новый бот у @BotFather (это и есть панель).
  3. OWNER_ID — ваш id (напишите @userinfobot).
  4. ENCRYPTION_KEY — сгенерируйте: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))".

Затем одноразовый вход в аккаунт (телефон, код, при наличии — пароль 2FA):

npm run login

Скрипт напечатает строку сессии — вставьте её в .env как SESSION=... и запускайте:

npm run dev

Откройте своего бота, нажмите Start — появится панель. При удалении сообщения собеседником или получении одноразового медиа бот пришлёт уведомление с кнопкой «Открыть».

npm run dev — с автоперезапуском, npm start — обычный запуск.

Как добавить модуль

Модуль — файл в src/modules/. Загрузчик обходит папку рекурсивно; файлы на _ игнорируются. Объявляйте только нужные хуки:

// src/modules/keywords.js
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,
      });
    }
  },
};
Хук Когда вызывается
setup(_, ctx) Один раз при старте
onMessage(message, ctx) Новое сообщение (NewMessage)
onEdited(message, ctx) Правка (EditedMessage)
onDeleted(event, ctx) Удаление; event.deletedIds — массив id

ctx даёт: client (GramJS), me, registry и capture(kind, data) — сохранить перехват и уведомить владельца. Ошибка в одном модуле не роняет остальные (registry.jsrunHook).

Устройство

src/
  index.js               сборка: юзербот + бот + модули + чистка
  config.js              .env, проверка на старте
  core/
    crypto.js            AES-256-GCM (шифр текста и медиа)
    db.js                node:sqlite: кэш входящих + лента перехватов
    media.js             тип медиа, детект one-time, скачать+шифровать
    loader.js            обход src/modules
    registry.js          реестр модулей, runHook
    logger.js
  userbot/
    client.js            MTProto-клиент из строки сессии
    events.js            подписка NewMessage/Edited/Deleted → модули
    context.js           ctx.capture — мост модуль↔БД↔бот
  modules/
    cache.js             кэширует входящие (основа antidelete)
    antidelete.js        удаление → достать из кэша → перехват
    onetime.js           one-time медиа → скачать → перехват
  bot/
    panel.js             компаньон-бот: инлайн-панель, лента, выдача медиа
scripts/
  login.js               генерация строки сессии

Поток: входящее → cache/onetime (кэш и/или скачивание) → при удалении antidelete поднимает из кэша → ctx.capture пишет в БД и шлёт уведомление → панель показывает и отдаёт расшифрованный файл.

Что стоит учесть

  • Перехватываются только входящие в личных диалогах. Группы, каналы и свои сообщения — нет (см. isPrivateIncoming в events.js).
  • «Удалить у себя» на стороне собеседника юзерботу не приходит — ловится только «удалить у всех».
  • CACHE_MEDIA=1 качает медиа каждого входящего, чтобы восстанавливать удалённые картинки. Это ест диск — при =0 останется только текст и пометка о типе медиа.
  • Ключ шифрования — в .env. Потеряете ключ — расшифровать хранилище нельзя.
  • Хранилище локальное; data/ в .gitignore.
S
Description
No description provided
Readme 146 KiB
Languages
JavaScript 100%