Повідомлення
Фото, документи, media group
Текстовые sendMessage — примерно треть трафика ботов. Остальные две трети — фото, PDF, голосовые, видео и альбомы. Для них Bot API даёт семейство методов: sendPhoto, sendDocument, sendVideo, sendAudio, sendVoice, sendAnimation, sendVideoNote. У всех одинаковая сигнатура: chat_id, медиа-поле (photo, document и т.д.), опциональные caption (до 1024 символов) и parse_mode (HTML/MarkdownV2 — см. HTML и Markdown).
Три способа передать файл
Поле медиа принимает три формы:
- file_id (строка) — файл уже лежит на серверах Telegram. Бесплатно, мгновенно, не тратит ваш исходящий трафик.
- HTTP/HTTPS URL — Telegram сам скачает файл (лимит: 5 МБ для фото, 20 МБ для остального). Удобно для генерации QR-кодов, графиков, скриншотов.
- multipart/form-data — загрузка с вашего диска через бота. Лимиты: 10 МБ фото, 50 МБ документ (через локальный Bot API сервер — до 2 ГБ).
Правило: если файл будет пересылаться много раз — отправьте его один раз, заберите file_id из ответа и дальше используйте только строку.
Хелпер telegramApi() из главы «Первый запрос: getMe»
Используем функцию telegramApi(), которую мы сделали в главе Первый запрос: getMe. Она делает POST к api.telegram.org, проверяет HTTP-код, json_last_error() и поле ok в ответе. Здесь только вызываем её — реализацию не копируем.
<?php
// reminder: telegramApi() из главы first-request
// function telegramApi(string $token, string $method, array $params = []): array { ... }
$token = getenv('TELEGRAM_BOT_TOKEN') ?: exit('TELEGRAM_BOT_TOKEN not set');
$chatId = 123456789; // замените на реальный chat_id
Отправка фото по URL (sendPhoto)
Самый частый кейс — сгенерировать картинку (QR, график, мем) и сразу отправить. Передаём URL в поле photo.
<?php
$photoUrl = 'https://example.com/qr.png?data=hello';
$caption = 'Ваш QR-код \n<code>hello</code>';
$result = telegramApi($token, 'sendPhoto', [
'chat_id' => $chatId,
'photo' => $photoUrl,
'caption' => $caption,
'parse_mode' => 'HTML',
]);
// Смотрим, что вернул API
print_r($result);
// или для красивого вывода в терминале:
// echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
Пример ответа Telegram (объект Message):
{
"ok": true,
"result": {
"message_id": 42,
"date": 1700000000,
"chat": { "id": 123456789, "type": "private", "first_name": "Ivan" },
"photo": [
{ "file_id": "AgAC...", "file_unique_id": "AQAD...", "width": 90, "height": 90, "file_size": 1234 },
{ "file_id": "AgAC...", "file_unique_id": "AQAD...", "width": 320, "height": 320, "file_size": 8901 },
{ "file_id": "AgAC...", "file_unique_id": "AQAD...", "width": 800, "height": 800, "file_size": 45210 }
],
"caption": "Ваш QR-код \nhello",
"caption_entities": [ { "type": "code", "offset": 11, "length": 5 } ]
}
}
Что смотреть: поле ok = true — успех. В result.photo массив объектов PhotoSize разного разрешения. Последний элемент — самый большой, его file_id сохраняйте для повторной отправки. file_unique_id одинаков для всех размеров одного файла — удобно для дедупликации в БД.
Загрузка файла с диска (sendDocument)
Для PDF, архивов, больших фото и любых файлов, которых нет в Telegram, используем multipart/form-data. В PHP 5.5+ для этого есть класс CURLFile.
<?php
$path = __DIR__ . '/files/price-list.pdf';
$filename = 'price-list-2024.pdf';
if (!is_file($path)) {
throw new RuntimeException("File not found: $path");
}
$result = telegramApi($token, 'sendDocument', [
'chat_id' => $chatId,
'document' => new CURLFile($path, 'application/pdf', $filename),
'caption' => 'Прайс-лист 2024 \n<b>актуален до 31.12</b>',
'parse_mode' => 'HTML',
]);
var_dump($result);
Ответ аналогичен фото, только вместо photo приходит объект document с полями file_id, file_unique_id, file_name, mime_type, file_size. file_id — ваш ключ для повторной отправки без загрузки.
Альбомы: sendMediaGroup
Метод sendMediaGroup шлёт 2–10 объектов одного типа (только фото ИЛИ только видео) как одно сообщение-альбом. В обновлении приходит одно Message с полем media_group_id — по нему можно сгруппировать входящие альбомы от пользователей.
- Параметр
media— массив JSON-строк, каждый элемент:type("photo"/"video"),media(file_id или URL), опциональноcaption,parse_mode. - Подпись (
caption) учитывается только у первого элемента, остальные игнорируют её. - Смешивать фото и видео в одном альбоме нельзя — отправляйте двумя группами подряд.
<?php
$urls = [
'https://example.com/photo1.jpg',
'https://example.com/photo2.jpg',
'https://example.com/photo3.jpg',
];
$media = array_map(function (string $url) {
return json_encode([
'type' => 'photo',
'media' => $url,
], JSON_UNESCAPED_SLASHES);
}, $urls);
// Подпись только для первого кадра
$media[0] = json_encode([
'type' => 'photo',
'media' => $urls[0],
'caption' => 'Наш фотоотчёт \n<i>3 кадра</i>',
'parse_mode' => 'HTML',
], JSON_UNESCAPED_SLASHES);
$result = telegramApi($token, 'sendMediaGroup', [
'chat_id' => $chatId,
'media' => $media,
]);
// Результат — массив Message (по одному на каждый элемент альбома)
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
Пример ответа (массив сообщений):
{
"ok": true,
"result": [
{
"message_id": 43,
"date": 1700000001,
"chat": { "id": 123456789, "type": "private" },
"media_group_id": "1234567890123456789",
"photo": [ { "file_id": "AgAC...", "width": 800, "height": 600, "file_size": 54321 } ],
"caption": "Наш фотоотчёт 3 кадра"
},
{
"message_id": 44,
"media_group_id": "1234567890123456789",
"photo": [ { "file_id": "AgAC...", "width": 800, "height": 600, "file_size": 51234 } ]
},
{
"message_id": 45,
"media_group_id": "1234567890123456789",
"photo": [ { "file_id": "AgAC...", "width": 800, "height": 600, "file_size": 49876 } ]
}
]
}
Важно: у каждого сообщения в альбоме свой message_id и свой file_id у фото. Редактировать или удалять можно каждое отдельно (см. следующую главу), но media_group_id одинаков — это маркер альбома.
Получение файла от пользователя (getFile + скачивание)
Когда приходит update с message.photo или message.document:
- Берёте
file_idнужного размера (для фото — обычно последний элемент массиваphoto, самый большой). - Вызываете
getFileс этимfile_id— получаетеfile_path(относительный путь на серверах Telegram). - Скачиваете по URL
https://api.telegram.org/file/bot<TOKEN>/<file_path>. Ссылка живёт минимум 1 час.
Используем функцию processUpdate(), которую мы разбирали в главе Long polling. Там мы уже получаем распарсенный $message — здесь показываем только обработку входящего фото.
<?php
// Пример внутри processUpdate() из главы long-polling
function handleIncomingPhoto(array $message, string $token): void {
if (empty($message['photo'])) return;
// Последний = максимальный размер
$largest = end($message['photo']);
$fileId = $largest['file_id'];
$fileInfo = telegramApi($token, 'getFile', ['file_id' => $fileId]);
$filePath = $fileInfo['result']['file_path'] ?? null;
if (!$filePath) {
error_log('getFile failed: ' . json_encode($fileInfo));
return;
}
$downloadUrl = "https://api.telegram.org/file/bot{$token}/{$filePath}";
$content = file_get_contents($downloadUrl); // или cURL для больших файлов
if ($content !== false) {
$localPath = sys_get_temp_dir() . '/' . basename($filePath);
file_put_contents($localPath, $content);
echo "Сохранено в $localPath (" . strlen($content) . " bytes)\n";
}
}
Ответ getFile:
{
"ok": true,
"result": {
"file_id": "AgAC...",
"file_unique_id": "AQAD...",
"file_size": 45210,
"file_path": "photos/file_123.jpg"
}
}
Подводные камни
- URL-картинка должна отдавать корректный
Content-Type(image/jpeg, image/png) и быть доступна без редиректов на логин/капчу. Telegram не следует за редиректами на авторизацию. - Один и тот же URL при повторной отправке Telegram перезальёт к себе и выдаст новый file_id — не путайте с переиспользованием старого
file_id. captionобрезается после 1024 символов. Длинные тексты — лучше в отдельномsendMessage.- Видео в альбоме требует одинаковых пропорций (aspect ratio) — иначе Telegram склеит криво или вернёт ошибку.
- Медиа-сообщения нельзя отредактировать как текст — только подпись (
editMessageCaption). Для замены самого файла — удалите сообщение и отправьте новое.
Дальше: Редактирование и удаление.