1use 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 fn new_user(&self, user: NewUser) -> impl Future<Output = NewUserResponse<Self>>;
141
142 fn send_message<CID: Into<ChatId>, M: MessageLike>(
145 &self,
146 chat_id: CID,
147 msg: M,
148 ) -> MessageBuilder<'_, Self, M::Kind>;
149
150 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 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
1109impl 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
1172pub trait FileSourceExt {
1177 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
1193pub 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}