Переписать проект в личный 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>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { config, mediaPath } from '../config.js';
|
||||
import { encrypt } from './crypto.js';
|
||||
import { logger } from './logger.js';
|
||||
|
||||
/** Определяем тип медиа по объекту сообщения GramJS. */
|
||||
export function detectMedia(message) {
|
||||
if (!message?.media) return null;
|
||||
if (message.photo) return 'photo';
|
||||
if (message.video) return 'video';
|
||||
if (message.voice) return 'voice';
|
||||
if (message.videoNote) return 'video_note';
|
||||
if (message.gif) return 'gif';
|
||||
if (message.audio) return 'audio';
|
||||
if (message.sticker) return 'sticker';
|
||||
if (message.document) return 'document';
|
||||
return 'media';
|
||||
}
|
||||
|
||||
/**
|
||||
* Одноразовые медиа помечены ttlSeconds на самом медиа. Такие файлы Telegram
|
||||
* стирает у клиента после просмотра — их важно скачать в момент прихода.
|
||||
*/
|
||||
export function isOneTime(message) {
|
||||
const media = message?.media;
|
||||
const ttl = media?.ttlSeconds ?? media?.photo?.ttlSeconds ?? media?.document?.ttlSeconds;
|
||||
return Boolean(ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Скачивает медиа сообщения, шифрует и кладёт на диск. Возвращает
|
||||
* { kind, path } либо null. Имя файла не раскрывает содержимого.
|
||||
*/
|
||||
export async function downloadEncrypted(client, message, tag = 'm') {
|
||||
const kind = detectMedia(message);
|
||||
if (!kind) return null;
|
||||
|
||||
try {
|
||||
const buffer = await client.downloadMedia(message, {});
|
||||
if (!buffer || !buffer.length) return { kind, path: null };
|
||||
|
||||
mkdirSync(mediaPath(), { recursive: true });
|
||||
const name = `${tag}_${message.id ?? 'x'}_${buffer.length}.enc`;
|
||||
const full = fileURLToPath(new URL(name, config.mediaDir));
|
||||
await writeFile(full, encrypt(buffer));
|
||||
logger.debug(`media: сохранено ${kind} -> ${name} (${buffer.length} б)`);
|
||||
return { kind, path: name };
|
||||
} catch (err) {
|
||||
logger.warn(`media: не удалось скачать ${kind}:`, err.message);
|
||||
return { kind, path: null };
|
||||
}
|
||||
}
|
||||
|
||||
/** Читает зашифрованный медиа-файл с диска по имени (как хранится в БД). */
|
||||
export function readMedia(name) {
|
||||
return readFile(fileURLToPath(new URL(name, config.mediaDir)));
|
||||
}
|
||||
Reference in New Issue
Block a user