33from __future__ import annotations
44
55from dataclasses import dataclass , field
6- from datetime import UTC , datetime , timedelta
6+ from datetime import timedelta
77import logging
88
99from httpx import HTTPStatusError , RequestError , TimeoutException
1010from xbox .webapi .api .client import XboxLiveClient
1111from xbox .webapi .api .provider .catalog .const import SYSTEM_PFN_ID_MAP
1212from xbox .webapi .api .provider .catalog .models import AlternateIdType , Product
13- from xbox .webapi .api .provider .people .models import (
14- PeopleResponse ,
15- Person ,
16- PresenceDetail ,
17- )
13+ from xbox .webapi .api .provider .people .models import Person
1814from xbox .webapi .api .provider .smartglass .models import (
1915 SmartglassConsoleList ,
2016 SmartglassConsoleStatus ,
2521from homeassistant .core import HomeAssistant
2622from homeassistant .exceptions import ConfigEntryNotReady
2723from homeassistant .helpers import config_entry_oauth2_flow , device_registry as dr
28- from homeassistant .helpers .update_coordinator import DataUpdateCoordinator
24+ from homeassistant .helpers .update_coordinator import DataUpdateCoordinator , UpdateFailed
2925
3026from . import api
3127from .const import DOMAIN
@@ -43,33 +39,12 @@ class ConsoleData:
4339 app_details : Product | None
4440
4541
46- @dataclass
47- class PresenceData :
48- """Xbox user presence data."""
49-
50- xuid : str
51- gamertag : str
52- display_pic : str
53- online : bool
54- status : str
55- in_party : bool
56- in_game : bool
57- in_multiplayer : bool
58- gamer_score : str
59- gold_tenure : str | None
60- account_tier : str
61- last_seen : datetime | None
62- following_count : int
63- follower_count : int
64- has_game_pass : bool
65-
66-
6742@dataclass
6843class XboxData :
6944 """Xbox dataclass for update coordinator."""
7045
7146 consoles : dict [str , ConsoleData ] = field (default_factory = dict )
72- presence : dict [str , PresenceData ] = field (default_factory = dict )
47+ presence : dict [str , Person ] = field (default_factory = dict )
7348
7449
7550class XboxUpdateCoordinator (DataUpdateCoordinator [XboxData ]):
@@ -107,7 +82,6 @@ async def _async_setup(self) -> None:
10782 raise ConfigEntryNotReady (
10883 translation_domain = DOMAIN ,
10984 translation_key = "request_exception" ,
110- translation_placeholders = {"error" : str (e )},
11185 ) from e
11286
11387 session = config_entry_oauth2_flow .OAuth2Session (
@@ -129,7 +103,6 @@ async def _async_setup(self) -> None:
129103 raise ConfigEntryNotReady (
130104 translation_domain = DOMAIN ,
131105 translation_key = "request_exception" ,
132- translation_placeholders = {"error" : str (e )},
133106 ) from e
134107
135108 _LOGGER .debug (
@@ -143,11 +116,20 @@ async def _async_update_data(self) -> XboxData:
143116 # Update Console Status
144117 new_console_data : dict [str , ConsoleData ] = {}
145118 for console in self .consoles .result :
146- current_state : ConsoleData | None = self .data .consoles .get (console .id )
147- status : SmartglassConsoleStatus = (
148- await self .client .smartglass .get_console_status (console .id )
149- )
150-
119+ current_state = self .data .consoles .get (console .id )
120+ try :
121+ status = await self .client .smartglass .get_console_status (console .id )
122+ except TimeoutException as e :
123+ raise UpdateFailed (
124+ translation_domain = DOMAIN ,
125+ translation_key = "timeout_exception" ,
126+ ) from e
127+ except (RequestError , HTTPStatusError ) as e :
128+ _LOGGER .debug ("Xbox exception:" , exc_info = True )
129+ raise UpdateFailed (
130+ translation_domain = DOMAIN ,
131+ translation_key = "request_exception" ,
132+ ) from e
151133 _LOGGER .debug (
152134 "%s status: %s" ,
153135 console .name ,
@@ -169,13 +151,26 @@ async def _async_update_data(self) -> XboxData:
169151 if app_id in SYSTEM_PFN_ID_MAP :
170152 id_type = AlternateIdType .LEGACY_XBOX_PRODUCT_ID
171153 app_id = SYSTEM_PFN_ID_MAP [app_id ][id_type ]
172- catalog_result = (
173- await self .client .catalog .get_product_from_alternate_id (
174- app_id , id_type
154+ try :
155+ catalog_result = (
156+ await self .client .catalog .get_product_from_alternate_id (
157+ app_id , id_type
158+ )
175159 )
176- )
177- if catalog_result and catalog_result .products :
178- app_details = catalog_result .products [0 ]
160+ except TimeoutException as e :
161+ raise UpdateFailed (
162+ translation_domain = DOMAIN ,
163+ translation_key = "timeout_exception" ,
164+ ) from e
165+ except (RequestError , HTTPStatusError ) as e :
166+ _LOGGER .debug ("Xbox exception:" , exc_info = True )
167+ raise UpdateFailed (
168+ translation_domain = DOMAIN ,
169+ translation_key = "request_exception" ,
170+ ) from e
171+ else :
172+ if catalog_result .products :
173+ app_details = catalog_result .products [0 ]
179174 else :
180175 app_details = None
181176
@@ -184,19 +179,25 @@ async def _async_update_data(self) -> XboxData:
184179 )
185180
186181 # Update user presence
187- presence_data : dict [str , PresenceData ] = {}
188- batch : PeopleResponse = await self .client .people .get_friends_own_batch (
189- [self .client .xuid ]
190- )
191- own_presence : Person = batch .people [0 ]
192- presence_data [own_presence .xuid ] = _build_presence_data (own_presence )
193-
194- friends : PeopleResponse = await self .client .people .get_friends_own ()
195- for friend in friends .people :
196- if not friend .is_favorite :
197- continue
198-
199- presence_data [friend .xuid ] = _build_presence_data (friend )
182+ try :
183+ batch = await self .client .people .get_friends_own_batch ([self .client .xuid ])
184+ friends = await self .client .people .get_friends_own ()
185+ except TimeoutException as e :
186+ raise UpdateFailed (
187+ translation_domain = DOMAIN ,
188+ translation_key = "timeout_exception" ,
189+ ) from e
190+ except (RequestError , HTTPStatusError ) as e :
191+ _LOGGER .debug ("Xbox exception:" , exc_info = True )
192+ raise UpdateFailed (
193+ translation_domain = DOMAIN ,
194+ translation_key = "request_exception" ,
195+ ) from e
196+ else :
197+ presence_data = {self .client .xuid : batch .people [0 ]}
198+ presence_data .update (
199+ {friend .xuid : friend for friend in friends .people if friend .is_favorite }
200+ )
200201
201202 if (
202203 self .current_friends
@@ -208,11 +209,11 @@ async def _async_update_data(self) -> XboxData:
208209
209210 return XboxData (new_console_data , presence_data )
210211
211- def remove_stale_devices (self , presence_data : dict [str , PresenceData ]) -> None :
212+ def remove_stale_devices (self , presence_data : dict [str , Person ]) -> None :
212213 """Remove stale devices from registry."""
213214
214215 device_reg = dr .async_get (self .hass )
215- identifiers = {(DOMAIN , person . xuid ) for person in presence_data . values ( )} | {
216+ identifiers = {(DOMAIN , xuid ) for xuid in set ( presence_data )} | {
216217 (DOMAIN , console .id ) for console in self .consoles .result
217218 }
218219
@@ -224,38 +225,3 @@ def remove_stale_devices(self, presence_data: dict[str, PresenceData]) -> None:
224225 device_reg .async_update_device (
225226 device .id , remove_config_entry_id = self .config_entry .entry_id
226227 )
227-
228-
229- def _build_presence_data (person : Person ) -> PresenceData :
230- """Build presence data from a person."""
231- active_app : PresenceDetail | None = None
232-
233- active_app = next (
234- (presence for presence in person .presence_details if presence .is_primary ),
235- None ,
236- )
237- in_game = (
238- active_app is not None and active_app .is_game and active_app .state == "Active"
239- )
240-
241- return PresenceData (
242- xuid = person .xuid ,
243- gamertag = person .gamertag ,
244- display_pic = person .display_pic_raw ,
245- online = person .presence_state == "Online" ,
246- status = person .presence_text ,
247- in_party = person .multiplayer_summary .in_party > 0 ,
248- in_game = in_game ,
249- in_multiplayer = person .multiplayer_summary .in_multiplayer_session ,
250- gamer_score = person .gamer_score ,
251- gold_tenure = person .detail .tenure ,
252- account_tier = person .detail .account_tier ,
253- last_seen = (
254- person .last_seen_date_time_utc .replace (tzinfo = UTC )
255- if person .last_seen_date_time_utc
256- else None
257- ),
258- follower_count = person .detail .follower_count ,
259- following_count = person .detail .following_count ,
260- has_game_pass = person .detail .has_game_pass ,
261- )
0 commit comments