From 07b8f2b9eeef3ec4c3822d40d07727370ac0b80b Mon Sep 17 00:00:00 2001 From: famfo Date: Thu, 11 Nov 2021 09:18:50 +0100 Subject: [PATCH] Moved commands to seperate file, fixed ping issue, added authors, bumped version number --- Cargo.lock | 2 +- Cargo.toml | 3 +- src/commands.rs | 200 +++++++++++++++++++++++++++++++++ src/lib.rs | 289 ++++++++++-------------------------------------- src/main.rs | 2 +- 5 files changed, 262 insertions(+), 234 deletions(-) create mode 100644 src/commands.rs diff --git a/Cargo.lock b/Cargo.lock index ef780e2..177eafa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "circe" -version = "0.1.2" +version = "0.1.3" dependencies = [ "native-tls", "serde", diff --git a/Cargo.toml b/Cargo.toml index d1a6ec8..2f551bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [package] +authors = ["famfo", "karx"] name = "circe" -version = "0.1.2" +version = "0.1.3" edition = "2021" license = "Unlicense" description = "IRC crate in Rust" diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..4532969 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,200 @@ +#[doc(hidden)] +#[derive(Debug)] +pub enum CapMode { + LS, + END, +} + +/// IRC commands +#[derive(Debug)] +pub enum Command { + // TODO: + // SERVICE + // SQUIT + // + /// Gets information about the admin of the IRC server. + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.admin("192.168.178.100")?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + ADMIN( + /// Target + String, + ), + /// Sets the user status to AWAY + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.away("AFK")?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + AWAY( + /// Message + String, + ), + #[doc(hidden)] + CAP(CapMode), + /// Invite user to channel + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.invite("liblirc", "#circe")?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + INVITE( + /// User + String, + /// Channel + String, + ), + /// Joins a channel + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.join("#main")?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + JOIN( + /// Channel + String, + ), + /// Lists all channels and their topics + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.list(None, None)?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + LIST( + /// Channel + Option, + /// Server to foreward request to + Option, + ), + /// Sets the mode of the user + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.mode("test", Some("+B"))?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + /// If the MODE is not given (e.g. None), then the client will send "MODE target" + MODE( + /// Channel + String, + /// Mode + Option, + ), + /// List all nicknames visiable to the Client + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.names("#main,#circe", None)?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + NAMES( + /// Channel + String, + /// Server to foreward request to + Option, + ), + #[doc(hidden)] + NICK(String), + /// Attempts to identify as a channel operator + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.oper("circe", "foo")?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + OPER( + /// Username + String, + /// Password + String, + ), + /// Everything that is not a command + OTHER(String), + /// Leave a channel + /// ```no_run + /// # use circe::* + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.part("#main")?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + PART( + /// Target + String, + ), + #[doc(hidden)] + PASS(String), + #[doc(hidden)] + PING(String), + #[doc(hidden)] + PONG(String), + /// Sends a message in a channel + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.privmsg("This is an example message")?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + PRIVMSG( + /// Channel + String, + /// Message + String, + ), + /// Leaves the IRC + /// ```no_run + /// # use circe::* + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.quit(Some("Leaving..."))?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + QUIT( + /// Leave message + String, + ), + /// Sets or gets the topic of a channel + /// ```no_run + /// # use circe::* + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.topic("#main", Some("main channel"))?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + TOPIC( + /// Channel + String, + /// Topic + Option, + ), + #[doc(hidden)] + USER(String, String, String, String), +} + +impl Command { + /// Performs the conversion + pub fn from_str(s: &str) -> Self { + let new = s.trim(); + let parts: Vec<&str> = new.split_whitespace().collect(); + + if parts[0] == "PING" { + let command = parts[1].to_string(); + return Self::PONG(command); + } else if parts[1] == "PRIVMSG" { + let target = parts[2]; + let mut builder = String::new(); + + for part in parts[3..].to_vec() { + builder.push_str(&format!("{} ", part)); + } + + return Self::PRIVMSG(target.to_string(), (&builder[1..]).to_string()); + } + + Self::OTHER(new.to_string()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 99452e0..607fce0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,9 @@ use std::path::Path; use serde_derive::Deserialize; +/// IRC comamnds +pub mod commands; + /// An IRC client pub struct Client { config: Config, @@ -50,206 +53,6 @@ pub struct Config { username: String, } -#[doc(hidden)] -#[derive(Debug)] -pub enum CapMode { - LS, - END, -} - -/// IRC commands -#[derive(Debug)] -pub enum Command { - // TODO: - // SERVICE - // SQUIT - // - /// Gets information about the admin of the IRC server. - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.admin("192.168.178.100")?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - ADMIN( - /// Target - String, - ), - /// Sets the user status to AWAY - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.away("AFK")?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - AWAY( - /// Message - String, - ), - #[doc(hidden)] - CAP(CapMode), - /// Invite user to channel - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.invite("liblirc", "#circe")?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - INVITE( - /// User - String, - /// Channel - String, - ), - /// Joins a channel - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.join("#main")?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - JOIN( - /// Channel - String, - ), - /// Lists all channels and their topics - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.list(None, None)?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - LIST( - /// Channel - Option, - // Server - Option, - ), - /// Sets the mode of the user - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.mode("test", Some("+B"))?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - /// If the MODE is not given (e.g. None), then the client will send "MODE target" - MODE( - /// Channel - String, - /// Mode - Option, - ), - /// List all nicknames visiable to the Client - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.names("#main,#circe", None)?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - NAMES( - /// Channel - String, - /// Server to foreward request to - Option, - ), - #[doc(hidden)] - NICK(String), - /// Attempts to identify as a channel operator - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.oper("circe", "foo")?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - OPER( - /// Username - String, - /// Password - String, - ), - /// Everything that is not a command - OTHER(String), - /// Leave a channel - /// ```no_run - /// # use circe::* - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.part("#main")?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - PART( - /// Target - String, - ), - #[doc(hidden)] - PASS(String), - #[doc(hidden)] - PING(String), - #[doc(hidden)] - PONG(String), - /// Sends a message in a channel - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.privmsg("This is an example message")?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - PRIVMSG( - /// Channel - String, - /// Message - String, - ), - /// Leaves the IRC - /// ```no_run - /// # use circe::* - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.quit(Some("Leaving..."))?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - QUIT( - /// Leave message - String, - ), - /// Sets or gets the topic of a channel - /// ```no_run - /// # use circe::* - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.topic("#main", Some("main channel"))?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - TOPIC( - /// Channel - String, - /// Topic - Option, - ), - #[doc(hidden)] - USER(String, String, String, String), -} - -impl Command { - fn from_str(s: &str) -> Self { - let new = s.trim(); - - if new.starts_with("PING") { - let command = new.split_whitespace().collect::>()[1].to_string(); - return Self::PONG(command); - } else if new.contains("PRIVMSG") { - let parts: Vec<&str> = new.split_whitespace().collect(); - let target = parts[2]; - let mut builder = String::new(); - - for part in parts[3..].to_vec() { - builder.push_str(&format!("{} ", part)); - } - - return Self::PRIVMSG(target.to_string(), (&builder[1..]).to_string()); - } - - Self::OTHER(new.to_string()) - } -} - impl Client { /// Creates a new client with a given [`Config`]. /// ```no_run @@ -291,8 +94,10 @@ impl Client { /// ``` /// Returns error if the client could not write to the stream. pub fn identify(&mut self) -> Result<(), Error> { - self.write_command(Command::CAP(CapMode::END))?; - self.write_command(Command::USER( + self.write_command(commands::Command::CAP(commands::CapMode::LS))?; + self.write_command(commands::Command::CAP(commands::CapMode::END))?; + + self.write_command(commands::Command::USER( self.config.username.clone(), "*".into(), "*".into(), @@ -300,19 +105,19 @@ impl Client { ))?; if let Some(nick) = self.config.nickname.clone() { - self.write_command(Command::NICK(nick.to_string()))?; + self.write_command(commands::Command::NICK(nick.to_string()))?; } else { - self.write_command(Command::NICK(self.config.username.clone()))?; + self.write_command(commands::Command::NICK(self.config.username.clone()))?; } loop { if let Ok(ref command) = self.read() { match command { - Command::PING(code) => { + commands::Command::PING(code) => { println!("{}", code); - self.write_command(Command::PONG(code.to_string()))?; + self.write_command(commands::Command::PONG(code.to_string()))?; } - Command::OTHER(line) => { + commands::Command::OTHER(line) => { print!("{}", line); if line.contains("001") { break; @@ -324,9 +129,9 @@ impl Client { } let config = self.config.clone(); - self.write_command(Command::MODE(config.username, config.mode))?; + self.write_command(commands::Command::MODE(config.username, config.mode))?; for channel in config.channels.iter() { - self.write_command(Command::JOIN(channel.to_string()))?; + self.write_command(commands::Command::JOIN(channel.to_string()))?; } Ok(()) @@ -350,7 +155,7 @@ impl Client { Some(String::from_utf8_lossy(&buffer).into()) } - /// Read data coming from the IRC as a [`Command`]. + /// Read data coming from the IRC as a [`commands::Command`]. /// ```no_run /// # use circe::*; /// # fn main() -> Result<(), std::io::Error> { @@ -365,9 +170,18 @@ impl Client { /// # } /// ``` /// Returns error if there are no new messages. This should not be taken as an actual error, because nothing went wrong. - pub fn read(&mut self) -> Result { + pub fn read(&mut self) -> Result { if let Some(string) = self.read_string() { - return Ok(Command::from_str(&string)); + let command = commands::Command::from_str(&string); + + if let commands::Command::PONG(command) = command { + if let Err(_e) = self.write_command(commands::Command::PONG(command)) { + return Err(()); + } + return Ok(commands::Command::PONG("".to_string())); + } + + return Ok(command); } Err(()) @@ -394,7 +208,7 @@ impl Client { Ok(()) } - /// Send a [`Command`] to the IRC.
+ /// Send a [`commands::Command`] to the IRC.
/// Not reccomended to use, use the helper functions instead. /// ```no_run /// # use circe::*; @@ -403,8 +217,8 @@ impl Client { /// # Ok::<(), std::io::Error>(()) /// ``` /// Returns error if the client could not write to the stream. - pub fn write_command(&mut self, command: Command) -> Result<(), Error> { - use Command::*; + pub fn write_command(&mut self, command: commands::Command) -> Result<(), Error> { + use commands::Command::*; let computed = match command { ADMIN(target) => { let formatted = format!("ADMIN {}", target); @@ -415,7 +229,7 @@ impl Client { Cow::Owned(formatted) as Cow } CAP(mode) => { - use CapMode::*; + use commands::CapMode::*; Cow::Borrowed(match mode { LS => "CAP LS 302", END => "CAP END", @@ -487,6 +301,7 @@ impl Client { } PONG(code) => { let formatted = format!("PONG {}", code); + println!("formatted\nPONG {}", formatted); Cow::Owned(formatted) as Cow } PRIVMSG(target, message) => { @@ -527,7 +342,7 @@ impl Client { /// # Ok::<(), std::io::Error>(()) /// ``` pub fn admin(&mut self, target: &str) -> Result<(), Error> { - self.write_command(Command::ADMIN(target.to_string()))?; + self.write_command(commands::Command::ADMIN(target.to_string()))?; Ok(()) } @@ -539,7 +354,7 @@ impl Client { /// # Ok::<(), std::io::Error>(()) /// ``` pub fn away(&mut self, message: &str) -> Result<(), Error> { - self.write_command(Command::AWAY(message.to_string()))?; + self.write_command(commands::Command::AWAY(message.to_string()))?; Ok(()) } @@ -551,7 +366,10 @@ impl Client { /// # Ok::<(), std::io::Error>(()) /// ``` pub fn privmsg(&mut self, channel: &str, message: &str) -> Result<(), Error> { - self.write_command(Command::PRIVMSG(channel.to_string(), message.to_string()))?; + self.write_command(commands::Command::PRIVMSG( + channel.to_string(), + message.to_string(), + ))?; Ok(()) } @@ -563,7 +381,10 @@ impl Client { /// # Ok::<(), std::io::Error>(()) /// ``` pub fn invite(&mut self, username: &str, channel: &str) -> Result<(), Error> { - self.write_command(Command::INVITE(username.to_string(), channel.to_string()))?; + self.write_command(commands::Command::INVITE( + username.to_string(), + channel.to_string(), + ))?; Ok(()) } @@ -575,7 +396,7 @@ impl Client { /// # Ok::<(), std::io::Error>(()) /// ``` pub fn join(&mut self, channel: &str) -> Result<(), Error> { - self.write_command(Command::JOIN(channel.to_string()))?; + self.write_command(commands::Command::JOIN(channel.to_string()))?; Ok(()) } @@ -601,7 +422,7 @@ impl Client { None } }; - self.write_command(Command::LIST(channel_config, server_config))?; + self.write_command(commands::Command::LIST(channel_config, server_config))?; Ok(()) } @@ -614,12 +435,12 @@ impl Client { /// ``` pub fn names(&mut self, channel: &str, server: Option<&str>) -> Result<(), Error> { if let Some(server) = server { - self.write_command(Command::NAMES( + self.write_command(commands::Command::NAMES( channel.to_string(), Some(server.to_string()), ))?; } else { - self.write_command(Command::NAMES(channel.to_string(), None))?; + self.write_command(commands::Command::NAMES(channel.to_string(), None))?; } Ok(()) } @@ -633,9 +454,12 @@ impl Client { /// ``` pub fn mode(&mut self, target: &str, mode: Option<&str>) -> Result<(), Error> { if let Some(mode) = mode { - self.write_command(Command::MODE(target.to_string(), Some(mode.to_string())))?; + self.write_command(commands::Command::MODE( + target.to_string(), + Some(mode.to_string()), + ))?; } else { - self.write_command(Command::MODE(target.to_string(), None))?; + self.write_command(commands::Command::MODE(target.to_string(), None))?; } Ok(()) } @@ -648,7 +472,7 @@ impl Client { /// # Ok::<(), std::io::Error>(()) /// ``` pub fn part(&mut self, target: &str) -> Result<(), Error> { - self.write_command(Command::PART(target.to_string()))?; + self.write_command(commands::Command::PART(target.to_string()))?; Ok(()) } @@ -661,9 +485,12 @@ impl Client { /// ``` pub fn topic(&mut self, channel: &str, topic: Option<&str>) -> Result<(), Error> { if let Some(topic) = topic { - self.write_command(Command::TOPIC(channel.to_string(), Some(topic.to_string())))?; + self.write_command(commands::Command::TOPIC( + channel.to_string(), + Some(topic.to_string()), + ))?; } else { - self.write_command(Command::TOPIC(channel.to_string(), None))?; + self.write_command(commands::Command::TOPIC(channel.to_string(), None))?; } Ok(()) } @@ -677,9 +504,9 @@ impl Client { /// ``` pub fn quit(&mut self, message: Option<&str>) -> Result<(), Error> { if let Some(message) = message { - self.write_command(Command::QUIT(message.to_string()))?; + self.write_command(commands::Command::QUIT(message.to_string()))?; } else { - self.write_command(Command::QUIT(format!( + self.write_command(commands::Command::QUIT(format!( "circe {} (https://crates.io/crates/circe)", env!("CARGO_PKG_VERSION") )))?; diff --git a/src/main.rs b/src/main.rs index 0f63a35..d0d84dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use circe::{Client, Command, Config}; +use circe::{commands::Command, Client, Config}; #[allow(unused_macros)] macro_rules! loop_n {