Работа с медиафайлами — одна из базовых задач при разработке 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-клавиатур (например, кнопка «Скачать прайс» или «Подтвердить получение»). На этом этапе часто допускают следующие ошибки:
- Превышение размера callback_data: Попытка записать путь к файлу или длинный URL прямо в
callback_dataинтерактивной кнопки. Максимальный объемcallback_dataсоставляет 64 байта. Создавайте короткий идентификатор в БД (например,lead_id = bin2hex(random_bytes(7))) и передавайте в кнопке только его. - Отсутствие обработки HTTP 429 (Too Many Requests): При массовой рассылке документов Telegram быстро вернет статус 429 с полем
retry_after. Все отправки медиафайлов должны логироваться и оборачиваться в систему очередей. - Отсутствие сохранения file_id: Повторная отправка PDF-каталогов или инструкций через multipart/form-data забивает канал бэкенда и приводит к банам по частоте запросов.
Если вам нужна помощь с разработкой сложных ботов или интеграцией Mini Apps, специалисты BotCreator помогут реализовать проект любой сложности.