mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 05:06:13 +01:00
Add more sensors to Mastodon integration (#160835)
This commit is contained in:
@@ -45,3 +45,4 @@ class MastodonEntity(CoordinatorEntity[MastodonCoordinator]):
|
||||
)
|
||||
|
||||
self.entity_description = entity_description
|
||||
self.instance = data.runtime_data.instance
|
||||
|
||||
@@ -20,8 +20,14 @@
|
||||
"following": {
|
||||
"default": "mdi:account-multiple"
|
||||
},
|
||||
"last_post": {
|
||||
"default": "mdi:message-text-clock"
|
||||
},
|
||||
"posts": {
|
||||
"default": "mdi:message-text"
|
||||
},
|
||||
"username": {
|
||||
"default": "mdi:account"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from mastodon.Mastodon import Account
|
||||
from mastodon.Mastodon import Account, Instance, InstanceV2
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
@@ -15,9 +18,11 @@ from homeassistant.components.sensor import (
|
||||
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
|
||||
@@ -27,7 +32,20 @@ PARALLEL_UPDATES = 0
|
||||
class MastodonSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes Mastodon sensor entity."""
|
||||
|
||||
value_fn: Callable[[Account], StateType]
|
||||
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(),
|
||||
**{f.name: f.value for f in data.fields},
|
||||
}
|
||||
|
||||
|
||||
ENTITY_DESCRIPTIONS = (
|
||||
@@ -35,19 +53,36 @@ ENTITY_DESCRIPTIONS = (
|
||||
key="followers",
|
||||
translation_key="followers",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
value_fn=lambda data: data.followers_count,
|
||||
value_fn=lambda data, _: data.followers_count,
|
||||
),
|
||||
MastodonSensorEntityDescription(
|
||||
key="following",
|
||||
translation_key="following",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
value_fn=lambda data: data.following_count,
|
||||
value_fn=lambda data, _: data.following_count,
|
||||
),
|
||||
MastodonSensorEntityDescription(
|
||||
key="posts",
|
||||
translation_key="posts",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
value_fn=lambda data: data.statuses_count,
|
||||
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,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -76,6 +111,24 @@ class MastodonSensorEntity(MastodonEntity, SensorEntity):
|
||||
entity_description: MastodonSensorEntityDescription
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
def native_value(self) -> StateType | datetime:
|
||||
"""Return the native value of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
||||
return self.entity_description.value_fn(self.coordinator.data, self.instance)
|
||||
|
||||
@property
|
||||
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
|
||||
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
|
||||
)
|
||||
|
||||
@@ -45,9 +45,26 @@
|
||||
"name": "Following",
|
||||
"unit_of_measurement": "[%key:component::mastodon::entity::sensor::followers::unit_of_measurement%]"
|
||||
},
|
||||
"last_post": {
|
||||
"name": "Last post"
|
||||
},
|
||||
"posts": {
|
||||
"name": "Posts",
|
||||
"unit_of_measurement": "posts"
|
||||
},
|
||||
"username": {
|
||||
"name": "Username",
|
||||
"state_attributes": {
|
||||
"bio": {
|
||||
"name": "Bio"
|
||||
},
|
||||
"created": {
|
||||
"name": "Created"
|
||||
},
|
||||
"display_name": {
|
||||
"name": "Display name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -105,6 +105,55 @@
|
||||
'state': '328',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.mastodon_trwnh_mastodon_social_last_post-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mastodon_trwnh_mastodon_social_last_post',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Last post',
|
||||
'platform': 'mastodon',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'last_post',
|
||||
'unique_id': 'trwnh_mastodon_social_last_post',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.mastodon_trwnh_mastodon_social_last_post-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'timestamp',
|
||||
'friendly_name': 'Mastodon @trwnh@mastodon.social Last post',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mastodon_trwnh_mastodon_social_last_post',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '2025-03-04T08:00:00+00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.mastodon_trwnh_mastodon_social_posts-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -158,3 +207,59 @@
|
||||
'state': '69523',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.mastodon_trwnh_mastodon_social_username-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mastodon_trwnh_mastodon_social_username',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Username',
|
||||
'platform': 'mastodon',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'username',
|
||||
'unique_id': 'trwnh_mastodon_social_username',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.mastodon_trwnh_mastodon_social_username-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'Fan of:': 'Punk-rock and post-hardcore (Circa Survive, letlive., La Dispute, THE FEVER 333)Manga (Yu-Gi-Oh!, One Piece, JoJo's Bizarre Adventure, Death Note, Shaman King)Platformers and RPGs (Banjo-Kazooie, Boktai, Final Fantasy Crystal Chronicles)',
|
||||
'Portfolio': '<a href="https://abdullahtarawneh.com" target="_blank" rel="nofollow noopener me" translate="no"><span class="invisible">https://</span><span class="">abdullahtarawneh.com</span><span class="invisible"></span></a>',
|
||||
'Website': '<a href="https://trwnh.com" target="_blank" rel="nofollow noopener me" translate="no"><span class="invisible">https://</span><span class="">trwnh.com</span><span class="invisible"></span></a>',
|
||||
'What to expect:': 'talking about various things i find interesting, and otherwise being a genuine and decent wholesome poster. i'm just here to hang out and talk to cool people! and to spill my thoughts.',
|
||||
'bio': '<p>i have approximate knowledge of many things. perpetual student. (nb/ace/they)</p><p>xmpp/email: a@trwnh.com<br /><a href="https://trwnh.com" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="">trwnh.com</span><span class="invisible"></span></a><br />help me live:<br />- <a href="https://donate.stripe.com/4gwcPCaMpcQ19RC4gg" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="ellipsis">donate.stripe.com/4gwcPCaMpcQ1</span><span class="invisible">9RC4gg</span></a><br />- <a href="https://liberapay.com/trwnh" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="">liberapay.com/trwnh</span><span class="invisible"></span></a></p><p>notes:<br />- my triggers are moths and glitter<br />- i have all notifs except mentions turned off, so please interact if you wanna be friends! i literally will not notice otherwise<br />- dm me if i did something wrong, so i can improve<br />- purest person on fedi, do not lewd in my presence</p>',
|
||||
'created': datetime.date(2016, 11, 23),
|
||||
'display_name': 'infinite love ⴳ',
|
||||
'entity_picture': 'https://files.mastodon.social/accounts/avatars/000/014/715/original/051c958388818705.png',
|
||||
'friendly_name': 'Mastodon @trwnh@mastodon.social Username',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mastodon_trwnh_mastodon_social_username',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '@trwnh@mastodon.social',
|
||||
})
|
||||
# ---
|
||||
|
||||
Reference in New Issue
Block a user