async-circe/src/client/mod.rs

500 lines
15 KiB
Rust

pub mod config;
pub use crate::client::config::Config;
use crate::commands::parser::{CapMode, Command};
use crate::stream::read::Read;
use crate::stream::write::Write;
use tokio::io::{split, BufReader, Error};
use tokio::net::TcpStream;
#[cfg(feature = "tls")]
use std::sync::Arc;
#[cfg(feature = "tls")]
use tokio_rustls::rustls::{self, OwnedTrustAnchor};
#[cfg(feature = "tls")]
use tokio_rustls::TlsConnector;
#[derive(Clone)]
pub struct Client {
config: Config,
tx: Write,
}
impl Client {
/// Creates a new circe client
pub async fn new(config: config::Config) -> (Self, Read) {
let stream = TcpStream::connect(format!("{}:{}", config.host, config.port))
.await
.expect("Failed to connect to the server");
#[cfg(feature = "tls")]
{
let mut root_cert_store = rustls::RootCertStore::empty();
root_cert_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(
|ta| {
OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
)
},
));
let tls_config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
let domain =
rustls::ServerName::try_from(config.host.as_str()).expect("Invalid DNS name");
let connector = TlsConnector::from(Arc::new(tls_config));
let tls_stream = connector
.connect(domain, stream)
.await
.expect("Failed to connect to the server");
let (recv, tx) = split(tls_stream);
let recv = Read::new(BufReader::new(recv));
let tx = Write::new(tx).await;
tracing::info!("New client created");
(Self { config, tx }, recv)
}
#[cfg(not(feature = "tls"))]
{
let (recv, tx) = split(stream);
let recv = Read::new(BufReader::new(recv));
let tx = Write::new(tx).await;
tracing::info!("New client created");
(Self { config, tx }, recv)
}
}
/// Identifys the client on the server
/// # Errors
/// Returns errors from the ``TcpStream``.
pub async fn identify(self, recv: &mut Read) -> Result<(), Error> {
let config = self.config.clone();
self.clone().cap_mode(CapMode::LS).await?;
self.clone().cap_mode(CapMode::END).await?;
self.clone()
.user(
self.config.username.clone(),
"*",
"*",
self.config.username.clone(),
)
.await?;
if let Some(nick) = self.config.nickname.clone() {
self.clone().nick(&nick).await?;
} else {
self.clone().nick(&self.config.username.clone()).await?;
}
loop {
if let Some(command) = recv.read().await? {
match command {
Command::PING(code) => {
self.clone().pong(&code).await?;
}
Command::OTHER(line) => {
if line.contains("001") {
break;
}
}
_ => {}
}
}
}
let user = {
if let Some(nick) = config.nickname {
nick
} else {
config.username
}
};
if let Some(mode) = config.mode {
self.clone().mode(&user, Some(&mode)).await?;
}
for channel in &config.channels {
self.clone().join(channel).await?;
}
Ok(())
}
// TODO: rewrite
/// Request information about the admin of a given server.
/// # Example
/// ```run_fut
/// # use async_circe::*;
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.admin("libera.chat").await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn admin(self, target: &str) -> Result<(), Error> {
self.tx
.clone()
.write(format!("ADMIN {}\r\n", target))
.await?;
Ok(())
}
/// Set the status of the client.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.away("afk").await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn away(self, message: &str) -> Result<(), Error> {
self.tx
.clone()
.write(format!("AWAY {}\r\n", message))
.await?;
Ok(())
}
#[doc(hidden)]
pub async fn cap_mode(self, mode: CapMode) -> Result<(), Error> {
let cap_mode = match mode {
CapMode::LS => "CAP LS 302\r\n",
CapMode::END => "CAP END\r\n",
};
self.tx.clone().write(cap_mode.to_string()).await?;
Ok(())
}
/// Invite someone to a channel.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.invite("liblemonirc", "#async-circe").await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn invite(self, username: &str, channel: &str) -> Result<(), Error> {
self.tx
.clone()
.write(format!("INVITE {} {}\r\n", username, channel))
.await?;
Ok(())
}
/// Join a channel.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.join("#chaos").await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn join(self, channel: &str) -> Result<(), Error> {
self.tx
.clone()
.write(format!("JOIN {}\r\n", channel))
.await?;
Ok(())
}
/// List available channels on an IRC, or users in a channel.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.list(None, None).await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn list(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.tx.clone().write(formatted).await?;
Ok(())
}
/// Set the mode for a user.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.mode("test", Some("+B")).await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn mode(self, target: &str, mode: Option<&str>) -> Result<(), Error> {
if let Some(mode) = mode {
self.tx
.clone()
.write(format!("MODE {} {}\r\n", target, mode,))
.await?;
} else {
self.tx
.clone()
.write(format!("MODE {}\r\n", target))
.await?;
}
Ok(())
}
/// Get all the people online in channels.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.names("#chaos,#async-circe", None).await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn names(self, channel: &str, server: Option<&str>) -> Result<(), Error> {
if let Some(server) = server {
self.tx
.clone()
.write(format!("NAMES {} {}\r\n", channel, server))
.await?;
} else {
self.tx
.clone()
.write(format!("NAMES {}\r\n", channel))
.await?;
}
Ok(())
}
/// Change your nickname on a server.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.nick("Not async-circe").await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn nick(self, nickname: &str) -> Result<(), Error> {
self.tx
.clone()
.write(format!("NICK {}\r\n", nickname))
.await?;
Ok(())
}
/// Authentificate as an operator on a server.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.oper("username", "password").await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn oper(self, username: &str, password: &str) -> Result<(), Error> {
self.tx
.clone()
.write(format!("OPER {} {}\r\n", username, password))
.await?;
Ok(())
}
/// Leave a channel.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.part("#chaos").await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn part(self, channel: &str) -> Result<(), Error> {
self.tx
.clone()
.write(format!("PART {}\r\n", channel))
.await?;
Ok(())
}
#[doc(hidden)]
pub async fn pass(self, password: &str) -> Result<(), Error> {
self.tx
.clone()
.write(format!("PASS {}\r\n", password))
.await?;
Ok(())
}
/// Tests the presence of a connection to a server.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.ping("libera.chat", None).await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn ping(self, server1: &str, server2: Option<&str>) -> Result<(), Error> {
if let Some(server2) = server2 {
self.tx
.clone()
.write(format!("PING {} {}\r\n", server1, server2))
.await?;
} else {
self.tx
.clone()
.write(format!("PING {}\r\n", server1))
.await?;
}
// TODO look if we actually get a PONG back
Ok(())
}
#[doc(hidden)]
pub async fn pong(self, ping: &str) -> Result<(), Error> {
self.tx.clone().write(format!("PONG {}\r\n", ping)).await?;
Ok(())
}
/// Send a message to a channel.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.privmsg("#chaos", "Hello").await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn privmsg(self, channel: &str, message: &str) -> Result<(), Error> {
self.tx
.clone()
.write(format!("PRIVMSG {} :{}\r\n", channel, message))
.await?;
Ok(())
}
/// Leave the IRC server you are connected to.
/// # Example
/// ```run_fut
/// # use async_circe::*;
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.quit(None).await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn quit(self, recv: Read, message: Option<&str>) -> Result<(), Error> {
if let Some(message) = message {
self.tx
.clone()
.write(format!("QUIT :{}\r\n", message))
.await?;
} else {
self.tx
.clone()
.write(format!(
"QUIT :async-circe {} (https://crates.io/crates/async-circe)\r\n",
env!("CARGO_PKG_VERSION")
))
.await?;
}
Ok(())
}
/// Get the topic of a channel.
/// # Example
/// ```run_fut
/// # use async_circe::*;
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.topic("#chaos", None).await?;
/// # Ok(())
/// ```
/// Set the topic of a channel.
/// # Example
/// ```run_fut
/// # let config = Default::default();
/// # let mut client = Client::new(config).await?;
/// # client.identify().await?;
/// client.topic("#chaos", Some("CHAOS")).await?;
/// # Ok(())
/// ```
/// # Errors
/// Returns IO errors from the ``TcpStream``.
pub async fn topic(self, channel: &str, topic: Option<&str>) -> Result<(), Error> {
if let Some(topic) = topic {
self.tx
.clone()
.write(format!("TOPIC {} :{}\r\n", channel, topic))
.await?;
} else {
self.tx
.clone()
.write(format!("TOPIC {}\r\n", channel))
.await?;
}
Ok(())
}
#[doc(hidden)]
pub async fn user(
self,
username: String,
s1: &str,
s2: &str,
realname: String,
) -> Result<(), Error> {
self.tx
.clone()
.write(format!("USER {} {} {} :{}\r\n", username, s1, s2, realname))
.await?;
Ok(())
}
}