Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Define a class to manage fetching Twitch data."""
2 
3 from dataclasses import dataclass
4 from datetime import datetime, timedelta
5 
6 from twitchAPI.helper import first
7 from twitchAPI.object.api import FollowedChannel, Stream, TwitchUser, UserSubscription
8 from twitchAPI.twitch import Twitch
9 from twitchAPI.type import TwitchAPIException, TwitchResourceNotFound
10 
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.core import HomeAssistant
13 from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
14 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
15 
16 from .const import CONF_CHANNELS, DOMAIN, LOGGER, OAUTH_SCOPES
17 
18 
19 def chunk_list(lst: list, chunk_size: int) -> list[list]:
20  """Split a list into chunks of chunk_size."""
21  return [lst[i : i + chunk_size] for i in range(0, len(lst), chunk_size)]
22 
23 
24 @dataclass
26  """Class for holding Twitch data."""
27 
28  name: str
29  followers: int
30  is_streaming: bool
31  game: str | None
32  title: str | None
33  started_at: datetime | None
34  stream_picture: str | None
35  picture: str
36  subscribed: bool | None
37  subscription_gifted: bool | None
38  subscription_tier: int | None
39  follows: bool
40  following_since: datetime | None
41  viewers: int | None
42 
43 
44 class TwitchCoordinator(DataUpdateCoordinator[dict[str, TwitchUpdate]]):
45  """Class to manage fetching Twitch data."""
46 
47  config_entry: ConfigEntry
48  users: list[TwitchUser]
49  current_user: TwitchUser
50 
51  def __init__(
52  self, hass: HomeAssistant, twitch: Twitch, session: OAuth2Session
53  ) -> None:
54  """Initialize the coordinator."""
55  self.twitchtwitch = twitch
56  super().__init__(
57  hass,
58  LOGGER,
59  name=DOMAIN,
60  update_interval=timedelta(minutes=5),
61  )
62  self.sessionsession = session
63 
64  async def _async_setup(self) -> None:
65  channels = self.config_entryconfig_entry.options[CONF_CHANNELS]
66  self.usersusers = []
67  # Split channels into chunks of 100 to avoid hitting the rate limit
68  for chunk in chunk_list(channels, 100):
69  self.usersusers.extend(
70  [channel async for channel in self.twitchtwitch.get_users(logins=chunk)]
71  )
72  if not (user := await first(self.twitchtwitch.get_users())):
73  raise UpdateFailed("Logged in user not found")
74  self.current_usercurrent_user = user
75 
76  async def _async_update_data(self) -> dict[str, TwitchUpdate]:
77  await self.sessionsession.async_ensure_token_valid()
78  await self.twitchtwitch.set_user_authentication(
79  self.sessionsession.token["access_token"],
80  OAUTH_SCOPES,
81  self.sessionsession.token["refresh_token"],
82  False,
83  )
84  data: dict[str, TwitchUpdate] = {}
85  streams: dict[str, Stream] = {
86  s.user_id: s
87  async for s in self.twitchtwitch.get_followed_streams(
88  user_id=self.current_usercurrent_user.id, first=100
89  )
90  }
91  follows: dict[str, FollowedChannel] = {
92  f.broadcaster_id: f
93  async for f in await self.twitchtwitch.get_followed_channels(
94  user_id=self.current_usercurrent_user.id, first=100
95  )
96  }
97  for channel in self.usersusers:
98  followers = await self.twitchtwitch.get_channel_followers(channel.id)
99  stream = streams.get(channel.id)
100  follow = follows.get(channel.id)
101  sub: UserSubscription | None = None
102  try:
103  sub = await self.twitchtwitch.check_user_subscription(
104  user_id=self.current_usercurrent_user.id, broadcaster_id=channel.id
105  )
106  except TwitchResourceNotFound:
107  LOGGER.debug("User is not subscribed to %s", channel.display_name)
108  except TwitchAPIException as exc:
109  LOGGER.error("Error response on check_user_subscription: %s", exc)
110 
111  data[channel.id] = TwitchUpdate(
112  channel.display_name,
113  followers.total,
114  bool(stream),
115  stream.game_name if stream else None,
116  stream.title if stream else None,
117  stream.started_at if stream else None,
118  stream.thumbnail_url if stream else None,
119  channel.profile_image_url,
120  bool(sub),
121  sub.is_gift if sub else None,
122  {"1000": 1, "2000": 2, "3000": 3}.get(sub.tier) if sub else None,
123  bool(follow),
124  follow.followed_at if follow else None,
125  stream.viewer_count if stream else None,
126  )
127  return data
None __init__(self, HomeAssistant hass, Twitch twitch, OAuth2Session session)
Definition: coordinator.py:53
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
list[list] chunk_list(list lst, int chunk_size)
Definition: coordinator.py:19