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
}
}
}