Skip to main content

simploxide_client/
ext.rs

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