756 lines
24 KiB
Rust
756 lines
24 KiB
Rust
//! A simple IRC crate written in rust
|
|
//! ```no_run
|
|
//! use async_circe::{commands::Command, Client, Config};
|
|
//! fn main() -> Result<(), tokio::io::Error> {
|
|
//! let config = Default::default();
|
|
//! let mut client = Client::new(config).await.unwrap();
|
|
//!
|
|
//! client.identify().await.unwrap();
|
|
//!
|
|
//! loop {
|
|
//! if let Ok(ref command) = client.read().await {
|
|
//! if let Command::PRIVMSG(nick, channel, message) = command {
|
|
//! println!("{} in {}: {}", nick, channel, message);
|
|
//! }
|
|
//! }
|
|
//! # break;
|
|
//! }
|
|
//! # Ok(())
|
|
//! }
|
|
|
|
#![warn(missing_docs)]
|
|
#![allow(clippy::too_many_lines)]
|
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
|
|
|
use tokio::io::BufReader;
|
|
use tokio::io::Error;
|
|
use tokio::io::{AsyncWriteExt, AsyncBufReadExt};
|
|
use tokio::net::TcpStream;
|
|
|
|
#[cfg(feature = "tls")]
|
|
use native_tls::TlsConnector;
|
|
|
|
#[cfg(feature = "toml_support")]
|
|
use serde_derive::Deserialize;
|
|
#[cfg(feature = "toml_support")]
|
|
use std::fs::File;
|
|
#[cfg(feature = "toml_support")]
|
|
use std::path::Path;
|
|
|
|
/// IRC comamnds
|
|
pub mod commands;
|
|
|
|
/// An IRC client
|
|
pub struct Client {
|
|
config: Config,
|
|
#[cfg(not(feature = "tls"))]
|
|
buf_reader: BufReader<TcpStream>,
|
|
#[cfg(feature = "tls")]
|
|
buf_reader: BufReader<tokio_native_tls::TlsStream<tokio::net::TcpStream>>,
|
|
}
|
|
|
|
/// Config for the IRC client
|
|
#[derive(Clone, Debug, Default)]
|
|
#[cfg_attr(feature = "toml_support", derive(Deserialize))]
|
|
pub struct Config {
|
|
channels: Vec<String>,
|
|
host: String,
|
|
mode: Option<String>,
|
|
nickname: Option<String>,
|
|
port: u16,
|
|
username: String,
|
|
}
|
|
|
|
/// Custom Error for the `read` function
|
|
#[derive(Debug)]
|
|
pub struct NoNewLines;
|
|
|
|
impl std::fmt::Display for NoNewLines {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "Now new lines from the stream.")
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for NoNewLines {}
|
|
|
|
impl Client {
|
|
/// Creates a new client with a given [`Config`].
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let config = Default::default();
|
|
/// let mut client = Client::new(config).await.unwrap()?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not connect to the host.
|
|
/// # Panics
|
|
/// Panics if the client can't connect to the given host.
|
|
pub async fn new(config: Config) -> Result<Self, Error> {
|
|
tracing::debug!("Creating TcpStream");
|
|
let stream = TcpStream::connect(format!("{}:{}", config.host, config.port))
|
|
.await
|
|
.unwrap();
|
|
tracing::debug!("New TcpStream created");
|
|
|
|
|
|
#[cfg(feature = "tls")]
|
|
{
|
|
let connector = TlsConnector::builder().build().unwrap();
|
|
let async_connector = tokio_native_tls::TlsConnector::from(connector);
|
|
|
|
let sslstream = async_connector
|
|
.connect(config.host.as_str(), stream)
|
|
.await
|
|
.unwrap();
|
|
|
|
let buf_reader = BufReader::new(sslstream);
|
|
|
|
return Ok(Self {
|
|
config,
|
|
buf_reader,
|
|
});
|
|
};
|
|
|
|
#[cfg(not(feature = "tls"))]
|
|
{
|
|
let buf_reader = BufReader::new(stream);
|
|
return Ok(Self { config, buf_reader });
|
|
};
|
|
}
|
|
|
|
/// Identify user and joins the in the [`Config`] specified channels.
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default())?;
|
|
/// client.identify()?.await.unwrap();
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn identify(&mut self) -> Result<(), Error> {
|
|
self.cap_mode(commands::CapMode::LS).await?;
|
|
self.cap_mode(commands::CapMode::END).await?;
|
|
|
|
self.user(
|
|
self.config.username.clone(),
|
|
"*",
|
|
"*",
|
|
self.config.username.clone(),
|
|
)
|
|
.await?;
|
|
|
|
if let Some(nick) = self.config.nickname.clone() {
|
|
self.nick(&nick).await?;
|
|
} else {
|
|
self.nick(&self.config.username.clone()).await?;
|
|
}
|
|
|
|
loop {
|
|
if let Ok(ref command) = self.read().await {
|
|
if let Some(command) = command {
|
|
match command {
|
|
commands::Command::PING(code) => {
|
|
self.pong(code).await?;
|
|
}
|
|
commands::Command::OTHER(line) => {
|
|
tracing::trace!("{}", line);
|
|
if line.contains("001") {
|
|
break;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let config = self.config.clone();
|
|
let user = {
|
|
if let Some(nick) = config.nickname {
|
|
nick
|
|
} else {
|
|
config.username
|
|
}
|
|
};
|
|
|
|
if let Some(mode) = config.mode {
|
|
self.mode(&user, Some(&mode)).await?;
|
|
}
|
|
|
|
for channel in &config.channels {
|
|
self.join(channel).await?;
|
|
}
|
|
|
|
tracing::info!("async-circe has identified");
|
|
Ok(())
|
|
}
|
|
|
|
async fn read_string(&mut self) -> Result<Option<String>, tokio::io::Error> {
|
|
let mut buffer = String::new();
|
|
|
|
let num_bytes = match self.buf_reader.read_line(&mut buffer).await {
|
|
Ok(b) => b,
|
|
Err(e) => {
|
|
tracing::error!("{}", e);
|
|
return Err(e);
|
|
}
|
|
};
|
|
|
|
if num_bytes == 0 {
|
|
return Ok(None);
|
|
}
|
|
|
|
// Slice to the number of bytes that were actually read
|
|
//let res = String::from_utf8_lossy(&buffer[..num_bytes]);
|
|
|
|
Ok(Some(buffer.into()))
|
|
}
|
|
|
|
/// Read data coming from the IRC as a [`commands::Command`].
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # fn main() -> Result<(), tokio::io::Error> {
|
|
/// # let config = Default::default();
|
|
/// # let mut client = Client::new(config)?;
|
|
/// if let Ok(ref command) = client.read().await {
|
|
/// if let Command::OTHER(line) = command {
|
|
/// print!("{}", line);
|
|
/// }
|
|
/// }
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// # }
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if there are no new messages. This should not be taken as an actual error, because nothing went wrong.
|
|
pub async fn read(&mut self) -> Result<Option<commands::Command>, tokio::io::Error> {
|
|
if let Some(string) = self.read_string().await? {
|
|
let command = commands::Command::command_from_str(&string).await;
|
|
|
|
if let commands::Command::PING(command) = command {
|
|
if let Err(_e) = self.pong(&command).await {
|
|
return Ok(None);
|
|
}
|
|
return Ok(Some(commands::Command::PONG("".to_string())));
|
|
}
|
|
|
|
return Ok(Some(command));
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
|
|
async fn write(&mut self, data: String) -> Result<(), Error> {
|
|
tracing::debug!("{:?}", data);
|
|
self.buf_reader.write_all(data.as_bytes()).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Send a [`commands::Command`] to the IRC.<br>
|
|
/// Not reccomended to use, use the helper functions instead.
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default())?;
|
|
/// client.write_command(Command::PRIVMSG("".to_string(), "#main".to_string(), "Hello".to_string())).await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
#[deprecated]
|
|
pub async fn write_command(&mut self, command: commands::Command) -> Result<(), Error> {
|
|
use commands::Command::{
|
|
ADMIN, AWAY, CAP, INVITE, JOIN, LIST, MODE, NAMES, NICK, OPER, OTHER, PART, PASS, PING,
|
|
PONG, PRIVMSG, QUIT, TOPIC, USER,
|
|
};
|
|
let computed = match command {
|
|
ADMIN(target) => {
|
|
format!("ADMIN {}\r\n", target)
|
|
}
|
|
AWAY(message) => {
|
|
format!("AWAY {}\r\n", message)
|
|
}
|
|
CAP(mode) => {
|
|
use commands::CapMode::{END, LS};
|
|
let formatted = {
|
|
match mode {
|
|
LS => "CAP LS 302\r\n",
|
|
END => "CAP END\r\n",
|
|
}
|
|
};
|
|
formatted.to_string()
|
|
}
|
|
INVITE(username, channel) => {
|
|
format!("INVITE {} {}\r\n", username, channel)
|
|
}
|
|
JOIN(channel) => {
|
|
format!("JOIN {}\r\n", channel)
|
|
}
|
|
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());
|
|
}
|
|
formatted.push_str("\r\n");
|
|
formatted
|
|
}
|
|
NAMES(channel, server) => {
|
|
if let Some(server) = server {
|
|
format!("NAMES {} {}\r\n", channel, server)
|
|
} else {
|
|
format!("NAMES {}\r\n", channel)
|
|
}
|
|
}
|
|
NICK(nickname) => {
|
|
format!("NICK {}\r\n", nickname)
|
|
}
|
|
MODE(target, mode) => {
|
|
if let Some(mode) = mode {
|
|
format!("MODE {} {}\r\n", target, mode)
|
|
} else {
|
|
format!("MODE {}\r\n", target)
|
|
}
|
|
}
|
|
OPER(nick, password) => {
|
|
format!("OPER {} {}\r\n", nick, password)
|
|
}
|
|
OTHER(_) => {
|
|
return Err(Error::new(
|
|
tokio::io::ErrorKind::Other,
|
|
"Cannot write commands of type OTHER",
|
|
));
|
|
}
|
|
PART(target) => {
|
|
format!("PART {}\r\n", target)
|
|
}
|
|
PASS(password) => {
|
|
format!("PASS {}\r\n", password)
|
|
}
|
|
PING(target) => {
|
|
format!("PING {}\r\n", target)
|
|
}
|
|
PONG(code) => {
|
|
format!("PONG {}\r\n", code)
|
|
}
|
|
PRIVMSG(_, target, message) => {
|
|
format!("PRIVMSG {} {}\r\n", target, message)
|
|
}
|
|
QUIT(message) => {
|
|
format!("QUIT :{}\r\n", message)
|
|
}
|
|
TOPIC(channel, topic) => {
|
|
if let Some(topic) = topic {
|
|
format!("TOPIC {} :{}\r\n", channel, topic)
|
|
} else {
|
|
format!("TOPIC {}\r\n", channel)
|
|
}
|
|
}
|
|
USER(username, s1, s2, realname) => {
|
|
format!("USER {} {} {} :{}\r\n", username, s1, s2, realname)
|
|
}
|
|
};
|
|
|
|
self.write(computed).await?;
|
|
Ok(())
|
|
}
|
|
|
|
// Helper commands
|
|
|
|
/// Helper function for requesting information about the ADMIN of an IRC server
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.admin("192.168.178.100").await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn admin(&mut self, target: &str) -> Result<(), Error> {
|
|
self.write(format!("ADMIN {}\r\n", target)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for setting the users status to AWAY
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.away("AFK").await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn away(&mut self, message: &str) -> Result<(), Error> {
|
|
self.write(format!("AWAY {}\r\n", message)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub async fn cap_mode(&mut self, mode: commands::CapMode) -> Result<(), Error> {
|
|
let cap_mode = match mode {
|
|
commands::CapMode::LS => "CAP LS 302\r\n",
|
|
commands::CapMode::END => "CAP END\r\n",
|
|
};
|
|
self.write(cap_mode.to_string()).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function to INVITE people to a channels
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.invite("liblemonirc", "#async_circe").await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn invite(&mut self, username: &str, channel: &str) -> Result<(), Error> {
|
|
self.write(format!("INVITE {} {}\r\n", username, channel))
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for sending JOINs.
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.join("#main").await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn join(&mut self, channel: &str) -> Result<(), Error> {
|
|
self.write(format!("JOIN {}\r\n", channel)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for ``LISTing`` channels and modes
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.list(None, None).await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn list(&mut self, channel: Option<&str>, server: Option<&str>) -> Result<(), Error> {
|
|
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());
|
|
}
|
|
formatted.push_str("\r\n");
|
|
self.write(formatted).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for sending MODEs.
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.mode("test", Some("+B")).await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn mode(&mut self, target: &str, mode: Option<&str>) -> Result<(), Error> {
|
|
if let Some(mode) = mode {
|
|
self.write(format!("MODE {} {}\r\n", target, mode,)).await?;
|
|
} else {
|
|
self.write(format!("MODE {}\r\n", target)).await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for getting all nicknames in a channel
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.names("#main,#async_circe", None).await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn names(&mut self, channel: &str, server: Option<&str>) -> Result<(), Error> {
|
|
if let Some(server) = server {
|
|
self.write(format!("NAMES {} {}\r\n", channel, server))
|
|
.await?;
|
|
} else {
|
|
self.write(format!("NAMES {}\r\n", channel)).await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for changeing your nick
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.nick("Not async-circe").await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn nick(&mut self, nickname: &str) -> Result<(), Error> {
|
|
self.write(format!("NICK {}\r\n", nickname)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function to try to register as a channel operator
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.oper("username", "password").await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn oper(&mut self, username: &str, password: &str) -> Result<(), Error> {
|
|
self.write(format!("OPER {} {}\r\n", username, password))
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for leaving channels.
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.part("#main").await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn part(&mut self, channel: &str) -> Result<(), Error> {
|
|
self.write(format!("PART {}\r\n", channel)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub async fn pass(&mut self, password: &str) -> Result<(), Error> {
|
|
self.write(format!("PASS {}\r\n", password)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Tests the presence of a connection.
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.ping("libera.chat", None).await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn ping(&mut self, server1: &str, server2: Option<&str>) -> Result<(), Error> {
|
|
if let Some(server2) = server2 {
|
|
self.write(format!("PING {} {}\r\n", server1, server2))
|
|
.await?;
|
|
} else {
|
|
self.write(format!("PING {}\r\n", server1)).await?;
|
|
}
|
|
// TODO look if we actually get a PONG back
|
|
Ok(())
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub async fn pong(&mut self, ping: &str) -> Result<(), Error> {
|
|
self.write(format!("PONG {}\r\n", ping)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for sending PRIVMSGs.
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.privmsg("#main", "Hello").await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn privmsg(&mut self, channel: &str, message: &str) -> Result<(), Error> {
|
|
self.write(format!("PRIVMSG {} {}\r\n", channel, message))
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for leaving the IRC server and shutting down the TCP stream afterwards.
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.quit(None).await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn quit(&mut self, message: Option<&str>) -> Result<(), Error> {
|
|
if let Some(message) = message {
|
|
self.write(format!("QUIT :{}\r\n", message)).await?;
|
|
} else {
|
|
self.write(format!(
|
|
"QUIT :async-circe {} (https://crates.io/crates/async-circe)\r\n",
|
|
env!("CARGO_PKG_VERSION")
|
|
))
|
|
.await?;
|
|
}
|
|
tracing::info!("async-circe shutting down");
|
|
self.buf_reader.shutdown().await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Helper function for setting or getting the topic of a channel
|
|
/// ```no_run
|
|
/// # use async_circe::*;
|
|
/// # let mut client = Client::new(Default::default()).await?;
|
|
/// client.topic("#main", Some("main channel")).await?;
|
|
/// # Ok::<(), tokio::io::Error>(())
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns error if the client could not write to the stream.
|
|
pub async fn topic(&mut self, channel: &str, topic: Option<&str>) -> Result<(), Error> {
|
|
if let Some(topic) = topic {
|
|
self.write(format!("TOPIC {} :{}\r\n", channel, topic))
|
|
.await?;
|
|
} else {
|
|
self.write(format!("TOPIC {}\r\n", channel)).await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub async fn user(
|
|
&mut self,
|
|
username: String,
|
|
s1: &str,
|
|
s2: &str,
|
|
realname: String,
|
|
) -> Result<(), Error> {
|
|
self.write(format!("USER {} {} {} :{}\r\n", username, s1, s2, realname))
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Config {
|
|
/// Create a new config for the client
|
|
///
|
|
/// ```rust
|
|
/// # use async_circe::*;
|
|
/// let config = Config::new(
|
|
/// &["#main", "#async_circe"],
|
|
/// "karx.xyz",
|
|
/// Some("+B"),
|
|
/// Some("async-circe"),
|
|
/// 6697,
|
|
/// "circe",
|
|
/// );
|
|
/// ```
|
|
pub fn new(
|
|
channels: &[&'static str],
|
|
host: &str,
|
|
mode: Option<&'static str>,
|
|
nickname: Option<&'static str>,
|
|
port: u16,
|
|
username: &str,
|
|
) -> Self {
|
|
tracing::info!("New async-circe client config");
|
|
// 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());
|
|
}
|
|
|
|
let config = Self {
|
|
channels: channels_config,
|
|
host: host.into(),
|
|
mode: mode_config,
|
|
nickname: nickname_config,
|
|
port,
|
|
username: username.into(),
|
|
};
|
|
|
|
tracing::debug!("async-circe config: {:?}", config);
|
|
config
|
|
}
|
|
|
|
/// Allows for configuring async-circe at runtime
|
|
pub fn runtime_config(
|
|
channels: Vec<String>,
|
|
host: String,
|
|
mode: Option<String>,
|
|
nickname: Option<String>,
|
|
port: u16,
|
|
username: String,
|
|
) -> Self {
|
|
tracing::info!("New async-circe client config");
|
|
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());
|
|
}
|
|
|
|
let config = Self {
|
|
channels,
|
|
host,
|
|
mode: mode_config,
|
|
nickname: nickname_config,
|
|
port,
|
|
username,
|
|
};
|
|
|
|
tracing::debug!("async-circe config: {:?}", config);
|
|
config
|
|
}
|
|
|
|
/// Create a config from a toml file
|
|
/// ```toml
|
|
/// channels = ["#main", "#main2"]
|
|
/// host = "192.168.178.100"
|
|
/// mode = "+B"
|
|
/// nickname = "async-circe"
|
|
/// port = 6667
|
|
/// username = "circe"
|
|
/// ```
|
|
/// # Errors
|
|
/// Returns an Error if the file cannot be opened or if the TOML is invalid
|
|
///
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "toml_support")))]
|
|
#[cfg(feature = "toml_support")]
|
|
pub fn from_toml<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
|
tracing::info!("New async-circe client config");
|
|
use std::io::Read;
|
|
let mut file = File::open(&path)?;
|
|
let mut data = String::new();
|
|
file.read_to_string(&mut data)?;
|
|
|
|
let config = toml::from_str(&data).map_err(|e| {
|
|
use tokio::io::ErrorKind;
|
|
Error::new(ErrorKind::Other, format!("Invalid TOML: {}", e))
|
|
});
|
|
tracing::debug!("async-circe config: {:?}", config);
|
|
config
|
|
}
|
|
}
|