Skip to content

Commit 2554a0d

Browse files
Add support for creating, getting, and resolving predictions (#207)
Resolves #178
1 parent 9af67ca commit 2554a0d

File tree

3 files changed

+199
-1
lines changed

3 files changed

+199
-1
lines changed

twitchio/http.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,63 @@ async def update_reward_redemption_status(
520520
)
521521
)
522522

523+
async def get_predictions(
524+
self,
525+
token: str,
526+
broadcaster_id: int,
527+
prediction_id: str = None,
528+
):
529+
params = [("broadcaster_id", str(broadcaster_id))]
530+
531+
if prediction_id:
532+
params.extend(("prediction_id", prediction_id))
533+
534+
return await self.request(Route("GET", "predictions", query=params, token=token), paginate=False)
535+
536+
async def patch_prediction(
537+
self,
538+
token: str,
539+
broadcaster_id: int,
540+
prediction_id: str,
541+
status: str,
542+
winning_outcome_id: str = None):
543+
body = {
544+
"broadcaster_id": str(broadcaster_id),
545+
"id": prediction_id,
546+
"status": status,
547+
}
548+
549+
if status == "RESOLVED":
550+
body["winning_outcome_id"] = winning_outcome_id
551+
552+
return await self.request(
553+
Route(
554+
"PATCH",
555+
"predictions",
556+
body=body,
557+
token=token,
558+
)
559+
)
560+
561+
async def post_prediction(self, token: str, broadcaster_id: int, title: str, blue_outcome: str, pink_outcome: str, prediction_window: int):
562+
body = {
563+
"broadcaster_id": broadcaster_id,
564+
"title": title,
565+
"prediction_window": prediction_window,
566+
"outcomes": [
567+
{
568+
"title": blue_outcome,
569+
},
570+
{
571+
"title": pink_outcome,
572+
}
573+
],
574+
}
575+
return await self.request(
576+
Route("POST", "predictions", body=body, token=token),
577+
paginate=False,
578+
)
579+
523580
async def post_create_clip(self, token: str, broadcaster_id: int, has_delay=False):
524581
return await self.request(
525582
Route("POST", "clips", query=[("broadcaster_id", broadcaster_id), ("has_delay", has_delay)], token=token),

twitchio/models.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"Video",
5656
"Tag",
5757
"WebhookSubscription",
58+
"Prediction",
5859
)
5960

6061

@@ -581,3 +582,62 @@ def __init__(self, http: "TwitchHTTP", data: dict):
581582

582583
def __repr__(self):
583584
return f"<ChannelInfo user={self.user} game_id={self.game_id} game_name={self.game_name} title={self.title} language={self.language} delay={self.delay}>"
585+
586+
587+
class Prediction:
588+
589+
__slots__ = ("user", "prediction_id", "title", "winning_outcome_id", "outcomes", "prediction_window", "prediction_status", "created_at", "ended_at", "locked_at")
590+
591+
def __init__(self, http: "TwitchHTTP", data: dict):
592+
self.user = PartialUser(http, data["broadcaster_id"], data["broadcaster_name"])
593+
self.prediction_id: str = data["id"]
594+
self.title: str = data["title"]
595+
self.winning_outcome_id: str = data["winning_outcome_id"]
596+
self.outcomes: List[PredictionOutcome] = [PredictionOutcome(http, x) for x in data["outcomes"]]
597+
self.prediction_window: int = data["prediction_window"]
598+
self.prediction_status: str = data["status"]
599+
self.created_at = self._parse_time(data, "created_at")
600+
self.ended_at = self._parse_time(data, "ended_at")
601+
self.locked_at = self._parse_time(data, "locked_at")
602+
603+
def _parse_time(self, data, field) -> Optional["Datetime"]:
604+
if field not in data or data[field] == None:
605+
return None
606+
607+
time = data[field].split(".")[0]
608+
return datetime.datetime.fromisoformat(time)
609+
610+
def __repr__(self):
611+
return f"<Prediction user={self.user} prediction_id={self.prediction_id} winning_outcome_id={self.winning_outcome_id} title={self.title}>"
612+
613+
class Predictor:
614+
615+
__slots__ = ("outcome_id", "title", "channel_points", "color")
616+
617+
def __init__(self, http: "TwitchHTTP", data: dict):
618+
self.channel_points_used: int = data["channel_points_used"]
619+
self.channel_points_won: int = data["channel_points_won"]
620+
self.user = PartialUser(http, data["user"]["id"], data["user"]["name"])
621+
622+
class PredictionOutcome:
623+
624+
__slots__ = ("outcome_id", "title", "channel_points", "color", "users", "top_predictors")
625+
626+
def __init__(self, http: "TwitchHTTP", data: dict):
627+
self.outcome_id: str = data["id"]
628+
self.title: str = data["title"]
629+
self.channel_points: int = data["channel_points"]
630+
self.color: str = data["color"]
631+
self.users: int = data["users"]
632+
if data["top_predictors"]:
633+
self.top_predictors: List[Predictor] = [Predictor(http, x) for x in data["top_predictors"]]
634+
else:
635+
self.top_predictors: List[Predictor] = None
636+
637+
@property
638+
def colour(self) -> str:
639+
"""The colour of the prediction. Alias to color."""
640+
return self.color
641+
642+
def __repr__(self):
643+
return f"<PredictionOutcome outcome_id={self.outcome_id} title={self.title} channel_points={self.channel_points} color={self.color}>"

twitchio/user.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
if TYPE_CHECKING:
3535
from .http import TwitchHTTP
3636
from .channel import Channel
37-
from .models import BitsLeaderboard, Clip, ExtensionBuilder, Tag, FollowEvent
37+
from .models import BitsLeaderboard, Clip, ExtensionBuilder, Tag, FollowEvent, Prediction
3838

3939

4040
__all__ = (
@@ -609,6 +609,87 @@ async def fetch_videos(self, period="all", sort="time", type="all", language=Non
609609
data = await self._http.get_videos(user_id=str(self.id), period=period, sort=sort, type=type, language=language)
610610
return [Video(self._http, x, self) for x in data]
611611

612+
async def end_prediction(self, token: str, prediction_id: str, status: str, winning_outcome_id: str = None) -> "Prediction":
613+
"""|coro|
614+
End a prediction with an outcome.
615+
616+
Parameters
617+
-----------
618+
token: :class:`str`
619+
An oauth token with the channel:manage:predictions scope
620+
prediction_id: :class:`str`
621+
ID of the prediction to end.
622+
623+
Returns
624+
--------
625+
:class:`twitchio.Prediction`
626+
"""
627+
from .models import Prediction
628+
629+
data = await self._http.patch_prediction(
630+
token,
631+
broadcaster_id=str(self.id),
632+
prediction_id=prediction_id,
633+
status=status,
634+
winning_outcome_id=winning_outcome_id)
635+
return Prediction(self._http, data[0])
636+
637+
async def get_predictions(self, token: str, prediction_id: str = None) -> List["Prediction"]:
638+
"""|coro|
639+
Gets information on a prediction or the list of predictions
640+
if none is provided.
641+
642+
Parameters
643+
-----------
644+
token: :class:`str`
645+
An oauth token with the channel:manage:predictions scope
646+
prediction_id: :class:`str`
647+
ID of the prediction to receive information about.
648+
649+
Returns
650+
--------
651+
:class:`twitchio.Prediction`
652+
"""
653+
from .models import Prediction
654+
655+
data = await self._http.get_predictions(
656+
token,
657+
broadcaster_id=str(self.id),
658+
prediction_id=prediction_id)
659+
return [Prediction(self._http, d) for d in data]
660+
661+
async def create_prediction(self, token: str, title: str, blue_outcome: str, pink_outcome: str, prediction_window: int) -> "Prediction":
662+
"""|coro|
663+
Creates a prediction for the channel.
664+
665+
Parameters
666+
-----------
667+
token: :class:`str`
668+
An oauth token with the channel:manage:predictions scope
669+
title: :class:`str`
670+
Title for the prediction (max of 45 characters)
671+
blue_outcome: :class:`str`
672+
Text for the first outcome people can vote for. (max 25 characters)
673+
pink_outcome: :class:`str`
674+
Text for the second outcome people can vote for. (max 25 characters)
675+
prediction_window: :class:`int`
676+
Total duration for the prediction (in seconds)
677+
678+
Returns
679+
--------
680+
:class:`twitchio.Prediction`
681+
"""
682+
from .models import Prediction
683+
684+
data = await self._http.post_prediction(
685+
token,
686+
broadcaster_id=str(self.id),
687+
title=title,
688+
blue_outcome=blue_outcome,
689+
pink_outcome=pink_outcome,
690+
prediction_window=prediction_window)
691+
return Prediction(self._http, data[0])
692+
612693

613694
class BitLeaderboardUser(PartialUser):
614695

0 commit comments

Comments
 (0)