Skip to content

Commit e6d6cf9

Browse files
committed
Merge Eventsub and Helix Predictions
1 parent 01ced9d commit e6d6cf9

File tree

4 files changed

+107
-40
lines changed

4 files changed

+107
-40
lines changed

twitchio/eventsub/payloads.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
"CharityCampaignStopSubscription",
101101
"ChannelPredictionBeginSubscription",
102102
"ChannelPredictionLockSubscription",
103-
"ChannelPredictionProgressubscription",
103+
"ChannelPredictionProgressSubscription",
104104
"ChannelPredictionEndSubscription",
105105
"SharedChatSessionBeginSubscription",
106106
"SharedChatSessionEndSubscription",
@@ -1786,7 +1786,7 @@ def condition(self) -> Condition:
17861786
return {"broadcaster_user_id": self.broadcaster_user_id}
17871787

17881788

1789-
class ChannelPredictionProgressubscription(SubscriptionPayload):
1789+
class ChannelPredictionProgressSubscription(SubscriptionPayload):
17901790
"""The ``channel.prediction.progress`` subscription type sends a notification when users participate in a Prediction on the specified channel.
17911791
17921792
.. important::

twitchio/models/eventsub_.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from twitchio.models.channel_points import CustomReward
3232
from twitchio.models.charity import CharityValues
3333
from twitchio.models.chat import EmoteSet
34+
from twitchio.models.predictions import PredictionOutcome
3435
from twitchio.user import PartialUser
3536
from twitchio.utils import Colour, parse_timestamp
3637

@@ -3143,6 +3144,56 @@ def __repr__(self) -> str:
31433144
return f"<ChannelPollEnd broadcaster={self.broadcaster} id={self.id} title={self.title} started_at={self.started_at} ended_at={self.ended_at}>"
31443145

31453146

3147+
class BaseChannelPrediction(BaseEvent):
3148+
def __init__(
3149+
self,
3150+
payload: ChannelPredictionBeginEvent
3151+
| ChannelPredictionProgressEvent
3152+
| ChannelPredictionLockEvent
3153+
| ChannelPredictionEndEvent,
3154+
*,
3155+
http: HTTPClient,
3156+
) -> None:
3157+
self.id: str = payload["id"]
3158+
self.broadcaster: PartialUser = PartialUser(
3159+
payload["broadcaster_user_id"], payload["broadcaster_user_login"], http=http
3160+
)
3161+
self.title: str = payload["title"]
3162+
self.outcomes: list[PredictionOutcome] = [PredictionOutcome(c, http=http) for c in payload["outcomes"]]
3163+
self.started_at: datetime.datetime = parse_timestamp(payload["started_at"])
3164+
3165+
def __repr__(self) -> str:
3166+
return f"<BaseChannelPrediction id={self.id} title={self.title} started_at={self.started_at}>"
3167+
3168+
3169+
class ChannelPredictionBegin(BaseChannelPrediction):
3170+
subscription_type = "channel.prediction.begin"
3171+
3172+
def __init__(self, data: ChannelPredictionBeginEvent, *, http: HTTPClient) -> None:
3173+
super().__init__(data, http=http)
3174+
3175+
3176+
class ChannelPredictionProgress(BaseChannelPrediction):
3177+
subscription_type = "channel.prediction.progress"
3178+
3179+
def __init__(self, data: ChannelPredictionProgressEvent, *, http: HTTPClient) -> None:
3180+
super().__init__(data, http=http)
3181+
3182+
3183+
class ChannelPredictionLock(BaseChannelPrediction):
3184+
subscription_type = "channel.prediction.lock"
3185+
3186+
def __init__(self, data: ChannelPredictionLockEvent, *, http: HTTPClient) -> None:
3187+
super().__init__(data, http=http)
3188+
3189+
3190+
class ChannelPredictionEnd(BaseChannelPrediction):
3191+
subscription_type = "channel.prediction.end"
3192+
3193+
def __init__(self, data: ChannelPredictionEndEvent, *, http: HTTPClient) -> None:
3194+
super().__init__(data, http=http)
3195+
3196+
31463197
class SuspiciousUserUpdate(BaseEvent):
31473198
"""
31483199
Represents a suspicious user update event.

twitchio/models/predictions.py

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
import datetime
3535

3636
from twitchio.http import HTTPClient
37+
from twitchio.types_.eventsub import (
38+
PredictionOutcomeData,
39+
PredictorData,
40+
)
3741
from twitchio.types_.responses import (
3842
PredictionsResponseData,
3943
PredictionsResponseOutcomes,
@@ -44,16 +48,20 @@
4448

4549

4650
class Prediction:
47-
"""
48-
Represents a Prediction
49-
50-
| Status | Description |
51-
| ----------- | ----------- |
52-
| ACTIVE | The Prediction is running and viewers can make predictions. |
53-
| CANCELED | The broadcaster canceled the Prediction and refunded the Channel Points to the participants. |
54-
| LOCKED | The broadcaster locked the Prediction, which means viewers can no longer make predictions. |
55-
| RESOLVED | The winning outcome was determined and the Channel Points were distributed to the viewers who predicted the correct outcome. |
56-
51+
"""Represents a Prediction
52+
53+
+-----------+----------------------------------------------------------------------------------------------------------------+
54+
| Status | Description |
55+
+===========+================================================================================================================+
56+
| ACTIVE | The Prediction is running and viewers can make predictions. |
57+
+-----------+----------------------------------------------------------------------------------------------------------------+
58+
| CANCELED | The broadcaster canceled the Prediction and refunded the Channel Points to the participants. |
59+
+-----------+----------------------------------------------------------------------------------------------------------------+
60+
| LOCKED | The broadcaster locked the Prediction, which means viewers can no longer make predictions. |
61+
+-----------+----------------------------------------------------------------------------------------------------------------+
62+
| RESOLVED | The winning outcome was determined and the Channel Points were distributed to the viewers who predicted the |
63+
| | correct outcome. |
64+
+-----------+----------------------------------------------------------------------------------------------------------------+
5765
5866
Attributes
5967
----------
@@ -72,9 +80,9 @@ class Prediction:
7280
created_at: datetime.datetime
7381
The datetime of when the prediction began.
7482
ended_at: datetime.datetime | None
75-
The datetime of when the prediction ended. This is None if status is `ACTIVE`.
83+
The datetime of when the prediction ended. This is None if status is ``ACTIVE``.
7684
locked_at: datetime.datetime | None
77-
The datetime of when the prediction locked. If status is not `LOCKED`, this is set to None.
85+
The datetime of when the prediction locked. If status is not ``LOCKED``, this is set to ``None``.
7886
"""
7987

8088
__slots__ = (
@@ -91,7 +99,12 @@ class Prediction:
9199
"locked_at",
92100
)
93101

94-
def __init__(self, data: PredictionsResponseData, *, http: HTTPClient) -> None:
102+
def __init__(
103+
self,
104+
data: PredictionsResponseData,
105+
*,
106+
http: HTTPClient,
107+
) -> None:
95108
self._http = http
96109
self.id: str = data["id"]
97110
self.broadcaster: PartialUser = PartialUser(data["broadcaster_id"], data["broadcaster_login"], http=http)
@@ -116,12 +129,11 @@ async def end_prediction(
116129
token_for: str,
117130
winning_outcome_id: str | None = None,
118131
) -> Prediction:
119-
"""
120-
End an active prediction.
132+
"""End an active prediction.
121133
122134
Parameters
123135
----------
124-
status Literal["RESOLVED", "CANCELED", "LOCKED"]
136+
status: Literal["RESOLVED", "CANCELED", "LOCKED"]
125137
The status to set the prediction to. Possible case-sensitive values are: `RESOLVED`, `CANCELED`, `LOCKED`
126138
token_for: str
127139
User access token that includes the `channel:manage:predictions` scope.
@@ -142,65 +154,69 @@ async def end_prediction(
142154

143155

144156
class PredictionOutcome:
145-
"""
146-
Represents a prediction outcome.
157+
"""Represents a prediction outcome.
147158
148159
Attributes
149160
----------
150161
id: str
151162
An ID that identifies the choice.
152163
title: str
153164
The choice's title.
154-
users: int
165+
users: int | None
155166
The number of unique viewers that chose this outcome.
156-
channel_points: int
167+
channel_points: int | None
157168
The number of Channel Points spent by viewers on this outcome.
158-
top_predictors: int
169+
top_predictors: list[Predictor]
159170
A list of viewers who were the top predictors; otherwise, None if none.
160-
colour: Literal["BLUE", "PINK"]
171+
colour: Literal["BLUE", "PINK", "blue", "pink"]
161172
The number of votes cast using Channel Points.
162173
"""
163174

164175
__slots__ = ("id", "title", "colour", "channel_points", "users", "top_predictors")
165176

166-
def __init__(self, data: PredictionsResponseOutcomes, *, http: HTTPClient) -> None:
177+
def __init__(
178+
self,
179+
data: PredictionsResponseOutcomes | PredictionOutcomeData,
180+
*,
181+
http: HTTPClient,
182+
) -> None:
167183
self.id: str = data["id"]
168184
self.title: str = data["title"]
169-
self.users: int = int(data["users"])
170-
self.channel_points: int = int(data["channel_points"])
171-
self.colour: str = data["color"]
172-
self.top_predictors: list[Predictor] | None = (
173-
[Predictor(d, http=http) for d in data["top_predictors"]] if data["top_predictors"] else None
174-
)
185+
self.colour: Literal["BLUE", "PINK", "blue", "pink"] = data["color"]
186+
users = data.get("users")
187+
self.users: int | None = int(users) if users is not None else None
188+
channel_points = data.get("channel_points")
189+
self.channel_points: int | None = int(channel_points) if channel_points is not None else None
190+
self.top_predictors: list[Predictor] = [Predictor(d, http=http) for d in data.get("top_predictors", [])]
175191

176192
@property
177193
def color(self) -> str:
178194
"""The color of the prediction. Alias to colour."""
179-
return self.color
195+
return self.colour
180196

181197
def __repr__(self) -> str:
182198
return f"<PredictionOutcome id={self.id} title={self.title} channel_points={self.channel_points}>"
183199

184200

185201
class Predictor:
186-
"""
187-
Represents a predictor
202+
"""Represents a predictor
188203
189204
Attributes
190205
-----------
191206
user: PartialUser
192207
The viewer.
193208
channel_points_used: int
194209
Number of Channel Points used by the user.
195-
channel_points_won: int
210+
channel_points_won: int | None
196211
Number of Channel Points won by the user.
197212
"""
198213

199214
__slots__ = ("channel_points_used", "channel_points_won", "user")
200215

201-
def __init__(self, data: PredictionsResponseTopPredictors, *, http: HTTPClient) -> None:
216+
def __init__(self, data: PredictionsResponseTopPredictors | PredictorData, *, http: HTTPClient) -> None:
202217
self.channel_points_used: int = data["channel_points_used"]
203-
self.channel_points_won: int = data["channel_points_won"]
218+
channel_points_won = data.get("channel_points_won")
219+
self.channel_points_won: int | None = int(channel_points_won) if channel_points_won is not None else None
204220
self.user = PartialUser(data["user_id"], data["user_login"], http=http)
205221

206222
def __repr__(self) -> str:

twitchio/types_/eventsub.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -999,16 +999,16 @@ class PredictorData(BaseUserEvent):
999999
class PredictionOutcomeData(TypedDict):
10001000
id: str
10011001
title: str
1002-
color: str
1003-
users: int
1002+
color: Literal["blue", "pink"]
1003+
users: NotRequired[int]
10041004
channel_points: NotRequired[int]
10051005
top_predictors: NotRequired[list[PredictorData]]
10061006

10071007

10081008
class BaseChannelPredictionData(BaseBroadcasterEvent):
10091009
id: str
10101010
title: str
1011-
outcomes: list[PredictorData]
1011+
outcomes: list[PredictionOutcomeData]
10121012
started_at: str
10131013

10141014

0 commit comments

Comments
 (0)