use std::time::Duration;
use ferogram::filters::{Dispatcher, Router, command, group, private, text};
use ferogram::fsm::{MemoryStorage, StateContext, StateStorage};
use ferogram::middleware::{PanicRecoveryMiddleware, RateLimitMiddleware, TracingMiddleware};
use ferogram::{ClientBuilder, FsmState, UpdateStream};
#[derive(FsmState, Clone, Debug, PartialEq)]
enum OrderState {
WaitingProduct,
WaitingQuantity,
WaitingAddress,
Confirm,
}
pub fn order_router() -> Router {
let mut r = Router::new().scope(private());
r.on_message(command("order"), handle_order_start);
r.on_message_fsm(text(), OrderState::WaitingProduct, handle_product);
r.on_message_fsm(text(), OrderState::WaitingQuantity, handle_quantity);
r.on_message_fsm(text(), OrderState::WaitingAddress, handle_address);
r.on_message_fsm(text(), OrderState::Confirm, handle_confirm);
for state in [
OrderState::WaitingProduct,
OrderState::WaitingQuantity,
OrderState::WaitingAddress,
OrderState::Confirm,
] {
r.on_message_fsm(command("cancel"), state, |msg, ctx| async move {
ctx.clear_all().await.ok();
msg.reply("โ Order cancelled.").await.ok();
});
}
r
}
pub fn info_router() -> Router {
let mut r = Router::new();
r.on_message(command("start"), handle_start);
r.on_message(command("help"), handle_help);
r
}
pub fn group_router() -> Router {
let mut r = Router::new().scope(group());
r.on_message(command("rules"), handle_rules);
r
}
async fn handle_start(msg: ferogram::update::IncomingMessage) {
msg.reply("๐ Welcome! Use /order to place an order.")
.await
.ok();
}
async fn handle_help(msg: ferogram::update::IncomingMessage) {
msg.reply("/order - start a new order\n/cancel - cancel current order")
.await
.ok();
}
async fn handle_rules(msg: ferogram::update::IncomingMessage) {
msg.reply("๐ Group rules: be respectful.").await.ok();
}
async fn handle_order_start(msg: ferogram::update::IncomingMessage) {
msg.reply("๐ What product would you like to order?")
.await
.ok();
}
async fn handle_product(msg: ferogram::update::IncomingMessage, state: StateContext) {
let product = msg.text().unwrap_or("unknown");
state.set_data("product", product).await.ok();
state.transition(OrderState::WaitingQuantity).await.ok();
msg.reply("๐ฆ How many would you like?").await.ok();
}
async fn handle_quantity(msg: ferogram::update::IncomingMessage, state: StateContext) {
let qty = msg.text().unwrap_or("1");
state.set_data("quantity", qty).await.ok();
state.transition(OrderState::WaitingAddress).await.ok();
msg.reply("๐ What's your shipping address?").await.ok();
}
async fn handle_address(msg: ferogram::update::IncomingMessage, state: StateContext) {
let addr = msg.text().unwrap_or("unknown");
state.set_data("address", addr).await.ok();
state.transition(OrderState::Confirm).await.ok();
let product: Option<String> = state.get_data("product").await.unwrap_or(None);
let quantity: Option<String> = state.get_data("quantity").await.unwrap_or(None);
msg.reply(format!(
"๐งพ Confirm order?\n\nProduct: {}\nQty: {}\nTo: {}\n\nReply 'yes' to confirm or /cancel.",
product.unwrap_or_default(),
quantity.unwrap_or_default(),
addr,
))
.await
.ok();
}
async fn handle_confirm(msg: ferogram::update::IncomingMessage, state: StateContext) {
match msg.text().unwrap_or("").to_lowercase().trim() {
"yes" | "confirm" | "ok" => {
let product: Option<String> = state.get_data("product").await.unwrap_or(None);
let quantity: Option<String> = state.get_data("quantity").await.unwrap_or(None);
let address: Option<String> = state.get_data("address").await.unwrap_or(None);
state.clear_all().await.ok();
msg.reply(format!(
"โ
Order placed!\n\n{} ร {} โ {}",
quantity.unwrap_or_default(),
product.unwrap_or_default(),
address.unwrap_or_default(),
))
.await
.ok();
}
_ => {
msg.reply("โ Reply 'yes' to confirm or /cancel to abort.")
.await
.ok();
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api_id: i32 = std::env::var("API_ID")?.parse()?;
let api_hash = std::env::var("API_HASH")?;
let bot_token = std::env::var("BOT_TOKEN")?;
let (client, _shutdown) = ClientBuilder::default()
.api_id(api_id)
.api_hash(api_hash)
.session("order_bot.session")
.connect()
.await?;
client.bot_sign_in(&bot_token).await?;
let storage: std::sync::Arc<dyn StateStorage> = std::sync::Arc::new(MemoryStorage::new());
let mut dp = Dispatcher::new();
dp.middleware(PanicRecoveryMiddleware::new()); dp.middleware(TracingMiddleware::new());
dp.middleware(RateLimitMiddleware::new(10, Duration::from_secs(1)));
dp.with_state_storage(storage);
dp.include(info_router());
dp.include(order_router());
dp.include(group_router());
tracing::info!("bot started");
let mut stream: UpdateStream = client.stream_updates();
while let Some(upd) = stream.next().await {
dp.dispatch(upd).await;
}
Ok(())
}