whatsapp_rust/features/
events.rs1use wacore::event;
4use wacore_binary::{Jid, JidExt};
5use waproto::whatsapp as wa;
6
7pub use waproto::whatsapp::message::event_response_message::EventResponseType;
8
9use crate::client::Client;
10use crate::send::{SendError, SendResult};
11
12#[derive(Debug, Clone, Default)]
14pub struct EventCreationParams {
15 pub name: String,
16 pub description: Option<String>,
17 pub start_time: Option<i64>,
18 pub end_time: Option<i64>,
19 pub join_link: Option<String>,
20 pub location: Option<wa::message::LocationMessage>,
21 pub is_scheduled_call: Option<bool>,
22 pub extra_guests_allowed: Option<bool>,
23}
24
25pub struct Events<'a> {
26 client: &'a Client,
27}
28
29impl<'a> Events<'a> {
30 pub(crate) fn new(client: &'a Client) -> Self {
31 Self { client }
32 }
33
34 pub async fn create(
37 &self,
38 to: impl Into<Jid>,
39 params: EventCreationParams,
40 ) -> Result<(SendResult, Vec<u8>), SendError> {
41 let to = &to.into();
42 if params.name.trim().is_empty() {
43 return Err(SendError::InvalidRequest(
44 "event name must not be empty".into(),
45 ));
46 }
47
48 let mut message = wa::Message {
49 event_message: buffa::MessageField::some(build_event_message(params)),
50 ..Default::default()
51 };
52
53 let message_secret: Vec<u8> = {
57 use rand::Rng;
58 let mut secret = vec![0u8; 32];
59 rand::rng().fill_bytes(&mut secret);
60 secret
61 };
62 message.message_context_info = buffa::MessageField::some(wa::MessageContextInfo {
63 message_secret: Some(message_secret.clone()),
64 ..Default::default()
65 });
66
67 let result = self.client.send_message(to, message).await?;
68 Ok((result, message_secret))
69 }
70
71 pub async fn respond(
74 &self,
75 chat_jid: impl Into<Jid>,
76 event_msg_id: &str,
77 event_creator_jid: &Jid,
78 message_secret: &[u8],
79 response: EventResponseType,
80 extra_guest_count: Option<i32>,
81 ) -> Result<SendResult, SendError> {
82 let chat_jid = &chat_jid.into();
83 if message_secret.len() != 32 {
86 return Err(SendError::InvalidRequest(format!(
87 "event message_secret must be 32 bytes, got {}",
88 message_secret.len()
89 )));
90 }
91 let my_jid = self.client.pn().ok_or(SendError::NotLoggedIn)?;
92 let my_base = my_jid.to_non_ad();
93
94 let responder = self
95 .resolve_responder_jid(event_creator_jid, &my_base)
96 .await;
97 let responder_str = responder.to_string();
98 let creator_str = event_creator_jid.to_non_ad_string();
99
100 let response_msg = wa::message::EventResponseMessage {
101 response: Some(response),
102 timestamp_ms: Some(wacore::time::now_millis()),
103 extra_guest_count,
104 };
105
106 let (enc_payload, iv) = event::encrypt_event_response_with_secret(
107 &response_msg,
108 message_secret,
109 event_msg_id,
110 &creator_str,
111 &responder_str,
112 )?;
113
114 let from_me = my_base.is_same_user_as(event_creator_jid);
115 let enc = wa::message::EncEventResponseMessage {
116 event_creation_message_key: buffa::MessageField::some(wa::MessageKey {
117 remote_jid: Some(chat_jid.to_string()),
118 from_me: Some(from_me),
119 id: Some(event_msg_id.to_string()),
120 participant: if chat_jid.is_group() {
121 Some(event_creator_jid.to_string())
122 } else {
123 None
124 },
125 }),
126 enc_payload: Some(enc_payload),
127 enc_iv: Some(iv.to_vec()),
128 };
129
130 let message = wa::Message {
131 enc_event_response_message: buffa::MessageField::some(enc),
132 ..Default::default()
133 };
134
135 self.client.send_message(chat_jid, message).await
136 }
137
138 async fn resolve_responder_jid(&self, event_creator_jid: &Jid, own_pn: &Jid) -> Jid {
142 if !event_creator_jid.is_lid() {
143 return own_pn.clone();
144 }
145 match self.client.lid() {
146 Some(lid) => lid.to_non_ad(),
147 None => own_pn.clone(),
148 }
149 }
150}
151
152impl Client {
153 pub fn events(&self) -> Events<'_> {
154 Events::new(self)
155 }
156}
157
158fn build_event_message(params: EventCreationParams) -> wa::message::EventMessage {
161 wa::message::EventMessage {
162 name: Some(params.name),
163 description: params.description,
164 start_time: params.start_time,
165 end_time: params.end_time,
166 join_link: params.join_link,
167 location: params.location.into(),
168 is_schedule_call: params.is_scheduled_call,
169 extra_guests_allowed: params.extra_guests_allowed,
170 ..Default::default()
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::test_utils::create_test_client;
178 use std::sync::Arc;
179
180 #[test]
181 fn build_event_message_maps_fields() {
182 let params = EventCreationParams {
183 name: "Launch".into(),
184 description: Some("desc".into()),
185 start_time: Some(1_700_000_000),
186 end_time: Some(1_700_003_600),
187 join_link: Some("https://call".into()),
188 is_scheduled_call: Some(true),
189 extra_guests_allowed: Some(true),
190 ..Default::default()
191 };
192 let msg = build_event_message(params);
193 assert_eq!(msg.name.as_deref(), Some("Launch"));
194 assert_eq!(msg.description.as_deref(), Some("desc"));
195 assert_eq!(msg.start_time, Some(1_700_000_000));
196 assert_eq!(msg.end_time, Some(1_700_003_600));
197 assert_eq!(msg.join_link.as_deref(), Some("https://call"));
198 assert_eq!(msg.is_schedule_call, Some(true));
199 assert_eq!(msg.extra_guests_allowed, Some(true));
200 assert!(msg.is_canceled.is_none());
201 }
202
203 #[tokio::test]
204 async fn responder_is_pn_when_creator_is_pn() {
205 let client: Arc<Client> = create_test_client().await;
206 let own_pn = Jid::pn("5511999999999");
207 let creator = Jid::pn("5511777777777");
208 let responder = client
209 .events()
210 .resolve_responder_jid(&creator, &own_pn)
211 .await;
212 assert_eq!(responder, own_pn);
213 }
214}