sendDocument и sendPhoto в Telegram на PHP: cURL, file_id и отправка по URL

Работа с медиафайлами — одна из базовых задач при разработке Telegram-ботов. Платформа предоставляет три основных способа передачи файлов в методах sendPhoto, sendDocument, sendVideo и других: прямая загрузка бинарных данных, передача публичной ссылки (URL) и использование ранее сохранённого file_id.

Каждый подход имеет свои лимиты, требования к сетевым ресурсам и нюансы обработки на стороне бэкенда. Использование непригодного метода под конкретный сценарий приводит к падению производительности, превышению таймаутов cURL и ошибкам API.

Лимиты Telegram Bot API на загрузку файлов

Прежде чем выбирать архитектуру отправки файлов, важно учитывать жесткие лимиты сервера Telegram:

  • Прямая загрузка (multipart/form-data): максимальный размер файла составляет 50 МБ для стандартных ботов. Если использовать локальный Bot API сервер (Local Bot API Server), лимит возрастает до 2 ГБ.
  • Передача по URL: Telegram самостоятельно скачивает файл по указанной ссылке. Лимит для фото составляет 5 МБ, для документов и других файлов — 20 МБ. Также серверы Telegram имеют жесткий таймаут на скачивание.
  • Повторная отправка по file_id: до 2 ГБ для любых типов файлов. Это самый быстрый и легковесный способ, так как файл уже находится на серверах Telegram.

Загрузка локального файла: multipart/form-data через cURL

Для загрузки файлов с диска сервера необходимо формировать HTTP-запрос с типом multipart/form-data. В PHP для этого используется класс CURLFile. Обратите внимание: функция file_get_contents не подходит для вызовов Telegram Bot API с загрузкой файлов, так как не позволяет эффективно управлять заголовками, стримингом и обработкой HTTP-статусов.

При отправке файлов обязательно проверяйте HTTP-код ответа, ошибку json_last_error() и поле ok в теле JSON. Ниже приведен готовый класс-сервис для отправки документов с диска.

<?php

declare(strict_types=1);

class TelegramUploader
{
private string $botToken;

public function __construct()
{
$token = getenv('TELEGRAM_BOT_TOKEN');
if (!$token) {
throw new RuntimeException('Переменная окружения TELEGRAM_BOT_TOKEN не задана');
}
$this->botToken = $token;
}

public function sendDocument(string $chatId, string $filePath, string $caption = ''): array
{
if (!file_exists($filePath) || !is_readable($filePath)) {
throw new InvalidArgumentException("Файл не найден или недоступен: {$filePath}");
}

$url = "https://api.telegram.org/bot{$this->botToken}/sendDocument";

$postFields = [
'chat_id' => $chatId,
'document' => new CURLFile(realpath($filePath)),
'caption' => $caption,
'parse_mode' => 'HTML',
];

$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 60,
]);

$response = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($response === false) {
throw new RuntimeException("cURL ошибка при отправке файла: {$curlError}");
}

$data = json_decode((string)$response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('Некорректный JSON-ответ от Telegram API: ' . json_last_error_msg());
}

if ($httpCode !== 200 || empty($data['ok'])) {
$description = $data['description'] ?? 'Неизвестная ошибка';
throw new RuntimeException("Telegram API Error [{$httpCode}]: {$description}");
}

return $data['result'];
}
}

Оптимизация: повторное использование file_id

Когда вы отправляете файл через multipart/form-data или URL, Telegram сохраняет его в своей инфраструктуре и возвращает в ответе объект сообщения. Внутри объекта document или photo содержится уникальный идентификатор file_id.

Запоминайте file_id в базе данных (например, в таблице каталога товаров или материалов). Повторная отправка файла по file_id происходит практически мгновенно, не тратит исходящий трафик вашего сервера и обходит лимит в 50 МБ.

Важный момент: file_id уникален для каждого бота. Вы не можете использовать file_id, полученный в одном боте, для отправки сообщения через другого бота. Ниже пример функции для отправки документа по file_id с заголовочным типом application/json.

<?php

declare(strict_types=1);

function sendDocumentByFileId(string $chatId, string $fileId, string $caption = ''): array
{
$botToken = getenv('TELEGRAM_BOT_TOKEN');
if (!$botToken) {
throw new RuntimeException('Токен бота не найден');
}

$url = "https://api.telegram.org/bot{$botToken}/sendDocument";

$payload = json_encode([
'chat_id' => $chatId,
'document' => $fileId,
'caption' => $caption,
'parse_mode' => 'HTML',
], JSON_UNESCAPED_UNICODE);

$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);

$response = curl_exec($ch);
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode((string)$response, true);
if ($httpCode !== 200 || empty($data['ok'])) {
$err = $data['description'] ?? 'Ошибка API';
throw new RuntimeException("Не удалось отправить файл по file_id: {$err}");
}

return $data['result'];
}

Отправка медиа по URL: плюсы и подводные камни

Передача HTTP URL в качестве значения параметра photo или document выглядит удобно: не нужно скачивать файл на бэкенд-сервер бота, достаточно передать прямую ссылку на S3 или CDN.

Ограничения и риски метода по URL:

  • Лимит размера: 5 МБ для фото и 20 МБ для документов. Файлы большего размера вызовут ошибку API.
  • Доступность CDN: серверы Telegram должны иметь прямой доступ к файлу без капчи, Basic Auth или сложных кук.
  • Таймаут скачивания: если ваш сервер отдает файл медленно (дольше 5–10 секунд), Telegram вернет ошибку bad request: wrong file identifier/HTTP URL specified.
  • Кэширование на стороне Telegram: Telegram может закэшировать файл по URL. Если содержимое файла по ссылке изменится, пользователи бота могут получать старую версию.
<?php

declare(strict_types=1);

function sendPhotoByUrl(string $chatId, string $photoUrl, string $caption = ''): array
{
$botToken = getenv('TELEGRAM_BOT_TOKEN');
$url = "https://api.telegram.org/bot{$botToken}/sendPhoto";

$payload = json_encode([
'chat_id' => $chatId,
'photo' => $photoUrl,
'caption' => $caption,
]);

$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);

$response = curl_exec($ch);
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode((string)$response, true);

if ($httpCode !== 200 || empty($data['ok'])) {
$msg = $data['description'] ?? 'Неизвестная ошибка Telegram API';
throw new RuntimeException("Ошибка отправки фото по URL: {$msg}");
}

return $data['result'];
}

Типичные ошибки при работе с файлами и интерактивными элементами

При построении логики бота часто сочетают отправку медиафайлов с кнопками inline-клавиатур (например, кнопка «Скачать прайс» или «Подтвердить получение»). На этом этапе часто допускают следующие ошибки:

  1. Превышение размера callback_data: Попытка записать путь к файлу или длинный URL прямо в callback_data интерактивной кнопки. Максимальный объем callback_data составляет 64 байта. Создавайте короткий идентификатор в БД (например, lead_id = bin2hex(random_bytes(7))) и передавайте в кнопке только его.
  2. Отсутствие обработки HTTP 429 (Too Many Requests): При массовой рассылке документов Telegram быстро вернет статус 429 с полем retry_after. Все отправки медиафайлов должны логироваться и оборачиваться в систему очередей.
  3. Отсутствие сохранения file_id: Повторная отправка PDF-каталогов или инструкций через multipart/form-data забивает канал бэкенда и приводит к банам по частоте запросов.

Если вам нужна помощь с разработкой сложных ботов или интеграцией Mini Apps, специалисты BotCreator помогут реализовать проект любой сложности.

Новые статьи — в Telegram

Разбираем, что автоматизировать в бизнесе и как это работает на практике. Без спама.