tg_client WebSocket Docs: Local Media Uploads
Що це за flow
Це сценарій відправки медіа без S3/CDN. Клієнт отримує короткоживучий локальний PUT URL,
завантажує файл у спільний для web та owner каталог session_data/_outgoing,
а потім викликає send_message, send_comment або edit_message з media.key.
- WS:
create_tg_media_upload_url - HTTP:
PUTbinary уupload_url - WS:
send_message,send_commentабоedit_messageзmedia - TDLib піднімає
updateFileGenerationStart, owner копіює локальний staging-файл уdestination_pathі завершує generation
Усередині TDLib використовується inputFileGenerated + persistent conversion
local_file_v1. Між owner і файлом немає HTTP, S3, CDN або presigned GET.
Для post_story photo backend визначає формат за вмістом, декодує підтримуваний Pillow image (JPEG/PNG/WebP та інші), застосовує EXIF orientation, center-crop/resize до обов'язкових TDLib 1080×1920 і атомарно створює RGB JPEG для передачі TDLib.
Для media.type="video_note" backend окремо готує квадратний MP4: center-crop, scale до запитаного length (максимум 640), H.264/yuv420p, AAC і faststart. Звичайні video цю обробку не проходять.
Upload записується через тимчасовий файл та атомарний os.replace, тому owner не читає частково завантажений файл. Staging-файли автоматично очищаються після TTL.
Підтримувані типи
| media.type | TDLib тип | Примітка |
|---|---|---|
document | inputMessageDocument | Будь-який файл, PDF, ZIP, DOCX, а також фото/відео "без стискання". |
photo | inputMessagePhoto | З підтримкою show_caption_above_media, has_spoiler, self_destruct_type. |
video | inputMessageVideo | З підтримкою thumbnail, cover, supports_streaming, has_spoiler. |
audio | inputMessageAudio | Music/audio track з title, performer, album_cover_thumbnail. |
voice_note | inputMessageVoiceNote | Native voice message. Для коректного UI бажано .ogg + Opus. |
video_note | inputMessageVideoNote | Native "відеокружечок". Обов'язково передати length. |
Для photo і video прапори high_quality,
without_compression, disable_compression, send_as_document,
as_document, preserve_quality перемикають відправку в document.
Крок 1. Отримати upload URL
{
"action": "create_tg_media_upload_url",
"userbot_id": 1,
"filename": "clip.mp4",
"content_type": "video/mp4",
"chat_id": "123",
"media_type": "video"
}
Відповідь:
{
"type": "create_tg_media_upload_url",
"result": {
"upload_url": "https://dev-api.hireme.group/api/tg-client/local-upload/OPAQUE_TOKEN/",
"key": "local-upload:OPAQUE_TOKEN",
"expires_in": 86400,
"source": "local_session",
"content_type": "video/mp4",
"method": "PUT",
"body_type": "raw_binary",
"multipart": false
}
}
key треба зберегти. Саме він передається в send_message або send_comment.
upload_url одноразовий/тимчасовий і в Telegram не відправляється.
Крок 2. Завантажити файл у локальний staging
HTTP-запит до upload_url:
PUT {upload_url}
Content-Type: video/mp4
Body: binary file
const response = await fetch(result.upload_url, {
method: result.method, // PUT
headers: result.upload_headers,
body: file, // саме File або Blob; НЕ FormData
});
if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
// Лише після завершення await fetch(...) надсилайте send_message/post_story.
Postman
- Створи звичайний HTTP request
- Method:
PUT - URL: встав
upload_url - Body →
binary - Header
Content-Typeмає збігатися з базовим MIME уresult.content_type. Backend порівнює лишеtype/subtype; параметри браузера на кшталт; codecs=vp9,opusабо; charset=utf-8ігноруються.
Важливо
- Не використовуй
form-data - Не передавай JSON, base64/data URL або об'єкт-обгортку на кшталт
{ file } - Не додавай
Authorization - Успішна відповідь —
200з фактичнимsize
Крок 3. Відправити повідомлення в Telegram
Базовий текстовий upload/send flow:
{
"action": "send_message",
"userbot_id": 1,
"chat_id": "123",
"text": "caption",
"media": {
"type": "video",
"key": "local-upload:OPAQUE_TOKEN",
"file_name": "clip.mp4"
}
}
text стає caption для media. Якщо потрібно, можна замість нього передати
media.caption і media.caption_entities.
Для альбому передайте media_items масивом. Top-level text і entities
будуть застосовані до першого елемента як caption fallback.
{
"action": "send_message",
"userbot_id": 1,
"chat_id": "123",
"text": "Album caption",
"entities": [
{
"offset": 0,
"length": 5,
"type": { "@type": "textEntityTypeBold" }
}
],
"media_items": [
{
"type": "photo",
"key": "local-upload:OPAQUE_PHOTO_TOKEN",
"file_name": "one.jpg"
},
{
"type": "video",
"key": "local-upload:OPAQUE_VIDEO_TOKEN",
"file_name": "two.mp4",
"supports_streaming": true
}
],
"reply_to": {
"replyToMsgId": "555"
},
"protect_content": true
}
Альбом підтримує 2-10 елементів. TDLib групує лише photo, video,
document і audio; для document і audio
усі елементи мають бути того самого типу. reply_markup для альбомів не підтримується.
{
"action": "send_message",
"userbot_id": 1,
"chat_id": "123",
"media_items": [
{
"type": "forward",
"from_chat_id": "-100555000111",
"message_id": "9001",
"without_sender": true,
"caption": "Copied forward",
"caption_entities": [
{
"offset": 0,
"length": 6,
"type": { "@type": "textEntityTypeItalic" }
}
],
"show_caption_above_media": true
},
{
"type": "forward",
"from_chat_id": "-100555000111",
"message_id": "9002"
}
]
}
Для comments flow використовуйте send_comment і передайте
comments_meta або пару discussion_chat_id + message_thread_id.
{
"action": "send_comment",
"userbot_id": 1,
"comments_meta": {
"discussion_chat_id": "-100999000777",
"message_thread_id": "9001"
},
"text": "caption",
"entities": [
{
"offset": 0,
"length": 7,
"type": { "@type": "textEntityTypeBold" }
}
],
"media": {
"type": "document",
"key": "local-upload:OPAQUE_DOCUMENT_TOKEN",
"file_name": "report.pdf"
},
"protect_content": true
}
Загальні top-level поля для send_message / send_comment / edit_message
Поля нижче підтримуються для send_message і send_comment. edit_message використовує той самий single-media payload (text, entities, reply_markup, media, media_type) плюс обов'язковий message_id; send-options і media_items для edit не застосовуються.
| Поле | Обов'язковість | Що робить |
|---|---|---|
userbot_id | required | ID userbot listener-а. |
chat_id | required для send_message / edit_message | Чат, куди відправляємо або де редагуємо повідомлення. |
message_id | required для edit_message | Повідомлення, яке потрібно відредагувати. |
discussion_chat_id | optional | Comments chat для send_comment. Може прийти явно або через comments_meta. |
text | optional | Caption для media або звичайний текст, якщо media нема. |
entities | optional | Text entities для text. |
message_thread_id | optional | Відправка в topic/forum thread. |
comments_meta | optional | Shortcut для send_comment: discussion_chat_id, message_thread_id, опц. comment_count. |
reply_to, reply_to_message_id | optional | Reply на повідомлення. Для reply в інший чат передай reply_to як об'єкт з chat_id/message_id або replyToChatId/replyToMsgId. |
reply_markup | optional | TDLib reply markup payload. Для media_items/sendMessageAlbum не підтримується. |
options | optional | Сирий messageSendOptions. |
disable_notification, protect_content, from_background | optional | Alias-и для messageSendOptions. |
allow_paid_broadcast, paid_message_star_count | optional | Платні опції відправки, якщо TDLib/чат їх підтримує. |
update_order_of_installed_sticker_sets | optional | Прокидається в messageSendOptions як у звичайному sendMessage. |
schedule_date, send_when_online | optional | Планована відправка. |
effect_id, message_effect_id | optional | Ефект повідомлення, якщо TDLib/чат це підтримує. |
sending_id, only_preview | optional | Додаткові прапори/ідентифікатори з messageSendOptions. |
direct_messages_chat_topic_id, suggested_post_info | optional | Специфічні поля TDLib для direct messages / suggested posts. |
send_large_photos | optional | Shortcut прапор, який бекенд кладе в options для відправки великих фото. |
media | optional | Об'єкт uploaded media payload. Також можна передати масив як alias для альбому, але рекомендований формат для нього - media_items. |
media_items | optional | Масив із 2-10 елементів для sendMessageAlbum. Елемент може бути uploaded media payload або {"type":"forward","from_chat_id":"...","message_id":"..."}. |
media_type | optional | Можна передати окремо, якщо в media.type або елементах media_items[].type його нема. |
Загальні поля media
| Поле | Для яких типів | Примітка |
|---|---|---|
type | all | document, photo, video, audio, voice_note, video_note |
key | all | Opaque local-upload: key із create_tg_media_upload_url. required |
file_name / filename | all | Ім'я файлу, яке піде в original_path. |
caption, caption_entities | document/photo/video/audio/voice_note | Caption у самому media payload. |
self_destruct_type | photo/video/voice_note/video_note | TTL/self-destruct timer. |
thumbnail | document/photo/video/video_note | Окремий local upload payload для preview. |
Параметри по типах
document
| Поле | Тип | Що робить |
|---|---|---|
thumbnail | object | Preview для документа. |
disable_content_type_detection | bool | Вимикає авто-детекцію MIME типу в Telegram. |
photo
| Поле | Тип | Що робить |
|---|---|---|
width, height | int | Розміри фото. |
added_sticker_file_ids | array | Стікери, прикріплені до фото. |
show_caption_above_media | bool | Підпис над фото. |
has_spoiler | bool | Спойлер-ефект. |
video
| Поле | Тип | Що робить |
|---|---|---|
thumbnail | object | Preview thumbnail. |
cover | object | Cover image для відео. |
duration | int | Тривалість у секундах. |
width, height | int | Розміри відео. |
start_timestamp | int | З якого місця стартує прев'ю/відтворення. |
supports_streaming | bool | Streaming-friendly video. |
show_caption_above_media | bool | Підпис над відео. |
has_spoiler | bool | Спойлер-ефект. |
audio
| Поле | Тип | Що робить |
|---|---|---|
duration | int | Тривалість. |
title | string | Назва треку. |
performer | string | Виконавець. |
album_cover_thumbnail | object | Обкладинка. |
voice_note
| Поле | Тип | Що робить |
|---|---|---|
duration | int | Тривалість. |
waveform | base64 / list / bytes | Хвиля голосового. Необов'язково. |
caption | string | Підпис для voice note. |
Для native вигляду Telegram очікує голосове у форматі OGG/Opus. Якщо дати, наприклад,
mp3 або m4a, результат може поводитися як звичайний audio file.
video_note
| Поле | Тип | Що робить |
|---|---|---|
duration | int | Тривалість. |
length | int | Діаметр/розмір відеокружечка. required |
thumbnail | object | Preview thumbnail. |
Для video_note бажано відправляти квадратне mp4, інакше Telegram може
обрізати/відобразити не так, як очікується.
Приклади payload-ів
Document
{
"action": "send_message",
"userbot_id": 1,
"chat_id": "123",
"text": "PDF",
"media": {
"type": "document",
"key": "local-upload:OPAQUE_DOCUMENT_TOKEN",
"file_name": "report.pdf",
"disable_content_type_detection": false
}
}
Photo
{
"action": "send_message",
"userbot_id": 1,
"chat_id": "123",
"media": {
"type": "photo",
"key": "local-upload:OPAQUE_PHOTO_TOKEN",
"file_name": "image.jpg",
"width": 1200,
"height": 900,
"show_caption_above_media": true,
"has_spoiler": false,
"caption": "Фото"
}
}
Video
{
"action": "send_message",
"userbot_id": 1,
"chat_id": "123",
"text": "ось відео",
"media": {
"type": "video",
"key": "local-upload:OPAQUE_VIDEO_TOKEN",
"file_name": "clip.mp4",
"duration": 17,
"width": 1920,
"height": 1080,
"supports_streaming": true,
"show_caption_above_media": true,
"has_spoiler": true
}
}
Audio
{
"action": "send_message",
"userbot_id": 1,
"chat_id": "123",
"media": {
"type": "audio",
"key": "local-upload:OPAQUE_AUDIO_TOKEN",
"file_name": "song.mp3",
"duration": 198,
"title": "Song title",
"performer": "Artist"
}
}
Voice Note
{
"action": "send_message",
"userbot_id": 1,
"chat_id": "123",
"media": {
"type": "voice_note",
"key": "local-upload:OPAQUE_VOICE_TOKEN",
"file_name": "voice.ogg",
"duration": 8
}
}
Video Note
{
"action": "send_message",
"userbot_id": 1,
"chat_id": "123",
"media": {
"type": "video_note",
"key": "local-upload:OPAQUE_TOKEN",
"file_name": "circle.mp4",
"duration": 12,
"length": 384
}
}
Що відбувається всередині
send_message/send_commentрезолвить opaque key і будуєinputFileGeneratedзconversion=local_file_v1:...- TDLib запускає
updateFileGenerationStart - Owner дістає з
conversionбезпечний relative path усерединіsession_data/_outgoing - Потоково копіює локальний файл у TDLib
destination_path - Завершує generation через
finishFileGeneration
Conversion містить persistent relative path, а не тимчасовий URL. Це дозволяє TDLib повторити generation після restart, доки staging-файл живе в межах TTL.
Типові помилки
| Симптом | Причина | Що перевірити |
|---|---|---|
create_tg_media_upload_url_error | Немає userbot_id або битий payload | Перевірити обов'язкові поля запиту. |
404 на PUT | Upload token протух або не існує | Повторити create_tg_media_upload_url. |
409 на PUT | Цей token уже завантажується або був успішно використаний | Не запускати паралельні PUT; для іншого файла створити новий upload token. |
415 на PUT | Базовий MIME type/subtype не збігся з upload contract | Використовувати result.upload_headers. Значення в ньому завжди без параметрів; параметри в фактичному request header не впливають на порівняння. |
send_message_error / send_comment_error / edit_message_error: media.key is required | Не переданий local-upload key | Перевірити media.key. |
send_message_error / send_comment_error / edit_message_error: Unsupported media.type | Непідтримуваний тип | Використовувати лише задокументовані значення. Для edit_message TDLib підтримує media edit тільки для photo, video, document і audio. |
send_comment_error: message_thread_id is required | Не переданий thread comments | Передати comments_meta.message_thread_id або top-level message_thread_id. |
| Voice відправився як файл/audio | Невірний формат голосового | Для native voice використовувати .ogg/Opus. |
| Video note виглядає не як кружечок | Немає length або невідповідний media format | Передати length і квадратне mp4. |
| Upload пройшов, але TDLib не відправив файл | Staging-файл протух/видалений або listener не обробив generation | Перевірити local upload logs, спільний mount та updateFileGenerationStart. |
Швидка перевірка в Postman
- WS connect до
/ws/chats/ - Надіслати
{"action":"open_client","userbot_id":1} - Надіслати
create_tg_media_upload_url - Зробити HTTP
PUTbinary уupload_url - Надіслати
send_message,send_commentабоedit_messageзmedia.key - Очікувати
send_message/send_comment/edit_messageі відповідні TDLib updates