use std::sync::Arc;
use rustigram_api::BotClient;
use rustigram_types::update::Update;
use tracing::{debug, error, info, warn};
use crate::context::Context;
use crate::error::{BotError, BotResult};
use crate::filter::Filter;
use crate::handler::{BoxHandler, Handler};
struct Route {
filter: Box<dyn Filter>,
handler: BoxHandler,
}
pub struct DispatcherBuilder {
client: BotClient,
routes: Vec<Route>,
fallback: Option<BoxHandler>,
}
impl DispatcherBuilder {
fn new(client: BotClient) -> Self {
Self {
client,
routes: Vec::new(),
fallback: None,
}
}
#[must_use]
pub fn on<F, H>(mut self, filter: F, handler: H) -> Self
where
F: Filter,
H: Handler,
{
self.routes.push(Route {
filter: Box::new(filter),
handler: Arc::new(handler),
});
self
}
#[must_use]
pub fn fallback<H: Handler>(mut self, handler: H) -> Self {
self.fallback = Some(Arc::new(handler));
self
}
#[must_use]
pub fn build(self) -> Dispatcher {
Dispatcher {
client: self.client,
routes: Arc::new(self.routes),
fallback: self.fallback,
}
}
}
#[derive(Clone)]
pub struct Dispatcher {
client: BotClient,
routes: Arc<Vec<Route>>,
fallback: Option<BoxHandler>,
}
impl Dispatcher {
#[must_use]
pub fn builder(client: BotClient) -> DispatcherBuilder {
DispatcherBuilder::new(client)
}
pub async fn dispatch(&self, update: Update) {
let ctx = Context::new(update, self.client.clone());
debug!("Dispatching update {}", ctx.update_id());
for route in self.routes.as_ref() {
if route.filter.check(&ctx) {
let handler = route.handler.clone();
let ctx = ctx.clone();
tokio::spawn(async move {
if let Err(e) = handler.handle(ctx).await {
error!("Handler error: {}", e);
}
});
return;
}
}
if let Some(fallback) = &self.fallback {
let fallback = fallback.clone();
let ctx = ctx.clone();
tokio::spawn(async move {
if let Err(e) = fallback.handle(ctx).await {
error!("Fallback handler error: {}", e);
}
});
} else {
debug!("No handler matched update {}", ctx.update_id());
}
}
pub async fn polling(self) -> BotResult<()> {
use crate::update_listener::polling::LongPoller;
info!("Starting long-polling dispatcher");
let mut poller = LongPoller::new(self.client.clone());
loop {
match poller.next_batch().await {
Ok(updates) => {
for update in updates {
self.dispatch(update).await;
}
}
Err(BotError::Api(rustigram_api::Error::RateLimit { retry_after })) => {
warn!("Rate-limited during polling, waiting {}s", retry_after);
tokio::time::sleep(std::time::Duration::from_secs(u64::from(retry_after)))
.await;
}
Err(BotError::Api(rustigram_api::Error::Http(ref e)))
if e.is_timeout() || e.is_connect() =>
{
warn!(
"Transient network error during polling, retrying in 3s: {}",
e
);
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
}
Err(e) => {
error!("Fatal polling error: {}", e);
return Err(e);
}
}
}
}
pub async fn webhook(
self,
config: impl Into<crate::update_listener::webhook::WebhookConfig>,
) -> BotResult<()> {
use crate::update_listener::webhook::WebhookServer;
let config = config.into();
info!("Starting webhook dispatcher on {}", config.addr);
let mut server = WebhookServer::new(config.addr, self);
if let Some(secret) = config.secret_token {
server = server.secret_token(secret);
}
server.serve().await
}
}