From 3a922fb716b390c31b31ee7ea8585bae2ff8092e Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Tue, 23 Jan 2024 19:36:14 +0100 Subject: [PATCH 1/5] send_stanza in tokio-xmpp client forwards message id --- tokio-xmpp/examples/download_avatars.rs | 22 ++++++++++++---------- tokio-xmpp/src/client/async_client.rs | 14 ++++++++++---- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/tokio-xmpp/examples/download_avatars.rs b/tokio-xmpp/examples/download_avatars.rs index 454e969..241efe7 100644 --- a/tokio-xmpp/examples/download_avatars.rs +++ b/tokio-xmpp/examples/download_avatars.rs @@ -68,16 +68,18 @@ async fn main() { .with_to(iq.from.unwrap()); client.send_stanza(iq.into()).await.unwrap(); } - Err(err) => client - .send_stanza(make_error( - iq.from.unwrap(), - iq.id, - ErrorType::Modify, - DefinedCondition::BadRequest, - &format!("{}", err), - )) - .await - .unwrap(), + Err(err) => { + client + .send_stanza(make_error( + iq.from.unwrap(), + iq.id, + ErrorType::Modify, + DefinedCondition::BadRequest, + &format!("{}", err), + )) + .await + .unwrap(); + } } } else { // We MUST answer unhandled get iqs with a service-unavailable error. diff --git a/tokio-xmpp/src/client/async_client.rs b/tokio-xmpp/src/client/async_client.rs index 4f85185..8118734 100644 --- a/tokio-xmpp/src/client/async_client.rs +++ b/tokio-xmpp/src/client/async_client.rs @@ -76,10 +76,16 @@ impl Client { } } - /// Send stanza - pub async fn send_stanza(&mut self, stanza: Element) -> Result<(), Error> { - self.send(Packet::Stanza(add_stanza_id(stanza, ns::JABBER_CLIENT))) - .await + /// Send stanza. An id will be assigned if not already present. + pub async fn send_stanza(&mut self, stanza: Element) -> Result { + let stanza = add_stanza_id(stanza, ns::JABBER_CLIENT); + + // Get the Id. (With add_stanza_id, it should always be present) + let id = stanza.attr("id").unwrap().to_string(); + + self.send(Packet::Stanza(stanza)).await?; + + Ok(id) } /// Get the stream features (``) of the underlying stream From d865e5dafbe7a3097efd199a59b808c2276e2884 Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Tue, 23 Jan 2024 20:03:27 +0100 Subject: [PATCH 2/5] Agent now forwards the message id (and the error if one occurs on sending). --- xmpp/examples/hello_bot.rs | 3 ++- xmpp/src/agent.rs | 33 +++++++++++++++++++++++++++++++-- xmpp/src/message/send.rs | 22 +++++++++++++++++++--- xmpp/src/muc/private_message.rs | 23 ++++++++++++++++++++--- 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/xmpp/examples/hello_bot.rs b/xmpp/examples/hello_bot.rs index 2642277..396acf5 100644 --- a/xmpp/examples/hello_bot.rs +++ b/xmpp/examples/hello_bot.rs @@ -77,7 +77,8 @@ async fn main() -> Result<(), Option<()>> { println!("Joined room {}.", jid); client .send_message(Jid::Bare(jid), MessageType::Groupchat, "en", "Hello world!") - .await; + .await + .expect("Could not send message."); } Event::RoomLeft(jid) => { println!("Left room {}.", jid); diff --git a/xmpp/src/agent.rs b/xmpp/src/agent.rs index 7d0a2f1..9054357 100644 --- a/xmpp/src/agent.rs +++ b/xmpp/src/agent.rs @@ -71,23 +71,52 @@ impl Agent { muc::room::leave_room(self, room_jid, nickname, lang, status).await } + /// Send a message to a given recipient. + /// + /// Returns the generated message ID. + /// + /// # Arguments + /// + /// * `recipient`: The JID of the recipient. + /// * `type_`: The type of message to send. + /// For a message to a MUC room, this should be `MessageType::Groupchat`. + /// For a private message to a MUC room member or regular contact, + /// this should be `MessageType::Chat`. + /// * `lang`: The language of the message. (See IETF RFC 5646) + /// * `text`: The text of the message. pub async fn send_message( &mut self, recipient: Jid, type_: MessageType, lang: &str, text: &str, - ) { + ) -> Result { message::send::send_message(self, recipient, type_, lang, text).await } + /// Send a private message to a given MUC room member. + /// + /// Returns the generated message ID. + /// + /// # Arguments + /// + /// * `room`: The JID of the room. + /// * `recipient`: The nickname of the recipient within the room. + /// * `type_`: The type of message to send. + /// For a message to a MUC room, this should be `MessageType::Groupchat`. + /// For a private message to a MUC room member or regular contact, + /// this should be `MessageType::Chat`. + /// * `lang`: The language of the message. (See IETF RFC 5646) + /// * `text`: The text of the message. + /// + /// Returns the generated message ID, or an error if the message could not be sent. pub async fn send_room_private_message( &mut self, room: BareJid, recipient: RoomNick, lang: &str, text: &str, - ) { + ) -> Result { muc::private_message::send_room_private_message(self, room, recipient, lang, text).await } diff --git a/xmpp/src/message/send.rs b/xmpp/src/message/send.rs index 877aca8..734060b 100644 --- a/xmpp/src/message/send.rs +++ b/xmpp/src/message/send.rs @@ -7,22 +7,38 @@ use tokio_xmpp::connect::ServerConnector; use tokio_xmpp::{ parsers::message::{Body, Message, MessageType}, - Jid, + Error, Jid, }; use crate::Agent; +/// Send a message to a given recipient. +/// +/// Returns the generated message ID. +/// +/// # Arguments +/// +/// * `agent`: The agent to use to send the message. +/// * `recipient`: The JID of the recipient. +/// * `type_`: The type of message to send. +/// For a message to a MUC room, this should be `MessageType::Groupchat`. +/// For a private message to a MUC room member or regular contact, +/// this should be `MessageType::Chat`. +/// * `lang`: The language of the message. (See IETF RFC 5646) +/// * `text`: The text of the message. +/// +/// Returns the generated message ID, or an error if the message could not be sent. pub async fn send_message( agent: &mut Agent, recipient: Jid, type_: MessageType, lang: &str, text: &str, -) { +) -> Result { let mut message = Message::new(Some(recipient)); message.type_ = type_; message .bodies .insert(String::from(lang), Body(String::from(text))); - let _ = agent.client.send_stanza(message.into()).await; + agent.client.send_stanza(message.into()).await } diff --git a/xmpp/src/muc/private_message.rs b/xmpp/src/muc/private_message.rs index 7b5883f..a3c9fc0 100644 --- a/xmpp/src/muc/private_message.rs +++ b/xmpp/src/muc/private_message.rs @@ -10,23 +10,40 @@ use tokio_xmpp::{ message::{Body, Message, MessageType}, muc::user::MucUser, }, - BareJid, Jid, + BareJid, Error, Jid, }; use crate::{Agent, RoomNick}; +/// Send a private message to a given MUC room member. +/// +/// Returns the generated message ID. +/// +/// # Arguments +/// +/// * `agent`: The agent to use to send the message. +/// * `room`: The JID of the room. +/// * `recipient`: The nickname of the recipient within the room. +/// * `type_`: The type of message to send. +/// For a message to a MUC room, this should be `MessageType::Groupchat`. +/// For a private message to a MUC room member or regular contact, +/// this should be `MessageType::Chat`. +/// * `lang`: The language of the message. (See IETF RFC 5646) +/// * `text`: The text of the message. +/// +/// Returns the generated message ID, or an error if the message could not be sent. pub async fn send_room_private_message( agent: &mut Agent, room: BareJid, recipient: RoomNick, lang: &str, text: &str, -) { +) -> Result { let recipient: Jid = room.with_resource_str(&recipient).unwrap().into(); let mut message = Message::new(recipient).with_payload(MucUser::new()); message.type_ = MessageType::Chat; message .bodies .insert(String::from(lang), Body(String::from(text))); - let _ = agent.client.send_stanza(message.into()).await; + agent.client.send_stanza(message.into()).await } From 8d25c7d47305d2bf4330a0472c534a2c7b5658b8 Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Tue, 23 Jan 2024 20:48:34 +0100 Subject: [PATCH 3/5] Event now returns the sender-assigned ID and the MUC-assigned ID in a combined struct. --- xmpp/src/event.rs | 44 +++++++++++++++++++++----- xmpp/src/lib.rs | 1 + xmpp/src/message/mod.rs | 19 +++++++++++ xmpp/src/message/receive/chat.rs | 3 +- xmpp/src/message/receive/group_chat.rs | 28 +++++++++++++++- 5 files changed, 85 insertions(+), 10 deletions(-) diff --git a/xmpp/src/event.rs b/xmpp/src/event.rs index c7a8e3a..c3d667c 100644 --- a/xmpp/src/event.rs +++ b/xmpp/src/event.rs @@ -8,6 +8,7 @@ use tokio_xmpp::parsers::Jid; use tokio_xmpp::parsers::{bookmarks2, message::Body, roster::Item as RosterItem, BareJid}; +use crate::message::MucMessageId; use crate::{delay::StanzaTimeInfo, Error, Id, RoomNick}; #[derive(Debug)] @@ -30,15 +31,42 @@ pub enum Event { LeaveAllRooms, RoomJoined(BareJid), RoomLeft(BareJid), - RoomMessage(Id, BareJid, RoomNick, Body, StanzaTimeInfo), + /// A message received from a group chat room. + /// - The [`MucMessageId`] is the ID of the message; it contains the ID assigned by the room, + /// and the ID assigned by the sending client. + /// - The [`BareJid`] is the room's address. + /// - The [`RoomNick`] is the nickname of the room member who sent the message. + /// - The [`Body`] is the message body. + /// - The [`StanzaTimeInfo`] about when message was received, and when the message was claimed sent. + /// + /// Note: if the sender_assigned_id matches the ID returned by Agent::send_message, + /// then the message is an echo of a message sent by the client. + RoomMessage(MucMessageId, BareJid, RoomNick, Body, StanzaTimeInfo), /// The subject of a room was received. - /// - The BareJid is the room's address. - /// - The RoomNick is the nickname of the room member who set the subject. - /// - The String is the new subject. - RoomSubject(BareJid, Option, String, StanzaTimeInfo), - /// A private message received from a room, containing the message ID, the room's BareJid, - /// the sender's nickname, and the message body. - RoomPrivateMessage(Id, BareJid, RoomNick, Body, StanzaTimeInfo), + /// - The MucMessageId is the ID of the message that contained the subject; + /// it contains the ID assigned by the room, and the ID assigned by the sending client. + /// - The [`BareJid`] is the room's address. + /// - The [`RoomNick`] is the nickname of the room member who set the subject. + /// - The [`String`] is the new subject. + /// - The [`StanzaTimeInfo`] about when message was received, and when the message was claimed sent. + RoomSubject( + MucMessageId, + BareJid, + Option, + String, + StanzaTimeInfo, + ), + /// A private message received from a member of a room. + /// - The [`MucMessageId`] is the ID of the message; it contains the ID assigned by the room, + /// and the ID assigned by the sending client. + /// - The [`BareJid`] is the room's address. + /// - The [`RoomNick`] is the nickname of the room member who sent the message. + /// - The [`Body`] is the message body. + /// - The [`StanzaTimeInfo`] about when message was received, and when the message was claimed sent. + /// + /// Note: if the sender_assigned_id matches the ID returned by Agent::send_room_private_message, + /// then the message is an echo of a message sent by the client. + RoomPrivateMessage(MucMessageId, BareJid, RoomNick, Body, StanzaTimeInfo), ServiceMessage(Id, BareJid, Body, StanzaTimeInfo), HttpUploadedFile(String), } diff --git a/xmpp/src/lib.rs b/xmpp/src/lib.rs index 780fe4e..00eaed4 100644 --- a/xmpp/src/lib.rs +++ b/xmpp/src/lib.rs @@ -33,6 +33,7 @@ pub use feature::ClientFeature; pub type Error = tokio_xmpp::Error; pub type Id = Option; + pub type RoomNick = String; #[cfg(all(test, any(feature = "starttls-rust", feature = "starttls-native")))] diff --git a/xmpp/src/message/mod.rs b/xmpp/src/message/mod.rs index f38387b..1db0580 100644 --- a/xmpp/src/message/mod.rs +++ b/xmpp/src/message/mod.rs @@ -4,5 +4,24 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use crate::Id; + pub mod receive; pub mod send; + +/// A message ID for a message in a MUC room. +/// +/// Note: this struct is a bit weird due to an inconsistency between XEPs 0308 and 0424. +/// +/// XEP-0424 (Message Retraction) designates messages to correct by the sender-assigned ID, +/// but XEP-0308 (Last Message Correction) designates messages to correct by the room-assigned ID. +/// +/// Hence, both are needed, but depending on context, one or the other may be missing or may +/// refer to something else; see the context-specific documentation for more information. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct MucMessageId { + /// The ID of the message as assigned by the client that sent it. + pub sender_assigned_id: Id, + /// The ID of the message as assigned by the room. + pub room_assigned_id: Id, +} diff --git a/xmpp/src/message/receive/chat.rs b/xmpp/src/message/receive/chat.rs index 198ad6a..eb37197 100644 --- a/xmpp/src/message/receive/chat.rs +++ b/xmpp/src/message/receive/chat.rs @@ -10,6 +10,7 @@ use tokio_xmpp::{ Jid, }; +use crate::message::receive::group_chat::extract_muc_message_id; use crate::{delay::StanzaTimeInfo, Agent, Event}; pub async fn handle_message_chat( @@ -37,7 +38,7 @@ pub async fn handle_message_chat( ) } Jid::Full(full) => Event::RoomPrivateMessage( - message.id.clone(), + extract_muc_message_id(message), full.to_bare(), full.resource_str().to_owned(), body.clone(), diff --git a/xmpp/src/message/receive/group_chat.rs b/xmpp/src/message/receive/group_chat.rs index 9fa17cd..8dbe832 100644 --- a/xmpp/src/message/receive/group_chat.rs +++ b/xmpp/src/message/receive/group_chat.rs @@ -7,6 +7,9 @@ use tokio_xmpp::connect::ServerConnector; use tokio_xmpp::{parsers::message::Message, Jid}; +use crate::message::MucMessageId; +use crate::parsers::ns::SID; +use crate::parsers::stanza_id::StanzaId; use crate::{delay::StanzaTimeInfo, Agent, Event}; pub async fn handle_message_group_chat( @@ -18,8 +21,11 @@ pub async fn handle_message_group_chat( ) { let langs: Vec<&str> = agent.lang.iter().map(String::as_str).collect(); + let id = extract_muc_message_id(message); + if let Some((_lang, subject)) = message.get_best_subject(langs.clone()) { events.push(Event::RoomSubject( + id.clone(), from.to_bare(), from.resource_str().map(String::from), subject.0.clone(), @@ -30,7 +36,7 @@ pub async fn handle_message_group_chat( if let Some((_lang, body)) = message.get_best_body(langs) { let event = match from.clone() { Jid::Full(full) => Event::RoomMessage( - message.id.clone(), + id, from.to_bare(), full.resource_str().to_owned(), body.clone(), @@ -43,3 +49,23 @@ pub async fn handle_message_group_chat( events.push(event) } } + +/// Extract the sender-assigned and room-assigned IDs from a message. +pub fn extract_muc_message_id(message: &Message) -> MucMessageId { + // Find a stanza-id element, if any. + let stanza_id = message + .payloads + .iter() + .find(|payload| payload.is("stanza-id", SID)) + .map(|elem| { + StanzaId::try_from(elem.clone()) + .expect("Failed to parse stanza-id") + .id + }); + + let id = MucMessageId { + sender_assigned_id: message.id.clone(), + room_assigned_id: stanza_id, + }; + id +} From 8af17d8b2db1ce329a07aa1b5e41d413ce01ffdc Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Thu, 25 Jan 2024 00:02:50 +0100 Subject: [PATCH 4/5] Added Event variants for message correction. --- xmpp/src/event.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/xmpp/src/event.rs b/xmpp/src/event.rs index c3d667c..79597d0 100644 --- a/xmpp/src/event.rs +++ b/xmpp/src/event.rs @@ -67,6 +67,23 @@ pub enum Event { /// Note: if the sender_assigned_id matches the ID returned by Agent::send_room_private_message, /// then the message is an echo of a message sent by the client. RoomPrivateMessage(MucMessageId, BareJid, RoomNick, Body, StanzaTimeInfo), + /// A message in a one-to-one chat was corrected/edited. + /// - The [`Id`] is the ID of the message that was corrected. (Only + /// - The [`BareJid`] is the JID of the other participant in the chat. + /// - The [`Body`] is the new body of the message, to replace the old one. + ChatMessageCorrection(Id, BareJid, Body), + /// A message in a MUC was corrected/edited. + /// - The [`MucMessageId`] is the ID of the message that was corrected. (Only muc_assigned_id is set.) + /// - The [`BareJid`] is the JID of the room where the message was sent. + /// - The [`RoomNick`] is the nickname of the sender of the message. + /// - The [`Body`] is the new body of the message, to replace the old one. + RoomMessageCorrection(MucMessageId, BareJid, RoomNick, Body), + /// A private message in a MUC was corrected/edited. + /// - The [`MucMessageId`] is the ID of the message that was corrected. (Only muc_assigned_id is set.) + /// - The [`BareJid`] is the JID of the room where the message was sent. + /// - The [`RoomNick`] is the nickname of the sender of the message. + /// - The [`Body`] is the new body of the message, to replace the old one. + RoomPrivateMessageCorrection(MucMessageId, BareJid, RoomNick, Body), ServiceMessage(Id, BareJid, Body, StanzaTimeInfo), HttpUploadedFile(String), } From 64476ed7235098f7b0b66a41627473d4ca31afbc Mon Sep 17 00:00:00 2001 From: Werner Kroneman Date: Thu, 25 Jan 2024 00:21:48 +0100 Subject: [PATCH 5/5] Added functions and methods for sending and receiving corrections under XEP 0308 --- xmpp/src/agent.rs | 56 +++++++++++ xmpp/src/message/correct.rs | 190 ++++++++++++++++++++++++++++++++++++ xmpp/src/message/mod.rs | 1 + 3 files changed, 247 insertions(+) create mode 100644 xmpp/src/message/correct.rs diff --git a/xmpp/src/agent.rs b/xmpp/src/agent.rs index 9054357..0890bc2 100644 --- a/xmpp/src/agent.rs +++ b/xmpp/src/agent.rs @@ -120,6 +120,62 @@ impl Agent { muc::private_message::send_room_private_message(self, room, recipient, lang, text).await } + /// Send a correction for a given earlier message. + /// + /// Note: XEP-0308 is only defined for the last-sent message; this method does not enforce this + /// restriction. Results may vary if the message being corrected is not the last-sent message. + /// + /// # Arguments + /// - `id`: The ID of the message to correct. + /// - `to`: The JID of the recipient of the message. + /// - `lang`: The language of the new message body. + /// - `text`: The new message body, to replace the old one. + /// + /// This method returns as soon as the correction has been sent, not when it has been received or acknowledged. + pub async fn correct_message(&mut self, id: &str, to: BareJid, lang: &str, text: &str) { + message::correct::send_correction(self, id, to, lang, text).await + } + + /// Send a correction for a given earlier message in a MUC. + /// + /// Note: XEP-0308 is only defined for the last-sent message; this method does not enforce this + /// restriction. Results may vary if the message being corrected is not the last-sent message. + /// + /// # Arguments + /// - `id`: The ID of the message to correct. + /// - `room`: The JID of the room. + /// - `lang`: The language of the new message body. + /// - `text`: The new message body, to replace the old one. + /// + /// This method returns as soon as the correction has been sent, not when it has been received or acknowledged. + pub async fn correct_room_message(&mut self, id: &str, room: BareJid, lang: &str, text: &str) { + message::correct::send_correction_group(self, id, room, lang, text).await + } + + /// Send a correction for a given earlier private message in a MUC. + /// + /// Note: XEP-0308 is only defined for the last-sent message; this method does not enforce this + /// restriction. Results may vary if the message being corrected is not the last-sent message. + /// + /// # Arguments + /// - `id`: The ID of the message to correct. + /// - `room`: The JID of the room. + /// - `nick`: The nickname of the recipient of the message. + /// - `lang`: The language of the new message body. + /// - `text`: The new message body, to replace the old one. + /// + /// This method returns as soon as the correction has been sent, not when it has been received or acknowledged. + pub async fn correct_room_private_message( + &mut self, + id: &str, + room: BareJid, + nick: RoomNick, + lang: &str, + text: &str, + ) { + message::correct::send_correction_private(self, id, room, nick, lang, text).await + } + /// Wait for new events. /// /// # Returns diff --git a/xmpp/src/message/correct.rs b/xmpp/src/message/correct.rs new file mode 100644 index 0000000..a57c4d4 --- /dev/null +++ b/xmpp/src/message/correct.rs @@ -0,0 +1,190 @@ +use crate::message::MucMessageId; +use crate::parsers::message::{Body, Message, MessageType}; +use crate::parsers::message_correct::Replace; +use crate::{Agent, BareJid, Element, Event, Jid, RoomNick}; +use tokio_xmpp::connect::ServerConnector; + +/// Send a stanza that corrects an earlier message. +/// +/// # Arguments +/// - `agent`: The agent to use to send the message. +/// - `id`: The ID of the message to correct. +/// - `to`: The (original) recipient of the message. +/// - `lang`: The language of the new message body. +/// - `text`: The new message body, to replace the old one. +pub async fn send_correction( + agent: &mut Agent, + id: &str, + to: BareJid, + lang: &str, + text: &str, +) { + // Create a message stanza with the correct payload. + let mut message = Message::new(Some(to.into())); + + // Set the new body. + message + .bodies + .insert(String::from(lang), Body(String::from(text))); + + let replace = Replace { id: id.to_owned() }; + + // Specify that this message is a correction of an earlier message. + message.payloads.push(replace.into()); + + agent + .client + .send_stanza(message.into()) + .await + .expect("Failed to send message correction."); +} + +/// Send a stanza that corrects an earlier message in a MUC. +/// +/// # Arguments +/// - `agent`: The agent to use to send the message. +/// - `id`: The ID of the message to correct. +/// - `room`: The JID of the room. +/// - `lang`: The language of the new message body. +/// - `text`: The new message body, to replace the old one. +pub async fn send_correction_group( + agent: &mut Agent, + id: &str, + room: BareJid, + lang: &str, + text: &str, +) { + // Create a message stanza with the correct payload. + let mut message = Message::new(Some(room.into())); + + // Set the new body. + message + .bodies + .insert(String::from(lang), Body(String::from(text))); + + message.type_ = MessageType::Groupchat; + + let replace = Replace { id: id.to_owned() }; + + // Specify that this message is a correction of an earlier message. + message.payloads.push(replace.into()); + + agent + .client + .send_stanza(message.into()) + .await + .expect("Failed to send message correction."); +} + +/// Send a stanza that corrects an earlier message in a private chat in a MUC. +/// +/// # Arguments +/// - `agent`: The agent to use to send the message. +/// - `id`: The ID of the message to correct. +/// - `room`: The JID of the room. +/// - `nick`: The nickname of the recipient of the message. +/// - `lang`: The language of the new message body. +/// - `text`: The new message body, to replace the old one. +pub async fn send_correction_private( + agent: &mut Agent, + id: &str, + room: BareJid, + nick: RoomNick, + lang: &str, + text: &str, +) { + // Create a message stanza with the correct payload. + let mut message = Message::new(Some( + room.with_resource_str(&nick) + .expect("Failed to create JID.") + .into(), + )); + + // Set the new body. + message + .bodies + .insert(String::from(lang), Body(String::from(text))); + + let replace = Replace { id: id.to_owned() }; + + // Specify that this message is a correction of an earlier message. + message.payloads.push(replace.into()); + + agent + .client + .send_stanza(message.into()) + .await + .expect("Failed to send message correction."); +} + +/// Convert a Message with a payload into a higher-level Event. +/// +/// # Arguments +/// - `message`: The message to convert. +/// - `replace`: The payload element. +/// - `from`: The JID of the sender. +pub fn handle_correction( + message: &Message, + replace: &Element, + from: &Jid, + langs: Vec<&str>, +) -> Event { + // Parse the payload. + let replace = Replace::try_from(replace.clone()) + // TODO: Should be handled with an error stanza back to the server, but panicking is consistent with behavior when facing other parsing issues. + .expect("Failed to parse message correction payload."); + + // Extract the ID of the message to correct. + let id = replace.id.clone(); + + // Extract the language and body of the new message. + let body = message + .get_best_body(langs) + .map(|(_lang, body)| body.clone()) + .expect("Received a replace payload without a body."); + + match message.type_ { + MessageType::Chat | MessageType::Normal => { + Event::ChatMessageCorrection(Some(id), from.to_bare(), body) + } + MessageType::Groupchat => { + let groupchat_jid = from.to_bare(); + let sender_nick = from + .resource_str() + .expect("A message in a group chat should have a resource part.") + .to_string(); + + // TODO: This is inconsistent with message-retraction; need to fix before merging. + match &message + .to + .clone() + .expect("A message in a group chat should have a 'to' attribute.") + { + Jid::Full(_) => Event::RoomMessageCorrection( + MucMessageId { + sender_assigned_id: None, + room_assigned_id: Some(id), // for XEP-0308, the ID of the message to correct is the room-assigned ID. + }, + groupchat_jid, + sender_nick, + body, + ), + Jid::Bare(_) => Event::RoomPrivateMessageCorrection( + MucMessageId { + sender_assigned_id: None, + room_assigned_id: Some(id), // for XEP-0308, the ID of the message to correct is the room-assigned ID. + }, + groupchat_jid, + sender_nick, + body, + ), + } + } + MessageType::Error => { + unimplemented!("Received message with payload and type=error.") + } + MessageType::Headline => { + unimplemented!("Received message with payload and type=headline.") + } + } +} diff --git a/xmpp/src/message/mod.rs b/xmpp/src/message/mod.rs index 1db0580..cac6a2b 100644 --- a/xmpp/src/message/mod.rs +++ b/xmpp/src/message/mod.rs @@ -6,6 +6,7 @@ use crate::Id; +pub mod correct; pub mod receive; pub mod send;