Skip to main content

twitch_irc/message/commands/
roomstate.rs

1use crate::message::commands::IRCMessageParseExt;
2use crate::message::{IRCMessage, ServerMessageParseError};
3use std::convert::TryFrom;
4use std::time::Duration;
5
6#[cfg(feature = "with-serde")]
7use {serde::Deserialize, serde::Serialize};
8
9/// Sent when a channel is initially joined or when a channel updates it state.
10///
11/// When a channel is initially is joined, a `ROOMSTATE` message is sent specifying
12/// all the settings.
13/// If any of these settings are updated while you are joined to a channel,
14/// a `ROOMSTATE` is sent only containing the new value for that particular setting.
15/// Other settings will be `None`.
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
18pub struct RoomStateMessage {
19    /// Login name of the channel whose "room state" is updated.
20    pub channel_login: String,
21    /// ID of the channel whose "room state" is updated.
22    pub channel_id: String,
23
24    /// If present, specifies a new setting for the "emote only" mode.
25    /// (Controlled by `/emoteonly` and `/emoteonlyoff` commands in chat)
26    ///
27    /// If `true`, emote-only mode was enabled, if `false` emote-only mode was disabled.
28    ///
29    /// In emote-only mode, users that are not moderator or VIP can only send messages that
30    /// are completely composed of emotes.
31    pub emote_only: Option<bool>,
32
33    /// If present, specifies a new setting for followers-only mode.
34    /// (Controlled by `/followers` and `/followersoff` commands in chat)
35    ///
36    /// See the documentation on `FollowersOnlyMode` for more details on the possible settings.
37    pub followers_only: Option<FollowersOnlyMode>,
38
39    /// If present, specifies a new setting for the "r9k" beta mode (also sometimes called
40    /// unique-chat mode, controlled by the `/r9kbeta` and `/r9kbetaoff` commands)
41    ///
42    /// If `true`, r9k mode was enabled, if `false` r9k mode was disabled.
43    pub r9k: Option<bool>,
44
45    /// If present, specifies a new slow-mode setting. (Controlled by `/slow` and `/slowoff` commands).
46    ///
47    /// A duration of 0 seconds specifies that slow mode was disabled.
48    /// Any non-0 duration specifies the minimum time users must wait between sending individual messages.
49    /// Slow-mode does not apply to moderators or VIPs, and in some cases does not apply to subscribers too
50    /// (via a setting that the streamer controls).
51    ///
52    /// Slow mode can only be controlled in increments of full seconds, so this `Duration` will
53    /// only contains values that are whole multiples of 1 second.
54    pub slow_mode: Option<Duration>,
55
56    /// If present, specifies a new setting for subscribers-only mode (`/subscribers` and
57    /// `/subscribersoff` commands).
58    ///
59    /// If `true`, subscribers-only mode was enabled, if `false`, it was disabled.
60    pub subscribers_only: Option<bool>,
61
62    /// The message that this `RoomStateMessage` was parsed from.
63    pub source: IRCMessage,
64}
65
66/// Specifies the followers-only mode a chat is in or was put in.
67#[derive(Debug, Clone, PartialEq, Eq)]
68#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
69pub enum FollowersOnlyMode {
70    /// Followers-only mode is/was disabled. All users, including user that are not followers,
71    /// can send chat messages.
72    Disabled,
73
74    /// Followers-only mode is/was enabled. All users must have been following for at least this
75    /// amount of time before being able to send chat messages.
76    ///
77    /// Note that this duration can be 0 to signal that all followers can chat. Otherwise,
78    /// it will always a value that is a multiple of 1 minute. (1 minute is the highest resolution
79    /// that can be specified)
80    ///
81    /// Moderator, VIPs or
82    /// [verified bots](https://dev.twitch.tv/docs/irc/guide#known-and-verified-bots) bypass
83    /// this setting and can send messages anyways.
84    Enabled(Duration),
85}
86
87impl TryFrom<IRCMessage> for RoomStateMessage {
88    type Error = ServerMessageParseError;
89
90    fn try_from(source: IRCMessage) -> Result<RoomStateMessage, ServerMessageParseError> {
91        if source.command != "ROOMSTATE" {
92            return Err(ServerMessageParseError::MismatchedCommand(Box::new(source)));
93        }
94
95        // examples:
96        // full state: @emote-only=0;followers-only=-1;r9k=0;rituals=0;room-id=40286300;slow=0;subs-only=0 :tmi.twitch.tv ROOMSTATE #randers
97        // just one of the properties was updated: @emote-only=1;room-id=40286300 :tmi.twitch.tv ROOMSTATE #randers
98
99        // emote-only, r9k, subs-only: 0 (disabled) or 1 (enabled).
100        // followers-only: -1 means disabled, 0 means all followers can chat (essentially
101        // duration = 0), and any number above 0 is the time in minutes before user can take)
102        // slow: number of seconds between messages that users have to wait. Disabled slow-mode
103        // is slow=0, anything other than that is enabled
104
105        Ok(RoomStateMessage {
106            channel_login: source.try_get_channel_login()?.to_owned(),
107            channel_id: source.try_get_nonempty_tag_value("room-id")?.to_owned(),
108            emote_only: source.try_get_optional_bool("emote-only")?,
109            followers_only: source
110                .try_get_optional_number::<i64>("followers-only")?
111                .map(|n| match n {
112                    n if n >= 0 => FollowersOnlyMode::Enabled(Duration::from_secs((n * 60) as u64)),
113                    _ => FollowersOnlyMode::Disabled,
114                }),
115            r9k: source.try_get_optional_bool("r9k")?,
116            slow_mode: source
117                .try_get_optional_number::<u64>("slow")?
118                .map(Duration::from_secs),
119            subscribers_only: source.try_get_optional_bool("subs-only")?,
120            source,
121        })
122    }
123}
124
125impl From<RoomStateMessage> for IRCMessage {
126    fn from(msg: RoomStateMessage) -> IRCMessage {
127        msg.source
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use crate::message::commands::roomstate::FollowersOnlyMode;
134    use crate::message::{IRCMessage, RoomStateMessage};
135    use std::convert::TryFrom;
136    use std::time::Duration;
137
138    #[test]
139    pub fn test_basic_full() {
140        let src = "@emote-only=0;followers-only=-1;r9k=0;rituals=0;room-id=40286300;slow=0;subs-only=0 :tmi.twitch.tv ROOMSTATE #randers";
141        let irc_message = IRCMessage::parse(src).unwrap();
142        let msg = RoomStateMessage::try_from(irc_message.clone()).unwrap();
143
144        assert_eq!(
145            msg,
146            RoomStateMessage {
147                channel_login: "randers".to_owned(),
148                channel_id: "40286300".to_owned(),
149                emote_only: Some(false),
150                followers_only: Some(FollowersOnlyMode::Disabled),
151                r9k: Some(false),
152                slow_mode: Some(Duration::from_secs(0)),
153                subscribers_only: Some(false),
154                source: irc_message
155            }
156        );
157    }
158
159    #[test]
160    pub fn test_basic_full2() {
161        let src = "@emote-only=1;followers-only=0;r9k=1;rituals=0;room-id=40286300;slow=5;subs-only=1 :tmi.twitch.tv ROOMSTATE #randers";
162        let irc_message = IRCMessage::parse(src).unwrap();
163        let msg = RoomStateMessage::try_from(irc_message.clone()).unwrap();
164
165        assert_eq!(
166            msg,
167            RoomStateMessage {
168                channel_login: "randers".to_owned(),
169                channel_id: "40286300".to_owned(),
170                emote_only: Some(true),
171                followers_only: Some(FollowersOnlyMode::Enabled(Duration::from_secs(0))),
172                r9k: Some(true),
173                slow_mode: Some(Duration::from_secs(5)),
174                subscribers_only: Some(true),
175                source: irc_message
176            }
177        );
178    }
179
180    #[test]
181    pub fn test_followers_non_zero() {
182        let src = "@emote-only=1;followers-only=10;r9k=1;rituals=0;room-id=40286300;slow=5;subs-only=1 :tmi.twitch.tv ROOMSTATE #randers";
183        let irc_message = IRCMessage::parse(src).unwrap();
184        let msg = RoomStateMessage::try_from(irc_message).unwrap();
185
186        assert_eq!(
187            msg.followers_only,
188            Some(FollowersOnlyMode::Enabled(Duration::from_secs(10 * 60))) // 10 minutes
189        );
190    }
191
192    #[test]
193    pub fn test_partial_1() {
194        let src = "@room-id=40286300;slow=5 :tmi.twitch.tv ROOMSTATE #randers";
195        let irc_message = IRCMessage::parse(src).unwrap();
196        let msg = RoomStateMessage::try_from(irc_message.clone()).unwrap();
197
198        assert_eq!(
199            msg,
200            RoomStateMessage {
201                channel_login: "randers".to_owned(),
202                channel_id: "40286300".to_owned(),
203                emote_only: None,
204                followers_only: None,
205                r9k: None,
206                slow_mode: Some(Duration::from_secs(5)),
207                subscribers_only: None,
208                source: irc_message
209            }
210        );
211    }
212
213    #[test]
214    pub fn test_partial_2() {
215        let src = "@emote-only=1;room-id=40286300 :tmi.twitch.tv ROOMSTATE #randers";
216        let irc_message = IRCMessage::parse(src).unwrap();
217        let msg = RoomStateMessage::try_from(irc_message.clone()).unwrap();
218
219        assert_eq!(
220            msg,
221            RoomStateMessage {
222                channel_login: "randers".to_owned(),
223                channel_id: "40286300".to_owned(),
224                emote_only: Some(true),
225                followers_only: None,
226                r9k: None,
227                slow_mode: None,
228                subscribers_only: None,
229                source: irc_message
230            }
231        );
232    }
233}