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