chore: move client-related code into its own directory
All checks were successful
build / test-alpine (push) Successful in 51s
build / test-debian (push) Successful in 1m23s

This commit is contained in:
user0-07161 2026-02-15 17:47:10 +01:00
parent 0489cac646
commit d3c3cd8292
12 changed files with 297 additions and 278 deletions

View file

@ -1,14 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use crate::{ use super::{ClientAction, ClientHandler};
commands::{IrcAction, IrcHandler}, use crate::user::User;
user::User,
};
pub struct Cap; pub struct Cap;
#[async_trait] #[async_trait]
impl IrcHandler for Cap { impl ClientHandler for Cap {
async fn handle( async fn handle(
&self, &self,
_arguments: Vec<String>, _arguments: Vec<String>,
@ -17,7 +15,7 @@ impl IrcHandler for Cap {
_server_outgoing_password: String, _server_outgoing_password: String,
_server_incoming_passwords: Vec<String>, _server_incoming_passwords: Vec<String>,
_user_passwords: Vec<String>, _user_passwords: Vec<String>,
) -> Vec<super::IrcAction> { ) -> Vec<super::ClientAction> {
vec![IrcAction::DoNothing] vec![ClientAction::DoNothing]
} }
} }

View file

@ -1,16 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use crate::{ use super::{ClientAction, ClientHandler};
JOINED_CHANNELS, use crate::{JOINED_CHANNELS, channels::Channel, user::User};
channels::Channel,
commands::{IrcAction, IrcHandler},
user::User,
};
pub struct Join; pub struct Join;
#[async_trait] #[async_trait]
impl IrcHandler for Join { impl ClientHandler for Join {
async fn handle( async fn handle(
&self, &self,
arguments: Vec<String>, arguments: Vec<String>,
@ -19,7 +15,7 @@ impl IrcHandler for Join {
_server_outgoing_password: String, _server_outgoing_password: String,
_server_incoming_passwords: Vec<String>, _server_incoming_passwords: Vec<String>,
_user_passwords: Vec<String>, _user_passwords: Vec<String>,
) -> Vec<super::IrcAction> { ) -> Vec<super::ClientAction> {
let mut joined_channels = JOINED_CHANNELS.lock().await; let mut joined_channels = JOINED_CHANNELS.lock().await;
let mut channels = Vec::new(); let mut channels = Vec::new();
@ -31,7 +27,7 @@ impl IrcHandler for Join {
} }
if !authenticated { if !authenticated {
return vec![IrcAction::ErrorAuthenticateFirst]; return vec![ClientAction::ErrorAuthenticateFirst];
} }
for existing_channel in joined_channels.clone() { for existing_channel in joined_channels.clone() {
@ -56,6 +52,6 @@ impl IrcHandler for Join {
} }
} }
vec![IrcAction::JoinChannels(channels)] vec![ClientAction::JoinChannels(channels)]
} }
} }

View file

@ -5,13 +5,13 @@ use async_trait::async_trait;
use tokio::{io::BufWriter, net::TcpStream, sync::broadcast::Sender}; use tokio::{io::BufWriter, net::TcpStream, sync::broadcast::Sender};
use tracing::debug; use tracing::debug;
use super::commands::{
cap::Cap, join::Join, nick::Nick, pass::Pass, ping::Ping, privmsg::PrivMsg,
user::User as UserHandler, who::Who,
};
use crate::{ use crate::{
SENDER, SENDER,
channels::Channel, channels::Channel,
commands::{
cap::Cap, join::Join, nick::Nick, pass::Pass, ping::Ping, privmsg::PrivMsg,
user::User as UserHandler, who::Who,
},
config::ServerInfo, config::ServerInfo,
error_structs::CommandExecError, error_structs::CommandExecError,
messages::{ChanJoinMessage, Message}, messages::{ChanJoinMessage, Message},
@ -29,17 +29,17 @@ mod user;
mod who; mod who;
#[derive(Debug)] #[derive(Debug)]
pub struct IrcCommand { pub struct ClientCommand {
command: String, command: String,
arguments: Vec<String>, arguments: Vec<String>,
} }
pub struct IrcMessage { pub struct ClientMessage {
pub sender: String, // TODO: replace with hostmask pub sender: String, // TODO: replace with hostmask
pub message: String, pub message: String,
} }
pub enum IrcAction { pub enum ClientAction {
SendText(IrcResponse), SendText(IrcResponse),
SendMessage(Message), SendMessage(Message),
JoinChannels(Vec<Channel>), JoinChannels(Vec<Channel>),
@ -55,7 +55,7 @@ pub enum ReturnAction {
} }
#[async_trait] #[async_trait]
pub trait IrcHandler: Send + Sync { pub trait ClientHandler: Send + Sync {
async fn handle( async fn handle(
&self, &self,
command: Vec<String>, command: Vec<String>,
@ -64,12 +64,12 @@ pub trait IrcHandler: Send + Sync {
server_outgoing_password: String, server_outgoing_password: String,
server_incoming_passwords: Vec<String>, server_incoming_passwords: Vec<String>,
user_passwords: Vec<String>, user_passwords: Vec<String>,
) -> Vec<IrcAction>; ) -> Vec<ClientAction>;
} }
pub struct SendMessage(Option<String>); pub struct SendMessage(Option<String>);
impl IrcCommand { impl ClientCommand {
pub async fn new(command_with_arguments: String) -> Self { pub async fn new(command_with_arguments: String) -> Self {
let mut split_command: Vec<&str> = command_with_arguments let mut split_command: Vec<&str> = command_with_arguments
.split_whitespace() .split_whitespace()
@ -114,7 +114,7 @@ impl IrcCommand {
user_state: &mut User, user_state: &mut User,
config: &ServerInfo, config: &ServerInfo,
) -> Result<Vec<ReturnAction>, CommandExecError> { ) -> Result<Vec<ReturnAction>, CommandExecError> {
let mut command_map: HashMap<String, &dyn IrcHandler> = HashMap::new(); let mut command_map: HashMap<String, &dyn ClientHandler> = HashMap::new();
let broadcast_sender = SENDER.lock().await.clone().unwrap(); let broadcast_sender = SENDER.lock().await.clone().unwrap();
// Command map is defined here // Command map is defined here
@ -159,7 +159,7 @@ impl IrcCommand {
} }
} }
impl IrcAction { impl ClientAction {
pub async fn execute( pub async fn execute(
&self, &self,
writer: &mut BufWriter<TcpStream>, writer: &mut BufWriter<TcpStream>,
@ -168,11 +168,11 @@ impl IrcAction {
sender: Sender<Message>, sender: Sender<Message>,
) -> ReturnAction { ) -> ReturnAction {
match self { match self {
IrcAction::SendText(msg) => { ClientAction::SendText(msg) => {
msg.send(hostname, writer, false).await.unwrap(); msg.send(hostname, writer, false).await.unwrap();
} }
IrcAction::JoinChannels(channels) => { ClientAction::JoinChannels(channels) => {
for channel in channels { for channel in channels {
let join_message = ChanJoinMessage { let join_message = ChanJoinMessage {
sender: user_state.clone().unwrap_all(), sender: user_state.clone().unwrap_all(),
@ -182,11 +182,11 @@ impl IrcAction {
} }
} }
IrcAction::SendMessage(msg) => { ClientAction::SendMessage(msg) => {
sender.send(msg.clone()).unwrap(); sender.send(msg.clone()).unwrap();
} }
IrcAction::UpgradeToServerConn => { ClientAction::UpgradeToServerConn => {
return ReturnAction::ServerConn; return ReturnAction::ServerConn;
} }

View file

@ -1,14 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use crate::{ use super::{ClientAction, ClientHandler};
commands::{IrcAction, IrcHandler}, use crate::user::User;
user::User,
};
pub struct Nick; pub struct Nick;
#[async_trait] #[async_trait]
impl IrcHandler for Nick { impl ClientHandler for Nick {
async fn handle( async fn handle(
&self, &self,
command: Vec<String>, command: Vec<String>,
@ -17,7 +15,7 @@ impl IrcHandler for Nick {
_server_outgoing_password: String, _server_outgoing_password: String,
_server_incoming_passwords: Vec<String>, _server_incoming_passwords: Vec<String>,
_user_passwords: Vec<String>, _user_passwords: Vec<String>,
) -> Vec<IrcAction> { ) -> Vec<ClientAction> {
user_state.nickname = Some({ user_state.nickname = Some({
if command[0].len() > 9 { if command[0].len() > 9 {
String::from_utf8( String::from_utf8(
@ -34,6 +32,6 @@ impl IrcHandler for Nick {
} }
}); });
vec![IrcAction::DoNothing] vec![ClientAction::DoNothing]
} }
} }

View file

@ -1,14 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use crate::{ use super::{ClientAction, ClientHandler};
commands::{IrcAction, IrcHandler}, use crate::user::User;
user::User,
};
pub struct Pass; pub struct Pass;
#[async_trait] #[async_trait]
impl IrcHandler for Pass { impl ClientHandler for Pass {
async fn handle( async fn handle(
&self, &self,
command: Vec<String>, command: Vec<String>,
@ -17,22 +15,22 @@ impl IrcHandler for Pass {
server_outgoing_password: String, server_outgoing_password: String,
server_incoming_passwords: Vec<String>, server_incoming_passwords: Vec<String>,
_user_passwords: Vec<String>, _user_passwords: Vec<String>,
) -> Vec<IrcAction> { ) -> Vec<ClientAction> {
// XXX // XXX
if server_incoming_passwords.contains(&command[0]) { if server_incoming_passwords.contains(&command[0]) {
vec![ vec![
IrcAction::SendText(crate::sender::IrcResponse { ClientAction::SendText(crate::sender::IrcResponse {
sender: None, sender: None,
command: "PASS".to_owned(), command: "PASS".to_owned(),
receiver: None, receiver: None,
arguments: Vec::new(), arguments: Vec::new(),
message: server_outgoing_password.clone(), message: server_outgoing_password.clone(),
}), }),
IrcAction::UpgradeToServerConn, ClientAction::UpgradeToServerConn,
] ]
} else { } else {
vec![IrcAction::DoNothing] vec![ClientAction::DoNothing]
} }
} }
} }

View file

@ -1,15 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use crate::{ use super::{ClientAction, ClientHandler};
commands::{IrcAction, IrcHandler}, use crate::{sender::IrcResponse, user::User};
sender::IrcResponse,
user::User,
};
pub struct Ping; pub struct Ping;
#[async_trait] #[async_trait]
impl IrcHandler for Ping { impl ClientHandler for Ping {
async fn handle( async fn handle(
&self, &self,
command: Vec<String>, command: Vec<String>,
@ -18,9 +15,9 @@ impl IrcHandler for Ping {
_server_outgoing_password: String, _server_outgoing_password: String,
_server_incoming_passwords: Vec<String>, _server_incoming_passwords: Vec<String>,
_user_passwords: Vec<String>, _user_passwords: Vec<String>,
) -> Vec<IrcAction> { ) -> Vec<ClientAction> {
if authenticated { if authenticated {
vec![IrcAction::SendText(IrcResponse { vec![ClientAction::SendText(IrcResponse {
sender: None, sender: None,
command: "PONG".into(), command: "PONG".into(),
arguments: Vec::new(), arguments: Vec::new(),
@ -28,7 +25,7 @@ impl IrcHandler for Ping {
message: format!(":{}", command[0].clone()), message: format!(":{}", command[0].clone()),
})] })]
} else { } else {
vec![IrcAction::DoNothing] vec![ClientAction::DoNothing]
} }
} }
} }

View file

@ -1,7 +1,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use super::{ClientAction, ClientHandler};
use crate::{ use crate::{
commands::{IrcAction, IrcHandler},
messages::{Message, PrivMessage, Receiver}, messages::{Message, PrivMessage, Receiver},
user::User, user::User,
}; };
@ -9,7 +9,7 @@ use crate::{
pub struct PrivMsg; pub struct PrivMsg;
#[async_trait] #[async_trait]
impl IrcHandler for PrivMsg { impl ClientHandler for PrivMsg {
async fn handle( async fn handle(
&self, &self,
command: Vec<String>, command: Vec<String>,
@ -18,9 +18,9 @@ impl IrcHandler for PrivMsg {
_server_outgoing_password: String, _server_outgoing_password: String,
_server_incoming_passwords: Vec<String>, _server_incoming_passwords: Vec<String>,
_user_passwords: Vec<String>, _user_passwords: Vec<String>,
) -> Vec<IrcAction> { ) -> Vec<ClientAction> {
if !authenticated { if !authenticated {
return vec![IrcAction::ErrorAuthenticateFirst]; return vec![ClientAction::ErrorAuthenticateFirst];
} }
let receiver = if command[0].clone().starts_with("#") { let receiver = if command[0].clone().starts_with("#") {
@ -35,6 +35,6 @@ impl IrcHandler for PrivMsg {
text: command[1].clone(), text: command[1].clone(),
}; };
vec![IrcAction::SendMessage(Message::PrivMessage(message))] vec![ClientAction::SendMessage(Message::PrivMessage(message))]
} }
} }

View file

@ -1,14 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use crate::{ use super::{ClientAction, ClientHandler};
commands::{IrcAction, IrcHandler}, use crate::user::User as UserState;
user::User as UserState,
};
pub struct User; pub struct User;
#[async_trait] #[async_trait]
impl IrcHandler for User { impl ClientHandler for User {
async fn handle( async fn handle(
&self, &self,
command: Vec<String>, command: Vec<String>,
@ -17,9 +15,9 @@ impl IrcHandler for User {
_server_outgoing_password: String, _server_outgoing_password: String,
_server_incoming_passwords: Vec<String>, _server_incoming_passwords: Vec<String>,
_user_passwords: Vec<String>, _user_passwords: Vec<String>,
) -> Vec<IrcAction> { ) -> Vec<ClientAction> {
if command.len() < 4 { if command.len() < 4 {
return vec![IrcAction::DoNothing]; // XXX: return an error return vec![ClientAction::DoNothing]; // XXX: return an error
} }
// oh my god this is a mess // oh my god this is a mess
@ -40,6 +38,6 @@ impl IrcHandler for User {
}); });
user_state.realname = Some(command[3].clone()); user_state.realname = Some(command[3].clone());
vec![IrcAction::DoNothing] vec![ClientAction::DoNothing]
} }
} }

View file

@ -1,14 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use crate::{ use super::{ClientAction, ClientHandler};
commands::{IrcAction, IrcHandler}, use crate::user::User;
user::User,
};
pub struct Who; pub struct Who;
#[async_trait] #[async_trait]
impl IrcHandler for Who { impl ClientHandler for Who {
async fn handle( async fn handle(
&self, &self,
_arguments: Vec<String>, _arguments: Vec<String>,
@ -17,7 +15,7 @@ impl IrcHandler for Who {
_server_outgoing_password: String, _server_outgoing_password: String,
_server_incoming_passwords: Vec<String>, _server_incoming_passwords: Vec<String>,
_user_passwords: Vec<String>, _user_passwords: Vec<String>,
) -> Vec<super::IrcAction> { ) -> Vec<super::ClientAction> {
vec![IrcAction::DoNothing] // TODO vec![ClientAction::DoNothing] // TODO
} }
} }

229
src/client/mod.rs Normal file
View file

@ -0,0 +1,229 @@
use std::{
net::TcpStream,
time::{Duration, SystemTime},
};
use crate::{
CONNECTED_USERS, JOINED_CHANNELS, SENDER, TcpListenerResult,
config::ServerInfo,
error_structs::{self, ListenerError},
login::send_motd,
messages::{Message, NetJoinMessage, Receiver as MsgReceiver},
sender::{IrcResponse, IrcResponseCodes},
ts6::structs::{ServerId, UserId},
user::User,
userid_gen,
};
use tokio::{
io::{AsyncBufReadExt, BufReader as TokioBufReader, BufWriter as TokioBufWriter},
net::TcpStream as TokioTcpStream,
spawn,
sync::{
Mutex,
broadcast::{self, Receiver, Sender},
},
time::sleep,
};
use tracing::debug;
mod commands;
#[derive(Clone, Debug, Default)]
pub struct Client {}
impl Client {
pub async fn tcp_listener(
&self,
stream: &TcpStream,
mut user_state: User,
info: &ServerInfo,
reader: &mut TokioBufReader<TokioTcpStream>,
our_sid: ServerId,
) -> Result<TcpListenerResult, ListenerError> {
let mut buffer = String::new();
let mut writer = TokioBufWriter::new(TokioTcpStream::from_std(stream.try_clone()?)?);
match reader.read_line(&mut buffer).await {
Ok(0) => return Err(ListenerError::ConnectionError),
Ok(_) => {}
Err(_) => {
let mut conneted_users = CONNECTED_USERS.lock().await;
let _ = conneted_users.remove(&user_state.clone().unwrap_all());
return Err(ListenerError::ConnectionError);
}
}
let command = commands::ClientCommand::new(buffer.clone()).await;
match command
.execute(&mut writer, &info.server_hostname, &mut user_state, info)
.await
{
Ok(return_actions) => {
for return_action in return_actions {
match return_action {
commands::ReturnAction::ServerConn => {
return Ok(TcpListenerResult::ServerConnectionInit);
}
_ => {}
}
}
}
Err(error) => match error {
error_structs::CommandExecError::NonexistantCommand => {
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 !user_state.identified && user_state.is_populated_without_uid() {
let id = userid_gen::increase_user_id()
.await
.unwrap()
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join("");
let user_id = format!("{our_sid}{id}");
user_state.identified = true;
user_state.user_id = Some(UserId::try_from(user_id).unwrap()); // XXX: error handling
user_state.timestamp = Some(SystemTime::now());
send_motd(info.clone(), user_state.clone(), &mut writer).await?;
let broadcast_sender = SENDER.lock().await.clone().unwrap();
broadcast_sender
.send(Message::NetJoinMessage(NetJoinMessage {
user: user_state.clone().unwrap_all(),
server_id: our_sid.clone(),
}))
.unwrap();
CONNECTED_USERS
.lock()
.await
.insert(user_state.clone().unwrap_all());
}
Ok(TcpListenerResult::UpdatedUser(user_state))
}
pub async fn message_listener(
&self,
user_wrapped: &User,
receiver: &mut Receiver<Message>,
writer: &mut TokioBufWriter<TokioTcpStream>,
hostname: &str,
) -> Result<(), ListenerError> {
if !user_wrapped.is_populated() {
sleep(Duration::from_millis(250)).await; // avoid immediate returns b'cuz they result in high
// cpu usage
return Err(ListenerError::UserIsUnidentified);
}
let user = user_wrapped.clone().unwrap_all();
let message: Message = receiver.recv().await.unwrap();
let joined_channels = JOINED_CHANNELS.lock().await;
let mut channel_name: Option<String> = None;
debug!("new message in the message stream: {message:?}");
match message {
Message::PrivMessage(message) => {
for channel in joined_channels.clone() {
if let MsgReceiver::ChannelName(channelname) = message.clone().receiver
&& channelname == channel.name
&& channel.joined_users.contains(user_wrapped)
{
channel_name = Some(channel.name.clone());
}
}
dbg!(&message);
if match message.clone().receiver {
MsgReceiver::UserId(userid) => {
debug!("{userid} ?= {}", user.user_id);
if userid == user.user_id { true } else { false }
}
MsgReceiver::Username(username) => {
if username.to_lowercase() == user.username.to_lowercase() {
true
} else {
false
}
}
_ => false,
} {
IrcResponse {
sender: Some(message.sender.hostmask()),
command: "PRIVMSG".into(),
arguments: Vec::new(),
message: message.text,
receiver: Some(user.username.clone()),
}
.send("", writer, true)
.await?;
} else if let Some(channel_name) = channel_name {
if message.sender != user {
IrcResponse {
sender: Some(message.sender.hostmask()),
command: "PRIVMSG".into(),
arguments: Vec::new(),
message: message.text,
receiver: Some(channel_name),
}
.send("", writer, true)
.await?;
}
}
}
Message::ChanJoinMessage(message) => {
if message.channel.joined_users.contains(user_wrapped) || message.sender == user {
let channel = message.channel.clone();
IrcResponse {
sender: Some(message.sender.hostmask().clone()),
command: "JOIN".into(),
arguments: Vec::new(),
message: message.channel.name.clone(),
receiver: None,
}
.send("", writer, true)
.await?;
channel
.send_topic(user_wrapped.clone(), writer, hostname)
.await
.unwrap();
channel
.names_list_send(user_wrapped.clone(), &channel, writer, hostname)
.await
.unwrap();
}
}
Message::NetJoinMessage(_) => {} // we don't care about these here :)
}
Ok(())
}
}

View file

@ -23,6 +23,7 @@ use tracing::{debug, instrument};
use crate::{ use crate::{
channels::Channel, channels::Channel,
client::Client,
config::ServerInfo, config::ServerInfo,
error_structs::{HandlerError, ListenerError}, error_structs::{HandlerError, ListenerError},
logging::LogLevel, logging::LogLevel,
@ -37,7 +38,7 @@ use crate::{
}; };
mod channels; mod channels;
mod commands; mod client;
mod config; mod config;
mod error_structs; mod error_structs;
mod logging; mod logging;
@ -93,9 +94,7 @@ async fn main() -> Result<(), AnyhowError> {
let tx_thread = tx.clone(); let tx_thread = tx.clone();
let info = info.clone(); let info = info.clone();
spawn(handle_connection( spawn(handle_connection(stream, info, tx_thread));
stream, info, /*&mut rx_thread,*/ tx_thread,
));
} }
Ok(()) Ok(())
@ -117,12 +116,13 @@ async fn handle_connection(
let hostname = info.server_hostname.clone(); let hostname = info.server_hostname.clone();
// TODO: generate randomally and allow overriding from config // TODO: generate randomly and allow overriding from config
let my_server_id = ServerId::try_from("000".to_owned()).unwrap(); let my_server_id = ServerId::try_from("000".to_owned()).unwrap();
let client_status = Client::default();
loop { loop {
tokio::select! { tokio::select! {
result = tcp_listener(&stream_tcp, state.clone(), &info, &mut tcp_reader, my_server_id.clone()) => { result = client_status.tcp_listener(&stream_tcp, state.clone(), &info, &mut tcp_reader, my_server_id.clone()) => {
match result { match result {
Ok(tcp_listener_result) => { Ok(tcp_listener_result) => {
match tcp_listener_result { match tcp_listener_result {
@ -141,7 +141,7 @@ async fn handle_connection(
} }
} }
}, },
result = message_listener(&state, &mut message_receiver, &mut tcp_writer, &hostname) => { result = client_status.message_listener(&state, &mut message_receiver, &mut tcp_writer, &hostname) => {
match result { match result {
Ok(_) => {}, Ok(_) => {},
Err(err) => { Err(err) => {
@ -191,198 +191,6 @@ async fn handle_connection(
Ok(()) Ok(())
} }
async fn tcp_listener(
stream: &TcpStream,
mut user_state: User,
info: &ServerInfo,
reader: &mut TokioBufReader<TokioTcpStream>,
our_sid: ServerId,
) -> Result<TcpListenerResult, ListenerError> {
let mut buffer = String::new();
let mut writer = TokioBufWriter::new(TokioTcpStream::from_std(stream.try_clone()?)?);
match reader.read_line(&mut buffer).await {
Ok(0) => return Err(ListenerError::ConnectionError),
Ok(_) => {}
Err(_) => {
let mut conneted_users = CONNECTED_USERS.lock().await;
let _ = conneted_users.remove(&user_state.clone().unwrap_all());
return Err(ListenerError::ConnectionError);
}
}
let command = commands::IrcCommand::new(buffer.clone()).await;
match command
.execute(&mut writer, &info.server_hostname, &mut user_state, info)
.await
{
Ok(return_actions) => {
for return_action in return_actions {
match return_action {
commands::ReturnAction::ServerConn => {
return Ok(TcpListenerResult::ServerConnectionInit);
}
_ => {}
}
}
}
Err(error) => match error {
error_structs::CommandExecError::NonexistantCommand => {
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 !user_state.identified && user_state.is_populated_without_uid() {
let id = userid_gen::increase_user_id()
.await
.unwrap()
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join("");
let user_id = format!("{our_sid}{id}");
user_state.identified = true;
user_state.user_id = Some(UserId::try_from(user_id).unwrap()); // XXX: error handling
user_state.timestamp = Some(SystemTime::now());
send_motd(info.clone(), user_state.clone(), &mut writer).await?;
let broadcast_sender = SENDER.lock().await.clone().unwrap();
broadcast_sender
.send(Message::NetJoinMessage(NetJoinMessage {
user: user_state.clone().unwrap_all(),
server_id: our_sid.clone(),
}))
.unwrap();
CONNECTED_USERS
.lock()
.await
.insert(user_state.clone().unwrap_all());
}
Ok(TcpListenerResult::UpdatedUser(user_state))
}
async fn message_listener(
user_wrapped: &User,
receiver: &mut Receiver<Message>,
writer: &mut TokioBufWriter<TokioTcpStream>,
hostname: &str,
) -> Result<(), ListenerError> {
if !user_wrapped.is_populated() {
sleep(Duration::from_millis(250)).await; // avoid immediate returns b'cuz they result in high
// cpu usage
return Err(ListenerError::UserIsUnidentified);
}
let user = user_wrapped.clone().unwrap_all();
let message: Message = receiver.recv().await.unwrap();
let joined_channels = JOINED_CHANNELS.lock().await;
let mut channel_name: Option<String> = None;
debug!("new message in the message stream: {message:?}");
match message {
Message::PrivMessage(message) => {
for channel in joined_channels.clone() {
if let MsgReceiver::ChannelName(channelname) = message.clone().receiver
&& channelname == channel.name
&& channel.joined_users.contains(user_wrapped)
{
channel_name = Some(channel.name.clone());
}
}
dbg!(&message);
if match message.clone().receiver {
MsgReceiver::UserId(userid) => {
debug!("{userid} ?= {}", user.user_id);
if userid == user.user_id { true } else { false }
}
MsgReceiver::Username(username) => {
if username.to_lowercase() == user.username.to_lowercase() {
true
} else {
false
}
}
_ => false,
} {
IrcResponse {
sender: Some(message.sender.hostmask()),
command: "PRIVMSG".into(),
arguments: Vec::new(),
message: message.text,
receiver: Some(user.username.clone()),
}
.send("", writer, true)
.await?;
} else if let Some(channel_name) = channel_name {
if message.sender != user {
IrcResponse {
sender: Some(message.sender.hostmask()),
command: "PRIVMSG".into(),
arguments: Vec::new(),
message: message.text,
receiver: Some(channel_name),
}
.send("", writer, true)
.await?;
}
}
}
Message::ChanJoinMessage(message) => {
if message.channel.joined_users.contains(user_wrapped) || message.sender == user {
let channel = message.channel.clone();
IrcResponse {
sender: Some(message.sender.hostmask().clone()),
command: "JOIN".into(),
arguments: Vec::new(),
message: message.channel.name.clone(),
receiver: None,
}
.send("", writer, true)
.await?;
channel
.send_topic(user_wrapped.clone(), writer, hostname)
.await
.unwrap();
channel
.names_list_send(user_wrapped.clone(), &channel, writer, hostname)
.await
.unwrap();
}
}
Message::NetJoinMessage(_) => {} // we don't care about these here :)
}
Ok(())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::userid_gen; use crate::userid_gen;

View file

@ -2,7 +2,6 @@ use std::collections::HashMap;
use crate::{ use crate::{
SENDER, SENDER,
commands::IrcMessage,
messages::Message, messages::Message,
sender::IrcResponse, sender::IrcResponse,
ts6::{ ts6::{