twitch_types/
stream.rs

1#[cfg(feature = "serde")]
2use serde::Serialize;
3
4manual_braid! {
5    /// A Stream ID
6    pub struct StreamId;
7    pub struct StreamIdRef;
8}
9impl_extra!(StreamId, StreamIdRef);
10
11manual_braid! {
12    /// A game or category ID
13    pub struct CategoryId;
14    pub struct CategoryIdRef;
15}
16impl_extra!(CategoryId, CategoryIdRef);
17
18manual_braid! {
19    /// A tag ID
20    pub struct TagId;
21    pub struct TagIdRef;
22}
23impl_extra!(TagId, TagIdRef);
24
25manual_braid! {
26    /// A Team ID
27    pub struct TeamId;
28    pub struct TeamIdRef;
29}
30impl_extra!(TeamId, TeamIdRef);
31
32manual_braid! {
33    /// A video ID
34    pub struct VideoId;
35    pub struct VideoIdRef;
36}
37impl_extra!(VideoId, VideoIdRef);
38
39manual_braid! {
40    /// A clip ID
41    pub struct ClipId;
42    pub struct ClipIdRef;
43}
44impl_extra!(ClipId, ClipIdRef);
45
46manual_braid! {
47    /// A Stream Segment ID.
48    pub struct StreamSegmentId;
49    pub struct StreamSegmentIdRef;
50}
51impl_extra!(StreamSegmentId, StreamSegmentIdRef);
52
53manual_braid! {
54    /// A Hype Train ID
55    pub struct HypeTrainId;
56    pub struct HypeTrainIdRef;
57}
58impl_extra!(HypeTrainId, HypeTrainIdRef);
59
60manual_braid! {
61    /// A Charity Campaign ID
62    pub struct CharityCampaignId;
63    pub struct CharityCampaignIdRef;
64}
65impl_extra!(CharityCampaignId, CharityCampaignIdRef);
66
67manual_braid! {
68    /// A Charity Donation ID
69    pub struct CharityDonationId;
70    pub struct CharityDonationIdRef;
71}
72impl_extra!(CharityDonationId, CharityDonationIdRef);
73
74manual_braid! {
75    /// A [IGDB](https://www.igdb.com/) ID
76    pub struct IgdbId;
77    pub struct IgdbIdRef;
78}
79impl_extra!(IgdbId, IgdbIdRef);
80
81manual_braid! {
82    /// A Guest Star Session ID
83    pub struct GuestStarSessionId;
84    pub struct GuestStarSessionIdRef;
85}
86impl_extra!(GuestStarSessionId, GuestStarSessionIdRef);
87
88manual_braid! {
89    /// A Guest Star Slot ID
90    pub struct GuestStarSlotId;
91    pub struct GuestStarSlotIdRef;
92}
93impl_extra!(GuestStarSlotId, GuestStarSlotIdRef);
94
95manual_braid! {
96    /// A stream marker ID
97    pub struct StreamMarkerId;
98    pub struct StreamMarkerIdRef;
99}
100impl_extra!(StreamMarkerId, StreamMarkerIdRef);
101
102manual_braid! {
103    redact("stream key");
104
105    /// A Stream Key (hidden [Debug] output)
106    pub struct StreamKey;
107    pub struct StreamKeyRef;
108}
109impl_extra!(StreamKey, StreamKeyRef);
110
111/// A game or category as defined by Twitch
112#[derive(PartialEq, Eq, Debug, Clone)]
113#[cfg_attr(
114    feature = "serde",
115    derive(serde_derive::Serialize, serde_derive::Deserialize)
116)]
117#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
118#[non_exhaustive]
119pub struct TwitchCategory {
120    /// Template URL for the game’s box art.
121    pub box_art_url: String,
122    /// Game or category ID.
123    pub id: CategoryId,
124    /// Game name.
125    pub name: String,
126    /// The ID that IGDB uses to identify this game.
127    ///
128    /// An empty value may indicate the endpoint does not return an id or that the category/game is not available on IGDB
129    #[cfg_attr(
130        feature = "serde",
131        serde(
132            deserialize_with = "crate::deserialize_none_from_empty_string",
133            default
134        )
135    )]
136    pub igdb_id: Option<IgdbId>,
137}
138
139/// Subscription tiers
140#[derive(Clone, Debug, PartialEq, Eq)]
141#[cfg_attr(feature = "serde", derive(serde_derive::Deserialize))]
142#[cfg_attr(feature = "serde", serde(field_identifier))]
143pub enum SubscriptionTier {
144    /// Tier 1. $4.99
145    #[cfg_attr(feature = "serde", serde(rename = "1000"))]
146    Tier1,
147    /// Tier 1. $9.99
148    #[cfg_attr(feature = "serde", serde(rename = "2000"))]
149    Tier2,
150    /// Tier 1. $24.99
151    #[cfg_attr(feature = "serde", serde(rename = "3000"))]
152    Tier3,
153    /// Prime subscription
154    Prime,
155    /// Other
156    Other(String),
157}
158
159#[cfg(feature = "serde")]
160impl Serialize for SubscriptionTier {
161    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
162    where S: serde::Serializer {
163        serializer.serialize_str(match self {
164            SubscriptionTier::Tier1 => "1000",
165            SubscriptionTier::Tier2 => "2000",
166            SubscriptionTier::Tier3 => "3000",
167            SubscriptionTier::Prime => "Prime",
168            SubscriptionTier::Other(o) => o,
169        })
170    }
171}
172
173/// Period during which the video was created
174#[derive(PartialEq, Eq, Clone, Debug)]
175#[cfg_attr(
176    feature = "serde",
177    derive(serde_derive::Serialize, serde_derive::Deserialize)
178)]
179#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
180pub enum VideoPeriod {
181    /// Filter by all. Effectively a no-op
182    All,
183    /// Filter by from this day only
184    Day,
185    /// Filter by this week
186    Week,
187    /// Filter by this month
188    Month,
189}
190
191/// Type of video
192#[derive(PartialEq, Eq, Clone, Debug)]
193#[cfg_attr(
194    feature = "serde",
195    derive(serde_derive::Serialize, serde_derive::Deserialize)
196)]
197#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
198pub enum VideoType {
199    /// A live video
200    Live,
201    // FIXME: What is this?
202    /// A playlist video
203    Playlist,
204    /// A uploaded video
205    Upload,
206    /// An archived video
207    Archive,
208    /// A highlight
209    Highlight,
210    /// A premiere
211    Premiere,
212    /// A rerun
213    Rerun,
214    /// A watch party
215    WatchParty,
216    /// A watchparty premiere,
217    WatchPartyPremiere,
218    /// A watchparty rerun
219    WatchPartyRerun,
220}
221
222/// Type of video
223#[derive(PartialEq, Eq, Clone, Debug)]
224#[cfg_attr(
225    feature = "serde",
226    derive(serde_derive::Serialize, serde_derive::Deserialize)
227)]
228#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
229pub enum VideoPrivacy {
230    /// Video is public
231    Public,
232    /// Video is private
233    Private,
234}
235
236/// Length of the commercial in seconds
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238#[repr(u64)]
239#[non_exhaustive]
240pub enum CommercialLength {
241    /// 30s
242    Length30 = 30,
243    /// 60s
244    Length60 = 60,
245    /// 90s
246    Length90 = 90,
247    /// 120s
248    Length120 = 120,
249    /// 150s
250    Length150 = 150,
251    /// 180s
252    Length180 = 180,
253}
254#[cfg(feature = "serde")]
255impl serde::Serialize for CommercialLength {
256    #[allow(clippy::use_self)]
257    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
258    where S: serde::Serializer {
259        let value: u64 = *self as u64;
260        serde::Serialize::serialize(&value, serializer)
261    }
262}
263
264/// TODO: macroify?
265#[cfg(feature = "serde")]
266impl<'de> serde::Deserialize<'de> for CommercialLength {
267    #[allow(clippy::use_self)]
268    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
269    where D: serde::Deserializer<'de> {
270        #[allow(non_camel_case_types)]
271        struct discriminant;
272
273        #[allow(non_upper_case_globals)]
274        impl discriminant {
275            const Length120: u64 = CommercialLength::Length120 as u64;
276            const Length150: u64 = CommercialLength::Length150 as u64;
277            const Length180: u64 = CommercialLength::Length180 as u64;
278            const Length30: u64 = CommercialLength::Length30 as u64;
279            const Length60: u64 = CommercialLength::Length60 as u64;
280            const Length90: u64 = CommercialLength::Length90 as u64;
281        }
282        match <u64 as serde::Deserialize>::deserialize(deserializer)? {
283            discriminant::Length30 => core::result::Result::Ok(CommercialLength::Length30),
284            discriminant::Length60 => core::result::Result::Ok(CommercialLength::Length60),
285            discriminant::Length90 => core::result::Result::Ok(CommercialLength::Length90),
286            discriminant::Length120 => core::result::Result::Ok(CommercialLength::Length120),
287            discriminant::Length150 => core::result::Result::Ok(CommercialLength::Length150),
288            discriminant::Length180 => core::result::Result::Ok(CommercialLength::Length180),
289            other => core::result::Result::Err(serde::de::Error::custom(format_args!(
290                "invalid value: {}, expected one of: {}, {}, {}, {}, {}, {}",
291                other,
292                discriminant::Length30,
293                discriminant::Length60,
294                discriminant::Length90,
295                discriminant::Length120,
296                discriminant::Length150,
297                discriminant::Length180
298            ))),
299        }
300    }
301}
302
303impl std::fmt::Display for CommercialLength {
304    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305        write!(f, "{}s", *self as u64)
306    }
307}
308
309impl std::convert::TryFrom<u64> for CommercialLength {
310    type Error = CommercialLengthParseError;
311
312    fn try_from(l: u64) -> Result<Self, Self::Error> {
313        match l {
314            30 => Ok(CommercialLength::Length30),
315            60 => Ok(CommercialLength::Length60),
316            90 => Ok(CommercialLength::Length90),
317            120 => Ok(CommercialLength::Length120),
318            150 => Ok(CommercialLength::Length150),
319            180 => Ok(CommercialLength::Length180),
320            other => Err(CommercialLengthParseError::InvalidLength(other)),
321        }
322    }
323}
324
325/// Error for the `TryFrom` on [`CommercialLength`]
326#[derive(Debug)]
327pub enum CommercialLengthParseError {
328    /// invalid length of {0}
329    InvalidLength(u64),
330}
331
332impl std::error::Error for CommercialLengthParseError {}
333
334impl core::fmt::Display for CommercialLengthParseError {
335    fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
336        #[allow(unused_variables)]
337        match self {
338            CommercialLengthParseError::InvalidLength(len) => {
339                write!(formatter, "invalid length of {len}")
340            }
341        }
342    }
343}
344
345/// IDs for [content classification labels](https://help.twitch.tv/s/article/content-classification-labels) also known as CCLs
346#[derive(Clone, Debug, PartialEq, Eq)]
347#[non_exhaustive]
348#[cfg_attr(feature = "serde", derive(serde_derive::Deserialize))]
349#[cfg_attr(feature = "serde", serde(field_identifier))]
350pub enum ContentClassificationId {
351    /// Politics and Sensitive Social Issues
352    ///
353    /// Discussions or debates about politics or sensitive social issues such as elections, civic integrity, military conflict, and civil rights in a polarizing manner.
354    DebatedSocialIssuesAndPolitics,
355    /// Drugs, Intoxication, or Excessive Tobacco Use
356    ///
357    /// Excessive tobacco glorification or promotion, any marijuana consumption/use, legal drug and alcohol induced intoxication, discussions of illegal drugs.
358    DrugsIntoxication,
359    /// Sexual Themes
360    ///
361    /// Content that focuses on sexualized physical attributes and activities, sexual topics, or experiences.
362    SexualThemes,
363    /// Violent and Graphic Depictions
364    ///
365    /// Simulations and/or depictions of realistic violence, gore, extreme injury, or death.
366    ViolentGraphic,
367    /// Gambling
368    ///
369    /// Participating in online or in-person gambling, poker or fantasy sports, that involve the exchange of real money.
370    Gambling,
371    /// Significant Profanity or Vulgarity
372    ///
373    /// Prolonged, and repeated use of obscenities, profanities, and vulgarities, especially as a regular part of speech.
374    ProfanityVulgarity,
375    /// Mature-rated game
376    ///
377    /// Games that are rated Mature or less suitable for a younger audience.
378    MatureGame,
379    /// Other
380    Other(String),
381}
382
383#[cfg(feature = "serde")]
384impl Serialize for ContentClassificationId {
385    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
386    where S: serde::Serializer {
387        serializer.serialize_str(match self {
388            ContentClassificationId::DebatedSocialIssuesAndPolitics => {
389                "DebatedSocialIssuesAndPolitics"
390            }
391            ContentClassificationId::DrugsIntoxication => "DrugsIntoxication",
392            ContentClassificationId::SexualThemes => "SexualThemes",
393            ContentClassificationId::ViolentGraphic => "ViolentGraphic",
394            ContentClassificationId::Gambling => "Gambling",
395            ContentClassificationId::ProfanityVulgarity => "ProfanityVulgarity",
396            ContentClassificationId::MatureGame => "MatureGame",
397            ContentClassificationId::Other(o) => o,
398        })
399    }
400}