764 lines
24 KiB
Rust
764 lines
24 KiB
Rust
//! A simple IRC crate written in rust
|
|
//! ```no_run
|
|
//! use circe::*;
|
|
//! fn main() -> Result<(), std::io::Error> {
|
|
//! let config = Config::from_toml("config.toml")?;
|
|
//! let mut client = Client::new(config)?;
|
|
//! client.identify()?;
|
|
//!
|
|
//! loop {
|
|
//! if let Ok(ref command) = client.read() {
|
|
//! if let Command::OTHER(line) = command {
|
|
//! print!("{}", line);
|
|
//! }
|
|
//! if let Command::PRIVMSG(channel, message) = command {
|
|
//! println!("PRIVMSG received: {} {}", channel, message);
|
|
//! }
|
|
//! }
|
|
//! # break;
|
|
//! }
|
|
//! # Ok(())
|
|
//! }
|
|
|
|
#![warn(missing_docs)]
|
|
use openssl::ssl::{SslConnector, SslMethod};
|
|
use std::borrow::Cow;
|
|
use std::fs::File;
|
|
use std::io::{Error, Read, Write};
|
|
use std::net::TcpStream;
|
|
use std::path::Path;
|
|
|
|
use serde_derive::Deserialize;
|
|
|
|
/// An IRC client
|
|
pub struct Client {
|
|
config: Config,
|
|
stream: Option<TcpStream>,
|
|
sslstream: Option<openssl::ssl::SslStream<TcpStream>>,
|
|
}
|
|
|
|
/// Config for the IRC client
|
|
#[derive(Clone, Deserialize, Default)]
|
|
pub struct Config {
|
|
channels: Vec<String>,
|
|
host: String,
|
|
mode: Option<String>,
|
|
nickname: Option<String>,
|
|
port: u16,
|
|
ssl: bool,
|
|
username: String,
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
#[derive(Debug)]
|
|
pub enum CapMode {
|
|
LS,
|
|
END,
|
|
}
|
|
|
|
/// IRC commands
|
|
#[derive(Debug)]
|
|
pub enum Command {
|
|
// TODO:
|
|
// SERVICE <nickname> <reserved> <distribution> <type> <reserved> <info>
|
|
// SQUIT <server> <comment>
|
|
//
|
|
/// 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<String>,
|
|
// Server
|
|
Option<String>,
|
|
),
|
|
/// 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<String>,
|
|
),
|
|
/// 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<String>,
|
|
),
|
|
#[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<String>,
|
|
),
|
|
#[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::<Vec<&str>>()[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
|
|
/// # use circe::*;
|
|
/// # let config = Config::from_toml("config.toml")?;
|
|
/// let mut client = Client::new(config)?;
|
|
/// # Ok::<(), std::io::Error>(())
|
|
/// ```
|
|
///
|
|
/// Returns error if the client could not connect to the host.
|
|
pub fn new(config: Config) -> Result<Self, Error> {
|
|
let stream = TcpStream::connect(format!("{}:{}", config.host, config.port))?;
|
|
let sslstream: openssl::ssl::SslStream<TcpStream>;
|
|
|
|
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 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>(())
|
|
/// ```
|
|
/// 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.config.username.clone(),
|
|
"*".into(),
|
|
"*".into(),
|
|
self.config.username.clone(),
|
|
))?;
|
|
|
|
if let Some(nick) = self.config.nickname.clone() {
|
|
self.write_command(Command::NICK(nick.to_string()))?;
|
|
} else {
|
|
self.write_command(Command::NICK(self.config.username.clone()))?;
|
|
}
|
|
|
|
loop {
|
|
if let Ok(ref command) = self.read() {
|
|
match command {
|
|
Command::PING(code) => self.write_command(Command::PONG(code.to_string()))?,
|
|
Command::OTHER(line) => {
|
|
if line.contains("001") {
|
|
break;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
let config = self.config.clone();
|
|
self.write_command(Command::MODE(config.username, config.mode))?;
|
|
for channel in config.channels.iter() {
|
|
self.write_command(Command::JOIN(channel.to_string()))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn read_string(&mut self) -> Option<String> {
|
|
let mut buffer = [0u8; 512];
|
|
|
|
if self.config.ssl {
|
|
match self.sslstream.as_mut().unwrap().read(&mut buffer) {
|
|
Ok(_) => {}
|
|
Err(_) => return None,
|
|
};
|
|
} else {
|
|
match self.stream.as_mut().unwrap().read(&mut buffer) {
|
|
Ok(_) => {}
|
|
Err(_) => return None,
|
|
};
|
|
}
|
|
|
|
Some(String::from_utf8_lossy(&buffer).into())
|
|
}
|
|
|
|
/// Read data coming from the IRC as a [`Command`].
|
|
/// ```no_run
|
|
/// # use circe::*;
|
|
/// # fn main() -> Result<(), std::io::Error> {
|
|
/// # let config = Config::from_toml("config.toml")?;
|
|
/// # let mut client = Client::new(config)?;
|
|
/// if let Ok(ref command) = client.read() {
|
|
/// if let Command::OTHER(line) = command {
|
|
/// print!("{}", line);
|
|
/// }
|
|
/// }
|
|
/// # Ok::<(), std::io::Error>(())
|
|
/// # }
|
|
/// ```
|
|
/// 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<Command, ()> {
|
|
if let Some(string) = self.read_string() {
|
|
return Ok(Command::from_str(&string));
|
|
}
|
|
|
|
Err(())
|
|
}
|
|
|
|
fn write(&mut self, data: &str) -> Result<(), Error> {
|
|
let formatted = {
|
|
let new = format!("{}\r\n", data);
|
|
Cow::Owned(new) as Cow<str>
|
|
};
|
|
if self.config.ssl {
|
|
self.sslstream
|
|
.as_mut()
|
|
.unwrap()
|
|
.write(formatted.as_bytes())?;
|
|
} else {
|
|
self.stream.as_mut().unwrap().write(formatted.as_bytes())?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Send a [`Command`] to the IRC.<br>
|
|
/// 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>(())
|
|
/// ```
|
|
/// 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<str>
|
|
}
|
|
AWAY(message) => {
|
|
let formatted = format!("AWAY {}", message);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
CAP(mode) => {
|
|
use CapMode::*;
|
|
Cow::Borrowed(match mode {
|
|
LS => "CAP LS 302",
|
|
END => "CAP END",
|
|
}) as Cow<str>
|
|
}
|
|
INVITE(username, channel) => {
|
|
let formatted = format!("INVITE {} {}", username, channel);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
JOIN(channel) => {
|
|
let formatted = format!("JOIN {}", channel);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
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<str>
|
|
}
|
|
NAMES(channel, server) => {
|
|
let formatted = {
|
|
if let Some(server) = server {
|
|
format!("NAMES {} {}", channel, server)
|
|
} else {
|
|
format!("NAMES {}", channel)
|
|
}
|
|
};
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
NICK(nickname) => {
|
|
let formatted = format!("NICK {}", nickname);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
MODE(target, mode) => {
|
|
let formatted = {
|
|
if let Some(mode) = mode {
|
|
format!("MODE {} {}", target, mode)
|
|
} else {
|
|
format!("MODE {}", target)
|
|
}
|
|
};
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
OPER(nick, password) => {
|
|
let formatted = format!("OPER {} {}", nick, password);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
OTHER(_) => {
|
|
return Err(Error::new(
|
|
std::io::ErrorKind::Other,
|
|
"Cannot write commands of type OTHER",
|
|
));
|
|
}
|
|
PART(target) => {
|
|
let formatted = format!("PART {}", target);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
PASS(password) => {
|
|
let formatted = format!("PASS {}", password);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
PING(target) => {
|
|
let formatted = format!("PING {}", target);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
PONG(code) => {
|
|
let formatted = format!("PONG {}", code);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
PRIVMSG(target, message) => {
|
|
let formatted = format!("PRIVMSG {} {}", target, message);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
QUIT(message) => {
|
|
let formatted = format!("QUIT :{}", message);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
TOPIC(channel, topic) => {
|
|
let formatted = {
|
|
if let Some(topic) = topic {
|
|
format!("TOPIC {} :{}", channel, topic)
|
|
} else {
|
|
format!("TOPIC {}", channel)
|
|
}
|
|
};
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
USER(username, s1, s2, realname) => {
|
|
let formatted = format!("USER {} {} {} :{}", username, s1, s2, realname);
|
|
Cow::Owned(formatted) as Cow<str>
|
|
}
|
|
};
|
|
|
|
self.write(&computed)?;
|
|
Ok(())
|
|
}
|
|
|
|
// 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.
|
|
/// ```no_run
|
|
/// # use circe::*;
|
|
/// # let mut client = Client::new(Config::from_toml("config.toml")?)?;
|
|
/// client.privmsg("#main", "Hello")?;
|
|
/// # 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()))?;
|
|
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.
|
|
/// ```no_run
|
|
/// # use circe::*;
|
|
/// # let mut client = Client::new(Config::from_toml("config.toml")?)?;
|
|
/// client.join("#main")?;
|
|
/// # Ok::<(), std::io::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.
|
|
/// ```no_run
|
|
/// # use circe::*;
|
|
/// # let mut client = Client::new(Config::from_toml("config.toml")?)?;
|
|
/// client.mode("test", Some("+B"))?;
|
|
/// # Ok::<(), std::io::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 {
|
|
self.write_command(Command::MODE(target.to_string(), None))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for leaving channels.
|
|
/// ```no_run
|
|
/// # use circe::*;
|
|
/// # let mut client = Client::new(Config::from_toml("config.toml")?)?;
|
|
/// client.part("#main")?;
|
|
/// # Ok::<(), std::io::Error>(())
|
|
/// ```
|
|
pub fn part(&mut self, target: &str) -> Result<(), Error> {
|
|
self.write_command(Command::PART(target.to_string()))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// 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.topic("#main", Some("main channel"))?;
|
|
/// # Ok::<(), std::io::Error>(())
|
|
/// ```
|
|
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::*;
|
|
/// # let mut client = Client::new(Config::from_toml("config.toml")?)?;
|
|
/// client.quit(None)?;
|
|
/// # Ok::<(), std::io::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")
|
|
)))?;
|
|
}
|
|
if self.config.ssl {
|
|
self.sslstream.as_mut().unwrap().shutdown().unwrap();
|
|
} else {
|
|
self.stream.as_mut().unwrap().shutdown(std::net::Shutdown::Both)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Config {
|
|
/// Create a new config for the client
|
|
///
|
|
/// ```rust
|
|
/// # use circe::*;
|
|
/// let config = Config::new(
|
|
/// vec!["#main", "#circe"],
|
|
/// "192.168.178.100",
|
|
/// Some("+B"),
|
|
/// Some("circe"),
|
|
/// 6667,
|
|
/// "circe",
|
|
/// );
|
|
/// ```
|
|
pub fn new(
|
|
channels: Vec<&'static str>,
|
|
host: &str,
|
|
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<String>;
|
|
if let Some(mode) = mode {
|
|
mode_config = Some(mode.to_string());
|
|
} else {
|
|
mode_config = None;
|
|
}
|
|
|
|
let nickname_config: Option<String>;
|
|
if let Some(nickname) = nickname {
|
|
nickname_config = Some(nickname.to_string());
|
|
} else {
|
|
nickname_config = Some(username.to_string());
|
|
}
|
|
|
|
Self {
|
|
channels: channels_config,
|
|
host: host.into(),
|
|
mode: mode_config,
|
|
nickname: nickname_config,
|
|
port,
|
|
ssl,
|
|
username: username.into(),
|
|
}
|
|
}
|
|
|
|
/// Create a config from a toml file
|
|
/// ```no_run
|
|
/// # use circe::*;
|
|
/// let config = Config::from_toml("config.toml")?;
|
|
/// # Ok::<(), std::io::Error>(())
|
|
/// ```
|
|
/// ```toml
|
|
/// channels = ["#main", "#main2"]
|
|
/// host = "192.168.178.100"
|
|
/// mode = "+B"
|
|
/// nickname = "circe"
|
|
/// port = 6667
|
|
/// username = "circe"
|
|
/// ```
|
|
/// Returns an Error if the file cannot be opened or if the TOML is invalid
|
|
pub fn from_toml<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
|
let mut file = File::open(&path)?;
|
|
let mut data = String::new();
|
|
file.read_to_string(&mut data)?;
|
|
|
|
toml::from_str(&data).map_err(|e| {
|
|
use std::io::ErrorKind;
|
|
Error::new(ErrorKind::Other, format!("Invalid TOML: {}", e))
|
|
})
|
|
}
|
|
}
|