Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02c0535f50 | |||
| c570e157be | |||
| 56d249b598 | |||
| ebed587f6f | |||
| a89d45268d | |||
| 950fe0fd91 | |||
| 91c30e086d | |||
| 6f39a8175d |
17
.github/workflows/validate.yaml
vendored
Normal file
17
.github/workflows/validate.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Validate
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
hassfest:
|
||||||
|
name: Hassfest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: home-assistant/actions/hassfest@master
|
||||||
|
if: github.server_url == 'https://github.com'
|
||||||
178
README.md
178
README.md
@@ -1,8 +1,42 @@
|
|||||||
# Immich Album Watcher
|
# Immich Album Watcher
|
||||||
|
|
||||||
A custom Home Assistant integration to monitor Immich albums for changes with sensors, events, and face recognition.
|
<img src="custom_components/immich_album_watcher/icon.png" alt="Immich" width="64" height="64">
|
||||||
|
|
||||||
For detailed documentation, see the [integration README](custom_components/immich_album_watcher/README.md).
|
A Home Assistant custom integration that monitors [Immich](https://immich.app/) photo/video library albums for changes and exposes them as Home Assistant entities with event-firing capabilities.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Album Monitoring** - Watch selected Immich albums for asset additions and removals
|
||||||
|
- **Rich Sensor Data** - Multiple sensors per album:
|
||||||
|
- Album ID (with share URL attribute)
|
||||||
|
- Asset count (with detected people list)
|
||||||
|
- Photo count
|
||||||
|
- Video count
|
||||||
|
- Last updated timestamp
|
||||||
|
- Creation date
|
||||||
|
- **Camera Entity** - Album thumbnail displayed as a camera entity for dashboards
|
||||||
|
- **Binary Sensor** - "New Assets" indicator that turns on when assets are added
|
||||||
|
- **Face Recognition** - Detects and lists people recognized in album photos
|
||||||
|
- **Event Firing** - Fires Home Assistant events when albums change:
|
||||||
|
- `immich_album_watcher_album_changed` - General album changes
|
||||||
|
- `immich_album_watcher_assets_added` - When new assets are added
|
||||||
|
- `immich_album_watcher_assets_removed` - When assets are removed
|
||||||
|
- **Enhanced Event Data** - Events include detailed asset info:
|
||||||
|
- Asset type (photo/video)
|
||||||
|
- Filename
|
||||||
|
- Creation date
|
||||||
|
- Asset owner (who uploaded the asset)
|
||||||
|
- Asset description/caption
|
||||||
|
- Public URL (if album has a shared link)
|
||||||
|
- Detected people in the asset
|
||||||
|
- **Services** - Custom service calls:
|
||||||
|
- `immich_album_watcher.refresh` - Force immediate data refresh
|
||||||
|
- `immich_album_watcher.get_recent_assets` - Get recent assets from an album
|
||||||
|
- **Share Link Management** - Button entities to create and delete share links:
|
||||||
|
- Create/delete public (unprotected) share links
|
||||||
|
- Create/delete password-protected share links
|
||||||
|
- Edit protected link passwords via Text entity
|
||||||
|
- **Configurable Polling** - Adjustable scan interval (10-3600 seconds)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -18,8 +52,6 @@ For detailed documentation, see the [integration README](custom_components/immic
|
|||||||
8. Restart Home Assistant
|
8. Restart Home Assistant
|
||||||
9. Add the integration via **Settings** → **Devices & Services** → **Add Integration**
|
9. Add the integration via **Settings** → **Devices & Services** → **Add Integration**
|
||||||
|
|
||||||
> **Tip:** For the best experience, use this integration with the [Immich Album Watcher Blueprint](https://github.com/DolgolyovAlexei/haos-blueprints/blob/main/Common/Immich%20Album%20Watcher.yaml) to easily create automations for album change notifications.
|
|
||||||
|
|
||||||
### Manual Installation
|
### Manual Installation
|
||||||
|
|
||||||
1. Download or clone this repository
|
1. Download or clone this repository
|
||||||
@@ -27,6 +59,144 @@ For detailed documentation, see the [integration README](custom_components/immic
|
|||||||
3. Restart Home Assistant
|
3. Restart Home Assistant
|
||||||
4. Add the integration via **Settings** → **Devices & Services** → **Add Integration**
|
4. Add the integration via **Settings** → **Devices & Services** → **Add Integration**
|
||||||
|
|
||||||
|
> **Tip:** For the best experience, use this integration with the [Immich Album Watcher Blueprint](https://github.com/DolgolyovAlexei/haos-blueprints/blob/main/Common/Immich%20Album%20Watcher.yaml) to easily create automations for album change notifications.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Option | Description | Default |
|
||||||
|
|--------|-------------|---------|
|
||||||
|
| Server URL | Your Immich server URL (e.g., `https://immich.example.com`) | Required |
|
||||||
|
| API Key | Your Immich API key | Required |
|
||||||
|
| Albums | Albums to monitor | Required |
|
||||||
|
| Scan Interval | How often to check for changes (seconds) | 60 |
|
||||||
|
|
||||||
|
## Entities Created (per album)
|
||||||
|
|
||||||
|
| Entity Type | Name | Description |
|
||||||
|
|-------------|------|-------------|
|
||||||
|
| Sensor | Album ID | Album identifier with `album_name` and `share_url` attributes |
|
||||||
|
| Sensor | Asset Count | Total number of assets (includes `people` list in attributes) |
|
||||||
|
| Sensor | Photo Count | Number of photos in the album |
|
||||||
|
| Sensor | Video Count | Number of videos in the album |
|
||||||
|
| Sensor | Last Updated | When the album was last modified |
|
||||||
|
| Sensor | Created | When the album was created |
|
||||||
|
| Sensor | Public URL | Public share link URL (accessible links without password) |
|
||||||
|
| Sensor | Protected URL | Password-protected share link URL (if any exist) |
|
||||||
|
| Sensor | Protected Password | Password for the protected share link (read-only) |
|
||||||
|
| Binary Sensor | New Assets | On when new assets were recently added |
|
||||||
|
| Camera | Thumbnail | Album cover image |
|
||||||
|
| Text | Protected Password | Editable password for the protected share link |
|
||||||
|
| Button | Create Share Link | Creates an unprotected public share link |
|
||||||
|
| Button | Delete Share Link | Deletes the unprotected public share link |
|
||||||
|
| Button | Create Protected Link | Creates a password-protected share link |
|
||||||
|
| Button | Delete Protected Link | Deletes the password-protected share link |
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
### Refresh
|
||||||
|
|
||||||
|
Force an immediate refresh of all album data:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: immich_album_watcher.refresh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Recent Assets
|
||||||
|
|
||||||
|
Get the most recent assets from a specific album (returns response data):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: immich_album_watcher.get_recent_assets
|
||||||
|
data:
|
||||||
|
album_id: "your-album-id-here"
|
||||||
|
count: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
Use these events in your automations:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "New photos added to album"
|
||||||
|
trigger:
|
||||||
|
- platform: event
|
||||||
|
event_type: immich_album_watcher_assets_added
|
||||||
|
action:
|
||||||
|
- service: notify.mobile_app
|
||||||
|
data:
|
||||||
|
title: "New Photos"
|
||||||
|
message: "{{ trigger.event.data.added_count }} new photos in {{ trigger.event.data.album_name }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event Data
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `album_id` | Album ID |
|
||||||
|
| `album_name` | Album name |
|
||||||
|
| `album_url` | Public URL to view the album (only present if album has a shared link) |
|
||||||
|
| `change_type` | Type of change (assets_added, assets_removed, changed) |
|
||||||
|
| `added_count` | Number of assets added |
|
||||||
|
| `removed_count` | Number of assets removed |
|
||||||
|
| `added_assets` | List of added assets with details (see below) |
|
||||||
|
| `removed_assets` | List of removed asset IDs |
|
||||||
|
| `people` | List of all people detected in the album |
|
||||||
|
|
||||||
|
### Added Assets Fields
|
||||||
|
|
||||||
|
Each item in the `added_assets` list contains the following fields:
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `id` | Unique asset ID |
|
||||||
|
| `asset_type` | Type of asset (`IMAGE` or `VIDEO`) |
|
||||||
|
| `asset_filename` | Original filename of the asset |
|
||||||
|
| `asset_created` | Date/time when the asset was originally created |
|
||||||
|
| `asset_owner` | Display name of the user who owns the asset |
|
||||||
|
| `asset_owner_id` | Unique ID of the user who owns the asset |
|
||||||
|
| `asset_description` | Description/caption of the asset (from EXIF data) |
|
||||||
|
| `asset_url` | Public URL to view the asset (only present if album has a shared link) |
|
||||||
|
| `people` | List of people detected in this specific asset |
|
||||||
|
|
||||||
|
Example accessing asset owner in an automation:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "Notify when someone adds photos"
|
||||||
|
trigger:
|
||||||
|
- platform: event
|
||||||
|
event_type: immich_album_watcher_assets_added
|
||||||
|
action:
|
||||||
|
- service: notify.mobile_app
|
||||||
|
data:
|
||||||
|
title: "New Photos"
|
||||||
|
message: >
|
||||||
|
{{ trigger.event.data.added_assets[0].asset_owner }} added
|
||||||
|
{{ trigger.event.data.added_count }} photos to {{ trigger.event.data.album_name }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Home Assistant 2024.1.0 or newer
|
||||||
|
- Immich server with API access
|
||||||
|
- Valid Immich API key with the following permissions:
|
||||||
|
|
||||||
|
### Required API Permissions
|
||||||
|
|
||||||
|
| Permission | Required | Description |
|
||||||
|
| ---------- | -------- | ----------- |
|
||||||
|
| `album.read` | Yes | Read album data and asset lists |
|
||||||
|
| `asset.read` | Yes | Read asset details (type, filename, creation date) |
|
||||||
|
| `user.read` | Yes | Resolve asset owner names |
|
||||||
|
| `person.read` | Yes | Read face recognition / people data |
|
||||||
|
| `sharedLink.read` | Yes | Read shared links for public/protected URL sensors |
|
||||||
|
| `sharedLink.create` | Optional | Create share links via the Button entities |
|
||||||
|
| `sharedLink.edit` | Optional | Edit shared link passwords via the Text entity |
|
||||||
|
| `sharedLink.delete` | Optional | Delete share links via the Button entities |
|
||||||
|
|
||||||
|
> **Note:** Without optional permissions, the corresponding entities will be unavailable or non-functional.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit issues or pull requests.
|
Contributions are welcome! Please feel free to submit issues or pull requests.
|
||||||
|
|||||||
@@ -1,180 +0,0 @@
|
|||||||
# Immich Album Watcher
|
|
||||||
|
|
||||||
<img src="icon.png" alt="Immich" width="64" height="64">
|
|
||||||
|
|
||||||
A Home Assistant custom integration that monitors [Immich](https://immich.app/) photo/video library albums for changes and exposes them as Home Assistant entities with event-firing capabilities.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Album Monitoring** - Watch selected Immich albums for asset additions and removals
|
|
||||||
- **Rich Sensor Data** - Multiple sensors per album:
|
|
||||||
- Asset count (total)
|
|
||||||
- Photo count
|
|
||||||
- Video count
|
|
||||||
- People count (detected faces)
|
|
||||||
- Last updated timestamp
|
|
||||||
- Creation date
|
|
||||||
- **Camera Entity** - Album thumbnail displayed as a camera entity for dashboards
|
|
||||||
- **Binary Sensor** - "New Assets" indicator that turns on when assets are added
|
|
||||||
- **Face Recognition** - Detects and lists people recognized in album photos
|
|
||||||
- **Event Firing** - Fires Home Assistant events when albums change:
|
|
||||||
- `immich_album_watcher_album_changed` - General album changes
|
|
||||||
- `immich_album_watcher_assets_added` - When new assets are added
|
|
||||||
- `immich_album_watcher_assets_removed` - When assets are removed
|
|
||||||
- **Enhanced Event Data** - Events include detailed asset info:
|
|
||||||
- Asset type (photo/video)
|
|
||||||
- Filename
|
|
||||||
- Creation date
|
|
||||||
- Asset owner (who uploaded the asset)
|
|
||||||
- Asset description/caption
|
|
||||||
- Public URL (if album has a shared link)
|
|
||||||
- Detected people in the asset
|
|
||||||
- **Services** - Custom service calls:
|
|
||||||
- `immich_album_watcher.refresh` - Force immediate data refresh
|
|
||||||
- `immich_album_watcher.get_recent_assets` - Get recent assets from an album
|
|
||||||
- **Configurable Polling** - Adjustable scan interval (10-3600 seconds)
|
|
||||||
|
|
||||||
## Entities Created (per album)
|
|
||||||
|
|
||||||
| Entity Type | Name | Description |
|
|
||||||
|-------------|------|-------------|
|
|
||||||
| Sensor | Asset Count | Total number of assets in the album |
|
|
||||||
| Sensor | Photo Count | Number of photos in the album |
|
|
||||||
| Sensor | Video Count | Number of videos in the album |
|
|
||||||
| Sensor | People Count | Number of unique people detected |
|
|
||||||
| Sensor | Last Updated | When the album was last modified |
|
|
||||||
| Sensor | Created | When the album was created |
|
|
||||||
| Sensor | Public URL | Public share link URL (accessible links without password) |
|
|
||||||
| Sensor | Protected URL | Password-protected share link URL (if any exist) |
|
|
||||||
| Sensor | Protected Password | Password for the protected share link (read-only) |
|
|
||||||
| Binary Sensor | New Assets | On when new assets were recently added |
|
|
||||||
| Camera | Thumbnail | Album cover image |
|
|
||||||
| Text | Share Password | Editable password for the protected share link |
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Copy the `immich_album_watcher` folder to your Home Assistant `custom_components` directory
|
|
||||||
2. Restart Home Assistant
|
|
||||||
3. Go to **Settings** → **Devices & Services** → **Add Integration**
|
|
||||||
4. Search for "Immich Album Watcher"
|
|
||||||
5. Enter your Immich server URL and API key
|
|
||||||
6. Select the albums you want to monitor
|
|
||||||
|
|
||||||
> **Tip:** For the best experience, use this integration with the [Immich Album Watcher Blueprint](https://github.com/DolgolyovAlexei/haos-blueprints/blob/main/Common/Immich%20Album%20Watcher.yaml) to easily create automations for album change notifications.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
| Option | Description | Default |
|
|
||||||
|--------|-------------|---------|
|
|
||||||
| Server URL | Your Immich server URL (e.g., `https://immich.example.com`) | Required |
|
|
||||||
| API Key | Your Immich API key | Required |
|
|
||||||
| Albums | Albums to monitor | Required |
|
|
||||||
| Scan Interval | How often to check for changes (seconds) | 60 |
|
|
||||||
|
|
||||||
## Services
|
|
||||||
|
|
||||||
### Refresh
|
|
||||||
|
|
||||||
Force an immediate refresh of all album data:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
service: immich_album_watcher.refresh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Recent Assets
|
|
||||||
|
|
||||||
Get the most recent assets from a specific album (returns response data):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
service: immich_album_watcher.get_recent_assets
|
|
||||||
data:
|
|
||||||
album_id: "your-album-id-here"
|
|
||||||
count: 10
|
|
||||||
```
|
|
||||||
|
|
||||||
## Events
|
|
||||||
|
|
||||||
Use these events in your automations:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
automation:
|
|
||||||
- alias: "New photos added to album"
|
|
||||||
trigger:
|
|
||||||
- platform: event
|
|
||||||
event_type: immich_album_watcher_assets_added
|
|
||||||
action:
|
|
||||||
- service: notify.mobile_app
|
|
||||||
data:
|
|
||||||
title: "New Photos"
|
|
||||||
message: "{{ trigger.event.data.added_count }} new photos in {{ trigger.event.data.album_name }}"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Event Data
|
|
||||||
|
|
||||||
| Field | Description |
|
|
||||||
|-------|-------------|
|
|
||||||
| `album_id` | Album ID |
|
|
||||||
| `album_name` | Album name |
|
|
||||||
| `album_url` | Public URL to view the album (only present if album has a shared link) |
|
|
||||||
| `change_type` | Type of change (assets_added, assets_removed, changed) |
|
|
||||||
| `added_count` | Number of assets added |
|
|
||||||
| `removed_count` | Number of assets removed |
|
|
||||||
| `added_assets` | List of added assets with details (see below) |
|
|
||||||
| `removed_assets` | List of removed asset IDs |
|
|
||||||
| `people` | List of all people detected in the album |
|
|
||||||
|
|
||||||
### Added Assets Fields
|
|
||||||
|
|
||||||
Each item in the `added_assets` list contains the following fields:
|
|
||||||
|
|
||||||
| Field | Description |
|
|
||||||
|-------|-------------|
|
|
||||||
| `id` | Unique asset ID |
|
|
||||||
| `asset_type` | Type of asset (`IMAGE` or `VIDEO`) |
|
|
||||||
| `asset_filename` | Original filename of the asset |
|
|
||||||
| `asset_created` | Date/time when the asset was originally created |
|
|
||||||
| `asset_owner` | Display name of the user who owns the asset |
|
|
||||||
| `asset_owner_id` | Unique ID of the user who owns the asset |
|
|
||||||
| `asset_description` | Description/caption of the asset (from EXIF data) |
|
|
||||||
| `asset_url` | Public URL to view the asset (only present if album has a shared link) |
|
|
||||||
| `people` | List of people detected in this specific asset |
|
|
||||||
|
|
||||||
Example accessing asset owner in an automation:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
automation:
|
|
||||||
- alias: "Notify when someone adds photos"
|
|
||||||
trigger:
|
|
||||||
- platform: event
|
|
||||||
event_type: immich_album_watcher_assets_added
|
|
||||||
action:
|
|
||||||
- service: notify.mobile_app
|
|
||||||
data:
|
|
||||||
title: "New Photos"
|
|
||||||
message: >
|
|
||||||
{{ trigger.event.data.added_assets[0].asset_owner }} added
|
|
||||||
{{ trigger.event.data.added_count }} photos to {{ trigger.event.data.album_name }}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- Home Assistant 2024.1.0 or newer
|
|
||||||
- Immich server with API access
|
|
||||||
- Valid Immich API key with the following permissions:
|
|
||||||
|
|
||||||
### Required API Permissions
|
|
||||||
|
|
||||||
| Permission | Required | Description |
|
|
||||||
| ---------- | -------- | ----------- |
|
|
||||||
| `album.read` | Yes | Read album data and asset lists |
|
|
||||||
| `asset.read` | Yes | Read asset details (type, filename, creation date) |
|
|
||||||
| `user.read` | Yes | Resolve asset owner names |
|
|
||||||
| `person.read` | Yes | Read face recognition / people data |
|
|
||||||
| `sharedLink.read` | Yes | Read shared links for public/protected URL sensors |
|
|
||||||
| `sharedLink.edit` | Optional | Edit shared link passwords via the Text entity |
|
|
||||||
|
|
||||||
> **Note:** If you don't grant `sharedLink.edit` permission, the "Share Password" text entity will not be able to update passwords but will still display the current password.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License - see the [LICENSE](../LICENSE) file for details.
|
|
||||||
@@ -137,7 +137,6 @@ class ImmichAlbumNewAssetsSensor(
|
|||||||
name=self._album_name,
|
name=self._album_name,
|
||||||
manufacturer="Immich",
|
manufacturer="Immich",
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
via_device=(DOMAIN, self._entry.entry_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ class ImmichCreateShareLinkButton(
|
|||||||
name=self._album_name,
|
name=self._album_name,
|
||||||
manufacturer="Immich",
|
manufacturer="Immich",
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
via_device=(DOMAIN, self._entry.entry_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -200,7 +199,6 @@ class ImmichDeleteShareLinkButton(
|
|||||||
name=self._album_name,
|
name=self._album_name,
|
||||||
manufacturer="Immich",
|
manufacturer="Immich",
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
via_device=(DOMAIN, self._entry.entry_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -298,7 +296,6 @@ class ImmichCreateProtectedLinkButton(
|
|||||||
name=self._album_name,
|
name=self._album_name,
|
||||||
manufacturer="Immich",
|
manufacturer="Immich",
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
via_device=(DOMAIN, self._entry.entry_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -393,7 +390,6 @@ class ImmichDeleteProtectedLinkButton(
|
|||||||
name=self._album_name,
|
name=self._album_name,
|
||||||
manufacturer="Immich",
|
manufacturer="Immich",
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
via_device=(DOMAIN, self._entry.entry_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ class ImmichAlbumThumbnailCamera(
|
|||||||
name=self._album_name,
|
name=self._album_name,
|
||||||
manufacturer="Immich",
|
manufacturer="Immich",
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
via_device=(DOMAIN, self._entry.entry_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from .const import (
|
|||||||
CONF_HUB_NAME,
|
CONF_HUB_NAME,
|
||||||
CONF_IMMICH_URL,
|
CONF_IMMICH_URL,
|
||||||
CONF_SCAN_INTERVAL,
|
CONF_SCAN_INTERVAL,
|
||||||
|
CONF_TELEGRAM_BOT_TOKEN,
|
||||||
DEFAULT_SCAN_INTERVAL,
|
DEFAULT_SCAN_INTERVAL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SUBENTRY_TYPE_ALBUM,
|
SUBENTRY_TYPE_ALBUM,
|
||||||
@@ -248,12 +249,18 @@ class ImmichAlbumWatcherOptionsFlow(OptionsFlow):
|
|||||||
CONF_SCAN_INTERVAL: user_input.get(
|
CONF_SCAN_INTERVAL: user_input.get(
|
||||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
||||||
),
|
),
|
||||||
|
CONF_TELEGRAM_BOT_TOKEN: user_input.get(
|
||||||
|
CONF_TELEGRAM_BOT_TOKEN, ""
|
||||||
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
current_interval = self._config_entry.options.get(
|
current_interval = self._config_entry.options.get(
|
||||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
||||||
)
|
)
|
||||||
|
current_bot_token = self._config_entry.options.get(
|
||||||
|
CONF_TELEGRAM_BOT_TOKEN, ""
|
||||||
|
)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="init",
|
step_id="init",
|
||||||
@@ -262,6 +269,9 @@ class ImmichAlbumWatcherOptionsFlow(OptionsFlow):
|
|||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_SCAN_INTERVAL, default=current_interval
|
CONF_SCAN_INTERVAL, default=current_interval
|
||||||
): vol.All(vol.Coerce(int), vol.Range(min=10, max=3600)),
|
): vol.All(vol.Coerce(int), vol.Range(min=10, max=3600)),
|
||||||
|
vol.Optional(
|
||||||
|
CONF_TELEGRAM_BOT_TOKEN, default=current_bot_token
|
||||||
|
): str,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ CONF_ALBUMS: Final = "albums"
|
|||||||
CONF_ALBUM_ID: Final = "album_id"
|
CONF_ALBUM_ID: Final = "album_id"
|
||||||
CONF_ALBUM_NAME: Final = "album_name"
|
CONF_ALBUM_NAME: Final = "album_name"
|
||||||
CONF_SCAN_INTERVAL: Final = "scan_interval"
|
CONF_SCAN_INTERVAL: Final = "scan_interval"
|
||||||
|
CONF_TELEGRAM_BOT_TOKEN: Final = "telegram_bot_token"
|
||||||
|
|
||||||
# Subentry type
|
# Subentry type
|
||||||
SUBENTRY_TYPE_ALBUM: Final = "album"
|
SUBENTRY_TYPE_ALBUM: Final = "album"
|
||||||
@@ -69,3 +70,4 @@ PLATFORMS: Final = ["sensor", "binary_sensor", "camera", "text", "button"]
|
|||||||
# Services
|
# Services
|
||||||
SERVICE_REFRESH: Final = "refresh"
|
SERVICE_REFRESH: Final = "refresh"
|
||||||
SERVICE_GET_RECENT_ASSETS: Final = "get_recent_assets"
|
SERVICE_GET_RECENT_ASSETS: Final = "get_recent_assets"
|
||||||
|
SERVICE_SEND_TELEGRAM_MEDIA_GROUP: Final = "send_telegram_media_group"
|
||||||
|
|||||||
@@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"issue_tracker": "https://github.com/DolgolyovAlexei/haos-hacs-immich-album-watcher/issues",
|
"issue_tracker": "https://github.com/DolgolyovAlexei/haos-hacs-immich-album-watcher/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"version": "1.3.0"
|
"version": "1.4.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,11 @@ from .const import (
|
|||||||
CONF_ALBUM_ID,
|
CONF_ALBUM_ID,
|
||||||
CONF_ALBUM_NAME,
|
CONF_ALBUM_NAME,
|
||||||
CONF_HUB_NAME,
|
CONF_HUB_NAME,
|
||||||
|
CONF_TELEGRAM_BOT_TOKEN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_GET_RECENT_ASSETS,
|
SERVICE_GET_RECENT_ASSETS,
|
||||||
SERVICE_REFRESH,
|
SERVICE_REFRESH,
|
||||||
|
SERVICE_SEND_TELEGRAM_MEDIA_GROUP,
|
||||||
)
|
)
|
||||||
from .coordinator import AlbumData, ImmichAlbumWatcherCoordinator
|
from .coordinator import AlbumData, ImmichAlbumWatcherCoordinator
|
||||||
|
|
||||||
@@ -96,6 +98,19 @@ async def async_setup_entry(
|
|||||||
supports_response=SupportsResponse.ONLY,
|
supports_response=SupportsResponse.ONLY,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
platform.async_register_entity_service(
|
||||||
|
SERVICE_SEND_TELEGRAM_MEDIA_GROUP,
|
||||||
|
{
|
||||||
|
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("caption"): str,
|
||||||
|
vol.Optional("reply_to_message_id"): vol.Coerce(int),
|
||||||
|
},
|
||||||
|
"async_send_telegram_media_group",
|
||||||
|
supports_response=SupportsResponse.ONLY,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], SensorEntity):
|
class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], SensorEntity):
|
||||||
"""Base sensor for Immich album."""
|
"""Base sensor for Immich album."""
|
||||||
@@ -143,7 +158,6 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
name=self._album_name,
|
name=self._album_name,
|
||||||
manufacturer="Immich",
|
manufacturer="Immich",
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
via_device=(DOMAIN, self._entry.entry_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@@ -160,6 +174,121 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se
|
|||||||
assets = await self.coordinator.async_get_recent_assets(count)
|
assets = await self.coordinator.async_get_recent_assets(count)
|
||||||
return {"assets": assets}
|
return {"assets": assets}
|
||||||
|
|
||||||
|
async def async_send_telegram_media_group(
|
||||||
|
self,
|
||||||
|
chat_id: str,
|
||||||
|
urls: list[dict[str, str]],
|
||||||
|
bot_token: str | None = None,
|
||||||
|
caption: str | None = None,
|
||||||
|
reply_to_message_id: int | None = None,
|
||||||
|
) -> ServiceResponse:
|
||||||
|
"""Send media URLs to Telegram as a media group.
|
||||||
|
|
||||||
|
Each item in urls should be a dict with 'url' and 'type' (photo/video).
|
||||||
|
Downloads media and uploads to Telegram to bypass CORS restrictions.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import aiohttp
|
||||||
|
from aiohttp import FormData
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
# Get bot token from parameter or config
|
||||||
|
token = bot_token or self._entry.options.get(CONF_TELEGRAM_BOT_TOKEN)
|
||||||
|
if not token:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "No bot token provided. Set it in integration options or pass as parameter.",
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
if not url:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Missing 'url' in item {i}",
|
||||||
|
}
|
||||||
|
|
||||||
|
if media_type not in ("photo", "video"):
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Invalid type '{media_type}' in item {i}. Must be 'photo' or 'video'.",
|
||||||
|
}
|
||||||
|
|
||||||
|
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}",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build multipart form
|
||||||
|
form = FormData()
|
||||||
|
form.add_field("chat_id", chat_id)
|
||||||
|
|
||||||
|
if 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}",
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
|
||||||
|
try:
|
||||||
|
_LOGGER.debug("Uploading %d files to Telegram", 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"):
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message_ids": [
|
||||||
|
msg.get("message_id") for msg in result.get("result", [])
|
||||||
|
],
|
||||||
|
}
|
||||||
|
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 upload failed: %s", err)
|
||||||
|
return {"success": False, "error": str(err)}
|
||||||
|
|
||||||
|
|
||||||
class ImmichAlbumIdSensor(ImmichAlbumBaseSensor):
|
class ImmichAlbumIdSensor(ImmichAlbumBaseSensor):
|
||||||
"""Sensor exposing the Immich album ID."""
|
"""Sensor exposing the Immich album ID."""
|
||||||
|
|||||||
@@ -24,3 +24,44 @@ get_recent_assets:
|
|||||||
min: 1
|
min: 1
|
||||||
max: 100
|
max: 100
|
||||||
mode: slider
|
mode: slider
|
||||||
|
|
||||||
|
send_telegram_media_group:
|
||||||
|
name: Send Telegram Media Group
|
||||||
|
description: Send specified media URLs to a Telegram chat as a media group.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
integration: immich_album_watcher
|
||||||
|
domain: sensor
|
||||||
|
fields:
|
||||||
|
bot_token:
|
||||||
|
name: Bot Token
|
||||||
|
description: Telegram bot token. Uses configured token if not provided.
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
chat_id:
|
||||||
|
name: Chat ID
|
||||||
|
description: Telegram chat ID to send to.
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
urls:
|
||||||
|
name: URLs
|
||||||
|
description: List of media URLs to send (max 10). Each item should have 'url' and 'type' (photo/video).
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
caption:
|
||||||
|
name: Caption
|
||||||
|
description: Optional caption for the media group (applied to first item).
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
multiline: true
|
||||||
|
reply_to_message_id:
|
||||||
|
name: Reply To Message ID
|
||||||
|
description: Message ID to reply to.
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
mode: box
|
||||||
|
|||||||
@@ -71,13 +71,15 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
"title": "Immich Album Watcher Options",
|
"title": "Immich Album Watcher Options",
|
||||||
"description": "Configure which albums to monitor and how often to check for changes.",
|
"description": "Configure how often to check for changes and optional Telegram integration.",
|
||||||
"data": {
|
"data": {
|
||||||
"albums": "Albums to watch",
|
"albums": "Albums to watch",
|
||||||
"scan_interval": "Scan interval (seconds)"
|
"scan_interval": "Scan interval (seconds)",
|
||||||
|
"telegram_bot_token": "Telegram Bot Token"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"scan_interval": "How often to check for album changes (10-3600 seconds)"
|
"scan_interval": "How often to check for album changes (10-3600 seconds)",
|
||||||
|
"telegram_bot_token": "Bot token for sending media to Telegram (optional)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ class ImmichAlbumProtectedPasswordText(
|
|||||||
name=self._album_name,
|
name=self._album_name,
|
||||||
manufacturer="Immich",
|
manufacturer="Immich",
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
via_device=(DOMAIN, self._entry.entry_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
Reference in New Issue
Block a user