Skip to content

Commit

Permalink
Add support for albums, fix refresh interval
Browse files Browse the repository at this point in the history
  • Loading branch information
outadoc committed Mar 16, 2024
1 parent 5bf1206 commit bba3e89
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 60 deletions.
5 changes: 1 addition & 4 deletions custom_components/immich/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
from __future__ import annotations

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.const import CONF_HOST, CONF_API_KEY
from homeassistant.const import CONF_API_KEY, CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from datetime import timedelta

from .const import DOMAIN
from .hub import ImmichHub, InvalidAuth

PLATFORMS: list[Platform] = [Platform.IMAGE]
SCAN_INTERVAL = timedelta(minutes=5)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand Down
72 changes: 66 additions & 6 deletions custom_components/immich/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@

import logging
from typing import Any
from url_normalize import url_normalize
from urllib.parse import urlparse

from url_normalize import url_normalize
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.const import CONF_API_KEY, CONF_HOST
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_validation as cv

from .const import DOMAIN
from .hub import ImmichHub, InvalidAuth, CannotConnect
from .const import CONF_WATCHED_ALBUMS, DOMAIN
from .hub import CannotConnect, ImmichHub, InvalidAuth

_LOGGER = logging.getLogger(__name__)

Expand All @@ -40,9 +41,13 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
if not await hub.authenticate():
raise InvalidAuth

user_info = await hub.get_my_user_info()
username = user_info["name"]
clean_hostname = urlparse(url).hostname

# Return info that you want to store in the config entry.
return {
"title": urlparse(url).hostname,
"title": f"{username} @ {clean_hostname}",
"data": {CONF_HOST: url, CONF_API_KEY: api_key},
}

Expand Down Expand Up @@ -73,3 +78,58 @@ async def async_step_user(
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)

@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Create the options flow."""
return OptionsFlowHandler(config_entry)


class OptionsFlowHandler(config_entries.OptionsFlow):
"""Immich options flow handler."""

def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)

# Get a connection to the hub in order to list the available albums
url = url_normalize(self.config_entry.data[CONF_HOST])
api_key = self.config_entry.data[CONF_API_KEY]
hub = ImmichHub(host=url, api_key=api_key)

if not await hub.authenticate():
raise InvalidAuth

# Get the list of albums and create a mapping of album id to album name
albums = await hub.list_all_albums()
album_map = {album["id"]: album["albumName"] for album in albums}

# Filter out any album ids that are no longer returned by the API
current_albums_value = [
album
for album in self.config_entry.options.get(CONF_WATCHED_ALBUMS, [])
if album in album_map
]

# Allow the user to select which albums they want to create entities for
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required(
CONF_WATCHED_ALBUMS,
default=current_albums_value,
): cv.multi_select(album_map)
}
),
)
1 change: 1 addition & 0 deletions custom_components/immich/const.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Constants for the immich integration."""

DOMAIN = "immich"
CONF_WATCHED_ALBUMS = "watched_albums"
92 changes: 73 additions & 19 deletions custom_components/immich/hub.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Hub for Immich integration."""
from __future__ import annotations

import aiohttp
import logging
from urllib.parse import urljoin
import random

import aiohttp

from homeassistant.exceptions import HomeAssistantError

Expand Down Expand Up @@ -33,9 +33,9 @@ async def authenticate(self) -> bool:
_LOGGER.error("Error from API: body=%s", raw_result)
return False

json_result = await response.json()
auth_result = await response.json()

if not json_result.get("authStatus"):
if not auth_result.get("authStatus"):
raw_result = await response.text()
_LOGGER.error("Error from API: body=%s", raw_result)
return False
Expand All @@ -45,21 +45,25 @@ async def authenticate(self) -> bool:
_LOGGER.error("Error connecting to the API: %s", exception)
raise CannotConnect from exception

async def get_random_picture(self) -> dict | None:
"""Get a random picture from the API."""
assets = [
asset for asset in await self._list_favorites() if asset["type"] == "IMAGE"
]
async def get_my_user_info(self) -> dict:
"""Get user info."""
try:
async with aiohttp.ClientSession() as session:
url = urljoin(self.host, "/api/user/me")
headers = {"Accept": "application/json", _HEADER_API_KEY: self.api_key}

if not assets:
_LOGGER.error("No assets found in favorites")
return None
async with session.get(url=url, headers=headers) as response:
if response.status != 200:
raw_result = await response.text()
_LOGGER.error("Error from API: body=%s", raw_result)
raise ApiError()

# Select random item in list
random_asset = random.choice(assets)
user_info: dict = await response.json()

_LOGGER.debug("Random asset: %s", random_asset)
return random_asset
return user_info
except aiohttp.ClientError as exception:
_LOGGER.error("Error connecting to the API: %s", exception)
raise CannotConnect from exception

async def download_asset(self, asset_id: str) -> bytes:
"""Download the asset."""
Expand All @@ -78,7 +82,8 @@ async def download_asset(self, asset_id: str) -> bytes:
_LOGGER.error("Error connecting to the API: %s", exception)
raise CannotConnect from exception

async def _list_favorites(self) -> list[dict]:
async def list_favorite_images(self) -> list[dict]:
"""List all favorite images."""
try:
async with aiohttp.ClientSession() as session:
url = urljoin(self.host, "/api/asset?isFavorite=true")
Expand All @@ -90,9 +95,58 @@ async def _list_favorites(self) -> list[dict]:
_LOGGER.error("Error from API: body=%s", raw_result)
raise ApiError()

json_result = await response.json()
assets: list[dict] = await response.json()

filtered_assets: list[dict] = [
asset for asset in assets if asset["type"] == "IMAGE"
]

return filtered_assets
except aiohttp.ClientError as exception:
_LOGGER.error("Error connecting to the API: %s", exception)
raise CannotConnect from exception

async def list_all_albums(self) -> list[dict]:
"""List all albums."""
try:
async with aiohttp.ClientSession() as session:
url = urljoin(self.host, "/api/album")
headers = {"Accept": "application/json", _HEADER_API_KEY: self.api_key}

async with session.get(url=url, headers=headers) as response:
if response.status != 200:
raw_result = await response.text()
_LOGGER.error("Error from API: body=%s", raw_result)
raise ApiError()

album_list: list[dict] = await response.json()

return album_list
except aiohttp.ClientError as exception:
_LOGGER.error("Error connecting to the API: %s", exception)
raise CannotConnect from exception

async def list_album_images(self, album_id: str) -> list[dict]:
"""List all images in an album."""
try:
async with aiohttp.ClientSession() as session:
url = urljoin(self.host, f"/api/album/{album_id}")
headers = {"Accept": "application/json", _HEADER_API_KEY: self.api_key}

async with session.get(url=url, headers=headers) as response:
if response.status != 200:
raw_result = await response.text()
_LOGGER.error("Error from API: body=%s", raw_result)
raise ApiError()

album_info: dict = await response.json()
assets: list[dict] = album_info["assets"]

filtered_assets: list[dict] = [
asset for asset in assets if asset["type"] == "IMAGE"
]

return json_result
return filtered_assets
except aiohttp.ClientError as exception:
_LOGGER.error("Error connecting to the API: %s", exception)
raise CannotConnect from exception
Expand Down
Loading

0 comments on commit bba3e89

Please sign in to comment.