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

Розбираємо, що автоматизувати в бізнесі та як це працює на практиці. Без спаму.