Skip to content

Commit 2bdb258

Browse files
tr4nt0rzweckj
andauthored
Add image platform to Xbox integration (#155369)
Co-authored-by: Josef Zweck <josef@zweck.dev>
1 parent 5b32352 commit 2bdb258

File tree

8 files changed

+1003
-19
lines changed

8 files changed

+1003
-19
lines changed

homeassistant/components/xbox/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
PLATFORMS = [
1919
Platform.BINARY_SENSOR,
20+
Platform.IMAGE,
2021
Platform.MEDIA_PLAYER,
2122
Platform.REMOTE,
2223
Platform.SENSOR,

homeassistant/components/xbox/binary_sensor.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
from xbox.webapi.api.provider.people.models import Person
1111
from xbox.webapi.api.provider.titlehub.models import Title
12-
from yarl import URL
1312

1413
from homeassistant.components.binary_sensor import (
1514
DOMAIN as BINARY_SENSOR_DOMAIN,
@@ -20,7 +19,12 @@
2019
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
2120

2221
from .coordinator import XboxConfigEntry
23-
from .entity import XboxBaseEntity, XboxBaseEntityDescription, check_deprecated_entity
22+
from .entity import (
23+
XboxBaseEntity,
24+
XboxBaseEntityDescription,
25+
check_deprecated_entity,
26+
profile_pic,
27+
)
2428

2529

2630
class XboxBinarySensor(StrEnum):
@@ -43,23 +47,6 @@ class XboxBinarySensorEntityDescription(
4347
deprecated: bool | None = None
4448

4549

46-
def profile_pic(person: Person, _: Title | None) -> str | None:
47-
"""Return the gamer pic."""
48-
49-
# Xbox sometimes returns a domain that uses a wrong certificate which
50-
# creates issues with loading the image.
51-
# The correct domain is images-eds-ssl which can just be replaced
52-
# to point to the correct image, with the correct domain and certificate.
53-
# We need to also remove the 'mode=Padding' query because with it,
54-
# it results in an error 400.
55-
url = URL(person.display_pic_raw)
56-
if url.host == "images-eds.xboxlive.com":
57-
url = url.with_host("images-eds-ssl.xboxlive.com").with_scheme("https")
58-
query = dict(url.query)
59-
query.pop("mode", None)
60-
return str(url.with_query(query))
61-
62-
6350
def profile_attributes(person: Person, _: Title | None) -> dict[str, Any]:
6451
"""Attributes for the profile."""
6552
attributes: dict[str, Any] = {}

homeassistant/components/xbox/entity.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from xbox.webapi.api.provider.people.models import Person
1010
from xbox.webapi.api.provider.smartglass.models import ConsoleType, SmartglassConsole
1111
from xbox.webapi.api.provider.titlehub.models import Title
12+
from yarl import URL
1213

1314
from homeassistant.components.automation import automations_with_entity
1415
from homeassistant.components.script import scripts_with_entity
@@ -39,6 +40,7 @@ class XboxBaseEntityDescription(EntityDescription):
3940
attributes_fn: Callable[[Person, Title | None], Mapping[str, Any] | None] | None = (
4041
None
4142
)
43+
deprecated: bool | None = None
4244

4345

4446
class XboxBaseEntity(CoordinatorEntity[XboxUpdateCoordinator]):
@@ -157,3 +159,20 @@ def check_deprecated_entity(
157159
ent_reg.async_remove(entity_id)
158160

159161
return False
162+
163+
164+
def profile_pic(person: Person, _: Title | None) -> str | None:
165+
"""Return the gamer pic."""
166+
167+
# Xbox sometimes returns a domain that uses a wrong certificate which
168+
# creates issues with loading the image.
169+
# The correct domain is images-eds-ssl which can just be replaced
170+
# to point to the correct image, with the correct domain and certificate.
171+
# We need to also remove the 'mode=Padding' query because with it,
172+
# it results in an error 400.
173+
url = URL(person.display_pic_raw)
174+
if url.host == "images-eds.xboxlive.com":
175+
url = url.with_host("images-eds-ssl.xboxlive.com").with_scheme("https")
176+
query = dict(url.query)
177+
query.pop("mode", None)
178+
return str(url.with_query(query))
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""Image platform for the Xbox integration."""
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Callable
6+
from dataclasses import dataclass
7+
from enum import StrEnum
8+
9+
from xbox.webapi.api.provider.people.models import Person
10+
from xbox.webapi.api.provider.titlehub.models import Title
11+
12+
from homeassistant.components.image import ImageEntity, ImageEntityDescription
13+
from homeassistant.core import HomeAssistant, callback
14+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
15+
from homeassistant.util import dt as dt_util
16+
17+
from .coordinator import XboxConfigEntry, XboxUpdateCoordinator
18+
from .entity import XboxBaseEntity, XboxBaseEntityDescription, profile_pic
19+
20+
PARALLEL_UPDATES = 0
21+
22+
23+
class XboxImage(StrEnum):
24+
"""Xbox image."""
25+
26+
NOW_PLAYING = "now_playing"
27+
GAMERPIC = "gamerpic"
28+
AVATAR = "avatar"
29+
30+
31+
@dataclass(kw_only=True, frozen=True)
32+
class XboxImageEntityDescription(XboxBaseEntityDescription, ImageEntityDescription):
33+
"""Xbox image description."""
34+
35+
image_url_fn: Callable[[Person, Title | None], str | None]
36+
37+
38+
IMAGE_DESCRIPTIONS: tuple[XboxImageEntityDescription, ...] = (
39+
XboxImageEntityDescription(
40+
key=XboxImage.GAMERPIC,
41+
translation_key=XboxImage.GAMERPIC,
42+
image_url_fn=profile_pic,
43+
),
44+
XboxImageEntityDescription(
45+
key=XboxImage.NOW_PLAYING,
46+
translation_key=XboxImage.NOW_PLAYING,
47+
image_url_fn=lambda _, title: title.display_image if title else None,
48+
),
49+
XboxImageEntityDescription(
50+
key=XboxImage.AVATAR,
51+
translation_key=XboxImage.AVATAR,
52+
image_url_fn=(
53+
lambda person,
54+
_: f"https://avatar-ssl.xboxlive.com/avatar/{person.gamertag}/avatar-body.png"
55+
),
56+
),
57+
)
58+
59+
60+
async def async_setup_entry(
61+
hass: HomeAssistant,
62+
config_entry: XboxConfigEntry,
63+
async_add_entities: AddConfigEntryEntitiesCallback,
64+
) -> None:
65+
"""Set up Xbox images."""
66+
67+
coordinator = config_entry.runtime_data
68+
69+
xuids_added: set[str] = set()
70+
71+
@callback
72+
def add_entities() -> None:
73+
"""Add image entities."""
74+
nonlocal xuids_added
75+
76+
current_xuids = set(coordinator.data.presence)
77+
if new_xuids := current_xuids - xuids_added:
78+
async_add_entities(
79+
[
80+
XboxImageEntity(hass, coordinator, xuid, description)
81+
for xuid in new_xuids
82+
for description in IMAGE_DESCRIPTIONS
83+
]
84+
)
85+
xuids_added |= new_xuids
86+
xuids_added &= current_xuids
87+
88+
coordinator.async_add_listener(add_entities)
89+
add_entities()
90+
91+
92+
class XboxImageEntity(XboxBaseEntity, ImageEntity):
93+
"""An image entity."""
94+
95+
entity_description: XboxImageEntityDescription
96+
97+
def __init__(
98+
self,
99+
hass: HomeAssistant,
100+
coordinator: XboxUpdateCoordinator,
101+
xuid: str,
102+
entity_description: XboxImageEntityDescription,
103+
) -> None:
104+
"""Initialize the image entity."""
105+
super().__init__(coordinator, xuid, entity_description)
106+
ImageEntity.__init__(self, hass)
107+
108+
self._attr_image_url = self.entity_description.image_url_fn(
109+
self.data, self.title_info
110+
)
111+
self._attr_image_last_updated = dt_util.utcnow()
112+
113+
def _handle_coordinator_update(self) -> None:
114+
"""Handle updated data from the coordinator."""
115+
116+
url = self.entity_description.image_url_fn(self.data, self.title_info)
117+
118+
if url != self._attr_image_url:
119+
self._attr_image_url = url
120+
self._cached_image = None
121+
self._attr_image_last_updated = dt_util.utcnow()
122+
123+
super()._handle_coordinator_update()

homeassistant/components/xbox/strings.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@
4444
}
4545
}
4646
},
47+
"image": {
48+
"avatar": {
49+
"name": "Avatar"
50+
},
51+
"gamerpic": {
52+
"name": "Gamerpic"
53+
},
54+
"now_playing": {
55+
"name": "[%key:component::xbox::entity::sensor::now_playing::name%]"
56+
}
57+
},
4758
"sensor": {
4859
"follower": {
4960
"name": "Follower",

0 commit comments

Comments
 (0)