"""Mastodon platform for sensor components.""" from collections.abc import Callable, Mapping from dataclasses import dataclass from datetime import datetime from typing import Any, override from mastodon.Mastodon import Account, Instance, InstanceV2 from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, SensorStateClass, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.util import dt as dt_util from .coordinator import MastodonConfigEntry from .entity import MastodonEntity from .utils import construct_mastodon_username # Coordinator is used to centralize the data updates PARALLEL_UPDATES = 0 @dataclass(frozen=True, kw_only=True) class MastodonSensorEntityDescription(SensorEntityDescription): """Describes Mastodon sensor entity.""" value_fn: Callable[[Account, InstanceV2 | Instance], StateType | datetime] attributes_fn: Callable[[Account], Mapping[str, Any]] | None = None entity_picture_fn: Callable[[Account], str] | None = None def account_meta(data: Account) -> Mapping[str, Any]: """Account attributes.""" return { "display_name": data.display_name, "bio": data.note, "created": dt_util.as_local(data.created_at).date(), } ENTITY_DESCRIPTIONS = ( MastodonSensorEntityDescription( key="followers", translation_key="followers", state_class=SensorStateClass.TOTAL, value_fn=lambda data, _: data.followers_count, ), MastodonSensorEntityDescription( key="following", translation_key="following", state_class=SensorStateClass.TOTAL, value_fn=lambda data, _: data.following_count, ), MastodonSensorEntityDescription( key="posts", translation_key="posts", state_class=SensorStateClass.TOTAL, value_fn=lambda data, _: data.statuses_count, ), MastodonSensorEntityDescription( key="last_post", translation_key="last_post", device_class=SensorDeviceClass.TIMESTAMP, value_fn=( lambda data, _: ( dt_util.as_local(data.last_status_at) if data.last_status_at else None ) ), ), MastodonSensorEntityDescription( key="username", translation_key="username", value_fn=lambda data, instance: construct_mastodon_username(instance, data), attributes_fn=account_meta, entity_picture_fn=lambda data: data.avatar, ), ) async def async_setup_entry( hass: HomeAssistant, entry: MastodonConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the sensor platform for entity.""" coordinator = entry.runtime_data.coordinator async_add_entities( MastodonSensorEntity( coordinator=coordinator, entity_description=entity_description, data=entry, ) for entity_description in ENTITY_DESCRIPTIONS ) class MastodonSensorEntity(MastodonEntity, SensorEntity): """A Mastodon sensor entity.""" entity_description: MastodonSensorEntityDescription @property @override def native_value(self) -> StateType | datetime: """Return the native value of the sensor.""" return self.entity_description.value_fn(self.coordinator.data, self.instance) @property @override def extra_state_attributes(self) -> Mapping[str, Any] | None: """Return entity specific state attributes.""" return ( fn(self.coordinator.data) if (fn := self.entity_description.attributes_fn) else super().extra_state_attributes ) @property @override def entity_picture(self) -> str | None: """Return the entity picture to use in the frontend, if any.""" return ( fn(self.coordinator.data) if (fn := self.entity_description.entity_picture_fn) else super().entity_picture )