1use crate::message::commands::IRCMessageParseExt;
2use crate::message::twitch::{Badge, Emote, RGBColor, TwitchUserBasics};
3use crate::message::{IRCMessage, ServerMessageParseError};
4use chrono::{DateTime, Utc};
5use std::convert::TryFrom;
6
7#[cfg(feature = "with-serde")]
8use {serde::Deserialize, serde::Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
23pub struct UserNoticeMessage {
24 pub channel_login: String,
26 pub channel_id: String,
28
29 pub sender: TwitchUserBasics,
38
39 pub message_text: Option<String>,
45 pub system_message: String,
52
53 pub event: UserNoticeEvent,
55
56 pub event_id: String,
59
60 pub badge_info: Vec<Badge>,
67 pub badges: Vec<Badge>,
69 pub emotes: Vec<Emote>,
75
76 pub name_color: Option<RGBColor>,
81
82 pub message_id: String,
85
86 pub server_timestamp: DateTime<Utc>,
88
89 pub source: IRCMessage,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
97#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
98pub struct SubGiftPromo {
99 pub total_gifts: u64,
101 pub promo_name: String,
103}
104
105impl SubGiftPromo {
106 fn parse_if_present(
107 source: &IRCMessage,
108 ) -> Result<Option<SubGiftPromo>, ServerMessageParseError> {
109 if let (Some(total_gifts), Some(promo_name)) = (
110 source.try_get_optional_number("msg-param-promo-gift-total")?,
111 source
112 .try_get_optional_nonempty_tag_value("msg-param-promo-name")?
113 .map(|s| s.to_owned()),
114 ) {
115 Ok(Some(SubGiftPromo {
116 total_gifts,
117 promo_name,
118 }))
119 } else {
120 Ok(None)
121 }
122 }
123}
124
125#[non_exhaustive]
150#[derive(Debug, Clone, PartialEq, Eq)]
151#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
152pub enum UserNoticeEvent {
153 SubOrResub {
160 is_resub: bool,
162 cumulative_months: u64,
164 streak_months: Option<u64>,
166 sub_plan: String,
168 sub_plan_name: String,
171 },
172
173 Raid {
176 viewer_count: u64,
178 profile_image_url: String,
184 },
185
186 SubGift {
193 is_sender_anonymous: bool,
197 cumulative_months: u64,
199 recipient: TwitchUserBasics,
201 sub_plan: String,
203 sub_plan_name: String,
206 num_gifted_months: u64,
208 },
209
210 SubMysteryGift {
213 mass_gift_count: u64,
215 sender_total_gifts: Option<u64>,
219 sub_plan: String,
222 },
223
224 AnonSubMysteryGift {
234 mass_gift_count: u64,
236 sub_plan: String,
239 },
240
241 GiftPaidUpgrade {
247 gifter_login: String,
251 gifter_name: String,
255 promotion: Option<SubGiftPromo>,
258 },
259
260 AnonGiftPaidUpgrade {
265 promotion: Option<SubGiftPromo>,
268 },
269
270 Ritual {
274 ritual_name: String,
276 },
277
278 BitsBadgeTier {
282 threshold: u64,
285 },
286
287 #[doc(hidden)]
290 Unknown,
291}
292
293impl TryFrom<IRCMessage> for UserNoticeMessage {
294 type Error = ServerMessageParseError;
295
296 fn try_from(source: IRCMessage) -> Result<UserNoticeMessage, ServerMessageParseError> {
297 if source.command != "USERNOTICE" {
298 return Err(ServerMessageParseError::MismatchedCommand(Box::new(source)));
299 }
300
301 let sender = TwitchUserBasics {
309 id: source.try_get_nonempty_tag_value("user-id")?.to_owned(),
310 login: source.try_get_nonempty_tag_value("login")?.to_owned(),
311 name: source
312 .try_get_nonempty_tag_value("display-name")?
313 .to_owned(),
314 };
315
316 let event_id = source.try_get_nonempty_tag_value("msg-id")?.to_owned();
327 let event = match event_id.as_str() {
328 "sub" | "resub" => UserNoticeEvent::SubOrResub {
336 is_resub: &event_id == "resub",
337 cumulative_months: source.try_get_number("msg-param-cumulative-months")?,
338 streak_months: if source.try_get_bool("msg-param-should-share-streak")? {
339 Some(source.try_get_number("msg-param-streak-months")?)
340 } else {
341 None
342 },
343 sub_plan: source
344 .try_get_nonempty_tag_value("msg-param-sub-plan")?
345 .to_owned(),
346 sub_plan_name: source
347 .try_get_nonempty_tag_value("msg-param-sub-plan-name")?
348 .to_owned(),
349 },
350 "raid" => UserNoticeEvent::Raid {
357 viewer_count: source.try_get_number::<u64>("msg-param-viewerCount")?,
358 profile_image_url: source
359 .try_get_nonempty_tag_value("msg-param-profileImageURL")?
360 .to_owned(),
361 },
362 "subgift" | "anonsubgift" => UserNoticeEvent::SubGift {
372 is_sender_anonymous: event_id == "anonsubgift" || sender.id == "274598607",
374 cumulative_months: source.try_get_number("msg-param-months")?,
375 recipient: TwitchUserBasics {
376 id: source
377 .try_get_nonempty_tag_value("msg-param-recipient-id")?
378 .to_owned(),
379 login: source
380 .try_get_nonempty_tag_value("msg-param-recipient-user-name")?
381 .to_owned(),
382 name: source
383 .try_get_nonempty_tag_value("msg-param-recipient-display-name")?
384 .to_owned(),
385 },
386 sub_plan: source
387 .try_get_nonempty_tag_value("msg-param-sub-plan")?
388 .to_owned(),
389 sub_plan_name: source
390 .try_get_nonempty_tag_value("msg-param-sub-plan-name")?
391 .to_owned(),
392 num_gifted_months: source.try_get_number("msg-param-gift-months")?,
393 },
394 _ if (sender.id == "274598607" && event_id == "submysterygift")
407 || event_id == "anonsubmysterygift" =>
408 {
409 UserNoticeEvent::AnonSubMysteryGift {
410 mass_gift_count: source.try_get_number("msg-param-mass-gift-count")?,
411 sub_plan: source
412 .try_get_nonempty_tag_value("msg-param-sub-plan")?
413 .to_owned(),
414 }
415 }
416 "submysterygift" => UserNoticeEvent::SubMysteryGift {
418 mass_gift_count: source.try_get_number("msg-param-mass-gift-count")?,
419 sender_total_gifts: if sender.login != "twitch" {
420 Some(source.try_get_number("msg-param-sender-count")?)
421 } else {
422 source.try_get_number("msg-param-sender-count").ok()
424 },
425 sub_plan: source
426 .try_get_nonempty_tag_value("msg-param-sub-plan")?
427 .to_owned(),
428 },
429 "giftpaidupgrade" => UserNoticeEvent::GiftPaidUpgrade {
442 gifter_login: source
443 .try_get_nonempty_tag_value("msg-param-sender-login")?
444 .to_owned(),
445 gifter_name: source
446 .try_get_nonempty_tag_value("msg-param-sender-name")?
447 .to_owned(),
448 promotion: SubGiftPromo::parse_if_present(&source)?,
449 },
450 "anongiftpaidupgrade" => UserNoticeEvent::AnonGiftPaidUpgrade {
451 promotion: SubGiftPromo::parse_if_present(&source)?,
452 },
453
454 "ritual" => UserNoticeEvent::Ritual {
460 ritual_name: source
461 .try_get_nonempty_tag_value("msg-param-ritual-name")?
462 .to_owned(),
463 },
464
465 "bitsbadgetier" => UserNoticeEvent::BitsBadgeTier {
471 threshold: source
472 .try_get_number::<u64>("msg-param-threshold")?
473 .to_owned(),
474 },
475
476 _ => UserNoticeEvent::Unknown,
478 };
479
480 let message_text = source.params.get(1).cloned(); let emotes = if let Some(message_text) = &message_text {
482 source.try_get_emotes("emotes", message_text)?
483 } else {
484 vec![]
485 };
486
487 Ok(UserNoticeMessage {
488 channel_login: source.try_get_channel_login()?.to_owned(),
489 channel_id: source.try_get_nonempty_tag_value("room-id")?.to_owned(),
490 sender,
491 message_text,
492 system_message: source.try_get_nonempty_tag_value("system-msg")?.to_owned(),
493 event,
494 event_id,
495 badge_info: source.try_get_badges("badge-info")?,
496 badges: source.try_get_badges("badges")?,
497 emotes,
498 name_color: source.try_get_color("color")?,
499 message_id: source.try_get_nonempty_tag_value("id")?.to_owned(),
500 server_timestamp: source.try_get_timestamp("tmi-sent-ts")?,
501 source,
502 })
503 }
504}
505
506impl From<UserNoticeMessage> for IRCMessage {
507 fn from(msg: UserNoticeMessage) -> IRCMessage {
508 msg.source
509 }
510}
511
512#[cfg(test)]
513mod tests {
514 use crate::message::twitch::{Badge, Emote, RGBColor, TwitchUserBasics};
515 use crate::message::{IRCMessage, SubGiftPromo, UserNoticeEvent, UserNoticeMessage};
516 use chrono::{TimeZone, Utc};
517 use std::convert::TryFrom;
518 use std::ops::Range;
519
520 #[test]
521 pub fn test_sub() {
522 let src = "@badge-info=subscriber/0;badges=subscriber/0,premium/1;color=;display-name=fallenseraphhh;emotes=;flags=;id=2a9bea11-a80a-49a0-a498-1642d457f775;login=fallenseraphhh;mod=0;msg-id=sub;msg-param-cumulative-months=1;msg-param-months=0;msg-param-should-share-streak=0;msg-param-sub-plan-name=Channel\\sSubscription\\s(xqcow);msg-param-sub-plan=Prime;room-id=71092938;subscriber=1;system-msg=fallenseraphhh\\ssubscribed\\swith\\sTwitch\\sPrime.;tmi-sent-ts=1582685713242;user-id=224005980;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
523 let irc_message = IRCMessage::parse(src).unwrap();
524 let msg = UserNoticeMessage::try_from(irc_message.clone()).unwrap();
525
526 assert_eq!(
527 msg,
528 UserNoticeMessage {
529 channel_login: "xqcow".to_owned(),
530 channel_id: "71092938".to_owned(),
531 sender: TwitchUserBasics {
532 id: "224005980".to_owned(),
533 login: "fallenseraphhh".to_owned(),
534 name: "fallenseraphhh".to_owned(),
535 },
536 message_text: None,
537 system_message: "fallenseraphhh subscribed with Twitch Prime.".to_owned(),
538 event: UserNoticeEvent::SubOrResub {
539 is_resub: false,
540 cumulative_months: 1,
541 streak_months: None,
542 sub_plan: "Prime".to_owned(),
543 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
544 },
545 event_id: "sub".to_owned(),
546 badge_info: vec![Badge {
547 name: "subscriber".to_owned(),
548 version: "0".to_owned(),
549 }],
550 badges: vec![
551 Badge {
552 name: "subscriber".to_owned(),
553 version: "0".to_owned(),
554 },
555 Badge {
556 name: "premium".to_owned(),
557 version: "1".to_owned(),
558 }
559 ],
560 emotes: vec![],
561 name_color: None,
562 message_id: "2a9bea11-a80a-49a0-a498-1642d457f775".to_owned(),
563 server_timestamp: Utc.timestamp_millis_opt(1_582_685_713_242).unwrap(),
564 source: irc_message,
565 }
566 );
567 }
568
569 #[test]
570 pub fn test_resub() {
571 let src = "@badge-info=subscriber/2;badges=subscriber/0,battlerite_1/1;color=#0000FF;display-name=Gutrin;emotes=1035663:0-3;flags=;id=e0975c76-054c-4954-8cb0-91b8867ec1ca;login=gutrin;mod=0;msg-id=resub;msg-param-cumulative-months=2;msg-param-months=0;msg-param-should-share-streak=1;msg-param-streak-months=2;msg-param-sub-plan-name=Channel\\sSubscription\\s(xqcow);msg-param-sub-plan=1000;room-id=71092938;subscriber=1;system-msg=Gutrin\\ssubscribed\\sat\\sTier\\s1.\\sThey've\\ssubscribed\\sfor\\s2\\smonths,\\scurrently\\son\\sa\\s2\\smonth\\sstreak!;tmi-sent-ts=1581713640019;user-id=21156217;user-type= :tmi.twitch.tv USERNOTICE #xqcow :xqcL";
572 let irc_message = IRCMessage::parse(src).unwrap();
573 let msg = UserNoticeMessage::try_from(irc_message.clone()).unwrap();
574
575 assert_eq!(
576 msg,
577 UserNoticeMessage {
578 channel_login: "xqcow".to_owned(),
579 channel_id: "71092938".to_owned(),
580 sender: TwitchUserBasics {
581 id: "21156217".to_owned(),
582 login: "gutrin".to_owned(),
583 name: "Gutrin".to_owned(),
584 },
585 message_text: Some("xqcL".to_owned()),
586 system_message: "Gutrin subscribed at Tier 1. They've subscribed for 2 months, currently on a 2 month streak!".to_owned(),
587 event: UserNoticeEvent::SubOrResub {
588 is_resub: true,
589 cumulative_months: 2,
590 streak_months: Some(2),
591 sub_plan: "1000".to_owned(),
592 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
593 },
594 event_id: "resub".to_owned(),
595 badge_info: vec![Badge {
596 name: "subscriber".to_owned(),
597 version: "2".to_owned(),
598 }],
599 badges: vec![
600 Badge {
601 name: "subscriber".to_owned(),
602 version: "0".to_owned(),
603 },
604 Badge {
605 name: "battlerite_1".to_owned(),
606 version: "1".to_owned(),
607 }
608 ],
609 emotes: vec![
610 Emote {
611 id: "1035663".to_owned(),
612 char_range: Range { start: 0, end: 4 },
613 code: "xqcL".to_owned(),
614 }
615 ],
616 name_color: Some(RGBColor {
617 r: 0x00,
618 g: 0x00,
619 b: 0xFF,
620 }),
621 message_id: "e0975c76-054c-4954-8cb0-91b8867ec1ca".to_owned(),
622 server_timestamp: Utc.timestamp_millis_opt(1_581_713_640_019).unwrap(),
623 source: irc_message,
624 }
625 );
626 }
627
628 #[test]
629 pub fn test_resub_no_share_streak() {
630 let src = "@badge-info=;badges=premium/1;color=#8A2BE2;display-name=rene_rs;emotes=;flags=;id=ca1f02fb-77ec-487d-a9b3-bc4bfef2fe8b;login=rene_rs;mod=0;msg-id=resub;msg-param-cumulative-months=11;msg-param-months=0;msg-param-should-share-streak=0;msg-param-sub-plan-name=Channel\\sSubscription\\s(xqcow);msg-param-sub-plan=Prime;room-id=71092938;subscriber=0;system-msg=rene_rs\\ssubscribed\\swith\\sTwitch\\sPrime.\\sThey've\\ssubscribed\\sfor\\s11\\smonths!;tmi-sent-ts=1590628650446;user-id=171356987;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
631 let irc_message = IRCMessage::parse(src).unwrap();
632 let msg = UserNoticeMessage::try_from(irc_message.clone()).unwrap();
633
634 assert_eq!(
635 msg,
636 UserNoticeMessage {
637 channel_login: "xqcow".to_owned(),
638 channel_id: "71092938".to_owned(),
639 sender: TwitchUserBasics {
640 id: "171356987".to_owned(),
641 login: "rene_rs".to_owned(),
642 name: "rene_rs".to_owned(),
643 },
644 message_text: None,
645 system_message:
646 "rene_rs subscribed with Twitch Prime. They've subscribed for 11 months!"
647 .to_owned(),
648 event: UserNoticeEvent::SubOrResub {
649 is_resub: true,
650 cumulative_months: 11,
651 streak_months: None,
652 sub_plan: "Prime".to_owned(),
653 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
654 },
655 event_id: "resub".to_owned(),
656 badge_info: vec![],
657 badges: vec![Badge {
658 name: "premium".to_owned(),
659 version: "1".to_owned(),
660 },],
661 emotes: vec![],
662 name_color: Some(RGBColor {
663 r: 0x8A,
664 g: 0x2B,
665 b: 0xE2,
666 }),
667 message_id: "ca1f02fb-77ec-487d-a9b3-bc4bfef2fe8b".to_owned(),
668 server_timestamp: Utc.timestamp_millis_opt(1_590_628_650_446).unwrap(),
669 source: irc_message,
670 }
671 );
672 }
673
674 #[test]
675 pub fn test_raid() {
676 let src = "@badge-info=;badges=glhf-pledge/1;color=#FF69B4;display-name=iamelisabete;emotes=;flags=;id=bb99dda7-3736-4583-9114-52aa11b23d17;login=iamelisabete;mod=0;msg-id=raid;msg-param-displayName=iamelisabete;msg-param-login=iamelisabete;msg-param-profileImageURL=https://static-cdn.jtvnw.net/jtv_user_pictures/cae3ca63-510d-4715-b4ce-059dcf938978-profile_image-70x70.png;msg-param-viewerCount=430;room-id=71092938;subscriber=0;system-msg=430\\sraiders\\sfrom\\siamelisabete\\shave\\sjoined!;tmi-sent-ts=1594517796120;user-id=155874595;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
677 let irc_message = IRCMessage::parse(src).unwrap();
678 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
679
680 assert_eq!(
681 msg.sender,
682 TwitchUserBasics {
683 id: "155874595".to_owned(),
684 login: "iamelisabete".to_owned(),
685 name: "iamelisabete".to_owned(),
686 }
687 );
688 assert_eq!(msg.event, UserNoticeEvent::Raid {
689 viewer_count: 430,
690 profile_image_url: "https://static-cdn.jtvnw.net/jtv_user_pictures/cae3ca63-510d-4715-b4ce-059dcf938978-profile_image-70x70.png".to_owned(),
691 });
692 }
693
694 #[test]
695 pub fn test_subgift() {
696 let src = "@badge-info=;badges=sub-gifter/50;color=;display-name=AdamAtReflectStudios;emotes=;flags=;id=e21409b1-d25d-4a1a-b5cf-ef27d8b7030e;login=adamatreflectstudios;mod=0;msg-id=subgift;msg-param-gift-months=1;msg-param-months=2;msg-param-origin-id=da\\s39\\sa3\\see\\s5e\\s6b\\s4b\\s0d\\s32\\s55\\sbf\\sef\\s95\\s60\\s18\\s90\\saf\\sd8\\s07\\s09;msg-param-recipient-display-name=qatarking24xd;msg-param-recipient-id=236653628;msg-param-recipient-user-name=qatarking24xd;msg-param-sender-count=0;msg-param-sub-plan-name=Channel\\sSubscription\\s(xqcow);msg-param-sub-plan=1000;room-id=71092938;subscriber=0;system-msg=AdamAtReflectStudios\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\sqatarking24xd!;tmi-sent-ts=1594583782376;user-id=211711554;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
697 let irc_message = IRCMessage::parse(src).unwrap();
698 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
699
700 assert_eq!(
701 msg.event,
702 UserNoticeEvent::SubGift {
703 is_sender_anonymous: false,
704 cumulative_months: 2,
705 recipient: TwitchUserBasics {
706 id: "236653628".to_owned(),
707 login: "qatarking24xd".to_owned(),
708 name: "qatarking24xd".to_owned(),
709 },
710 sub_plan: "1000".to_owned(),
711 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
712 num_gifted_months: 1,
713 }
714 );
715 }
716
717 #[test]
718 pub fn test_subgift_ananonymousgifter() {
719 let src = "@badge-info=;badges=;color=;display-name=AnAnonymousGifter;emotes=;flags=;id=62c3fd39-84cc-452a-9096-628a5306633a;login=ananonymousgifter;mod=0;msg-id=subgift;msg-param-fun-string=FunStringThree;msg-param-gift-months=1;msg-param-months=13;msg-param-origin-id=da\\s39\\sa3\\see\\s5e\\s6b\\s4b\\s0d\\s32\\s55\\sbf\\sef\\s95\\s60\\s18\\s90\\saf\\sd8\\s07\\s09;msg-param-recipient-display-name=Dot0422;msg-param-recipient-id=151784015;msg-param-recipient-user-name=dot0422;msg-param-sub-plan-name=Channel\\sSubscription\\s(xqcow);msg-param-sub-plan=1000;room-id=71092938;subscriber=0;system-msg=An\\sanonymous\\suser\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\sDot0422!\\s;tmi-sent-ts=1594495108936;user-id=274598607;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
720 let irc_message = IRCMessage::parse(src).unwrap();
721 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
722
723 assert_eq!(
724 msg.event,
725 UserNoticeEvent::SubGift {
726 is_sender_anonymous: true,
727 cumulative_months: 13,
728 recipient: TwitchUserBasics {
729 id: "151784015".to_owned(),
730 login: "dot0422".to_owned(),
731 name: "Dot0422".to_owned(),
732 },
733 sub_plan: "1000".to_owned(),
734 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
735 num_gifted_months: 1,
736 }
737 );
738 }
739
740 #[test]
741 pub fn test_anonsubgift() {
742 let src = "@badge-info=;badges=;color=;display-name=xQcOW;emotes=;flags=;id=e21409b1-d25d-4a1a-b5cf-ef27d8b7030e;login=xqcow;mod=0;msg-id=anonsubgift;msg-param-gift-months=1;msg-param-months=2;msg-param-origin-id=da\\s39\\sa3\\see\\s5e\\s6b\\s4b\\s0d\\s32\\s55\\sbf\\sef\\s95\\s60\\s18\\s90\\saf\\sd8\\s07\\s09;msg-param-recipient-display-name=qatarking24xd;msg-param-recipient-id=236653628;msg-param-recipient-user-name=qatarking24xd;msg-param-sender-count=0;msg-param-sub-plan-name=Channel\\sSubscription\\s(xqcow);msg-param-sub-plan=1000;room-id=71092938;subscriber=0;system-msg=An\\sanonymous\\sgifter\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\sqatarking24xd!;tmi-sent-ts=1594583782376;user-id=71092938;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
745 let irc_message = IRCMessage::parse(src).unwrap();
746 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
747
748 assert_eq!(
749 msg.event,
750 UserNoticeEvent::SubGift {
751 is_sender_anonymous: true,
752 cumulative_months: 2,
753 recipient: TwitchUserBasics {
754 id: "236653628".to_owned(),
755 login: "qatarking24xd".to_owned(),
756 name: "qatarking24xd".to_owned(),
757 },
758 sub_plan: "1000".to_owned(),
759 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
760 num_gifted_months: 1,
761 }
762 );
763 }
764
765 #[test]
766 pub fn test_submysterygift() {
767 let src = "@badge-info=;badges=sub-gifter/50;color=;display-name=AdamAtReflectStudios;emotes=;flags=;id=049e6371-7023-4fca-8605-7dec60e72e12;login=adamatreflectstudios;mod=0;msg-id=submysterygift;msg-param-mass-gift-count=20;msg-param-origin-id=1f\\sbe\\sbb\\s4a\\s81\\s9a\\s65\\sd1\\s4b\\s77\\sf5\\s23\\s16\\s4a\\sd3\\s13\\s09\\se7\\sbe\\s55;msg-param-sender-count=100;msg-param-sub-plan=1000;room-id=71092938;subscriber=0;system-msg=AdamAtReflectStudios\\sis\\sgifting\\s20\\sTier\\s1\\sSubs\\sto\\sxQcOW's\\scommunity!\\sThey've\\sgifted\\sa\\stotal\\sof\\s100\\sin\\sthe\\schannel!;tmi-sent-ts=1594583777669;user-id=211711554;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
768 let irc_message = IRCMessage::parse(src).unwrap();
769 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
770
771 assert_eq!(
772 msg.event,
773 UserNoticeEvent::SubMysteryGift {
774 mass_gift_count: 20,
775 sender_total_gifts: Some(100),
776 sub_plan: "1000".to_owned(),
777 }
778 );
779 }
780
781 #[test]
782 pub fn test_submysterygift_twitch() {
783 let src = "@badge-info=;badges=sub-gifter/50;color=;display-name=twitch;emotes=;flags=;id=049e6371-7023-4fca-8605-7dec60e72e12;login=twitch;mod=0;msg-id=submysterygift;msg-param-mass-gift-count=20;msg-param-sender-count=50;msg-param-origin-id=1f\\sbe\\sbb\\s4a\\s81\\s9a\\s65\\sd1\\s4b\\s77\\sf5\\s23\\s16\\s4a\\sd3\\s13\\s09\\se7\\sbe\\s55;msg-param-sub-plan=1000;room-id=71092938;subscriber=0;system-msg=AdamAtReflectStudios\\sis\\sgifting\\s20\\sTier\\s1\\sSubs\\sto\\sxQcOW's\\scommunity!\\sThey've\\sgifted\\sa\\stotal\\sof\\s100\\sin\\sthe\\schannel!;tmi-sent-ts=1594583777669;user-id=12826;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
784 let irc_message = IRCMessage::parse(src).unwrap();
785 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
786
787 assert_eq!(
788 msg.event,
789 UserNoticeEvent::SubMysteryGift {
790 mass_gift_count: 20,
791 sender_total_gifts: Some(50),
792 sub_plan: "1000".to_owned(),
793 }
794 );
795 }
796
797 #[test]
798 pub fn test_submysterygift_twitch_missing_count() {
799 let src = "@badge-info=;badges=sub-gifter/50;color=;display-name=twitch;emotes=;flags=;id=049e6371-7023-4fca-8605-7dec60e72e12;login=twitch;mod=0;msg-id=submysterygift;msg-param-mass-gift-count=20;msg-param-origin-id=1f\\sbe\\sbb\\s4a\\s81\\s9a\\s65\\sd1\\s4b\\s77\\sf5\\s23\\s16\\s4a\\sd3\\s13\\s09\\se7\\sbe\\s55;msg-param-sub-plan=1000;room-id=71092938;subscriber=0;system-msg=AdamAtReflectStudios\\sis\\sgifting\\s20\\sTier\\s1\\sSubs\\sto\\sxQcOW's\\scommunity!\\sThey've\\sgifted\\sa\\stotal\\sof\\s100\\sin\\sthe\\schannel!;tmi-sent-ts=1594583777669;user-id=12826;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
800 let irc_message = IRCMessage::parse(src).unwrap();
801 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
802
803 assert_eq!(
804 msg.event,
805 UserNoticeEvent::SubMysteryGift {
806 mass_gift_count: 20,
807 sender_total_gifts: None,
808 sub_plan: "1000".to_owned(),
809 }
810 );
811 }
812
813 #[test]
814 pub fn test_submysterygift_ananonymousgifter() {
815 let src = "@badge-info=;badges=;color=;display-name=AnAnonymousGifter;emotes=;flags=;id=8db97752-3dee-460b-9001-e925d0e2ba5b;login=ananonymousgifter;mod=0;msg-id=submysterygift;msg-param-mass-gift-count=10;msg-param-origin-id=13\\s33\\sed\\sc0\\sef\\sa0\\s7b\\s9b\\s48\\s59\\scb\\scc\\se4\\s39\\s7b\\s90\\sf9\\s54\\s75\\s66;msg-param-sub-plan=1000;room-id=71092938;subscriber=0;system-msg=An\\sanonymous\\suser\\sis\\sgifting\\s10\\sTier\\s1\\sSubs\\sto\\sxQcOW's\\scommunity!;tmi-sent-ts=1585447099603;user-id=274598607;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
816 let irc_message = IRCMessage::parse(src).unwrap();
817 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
818
819 assert_eq!(
820 msg.event,
821 UserNoticeEvent::AnonSubMysteryGift {
822 mass_gift_count: 10,
823 sub_plan: "1000".to_owned(),
824 }
825 );
826 }
827
828 #[test]
829 pub fn test_anonsubmysterygift() {
830 let src = "@badge-info=;badges=;color=;display-name=xQcOW;emotes=;flags=;id=8db97752-3dee-460b-9001-e925d0e2ba5b;login=xqcow;mod=0;msg-id=anonsubmysterygift;msg-param-mass-gift-count=15;msg-param-origin-id=13\\s33\\sed\\sc0\\sef\\sa0\\s7b\\s9b\\s48\\s59\\scb\\scc\\se4\\s39\\s7b\\s90\\sf9\\s54\\s75\\s66;msg-param-sub-plan=2000;room-id=71092938;subscriber=0;system-msg=An\\sanonymous\\suser\\sis\\sgifting\\s10\\sTier\\s1\\sSubs\\sto\\sxQcOW's\\scommunity!;tmi-sent-ts=1585447099603;user-id=71092938;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
833 let irc_message = IRCMessage::parse(src).unwrap();
834 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
835
836 assert_eq!(
837 msg.event,
838 UserNoticeEvent::AnonSubMysteryGift {
839 mass_gift_count: 15,
840 sub_plan: "2000".to_owned(),
841 }
842 );
843 }
844
845 #[test]
846 pub fn test_giftpaidupgrade_no_promo() {
847 let src = "@badge-info=subscriber/2;badges=subscriber/2;color=#00FFF5;display-name=CrazyCrackAnimal;emotes=;flags=;id=7006f242-a45c-4e07-83b3-11f9c6d1ee28;login=crazycrackanimal;mod=0;msg-id=giftpaidupgrade;msg-param-sender-login=stridezgum;msg-param-sender-name=Stridezgum;room-id=71092938;subscriber=1;system-msg=CrazyCrackAnimal\\sis\\scontinuing\\sthe\\sGift\\sSub\\sthey\\sgot\\sfrom\\sStridezgum!;tmi-sent-ts=1594518849459;user-id=86082877;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
848
849 let irc_message = IRCMessage::parse(src).unwrap();
850 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
851
852 assert_eq!(
853 msg.event,
854 UserNoticeEvent::GiftPaidUpgrade {
855 gifter_login: "stridezgum".to_owned(),
856 gifter_name: "Stridezgum".to_owned(),
857 promotion: None,
858 }
859 );
860 }
861
862 #[test]
863 pub fn test_giftpaidupgrade_with_promo() {
864 let src = "@badge-info=subscriber/2;badges=subscriber/2;color=#00FFF5;display-name=CrazyCrackAnimal;emotes=;flags=;id=7006f242-a45c-4e07-83b3-11f9c6d1ee28;login=crazycrackanimal;mod=0;msg-id=giftpaidupgrade;msg-param-sender-login=stridezgum;msg-param-sender-name=Stridezgum;msg-param-promo-name=TestSubtember2020;msg-param-promo-gift-total=4003;room-id=71092938;subscriber=1;system-msg=CrazyCrackAnimal\\sis\\scontinuing\\sthe\\sGift\\sSub\\sthey\\sgot\\sfrom\\sStridezgum!\\sbla\\sbla\\bla\\sstuff\\sabout\\spromo\\shere;tmi-sent-ts=1594518849459;user-id=86082877;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
867
868 let irc_message = IRCMessage::parse(src).unwrap();
869 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
870
871 assert_eq!(
872 msg.event,
873 UserNoticeEvent::GiftPaidUpgrade {
874 gifter_login: "stridezgum".to_owned(),
875 gifter_name: "Stridezgum".to_owned(),
876 promotion: Some(SubGiftPromo {
877 promo_name: "TestSubtember2020".to_owned(),
878 total_gifts: 4003,
879 }),
880 }
881 );
882 }
883
884 #[test]
885 pub fn test_anongiftpaidupgrade_no_promo() {
886 let src = "@badge-info=subscriber/1;badges=subscriber/0,premium/1;color=#8A2BE2;display-name=samura1jack_ttv;emotes=;flags=;id=144ee636-0c1d-404e-8b29-35449a045a7e;login=samura1jack_ttv;mod=0;msg-id=anongiftpaidupgrade;room-id=71092938;subscriber=1;system-msg=samura1jack_ttv\\sis\\scontinuing\\sthe\\sGift\\sSub\\sthey\\sgot\\sfrom\\san\\sanonymous\\suser!;tmi-sent-ts=1594327421732;user-id=102707709;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
887
888 let irc_message = IRCMessage::parse(src).unwrap();
889 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
890
891 assert_eq!(
892 msg.event,
893 UserNoticeEvent::AnonGiftPaidUpgrade { promotion: None }
894 );
895 }
896
897 #[test]
898 pub fn test_anongiftpaidupgrade_with_promo() {
899 let src = "@badge-info=subscriber/1;badges=subscriber/0,premium/1;color=#8A2BE2;display-name=samura1jack_ttv;emotes=;flags=;id=144ee636-0c1d-404e-8b29-35449a045a7e;msg-param-promo-name=TestSubtember2020;msg-param-promo-gift-total=4003;login=samura1jack_ttv;mod=0;msg-id=anongiftpaidupgrade;room-id=71092938;subscriber=1;system-msg=samura1jack_ttv\\sis\\scontinuing\\sthe\\sGift\\sSub\\sthey\\sgot\\sfrom\\san\\sanonymous\\suser!\\sbla\\sbla\\bla\\sstuff\\sabout\\spromo\\shere;tmi-sent-ts=1594327421732;user-id=102707709;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
902 let irc_message = IRCMessage::parse(src).unwrap();
903 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
904
905 assert_eq!(
906 msg.event,
907 UserNoticeEvent::AnonGiftPaidUpgrade {
908 promotion: Some(SubGiftPromo {
909 promo_name: "TestSubtember2020".to_owned(),
910 total_gifts: 4003,
911 })
912 }
913 );
914 }
915
916 #[test]
917 pub fn test_ritual() {
918 let src = "@badge-info=;badges=;color=;display-name=SevenTest1;emotes=30259:0-6;id=37feed0f-b9c7-4c3a-b475-21c6c6d21c3d;login=seventest1;mod=0;msg-id=ritual;msg-param-ritual-name=new_chatter;room-id=6316121;subscriber=0;system-msg=Seventoes\\sis\\snew\\shere!;tmi-sent-ts=1508363903826;turbo=0;user-id=131260580;user-type= :tmi.twitch.tv USERNOTICE #seventoes :HeyGuys";
919 let irc_message = IRCMessage::parse(src).unwrap();
920 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
921
922 assert_eq!(
923 msg.event,
924 UserNoticeEvent::Ritual {
925 ritual_name: "new_chatter".to_owned()
926 }
927 );
928 }
929
930 #[test]
931 pub fn test_bitsbadgetier() {
932 let src = "@badge-info=subscriber/2;badges=subscriber/2,bits/1000;color=#FF4500;display-name=whoopiix;emotes=;flags=;id=d2b32a02-3071-4c52-b2ce-bc3716acdc44;login=whoopiix;mod=0;msg-id=bitsbadgetier;msg-param-threshold=1000;room-id=71092938;subscriber=1;system-msg=bits\\sbadge\\stier\\snotification;tmi-sent-ts=1594520403813;user-id=104252055;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
933 let irc_message = IRCMessage::parse(src).unwrap();
934 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
935
936 assert_eq!(
937 msg.event,
938 UserNoticeEvent::BitsBadgeTier { threshold: 1000 }
939 );
940 }
941
942 #[test]
943 pub fn test_unknown() {
944 let src = "@badge-info=;badges=sub-gifter/50;color=;display-name=AdamAtReflectStudios;emotes=;flags=;id=7f1336e4-f84a-4510-809d-e57bf50af0cc;login=adamatreflectstudios;mod=0;msg-id=rewardgift;msg-param-domain=pride_megacommerce_2020;msg-param-selected-count=100;msg-param-total-reward-count=100;msg-param-trigger-amount=20;msg-param-trigger-type=SUBGIFT;room-id=71092938;subscriber=0;system-msg=AdamAtReflectStudios's\\sGift\\sshared\\srewards\\sto\\s100\\sothers\\sin\\sChat!;tmi-sent-ts=1594583778756;user-id=211711554;user-type= :tmi.twitch.tv USERNOTICE #xqcow";
946 let irc_message = IRCMessage::parse(src).unwrap();
947 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
948
949 assert_eq!(msg.event, UserNoticeEvent::Unknown);
950 }
951
952 #[test]
953 pub fn test_sneaky_action_invalid_emote_tag() {
954 let src = r"@badge-info=subscriber/23;badges=moderator/1,subscriber/12;color=#19E6E6;display-name=randers;emotes=25:7-11,23-27/499:29-30;flags=;id=8c2918c2-adf4-4208-a554-8a72d016de70;login=randers;mod=1;msg-id=resub;msg-param-cumulative-months=23;msg-param-months=0;msg-param-should-share-streak=1;msg-param-streak-months=23;msg-param-sub-plan-name=look\sat\sthose\sshitty\semotes,\srip\s$5\sLUL;msg-param-sub-plan=1000;room-id=11148817;subscriber=1;system-msg=randers\ssubscribed\sat\sTier\s1.\sThey've\ssubscribed\sfor\s23\smonths,\scurrently\son\sa\s23\smonth\sstreak!;tmi-sent-ts=1595497450553;user-id=40286300;user-type=mod :tmi.twitch.tv USERNOTICE #pajlada :ACTION Kappa TEST TEST Kappa :)";
956 let irc_message = IRCMessage::parse(src).unwrap();
957 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
958
959 assert_eq!(
960 msg.message_text,
961 Some("ACTION Kappa TEST TEST Kappa :)".to_owned())
962 );
963 assert_eq!(
964 msg.emotes,
965 vec![
966 Emote {
967 id: "25".to_owned(),
968 char_range: Range { start: 7, end: 12 },
969 code: " Kapp".to_owned(),
970 },
971 Emote {
972 id: "25".to_owned(),
973 char_range: Range { start: 23, end: 28 },
974 code: " Kapp".to_owned(),
975 },
976 Emote {
977 id: "499".to_owned(),
978 char_range: Range { start: 29, end: 31 },
979 code: " :".to_owned(),
980 },
981 ]
982 );
983 }
984}