irc_async/proto/
mode.rs

1//! A module defining an API for IRC user and channel modes.
2use std::fmt;
3
4use crate::proto::Command;
5use crate::proto::MessageParseError::{self, *};
6use crate::proto::ModeParseError::*;
7
8/// A marker trait for different kinds of Modes.
9pub trait ModeType: fmt::Display + fmt::Debug + Clone + PartialEq {
10    /// Creates a command of this kind.
11    fn mode(target: &str, modes: &[Mode<Self>]) -> Command;
12
13    /// Returns true if this mode takes an argument, and false otherwise.
14    fn takes_arg(&self) -> bool;
15}
16
17/// User modes for the MODE command.
18#[derive(Clone, Debug, PartialEq)]
19pub enum UserMode {
20    /// a - user is flagged as away
21    Away,
22    /// i - marks a users as invisible
23    Invisible,
24    /// w - user receives wallops
25    Wallops,
26    /// r - restricted user connection
27    Restricted,
28    /// o - operator flag
29    Oper,
30    /// O - local operator flag
31    LocalOper,
32    /// s - marks a user for receipt of server notices
33    ServerNotices,
34    /// x - masked hostname
35    MaskedHost,
36
37    /// Any other unknown-to-the-crate mode.
38    Unknown(char),
39}
40
41impl ModeType for UserMode {
42    fn mode(target: &str, modes: &[Mode<Self>]) -> Command {
43        Command::UserMODE(target.to_owned(), modes.to_owned())
44    }
45
46    fn takes_arg(&self) -> bool {
47        false
48    }
49}
50
51impl UserMode {
52    fn from_char(c: char) -> UserMode {
53        use self::UserMode::*;
54
55        match c {
56            'a' => Away,
57            'i' => Invisible,
58            'w' => Wallops,
59            'r' => Restricted,
60            'o' => Oper,
61            'O' => LocalOper,
62            's' => ServerNotices,
63            'x' => MaskedHost,
64            _ => Unknown(c),
65        }
66    }
67}
68
69impl fmt::Display for UserMode {
70    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71        use self::UserMode::*;
72
73        write!(
74            f,
75            "{}",
76            match *self {
77                Away => 'a',
78                Invisible => 'i',
79                Wallops => 'w',
80                Restricted => 'r',
81                Oper => 'o',
82                LocalOper => 'O',
83                ServerNotices => 's',
84                MaskedHost => 'x',
85                Unknown(c) => c,
86            }
87        )
88    }
89}
90
91/// Channel modes for the MODE command.
92#[derive(Clone, Debug, PartialEq)]
93pub enum ChannelMode {
94    /// b - ban the user from joining or speaking in the channel
95    Ban,
96    /// e - exemptions from bans
97    Exception,
98    /// l - limit the maximum number of users in a channel
99    Limit,
100    /// i - channel becomes invite-only
101    InviteOnly,
102    /// I - exception to invite-only rule
103    InviteException,
104    /// k - specify channel key
105    Key,
106    /// m - channel is in moderated mode
107    Moderated,
108    /// r - entry for registered users only
109    RegisteredOnly,
110    /// s - channel is hidden from listings
111    Secret,
112    /// t - require permissions to edit topic
113    ProtectedTopic,
114    /// n - users must join channels to message them
115    NoExternalMessages,
116
117    /// q - user gets founder permission
118    Founder,
119    /// a - user gets admin or protected permission
120    Admin,
121    /// o - user gets oper permission
122    Oper,
123    /// h - user gets halfop permission
124    Halfop,
125    /// v - user gets voice permission
126    Voice,
127
128    /// Any other unknown-to-the-crate mode.
129    Unknown(char),
130}
131
132impl ModeType for ChannelMode {
133    fn mode(target: &str, modes: &[Mode<Self>]) -> Command {
134        Command::ChannelMODE(target.to_owned(), modes.to_owned())
135    }
136
137    fn takes_arg(&self) -> bool {
138        use self::ChannelMode::*;
139
140        match *self {
141            Ban | Exception | Limit | InviteException | Key | Founder | Admin | Oper | Halfop
142            | Voice => true,
143            _ => false,
144        }
145    }
146}
147
148impl ChannelMode {
149    fn from_char(c: char) -> ChannelMode {
150        use self::ChannelMode::*;
151
152        match c {
153            'b' => Ban,
154            'e' => Exception,
155            'l' => Limit,
156            'i' => InviteOnly,
157            'I' => InviteException,
158            'k' => Key,
159            'm' => Moderated,
160            'r' => RegisteredOnly,
161            's' => Secret,
162            't' => ProtectedTopic,
163            'n' => NoExternalMessages,
164            'q' => Founder,
165            'a' => Admin,
166            'o' => Oper,
167            'h' => Halfop,
168            'v' => Voice,
169            _ => Unknown(c),
170        }
171    }
172}
173
174impl fmt::Display for ChannelMode {
175    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176        use self::ChannelMode::*;
177
178        write!(
179            f,
180            "{}",
181            match *self {
182                Ban => 'b',
183                Exception => 'e',
184                Limit => 'l',
185                InviteOnly => 'i',
186                InviteException => 'I',
187                Key => 'k',
188                Moderated => 'm',
189                RegisteredOnly => 'r',
190                Secret => 's',
191                ProtectedTopic => 't',
192                NoExternalMessages => 'n',
193                Founder => 'q',
194                Admin => 'a',
195                Oper => 'o',
196                Halfop => 'h',
197                Voice => 'v',
198                Unknown(c) => c,
199            }
200        )
201    }
202}
203
204/// A mode argument for the MODE command.
205#[derive(Clone, Debug, PartialEq)]
206pub enum Mode<T>
207where
208    T: ModeType,
209{
210    /// Adding the specified mode, optionally with an argument.
211    Plus(T, Option<String>),
212    /// Removing the specified mode, optionally with an argument.
213    Minus(T, Option<String>),
214}
215
216impl<T> Mode<T>
217where
218    T: ModeType,
219{
220    /// Creates a plus mode with an `&str` argument.
221    pub fn plus(inner: T, arg: Option<&str>) -> Mode<T> {
222        Mode::Plus(inner, arg.map(|s| s.to_owned()))
223    }
224
225    /// Creates a minus mode with an `&str` argument.
226    pub fn minus(inner: T, arg: Option<&str>) -> Mode<T> {
227        Mode::Minus(inner, arg.map(|s| s.to_owned()))
228    }
229}
230
231impl<T> fmt::Display for Mode<T>
232where
233    T: ModeType,
234{
235    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
236        match *self {
237            Mode::Plus(ref mode, Some(ref arg)) => write!(f, "+{} {}", mode, arg),
238            Mode::Minus(ref mode, Some(ref arg)) => write!(f, "+{} {}", mode, arg),
239            Mode::Plus(ref mode, None) => write!(f, "+{}", mode),
240            Mode::Minus(ref mode, None) => write!(f, "-{}", mode),
241        }
242    }
243}
244
245enum PlusMinus {
246    Plus,
247    Minus,
248}
249
250// MODE user [modes]
251impl Mode<UserMode> {
252    // TODO: turning more edge cases into errors.
253    /// Parses the specified mode string as user modes.
254    pub fn from_user_mode_string(s: &str) -> Result<Vec<Mode<UserMode>>, MessageParseError> {
255        use self::PlusMinus::*;
256
257        let mut res = vec![];
258        let mut pieces = s.split(' ');
259        for term in pieces.clone() {
260            if term.starts_with('+') || term.starts_with('-') {
261                let _ = pieces.next();
262
263                let mut chars = term.chars();
264                let init = match chars.next() {
265                    Some('+') => Plus,
266                    Some('-') => Minus,
267                    Some(c) => {
268                        return Err(InvalidModeString {
269                            string: s.to_owned(),
270                            cause: InvalidModeModifier { modifier: c },
271                        })
272                    }
273                    None => {
274                        return Err(InvalidModeString {
275                            string: s.to_owned(),
276                            cause: MissingModeModifier,
277                        })
278                    }
279                };
280
281                for c in chars {
282                    let mode = UserMode::from_char(c);
283                    let arg = if mode.takes_arg() {
284                        pieces.next()
285                    } else {
286                        None
287                    };
288                    res.push(match init {
289                        Plus => Mode::Plus(mode, arg.map(|s| s.to_owned())),
290                        Minus => Mode::Minus(mode, arg.map(|s| s.to_owned())),
291                    })
292                }
293            }
294        }
295
296        Ok(res)
297    }
298}
299
300// MODE channel [modes [modeparams]]
301impl Mode<ChannelMode> {
302    // TODO: turning more edge cases into errors.
303    /// Parses the specified mode string as channel modes.
304    pub fn from_channel_mode_string(s: &str) -> Result<Vec<Mode<ChannelMode>>, MessageParseError> {
305        use self::PlusMinus::*;
306
307        let mut res = vec![];
308        let mut pieces = s.split(' ');
309        for term in pieces.clone() {
310            if term.starts_with('+') || term.starts_with('-') {
311                let _ = pieces.next();
312
313                let mut chars = term.chars();
314                let init = match chars.next() {
315                    Some('+') => Plus,
316                    Some('-') => Minus,
317                    Some(c) => {
318                        return Err(InvalidModeString {
319                            string: s.to_owned(),
320                            cause: InvalidModeModifier { modifier: c },
321                        })
322                    }
323                    None => {
324                        return Err(InvalidModeString {
325                            string: s.to_owned(),
326                            cause: MissingModeModifier,
327                        })
328                    }
329                };
330
331                for c in chars {
332                    let mode = ChannelMode::from_char(c);
333                    let arg = if mode.takes_arg() {
334                        pieces.next()
335                    } else {
336                        None
337                    };
338                    res.push(match init {
339                        Plus => Mode::Plus(mode, arg.map(|s| s.to_owned())),
340                        Minus => Mode::Minus(mode, arg.map(|s| s.to_owned())),
341                    })
342                }
343            }
344        }
345
346        Ok(res)
347    }
348}