1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-21 19:39:06 +00:00
Files
core/homeassistant/components/xbox/entity.py

180 lines
5.6 KiB
Python

"""Base Sensor for the Xbox Integration."""
from __future__ import annotations
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from typing import Any
from pythonxbox.api.provider.people.models import Person
from pythonxbox.api.provider.smartglass.models import ConsoleType, SmartglassConsole
from pythonxbox.api.provider.titlehub.models import Title
from yarl import URL
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import ConsoleData, XboxUpdateCoordinator
MAP_MODEL = {
ConsoleType.XboxOne: "Xbox One",
ConsoleType.XboxOneS: "Xbox One S",
ConsoleType.XboxOneSDigital: "Xbox One S All-Digital",
ConsoleType.XboxOneX: "Xbox One X",
ConsoleType.XboxSeriesS: "Xbox Series S",
ConsoleType.XboxSeriesX: "Xbox Series X",
}
@dataclass(kw_only=True, frozen=True)
class XboxBaseEntityDescription(EntityDescription):
"""Xbox base entity description."""
entity_picture_fn: Callable[[Person, Title | None], str | None] | None = None
attributes_fn: Callable[[Person, Title | None], Mapping[str, Any] | None] | None = (
None
)
deprecated: bool | None = None
class XboxBaseEntity(CoordinatorEntity[XboxUpdateCoordinator]):
"""Base Sensor for the Xbox Integration."""
_attr_has_entity_name = True
entity_description: XboxBaseEntityDescription
def __init__(
self,
coordinator: XboxUpdateCoordinator,
xuid: str,
entity_description: XboxBaseEntityDescription,
) -> None:
"""Initialize Xbox entity."""
super().__init__(coordinator)
self.xuid = xuid
self.entity_description = entity_description
self._attr_unique_id = f"{xuid}_{entity_description.key}"
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, xuid)},
manufacturer="Microsoft",
model="Xbox Network",
name=self.data.gamertag,
)
@property
def data(self) -> Person:
"""Return coordinator data for this person."""
return self.coordinator.data.presence[self.xuid]
@property
def title_info(self) -> Title | None:
"""Return title info."""
return self.coordinator.data.title_info.get(self.xuid)
@property
def entity_picture(self) -> str | None:
"""Return the entity picture."""
return (
entity_picture
if self.available
and (fn := self.entity_description.entity_picture_fn) is not None
and (entity_picture := fn(self.data, self.title_info)) is not None
else super().entity_picture
)
@property
def extra_state_attributes(self) -> Mapping[str, float | None] | None:
"""Return entity specific state attributes."""
return (
fn(self.data, self.title_info)
if (fn := self.entity_description.attributes_fn)
else super().extra_state_attributes
)
@property
def available(self) -> bool:
"""Return True if entity is available."""
return super().available and self.xuid in self.coordinator.data.presence
class XboxConsoleBaseEntity(CoordinatorEntity[XboxUpdateCoordinator]):
"""Console base entity for the Xbox integration."""
_attr_has_entity_name = True
def __init__(
self,
console: SmartglassConsole,
coordinator: XboxUpdateCoordinator,
) -> None:
"""Initialize the Xbox Console entity."""
super().__init__(coordinator)
self.client = coordinator.client
self._console = console
self._attr_name = None
self._attr_unique_id = console.id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, console.id)},
manufacturer="Microsoft",
model=MAP_MODEL.get(self._console.console_type),
name=console.name,
)
@property
def data(self) -> ConsoleData:
"""Return coordinator data for this console."""
return self.coordinator.data.consoles[self._console.id]
def check_deprecated_entity(
hass: HomeAssistant,
xuid: str,
entity_description: XboxBaseEntityDescription,
entity_domain: str,
) -> bool:
"""Check for deprecated entity and remove it."""
if not entity_description.deprecated:
return True
ent_reg = er.async_get(hass)
if entity_id := ent_reg.async_get_entity_id(
entity_domain,
DOMAIN,
f"{xuid}_{entity_description.key}",
):
ent_reg.async_remove(entity_id)
return False
def to_https(image_url: str) -> str:
"""Convert image URLs to secure URLs."""
url = URL(image_url)
if url.host == "images-eds.xboxlive.com":
url = url.with_host("images-eds-ssl.xboxlive.com")
return str(url.with_scheme("https"))
def profile_pic(person: Person, _: Title | None = None) -> str | None:
"""Return the gamer pic."""
# Xbox sometimes returns a domain that uses a wrong certificate which
# creates issues with loading the image.
# The correct domain is images-eds-ssl which can just be replaced
# to point to the correct image, with the correct domain and certificate.
# We need to also remove the 'mode=Padding' query because with it,
# it results in an error 400.
return str(URL(to_https(person.display_pic_raw)).without_query_params("mode"))