1use crate::client::Client;
2use std::collections::HashMap;
3use wacore::client::context::GroupInfo;
4use wacore::iq::groups::{
5 AddParticipantsIq, DemoteParticipantsIq, GetGroupInviteLinkIq, GroupCreateIq,
6 GroupParticipantResponse, GroupParticipatingIq, GroupQueryIq, LeaveGroupIq,
7 PromoteParticipantsIq, RemoveParticipantsIq, SetGroupDescriptionIq, SetGroupSubjectIq,
8 normalize_participants,
9};
10use wacore::types::message::AddressingMode;
11use wacore_binary::jid::Jid;
12
13pub use wacore::iq::groups::{
14 GroupCreateOptions, GroupDescription, GroupParticipantOptions, GroupSubject, MemberAddMode,
15 MemberLinkMode, MembershipApprovalMode, ParticipantChangeResponse,
16};
17
18#[derive(Debug, Clone)]
19pub struct GroupMetadata {
20 pub id: Jid,
21 pub subject: String,
22 pub participants: Vec<GroupParticipant>,
23 pub addressing_mode: AddressingMode,
24}
25
26#[derive(Debug, Clone)]
27pub struct GroupParticipant {
28 pub jid: Jid,
29 pub phone_number: Option<Jid>,
30 pub is_admin: bool,
31}
32
33impl From<GroupParticipantResponse> for GroupParticipant {
34 fn from(p: GroupParticipantResponse) -> Self {
35 Self {
36 jid: p.jid,
37 phone_number: p.phone_number,
38 is_admin: p.participant_type.is_admin(),
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
44pub struct CreateGroupResult {
45 pub gid: Jid,
46}
47
48pub struct Groups<'a> {
49 client: &'a Client,
50}
51
52impl<'a> Groups<'a> {
53 pub(crate) fn new(client: &'a Client) -> Self {
54 Self { client }
55 }
56
57 pub async fn query_info(&self, jid: &Jid) -> Result<GroupInfo, anyhow::Error> {
58 if let Some(cached) = self.client.get_group_cache().await.get(jid).await {
59 return Ok(cached);
60 }
61
62 let group = self.client.execute(GroupQueryIq::new(jid)).await?;
63
64 let participants: Vec<Jid> = group.participants.iter().map(|p| p.jid.clone()).collect();
65
66 let lid_to_pn_map: HashMap<String, Jid> = if group.addressing_mode == AddressingMode::Lid {
67 group
68 .participants
69 .iter()
70 .filter_map(|p| {
71 p.phone_number
72 .as_ref()
73 .map(|pn| (p.jid.user.clone(), pn.clone()))
74 })
75 .collect()
76 } else {
77 HashMap::new()
78 };
79
80 let mut info = GroupInfo::new(participants, group.addressing_mode);
81 if !lid_to_pn_map.is_empty() {
82 info.set_lid_to_pn_map(lid_to_pn_map);
83 }
84
85 self.client
86 .get_group_cache()
87 .await
88 .insert(jid.clone(), info.clone())
89 .await;
90
91 Ok(info)
92 }
93
94 pub async fn get_participating(&self) -> Result<HashMap<String, GroupMetadata>, anyhow::Error> {
95 let response = self.client.execute(GroupParticipatingIq::new()).await?;
96
97 let result = response
98 .groups
99 .into_iter()
100 .map(|group| {
101 let key = group.id.to_string();
102 let metadata = GroupMetadata {
103 id: group.id,
104 subject: group.subject.into_string(),
105 participants: group.participants.into_iter().map(Into::into).collect(),
106 addressing_mode: group.addressing_mode,
107 };
108 (key, metadata)
109 })
110 .collect();
111
112 Ok(result)
113 }
114
115 pub async fn get_metadata(&self, jid: &Jid) -> Result<GroupMetadata, anyhow::Error> {
116 let group = self.client.execute(GroupQueryIq::new(jid)).await?;
117
118 Ok(GroupMetadata {
119 id: group.id,
120 subject: group.subject.into_string(),
121 participants: group.participants.into_iter().map(Into::into).collect(),
122 addressing_mode: group.addressing_mode,
123 })
124 }
125
126 pub async fn create_group(
127 &self,
128 mut options: GroupCreateOptions,
129 ) -> Result<CreateGroupResult, anyhow::Error> {
130 let mut resolved_participants = Vec::with_capacity(options.participants.len());
132
133 for participant in options.participants {
134 let resolved = if participant.jid.is_lid() && participant.phone_number.is_none() {
135 let phone_number = self
136 .client
137 .get_phone_number_from_lid(&participant.jid.user)
138 .await
139 .ok_or_else(|| {
140 anyhow::anyhow!("Missing phone number mapping for LID {}", participant.jid)
141 })?;
142 participant.with_phone_number(Jid::pn(phone_number))
143 } else {
144 participant
145 };
146 resolved_participants.push(resolved);
147 }
148
149 options.participants = normalize_participants(&resolved_participants);
150
151 let gid = self.client.execute(GroupCreateIq::new(options)).await?;
152
153 Ok(CreateGroupResult { gid })
154 }
155
156 pub async fn set_subject(&self, jid: &Jid, subject: GroupSubject) -> Result<(), anyhow::Error> {
157 Ok(self
158 .client
159 .execute(SetGroupSubjectIq::new(jid, subject))
160 .await?)
161 }
162
163 pub async fn set_description(
168 &self,
169 jid: &Jid,
170 description: Option<GroupDescription>,
171 prev: Option<String>,
172 ) -> Result<(), anyhow::Error> {
173 Ok(self
174 .client
175 .execute(SetGroupDescriptionIq::new(jid, description, prev))
176 .await?)
177 }
178
179 pub async fn leave(&self, jid: &Jid) -> Result<(), anyhow::Error> {
180 Ok(self.client.execute(LeaveGroupIq::new(jid)).await?)
181 }
182
183 pub async fn add_participants(
184 &self,
185 jid: &Jid,
186 participants: &[Jid],
187 ) -> Result<Vec<ParticipantChangeResponse>, anyhow::Error> {
188 Ok(self
189 .client
190 .execute(AddParticipantsIq::new(jid, participants))
191 .await?)
192 }
193
194 pub async fn remove_participants(
195 &self,
196 jid: &Jid,
197 participants: &[Jid],
198 ) -> Result<Vec<ParticipantChangeResponse>, anyhow::Error> {
199 Ok(self
200 .client
201 .execute(RemoveParticipantsIq::new(jid, participants))
202 .await?)
203 }
204
205 pub async fn promote_participants(
206 &self,
207 jid: &Jid,
208 participants: &[Jid],
209 ) -> Result<(), anyhow::Error> {
210 Ok(self
211 .client
212 .execute(PromoteParticipantsIq::new(jid, participants))
213 .await?)
214 }
215
216 pub async fn demote_participants(
217 &self,
218 jid: &Jid,
219 participants: &[Jid],
220 ) -> Result<(), anyhow::Error> {
221 Ok(self
222 .client
223 .execute(DemoteParticipantsIq::new(jid, participants))
224 .await?)
225 }
226
227 pub async fn get_invite_link(&self, jid: &Jid, reset: bool) -> Result<String, anyhow::Error> {
228 Ok(self
229 .client
230 .execute(GetGroupInviteLinkIq::new(jid, reset))
231 .await?)
232 }
233}
234
235impl Client {
236 pub fn groups(&self) -> Groups<'_> {
237 Groups::new(self)
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_group_metadata_struct() {
247 let jid: Jid = "123456789@g.us"
248 .parse()
249 .expect("test group JID should be valid");
250 let participant_jid: Jid = "1234567890@s.whatsapp.net"
251 .parse()
252 .expect("test participant JID should be valid");
253
254 let metadata = GroupMetadata {
255 id: jid.clone(),
256 subject: "Test Group".to_string(),
257 participants: vec![GroupParticipant {
258 jid: participant_jid,
259 phone_number: None,
260 is_admin: true,
261 }],
262 addressing_mode: AddressingMode::Pn,
263 };
264
265 assert_eq!(metadata.subject, "Test Group");
266 assert_eq!(metadata.participants.len(), 1);
267 assert!(metadata.participants[0].is_admin);
268 }
269
270 }