Enhance Telegram service with multi-format support and chunking
All checks were successful
Validate / Hassfest (push) Successful in 2s

Renamed send_telegram_media_group to send_telegram_notification with expanded capabilities:
- Text messages (when urls is empty)
- Single photo/video (uses sendPhoto/sendVideo APIs)
- Media groups (uses sendMediaGroup API)
- Automatic chunking for unlimited media URLs
- Smart optimization: single-item chunks use appropriate single-item APIs

New parameters:
- max_group_size (2-10, default 10): control items per media group
- chunk_delay (0-60000ms, default 0): delay between chunks for rate limiting
- disable_web_page_preview: disable link previews in text messages

The service now intelligently selects the most efficient Telegram API endpoint based on content type and chunk size, with comprehensive error handling and logging.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 14:33:18 +03:00
parent 1cc5d7cc7d
commit 2ae706d700
6 changed files with 476 additions and 96 deletions

View File

@@ -32,7 +32,7 @@ A Home Assistant custom integration that monitors [Immich](https://immich.app/)
- **Services** - Custom service calls:
- `immich_album_watcher.refresh` - Force immediate data refresh
- `immich_album_watcher.get_recent_assets` - Get recent assets from an album
- `immich_album_watcher.send_telegram_media_group` - Send media to Telegram chats
- `immich_album_watcher.send_telegram_notification` - Send text, photo, video, or media group to Telegram
- **Share Link Management** - Button entities to create and delete share links:
- Create/delete public (unprotected) share links
- Create/delete password-protected share links
@@ -115,12 +115,49 @@ data:
count: 10
```
### Send Telegram Media Group
### Send Telegram Notification
Send media URLs to a Telegram chat as a media group (up to 10 items). Useful for forwarding album photos/videos to Telegram. The service downloads media from Immich and uploads it to Telegram, bypassing any CORS restrictions.
Send notifications to Telegram. Supports multiple formats:
- **Text message** - When `urls` is empty or not provided
- **Single photo** - When `urls` contains one photo
- **Single video** - When `urls` contains one video
- **Media group** - When `urls` contains multiple items
The service downloads media from Immich and uploads it to Telegram, bypassing any CORS restrictions. Large lists of media are automatically split into multiple media groups based on the `max_group_size` parameter (default: 10 items per group).
**Examples:**
Text message:
```yaml
service: immich_album_watcher.send_telegram_media_group
service: immich_album_watcher.send_telegram_notification
target:
entity_id: sensor.album_name_asset_count
data:
chat_id: "-1001234567890"
caption: "Check out the new album!"
disable_web_page_preview: true
```
Single photo:
```yaml
service: immich_album_watcher.send_telegram_notification
target:
entity_id: sensor.album_name_asset_count
data:
chat_id: "-1001234567890"
urls:
- url: "https://immich.example.com/api/assets/xxx/thumbnail?key=yyy"
type: photo
caption: "Beautiful sunset!"
```
Media group:
```yaml
service: immich_album_watcher.send_telegram_notification
target:
entity_id: sensor.album_name_asset_count
data:
@@ -131,18 +168,21 @@ data:
- url: "https://immich.example.com/api/assets/zzz/video/playback?key=yyy"
type: video
caption: "New photos from the album!"
reply_to_message_id: 123 # optional
reply_to_message_id: 123
```
| Field | Description | Required |
|-------|-------------|----------|
| `chat_id` | Telegram chat ID to send to | Yes |
| `urls` | List of media items with `url` and `type` (photo/video) | Yes |
| `urls` | List of media items with `url` and `type` (photo/video). Empty for text message. | No |
| `bot_token` | Telegram bot token (uses configured token if not provided) | No |
| `caption` | Caption for the media group (applied to first item) | No |
| `caption` | For media: caption applied to first item. For text: the message text. | No |
| `reply_to_message_id` | Message ID to reply to | No |
| `disable_web_page_preview` | Disable link previews in text messages | No |
| `max_group_size` | Maximum media items per group (2-10). Large lists split into multiple groups. Default: 10 | No |
| `chunk_delay` | Delay in milliseconds between sending multiple groups (0-60000). Useful for rate limiting. Default: 0 | No |
The service returns a response with `success` status and `message_ids` of sent messages.
The service returns a response with `success` status and `message_id` (single message), `message_ids` (media group), or `groups_sent` (number of groups when split).
## Events

View File

@@ -70,4 +70,4 @@ PLATFORMS: Final = ["sensor", "binary_sensor", "camera", "text", "button"]
# Services
SERVICE_REFRESH: Final = "refresh"
SERVICE_GET_RECENT_ASSETS: Final = "get_recent_assets"
SERVICE_SEND_TELEGRAM_MEDIA_GROUP: Final = "send_telegram_media_group"
SERVICE_SEND_TELEGRAM_NOTIFICATION: Final = "send_telegram_notification"

View File

@@ -42,7 +42,7 @@ from .const import (
DOMAIN,
SERVICE_GET_RECENT_ASSETS,
SERVICE_REFRESH,
SERVICE_SEND_TELEGRAM_MEDIA_GROUP,
SERVICE_SEND_TELEGRAM_NOTIFICATION,
)
from .coordinator import AlbumData, ImmichAlbumWatcherCoordinator
@@ -99,15 +99,22 @@ async def async_setup_entry(
)
platform.async_register_entity_service(
SERVICE_SEND_TELEGRAM_MEDIA_GROUP,
SERVICE_SEND_TELEGRAM_NOTIFICATION,
{
vol.Optional("bot_token"): str,
vol.Required("chat_id"): vol.Coerce(str),
vol.Required("urls"): vol.All(list, vol.Length(min=1, max=10)),
vol.Optional("urls"): list,
vol.Optional("caption"): str,
vol.Optional("reply_to_message_id"): vol.Coerce(int),
vol.Optional("disable_web_page_preview"): bool,
vol.Optional("max_group_size", default=10): vol.All(
vol.Coerce(int), vol.Range(min=2, max=10)
),
vol.Optional("chunk_delay", default=0): vol.All(
vol.Coerce(int), vol.Range(min=0, max=60000)
),
},
"async_send_telegram_media_group",
"async_send_telegram_notification",
supports_response=SupportsResponse.ONLY,
)
@@ -167,15 +174,24 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
assets = await self.coordinator.async_get_recent_assets(count)
return {"assets": assets}
async def async_send_telegram_media_group(
async def async_send_telegram_notification(
self,
chat_id: str,
urls: list[dict[str, str]],
urls: list[dict[str, str]] | None = None,
bot_token: str | None = None,
caption: str | None = None,
reply_to_message_id: int | None = None,
disable_web_page_preview: bool | None = None,
max_group_size: int = 10,
chunk_delay: int = 0,
) -> ServiceResponse:
"""Send media URLs to Telegram as a media group.
"""Send notification to Telegram.
Supports:
- Empty URLs: sends a simple text message
- Single photo: uses sendPhoto API
- Single video: uses sendVideo API
- Multiple items: uses sendMediaGroup API (splits into multiple groups if needed)
Each item in urls should be a dict with 'url' and 'type' (photo/video).
Downloads media and uploads to Telegram to bypass CORS restrictions.
@@ -195,81 +211,63 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
session = async_get_clientsession(self.hass)
# Download all media files
media_files: list[tuple[str, bytes, str]] = []
for i, item in enumerate(urls):
url = item.get("url")
media_type = item.get("type", "photo")
# Handle empty URLs - send simple text message
if not urls:
return await self._send_telegram_message(
session, token, chat_id, caption or "", reply_to_message_id, disable_web_page_preview
)
if not url:
return {
"success": False,
"error": f"Missing 'url' in item {i}",
}
# Handle single photo
if len(urls) == 1 and urls[0].get("type", "photo") == "photo":
return await self._send_telegram_photo(
session, token, chat_id, urls[0].get("url"), caption, reply_to_message_id
)
if media_type not in ("photo", "video"):
return {
"success": False,
"error": f"Invalid type '{media_type}' in item {i}. Must be 'photo' or 'video'.",
}
# Handle single video
if len(urls) == 1 and urls[0].get("type") == "video":
return await self._send_telegram_video(
session, token, chat_id, urls[0].get("url"), caption, reply_to_message_id
)
try:
_LOGGER.debug("Downloading media %d from %s", i, url[:80])
async with session.get(url) as resp:
if resp.status != 200:
return {
"success": False,
"error": f"Failed to download media {i}: HTTP {resp.status}",
}
data = await resp.read()
ext = "jpg" if media_type == "photo" else "mp4"
filename = f"media_{i}.{ext}"
media_files.append((media_type, data, filename))
_LOGGER.debug("Downloaded media %d: %d bytes", i, len(data))
except aiohttp.ClientError as err:
return {
"success": False,
"error": f"Failed to download media {i}: {err}",
}
# Handle multiple items - send as media group(s)
return await self._send_telegram_media_group(
session, token, chat_id, urls, caption, reply_to_message_id, max_group_size, chunk_delay
)
# Build multipart form
form = FormData()
form.add_field("chat_id", chat_id)
async def _send_telegram_message(
self,
session: Any,
token: str,
chat_id: str,
text: str,
reply_to_message_id: int | None = None,
disable_web_page_preview: bool | None = None,
) -> ServiceResponse:
"""Send a simple text message to Telegram."""
import aiohttp
telegram_url = f"https://api.telegram.org/bot{token}/sendMessage"
payload: dict[str, Any] = {
"chat_id": chat_id,
"text": text or "Notification from Home Assistant",
}
if reply_to_message_id:
form.add_field("reply_to_message_id", str(reply_to_message_id))
payload["reply_to_message_id"] = reply_to_message_id
# Build media JSON with attach:// references
media_json = []
for i, (media_type, data, filename) in enumerate(media_files):
attach_name = f"file{i}"
media_item: dict[str, Any] = {
"type": media_type,
"media": f"attach://{attach_name}",
}
if i == 0 and caption:
media_item["caption"] = caption
media_json.append(media_item)
content_type = "image/jpeg" if media_type == "photo" else "video/mp4"
form.add_field(attach_name, data, filename=filename, content_type=content_type)
form.add_field("media", json.dumps(media_json))
# Send to Telegram
telegram_url = f"https://api.telegram.org/bot{token}/sendMediaGroup"
if disable_web_page_preview is not None:
payload["disable_web_page_preview"] = disable_web_page_preview
try:
_LOGGER.debug("Uploading %d files to Telegram", len(media_files))
async with session.post(telegram_url, data=form) as response:
_LOGGER.debug("Sending text message to Telegram")
async with session.post(telegram_url, json=payload) as response:
result = await response.json()
_LOGGER.debug("Telegram API response: status=%d, ok=%s", response.status, result.get("ok"))
if response.status == 200 and result.get("ok"):
return {
"success": True,
"message_ids": [
msg.get("message_id") for msg in result.get("result", [])
],
"message_id": result.get("result", {}).get("message_id"),
}
else:
_LOGGER.error("Telegram API error: %s", result)
@@ -279,9 +277,299 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
"error_code": result.get("error_code"),
}
except aiohttp.ClientError as err:
_LOGGER.error("Telegram upload failed: %s", err)
_LOGGER.error("Telegram message send failed: %s", err)
return {"success": False, "error": str(err)}
async def _send_telegram_photo(
self,
session: Any,
token: str,
chat_id: str,
url: str | None,
caption: str | None = None,
reply_to_message_id: int | None = None,
) -> ServiceResponse:
"""Send a single photo to Telegram."""
import aiohttp
from aiohttp import FormData
if not url:
return {"success": False, "error": "Missing 'url' for photo"}
try:
# Download the photo
_LOGGER.debug("Downloading photo from %s", url[:80])
async with session.get(url) as resp:
if resp.status != 200:
return {
"success": False,
"error": f"Failed to download photo: HTTP {resp.status}",
}
data = await resp.read()
_LOGGER.debug("Downloaded photo: %d bytes", len(data))
# Build multipart form
form = FormData()
form.add_field("chat_id", chat_id)
form.add_field("photo", data, filename="photo.jpg", content_type="image/jpeg")
if caption:
form.add_field("caption", caption)
if reply_to_message_id:
form.add_field("reply_to_message_id", str(reply_to_message_id))
# Send to Telegram
telegram_url = f"https://api.telegram.org/bot{token}/sendPhoto"
_LOGGER.debug("Uploading photo to Telegram")
async with session.post(telegram_url, data=form) as response:
result = await response.json()
_LOGGER.debug("Telegram API response: status=%d, ok=%s", response.status, result.get("ok"))
if response.status == 200 and result.get("ok"):
return {
"success": True,
"message_id": result.get("result", {}).get("message_id"),
}
else:
_LOGGER.error("Telegram API error: %s", result)
return {
"success": False,
"error": result.get("description", "Unknown Telegram error"),
"error_code": result.get("error_code"),
}
except aiohttp.ClientError as err:
_LOGGER.error("Telegram photo upload failed: %s", err)
return {"success": False, "error": str(err)}
async def _send_telegram_video(
self,
session: Any,
token: str,
chat_id: str,
url: str | None,
caption: str | None = None,
reply_to_message_id: int | None = None,
) -> ServiceResponse:
"""Send a single video to Telegram."""
import aiohttp
from aiohttp import FormData
if not url:
return {"success": False, "error": "Missing 'url' for video"}
try:
# Download the video
_LOGGER.debug("Downloading video from %s", url[:80])
async with session.get(url) as resp:
if resp.status != 200:
return {
"success": False,
"error": f"Failed to download video: HTTP {resp.status}",
}
data = await resp.read()
_LOGGER.debug("Downloaded video: %d bytes", len(data))
# Build multipart form
form = FormData()
form.add_field("chat_id", chat_id)
form.add_field("video", data, filename="video.mp4", content_type="video/mp4")
if caption:
form.add_field("caption", caption)
if reply_to_message_id:
form.add_field("reply_to_message_id", str(reply_to_message_id))
# Send to Telegram
telegram_url = f"https://api.telegram.org/bot{token}/sendVideo"
_LOGGER.debug("Uploading video to Telegram")
async with session.post(telegram_url, data=form) as response:
result = await response.json()
_LOGGER.debug("Telegram API response: status=%d, ok=%s", response.status, result.get("ok"))
if response.status == 200 and result.get("ok"):
return {
"success": True,
"message_id": result.get("result", {}).get("message_id"),
}
else:
_LOGGER.error("Telegram API error: %s", result)
return {
"success": False,
"error": result.get("description", "Unknown Telegram error"),
"error_code": result.get("error_code"),
}
except aiohttp.ClientError as err:
_LOGGER.error("Telegram video upload failed: %s", err)
return {"success": False, "error": str(err)}
async def _send_telegram_media_group(
self,
session: Any,
token: str,
chat_id: str,
urls: list[dict[str, str]],
caption: str | None = None,
reply_to_message_id: int | None = None,
max_group_size: int = 10,
chunk_delay: int = 0,
) -> ServiceResponse:
"""Send media URLs to Telegram as media group(s).
If urls list exceeds max_group_size, splits into multiple media groups.
For chunks with single items, uses sendPhoto/sendVideo APIs.
Applies chunk_delay (in milliseconds) between groups if specified.
"""
import json
import asyncio
import aiohttp
from aiohttp import FormData
# Split URLs into chunks based on max_group_size
chunks = [urls[i:i + max_group_size] for i in range(0, len(urls), max_group_size)]
all_message_ids = []
_LOGGER.debug("Sending %d media items in %d chunk(s) of max %d items (delay: %dms)",
len(urls), len(chunks), max_group_size, chunk_delay)
for chunk_idx, chunk in enumerate(chunks):
# Add delay before sending subsequent chunks
if chunk_idx > 0 and chunk_delay > 0:
delay_seconds = chunk_delay / 1000
_LOGGER.debug("Waiting %dms (%ss) before sending chunk %d/%d",
chunk_delay, delay_seconds, chunk_idx + 1, len(chunks))
await asyncio.sleep(delay_seconds)
# Optimize: Use single-item APIs for chunks with 1 item
if len(chunk) == 1:
item = chunk[0]
media_type = item.get("type", "photo")
url = item.get("url")
# Only apply caption and reply_to to the first chunk
chunk_caption = caption if chunk_idx == 0 else None
chunk_reply_to = reply_to_message_id if chunk_idx == 0 else None
if media_type == "photo":
_LOGGER.debug("Sending chunk %d/%d as single photo", chunk_idx + 1, len(chunks))
result = await self._send_telegram_photo(
session, token, chat_id, url, chunk_caption, chunk_reply_to
)
else: # video
_LOGGER.debug("Sending chunk %d/%d as single video", chunk_idx + 1, len(chunks))
result = await self._send_telegram_video(
session, token, chat_id, url, chunk_caption, chunk_reply_to
)
if not result.get("success"):
result["failed_at_chunk"] = chunk_idx + 1
return result
all_message_ids.append(result.get("message_id"))
continue
# Multi-item chunk: use sendMediaGroup
_LOGGER.debug("Sending chunk %d/%d as media group (%d items)", chunk_idx + 1, len(chunks), len(chunk))
# Download all media files for this chunk
media_files: list[tuple[str, bytes, str]] = []
for i, item in enumerate(chunk):
url = item.get("url")
media_type = item.get("type", "photo")
if not url:
return {
"success": False,
"error": f"Missing 'url' in item {chunk_idx * max_group_size + i}",
}
if media_type not in ("photo", "video"):
return {
"success": False,
"error": f"Invalid type '{media_type}' in item {chunk_idx * max_group_size + i}. Must be 'photo' or 'video'.",
}
try:
_LOGGER.debug("Downloading media %d from %s", chunk_idx * max_group_size + i, url[:80])
async with session.get(url) as resp:
if resp.status != 200:
return {
"success": False,
"error": f"Failed to download media {chunk_idx * max_group_size + i}: HTTP {resp.status}",
}
data = await resp.read()
ext = "jpg" if media_type == "photo" else "mp4"
filename = f"media_{chunk_idx * max_group_size + i}.{ext}"
media_files.append((media_type, data, filename))
_LOGGER.debug("Downloaded media %d: %d bytes", chunk_idx * max_group_size + i, len(data))
except aiohttp.ClientError as err:
return {
"success": False,
"error": f"Failed to download media {chunk_idx * max_group_size + i}: {err}",
}
# Build multipart form
form = FormData()
form.add_field("chat_id", chat_id)
# Only use reply_to_message_id for the first chunk
if chunk_idx == 0 and reply_to_message_id:
form.add_field("reply_to_message_id", str(reply_to_message_id))
# Build media JSON with attach:// references
media_json = []
for i, (media_type, data, filename) in enumerate(media_files):
attach_name = f"file{i}"
media_item: dict[str, Any] = {
"type": media_type,
"media": f"attach://{attach_name}",
}
# Only add caption to the first item of the first chunk
if chunk_idx == 0 and i == 0 and caption:
media_item["caption"] = caption
media_json.append(media_item)
content_type = "image/jpeg" if media_type == "photo" else "video/mp4"
form.add_field(attach_name, data, filename=filename, content_type=content_type)
form.add_field("media", json.dumps(media_json))
# Send to Telegram
telegram_url = f"https://api.telegram.org/bot{token}/sendMediaGroup"
try:
_LOGGER.debug("Uploading media group chunk %d/%d (%d files) to Telegram",
chunk_idx + 1, len(chunks), len(media_files))
async with session.post(telegram_url, data=form) as response:
result = await response.json()
_LOGGER.debug("Telegram API response: status=%d, ok=%s", response.status, result.get("ok"))
if response.status == 200 and result.get("ok"):
chunk_message_ids = [
msg.get("message_id") for msg in result.get("result", [])
]
all_message_ids.extend(chunk_message_ids)
else:
_LOGGER.error("Telegram API error for chunk %d: %s", chunk_idx + 1, result)
return {
"success": False,
"error": result.get("description", "Unknown Telegram error"),
"error_code": result.get("error_code"),
"failed_at_chunk": chunk_idx + 1,
}
except aiohttp.ClientError as err:
_LOGGER.error("Telegram upload failed for chunk %d: %s", chunk_idx + 1, err)
return {
"success": False,
"error": str(err),
"failed_at_chunk": chunk_idx + 1,
}
return {
"success": True,
"message_ids": all_message_ids,
"chunks_sent": len(chunks),
}
class ImmichAlbumIdSensor(ImmichAlbumBaseSensor):
"""Sensor exposing the Immich album ID."""

View File

@@ -25,9 +25,9 @@ get_recent_assets:
max: 100
mode: slider
send_telegram_media_group:
name: Send Telegram Media Group
description: Send specified media URLs to a Telegram chat as a media group.
send_telegram_notification:
name: Send Telegram Notification
description: Send a notification to Telegram (text, photo, video, or media group).
target:
entity:
integration: immich_album_watcher
@@ -47,13 +47,13 @@ send_telegram_media_group:
text:
urls:
name: URLs
description: List of media URLs to send (max 10). Each item should have 'url' and 'type' (photo/video).
required: true
description: List of media URLs to send. Each item should have 'url' and 'type' (photo/video). If empty, sends a text message. Large lists are automatically split into multiple media groups.
required: false
selector:
object:
caption:
name: Caption
description: Optional caption for the media group (applied to first item).
description: Caption text. For media, applied to first item. For empty URLs, this is the message text.
required: false
selector:
text:
@@ -65,3 +65,31 @@ send_telegram_media_group:
selector:
number:
mode: box
disable_web_page_preview:
name: Disable Web Page Preview
description: Disable link previews in text messages.
required: false
selector:
boolean:
max_group_size:
name: Max Group Size
description: Maximum number of media items per media group (2-10). Large lists will be split into multiple groups.
required: false
default: 10
selector:
number:
min: 2
max: 10
mode: slider
chunk_delay:
name: Chunk Delay
description: Delay in milliseconds between sending multiple media groups (0-60000). Useful for rate limiting.
required: false
default: 0
selector:
number:
min: 0
max: 60000
step: 100
unit_of_measurement: "ms"
mode: slider

View File

@@ -123,7 +123,7 @@
},
"data_description": {
"scan_interval": "How often to check for album changes (10-3600 seconds)",
"telegram_bot_token": "Bot token for sending media groups to Telegram"
"telegram_bot_token": "Bot token for sending notifications to Telegram"
}
}
}
@@ -143,9 +143,9 @@
}
}
},
"send_telegram_media_group": {
"name": "Send Telegram Media Group",
"description": "Send media URLs to Telegram as a media group.",
"send_telegram_notification": {
"name": "Send Telegram Notification",
"description": "Send a notification to Telegram (text, photo, video, or media group).",
"fields": {
"bot_token": {
"name": "Bot Token",
@@ -157,15 +157,27 @@
},
"urls": {
"name": "URLs",
"description": "List of media URLs with type (photo/video)."
"description": "List of media URLs with type (photo/video). If empty, sends a text message. Large lists are automatically split into multiple media groups."
},
"caption": {
"name": "Caption",
"description": "Optional caption for the first media item."
"description": "Caption text. For media, applied to first item. For empty URLs, this is the message text."
},
"reply_to_message_id": {
"name": "Reply To",
"description": "Optional message ID to reply to."
},
"disable_web_page_preview": {
"name": "Disable Web Page Preview",
"description": "Disable link previews in text messages."
},
"max_group_size": {
"name": "Max Group Size",
"description": "Maximum number of media items per media group (2-10). Large lists will be split into multiple groups."
},
"chunk_delay": {
"name": "Chunk Delay",
"description": "Delay in milliseconds between sending multiple media groups (0-60000). Useful for rate limiting."
}
}
}

View File

@@ -123,7 +123,7 @@
},
"data_description": {
"scan_interval": "Как часто проверять изменения в альбомах (10-3600 секунд)",
"telegram_bot_token": "Токен бота для отправки медиа-групп в Telegram"
"telegram_bot_token": "Токен бота для отправки уведомлений в Telegram"
}
}
}
@@ -143,9 +143,9 @@
}
}
},
"send_telegram_media_group": {
"name": "Отправить медиа-группу в Telegram",
"description": "Отправить медиа-файлы в Telegram как группу.",
"send_telegram_notification": {
"name": "Отправить уведомление в Telegram",
"description": "Отправить уведомление в Telegram (текст, фото, видео или медиа-группу).",
"fields": {
"bot_token": {
"name": "Токен бота",
@@ -157,15 +157,27 @@
},
"urls": {
"name": "URL-адреса",
"description": "Список URL медиа-файлов с типом (photo/video)."
"description": "Список URL медиа-файлов с типом (photo/video). Если пусто, отправляет текстовое сообщение. Большие списки автоматически разделяются на несколько медиа-групп."
},
"caption": {
"name": "Подпись",
"description": "Необязательная подпись для первого медиа-файла."
"description": "Текст подписи. Для медиа применяется к первому элементу. Для пустых URLs это текст сообщения."
},
"reply_to_message_id": {
"name": "Ответ на",
"description": "ID сообщения для ответа (необязательно)."
},
"disable_web_page_preview": {
"name": "Отключить предпросмотр ссылок",
"description": "Отключить предпросмотр ссылок в текстовых сообщениях."
},
"max_group_size": {
"name": "Макс. размер группы",
"description": "Максимальное количество медиа-файлов в одной группе (2-10). Большие списки будут разделены на несколько групп."
},
"chunk_delay": {
"name": "Задержка между группами",
"description": "Задержка в миллисекундах между отправкой нескольких медиа-групп (0-60000). Полезно для ограничения скорости."
}
}
}