tw_api/
helix.rs

1use crate::auth::{Token, TokenStorage, TokenType};
2use anyhow::bail;
3use anyhow::Result;
4
5use reqwest::Client as HttpClient;
6use reqwest::{Method, Response};
7use serde::{Deserialize, Serialize};
8
9#[derive(Serialize, Deserialize)]
10pub struct TwitchData<T> {
11    pub data: Vec<T>,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Pagination {
16    pub cursor: Option<String>,
17}
18
19#[derive(Debug, Clone)]
20pub struct Client<T: TokenStorage> {
21    pub client_id: String,
22    pub client_secret: String,
23    pub token: Token,
24    pub http_client: HttpClient,
25    pub token_storage: T,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct User {
30    pub id: String,
31    pub login: String,
32    pub display_name: String,
33    pub r#type: String,
34    pub broadcaster_type: String,
35    pub description: String,
36    pub profile_image_url: String,
37    pub offline_image_url: String,
38    pub view_count: i64,
39    pub email: Option<String>,
40    pub created_at: String,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct RewardImage {
45    pub url_1x: String,
46    pub url_2x: String,
47    pub url_4x: String,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct RewardMaxPerStream {
52    pub is_enabled: bool,
53    pub max_per_stream: i64,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct RewardMaxPerUserPerStream {
58    pub is_enabled: bool,
59    pub max_per_user_per_stream: i64,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct RewardGlobalCooldown {
64    pub is_enabled: bool,
65    pub global_cooldown_seconds: i64,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct Reward {
70    pub broadcaster_id: String,
71    pub broadcaster_login: String,
72    pub broadcaster_name: String,
73    pub id: String,
74    pub title: String,
75    pub prompt: String,
76    pub cost: i64,
77    pub image: Option<RewardImage>,
78    pub default_image: RewardImage,
79    pub background_color: String,
80    pub is_enabled: bool,
81    pub is_user_input_required: bool,
82    pub max_per_stream_setting: RewardMaxPerStream,
83    pub max_per_user_per_stream_setting: RewardMaxPerUserPerStream,
84    pub global_cooldown_setting: RewardGlobalCooldown,
85    pub is_paused: bool,
86    pub is_in_stock: bool,
87    pub should_redemptions_skip_request_queue: bool,
88    pub redemptions_redeemed_current_stream: Option<i64>,
89    pub cooldown_expires_at: Option<String>,
90}
91
92#[derive(Debug, Default, Clone, Serialize, Deserialize)]
93pub struct RewardCreate {
94    pub title: String,
95    pub cost: i64,
96    pub prompt: Option<String>,
97    pub is_enabled: Option<bool>,
98    pub background_color: Option<String>,
99    pub is_user_input_required: Option<bool>,
100    pub is_max_per_stream_enabled: Option<bool>,
101    pub max_per_stream: Option<i64>,
102    pub is_max_per_user_per_stream_enabled: Option<bool>,
103    pub max_per_user_per_stream: Option<i64>,
104    pub is_global_cooldown_enabled: Option<bool>,
105    pub global_cooldown_seconds: Option<i64>,
106    pub should_redemptions_skip_request_queue: Option<bool>,
107}
108
109#[derive(Debug, Default, Clone, Serialize, Deserialize)]
110pub struct RewardUpdate {
111    pub title: Option<String>,
112    pub cost: Option<i64>,
113    pub prompt: Option<String>,
114    pub is_enabled: Option<bool>,
115    pub background_color: Option<String>,
116    pub is_user_input_required: Option<bool>,
117    pub is_max_per_stream_enabled: Option<bool>,
118    pub max_per_stream: Option<i64>,
119    pub is_max_per_user_per_stream_enabled: Option<bool>,
120    pub max_per_user_per_stream: Option<i64>,
121    pub is_global_cooldown_enabled: Option<bool>,
122    pub global_cooldown_seconds: Option<i64>,
123    pub is_paused: Option<bool>,
124    pub should_redemptions_skip_request_queue: Option<bool>,
125}
126
127#[derive(Debug, Default, Clone, Serialize, Deserialize)]
128pub struct RedemptionStatus {
129    pub status: String,
130}
131
132#[derive(Debug, Default, Clone, Serialize, Deserialize)]
133pub struct EventSubTransport {
134    pub method: String,
135    pub callback: Option<String>,
136    pub secret: Option<String>,
137    pub session_id: Option<String>,
138    pub connected_at: Option<String>,
139}
140
141#[derive(Debug, Default, Clone, Serialize, Deserialize)]
142pub struct EventSubCondition {
143    pub broadcaster_id: Option<String>,
144    pub broadcaster_user_id: Option<String>,
145    pub moderator_user_id: Option<String>,
146    pub user_id: Option<String>,
147    pub from_broadcaster_user_id: Option<String>,
148    pub to_broadcaster_user_id: Option<String>,
149    pub reward_id: Option<String>,
150}
151
152#[derive(Debug, Default, Clone, Serialize, Deserialize)]
153pub struct EventSub {
154    pub id: String,
155    pub status: String,
156    pub r#type: String,
157    pub version: String,
158    pub condition: EventSubCondition,
159    pub created_at: String,
160    pub transport: EventSubTransport,
161    pub cost: i64,
162}
163
164#[derive(Debug, Default, Clone, Serialize, Deserialize)]
165pub struct EventSubCreate {
166    pub r#type: String,
167    pub version: String,
168    pub condition: EventSubCondition,
169    pub transport: EventSubTransport,
170}
171
172#[derive(Debug, Default, Clone, Serialize, Deserialize)]
173pub struct BanUser {
174    pub user_id: String,
175    pub duration: i64,
176    pub reason: Option<String>,
177}
178
179#[derive(Debug, Default, Clone, Serialize, Deserialize)]
180pub struct BanUserObj {
181    pub data: BanUser,
182}
183
184#[derive(Debug, Default, Clone, Serialize, Deserialize)]
185pub struct BannedUser {
186    pub broadcaster_id: String,
187    pub moderator_id: String,
188    pub user_id: String,
189    pub created_at: String,
190    pub end_time: String,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct ChannelInformation {
195    pub broadcaster_id: String,
196    pub broadcaster_login: String,
197    pub broadcaster_name: String,
198    pub broadcaster_language: String,
199    pub game_name: String,
200    pub game_id: String,
201    pub title: String,
202    pub delay: i64,
203    pub tags: Vec<String>,
204    pub content_classification_labels: Vec<String>,
205    pub is_branded_content: bool,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct PredictionTopPredictor {
210    pub user_id: String,
211    pub user_name: String,
212    pub user_login: String,
213    pub channel_points_used: i64,
214    pub channel_points_won: i64,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct PredictionOutcome {
219    pub id: String,
220    pub title: String,
221    pub users: i64,
222    pub channel_points: i64,
223    pub top_predictors: Option<Vec<PredictionTopPredictor>>,
224    pub color: String,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct Prediction {
229    pub id: String,
230    pub broadcaster_id: String,
231    pub broadcaster_name: String,
232    pub broadcaster_login: String,
233    pub title: String,
234    pub winning_outcome_id: Option<String>,
235    pub outcomes: Vec<PredictionOutcome>,
236    pub prediction_window: i64,
237    pub status: String,
238    pub created_at: String,
239    pub ended_at: Option<String>,
240    pub locked_at: Option<String>,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct PredictionOutcomeCreate {
245    pub title: String,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct PredictionCreate {
250    pub broadcaster_id: String,
251    pub title: String,
252    pub outcomes: Vec<PredictionOutcomeCreate>,
253    pub prediction_window: i64,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
257pub struct PredictionEnd {
258    pub broadcaster_id: String,
259    pub id: String,
260    pub status: String,
261    pub winning_outcome_id: Option<String>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct CommercialStart {
266    pub broadcaster_id: String,
267    pub length: i64,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct Commercial {
272    pub length: i64,
273    pub message: String,
274    pub retry_after: i64,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct Announcement {
279    pub message: String,
280    pub color: Option<String>,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct Whisper {
285    pub message: String,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct Stream {
290    pub id: String,
291    pub user_id: String,
292    pub user_login: String,
293    pub user_name: String,
294    pub game_id: String,
295    pub game_name: String,
296    pub r#type: String,
297    pub title: String,
298    pub tags: Vec<String>,
299    pub viewer_count: i64,
300    pub started_at: String,
301    pub language: String,
302    pub thumbnail_url: String,
303    pub is_mature: bool,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct ChannelFollowers {
308    pub followed_at: String,
309    pub user_id: String,
310    pub user_login: String,
311    pub user_name: String,
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct ChannelFollowersData {
316    pub data: Vec<ChannelFollowers>,
317    pub pagination: Pagination,
318    pub total: i64,
319}
320
321pub enum VideoId {
322    Id(String),
323    UserId(String),
324    GameId(String),
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct VideoMutedSegment {
329    pub duration: i64,
330    pub offset: i64,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct Video {
335    pub id: String,
336    pub stream_id: String,
337    pub user_id: String,
338    pub user_login: String,
339    pub user_name: String,
340    pub title: String,
341    pub description: String,
342    pub created_at: String,
343    pub published_at: String,
344    pub url: String,
345    pub thumbnail_url: String,
346    pub viewable: String,
347    pub view_count: i64,
348    pub language: String,
349    pub r#type: String,
350    pub duration: String,
351    pub muted_segments: Option<Vec<VideoMutedSegment>>,
352}
353
354impl<T: TokenStorage> Client<T> {
355    pub async fn http_request<T2: serde::Serialize>(
356        &mut self,
357        method: Method,
358        uri: String,
359        data_json: Option<T2>,
360        data_form: Option<String>,
361    ) -> Result<Response> {
362        let mut req = self.http_client.request(method, uri);
363
364        req = match data_json {
365            Some(data_json) => req.json(&data_json),
366            None => match data_form {
367                Some(data_form) => req.body(data_form),
368                None => req,
369            },
370        };
371
372        let req = req
373            .timeout(core::time::Duration::from_secs(5))
374            .header(
375                "Authorization",
376                format!("Bearer {0}", self.token.access_token),
377            )
378            .header("Client-Id", self.client_id.clone());
379
380        Ok(req.send().await?)
381    }
382
383    pub async fn request<T1: serde::Serialize + std::clone::Clone>(
384        &mut self,
385        method: Method,
386        uri: String,
387        data_json: Option<T1>,
388        data_form: Option<String>,
389    ) -> Result<Response> {
390        let mut res = self
391            .http_request(
392                method.clone(),
393                uri.clone(),
394                data_json.clone(),
395                data_form.clone(),
396            )
397            .await?;
398
399        if res.status() == reqwest::StatusCode::UNAUTHORIZED {
400            //Token invalid, get new? If fail, or fail again, return error.
401            self.refresh_token().await?;
402            res = self.http_request(method, uri, data_json, data_form).await?;
403        }
404
405        Ok(res)
406    }
407
408    pub async fn request_result<
409        T1: for<'de> serde::Deserialize<'de>,
410        T2: serde::Serialize + std::clone::Clone,
411    >(
412        &mut self,
413        method: Method,
414        uri: String,
415        data_json: Option<T2>,
416        data_form: Option<String>,
417    ) -> Result<T1> {
418        let res = self
419            .request::<T2>(method, uri, data_json, data_form)
420            .await?;
421        Ok(res.json::<T1>().await?)
422    }
423
424    pub async fn get<T1: for<'de> serde::Deserialize<'de>>(&mut self, uri: String) -> Result<T1> {
425        return self
426            .request_result::<T1, String>(Method::GET, uri, None, None)
427            .await;
428    }
429
430    pub async fn post_empty(&mut self, uri: String) -> Result<()> {
431        match self.request::<String>(Method::POST, uri, None, None).await {
432            Ok(..) => Ok(()),
433            Err(e) => Err(e),
434        }
435    }
436
437    pub async fn post_form<T1: for<'de> serde::Deserialize<'de>>(
438        &mut self,
439        uri: String,
440        data: String,
441    ) -> Result<T1> {
442        return self
443            .request_result::<T1, String>(Method::POST, uri, None, Some(data))
444            .await;
445    }
446
447    pub async fn post_json<
448        T1: for<'de> serde::Deserialize<'de>,
449        T2: serde::Serialize + std::clone::Clone,
450    >(
451        &mut self,
452        uri: String,
453        data: T2,
454    ) -> Result<T1> {
455        return self
456            .request_result::<T1, T2>(Method::POST, uri, Some(data), None)
457            .await;
458    }
459
460    pub async fn post_json_empty<T1: serde::Serialize + std::clone::Clone>(
461        &mut self,
462        uri: String,
463        data: T1,
464    ) -> Result<()> {
465        match self
466            .request::<T1>(Method::POST, uri, Some(data), None)
467            .await
468        {
469            Ok(..) => Ok(()),
470            Err(e) => Err(e),
471        }
472    }
473
474    pub async fn patch_json<
475        T1: for<'de> serde::Deserialize<'de>,
476        T2: serde::Serialize + std::clone::Clone,
477    >(
478        &mut self,
479        uri: String,
480        data: T2,
481    ) -> Result<T1> {
482        return self
483            .request_result::<T1, T2>(Method::PATCH, uri, Some(data), None)
484            .await;
485    }
486
487    pub async fn delete(&mut self, uri: String) -> Result<()> {
488        self.request::<String>(Method::DELETE, uri, None, None)
489            .await?;
490        Ok(())
491    }
492
493    pub async fn get_token_user(&mut self) -> Result<User> {
494        match &self.token.user {
495            Some(v) => Ok(v.clone()),
496            None => {
497                if self.token.token_type == TokenType::UserAccessToken && self.token.user.is_none()
498                {
499                    let user = self.get_user().await?;
500                    self.token.user = Some(user.clone());
501                    return Ok(user);
502                }
503
504                bail!("No User");
505            }
506        }
507    }
508
509    pub async fn get_token_user_id(&mut self) -> Result<String> {
510        match &self.token.user {
511            Some(v) => Ok(v.id.clone()),
512            None => {
513                let user = self.get_token_user().await?;
514                return Ok(user.id.clone());
515            }
516        }
517    }
518
519    pub async fn get_token_user_login(&mut self) -> Result<String> {
520        match &self.token.user {
521            Some(v) => Ok(v.id.clone()),
522            None => {
523                let user = self.get_token_user().await?;
524                return Ok(user.login.clone());
525            }
526        }
527    }
528
529    pub async fn get_users_by_ids(&mut self, user_ids: Vec<String>) -> Result<Vec<User>> {
530        Ok(self
531            .get::<TwitchData<User>>(format!(
532                "https://api.twitch.tv/helix/users?id={0}",
533                user_ids.join("&id=")
534            ))
535            .await?
536            .data)
537    }
538
539    pub async fn get_users_by_logins(&mut self, user_logins: Vec<String>) -> Result<Vec<User>> {
540        Ok(self
541            .get::<TwitchData<User>>(format!(
542                "https://api.twitch.tv/helix/users?login={0}",
543                user_logins.join("&login=")
544            ))
545            .await?
546            .data)
547    }
548
549    pub async fn get_user_by_id(&mut self, user_id: String) -> Result<User> {
550        match self.get_users_by_ids(vec![user_id]).await?.first() {
551            Some(user) => Ok(user.clone()),
552            None => bail!("No User found"),
553        }
554    }
555
556    pub async fn get_user_by_login(&mut self, user_login: String) -> Result<User> {
557        match self.get_users_by_logins(vec![user_login]).await?.first() {
558            Some(user) => Ok(user.clone()),
559            None => bail!("No User found"),
560        }
561    }
562
563    pub async fn get_user(&mut self) -> Result<User> {
564        match self
565            .get::<TwitchData<User>>("https://api.twitch.tv/helix/users".to_string())
566            .await?
567            .data
568            .first()
569        {
570            Some(user) => Ok(user.clone()),
571            None => bail!("No User found"),
572        }
573    }
574
575    pub async fn create_custom_reward(&mut self, reward: &RewardCreate) -> Result<Reward> {
576        let broadcaster_id = self.get_token_user_id().await?;
577        match self
578                .post_json::<TwitchData<Reward>, _>(format!("https://api.twitch.tv/helix/channel_points/custom_rewards?broadcaster_id={broadcaster_id}"), reward)
579                .await?
580                .data
581                .first()
582            {
583                Some(reward) => Ok(reward.clone()),
584                None => bail!("No User found"),
585            }
586    }
587
588    pub async fn update_custom_reward(
589        &mut self,
590        id: String,
591        reward: &RewardUpdate,
592    ) -> Result<Reward> {
593        let broadcaster_id = self.get_token_user_id().await?;
594        match self
595                .patch_json::<TwitchData<Reward>, _>(format!("https://api.twitch.tv/helix/channel_points/custom_rewards?broadcaster_id={broadcaster_id}&id={id}"), reward)
596                .await?
597                .data
598                .first()
599            {
600                Some(reward) => Ok(reward.clone()),
601                None => bail!("No User found"),
602            }
603    }
604
605    pub async fn get_custom_rewards(&mut self, ids: Vec<String>) -> Result<Vec<Reward>> {
606        let broadcaster_id = self.get_token_user_id().await?;
607        Ok(self
608                .get::<TwitchData<Reward>>(format!(
609                    "https://api.twitch.tv/helix/channel_points/custom_rewards?broadcaster_id={broadcaster_id}{0}",
610                    if ids.len() > 0 { format!("&id={0}", ids.join("&id=") ) } else { "".to_string() }
611                ))
612                .await?
613                .data)
614    }
615
616    pub async fn get_custom_reward(&mut self, id: String) -> Result<Reward> {
617        match self.get_custom_rewards(vec![id]).await?.first() {
618            Some(reward) => Ok(reward.clone()),
619            None => bail!("No Reward found"),
620        }
621    }
622
623    pub async fn delete_custom_reward(&mut self, id: String) -> Result<()> {
624        let broadcaster_id = self.get_token_user_id().await?;
625        Ok(self
626                .delete(format!(
627                    "https://api.twitch.tv/helix/channel_points/custom_rewards?broadcaster_id={broadcaster_id}&id={id}"
628                ))
629                .await?)
630    }
631
632    pub async fn update_redemptions_status(
633        &mut self,
634        id: &String,
635        redemptions: Vec<String>,
636        status: &RedemptionStatus,
637    ) -> Result<Vec<RedemptionStatus>> {
638        let broadcaster_id = self.get_token_user_id().await?;
639        Ok(self
640                .patch_json::<TwitchData<RedemptionStatus>, _>(format!(
641                    "https://api.twitch.tv/helix/channel_points/custom_rewards/redemptions?broadcaster_id={broadcaster_id}&reward_id={id}{0}",
642                    format!("&id={0}", redemptions.join("&id=") )
643                ), status)
644                .await?
645                .data)
646    }
647
648    pub async fn update_redemption_status(
649        &mut self,
650        id: &String,
651        redemption: &String,
652        status: &RedemptionStatus,
653    ) -> Result<RedemptionStatus> {
654        match self
655            .update_redemptions_status(id, vec![redemption.clone()], status)
656            .await?
657            .first()
658        {
659            Some(status) => Ok(status.clone()),
660            None => bail!("No Redemption found"),
661        }
662    }
663
664    pub async fn create_eventsub_subscription(
665        &mut self,
666        eventsub: &EventSubCreate,
667    ) -> Result<EventSub> {
668        match self
669            .post_json::<TwitchData<EventSub>, _>(
670                format!("https://api.twitch.tv/helix/eventsub/subscriptions"),
671                eventsub,
672            )
673            .await?
674            .data
675            .first()
676        {
677            Some(eventsub) => Ok(eventsub.clone()),
678            None => bail!("No EventSub found"),
679        }
680    }
681
682    pub async fn delete_eventsub_subscription(&mut self, id: String) -> Result<()> {
683        Ok(self
684            .delete(format!(
685                "https://api.twitch.tv/helix/eventsub/subscriptions?id={id}"
686            ))
687            .await?)
688    }
689
690    pub async fn add_channel_moderator(&mut self, id: String) -> Result<()> {
691        let broadcaster_id = self.get_token_user_id().await?;
692        Ok(self
693                .post_empty(format!(
694                    "https://api.twitch.tv/helix/moderation/moderators?broadcaster_id={broadcaster_id}&user_id={id}"
695                ))
696                .await?)
697    }
698
699    pub async fn remove_channel_moderator(&mut self, id: String) -> Result<()> {
700        let broadcaster_id = self.get_token_user_id().await?;
701        Ok(self
702                .delete(format!(
703                    "https://api.twitch.tv/helix/moderation/moderators?broadcaster_id={broadcaster_id}&user_id={id}"
704                ))
705                .await?)
706    }
707
708    pub async fn ban_user(
709        &mut self,
710        broadcaster_id: String,
711        banuser: &BanUser,
712    ) -> Result<BannedUser> {
713        let moderator_id = self.get_token_user_id().await?;
714        match self
715                .post_json::<TwitchData<BannedUser>, _>(
716                    format!("https://api.twitch.tv/helix/moderation/bans?moderator_id={moderator_id}&broadcaster_id={broadcaster_id}"),
717                    BanUserObj {
718                        data: banuser.clone()
719                    },
720                )
721                .await?
722                .data
723                .first()
724            {
725                Some(banneduser) => Ok(banneduser.clone()),
726                None => bail!("Ban User failed"),
727            }
728    }
729
730    pub async fn unban_user(&mut self, broadcaster_id: String, user_id: String) -> Result<()> {
731        let moderator_id = self.get_token_user_id().await?;
732        Ok(self
733                .delete(format!(
734                    "https://api.twitch.tv/helix/moderation/bans?moderator_id={moderator_id}&broadcaster_id={broadcaster_id}&user_id={user_id}"
735                ))
736                .await?)
737    }
738
739    pub async fn shoutout(
740        &mut self,
741        from_broadcaster_id: String,
742        to_broadcaster_id: String,
743    ) -> Result<()> {
744        let moderator_id = self.get_token_user_id().await?;
745        Ok(self
746                .post_empty(format!(
747                    "https://api.twitch.tv/helix/chat/shoutouts?from_broadcaster_id={from_broadcaster_id}&to_broadcaster_id={to_broadcaster_id}&moderator_id={moderator_id}"
748                ))
749                .await?)
750    }
751
752    pub async fn get_channel_information(
753        &mut self,
754        broadcaster_ids: Vec<String>,
755    ) -> Result<Vec<ChannelInformation>> {
756        Ok(self
757            .get::<TwitchData<ChannelInformation>>(format!(
758                "https://api.twitch.tv/helix/channels?{0}",
759                if broadcaster_ids.len() > 0 {
760                    format!(
761                        "broadcaster_id={0}",
762                        broadcaster_ids.join("&broadcaster_id=")
763                    )
764                } else {
765                    "".to_string()
766                }
767            ))
768            .await?
769            .data)
770    }
771
772    pub async fn whisper(&mut self, to_user_id: String, message: String) -> Result<()> {
773        let from_user_id = self.get_token_user_id().await?;
774        Ok(self
775                .post_json_empty(
776                    format!("https://api.twitch.tv/helix/whispers?from_user_id={from_user_id}&to_user_id={to_user_id}"),
777                    Whisper {
778                        message: message
779                    },
780                )
781                .await?)
782    }
783
784    pub async fn get_predictions(
785        &mut self,
786        id: Option<String>,
787        first: Option<String>,
788        after: Option<String>,
789    ) -> Result<Vec<Prediction>> {
790        let broadcaster_id = self.get_token_user_id().await?;
791        Ok(self
792            .get::<TwitchData<Prediction>>(format!(
793                "https://api.twitch.tv/helix/predictions?broadcaster_id={broadcaster_id}{0}{1}{2}",
794                if let Some(id) = id {
795                    format!("&id={id}")
796                } else {
797                    "".to_string()
798                },
799                if let Some(first) = first {
800                    format!("&first={first}")
801                } else {
802                    "".to_string()
803                },
804                if let Some(after) = after {
805                    format!("&after={after}")
806                } else {
807                    "".to_string()
808                },
809            ))
810            .await?
811            .data)
812    }
813
814    pub async fn create_prediction(
815        &mut self,
816        title: String,
817        outcomes: Vec<String>,
818        prediction_window: i64,
819    ) -> Result<Prediction> {
820        let broadcaster_id = self.get_token_user_id().await?;
821        match self
822            .post_json::<TwitchData<Prediction>, _>(
823                "https://api.twitch.tv/helix/predictions".to_string(),
824                PredictionCreate {
825                    broadcaster_id: broadcaster_id,
826                    title: title,
827                    outcomes: outcomes
828                        .into_iter()
829                        .map(|o| PredictionOutcomeCreate { title: o })
830                        .collect(),
831                    prediction_window: prediction_window,
832                },
833            )
834            .await?
835            .data
836            .first()
837        {
838            Some(prediction) => Ok(prediction.clone()),
839            None => bail!("Create Prediction failed"),
840        }
841    }
842
843    pub async fn end_prediction(
844        &mut self,
845        id: String,
846        status: String,
847        winning_outcome_id: Option<String>,
848    ) -> Result<Prediction> {
849        let broadcaster_id = self.get_token_user_id().await?;
850        match self
851            .patch_json::<TwitchData<Prediction>, _>(
852                "https://api.twitch.tv/helix/predictions".to_string(),
853                PredictionEnd {
854                    broadcaster_id: broadcaster_id,
855                    id: id,
856                    status: status,
857                    winning_outcome_id: winning_outcome_id,
858                },
859            )
860            .await?
861            .data
862            .first()
863        {
864            Some(prediction) => Ok(prediction.clone()),
865            None => bail!("End Prediction failed"),
866        }
867    }
868
869    pub async fn send_chat_announcement(
870        &mut self,
871        broadcaster_id: String,
872        message: String,
873        color: Option<String>,
874    ) -> Result<()> {
875        let moderator_id = self.get_token_user_id().await?;
876        Ok(self
877                .post_json_empty(
878                    format!("https://api.twitch.tv/helix/chat/announcements?broadcaster_id={broadcaster_id}&moderator_id={moderator_id}"),
879                    Announcement {
880                        message: message,
881                        color: color,
882                    }
883                )
884                .await?)
885    }
886
887    pub async fn start_commercial(&mut self, length: i64) -> Result<Commercial> {
888        let broadcaster_id = self.get_token_user_id().await?;
889        match self
890            .post_json::<TwitchData<Commercial>, _>(
891                "https://api.twitch.tv/helix/channels/commercial".to_string(),
892                CommercialStart {
893                    broadcaster_id: broadcaster_id,
894                    length: length,
895                },
896            )
897            .await?
898            .data
899            .first()
900        {
901            Some(commercial) => Ok(commercial.clone()),
902            None => bail!("Start Commercial failed"),
903        }
904    }
905
906    pub async fn get_streams(
907        &mut self,
908        user_ids: Option<Vec<String>>,
909        user_logins: Option<Vec<String>>,
910        game_ids: Option<Vec<String>>,
911        r#type: Option<String>,
912        languages: Option<Vec<String>>,
913        first: Option<i64>,
914        before: Option<String>,
915        after: Option<String>,
916    ) -> Result<Vec<Stream>> {
917        Ok(self
918            .get::<TwitchData<Stream>>(format!(
919                "https://api.twitch.tv/helix/streams?{0}{1}{2}{3}{4}{5}{6}{7}",
920                if let Some(user_ids) = user_ids {
921                    format!("&user_id={}", user_ids.join("&user_id="))
922                } else {
923                    "".to_string()
924                },
925                if let Some(user_logins) = user_logins {
926                    format!("&user_login={}", user_logins.join("&user_login="))
927                } else {
928                    "".to_string()
929                },
930                if let Some(game_ids) = game_ids {
931                    format!("&game_id={}", game_ids.join("&game_id="))
932                } else {
933                    "".to_string()
934                },
935                if let Some(type_) = r#type {
936                    format!("&type={type_}")
937                } else {
938                    "".to_string()
939                },
940                if let Some(languages) = languages {
941                    format!("&language={}", languages.join("&language="))
942                } else {
943                    "".to_string()
944                },
945                if let Some(first) = first {
946                    format!("&first={first}")
947                } else {
948                    "".to_string()
949                },
950                if let Some(before) = before {
951                    format!("&before={before}")
952                } else {
953                    "".to_string()
954                },
955                if let Some(after) = after {
956                    format!("&after={after}")
957                } else {
958                    "".to_string()
959                },
960            ))
961            .await?
962            .data)
963    }
964
965    pub async fn get_stream(&mut self) -> Result<Stream> {
966        let broadcaster_id = self.get_token_user_id().await?;
967        match self
968            .get_streams(
969                vec![broadcaster_id].into(),
970                None,
971                None,
972                None,
973                None,
974                None,
975                None,
976                None,
977            )
978            .await?
979            .first()
980        {
981            Some(stream) => Ok(stream.clone()),
982            None => bail!("No stream found"),
983        }
984    }
985
986    pub async fn add_channel_vip(&mut self, id: String) -> Result<()> {
987        let broadcaster_id = self.get_token_user_id().await?;
988        Ok(self
989                .post_empty(format!(
990                    "https://api.twitch.tv/helix/channels/vips?broadcaster_id={broadcaster_id}&user_id={id}"
991                ))
992                .await?)
993    }
994
995    pub async fn remove_channel_vip(&mut self, id: String) -> Result<()> {
996        let broadcaster_id = self.get_token_user_id().await?;
997        Ok(self
998                .delete(format!(
999                    "https://api.twitch.tv/helix/channels/vips?broadcaster_id={broadcaster_id}&user_id={id}"
1000                ))
1001                .await?)
1002    }
1003
1004    pub async fn get_channel_followers_total(&mut self, broadcaster_id: String) -> Result<i64> {
1005        Ok(self
1006            .get::<ChannelFollowersData>(format!(
1007                "https://api.twitch.tv/helix/channels/followers?broadcaster_id={0}",
1008                broadcaster_id
1009            ))
1010            .await?
1011            .total)
1012    }
1013
1014    pub async fn get_videos(
1015        &mut self,
1016        id: VideoId,
1017        language: Option<String>,
1018        period: Option<String>,
1019        sort: Option<String>,
1020        r#type: Option<String>,
1021        first: Option<String>,
1022        after: Option<String>,
1023        before: Option<String>,
1024    ) -> Result<Vec<Video>> {
1025        Ok(self
1026            .get::<TwitchData<Video>>(format!(
1027                "https://api.twitch.tv/helix/videos?{}{}{}{}{}{}{}{}",
1028                match id {
1029                    VideoId::Id(value) => format!("id={}", value),
1030                    VideoId::UserId(value) => format!("user_id={}", value),
1031                    VideoId::GameId(value) => format!("game_id={}", value),
1032                },
1033                if let Some(value) = language {
1034                    format!("&language={}", value)
1035                } else {
1036                    "".to_string()
1037                },
1038                if let Some(value) = period {
1039                    format!("&period={}", value)
1040                } else {
1041                    "".to_string()
1042                },
1043                if let Some(value) = sort {
1044                    format!("&sort={}", value)
1045                } else {
1046                    "".to_string()
1047                },
1048                if let Some(value) = r#type {
1049                    format!("&type={}", value)
1050                } else {
1051                    "".to_string()
1052                },
1053                if let Some(value) = first {
1054                    format!("&first={}", value)
1055                } else {
1056                    "".to_string()
1057                },
1058                if let Some(value) = after {
1059                    format!("&after={}", value)
1060                } else {
1061                    "".to_string()
1062                },
1063                if let Some(value) = before {
1064                    format!("&before={}", value)
1065                } else {
1066                    "".to_string()
1067                }
1068            ))
1069            .await?
1070            .data)
1071    }
1072}