From 92ee775225363a1ba3d503418a85064fa116034e Mon Sep 17 00:00:00 2001 From: famfo Date: Mon, 8 Nov 2021 17:56:06 +0100 Subject: [PATCH] Numerous new commands, documentation changews, started with ssl --- Cargo.lock | 102 +++++++++++++ Cargo.toml | 8 +- src/lib.rs | 431 +++++++++++++++++++++++++++++++++++++++------------- src/main.rs | 4 +- 4 files changed, 438 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5599b7f..6c064b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,111 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "circe" version = "0.1.2" dependencies = [ + "openssl", + "openssl-sys", "serde", "serde_derive", "toml", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "openssl" +version = "0.10.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-src" +version = "300.0.2+3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a760a11390b1a5daf72074d4f6ff1a6e772534ae191f999f57e9ee8146d1fb" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6517987b3f8226b5da3661dad65ff7f300cc59fb5ea8333ca191fc65fde3edf" +dependencies = [ + "autocfg", + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pkg-config" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" + [[package]] name = "proc-macro2" version = "1.0.32" @@ -71,3 +167,9 @@ name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" diff --git a/Cargo.toml b/Cargo.toml index 46d58eb..8344465 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,8 @@ exclude = ["src/main.rs", "config.toml"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -toml = "0.5.8" -serde_derive = "1.0.130" -serde = "1.0.130" +toml = "0.5" +serde_derive = "1.0" +serde = "1.0" +openssl = { version = "0.10", features = ["vendored"] } +openssl-sys = "0.9" diff --git a/src/lib.rs b/src/lib.rs index 9ff561f..3b565ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,11 +17,11 @@ //! } //! # break; //! } -//! //! # Ok(()) //! } #![warn(missing_docs)] +use openssl::ssl::{SslConnector, SslMethod}; use std::borrow::Cow; use std::fs::File; use std::io::{Error, Read, Write}; @@ -33,7 +33,8 @@ use serde_derive::Deserialize; /// An IRC client pub struct Client { config: Config, - stream: TcpStream, + stream: Option, + sslstream: Option>, } /// Config for the IRC client @@ -44,6 +45,7 @@ pub struct Config { mode: Option, nickname: Option, port: u16, + ssl: bool, username: String, } @@ -55,27 +57,78 @@ pub enum CapMode { } /// IRC commands -/// Not reccomended to use, use the helper functions instead #[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.write_command(Command::JOIN("#main".to_string()))?; + /// 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.write_command(Command::MODE("#main".to_string(), Some("+B".to_string())))?; + /// 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" @@ -85,18 +138,49 @@ pub enum Command { /// 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), - /// Leaves channels + /// Leave a channel /// ```no_run /// # use circe::* /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.write_part(Command::PART("#main".to_string()))?; + /// client.part("#main")?; /// # Ok::<(), std::io::Error>(()) /// ``` - PART(String), + PART( + /// Target + String, + ), + #[doc(hidden)] + PASS(String), #[doc(hidden)] PING(String), #[doc(hidden)] @@ -105,7 +189,7 @@ pub enum Command { /// ```no_run /// # use circe::*; /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.write_command(Command::PRIVMSG("#main".to_string(), "This is an example message".to_string()))?; + /// client.privmsg("This is an example message")?; /// # Ok::<(), std::io::Error>(()) /// ``` PRIVMSG( @@ -118,10 +202,26 @@ pub enum Command { /// ```no_run /// # use circe::* /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.write_command(Command::QUIT("Leaving...".to_string()))?; + /// client.quit(Some("Leaving..."))?; /// # Ok::<(), std::io::Error>(()) /// ``` - QUIT(String), + 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), } @@ -135,9 +235,9 @@ impl Command { 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)); } @@ -150,7 +250,7 @@ impl Command { } impl Client { - /// Creates a new client with a given config + /// Creates a new client with a given [`Config`]. /// ```no_run /// # use circe::*; /// # let config = Config::from_toml("config.toml")?; @@ -158,21 +258,36 @@ impl Client { /// # Ok::<(), std::io::Error>(()) /// ``` /// - /// Errors if the client could not connect to the given host. + /// Returns error if the client could not connect to the host. pub fn new(config: Config) -> Result { let stream = TcpStream::connect(format!("{}:{}", config.host, config.port))?; - Ok(Self { stream, config }) + let sslstream: openssl::ssl::SslStream; + + if config.ssl { + let connector = SslConnector::builder(SslMethod::tls())?.build(); + sslstream = connector.connect(config.host.as_str(), stream).unwrap(); + return Ok(Self { + config, + stream: None, + sslstream: Some(sslstream), + }); + } else { + return Ok(Self { + config, + stream: Some(stream), + sslstream: None, + }); + } } - /// Identify user and join the specified channels + /// Identify user and joins the in the [`Config`] specified channels. /// ```no_run /// # use circe::*; /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; /// client.identify()?; /// # Ok::<(), std::io::Error>(()) /// ``` - /// - /// Errors if the client could not write to the stream. + /// 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( @@ -214,15 +329,22 @@ impl Client { fn read_string(&mut self) -> Option { let mut buffer = [0u8; 512]; - match self.stream.read(&mut buffer) { - Ok(_) => {} - Err(_) => return None, - }; + if self.config.ssl { + match self.sslstream.unwrap().read(&mut buffer) { + Ok(_) => {} + Err(_) => return None, + }; + } else { + match self.stream.as_ref().unwrap().read(&mut buffer) { + Ok(_) => {} + Err(_) => return None, + }; + } Some(String::from_utf8_lossy(&buffer).into()) } - /// Read data coming from the IRC as a [`Command`] + /// Read data coming from the IRC as a [`Command`]. /// ```no_run /// # use circe::*; /// # fn main() -> Result<(), std::io::Error> { @@ -236,8 +358,7 @@ impl Client { /// # Ok::<(), std::io::Error>(()) /// # } /// ``` - /// - /// Errors if there are no new messages. This should not be taken as an actual Error, because nothing went wrong. + /// 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 { if let Some(string) = self.read_string() { return Ok(Command::from_str(&string)); @@ -249,26 +370,40 @@ impl Client { fn write(&mut self, data: &str) -> Result<(), Error> { let formatted = { let new = format!("{}\r\n", data); - Cow::Owned(new) as Cow }; - self.stream.write(formatted.as_bytes())?; + if self.config.ssl { + self.sslstream + .as_ref() + .unwrap() + .write(formatted.as_bytes())?; + } else { + self.stream.as_ref().unwrap().write(formatted.as_bytes())?; + } Ok(()) } - /// Send a [`Command`] to the IRC + /// Send a [`Command`] to the IRC.
+ /// Not reccomended to use, use the helper functions instead. /// ```no_run /// # use circe::*; /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; /// client.write_command(Command::PRIVMSG("#main".to_string(), "Hello".to_string()))?; /// # Ok::<(), std::io::Error>(()) /// ``` - /// - /// Errors if the stream could not write. + /// Returns error if the client could not write to the stream. pub fn write_command(&mut self, command: Command) -> Result<(), Error> { use Command::*; let computed = match command { + ADMIN(target) => { + let formatted = format!("ADMIN {}", target); + Cow::Owned(formatted) as Cow + } + AWAY(message) => { + let formatted = format!("AWAY {}", message); + Cow::Owned(formatted) as Cow + } CAP(mode) => { use CapMode::*; Cow::Borrowed(match mode { @@ -276,10 +411,34 @@ impl Client { END => "CAP END", }) as Cow } + INVITE(username, channel) => { + let formatted = format!("INVITE {} {}", username, channel); + Cow::Owned(formatted) as Cow + } JOIN(channel) => { let formatted = format!("JOIN {}", channel); Cow::Owned(formatted) as Cow } + LIST(channel, server) => { + let mut formatted = "LIST".to_string(); + if let Some(channel) = channel { + formatted.push_str(format!(" {}", channel).as_str()); + } + if let Some(server) = server { + formatted.push_str(format!(" {}", server).as_str()); + } + Cow::Owned(formatted) as Cow + } + NAMES(channel, server) => { + let formatted = { + if let Some(server) = server { + format!("NAMES {} {}", channel, server) + } else { + format!("NAMES {}", channel) + } + }; + Cow::Owned(formatted) as Cow + } NICK(nickname) => { let formatted = format!("NICK {}", nickname); Cow::Owned(formatted) as Cow @@ -292,7 +451,10 @@ impl Client { format!("MODE {}", target) } }; - + Cow::Owned(formatted) as Cow + } + OPER(nick, password) => { + let formatted = format!("OPER {} {}", nick, password); Cow::Owned(formatted) as Cow } OTHER(_) => { @@ -305,6 +467,10 @@ impl Client { let formatted = format!("PART {}", target); Cow::Owned(formatted) as Cow } + PASS(password) => { + let formatted = format!("PASS {}", password); + Cow::Owned(formatted) as Cow + } PING(target) => { let formatted = format!("PING {}", target); Cow::Owned(formatted) as Cow @@ -318,7 +484,17 @@ impl Client { Cow::Owned(formatted) as Cow } QUIT(message) => { - let formatted = format!("QUIT {}", message); + let formatted = format!("QUIT :{}", message); + Cow::Owned(formatted) as Cow + } + TOPIC(channel, topic) => { + let formatted = { + if let Some(topic) = topic { + format!("TOPIC {} :{}", channel, topic) + } else { + format!("TOPIC {}", channel) + } + }; Cow::Owned(formatted) as Cow } USER(username, s1, s2, realname) => { @@ -331,70 +507,121 @@ impl Client { Ok(()) } - // Helper functions + // Helper commands + + /// Helper function for requesting information about the ADMIN of an 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>(()) + /// ``` + pub fn admin(&mut self, target: &str) -> Result<(), Error> { + self.write_command(Command::ADMIN(target.to_string()))?; + Ok(()) + } + + /// Helper function for setting the users status to AWAY + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.away("AFK")?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + pub fn away(&mut self, message: &str) -> Result<(), Error> { + self.write_command(Command::AWAY(message.to_string()))?; + Ok(()) + } /// Helper function for sending PRIVMSGs. - /// This makes /// ```no_run /// # use circe::*; /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.send_privmsg("#main", "Hello")?; + /// client.privmsg("#main", "Hello")?; /// # Ok::<(), std::io::Error>(()) /// ``` - /// - /// equivalent to - /// - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.write_command(Command::PRIVMSG("#main".to_string(), "Hello".to_string()))?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - pub fn send_privmsg(&mut self, channel: &str, message: &str) -> Result<(), Error> { + pub fn privmsg(&mut self, channel: &str, message: &str) -> Result<(), Error> { self.write_command(Command::PRIVMSG(channel.to_string(), message.to_string()))?; Ok(()) } + /// Helper function to INVITE people to a channels + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.invite("liblirc", "#circe")?; + /// # 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()))?; + Ok(()) + } + /// Helper function for sending JOINs. - /// This makes /// ```no_run /// # use circe::*; /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.send_join("#main")?; + /// client.join("#main")?; /// # Ok::<(), std::io::Error>(()) /// ``` - /// - /// equivalent to - /// - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.write_command(Command::JOIN("#main".to_string()))?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - pub fn send_join(&mut self, channel: &str) -> Result<(), Error> { + pub fn join(&mut self, channel: &str) -> Result<(), Error> { self.write_command(Command::JOIN(channel.to_string()))?; Ok(()) } + /// Helper function for LISTing channels and modes + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.list(None, None)?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + pub fn list(&mut self, channel: Option<&str>, server: Option<&str>) -> Result<(), Error> { + let channel_config = { + if let Some(channel) = channel { + Some(channel.to_string()) + } else { + None + } + }; + let server_config = { + if let Some(server) = server { + Some(server.to_string()) + } else { + None + } + }; + self.write_command(Command::LIST(channel_config, server_config))?; + Ok(()) + } + + /// Helper function for getting all nicknames in a channel + /// ```no_run + /// # use circe::*; + /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; + /// client.names("#main,#circe", None)?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + pub fn names(&mut self, channel: &str, server: Option<&str>) -> Result<(), Error> { + if let Some(server) = server { + self.write_command(Command::NAMES( + channel.to_string(), + Some(server.to_string()), + ))?; + } else { + self.write_command(Command::NAMES(channel.to_string(), None))?; + } + Ok(()) + } + /// Helper function for sending MODEs. - /// This makes /// ```no_run /// # use circe::*; /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.send_mode("test", Some("+B"))?; + /// client.mode("test", Some("+B"))?; /// # Ok::<(), std::io::Error>(()) /// ``` - /// - /// equivalent to - /// - /// ```no_run - /// # use circe::*; - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.write_command(Command::MODE("test".to_string(), Some("+B".to_string())))?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - pub fn send_mode(&mut self, target: &str, mode: Option<&str>) -> Result<(), Error> { + 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())))?; } else { @@ -404,54 +631,54 @@ impl Client { } /// Helper function for leaving channels. - /// This makes /// ```no_run /// # use circe::*; /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.send_part("#main")?; + /// client.part("#main")?; /// # Ok::<(), std::io::Error>(()) /// ``` - /// - /// equivalent to - /// - /// ```no_run - /// # use circe::* - /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.write_part(Command::PART("#main".to_string()))?; - /// # Ok::<(), std::io::Error>(()) - /// ``` - pub fn send_part(&mut self, target: &str) -> Result<(), Error> { + pub fn part(&mut self, target: &str) -> Result<(), Error> { self.write_command(Command::PART(target.to_string()))?; Ok(()) } - /// Helper function for leaving the IRC server and shutting down the TCP stream afterwards. - /// This makes + /// Helper function for setting or getting the topic of a channel /// ```no_run /// # use circe::*; /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.send_quit(None)?; + /// client.topic("#main", Some("main channel"))?; /// # Ok::<(), std::io::Error>(()) /// ``` - /// - /// equivalent to - /// + 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())))?; + } else { + self.write_command(Command::TOPIC(channel.to_string(), None))?; + } + Ok(()) + } + + /// Helper function for leaving the IRC server and shutting down the TCP stream afterwards. /// ```no_run - /// # use circe::* + /// # use circe::*; /// # let mut client = Client::new(Config::from_toml("config.toml")?)?; - /// client.write_part(Command::QUIT("Leaving...".to_string()))?; - /// client.stream.shutdown(std::net::Shutdown::Both)?; + /// client.quit(None)?; /// # Ok::<(), std::io::Error>(()) /// ``` - pub fn send_quit(&mut self, message: Option<&str>) -> Result<(), Error> { + pub fn quit(&mut self, message: Option<&str>) -> Result<(), Error> { if let Some(message) = message { self.write_command(Command::QUIT(message.to_string()))?; } else { - self.write_command(Command::QUIT( - format!("circe {} (https://crates.io/crates/circe)", env!("CARGO_PKG_VERSION")), - ))?; + self.write_command(Command::QUIT(format!( + "circe {} (https://crates.io/crates/circe)", + env!("CARGO_PKG_VERSION") + )))?; + } + if self.config.ssl { + self.sslstream.as_ref().unwrap().shutdown().unwrap(); + } else { + self.stream.unwrap().shutdown(std::net::Shutdown::Both)?; } - self.stream.shutdown(std::net::Shutdown::Both)?; Ok(()) } @@ -466,7 +693,7 @@ impl Config { /// vec!["#main", "#circe"], /// "192.168.178.100", /// Some("+B"), - /// Some("IRSC"), + /// Some("circe"), /// 6667, /// "circe", /// ); @@ -477,21 +704,22 @@ impl Config { mode: Option<&'static str>, nickname: Option<&'static str>, port: u16, + ssl: bool, username: &str, ) -> Self { // Conversion from &'static str to String let channels_config = channels.iter().map(|channel| channel.to_string()).collect(); let mode_config: Option; - if mode.is_some() { - mode_config = Some(mode.unwrap().to_string()); + if let Some(mode) = mode { + mode_config = Some(mode.to_string()); } else { mode_config = None; } let nickname_config: Option; - if nickname.is_some() { - nickname_config = Some(nickname.unwrap().to_string()); + if let Some(nickname) = nickname { + nickname_config = Some(nickname.to_string()); } else { nickname_config = Some(username.to_string()); } @@ -502,6 +730,7 @@ impl Config { mode: mode_config, nickname: nickname_config, port, + ssl, username: username.into(), } } @@ -512,7 +741,6 @@ impl Config { /// let config = Config::from_toml("config.toml")?; /// # Ok::<(), std::io::Error>(()) /// ``` - /// /// ```toml /// channels = ["#main", "#main2"] /// host = "192.168.178.100" @@ -521,7 +749,6 @@ impl Config { /// port = 6667 /// username = "circe" /// ``` - /// /// Returns an Error if the file cannot be opened or if the TOML is invalid pub fn from_toml>(path: P) -> Result { let mut file = File::open(&path)?; diff --git a/src/main.rs b/src/main.rs index b9195d4..504fbea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,12 +29,12 @@ fn main() -> Result<(), std::io::Error> { } if let Command::PRIVMSG(channel, message) = command { if message.contains("!quit") { - client.send_quit(None)?; + client.quit(None)?; break; } if message.contains("!close") { - client.send_part("#main")?; + client.part("#main")?; } println!("PRIVMSG received: {} {}", channel, message); }