use crate::__private::{GenericHandler, HandlerError};
use crate::models::{GetUpdates, Update, UpdateType};
use crate::{Bot, BotState};
use std::env::var;
use std::fs::File;
use std::io::Read;
#[cfg(feature = "commands")]
use {crate::__private::command::CommandHandler, crate::models::MessageEntityType, lazy_static::lazy_static, regex::Regex, std::collections::BTreeMap};
#[cfg(feature = "daemons")]
use {crate::__private::daemon::DaemonStruct, tokio::sync::Mutex};
use log::{error, info, trace};
use std::sync::Arc;
use std::time::Duration;
pub struct BotBuilder<T>
where
T: Sync + Send + 'static,
{
bot: Arc<Bot>,
state: Arc<Option<BotState<T>>>,
#[cfg(feature = "daemons")]
daemons: Vec<DaemonStruct<T>>,
#[cfg(feature = "commands")]
commands: Option<BTreeMap<String, Vec<CommandHandler<T>>>>,
handler: Vec<GenericHandler<T>>,
update_limit: Option<i64>,
allowed_updates: Option<Vec<UpdateType>>,
offset: Option<i64>,
timeout: Option<i64>,
interval: Option<Duration>,
}
impl BotBuilder<()> {
pub fn new() -> BotBuilder<()> {
BotBuilder {
bot: Arc::new(Bot::login(
var("BOT_TOKEN")
.ok()
.or(var("BOT_TOKEN_FILE").ok().and_then(|f| {
let mut s = String::new();
File::open(f).expect("Could not open token file").read_to_string(&mut s).expect("Could not read from token file");
Some(s)
}))
.expect("No token provided (define BOT_TOKEN or BOT_TOKEN_FILE variable)"),
)),
state: Arc::new(None),
#[cfg(feature = "commands")]
commands: None,
#[cfg(feature = "daemons")]
daemons: vec![],
handler: vec![],
update_limit: None,
allowed_updates: None,
offset: None,
timeout: None,
interval: None,
}
}
pub fn new_with_token(token: String) -> BotBuilder<()> {
BotBuilder {
bot: Arc::new(Bot::login(token)),
state: Arc::new(None),
#[cfg(feature = "commands")]
commands: None,
#[cfg(feature = "daemons")]
daemons: vec![],
handler: vec![],
update_limit: None,
allowed_updates: None,
offset: None,
timeout: None,
interval: None,
}
}
}
impl Default for BotBuilder<()> {
fn default() -> Self {
BotBuilder::new()
}
}
impl<T: Sync + Send + 'static> BotBuilder<T> {
pub fn with_state<S>(self, state: S) -> BotBuilder<S>
where
S: Sync + Send + 'static,
{
if self.state.is_some() {
panic!("State already set")
}
if !self.handler.is_empty() {
panic!("Cannot set state after handler")
}
#[cfg(feature = "daemons")]
if !self.daemons.is_empty() {
panic!("Cannot set state after daemons")
}
#[cfg(feature = "commands")]
if self.commands.is_some() {
panic!("Cannot set state after commands")
}
BotBuilder {
bot: self.bot,
state: Arc::new(Some(BotState { state })),
#[cfg(feature = "commands")]
commands: None,
#[cfg(feature = "daemons")]
daemons: vec![],
handler: vec![],
update_limit: self.update_limit,
allowed_updates: self.allowed_updates,
offset: self.offset,
timeout: self.timeout,
interval: self.interval,
}
}
pub fn handlers(mut self, handlers: Vec<GenericHandler<T>>) -> Self {
if !self.handler.is_empty() {
panic!("Handlers already set")
}
handlers.iter().for_each(|h| info!("Registered handler {} with priority {}", h.name, h.rank));
self.handler = handlers;
self.handler.sort_by_key(|f| f.rank);
self
}
pub fn update_limit(mut self, limit: i64) -> BotBuilder<T> {
if self.update_limit.is_some() {
panic!("Update limit already set")
}
self.update_limit = Some(limit);
self
}
pub fn timeout(mut self, timeout: i64) -> BotBuilder<T> {
if self.timeout.is_some() {
panic!("Timeout already set")
}
self.timeout = Some(timeout);
self
}
pub fn interval(mut self, interval: Duration) -> BotBuilder<T> {
if self.interval.is_some() {
panic!("Interval already set")
}
self.interval = Some(interval);
self
}
#[cfg(feature = "commands")]
pub fn commands(mut self, commands: BTreeMap<String, Vec<CommandHandler<T>>>) -> BotBuilder<T> {
if self.commands.is_some() {
panic!("Commands already set")
}
for cmd in &commands {
info!("Registering handler for command {}: ", cmd.0);
for handler in cmd.1 {
info!(r#" - "{}" with priority {}"#, handler.syntax, handler.rank);
}
}
self.commands = Some(commands);
self
}
#[cfg(feature = "daemons")]
pub fn daemons(mut self, daemons: Vec<DaemonStruct<T>>) -> BotBuilder<T> {
if !self.daemons.is_empty() {
panic!("Daemons already set")
}
daemons.iter().for_each(|d| info!("Registering daemon {}", d.syntax));
self.daemons = daemons;
self
}
pub async fn launch(mut self) {
let allowed_updates = self.allowed_updates.unwrap_or_default();
let timeout = self.timeout.or(Some(5));
info!("Launching bot with parameters: [interval: {:?}, timeout: {:?}]", self.interval, timeout);
#[cfg(feature = "daemons")]
Self::launch_daemons(self.daemons, self.bot.clone(), self.state.clone()).await;
loop {
if let Some(interval) = self.interval {
tokio::time::sleep(interval).await;
}
let mut updates = vec![];
while updates.is_empty() {
updates = self
.bot
.get_updates(GetUpdates {
offset: self.offset,
limit: self.update_limit,
timeout,
allowed_updates: {
#[cfg(feature = "commands")]
if self.commands.is_some() {
[allowed_updates.clone(), vec![UpdateType::NewMessage]].concat()
} else {
allowed_updates.clone()
}
#[cfg(not(feature = "commands"))]
allowed_updates.clone()
},
})
.await
.unwrap()
}
updates.sort_by_key(|u| u.update_id);
self.offset = Some(updates.last().unwrap().update_id + 1);
for update in updates {
if let Ok(update) = Update::try_from(update.clone()) {
trace!("Received update {} ({:?})", update.get_id(), update.get_type());
#[cfg(feature = "commands")]
if Self::try_commands(&self.commands, &self.bot, self.state.as_ref().as_ref(), &update).await {
continue;
}
for h in &self.handler {
if h.updates.is_empty() || h.updates.iter().any(|u| *u == update.get_type()) {
match h.handler.handle(&self.bot, &update, self.state.as_ref().as_ref()).await {
Ok(_) => break,
Err(HandlerError::Parse) => continue,
Err(HandlerError::Runtime) => {
error!("Handler failure for {:?}", update);
break;
}
}
}
}
} else {
error!("Invalid update received: {:?}", update)
}
}
}
}
#[cfg(feature = "commands")]
async fn try_commands(commands: &Option<BTreeMap<String, Vec<CommandHandler<T>>>>, bot: &Bot, state: Option<&BotState<T>>, update: &Update) -> bool {
if let (Update::NewMessage(_, m), Some(commands)) = (&update, commands) {
if let Some(e) = m.entities.iter().find(|e| e.offset == 0 && matches!(e._type, MessageEntityType::BotCommand)) {
trace!("Command \"{}\" issued", m.text.as_ref().unwrap_or(&"".to_string()));
lazy_static! {
static ref PARAMETER_DELIMITER: Regex = Regex::new(r"\s+").unwrap();
}
if let Some(handlers) = commands.get(&m.text.as_ref().unwrap().as_str()[(e.offset + 1) as usize..(e.offset + e.length) as usize]) {
let args: Vec<String> = PARAMETER_DELIMITER
.split(&m.text.as_ref().unwrap().as_str()[(e.offset + e.length) as usize..])
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
for h in handlers {
match h.handler.handle(&args, update, bot, state).await {
Ok(_) => break,
Err(HandlerError::Parse) => continue,
Err(HandlerError::Runtime) => panic!(),
}
}
trace!("Could not delegate to any handler");
return true;
} else {
trace!("No corresponding handler found")
}
}
}
false
}
#[cfg(feature = "daemons")]
async fn launch_daemons(daemons: Vec<DaemonStruct<T>>, bot: Arc<Bot>, state: Arc<Option<BotState<T>>>) -> Vec<Arc<Mutex<()>>> {
daemons
.into_iter()
.map(|d| {
let mutex = Arc::new(Mutex::new(()));
let bot = bot.clone();
let state = state.clone();
let lock = mutex.clone();
tokio::task::spawn(async move { d.daemon.launch(bot, state, lock).await });
mutex
})
.collect()
}
}