oxidebot 0.1.8

A lightweight yet powerful chatbot framework
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
use std::time::Duration;

use anyhow::Result;

pub mod payload;
pub mod platform;
pub mod response;

use payload::{GroupAdminChangeType, GroupMuteType, RequestResponse, SendMessageTarget};
pub use response::{
    BotGetFriendListResponse, BotGetGroupListResponse, BotGetProfileResponse,
    GetMessageDetailResponse, GroupGetFileCountResponse, GroupGetFsListResponse,
    GroupGetProfileResponse, GroupMemberListResponse, SendMessageResponse, UserGetProfileResponse,
};

use crate::interaction::{
    BotCommand, BotCommandQuery, BotCommandSet, ChatMenu, InteractionCapabilities,
    InteractionResponse, InteractionResponseHandle, MessageComponents, MessageOptions,
    UnsupportedInteractionError,
};
use crate::source::{
    group::GroupProfile,
    message::{File, MessageSegment},
    user::UserProfile,
};
use crate::{
    application::{
        AppSurface, BotProfile, CommandDefinition, MiniAppEvent, Suggestion, SuggestionRequest,
    },
    capability::{BotCapabilities, UnsupportedFeatureError},
    collaboration::{
        CallOptions, CallSession, ChatActivity, PinOptions, PinnedMessage, Reaction,
        ReactionOptions,
    },
    commerce::{CheckoutRequest, Invoice, InvoiceOptions, Payment, ShippingOption},
    content::{
        BatchMessage, BatchSendResult, Checklist, ForwardOptions, MessageEnvelope, MessageQuery,
        OutgoingMessage, Poll,
    },
    conversation::{
        ConversationMember, ConversationProfile, ConversationRef, InviteLink, InviteLinkOptions,
        MessageRef, MessageTarget, Page, PageRequest, PermissionSet, Thread, ThreadOptions,
    },
};
use platform::{PlatformApiRequest, PlatformApiResponse, UnsupportedPlatformApiError};

/// CallApiTrait is a trait that defines the methods that a bot should implement to interact with the API.
/// If the bot does not implement the method, it will return an error.
#[async_trait::async_trait]
#[allow(unused_variables)]
pub trait CallApiTrait {
    /// Calls a platform-native API without losing platform-specific fields.
    ///
    /// Adapters should implement this once to expose their complete native API.
    /// The common methods below remain the ergonomic cross-platform layer.
    async fn call_platform_api(&self, request: PlatformApiRequest) -> Result<PlatformApiResponse> {
        Err(UnsupportedPlatformApiError {
            method: request.method,
        }
        .into())
    }

    /// Returns the platform-native methods explicitly known by this adapter.
    /// Calls are not required to be limited to this list, which keeps adapters
    /// forward-compatible with newly released platform methods.
    fn platform_api_methods(&self) -> &'static [&'static str] {
        &[]
    }

    fn supports_platform_api_method(&self, method: &str) -> bool {
        self.platform_api_methods().contains(&method)
    }

    /// Returns the high-level interactive capabilities implemented by this
    /// adapter. Platform-native APIs can expose additional capabilities.
    fn interaction_capabilities(&self) -> InteractionCapabilities {
        InteractionCapabilities::default()
    }

    /// Returns granular capabilities and platform limits. This is the
    /// preferred capability API for new adapters.
    fn bot_capabilities(&self) -> BotCapabilities {
        BotCapabilities::default()
    }

    /// Sends a fully modeled message. The default converts portable basic
    /// content to the legacy API, keeping existing adapters source-compatible.
    async fn send_outgoing_message(
        &self,
        target: MessageTarget,
        message: OutgoingMessage,
    ) -> Result<Vec<MessageRef>> {
        let legacy_target = legacy_send_target(&target)?;
        let conversation = target.conversation;
        let (segments, options) = message.try_into_legacy()?;
        Ok(self
            .send_message_with_options(segments, legacy_target, options)
            .await?
            .into_iter()
            .map(|response| {
                MessageRef::new(response.sent_message_id).in_conversation(conversation.clone())
            })
            .collect())
    }

    async fn edit_outgoing_message(
        &self,
        message: MessageRef,
        new_message: OutgoingMessage,
    ) -> Result<()> {
        let (segments, options) = new_message.try_into_legacy()?;
        if !options.is_empty() {
            return Err(UnsupportedFeatureError::new("editing message options atomically").into());
        }
        self.edit_message(message.id, segments).await
    }

    async fn delete_message_ref(&self, message: MessageRef) -> Result<()> {
        self.delete_message(message.id).await
    }

    async fn delete_messages(&self, messages: Vec<MessageRef>) -> Result<()> {
        for message in messages {
            self.delete_message_ref(message).await?;
        }
        Ok(())
    }

    /// Sends a message with interactive components while keeping the original
    /// `send_message` API source-compatible for existing adapters.
    async fn send_message_with_options(
        &self,
        message: Vec<MessageSegment>,
        target: SendMessageTarget,
        options: MessageOptions,
    ) -> Result<Vec<SendMessageResponse>> {
        if options.is_empty() {
            self.send_message(message, target).await
        } else {
            Err(UnsupportedInteractionError::new("message components").into())
        }
    }

    async fn edit_message_components(
        &self,
        message_id: String,
        components: Option<MessageComponents>,
    ) -> Result<()> {
        Err(UnsupportedInteractionError::new("editing message components").into())
    }

    async fn answer_interaction(
        &self,
        interaction_id: String,
        response: InteractionResponse,
    ) -> Result<()> {
        Err(UnsupportedInteractionError::new("answering interactions").into())
    }

    async fn set_bot_commands(&self, commands: BotCommandSet) -> Result<()> {
        Err(UnsupportedInteractionError::new("bot commands").into())
    }

    async fn get_bot_commands(&self, query: BotCommandQuery) -> Result<Vec<BotCommand>> {
        Err(UnsupportedInteractionError::new("bot commands").into())
    }

    async fn delete_bot_commands(&self, query: BotCommandQuery) -> Result<()> {
        Err(UnsupportedInteractionError::new("bot commands").into())
    }

    async fn set_chat_menu(&self, chat_id: Option<String>, menu: ChatMenu) -> Result<()> {
        Err(UnsupportedInteractionError::new("chat menu").into())
    }

    async fn get_chat_menu(&self, chat_id: Option<String>) -> Result<ChatMenu> {
        Err(UnsupportedInteractionError::new("chat menu").into())
    }

    async fn defer_interaction(
        &self,
        handle: InteractionResponseHandle,
        visibility: crate::interaction::InteractionVisibility,
    ) -> Result<()> {
        self.answer_interaction(handle.id, InteractionResponse::Defer { visibility })
            .await
    }

    async fn send_interaction_followup(
        &self,
        handle: InteractionResponseHandle,
        message: OutgoingMessage,
    ) -> Result<Vec<MessageRef>> {
        Err(UnsupportedInteractionError::new("interaction follow-up messages").into())
    }

    async fn edit_interaction_response(
        &self,
        handle: InteractionResponseHandle,
        message: OutgoingMessage,
    ) -> Result<()> {
        Err(UnsupportedInteractionError::new("editing the original interaction response").into())
    }

    async fn delete_interaction_response(&self, handle: InteractionResponseHandle) -> Result<()> {
        Err(UnsupportedInteractionError::new("deleting the original interaction response").into())
    }

    async fn set_message_reactions(
        &self,
        message: MessageRef,
        reactions: Vec<Reaction>,
    ) -> Result<()> {
        if let [reaction] = reactions.as_slice() {
            self.set_message_reaction(message.id, legacy_reaction_id(reaction)?)
                .await
        } else {
            Err(UnsupportedFeatureError::new("setting multiple message reactions").into())
        }
    }

    async fn add_message_reaction(
        &self,
        message: MessageRef,
        reaction: Reaction,
        options: ReactionOptions,
    ) -> Result<()> {
        self.set_message_reaction(message.id, legacy_reaction_id(&reaction)?)
            .await
    }

    async fn remove_message_reaction(&self, message: MessageRef, reaction: Reaction) -> Result<()> {
        Err(UnsupportedFeatureError::new("removing message reactions").into())
    }

    async fn clear_message_reactions(&self, message: MessageRef) -> Result<()> {
        Err(UnsupportedFeatureError::new("clearing message reactions").into())
    }

    async fn list_reaction_users(
        &self,
        message: MessageRef,
        reaction: Reaction,
        page: PageRequest,
    ) -> Result<Page<crate::source::user::User>> {
        Err(UnsupportedFeatureError::new("listing reaction users").into())
    }

    async fn pin_message(&self, message: MessageRef, options: PinOptions) -> Result<()> {
        Err(UnsupportedFeatureError::new("pinning messages").into())
    }

    async fn unpin_message(&self, message: MessageRef) -> Result<()> {
        Err(UnsupportedFeatureError::new("unpinning messages").into())
    }

    async fn clear_pins(&self, conversation: ConversationRef) -> Result<()> {
        Err(UnsupportedFeatureError::new("clearing pinned messages").into())
    }

    async fn list_pins(
        &self,
        conversation: ConversationRef,
        page: PageRequest,
    ) -> Result<Page<PinnedMessage>> {
        Err(UnsupportedFeatureError::new("listing pinned messages").into())
    }

    async fn set_chat_activity(
        &self,
        conversation: ConversationRef,
        activity: ChatActivity,
        duration: Option<Duration>,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("chat activity indicators").into())
    }

    async fn mark_read(
        &self,
        conversation: ConversationRef,
        through_message: Option<MessageRef>,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("read receipts").into())
    }

    async fn create_call(
        &self,
        conversation: ConversationRef,
        options: CallOptions,
    ) -> Result<CallSession> {
        Err(UnsupportedFeatureError::new("creating calls or meetings").into())
    }

    async fn end_call(&self, call: CallSession) -> Result<()> {
        Err(UnsupportedFeatureError::new("ending calls or meetings").into())
    }

    async fn list_call_participants(
        &self,
        call: CallSession,
        page: PageRequest,
    ) -> Result<Page<crate::source::user::User>> {
        Err(UnsupportedFeatureError::new("listing call participants").into())
    }

    async fn stop_poll(&self, message: MessageRef) -> Result<Poll> {
        Err(UnsupportedFeatureError::new("stopping polls").into())
    }

    async fn edit_checklist(&self, message: MessageRef, checklist: Checklist) -> Result<()> {
        Err(UnsupportedFeatureError::new("editing checklists").into())
    }

    async fn create_thread(
        &self,
        parent: ConversationRef,
        options: ThreadOptions,
    ) -> Result<Thread> {
        Err(UnsupportedFeatureError::new("creating threads or topics").into())
    }

    async fn edit_thread(&self, thread: ConversationRef, options: ThreadOptions) -> Result<Thread> {
        Err(UnsupportedFeatureError::new("editing threads or topics").into())
    }

    async fn close_thread(&self, thread: ConversationRef) -> Result<()> {
        Err(UnsupportedFeatureError::new("closing threads or topics").into())
    }

    async fn reopen_thread(&self, thread: ConversationRef) -> Result<()> {
        Err(UnsupportedFeatureError::new("reopening threads or topics").into())
    }

    async fn delete_thread(&self, thread: ConversationRef) -> Result<()> {
        Err(UnsupportedFeatureError::new("deleting threads or topics").into())
    }

    async fn list_threads(
        &self,
        parent: ConversationRef,
        page: PageRequest,
    ) -> Result<Page<Thread>> {
        Err(UnsupportedFeatureError::new("listing threads or topics").into())
    }

    async fn get_conversation_profile(
        &self,
        conversation: ConversationRef,
    ) -> Result<ConversationProfile> {
        Err(UnsupportedFeatureError::new("conversation profiles").into())
    }

    async fn set_conversation_profile(
        &self,
        conversation: ConversationRef,
        profile: ConversationProfile,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("conversation profiles").into())
    }

    async fn get_conversation_member(
        &self,
        conversation: ConversationRef,
        user_id: String,
    ) -> Result<ConversationMember> {
        Err(UnsupportedFeatureError::new("conversation members").into())
    }

    async fn list_conversation_members(
        &self,
        conversation: ConversationRef,
        page: PageRequest,
    ) -> Result<Page<ConversationMember>> {
        Err(UnsupportedFeatureError::new("conversation members").into())
    }

    async fn set_member_permissions(
        &self,
        conversation: ConversationRef,
        user_id: String,
        permissions: PermissionSet,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("member permissions").into())
    }

    async fn set_default_permissions(
        &self,
        conversation: ConversationRef,
        permissions: PermissionSet,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("default conversation permissions").into())
    }

    async fn set_member_tag(
        &self,
        conversation: ConversationRef,
        user_id: String,
        tag: Option<String>,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("member tags or labels").into())
    }

    async fn remove_conversation_member(
        &self,
        conversation: ConversationRef,
        user_id: String,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("removing conversation members").into())
    }

    async fn ban_conversation_member(
        &self,
        conversation: ConversationRef,
        user_id: String,
        duration: Option<Duration>,
        delete_history: bool,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("banning conversation members").into())
    }

    async fn unban_conversation_member(
        &self,
        conversation: ConversationRef,
        user_id: String,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("unbanning conversation members").into())
    }

    async fn approve_conversation_join_request(
        &self,
        conversation: ConversationRef,
        user_id: String,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("approving conversation join requests").into())
    }

    async fn decline_conversation_join_request(
        &self,
        conversation: ConversationRef,
        user_id: String,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("declining conversation join requests").into())
    }

    async fn leave_conversation(&self, conversation: ConversationRef) -> Result<()> {
        Err(UnsupportedFeatureError::new("leaving conversations").into())
    }

    async fn create_invite_link(
        &self,
        conversation: ConversationRef,
        options: InviteLinkOptions,
    ) -> Result<InviteLink> {
        Err(UnsupportedFeatureError::new("conversation invite links").into())
    }

    async fn edit_invite_link(
        &self,
        invite: InviteLink,
        options: InviteLinkOptions,
    ) -> Result<InviteLink> {
        Err(UnsupportedFeatureError::new("conversation invite links").into())
    }

    async fn revoke_invite_link(&self, invite: InviteLink) -> Result<()> {
        Err(UnsupportedFeatureError::new("conversation invite links").into())
    }

    async fn list_messages(&self, query: MessageQuery) -> Result<Page<MessageEnvelope>> {
        Err(UnsupportedFeatureError::new("message history").into())
    }

    async fn forward_messages(
        &self,
        messages: Vec<MessageRef>,
        target: MessageTarget,
        options: ForwardOptions,
    ) -> Result<Vec<MessageRef>> {
        Err(UnsupportedFeatureError::new("forwarding messages").into())
    }

    async fn copy_messages(
        &self,
        messages: Vec<MessageRef>,
        target: MessageTarget,
        options: ForwardOptions,
    ) -> Result<Vec<MessageRef>> {
        Err(UnsupportedFeatureError::new("copying messages").into())
    }

    async fn send_batch(&self, messages: Vec<BatchMessage>) -> Result<BatchSendResult> {
        let mut result = BatchSendResult::default();
        for (index, item) in messages.into_iter().enumerate() {
            match self.send_outgoing_message(item.target, item.message).await {
                Ok(messages) => {
                    for message in messages {
                        result.items.push(crate::content::BatchItemResult {
                            index,
                            message: Some(message),
                            error: None,
                        });
                    }
                }
                Err(error) => result.items.push(crate::content::BatchItemResult {
                    index,
                    message: None,
                    error: Some(error.to_string()),
                }),
            }
        }
        Ok(result)
    }

    async fn set_command_definitions(&self, commands: Vec<CommandDefinition>) -> Result<()> {
        Err(UnsupportedFeatureError::new("structured application commands").into())
    }

    async fn get_command_definitions(
        &self,
        conversation: Option<ConversationRef>,
    ) -> Result<Vec<CommandDefinition>> {
        Err(UnsupportedFeatureError::new("structured application commands").into())
    }

    async fn delete_command_definitions(
        &self,
        conversation: Option<ConversationRef>,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("structured application commands").into())
    }

    async fn answer_suggestion_request(
        &self,
        request: SuggestionRequest,
        suggestions: Vec<Suggestion>,
        next_cursor: Option<String>,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("dynamic suggestions or inline queries").into())
    }

    async fn publish_surface(&self, surface: AppSurface) -> Result<AppSurface> {
        Err(UnsupportedFeatureError::new("application surfaces").into())
    }

    async fn delete_surface(&self, surface: AppSurface) -> Result<()> {
        Err(UnsupportedFeatureError::new("application surfaces").into())
    }

    async fn answer_mini_app_query(
        &self,
        event: MiniAppEvent,
        message: OutgoingMessage,
    ) -> Result<Vec<MessageRef>> {
        Err(UnsupportedFeatureError::new("answering mini-app queries").into())
    }

    async fn set_bot_profile_v2(&self, profile: BotProfile) -> Result<()> {
        Err(UnsupportedFeatureError::new("localized bot profiles").into())
    }

    async fn get_bot_profile_v2(&self) -> Result<BotProfile> {
        Err(UnsupportedFeatureError::new("localized bot profiles").into())
    }

    async fn send_invoice(
        &self,
        target: MessageTarget,
        invoice: Invoice,
        options: InvoiceOptions,
    ) -> Result<MessageRef> {
        Err(UnsupportedFeatureError::new("invoices").into())
    }

    async fn answer_shipping_request(
        &self,
        request_id: String,
        options: Vec<ShippingOption>,
        error: Option<String>,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("shipping requests").into())
    }

    async fn answer_checkout_request(
        &self,
        request: CheckoutRequest,
        approved: bool,
        error: Option<String>,
    ) -> Result<()> {
        Err(UnsupportedFeatureError::new("checkout requests").into())
    }

    async fn refund_payment(&self, payment: Payment) -> Result<()> {
        Err(UnsupportedFeatureError::new("payment refunds").into())
    }

    async fn send_message(
        &self,
        message: Vec<MessageSegment>,
        target: SendMessageTarget,
    ) -> Result<Vec<SendMessageResponse>> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn delete_message(&self, message_id: String) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    /// Edits an existing message.
    ///
    /// The default delegates to the legacy misspelled `edit_messagee` method
    /// so existing 0.1 adapters remain source-compatible.
    async fn edit_message(
        &self,
        message_id: String,
        new_message: Vec<MessageSegment>,
    ) -> Result<()> {
        self.edit_messagee(message_id, new_message).await
    }

    /// Legacy spelling retained for 0.1 adapter compatibility.
    async fn edit_messagee(
        &self,
        message_id: String,
        new_message: Vec<MessageSegment>,
    ) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn get_message_detail(&self, message_id: String) -> Result<GetMessageDetailResponse> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn set_message_reaction(&self, message_id: String, reaction_id: String) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn get_group_member_list(&self, group_id: String) -> Result<GroupMemberListResponse> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn kick_group_member(
        &self,
        group_id: String,
        user_id: String,
        reject_add_request: Option<bool>,
    ) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn mute_group(
        &self,
        group_id: String,
        duration: Option<Duration>,
        r#type: GroupMuteType,
    ) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn mute_group_member(
        &self,
        group_id: String,
        user_id: String,
        r#type: GroupMuteType,
        duration: Option<Duration>,
    ) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn change_group_admin(
        &self,
        group_id: String,
        user_id: String,
        r#type: GroupAdminChangeType,
    ) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn set_group_member_alias(
        &self,
        group_id: String,
        user_id: String,
        new_alias: String,
    ) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn get_group_profile(&self, group_id: String) -> Result<GroupGetProfileResponse> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn set_group_profile(&self, group_id: String, new_profile: GroupProfile) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn get_group_file_count(
        &self,
        group_id: String,
        parent_folder_id: Option<String>,
    ) -> Result<GroupGetFileCountResponse> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn get_group_fs_list(
        &self,
        group_id: String,
        start_index: u64,
        count: u64,
    ) -> Result<GroupGetFsListResponse> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn delete_group_file(&self, group_id: String, file_id: String) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn delete_group_folder(&self, group_id: String, folder_id: String) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn create_group_folder(
        &self,
        group_id: String,
        folder_name: String,
        parent_folder_id: Option<String>,
    ) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn get_user_profile(&self, user_id: String) -> Result<UserGetProfileResponse> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn set_bot_profile(&self, new_profile: UserProfile) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn get_bot_profile(&self) -> Result<BotGetProfileResponse> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn get_bot_friend_list(&self) -> Result<BotGetFriendListResponse> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn get_bot_group_list(&self) -> Result<BotGetGroupListResponse> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn handle_add_friend_request(&self, id: String, response: RequestResponse) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn handle_add_group_request(&self, id: String, response: RequestResponse) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn handle_invite_group_request(
        &self,
        id: String,
        response: RequestResponse,
    ) -> Result<()> {
        Err(anyhow::anyhow!("Not implemented"))
    }

    async fn get_file_info(&self, file_id: String) -> Result<File> {
        Err(anyhow::anyhow!("Not implemented"))
    }
}

fn legacy_send_target(target: &MessageTarget) -> Result<SendMessageTarget> {
    anyhow::ensure!(
        target.conversation.parent.is_none()
            && target.conversation.platform_data.is_none()
            && target.platform_data.is_none()
            && target.recipients.is_empty(),
        "nested or platform-specific message targets require the v2 adapter API"
    );
    Ok(match target.conversation.kind {
        crate::conversation::ConversationKind::Direct => {
            SendMessageTarget::Private(target.conversation.id.clone())
        }
        crate::conversation::ConversationKind::Group
        | crate::conversation::ConversationKind::Channel => {
            SendMessageTarget::Group(target.conversation.id.clone())
        }
        _ => {
            return Err(UnsupportedFeatureError::new(
                "this conversation kind in the legacy message API",
            )
            .into())
        }
    })
}

fn legacy_reaction_id(reaction: &Reaction) -> Result<String> {
    Ok(match reaction {
        Reaction::UnicodeEmoji(emoji) => emoji.clone(),
        Reaction::CustomEmoji { id, .. } => id.clone(),
        Reaction::Paid => "paid".to_owned(),
        Reaction::PlatformNative { .. } => {
            return Err(UnsupportedFeatureError::new(
                "platform-native reactions in the legacy reaction API",
            )
            .into())
        }
    })
}