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))); }