use std::str::FromStr; use chrono::Utc; use lampada::{Connected, SupervisorSender}; use stanza::client::Stanza; use uuid::Uuid; use crate::{ UpdateMessage, chat::{Body, Message}, error::{Error, IqError, MessageRecvError, PresenceError, RosterError}, presence::{Offline, Online, Presence, PresenceType, Show}, roster::Contact, }; use super::ClientLogic; pub async fn handle_stanza( logic: ClientLogic, stanza: Stanza, connection: Connected, supervisor: SupervisorSender, ) { match stanza { Stanza::Message(stanza_message) => { if let Some(mut from) = stanza_message.from { // TODO: don't ignore delay from. xep says SHOULD send error if incorrect. let timestamp = stanza_message .delay .map(|delay| delay.stamp) .unwrap_or_else(|| Utc::now()); // TODO: group chat messages let mut message = Message { id: stanza_message .id // TODO: proper id storage .map(|id| Uuid::from_str(&id).unwrap_or_else(|_| Uuid::new_v4())) .unwrap_or_else(|| Uuid::new_v4()), from: from.clone(), timestamp, body: Body { // TODO: should this be an option? body: stanza_message .body .map(|body| body.body) .unwrap_or_default() .unwrap_or_default(), }, }; // TODO: can this be more efficient? let result = logic .db() .create_message_with_user_resource_and_chat(message.clone(), from.clone()) .await; if let Err(e) = result { tracing::error!("messagecreate"); let _ = logic .update_sender() .send(UpdateMessage::Error(Error::MessageRecv( MessageRecvError::MessageHistory(e.into()), ))) .await; } message.from = message.from.as_bare(); from = from.as_bare(); let _ = logic .update_sender() .send(UpdateMessage::Message { to: from, message }) .await; } else { let _ = logic .update_sender() .send(UpdateMessage::Error(Error::MessageRecv( MessageRecvError::MissingFrom, ))) .await; } } Stanza::Presence(presence) => { if let Some(from) = presence.from { match presence.r#type { Some(r#type) => match r#type { // error processing a presence from somebody stanza::client::presence::PresenceType::Error => { // TODO: is there any other information that should go with the error? also MUST have an error, otherwise it's a different error. maybe it shoulnd't be an option. let _ = logic .update_sender() .send(UpdateMessage::Error(Error::Presence( // TODO: ughhhhhhhhhhhhh these stanza errors should probably just have an option, and custom display PresenceError::StanzaError( presence .errors .first() .cloned() .expect("error MUST have error"), ), ))) .await; } // should not happen (error to server) stanza::client::presence::PresenceType::Probe => { // TODO: should probably write an error and restart stream let _ = logic .update_sender() .send(UpdateMessage::Error(Error::Presence( PresenceError::Unsupported, ))) .await; } stanza::client::presence::PresenceType::Subscribe => { // may get a subscription request from somebody who is not a contact!!! therefore should be its own kind of event let _ = logic .update_sender() .send(UpdateMessage::SubscriptionRequest(from)) .await; } stanza::client::presence::PresenceType::Unavailable => { let offline = Offline { status: presence.status.map(|status| status.status.0), }; let timestamp = presence .delay .map(|delay| delay.stamp) .unwrap_or_else(|| Utc::now()); let _ = logic .update_sender() .send(UpdateMessage::Presence { from, presence: Presence { timestamp, presence: PresenceType::Offline(offline), }, }) .await; } // for now, do nothing, as these are simply informational. will receive roster push from the server regarding the changes to do with them. stanza::client::presence::PresenceType::Subscribed => {} stanza::client::presence::PresenceType::Unsubscribe => {} stanza::client::presence::PresenceType::Unsubscribed => {} }, None => { let online = Online { show: presence.show.map(|show| match show { stanza::client::presence::Show::Away => Show::Away, stanza::client::presence::Show::Chat => Show::Chat, stanza::client::presence::Show::Dnd => Show::DoNotDisturb, stanza::client::presence::Show::Xa => Show::ExtendedAway, }), status: presence.status.map(|status| status.status.0), priority: presence.priority.map(|priority| priority.0), }; let timestamp = presence .delay .map(|delay| delay.stamp) .unwrap_or_else(|| Utc::now()); let _ = logic .update_sender() .send(UpdateMessage::Presence { from, presence: Presence { timestamp, presence: PresenceType::Online(online), }, }) .await; } } } else { let _ = logic .update_sender() .send(UpdateMessage::Error(Error::Presence( PresenceError::MissingFrom, ))) .await; } } Stanza::Iq(iq) => match iq.r#type { stanza::client::iq::IqType::Error | stanza::client::iq::IqType::Result => { let send; { send = logic.pending().lock().await.remove(&iq.id); } if let Some(send) = send { send.send(Ok(Stanza::Iq(iq))); } else { let _ = logic .update_sender() .send(UpdateMessage::Error(Error::Iq(IqError::NoMatchingId( iq.id, )))) .await; } } // TODO: send unsupported to server // TODO: proper errors i am so tired please stanza::client::iq::IqType::Get => {} stanza::client::iq::IqType::Set => { if let Some(query) = iq.query { match query { stanza::client::iq::Query::Roster(mut query) => { // TODO: there should only be one if let Some(item) = query.items.pop() { match item.subscription { Some(stanza::roster::Subscription::Remove) => { logic.db().delete_contact(item.jid.clone()).await; logic .update_sender() .send(UpdateMessage::RosterDelete(item.jid)) .await; // TODO: send result } _ => { let contact: Contact = item.into(); if let Err(e) = logic.db().upsert_contact(contact.clone()).await { let _ = logic .update_sender() .send(UpdateMessage::Error(Error::Roster( RosterError::Cache(e.into()), ))) .await; } let _ = logic .update_sender() .send(UpdateMessage::RosterUpdate(contact)) .await; // TODO: send result // write_handle.write(Stanza::Iq(stanza::client::iq::Iq { // from: , // id: todo!(), // to: todo!(), // r#type: todo!(), // lang: todo!(), // query: todo!(), // errors: todo!(), // })); } } } } // TODO: send unsupported to server _ => {} } } else { // TODO: send error (unsupported) to server } } }, Stanza::Error(error) => { let _ = logic .update_sender() .send(UpdateMessage::Error(Error::Stream(error))) .await; // TODO: reconnect } Stanza::OtherContent(content) => { let _ = logic .update_sender() .send(UpdateMessage::Error(Error::UnrecognizedContent)); // TODO: send error to write_thread } } }