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