Файлы в боте — больше, чем «принять картинку». Реальный продакшн обрабатывает фото с ресайзом и анализом, PDF/Word с OCR, голосовые с STT, видео с превью, документы с антивирусом, всё это сохраняет в S3 с подписанными URL и удаляет по требованию пользователя. В этой статье — полная анатомия работы с файлами в боте MAX: как принимать, валидировать, ресайзить, OCR'ить, обрабатывать через ИИ, сохранять, ссылаться, удалять, защищать от вирусов и утечек.
Типы файлов
| Тип | Лимит размера | Use cases |
|---|---|---|
| photo | до 10–20 МБ | проверка документов, foto проблемы, фото товара |
| document | до 50 МБ (зависит от платформы) | договор PDF, прайс XLSX, сертификат |
| voice | до 50 МБ | голосовые вопросы, диктовка |
| video | до 50 МБ | проблема в действии, инструкция |
| audio | до 50 МБ | музыка, подкасты |
Приём файла
@bot.message_handler(content_types=["photo"])
async def on_photo(msg):
photo = msg.photo[-1] # самое большое разрешение
file_info = await bot.get_file(photo.file_id)
raw = await bot.download_file(file_info.file_path)
# raw — это байты файла
msg.photo — массив размеров; берите последний (largest). Для документов:
@bot.message_handler(content_types=["document"])
async def on_document(msg):
if msg.document.file_size > 50 * 1024 * 1024:
return await bot.send_message(msg.chat.id, "Файл слишком большой (макс. 50 МБ)")
if msg.document.mime_type not in ALLOWED_MIMES:
return await bot.send_message(msg.chat.id, f"Поддерживается: {', '.join(ALLOWED_MIMES)}")
file = await bot.get_file(msg.document.file_id)
raw = await bot.download_file(file.file_path)
Валидация до скачивания — не качайте 100 МБ-bomb просто чтобы отбить.
Whitelist MIME и расширений
ALLOWED_MIMES = {
"image/jpeg", "image/png", "image/webp",
"application/pdf",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", # docx
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # xlsx
"audio/ogg", "audio/mpeg",
}
Проверяйте mime_type от платформы и дополнительно магические байты на сервере (через python-magic):
import magic
mime_real = magic.from_buffer(raw[:8192], mime=True)
if mime_real not in ALLOWED_MIMES:
raise ValueError(f"MIME mismatch: declared={msg.document.mime_type}, real={mime_real}")
Атакующий может прислать .exe, переименованный в .pdf — без проверки магических байт это легко пропустить.
Антивирус: ClamAV
import clamd
cd = clamd.ClamdNetworkSocket(host="clamav", port=3310)
async def scan(raw: bytes) -> tuple[bool, str | None]:
result = cd.instream(io.BytesIO(raw))
status, signature = result["stream"]
if status == "OK":
return True, None
return False, signature
ClamAV в отдельном контейнере, обновление баз раз в сутки. Файлы с положительным detect — отбиваем, логируем, не сохраняем.
Сохранение в S3
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://storage.yandexcloud.net",
aws_access_key_id=os.environ["S3_KEY"],
aws_secret_access_key=os.environ["S3_SECRET"],
)
async def save_to_s3(raw: bytes, ext: str, user_id: int) -> str:
key = f"uploads/{user_id}/{uuid.uuid4()}.{ext}"
s3.put_object(
Bucket="botmax-files",
Key=key,
Body=raw,
ServerSideEncryption="AES256",
Metadata={"user_id": str(user_id)},
)
return key
Бакет — приватный. Доступ только через подписанные URL с TTL:
def signed_url(key: str, ttl: int = 3600) -> str:
return s3.generate_presigned_url(
"get_object",
Params={"Bucket": "botmax-files", "Key": key},
ExpiresIn=ttl,
)
Ресайз фото
from PIL import Image
import io
def resize(raw: bytes, max_dim: int = 1200, quality: int = 80) -> bytes:
img = Image.open(io.BytesIO(raw))
img.thumbnail((max_dim, max_dim))
out = io.BytesIO()
img.save(out, format="WEBP", quality=quality)
return out.getvalue()
Сохраняйте оригинал и thumb (400×400) для каталога. Это снижает трафик в 5–10 раз.
OCR для PDF и фото
Yandex Vision OCR — стандарт для русского языка:
async def ocr_image(raw: bytes) -> str:
payload = {
"mimeType": "image/jpeg",
"languageCodes": ["ru", "en"],
"model": "page",
"content": base64.b64encode(raw).decode(),
}
async with httpx.AsyncClient() as cli:
r = await cli.post(
"https://vision.api.cloud.yandex.net/vision/v1/batchAnalyze",
headers={"Authorization": f"Bearer {YC_IAM_TOKEN}"},
json={"folderId": YC_FOLDER, "analyze_specs": [{"content": payload["content"], "features": [{"type": "TEXT_DETECTION", "text_detection_config": {"language_codes": ["ru", "en"]}}]}]},
)
return parse_ocr(r.json())
Для PDF — pdfplumber для текстовых, OCR для сканированных. Альтернативы: Tesseract (open source, хуже на русском), GigaChat Vision.
STT для голосовых
@bot.message_handler(content_types=["voice"])
async def on_voice(msg):
file = await bot.get_file(msg.voice.file_id)
raw = await bot.download_file(file.file_path)
text = await stt(raw, mime="audio/ogg")
await dispatch_text(msg.chat.id, msg.from_user.id, text)
async def stt(raw: bytes, mime: str = "audio/ogg") -> str:
# YandexSpeechKit / GigaChat Audio / Whisper
async with httpx.AsyncClient() as cli:
r = await cli.post(
"https://stt.api.cloud.yandex.net/speech/v1/stt:recognize",
params={"folderId": YC_FOLDER, "lang": "ru-RU"},
content=raw,
headers={"Authorization": f"Bearer {YC_IAM_TOKEN}"},
)
return r.json().get("result", "")
Анализ изображений через ИИ
GPT-4o vision / GigaChat-Vision / Claude 3.5 для описания, классификации, проверки документов:
async def analyze_photo(url: str, prompt: str) -> str:
response = await llm.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": {"url": url}},
],
}],
max_tokens=300,
)
return response.choices[0].message.content
Use cases: «опиши, что не так с счётом», «прочти показания счётчика», «определи марку машины», «проверь, что на фото человек, а не картинка».
Лимиты и квоты
async def check_upload_quota(user_id: int) -> bool:
# Не больше 100 МБ или 50 файлов в день
today = await db.fetch_one("""
SELECT COALESCE(sum(size_bytes), 0) AS bytes,
COUNT(*) AS files
FROM uploads
WHERE user_id = $1 AND created_at::date = current_date
""", user_id)
return today.bytes < 100 * 1024 * 1024 and today.files < 50
Без квот один пользователь зальёт 10 ГБ за вечер.
Удаление по требованию (152-ФЗ)
@bot.message_handler(commands=["delete_my_files"])
async def on_delete_files(msg):
files = await db.fetch_all("SELECT s3_key FROM uploads WHERE user_id = $1", msg.from_user.id)
for f in files:
s3.delete_object(Bucket="botmax-files", Key=f.s3_key)
await db.execute("DELETE FROM uploads WHERE user_id = $1", msg.from_user.id)
await bot.send_message(msg.chat.id, f"Удалено {len(files)} файлов")
Бэкапы тоже надо чистить (или политикой retention < 90 дней).
CDN для отдачи
Для публичных файлов (фото товаров) — CDN с длинным cache TTL. Для приватных — подписанные URL с TTL 1 час, новый URL на каждый запрос.
Защита от утечек
- Подписанные URL с коротким TTL (1 час).
- Никаких прямых ссылок на S3 в чате — только через ваш
/file/{token}редирект. - Бакет приватный, доступ только через service account.
- Логирование выдачи каждого файла (audit trail).
Common pitfalls
- Доверие mime_type — атакующий шлёт exe под видом jpg.
- Файлы прямо в БД (BYTEA) — раздувает Postgres, медленные бэкапы. Только S3.
- Без антивируса — клиент скачает «договор», получит ransomware.
- Без лимитов — DoS заливкой.
- Подписанные URL с TTL 30 дней — утечка ссылки = доступ на месяц.
- Нет /delete_my_files — нарушение 152-ФЗ.
Итого
Работа с файлами в боте MAX: валидация по mime_type + магическим байтам, антивирус ClamAV в отдельном контейнере, сохранение в приватный S3-бакет с server-side encryption, выдача через подписанные URL с TTL 1 час, ресайз фото для thumbnails, OCR через Yandex Vision для PDF/изображений, STT для голосовых через YandexSpeechKit/GigaChat Audio, анализ через GPT-4o/GigaChat Vision для умных сценариев. Лимиты на пользователя (100 МБ / 50 файлов в день) против DoS, /delete_my_files для соответствия 152-ФЗ. Логирование выдачи для аудита. Грамотная обработка файлов — это пара недель работы, но без неё бот рано или поздно станет каналом утечки или заражения вирусами.
Частые вопросы
Какие лимиты на размер файлов в боте MAX?
Платформа MAX/Telegram-style обычно ограничивает: фото — 10–20 МБ, документы — 50 МБ, голосовые и видео — 50 МБ. Точные значения зависят от версии Bot API, проверяйте в документации. На своей стороне ставьте более строгие квоты: дневной лимит на пользователя (100 МБ и 50 файлов), общий бакет с ротацией. Проверку размера делайте до скачивания через msg.document.file_size — нет смысла качать 50 МБ, чтобы потом отбить.
Как защититься от вирусов в загружаемых файлах?
Поднимайте ClamAV в отдельном контейнере с обновлением баз раз в сутки. Каждый загружаемый файл прогоняется через clamd.instream до сохранения; при detect — отбиваете, логируете incident, не сохраняете. Это защищает не только пользователей бота от заражения, но и вашу инфраструктуру (если файл откроют операторы для проверки). Для PDF дополнительно проверяйте на наличие активных JavaScript через pdfid. На критичных сценариях (документы юридические, медицинские) — двойная проверка через два разных движка.
Где хранить загруженные файлы?
Только в S3-совместимом Object Storage (Yandex Object Storage, Selectel, MinIO), не в Postgres BYTEA — раздувает базу, замедляет бэкапы, дорогое хранение. Бакет приватный, server-side encryption AES256, доступ только service account с минимальными правами (put-object для бота, get-object для подписанных URL). Доступ пользователю — через подписанные URL с TTL 1 час, новый URL на каждый запрос. Никогда не публикуйте S3 URL напрямую в чат — только через ваш редирект /file/{token}, который проверяет права и логирует выдачу.
Как делать OCR на русском языке?
Лучший вариант для RU 2026 года — Yandex Vision OCR. Поддерживает русский, печатный и рукописный, таблицы, многостраничные PDF. Стоит около 1.5 копейки за страницу. Альтернативы: GigaChat Vision (хорошо для смешанных задач OCR + анализ), Tesseract (open source, бесплатно, но качество хуже на русском), OpenAI GPT-4o vision (отлично для сложных layout, дороже). Для простого PDF с текстовым слоем сначала пробуйте pdfplumber — это бесплатно и точно; OCR только для сканов и фото.
Как обрабатывать голосовые сообщения?
YandexSpeechKit — основной для русского (10 копеек за минуту, latency 1–2 секунды), GigaChat Audio — альтернатива. Для зарубежных языков и максимальной точности — Whisper API от OpenAI ($0.006/мин). Принимаете voice из msg.voice.file_id, скачиваете байты через get_file + download_file, отправляете в STT, получаете текст, дальше обрабатываете как обычное сообщение. Это даёт boost UX в 2–3 раза для пользователей в дороге, на ходу или с ограниченной возможностью печатать.
Как защитить файлы от утечек?
Подписанные URL с коротким TTL (1 час, не 30 дней). Никаких прямых ссылок на S3 в чате — только через редирект /file/{token}, где token связан с пользователем и проверяет права. Логирование каждой выдачи (user_id, file, IP, ts) для аудита. Бакет приватный, IAM с минимальными правами. Регулярная ротация ключей access. Алерт «один файл скачан 100+ раз за час» — возможная утечка ссылки. Для критичных файлов (медкарты, договоры) — водяной знак с user_id поверх PDF при выдаче.
Что нужно по 152-ФЗ для файлов с ПДн?
Согласие пользователя на обработку при первой загрузке файла с ПДн (паспорт, медкарта, договор). Шифрование при хранении (S3 SSE + опционально client-side AES256 для самых чувствительных). Логирование доступа (кто и когда смотрел файл). Команда /delete_my_files для удаления по требованию пользователя (включая бэкапы — или политикой retention ≤ 90 дней). Хранение и обработка — на серверах в РФ. Уведомление в РКН о начале обработки ПДн. Запрет передачи в зарубежные сервисы без анонимизации (для OCR — используйте Yandex Vision, не Google Vision).