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, AddressSettings, CIDeleteMode, CIFile, ChatDeleteMode, ChatItem, ChatListQuery,
6    Contact, CryptoFile, GroupInfo, GroupMember, GroupMemberRole, GroupProfile, JsonObject,
7    MsgContent, MsgReaction, NewUser, PaginationByTime, PlanResolveMode, Preferences, Profile,
8    UpdatedMessage, UserInfo,
9    client_api::{
10        AllowUndocumentedResponses as _, BadResponseError, ClientApi, ClientApiError as _,
11        ExtractResponse as _, UndocumentedResponse,
12    },
13    commands::{
14        ApiAddContact, ApiBlockMembersForAll, ApiChatItemReaction, ApiConnectPlan, ApiGetChats,
15        ApiListGroups, ApiNewGroup, ApiNewPublicGroup, ApiRemoveMembers, ApiSetContactCustomData,
16        ApiSetGroupCustomData, ApiSetProfileAddress, ApiSetUserAutoAcceptMemberContacts,
17        ApiUpdateChatItem, Connect, ReceiveFile,
18    },
19    responses::{
20        AcceptingContactRequestResponse, ActiveUserResponse, ApiAddGroupRelaysResponse,
21        ApiChatsResponse, ApiDeleteChatResponse, ApiNewPublicGroupResponse,
22        ApiUpdateChatItemResponse, ApiUpdateProfileResponse, CancelFileResponse,
23        ChatItemReactionResponse, ChatItemsDeletedResponse, CmdOkResponse, ConnectResponse,
24        ConnectionPlanResponse, ContactPrefsUpdatedResponse, ContactRequestRejectedResponse,
25        GroupCreatedResponse, GroupLinkCreatedResponse, GroupLinkDeletedResponse,
26        GroupLinkResponse, GroupRelaysResponse, GroupUpdatedResponse, InvitationResponse,
27        LeftMemberUserResponse, MemberAcceptedResponse, MembersBlockedForAllUserResponse,
28        MembersRoleUserResponse, ReceiveFileResponse, RelayGroupAllowedResponse,
29        SentGroupInvitationResponse, UserAcceptedGroupSentResponse, UserContactLinkCreatedResponse,
30        UserContactLinkDeletedResponse, UserContactLinkResponse, UserContactLinkUpdatedResponse,
31        UserDeletedMembersResponse, UserProfileUpdatedResponse,
32    },
33};
34
35use std::{pin::Pin, sync::Arc};
36
37use crate::{
38    id::{
39        ChatId, ContactId, ContactRequestId, FileId, GroupId, MemberId, MessageId, RelayId, UserId,
40    },
41    messages::{MessageBuilder, MessageLike, MulticastBuilder},
42    util,
43};
44
45pub type InitiateConnectionResponse<C> =
46    Result<UndocumentedResponse<ConnectResponse>, <C as ClientApi>::Error>;
47
48pub type AcceptContactResponse<C> =
49    Result<Arc<AcceptingContactRequestResponse>, <C as ClientApi>::Error>;
50pub type RejectContactResponse<C> =
51    Result<Arc<ContactRequestRejectedResponse>, <C as ClientApi>::Error>;
52
53pub type RejectFileResponse<C> = Result<CancelFileResponse, <C as ClientApi>::Error>;
54
55pub type ContactsResponse<C> = Result<Vec<Contact>, <C as ClientApi>::Error>;
56pub type SetContactPreferencesResponse<C> =
57    Result<Arc<ContactPrefsUpdatedResponse>, <C as ClientApi>::Error>;
58pub type GroupsResponse<C> = Result<Vec<GroupInfo>, <C as ClientApi>::Error>;
59
60pub type CreateInvitationLinkResponse<C> = Result<Arc<InvitationResponse>, <C as ClientApi>::Error>;
61pub type CreateAddressResponse<C> =
62    Result<Arc<UserContactLinkCreatedResponse>, <C as ClientApi>::Error>;
63pub type CreateGroupLinkResult<C> = Result<Arc<GroupLinkCreatedResponse>, <C as ClientApi>::Error>;
64pub type GroupLinkResult<C> = Result<Arc<GroupLinkResponse>, <C as ClientApi>::Error>;
65pub type DeleteGroupLinkResult<C> = Result<Arc<GroupLinkDeletedResponse>, <C as ClientApi>::Error>;
66
67pub type ShowAddressResponse<C> = Result<Arc<UserContactLinkResponse>, <C as ClientApi>::Error>;
68pub type ConfigureAddressResponse<C> =
69    Result<Arc<UserContactLinkUpdatedResponse>, <C as ClientApi>::Error>;
70pub type SetProfileAddressResponse<C> =
71    Result<Arc<UserProfileUpdatedResponse>, <C as ClientApi>::Error>;
72pub type DeleteAddressResponse<C> =
73    Result<Arc<UserContactLinkDeletedResponse>, <C as ClientApi>::Error>;
74
75pub type UpdateProfileResponse<C> = Result<ApiUpdateProfileResponse, <C as ClientApi>::Error>;
76pub type ConnectionPlanResult<C> = Result<Arc<ConnectionPlanResponse>, <C as ClientApi>::Error>;
77
78pub type DeleteChatResponse<C> = Result<ApiDeleteChatResponse, <C as ClientApi>::Error>;
79pub type DeleteMessageResponse<C> = Result<Arc<ChatItemsDeletedResponse>, <C as ClientApi>::Error>;
80
81pub type UpdateMessageReactionsResponse<C> =
82    Vec<Result<Arc<ChatItemReactionResponse>, <C as ClientApi>::Error>>;
83pub type UpdateMessageResponse<C> = Result<ApiUpdateChatItemResponse, <C as ClientApi>::Error>;
84
85pub type NewUserResponse<C> = Result<Arc<ActiveUserResponse>, <C as ClientApi>::Error>;
86pub type UsersResponse<C> = Result<Vec<UserInfo>, <C as ClientApi>::Error>;
87
88pub type CreateGroupResponse<C> = Result<Arc<GroupCreatedResponse>, <C as ClientApi>::Error>;
89pub type CreatePublicGroupResponse<C> = Result<ApiNewPublicGroupResponse, <C as ClientApi>::Error>;
90pub type JoinGroupResponse<C> = Result<Arc<UserAcceptedGroupSentResponse>, <C as ClientApi>::Error>;
91pub type LeaveGroupResponse<C> = Result<Arc<LeftMemberUserResponse>, <C as ClientApi>::Error>;
92pub type AddMemberResponse<C> = Result<Arc<SentGroupInvitationResponse>, <C as ClientApi>::Error>;
93pub type SetAutoAcceptMemberContactsResponse<C> =
94    Result<Arc<CmdOkResponse>, <C as ClientApi>::Error>;
95pub type AcceptMemberResponse<C> = Result<Arc<MemberAcceptedResponse>, <C as ClientApi>::Error>;
96pub type SetMembersRoleResponse<C> = Result<Arc<MembersRoleUserResponse>, <C as ClientApi>::Error>;
97pub type BlockMembersResponse<C> =
98    Result<Arc<MembersBlockedForAllUserResponse>, <C as ClientApi>::Error>;
99pub type RemoveMembersResponse<C> =
100    Result<Arc<UserDeletedMembersResponse>, <C as ClientApi>::Error>;
101pub type ListMembersResponse<C> = Result<Vec<GroupMember>, <C as ClientApi>::Error>;
102pub type UpdateGroupProfileResponse<C> = Result<Arc<GroupUpdatedResponse>, <C as ClientApi>::Error>;
103
104pub type SetContactCustomDataResponse<C> = Result<Arc<CmdOkResponse>, <C as ClientApi>::Error>;
105pub type SetGroupCustomDataResponse<C> = Result<Arc<CmdOkResponse>, <C as ClientApi>::Error>;
106
107pub type GetGroupRelaysResponse<C> = Result<Arc<GroupRelaysResponse>, <C as ClientApi>::Error>;
108pub type AddGroupRelaysResponse<C> = Result<ApiAddGroupRelaysResponse, <C as ClientApi>::Error>;
109pub type AllowRelayGroupsResponse<C> =
110    Result<Arc<RelayGroupAllowedResponse>, <C as ClientApi>::Error>;
111
112pub type DefaultRelaysResponse<C> = Result<Vec<RelayId>, <C as ClientApi>::Error>;
113
114pub type GetChatsResponse<C> = Result<Arc<ApiChatsResponse>, <C as ClientApi>::Error>;
115
116pub trait ClientApiExt: ClientApi {
117    fn users(&self) -> impl Future<Output = UsersResponse<Self>>;
118
119    fn contacts<UID: Into<UserId>>(
120        &self,
121        user_id: UID,
122    ) -> impl Future<Output = ContactsResponse<Self>>;
123
124    fn groups<UID: Into<UserId>>(&self, user_id: UID)
125    -> impl Future<Output = GroupsResponse<Self>>;
126
127    fn accept_contact<CRID: Into<ContactRequestId>>(
128        &self,
129        contact_request_id: CRID,
130    ) -> impl Future<Output = AcceptContactResponse<Self>>;
131
132    fn reject_contact<CRID: Into<ContactRequestId>>(
133        &self,
134        contact_request_id: CRID,
135    ) -> impl Future<Output = RejectContactResponse<Self>>;
136
137    /// Like [ClientApi::create_active_user] but ensures that user is created even if the name
138    /// contains disallowed in SimpleX-Chat UTF-8 characters. The [NewUser] struct gets cloned when
139    /// performing the original request
140    fn new_user(&self, user: NewUser) -> impl Future<Output = NewUserResponse<Self>>;
141
142    /// Returns a powerful awaitable [MessageBuilder] type. Check its docs to learn how to build
143    /// any message kind ergonomically
144    fn send_message<CID: Into<ChatId>, M: MessageLike>(
145        &self,
146        chat_id: CID,
147        msg: M,
148    ) -> MessageBuilder<'_, Self, M::Kind>;
149
150    /// Deliver the same message to multiple recepients
151    fn multicast_message<I, M>(
152        &self,
153        chat_ids: I,
154        msg: M,
155    ) -> MulticastBuilder<'_, I, Self, M::Kind>
156    where
157        I: IntoIterator<Item = ChatId>,
158        M: MessageLike;
159
160    fn update_message<CID: Into<ChatId>, MID: Into<MessageId>>(
161        &self,
162        chat_id: CID,
163        message_id: MID,
164        new_content: MsgContent,
165    ) -> impl Future<Output = UpdateMessageResponse<Self>>;
166
167    fn batch_delete_messages<CID: Into<ChatId>, I: IntoIterator<Item = MessageId>>(
168        &self,
169        chat_id: CID,
170        message_ids: I,
171        mode: CIDeleteMode,
172    ) -> impl Future<Output = DeleteMessageResponse<Self>>;
173
174    fn delete_message<CID: Into<ChatId>, MID: Into<MessageId>>(
175        &self,
176        chat_id: CID,
177        message_id: MID,
178        mode: CIDeleteMode,
179    ) -> impl Future<Output = DeleteMessageResponse<Self>> {
180        self.batch_delete_messages(chat_id, std::iter::once(message_id.into()), mode)
181    }
182
183    fn batch_message_reactions<
184        CID: Into<ChatId>,
185        MID: Into<MessageId>,
186        I: IntoIterator<Item = Reaction>,
187    >(
188        &self,
189        chat_id: CID,
190        message_id: MID,
191        reactions: I,
192    ) -> impl Future<Output = UpdateMessageReactionsResponse<Self>>;
193
194    fn update_message_reaction<CID: Into<ChatId>, MID: Into<MessageId>>(
195        &self,
196        chat_id: CID,
197        message_id: MID,
198        reaction: Reaction,
199    ) -> impl Future<Output = UpdateMessageReactionsResponse<Self>> {
200        self.batch_message_reactions(chat_id, message_id, std::iter::once(reaction))
201    }
202
203    fn accept_file<FID: Into<FileId>>(&self, file_id: FID) -> AcceptFileBuilder<'_, Self>;
204
205    fn reject_file<FID: Into<FileId>>(
206        &self,
207        file_id: FID,
208    ) -> impl Future<Output = RejectFileResponse<Self>>;
209
210    fn initiate_connection(
211        &self,
212        link: impl Into<String>,
213    ) -> impl Future<Output = InitiateConnectionResponse<Self>>;
214
215    fn delete_chat<CID: Into<ChatId>>(
216        &self,
217        chat_id: CID,
218        mode: DeleteMode,
219    ) -> impl Future<Output = DeleteChatResponse<Self>>;
220
221    fn add_member<GID: Into<GroupId>, CID: Into<ContactId>>(
222        &self,
223        group_id: GID,
224        contact_id: CID,
225        role: GroupMemberRole,
226    ) -> impl Future<Output = AddMemberResponse<Self>>;
227
228    fn join_group<GID: Into<GroupId>>(
229        &self,
230        group_id: GID,
231    ) -> impl Future<Output = JoinGroupResponse<Self>>;
232
233    fn accept_member<GID: Into<GroupId>, MID: Into<MemberId>>(
234        &self,
235        group_id: GID,
236        member_id: MID,
237        role: GroupMemberRole,
238    ) -> impl Future<Output = AcceptMemberResponse<Self>>;
239
240    fn set_members_role<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
241        &self,
242        group_id: GID,
243        member_ids: I,
244        role: GroupMemberRole,
245    ) -> impl Future<Output = SetMembersRoleResponse<Self>>;
246
247    fn set_member_role<GID: Into<GroupId>, MID: Into<MemberId>>(
248        &self,
249        group_id: GID,
250        member_id: MID,
251        role: GroupMemberRole,
252    ) -> impl Future<Output = SetMembersRoleResponse<Self>> {
253        self.set_members_role(group_id, std::iter::once(member_id.into()), role)
254    }
255
256    fn block_members_for_all<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
257        &self,
258        group_id: GID,
259        member_ids: I,
260    ) -> impl Future<Output = BlockMembersResponse<Self>>;
261
262    fn unblock_members_for_all<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
263        &self,
264        group_id: GID,
265        member_ids: I,
266    ) -> impl Future<Output = BlockMembersResponse<Self>>;
267
268    fn block_member_for_all<GID: Into<GroupId>, MID: Into<MemberId>>(
269        &self,
270        group_id: GID,
271        member_id: MID,
272    ) -> impl Future<Output = BlockMembersResponse<Self>> {
273        self.block_members_for_all(group_id, std::iter::once(member_id.into()))
274    }
275
276    fn unblock_member_for_all<GID: Into<GroupId>, MID: Into<MemberId>>(
277        &self,
278        group_id: GID,
279        member_id: MID,
280    ) -> impl Future<Output = BlockMembersResponse<Self>> {
281        self.unblock_members_for_all(group_id, std::iter::once(member_id.into()))
282    }
283
284    fn remove_members<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
285        &self,
286        group_id: GID,
287        member_ids: I,
288    ) -> impl Future<Output = RemoveMembersResponse<Self>>;
289
290    fn remove_members_with_messages<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
291        &self,
292        group_id: GID,
293        member_ids: I,
294    ) -> impl Future<Output = RemoveMembersResponse<Self>>;
295
296    fn remove_member<GID: Into<GroupId>, MID: Into<MemberId>>(
297        &self,
298        group_id: GID,
299        member_id: MID,
300    ) -> impl Future<Output = RemoveMembersResponse<Self>> {
301        self.remove_members(group_id, std::iter::once(member_id.into()))
302    }
303
304    fn remove_member_with_messages<GID: Into<GroupId>, MID: Into<MemberId>>(
305        &self,
306        group_id: GID,
307        member_id: MID,
308    ) -> impl Future<Output = RemoveMembersResponse<Self>> {
309        self.remove_members_with_messages(group_id, std::iter::once(member_id.into()))
310    }
311
312    fn leave_group<GID: Into<GroupId>>(
313        &self,
314        group_id: GID,
315    ) -> impl Future<Output = LeaveGroupResponse<Self>>;
316
317    fn list_members<GID: Into<GroupId>>(
318        &self,
319        group_id: GID,
320    ) -> impl Future<Output = ListMembersResponse<Self>>;
321
322    fn moderate_messages<GID: Into<GroupId>, I: IntoIterator<Item = MessageId>>(
323        &self,
324        group_id: GID,
325        message_ids: I,
326    ) -> impl Future<Output = DeleteMessageResponse<Self>>;
327
328    fn moderate_message<GID: Into<GroupId>, MID: Into<MessageId>>(
329        &self,
330        group_id: GID,
331        message_id: MID,
332    ) -> impl Future<Output = DeleteMessageResponse<Self>> {
333        self.moderate_messages(group_id, std::iter::once(message_id.into()))
334    }
335
336    fn update_group_profile<GID: Into<GroupId>>(
337        &self,
338        group_id: GID,
339        profile: GroupProfile,
340    ) -> impl Future<Output = UpdateGroupProfileResponse<Self>>;
341
342    fn set_group_custom_data<GID: Into<GroupId>>(
343        &self,
344        group_id: GID,
345        data: Option<JsonObject>,
346    ) -> impl Future<Output = SetGroupCustomDataResponse<Self>>;
347
348    fn set_contact_custom_data<CID: Into<ContactId>>(
349        &self,
350        contact_id: CID,
351        data: Option<JsonObject>,
352    ) -> impl Future<Output = SetContactCustomDataResponse<Self>>;
353
354    fn create_group_link<GID: Into<GroupId>>(
355        &self,
356        group_id: GID,
357        role: GroupMemberRole,
358    ) -> impl Future<Output = CreateGroupLinkResult<Self>>;
359
360    fn set_group_link_role<GID: Into<GroupId>>(
361        &self,
362        group_id: GID,
363        role: GroupMemberRole,
364    ) -> impl Future<Output = GroupLinkResult<Self>>;
365
366    fn delete_group_link<GID: Into<GroupId>>(
367        &self,
368        group_id: GID,
369    ) -> impl Future<Output = DeleteGroupLinkResult<Self>>;
370
371    fn get_group_link<GID: Into<GroupId>>(
372        &self,
373        group_id: GID,
374    ) -> impl Future<Output = GroupLinkResult<Self>>;
375
376    fn get_group_relays<GID: Into<GroupId>>(
377        &self,
378        group_id: GID,
379    ) -> impl Future<Output = GetGroupRelaysResponse<Self>>;
380
381    fn add_group_relays<GID: Into<GroupId>, I: IntoIterator<Item = RelayId>>(
382        &self,
383        group_id: GID,
384        relay_ids: I,
385    ) -> impl Future<Output = AddGroupRelaysResponse<Self>>;
386
387    fn add_group_relay<GID: Into<GroupId>, RID: Into<RelayId>>(
388        &self,
389        group_id: GID,
390        relay_id: RID,
391    ) -> impl Future<Output = AddGroupRelaysResponse<Self>> {
392        self.add_group_relays(group_id, std::iter::once(relay_id.into()))
393    }
394
395    fn allow_replay_group<GID: Into<GroupId>>(
396        &self,
397        group_id: GID,
398    ) -> impl Future<Output = AllowRelayGroupsResponse<Self>>;
399
400    fn default_relays(&self) -> impl Future<Output = DefaultRelaysResponse<Self>>;
401
402    fn create_invitation_link<UID: Into<UserId>>(
403        &self,
404        user_id: UID,
405    ) -> impl Future<Output = CreateInvitationLinkResponse<Self>>;
406
407    fn create_address<UID: Into<UserId>>(
408        &self,
409        user_id: UID,
410    ) -> impl Future<Output = CreateAddressResponse<Self>>;
411
412    fn show_address<UID: Into<UserId>>(
413        &self,
414        user_id: UID,
415    ) -> impl Future<Output = ShowAddressResponse<Self>>;
416
417    fn configure_address<UID: Into<UserId>>(
418        &self,
419        user_id: UID,
420        settings: AddressSettings,
421    ) -> impl Future<Output = ConfigureAddressResponse<Self>>;
422
423    fn publish_address<UID: Into<UserId>>(
424        &self,
425        user_id: UID,
426    ) -> impl Future<Output = SetProfileAddressResponse<Self>>;
427
428    fn hide_address<UID: Into<UserId>>(
429        &self,
430        user_id: UID,
431    ) -> impl Future<Output = SetProfileAddressResponse<Self>>;
432
433    fn delete_address<UID: Into<UserId>>(
434        &self,
435        user_id: UID,
436    ) -> impl Future<Output = DeleteAddressResponse<Self>>;
437
438    fn update_profile<UID: Into<UserId>>(
439        &self,
440        user_id: UID,
441        profile: Profile,
442    ) -> impl Future<Output = UpdateProfileResponse<Self>>;
443
444    fn set_contact_prefs<CID: Into<ContactId>>(
445        &self,
446        contact_id: CID,
447        preferences: Preferences,
448    ) -> impl Future<Output = SetContactPreferencesResponse<Self>>;
449
450    fn connection_plan<UID: Into<UserId>>(
451        &self,
452        user_id: UID,
453        target: impl Into<String>,
454    ) -> impl Future<Output = ConnectionPlanResult<Self>>;
455
456    fn create_group<UID: Into<UserId>>(
457        &self,
458        user_id: UID,
459        profile: GroupProfile,
460    ) -> impl Future<Output = CreateGroupResponse<Self>>;
461
462    fn create_public_group<UID: Into<UserId>, I: IntoIterator<Item = RelayId>>(
463        &self,
464        user_id: UID,
465        relay_ids: I,
466        profile: GroupProfile,
467    ) -> impl Future<Output = CreatePublicGroupResponse<Self>>;
468
469    fn set_auto_accept_member_contacts<UID: Into<UserId>>(
470        &self,
471        user_id: UID,
472        on: bool,
473    ) -> impl Future<Output = SetAutoAcceptMemberContactsResponse<Self>>;
474
475    fn get_chats<UID: Into<UserId>>(
476        &self,
477        user_id: UID,
478        pagination: PaginationByTime,
479        query: ChatListQuery,
480    ) -> impl Future<Output = GetChatsResponse<Self>>;
481}
482
483impl<C> ClientApiExt for C
484where
485    C: ClientApi,
486{
487    async fn users(&self) -> UsersResponse<Self> {
488        let mut response = self.list_users().await?;
489        let response = Arc::get_mut(&mut response).unwrap();
490
491        Ok(std::mem::take(&mut response.users))
492    }
493
494    async fn contacts<UID: Into<UserId>>(&self, user_id: UID) -> ContactsResponse<Self> {
495        let mut response = self.api_list_contacts(user_id.into().raw()).await?;
496        let response = Arc::get_mut(&mut response).unwrap();
497
498        Ok(std::mem::take(&mut response.contacts))
499    }
500
501    async fn groups<UID: Into<UserId>>(&self, user_id: UID) -> GroupsResponse<Self> {
502        let mut response = self
503            .api_list_groups(ApiListGroups::new(user_id.into().raw()))
504            .await?;
505        let response = Arc::get_mut(&mut response).unwrap();
506
507        Ok(std::mem::take(&mut response.groups))
508    }
509
510    async fn new_user(&self, mut user: NewUser) -> NewUserResponse<Self> {
511        match self.create_active_user(user.clone()).await {
512            Ok(response) => Ok(response),
513            Err(e) => match e.bad_response().and_then(|e| {
514                e.chat_error()
515                    .and_then(|e| e.error().and_then(|e| e.invalid_display_name()))
516            }) {
517                Some(err) => {
518                    user.profile.as_mut().unwrap().display_name = err.valid_name.clone();
519                    self.create_active_user(user).await
520                }
521                None => Err(e),
522            },
523        }
524    }
525
526    fn accept_contact<CRID: Into<ContactRequestId>>(
527        &self,
528        contact_request_id: CRID,
529    ) -> impl Future<Output = AcceptContactResponse<Self>> {
530        self.api_accept_contact(contact_request_id.into().raw())
531    }
532
533    fn reject_contact<CRID: Into<ContactRequestId>>(
534        &self,
535        contact_request_id: CRID,
536    ) -> impl Future<Output = RejectContactResponse<Self>> {
537        self.api_reject_contact(contact_request_id.into().raw())
538    }
539
540    fn send_message<CID: Into<ChatId>, M: MessageLike>(
541        &self,
542        cid: CID,
543        msg: M,
544    ) -> MessageBuilder<'_, Self, M::Kind> {
545        let (composed, kind) = msg.into_builder_parts();
546        MessageBuilder {
547            client: self,
548            chat_id: cid.into(),
549            live: false,
550            sign: false,
551            ttl: None,
552            msg: composed,
553            kind,
554        }
555    }
556
557    fn multicast_message<I, M>(&self, chat_ids: I, msg: M) -> MulticastBuilder<'_, I, Self, M::Kind>
558    where
559        I: IntoIterator<Item = ChatId>,
560        M: MessageLike,
561    {
562        let (msg, kind) = msg.into_builder_parts();
563        MulticastBuilder {
564            client: self,
565            chat_ids,
566            ttl: None,
567            sign: false,
568            msg,
569            kind,
570        }
571    }
572
573    fn update_message<CID: Into<ChatId>, MID: Into<MessageId>>(
574        &self,
575        chat_id: CID,
576        message_id: MID,
577        new_content: MsgContent,
578    ) -> impl Future<Output = UpdateMessageResponse<Self>> {
579        self.api_update_chat_item(ApiUpdateChatItem {
580            chat_ref: chat_id.into().into_chat_ref(),
581            chat_item_id: message_id.into().raw(),
582            live_message: false,
583            updated_message: UpdatedMessage {
584                msg_content: new_content,
585                mentions: Default::default(),
586                undocumented: Default::default(),
587            },
588        })
589    }
590
591    fn batch_delete_messages<CID: Into<ChatId>, I: IntoIterator<Item = MessageId>>(
592        &self,
593        chat_id: CID,
594        message_ids: I,
595        mode: CIDeleteMode,
596    ) -> impl Future<Output = DeleteMessageResponse<Self>> {
597        self.api_delete_chat_item(
598            chat_id.into().into_chat_ref(),
599            message_ids.into_iter().map(|id| id.raw()).collect(),
600            mode,
601        )
602    }
603
604    fn batch_message_reactions<
605        CID: Into<ChatId>,
606        MID: Into<MessageId>,
607        I: IntoIterator<Item = Reaction>,
608    >(
609        &self,
610        chat_id: CID,
611        message_id: MID,
612        reactions: I,
613    ) -> impl Future<Output = UpdateMessageReactionsResponse<Self>> {
614        let chat_id = chat_id.into();
615        let message_id = message_id.into();
616
617        futures::future::join_all(reactions.into_iter().map(|r| {
618            let (add, emoji) = match r {
619                Reaction::Set(e) => (true, e),
620                Reaction::Unset(e) => (false, e),
621            };
622
623            self.api_chat_item_reaction(ApiChatItemReaction {
624                chat_ref: chat_id.into_chat_ref(),
625                chat_item_id: message_id.raw(),
626                add,
627                reaction: MsgReaction::Emoji {
628                    emoji,
629                    undocumented: Default::default(),
630                },
631            })
632        }))
633    }
634
635    fn accept_file<FID: Into<FileId>>(&self, file_id: FID) -> AcceptFileBuilder<'_, Self> {
636        AcceptFileBuilder {
637            client: self,
638            cmd: ReceiveFile::new(file_id.into().raw()),
639        }
640    }
641
642    fn reject_file<FID: Into<FileId>>(
643        &self,
644        file_id: FID,
645    ) -> impl Future<Output = RejectFileResponse<Self>> {
646        self.cancel_file(file_id.into().raw())
647    }
648
649    /// `target` can be SimpleX link or SimpleX name.
650    fn initiate_connection(
651        &self,
652        target: impl Into<String>,
653    ) -> impl Future<Output = InitiateConnectionResponse<Self>> {
654        self.connect(Connect {
655            incognito: false,
656            conn_target: Some(target.into()),
657        })
658        .map(|res| res.allow_undocumented())
659    }
660
661    async fn delete_chat<CID: Into<ChatId>>(
662        &self,
663        chat_id: CID,
664        mode: DeleteMode,
665    ) -> DeleteChatResponse<Self> {
666        let chat_id = chat_id.into();
667
668        self.api_delete_chat(chat_id.into_chat_ref(), mode.into())
669            .await
670    }
671
672    fn add_member<GID: Into<GroupId>, CID: Into<ContactId>>(
673        &self,
674        group_id: GID,
675        contact_id: CID,
676        role: GroupMemberRole,
677    ) -> impl Future<Output = AddMemberResponse<Self>> {
678        self.api_add_member(group_id.into().raw(), contact_id.into().raw(), role)
679    }
680
681    fn join_group<GID: Into<GroupId>>(
682        &self,
683        group_id: GID,
684    ) -> impl Future<Output = JoinGroupResponse<Self>> {
685        self.api_join_group(group_id.into().raw())
686    }
687
688    fn accept_member<GID: Into<GroupId>, MID: Into<MemberId>>(
689        &self,
690        group_id: GID,
691        member_id: MID,
692        role: GroupMemberRole,
693    ) -> impl Future<Output = AcceptMemberResponse<Self>> {
694        self.api_accept_member(group_id.into().raw(), member_id.into().raw(), role)
695    }
696
697    fn set_members_role<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
698        &self,
699        group_id: GID,
700        member_ids: I,
701        role: GroupMemberRole,
702    ) -> impl Future<Output = SetMembersRoleResponse<Self>> {
703        self.api_members_role(
704            group_id.into().raw(),
705            member_ids.into_iter().map(|id| id.raw()).collect(),
706            role,
707        )
708    }
709
710    fn block_members_for_all<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
711        &self,
712        group_id: GID,
713        member_ids: I,
714    ) -> impl Future<Output = BlockMembersResponse<Self>> {
715        self.api_block_members_for_all(ApiBlockMembersForAll {
716            group_id: group_id.into().raw(),
717            group_member_ids: member_ids.into_iter().map(|id| id.raw()).collect(),
718            blocked: true,
719        })
720    }
721
722    fn unblock_members_for_all<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
723        &self,
724        group_id: GID,
725        member_ids: I,
726    ) -> impl Future<Output = BlockMembersResponse<Self>> {
727        self.api_block_members_for_all(ApiBlockMembersForAll {
728            group_id: group_id.into().raw(),
729            group_member_ids: member_ids.into_iter().map(|id| id.raw()).collect(),
730            blocked: false,
731        })
732    }
733
734    fn remove_members<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
735        &self,
736        group_id: GID,
737        member_ids: I,
738    ) -> impl Future<Output = RemoveMembersResponse<Self>> {
739        self.api_remove_members(ApiRemoveMembers {
740            group_id: group_id.into().raw(),
741            group_member_ids: member_ids.into_iter().map(|id| id.raw()).collect(),
742            with_messages: false,
743        })
744    }
745
746    fn remove_members_with_messages<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
747        &self,
748        group_id: GID,
749        member_ids: I,
750    ) -> impl Future<Output = RemoveMembersResponse<Self>> {
751        self.api_remove_members(ApiRemoveMembers {
752            group_id: group_id.into().raw(),
753            group_member_ids: member_ids.into_iter().map(|id| id.raw()).collect(),
754            with_messages: true,
755        })
756    }
757
758    fn leave_group<GID: Into<GroupId>>(
759        &self,
760        group_id: GID,
761    ) -> impl Future<Output = LeaveGroupResponse<Self>> {
762        self.api_leave_group(group_id.into().raw())
763    }
764
765    async fn list_members<GID: Into<GroupId>>(&self, group_id: GID) -> ListMembersResponse<Self> {
766        let mut response = self.api_list_members(group_id.into().raw()).await?;
767        let response = Arc::get_mut(&mut response).unwrap();
768        Ok(std::mem::take(&mut response.group.members))
769    }
770
771    fn moderate_messages<GID: Into<GroupId>, I: IntoIterator<Item = MessageId>>(
772        &self,
773        group_id: GID,
774        message_ids: I,
775    ) -> impl Future<Output = DeleteMessageResponse<Self>> {
776        self.api_delete_member_chat_item(
777            group_id.into().raw(),
778            message_ids.into_iter().map(|id| id.raw()).collect(),
779        )
780    }
781
782    fn update_group_profile<GID: Into<GroupId>>(
783        &self,
784        group_id: GID,
785        profile: GroupProfile,
786    ) -> impl Future<Output = UpdateGroupProfileResponse<Self>> {
787        self.api_update_group_profile(group_id.into().raw(), profile)
788    }
789
790    fn set_group_custom_data<GID: Into<GroupId>>(
791        &self,
792        group_id: GID,
793        data: Option<JsonObject>,
794    ) -> impl Future<Output = SetGroupCustomDataResponse<Self>> {
795        self.api_set_group_custom_data(ApiSetGroupCustomData {
796            group_id: group_id.into().raw(),
797            custom_data: data,
798        })
799    }
800
801    fn set_contact_custom_data<CID: Into<ContactId>>(
802        &self,
803        contact_id: CID,
804        data: Option<JsonObject>,
805    ) -> impl Future<Output = SetContactCustomDataResponse<Self>> {
806        self.api_set_contact_custom_data(ApiSetContactCustomData {
807            contact_id: contact_id.into().raw(),
808            custom_data: data,
809        })
810    }
811
812    fn create_group_link<GID: Into<GroupId>>(
813        &self,
814        group_id: GID,
815        role: GroupMemberRole,
816    ) -> impl Future<Output = CreateGroupLinkResult<Self>> {
817        self.api_create_group_link(group_id.into().raw(), role)
818    }
819
820    fn set_group_link_role<GID: Into<GroupId>>(
821        &self,
822        group_id: GID,
823        role: GroupMemberRole,
824    ) -> impl Future<Output = GroupLinkResult<Self>> {
825        self.api_group_link_member_role(group_id.into().raw(), role)
826    }
827
828    fn delete_group_link<GID: Into<GroupId>>(
829        &self,
830        group_id: GID,
831    ) -> impl Future<Output = DeleteGroupLinkResult<Self>> {
832        self.api_delete_group_link(group_id.into().raw())
833    }
834
835    fn get_group_link<GID: Into<GroupId>>(
836        &self,
837        group_id: GID,
838    ) -> impl Future<Output = GroupLinkResult<Self>> {
839        self.api_get_group_link(group_id.into().raw())
840    }
841
842    fn get_group_relays<GID: Into<GroupId>>(
843        &self,
844        group_id: GID,
845    ) -> impl Future<Output = GetGroupRelaysResponse<Self>> {
846        self.api_get_group_relays(group_id.into().raw())
847    }
848
849    fn add_group_relays<GID: Into<GroupId>, I: IntoIterator<Item = RelayId>>(
850        &self,
851        group_id: GID,
852        relay_ids: I,
853    ) -> impl Future<Output = AddGroupRelaysResponse<Self>> {
854        self.api_add_group_relays(
855            group_id.into().raw(),
856            relay_ids.into_iter().map(|id| id.raw()).collect(),
857        )
858    }
859
860    fn allow_replay_group<GID: Into<GroupId>>(
861        &self,
862        group_id: GID,
863    ) -> impl Future<Output = AllowRelayGroupsResponse<Self>> {
864        self.api_allow_relay_group(group_id.into().raw())
865    }
866
867    fn create_invitation_link<UID: Into<UserId>>(
868        &self,
869        user_id: UID,
870    ) -> impl Future<Output = CreateInvitationLinkResponse<Self>> {
871        self.api_add_contact(ApiAddContact::new(user_id.into().raw()))
872    }
873
874    fn create_address<UID: Into<UserId>>(
875        &self,
876        user_id: UID,
877    ) -> impl Future<Output = CreateAddressResponse<Self>> {
878        self.api_create_my_address(user_id.into().raw())
879    }
880
881    fn show_address<UID: Into<UserId>>(
882        &self,
883        user_id: UID,
884    ) -> impl Future<Output = ShowAddressResponse<Self>> {
885        self.api_show_my_address(user_id.into().raw())
886    }
887
888    fn configure_address<UID: Into<UserId>>(
889        &self,
890        user_id: UID,
891        settings: AddressSettings,
892    ) -> impl Future<Output = ConfigureAddressResponse<Self>> {
893        self.api_set_address_settings(user_id.into().raw(), settings)
894    }
895
896    fn publish_address<UID: Into<UserId>>(
897        &self,
898        user_id: UID,
899    ) -> impl Future<Output = SetProfileAddressResponse<Self>> {
900        self.api_set_profile_address(ApiSetProfileAddress {
901            user_id: user_id.into().raw(),
902            enable: true,
903        })
904    }
905
906    fn hide_address<UID: Into<UserId>>(
907        &self,
908        user_id: UID,
909    ) -> impl Future<Output = SetProfileAddressResponse<Self>> {
910        self.api_set_profile_address(ApiSetProfileAddress {
911            user_id: user_id.into().raw(),
912            enable: false,
913        })
914    }
915
916    fn delete_address<UID: Into<UserId>>(
917        &self,
918        user_id: UID,
919    ) -> impl Future<Output = DeleteAddressResponse<Self>> {
920        self.api_delete_my_address(user_id.into().raw())
921    }
922
923    async fn update_profile<UID: Into<UserId>>(
924        &self,
925        user_id: UID,
926        mut profile: Profile,
927    ) -> UpdateProfileResponse<Self> {
928        let user_id = user_id.into().raw();
929        match self.api_update_profile(user_id, profile.clone()).await {
930            Ok(resp) => Ok(resp),
931            Err(e) => match e.bad_response().and_then(|e| {
932                e.chat_error()
933                    .and_then(|e| e.error().and_then(|e| e.invalid_display_name()))
934            }) {
935                Some(err) => {
936                    profile.display_name = err.valid_name.clone();
937                    self.api_update_profile(user_id, profile).await
938                }
939                None => Err(e),
940            },
941        }
942    }
943
944    fn set_contact_prefs<CID: Into<ContactId>>(
945        &self,
946        contact_id: CID,
947        preferences: Preferences,
948    ) -> impl Future<Output = SetContactPreferencesResponse<C>> {
949        self.api_set_contact_prefs(contact_id.into().raw(), preferences)
950    }
951
952    fn connection_plan<UID: Into<UserId>>(
953        &self,
954        user_id: UID,
955        target: impl Into<String>,
956    ) -> impl Future<Output = ConnectionPlanResult<Self>> {
957        self.api_connect_plan(ApiConnectPlan {
958            user_id: user_id.into().raw(),
959            connect_target: Some(target.into()),
960            resolve_mode: PlanResolveMode::Unknown,
961            link_owner_sig: None,
962        })
963    }
964
965    async fn create_group<UID: Into<UserId>>(
966        &self,
967        user_id: UID,
968        mut profile: GroupProfile,
969    ) -> CreateGroupResponse<Self> {
970        let user_id = user_id.into().raw();
971        match self
972            .api_new_group(ApiNewGroup::new(user_id, profile.clone()))
973            .await
974        {
975            Ok(resp) => Ok(resp),
976            Err(e) => match e.bad_response().and_then(|e| {
977                e.chat_error()
978                    .and_then(|e| e.error().and_then(|e| e.invalid_display_name()))
979            }) {
980                Some(err) => {
981                    profile.display_name = err.valid_name.clone();
982                    self.api_new_group(ApiNewGroup::new(user_id, profile)).await
983                }
984                None => Err(e),
985            },
986        }
987    }
988
989    async fn create_public_group<UID: Into<UserId>, I: IntoIterator<Item = RelayId>>(
990        &self,
991        user_id: UID,
992        relay_ids: I,
993        mut profile: GroupProfile,
994    ) -> CreatePublicGroupResponse<Self> {
995        let user_id = user_id.into().raw();
996        let relays: Vec<_> = relay_ids.into_iter().map(|id| id.raw()).collect();
997        match self
998            .api_new_public_group(ApiNewPublicGroup::new(
999                user_id,
1000                relays.clone(),
1001                profile.clone(),
1002            ))
1003            .await
1004        {
1005            Ok(resp) => Ok(resp),
1006            Err(e) => match e.bad_response().and_then(|e| {
1007                e.chat_error()
1008                    .and_then(|e| e.error().and_then(|e| e.invalid_display_name()))
1009            }) {
1010                Some(err) => {
1011                    profile.display_name = err.valid_name.clone();
1012                    self.api_new_public_group(ApiNewPublicGroup::new(user_id, relays, profile))
1013                        .await
1014                }
1015                None => Err(e),
1016            },
1017        }
1018    }
1019
1020    fn set_auto_accept_member_contacts<UID: Into<UserId>>(
1021        &self,
1022        user_id: UID,
1023        on: bool,
1024    ) -> impl Future<Output = SetAutoAcceptMemberContactsResponse<Self>> {
1025        self.api_set_user_auto_accept_member_contacts(ApiSetUserAutoAcceptMemberContacts {
1026            user_id: user_id.into().raw(),
1027            on_off: on,
1028        })
1029    }
1030
1031    fn get_chats<UID: Into<UserId>>(
1032        &self,
1033        user_id: UID,
1034        pagination: PaginationByTime,
1035        query: ChatListQuery,
1036    ) -> impl Future<Output = GetChatsResponse<Self>> {
1037        self.api_get_chats(ApiGetChats::new(user_id.into().raw(), pagination, query))
1038    }
1039
1040    async fn default_relays(&self) -> DefaultRelaysResponse<Self> {
1041        let raw = self.send_raw("/relays".to_owned()).await?;
1042        let response: Self::ResponseShape<'_, util::RelaysResp> =
1043            serde_json::from_str(&raw).map_err(BadResponseError::InvalidJson)?;
1044
1045        let response = response.extract_response()?;
1046        let ids = response
1047            .user_servers
1048            .into_iter()
1049            .flat_map(|g| g.chat_relays)
1050            .filter_map(|r| {
1051                if r.enabled {
1052                    RelayId::try_from(r.chat_relay_id).ok()
1053                } else {
1054                    None
1055                }
1056            })
1057            .collect();
1058
1059        Ok(ids)
1060    }
1061}
1062
1063pub trait FilterChatItems {
1064    fn filter_messages(&self) -> impl Iterator<Item = (ChatId, &ChatItem, &MsgContent)>;
1065}
1066
1067impl FilterChatItems for Vec<AChatItem> {
1068    fn filter_messages(&self) -> impl Iterator<Item = (ChatId, &ChatItem, &MsgContent)> {
1069        self.iter().filter_map(|item| {
1070            ChatId::from_chat_info(&item.chat_info).and_then(|cid| {
1071                item.chat_item
1072                    .content
1073                    .rcv_msg_content()
1074                    .map(|msg| (cid, &item.chat_item, msg))
1075            })
1076        })
1077    }
1078}
1079
1080#[derive(Debug, Clone, Copy)]
1081pub enum DeleteMode {
1082    Full { notify: bool },
1083    Entity { notify: bool },
1084    Messages,
1085}
1086
1087impl Default for DeleteMode {
1088    fn default() -> Self {
1089        Self::Full { notify: true }
1090    }
1091}
1092
1093impl From<DeleteMode> for ChatDeleteMode {
1094    fn from(mode: DeleteMode) -> Self {
1095        match mode {
1096            DeleteMode::Full { notify } => ChatDeleteMode::Full {
1097                notify,
1098                undocumented: Default::default(),
1099            },
1100            DeleteMode::Entity { notify } => ChatDeleteMode::Entity {
1101                notify,
1102                undocumented: Default::default(),
1103            },
1104            DeleteMode::Messages => ChatDeleteMode::Messages,
1105        }
1106    }
1107}
1108
1109// This impl mainly exist to catch breaking changes
1110impl TryFrom<ChatDeleteMode> for DeleteMode {
1111    type Error = ChatDeleteMode;
1112
1113    fn try_from(mode: ChatDeleteMode) -> Result<Self, Self::Error> {
1114        match mode {
1115            ChatDeleteMode::Full {
1116                notify,
1117                undocumented: _,
1118            } => Ok(Self::Full { notify }),
1119            ChatDeleteMode::Entity {
1120                notify,
1121                undocumented: _,
1122            } => Ok(Self::Entity { notify }),
1123            ChatDeleteMode::Messages => Ok(Self::Messages),
1124            ChatDeleteMode::Undocumented(_) => Err(mode),
1125            _ => Err(mode),
1126        }
1127    }
1128}
1129
1130pub struct AcceptFileBuilder<'a, C: 'a + ?Sized> {
1131    client: &'a C,
1132    cmd: ReceiveFile,
1133}
1134
1135impl<'a, C: 'a + ?Sized> AcceptFileBuilder<'a, C> {
1136    pub fn via_user_approved_relays(mut self) -> Self {
1137        self.cmd.user_approved_relays = true;
1138        self
1139    }
1140
1141    pub fn store_encrypted(mut self) -> Self {
1142        self.cmd.store_encrypted = Some(true);
1143        self
1144    }
1145
1146    pub fn inline(mut self) -> Self {
1147        self.cmd.file_inline = Some(true);
1148        self
1149    }
1150
1151    pub fn file_path<P: AsRef<std::path::Path>>(mut self, path: P) -> Self {
1152        self.cmd.file_path = Some(path.as_ref().display().to_string());
1153        self
1154    }
1155}
1156
1157impl<'a, C: 'a + ?Sized + ClientApi> IntoFuture for AcceptFileBuilder<'a, C> {
1158    type Output = Result<ReceiveFileResponse, C::Error>;
1159    type IntoFuture = Pin<Box<dyn 'a + Send + Future<Output = Self::Output>>>;
1160
1161    fn into_future(self) -> Self::IntoFuture {
1162        Box::pin(self.client.receive_file(self.cmd))
1163    }
1164}
1165
1166#[derive(Debug, Clone)]
1167pub enum Reaction {
1168    Set(String),
1169    Unset(String),
1170}
1171
1172/// Convenience accessor for the [`CryptoFile`] stored inside a received file item.
1173///
1174/// Implemented for [`CIFile`] (direct access) and for [`simploxide_api_types::events::RcvFileComplete`]
1175/// (drills through `chat_item.chat_item.file`).
1176pub trait FileSourceExt {
1177    /// Returns the file source, or `None` if no file source is present.
1178    fn file_source(&self) -> Option<CryptoFile>;
1179}
1180
1181impl FileSourceExt for CIFile {
1182    fn file_source(&self) -> Option<CryptoFile> {
1183        self.file_source.clone()
1184    }
1185}
1186
1187impl FileSourceExt for simploxide_api_types::events::RcvFileComplete {
1188    fn file_source(&self) -> Option<CryptoFile> {
1189        self.chat_item.chat_item.file.as_ref()?.file_source.clone()
1190    }
1191}
1192
1193/// Convenience accessor for group links
1194pub trait GroupLinkExt {
1195    fn link(&self) -> String;
1196}
1197
1198impl GroupLinkExt for simploxide_api_types::GroupLink {
1199    fn link(&self) -> String {
1200        self.conn_link_contact
1201            .conn_short_link
1202            .clone()
1203            .unwrap_or_else(|| self.conn_link_contact.conn_full_link.clone())
1204    }
1205}
1206
1207impl GroupLinkExt for simploxide_api_types::PreparedGroup {
1208    fn link(&self) -> String {
1209        self.conn_link_to_connect
1210            .conn_short_link
1211            .clone()
1212            .unwrap_or_else(|| self.conn_link_to_connect.conn_full_link.clone())
1213    }
1214}