1#![doc(alias = "subscription")]
2#![doc(alias = "subscriptions")]
3#![doc(alias = "channel-subscribe-events-v1")]
4use crate::{pubsub, types};
6use serde_derive::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
10#[serde(into = "String", try_from = "String")]
11pub struct ChannelSubscribeEventsV1 {
12 pub channel_id: u32,
14}
15
16impl_de_ser!(
17 ChannelSubscribeEventsV1,
18 "channel-subscribe-events-v1",
19 channel_id );
21
22impl pubsub::Topic for ChannelSubscribeEventsV1 {
23 #[cfg(feature = "twitch_oauth2")]
24 const SCOPE: twitch_oauth2::Validator = twitch_oauth2::validator![];
25
26 fn into_topic(self) -> pubsub::Topics { super::Topics::ChannelSubscribeEventsV1(self) }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
32#[non_exhaustive]
33pub struct Sub {
34 pub benefit_end_month: i64,
37 pub channel_id: types::UserId,
39 pub channel_name: types::UserName,
41 pub cumulative_months: i64,
43 pub is_gift: bool,
45 #[doc(hidden)]
46 pub months: i64,
47 pub multi_month_duration: i64,
50 pub sub_message: SubMessage,
52 pub sub_plan: types::SubscriptionTier,
54 pub sub_plan_name: String,
56 pub time: types::Timestamp,
58 pub user_id: types::UserId,
60 pub user_name: types::UserName,
62 pub display_name: types::DisplayName,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
68#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
69#[non_exhaustive]
70pub struct ReSub {
71 pub benefit_end_month: Option<i64>,
75 pub channel_id: types::UserId,
77 pub channel_name: types::UserName,
79 pub cumulative_months: i64,
81 pub is_gift: bool,
83 pub streak_months: Option<i64>,
85 #[doc(hidden)]
86 pub months: i64,
87 pub multi_month_duration: Option<i64>,
89 pub sub_message: SubMessage,
91 pub sub_plan: types::SubscriptionTier,
93 pub sub_plan_name: String,
95 pub time: types::Timestamp,
97 pub user_id: types::UserId,
99 pub user_name: types::UserName,
101 pub display_name: types::DisplayName,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
108#[non_exhaustive]
109pub struct SubGift {
110 pub benefit_end_month: Option<i64>,
114 pub channel_id: types::UserId,
116 pub channel_name: types::UserName,
118 #[doc(hidden)]
119 pub is_gift: bool,
120 pub months: i64,
122 pub multi_month_duration: Option<i64>,
125 pub recipient_display_name: types::DisplayName,
127 pub recipient_id: types::UserId,
129 pub recipient_user_name: types::UserName,
131 #[doc(hidden)]
133 pub sub_message: SubMessage,
134 pub sub_plan: types::SubscriptionTier,
136 pub sub_plan_name: String,
138 pub time: types::Timestamp,
140 pub user_id: types::UserId,
142 pub user_name: types::UserName,
144 pub display_name: types::DisplayName,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
150#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
151#[non_exhaustive]
152pub struct ResubGift {
153 pub benefit_end_month: Option<i64>,
157 pub channel_id: types::UserId,
159 pub channel_name: types::UserName,
161 pub cumulative_months: i64,
163 #[doc(hidden)]
164 pub is_gift: bool,
165 pub months: i64,
167 pub multi_month_duration: Option<i64>,
171 pub recipient_display_name: types::DisplayName,
173 pub recipient_user_name: types::UserName,
175 pub streak_months: Option<i64>,
178 pub sub_message: SubMessage,
180 pub sub_plan: types::SubscriptionTier,
182 pub sub_plan_name: String,
184 pub time: types::Timestamp,
186 pub user_id: types::UserId,
188 pub user_name: types::UserName,
190 pub display_name: types::DisplayName,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
196#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
197#[non_exhaustive]
198pub struct ExtendSub {
199 pub benefit_end_month: Option<i64>,
201 pub channel_id: types::UserId,
203 pub channel_name: types::UserName,
205 pub cumulative_months: i64,
207 #[doc(hidden)]
208 pub is_gift: bool,
209 pub months: i64,
211 pub multi_month_duration: Option<i64>,
214 pub sub_message: SubMessage,
216 pub sub_plan: types::SubscriptionTier,
218 pub sub_plan_name: String,
220 pub time: types::Timestamp,
222 pub user_id: types::UserId,
224 pub user_name: types::UserName,
226 pub display_name: types::DisplayName,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
234#[serde(tag = "context")]
235#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
236#[non_exhaustive]
237pub enum ChannelSubscribeEventsV1Reply {
238 #[serde(rename = "sub")]
240 Sub(Sub),
241 #[serde(rename = "resub")]
243 ReSub(ReSub),
244 #[serde(rename = "subgift")]
246 SubGift(SubGift),
247 #[serde(rename = "resubgift")]
249 ResubGift(ResubGift),
250 #[serde(rename = "extendsub")]
254 ExtendSub(ExtendSub),
255}
256
257#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
259#[non_exhaustive]
260pub struct Emote {
261 pub id: String,
264 pub start: i64,
266 pub end: i64,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
272#[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))]
273#[non_exhaustive]
274pub struct SubMessage {
275 #[serde(deserialize_with = "pubsub::deserialize_default_from_null")]
277 pub emotes: Vec<Emote>,
278 pub message: String,
280}
281
282#[cfg(test)]
283mod tests {
284 use super::super::{Response, TopicData};
285 use super::*;
286
287 #[test]
288 fn subscription_doc_example_resub() {
289 let message = r##"
291{
292 "user_name": "tww2",
293 "display_name": "TWW2",
294 "channel_name": "mr_woodchuck",
295 "user_id": "13405587",
296 "channel_id": "89614178",
297 "time": "2015-12-19T16:39:57-08:00",
298 "sub_plan": "1000",
299 "sub_plan_name": "Channel Subscription (mr_woodchuck)",
300 "cumulative_months": 9,
301 "streak_months": 3,
302 "months": 0,
303 "context": "resub",
304 "is_gift": false,
305 "sub_message": {
306 "message": "A Twitch baby is born! KappaHD",
307 "emotes": [
308 {
309 "start": 23,
310 "end": 7,
311 "id": "2867"
312 }
313 ]
314 }
315}
316"##;
317
318 let source = format!(
319 r#"{{"type": "MESSAGE", "data": {{ "topic": "channel-subscribe-events-v1.27620241", "message": {message:?} }}}}"#
320 );
321 let actual = dbg!(Response::parse(&source).unwrap());
322 assert!(matches!(
323 actual,
324 Response::Message {
325 data: TopicData::ChannelSubscribeEventsV1 { .. },
326 }
327 ));
328 }
329
330 #[test]
331 fn subscription_doc_example_subgift() {
332 let message = r##"
333{
334 "user_name": "tww2",
335 "display_name": "TWW2",
336 "channel_name": "mr_woodchuck",
337 "user_id": "13405587",
338 "channel_id": "89614178",
339 "time": "2015-12-19T16:39:57-08:00",
340 "sub_plan": "1000",
341 "sub_plan_name": "Channel Subscription (mr_woodchuck)",
342 "months": 9,
343 "context": "subgift",
344 "is_gift": true,
345 "sub_message": {
346 "message": "",
347 "emotes": null
348 },
349 "recipient_id": "19571752",
350 "recipient_user_name": "forstycup",
351 "recipient_display_name": "forstycup"
352}
353"##;
354
355 let source = format!(
356 r#"{{"type": "MESSAGE", "data": {{ "topic": "channel-subscribe-events-v1.27620241", "message": {message:?} }}}}"#
357 );
358 let actual = dbg!(Response::parse(&source).unwrap());
359 assert!(matches!(
360 actual,
361 Response::Message {
362 data: TopicData::ChannelSubscribeEventsV1 { .. },
363 }
364 ));
365 }
366
367 #[test]
368 fn new_sub() {
369 let message = r##"
370{
371 "benefit_end_month": 11,
372 "user_name": "tmi",
373 "display_name": "tmi",
374 "channel_name": "emilgardis",
375 "user_id": "1234",
376 "channel_id": "27620241",
377 "time": "2020-10-20T22:17:43.242793831Z",
378 "sub_message": {
379 "message": "",
380 "emotes": null
381 },
382 "sub_plan": "1000",
383 "sub_plan_name": "Channel Subscription (emilgardis)",
384 "months": 0,
385 "cumulative_months": 1,
386 "context": "sub",
387 "is_gift": false,
388 "multi_month_duration": 0
389}
390"##;
391
392 let source = format!(
393 r#"{{"type": "MESSAGE", "data": {{ "topic": "channel-subscribe-events-v1.27620241", "message": {message:?} }}}}"#
394 );
395 let actual = dbg!(Response::parse(&source).unwrap());
396 assert!(matches!(
397 actual,
398 Response::Message {
399 data: TopicData::ChannelSubscribeEventsV1 { .. },
400 }
401 ));
402 }
403
404 #[test]
405 fn gifted_sub() {
406 let message = r##"
407{
408 "benefit_end_month": 0,
409 "user_name": "tmi",
410 "display_name": "tmi",
411 "channel_name": "emilgardis",
412 "user_id": "1234",
413 "channel_id": "27620241",
414 "recipient_id": "1337",
415 "recipient_user_name": "justintv",
416 "recipient_display_name": "justintv",
417 "time": "2020-10-20T22:18:17.542444893Z",
418 "sub_message": {
419 "message": "",
420 "emotes": null
421 },
422 "sub_plan": "1000",
423 "sub_plan_name": "Channel Subscription (emilgardis)",
424 "months": 1,
425 "context": "subgift",
426 "is_gift": true,
427 "multi_month_duration": 1
428}
429"##;
430
431 let source = format!(
432 r#"{{"type": "MESSAGE", "data": {{ "topic": "channel-subscribe-events-v1.27620241", "message": {message:?} }}}}"#
433 );
434 let actual = dbg!(Response::parse(&source).unwrap());
435 assert!(matches!(
436 actual,
437 Response::Message {
438 data: TopicData::ChannelSubscribeEventsV1 { .. },
439 }
440 ));
441 }
442
443 #[test]
444 fn resub() {
445 let message = r##"
446{
447 "benefit_end_month": 0,
448 "user_name": "tmi",
449 "display_name": "tmi",
450 "channel_name": "emilgardis",
451 "user_id": "1234",
452 "channel_id": "80525799",
453 "time": "2020-10-25T17:15:36.541972298Z",
454 "sub_message": {
455 "message": "message here",
456 "emotes": [
457 {
458 "start": 191,
459 "end": 197,
460 "id": "12342378"
461 }
462 ]
463 },
464 "sub_plan": "2000",
465 "sub_plan_name": "Channel Subscription (emilgardis): $9.99 Sub",
466 "months": 0,
467 "cumulative_months": 12,
468 "streak_months": 12,
469 "context": "resub",
470 "is_gift": false,
471 "multi_month_duration": 0
472}
473"##;
474
475 let source = format!(
476 r#"{{"type": "MESSAGE", "data": {{ "topic": "channel-subscribe-events-v1.27620241", "message": {message:?} }}}}"#
477 );
478 let actual = dbg!(Response::parse(&source).unwrap());
479 assert!(matches!(
480 actual,
481 Response::Message {
482 data: TopicData::ChannelSubscribeEventsV1 { .. },
483 }
484 ));
485 }
486
487 #[test]
488 fn resub_gift() {
489 let message = r##"
490{
491 "benefit_end_month": 0,
492 "user_name": "emilgardis",
493 "display_name": "emilgardis",
494 "channel_name": "sessis",
495 "user_id": "158640756",
496 "channel_id": "80525799",
497 "recipient_user_name": "champi70",
498 "recipient_display_name": "Champi70",
499 "time": "2020-12-06T18:54:52.804481633Z",
500 "sub_message": {
501 "message": "¡Gracias, @emilgardis, por regalarme una suscripción! thank you so mych sessis for the streams you brighten my day each time you are in stream you are awesome sessHug",
502 "emotes": [
503 {
504 "start": 161,
505 "end": 167,
506 "id": "300741652"
507 }
508 ]
509 },
510 "sub_plan": "1000",
511 "sub_plan_name": "Channel Subscription (sessis)",
512 "months": 0,
513 "cumulative_months": 24,
514 "context": "resubgift",
515 "is_gift": true,
516 "multi_month_duration": 0
517}
518"##;
519
520 let source = format!(
521 r#"{{"type": "MESSAGE", "data": {{ "topic": "channel-subscribe-events-v1.27620241", "message": {message:?} }}}}"#
522 );
523 let actual = dbg!(Response::parse(&source).unwrap());
524 assert!(matches!(
525 actual,
526 Response::Message {
527 data: TopicData::ChannelSubscribeEventsV1 { .. },
528 }
529 ));
530 }
531
532 #[test]
533 fn extendsub() {
534 let message = r##"
535{
536 "benefit_end_month": 6,
537 "user_name": "user",
538 "display_name": "User!",
539 "channel_name": "twitch",
540 "user_id": "1234",
541 "channel_id": "123",
542 "time": "2021-05-14T20:54:06.805273338Z",
543 "sub_message": {
544 "message": "",
545 "emotes": null
546 },
547 "sub_plan": "1000",
548 "sub_plan_name": "Channel Subscription (twitch)",
549 "months": 0,
550 "cumulative_months": 5,
551 "context": "extendsub",
552 "is_gift": false,
553 "multi_month_duration": 0
554}
555"##;
556
557 let source = format!(
558 r#"{{"type": "MESSAGE", "data": {{ "topic": "channel-subscribe-events-v1.27620241", "message": {message:?} }}}}"#
559 );
560 let actual = dbg!(Response::parse(&source).unwrap());
561 assert!(matches!(
562 actual,
563 Response::Message {
564 data: TopicData::ChannelSubscribeEventsV1 { .. },
565 }
566 ));
567 }
568
569 #[test]
570 fn check_deser() {
571 use std::convert::TryInto as _;
572 let s = "channel-subscribe-events-v1.1234";
573 assert_eq!(
574 ChannelSubscribeEventsV1 { channel_id: 1234 },
575 s.to_string().try_into().unwrap()
576 );
577 }
578
579 #[test]
580 fn check_ser() {
581 let s = "channel-subscribe-events-v1.1234";
582 let right: String = ChannelSubscribeEventsV1 { channel_id: 1234 }.into();
583 assert_eq!(s.to_string(), right);
584 }
585}