whatsapp_rust/features/
media_reupload.rs1use crate::client::{Client, ClientError, NodeFilter};
10use anyhow::Result;
11use log::debug;
12use std::sync::Arc;
13use std::time::Duration;
14pub use wacore::media_retry::MediaRetryResult;
15use wacore::media_retry::{
16 build_media_retry_receipt, encrypt_media_retry_receipt, parse_media_retry_notification,
17};
18use wacore_binary::jid::{Jid, JidExt as _};
19
20const MEDIA_RETRY_TIMEOUT: Duration = Duration::from_secs(30);
21
22pub struct MediaReuploadRequest<'a> {
24 pub msg_id: &'a str,
26 pub chat_jid: &'a Jid,
28 pub media_key: &'a [u8],
30 pub is_from_me: bool,
32 pub participant: Option<&'a Jid>,
34}
35
36pub struct MediaReupload<'a> {
37 client: &'a Arc<Client>,
38}
39
40impl<'a> MediaReupload<'a> {
41 pub(crate) fn new(client: &'a Arc<Client>) -> Self {
42 Self { client }
43 }
44
45 pub async fn request(&self, req: &MediaReuploadRequest<'_>) -> Result<MediaRetryResult> {
56 anyhow::ensure!(
58 !req.chat_jid.is_newsletter(),
59 "media reupload is not supported for newsletter messages"
60 );
61
62 debug!(
63 "[media][rmr] Requesting media reupload for msg {} in chat {}",
64 req.msg_id, req.chat_jid
65 );
66
67 let (ciphertext, iv) = encrypt_media_retry_receipt(req.media_key, req.msg_id)?;
69
70 let device_snapshot = self.client.persistence_manager.get_device_snapshot().await;
72 let own_jid = device_snapshot.pn.clone().ok_or(ClientError::NotLoggedIn)?;
73
74 let waiter = self.client.wait_for_node(
76 NodeFilter::tag("notification")
77 .attr("type", "mediaretry")
78 .attr("id", req.msg_id),
79 );
80
81 let receipt_node = build_media_retry_receipt(
83 &own_jid,
84 req.msg_id,
85 req.chat_jid,
86 req.is_from_me,
87 req.participant,
88 &ciphertext,
89 &iv,
90 );
91
92 self.client.send_node(receipt_node).await?;
93
94 debug!(
95 "[media][rmr] Sent server-error receipt for {}, waiting for response",
96 req.msg_id
97 );
98
99 let notification_node =
101 wacore::runtime::timeout(&*self.client.runtime, MEDIA_RETRY_TIMEOUT, waiter)
102 .await
103 .map_err(|_| anyhow::anyhow!("media retry notification timed out after 30s"))?
104 .map_err(|_| anyhow::anyhow!("media retry waiter cancelled"))?;
105
106 debug!(
107 "[media][rmr] Received mediaretry notification for {}",
108 req.msg_id
109 );
110
111 parse_media_retry_notification(¬ification_node, req.media_key)
113 }
114}
115
116impl Client {
117 pub fn media_reupload(self: &Arc<Self>) -> MediaReupload<'_> {
119 MediaReupload::new(self)
120 }
121}