feat: basic messaging implementation
This commit is contained in:
parent
ad75f01112
commit
2ae02b4a22
13 changed files with 565 additions and 70 deletions
19
Cargo.lock
generated
19
Cargo.lock
generated
|
|
@ -73,6 +73,17 @@ version = "1.0.100"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
|
|
@ -186,7 +197,9 @@ name = "irs"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"clap",
|
||||
"once_cell",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
|
@ -247,6 +260,12 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
|
|
|
|||
|
|
@ -5,5 +5,7 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
async-trait = "0.1.89"
|
||||
clap = { version = "4.5.48", features = ["derive"] }
|
||||
once_cell = "1.21.3"
|
||||
tokio = { version = "1.47.1", features = ["full"] }
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
commands::{IrcAction, IrcHandler},
|
||||
sender::IrcResponse,
|
||||
user::User,
|
||||
};
|
||||
|
||||
pub struct Cap;
|
||||
|
||||
#[async_trait]
|
||||
impl IrcHandler for Cap {
|
||||
fn handle(&self, _arguments: Vec<String>) -> super::IrcAction {
|
||||
// TODO: parse the args, etc
|
||||
IrcAction::SendText(IrcResponse {
|
||||
command: "CAP".into(),
|
||||
receiver: "*".into(),
|
||||
arguments: Some("LS".into()),
|
||||
message: "TODO".into(),
|
||||
})
|
||||
async fn handle(
|
||||
&self,
|
||||
arguments: Vec<String>,
|
||||
_authenticated: bool,
|
||||
_user_state: &mut User,
|
||||
) -> super::IrcAction {
|
||||
IrcAction::DoNothing
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,54 @@
|
|||
#![allow(dead_code)]
|
||||
use std::{collections::HashMap, io::BufWriter, net::TcpStream};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use async_trait::async_trait;
|
||||
use tokio::{io::BufWriter, net::TcpStream};
|
||||
|
||||
use crate::{commands::cap::Cap, sender::IrcResponse};
|
||||
use crate::{
|
||||
commands::{cap::Cap, nick::Nick, ping::Ping, privmsg::PrivMsg, user::User as UserHandler},
|
||||
sender::IrcResponse,
|
||||
user::User,
|
||||
};
|
||||
|
||||
mod cap;
|
||||
mod nick;
|
||||
mod ping;
|
||||
mod privmsg;
|
||||
mod user;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IrcCommand {
|
||||
command: String,
|
||||
arguments: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct IrcMessage {
|
||||
pub sender: String, // TODO: replace with hostmask
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub enum IrcAction {
|
||||
MultipleActions(Vec<Self>),
|
||||
ModifyDatabase(DatabaseAction),
|
||||
SendText(IrcResponse),
|
||||
ErrorAuthenticateFirst,
|
||||
DoNothing,
|
||||
}
|
||||
|
||||
pub enum DatabaseAction {}
|
||||
|
||||
pub trait IrcHandler {
|
||||
fn handle(&self, command: Vec<String>) -> IrcAction;
|
||||
#[async_trait]
|
||||
pub trait IrcHandler: Send + Sync {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
authenticated: bool,
|
||||
user_state: &mut User,
|
||||
) -> IrcAction;
|
||||
}
|
||||
|
||||
pub struct SendMessage(Option<String>);
|
||||
|
||||
impl IrcCommand {
|
||||
pub fn new(command_with_arguments: String) -> Self {
|
||||
let split_command: Vec<&str> = command_with_arguments
|
||||
|
|
@ -32,10 +57,24 @@ impl IrcCommand {
|
|||
.collect();
|
||||
let command = split_command[0].to_owned();
|
||||
let mut arguments = Vec::new();
|
||||
let mut buffer: Option<String> = None;
|
||||
|
||||
split_command[1..]
|
||||
.iter()
|
||||
.for_each(|e| arguments.push(e.to_string()));
|
||||
.for_each(|e| match (buffer.as_mut(), e.starts_with(":")) {
|
||||
(None, false) => arguments.push(e.to_string()),
|
||||
(None, true) => {
|
||||
buffer = Some(e[1..].to_string());
|
||||
}
|
||||
(Some(buf), starts_with_colon) => {
|
||||
buf.push(' ');
|
||||
buf.push_str(if starts_with_colon { &e[1..] } else { &e });
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(buf) = buffer {
|
||||
arguments.push(buf.to_string());
|
||||
}
|
||||
|
||||
Self {
|
||||
command: command,
|
||||
|
|
@ -43,35 +82,56 @@ impl IrcCommand {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn execute(&self, writer: &mut BufWriter<&TcpStream>, hostname: &str) -> Result<()> {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
writer: &mut BufWriter<TcpStream>,
|
||||
hostname: &str,
|
||||
user_state: &mut User,
|
||||
) -> Result<()> {
|
||||
let mut command_map: HashMap<String, &dyn IrcHandler> = HashMap::new();
|
||||
|
||||
// Command map is defined here
|
||||
command_map.insert("CAP".to_owned(), &Cap);
|
||||
command_map.insert("NICK".to_owned(), &Nick);
|
||||
command_map.insert("USER".to_owned(), &UserHandler);
|
||||
command_map.insert("PRIVMSG".to_owned(), &PrivMsg);
|
||||
command_map.insert("PING".to_owned(), &Ping);
|
||||
|
||||
println!("{self:#?}");
|
||||
|
||||
let command_to_execute = command_map
|
||||
.get(&self.command.to_uppercase())
|
||||
.map(|v| *v)
|
||||
.ok_or(anyhow!("unknown command!"))?;
|
||||
|
||||
let action = command_to_execute.handle(self.arguments.clone());
|
||||
action.execute(writer, hostname);
|
||||
let action = command_to_execute
|
||||
.handle(
|
||||
self.arguments.clone(),
|
||||
user_state.is_populated(),
|
||||
user_state,
|
||||
)
|
||||
.await;
|
||||
action.execute(writer, hostname, user_state).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IrcAction {
|
||||
pub fn execute(&self, writer: &mut BufWriter<&TcpStream>, hostname: &str) {
|
||||
pub async fn execute(
|
||||
&self,
|
||||
writer: &mut BufWriter<TcpStream>,
|
||||
hostname: &str,
|
||||
user_state: &mut User,
|
||||
) {
|
||||
match self {
|
||||
IrcAction::MultipleActions(actions) => {
|
||||
/*IrcAction::MultipleActions(actions) => {
|
||||
for action in actions {
|
||||
action.execute(writer, hostname);
|
||||
action.execute(writer, hostname, user_state);
|
||||
}
|
||||
}
|
||||
|
||||
}*/
|
||||
IrcAction::SendText(msg) => {
|
||||
msg.send(hostname, writer).unwrap();
|
||||
msg.send(hostname, writer, false).await.unwrap();
|
||||
}
|
||||
|
||||
_ => {}
|
||||
|
|
|
|||
22
src/commands/nick.rs
Normal file
22
src/commands/nick.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
commands::{IrcAction, IrcHandler},
|
||||
user::User,
|
||||
};
|
||||
|
||||
pub struct Nick;
|
||||
|
||||
#[async_trait]
|
||||
impl IrcHandler for Nick {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
_authenticated: bool,
|
||||
user_state: &mut User,
|
||||
) -> IrcAction {
|
||||
user_state.nickname = Some(command[0].clone());
|
||||
|
||||
IrcAction::DoNothing
|
||||
}
|
||||
}
|
||||
30
src/commands/ping.rs
Normal file
30
src/commands/ping.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
commands::{IrcAction, IrcHandler},
|
||||
sender::IrcResponse,
|
||||
user::User,
|
||||
};
|
||||
|
||||
pub struct Ping;
|
||||
|
||||
#[async_trait]
|
||||
impl IrcHandler for Ping {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
authenticated: bool,
|
||||
user_state: &mut User,
|
||||
) -> IrcAction {
|
||||
if authenticated {
|
||||
IrcAction::SendText(IrcResponse {
|
||||
sender: None,
|
||||
command: "PONG".into(),
|
||||
receiver: user_state.nickname.clone().unwrap(),
|
||||
message: command[0].clone(),
|
||||
})
|
||||
} else {
|
||||
IrcAction::DoNothing
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/commands/privmsg.rs
Normal file
40
src/commands/privmsg.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use async_trait::async_trait;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
|
||||
use crate::{
|
||||
CONNECTED_USERS, SENDER,
|
||||
commands::{IrcAction, IrcHandler},
|
||||
messages::Message,
|
||||
user::User,
|
||||
};
|
||||
|
||||
pub struct PrivMsg;
|
||||
|
||||
#[async_trait]
|
||||
impl IrcHandler for PrivMsg {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
authenticated: bool,
|
||||
user_state: &mut User,
|
||||
) -> IrcAction {
|
||||
if !authenticated {
|
||||
return IrcAction::ErrorAuthenticateFirst;
|
||||
}
|
||||
let connected_users = CONNECTED_USERS.lock().await;
|
||||
let sender = SENDER.lock().await.clone().unwrap();
|
||||
|
||||
println!("{connected_users:#?}");
|
||||
drop(connected_users);
|
||||
|
||||
let message = Message {
|
||||
sender: user_state.clone().unwrap_all(),
|
||||
receiver: command[0].clone(),
|
||||
text: command[1].clone(),
|
||||
};
|
||||
println!("SENDING: {message:#?}");
|
||||
sender.send(message).unwrap();
|
||||
|
||||
IrcAction::DoNothing
|
||||
}
|
||||
}
|
||||
23
src/commands/user.rs
Normal file
23
src/commands/user.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use crate::{
|
||||
commands::{IrcAction, IrcHandler},
|
||||
user::User as UserState,
|
||||
};
|
||||
|
||||
pub struct User;
|
||||
|
||||
#[async_trait]
|
||||
impl IrcHandler for User {
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Vec<String>,
|
||||
_authenticated: bool,
|
||||
user_state: &mut UserState,
|
||||
) -> IrcAction {
|
||||
user_state.username = Some(command[0].clone());
|
||||
user_state.realname = Some(command[3].clone());
|
||||
|
||||
IrcAction::DoNothing
|
||||
}
|
||||
}
|
||||
53
src/login.rs
Normal file
53
src/login.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
use anyhow::Result;
|
||||
use tokio::{io::BufWriter, net::TcpStream};
|
||||
|
||||
use crate::{ServerInfo, sender::IrcResponseCodes, user::User};
|
||||
|
||||
pub async fn send_motd(
|
||||
server_info: ServerInfo,
|
||||
user_info: User,
|
||||
writer: &mut BufWriter<TcpStream>,
|
||||
) -> Result<()> {
|
||||
let user_info = user_info.unwrap_all();
|
||||
let server_version = &format!("IRS-v{}", env!("CARGO_PKG_VERSION")) as &str;
|
||||
|
||||
let welcome_text = format!(
|
||||
"Welcome to the {} Internet Relay Chat Network {}",
|
||||
server_info.network_name, user_info.nickname
|
||||
);
|
||||
let yourhost_text = format!(
|
||||
"Your host is {}, running version {}",
|
||||
server_info.server_hostname, server_version
|
||||
);
|
||||
let myinfo_text = format!("{} {} i b", server_info.server_hostname, server_version);
|
||||
let isupport_text = format!(
|
||||
"CHANTYPES=# NETWORK={} :are supported by this server",
|
||||
server_info.network_name
|
||||
);
|
||||
|
||||
IrcResponseCodes::Welcome
|
||||
.into_irc_response(user_info.username.clone(), welcome_text)
|
||||
.send(&server_info.server_hostname, writer, true)
|
||||
.await?;
|
||||
IrcResponseCodes::YourHost
|
||||
.into_irc_response(user_info.username.clone(), yourhost_text)
|
||||
.send(&server_info.server_hostname, writer, true)
|
||||
.await?;
|
||||
IrcResponseCodes::MyInfo
|
||||
.into_irc_response(user_info.username.clone(), myinfo_text)
|
||||
.send(&server_info.server_hostname, writer, false)
|
||||
.await?;
|
||||
IrcResponseCodes::ISupport
|
||||
.into_irc_response(user_info.username.clone(), isupport_text)
|
||||
.send(&server_info.server_hostname, writer, false)
|
||||
.await?;
|
||||
IrcResponseCodes::NoMotd
|
||||
.into_irc_response(
|
||||
user_info.username.clone(),
|
||||
"MOTD not implemented yet".into(),
|
||||
)
|
||||
.send(&server_info.server_hostname, writer, true)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
198
src/main.rs
198
src/main.rs
|
|
@ -1,61 +1,201 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
io::{BufRead, BufReader, BufWriter},
|
||||
net::{SocketAddr, TcpListener, TcpStream},
|
||||
str::FromStr,
|
||||
sync::mpsc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::spawn;
|
||||
use anyhow::{Result, bail};
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, BufReader as TokioBufReader, BufWriter as TokioBufWriter},
|
||||
net::TcpStream as TokioTcpStream,
|
||||
spawn,
|
||||
sync::{
|
||||
Mutex,
|
||||
broadcast::{self, Receiver, Sender},
|
||||
},
|
||||
time::sleep,
|
||||
};
|
||||
|
||||
use crate::sender::IrcResponseCodes;
|
||||
use crate::{
|
||||
login::send_motd,
|
||||
messages::Message,
|
||||
sender::{IrcResponse, IrcResponseCodes},
|
||||
user::{User, UserUnwrapped},
|
||||
};
|
||||
|
||||
mod commands;
|
||||
mod login;
|
||||
mod messages;
|
||||
mod sender;
|
||||
mod user;
|
||||
|
||||
pub static CONNECTED_USERS: Lazy<Mutex<HashSet<UserUnwrapped>>> =
|
||||
Lazy::new(|| Mutex::new(HashSet::new()));
|
||||
pub static SENDER: Lazy<Mutex<Option<Sender<Message>>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
struct ServerInfo {
|
||||
ip: String,
|
||||
port: String,
|
||||
server_hostname: String,
|
||||
network_name: String,
|
||||
operators: Vec<String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let ip = "0.0.0.0";
|
||||
let port = "6667";
|
||||
let server_hostname = "irc.blah.blah";
|
||||
let info = ServerInfo {
|
||||
ip: "0.0.0.0".into(),
|
||||
port: "6667".into(),
|
||||
server_hostname: "irc.blah.blah".into(),
|
||||
network_name: "TeamDunno".into(),
|
||||
operators: Vec::new(),
|
||||
};
|
||||
// TODO: ^ pull these from a config file
|
||||
|
||||
let listener = TcpListener::bind(SocketAddr::from_str(&format!("{}:{}", ip, port))?)?;
|
||||
let listener = TcpListener::bind(SocketAddr::from_str(&format!("{}:{}", info.ip, info.port))?)?;
|
||||
let (tx, mut _rx) = broadcast::channel::<Message>(32);
|
||||
let mut sender_mut = SENDER.lock().await;
|
||||
*sender_mut = Some(tx.clone());
|
||||
drop(sender_mut);
|
||||
|
||||
for stream in listener.incoming() {
|
||||
let stream = stream?;
|
||||
stream.set_nonblocking(true)?;
|
||||
let tx_thread = tx.clone();
|
||||
let info = info.clone();
|
||||
|
||||
spawn(async move { handle_connection(stream, server_hostname).await.unwrap() });
|
||||
spawn(async move {
|
||||
handle_connection(stream, info, /*&mut rx_thread,*/ tx_thread)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_connection(stream: TcpStream, hostname: &str) -> Result<()> {
|
||||
let reader_stream = stream.try_clone()?;
|
||||
let mut reader = BufReader::new(&reader_stream);
|
||||
let mut writer = BufWriter::new(&stream);
|
||||
let mut buffer = String::new();
|
||||
async fn handle_connection(stream: TcpStream, info: ServerInfo, tx: Sender<Message>) -> Result<()> {
|
||||
let stream_tcp = stream.try_clone()?;
|
||||
let mut message_receiver = tx.clone().subscribe();
|
||||
let mut tcp_reader = TokioBufReader::new(TokioTcpStream::from_std(stream.try_clone()?)?);
|
||||
let mut tcp_writer = TokioBufWriter::new(TokioTcpStream::from_std(stream)?);
|
||||
let mut state = User::default();
|
||||
|
||||
loop {
|
||||
buffer.clear();
|
||||
if reader.read_line(&mut buffer).unwrap() == 0 {
|
||||
break;
|
||||
}
|
||||
tokio::select! {
|
||||
result = tcp_listener(&stream_tcp, state.clone(), &info, &mut tcp_reader) => {
|
||||
match result {
|
||||
Ok(modified_user) => {
|
||||
state = modified_user;
|
||||
}
|
||||
|
||||
let command = commands::IrcCommand::new(buffer.clone());
|
||||
match command.execute(&mut writer, hostname) {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
let error_string = format!("error processing your command: {error:#?}\n");
|
||||
let error = IrcResponseCodes::UnknownCommand;
|
||||
|
||||
error
|
||||
.into_irc_response("*".into(), error_string.into())
|
||||
.send(hostname, &mut writer)
|
||||
.unwrap();
|
||||
}
|
||||
Err(_) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
result = message_listener(&state, &mut message_receiver, &mut tcp_writer) => {
|
||||
match result {
|
||||
Ok(_) => {},
|
||||
Err(_) => {
|
||||
// break;
|
||||
}
|
||||
}
|
||||
},
|
||||
_ = sleep(Duration::from_millis(200)) => {},
|
||||
}
|
||||
}
|
||||
|
||||
stream_tcp.shutdown(std::net::Shutdown::Both)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn tcp_listener(
|
||||
stream: &TcpStream,
|
||||
mut state: User,
|
||||
info: &ServerInfo,
|
||||
reader: &mut TokioBufReader<TokioTcpStream>,
|
||||
) -> Result<User> {
|
||||
let mut buffer = String::new();
|
||||
|
||||
let mut writer = TokioBufWriter::new(TokioTcpStream::from_std(stream.try_clone()?)?);
|
||||
|
||||
buffer.clear();
|
||||
match reader.read_line(&mut buffer).await {
|
||||
Ok(0) => bail!("invalid response"),
|
||||
Ok(_) => {}
|
||||
|
||||
Err(_) => {
|
||||
let mut conneted_users = CONNECTED_USERS.lock().await;
|
||||
let _ = conneted_users.remove(&state.clone().unwrap_all());
|
||||
|
||||
bail!("client disconnected")
|
||||
}
|
||||
}
|
||||
|
||||
let command = commands::IrcCommand::new(buffer.clone());
|
||||
match command
|
||||
.execute(&mut writer, &info.server_hostname, &mut state)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
let error_string = format!("error processing your command: {error:#?}\n");
|
||||
let error = IrcResponseCodes::UnknownCommand;
|
||||
|
||||
error
|
||||
.into_irc_response("*".into(), error_string.into())
|
||||
.send(&info.server_hostname, &mut writer, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if !state.identified && state.is_populated() {
|
||||
send_motd(info.clone(), state.clone(), &mut writer).await?;
|
||||
|
||||
state.identified = true;
|
||||
CONNECTED_USERS
|
||||
.lock()
|
||||
.await
|
||||
.insert(state.clone().unwrap_all());
|
||||
}
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
async fn message_listener(
|
||||
user_wrapped: &User,
|
||||
receiver: &mut Receiver<Message>,
|
||||
writer: &mut TokioBufWriter<TokioTcpStream>,
|
||||
) -> Result<()> {
|
||||
if !user_wrapped.is_populated() {
|
||||
bail!("user has not registered yet, returning...");
|
||||
}
|
||||
|
||||
let user = user_wrapped.unwrap_all();
|
||||
|
||||
let message: Message = receiver.recv().await.unwrap();
|
||||
println!("{message:#?}");
|
||||
|
||||
if user.nickname.clone().to_ascii_lowercase() == message.receiver.to_ascii_lowercase() {
|
||||
IrcResponse {
|
||||
sender: Some(message.sender.hostmask()),
|
||||
command: "PRIVMSG".into(),
|
||||
message: message.text,
|
||||
receiver: user.username.clone(),
|
||||
}
|
||||
.send("", writer, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
9
src/messages.rs
Normal file
9
src/messages.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use crate::user::UserUnwrapped;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Message {
|
||||
pub sender: UserUnwrapped,
|
||||
pub receiver: String,
|
||||
pub text: String,
|
||||
}
|
||||
|
|
@ -1,59 +1,81 @@
|
|||
use std::{
|
||||
io::{BufWriter, Write},
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::{
|
||||
io::{AsyncWriteExt, BufWriter},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IrcResponse {
|
||||
pub sender: Option<String>,
|
||||
pub command: String,
|
||||
pub receiver: String,
|
||||
pub arguments: Option<String>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum IrcResponseCodes {
|
||||
UnknownCommand,
|
||||
Welcome,
|
||||
YourHost,
|
||||
MyInfo,
|
||||
ISupport,
|
||||
NoMotd,
|
||||
}
|
||||
|
||||
impl IrcResponse {
|
||||
pub fn send(&self, hostname: &str, writer: &mut BufWriter<&TcpStream>) -> Result<()> {
|
||||
let mut response = format!(":{} {} {} ", hostname, self.command, self.receiver);
|
||||
pub async fn send(
|
||||
&self,
|
||||
hostname: &str,
|
||||
writer: &mut BufWriter<TcpStream>,
|
||||
prepend_column: bool,
|
||||
) -> Result<()> {
|
||||
let mut response = format!(
|
||||
":{} {} {} ",
|
||||
self.sender.clone().unwrap_or(hostname.to_string()),
|
||||
self.command,
|
||||
self.receiver
|
||||
);
|
||||
|
||||
if let Some(arguments) = &self.arguments {
|
||||
response.push_str(&format!("{} ", arguments));
|
||||
};
|
||||
if prepend_column {
|
||||
response.push_str(&format!(":{}\r\n", self.message.trim_end()));
|
||||
} else {
|
||||
response.push_str(&format!("{}\r\n", self.message.trim_end()));
|
||||
}
|
||||
|
||||
response.push_str(&format!(":{}\n", self.message.trim_end()));
|
||||
|
||||
writer.write_all(response.as_bytes())?;
|
||||
writer.flush()?;
|
||||
writer.write_all(response.as_bytes()).await?;
|
||||
writer.flush().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IrcResponseCodes> for u32 {
|
||||
impl From<IrcResponseCodes> for &str {
|
||||
fn from(value: IrcResponseCodes) -> Self {
|
||||
match value {
|
||||
IrcResponseCodes::UnknownCommand => 421,
|
||||
IrcResponseCodes::UnknownCommand => "421",
|
||||
IrcResponseCodes::Welcome => "001",
|
||||
IrcResponseCodes::YourHost => "002",
|
||||
IrcResponseCodes::MyInfo => "004",
|
||||
IrcResponseCodes::ISupport => "005",
|
||||
IrcResponseCodes::NoMotd => "422",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IrcResponseCodes> for String {
|
||||
fn from(value: IrcResponseCodes) -> Self {
|
||||
Into::<u32>::into(value).to_string()
|
||||
Into::<&str>::into(value).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl IrcResponseCodes {
|
||||
pub fn into_irc_response(&self, receiver: String, message: String) -> IrcResponse {
|
||||
IrcResponse {
|
||||
sender: None,
|
||||
command: (*self).into(),
|
||||
receiver,
|
||||
arguments: None,
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
72
src/user.rs
Normal file
72
src/user.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct User {
|
||||
pub nickname: Option<String>,
|
||||
pub username: Option<String>,
|
||||
pub realname: Option<String>,
|
||||
pub identified: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct UserUnwrapped {
|
||||
pub nickname: String,
|
||||
pub username: String,
|
||||
pub realname: String,
|
||||
pub identified: bool,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn is_populated(&self) -> bool {
|
||||
self.realname.is_some() && self.username.is_some() && self.nickname.is_some()
|
||||
}
|
||||
|
||||
pub fn unwrap_all(&self) -> UserUnwrapped {
|
||||
UserUnwrapped {
|
||||
nickname: self.nickname.clone().unwrap(),
|
||||
username: self.username.clone().unwrap(),
|
||||
realname: self.realname.clone().unwrap(),
|
||||
identified: self.identified,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
nickname: None,
|
||||
username: None,
|
||||
realname: None,
|
||||
identified: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserUnwrapped {
|
||||
pub fn hostmask(&self) -> String {
|
||||
format!(
|
||||
"{}!~{}@{}",
|
||||
self.nickname.clone(),
|
||||
self.realname.clone(),
|
||||
"unimplement.ed"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for UserUnwrapped {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
self.username == other.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<UserUnwrapped> for String {
|
||||
fn eq(&self, other: &UserUnwrapped) -> bool {
|
||||
self == &other.username.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<String> for UserUnwrapped {
|
||||
fn borrow(&self) -> &String {
|
||||
&self.username
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue