//! Handling of all client2server presence stanzas use anyhow::Result; use quick_xml::events::Event; use serde::Serialize; use lavina_core::room::RoomId; use proto_xmpp::bind::{Jid, Name, Resource, Server}; use proto_xmpp::client::Presence; use proto_xmpp::muc::{Delay, XUser, XmppHistoryMessage}; use proto_xmpp::xml::{Ignore, ToXml}; use crate::XmppConnection; impl<'a> XmppConnection<'a> { pub async fn handle_presence(&mut self, output: &mut Vec>, p: Presence) -> Result<()> { match p.to { None => { self.self_presence(output, p.r#type.as_deref()).await; } Some(Jid { name: Some(name), server, // resources in MUCs are members' personas – not implemented (yet?) resource: Some(_), }) if server.0 == self.hostname_rooms => { let muc_presence = self.retrieve_muc_presence(&name).await?; muc_presence.serialize(output); let messages = self.retrieve_message_history(&name).await?; for message in messages { message.serialize(output) } } _ => { // TODO other presence cases let response = Presence::<()>::default(); response.serialize(output); } } Ok(()) } async fn self_presence(&mut self, output: &mut Vec>, r#type: Option<&str>) { match r#type { Some("unavailable") => { // do not print anything } None => { let response = Presence::<()> { to: Some(Jid { name: Some(self.user.xmpp_name.clone()), server: Server(self.hostname.clone()), resource: Some(self.user.xmpp_resource.clone()), }), from: Some(Jid { name: Some(self.user.xmpp_name.clone()), server: Server(self.hostname.clone()), resource: Some(self.user.xmpp_resource.clone()), }), ..Default::default() }; response.serialize(output); } _ => todo!(), } } async fn retrieve_muc_presence(&mut self, name: &Name) -> Result<(Presence)> { let a = self.user_handle.join_room(RoomId::try_from(name.0.clone())?).await?; // TODO handle bans let response = Presence { to: Some(Jid { name: Some(self.user.xmpp_name.clone()), server: Server(self.hostname.clone()), resource: Some(self.user.xmpp_resource.clone()), }), from: Some(Jid { name: Some(name.clone()), server: Server(self.hostname_rooms.clone()), resource: Some(self.user.xmpp_muc_name.clone()), }), custom: vec![XUser], ..Default::default() }; Ok(response) } /* Retrieve a room's message history. The output can be serialized into a stream of XML stanzas. Example of such a stanza: */ async fn retrieve_message_history(&self, room_name: &Name) -> Result<(Vec)> { let room_id = RoomId::try_from(room_name.0.clone())?; let history_messages = self.user_handle.get_room_message_history(room_id).await?; let mut response = vec![]; for history_message in history_messages.into_iter() { response.push(XmppHistoryMessage { id: history_message.id.to_string(), to: Jid { name: Option::from(Name(self.user.xmpp_muc_name.0.clone().into())), server: Server(self.hostname.clone()), resource: None, }, from: Jid { name: Option::from(room_name.clone()), server: Server(self.hostname_rooms.clone()), resource: Option::from(Resource(history_message.author.name.clone().into())), }, delay: Delay::new( Jid { name: Option::from(Name(history_message.author.name.clone().into())), server: Server(self.hostname_rooms.clone()), resource: None, }, history_message.created_at.to_rfc3339(), ), body: history_message.content.clone(), }); tracing::info!( "Retrieved message: {:?} {:?}", history_message.author, history_message.content.clone() ); } return Ok(response); } } #[cfg(test)] mod tests { use anyhow::Result; use lavina_core::player::PlayerId; use proto_xmpp::bind::{Jid, Name, Resource, Server}; use proto_xmpp::client::Presence; use proto_xmpp::muc::XUser; use crate::testkit::{expect_user_authenticated, TestServer}; use crate::Authenticated; #[tokio::test] async fn test_muc_joining() -> Result<()> { let server = TestServer::start().await.unwrap(); server.core.create_player(&PlayerId::from("tester")?).await?; let player_id = PlayerId::from("tester").unwrap(); let user = Authenticated { player_id, xmpp_name: Name("tester".into()), xmpp_resource: Resource("tester".into()), xmpp_muc_name: Resource("tester".into()), }; let mut player_conn = server.core.connect_to_player(&user.player_id).await; let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await.unwrap(); let response = conn.retrieve_muc_presence(&user.xmpp_name).await.unwrap(); let expected = Presence { to: Some(Jid { name: Some(conn.user.xmpp_name.clone()), server: Server(conn.hostname.clone()), resource: Some(conn.user.xmpp_resource.clone()), }), from: Some(Jid { name: Some(user.xmpp_name.clone()), server: Server(conn.hostname_rooms.clone()), resource: Some(conn.user.xmpp_muc_name.clone()), }), custom: vec![XUser], ..Default::default() }; assert_eq!(expected, response); server.shutdown().await.unwrap(); Ok(()) } // Test that joining a room second time after a server restart, // i.e. in-memory cache of memberships is cleaned, does not cause any issues. #[tokio::test] async fn test_muc_joining_twice() -> Result<()> { let server = TestServer::start().await.unwrap(); server.core.create_player(&PlayerId::from("tester")?).await?; let player_id = PlayerId::from("tester").unwrap(); let user = Authenticated { player_id, xmpp_name: Name("tester".into()), xmpp_resource: Resource("tester".into()), xmpp_muc_name: Resource("tester".into()), }; let mut player_conn = server.core.connect_to_player(&user.player_id).await; let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await.unwrap(); let response = conn.retrieve_muc_presence(&user.xmpp_name).await.unwrap(); let expected = Presence { to: Some(Jid { name: Some(conn.user.xmpp_name.clone()), server: Server(conn.hostname.clone()), resource: Some(conn.user.xmpp_resource.clone()), }), from: Some(Jid { name: Some(user.xmpp_name.clone()), server: Server(conn.hostname_rooms.clone()), resource: Some(conn.user.xmpp_muc_name.clone()), }), custom: vec![XUser], ..Default::default() }; assert_eq!(expected, response); drop(conn); let server = server.reboot().await.unwrap(); let mut player_conn = server.core.connect_to_player(&user.player_id).await; let mut conn = expect_user_authenticated(&server, &user, &mut player_conn).await.unwrap(); let response = conn.retrieve_muc_presence(&user.xmpp_name).await.unwrap(); assert_eq!(expected, response); server.shutdown().await.unwrap(); Ok(()) } }