From 89f85b4fee3aa14a16fb3f781377507bc7c66147 Mon Sep 17 00:00:00 2001 From: Nikita Vilunov Date: Mon, 13 Feb 2023 18:08:37 +0100 Subject: [PATCH] handle join and privmsg irc commands --- src/projections/irc.rs | 31 ++++++++++-- src/protos/irc/client.rs | 38 +++++++++------ src/protos/irc/mod.rs | 103 +++++++++++++++++++++++++++++++++++++++ src/protos/irc/server.rs | 10 ++++ 4 files changed, 163 insertions(+), 19 deletions(-) diff --git a/src/projections/irc.rs b/src/projections/irc.rs index cfa7a89..1459d69 100644 --- a/src/projections/irc.rs +++ b/src/projections/irc.rs @@ -7,12 +7,12 @@ use tokio::net::tcp::{ReadHalf, WriteHalf}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::oneshot::channel; -use crate::core::player::{PlayerId, PlayerRegistry}; +use crate::core::player::{PlayerId, PlayerRegistry, Updates}; use crate::core::room::RoomId; use crate::prelude::*; use crate::protos::irc::client::{client_message, ClientMessage}; use crate::protos::irc::server::{ServerMessage, ServerMessageBody}; -use crate::protos::irc::Chan; +use crate::protos::irc::{Chan, Recipient}; use crate::util::Terminator; #[derive(Deserialize, Debug, Clone)] @@ -134,6 +134,7 @@ async fn handle_registered_socket<'a>( let mut user_handle = players .get_or_create_player(PlayerId(user.nickname.clone())) .await; + let mut connnection = user_handle.subscribe().await; ServerMessage { tags: vec![], @@ -221,7 +222,18 @@ async fn handle_registered_socket<'a>( }, Chan::Local(_) => {}, }; - } + }, + ClientMessage::PrivateMessage { recipient, body } => { + match recipient { + Recipient::Chan(Chan::Global(room)) => { + match String::from_utf8(body) { + Ok(body) => user_handle.send_message(RoomId(room.clone()), body.clone()).await, + Err(err) => log::warn!("failed to parse incoming message: {err}"), + } + }, + _ => log::warn!("Unsupported target type"), + } + }, _ => {}, } }, @@ -231,6 +243,19 @@ async fn handle_registered_socket<'a>( } buffer.clear(); }, + update = connnection.recv() => { + match update.unwrap() { + Updates::RoomJoined { room_id } => {}, + Updates::NewMessage { room_id, body } => { + ServerMessage { + tags: vec![], + sender: None, + body: ServerMessageBody::PrivateMessage { target: Recipient::Chan(Chan::Global(room_id.0)), body: body.as_bytes().to_vec() } + }.write_async(&mut writer).await?; + writer.flush().await? + }, + } + } } } Ok(()) diff --git a/src/protos/irc/client.rs b/src/protos/irc/client.rs index 3e56a12..c754e78 100644 --- a/src/protos/irc/client.rs +++ b/src/protos/irc/client.rs @@ -4,29 +4,24 @@ use super::*; #[derive(Clone, Debug, PartialEq, Eq)] pub enum ClientMessage { /// CAP. Capability-related commands. - Capability { - subcommand: CapabilitySubcommand, - }, + Capability { subcommand: CapabilitySubcommand }, /// PING - Ping { - token: ByteVec, - }, + Ping { token: ByteVec }, /// PONG - Pong { - token: ByteVec, - }, - /// NICK - Nick { - nickname: ByteVec, - }, + Pong { token: ByteVec }, + /// NICK + Nick { nickname: ByteVec }, + /// USER 0 * : User { username: ByteVec, realname: ByteVec, }, + /// JOIN Join(Chan), - Quit { - reason: ByteVec, - }, + /// PRIVMSG : + PrivateMessage { recipient: Recipient, body: ByteVec }, + /// QUIT : + Quit { reason: ByteVec }, } pub fn client_message(input: &[u8]) -> IResult<&[u8], ClientMessage> { @@ -37,6 +32,7 @@ pub fn client_message(input: &[u8]) -> IResult<&[u8], ClientMessage> { client_message_nick, client_message_user, client_message_join, + client_message_privmsg, client_message_quit, ))(input) } @@ -107,6 +103,16 @@ fn client_message_join(input: &[u8]) -> IResult<&[u8], ClientMessage> { Ok((input, ClientMessage::Join(chan))) } +fn client_message_privmsg(input: &[u8]) -> IResult<&[u8], ClientMessage> { + let (input, _) = tag("PRIVMSG ")(input)?; + let (input, recipient) = recipient(input)?; + let (input, _) = tag(" :")(input)?; + let (input, body) = token(input)?; + + let body = body.to_vec(); + Ok((input, ClientMessage::PrivateMessage { recipient, body })) +} + fn client_message_quit(input: &[u8]) -> IResult<&[u8], ClientMessage> { let (input, _) = tag("QUIT :")(input)?; let (input, reason) = token(input)?; diff --git a/src/protos/irc/mod.rs b/src/protos/irc/mod.rs index 06f5434..f563f56 100644 --- a/src/protos/irc/mod.rs +++ b/src/protos/irc/mod.rs @@ -2,11 +2,14 @@ pub mod client; pub mod server; +use std::io::Result; + use nom::{ branch::alt, bytes::complete::{tag, take, take_while}, IResult, }; +use tokio::io::{AsyncWrite, AsyncWriteExt}; type ByteVec = Vec; @@ -32,6 +35,21 @@ pub enum Chan { /// & — server-local channel, available only to connections to the same server. Rarely used in practice. Local(ByteVec), } +impl Chan { + pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> Result<()> { + match self { + Chan::Global(name) => { + writer.write_all(b"#").await?; + writer.write_all(&name).await?; + } + Chan::Local(name) => { + writer.write_all(b"&").await?; + writer.write_all(&name).await?; + } + } + Ok(()) + } +} fn chan(input: &[u8]) -> IResult<&[u8], Chan> { fn chan_global(input: &[u8]) -> IResult<&[u8], Chan> { @@ -48,3 +66,88 @@ fn chan(input: &[u8]) -> IResult<&[u8], Chan> { alt((chan_global, chan_local))(input) } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Recipient { + Nick(ByteVec), + Chan(Chan), +} +impl Recipient { + pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> Result<()> { + match self { + Recipient::Nick(nick) => writer.write_all(&nick).await?, + Recipient::Chan(chan) => chan.write_async(writer).await?, + } + Ok(()) + } +} + +fn recipient(input: &[u8]) -> IResult<&[u8], Recipient> { + fn recipient_chan(input: &[u8]) -> IResult<&[u8], Recipient> { + let (input, chan) = chan(input)?; + Ok((input, Recipient::Chan(chan))) + } + + fn recipient_nick(input: &[u8]) -> IResult<&[u8], Recipient> { + let (input, nick) = receiver(input)?; + Ok((input, Recipient::Nick(nick.to_vec()))) + } + + alt((recipient_chan, recipient_nick))(input) +} + +#[cfg(test)] +mod test { + use assert_matches::*; + + use super::*; + use crate::util::testkit::*; + + #[test] + fn test_chan_global() { + let input = b"#testchan"; + let expected = Chan::Global(b"testchan".to_vec()); + + let result = chan(input); + assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result)); + + let mut bytes = vec![]; + sync_future(expected.write_async(&mut bytes)) + .unwrap() + .unwrap(); + + assert_eq!(bytes.as_slice(), input); + } + + #[test] + fn test_chan_local() { + let input = b"&localchan"; + let expected = Chan::Local(b"localchan".to_vec()); + + let result = chan(input); + assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result)); + + let mut bytes = vec![]; + sync_future(expected.write_async(&mut bytes)) + .unwrap() + .unwrap(); + + assert_eq!(bytes.as_slice(), input); + } + + #[test] + fn test_recipient_user() { + let input = b"User"; + let expected = Recipient::Nick(b"User".to_vec()); + + let result = recipient(input); + assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result)); + + let mut bytes = vec![]; + sync_future(expected.write_async(&mut bytes)) + .unwrap() + .unwrap(); + + assert_eq!(bytes.as_slice(), input); + } +} diff --git a/src/protos/irc/server.rs b/src/protos/irc/server.rs index dd53a50..1f6765b 100644 --- a/src/protos/irc/server.rs +++ b/src/protos/irc/server.rs @@ -55,6 +55,10 @@ pub enum ServerMessageBody { from: ByteVec, token: ByteVec, }, + PrivateMessage { + target: Recipient, + body: ByteVec, + }, N001Welcome { client: ByteVec, text: ByteVec, @@ -102,6 +106,12 @@ impl ServerMessageBody { writer.write_all(b" :").await?; writer.write_all(&token).await?; } + ServerMessageBody::PrivateMessage { target, body } => { + writer.write_all(b"PRIVMSG ").await?; + target.write_async(writer).await?; + writer.write_all(b" :").await?; + writer.write_all(&body).await?; + } ServerMessageBody::N001Welcome { client, text } => { writer.write_all(b"001 ").await?; writer.write_all(&client).await?;