Add parse_mode to service call API
All checks were successful
Validate / Hassfest (push) Successful in 3s
All checks were successful
Validate / Hassfest (push) Successful in 3s
This commit is contained in:
15
CLAUDE.md
15
CLAUDE.md
@@ -14,3 +14,18 @@ Use semantic versioning:
|
|||||||
- **MAJOR** (x.0.0): Breaking changes
|
- **MAJOR** (x.0.0): Breaking changes
|
||||||
- **MINOR** (0.x.0): New features, backward compatible
|
- **MINOR** (0.x.0): New features, backward compatible
|
||||||
- **PATCH** (0.0.x): Bug fixes, integration documentation updates
|
- **PATCH** (0.0.x): Bug fixes, integration documentation updates
|
||||||
|
|
||||||
|
## Documentation Updates
|
||||||
|
|
||||||
|
**IMPORTANT**: Always keep the README.md synchronized with integration changes.
|
||||||
|
|
||||||
|
When modifying the integration interface, you MUST update the corresponding documentation:
|
||||||
|
|
||||||
|
- **Service parameters**: Update parameter tables and examples in README.md
|
||||||
|
- **New events**: Add event documentation with examples and field descriptions
|
||||||
|
- **New entities**: Document entity types, attributes, and usage
|
||||||
|
- **Configuration options**: Update configuration documentation
|
||||||
|
- **Translation files**: Add translations for new parameters/entities in `en.json` and `ru.json`
|
||||||
|
- **services.yaml**: Keep service definitions in sync with implementation
|
||||||
|
|
||||||
|
The README is the primary user-facing documentation and must accurately reflect the current state of the integration.
|
||||||
|
|||||||
81
README.md
81
README.md
@@ -171,14 +171,30 @@ data:
|
|||||||
reply_to_message_id: 123
|
reply_to_message_id: 123
|
||||||
```
|
```
|
||||||
|
|
||||||
|
HTML formatting:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: immich_album_watcher.send_telegram_notification
|
||||||
|
target:
|
||||||
|
entity_id: sensor.album_name_asset_count
|
||||||
|
data:
|
||||||
|
chat_id: "-1001234567890"
|
||||||
|
caption: |
|
||||||
|
<b>Album Updated!</b>
|
||||||
|
New photos by <i>{{ trigger.event.data.added_assets[0].asset_owner }}</i>
|
||||||
|
<a href="https://immich.example.com/album">View Album</a>
|
||||||
|
parse_mode: "HTML" # Default, can be omitted
|
||||||
|
```
|
||||||
|
|
||||||
| Field | Description | Required |
|
| Field | Description | Required |
|
||||||
|-------|-------------|----------|
|
|-------|-------------|----------|
|
||||||
| `chat_id` | Telegram chat ID to send to | Yes |
|
| `chat_id` | Telegram chat ID to send to | Yes |
|
||||||
| `urls` | List of media items with `url` and `type` (photo/video). Empty for text message. | No |
|
| `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 |
|
| `bot_token` | Telegram bot token (uses configured token if not provided) | No |
|
||||||
| `caption` | For media: caption applied to first item. For text: the message text. | No |
|
| `caption` | For media: caption applied to first item. For text: the message text. Supports HTML formatting by default. | No |
|
||||||
| `reply_to_message_id` | Message ID to reply to | No |
|
| `reply_to_message_id` | Message ID to reply to | No |
|
||||||
| `disable_web_page_preview` | Disable link previews in text messages | No |
|
| `disable_web_page_preview` | Disable link previews in text messages | No |
|
||||||
|
| `parse_mode` | How to parse caption/text. Options: `HTML`, `Markdown`, `MarkdownV2`, or empty string for plain text. Default: `HTML` | No |
|
||||||
| `max_group_size` | Maximum media items per group (2-10). Large lists split into multiple groups. Default: 10 | 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 |
|
| `chunk_delay` | Delay in milliseconds between sending multiple groups (0-60000). Useful for rate limiting. Default: 0 | No |
|
||||||
|
|
||||||
@@ -186,7 +202,20 @@ The service returns a response with `success` status and `message_id` (single me
|
|||||||
|
|
||||||
## Events
|
## Events
|
||||||
|
|
||||||
Use these events in your automations:
|
The integration fires multiple event types that you can use in your automations:
|
||||||
|
|
||||||
|
### Available Events
|
||||||
|
|
||||||
|
| Event Type | Description | When Fired |
|
||||||
|
|------------|-------------|------------|
|
||||||
|
| `immich_album_watcher_album_changed` | General album change event | Fired for any album change |
|
||||||
|
| `immich_album_watcher_assets_added` | Assets were added to the album | When new photos/videos are added |
|
||||||
|
| `immich_album_watcher_assets_removed` | Assets were removed from the album | When photos/videos are removed |
|
||||||
|
| `immich_album_watcher_album_renamed` | Album name was changed | When the album is renamed |
|
||||||
|
| `immich_album_watcher_album_deleted` | Album was deleted | When the album is deleted from Immich |
|
||||||
|
| `immich_album_watcher_album_sharing_changed` | Album sharing status changed | When album is shared or unshared |
|
||||||
|
|
||||||
|
### Example Usage
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
automation:
|
automation:
|
||||||
@@ -199,21 +228,47 @@ automation:
|
|||||||
data:
|
data:
|
||||||
title: "New Photos"
|
title: "New Photos"
|
||||||
message: "{{ trigger.event.data.added_count }} new photos in {{ trigger.event.data.album_name }}"
|
message: "{{ trigger.event.data.added_count }} new photos in {{ trigger.event.data.album_name }}"
|
||||||
|
|
||||||
|
- alias: "Album renamed"
|
||||||
|
trigger:
|
||||||
|
- platform: event
|
||||||
|
event_type: immich_album_watcher_album_renamed
|
||||||
|
action:
|
||||||
|
- service: notify.mobile_app
|
||||||
|
data:
|
||||||
|
title: "Album Renamed"
|
||||||
|
message: "Album '{{ trigger.event.data.old_name }}' renamed to '{{ trigger.event.data.new_name }}'"
|
||||||
|
|
||||||
|
- alias: "Album deleted"
|
||||||
|
trigger:
|
||||||
|
- platform: event
|
||||||
|
event_type: immich_album_watcher_album_deleted
|
||||||
|
action:
|
||||||
|
- service: notify.mobile_app
|
||||||
|
data:
|
||||||
|
title: "Album Deleted"
|
||||||
|
message: "Album '{{ trigger.event.data.album_name }}' was deleted"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Event Data
|
### Event Data
|
||||||
|
|
||||||
| Field | Description |
|
| Field | Description | Available In |
|
||||||
|-------|-------------|
|
|-------|-------------|--------------|
|
||||||
| `album_id` | Album ID |
|
| `hub_name` | Hub name configured in integration | All events |
|
||||||
| `album_name` | Album name |
|
| `album_id` | Album ID | All events |
|
||||||
| `album_url` | Public URL to view the album (only present if album has a shared link) |
|
| `album_name` | Current album name | All events |
|
||||||
| `change_type` | Type of change (assets_added, assets_removed, changed) |
|
| `album_url` | Public URL to view the album (only present if album has a shared link) | All events except `album_deleted` |
|
||||||
| `added_count` | Number of assets added |
|
| `change_type` | Type of change (assets_added, assets_removed, album_renamed, album_sharing_changed, changed) | All events except `album_deleted` |
|
||||||
| `removed_count` | Number of assets removed |
|
| `shared` | Current sharing status of the album | All events except `album_deleted` |
|
||||||
| `added_assets` | List of added assets with details (see below) |
|
| `added_count` | Number of assets added | `album_changed`, `assets_added` |
|
||||||
| `removed_assets` | List of removed asset IDs |
|
| `removed_count` | Number of assets removed | `album_changed`, `assets_removed` |
|
||||||
| `people` | List of all people detected in the album |
|
| `added_assets` | List of added assets with details (see below) | `album_changed`, `assets_added` |
|
||||||
|
| `removed_assets` | List of removed asset IDs | `album_changed`, `assets_removed` |
|
||||||
|
| `people` | List of all people detected in the album | All events except `album_deleted` |
|
||||||
|
| `old_name` | Previous album name | `album_renamed` |
|
||||||
|
| `new_name` | New album name | `album_renamed` |
|
||||||
|
| `old_shared` | Previous sharing status | `album_sharing_changed` |
|
||||||
|
| `new_shared` | New sharing status | `album_sharing_changed` |
|
||||||
|
|
||||||
### Added Assets Fields
|
### Added Assets Fields
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ DEFAULT_SHARE_PASSWORD: Final = "immich123"
|
|||||||
EVENT_ALBUM_CHANGED: Final = f"{DOMAIN}_album_changed"
|
EVENT_ALBUM_CHANGED: Final = f"{DOMAIN}_album_changed"
|
||||||
EVENT_ASSETS_ADDED: Final = f"{DOMAIN}_assets_added"
|
EVENT_ASSETS_ADDED: Final = f"{DOMAIN}_assets_added"
|
||||||
EVENT_ASSETS_REMOVED: Final = f"{DOMAIN}_assets_removed"
|
EVENT_ASSETS_REMOVED: Final = f"{DOMAIN}_assets_removed"
|
||||||
|
EVENT_ALBUM_RENAMED: Final = f"{DOMAIN}_album_renamed"
|
||||||
|
EVENT_ALBUM_DELETED: Final = f"{DOMAIN}_album_deleted"
|
||||||
|
EVENT_ALBUM_SHARING_CHANGED: Final = f"{DOMAIN}_album_sharing_changed"
|
||||||
|
|
||||||
# Attributes
|
# Attributes
|
||||||
ATTR_HUB_NAME: Final = "hub_name"
|
ATTR_HUB_NAME: Final = "hub_name"
|
||||||
@@ -50,6 +53,10 @@ ATTR_THUMBNAIL_URL: Final = "thumbnail_url"
|
|||||||
ATTR_SHARED: Final = "shared"
|
ATTR_SHARED: Final = "shared"
|
||||||
ATTR_OWNER: Final = "owner"
|
ATTR_OWNER: Final = "owner"
|
||||||
ATTR_PEOPLE: Final = "people"
|
ATTR_PEOPLE: Final = "people"
|
||||||
|
ATTR_OLD_NAME: Final = "old_name"
|
||||||
|
ATTR_NEW_NAME: Final = "new_name"
|
||||||
|
ATTR_OLD_SHARED: Final = "old_shared"
|
||||||
|
ATTR_NEW_SHARED: Final = "new_shared"
|
||||||
ATTR_ASSET_TYPE: Final = "asset_type"
|
ATTR_ASSET_TYPE: Final = "asset_type"
|
||||||
ATTR_ASSET_FILENAME: Final = "asset_filename"
|
ATTR_ASSET_FILENAME: Final = "asset_filename"
|
||||||
ATTR_ASSET_CREATED: Final = "asset_created"
|
ATTR_ASSET_CREATED: Final = "asset_created"
|
||||||
|
|||||||
@@ -38,10 +38,18 @@ from .const import (
|
|||||||
ATTR_PEOPLE,
|
ATTR_PEOPLE,
|
||||||
ATTR_REMOVED_ASSETS,
|
ATTR_REMOVED_ASSETS,
|
||||||
ATTR_REMOVED_COUNT,
|
ATTR_REMOVED_COUNT,
|
||||||
|
ATTR_OLD_NAME,
|
||||||
|
ATTR_NEW_NAME,
|
||||||
|
ATTR_OLD_SHARED,
|
||||||
|
ATTR_NEW_SHARED,
|
||||||
|
ATTR_SHARED,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EVENT_ALBUM_CHANGED,
|
EVENT_ALBUM_CHANGED,
|
||||||
EVENT_ASSETS_ADDED,
|
EVENT_ASSETS_ADDED,
|
||||||
EVENT_ASSETS_REMOVED,
|
EVENT_ASSETS_REMOVED,
|
||||||
|
EVENT_ALBUM_RENAMED,
|
||||||
|
EVENT_ALBUM_DELETED,
|
||||||
|
EVENT_ALBUM_SHARING_CHANGED,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -210,6 +218,10 @@ class AlbumChange:
|
|||||||
removed_count: int = 0
|
removed_count: int = 0
|
||||||
added_assets: list[AssetInfo] = field(default_factory=list)
|
added_assets: list[AssetInfo] = field(default_factory=list)
|
||||||
removed_asset_ids: list[str] = field(default_factory=list)
|
removed_asset_ids: list[str] = field(default_factory=list)
|
||||||
|
old_name: str | None = None
|
||||||
|
new_name: str | None = None
|
||||||
|
old_shared: bool | None = None
|
||||||
|
new_shared: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]):
|
class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]):
|
||||||
@@ -510,6 +522,15 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]):
|
|||||||
) as response:
|
) as response:
|
||||||
if response.status == 404:
|
if response.status == 404:
|
||||||
_LOGGER.warning("Album %s not found", self._album_id)
|
_LOGGER.warning("Album %s not found", self._album_id)
|
||||||
|
# Fire album_deleted event if we had previous state (album was deleted)
|
||||||
|
if self._previous_state:
|
||||||
|
event_data = {
|
||||||
|
ATTR_HUB_NAME: self._hub_name,
|
||||||
|
ATTR_ALBUM_ID: self._album_id,
|
||||||
|
ATTR_ALBUM_NAME: self._previous_state.name,
|
||||||
|
}
|
||||||
|
self.hass.bus.async_fire(EVENT_ALBUM_DELETED, event_data)
|
||||||
|
_LOGGER.info("Album '%s' was deleted", self._previous_state.name)
|
||||||
return None
|
return None
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
raise UpdateFailed(
|
raise UpdateFailed(
|
||||||
@@ -599,13 +620,23 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]):
|
|||||||
added_ids = new_state.asset_ids - old_state.asset_ids
|
added_ids = new_state.asset_ids - old_state.asset_ids
|
||||||
removed_ids = old_state.asset_ids - new_state.asset_ids
|
removed_ids = old_state.asset_ids - new_state.asset_ids
|
||||||
|
|
||||||
if not added_ids and not removed_ids:
|
# Detect metadata changes
|
||||||
|
name_changed = old_state.name != new_state.name
|
||||||
|
sharing_changed = old_state.shared != new_state.shared
|
||||||
|
|
||||||
|
# Return None only if nothing changed at all
|
||||||
|
if not added_ids and not removed_ids and not name_changed and not sharing_changed:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Determine primary change type
|
||||||
change_type = "changed"
|
change_type = "changed"
|
||||||
if added_ids and not removed_ids:
|
if name_changed and not added_ids and not removed_ids and not sharing_changed:
|
||||||
|
change_type = "album_renamed"
|
||||||
|
elif sharing_changed and not added_ids and not removed_ids and not name_changed:
|
||||||
|
change_type = "album_sharing_changed"
|
||||||
|
elif added_ids and not removed_ids and not name_changed and not sharing_changed:
|
||||||
change_type = "assets_added"
|
change_type = "assets_added"
|
||||||
elif removed_ids and not added_ids:
|
elif removed_ids and not added_ids and not name_changed and not sharing_changed:
|
||||||
change_type = "assets_removed"
|
change_type = "assets_removed"
|
||||||
|
|
||||||
added_assets = [
|
added_assets = [
|
||||||
@@ -620,6 +651,10 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]):
|
|||||||
removed_count=len(removed_ids),
|
removed_count=len(removed_ids),
|
||||||
added_assets=added_assets,
|
added_assets=added_assets,
|
||||||
removed_asset_ids=list(removed_ids),
|
removed_asset_ids=list(removed_ids),
|
||||||
|
old_name=old_state.name if name_changed else None,
|
||||||
|
new_name=new_state.name if name_changed else None,
|
||||||
|
old_shared=old_state.shared if sharing_changed else None,
|
||||||
|
new_shared=new_state.shared if sharing_changed else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _fire_events(self, change: AlbumChange, album: AlbumData) -> None:
|
def _fire_events(self, change: AlbumChange, album: AlbumData) -> None:
|
||||||
@@ -658,8 +693,18 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]):
|
|||||||
ATTR_ADDED_ASSETS: added_assets_detail,
|
ATTR_ADDED_ASSETS: added_assets_detail,
|
||||||
ATTR_REMOVED_ASSETS: change.removed_asset_ids,
|
ATTR_REMOVED_ASSETS: change.removed_asset_ids,
|
||||||
ATTR_PEOPLE: list(album.people),
|
ATTR_PEOPLE: list(album.people),
|
||||||
|
ATTR_SHARED: album.shared,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add metadata change attributes if applicable
|
||||||
|
if change.old_name is not None:
|
||||||
|
event_data[ATTR_OLD_NAME] = change.old_name
|
||||||
|
event_data[ATTR_NEW_NAME] = change.new_name
|
||||||
|
|
||||||
|
if change.old_shared is not None:
|
||||||
|
event_data[ATTR_OLD_SHARED] = change.old_shared
|
||||||
|
event_data[ATTR_NEW_SHARED] = change.new_shared
|
||||||
|
|
||||||
album_url = self.get_any_url()
|
album_url = self.get_any_url()
|
||||||
if album_url:
|
if album_url:
|
||||||
event_data[ATTR_ALBUM_URL] = album_url
|
event_data[ATTR_ALBUM_URL] = album_url
|
||||||
@@ -679,6 +724,24 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]):
|
|||||||
if change.removed_count > 0:
|
if change.removed_count > 0:
|
||||||
self.hass.bus.async_fire(EVENT_ASSETS_REMOVED, event_data)
|
self.hass.bus.async_fire(EVENT_ASSETS_REMOVED, event_data)
|
||||||
|
|
||||||
|
# Fire specific events for metadata changes
|
||||||
|
if change.old_name is not None:
|
||||||
|
self.hass.bus.async_fire(EVENT_ALBUM_RENAMED, event_data)
|
||||||
|
_LOGGER.info(
|
||||||
|
"Album renamed: '%s' -> '%s'",
|
||||||
|
change.old_name,
|
||||||
|
change.new_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
if change.old_shared is not None:
|
||||||
|
self.hass.bus.async_fire(EVENT_ALBUM_SHARING_CHANGED, event_data)
|
||||||
|
_LOGGER.info(
|
||||||
|
"Album '%s' sharing changed: %s -> %s",
|
||||||
|
change.album_name,
|
||||||
|
change.old_shared,
|
||||||
|
change.new_shared,
|
||||||
|
)
|
||||||
|
|
||||||
def get_protected_link_id(self) -> str | None:
|
def get_protected_link_id(self) -> str | None:
|
||||||
"""Get the ID of the first protected link."""
|
"""Get the ID of the first protected link."""
|
||||||
protected_links = self._get_protected_links()
|
protected_links = self._get_protected_links()
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ async def async_setup_entry(
|
|||||||
vol.Optional("caption"): str,
|
vol.Optional("caption"): str,
|
||||||
vol.Optional("reply_to_message_id"): vol.Coerce(int),
|
vol.Optional("reply_to_message_id"): vol.Coerce(int),
|
||||||
vol.Optional("disable_web_page_preview"): bool,
|
vol.Optional("disable_web_page_preview"): bool,
|
||||||
|
vol.Optional("parse_mode", default="HTML"): str,
|
||||||
vol.Optional("max_group_size", default=10): vol.All(
|
vol.Optional("max_group_size", default=10): vol.All(
|
||||||
vol.Coerce(int), vol.Range(min=2, max=10)
|
vol.Coerce(int), vol.Range(min=2, max=10)
|
||||||
),
|
),
|
||||||
@@ -182,6 +183,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
caption: str | None = None,
|
caption: str | None = None,
|
||||||
reply_to_message_id: int | None = None,
|
reply_to_message_id: int | None = None,
|
||||||
disable_web_page_preview: bool | None = None,
|
disable_web_page_preview: bool | None = None,
|
||||||
|
parse_mode: str = "HTML",
|
||||||
max_group_size: int = 10,
|
max_group_size: int = 10,
|
||||||
chunk_delay: int = 0,
|
chunk_delay: int = 0,
|
||||||
) -> ServiceResponse:
|
) -> ServiceResponse:
|
||||||
@@ -214,24 +216,24 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
# Handle empty URLs - send simple text message
|
# Handle empty URLs - send simple text message
|
||||||
if not urls:
|
if not urls:
|
||||||
return await self._send_telegram_message(
|
return await self._send_telegram_message(
|
||||||
session, token, chat_id, caption or "", reply_to_message_id, disable_web_page_preview
|
session, token, chat_id, caption or "", reply_to_message_id, disable_web_page_preview, parse_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle single photo
|
# Handle single photo
|
||||||
if len(urls) == 1 and urls[0].get("type", "photo") == "photo":
|
if len(urls) == 1 and urls[0].get("type", "photo") == "photo":
|
||||||
return await self._send_telegram_photo(
|
return await self._send_telegram_photo(
|
||||||
session, token, chat_id, urls[0].get("url"), caption, reply_to_message_id
|
session, token, chat_id, urls[0].get("url"), caption, reply_to_message_id, parse_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle single video
|
# Handle single video
|
||||||
if len(urls) == 1 and urls[0].get("type") == "video":
|
if len(urls) == 1 and urls[0].get("type") == "video":
|
||||||
return await self._send_telegram_video(
|
return await self._send_telegram_video(
|
||||||
session, token, chat_id, urls[0].get("url"), caption, reply_to_message_id
|
session, token, chat_id, urls[0].get("url"), caption, reply_to_message_id, parse_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle multiple items - send as media group(s)
|
# Handle multiple items - send as media group(s)
|
||||||
return await self._send_telegram_media_group(
|
return await self._send_telegram_media_group(
|
||||||
session, token, chat_id, urls, caption, reply_to_message_id, max_group_size, chunk_delay
|
session, token, chat_id, urls, caption, reply_to_message_id, max_group_size, chunk_delay, parse_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _send_telegram_message(
|
async def _send_telegram_message(
|
||||||
@@ -242,6 +244,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
text: str,
|
text: str,
|
||||||
reply_to_message_id: int | None = None,
|
reply_to_message_id: int | None = None,
|
||||||
disable_web_page_preview: bool | None = None,
|
disable_web_page_preview: bool | None = None,
|
||||||
|
parse_mode: str = "HTML",
|
||||||
) -> ServiceResponse:
|
) -> ServiceResponse:
|
||||||
"""Send a simple text message to Telegram."""
|
"""Send a simple text message to Telegram."""
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -251,6 +254,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
payload: dict[str, Any] = {
|
payload: dict[str, Any] = {
|
||||||
"chat_id": chat_id,
|
"chat_id": chat_id,
|
||||||
"text": text or "Notification from Home Assistant",
|
"text": text or "Notification from Home Assistant",
|
||||||
|
"parse_mode": parse_mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
if reply_to_message_id:
|
if reply_to_message_id:
|
||||||
@@ -288,6 +292,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
url: str | None,
|
url: str | None,
|
||||||
caption: str | None = None,
|
caption: str | None = None,
|
||||||
reply_to_message_id: int | None = None,
|
reply_to_message_id: int | None = None,
|
||||||
|
parse_mode: str = "HTML",
|
||||||
) -> ServiceResponse:
|
) -> ServiceResponse:
|
||||||
"""Send a single photo to Telegram."""
|
"""Send a single photo to Telegram."""
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -312,6 +317,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
form = FormData()
|
form = FormData()
|
||||||
form.add_field("chat_id", chat_id)
|
form.add_field("chat_id", chat_id)
|
||||||
form.add_field("photo", data, filename="photo.jpg", content_type="image/jpeg")
|
form.add_field("photo", data, filename="photo.jpg", content_type="image/jpeg")
|
||||||
|
form.add_field("parse_mode", parse_mode)
|
||||||
|
|
||||||
if caption:
|
if caption:
|
||||||
form.add_field("caption", caption)
|
form.add_field("caption", caption)
|
||||||
@@ -350,6 +356,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
url: str | None,
|
url: str | None,
|
||||||
caption: str | None = None,
|
caption: str | None = None,
|
||||||
reply_to_message_id: int | None = None,
|
reply_to_message_id: int | None = None,
|
||||||
|
parse_mode: str = "HTML",
|
||||||
) -> ServiceResponse:
|
) -> ServiceResponse:
|
||||||
"""Send a single video to Telegram."""
|
"""Send a single video to Telegram."""
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -374,6 +381,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
form = FormData()
|
form = FormData()
|
||||||
form.add_field("chat_id", chat_id)
|
form.add_field("chat_id", chat_id)
|
||||||
form.add_field("video", data, filename="video.mp4", content_type="video/mp4")
|
form.add_field("video", data, filename="video.mp4", content_type="video/mp4")
|
||||||
|
form.add_field("parse_mode", parse_mode)
|
||||||
|
|
||||||
if caption:
|
if caption:
|
||||||
form.add_field("caption", caption)
|
form.add_field("caption", caption)
|
||||||
@@ -414,6 +422,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
reply_to_message_id: int | None = None,
|
reply_to_message_id: int | None = None,
|
||||||
max_group_size: int = 10,
|
max_group_size: int = 10,
|
||||||
chunk_delay: int = 0,
|
chunk_delay: int = 0,
|
||||||
|
parse_mode: str = "HTML",
|
||||||
) -> ServiceResponse:
|
) -> ServiceResponse:
|
||||||
"""Send media URLs to Telegram as media group(s).
|
"""Send media URLs to Telegram as media group(s).
|
||||||
|
|
||||||
@@ -454,12 +463,12 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
if media_type == "photo":
|
if media_type == "photo":
|
||||||
_LOGGER.debug("Sending chunk %d/%d as single photo", chunk_idx + 1, len(chunks))
|
_LOGGER.debug("Sending chunk %d/%d as single photo", chunk_idx + 1, len(chunks))
|
||||||
result = await self._send_telegram_photo(
|
result = await self._send_telegram_photo(
|
||||||
session, token, chat_id, url, chunk_caption, chunk_reply_to
|
session, token, chat_id, url, chunk_caption, chunk_reply_to, parse_mode
|
||||||
)
|
)
|
||||||
else: # video
|
else: # video
|
||||||
_LOGGER.debug("Sending chunk %d/%d as single video", chunk_idx + 1, len(chunks))
|
_LOGGER.debug("Sending chunk %d/%d as single video", chunk_idx + 1, len(chunks))
|
||||||
result = await self._send_telegram_video(
|
result = await self._send_telegram_video(
|
||||||
session, token, chat_id, url, chunk_caption, chunk_reply_to
|
session, token, chat_id, url, chunk_caption, chunk_reply_to, parse_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
if not result.get("success"):
|
if not result.get("success"):
|
||||||
@@ -527,6 +536,7 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
# Only add caption to the first item of the first chunk
|
# Only add caption to the first item of the first chunk
|
||||||
if chunk_idx == 0 and i == 0 and caption:
|
if chunk_idx == 0 and i == 0 and caption:
|
||||||
media_item["caption"] = caption
|
media_item["caption"] = caption
|
||||||
|
media_item["parse_mode"] = parse_mode
|
||||||
media_json.append(media_item)
|
media_json.append(media_item)
|
||||||
|
|
||||||
content_type = "image/jpeg" if media_type == "photo" else "video/mp4"
|
content_type = "image/jpeg" if media_type == "photo" else "video/mp4"
|
||||||
|
|||||||
@@ -71,6 +71,22 @@ send_telegram_notification:
|
|||||||
required: false
|
required: false
|
||||||
selector:
|
selector:
|
||||||
boolean:
|
boolean:
|
||||||
|
parse_mode:
|
||||||
|
name: Parse Mode
|
||||||
|
description: How to parse the caption/text. Options are "HTML", "Markdown", "MarkdownV2", or empty string for plain text.
|
||||||
|
required: false
|
||||||
|
default: "HTML"
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
options:
|
||||||
|
- label: "HTML"
|
||||||
|
value: "HTML"
|
||||||
|
- label: "Markdown"
|
||||||
|
value: "Markdown"
|
||||||
|
- label: "MarkdownV2"
|
||||||
|
value: "MarkdownV2"
|
||||||
|
- label: "Plain Text"
|
||||||
|
value: ""
|
||||||
max_group_size:
|
max_group_size:
|
||||||
name: 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.
|
description: Maximum number of media items per media group (2-10). Large lists will be split into multiple groups.
|
||||||
|
|||||||
@@ -171,6 +171,10 @@
|
|||||||
"name": "Disable Web Page Preview",
|
"name": "Disable Web Page Preview",
|
||||||
"description": "Disable link previews in text messages."
|
"description": "Disable link previews in text messages."
|
||||||
},
|
},
|
||||||
|
"parse_mode": {
|
||||||
|
"name": "Parse Mode",
|
||||||
|
"description": "How to parse the caption/text. Options are HTML, Markdown, MarkdownV2, or empty string for plain text."
|
||||||
|
},
|
||||||
"max_group_size": {
|
"max_group_size": {
|
||||||
"name": "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."
|
"description": "Maximum number of media items per media group (2-10). Large lists will be split into multiple groups."
|
||||||
|
|||||||
@@ -171,6 +171,10 @@
|
|||||||
"name": "Отключить предпросмотр ссылок",
|
"name": "Отключить предпросмотр ссылок",
|
||||||
"description": "Отключить предпросмотр ссылок в текстовых сообщениях."
|
"description": "Отключить предпросмотр ссылок в текстовых сообщениях."
|
||||||
},
|
},
|
||||||
|
"parse_mode": {
|
||||||
|
"name": "Режим парсинга",
|
||||||
|
"description": "Как парсить подпись/текст. Варианты: HTML, Markdown, MarkdownV2, или пустая строка для обычного текста."
|
||||||
|
},
|
||||||
"max_group_size": {
|
"max_group_size": {
|
||||||
"name": "Макс. размер группы",
|
"name": "Макс. размер группы",
|
||||||
"description": "Максимальное количество медиа-файлов в одной группе (2-10). Большие списки будут разделены на несколько групп."
|
"description": "Максимальное количество медиа-файлов в одной группе (2-10). Большие списки будут разделены на несколько групп."
|
||||||
|
|||||||
Reference in New Issue
Block a user