diff --git a/src/core/player.rs b/src/core/player.rs index 7a4a984..896a9ec 100644 --- a/src/core/player.rs +++ b/src/core/player.rs @@ -79,7 +79,12 @@ impl PlayerConnection { pub async fn leave_room(&mut self, room_id: RoomId) -> Result<()> { let (promise, deferred) = oneshot(); - self.player_handle.send(PlayerCommand::Cmd(Cmd::LeaveRoom { room_id, promise }, self.connection_id.clone())).await; + self.player_handle + .send(PlayerCommand::Cmd( + Cmd::LeaveRoom { room_id, promise }, + self.connection_id.clone(), + )) + .await; Ok(deferred.await?) } @@ -346,7 +351,8 @@ impl Player { } promise.send(()); let update = Updates::RoomLeft { - room_id, former_member_id: self.player_id.clone(), + room_id, + former_member_id: self.player_id.clone(), }; self.broadcast_update(update, connection_id).await; } diff --git a/src/projections/irc/mod.rs b/src/projections/irc/mod.rs index 77e6fc3..863203d 100644 --- a/src/projections/irc/mod.rs +++ b/src/projections/irc/mod.rs @@ -11,7 +11,7 @@ use crate::core::player::{PlayerConnection, PlayerId, PlayerRegistry, Updates}; use crate::core::room::{RoomId, RoomInfo, RoomRegistry}; use crate::prelude::*; use crate::protos::irc::client::{client_message, ClientMessage}; -use crate::protos::irc::server::{ServerMessage, ServerMessageBody}; +use crate::protos::irc::server::{AwayStatus, ServerMessage, ServerMessageBody}; use crate::protos::irc::{Chan, Recipient}; use crate::util::Terminator; @@ -244,10 +244,13 @@ async fn handle_update( player_id: &PlayerId, writer: &mut (impl AsyncWrite + Unpin), rooms: &RoomRegistry, - update: Updates + update: Updates, ) -> Result<()> { match update { - Updates::RoomJoined { new_member_id, room_id } => { + Updates::RoomJoined { + new_member_id, + room_id, + } => { if player_id == &new_member_id { if let Some(room) = rooms.get_room(&room_id) { let room_info = room.get_room_info().await; @@ -262,26 +265,42 @@ async fn handle_update( tags: vec![], sender: Some(new_member_id.as_bytes().clone()), body: ServerMessageBody::Join(Chan::Global(room_id.as_bytes().clone())), - }.write_async(writer).await?; + } + .write_async(writer) + .await?; writer.flush().await? } - }, - Updates::RoomLeft { room_id, former_member_id } => { + } + Updates::RoomLeft { + room_id, + former_member_id, + } => { ServerMessage { tags: vec![], sender: Some(former_member_id.as_bytes().clone()), body: ServerMessageBody::Part(Chan::Global(room_id.as_bytes().clone())), - }.write_async(writer).await?; + } + .write_async(writer) + .await?; writer.flush().await? - }, - Updates::NewMessage { author_id, room_id, body } => { + } + Updates::NewMessage { + author_id, + room_id, + body, + } => { ServerMessage { tags: vec![], sender: Some(author_id.as_bytes().clone()), - body: ServerMessageBody::PrivateMessage { target: Recipient::Chan(Chan::Global(room_id.as_bytes().clone())), body: body.as_bytes().to_vec() } - }.write_async(writer).await?; + body: ServerMessageBody::PrivateMessage { + target: Recipient::Chan(Chan::Global(room_id.as_bytes().clone())), + body: body.as_bytes().to_vec(), + }, + } + .write_async(writer) + .await?; writer.flush().await? - }, + } Updates::RoomTopicChanged { room_id, new_topic } => { ServerMessage { tags: vec![], @@ -291,9 +310,11 @@ async fn handle_update( chat: Chan::Global(room_id.as_bytes().clone()), topic: new_topic, }, - }.write_async(writer).await?; + } + .write_async(writer) + .await?; writer.flush().await? - }, + } } Ok(()) } @@ -360,6 +381,46 @@ async fn handle_incoming_message( Chan::Local(_) => {} }; } + ClientMessage::Who { target } => match &target { + Recipient::Nick(nick) => { + if nick == &user.nickname { + let mut username = Vec::with_capacity(user.username.len() + 1); + username.push(b'~'); + username.extend_from_slice(user.username.as_slice()); + ServerMessage { + tags: vec![], + sender: Some(config.server_name.as_bytes().to_vec()), + body: ServerMessageBody::N352WhoReply { + client: user.nickname.clone(), + username, + host: b"*".to_vec(), // TODO need to decide what to send as user's host + server: config.server_name.as_bytes().to_vec(), + flags: AwayStatus::Here, + hops: 0, + realname: user.realname.clone(), + }, + } + .write_async(writer) + .await?; + ServerMessage { + tags: vec![], + sender: Some(config.server_name.as_bytes().to_vec()), + body: ServerMessageBody::N315EndOfWho { + client: user.nickname.clone(), + mask: target.clone(), + msg: b"End of WHO list".to_vec(), + }, + } + .write_async(writer) + .await?; + writer.flush().await?; + } + log::warn!("WHO for other users is not implemented") + } + Recipient::Chan(_) => { + log::warn!("WHO for chans not implemented") + } + }, cmd => { log::warn!("Not implemented handler for client command: {cmd:?}"); } diff --git a/src/protos/irc/client.rs b/src/protos/irc/client.rs index 2c8614d..6955b98 100644 --- a/src/protos/irc/client.rs +++ b/src/protos/irc/client.rs @@ -29,7 +29,9 @@ pub enum ClientMessage { /// MODE Mode(Chan), // TODO support not only chan /// WHO - Who(Chan), // TODO support not only chan + Who { + target: Recipient, // aka mask + }, /// TOPIC : Topic { chan: Chan, @@ -144,9 +146,9 @@ fn client_message_mode(input: &[u8]) -> IResult<&[u8], ClientMessage> { fn client_message_who(input: &[u8]) -> IResult<&[u8], ClientMessage> { let (input, _) = tag("WHO ")(input)?; - let (input, chan) = chan(input)?; + let (input, target) = recipient(input)?; - Ok((input, ClientMessage::Who(chan))) + Ok((input, ClientMessage::Who { target })) } fn client_message_topic(input: &[u8]) -> IResult<&[u8], ClientMessage> { diff --git a/src/protos/irc/server.rs b/src/protos/irc/server.rs index d81d351..bc5aeff 100644 --- a/src/protos/irc/server.rs +++ b/src/protos/irc/server.rs @@ -83,11 +83,32 @@ pub enum ServerMessageBody { client: ByteVec, params: ByteVec, // TODO make this a datatype }, + /// Final reply to a client's [Who](crate::protos::irc::client::ClientMessage::Who) request. + N315EndOfWho { + client: ByteVec, + mask: Recipient, + /// Usually `b"End of WHO list"` + msg: ByteVec, + }, N332Topic { client: ByteVec, chat: Chan, topic: ByteVec, }, + /// A reply to a client's [Who](crate::protos::irc::client::ClientMessage::Who) request. + N352WhoReply { + client: ByteVec, + // chan = * + username: ByteVec, + /// User's hostname + host: ByteVec, + /// Hostname of the server the user is connected to + server: ByteVec, + /// Flags + flags: AwayStatus, + hops: u8, + realname: ByteVec, + }, N353NamesReply { client: ByteVec, chan: Chan, @@ -177,6 +198,14 @@ impl ServerMessageBody { writer.write_all(¶ms).await?; writer.write_all(b" :are supported by this server").await?; } + ServerMessageBody::N315EndOfWho { client, mask, msg } => { + writer.write_all(b"315 ").await?; + writer.write_all(&client).await?; + writer.write_all(b" ").await?; + mask.write_async(writer).await?; + writer.write_all(b" :").await?; + writer.write_all(&msg).await?; + } ServerMessageBody::N332Topic { client, chat, @@ -189,6 +218,33 @@ impl ServerMessageBody { writer.write_all(b" :").await?; writer.write_all(&topic).await?; } + ServerMessageBody::N352WhoReply { + client, + username, + host, + server, + flags, + hops, + realname, + } => { + writer.write_all(b"352 ").await?; + writer.write_all(&client).await?; + writer.write_all(b" * ").await?; + writer.write_all(&username).await?; + writer.write_all(b" ").await?; + writer.write_all(&host).await?; + writer.write_all(b" ").await?; + writer.write_all(&server).await?; + writer.write_all(b" ").await?; + match flags { + AwayStatus::Here => writer.write_all(b"H").await?, + AwayStatus::Gone => writer.write_all(b"G").await?, + } + writer.write_all(b" :").await?; + writer.write_all(hops.to_string().as_bytes()).await?; + writer.write_all(b" ").await?; + writer.write_all(&realname).await?; + } ServerMessageBody::N353NamesReply { client, chan, @@ -213,6 +269,12 @@ impl ServerMessageBody { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum AwayStatus { + Here, + Gone, +} + fn server_message_body(input: &[u8]) -> IResult<&[u8], ServerMessageBody> { alt(( server_message_body_notice,