Skip to main content

simploxide_client/
ext.rs

1use futures::FutureExt as _;
2use simploxide_api_types::{
3    AChatItem, CIDeleteMode, CIFile, ChatDeleteMode, ChatItem, Contact, CryptoFile, GroupInfo,
4    GroupMember, GroupMemberRole, GroupProfile, JsonObject, MsgContent, MsgReaction, NewUser,
5    UpdatedMessage, UserInfo,
6    client_api::{
7        AllowUndocumentedResponses as _, ClientApi, ClientApiError as _, UndocumentedResponse,
8    },
9    commands::{
10        ApiBlockMembersForAll, ApiChatItemReaction, ApiListGroups, ApiRemoveMembers,
11        ApiSetContactCustomData, ApiSetGroupCustomData, ApiUpdateChatItem, Connect, ReceiveFile,
12    },
13    responses::{
14        AcceptingContactRequestResponse, ActiveUserResponse, ApiAddGroupRelaysResponse,
15        ApiDeleteChatResponse, ApiUpdateChatItemResponse, CancelFileResponse,
16        ChatItemReactionResponse, ChatItemsDeletedResponse, CmdOkResponse, ConnectResponse,
17        ContactRequestRejectedResponse, GroupLinkCreatedResponse, GroupLinkDeletedResponse,
18        GroupLinkResponse, GroupRelaysResponse, GroupUpdatedResponse, LeftMemberUserResponse,
19        MemberAcceptedResponse, MembersBlockedForAllUserResponse, MembersRoleUserResponse,
20        ReceiveFileResponse, SentGroupInvitationResponse, UserAcceptedGroupSentResponse,
21        UserDeletedMembersResponse,
22    },
23};
24
25use std::{pin::Pin, sync::Arc};
26
27use crate::{
28    id::{
29        ChatId, ContactId, ContactRequestId, FileId, GroupId, MemberId, MessageId, RelayId, UserId,
30    },
31    messages::{MessageBuilder, MessageLike, MulticastBuilder},
32};
33
34pub type InitiateConnectionResponse<C> =
35    Result<UndocumentedResponse<ConnectResponse>, <C as ClientApi>::Error>;
36
37pub type AcceptContactResponse<C> =
38    Result<Arc<AcceptingContactRequestResponse>, <C as ClientApi>::Error>;
39pub type RejectContactResponse<C> =
40    Result<Arc<ContactRequestRejectedResponse>, <C as ClientApi>::Error>;
41
42pub type RejectFileResponse<C> = Result<CancelFileResponse, <C as ClientApi>::Error>;
43
44pub type ContactsResponse<C> = Result<Vec<Contact>, <C as ClientApi>::Error>;
45pub type GroupsResponse<C> = Result<Vec<GroupInfo>, <C as ClientApi>::Error>;
46
47pub type DeleteChatResponse<C> = Result<ApiDeleteChatResponse, <C as ClientApi>::Error>;
48pub type DeleteMessageResponse<C> = Result<Arc<ChatItemsDeletedResponse>, <C as ClientApi>::Error>;
49
50pub type UpdateMessageReactionsResponse<C> =
51    Vec<Result<Arc<ChatItemReactionResponse>, <C as ClientApi>::Error>>;
52pub type UpdateMessageResponse<C> = Result<ApiUpdateChatItemResponse, <C as ClientApi>::Error>;
53
54pub type NewUserResponse<C> = Result<Arc<ActiveUserResponse>, <C as ClientApi>::Error>;
55pub type UsersResponse<C> = Result<Vec<UserInfo>, <C as ClientApi>::Error>;
56
57pub type AddMemberResponse<C> = Result<Arc<SentGroupInvitationResponse>, <C as ClientApi>::Error>;
58pub type JoinGroupResponse<C> = Result<Arc<UserAcceptedGroupSentResponse>, <C as ClientApi>::Error>;
59pub type AcceptMemberResponse<C> = Result<Arc<MemberAcceptedResponse>, <C as ClientApi>::Error>;
60pub type SetMembersRoleResponse<C> = Result<Arc<MembersRoleUserResponse>, <C as ClientApi>::Error>;
61pub type BlockMembersResponse<C> =
62    Result<Arc<MembersBlockedForAllUserResponse>, <C as ClientApi>::Error>;
63pub type RemoveMembersResponse<C> =
64    Result<Arc<UserDeletedMembersResponse>, <C as ClientApi>::Error>;
65pub type LeaveGroupResponse<C> = Result<Arc<LeftMemberUserResponse>, <C as ClientApi>::Error>;
66pub type ListMembersResponse<C> = Result<Vec<GroupMember>, <C as ClientApi>::Error>;
67pub type UpdateGroupProfileResponse<C> = Result<Arc<GroupUpdatedResponse>, <C as ClientApi>::Error>;
68pub type SetContactCustomDataResponse<C> = Result<Arc<CmdOkResponse>, <C as ClientApi>::Error>;
69pub type SetGroupCustomDataResponse<C> = Result<Arc<CmdOkResponse>, <C as ClientApi>::Error>;
70pub type CreateGroupLinkResult<C> = Result<Arc<GroupLinkCreatedResponse>, <C as ClientApi>::Error>;
71pub type GroupLinkResult<C> = Result<Arc<GroupLinkResponse>, <C as ClientApi>::Error>;
72pub type DeleteGroupLinkResult<C> = Result<Arc<GroupLinkDeletedResponse>, <C as ClientApi>::Error>;
73pub type GetGroupRelaysResponse<C> = Result<Arc<GroupRelaysResponse>, <C as ClientApi>::Error>;
74pub type AddGroupRelaysResponse<C> = Result<ApiAddGroupRelaysResponse, <C as ClientApi>::Error>;
75
76pub trait ClientApiExt: ClientApi {
77    fn users(&self) -> impl Future<Output = UsersResponse<Self>>;
78
79    fn contacts<UID: Into<UserId>>(
80        &self,
81        user_id: UID,
82    ) -> impl Future<Output = ContactsResponse<Self>>;
83
84    fn groups<UID: Into<UserId>>(&self, user_id: UID)
85    -> impl Future<Output = GroupsResponse<Self>>;
86
87    fn accept_contact<CRID: Into<ContactRequestId>>(
88        &self,
89        contact_request_id: CRID,
90    ) -> impl Future<Output = AcceptContactResponse<Self>>;
91
92    fn reject_contact<CRID: Into<ContactRequestId>>(
93        &self,
94        contact_request_id: CRID,
95    ) -> impl Future<Output = RejectContactResponse<Self>>;
96
97    /// Like [ClientApi::create_active_user] but ensures that user is created even if the name
98    /// contains disallowed in SimpleX-Chat UTF-8 characters. The [NewUser] struct gets cloned when
99    /// performing the original request
100    fn new_user(&self, user: NewUser) -> impl Future<Output = NewUserResponse<Self>>;
101
102    /// Returns a powerful awaitable [MessageBuilder] type. Check its docs to learn how to build
103    /// any message kind ergonomically
104    fn send_message<CID: Into<ChatId>, M: MessageLike>(
105        &self,
106        chat_id: CID,
107        msg: M,
108    ) -> MessageBuilder<'_, Self, M::Kind>;
109
110    /// Deliver the same message to multiple recepients
111    fn multicast_message<I, M>(
112        &self,
113        chat_ids: I,
114        msg: M,
115    ) -> MulticastBuilder<'_, I, Self, M::Kind>
116    where
117        I: IntoIterator<Item = ChatId>,
118        M: MessageLike;
119
120    fn update_message<CID: Into<ChatId>, MID: Into<MessageId>>(
121        &self,
122        chat_id: CID,
123        message_id: MID,
124        new_content: MsgContent,
125    ) -> impl Future<Output = UpdateMessageResponse<Self>>;
126
127    fn batch_delete_messages<CID: Into<ChatId>, I: IntoIterator<Item = MessageId>>(
128        &self,
129        chat_id: CID,
130        message_ids: I,
131        mode: CIDeleteMode,
132    ) -> impl Future<Output = DeleteMessageResponse<Self>>;
133
134    fn delete_message<CID: Into<ChatId>, MID: Into<MessageId>>(
135        &self,
136        chat_id: CID,
137        message_id: MID,
138        mode: CIDeleteMode,
139    ) -> impl Future<Output = DeleteMessageResponse<Self>> {
140        self.batch_delete_messages(chat_id, std::iter::once(message_id.into()), mode)
141    }
142
143    fn batch_message_reactions<
144        CID: Into<ChatId>,
145        MID: Into<MessageId>,
146        I: IntoIterator<Item = Reaction>,
147    >(
148        &self,
149        chat_id: CID,
150        message_id: MID,
151        reactions: I,
152    ) -> impl Future<Output = UpdateMessageReactionsResponse<Self>>;
153
154    fn update_message_reaction<CID: Into<ChatId>, MID: Into<MessageId>>(
155        &self,
156        chat_id: CID,
157        message_id: MID,
158        reaction: Reaction,
159    ) -> impl Future<Output = UpdateMessageReactionsResponse<Self>> {
160        self.batch_message_reactions(chat_id, message_id, std::iter::once(reaction))
161    }
162
163    fn accept_file<FID: Into<FileId>>(&self, file_id: FID) -> AcceptFileBuilder<'_, Self>;
164
165    fn reject_file<FID: Into<FileId>>(
166        &self,
167        file_id: FID,
168    ) -> impl Future<Output = RejectFileResponse<Self>>;
169
170    fn initiate_connection(
171        &self,
172        link: impl Into<String>,
173    ) -> impl Future<Output = InitiateConnectionResponse<Self>>;
174
175    fn delete_chat<CID: Into<ChatId>>(
176        &self,
177        chat_id: CID,
178        mode: DeleteMode,
179    ) -> impl Future<Output = DeleteChatResponse<Self>>;
180
181    fn add_member<GID: Into<GroupId>, CID: Into<ContactId>>(
182        &self,
183        group_id: GID,
184        contact_id: CID,
185        role: GroupMemberRole,
186    ) -> impl Future<Output = AddMemberResponse<Self>>;
187
188    fn join_group<GID: Into<GroupId>>(
189        &self,
190        group_id: GID,
191    ) -> impl Future<Output = JoinGroupResponse<Self>>;
192
193    fn accept_member<GID: Into<GroupId>, MID: Into<MemberId>>(
194        &self,
195        group_id: GID,
196        member_id: MID,
197        role: GroupMemberRole,
198    ) -> impl Future<Output = AcceptMemberResponse<Self>>;
199
200    fn set_members_role<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
201        &self,
202        group_id: GID,
203        member_ids: I,
204        role: GroupMemberRole,
205    ) -> impl Future<Output = SetMembersRoleResponse<Self>>;
206
207    fn set_member_role<GID: Into<GroupId>, MID: Into<MemberId>>(
208        &self,
209        group_id: GID,
210        member_id: MID,
211        role: GroupMemberRole,
212    ) -> impl Future<Output = SetMembersRoleResponse<Self>> {
213        self.set_members_role(group_id, std::iter::once(member_id.into()), role)
214    }
215
216    fn block_members_for_all<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
217        &self,
218        group_id: GID,
219        member_ids: I,
220    ) -> impl Future<Output = BlockMembersResponse<Self>>;
221
222    fn unblock_members_for_all<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
223        &self,
224        group_id: GID,
225        member_ids: I,
226    ) -> impl Future<Output = BlockMembersResponse<Self>>;
227
228    fn block_member_for_all<GID: Into<GroupId>, MID: Into<MemberId>>(
229        &self,
230        group_id: GID,
231        member_id: MID,
232    ) -> impl Future<Output = BlockMembersResponse<Self>> {
233        self.block_members_for_all(group_id, std::iter::once(member_id.into()))
234    }
235
236    fn unblock_member_for_all<GID: Into<GroupId>, MID: Into<MemberId>>(
237        &self,
238        group_id: GID,
239        member_id: MID,
240    ) -> impl Future<Output = BlockMembersResponse<Self>> {
241        self.unblock_members_for_all(group_id, std::iter::once(member_id.into()))
242    }
243
244    fn remove_members<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
245        &self,
246        group_id: GID,
247        member_ids: I,
248    ) -> impl Future<Output = RemoveMembersResponse<Self>>;
249
250    fn remove_members_with_messages<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
251        &self,
252        group_id: GID,
253        member_ids: I,
254    ) -> impl Future<Output = RemoveMembersResponse<Self>>;
255
256    fn remove_member<GID: Into<GroupId>, MID: Into<MemberId>>(
257        &self,
258        group_id: GID,
259        member_id: MID,
260    ) -> impl Future<Output = RemoveMembersResponse<Self>> {
261        self.remove_members(group_id, std::iter::once(member_id.into()))
262    }
263
264    fn remove_member_with_messages<GID: Into<GroupId>, MID: Into<MemberId>>(
265        &self,
266        group_id: GID,
267        member_id: MID,
268    ) -> impl Future<Output = RemoveMembersResponse<Self>> {
269        self.remove_members_with_messages(group_id, std::iter::once(member_id.into()))
270    }
271
272    fn leave_group<GID: Into<GroupId>>(
273        &self,
274        group_id: GID,
275    ) -> impl Future<Output = LeaveGroupResponse<Self>>;
276
277    fn list_members<GID: Into<GroupId>>(
278        &self,
279        group_id: GID,
280    ) -> impl Future<Output = ListMembersResponse<Self>>;
281
282    fn moderate_messages<GID: Into<GroupId>, I: IntoIterator<Item = MessageId>>(
283        &self,
284        group_id: GID,
285        message_ids: I,
286    ) -> impl Future<Output = DeleteMessageResponse<Self>>;
287
288    fn moderate_message<GID: Into<GroupId>, MID: Into<MessageId>>(
289        &self,
290        group_id: GID,
291        message_id: MID,
292    ) -> impl Future<Output = DeleteMessageResponse<Self>> {
293        self.moderate_messages(group_id, std::iter::once(message_id.into()))
294    }
295
296    fn update_group_profile<GID: Into<GroupId>>(
297        &self,
298        group_id: GID,
299        profile: GroupProfile,
300    ) -> impl Future<Output = UpdateGroupProfileResponse<Self>>;
301
302    fn set_group_custom_data<GID: Into<GroupId>>(
303        &self,
304        group_id: GID,
305        data: Option<JsonObject>,
306    ) -> impl Future<Output = SetGroupCustomDataResponse<Self>>;
307
308    fn set_contact_custom_data<CID: Into<ContactId>>(
309        &self,
310        contact_id: CID,
311        data: Option<JsonObject>,
312    ) -> impl Future<Output = SetContactCustomDataResponse<Self>>;
313
314    fn create_group_link<GID: Into<GroupId>>(
315        &self,
316        group_id: GID,
317        role: GroupMemberRole,
318    ) -> impl Future<Output = CreateGroupLinkResult<Self>>;
319
320    fn set_group_link_role<GID: Into<GroupId>>(
321        &self,
322        group_id: GID,
323        role: GroupMemberRole,
324    ) -> impl Future<Output = GroupLinkResult<Self>>;
325
326    fn delete_group_link<GID: Into<GroupId>>(
327        &self,
328        group_id: GID,
329    ) -> impl Future<Output = DeleteGroupLinkResult<Self>>;
330
331    fn get_group_link<GID: Into<GroupId>>(
332        &self,
333        group_id: GID,
334    ) -> impl Future<Output = GroupLinkResult<Self>>;
335
336    fn get_group_relays<GID: Into<GroupId>>(
337        &self,
338        group_id: GID,
339    ) -> impl Future<Output = GetGroupRelaysResponse<Self>>;
340
341    fn add_group_relays<GID: Into<GroupId>, I: IntoIterator<Item = RelayId>>(
342        &self,
343        group_id: GID,
344        relay_ids: I,
345    ) -> impl Future<Output = AddGroupRelaysResponse<Self>>;
346
347    fn add_group_relay<GID: Into<GroupId>, RID: Into<RelayId>>(
348        &self,
349        group_id: GID,
350        relay_id: RID,
351    ) -> impl Future<Output = AddGroupRelaysResponse<Self>> {
352        self.add_group_relays(group_id, std::iter::once(relay_id.into()))
353    }
354}
355
356impl<C> ClientApiExt for C
357where
358    C: ClientApi,
359{
360    async fn users(&self) -> UsersResponse<Self> {
361        let mut response = self.list_users().await?;
362        let response = Arc::get_mut(&mut response).unwrap();
363
364        Ok(std::mem::take(&mut response.users))
365    }
366
367    async fn contacts<UID: Into<UserId>>(&self, user_id: UID) -> ContactsResponse<Self> {
368        let mut response = self.api_list_contacts(user_id.into().0).await?;
369        let response = Arc::get_mut(&mut response).unwrap();
370
371        Ok(std::mem::take(&mut response.contacts))
372    }
373
374    async fn groups<UID: Into<UserId>>(&self, user_id: UID) -> GroupsResponse<Self> {
375        let mut response = self
376            .api_list_groups(ApiListGroups::new(user_id.into().0))
377            .await?;
378        let response = Arc::get_mut(&mut response).unwrap();
379
380        Ok(std::mem::take(&mut response.groups))
381    }
382
383    async fn new_user(&self, mut user: NewUser) -> NewUserResponse<Self> {
384        match self.create_active_user(user.clone()).await {
385            Ok(response) => Ok(response),
386            Err(e) => match e.bad_response().and_then(|e| {
387                e.chat_error()
388                    .and_then(|e| e.error().and_then(|e| e.invalid_display_name()))
389            }) {
390                Some(err) => {
391                    user.profile.as_mut().unwrap().display_name = err.valid_name.clone();
392                    self.create_active_user(user).await
393                }
394                None => Err(e),
395            },
396        }
397    }
398
399    fn accept_contact<CRID: Into<ContactRequestId>>(
400        &self,
401        contact_request_id: CRID,
402    ) -> impl Future<Output = AcceptContactResponse<Self>> {
403        self.api_accept_contact(contact_request_id.into().0)
404    }
405
406    fn reject_contact<CRID: Into<ContactRequestId>>(
407        &self,
408        contact_request_id: CRID,
409    ) -> impl Future<Output = RejectContactResponse<Self>> {
410        self.api_reject_contact(contact_request_id.into().0)
411    }
412
413    fn send_message<CID: Into<ChatId>, M: MessageLike>(
414        &self,
415        cid: CID,
416        msg: M,
417    ) -> MessageBuilder<'_, Self, M::Kind> {
418        let (composed, kind) = msg.into_builder_parts();
419        MessageBuilder {
420            client: self,
421            chat_id: cid.into(),
422            live: false,
423            ttl: None,
424            msg: composed,
425            kind,
426        }
427    }
428
429    fn multicast_message<I, M>(&self, chat_ids: I, msg: M) -> MulticastBuilder<'_, I, Self, M::Kind>
430    where
431        I: IntoIterator<Item = ChatId>,
432        M: MessageLike,
433    {
434        let (msg, kind) = msg.into_builder_parts();
435        MulticastBuilder {
436            client: self,
437            chat_ids,
438            ttl: None,
439            msg,
440            kind,
441        }
442    }
443
444    fn update_message<CID: Into<ChatId>, MID: Into<MessageId>>(
445        &self,
446        chat_id: CID,
447        message_id: MID,
448        new_content: MsgContent,
449    ) -> impl Future<Output = UpdateMessageResponse<Self>> {
450        self.api_update_chat_item(ApiUpdateChatItem {
451            chat_ref: chat_id.into().into_chat_ref(),
452            chat_item_id: message_id.into().0,
453            live_message: false,
454            updated_message: UpdatedMessage {
455                msg_content: new_content,
456                mentions: Default::default(),
457                undocumented: Default::default(),
458            },
459        })
460    }
461
462    fn batch_delete_messages<CID: Into<ChatId>, I: IntoIterator<Item = MessageId>>(
463        &self,
464        chat_id: CID,
465        message_ids: I,
466        mode: CIDeleteMode,
467    ) -> impl Future<Output = DeleteMessageResponse<Self>> {
468        self.api_delete_chat_item(
469            chat_id.into().into_chat_ref(),
470            message_ids.into_iter().map(|id| id.0).collect(),
471            mode,
472        )
473    }
474
475    fn batch_message_reactions<
476        CID: Into<ChatId>,
477        MID: Into<MessageId>,
478        I: IntoIterator<Item = Reaction>,
479    >(
480        &self,
481        chat_id: CID,
482        message_id: MID,
483        reactions: I,
484    ) -> impl Future<Output = UpdateMessageReactionsResponse<Self>> {
485        let chat_id = chat_id.into();
486        let message_id = message_id.into();
487
488        futures::future::join_all(reactions.into_iter().map(|r| {
489            let (add, emoji) = match r {
490                Reaction::Set(e) => (true, e),
491                Reaction::Unset(e) => (false, e),
492            };
493
494            self.api_chat_item_reaction(ApiChatItemReaction {
495                chat_ref: chat_id.into_chat_ref(),
496                chat_item_id: message_id.0,
497                add,
498                reaction: MsgReaction::Emoji {
499                    emoji,
500                    undocumented: Default::default(),
501                },
502            })
503        }))
504    }
505
506    fn accept_file<FID: Into<FileId>>(&self, file_id: FID) -> AcceptFileBuilder<'_, Self> {
507        AcceptFileBuilder {
508            client: self,
509            cmd: ReceiveFile::new(file_id.into().0),
510        }
511    }
512
513    fn reject_file<FID: Into<FileId>>(
514        &self,
515        file_id: FID,
516    ) -> impl Future<Output = RejectFileResponse<Self>> {
517        self.cancel_file(file_id.into().0)
518    }
519
520    fn initiate_connection(
521        &self,
522        link: impl Into<String>,
523    ) -> impl Future<Output = InitiateConnectionResponse<Self>> {
524        self.connect(Connect {
525            incognito: false,
526            conn_link: Some(link.into()),
527        })
528        .map(|res| res.allow_undocumented())
529    }
530
531    fn delete_chat<CID: Into<ChatId>>(
532        &self,
533        chat_id: CID,
534        mode: DeleteMode,
535    ) -> impl Future<Output = DeleteChatResponse<Self>> {
536        self.api_delete_chat(chat_id.into().into_chat_ref(), mode.into())
537    }
538
539    fn add_member<GID: Into<GroupId>, CID: Into<ContactId>>(
540        &self,
541        group_id: GID,
542        contact_id: CID,
543        role: GroupMemberRole,
544    ) -> impl Future<Output = AddMemberResponse<Self>> {
545        self.api_add_member(group_id.into().0, contact_id.into().0, role)
546    }
547
548    fn join_group<GID: Into<GroupId>>(
549        &self,
550        group_id: GID,
551    ) -> impl Future<Output = JoinGroupResponse<Self>> {
552        self.api_join_group(group_id.into().0)
553    }
554
555    fn accept_member<GID: Into<GroupId>, MID: Into<MemberId>>(
556        &self,
557        group_id: GID,
558        member_id: MID,
559        role: GroupMemberRole,
560    ) -> impl Future<Output = AcceptMemberResponse<Self>> {
561        self.api_accept_member(group_id.into().0, member_id.into().0, role)
562    }
563
564    fn set_members_role<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
565        &self,
566        group_id: GID,
567        member_ids: I,
568        role: GroupMemberRole,
569    ) -> impl Future<Output = SetMembersRoleResponse<Self>> {
570        self.api_members_role(
571            group_id.into().0,
572            member_ids.into_iter().map(|id| id.0).collect(),
573            role,
574        )
575    }
576
577    fn block_members_for_all<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
578        &self,
579        group_id: GID,
580        member_ids: I,
581    ) -> impl Future<Output = BlockMembersResponse<Self>> {
582        self.api_block_members_for_all(ApiBlockMembersForAll {
583            group_id: group_id.into().0,
584            group_member_ids: member_ids.into_iter().map(|id| id.0).collect(),
585            blocked: true,
586        })
587    }
588
589    fn unblock_members_for_all<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
590        &self,
591        group_id: GID,
592        member_ids: I,
593    ) -> impl Future<Output = BlockMembersResponse<Self>> {
594        self.api_block_members_for_all(ApiBlockMembersForAll {
595            group_id: group_id.into().0,
596            group_member_ids: member_ids.into_iter().map(|id| id.0).collect(),
597            blocked: false,
598        })
599    }
600
601    fn remove_members<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
602        &self,
603        group_id: GID,
604        member_ids: I,
605    ) -> impl Future<Output = RemoveMembersResponse<Self>> {
606        self.api_remove_members(ApiRemoveMembers {
607            group_id: group_id.into().0,
608            group_member_ids: member_ids.into_iter().map(|id| id.0).collect(),
609            with_messages: false,
610        })
611    }
612
613    fn remove_members_with_messages<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
614        &self,
615        group_id: GID,
616        member_ids: I,
617    ) -> impl Future<Output = RemoveMembersResponse<Self>> {
618        self.api_remove_members(ApiRemoveMembers {
619            group_id: group_id.into().0,
620            group_member_ids: member_ids.into_iter().map(|id| id.0).collect(),
621            with_messages: true,
622        })
623    }
624
625    fn leave_group<GID: Into<GroupId>>(
626        &self,
627        group_id: GID,
628    ) -> impl Future<Output = LeaveGroupResponse<Self>> {
629        self.api_leave_group(group_id.into().0)
630    }
631
632    async fn list_members<GID: Into<GroupId>>(&self, group_id: GID) -> ListMembersResponse<Self> {
633        let mut response = self.api_list_members(group_id.into().0).await?;
634        let response = Arc::get_mut(&mut response).unwrap();
635        Ok(std::mem::take(&mut response.group.members))
636    }
637
638    fn moderate_messages<GID: Into<GroupId>, I: IntoIterator<Item = MessageId>>(
639        &self,
640        group_id: GID,
641        message_ids: I,
642    ) -> impl Future<Output = DeleteMessageResponse<Self>> {
643        self.api_delete_member_chat_item(
644            group_id.into().0,
645            message_ids.into_iter().map(|id| id.0).collect(),
646        )
647    }
648
649    fn update_group_profile<GID: Into<GroupId>>(
650        &self,
651        group_id: GID,
652        profile: GroupProfile,
653    ) -> impl Future<Output = UpdateGroupProfileResponse<Self>> {
654        self.api_update_group_profile(group_id.into().0, profile)
655    }
656
657    fn set_group_custom_data<GID: Into<GroupId>>(
658        &self,
659        group_id: GID,
660        data: Option<JsonObject>,
661    ) -> impl Future<Output = SetGroupCustomDataResponse<Self>> {
662        self.api_set_group_custom_data(ApiSetGroupCustomData {
663            group_id: group_id.into().0,
664            custom_data: data,
665        })
666    }
667
668    fn set_contact_custom_data<CID: Into<ContactId>>(
669        &self,
670        contact_id: CID,
671        data: Option<JsonObject>,
672    ) -> impl Future<Output = SetContactCustomDataResponse<Self>> {
673        self.api_set_contact_custom_data(ApiSetContactCustomData {
674            contact_id: contact_id.into().0,
675            custom_data: data,
676        })
677    }
678
679    fn create_group_link<GID: Into<GroupId>>(
680        &self,
681        group_id: GID,
682        role: GroupMemberRole,
683    ) -> impl Future<Output = CreateGroupLinkResult<Self>> {
684        self.api_create_group_link(group_id.into().0, role)
685    }
686
687    fn set_group_link_role<GID: Into<GroupId>>(
688        &self,
689        group_id: GID,
690        role: GroupMemberRole,
691    ) -> impl Future<Output = GroupLinkResult<Self>> {
692        self.api_group_link_member_role(group_id.into().0, role)
693    }
694
695    fn delete_group_link<GID: Into<GroupId>>(
696        &self,
697        group_id: GID,
698    ) -> impl Future<Output = DeleteGroupLinkResult<Self>> {
699        self.api_delete_group_link(group_id.into().0)
700    }
701
702    fn get_group_link<GID: Into<GroupId>>(
703        &self,
704        group_id: GID,
705    ) -> impl Future<Output = GroupLinkResult<Self>> {
706        self.api_get_group_link(group_id.into().0)
707    }
708
709    fn get_group_relays<GID: Into<GroupId>>(
710        &self,
711        group_id: GID,
712    ) -> impl Future<Output = GetGroupRelaysResponse<Self>> {
713        self.api_get_group_relays(group_id.into().0)
714    }
715
716    fn add_group_relays<GID: Into<GroupId>, I: IntoIterator<Item = RelayId>>(
717        &self,
718        group_id: GID,
719        relay_ids: I,
720    ) -> impl Future<Output = AddGroupRelaysResponse<Self>> {
721        self.api_add_group_relays(
722            group_id.into().0,
723            relay_ids.into_iter().map(|id| id.0).collect(),
724        )
725    }
726}
727
728pub trait FilterChatItems {
729    fn filter_messages(&self) -> impl Iterator<Item = (ChatId, &ChatItem, &MsgContent)>;
730}
731
732impl FilterChatItems for Vec<AChatItem> {
733    fn filter_messages(&self) -> impl Iterator<Item = (ChatId, &ChatItem, &MsgContent)> {
734        self.iter().filter_map(|item| {
735            ChatId::from_chat_info(&item.chat_info).and_then(|cid| {
736                item.chat_item
737                    .content
738                    .rcv_msg_content()
739                    .map(|msg| (cid, &item.chat_item, msg))
740            })
741        })
742    }
743}
744
745#[derive(Debug, Clone, Copy)]
746pub enum DeleteMode {
747    Full { notify: bool },
748    Entity { notify: bool },
749    Messages,
750}
751
752impl Default for DeleteMode {
753    fn default() -> Self {
754        Self::Full { notify: true }
755    }
756}
757
758impl From<DeleteMode> for ChatDeleteMode {
759    fn from(mode: DeleteMode) -> Self {
760        match mode {
761            DeleteMode::Full { notify } => ChatDeleteMode::Full {
762                notify,
763                undocumented: Default::default(),
764            },
765            DeleteMode::Entity { notify } => ChatDeleteMode::Entity {
766                notify,
767                undocumented: Default::default(),
768            },
769            DeleteMode::Messages => ChatDeleteMode::Messages,
770        }
771    }
772}
773
774// This impl mainly exist to catch breaking changes
775impl TryFrom<ChatDeleteMode> for DeleteMode {
776    type Error = ChatDeleteMode;
777
778    fn try_from(mode: ChatDeleteMode) -> Result<Self, Self::Error> {
779        match mode {
780            ChatDeleteMode::Full {
781                notify,
782                undocumented: _,
783            } => Ok(Self::Full { notify }),
784            ChatDeleteMode::Entity {
785                notify,
786                undocumented: _,
787            } => Ok(Self::Entity { notify }),
788            ChatDeleteMode::Messages => Ok(Self::Messages),
789            ChatDeleteMode::Undocumented(_) => Err(mode),
790            _ => Err(mode),
791        }
792    }
793}
794
795pub struct AcceptFileBuilder<'a, C: 'a + ?Sized> {
796    client: &'a C,
797    cmd: ReceiveFile,
798}
799
800impl<'a, C: 'a + ?Sized> AcceptFileBuilder<'a, C> {
801    pub fn via_user_approved_relays(mut self) -> Self {
802        self.cmd.user_approved_relays = true;
803        self
804    }
805
806    pub fn store_encrypted(mut self) -> Self {
807        self.cmd.store_encrypted = Some(true);
808        self
809    }
810
811    pub fn inline(mut self) -> Self {
812        self.cmd.file_inline = Some(true);
813        self
814    }
815
816    pub fn file_path<P: AsRef<std::path::Path>>(mut self, path: P) -> Self {
817        self.cmd.file_path = Some(path.as_ref().display().to_string());
818        self
819    }
820}
821
822impl<'a, C: 'a + ?Sized + ClientApi> IntoFuture for AcceptFileBuilder<'a, C> {
823    type Output = Result<ReceiveFileResponse, C::Error>;
824    type IntoFuture = Pin<Box<dyn 'a + Send + Future<Output = Self::Output>>>;
825
826    fn into_future(self) -> Self::IntoFuture {
827        Box::pin(self.client.receive_file(self.cmd))
828    }
829}
830
831#[derive(Debug, Clone)]
832pub enum Reaction {
833    Set(String),
834    Unset(String),
835}
836
837/// Convenience accessor for the [`CryptoFile`] stored inside a received file item.
838///
839/// Implemented for [`CIFile`] (direct access) and for [`simploxide_api_types::events::RcvFileComplete`]
840/// (drills through `chat_item.chat_item.file`).
841pub trait FileSourceExt {
842    /// Returns the file source, or `None` if no file source is present.
843    fn file_source(&self) -> Option<CryptoFile>;
844}
845
846impl FileSourceExt for CIFile {
847    fn file_source(&self) -> Option<CryptoFile> {
848        self.file_source.clone()
849    }
850}
851
852impl FileSourceExt for simploxide_api_types::events::RcvFileComplete {
853    fn file_source(&self) -> Option<CryptoFile> {
854        self.chat_item.chat_item.file.as_ref()?.file_source.clone()
855    }
856}