twitch_irc/message/commands/
userstate.rs

1use crate::message::commands::IRCMessageParseExt;
2use crate::message::twitch::{Badge, RGBColor};
3use crate::message::{IRCMessage, ServerMessageParseError};
4use std::collections::HashSet;
5use std::convert::TryFrom;
6
7#[cfg(feature = "with-serde")]
8use {serde::Deserialize, serde::Serialize};
9
10/// Sent when you join a channel or when you successfully sent a `PRIVMSG` message to a channel.
11///
12/// This specifies details about the logged in user in a given channel.
13///
14/// This message is similar to `GLOBALUSERSTATE`, but carries the context of a `channel_login`
15/// (and therefore possibly different `badges` and `badge_info`) and omits the `user_id`.
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
18pub struct UserStateMessage {
19    /// Login name of the channel this `USERSTATE` message specifies the logged in user's state in.
20    pub channel_login: String,
21    /// (Display) name of the logged in user.
22    pub user_name: String,
23    /// Metadata related to the chat badges in the `badges` tag.
24    ///
25    /// Currently this is used only for `subscriber`, to indicate the exact number of months
26    /// the user has been a subscriber. This number is finer grained than the version number in
27    /// badges. For example, a user who has been a subscriber for 45 months would have a
28    /// `badge_info` value of 45 but might have a `badges` `version` number for only 3 years.
29    pub badge_info: Vec<Badge>,
30    /// List of badges the logged in user has in this channel.
31    pub badges: Vec<Badge>,
32    /// List of emote set IDs the logged in user has available. This always contains at least 0.
33    pub emote_sets: HashSet<String>,
34    /// What name color the logged in user has chosen. The same color is used in all channels.
35    pub name_color: Option<RGBColor>,
36
37    /// The message that this `UserStateMessage` was parsed from.
38    pub source: IRCMessage,
39}
40
41impl TryFrom<IRCMessage> for UserStateMessage {
42    type Error = ServerMessageParseError;
43
44    fn try_from(source: IRCMessage) -> Result<UserStateMessage, ServerMessageParseError> {
45        if source.command != "USERSTATE" {
46            return Err(ServerMessageParseError::MismatchedCommand(source));
47        }
48
49        Ok(UserStateMessage {
50            channel_login: source.try_get_channel_login()?.to_owned(),
51            user_name: source
52                .try_get_nonempty_tag_value("display-name")?
53                .to_owned(),
54            badge_info: source.try_get_badges("badge-info")?,
55            badges: source.try_get_badges("badges")?,
56            emote_sets: source.try_get_emote_sets("emote-sets")?,
57            name_color: source.try_get_color("color")?,
58            source,
59        })
60    }
61}
62
63impl From<UserStateMessage> for IRCMessage {
64    fn from(msg: UserStateMessage) -> IRCMessage {
65        msg.source
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use crate::message::commands::userstate::UserStateMessage;
72    use crate::message::twitch::RGBColor;
73    use crate::message::{Badge, IRCMessage};
74    use std::convert::TryFrom;
75
76    #[test]
77    pub fn test_basic() {
78        let src = "@badge-info=;badges=;color=#FF0000;display-name=TESTUSER;emote-sets=0;mod=0;subscriber=0;user-type= :tmi.twitch.tv USERSTATE #randers";
79        let irc_message = IRCMessage::parse(src).unwrap();
80        let msg = UserStateMessage::try_from(irc_message.clone()).unwrap();
81
82        assert_eq!(
83            msg,
84            UserStateMessage {
85                channel_login: "randers".to_owned(),
86                user_name: "TESTUSER".to_owned(),
87                badge_info: vec![],
88                badges: vec![],
89                emote_sets: vec!["0".to_owned()].into_iter().collect(),
90                name_color: Some(RGBColor {
91                    r: 0xFF,
92                    g: 0x00,
93                    b: 0x00
94                }),
95                source: irc_message
96            }
97        )
98    }
99
100    #[test]
101    pub fn test_uuid_emote_set_id() {
102        let src = "@badge-info=;badges=moderator/1;color=#8A2BE2;display-name=TESTUSER;emote-sets=0,75c09c7b-332a-43ec-8be8-1d4571706155;mod=1;subscriber=0;user-type=mod :tmi.twitch.tv USERSTATE #randers";
103        let irc_message = IRCMessage::parse(src).unwrap();
104        let msg = UserStateMessage::try_from(irc_message.clone()).unwrap();
105
106        assert_eq!(
107            msg,
108            UserStateMessage {
109                channel_login: "randers".to_owned(),
110                user_name: "TESTUSER".to_owned(),
111                badge_info: vec![],
112                badges: vec![Badge {
113                    name: "moderator".to_owned(),
114                    version: "1".to_owned()
115                }],
116                emote_sets: vec![
117                    "0".to_owned(),
118                    "75c09c7b-332a-43ec-8be8-1d4571706155".to_owned()
119                ]
120                .into_iter()
121                .collect(),
122                name_color: Some(RGBColor {
123                    r: 0x8A,
124                    g: 0x2B,
125                    b: 0xE2
126                }),
127                source: irc_message
128            }
129        )
130    }
131}