1use simploxide_api_types::{
18 AddressSettings, AutoAccept, BadgeProof, CIDeleteMode, ChatListQuery, ChatPeerType,
19 ConnectionPlan, Contact, CreatedConnLink, GroupInfo, GroupMember, GroupMemberRole,
20 GroupPreferences, GroupProfile, JsonObject, LocalProfile, MsgContent, NewUser,
21 PaginationByTime, Preferences, Profile, SimplexDomainClaim, User, UserInfo,
22 client_api::{BadResponseError, ClientApi, ClientApiError as _, UndocumentedResponse},
23 commands::ApiSetActiveUser,
24 responses::{
25 AcceptingContactRequestResponse, ActiveUserResponse, ApiChatsResponse,
26 ApiDeleteChatResponse, ApiNewPublicGroupResponse, ApiUpdateChatItemResponse,
27 ApiUpdateProfileResponse, CancelFileResponse, ChatItemReactionResponse,
28 ChatItemsDeletedResponse, CmdOkResponse, ConnectResponse, ConnectionPlanResponse,
29 ContactPrefsUpdatedResponse, ContactRequestRejectedResponse, GroupCreatedResponse,
30 GroupLinkCreatedResponse, GroupLinkDeletedResponse, GroupUpdatedResponse,
31 InvitationResponse, LeftMemberUserResponse, MemberAcceptedResponse,
32 MembersBlockedForAllUserResponse, MembersRoleUserResponse, SentGroupInvitationResponse,
33 UserAcceptedGroupSentResponse, UserDeletedMembersResponse, UserProfileUpdatedResponse,
34 },
35};
36
37use std::sync::Arc;
38
39use futures::{FutureExt as _, TryFutureExt as _};
40
41use crate::{
42 ext::{
43 AcceptFileBuilder, AddGroupRelaysResponse, ClientApiExt as _, DeleteMode,
44 GetGroupRelaysResponse, GroupLinkResult, Reaction,
45 },
46 id::{
47 ChatId, ContactId, ContactRequestId, FileId, GroupId, MemberId, MessageId, RelayId, UserId,
48 },
49 messages::{MessageBuilder, MessageLike, MulticastBuilder},
50 preferences,
51 preview::ImagePreview,
52};
53
54#[cfg(feature = "farm")]
55pub mod farm;
56
57#[cfg(feature = "farm")]
58pub use farm::BotFarm;
59
60#[derive(Clone)]
62pub struct Bot<C> {
63 client: C,
64 user_id: i64,
65}
66
67impl<C> Bot<C> {
68 pub fn client(&self) -> &C {
69 &self.client
70 }
71
72 pub fn user_id(&self) -> UserId {
73 UserId::from_raw(self.user_id)
74 }
75}
76
77impl<C: ClientApi> Bot<C> {
78 #[cfg(feature = "farm")]
79 fn new(client: C, user_id: UserId) -> Self {
80 Self {
81 client,
82 user_id: user_id.raw(),
83 }
84 }
85
86 pub async fn init(client: C, settings: BotSettings) -> Result<Self, C::Error> {
87 let mut users = client.users().await?;
88
89 match settings.display_name.match_user(&mut users) {
90 Some(current) => Self::init_existing(client, current, settings).await,
91 None => Self::init_new(client, settings).await,
92 }
93 }
94
95 async fn init_existing(
96 client: C,
97 user: &mut User,
98 settings: BotSettings,
99 ) -> Result<Self, C::Error> {
100 if !user.active_user {
101 client
102 .api_set_active_user(ApiSetActiveUser::new(user.user_id))
103 .await?;
104 }
105
106 let avatar = if let Some(preview) = settings.avatar {
107 Some(preview.resolve().await)
108 } else {
109 None
110 };
111
112 let bot = Bot {
113 client,
114 user_id: user.user_id,
115 };
116
117 let mut current = extract_profile(&mut user.profile);
118
119 current.display_name = settings.display_name.current();
120 let has_existing_address = current.contact_link.is_some();
121
122 let keep_contact_link = settings.auto_accept.is_some() || !has_existing_address;
127 let preserved_contact_link = keep_contact_link
128 .then(|| current.contact_link.take())
129 .flatten();
130
131 let profile = match settings.profile_settings {
132 Some(BotProfileSettings::Preferences(preferences)) => {
133 current.preferences = Some(preferences);
134 current.contact_link = preserved_contact_link;
135 current.image = avatar.or(current.image);
136 current.short_descr = settings.bio.or(current.short_descr);
137 current.description = settings.description.or(current.description);
138 current
139 }
140 Some(BotProfileSettings::FullProfile(mut new_profile)) => {
141 new_profile.contact_link = preserved_contact_link;
142 new_profile.image = new_profile.image.or(avatar);
143 new_profile.short_descr = new_profile.short_descr.or(settings.bio);
144 new_profile.description = new_profile.description.or(settings.description);
145 new_profile
146 }
147 None => {
148 let mut p = Self::default_profile(current.display_name);
149 p.contact_link = preserved_contact_link;
150 p.image = avatar;
151 p.short_descr = settings.bio;
152 p.description = settings.description;
153 p
154 }
155 };
156
157 bot.client
158 .update_profile(UserId::from_raw(user.user_id), profile)
159 .await?;
160
161 bot.setup_auto_accept(settings.auto_accept, has_existing_address)
162 .await?;
163
164 Ok(bot)
165 }
166
167 async fn init_new(client: C, settings: BotSettings) -> Result<Self, C::Error> {
168 let avatar = if let Some(preview) = settings.avatar {
169 Some(preview.resolve().await)
170 } else {
171 None
172 };
173
174 let bot_profile = match settings.profile_settings {
175 Some(BotProfileSettings::Preferences(preferences)) => {
176 let mut profile = Self::default_profile(settings.display_name.current());
177 profile.preferences = Some(preferences);
178 profile.image = avatar;
179 profile.short_descr = settings.bio;
180 profile.description = settings.description;
181 profile
182 }
183 Some(BotProfileSettings::FullProfile(mut profile)) => {
184 profile.image = profile.image.or(avatar);
185 profile.short_descr = profile.short_descr.or(settings.bio);
186 profile.description = profile.description.or(settings.description);
187 profile
188 }
189 None => {
190 let mut profile = Self::default_profile(settings.display_name.current());
191 profile.image = avatar;
192 profile.short_descr = settings.bio;
193 profile.description = settings.description;
194 profile
195 }
196 };
197
198 let response = client
199 .new_user(NewUser {
200 profile: Some(bot_profile),
201 client_service: false,
202 past_timestamp: false,
203 user_chat_relay: false,
204 undocumented: Default::default(),
205 })
206 .await?;
207
208 let bot = Bot {
209 client,
210 user_id: response.user.user_id,
211 };
212
213 bot.setup_auto_accept(settings.auto_accept, false).await?;
214 Ok(bot)
215 }
216
217 async fn setup_auto_accept(
218 &self,
219 auto_accept: Option<String>,
220 has_existing_address: bool,
221 ) -> Result<(), C::Error> {
222 if let Some(welcome_message) = auto_accept {
223 if !has_existing_address {
224 self.get_or_create_address().await?;
225 self.publish_address().await?;
226 }
227
228 self.configure_address(AddressSettings {
229 business_address: false,
230 auto_accept: Some(AutoAccept {
231 accept_incognito: false,
232 undocumented: Default::default(),
233 }),
234 auto_reply: (!welcome_message.is_empty())
235 .then(|| MsgContent::make_text(welcome_message)),
236 undocumented: Default::default(),
237 })
238 .await?;
239 } else if has_existing_address {
240 self.delete_address().await?;
241 }
242
243 Ok(())
244 }
245
246 pub fn wrap_client<W, F>(self, wrap: F) -> Bot<W>
252 where
253 W: ClientApi,
254 F: FnOnce(C) -> W,
255 {
256 let new_client = wrap(self.client);
257
258 Bot {
259 client: new_client,
260 user_id: self.user_id,
261 }
262 }
263
264 pub fn default_preferences() -> Preferences {
266 Preferences {
267 timed_messages: preferences::timed_messages::NO,
268 full_delete: preferences::YES,
269 reactions: preferences::NO,
270 voice: preferences::NO,
271 files: preferences::NO,
272 calls: preferences::NO,
273 sessions: preferences::NO,
274 commands: None,
275 undocumented: Default::default(),
276 }
277 }
278
279 pub fn default_profile(name: impl Into<String>) -> Profile {
281 Profile {
282 display_name: name.into(),
283 full_name: String::default(),
284 short_descr: None,
285 description: None,
286 image: None,
287 contact_link: None,
288 contact_domain: None,
289 preferences: Some(Self::default_preferences()),
290 badge: None,
291 peer_type: Some(ChatPeerType::Bot),
292 undocumented: serde_json::Value::Null,
293 }
294 }
295
296 pub fn info(&self) -> impl Future<Output = Result<Arc<ActiveUserResponse>, C::Error>> {
298 self.client.show_active_user()
299 }
300
301 pub fn initiate_connection(
313 &self,
314 link: impl Into<String>,
315 ) -> impl Future<Output = Result<UndocumentedResponse<ConnectResponse>, C::Error>> {
316 self.client.initiate_connection(link)
317 }
318
319 pub fn check_connection_plan(
322 &self,
323 target: impl Into<String>,
324 ) -> impl Future<Output = Result<Arc<ConnectionPlanResponse>, C::Error>> {
325 self.client.connection_plan(self.user_id(), target)
326 }
327
328 pub async fn initiate_connection_if<F: FnOnce(&ConnectionPlan) -> bool>(
342 &self,
343 link: impl Into<String>,
344 predicate: F,
345 ) -> Result<Connection, C::Error> {
346 let link = link.into();
347 let plan_resp = self.check_connection_plan(link.clone()).await?;
348
349 if !predicate(&plan_resp.connection_plan) {
350 return Ok(Connection::Rejected(plan_resp));
351 }
352
353 self.initiate_connection(link)
354 .await
355 .map(Connection::Initiated)
356 }
357
358 pub fn create_invitation_link(
363 &self,
364 ) -> impl Future<Output = Result<(String, Arc<InvitationResponse>), C::Error>> {
365 self.client
366 .create_invitation_link(self.user_id())
367 .map_ok(|resp| (extract_address(&resp.conn_link_invitation), resp))
368 }
369
370 pub fn create_address(&self) -> impl Future<Output = Result<String, C::Error>> {
371 self.client
372 .create_address(self.user_id())
373 .map_ok(|resp| extract_address(&resp.conn_link_contact))
374 }
375
376 pub fn address(&self) -> impl Future<Output = Result<String, C::Error>> {
379 self.client
380 .show_address(self.user_id())
381 .map_ok(|resp| extract_address(&resp.contact_link.conn_link_contact))
382 }
383
384 pub async fn get_or_create_address(&self) -> Result<String, C::Error> {
385 match self.address().await {
386 Ok(address) => Ok(address),
387 Err(e)
388 if e.bad_response()
389 .and_then(|e| {
390 e.chat_error().and_then(|e| {
391 e.error_store().map(|e| e.is_user_contact_link_not_found())
392 })
393 })
394 .unwrap_or(false) =>
395 {
396 self.create_address().await
397 }
398 Err(e) => Err(e),
399 }
400 }
401
402 pub fn configure_address(
403 &self,
404 settings: AddressSettings,
405 ) -> impl Future<Output = Result<(), C::Error>> {
406 self.client
407 .configure_address(self.user_id(), settings)
408 .map(|r| r.map(drop))
409 }
410
411 pub fn publish_address(
413 &self,
414 ) -> impl Future<Output = Result<Arc<UserProfileUpdatedResponse>, C::Error>> {
415 self.client.publish_address(self.user_id())
416 }
417
418 pub fn hide_address(
420 &self,
421 ) -> impl Future<Output = Result<Arc<UserProfileUpdatedResponse>, C::Error>> {
422 self.client.hide_address(self.user_id())
423 }
424
425 pub fn delete_address(&self) -> impl Future<Output = Result<(), C::Error>> {
426 self.client
427 .delete_address(self.user_id())
428 .map(|r| r.map(drop))
429 }
430
431 pub fn profile(&self) -> impl Future<Output = Result<Profile, C::Error>> {
432 self.client.show_active_user().map_ok(|mut resp| {
433 let resp = Arc::get_mut(&mut resp).unwrap();
434 extract_profile(&mut resp.user.profile)
435 })
436 }
437
438 pub async fn update_profile<F>(&self, updater: F) -> Result<ApiUpdateProfileResponse, C::Error>
440 where
441 F: 'static + Send + FnOnce(&mut Profile),
442 {
443 let mut profile = self.profile().await?;
444 updater(&mut profile);
445 self.client
446 .update_profile(self.user_id(), profile.clone())
447 .await
448 }
449
450 pub fn set_display_name(
451 &self,
452 name: impl Into<String>,
453 ) -> impl Future<Output = Result<ApiUpdateProfileResponse, C::Error>> {
454 let name = name.into();
455 self.update_profile(move |profile| profile.display_name = name)
456 }
457
458 pub fn set_full_name(
459 &self,
460 full_name: impl Into<String>,
461 ) -> impl Future<Output = Result<ApiUpdateProfileResponse, C::Error>> {
462 let full_name = full_name.into();
463 self.update_profile(move |profile| profile.full_name = full_name)
464 }
465
466 pub fn set_bio(
467 &self,
468 bio: impl Into<String>,
469 ) -> impl Future<Output = Result<ApiUpdateProfileResponse, C::Error>> {
470 let bio = bio.into();
471 self.update_profile(move |profile| profile.short_descr = Some(bio))
472 }
473
474 pub fn set_description(
475 &self,
476 description: impl Into<String>,
477 ) -> impl Future<Output = Result<ApiUpdateProfileResponse, C::Error>> {
478 let description = description.into();
479 self.update_profile(move |profile| profile.description = Some(description))
480 }
481
482 pub async fn set_avatar(
484 &self,
485 avatar: ImagePreview,
486 ) -> Result<ApiUpdateProfileResponse, C::Error> {
487 let image = avatar.resolve().await;
488 self.update_profile(move |profile| profile.image = Some(image))
489 .await
490 }
491
492 pub fn set_peer_type(
494 &self,
495 peer_type: ChatPeerType,
496 ) -> impl Future<Output = Result<ApiUpdateProfileResponse, C::Error>> {
497 self.update_profile(move |profile| profile.peer_type = Some(peer_type))
498 }
499
500 pub fn set_badge(
501 &self,
502 badge: BadgeProof,
503 ) -> impl Future<Output = Result<ApiUpdateProfileResponse, C::Error>> {
504 self.update_profile(move |profile| profile.badge = Some(badge))
505 }
506
507 pub fn set_contact_domain(
508 &self,
509 domain: SimplexDomainClaim,
510 ) -> impl Future<Output = Result<ApiUpdateProfileResponse, C::Error>> {
511 self.update_profile(move |profile| profile.contact_domain = Some(domain))
512 }
513
514 pub fn clear_contact_domain(
515 &self,
516 ) -> impl Future<Output = Result<ApiUpdateProfileResponse, C::Error>> {
517 self.update_profile(|profile| profile.contact_domain = None)
518 }
519
520 pub fn set_preferences(
522 &self,
523 preferences: Preferences,
524 ) -> impl Future<Output = Result<ApiUpdateProfileResponse, C::Error>> {
525 self.update_profile(move |profile| profile.preferences = Some(preferences))
526 }
527
528 pub async fn update_preferences<F>(
530 &self,
531 updater: F,
532 ) -> Result<ApiUpdateProfileResponse, C::Error>
533 where
534 F: 'static + Send + FnOnce(&mut Preferences),
535 {
536 let mut response = self.client.show_active_user().await?;
537 let response = Arc::get_mut(&mut response).unwrap();
538
539 let mut profile = extract_profile(&mut response.user.profile);
540 let mut preferences = extract_preferences(&mut profile.preferences);
541 updater(&mut preferences);
542 profile.preferences = Some(preferences);
543
544 self.client.update_profile(self.user_id(), profile).await
545 }
546
547 pub fn set_contact_preferences<CID: Into<ContactId>>(
549 &self,
550 contact_id: CID,
551 preferences: Preferences,
552 ) -> impl Future<Output = Result<Arc<ContactPrefsUpdatedResponse>, C::Error>> {
553 self.client.set_contact_prefs(contact_id, preferences)
554 }
555
556 pub async fn tweak_preferences_for_contact<CID: Into<ContactId>, F>(
559 &self,
560 contact_id: CID,
561 updater: F,
562 ) -> Result<Arc<ContactPrefsUpdatedResponse>, C::Error>
563 where
564 F: 'static + Send + FnOnce(&mut Preferences),
565 {
566 let mut response = self.client.show_active_user().await?;
567 let response = Arc::get_mut(&mut response).unwrap();
568
569 let mut preferences = extract_preferences(&mut response.user.profile.preferences);
570 updater(&mut preferences);
571
572 self.client.set_contact_prefs(contact_id, preferences).await
573 }
574
575 pub fn contacts(&self) -> impl Future<Output = Result<Vec<Contact>, C::Error>> {
577 self.client.contacts(self.user_id())
578 }
579
580 pub fn groups(&self) -> impl Future<Output = Result<Vec<GroupInfo>, C::Error>> {
582 self.client.groups(self.user_id())
583 }
584
585 pub fn accept_contact<CRID: Into<ContactRequestId>>(
587 &self,
588 contact_request_id: CRID,
589 ) -> impl Future<Output = Result<Arc<AcceptingContactRequestResponse>, C::Error>> {
590 self.client.accept_contact(contact_request_id)
591 }
592
593 pub fn reject_contact<CRID: Into<ContactRequestId>>(
595 &self,
596 contact_request_id: CRID,
597 ) -> impl Future<Output = Result<Arc<ContactRequestRejectedResponse>, C::Error>> {
598 self.client.reject_contact(contact_request_id)
599 }
600
601 pub fn send_msg<CID: Into<ChatId>, M: MessageLike>(
603 &self,
604 chat_id: CID,
605 msg: M,
606 ) -> MessageBuilder<'_, C, M::Kind> {
607 self.client.send_message(chat_id.into(), msg)
608 }
609
610 pub fn multicast<I, M>(&self, chat_ids: I, msg: M) -> MulticastBuilder<'_, I, C, M::Kind>
612 where
613 I: IntoIterator<Item = ChatId>,
614 M: MessageLike,
615 {
616 self.client.multicast_message(chat_ids, msg)
617 }
618
619 pub fn chat_ids(&self) -> impl Future<Output = Result<impl Iterator<Item = ChatId>, C::Error>> {
621 self.chat_ids_with(|_| true)
622 }
623
624 pub async fn chat_ids_with<F>(
626 &self,
627 f: F,
628 ) -> Result<impl 'static + Send + Iterator<Item = ChatId>, C::Error>
629 where
630 F: 'static + Send + FnMut(&ChatId) -> bool,
631 {
632 let (contacts, groups) = futures::future::try_join(self.contacts(), self.groups()).await?;
633
634 Ok(contacts
635 .into_iter()
636 .map(ChatId::from)
637 .chain(groups.into_iter().map(ChatId::from))
638 .filter(f))
639 }
640
641 pub fn prepare_broadcast<M: MessageLike>(
650 &self,
651 msg: M,
652 ) -> impl Future<
653 Output = Result<
654 MulticastBuilder<'_, impl 'static + Send + Iterator<Item = ChatId>, C, M::Kind>,
655 C::Error,
656 >,
657 > {
658 self.prepare_broadcast_with(msg, |_| true)
659 }
660
661 pub fn prepare_broadcast_with<M, F>(
671 &self,
672 msg: M,
673 f: F,
674 ) -> impl Future<
675 Output = Result<
676 MulticastBuilder<'_, impl 'static + Send + Iterator<Item = ChatId>, C, M::Kind>,
677 C::Error,
678 >,
679 >
680 where
681 F: 'static + Send + FnMut(&ChatId) -> bool,
682 M: MessageLike,
683 {
684 let (msg, kind) = msg.into_builder_parts();
685 self.chat_ids_with(f).map_ok(move |ids| MulticastBuilder {
686 client: self.client(),
687 chat_ids: ids,
688 ttl: None,
689 sign: false,
690 msg,
691 kind,
692 })
693 }
694
695 pub fn update_msg<CID: Into<ChatId>, MID: Into<MessageId>>(
696 &self,
697 chat_id: CID,
698 message_id: MID,
699 new_content: MsgContent,
700 ) -> impl Future<Output = Result<ApiUpdateChatItemResponse, C::Error>> {
701 self.client.update_message(chat_id, message_id, new_content)
702 }
703
704 pub fn delete_msg<CID: Into<ChatId>, MID: Into<MessageId>>(
705 &self,
706 chat_id: CID,
707 message_id: MID,
708 mode: CIDeleteMode,
709 ) -> impl Future<Output = Result<Arc<ChatItemsDeletedResponse>, C::Error>> {
710 self.client.delete_message(chat_id, message_id, mode)
711 }
712
713 pub fn batch_delete_msgs<CID: Into<ChatId>, I: IntoIterator<Item = MessageId>>(
714 &self,
715 chat_id: CID,
716 message_ids: I,
717 mode: CIDeleteMode,
718 ) -> impl Future<Output = Result<Arc<ChatItemsDeletedResponse>, C::Error>> {
719 self.client
720 .batch_delete_messages(chat_id, message_ids, mode)
721 }
722
723 pub fn batch_msg_reactions<
725 CID: Into<ChatId>,
726 MID: Into<MessageId>,
727 I: IntoIterator<Item = Reaction>,
728 >(
729 &self,
730 chat_id: CID,
731 message_id: MID,
732 reactions: I,
733 ) -> impl Future<Output = Vec<Result<Arc<ChatItemReactionResponse>, C::Error>>> {
734 self.client
735 .batch_message_reactions(chat_id, message_id, reactions)
736 }
737
738 pub fn update_msg_reaction<CID: Into<ChatId>, MID: Into<MessageId>>(
739 &self,
740 chat_id: CID,
741 message_id: MID,
742 reaction: Reaction,
743 ) -> impl Future<Output = Vec<Result<Arc<ChatItemReactionResponse>, C::Error>>> {
744 self.client
745 .update_message_reaction(chat_id, message_id, reaction)
746 }
747
748 pub fn accept_file<FID: Into<FileId>>(&self, file_id: FID) -> AcceptFileBuilder<'_, C> {
750 self.client.accept_file(file_id)
751 }
752
753 pub fn reject_file<FID: Into<FileId>>(
754 &self,
755 file_id: FID,
756 ) -> impl Future<Output = Result<CancelFileResponse, C::Error>> {
757 self.client.reject_file(file_id)
758 }
759
760 pub fn delete_chat<CID: Into<ChatId>>(
761 &self,
762 chat_id: CID,
763 mode: DeleteMode,
764 ) -> impl Future<Output = Result<ApiDeleteChatResponse, C::Error>> {
765 self.client.delete_chat(chat_id, mode)
766 }
767
768 pub fn create_group(
770 &self,
771 profile: GroupProfile,
772 ) -> impl Future<Output = Result<Arc<GroupCreatedResponse>, C::Error>> {
773 self.client.create_group(self.user_id(), profile)
774 }
775
776 pub fn create_public_group<I: IntoIterator<Item = RelayId>>(
779 &self,
780 relay_ids: I,
781 profile: GroupProfile,
782 ) -> impl Future<Output = Result<ApiNewPublicGroupResponse, C::Error>> {
783 self.client
784 .create_public_group(self.user_id(), relay_ids, profile)
785 }
786
787 pub fn set_auto_accept_member_contacts(
789 &self,
790 on: bool,
791 ) -> impl Future<Output = Result<Arc<CmdOkResponse>, C::Error>> {
792 self.client
793 .set_auto_accept_member_contacts(self.user_id(), on)
794 }
795
796 pub fn add_member<GID: Into<GroupId>, CID: Into<ContactId>>(
798 &self,
799 group_id: GID,
800 contact_id: CID,
801 role: GroupMemberRole,
802 ) -> impl Future<Output = Result<Arc<SentGroupInvitationResponse>, C::Error>> {
803 self.client.add_member(group_id, contact_id, role)
804 }
805
806 pub fn join_group<GID: Into<GroupId>>(
808 &self,
809 group_id: GID,
810 ) -> impl Future<Output = Result<Arc<UserAcceptedGroupSentResponse>, C::Error>> {
811 self.client.join_group(group_id)
812 }
813
814 pub fn accept_member<GID: Into<GroupId>, MID: Into<MemberId>>(
816 &self,
817 group_id: GID,
818 member_id: MID,
819 role: GroupMemberRole,
820 ) -> impl Future<Output = Result<Arc<MemberAcceptedResponse>, C::Error>> {
821 self.client.accept_member(group_id, member_id, role)
822 }
823
824 pub fn set_members_role<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
825 &self,
826 group_id: GID,
827 member_ids: I,
828 role: GroupMemberRole,
829 ) -> impl Future<Output = Result<Arc<MembersRoleUserResponse>, C::Error>> {
830 self.client.set_members_role(group_id, member_ids, role)
831 }
832
833 pub fn set_member_role<GID: Into<GroupId>, MID: Into<MemberId>>(
834 &self,
835 group_id: GID,
836 member_id: MID,
837 role: GroupMemberRole,
838 ) -> impl Future<Output = Result<Arc<MembersRoleUserResponse>, C::Error>> {
839 self.client.set_member_role(group_id, member_id, role)
840 }
841
842 pub fn block_members_for_all<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
844 &self,
845 group_id: GID,
846 member_ids: I,
847 ) -> impl Future<Output = Result<Arc<MembersBlockedForAllUserResponse>, C::Error>> {
848 self.client.block_members_for_all(group_id, member_ids)
849 }
850
851 pub fn unblock_members_for_all<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
853 &self,
854 group_id: GID,
855 member_ids: I,
856 ) -> impl Future<Output = Result<Arc<MembersBlockedForAllUserResponse>, C::Error>> {
857 self.client.unblock_members_for_all(group_id, member_ids)
858 }
859
860 pub fn block_member_for_all<GID: Into<GroupId>, MID: Into<MemberId>>(
862 &self,
863 group_id: GID,
864 member_id: MID,
865 ) -> impl Future<Output = Result<Arc<MembersBlockedForAllUserResponse>, C::Error>> {
866 self.client.block_member_for_all(group_id, member_id)
867 }
868
869 pub fn unblock_member_for_all<GID: Into<GroupId>, MID: Into<MemberId>>(
871 &self,
872 group_id: GID,
873 member_id: MID,
874 ) -> impl Future<Output = Result<Arc<MembersBlockedForAllUserResponse>, C::Error>> {
875 self.client.unblock_member_for_all(group_id, member_id)
876 }
877
878 pub fn remove_members<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
880 &self,
881 group_id: GID,
882 member_ids: I,
883 ) -> impl Future<Output = Result<Arc<UserDeletedMembersResponse>, C::Error>> {
884 self.client.remove_members(group_id, member_ids)
885 }
886
887 pub fn remove_members_with_messages<GID: Into<GroupId>, I: IntoIterator<Item = MemberId>>(
889 &self,
890 group_id: GID,
891 member_ids: I,
892 ) -> impl Future<Output = Result<Arc<UserDeletedMembersResponse>, C::Error>> {
893 self.client
894 .remove_members_with_messages(group_id, member_ids)
895 }
896
897 pub fn remove_member<GID: Into<GroupId>, MID: Into<MemberId>>(
899 &self,
900 group_id: GID,
901 member_id: MID,
902 ) -> impl Future<Output = Result<Arc<UserDeletedMembersResponse>, C::Error>> {
903 self.client.remove_member(group_id, member_id)
904 }
905
906 pub fn remove_member_with_messages<GID: Into<GroupId>, MID: Into<MemberId>>(
908 &self,
909 group_id: GID,
910 member_id: MID,
911 ) -> impl Future<Output = Result<Arc<UserDeletedMembersResponse>, C::Error>> {
912 self.client.remove_member_with_messages(group_id, member_id)
913 }
914
915 pub fn leave_group<GID: Into<GroupId>>(
916 &self,
917 group_id: GID,
918 ) -> impl Future<Output = Result<Arc<LeftMemberUserResponse>, C::Error>> {
919 self.client.leave_group(group_id)
920 }
921
922 pub fn list_members<GID: Into<GroupId>>(
923 &self,
924 group_id: GID,
925 ) -> impl Future<Output = Result<Vec<GroupMember>, C::Error>> {
926 self.client.list_members(group_id)
927 }
928
929 pub fn moderate_messages<GID: Into<GroupId>, I: IntoIterator<Item = MessageId>>(
931 &self,
932 group_id: GID,
933 message_ids: I,
934 ) -> impl Future<Output = Result<Arc<ChatItemsDeletedResponse>, C::Error>> {
935 self.client.moderate_messages(group_id, message_ids)
936 }
937
938 pub fn moderate_message<GID: Into<GroupId>, MID: Into<MessageId>>(
940 &self,
941 group_id: GID,
942 message_id: MID,
943 ) -> impl Future<Output = Result<Arc<ChatItemsDeletedResponse>, C::Error>> {
944 self.client.moderate_message(group_id, message_id)
945 }
946
947 pub fn update_group_profile<GID: Into<GroupId>>(
948 &self,
949 group_id: GID,
950 profile: GroupProfile,
951 ) -> impl Future<Output = Result<Arc<GroupUpdatedResponse>, C::Error>> {
952 self.client.update_group_profile(group_id, profile)
953 }
954
955 pub async fn update_group_profile_with<GID, F>(
958 &self,
959 group_id: GID,
960 updater: F,
961 ) -> Result<Arc<GroupUpdatedResponse>, C::Error>
962 where
963 GID: Into<GroupId>,
964 F: FnOnce(&mut GroupProfile),
965 {
966 let group_id = group_id.into();
967 let groups = self.groups().await?;
968 let Some(group) = groups.into_iter().find(|g| g.group_id == group_id.raw()) else {
969 return Err(BadResponseError::Undocumented(serde_json::json!({
970 "type": "groupNotFound",
971 "groupId": group_id.raw(),
972 }))
973 .into());
974 };
975 let mut profile = group.group_profile;
976 updater(&mut profile);
977 self.update_group_profile(group_id, profile).await
978 }
979
980 pub fn update_group_preferences<GID, F>(
981 &self,
982 group_id: GID,
983 updater: F,
984 ) -> impl Future<Output = Result<Arc<GroupUpdatedResponse>, C::Error>>
985 where
986 GID: Into<GroupId>,
987 F: FnOnce(&mut GroupPreferences),
988 {
989 self.update_group_profile_with(group_id, |profile| {
990 let mut prefs = extract_group_preferences(&mut profile.group_preferences);
991 updater(&mut prefs);
992 profile.group_preferences = Some(prefs);
993 })
994 }
995
996 pub fn set_group_sign_messages<GID: Into<GroupId>>(
997 &self,
998 group_id: GID,
999 on: bool,
1000 ) -> impl Future<Output = Result<Arc<GroupUpdatedResponse>, C::Error>> {
1001 self.update_group_preferences(group_id, move |prefs| {
1002 prefs.sign_messages = if on {
1003 preferences::group::YES
1004 } else {
1005 preferences::group::NO
1006 };
1007 })
1008 }
1009
1010 pub fn set_group_custom_data<GID: Into<GroupId>>(
1012 &self,
1013 group_id: GID,
1014 data: Option<JsonObject>,
1015 ) -> impl Future<Output = Result<Arc<CmdOkResponse>, C::Error>> {
1016 self.client.set_group_custom_data(group_id, data)
1017 }
1018
1019 pub fn set_contact_custom_data<CID: Into<ContactId>>(
1021 &self,
1022 contact_id: CID,
1023 data: Option<JsonObject>,
1024 ) -> impl Future<Output = Result<Arc<CmdOkResponse>, C::Error>> {
1025 self.client.set_contact_custom_data(contact_id, data)
1026 }
1027
1028 pub fn create_group_link<GID: Into<GroupId>>(
1029 &self,
1030 group_id: GID,
1031 role: GroupMemberRole,
1032 ) -> impl Future<Output = Result<Arc<GroupLinkCreatedResponse>, C::Error>> {
1033 self.client.create_group_link(group_id, role)
1034 }
1035
1036 pub fn set_group_link_role<GID: Into<GroupId>>(
1038 &self,
1039 group_id: GID,
1040 role: GroupMemberRole,
1041 ) -> impl Future<Output = GroupLinkResult<C>> {
1042 self.client.set_group_link_role(group_id, role)
1043 }
1044
1045 pub fn delete_group_link<GID: Into<GroupId>>(
1046 &self,
1047 group_id: GID,
1048 ) -> impl Future<Output = Result<Arc<GroupLinkDeletedResponse>, C::Error>> {
1049 self.client.delete_group_link(group_id)
1050 }
1051
1052 pub fn get_group_link<GID: Into<GroupId>>(
1053 &self,
1054 group_id: GID,
1055 ) -> impl Future<Output = GroupLinkResult<C>> {
1056 self.client.get_group_link(group_id)
1057 }
1058
1059 pub fn get_group_relays<GID: Into<GroupId>>(
1060 &self,
1061 group_id: GID,
1062 ) -> impl Future<Output = GetGroupRelaysResponse<C>> {
1063 self.client.get_group_relays(group_id)
1064 }
1065
1066 pub fn add_group_relays<GID: Into<GroupId>, I: IntoIterator<Item = RelayId>>(
1067 &self,
1068 group_id: GID,
1069 relay_ids: I,
1070 ) -> impl Future<Output = AddGroupRelaysResponse<C>> {
1071 self.client.add_group_relays(group_id, relay_ids)
1072 }
1073
1074 pub fn add_group_relay<GID: Into<GroupId>, RID: Into<RelayId>>(
1075 &self,
1076 group_id: GID,
1077 relay_id: RID,
1078 ) -> impl Future<Output = AddGroupRelaysResponse<C>> {
1079 self.client.add_group_relay(group_id, relay_id)
1080 }
1081
1082 pub fn get_chats(
1085 &self,
1086 pagination: PaginationByTime,
1087 query: ChatListQuery,
1088 ) -> impl Future<Output = Result<Arc<ApiChatsResponse>, C::Error>> {
1089 self.client.get_chats(self.user_id(), pagination, query)
1090 }
1091
1092 pub fn default_relays(&self) -> impl Future<Output = Result<Vec<RelayId>, C::Error>> {
1094 self.client.default_relays()
1095 }
1096
1097 pub fn accept_remote_ctrl(
1106 &self,
1107 handle: &crate::remote::CtrlHandle,
1108 link: &str,
1109 ) -> impl Future<Output = Result<(), crate::remote::CtrlError<C::Error>>> {
1110 handle.accept_remote_ctrl(&self.client, link)
1111 }
1112}
1113
1114#[cfg(feature = "xftp")]
1115impl<C: crate::xftp::XftpExt> Bot<C> {
1116 pub fn download_file<FID: Into<FileId>>(
1117 &self,
1118 file_id: FID,
1119 ) -> crate::xftp::DownloadFileBuilder<'_, C> {
1120 self.client.download_file(file_id)
1121 }
1122}
1123
1124#[cfg(feature = "websocket")]
1125impl crate::ws::Bot {
1126 pub fn shutdown(self) -> impl Future<Output = ()> {
1127 self.client.disconnect()
1128 }
1129}
1130
1131#[cfg(feature = "ffi")]
1132impl crate::ffi::Bot {
1133 pub fn shutdown(self) -> impl Future<Output = ()> {
1134 self.client.disconnect()
1135 }
1136}
1137
1138#[derive(Debug, Clone)]
1140pub struct BotSettings {
1141 pub display_name: BotName,
1142 pub auto_accept: Option<String>,
1145 pub profile_settings: Option<BotProfileSettings>,
1146 pub avatar: Option<ImagePreview>,
1147 pub bio: Option<String>,
1148 pub description: Option<String>,
1149}
1150
1151impl BotSettings {
1152 pub fn new(display_name: impl Into<BotName>) -> Self {
1153 Self {
1154 display_name: display_name.into(),
1155 auto_accept: None,
1156 profile_settings: None,
1157 avatar: None,
1158 bio: None,
1159 description: None,
1160 }
1161 }
1162
1163 pub fn with_avatar(mut self, avatar: ImagePreview) -> Self {
1164 self.avatar = Some(avatar);
1165 self
1166 }
1167
1168 pub fn with_bio(mut self, bio: impl Into<String>) -> Self {
1169 self.bio = Some(bio.into());
1170 self
1171 }
1172
1173 pub fn with_description(mut self, description: impl Into<String>) -> Self {
1174 self.description = Some(description.into());
1175 self
1176 }
1177
1178 pub fn auto_accept(mut self) -> Self {
1180 self.auto_accept = Some(String::default());
1181 self
1182 }
1183
1184 pub fn auto_accept_with(mut self, welcome_message: impl Into<String>) -> Self {
1186 self.auto_accept = Some(welcome_message.into());
1187 self
1188 }
1189
1190 pub fn with_profile_settings(mut self, settings: BotProfileSettings) -> Self {
1191 self.profile_settings = Some(settings);
1192 self
1193 }
1194}
1195
1196#[derive(Debug, Clone)]
1197pub enum BotName {
1198 Current(String),
1199 Rename { from: Vec<String>, to: String },
1200}
1201
1202impl<S: Into<String>> From<S> for BotName {
1203 fn from(name: S) -> Self {
1204 BotName::Current(name.into())
1205 }
1206}
1207
1208impl BotName {
1209 pub fn new(name: impl Into<String>) -> Self {
1210 Self::Current(name.into())
1211 }
1212
1213 pub fn rename<S: Into<String>>(
1214 from: impl IntoIterator<Item = S>,
1215 to: impl Into<String>,
1216 ) -> Self {
1217 let from = from.into_iter().map(|s| s.into()).collect();
1218 let to = to.into();
1219 Self::Rename { from, to }
1220 }
1221
1222 pub(crate) fn current(&self) -> String {
1223 match self {
1224 Self::Current(name) | Self::Rename { from: _, to: name } => name.clone(),
1225 }
1226 }
1227
1228 pub(crate) fn matches_new(&self, name: &String) -> bool {
1229 match self {
1230 Self::Current(current) => current == name,
1231 Self::Rename { from: _, to } => to == name,
1232 }
1233 }
1234
1235 pub(crate) fn matches_old(&self, name: &String) -> bool {
1236 match self {
1237 Self::Current(current) => current == name,
1238 Self::Rename { from, to: _ } => from.contains(name),
1239 }
1240 }
1241
1242 #[cfg(feature = "farm")]
1243 pub(crate) fn matches(&self, name: &String) -> bool {
1244 match self {
1245 Self::Current(current) => current == name,
1246 Self::Rename { from, to } => to == name || from.contains(name),
1247 }
1248 }
1249
1250 pub(crate) fn match_user<'a>(&self, users: &'a mut [UserInfo]) -> Option<&'a mut User> {
1254 let mut existing_user = None;
1255
1256 for info in users {
1257 if self.matches_new(&info.user.profile.display_name) {
1258 existing_user = Some(&mut info.user);
1259 break;
1260 }
1261
1262 if self.matches_old(&info.user.profile.display_name) {
1263 existing_user.get_or_insert(&mut info.user);
1264 }
1265 }
1266
1267 existing_user
1268 }
1269}
1270
1271#[allow(clippy::large_enum_variant)]
1273#[derive(Debug, Clone)]
1274pub enum BotProfileSettings {
1275 Preferences(Preferences),
1277 FullProfile(Profile),
1279}
1280
1281pub enum Connection {
1282 Initiated(UndocumentedResponse<ConnectResponse>),
1283 Rejected(Arc<ConnectionPlanResponse>),
1284}
1285
1286impl Connection {
1287 pub fn rejected(&self) -> Option<&ConnectionPlan> {
1288 if let Self::Rejected(resp) = self {
1289 Some(&resp.connection_plan)
1290 } else {
1291 None
1292 }
1293 }
1294
1295 pub fn initiated(&self) -> Option<&UndocumentedResponse<ConnectResponse>> {
1296 if let Self::Initiated(resp) = self {
1297 Some(resp)
1298 } else {
1299 None
1300 }
1301 }
1302
1303 pub fn is_rejected(&self) -> bool {
1304 self.rejected().is_some()
1305 }
1306
1307 pub fn is_initiated(&self) -> bool {
1308 self.initiated().is_some()
1309 }
1310}
1311
1312fn extract_address(link: &CreatedConnLink) -> String {
1313 link.conn_short_link
1314 .clone()
1315 .unwrap_or_else(|| link.conn_full_link.clone())
1316}
1317
1318fn extract_profile(local: &mut LocalProfile) -> Profile {
1319 Profile {
1320 display_name: std::mem::take(&mut local.display_name),
1321 full_name: std::mem::take(&mut local.full_name),
1322 short_descr: local.short_descr.take(),
1323 description: local.description.take(),
1324 image: local.image.take(),
1325 contact_link: local.contact_link.take(),
1326 contact_domain: local.contact_domain.take(),
1327 preferences: local.preferences.take(),
1328 peer_type: local.peer_type.take(),
1329 badge: None,
1330 undocumented: std::mem::take(&mut local.undocumented),
1331 }
1332}
1333
1334fn extract_group_preferences(prefs: &mut Option<GroupPreferences>) -> GroupPreferences {
1335 match prefs.as_mut() {
1336 Some(p) => GroupPreferences {
1337 timed_messages: p.timed_messages.take(),
1338 direct_messages: p.direct_messages.take(),
1339 full_delete: p.full_delete.take(),
1340 reactions: p.reactions.take(),
1341 voice: p.voice.take(),
1342 files: p.files.take(),
1343 simplex_links: p.simplex_links.take(),
1344 reports: p.reports.take(),
1345 history: p.history.take(),
1346 support: p.support.take(),
1347 sessions: p.sessions.take(),
1348 comments: p.comments.take(),
1349 sign_messages: p.sign_messages.take(),
1350 commands: p.commands.take(),
1351 undocumented: std::mem::take(&mut p.undocumented),
1352 },
1353 None => GroupPreferences {
1354 timed_messages: None,
1355 direct_messages: None,
1356 full_delete: None,
1357 reactions: None,
1358 voice: None,
1359 files: None,
1360 simplex_links: None,
1361 reports: None,
1362 history: None,
1363 support: None,
1364 sessions: None,
1365 comments: None,
1366 sign_messages: None,
1367 commands: None,
1368 undocumented: Default::default(),
1369 },
1370 }
1371}
1372
1373fn extract_preferences(preferences: &mut Option<Preferences>) -> Preferences {
1374 match preferences.as_mut() {
1375 Some(prefs) => Preferences {
1376 timed_messages: prefs.timed_messages.take(),
1377 full_delete: prefs.full_delete.take(),
1378 reactions: prefs.reactions.take(),
1379 voice: prefs.voice.take(),
1380 files: prefs.files.take(),
1381 calls: prefs.calls.take(),
1382 sessions: prefs.sessions.take(),
1383 commands: prefs.commands.take(),
1384 undocumented: std::mem::take(&mut prefs.undocumented),
1385 },
1386 None => Preferences {
1387 timed_messages: None,
1388 full_delete: None,
1389 reactions: None,
1390 voice: None,
1391 files: None,
1392 calls: None,
1393 sessions: None,
1394 commands: None,
1395 undocumented: Default::default(),
1396 },
1397 }
1398}