irc: move CHATHISTORY message handling into a separate module

This commit is contained in:
Nikita Vilunov 2024-06-05 02:59:22 +02:00
parent a22cde0ea8
commit 25fe041698
5 changed files with 85 additions and 61 deletions

View File

@ -3,15 +3,15 @@ use std::future::Future;
use anyhow::Result; use anyhow::Result;
use tokio::io::AsyncWrite; use tokio::io::AsyncWrite;
use crate::RegisteredUser;
use lavina_core::player::PlayerConnection; use lavina_core::player::PlayerConnection;
use lavina_core::prelude::Str; use lavina_core::prelude::Str;
pub struct IrcConnection<'a, T: AsyncWrite + Unpin> { pub struct IrcConnection<'a, T: AsyncWrite + Unpin> {
pub server_name: Str, pub server_name: Str,
/// client is nick of requester
pub client: Str,
pub writer: &'a mut T, pub writer: &'a mut T,
pub player_connection: &'a mut PlayerConnection, pub player_connection: &'a mut PlayerConnection,
pub user: &'a RegisteredUser,
} }
/// Represents a client-to-server IRC message that can be handled by the server. /// Represents a client-to-server IRC message that can be handled by the server.

View File

@ -0,0 +1,57 @@
use anyhow::Result;
use chrono::SecondsFormat;
use tokio::io::{AsyncWrite, AsyncWriteExt};
use crate::cap::Capabilities;
use crate::handler::{IrcCommand, IrcConnection};
use lavina_core::player::RoomHistoryResult;
use lavina_core::room::RoomId;
use proto_irc::client::ChatHistory;
use proto_irc::server::{ServerMessage, ServerMessageBody};
use proto_irc::{Chan, Recipient, Tag};
impl IrcCommand for ChatHistory {
async fn handle_with(&self, conn: &mut IrcConnection<'_, impl AsyncWrite + Unpin>) -> Result<()> {
if !conn.user.enabled_capabilities.contains(Capabilities::ChatHistory) {
tracing::debug!(
"Requested chat history for user {:?} even though the capability was not negotiated",
conn.user.nickname
);
return Ok(());
}
let channel_name = match self.chan.clone() {
Chan::Global(chan) => chan,
// TODO Respond with an error when a local channel is requested
Chan::Local(chan) => chan,
};
let room_id = &RoomId::try_from(channel_name.clone())?;
let res = conn.player_connection.get_room_message_history(room_id, self.limit).await?;
match res {
RoomHistoryResult::Success(messages) => {
for message in messages {
let mut tags = vec![];
if conn.user.enabled_capabilities.contains(Capabilities::ServerTime) {
let tag = Tag {
key: "time".into(),
value: Some(message.created_at.to_rfc3339_opts(SecondsFormat::Millis, true).into()),
};
tags.push(tag);
}
ServerMessage {
tags,
sender: Some(message.author_name.into()),
body: ServerMessageBody::PrivateMessage {
target: Recipient::Chan(self.chan.clone()),
body: message.content.into(),
},
}
.write_async(conn.writer)
.await?;
}
conn.writer.flush().await?;
}
RoomHistoryResult::NoSuchRoom => {}
}
Ok(())
}
}

View File

@ -14,6 +14,8 @@ use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
use tokio::sync::mpsc::channel; use tokio::sync::mpsc::channel;
use crate::cap::Capabilities;
use crate::handler::{IrcCommand, IrcConnection};
use lavina_core::auth::Verdict; use lavina_core::auth::Verdict;
use lavina_core::player::*; use lavina_core::player::*;
use lavina_core::prelude::*; use lavina_core::prelude::*;
@ -29,14 +31,9 @@ use proto_irc::{Chan, Recipient, Tag};
use sasl::AuthBody; use sasl::AuthBody;
mod cap; mod cap;
use handler::{IrcCommand, IrcConnection};
mod whois;
use crate::cap::Capabilities;
mod handler; mod handler;
mod history;
mod whois;
pub const APP_VERSION: &str = concat!("lavina", "_", env!("CARGO_PKG_VERSION")); pub const APP_VERSION: &str = concat!("lavina", "_", env!("CARGO_PKG_VERSION"));
@ -829,9 +826,9 @@ async fn handle_incoming_message(
ClientMessage::Whois(cmd) => { ClientMessage::Whois(cmd) => {
let mut conn = IrcConnection { let mut conn = IrcConnection {
server_name: config.server_name.clone(), server_name: config.server_name.clone(),
client: user.nickname.clone(),
writer, writer,
player_connection: user_handle, player_connection: user_handle,
user,
}; };
cmd.handle_with(&mut conn).await?; cmd.handle_with(&mut conn).await?;
writer.flush().await?; writer.flush().await?;
@ -874,49 +871,16 @@ async fn handle_incoming_message(
log::info!("Received QUIT"); log::info!("Received QUIT");
return Ok(HandleResult::Leave); return Ok(HandleResult::Leave);
} }
ClientMessage::ChatHistory { chan, limit } => { ClientMessage::ChatHistory(cmd) => {
if user.enabled_capabilities.contains(Capabilities::ChatHistory) { let mut conn = IrcConnection {
let channel_name = match chan.clone() { server_name: config.server_name.clone(),
Chan::Global(chan) => chan, writer,
// TODO Respond with an error when a local channel is requested player_connection: user_handle,
Chan::Local(chan) => chan, user,
}; };
let room_id = &RoomId::try_from(channel_name.clone())?; cmd.handle_with(&mut conn).await?;
let res = user_handle.get_room_message_history(room_id, limit).await?;
match res {
RoomHistoryResult::Success(messages) => {
for message in messages {
let mut tags = vec![];
if user.enabled_capabilities.contains(Capabilities::ServerTime) {
let tag = Tag {
key: "time".into(),
value: Some(
message.created_at.to_rfc3339_opts(SecondsFormat::Millis, true).into(),
),
};
tags.push(tag);
}
ServerMessage {
tags,
sender: Some(message.author_name.into()),
body: ServerMessageBody::PrivateMessage {
target: Recipient::Chan(chan.clone()),
body: message.content.into(),
},
}
.write_async(writer)
.await?;
}
writer.flush().await?; writer.flush().await?;
} }
RoomHistoryResult::NoSuchRoom => {}
}
} else {
log::warn!(
"Requested chat history for user {user:?} even though the capability was not negotiated"
);
}
}
cmd => { cmd => {
log::warn!("Not implemented handler for client command: {cmd:?}"); log::warn!("Not implemented handler for client command: {cmd:?}");
} }

View File

@ -35,7 +35,7 @@ async fn handle_nick_target(nick: Str, conn: &mut IrcConnection<'_, impl AsyncWr
GetInfoResult::UserDoesntExist => { GetInfoResult::UserDoesntExist => {
IrcResponseMessage::empty_tags( IrcResponseMessage::empty_tags(
Some(conn.server_name.clone()), Some(conn.server_name.clone()),
ErrNoSuchNick401::new(conn.client.clone(), nick.clone()), ErrNoSuchNick401::new(conn.user.nickname.clone(), nick.clone()),
) )
.write_response(conn.writer) .write_response(conn.writer)
.await? .await?
@ -43,7 +43,7 @@ async fn handle_nick_target(nick: Str, conn: &mut IrcConnection<'_, impl AsyncWr
} }
IrcResponseMessage::empty_tags( IrcResponseMessage::empty_tags(
Some(conn.server_name.clone()), Some(conn.server_name.clone()),
RplEndOfWhois318::new(conn.client.clone(), nick.clone()), RplEndOfWhois318::new(conn.user.nickname.clone(), nick.clone()),
) )
.write_response(conn.writer) .write_response(conn.writer)
.await?; .await?;

View File

@ -63,10 +63,7 @@ pub enum ClientMessage {
reason: Str, reason: Str,
}, },
Authenticate(Str), Authenticate(Str),
ChatHistory { ChatHistory(ChatHistory),
chan: Chan,
limit: u32,
},
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -76,6 +73,12 @@ pub enum Whois {
EmptyArgs, EmptyArgs,
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChatHistory {
pub chan: Chan,
pub limit: u32,
}
pub fn client_message(input: &str) -> Result<ClientMessage> { pub fn client_message(input: &str) -> Result<ClientMessage> {
let res = all_consuming(alt(( let res = all_consuming(alt((
client_message_capability, client_message_capability,
@ -273,7 +276,7 @@ fn client_message_chathistory(input: &str) -> IResult<&str, ClientMessage> {
let (input, _) = tag(" * ")(input)?; let (input, _) = tag(" * ")(input)?;
let (input, limit) = limit(input)?; let (input, limit) = limit(input)?;
Ok((input, ClientMessage::ChatHistory { chan, limit })) Ok((input, ClientMessage::ChatHistory(ChatHistory { chan, limit })))
} }
fn limit(input: &str) -> IResult<&str, u32> { fn limit(input: &str) -> IResult<&str, u32> {
@ -514,10 +517,10 @@ mod test {
#[test] #[test]
fn test_client_chat_history_latest() { fn test_client_chat_history_latest() {
let input = "CHATHISTORY LATEST #chan * 10"; let input = "CHATHISTORY LATEST #chan * 10";
let expected = ClientMessage::ChatHistory { let expected = ClientMessage::ChatHistory(ChatHistory {
chan: Chan::Global("chan".into()), chan: Chan::Global("chan".into()),
limit: 10, limit: 10,
}; });
let result = client_message(input); let result = client_message(input);
assert_matches!(result, Ok(result) => assert_eq!(expected, result)); assert_matches!(result, Ok(result) => assert_eq!(expected, result));