1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::time::Duration;
6
7#[cfg(not(target_arch = "wasm32"))]
8use async_io::Timer;
9#[cfg(target_arch = "wasm32")]
10use gloo_timers::future::sleep;
11
12use crate::BotError;
13
14#[cfg(not(target_arch = "wasm32"))]
15pub type ChatActionFuture<'a> = Pin<Box<dyn Future<Output = Result<(), BotError>> + Send + 'a>>;
16#[cfg(target_arch = "wasm32")]
17pub type ChatActionFuture<'a> = Pin<Box<dyn Future<Output = Result<(), BotError>> + 'a>>;
18
19#[cfg(not(target_arch = "wasm32"))]
20pub trait ChatActionSenderBounds: Send + Sync {}
21#[cfg(not(target_arch = "wasm32"))]
22impl<T: Send + Sync + ?Sized> ChatActionSenderBounds for T {}
23
24#[cfg(target_arch = "wasm32")]
25pub trait ChatActionSenderBounds {}
26#[cfg(target_arch = "wasm32")]
27impl<T: ?Sized> ChatActionSenderBounds for T {}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ChatAction {
35 Typing,
37 UploadPhoto,
39 RecordVideo,
41 UploadVideo,
43 RecordVoice,
45 UploadVoice,
47 UploadDocument,
49 ChooseSticker,
51 FindLocation,
53 RecordVideoNote,
55 UploadVideoNote,
57}
58
59pub trait ChatActionSender: ChatActionSenderBounds + 'static {
63 fn send_action(&self, action: ChatAction) -> ChatActionFuture<'_>;
65
66 fn action_expiry(&self) -> Duration;
70
71 fn clone_boxed(&self) -> Box<dyn ChatActionSender>;
73}
74
75pub struct ChatActionGuard {
89 stop_flag: Arc<AtomicBool>,
90}
91
92impl ChatActionGuard {
93 pub fn start(sender: Box<dyn ChatActionSender>, action: ChatAction) -> Self {
98 let stop_flag = Arc::new(AtomicBool::new(false));
99 let flag_clone = Arc::clone(&stop_flag);
100
101 let expiry = sender.action_expiry();
103 let renewal_interval = Duration::from_millis((expiry.as_millis() as u64 * 80) / 100);
104
105 spawn_renewal(async move {
106 let _ = sender.send_action(action).await;
108
109 loop {
110 sleep_for(renewal_interval).await;
112
113 if flag_clone.load(Ordering::Acquire) {
115 break;
116 }
117
118 if sender.send_action(action).await.is_err() {
120 break;
121 }
122 }
123 });
124
125 Self { stop_flag }
126 }
127}
128
129#[cfg(not(target_arch = "wasm32"))]
130async fn sleep_for(duration: Duration) {
131 Timer::after(duration).await;
132}
133
134#[cfg(not(target_arch = "wasm32"))]
135fn spawn_renewal(task: impl Future<Output = ()> + Send + 'static) {
136 executor_core::spawn(task).detach();
137}
138
139#[cfg(target_arch = "wasm32")]
140async fn sleep_for(duration: Duration) {
141 sleep(duration).await;
142}
143
144#[cfg(target_arch = "wasm32")]
145fn spawn_renewal(task: impl Future<Output = ()> + 'static) {
146 executor_core::spawn_local(task).detach();
147}
148
149impl Drop for ChatActionGuard {
150 fn drop(&mut self) {
151 self.stop_flag.store(true, Ordering::Release);
152 }
153}