Skip to content

Commit ebabb08

Browse files
committed
Initial draft of HypeTrainStatus
1 parent 2292278 commit ebabb08

File tree

4 files changed

+235
-10
lines changed

4 files changed

+235
-10
lines changed

twitchio/http.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
GlobalChatBadgesResponse,
124124
GlobalEmotesResponse,
125125
HypeTrainEventsResponseData,
126+
HypeTrainStatusResponse,
126127
ModeratedChannelsResponseData,
127128
ModeratorsResponseData,
128129
PollsResponse,
@@ -1609,6 +1610,12 @@ def converter(data: HypeTrainEventsResponseData, *, raw: Any) -> HypeTrainEvent:
16091610

16101611
return iterator
16111612

1613+
async def get_hype_train_status(self, broadcaster_id: str | int, token_for: str) -> HypeTrainStatusResponse:
1614+
params = {"broadcaster_id": broadcaster_id}
1615+
1616+
route: Route = Route("GET", "hypetrain/status", params=params, token_for=token_for)
1617+
return await self.request_json(route)
1618+
16121619
### Moderation ###
16131620

16141621
@handle_user_ids()

twitchio/models/hype_train.py

Lines changed: 155 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
from __future__ import annotations
2626

27-
from typing import TYPE_CHECKING, Literal
27+
from typing import TYPE_CHECKING, Literal, NamedTuple
2828

2929
from twitchio.user import PartialUser
3030
from twitchio.utils import parse_timestamp
@@ -34,9 +34,16 @@
3434
import datetime
3535

3636
from twitchio.http import HTTPClient
37-
from twitchio.types_.responses import HypeTrainEventsResponseContributions, HypeTrainEventsResponseData
37+
from twitchio.types_.responses import (
38+
HypeTrainEventsResponseContributions,
39+
HypeTrainEventsResponseData,
40+
HypeTrainStatusResponseData,
41+
HypeTrainStatusTopContributions,
42+
)
43+
44+
CombinedContributionType = Literal["BITS", "SUBS", "OTHER", "bits", "subscriptions", "other"]
3845

39-
__all__ = ("HypeTrainContribution", "HypeTrainEvent")
46+
__all__ = ("HypeTrainAllTimeHigh", "HypeTrainContribution", "HypeTrainEvent", "HypeTrainStatus")
4047

4148

4249
class HypeTrainEvent:
@@ -119,18 +126,157 @@ class HypeTrainContribution:
119126
total: int
120127
The total amount contributed. If type is ``BITS``, total represents the amount of Bits used.
121128
If type is ``SUBS``, total is 500, 1000, or 2500 to represent tier 1, 2, or 3 subscriptions, respectively.
122-
type: typing.Literal["BITS", "SUBS", "OTHER"]
123-
Identifies the contribution method, either BITS, SUBS or OTHER.
129+
type: typing.Literal["BITS", "SUBS", "OTHER", "bits", "subscription", "other"]
130+
Identifies the contribution method.
131+
132+
**HypeTrainEvent:**
133+
134+
- **BITS**: cheering with Bits.
135+
- **SUBS**: subscription activity like subscribing or gifting subscriptions.
136+
- **OTHER**: covers other contribution methods not listed.
137+
138+
**HypeTrainStatus:**
139+
140+
- **bits**: cheering with Bits.
141+
- **subscription**: subscription activity like subscribing or gifting subscriptions.
142+
- **other**: covers other contribution methods not listed.
143+
124144
user: PartialUser
125145
The user making the contribution.
126146
"""
127147

128-
__slots__ = "total", "type", "user"
148+
__slots__ = ("total", "type", "user")
129149

130-
def __init__(self, data: HypeTrainEventsResponseContributions, *, http: HTTPClient) -> None:
150+
def __init__(
151+
self, data: HypeTrainEventsResponseContributions | HypeTrainStatusTopContributions, *, http: HTTPClient
152+
) -> None:
131153
self.total: int = int(data["total"])
132-
self.type: Literal["BITS", "SUBS", "OTHER"] = data["type"]
133-
self.user = PartialUser(data["user"], None, http=http)
154+
self.type: CombinedContributionType = data["type"]
155+
156+
if "user" in data:
157+
self.user = PartialUser(data["user"], http=http)
158+
else:
159+
self.user = PartialUser(data["user_id"], data["user_login"], data["user_name"], http=http)
134160

135161
def __repr__(self) -> str:
136162
return f"<HypeTrainContribution total={self.total} type={self.type} user={self.user}>"
163+
164+
165+
class HypeTrainAllTimeHigh(NamedTuple):
166+
"""The all time high data for a Hype Train.
167+
168+
Attributes
169+
-----------
170+
level: int
171+
The level of the record Hype Train.
172+
total: int
173+
The total amount contributed to the record Hype Train.
174+
achieved_at: datetime.datetime
175+
The datetime when the record was achieved.
176+
"""
177+
178+
level: int
179+
total: int
180+
achieved_at: datetime.datetime
181+
182+
183+
class HypeTrainStatus:
184+
"""Represents the current status of a Hype Train.
185+
186+
Attributes
187+
-----------
188+
id: str
189+
The ID of the Hype Train.
190+
broadcaster: PartialUser
191+
The user whose channel the Hype Train is occurring on.
192+
level: int
193+
The current level of the Hype Train.
194+
total: int1
195+
The total amount contributed to the Hype Train.
196+
progress: int
197+
The number of points contributed to the Hype Train at the current level.
198+
goal: int
199+
The value needed to reach the next level.
200+
top_contributions: list[HypeTrainContribution]
201+
The contributors with the most points contributed.
202+
started_at: datetime.datetime
203+
The time the Hype Train started.
204+
expires_at: datetime.datetime
205+
The time the Hype Train expires.
206+
type: Literal["treasure", "golden_kappa", "regular"]
207+
The type of Hype Train. Can be one of `treasure`, `golden_kappa`, or `regular`.
208+
all_time_high: HypeTrainAllTimeHigh | None
209+
Information about the channel's hype train records.
210+
shared_train: bool
211+
Whether this Hype Train is a shared train.
212+
shared_train_participants: list[PartialUser]
213+
A list containing the broadcasters participating in the shared hype train.
214+
shared_all_time_high: HypeTrainAllTimeHigh | None
215+
Information about the channel's shared hype train records.
216+
"""
217+
218+
__slots__ = (
219+
"all_time_high",
220+
"broadcaster",
221+
"expires_at",
222+
"goal",
223+
"id",
224+
"level",
225+
"progress",
226+
"shared_all_time_high",
227+
"shared_train",
228+
"shared_train_participants",
229+
"started_at",
230+
"top_contributions",
231+
"total",
232+
"type",
233+
)
234+
235+
def __init__(self, data: HypeTrainStatusResponseData, *, http: HTTPClient) -> None:
236+
current = data.get("current")
237+
if current is None:
238+
raise ValueError("HypeTrainStatus requires 'current'")
239+
all_time_high = data.get("all_time_high")
240+
shared_all_time_high = data.get("shared_all_time_high")
241+
242+
self.id: str = current["id"]
243+
self.broadcaster = PartialUser(
244+
current["broadcaster_user_id"], current["broadcaster_user_id"], current["broadcaster_user_name"], http=http
245+
)
246+
self.level: int = current["level"]
247+
self.total: int = current["total"]
248+
self.progress: int = current["progress"]
249+
self.goal: int = current["goal"]
250+
self.started_at: datetime.datetime = parse_timestamp(current["started_at"])
251+
self.expires_at: datetime.datetime = parse_timestamp(current["expires_at"])
252+
self.type: Literal["treasure", "golden_kappa", "regular"] = current["type"]
253+
self.top_contributions: list[HypeTrainContribution] = [
254+
HypeTrainContribution(c, http=http) for c in current["top_contributions"]
255+
]
256+
self.all_time_high: HypeTrainAllTimeHigh | None = (
257+
HypeTrainAllTimeHigh(
258+
level=all_time_high["level"],
259+
total=all_time_high["total"],
260+
achieved_at=parse_timestamp(all_time_high["achieved_at"]),
261+
)
262+
if all_time_high is not None
263+
else None
264+
)
265+
self.shared_train: bool = current["is_shared_train"]
266+
self.shared_train_participants: list[PartialUser] = (
267+
[
268+
PartialUser(u["broadcaster_user_id"], u["broadcaster_user_login"], u["broadcaster_user_name"], http=http)
269+
for u in current["shared_train_participants"]
270+
]
271+
if self.shared_train
272+
else []
273+
)
274+
self.shared_all_time_high: HypeTrainAllTimeHigh | None = (
275+
HypeTrainAllTimeHigh(
276+
level=shared_all_time_high["level"],
277+
total=shared_all_time_high["total"],
278+
achieved_at=parse_timestamp(shared_all_time_high["achieved_at"]),
279+
)
280+
if shared_all_time_high is not None
281+
else None
282+
)

twitchio/types_/responses.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@
6666
"GlobalChatBadgesResponseVersions",
6767
"GlobalEmotesResponse",
6868
"GlobalEmotesResponseData",
69+
"HypeTrainEventsResponse",
70+
"HypeTrainStatusResponse",
71+
"HypeTrainStatusResponseData",
72+
"HypeTrainStatusTopContributions",
6973
"OAuthResponses",
7074
"RawResponse",
7175
"RefreshTokenResponse",
@@ -1088,6 +1092,53 @@ class HypeTrainEventsResponse(TypedDict):
10881092
pagination: Pagination
10891093

10901094

1095+
class HypeTrainStatusTopContributions(TypedDict):
1096+
user_id: str
1097+
user_login: str
1098+
user_name: str
1099+
type: Literal["bits", "subscriptions", "other"]
1100+
total: int
1101+
1102+
1103+
class HypeTrainStatusSharedParticipantsData(TypedDict):
1104+
broadcaster_user_id: str
1105+
broadcaster_user_login: str
1106+
broadcaster_user_name: str
1107+
1108+
1109+
class HypeTrainStatusAllTimeHighData(TypedDict):
1110+
level: int
1111+
total: int
1112+
achieved_at: str
1113+
1114+
1115+
class HypeTrainStatusCurrentData(TypedDict):
1116+
id: str
1117+
broadcaster_user_id: str
1118+
broadcaster_user_login: str
1119+
broadcaster_user_name: str
1120+
level: int
1121+
total: int
1122+
progress: int
1123+
goal: int
1124+
top_contributions: list[HypeTrainStatusTopContributions]
1125+
shared_train_participants: list[HypeTrainStatusSharedParticipantsData]
1126+
started_at: str
1127+
expires_at: str
1128+
type: Literal["treasure", "golden_kappa", "regular"]
1129+
is_shared_train: bool
1130+
1131+
1132+
class HypeTrainStatusResponseData(TypedDict):
1133+
current: HypeTrainStatusCurrentData | None
1134+
all_time_high: HypeTrainStatusAllTimeHighData | None
1135+
shared_all_time_high: HypeTrainStatusAllTimeHighData | None
1136+
1137+
1138+
class HypeTrainStatusResponse(TypedDict):
1139+
data: list[HypeTrainStatusResponseData]
1140+
1141+
10911142
class CheckAutomodStatusResponseData(TypedDict):
10921143
msg_id: str
10931144
is_permitted: bool

twitchio/user.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
from .models.clips import Clip, CreatedClip
5656
from .models.eventsub_ import ChannelChatMessageEvent, ChatMessageBadge
5757
from .models.goals import Goal
58-
from .models.hype_train import HypeTrainEvent
58+
from .models.hype_train import HypeTrainEvent, HypeTrainStatus
5959
from .models.moderation import (
6060
AutomodCheckMessage,
6161
AutomodSettings,
@@ -1377,6 +1377,27 @@ def fetch_hype_train_events(
13771377
max_results=max_results,
13781378
)
13791379

1380+
async def fetch_hype_train_status(self) -> HypeTrainStatus | None:
1381+
"""|coro|
1382+
1383+
Fetches the current Hype Train status for the broadcaster.
1384+
1385+
.. note::
1386+
Requires a user access token that includes the ``channel:read:hype_train`` scope.
1387+
1388+
Returns
1389+
-------
1390+
HypeTrainStatus | None
1391+
HypeTrainStatus object if a Hype Train is currently active, otherwise None.
1392+
1393+
"""
1394+
data = await self._http.get_hype_train_status(broadcaster_id=self.id, token_for=self.id)
1395+
1396+
if data["data"][0]["current"] is None:
1397+
return None
1398+
1399+
return HypeTrainStatus(data["data"][0], http=self._http)
1400+
13801401
async def start_raid(self, to_broadcaster: str | int | PartialUser) -> Raid:
13811402
"""|coro|
13821403

0 commit comments

Comments
 (0)