Add examples/simple_fsm

This commit is contained in:
Temirkhan Myrzamadi 2020-01-30 04:54:40 +06:00
parent c98b53b9a8
commit 1a6297747c
5 changed files with 223 additions and 11 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ Cargo.lock
.vscode/
examples/target
examples/ping_pong_bot/target
examples/simple_fsm/target

View file

@ -0,0 +1,15 @@
[package]
name = "simple_fsm"
version = "0.1.0"
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pretty_env_logger = "0.3.1"
log = "0.4.8"
tokio = "0.2.9"
strum = "0.17.1"
strum_macros = "0.17.1"
teloxide = { path = "../../" }

View file

@ -0,0 +1,174 @@
#[macro_use]
extern crate strum_macros;
use std::fmt::{self, Display, Formatter};
use teloxide::{
prelude::*,
types::{KeyboardButton, ReplyKeyboardMarkup},
};
// ============================================================================
// [Favourite music kinds]
// ============================================================================
#[derive(Copy, Clone, Display, EnumString)]
enum FavouriteMusic {
Rock,
Metal,
Pop,
Other,
}
impl FavouriteMusic {
fn markup() -> ReplyKeyboardMarkup {
ReplyKeyboardMarkup {
keyboard: vec![vec![
KeyboardButton {
text: "Rock".to_owned(),
request: None,
},
KeyboardButton {
text: "Metal".to_owned(),
request: None,
},
KeyboardButton {
text: "Pop".to_owned(),
request: None,
},
KeyboardButton {
text: "Other".to_owned(),
request: None,
},
]],
resize_keyboard: None,
one_time_keyboard: None,
selective: None,
}
}
}
// ============================================================================
// [A user's data]
// ============================================================================
#[derive(Default)]
struct User {
full_name: Option<String>,
age: Option<u8>,
favourite_music: Option<FavouriteMusic>,
}
impl Display for User {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(
f,
"Your full name: {}, your age: {}, your favourite music: {}",
self.full_name.as_ref().unwrap(),
self.age.unwrap(),
self.favourite_music.unwrap()
)
}
}
// ============================================================================
// [Some macros]
// ============================================================================
#[macro_export]
macro_rules! reply {
($ctx:ident, $text:expr) => {
$ctx.reply($text).await?;
};
}
// ============================================================================
// [Control our FSM]
// ============================================================================
async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> {
ctx.bot
.send_message(ctx.chat_id(), "Good. Now choose your favourite music:")
.reply_markup(FavouriteMusic::markup())
.send()
.await?;
Ok(())
}
type Ctx = SessionHandlerCtx<Message, User>;
type Res = Result<SessionState<User>, RequestError>;
async fn start(ctx: Ctx) -> Res {
reply!(ctx, "Let's start! First, what's your full name?");
Ok(SessionState::Continue(ctx.session))
}
async fn full_name(mut ctx: Ctx) -> Res {
reply!(ctx, "What a wonderful name! Your age?");
ctx.session.full_name = Some(ctx.update.text().unwrap().to_owned());
Ok(SessionState::Continue(ctx.session))
}
async fn age(mut ctx: Ctx) -> Res {
match ctx.update.text().unwrap().parse() {
Ok(ok) => {
send_favourite_music_types(&ctx).await?;
ctx.session.age = Some(ok);
}
Err(_) => reply!(ctx, "Oh, please, enter a number!"),
}
Ok(SessionState::Continue(ctx.session))
}
async fn favourite_music(mut ctx: Ctx) -> Res {
match ctx.update.text().unwrap().parse() {
Ok(ok) => {
ctx.session.favourite_music = Some(ok);
reply!(ctx, format!("Fine. {}", ctx.session));
Ok(SessionState::Terminate)
}
Err(_) => {
reply!(ctx, "Oh, please, enter from the keyboard!");
Ok(SessionState::Continue(ctx.session))
}
}
}
async fn handle_message(ctx: Ctx) -> Res {
if ctx.session.full_name.is_none() {
return full_name(ctx).await;
}
if ctx.session.age.is_none() {
return age(ctx).await;
}
if ctx.session.favourite_music.is_none() {
return favourite_music(ctx).await;
}
Ok(SessionState::Terminate)
}
// ============================================================================
// [Run!]
// ============================================================================
#[tokio::main]
async fn main() {
std::env::set_var("RUST_LOG", "simple_fsm=trace");
pretty_env_logger::init();
log::info!("Starting the simple_fsm bot!");
Dispatcher::new(Bot::new("YourAwesomeToken"))
.message_handler(SessionDispatcher::new(|ctx| async move {
match handle_message(ctx).await {
Ok(ok) => ok,
Err(error) => {
panic!("Something wrong with the bot: {}!", error)
}
}
}))
.dispatch()
.await;
}

View file

@ -32,7 +32,12 @@
mod get_chat_id;
mod storage;
use crate::{dispatching::AsyncHandler, Bot};
use crate::{
dispatching::{AsyncHandler, HandlerCtx},
requests::{Request, ResponseResult},
types::Message,
Bot,
};
pub use get_chat_id::*;
use std::{future::Future, pin::Pin, sync::Arc};
pub use storage::*;
@ -44,10 +49,21 @@ pub struct SessionHandlerCtx<Upd, Session> {
pub session: Session,
}
/// A context of a session dispatcher.
pub struct SessionDispatcherCtx<Upd> {
pub bot: Arc<Bot>,
pub update: Upd,
impl<Session> SessionHandlerCtx<Message, Session> {
pub fn chat_id(&self) -> i64 {
self.update.chat_id()
}
pub async fn reply<T>(&self, text: T) -> ResponseResult<()>
where
T: Into<String>,
{
self.bot
.send_message(self.chat_id(), text)
.send()
.await
.map(|_| ())
}
}
/// Continue or terminate a user session.
@ -92,7 +108,7 @@ where
}
}
impl<'a, Session, H, Upd> AsyncHandler<SessionDispatcherCtx<Upd>, ()>
impl<'a, Session, H, Upd> AsyncHandler<HandlerCtx<Upd>, Result<(), ()>>
for SessionDispatcher<'a, Session, H>
where
H: AsyncHandler<SessionHandlerCtx<Upd, Session>, SessionState<Session>>,
@ -102,8 +118,8 @@ where
/// Dispatches a single `message` from a private chat.
fn handle<'b>(
&'b self,
ctx: SessionDispatcherCtx<Upd>,
) -> Pin<Box<dyn Future<Output = ()> + 'b>>
ctx: HandlerCtx<Upd>,
) -> Pin<Box<dyn Future<Output = Result<(), ()>> + 'b>>
where
Upd: 'b,
{
@ -137,6 +153,8 @@ where
);
}
}
Ok(())
})
}
}

View file

@ -1,7 +1,11 @@
//! Commonly used items.
pub use crate::{
dispatching::{Dispatcher, HandlerCtx},
dispatching::{
session::{SessionDispatcher, SessionHandlerCtx, SessionState},
Dispatcher, HandlerCtx,
},
requests::{Request, ResponseResult},
types::Message,
Bot,
Bot, RequestError,
};