Implemented new Mode API (fixes #48).
This commit is contained in:
parent
54326e0047
commit
d9f4f82051
7 changed files with 396 additions and 88 deletions
|
@ -42,7 +42,7 @@ pub struct Config {
|
||||||
pub channels: Option<Vec<String>>,
|
pub channels: Option<Vec<String>>,
|
||||||
/// A mapping of channel names to keys for join-on-connect.
|
/// A mapping of channel names to keys for join-on-connect.
|
||||||
pub channel_keys: Option<HashMap<String, String>>,
|
pub channel_keys: Option<HashMap<String, String>>,
|
||||||
/// User modes to set on connect. Example: "+RB-x"
|
/// User modes to set on connect. Example: "+RB -x"
|
||||||
pub umodes: Option<String>,
|
pub umodes: Option<String>,
|
||||||
/// The text that'll be sent in response to CTCP USERINFO requests.
|
/// The text that'll be sent in response to CTCP USERINFO requests.
|
||||||
pub user_info: Option<String>,
|
pub user_info: Option<String>,
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::borrow::ToOwned;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::cmp::Ordering::{Less, Equal, Greater};
|
use std::cmp::Ordering::{Less, Equal, Greater};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use proto::{Mode, ChannelMode};
|
||||||
|
|
||||||
/// IRC User data.
|
/// IRC User data.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -77,18 +78,18 @@ impl User {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the user's access level.
|
/// Updates the user's access level.
|
||||||
pub fn update_access_level(&mut self, mode: &str) {
|
pub fn update_access_level(&mut self, mode: &Mode<ChannelMode>) {
|
||||||
match mode {
|
match mode {
|
||||||
"+q" => self.add_access_level(AccessLevel::Owner),
|
&Mode::Plus(ChannelMode::Founder, _) => self.add_access_level(AccessLevel::Owner),
|
||||||
"-q" => self.sub_access_level(AccessLevel::Owner),
|
&Mode::Minus(ChannelMode::Founder, _) => self.sub_access_level(AccessLevel::Owner),
|
||||||
"+a" => self.add_access_level(AccessLevel::Admin),
|
&Mode::Plus(ChannelMode::Admin, _) => self.add_access_level(AccessLevel::Admin),
|
||||||
"-a" => self.sub_access_level(AccessLevel::Admin),
|
&Mode::Minus(ChannelMode::Admin, _) => self.sub_access_level(AccessLevel::Admin),
|
||||||
"+o" => self.add_access_level(AccessLevel::Oper),
|
&Mode::Plus(ChannelMode::Oper, _) => self.add_access_level(AccessLevel::Oper),
|
||||||
"-o" => self.sub_access_level(AccessLevel::Oper),
|
&Mode::Minus(ChannelMode::Oper, _) => self.sub_access_level(AccessLevel::Oper),
|
||||||
"+h" => self.add_access_level(AccessLevel::HalfOp),
|
&Mode::Plus(ChannelMode::Halfop, _) => self.add_access_level(AccessLevel::HalfOp),
|
||||||
"-h" => self.sub_access_level(AccessLevel::HalfOp),
|
&Mode::Minus(ChannelMode::Halfop, _) => self.sub_access_level(AccessLevel::HalfOp),
|
||||||
"+v" => self.add_access_level(AccessLevel::Voice),
|
&Mode::Plus(ChannelMode::Voice, _) => self.add_access_level(AccessLevel::Voice),
|
||||||
"-v" => self.sub_access_level(AccessLevel::Voice),
|
&Mode::Minus(ChannelMode::Voice, _) => self.sub_access_level(AccessLevel::Voice),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,6 +226,8 @@ impl Iterator for AccessLevelIterator {
|
||||||
mod test {
|
mod test {
|
||||||
use super::{AccessLevel, User};
|
use super::{AccessLevel, User};
|
||||||
use super::AccessLevel::*;
|
use super::AccessLevel::*;
|
||||||
|
use proto::Mode::*;
|
||||||
|
use proto::ChannelMode as M;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_access_level() {
|
fn parse_access_level() {
|
||||||
|
@ -300,25 +303,25 @@ mod test {
|
||||||
fn update_user_rank() {
|
fn update_user_rank() {
|
||||||
let mut user = User::new("user");
|
let mut user = User::new("user");
|
||||||
assert_eq!(user.highest_access_level, Member);
|
assert_eq!(user.highest_access_level, Member);
|
||||||
user.update_access_level("+q");
|
user.update_access_level(&Plus(M::Founder, None));
|
||||||
assert_eq!(user.highest_access_level, Owner);
|
assert_eq!(user.highest_access_level, Owner);
|
||||||
user.update_access_level("-q");
|
user.update_access_level(&Minus(M::Founder, None));
|
||||||
assert_eq!(user.highest_access_level, Member);
|
assert_eq!(user.highest_access_level, Member);
|
||||||
user.update_access_level("+a");
|
user.update_access_level(&Plus(M::Admin, None));
|
||||||
assert_eq!(user.highest_access_level, Admin);
|
assert_eq!(user.highest_access_level, Admin);
|
||||||
user.update_access_level("-a");
|
user.update_access_level(&Minus(M::Admin, None));
|
||||||
assert_eq!(user.highest_access_level, Member);
|
assert_eq!(user.highest_access_level, Member);
|
||||||
user.update_access_level("+o");
|
user.update_access_level(&Plus(M::Oper, None));
|
||||||
assert_eq!(user.highest_access_level, Oper);
|
assert_eq!(user.highest_access_level, Oper);
|
||||||
user.update_access_level("-o");
|
user.update_access_level(&Minus(M::Oper, None));
|
||||||
assert_eq!(user.highest_access_level, Member);
|
assert_eq!(user.highest_access_level, Member);
|
||||||
user.update_access_level("+h");
|
user.update_access_level(&Plus(M::Halfop, None));
|
||||||
assert_eq!(user.highest_access_level, HalfOp);
|
assert_eq!(user.highest_access_level, HalfOp);
|
||||||
user.update_access_level("-h");
|
user.update_access_level(&Minus(M::Halfop, None));
|
||||||
assert_eq!(user.highest_access_level, Member);
|
assert_eq!(user.highest_access_level, Member);
|
||||||
user.update_access_level("+v");
|
user.update_access_level(&Plus(M::Voice, None));
|
||||||
assert_eq!(user.highest_access_level, Voice);
|
assert_eq!(user.highest_access_level, Voice);
|
||||||
user.update_access_level("-v");
|
user.update_access_level(&Minus(M::Voice, None));
|
||||||
assert_eq!(user.highest_access_level, Member);
|
assert_eq!(user.highest_access_level, Member);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,19 +333,19 @@ mod test {
|
||||||
user.access_levels,
|
user.access_levels,
|
||||||
vec![Owner, Admin, Oper, HalfOp, Voice, Member]
|
vec![Owner, Admin, Oper, HalfOp, Voice, Member]
|
||||||
);
|
);
|
||||||
user.update_access_level("-h");
|
user.update_access_level(&Minus(M::Halfop, None));
|
||||||
assert_eq!(user.highest_access_level, Owner);
|
assert_eq!(user.highest_access_level, Owner);
|
||||||
assert_eq!(user.access_levels, vec![Owner, Admin, Oper, Member, Voice]);
|
assert_eq!(user.access_levels, vec![Owner, Admin, Oper, Member, Voice]);
|
||||||
user.update_access_level("-q");
|
user.update_access_level(&Minus(M::Founder, None));
|
||||||
assert_eq!(user.highest_access_level, Admin);
|
assert_eq!(user.highest_access_level, Admin);
|
||||||
assert_eq!(user.access_levels, vec![Voice, Admin, Oper, Member]);
|
assert_eq!(user.access_levels, vec![Voice, Admin, Oper, Member]);
|
||||||
user.update_access_level("-a");
|
user.update_access_level(&Minus(M::Admin, None));
|
||||||
assert_eq!(user.highest_access_level, Oper);
|
assert_eq!(user.highest_access_level, Oper);
|
||||||
assert_eq!(user.access_levels, vec![Voice, Member, Oper]);
|
assert_eq!(user.access_levels, vec![Voice, Member, Oper]);
|
||||||
user.update_access_level("-o");
|
user.update_access_level(&Minus(M::Oper, None));
|
||||||
assert_eq!(user.highest_access_level, Voice);
|
assert_eq!(user.highest_access_level, Voice);
|
||||||
assert_eq!(user.access_levels, vec![Voice, Member]);
|
assert_eq!(user.access_levels, vec![Voice, Member]);
|
||||||
user.update_access_level("-v");
|
user.update_access_level(&Minus(M::Voice, None));
|
||||||
assert_eq!(user.highest_access_level, Member);
|
assert_eq!(user.highest_access_level, Member);
|
||||||
assert_eq!(user.access_levels, vec![Member]);
|
assert_eq!(user.access_levels, vec![Member]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ use client::conn::Connection;
|
||||||
use client::data::{Config, User};
|
use client::data::{Config, User};
|
||||||
use client::server::utils::ServerExt;
|
use client::server::utils::ServerExt;
|
||||||
use client::transport::LogView;
|
use client::transport::LogView;
|
||||||
use proto::{Command, Message, Response};
|
use proto::{ChannelMode, Command, Message, Mode, Response};
|
||||||
use proto::Command::{JOIN, NICK, NICKSERV, PART, PRIVMSG, MODE, QUIT};
|
use proto::Command::{JOIN, NICK, NICKSERV, PART, PRIVMSG, ChannelMODE, QUIT};
|
||||||
use futures::{Async, Poll, Future, Sink, Stream};
|
use futures::{Async, Poll, Future, Sink, Stream};
|
||||||
use futures::stream::SplitStream;
|
use futures::stream::SplitStream;
|
||||||
use futures::sync::mpsc;
|
use futures::sync::mpsc;
|
||||||
|
@ -206,7 +206,7 @@ impl ServerState {
|
||||||
NICK(ref new_nick) => {
|
NICK(ref new_nick) => {
|
||||||
self.handle_nick_change(msg.source_nickname().unwrap_or(""), new_nick)
|
self.handle_nick_change(msg.source_nickname().unwrap_or(""), new_nick)
|
||||||
}
|
}
|
||||||
MODE(ref chan, ref mode, Some(ref user)) => self.handle_mode(chan, mode, user),
|
ChannelMODE(ref chan, ref modes) => self.handle_mode(chan, modes),
|
||||||
PRIVMSG(ref target, ref body) => {
|
PRIVMSG(ref target, ref body) => {
|
||||||
if body.starts_with('\u{001}') {
|
if body.starts_with('\u{001}') {
|
||||||
let tokens: Vec<_> = {
|
let tokens: Vec<_> = {
|
||||||
|
@ -290,7 +290,7 @@ impl ServerState {
|
||||||
if self.config().umodes().is_empty() {
|
if self.config().umodes().is_empty() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
self.send_mode(self.current_nickname(), self.config().umodes(), "")
|
self.send_mode(self.current_nickname(), &Mode::as_user_modes(self.config().umodes())?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,16 +358,23 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "nochanlists")]
|
#[cfg(feature = "nochanlists")]
|
||||||
fn handle_mode(&self, chan: &str, mode: &str, user: &str) {}
|
fn handle_mode(&self, _: &str, _: &[Mode<ChannelMODE>]) {}
|
||||||
|
|
||||||
#[cfg(not(feature = "nochanlists"))]
|
#[cfg(not(feature = "nochanlists"))]
|
||||||
fn handle_mode(&self, chan: &str, mode: &str, user: &str) {
|
fn handle_mode(&self, chan: &str, modes: &[Mode<ChannelMode>]) {
|
||||||
|
for mode in modes {
|
||||||
|
match mode {
|
||||||
|
&Mode::Plus(_, Some(ref user)) | &Mode::Minus(_, Some(ref user)) => {
|
||||||
if let Some(vec) = self.chanlists.lock().unwrap().get_mut(chan) {
|
if let Some(vec) = self.chanlists.lock().unwrap().get_mut(chan) {
|
||||||
if let Some(n) = vec.iter().position(|x| x.get_nickname() == user) {
|
if let Some(n) = vec.iter().position(|x| x.get_nickname() == user) {
|
||||||
vec[n].update_access_level(mode)
|
vec[n].update_access_level(mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "nochanlists")]
|
#[cfg(feature = "nochanlists")]
|
||||||
fn handle_namreply(&self, _: &[String], _: &Option<String>) {}
|
fn handle_namreply(&self, _: &[String], _: &Option<String>) {}
|
||||||
|
@ -556,6 +563,7 @@ mod test {
|
||||||
use client::data::Config;
|
use client::data::Config;
|
||||||
#[cfg(not(feature = "nochanlists"))]
|
#[cfg(not(feature = "nochanlists"))]
|
||||||
use client::data::User;
|
use client::data::User;
|
||||||
|
use proto::{ChannelMode, Mode};
|
||||||
use proto::command::Command::{PART, PRIVMSG};
|
use proto::command::Command::{PART, PRIVMSG};
|
||||||
use futures::{Future, Stream};
|
use futures::{Future, Stream};
|
||||||
|
|
||||||
|
@ -893,7 +901,7 @@ mod test {
|
||||||
vec![User::new("@test"), User::new("~owner"), User::new("&admin")]
|
vec![User::new("@test"), User::new("~owner"), User::new("&admin")]
|
||||||
);
|
);
|
||||||
let mut exp = User::new("@test");
|
let mut exp = User::new("@test");
|
||||||
exp.update_access_level("+v");
|
exp.update_access_level(&Mode::Plus(ChannelMode::Voice, None));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
server.list_users("#test").unwrap()[0].highest_access_level(),
|
server.list_users("#test").unwrap()[0].highest_access_level(),
|
||||||
exp.highest_access_level()
|
exp.highest_access_level()
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
//! Utilities and shortcuts for working with IRC servers.
|
//! Utilities and shortcuts for working with IRC servers.
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
use error::Result;
|
use error::Result;
|
||||||
use proto::{Capability, NegotiationVersion};
|
use proto::{Capability, Command, Mode, NegotiationVersion};
|
||||||
use proto::Command::{AUTHENTICATE, CAP, INVITE, JOIN, KICK, KILL, MODE, NICK, NOTICE};
|
use proto::command::Command::*;
|
||||||
use proto::Command::{OPER, PART, PASS, PONG, PRIVMSG, QUIT, SAMODE, SANICK, TOPIC, USER};
|
use proto::command::ModeType;
|
||||||
use client::server::Server;
|
use client::server::Server;
|
||||||
use proto::command::CapSubCommand::{END, LS, REQ};
|
use proto::command::CapSubCommand::{END, LS, REQ};
|
||||||
#[cfg(feature = "ctcp")]
|
#[cfg(feature = "ctcp")]
|
||||||
|
@ -16,7 +16,7 @@ pub trait ServerExt: Server {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.send(CAP(
|
self.send(Command::CAP(
|
||||||
None,
|
None,
|
||||||
LS,
|
LS,
|
||||||
match version {
|
match version {
|
||||||
|
@ -201,21 +201,13 @@ pub trait ServerExt: Server {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes the mode of the target.
|
/// Changes the modes for the specified target.
|
||||||
/// If `modeparmas` is an empty string, it won't be included in the message.
|
fn send_mode<T>(&self, target: &str, modes: &[Mode<T>]) -> Result<()>
|
||||||
fn send_mode(&self, target: &str, mode: &str, modeparams: &str) -> Result<()>
|
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
T: ModeType,
|
||||||
{
|
{
|
||||||
self.send(MODE(
|
self.send(T::mode(target, modes))
|
||||||
target.to_owned(),
|
|
||||||
mode.to_owned(),
|
|
||||||
if modeparams.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(modeparams.to_owned())
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes the mode of the target by force.
|
/// Changes the mode of the target by force.
|
||||||
|
@ -358,6 +350,7 @@ mod test {
|
||||||
use client::data::Config;
|
use client::data::Config;
|
||||||
use client::server::IrcServer;
|
use client::server::IrcServer;
|
||||||
use client::server::test::{get_server_value, test_config};
|
use client::server::test::{get_server_value, test_config};
|
||||||
|
use proto::{ChannelMode, Mode};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn identify() {
|
fn identify() {
|
||||||
|
@ -483,14 +476,15 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn send_mode_no_modeparams() {
|
fn send_mode_no_modeparams() {
|
||||||
let server = IrcServer::from_config(test_config()).unwrap();
|
let server = IrcServer::from_config(test_config()).unwrap();
|
||||||
server.send_mode("#test", "+i", "").unwrap();
|
server.send_mode("#test", &[Mode::Plus(ChannelMode::InviteOnly, None)]).unwrap();
|
||||||
assert_eq!(&get_server_value(server)[..], "MODE #test +i\r\n");
|
assert_eq!(&get_server_value(server)[..], "MODE #test +i\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn send_mode() {
|
fn send_mode() {
|
||||||
let server = IrcServer::from_config(test_config()).unwrap();
|
let server = IrcServer::from_config(test_config()).unwrap();
|
||||||
server.send_mode("#test", "+o", "test").unwrap();
|
server.send_mode("#test", &[Mode::Plus(ChannelMode::Oper, Some("test".to_owned()))])
|
||||||
|
.unwrap();
|
||||||
assert_eq!(&get_server_value(server)[..], "MODE #test +o test\r\n");
|
assert_eq!(&get_server_value(server)[..], "MODE #test +o test\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,11 @@ error_chain! {
|
||||||
display("Failed to parse an IRC subcommand.")
|
display("Failed to parse an IRC subcommand.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModeParsingFailed {
|
||||||
|
description("Failed to parse a mode correctly.")
|
||||||
|
display("Failed to parse a mode correctly.")
|
||||||
|
}
|
||||||
|
|
||||||
/// An error occurred on one of the internal channels of the `IrcServer`.
|
/// An error occurred on one of the internal channels of the `IrcServer`.
|
||||||
ChannelError {
|
ChannelError {
|
||||||
description("An error occured on one of the IrcServer's internal channels.")
|
description("An error occured on one of the IrcServer's internal channels.")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! Enumeration of all available client commands.
|
//! Enumeration of all available client commands.
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use error;
|
use error;
|
||||||
use proto::Response;
|
use proto::Response;
|
||||||
|
@ -20,8 +21,7 @@ pub enum Command {
|
||||||
/// OPER name :password
|
/// OPER name :password
|
||||||
OPER(String, String),
|
OPER(String, String),
|
||||||
/// MODE nickname modes
|
/// MODE nickname modes
|
||||||
/// MODE channel modes [modeparams]
|
UserMODE(String, Vec<Mode<UserMode>>),
|
||||||
MODE(String, String, Option<String>),
|
|
||||||
/// SERVICE nickname reserved distribution type reserved :info
|
/// SERVICE nickname reserved distribution type reserved :info
|
||||||
SERVICE(String, String, String, String, String, String),
|
SERVICE(String, String, String, String, String, String),
|
||||||
/// QUIT :comment
|
/// QUIT :comment
|
||||||
|
@ -34,8 +34,8 @@ pub enum Command {
|
||||||
JOIN(String, Option<String>, Option<String>),
|
JOIN(String, Option<String>, Option<String>),
|
||||||
/// PART chanlist :[comment]
|
/// PART chanlist :[comment]
|
||||||
PART(String, Option<String>),
|
PART(String, Option<String>),
|
||||||
// MODE is already defined.
|
/// MODE channel [modes [modeparams]]
|
||||||
// MODE(String, String, Option<String>),
|
ChannelMODE(String, Vec<Mode<ChannelMode>>),
|
||||||
/// TOPIC channel :[topic]
|
/// TOPIC channel :[topic]
|
||||||
TOPIC(String, Option<String>),
|
TOPIC(String, Option<String>),
|
||||||
/// NAMES [chanlist :[target]]
|
/// NAMES [chanlist :[target]]
|
||||||
|
@ -191,8 +191,13 @@ impl<'a> From<&'a Command> for String {
|
||||||
Command::NICK(ref n) => stringify("NICK", &[], Some(n)),
|
Command::NICK(ref n) => stringify("NICK", &[], Some(n)),
|
||||||
Command::USER(ref u, ref m, ref r) => stringify("USER", &[u, m, "*"], Some(r)),
|
Command::USER(ref u, ref m, ref r) => stringify("USER", &[u, m, "*"], Some(r)),
|
||||||
Command::OPER(ref u, ref p) => stringify("OPER", &[u], Some(p)),
|
Command::OPER(ref u, ref p) => stringify("OPER", &[u], Some(p)),
|
||||||
Command::MODE(ref t, ref m, Some(ref p)) => stringify("MODE", &[t, m, p], None),
|
Command::UserMODE(ref u, ref m) => {
|
||||||
Command::MODE(ref t, ref m, None) => stringify("MODE", &[t, m], None),
|
format!("MODE {}{}", u, m.iter().fold(String::new(), |mut acc, mode| {
|
||||||
|
acc.push_str(" ");
|
||||||
|
acc.push_str(&mode.to_string());
|
||||||
|
acc
|
||||||
|
}))
|
||||||
|
}
|
||||||
Command::SERVICE(ref n, ref r, ref d, ref t, ref re, ref i) => {
|
Command::SERVICE(ref n, ref r, ref d, ref t, ref re, ref i) => {
|
||||||
stringify("SERVICE", &[n, r, d, t, re], Some(i))
|
stringify("SERVICE", &[n, r, d, t, re], Some(i))
|
||||||
}
|
}
|
||||||
|
@ -205,6 +210,13 @@ impl<'a> From<&'a Command> for String {
|
||||||
Command::JOIN(ref c, None, None) => stringify("JOIN", &[c], None),
|
Command::JOIN(ref c, None, None) => stringify("JOIN", &[c], None),
|
||||||
Command::PART(ref c, Some(ref m)) => stringify("PART", &[c], Some(m)),
|
Command::PART(ref c, Some(ref m)) => stringify("PART", &[c], Some(m)),
|
||||||
Command::PART(ref c, None) => stringify("PART", &[c], None),
|
Command::PART(ref c, None) => stringify("PART", &[c], None),
|
||||||
|
Command::ChannelMODE(ref u, ref m) => {
|
||||||
|
format!("MODE {}{}", u, m.iter().fold(String::new(), |mut acc, mode| {
|
||||||
|
acc.push_str(" ");
|
||||||
|
acc.push_str(&mode.to_string());
|
||||||
|
acc
|
||||||
|
}))
|
||||||
|
}
|
||||||
Command::TOPIC(ref c, Some(ref t)) => stringify("TOPIC", &[c], Some(t)),
|
Command::TOPIC(ref c, Some(ref t)) => stringify("TOPIC", &[c], Some(t)),
|
||||||
Command::TOPIC(ref c, None) => stringify("TOPIC", &[c], None),
|
Command::TOPIC(ref c, None) => stringify("TOPIC", &[c], None),
|
||||||
Command::NAMES(Some(ref c), Some(ref t)) => stringify("NAMES", &[c], Some(t)),
|
Command::NAMES(Some(ref c), Some(ref t)) => stringify("NAMES", &[c], Some(t)),
|
||||||
|
@ -505,32 +517,14 @@ impl Command {
|
||||||
}
|
}
|
||||||
} else if cmd.eq_ignore_ascii_case("MODE") {
|
} else if cmd.eq_ignore_ascii_case("MODE") {
|
||||||
match suffix {
|
match suffix {
|
||||||
Some(suffix) => {
|
Some(suffix) => raw(cmd, args, Some(suffix)),
|
||||||
if args.len() == 2 {
|
None => if args[0].is_channel_name() {
|
||||||
Command::MODE(
|
let arg = args[1..].join(" ");
|
||||||
args[0].to_owned(),
|
Command::ChannelMODE(args[0].to_owned(), Mode::as_channel_modes(&arg)?)
|
||||||
args[1].to_owned(),
|
|
||||||
Some(suffix.to_owned()),
|
|
||||||
)
|
|
||||||
} else if args.len() == 1 {
|
|
||||||
Command::MODE(args[0].to_owned(), suffix.to_owned(), None)
|
|
||||||
} else {
|
} else {
|
||||||
raw(cmd, args, Some(suffix))
|
let arg = args[1..].join(" ");
|
||||||
}
|
Command::UserMODE(args[0].to_owned(), Mode::as_user_modes(&arg)?)
|
||||||
}
|
},
|
||||||
None => {
|
|
||||||
if args.len() == 3 {
|
|
||||||
Command::MODE(
|
|
||||||
args[0].to_owned(),
|
|
||||||
args[1].to_owned(),
|
|
||||||
Some(args[2].to_owned()),
|
|
||||||
)
|
|
||||||
} else if args.len() == 2 {
|
|
||||||
Command::MODE(args[0].to_owned(), args[1].to_owned(), None)
|
|
||||||
} else {
|
|
||||||
raw(cmd, args, suffix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if cmd.eq_ignore_ascii_case("SERVICE") {
|
} else if cmd.eq_ignore_ascii_case("SERVICE") {
|
||||||
match suffix {
|
match suffix {
|
||||||
|
@ -1760,3 +1754,307 @@ impl FromStr for BatchSubCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A marker trait for different kinds of Modes.
|
||||||
|
pub trait ModeType: fmt::Display + fmt::Debug + Clone + PartialEq {
|
||||||
|
/// Creates a command of this kind.
|
||||||
|
fn mode(target: &str, modes: &[Mode<Self>]) -> Command;
|
||||||
|
|
||||||
|
/// Returns true if this mode takes an argument, and false otherwise.
|
||||||
|
fn takes_arg(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User modes for the MODE command.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum UserMode {
|
||||||
|
/// a - user is flagged as away
|
||||||
|
Away,
|
||||||
|
/// i - marks a users as invisible
|
||||||
|
Invisible,
|
||||||
|
/// w - user receives wallops
|
||||||
|
Wallops,
|
||||||
|
/// r - restricted user connection
|
||||||
|
Restricted,
|
||||||
|
/// o - operator flag
|
||||||
|
Oper,
|
||||||
|
/// O - local operator flag
|
||||||
|
LocalOper,
|
||||||
|
/// s - marks a user for receipt of server notices
|
||||||
|
ServerNotices,
|
||||||
|
|
||||||
|
/// Any other unknown-to-the-crate mode.
|
||||||
|
Unknown(char),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModeType for UserMode {
|
||||||
|
fn mode(target: &str, modes: &[Mode<Self>]) -> Command {
|
||||||
|
Command::UserMODE(target.to_owned(), modes.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn takes_arg(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserMode {
|
||||||
|
fn from_char(c: char) -> error::Result<UserMode> {
|
||||||
|
use self::UserMode::*;
|
||||||
|
|
||||||
|
Ok(match c {
|
||||||
|
'a' => Away,
|
||||||
|
'i' => Invisible,
|
||||||
|
'w' => Wallops,
|
||||||
|
'r' => Restricted,
|
||||||
|
'o' => Oper,
|
||||||
|
'O' => LocalOper,
|
||||||
|
's' => ServerNotices,
|
||||||
|
_ => Unknown(c),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for UserMode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::UserMode::*;
|
||||||
|
|
||||||
|
write!(f, "{}", match *self {
|
||||||
|
Away => 'a',
|
||||||
|
Invisible => 'i',
|
||||||
|
Wallops => 'w',
|
||||||
|
Restricted => 'r',
|
||||||
|
Oper => 'o',
|
||||||
|
LocalOper => 'O',
|
||||||
|
ServerNotices => 's',
|
||||||
|
Unknown(c) => c,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Channel modes for the MODE command.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum ChannelMode {
|
||||||
|
/// b - ban the user from joining or speaking in the channel
|
||||||
|
Ban,
|
||||||
|
/// e - exemptions from bans
|
||||||
|
Exception,
|
||||||
|
/// l - limit the maximum number of users in a channel
|
||||||
|
Limit,
|
||||||
|
/// i - channel becomes invite-only
|
||||||
|
InviteOnly,
|
||||||
|
/// I - exception to invite-only rule
|
||||||
|
InviteException,
|
||||||
|
/// k - specify channel key
|
||||||
|
Key,
|
||||||
|
/// m - channel is in moderated mode
|
||||||
|
Moderated,
|
||||||
|
/// s - channel is hidden from listings
|
||||||
|
Secret,
|
||||||
|
/// t - require permissions to edit topic
|
||||||
|
ProtectedTopic,
|
||||||
|
/// n - users must join channels to message them
|
||||||
|
NoExternalMessages,
|
||||||
|
|
||||||
|
/// q - user gets founder permission
|
||||||
|
Founder,
|
||||||
|
/// a - user gets admin or protected permission
|
||||||
|
Admin,
|
||||||
|
/// o - user gets oper permission
|
||||||
|
Oper,
|
||||||
|
/// h - user gets halfop permission
|
||||||
|
Halfop,
|
||||||
|
/// v - user gets voice permission
|
||||||
|
Voice,
|
||||||
|
|
||||||
|
/// Any other unknown-to-the-crate mode.
|
||||||
|
Unknown(char),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModeType for ChannelMode {
|
||||||
|
fn mode(target: &str, modes: &[Mode<Self>]) -> Command {
|
||||||
|
Command::ChannelMODE(target.to_owned(), modes.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn takes_arg(&self) -> bool {
|
||||||
|
use self::ChannelMode::*;
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
Ban | Exception | Limit | InviteException | Key | Founder | Admin | Oper | Halfop |
|
||||||
|
Voice => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChannelMode {
|
||||||
|
fn from_char(c: char) -> error::Result<ChannelMode> {
|
||||||
|
use self::ChannelMode::*;
|
||||||
|
|
||||||
|
Ok(match c {
|
||||||
|
'b' => Ban,
|
||||||
|
'e' => Exception,
|
||||||
|
'l' => Limit,
|
||||||
|
'i' => InviteOnly,
|
||||||
|
'I' => InviteException,
|
||||||
|
'k' => Key,
|
||||||
|
'm' => Moderated,
|
||||||
|
's' => Secret,
|
||||||
|
't' => ProtectedTopic,
|
||||||
|
'n' => NoExternalMessages,
|
||||||
|
'q' => Founder,
|
||||||
|
'a' => Admin,
|
||||||
|
'o' => Oper,
|
||||||
|
'h' => Halfop,
|
||||||
|
'v' => Voice,
|
||||||
|
_ => Unknown(c),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ChannelMode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::ChannelMode::*;
|
||||||
|
|
||||||
|
write!(f, "{}", match *self {
|
||||||
|
Ban => 'b',
|
||||||
|
Exception => 'e',
|
||||||
|
Limit => 'l',
|
||||||
|
InviteOnly => 'i',
|
||||||
|
InviteException => 'I',
|
||||||
|
Key => 'k',
|
||||||
|
Moderated => 'm',
|
||||||
|
Secret => 's',
|
||||||
|
ProtectedTopic => 't',
|
||||||
|
NoExternalMessages => 'n',
|
||||||
|
Founder => 'q',
|
||||||
|
Admin => 'a',
|
||||||
|
Oper => 'o',
|
||||||
|
Halfop => 'h',
|
||||||
|
Voice => 'v',
|
||||||
|
Unknown(c) => c,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mode argument for the MODE command.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Mode<T>
|
||||||
|
where
|
||||||
|
T: ModeType,
|
||||||
|
{
|
||||||
|
/// Adding the specified mode, optionally with an argument.
|
||||||
|
Plus(T, Option<String>),
|
||||||
|
/// Removing the specified mode, optionally with an argument.
|
||||||
|
Minus(T, Option<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Display for Mode<T>
|
||||||
|
where
|
||||||
|
T: ModeType,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
&Mode::Plus(ref mode, Some(ref arg)) => write!(f, "{}{} {}", "+", mode, arg),
|
||||||
|
&Mode::Minus(ref mode, Some(ref arg)) => write!(f, "{}{} {}", "-", mode, arg),
|
||||||
|
&Mode::Plus(ref mode, None) => write!(f, "{}{}", "+", mode),
|
||||||
|
&Mode::Minus(ref mode, None) => write!(f, "{}{}", "-", mode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PlusMinus {
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODE user [modes]
|
||||||
|
impl Mode<UserMode> {
|
||||||
|
// TODO: turning more edge cases into errors.
|
||||||
|
/// Parses the specified mode string as user modes.
|
||||||
|
pub fn as_user_modes(s: &str) -> error::Result<Vec<Mode<UserMode>>> {
|
||||||
|
use self::PlusMinus::*;
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
let mut pieces = s.split(" ");
|
||||||
|
for term in pieces.clone() {
|
||||||
|
if term.starts_with("+") || term.starts_with("-") {
|
||||||
|
let _ = pieces.next();
|
||||||
|
|
||||||
|
let mut chars = term.chars();
|
||||||
|
let init = match chars.next() {
|
||||||
|
Some('+') => Plus,
|
||||||
|
Some('-') => Minus,
|
||||||
|
_ => return Err(error::ErrorKind::ModeParsingFailed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
for c in chars {
|
||||||
|
let mode = UserMode::from_char(c)?;
|
||||||
|
let arg = if mode.takes_arg() {
|
||||||
|
pieces.next()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
res.push(match init {
|
||||||
|
Plus => Mode::Plus(mode, arg.map(|s| s.to_owned())),
|
||||||
|
Minus => Mode::Minus(mode, arg.map(|s| s.to_owned())),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODE channel [modes [modeparams]]
|
||||||
|
impl Mode<ChannelMode> {
|
||||||
|
// TODO: turning more edge cases into errors.
|
||||||
|
/// Parses the specified mode string as channel modes.
|
||||||
|
pub fn as_channel_modes(s: &str) -> error::Result<Vec<Mode<ChannelMode>>> {
|
||||||
|
use self::PlusMinus::*;
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
let mut pieces = s.split(" ");
|
||||||
|
for term in pieces.clone() {
|
||||||
|
if term.starts_with("+") || term.starts_with("-") {
|
||||||
|
let _ = pieces.next();
|
||||||
|
|
||||||
|
let mut chars = term.chars();
|
||||||
|
let init = match chars.next() {
|
||||||
|
Some('+') => Plus,
|
||||||
|
Some('-') => Minus,
|
||||||
|
_ => return Err(error::ErrorKind::ModeParsingFailed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
for c in chars {
|
||||||
|
let mode = ChannelMode::from_char(c)?;
|
||||||
|
let arg = if mode.takes_arg() {
|
||||||
|
pieces.next()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
res.push(match init {
|
||||||
|
Plus => Mode::Plus(mode, arg.map(|s| s.to_owned())),
|
||||||
|
Minus => Mode::Minus(mode, arg.map(|s| s.to_owned())),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An extension trait giving strings a function to check if they are a channel.
|
||||||
|
pub trait ChannelExt {
|
||||||
|
/// Returns true if the specified name is a channel name.
|
||||||
|
fn is_channel_name(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ChannelExt for &'a str {
|
||||||
|
fn is_channel_name(&self) -> bool {
|
||||||
|
self.starts_with("#") ||
|
||||||
|
self.starts_with("&") ||
|
||||||
|
self.starts_with("+") ||
|
||||||
|
self.starts_with("!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub mod message;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
pub use self::caps::{Capability, NegotiationVersion};
|
pub use self::caps::{Capability, NegotiationVersion};
|
||||||
pub use self::command::{BatchSubCommand, CapSubCommand, Command};
|
pub use self::command::{BatchSubCommand, CapSubCommand, ChannelMode, Command, Mode, UserMode};
|
||||||
pub use self::irc::IrcCodec;
|
pub use self::irc::IrcCodec;
|
||||||
pub use self::message::Message;
|
pub use self::message::Message;
|
||||||
pub use self::response::Response;
|
pub use self::response::Response;
|
||||||
|
|
Loading…
Reference in a new issue