Add chat_action parameter to send_telegram_notification service
All checks were successful
Validate / Hassfest (push) Successful in 4s
All checks were successful
Validate / Hassfest (push) Successful in 4s
Shows typing/upload indicator while processing media. Supports: typing, upload_photo, upload_video, upload_document actions. Set to empty string to disable. Default: typing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -432,6 +432,7 @@ data:
|
||||
| `wait_for_response` | Wait for Telegram to finish processing. Set to `false` for fire-and-forget (automation continues immediately). Default: `true` | No |
|
||||
| `max_asset_data_size` | Maximum asset size in bytes. Assets exceeding this limit will be skipped. Default: no limit | No |
|
||||
| `send_large_photos_as_documents` | Handle photos exceeding Telegram limits (10MB or 10000px dimension sum). If `true`, send as documents. If `false`, skip oversized photos. Default: `false` | No |
|
||||
| `chat_action` | Chat action to display while processing media (`typing`, `upload_photo`, `upload_video`, `upload_document`). Set to empty string to disable. Default: `typing` | No |
|
||||
|
||||
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). When `wait_for_response` is `false`, the service returns immediately with `{"success": true, "status": "queued"}` while processing continues in the background.
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
@@ -144,6 +145,9 @@ async def async_setup_entry(
|
||||
vol.Coerce(int), vol.Range(min=1, max=52428800)
|
||||
),
|
||||
vol.Optional("send_large_photos_as_documents", default=False): bool,
|
||||
vol.Optional("chat_action", default="typing"): vol.Any(
|
||||
None, vol.In(["typing", "upload_photo", "upload_video", "upload_document"])
|
||||
),
|
||||
},
|
||||
"async_send_telegram_notification",
|
||||
supports_response=SupportsResponse.OPTIONAL,
|
||||
@@ -248,6 +252,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
||||
wait_for_response: bool = True,
|
||||
max_asset_data_size: int | None = None,
|
||||
send_large_photos_as_documents: bool = False,
|
||||
chat_action: str | None = "typing",
|
||||
) -> ServiceResponse:
|
||||
"""Send notification to Telegram.
|
||||
|
||||
@@ -278,6 +283,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
||||
chunk_delay=chunk_delay,
|
||||
max_asset_data_size=max_asset_data_size,
|
||||
send_large_photos_as_documents=send_large_photos_as_documents,
|
||||
chat_action=chat_action,
|
||||
)
|
||||
)
|
||||
return {"success": True, "status": "queued", "message": "Notification queued for background processing"}
|
||||
@@ -295,6 +301,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
||||
chunk_delay=chunk_delay,
|
||||
max_asset_data_size=max_asset_data_size,
|
||||
send_large_photos_as_documents=send_large_photos_as_documents,
|
||||
chat_action=chat_action,
|
||||
)
|
||||
|
||||
async def _execute_telegram_notification(
|
||||
@@ -310,6 +317,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
||||
chunk_delay: int = 0,
|
||||
max_asset_data_size: int | None = None,
|
||||
send_large_photos_as_documents: bool = False,
|
||||
chat_action: str | None = "typing",
|
||||
) -> ServiceResponse:
|
||||
"""Execute the Telegram notification (internal method)."""
|
||||
import json
|
||||
@@ -327,30 +335,44 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
||||
|
||||
session = async_get_clientsession(self.hass)
|
||||
|
||||
# Handle empty URLs - send simple text message
|
||||
# Handle empty URLs - send simple text message (no typing indicator needed)
|
||||
if not urls:
|
||||
return await self._send_telegram_message(
|
||||
session, token, chat_id, caption or "", reply_to_message_id, disable_web_page_preview, parse_mode
|
||||
)
|
||||
|
||||
# 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, parse_mode,
|
||||
# Start chat action indicator for media notifications (before downloading assets)
|
||||
typing_task = None
|
||||
if chat_action:
|
||||
typing_task = self._start_typing_indicator(session, token, chat_id, chat_action)
|
||||
|
||||
try:
|
||||
# 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, parse_mode,
|
||||
max_asset_data_size, send_large_photos_as_documents
|
||||
)
|
||||
|
||||
# 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, parse_mode, max_asset_data_size
|
||||
)
|
||||
|
||||
# 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, parse_mode,
|
||||
max_asset_data_size, send_large_photos_as_documents
|
||||
)
|
||||
|
||||
# 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, parse_mode, max_asset_data_size
|
||||
)
|
||||
|
||||
# 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, parse_mode,
|
||||
max_asset_data_size, send_large_photos_as_documents
|
||||
)
|
||||
finally:
|
||||
# Stop chat action indicator when done (success or error)
|
||||
if typing_task:
|
||||
typing_task.cancel()
|
||||
try:
|
||||
await typing_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
async def _send_telegram_message(
|
||||
self,
|
||||
@@ -400,6 +422,74 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
||||
_LOGGER.error("Telegram message send failed: %s", err)
|
||||
return {"success": False, "error": str(err)}
|
||||
|
||||
async def _send_telegram_chat_action(
|
||||
self,
|
||||
session: Any,
|
||||
token: str,
|
||||
chat_id: str,
|
||||
action: str = "typing",
|
||||
) -> bool:
|
||||
"""Send a chat action to Telegram (e.g., typing indicator).
|
||||
|
||||
Args:
|
||||
session: aiohttp client session
|
||||
token: Telegram bot token
|
||||
chat_id: Target chat ID
|
||||
action: Chat action type (typing, upload_photo, upload_video, etc.)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
import aiohttp
|
||||
|
||||
telegram_url = f"https://api.telegram.org/bot{token}/sendChatAction"
|
||||
payload = {"chat_id": chat_id, "action": action}
|
||||
|
||||
try:
|
||||
async with session.post(telegram_url, json=payload) as response:
|
||||
result = await response.json()
|
||||
if response.status == 200 and result.get("ok"):
|
||||
_LOGGER.debug("Sent chat action '%s' to chat %s", action, chat_id)
|
||||
return True
|
||||
else:
|
||||
_LOGGER.debug("Failed to send chat action: %s", result.get("description"))
|
||||
return False
|
||||
except aiohttp.ClientError as err:
|
||||
_LOGGER.debug("Chat action request failed: %s", err)
|
||||
return False
|
||||
|
||||
def _start_typing_indicator(
|
||||
self,
|
||||
session: Any,
|
||||
token: str,
|
||||
chat_id: str,
|
||||
action: str = "typing",
|
||||
) -> asyncio.Task:
|
||||
"""Start a background task that sends chat action indicator periodically.
|
||||
|
||||
The chat action indicator expires after ~5 seconds, so we refresh it every 4 seconds.
|
||||
|
||||
Args:
|
||||
session: aiohttp client session
|
||||
token: Telegram bot token
|
||||
chat_id: Target chat ID
|
||||
action: Chat action type (typing, upload_photo, upload_video, etc.)
|
||||
|
||||
Returns:
|
||||
The background task (cancel it when done)
|
||||
"""
|
||||
|
||||
async def action_loop() -> None:
|
||||
"""Keep sending chat action until cancelled."""
|
||||
try:
|
||||
while True:
|
||||
await self._send_telegram_chat_action(session, token, chat_id, action)
|
||||
await asyncio.sleep(4)
|
||||
except asyncio.CancelledError:
|
||||
_LOGGER.debug("Chat action indicator stopped for action '%s'", action)
|
||||
|
||||
return asyncio.create_task(action_loop())
|
||||
|
||||
def _log_telegram_error(
|
||||
self,
|
||||
error_code: int | None,
|
||||
|
||||
@@ -238,3 +238,21 @@ send_telegram_notification:
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
chat_action:
|
||||
name: Chat Action
|
||||
description: Chat action to display while processing (typing, upload_photo, upload_video, upload_document). Set to empty to disable.
|
||||
required: false
|
||||
default: "typing"
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- label: "Typing"
|
||||
value: "typing"
|
||||
- label: "Uploading Photo"
|
||||
value: "upload_photo"
|
||||
- label: "Uploading Video"
|
||||
value: "upload_video"
|
||||
- label: "Uploading Document"
|
||||
value: "upload_document"
|
||||
- label: "Disabled"
|
||||
value: ""
|
||||
|
||||
@@ -244,6 +244,10 @@
|
||||
"send_large_photos_as_documents": {
|
||||
"name": "Send Large Photos As Documents",
|
||||
"description": "How to handle photos exceeding Telegram's limits (10MB or 10000px dimension sum). If true, send as documents. If false, downsize to fit limits."
|
||||
},
|
||||
"chat_action": {
|
||||
"name": "Chat Action",
|
||||
"description": "Chat action to display while processing (typing, upload_photo, upload_video, upload_document). Set to empty to disable."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +244,10 @@
|
||||
"send_large_photos_as_documents": {
|
||||
"name": "Большие фото как документы",
|
||||
"description": "Как обрабатывать фото, превышающие лимиты Telegram (10МБ или сумма размеров 10000пкс). Если true, отправлять как документы. Если false, уменьшать для соответствия лимитам."
|
||||
},
|
||||
"chat_action": {
|
||||
"name": "Действие в чате",
|
||||
"description": "Действие для отображения во время обработки (typing, upload_photo, upload_video, upload_document). Оставьте пустым для отключения."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user