whatsapp_rust/client/
iq_ops.rs1use super::*;
4
5impl Client {
6 pub async fn set_passive(&self, passive: bool) -> Result<(), crate::request::IqError> {
7 use wacore::iq::passive::PassiveModeSpec;
8 self.execute(PassiveModeSpec::new(passive)).await
9 }
10
11 pub async fn fetch_props(&self) -> Result<(), crate::request::IqError> {
12 use wacore::iq::props::PropsSpec;
13 use wacore::store::commands::DeviceCommand;
14
15 let stored_hash = self
16 .persistence_manager
17 .get_device_snapshot()
18 .props_hash
19 .clone();
20
21 let spec = match &stored_hash {
23 Some(hash) if self.ab_props.is_seeded() => {
24 debug!("Fetching props with hash for delta update...");
25 PropsSpec::with_hash(hash)
26 }
27 _ => {
28 debug!("Fetching props (full)...");
29 PropsSpec::new()
30 }
31 };
32
33 let response = self.execute(spec).await?;
34
35 if response.delta_update {
36 debug!(
37 "Props delta update received ({} changed props)",
38 response.experiment_props.len()
39 );
40 } else {
41 debug!(
42 "Props full update received ({} props, hash={:?})",
43 response.experiment_props.len(),
44 response.hash
45 );
46 }
47
48 self.ab_props
49 .apply_props(response.delta_update, response.experiment_props.into_iter())
50 .await;
51 self.latch_lid_migrated_from_props().await;
52
53 if let Some(new_hash) = response.hash {
54 self.persistence_manager
55 .process_command(DeviceCommand::SetPropsHash(Some(new_hash)))
56 .await;
57 }
58
59 Ok(())
60 }
61
62 pub(crate) fn ab_props(&self) -> &wacore::store::ab_props::AbPropsCache {
63 &self.ab_props
64 }
65
66 pub async fn fetch_privacy_settings(
67 &self,
68 ) -> Result<wacore::iq::privacy::PrivacySettingsResponse, crate::request::IqError> {
69 use wacore::iq::privacy::PrivacySettingsSpec;
70
71 debug!("Fetching privacy settings...");
72
73 self.execute(PrivacySettingsSpec::new()).await
74 }
75
76 pub async fn set_privacy_setting(
87 &self,
88 category: wacore::iq::privacy::PrivacyCategory,
89 value: wacore::iq::privacy::PrivacyValue,
90 ) -> Result<wacore::iq::privacy::SetPrivacySettingResponse, crate::request::IqError> {
91 use wacore::iq::privacy::SetPrivacySettingSpec;
92 self.execute(SetPrivacySettingSpec::new(category, value))
93 .await
94 }
95
96 pub async fn set_privacy_disallowed_list(
101 &self,
102 category: wacore::iq::privacy::PrivacyCategory,
103 update: wacore::iq::privacy::DisallowedListUpdate,
104 ) -> Result<wacore::iq::privacy::SetPrivacySettingResponse, crate::request::IqError> {
105 use wacore::iq::privacy::SetPrivacySettingSpec;
106 self.execute(SetPrivacySettingSpec::with_disallowed_list(
107 category, update,
108 ))
109 .await
110 }
111
112 pub async fn set_default_disappearing_mode(
114 &self,
115 duration: u32,
116 ) -> Result<(), crate::request::IqError> {
117 use wacore::iq::privacy::SetDefaultDisappearingModeSpec;
118 self.execute(SetDefaultDisappearingModeSpec::new(duration))
119 .await
120 }
121
122 pub async fn set_chat_disappearing_timer(
130 &self,
131 chat: Jid,
132 duration: u32,
133 ) -> Result<crate::send::SendResult, crate::send::SendError> {
134 if !(chat.is_pn() || chat.is_lid()) {
138 return Err(crate::send::SendError::InvalidRequest(
139 "set_chat_disappearing_timer is 1:1-only; use Groups::set_ephemeral for groups"
140 .into(),
141 ));
142 }
143 let msg = build_ephemeral_setting_message(duration, wacore::time::now_secs_u64() as i64);
144 self.send_message(chat, msg).await
145 }
146
147 pub async fn get_business_profile(
149 &self,
150 jid: &Jid,
151 ) -> Result<Option<wacore::iq::business::BusinessProfile>, crate::request::IqError> {
152 use wacore::iq::business::BusinessProfileSpec;
153 self.execute(BusinessProfileSpec::new(jid)).await
154 }
155
156 pub async fn send_digest_key_bundle(&self) -> Result<(), crate::request::IqError> {
157 use wacore::iq::prekeys::DigestKeyBundleSpec;
158
159 debug!("Sending digest key bundle...");
160
161 self.execute(DigestKeyBundleSpec::new()).await.map(|_| ())
162 }
163
164 pub async fn set_device_props(&self, override_: wacore::store::DevicePropsOverride) {
170 use wacore::store::commands::DeviceCommand;
171 if override_.is_empty() {
172 return;
173 }
174 if self.persistence_manager.get_device_snapshot().pn.is_some() {
175 warn!(
176 target: "Client/DeviceProps",
177 "set_device_props called after pairing — stored but not sent on the wire"
178 );
179 }
180 self.persistence_manager
181 .process_command(DeviceCommand::SetDeviceProps(override_))
182 .await;
183 }
184
185 pub async fn set_client_profile(&self, profile: wacore::client_profile::ClientProfile) {
188 use wacore::store::commands::DeviceCommand;
189 self.persistence_manager
190 .process_command(DeviceCommand::SetClientProfile(profile))
191 .await;
192 }
193}
194
195fn build_ephemeral_setting_message(duration: u32, now_secs: i64) -> waproto::whatsapp::Message {
202 use waproto::whatsapp as wa;
203 wa::Message {
204 protocol_message: buffa::MessageField::some(wa::message::ProtocolMessage {
205 r#type: Some(wa::message::protocol_message::Type::EphemeralSetting),
206 ephemeral_expiration: Some(duration),
207 ephemeral_setting_timestamp: Some(now_secs),
208 disappearing_mode: buffa::MessageField::some(wa::DisappearingMode {
209 initiator: Some(wa::disappearing_mode::Initiator::ChangedInChat),
210 trigger: Some(wa::disappearing_mode::Trigger::Unknown),
211 ..Default::default()
212 }),
213 ..Default::default()
214 }),
215 ..Default::default()
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::build_ephemeral_setting_message;
222 use waproto::whatsapp as wa;
223
224 #[test]
225 fn ephemeral_setting_message_shape() {
226 let msg = build_ephemeral_setting_message(86400, 1_700_000_000);
227 let pm = msg
228 .protocol_message
229 .as_option()
230 .expect("protocol_message set");
231 assert_eq!(
232 pm.r#type,
233 Some(wa::message::protocol_message::Type::EphemeralSetting)
234 );
235 assert_eq!(pm.ephemeral_expiration, Some(86400));
236 assert_eq!(pm.ephemeral_setting_timestamp, Some(1_700_000_000));
237 let dm = pm
238 .disappearing_mode
239 .as_option()
240 .expect("disappearing_mode set");
241 assert_eq!(
242 dm.initiator,
243 Some(wa::disappearing_mode::Initiator::ChangedInChat)
244 );
245 assert_eq!(dm.trigger, Some(wa::disappearing_mode::Trigger::Unknown));
246 }
247
248 #[test]
249 fn ephemeral_setting_disable_uses_zero_duration() {
250 let msg = build_ephemeral_setting_message(0, 1);
251 assert_eq!(
252 msg.protocol_message
253 .as_option()
254 .unwrap()
255 .ephemeral_expiration,
256 Some(0)
257 );
258 }
259}