irc_proto/
command.rs

1//! Enumeration of all available client commands.
2use std::str::FromStr;
3
4use crate::chan::ChannelExt;
5use crate::error::MessageParseError;
6use crate::mode::{ChannelMode, Mode, UserMode};
7use crate::response::Response;
8
9/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812). This
10/// also includes commands from the
11/// [capabilities extension](https://tools.ietf.org/html/draft-mitchell-irc-capabilities-01).
12/// Additionally, this includes some common additional commands from popular IRCds.
13#[derive(Clone, Debug, PartialEq)]
14pub enum Command {
15    // 3.1 Connection Registration
16    /// PASS :password
17    PASS(String),
18    /// NICK :nickname
19    NICK(String),
20    /// USER user mode * :realname
21    USER(String, String, String),
22    /// OPER name :password
23    OPER(String, String),
24    /// MODE nickname modes
25    UserMODE(String, Vec<Mode<UserMode>>),
26    /// SERVICE nickname reserved distribution type reserved :info
27    SERVICE(String, String, String, String, String, String),
28    /// QUIT :comment
29    QUIT(Option<String>),
30    /// SQUIT server :comment
31    SQUIT(String, String),
32
33    // 3.2 Channel operations
34    /// JOIN chanlist [chankeys] :[Real name]
35    JOIN(String, Option<String>, Option<String>),
36    /// PART chanlist :[comment]
37    PART(String, Option<String>),
38    /// MODE channel [modes [modeparams]]
39    ChannelMODE(String, Vec<Mode<ChannelMode>>),
40    /// TOPIC channel :[topic]
41    TOPIC(String, Option<String>),
42    /// NAMES [chanlist :[target]]
43    NAMES(Option<String>, Option<String>),
44    /// LIST [chanlist :[target]]
45    LIST(Option<String>, Option<String>),
46    /// INVITE nickname channel
47    INVITE(String, String),
48    /// KICK chanlist userlist :[comment]
49    KICK(String, String, Option<String>),
50
51    // 3.3 Sending messages
52    /// PRIVMSG msgtarget :message
53    ///
54    /// ## Responding to a `PRIVMSG`
55    ///
56    /// When responding to a message, it is not sufficient to simply copy the message target
57    /// (msgtarget). This will work just fine for responding to messages in channels where the
58    /// target is the same for all participants. However, when the message is sent directly to a
59    /// user, this target will be that client's username, and responding to that same target will
60    /// actually mean sending itself a response. In such a case, you should instead respond to the
61    /// user sending the message as specified in the message prefix. Since this is a common
62    /// pattern, there is a utility function
63    /// [`Message::response_target`]
64    /// which is used for this exact purpose.
65    PRIVMSG(String, String),
66    /// NOTICE msgtarget :message
67    ///
68    /// ## Responding to a `NOTICE`
69    ///
70    /// When responding to a notice, it is not sufficient to simply copy the message target
71    /// (msgtarget). This will work just fine for responding to messages in channels where the
72    /// target is the same for all participants. However, when the message is sent directly to a
73    /// user, this target will be that client's username, and responding to that same target will
74    /// actually mean sending itself a response. In such a case, you should instead respond to the
75    /// user sending the message as specified in the message prefix. Since this is a common
76    /// pattern, there is a utility function
77    /// [`Message::response_target`]
78    /// which is used for this exact purpose.
79    NOTICE(String, String),
80
81    // 3.4 Server queries and commands
82    /// MOTD :[target]
83    MOTD(Option<String>),
84    /// LUSERS [mask :[target]]
85    LUSERS(Option<String>, Option<String>),
86    /// VERSION :[target]
87    VERSION(Option<String>),
88    /// STATS [query :[target]]
89    STATS(Option<String>, Option<String>),
90    /// LINKS [[remote server] server :mask]
91    LINKS(Option<String>, Option<String>),
92    /// TIME :[target]
93    TIME(Option<String>),
94    /// CONNECT target server port :[remote server]
95    CONNECT(String, String, Option<String>),
96    /// TRACE :[target]
97    TRACE(Option<String>),
98    /// ADMIN :[target]
99    ADMIN(Option<String>),
100    /// INFO :[target]
101    INFO(Option<String>),
102
103    // 3.5 Service Query and Commands
104    /// SERVLIST [mask :[type]]
105    SERVLIST(Option<String>, Option<String>),
106    /// SQUERY servicename text
107    SQUERY(String, String),
108
109    // 3.6 User based queries
110    /// WHO [mask ["o"]]
111    WHO(Option<String>, Option<bool>),
112    /// WHOIS [target] masklist
113    WHOIS(Option<String>, String),
114    /// WHOWAS nicklist [count :[target]]
115    WHOWAS(String, Option<String>, Option<String>),
116
117    // 3.7 Miscellaneous messages
118    /// KILL nickname :comment
119    KILL(String, String),
120    /// PING server1 :[server2]
121    PING(String, Option<String>),
122    /// PONG server :[server2]
123    PONG(String, Option<String>),
124    /// ERROR :message
125    ERROR(String),
126
127    // 4 Optional Features
128    /// AWAY :[message]
129    AWAY(Option<String>),
130    /// REHASH
131    REHASH,
132    /// DIE
133    DIE,
134    /// RESTART
135    RESTART,
136    /// SUMMON user [target :[channel]]
137    SUMMON(String, Option<String>, Option<String>),
138    /// USERS :[target]
139    USERS(Option<String>),
140    /// WALLOPS :Text to be sent
141    WALLOPS(String),
142    /// USERHOST space-separated nicklist
143    USERHOST(Vec<String>),
144    /// ISON space-separated nicklist
145    ISON(Vec<String>),
146
147    // Non-RFC commands from InspIRCd
148    /// SAJOIN nickname channel
149    SAJOIN(String, String),
150    /// SAMODE target modes [modeparams]
151    SAMODE(String, String, Option<String>),
152    /// SANICK old nickname new nickname
153    SANICK(String, String),
154    /// SAPART nickname :comment
155    SAPART(String, String),
156    /// SAQUIT nickname :comment
157    SAQUIT(String, String),
158    /// NICKSERV message
159    NICKSERV(Vec<String>),
160    /// CHANSERV message
161    CHANSERV(String),
162    /// OPERSERV message
163    OPERSERV(String),
164    /// BOTSERV message
165    BOTSERV(String),
166    /// HOSTSERV message
167    HOSTSERV(String),
168    /// MEMOSERV message
169    MEMOSERV(String),
170
171    // IRCv3 support
172    /// CAP [*] COMMAND [*] :[param]
173    CAP(
174        Option<String>,
175        CapSubCommand,
176        Option<String>,
177        Option<String>,
178    ),
179
180    // IRCv3.1 extensions
181    /// AUTHENTICATE data
182    AUTHENTICATE(String),
183    /// ACCOUNT [account name]
184    ACCOUNT(String),
185    // AWAY is already defined as a send-only message.
186    // AWAY(Option<String>),
187    // JOIN is already defined.
188    // JOIN(String, Option<String>, Option<String>),
189
190    // IRCv3.2 extensions
191    /// METADATA target COMMAND [params] :[param]
192    METADATA(String, Option<MetadataSubCommand>, Option<Vec<String>>),
193    /// MONITOR command [nicklist]
194    MONITOR(String, Option<String>),
195    /// BATCH (+/-)reference-tag [type [params]]
196    BATCH(String, Option<BatchSubCommand>, Option<Vec<String>>),
197    /// CHGHOST user host
198    CHGHOST(String, String),
199
200    // Default option.
201    /// An IRC response code with arguments and optional suffix.
202    Response(Response, Vec<String>),
203    /// A raw IRC command unknown to the crate.
204    Raw(String, Vec<String>),
205}
206
207fn stringify(cmd: &str, args: &[&str]) -> String {
208    match args.split_last() {
209        Some((suffix, args)) => {
210            let args = args.join(" ");
211            let sp = if args.is_empty() { "" } else { " " };
212            let co = if suffix.is_empty() || suffix.contains(' ') || suffix.starts_with(':') {
213                ":"
214            } else {
215                ""
216            };
217            format!("{}{}{} {}{}", cmd, sp, args, co, suffix)
218        }
219        None => cmd.to_string(),
220    }
221}
222
223impl<'a> From<&'a Command> for String {
224    fn from(cmd: &'a Command) -> String {
225        match *cmd {
226            Command::PASS(ref p) => stringify("PASS", &[p]),
227            Command::NICK(ref n) => stringify("NICK", &[n]),
228            Command::USER(ref u, ref m, ref r) => stringify("USER", &[u, m, "*", r]),
229            Command::OPER(ref u, ref p) => stringify("OPER", &[u, p]),
230            Command::UserMODE(ref u, ref m) => {
231                // User modes never have arguments.
232                m.iter().fold(format!("MODE {u} "), |mut acc, m| {
233                    acc.push_str(&m.flag());
234                    acc
235                })
236            }
237            Command::SERVICE(ref nick, ref r0, ref dist, ref typ, ref r1, ref info) => {
238                stringify("SERVICE", &[nick, r0, dist, typ, r1, info])
239            }
240            Command::QUIT(Some(ref m)) => stringify("QUIT", &[m]),
241            Command::QUIT(None) => stringify("QUIT", &[]),
242            Command::SQUIT(ref s, ref c) => stringify("SQUIT", &[s, c]),
243            Command::JOIN(ref c, Some(ref k), Some(ref n)) => stringify("JOIN", &[c, k, n]),
244            Command::JOIN(ref c, Some(ref k), None) => stringify("JOIN", &[c, k]),
245            Command::JOIN(ref c, None, Some(ref n)) => stringify("JOIN", &[c, n]),
246            Command::JOIN(ref c, None, None) => stringify("JOIN", &[c]),
247            Command::PART(ref c, Some(ref m)) => stringify("PART", &[c, m]),
248            Command::PART(ref c, None) => stringify("PART", &[c]),
249            Command::ChannelMODE(ref c, ref m) => {
250                let cmd = m.iter().fold(format!("MODE {c} "), |mut acc, m| {
251                    acc.push_str(&m.flag());
252                    acc
253                });
254                m.iter().filter_map(|m| m.arg()).fold(cmd, |mut acc, arg| {
255                    acc.push(' ');
256                    acc.push_str(arg);
257                    acc
258                })
259            }
260            Command::TOPIC(ref c, Some(ref t)) => stringify("TOPIC", &[c, t]),
261            Command::TOPIC(ref c, None) => stringify("TOPIC", &[c]),
262            Command::NAMES(Some(ref c), Some(ref t)) => stringify("NAMES", &[c, t]),
263            Command::NAMES(Some(ref c), None) => stringify("NAMES", &[c]),
264            Command::NAMES(None, _) => stringify("NAMES", &[]),
265            Command::LIST(Some(ref c), Some(ref t)) => stringify("LIST", &[c, t]),
266            Command::LIST(Some(ref c), None) => stringify("LIST", &[c]),
267            Command::LIST(None, _) => stringify("LIST", &[]),
268            Command::INVITE(ref n, ref c) => stringify("INVITE", &[n, c]),
269            Command::KICK(ref c, ref n, Some(ref r)) => stringify("KICK", &[c, n, r]),
270            Command::KICK(ref c, ref n, None) => stringify("KICK", &[c, n]),
271            Command::PRIVMSG(ref t, ref m) => stringify("PRIVMSG", &[t, m]),
272            Command::NOTICE(ref t, ref m) => stringify("NOTICE", &[t, m]),
273            Command::MOTD(Some(ref t)) => stringify("MOTD", &[t]),
274            Command::MOTD(None) => stringify("MOTD", &[]),
275            Command::LUSERS(Some(ref m), Some(ref t)) => stringify("LUSERS", &[m, t]),
276            Command::LUSERS(Some(ref m), None) => stringify("LUSERS", &[m]),
277            Command::LUSERS(None, _) => stringify("LUSERS", &[]),
278            Command::VERSION(Some(ref t)) => stringify("VERSION", &[t]),
279            Command::VERSION(None) => stringify("VERSION", &[]),
280            Command::STATS(Some(ref q), Some(ref t)) => stringify("STATS", &[q, t]),
281            Command::STATS(Some(ref q), None) => stringify("STATS", &[q]),
282            Command::STATS(None, _) => stringify("STATS", &[]),
283            Command::LINKS(Some(ref r), Some(ref s)) => stringify("LINKS", &[r, s]),
284            Command::LINKS(None, Some(ref s)) => stringify("LINKS", &[s]),
285            Command::LINKS(_, None) => stringify("LINKS", &[]),
286            Command::TIME(Some(ref t)) => stringify("TIME", &[t]),
287            Command::TIME(None) => stringify("TIME", &[]),
288            Command::CONNECT(ref t, ref p, Some(ref r)) => stringify("CONNECT", &[t, p, r]),
289            Command::CONNECT(ref t, ref p, None) => stringify("CONNECT", &[t, p]),
290            Command::TRACE(Some(ref t)) => stringify("TRACE", &[t]),
291            Command::TRACE(None) => stringify("TRACE", &[]),
292            Command::ADMIN(Some(ref t)) => stringify("ADMIN", &[t]),
293            Command::ADMIN(None) => stringify("ADMIN", &[]),
294            Command::INFO(Some(ref t)) => stringify("INFO", &[t]),
295            Command::INFO(None) => stringify("INFO", &[]),
296            Command::SERVLIST(Some(ref m), Some(ref t)) => stringify("SERVLIST", &[m, t]),
297            Command::SERVLIST(Some(ref m), None) => stringify("SERVLIST", &[m]),
298            Command::SERVLIST(None, _) => stringify("SERVLIST", &[]),
299            Command::SQUERY(ref s, ref t) => stringify("SQUERY", &[s, t]),
300            Command::WHO(Some(ref s), Some(true)) => stringify("WHO", &[s, "o"]),
301            Command::WHO(Some(ref s), _) => stringify("WHO", &[s]),
302            Command::WHO(None, _) => stringify("WHO", &[]),
303            Command::WHOIS(Some(ref t), ref m) => stringify("WHOIS", &[t, m]),
304            Command::WHOIS(None, ref m) => stringify("WHOIS", &[m]),
305            Command::WHOWAS(ref n, Some(ref c), Some(ref t)) => stringify("WHOWAS", &[n, c, t]),
306            Command::WHOWAS(ref n, Some(ref c), None) => stringify("WHOWAS", &[n, c]),
307            Command::WHOWAS(ref n, None, _) => stringify("WHOWAS", &[n]),
308            Command::KILL(ref n, ref c) => stringify("KILL", &[n, c]),
309            Command::PING(ref s, Some(ref t)) => stringify("PING", &[s, t]),
310            Command::PING(ref s, None) => stringify("PING", &[s]),
311            Command::PONG(ref s, Some(ref t)) => stringify("PONG", &[s, t]),
312            Command::PONG(ref s, None) => stringify("PONG", &[s]),
313            Command::ERROR(ref m) => stringify("ERROR", &[m]),
314            Command::AWAY(Some(ref m)) => stringify("AWAY", &[m]),
315            Command::AWAY(None) => stringify("AWAY", &[]),
316            Command::REHASH => stringify("REHASH", &[]),
317            Command::DIE => stringify("DIE", &[]),
318            Command::RESTART => stringify("RESTART", &[]),
319            Command::SUMMON(ref u, Some(ref t), Some(ref c)) => stringify("SUMMON", &[u, t, c]),
320            Command::SUMMON(ref u, Some(ref t), None) => stringify("SUMMON", &[u, t]),
321            Command::SUMMON(ref u, None, _) => stringify("SUMMON", &[u]),
322            Command::USERS(Some(ref t)) => stringify("USERS", &[t]),
323            Command::USERS(None) => stringify("USERS", &[]),
324            Command::WALLOPS(ref t) => stringify("WALLOPS", &[t]),
325            Command::USERHOST(ref u) => {
326                stringify("USERHOST", &u.iter().map(|s| &s[..]).collect::<Vec<_>>())
327            }
328            Command::ISON(ref u) => {
329                stringify("ISON", &u.iter().map(|s| &s[..]).collect::<Vec<_>>())
330            }
331
332            Command::SAJOIN(ref n, ref c) => stringify("SAJOIN", &[n, c]),
333            Command::SAMODE(ref t, ref m, Some(ref p)) => stringify("SAMODE", &[t, m, p]),
334            Command::SAMODE(ref t, ref m, None) => stringify("SAMODE", &[t, m]),
335            Command::SANICK(ref o, ref n) => stringify("SANICK", &[o, n]),
336            Command::SAPART(ref c, ref r) => stringify("SAPART", &[c, r]),
337            Command::SAQUIT(ref c, ref r) => stringify("SAQUIT", &[c, r]),
338
339            Command::NICKSERV(ref p) => {
340                stringify("NICKSERV", &p.iter().map(|s| &s[..]).collect::<Vec<_>>())
341            }
342            Command::CHANSERV(ref m) => stringify("CHANSERV", &[m]),
343            Command::OPERSERV(ref m) => stringify("OPERSERV", &[m]),
344            Command::BOTSERV(ref m) => stringify("BOTSERV", &[m]),
345            Command::HOSTSERV(ref m) => stringify("HOSTSERV", &[m]),
346            Command::MEMOSERV(ref m) => stringify("MEMOSERV", &[m]),
347
348            Command::CAP(None, ref s, None, Some(ref p)) => stringify("CAP", &[s.to_str(), p]),
349            Command::CAP(None, ref s, None, None) => stringify("CAP", &[s.to_str()]),
350            Command::CAP(Some(ref k), ref s, None, Some(ref p)) => {
351                stringify("CAP", &[k, s.to_str(), p])
352            }
353            Command::CAP(Some(ref k), ref s, None, None) => stringify("CAP", &[k, s.to_str()]),
354            Command::CAP(None, ref s, Some(ref c), Some(ref p)) => {
355                stringify("CAP", &[s.to_str(), c, p])
356            }
357            Command::CAP(None, ref s, Some(ref c), None) => stringify("CAP", &[s.to_str(), c]),
358            Command::CAP(Some(ref k), ref s, Some(ref c), Some(ref p)) => {
359                stringify("CAP", &[k, s.to_str(), c, p])
360            }
361            Command::CAP(Some(ref k), ref s, Some(ref c), None) => {
362                stringify("CAP", &[k, s.to_str(), c])
363            }
364
365            Command::AUTHENTICATE(ref d) => stringify("AUTHENTICATE", &[d]),
366            Command::ACCOUNT(ref a) => stringify("ACCOUNT", &[a]),
367
368            Command::METADATA(ref t, Some(ref c), None) => {
369                stringify("METADATA", &[&t[..], c.to_str()])
370            }
371            Command::METADATA(ref t, Some(ref c), Some(ref a)) => stringify(
372                "METADATA",
373                &vec![t, &c.to_str().to_owned()]
374                    .iter()
375                    .map(|s| &s[..])
376                    .chain(a.iter().map(|s| &s[..]))
377                    .collect::<Vec<_>>(),
378            ),
379
380            // Note that it shouldn't be possible to have a later arg *and* be
381            // missing an early arg, so in order to serialize this as valid, we
382            // return it as just the command.
383            Command::METADATA(ref t, None, _) => stringify("METADATA", &[t]),
384
385            Command::MONITOR(ref c, Some(ref t)) => stringify("MONITOR", &[c, t]),
386            Command::MONITOR(ref c, None) => stringify("MONITOR", &[c]),
387            Command::BATCH(ref t, Some(ref c), Some(ref a)) => stringify(
388                "BATCH",
389                &vec![t, &c.to_str().to_owned()]
390                    .iter()
391                    .map(|s| &s[..])
392                    .chain(a.iter().map(|s| &s[..]))
393                    .collect::<Vec<_>>(),
394            ),
395            Command::BATCH(ref t, Some(ref c), None) => stringify("BATCH", &[t, c.to_str()]),
396            Command::BATCH(ref t, None, Some(ref a)) => stringify(
397                "BATCH",
398                &vec![t]
399                    .iter()
400                    .map(|s| &s[..])
401                    .chain(a.iter().map(|s| &s[..]))
402                    .collect::<Vec<_>>(),
403            ),
404            Command::BATCH(ref t, None, None) => stringify("BATCH", &[t]),
405            Command::CHGHOST(ref u, ref h) => stringify("CHGHOST", &[u, h]),
406
407            Command::Response(ref resp, ref a) => stringify(
408                &format!("{:03}", *resp as u16),
409                &a.iter().map(|s| &s[..]).collect::<Vec<_>>(),
410            ),
411            Command::Raw(ref c, ref a) => {
412                stringify(c, &a.iter().map(|s| &s[..]).collect::<Vec<_>>())
413            }
414        }
415    }
416}
417
418impl Command {
419    /// Constructs a new Command.
420    pub fn new(cmd: &str, args: Vec<&str>) -> Result<Command, MessageParseError> {
421        Ok(if cmd.eq_ignore_ascii_case("PASS") {
422            if args.len() != 1 {
423                raw(cmd, args)
424            } else {
425                Command::PASS(args[0].to_owned())
426            }
427        } else if cmd.eq_ignore_ascii_case("NICK") {
428            if args.len() != 1 {
429                raw(cmd, args)
430            } else {
431                Command::NICK(args[0].to_owned())
432            }
433        } else if cmd.eq_ignore_ascii_case("USER") {
434            if args.len() != 4 {
435                raw(cmd, args)
436            } else {
437                Command::USER(args[0].to_owned(), args[1].to_owned(), args[3].to_owned())
438            }
439        } else if cmd.eq_ignore_ascii_case("OPER") {
440            if args.len() != 2 {
441                raw(cmd, args)
442            } else {
443                Command::OPER(args[0].to_owned(), args[1].to_owned())
444            }
445        } else if cmd.eq_ignore_ascii_case("MODE") {
446            if args.is_empty() {
447                raw(cmd, args)
448            } else if args[0].is_channel_name() {
449                Command::ChannelMODE(args[0].to_owned(), Mode::as_channel_modes(&args[1..])?)
450            } else {
451                Command::UserMODE(args[0].to_owned(), Mode::as_user_modes(&args[1..])?)
452            }
453        } else if cmd.eq_ignore_ascii_case("SERVICE") {
454            if args.len() != 6 {
455                raw(cmd, args)
456            } else {
457                Command::SERVICE(
458                    args[0].to_owned(),
459                    args[1].to_owned(),
460                    args[2].to_owned(),
461                    args[3].to_owned(),
462                    args[4].to_owned(),
463                    args[5].to_owned(),
464                )
465            }
466        } else if cmd.eq_ignore_ascii_case("QUIT") {
467            if args.is_empty() {
468                Command::QUIT(None)
469            } else if args.len() == 1 {
470                Command::QUIT(Some(args[0].to_owned()))
471            } else {
472                raw(cmd, args)
473            }
474        } else if cmd.eq_ignore_ascii_case("SQUIT") {
475            if args.len() != 2 {
476                raw(cmd, args)
477            } else {
478                Command::SQUIT(args[0].to_owned(), args[1].to_owned())
479            }
480        } else if cmd.eq_ignore_ascii_case("JOIN") {
481            if args.len() == 1 {
482                Command::JOIN(args[0].to_owned(), None, None)
483            } else if args.len() == 2 {
484                Command::JOIN(args[0].to_owned(), Some(args[1].to_owned()), None)
485            } else if args.len() == 3 {
486                Command::JOIN(
487                    args[0].to_owned(),
488                    Some(args[1].to_owned()),
489                    Some(args[2].to_owned()),
490                )
491            } else {
492                raw(cmd, args)
493            }
494        } else if cmd.eq_ignore_ascii_case("PART") {
495            if args.len() == 1 {
496                Command::PART(args[0].to_owned(), None)
497            } else if args.len() == 2 {
498                Command::PART(args[0].to_owned(), Some(args[1].to_owned()))
499            } else {
500                raw(cmd, args)
501            }
502        } else if cmd.eq_ignore_ascii_case("TOPIC") {
503            if args.len() == 1 {
504                Command::TOPIC(args[0].to_owned(), None)
505            } else if args.len() == 2 {
506                Command::TOPIC(args[0].to_owned(), Some(args[1].to_owned()))
507            } else {
508                raw(cmd, args)
509            }
510        } else if cmd.eq_ignore_ascii_case("NAMES") {
511            if args.is_empty() {
512                Command::NAMES(None, None)
513            } else if args.len() == 1 {
514                Command::NAMES(Some(args[0].to_owned()), None)
515            } else if args.len() == 2 {
516                Command::NAMES(Some(args[0].to_owned()), Some(args[1].to_owned()))
517            } else {
518                raw(cmd, args)
519            }
520        } else if cmd.eq_ignore_ascii_case("LIST") {
521            if args.is_empty() {
522                Command::LIST(None, None)
523            } else if args.len() == 1 {
524                Command::LIST(Some(args[0].to_owned()), None)
525            } else if args.len() == 2 {
526                Command::LIST(Some(args[0].to_owned()), Some(args[1].to_owned()))
527            } else {
528                raw(cmd, args)
529            }
530        } else if cmd.eq_ignore_ascii_case("INVITE") {
531            if args.len() != 2 {
532                raw(cmd, args)
533            } else {
534                Command::INVITE(args[0].to_owned(), args[1].to_owned())
535            }
536        } else if cmd.eq_ignore_ascii_case("KICK") {
537            if args.len() == 3 {
538                Command::KICK(
539                    args[0].to_owned(),
540                    args[1].to_owned(),
541                    Some(args[2].to_owned()),
542                )
543            } else if args.len() == 2 {
544                Command::KICK(args[0].to_owned(), args[1].to_owned(), None)
545            } else {
546                raw(cmd, args)
547            }
548        } else if cmd.eq_ignore_ascii_case("PRIVMSG") {
549            if args.len() != 2 {
550                raw(cmd, args)
551            } else {
552                Command::PRIVMSG(args[0].to_owned(), args[1].to_owned())
553            }
554        } else if cmd.eq_ignore_ascii_case("NOTICE") {
555            if args.len() != 2 {
556                raw(cmd, args)
557            } else {
558                Command::NOTICE(args[0].to_owned(), args[1].to_owned())
559            }
560        } else if cmd.eq_ignore_ascii_case("MOTD") {
561            if args.is_empty() {
562                Command::MOTD(None)
563            } else if args.len() == 1 {
564                Command::MOTD(Some(args[0].to_owned()))
565            } else {
566                raw(cmd, args)
567            }
568        } else if cmd.eq_ignore_ascii_case("LUSERS") {
569            if args.is_empty() {
570                Command::LUSERS(None, None)
571            } else if args.len() == 1 {
572                Command::LUSERS(Some(args[0].to_owned()), None)
573            } else if args.len() == 2 {
574                Command::LUSERS(Some(args[0].to_owned()), Some(args[1].to_owned()))
575            } else {
576                raw(cmd, args)
577            }
578        } else if cmd.eq_ignore_ascii_case("VERSION") {
579            if args.is_empty() {
580                Command::VERSION(None)
581            } else if args.len() == 1 {
582                Command::VERSION(Some(args[0].to_owned()))
583            } else {
584                raw(cmd, args)
585            }
586        } else if cmd.eq_ignore_ascii_case("STATS") {
587            if args.is_empty() {
588                Command::STATS(None, None)
589            } else if args.len() == 1 {
590                Command::STATS(Some(args[0].to_owned()), None)
591            } else if args.len() == 2 {
592                Command::STATS(Some(args[0].to_owned()), Some(args[1].to_owned()))
593            } else {
594                raw(cmd, args)
595            }
596        } else if cmd.eq_ignore_ascii_case("LINKS") {
597            if args.is_empty() {
598                Command::LINKS(None, None)
599            } else if args.len() == 1 {
600                Command::LINKS(Some(args[0].to_owned()), None)
601            } else if args.len() == 2 {
602                Command::LINKS(Some(args[0].to_owned()), Some(args[1].to_owned()))
603            } else {
604                raw(cmd, args)
605            }
606        } else if cmd.eq_ignore_ascii_case("TIME") {
607            if args.is_empty() {
608                Command::TIME(None)
609            } else if args.len() == 1 {
610                Command::TIME(Some(args[0].to_owned()))
611            } else {
612                raw(cmd, args)
613            }
614        } else if cmd.eq_ignore_ascii_case("CONNECT") {
615            if args.len() != 2 {
616                raw(cmd, args)
617            } else {
618                Command::CONNECT(args[0].to_owned(), args[1].to_owned(), None)
619            }
620        } else if cmd.eq_ignore_ascii_case("TRACE") {
621            if args.is_empty() {
622                Command::TRACE(None)
623            } else if args.len() == 1 {
624                Command::TRACE(Some(args[0].to_owned()))
625            } else {
626                raw(cmd, args)
627            }
628        } else if cmd.eq_ignore_ascii_case("ADMIN") {
629            if args.is_empty() {
630                Command::ADMIN(None)
631            } else if args.len() == 1 {
632                Command::ADMIN(Some(args[0].to_owned()))
633            } else {
634                raw(cmd, args)
635            }
636        } else if cmd.eq_ignore_ascii_case("INFO") {
637            if args.is_empty() {
638                Command::INFO(None)
639            } else if args.len() == 1 {
640                Command::INFO(Some(args[0].to_owned()))
641            } else {
642                raw(cmd, args)
643            }
644        } else if cmd.eq_ignore_ascii_case("SERVLIST") {
645            if args.is_empty() {
646                Command::SERVLIST(None, None)
647            } else if args.len() == 1 {
648                Command::SERVLIST(Some(args[0].to_owned()), None)
649            } else if args.len() == 2 {
650                Command::SERVLIST(Some(args[0].to_owned()), Some(args[1].to_owned()))
651            } else {
652                raw(cmd, args)
653            }
654        } else if cmd.eq_ignore_ascii_case("SQUERY") {
655            if args.len() != 2 {
656                raw(cmd, args)
657            } else {
658                Command::SQUERY(args[0].to_owned(), args[1].to_owned())
659            }
660        } else if cmd.eq_ignore_ascii_case("WHO") {
661            if args.is_empty() {
662                Command::WHO(None, None)
663            } else if args.len() == 1 {
664                Command::WHO(Some(args[0].to_owned()), None)
665            } else if args.len() == 2 {
666                Command::WHO(Some(args[0].to_owned()), Some(args[1] == "o"))
667            } else {
668                raw(cmd, args)
669            }
670        } else if cmd.eq_ignore_ascii_case("WHOIS") {
671            if args.len() == 1 {
672                Command::WHOIS(None, args[0].to_owned())
673            } else if args.len() == 2 {
674                Command::WHOIS(Some(args[0].to_owned()), args[1].to_owned())
675            } else {
676                raw(cmd, args)
677            }
678        } else if cmd.eq_ignore_ascii_case("WHOWAS") {
679            if args.len() == 1 {
680                Command::WHOWAS(args[0].to_owned(), None, None)
681            } else if args.len() == 2 {
682                Command::WHOWAS(args[0].to_owned(), None, Some(args[1].to_owned()))
683            } else if args.len() == 3 {
684                Command::WHOWAS(
685                    args[0].to_owned(),
686                    Some(args[1].to_owned()),
687                    Some(args[2].to_owned()),
688                )
689            } else {
690                raw(cmd, args)
691            }
692        } else if cmd.eq_ignore_ascii_case("KILL") {
693            if args.len() != 2 {
694                raw(cmd, args)
695            } else {
696                Command::KILL(args[0].to_owned(), args[1].to_owned())
697            }
698        } else if cmd.eq_ignore_ascii_case("PING") {
699            if args.len() == 1 {
700                Command::PING(args[0].to_owned(), None)
701            } else if args.len() == 2 {
702                Command::PING(args[0].to_owned(), Some(args[1].to_owned()))
703            } else {
704                raw(cmd, args)
705            }
706        } else if cmd.eq_ignore_ascii_case("PONG") {
707            if args.len() == 1 {
708                Command::PONG(args[0].to_owned(), None)
709            } else if args.len() == 2 {
710                Command::PONG(args[0].to_owned(), Some(args[1].to_owned()))
711            } else {
712                raw(cmd, args)
713            }
714        } else if cmd.eq_ignore_ascii_case("ERROR") {
715            if args.len() != 1 {
716                raw(cmd, args)
717            } else {
718                Command::ERROR(args[0].to_owned())
719            }
720        } else if cmd.eq_ignore_ascii_case("AWAY") {
721            if args.is_empty() {
722                Command::AWAY(None)
723            } else if args.len() == 1 {
724                Command::AWAY(Some(args[0].to_owned()))
725            } else {
726                raw(cmd, args)
727            }
728        } else if cmd.eq_ignore_ascii_case("REHASH") {
729            if args.is_empty() {
730                Command::REHASH
731            } else {
732                raw(cmd, args)
733            }
734        } else if cmd.eq_ignore_ascii_case("DIE") {
735            if args.is_empty() {
736                Command::DIE
737            } else {
738                raw(cmd, args)
739            }
740        } else if cmd.eq_ignore_ascii_case("RESTART") {
741            if args.is_empty() {
742                Command::RESTART
743            } else {
744                raw(cmd, args)
745            }
746        } else if cmd.eq_ignore_ascii_case("SUMMON") {
747            if args.len() == 1 {
748                Command::SUMMON(args[0].to_owned(), None, None)
749            } else if args.len() == 2 {
750                Command::SUMMON(args[0].to_owned(), Some(args[1].to_owned()), None)
751            } else if args.len() == 3 {
752                Command::SUMMON(
753                    args[0].to_owned(),
754                    Some(args[1].to_owned()),
755                    Some(args[2].to_owned()),
756                )
757            } else {
758                raw(cmd, args)
759            }
760        } else if cmd.eq_ignore_ascii_case("USERS") {
761            if args.len() != 1 {
762                raw(cmd, args)
763            } else {
764                Command::USERS(Some(args[0].to_owned()))
765            }
766        } else if cmd.eq_ignore_ascii_case("WALLOPS") {
767            if args.len() != 1 {
768                raw(cmd, args)
769            } else {
770                Command::WALLOPS(args[0].to_owned())
771            }
772        } else if cmd.eq_ignore_ascii_case("USERHOST") {
773            Command::USERHOST(args.into_iter().map(|s| s.to_owned()).collect())
774        } else if cmd.eq_ignore_ascii_case("ISON") {
775            Command::USERHOST(args.into_iter().map(|s| s.to_owned()).collect())
776        } else if cmd.eq_ignore_ascii_case("SAJOIN") {
777            if args.len() != 2 {
778                raw(cmd, args)
779            } else {
780                Command::SAJOIN(args[0].to_owned(), args[1].to_owned())
781            }
782        } else if cmd.eq_ignore_ascii_case("SAMODE") {
783            if args.len() == 2 {
784                Command::SAMODE(args[0].to_owned(), args[1].to_owned(), None)
785            } else if args.len() == 3 {
786                Command::SAMODE(
787                    args[0].to_owned(),
788                    args[1].to_owned(),
789                    Some(args[2].to_owned()),
790                )
791            } else {
792                raw(cmd, args)
793            }
794        } else if cmd.eq_ignore_ascii_case("SANICK") {
795            if args.len() != 2 {
796                raw(cmd, args)
797            } else {
798                Command::SANICK(args[0].to_owned(), args[1].to_owned())
799            }
800        } else if cmd.eq_ignore_ascii_case("SAPART") {
801            if args.len() != 2 {
802                raw(cmd, args)
803            } else {
804                Command::SAPART(args[0].to_owned(), args[1].to_owned())
805            }
806        } else if cmd.eq_ignore_ascii_case("SAQUIT") {
807            if args.len() != 2 {
808                raw(cmd, args)
809            } else {
810                Command::SAQUIT(args[0].to_owned(), args[1].to_owned())
811            }
812        } else if cmd.eq_ignore_ascii_case("NICKSERV") {
813            if args.len() != 1 {
814                raw(cmd, args)
815            } else {
816                Command::NICKSERV(args[1..].iter().map(|s| s.to_string()).collect())
817            }
818        } else if cmd.eq_ignore_ascii_case("CHANSERV") {
819            if args.len() != 1 {
820                raw(cmd, args)
821            } else {
822                Command::CHANSERV(args[0].to_owned())
823            }
824        } else if cmd.eq_ignore_ascii_case("OPERSERV") {
825            if args.len() != 1 {
826                raw(cmd, args)
827            } else {
828                Command::OPERSERV(args[0].to_owned())
829            }
830        } else if cmd.eq_ignore_ascii_case("BOTSERV") {
831            if args.len() != 1 {
832                raw(cmd, args)
833            } else {
834                Command::BOTSERV(args[0].to_owned())
835            }
836        } else if cmd.eq_ignore_ascii_case("HOSTSERV") {
837            if args.len() != 1 {
838                raw(cmd, args)
839            } else {
840                Command::HOSTSERV(args[0].to_owned())
841            }
842        } else if cmd.eq_ignore_ascii_case("MEMOSERV") {
843            if args.len() != 1 {
844                raw(cmd, args)
845            } else {
846                Command::MEMOSERV(args[0].to_owned())
847            }
848        } else if cmd.eq_ignore_ascii_case("CAP") {
849            if args.len() == 1 {
850                if let Ok(cmd) = args[0].parse() {
851                    Command::CAP(None, cmd, None, None)
852                } else {
853                    raw(cmd, args)
854                }
855            } else if args.len() == 2 {
856                if let Ok(cmd) = args[0].parse() {
857                    Command::CAP(None, cmd, Some(args[1].to_owned()), None)
858                } else if let Ok(cmd) = args[1].parse() {
859                    Command::CAP(Some(args[0].to_owned()), cmd, None, None)
860                } else {
861                    raw(cmd, args)
862                }
863            } else if args.len() == 3 {
864                if let Ok(cmd) = args[0].parse() {
865                    Command::CAP(
866                        None,
867                        cmd,
868                        Some(args[1].to_owned()),
869                        Some(args[2].to_owned()),
870                    )
871                } else if let Ok(cmd) = args[1].parse() {
872                    Command::CAP(
873                        Some(args[0].to_owned()),
874                        cmd,
875                        Some(args[2].to_owned()),
876                        None,
877                    )
878                } else {
879                    raw(cmd, args)
880                }
881            } else if args.len() == 4 {
882                if let Ok(cmd) = args[1].parse() {
883                    Command::CAP(
884                        Some(args[0].to_owned()),
885                        cmd,
886                        Some(args[2].to_owned()),
887                        Some(args[3].to_owned()),
888                    )
889                } else {
890                    raw(cmd, args)
891                }
892            } else {
893                raw(cmd, args)
894            }
895        } else if cmd.eq_ignore_ascii_case("AUTHENTICATE") {
896            if args.len() == 1 {
897                Command::AUTHENTICATE(args[0].to_owned())
898            } else {
899                raw(cmd, args)
900            }
901        } else if cmd.eq_ignore_ascii_case("ACCOUNT") {
902            if args.len() == 1 {
903                Command::ACCOUNT(args[0].to_owned())
904            } else {
905                raw(cmd, args)
906            }
907        } else if cmd.eq_ignore_ascii_case("METADATA") {
908            match args.len() {
909                2 => match args[1].parse() {
910                    Ok(c) => Command::METADATA(args[0].to_owned(), Some(c), None),
911                    Err(_) => raw(cmd, args),
912                },
913                3.. => match args[1].parse() {
914                    Ok(c) => Command::METADATA(
915                        args[0].to_owned(),
916                        Some(c),
917                        Some(args.into_iter().skip(1).map(|s| s.to_owned()).collect()),
918                    ),
919                    Err(_) => {
920                        if args.len() == 3 {
921                            Command::METADATA(
922                                args[0].to_owned(),
923                                None,
924                                Some(args.into_iter().skip(1).map(|s| s.to_owned()).collect()),
925                            )
926                        } else {
927                            raw(cmd, args)
928                        }
929                    }
930                },
931                _ => raw(cmd, args),
932            }
933        } else if cmd.eq_ignore_ascii_case("MONITOR") {
934            if args.len() == 2 {
935                Command::MONITOR(args[0].to_owned(), Some(args[1].to_owned()))
936            } else if args.len() == 1 {
937                Command::MONITOR(args[0].to_owned(), None)
938            } else {
939                raw(cmd, args)
940            }
941        } else if cmd.eq_ignore_ascii_case("BATCH") {
942            if args.len() == 1 {
943                Command::BATCH(args[0].to_owned(), None, None)
944            } else if args.len() == 2 {
945                Command::BATCH(args[0].to_owned(), Some(args[1].parse().unwrap()), None)
946            } else if args.len() > 2 {
947                Command::BATCH(
948                    args[0].to_owned(),
949                    Some(args[1].parse().unwrap()),
950                    Some(args.iter().skip(2).map(|&s| s.to_owned()).collect()),
951                )
952            } else {
953                raw(cmd, args)
954            }
955        } else if cmd.eq_ignore_ascii_case("CHGHOST") {
956            if args.len() == 2 {
957                Command::CHGHOST(args[0].to_owned(), args[1].to_owned())
958            } else {
959                raw(cmd, args)
960            }
961        } else if let Ok(resp) = cmd.parse() {
962            Command::Response(resp, args.into_iter().map(|s| s.to_owned()).collect())
963        } else {
964            raw(cmd, args)
965        })
966    }
967}
968
969/// Makes a raw message from the specified command, arguments, and suffix.
970fn raw(cmd: &str, args: Vec<&str>) -> Command {
971    Command::Raw(
972        cmd.to_owned(),
973        args.into_iter().map(|s| s.to_owned()).collect(),
974    )
975}
976
977/// A list of all of the subcommands for the capabilities extension.
978#[derive(Clone, Copy, Debug, PartialEq)]
979pub enum CapSubCommand {
980    /// Requests a list of the server's capabilities.
981    LS,
982    /// Requests a list of the server's capabilities.
983    LIST,
984    /// Requests specific capabilities blindly.
985    REQ,
986    /// Acknowledges capabilities.
987    ACK,
988    /// Does not acknowledge certain capabilities.
989    NAK,
990    /// Ends the capability negotiation before registration.
991    END,
992    /// Signals that new capabilities are now being offered.
993    NEW,
994    /// Signasl that the specified capabilities are cancelled and no longer available.
995    DEL,
996}
997
998impl CapSubCommand {
999    /// Gets the string that corresponds to this subcommand.
1000    pub fn to_str(&self) -> &str {
1001        match *self {
1002            CapSubCommand::LS => "LS",
1003            CapSubCommand::LIST => "LIST",
1004            CapSubCommand::REQ => "REQ",
1005            CapSubCommand::ACK => "ACK",
1006            CapSubCommand::NAK => "NAK",
1007            CapSubCommand::END => "END",
1008            CapSubCommand::NEW => "NEW",
1009            CapSubCommand::DEL => "DEL",
1010        }
1011    }
1012}
1013
1014impl FromStr for CapSubCommand {
1015    type Err = MessageParseError;
1016
1017    fn from_str(s: &str) -> Result<CapSubCommand, Self::Err> {
1018        if s.eq_ignore_ascii_case("LS") {
1019            Ok(CapSubCommand::LS)
1020        } else if s.eq_ignore_ascii_case("LIST") {
1021            Ok(CapSubCommand::LIST)
1022        } else if s.eq_ignore_ascii_case("REQ") {
1023            Ok(CapSubCommand::REQ)
1024        } else if s.eq_ignore_ascii_case("ACK") {
1025            Ok(CapSubCommand::ACK)
1026        } else if s.eq_ignore_ascii_case("NAK") {
1027            Ok(CapSubCommand::NAK)
1028        } else if s.eq_ignore_ascii_case("END") {
1029            Ok(CapSubCommand::END)
1030        } else if s.eq_ignore_ascii_case("NEW") {
1031            Ok(CapSubCommand::NEW)
1032        } else if s.eq_ignore_ascii_case("DEL") {
1033            Ok(CapSubCommand::DEL)
1034        } else {
1035            Err(MessageParseError::InvalidSubcommand {
1036                cmd: "CAP",
1037                sub: s.to_owned(),
1038            })
1039        }
1040    }
1041}
1042
1043/// A list of all the subcommands for the
1044/// [metadata extension](http://ircv3.net/specs/core/metadata-3.2.html).
1045#[derive(Clone, Copy, Debug, PartialEq)]
1046pub enum MetadataSubCommand {
1047    /// Looks up the value for some keys.
1048    GET,
1049    /// Lists all of the metadata keys and values.
1050    LIST,
1051    /// Sets the value for some key.
1052    SET,
1053    /// Removes all metadata.
1054    CLEAR,
1055}
1056
1057impl MetadataSubCommand {
1058    /// Gets the string that corresponds to this subcommand.
1059    pub fn to_str(&self) -> &str {
1060        match *self {
1061            MetadataSubCommand::GET => "GET",
1062            MetadataSubCommand::LIST => "LIST",
1063            MetadataSubCommand::SET => "SET",
1064            MetadataSubCommand::CLEAR => "CLEAR",
1065        }
1066    }
1067}
1068
1069impl FromStr for MetadataSubCommand {
1070    type Err = MessageParseError;
1071
1072    fn from_str(s: &str) -> Result<MetadataSubCommand, Self::Err> {
1073        if s.eq_ignore_ascii_case("GET") {
1074            Ok(MetadataSubCommand::GET)
1075        } else if s.eq_ignore_ascii_case("LIST") {
1076            Ok(MetadataSubCommand::LIST)
1077        } else if s.eq_ignore_ascii_case("SET") {
1078            Ok(MetadataSubCommand::SET)
1079        } else if s.eq_ignore_ascii_case("CLEAR") {
1080            Ok(MetadataSubCommand::CLEAR)
1081        } else {
1082            Err(MessageParseError::InvalidSubcommand {
1083                cmd: "METADATA",
1084                sub: s.to_owned(),
1085            })
1086        }
1087    }
1088}
1089
1090/// [batch extension](http://ircv3.net/specs/extensions/batch-3.2.html).
1091#[derive(Clone, Debug, PartialEq)]
1092pub enum BatchSubCommand {
1093    /// [NETSPLIT](http://ircv3.net/specs/extensions/batch/netsplit.html)
1094    NETSPLIT,
1095    /// [NETJOIN](http://ircv3.net/specs/extensions/batch/netsplit.html)
1096    NETJOIN,
1097    /// Vendor-specific BATCH subcommands.
1098    CUSTOM(String),
1099}
1100
1101impl BatchSubCommand {
1102    /// Gets the string that corresponds to this subcommand.
1103    pub fn to_str(&self) -> &str {
1104        match *self {
1105            BatchSubCommand::NETSPLIT => "NETSPLIT",
1106            BatchSubCommand::NETJOIN => "NETJOIN",
1107            BatchSubCommand::CUSTOM(ref s) => s,
1108        }
1109    }
1110}
1111
1112impl FromStr for BatchSubCommand {
1113    type Err = MessageParseError;
1114
1115    fn from_str(s: &str) -> Result<BatchSubCommand, Self::Err> {
1116        if s.eq_ignore_ascii_case("NETSPLIT") {
1117            Ok(BatchSubCommand::NETSPLIT)
1118        } else if s.eq_ignore_ascii_case("NETJOIN") {
1119            Ok(BatchSubCommand::NETJOIN)
1120        } else {
1121            Ok(BatchSubCommand::CUSTOM(s.to_uppercase()))
1122        }
1123    }
1124}
1125
1126#[cfg(test)]
1127mod test {
1128    use super::Command;
1129    use super::Response;
1130    use crate::Message;
1131
1132    #[test]
1133    fn format_response() {
1134        assert!(
1135            String::from(&Command::Response(
1136                Response::RPL_WELCOME,
1137                vec!["foo".into()],
1138            )) == "001 foo"
1139        );
1140    }
1141
1142    #[test]
1143    fn user_round_trip() {
1144        let cmd = Command::USER("a".to_string(), "b".to_string(), "c".to_string());
1145        let line = Message::from(cmd.clone()).to_string();
1146        let returned_cmd = line.parse::<Message>().unwrap().command;
1147        assert_eq!(cmd, returned_cmd);
1148    }
1149
1150    #[test]
1151    fn parse_user_message() {
1152        let cmd = "USER a 0 * b".parse::<Message>().unwrap().command;
1153        assert_eq!(
1154            Command::USER("a".to_string(), "0".to_string(), "b".to_string()),
1155            cmd
1156        );
1157    }
1158}