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,
51
52 pub event: UserNoticeEvent,
54
55 pub event_id: String,
58
59 pub badge_info: Vec<Badge>,
66 pub badges: Vec<Badge>,
68 pub emotes: Vec<Emote>,
74
75 pub name_color: Option<RGBColor>,
80
81 pub message_id: String,
84
85 pub server_timestamp: DateTime<Utc>,
87
88 pub source: IRCMessage,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
96#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
97pub struct SubGiftPromo {
98 pub total_gifts: u64,
100 pub promo_name: String,
102}
103
104impl SubGiftPromo {
105 fn parse_if_present(
106 source: &IRCMessage,
107 ) -> Result<Option<SubGiftPromo>, ServerMessageParseError> {
108 if let (Some(total_gifts), Some(promo_name)) = (
109 source.try_get_optional_number("msg-param-promo-gift-total")?,
110 source
111 .try_get_optional_nonempty_tag_value("msg-param-promo-name")?
112 .map(|s| s.to_owned()),
113 ) {
114 Ok(Some(SubGiftPromo {
115 total_gifts,
116 promo_name,
117 }))
118 } else {
119 Ok(None)
120 }
121 }
122}
123
124#[non_exhaustive]
149#[derive(Debug, Clone, PartialEq, Eq)]
150#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
151pub enum UserNoticeEvent {
152 SubOrResub {
159 is_resub: bool,
161 cumulative_months: u64,
163 streak_months: Option<u64>,
165 sub_plan: String,
167 sub_plan_name: String,
170 },
171
172 Raid {
175 viewer_count: u64,
177 profile_image_url: String,
183 },
184
185 SubGift {
192 is_sender_anonymous: bool,
196 cumulative_months: u64,
198 recipient: TwitchUserBasics,
200 sub_plan: String,
202 sub_plan_name: String,
205 num_gifted_months: u64,
207 },
208
209 SubMysteryGift {
212 mass_gift_count: u64,
214 sender_total_gifts: Option<u64>,
218 sub_plan: String,
221 },
222
223 AnonSubMysteryGift {
233 mass_gift_count: u64,
235 sub_plan: String,
238 },
239
240 GiftPaidUpgrade {
246 gifter_login: String,
250 gifter_name: String,
254 promotion: Option<SubGiftPromo>,
257 },
258
259 AnonGiftPaidUpgrade {
264 promotion: Option<SubGiftPromo>,
267 },
268
269 Ritual {
273 ritual_name: String,
275 },
276
277 BitsBadgeTier {
281 threshold: u64,
284 },
285
286 Announcement {
288 color: Option<String>,
290 },
291
292 #[doc(hidden)]
295 Unknown,
296}
297
298impl TryFrom<IRCMessage> for UserNoticeMessage {
299 type Error = ServerMessageParseError;
300
301 fn try_from(source: IRCMessage) -> Result<UserNoticeMessage, ServerMessageParseError> {
302 if source.command != "USERNOTICE" {
303 return Err(ServerMessageParseError::MismatchedCommand(Box::new(source)));
304 }
305
306 let sender = TwitchUserBasics {
314 id: source.try_get_nonempty_tag_value("user-id")?.to_owned(),
315 login: source.try_get_nonempty_tag_value("login")?.to_owned(),
316 name: source
317 .try_get_nonempty_tag_value("display-name")?
318 .to_owned(),
319 };
320
321 let event_id = source.try_get_nonempty_tag_value("msg-id")?.to_owned();
332 let event = match event_id.as_str() {
333 "sub" | "resub" => UserNoticeEvent::SubOrResub {
341 is_resub: &event_id == "resub",
342 cumulative_months: source.try_get_number("msg-param-cumulative-months")?,
343 streak_months: if source.try_get_bool("msg-param-should-share-streak")? {
344 Some(source.try_get_number("msg-param-streak-months")?)
345 } else {
346 None
347 },
348 sub_plan: source
349 .try_get_nonempty_tag_value("msg-param-sub-plan")?
350 .to_owned(),
351 sub_plan_name: source
352 .try_get_nonempty_tag_value("msg-param-sub-plan-name")?
353 .to_owned(),
354 },
355 "raid" => UserNoticeEvent::Raid {
362 viewer_count: source.try_get_number::<u64>("msg-param-viewerCount")?,
363 profile_image_url: source
364 .try_get_nonempty_tag_value("msg-param-profileImageURL")?
365 .to_owned(),
366 },
367 "subgift" | "anonsubgift" => UserNoticeEvent::SubGift {
377 is_sender_anonymous: event_id == "anonsubgift" || sender.id == "274598607",
379 cumulative_months: source.try_get_number("msg-param-months")?,
380 recipient: TwitchUserBasics {
381 id: source
382 .try_get_nonempty_tag_value("msg-param-recipient-id")?
383 .to_owned(),
384 login: source
385 .try_get_nonempty_tag_value("msg-param-recipient-user-name")?
386 .to_owned(),
387 name: source
388 .try_get_nonempty_tag_value("msg-param-recipient-display-name")?
389 .to_owned(),
390 },
391 sub_plan: source
392 .try_get_nonempty_tag_value("msg-param-sub-plan")?
393 .to_owned(),
394 sub_plan_name: source
395 .try_get_nonempty_tag_value("msg-param-sub-plan-name")?
396 .to_owned(),
397 num_gifted_months: source.try_get_number("msg-param-gift-months")?,
398 },
399 _ if (sender.id == "274598607" && event_id == "submysterygift")
412 || event_id == "anonsubmysterygift" =>
413 {
414 UserNoticeEvent::AnonSubMysteryGift {
415 mass_gift_count: source.try_get_number("msg-param-mass-gift-count")?,
416 sub_plan: source
417 .try_get_nonempty_tag_value("msg-param-sub-plan")?
418 .to_owned(),
419 }
420 }
421 "submysterygift" => UserNoticeEvent::SubMysteryGift {
423 mass_gift_count: source.try_get_number("msg-param-mass-gift-count")?,
424 sender_total_gifts: if sender.login != "twitch" {
425 Some(source.try_get_number("msg-param-sender-count")?)
426 } else {
427 source.try_get_number("msg-param-sender-count").ok()
429 },
430 sub_plan: source
431 .try_get_nonempty_tag_value("msg-param-sub-plan")?
432 .to_owned(),
433 },
434 "giftpaidupgrade" => UserNoticeEvent::GiftPaidUpgrade {
447 gifter_login: source
448 .try_get_nonempty_tag_value("msg-param-sender-login")?
449 .to_owned(),
450 gifter_name: source
451 .try_get_nonempty_tag_value("msg-param-sender-name")?
452 .to_owned(),
453 promotion: SubGiftPromo::parse_if_present(&source)?,
454 },
455 "anongiftpaidupgrade" => UserNoticeEvent::AnonGiftPaidUpgrade {
456 promotion: SubGiftPromo::parse_if_present(&source)?,
457 },
458
459 "ritual" => UserNoticeEvent::Ritual {
465 ritual_name: source
466 .try_get_nonempty_tag_value("msg-param-ritual-name")?
467 .to_owned(),
468 },
469
470 "bitsbadgetier" => UserNoticeEvent::BitsBadgeTier {
476 threshold: source
477 .try_get_number::<u64>("msg-param-threshold")?
478 .to_owned(),
479 },
480
481 "announcement" => UserNoticeEvent::Announcement {
485 color: source.tags.0.get("msg-param-color").cloned(),
486 },
487
488 _ => UserNoticeEvent::Unknown,
490 };
491
492 let message_text = source.params.get(1).cloned(); let emotes = if let Some(message_text) = &message_text {
494 source.try_get_emotes("emotes", message_text)?
495 } else {
496 vec![]
497 };
498
499 Ok(UserNoticeMessage {
500 channel_login: source.try_get_channel_login()?.to_owned(),
501 channel_id: source.try_get_nonempty_tag_value("room-id")?.to_owned(),
502 sender,
503 message_text,
504 system_message: source.try_get_tag_value("system-msg")?.to_owned(),
505 event,
506 event_id,
507 badge_info: source.try_get_badges("badge-info")?,
508 badges: source.try_get_badges("badges")?,
509 emotes,
510 name_color: source.try_get_color("color")?,
511 message_id: source.try_get_nonempty_tag_value("id")?.to_owned(),
512 server_timestamp: source.try_get_timestamp("tmi-sent-ts")?,
513 source,
514 })
515 }
516}
517
518impl From<UserNoticeMessage> for IRCMessage {
519 fn from(msg: UserNoticeMessage) -> IRCMessage {
520 msg.source
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use crate::message::twitch::{Badge, Emote, RGBColor, TwitchUserBasics};
527 use crate::message::{IRCMessage, SubGiftPromo, UserNoticeEvent, UserNoticeMessage};
528 use chrono::{TimeZone, Utc};
529 use std::convert::TryFrom;
530 use std::ops::Range;
531
532 #[test]
533 pub fn test_sub() {
534 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";
535 let irc_message = IRCMessage::parse(src).unwrap();
536 let msg = UserNoticeMessage::try_from(irc_message.clone()).unwrap();
537
538 assert_eq!(
539 msg,
540 UserNoticeMessage {
541 channel_login: "xqcow".to_owned(),
542 channel_id: "71092938".to_owned(),
543 sender: TwitchUserBasics {
544 id: "224005980".to_owned(),
545 login: "fallenseraphhh".to_owned(),
546 name: "fallenseraphhh".to_owned(),
547 },
548 message_text: None,
549 system_message: "fallenseraphhh subscribed with Twitch Prime.".to_owned(),
550 event: UserNoticeEvent::SubOrResub {
551 is_resub: false,
552 cumulative_months: 1,
553 streak_months: None,
554 sub_plan: "Prime".to_owned(),
555 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
556 },
557 event_id: "sub".to_owned(),
558 badge_info: vec![Badge {
559 name: "subscriber".to_owned(),
560 version: "0".to_owned(),
561 }],
562 badges: vec![
563 Badge {
564 name: "subscriber".to_owned(),
565 version: "0".to_owned(),
566 },
567 Badge {
568 name: "premium".to_owned(),
569 version: "1".to_owned(),
570 }
571 ],
572 emotes: vec![],
573 name_color: None,
574 message_id: "2a9bea11-a80a-49a0-a498-1642d457f775".to_owned(),
575 server_timestamp: Utc.timestamp_millis_opt(1_582_685_713_242).unwrap(),
576 source: irc_message,
577 }
578 );
579 }
580
581 #[test]
582 pub fn test_resub() {
583 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";
584 let irc_message = IRCMessage::parse(src).unwrap();
585 let msg = UserNoticeMessage::try_from(irc_message.clone()).unwrap();
586
587 assert_eq!(
588 msg,
589 UserNoticeMessage {
590 channel_login: "xqcow".to_owned(),
591 channel_id: "71092938".to_owned(),
592 sender: TwitchUserBasics {
593 id: "21156217".to_owned(),
594 login: "gutrin".to_owned(),
595 name: "Gutrin".to_owned(),
596 },
597 message_text: Some("xqcL".to_owned()),
598 system_message: "Gutrin subscribed at Tier 1. They've subscribed for 2 months, currently on a 2 month streak!".to_owned(),
599 event: UserNoticeEvent::SubOrResub {
600 is_resub: true,
601 cumulative_months: 2,
602 streak_months: Some(2),
603 sub_plan: "1000".to_owned(),
604 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
605 },
606 event_id: "resub".to_owned(),
607 badge_info: vec![Badge {
608 name: "subscriber".to_owned(),
609 version: "2".to_owned(),
610 }],
611 badges: vec![
612 Badge {
613 name: "subscriber".to_owned(),
614 version: "0".to_owned(),
615 },
616 Badge {
617 name: "battlerite_1".to_owned(),
618 version: "1".to_owned(),
619 }
620 ],
621 emotes: vec![
622 Emote {
623 id: "1035663".to_owned(),
624 char_range: Range { start: 0, end: 4 },
625 code: "xqcL".to_owned(),
626 }
627 ],
628 name_color: Some(RGBColor {
629 r: 0x00,
630 g: 0x00,
631 b: 0xFF,
632 }),
633 message_id: "e0975c76-054c-4954-8cb0-91b8867ec1ca".to_owned(),
634 server_timestamp: Utc.timestamp_millis_opt(1_581_713_640_019).unwrap(),
635 source: irc_message,
636 }
637 );
638 }
639
640 #[test]
641 pub fn test_resub_no_share_streak() {
642 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";
643 let irc_message = IRCMessage::parse(src).unwrap();
644 let msg = UserNoticeMessage::try_from(irc_message.clone()).unwrap();
645
646 assert_eq!(
647 msg,
648 UserNoticeMessage {
649 channel_login: "xqcow".to_owned(),
650 channel_id: "71092938".to_owned(),
651 sender: TwitchUserBasics {
652 id: "171356987".to_owned(),
653 login: "rene_rs".to_owned(),
654 name: "rene_rs".to_owned(),
655 },
656 message_text: None,
657 system_message:
658 "rene_rs subscribed with Twitch Prime. They've subscribed for 11 months!"
659 .to_owned(),
660 event: UserNoticeEvent::SubOrResub {
661 is_resub: true,
662 cumulative_months: 11,
663 streak_months: None,
664 sub_plan: "Prime".to_owned(),
665 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
666 },
667 event_id: "resub".to_owned(),
668 badge_info: vec![],
669 badges: vec![Badge {
670 name: "premium".to_owned(),
671 version: "1".to_owned(),
672 },],
673 emotes: vec![],
674 name_color: Some(RGBColor {
675 r: 0x8A,
676 g: 0x2B,
677 b: 0xE2,
678 }),
679 message_id: "ca1f02fb-77ec-487d-a9b3-bc4bfef2fe8b".to_owned(),
680 server_timestamp: Utc.timestamp_millis_opt(1_590_628_650_446).unwrap(),
681 source: irc_message,
682 }
683 );
684 }
685
686 #[test]
687 pub fn test_raid() {
688 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";
689 let irc_message = IRCMessage::parse(src).unwrap();
690 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
691
692 assert_eq!(
693 msg.sender,
694 TwitchUserBasics {
695 id: "155874595".to_owned(),
696 login: "iamelisabete".to_owned(),
697 name: "iamelisabete".to_owned(),
698 }
699 );
700 assert_eq!(msg.event, UserNoticeEvent::Raid {
701 viewer_count: 430,
702 profile_image_url: "https://static-cdn.jtvnw.net/jtv_user_pictures/cae3ca63-510d-4715-b4ce-059dcf938978-profile_image-70x70.png".to_owned(),
703 });
704 }
705
706 #[test]
707 pub fn test_subgift() {
708 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";
709 let irc_message = IRCMessage::parse(src).unwrap();
710 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
711
712 assert_eq!(
713 msg.event,
714 UserNoticeEvent::SubGift {
715 is_sender_anonymous: false,
716 cumulative_months: 2,
717 recipient: TwitchUserBasics {
718 id: "236653628".to_owned(),
719 login: "qatarking24xd".to_owned(),
720 name: "qatarking24xd".to_owned(),
721 },
722 sub_plan: "1000".to_owned(),
723 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
724 num_gifted_months: 1,
725 }
726 );
727 }
728
729 #[test]
730 pub fn test_subgift_ananonymousgifter() {
731 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";
732 let irc_message = IRCMessage::parse(src).unwrap();
733 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
734
735 assert_eq!(
736 msg.event,
737 UserNoticeEvent::SubGift {
738 is_sender_anonymous: true,
739 cumulative_months: 13,
740 recipient: TwitchUserBasics {
741 id: "151784015".to_owned(),
742 login: "dot0422".to_owned(),
743 name: "Dot0422".to_owned(),
744 },
745 sub_plan: "1000".to_owned(),
746 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
747 num_gifted_months: 1,
748 }
749 );
750 }
751
752 #[test]
753 pub fn test_anonsubgift() {
754 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";
757 let irc_message = IRCMessage::parse(src).unwrap();
758 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
759
760 assert_eq!(
761 msg.event,
762 UserNoticeEvent::SubGift {
763 is_sender_anonymous: true,
764 cumulative_months: 2,
765 recipient: TwitchUserBasics {
766 id: "236653628".to_owned(),
767 login: "qatarking24xd".to_owned(),
768 name: "qatarking24xd".to_owned(),
769 },
770 sub_plan: "1000".to_owned(),
771 sub_plan_name: "Channel Subscription (xqcow)".to_owned(),
772 num_gifted_months: 1,
773 }
774 );
775 }
776
777 #[test]
778 pub fn test_submysterygift() {
779 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";
780 let irc_message = IRCMessage::parse(src).unwrap();
781 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
782
783 assert_eq!(
784 msg.event,
785 UserNoticeEvent::SubMysteryGift {
786 mass_gift_count: 20,
787 sender_total_gifts: Some(100),
788 sub_plan: "1000".to_owned(),
789 }
790 );
791 }
792
793 #[test]
794 pub fn test_submysterygift_twitch() {
795 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";
796 let irc_message = IRCMessage::parse(src).unwrap();
797 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
798
799 assert_eq!(
800 msg.event,
801 UserNoticeEvent::SubMysteryGift {
802 mass_gift_count: 20,
803 sender_total_gifts: Some(50),
804 sub_plan: "1000".to_owned(),
805 }
806 );
807 }
808
809 #[test]
810 pub fn test_submysterygift_twitch_missing_count() {
811 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";
812 let irc_message = IRCMessage::parse(src).unwrap();
813 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
814
815 assert_eq!(
816 msg.event,
817 UserNoticeEvent::SubMysteryGift {
818 mass_gift_count: 20,
819 sender_total_gifts: None,
820 sub_plan: "1000".to_owned(),
821 }
822 );
823 }
824
825 #[test]
826 pub fn test_submysterygift_ananonymousgifter() {
827 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";
828 let irc_message = IRCMessage::parse(src).unwrap();
829 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
830
831 assert_eq!(
832 msg.event,
833 UserNoticeEvent::AnonSubMysteryGift {
834 mass_gift_count: 10,
835 sub_plan: "1000".to_owned(),
836 }
837 );
838 }
839
840 #[test]
841 pub fn test_anonsubmysterygift() {
842 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";
845 let irc_message = IRCMessage::parse(src).unwrap();
846 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
847
848 assert_eq!(
849 msg.event,
850 UserNoticeEvent::AnonSubMysteryGift {
851 mass_gift_count: 15,
852 sub_plan: "2000".to_owned(),
853 }
854 );
855 }
856
857 #[test]
858 pub fn test_giftpaidupgrade_no_promo() {
859 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";
860
861 let irc_message = IRCMessage::parse(src).unwrap();
862 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
863
864 assert_eq!(
865 msg.event,
866 UserNoticeEvent::GiftPaidUpgrade {
867 gifter_login: "stridezgum".to_owned(),
868 gifter_name: "Stridezgum".to_owned(),
869 promotion: None,
870 }
871 );
872 }
873
874 #[test]
875 pub fn test_giftpaidupgrade_with_promo() {
876 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";
879
880 let irc_message = IRCMessage::parse(src).unwrap();
881 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
882
883 assert_eq!(
884 msg.event,
885 UserNoticeEvent::GiftPaidUpgrade {
886 gifter_login: "stridezgum".to_owned(),
887 gifter_name: "Stridezgum".to_owned(),
888 promotion: Some(SubGiftPromo {
889 promo_name: "TestSubtember2020".to_owned(),
890 total_gifts: 4003,
891 }),
892 }
893 );
894 }
895
896 #[test]
897 pub fn test_anongiftpaidupgrade_no_promo() {
898 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";
899
900 let irc_message = IRCMessage::parse(src).unwrap();
901 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
902
903 assert_eq!(
904 msg.event,
905 UserNoticeEvent::AnonGiftPaidUpgrade { promotion: None }
906 );
907 }
908
909 #[test]
910 pub fn test_anongiftpaidupgrade_with_promo() {
911 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";
914 let irc_message = IRCMessage::parse(src).unwrap();
915 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
916
917 assert_eq!(
918 msg.event,
919 UserNoticeEvent::AnonGiftPaidUpgrade {
920 promotion: Some(SubGiftPromo {
921 promo_name: "TestSubtember2020".to_owned(),
922 total_gifts: 4003,
923 })
924 }
925 );
926 }
927
928 #[test]
929 pub fn test_ritual() {
930 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";
931 let irc_message = IRCMessage::parse(src).unwrap();
932 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
933
934 assert_eq!(
935 msg.event,
936 UserNoticeEvent::Ritual {
937 ritual_name: "new_chatter".to_owned()
938 }
939 );
940 }
941
942 #[test]
943 pub fn test_bitsbadgetier() {
944 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";
945 let irc_message = IRCMessage::parse(src).unwrap();
946 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
947
948 assert_eq!(
949 msg.event,
950 UserNoticeEvent::BitsBadgeTier { threshold: 1000 }
951 );
952 }
953
954 #[test]
955 pub fn test_unknown() {
956 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";
958 let irc_message = IRCMessage::parse(src).unwrap();
959 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
960
961 assert_eq!(msg.event, UserNoticeEvent::Unknown);
962 }
963
964 #[test]
965 pub fn test_sneaky_action_invalid_emote_tag() {
966 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 :)";
968 let irc_message = IRCMessage::parse(src).unwrap();
969 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
970
971 assert_eq!(
972 msg.message_text,
973 Some("ACTION Kappa TEST TEST Kappa :)".to_owned())
974 );
975 assert_eq!(
976 msg.emotes,
977 vec![
978 Emote {
979 id: "25".to_owned(),
980 char_range: Range { start: 7, end: 12 },
981 code: " Kapp".to_owned(),
982 },
983 Emote {
984 id: "25".to_owned(),
985 char_range: Range { start: 23, end: 28 },
986 code: " Kapp".to_owned(),
987 },
988 Emote {
989 id: "499".to_owned(),
990 char_range: Range { start: 29, end: 31 },
991 code: " :".to_owned(),
992 },
993 ]
994 );
995 }
996
997 #[test]
998 pub fn test_announcement() {
999 let src = r"@badge-info=subscriber/88;badges=moderator/1,subscriber/72;color=#19E6E6;display-name=randers;emotes=185989:11-15/25:17-21/1902:23-27;flags=;id=9045a344-75b9-44fa-af49-c413bcb39559;login=randers;mod=1;msg-id=announcement;msg-param-color=PRIMARY;room-id=11148817;subscriber=1;system-msg=;tmi-sent-ts=1780872797175;user-id=40286300;user-type=mod;vip=0 :tmi.twitch.tv USERNOTICE #pajlada :hello pajs pajaH Kappa Keepo";
1000
1001 let irc_message = IRCMessage::parse(src).unwrap();
1002 let msg = UserNoticeMessage::try_from(irc_message).unwrap();
1003
1004 assert_eq!(
1005 msg.message_text,
1006 Some("hello pajs pajaH Kappa Keepo".to_owned())
1007 );
1008 assert_eq!(
1009 msg.event,
1010 UserNoticeEvent::Announcement {
1011 color: Some("PRIMARY".to_owned())
1012 }
1013 );
1014 }
1015}