1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4use std::time::Duration;
5
6#[cfg(not(target_arch = "wasm32"))]
7use async_io::Timer;
8#[cfg(target_arch = "wasm32")]
9use gloo_timers::future::sleep;
10
11use crate::BotError;
12
13#[cfg(not(target_arch = "wasm32"))]
14type ChatActionFuture<'a> = Pin<Box<dyn Future<Output = Result<(), BotError>> + Send + 'a>>;
15#[cfg(target_arch = "wasm32")]
16type ChatActionFuture<'a> = Pin<Box<dyn Future<Output = Result<(), BotError>> + 'a>>;
17
18#[cfg(not(target_arch = "wasm32"))]
19pub trait ChatActionSenderBounds: Send + Sync {}
20#[cfg(not(target_arch = "wasm32"))]
21impl<T: Send + Sync + ?Sized> ChatActionSenderBounds for T {}
22
23#[cfg(target_arch = "wasm32")]
24pub trait ChatActionSenderBounds {}
25#[cfg(target_arch = "wasm32")]
26impl<T: ?Sized> ChatActionSenderBounds for T {}
27
28#[cfg(not(target_arch = "wasm32"))]
29pub trait ChatActionFutureBounds: Future + Send {}
30#[cfg(not(target_arch = "wasm32"))]
31impl<T: Future + Send + ?Sized> ChatActionFutureBounds for T {}
32
33#[cfg(target_arch = "wasm32")]
34pub trait ChatActionFutureBounds: Future {}
35#[cfg(target_arch = "wasm32")]
36impl<T: Future + ?Sized> ChatActionFutureBounds for T {}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum ChatAction {
44 Typing,
46 UploadPhoto,
48 RecordVideo,
50 UploadVideo,
52 RecordVoice,
54 UploadVoice,
56 UploadDocument,
58 ChooseSticker,
60 FindLocation,
62 RecordVideoNote,
64 UploadVideoNote,
66}
67
68pub trait ChatActionSender: ChatActionSenderBounds + 'static {
74 fn send_action(
76 &self,
77 action: ChatAction,
78 ) -> impl ChatActionFutureBounds<Output = Result<(), BotError>> + '_;
79
80 fn action_expiry(&self) -> Duration;
84
85 fn clear_action(&self) -> impl ChatActionFutureBounds<Output = Result<(), BotError>> + '_ {
91 async { Ok(()) }
92 }
93}
94
95trait ChatActionSenderImpl: ChatActionSenderBounds {
97 fn send_action_boxed<'a>(&'a self, action: ChatAction) -> ChatActionFuture<'a>;
98 fn action_expiry(&self) -> Duration;
99 fn clear_action_boxed<'a>(&'a self) -> ChatActionFuture<'a>;
100}
101
102impl<T: ChatActionSender> ChatActionSenderImpl for T {
103 fn send_action_boxed<'a>(&'a self, action: ChatAction) -> ChatActionFuture<'a> {
104 Box::pin(ChatActionSender::send_action(self, action))
105 }
106
107 fn action_expiry(&self) -> Duration {
108 ChatActionSender::action_expiry(self)
109 }
110
111 fn clear_action_boxed<'a>(&'a self) -> ChatActionFuture<'a> {
112 Box::pin(ChatActionSender::clear_action(self))
113 }
114}
115
116#[derive(Clone)]
122pub struct AnyChatActionSender {
123 inner: Arc<dyn ChatActionSenderImpl>,
124}
125
126impl AnyChatActionSender {
127 pub fn new(sender: impl ChatActionSender) -> Self {
129 Self {
130 inner: Arc::new(sender),
131 }
132 }
133
134 pub fn send_action(
136 &self,
137 action: ChatAction,
138 ) -> impl ChatActionFutureBounds<Output = Result<(), BotError>> + '_ {
139 self.inner.send_action_boxed(action)
140 }
141
142 pub fn action_expiry(&self) -> Duration {
144 self.inner.action_expiry()
145 }
146
147 pub fn clear_action(&self) -> impl ChatActionFutureBounds<Output = Result<(), BotError>> + '_ {
149 self.inner.clear_action_boxed()
150 }
151}
152
153pub struct ChatActionGuard {
167 stop: async_channel::Sender<()>,
170}
171
172impl ChatActionGuard {
173 pub fn start(sender: AnyChatActionSender, action: ChatAction) -> Self {
178 let (stop, stopped) = async_channel::bounded::<()>(1);
179
180 let renewal_interval = sender.action_expiry().mul_f32(0.8);
183
184 spawn_renewal(async move {
185 let _ = sender.send_action(action).await;
186
187 while !renewal_interval.is_zero() {
188 let on_stop = async {
191 while stopped.recv().await.is_ok() {}
193 };
194
195 if race(sleep_for(renewal_interval), on_stop).await.is_err() {
196 break;
197 }
198
199 if sender.send_action(action).await.is_err() {
200 return;
201 }
202 }
203
204 let _ = sender.clear_action().await;
205 });
206
207 Self { stop }
208 }
209}
210
211impl Drop for ChatActionGuard {
212 fn drop(&mut self) {
213 self.stop.close();
214 }
215}
216
217async fn race<L: Future<Output = ()>, R: Future<Output = ()>>(left: L, right: R) -> Result<(), ()> {
219 futures_lite::future::or(
220 async {
221 left.await;
222 Ok(())
223 },
224 async {
225 right.await;
226 Err(())
227 },
228 )
229 .await
230}
231
232#[cfg(not(target_arch = "wasm32"))]
233async fn sleep_for(duration: Duration) {
234 Timer::after(duration).await;
235}
236
237#[cfg(not(target_arch = "wasm32"))]
238fn spawn_renewal(task: impl Future<Output = ()> + Send + 'static) {
239 executor_core::spawn(task).detach();
240}
241
242#[cfg(target_arch = "wasm32")]
243async fn sleep_for(duration: Duration) {
244 sleep(duration).await;
245}
246
247#[cfg(target_arch = "wasm32")]
248fn spawn_renewal(task: impl Future<Output = ()> + 'static) {
249 executor_core::spawn_local(task).detach();
250}