use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use async_io::Timer;
#[cfg(target_arch = "wasm32")]
use gloo_timers::future::sleep;
use crate::BotError;
#[cfg(not(target_arch = "wasm32"))]
type ChatActionFuture<'a> = Pin<Box<dyn Future<Output = Result<(), BotError>> + Send + 'a>>;
#[cfg(target_arch = "wasm32")]
type ChatActionFuture<'a> = Pin<Box<dyn Future<Output = Result<(), BotError>> + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
pub trait ChatActionSenderBounds: Send + Sync {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Send + Sync + ?Sized> ChatActionSenderBounds for T {}
#[cfg(target_arch = "wasm32")]
pub trait ChatActionSenderBounds {}
#[cfg(target_arch = "wasm32")]
impl<T: ?Sized> ChatActionSenderBounds for T {}
#[cfg(not(target_arch = "wasm32"))]
pub trait ChatActionFutureBounds: Future + Send {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Future + Send + ?Sized> ChatActionFutureBounds for T {}
#[cfg(target_arch = "wasm32")]
pub trait ChatActionFutureBounds: Future {}
#[cfg(target_arch = "wasm32")]
impl<T: Future + ?Sized> ChatActionFutureBounds for T {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChatAction {
Typing,
UploadPhoto,
RecordVideo,
UploadVideo,
RecordVoice,
UploadVoice,
UploadDocument,
ChooseSticker,
FindLocation,
RecordVideoNote,
UploadVideoNote,
}
pub trait ChatActionSender: ChatActionSenderBounds + 'static {
fn send_action(
&self,
action: ChatAction,
) -> impl ChatActionFutureBounds<Output = Result<(), BotError>> + '_;
fn action_expiry(&self) -> Duration;
fn clear_action(&self) -> impl ChatActionFutureBounds<Output = Result<(), BotError>> + '_ {
async { Ok(()) }
}
}
trait ChatActionSenderImpl: ChatActionSenderBounds {
fn send_action_boxed<'a>(&'a self, action: ChatAction) -> ChatActionFuture<'a>;
fn action_expiry(&self) -> Duration;
fn clear_action_boxed<'a>(&'a self) -> ChatActionFuture<'a>;
}
impl<T: ChatActionSender> ChatActionSenderImpl for T {
fn send_action_boxed<'a>(&'a self, action: ChatAction) -> ChatActionFuture<'a> {
Box::pin(ChatActionSender::send_action(self, action))
}
fn action_expiry(&self) -> Duration {
ChatActionSender::action_expiry(self)
}
fn clear_action_boxed<'a>(&'a self) -> ChatActionFuture<'a> {
Box::pin(ChatActionSender::clear_action(self))
}
}
#[derive(Clone)]
pub struct AnyChatActionSender {
inner: Arc<dyn ChatActionSenderImpl>,
}
impl AnyChatActionSender {
pub fn new(sender: impl ChatActionSender) -> Self {
Self {
inner: Arc::new(sender),
}
}
pub fn send_action(
&self,
action: ChatAction,
) -> impl ChatActionFutureBounds<Output = Result<(), BotError>> + '_ {
self.inner.send_action_boxed(action)
}
pub fn action_expiry(&self) -> Duration {
self.inner.action_expiry()
}
pub fn clear_action(&self) -> impl ChatActionFutureBounds<Output = Result<(), BotError>> + '_ {
self.inner.clear_action_boxed()
}
}
pub struct ChatActionGuard {
stop: async_channel::Sender<()>,
}
impl ChatActionGuard {
pub fn start(sender: AnyChatActionSender, action: ChatAction) -> Self {
let (stop, stopped) = async_channel::bounded::<()>(1);
let renewal_interval = sender.action_expiry().mul_f32(0.8);
spawn_renewal(async move {
let _ = sender.send_action(action).await;
while !renewal_interval.is_zero() {
let on_stop = async {
while stopped.recv().await.is_ok() {}
};
if race(sleep_for(renewal_interval), on_stop).await.is_err() {
break;
}
if sender.send_action(action).await.is_err() {
return;
}
}
let _ = sender.clear_action().await;
});
Self { stop }
}
}
impl Drop for ChatActionGuard {
fn drop(&mut self) {
self.stop.close();
}
}
async fn race<L: Future<Output = ()>, R: Future<Output = ()>>(left: L, right: R) -> Result<(), ()> {
futures_lite::future::or(
async {
left.await;
Ok(())
},
async {
right.await;
Err(())
},
)
.await
}
#[cfg(not(target_arch = "wasm32"))]
async fn sleep_for(duration: Duration) {
Timer::after(duration).await;
}
#[cfg(not(target_arch = "wasm32"))]
fn spawn_renewal(task: impl Future<Output = ()> + Send + 'static) {
executor_core::spawn(task).detach();
}
#[cfg(target_arch = "wasm32")]
async fn sleep_for(duration: Duration) {
sleep(duration).await;
}
#[cfg(target_arch = "wasm32")]
fn spawn_renewal(task: impl Future<Output = ()> + 'static) {
executor_core::spawn_local(task).detach();
}