forked from lavina/lavina
handle join and privmsg irc commands
This commit is contained in:
parent
20b461e81c
commit
89f85b4fee
|
@ -7,12 +7,12 @@ use tokio::net::tcp::{ReadHalf, WriteHalf};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::sync::oneshot::channel;
|
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::core::room::RoomId;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::protos::irc::client::{client_message, ClientMessage};
|
use crate::protos::irc::client::{client_message, ClientMessage};
|
||||||
use crate::protos::irc::server::{ServerMessage, ServerMessageBody};
|
use crate::protos::irc::server::{ServerMessage, ServerMessageBody};
|
||||||
use crate::protos::irc::Chan;
|
use crate::protos::irc::{Chan, Recipient};
|
||||||
use crate::util::Terminator;
|
use crate::util::Terminator;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
@ -134,6 +134,7 @@ async fn handle_registered_socket<'a>(
|
||||||
let mut user_handle = players
|
let mut user_handle = players
|
||||||
.get_or_create_player(PlayerId(user.nickname.clone()))
|
.get_or_create_player(PlayerId(user.nickname.clone()))
|
||||||
.await;
|
.await;
|
||||||
|
let mut connnection = user_handle.subscribe().await;
|
||||||
|
|
||||||
ServerMessage {
|
ServerMessage {
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
|
@ -221,7 +222,18 @@ async fn handle_registered_socket<'a>(
|
||||||
},
|
},
|
||||||
Chan::Local(_) => {},
|
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();
|
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(())
|
Ok(())
|
||||||
|
|
|
@ -4,29 +4,24 @@ use super::*;
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ClientMessage {
|
pub enum ClientMessage {
|
||||||
/// CAP. Capability-related commands.
|
/// CAP. Capability-related commands.
|
||||||
Capability {
|
Capability { subcommand: CapabilitySubcommand },
|
||||||
subcommand: CapabilitySubcommand,
|
|
||||||
},
|
|
||||||
/// PING <token>
|
/// PING <token>
|
||||||
Ping {
|
Ping { token: ByteVec },
|
||||||
token: ByteVec,
|
|
||||||
},
|
|
||||||
/// PONG <token>
|
/// PONG <token>
|
||||||
Pong {
|
Pong { token: ByteVec },
|
||||||
token: ByteVec,
|
/// NICK <nickname>
|
||||||
},
|
Nick { nickname: ByteVec },
|
||||||
/// NICK <name>
|
/// USER <username> 0 * :<realname>
|
||||||
Nick {
|
|
||||||
nickname: ByteVec,
|
|
||||||
},
|
|
||||||
User {
|
User {
|
||||||
username: ByteVec,
|
username: ByteVec,
|
||||||
realname: ByteVec,
|
realname: ByteVec,
|
||||||
},
|
},
|
||||||
|
/// JOIN <chan>
|
||||||
Join(Chan),
|
Join(Chan),
|
||||||
Quit {
|
/// PRIVMSG <target> :<msg>
|
||||||
reason: ByteVec,
|
PrivateMessage { recipient: Recipient, body: ByteVec },
|
||||||
},
|
/// QUIT :<reason>
|
||||||
|
Quit { reason: ByteVec },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn client_message(input: &[u8]) -> IResult<&[u8], ClientMessage> {
|
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_nick,
|
||||||
client_message_user,
|
client_message_user,
|
||||||
client_message_join,
|
client_message_join,
|
||||||
|
client_message_privmsg,
|
||||||
client_message_quit,
|
client_message_quit,
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
@ -107,6 +103,16 @@ fn client_message_join(input: &[u8]) -> IResult<&[u8], ClientMessage> {
|
||||||
Ok((input, ClientMessage::Join(chan)))
|
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> {
|
fn client_message_quit(input: &[u8]) -> IResult<&[u8], ClientMessage> {
|
||||||
let (input, _) = tag("QUIT :")(input)?;
|
let (input, _) = tag("QUIT :")(input)?;
|
||||||
let (input, reason) = token(input)?;
|
let (input, reason) = token(input)?;
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
|
use std::io::Result;
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{tag, take, take_while},
|
bytes::complete::{tag, take, take_while},
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||||
|
|
||||||
type ByteVec = Vec<u8>;
|
type ByteVec = Vec<u8>;
|
||||||
|
|
||||||
|
@ -32,6 +35,21 @@ pub enum Chan {
|
||||||
/// &<name> — server-local channel, available only to connections to the same server. Rarely used in practice.
|
/// &<name> — server-local channel, available only to connections to the same server. Rarely used in practice.
|
||||||
Local(ByteVec),
|
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(input: &[u8]) -> IResult<&[u8], Chan> {
|
||||||
fn chan_global(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)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -55,6 +55,10 @@ pub enum ServerMessageBody {
|
||||||
from: ByteVec,
|
from: ByteVec,
|
||||||
token: ByteVec,
|
token: ByteVec,
|
||||||
},
|
},
|
||||||
|
PrivateMessage {
|
||||||
|
target: Recipient,
|
||||||
|
body: ByteVec,
|
||||||
|
},
|
||||||
N001Welcome {
|
N001Welcome {
|
||||||
client: ByteVec,
|
client: ByteVec,
|
||||||
text: ByteVec,
|
text: ByteVec,
|
||||||
|
@ -102,6 +106,12 @@ impl ServerMessageBody {
|
||||||
writer.write_all(b" :").await?;
|
writer.write_all(b" :").await?;
|
||||||
writer.write_all(&token).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 } => {
|
ServerMessageBody::N001Welcome { client, text } => {
|
||||||
writer.write_all(b"001 ").await?;
|
writer.write_all(b"001 ").await?;
|
||||||
writer.write_all(&client).await?;
|
writer.write_all(&client).await?;
|
||||||
|
|
Loading…
Reference in New Issue