mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 06:25:10 +01:00
Add examples/simple_fsm
This commit is contained in:
parent
c98b53b9a8
commit
1a6297747c
5 changed files with 223 additions and 11 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,4 +4,5 @@ Cargo.lock
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
examples/target
|
examples/target
|
||||||
examples/ping_pong_bot/target
|
examples/ping_pong_bot/target
|
||||||
|
examples/simple_fsm/target
|
15
examples/simple_fsm/Cargo.toml
Normal file
15
examples/simple_fsm/Cargo.toml
Normal 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 = "../../" }
|
174
examples/simple_fsm/src/main.rs
Normal file
174
examples/simple_fsm/src/main.rs
Normal 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;
|
||||||
|
}
|
|
@ -32,7 +32,12 @@
|
||||||
mod get_chat_id;
|
mod get_chat_id;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|
||||||
use crate::{dispatching::AsyncHandler, Bot};
|
use crate::{
|
||||||
|
dispatching::{AsyncHandler, HandlerCtx},
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::Message,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
pub use get_chat_id::*;
|
pub use get_chat_id::*;
|
||||||
use std::{future::Future, pin::Pin, sync::Arc};
|
use std::{future::Future, pin::Pin, sync::Arc};
|
||||||
pub use storage::*;
|
pub use storage::*;
|
||||||
|
@ -44,10 +49,21 @@ pub struct SessionHandlerCtx<Upd, Session> {
|
||||||
pub session: Session,
|
pub session: Session,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A context of a session dispatcher.
|
impl<Session> SessionHandlerCtx<Message, Session> {
|
||||||
pub struct SessionDispatcherCtx<Upd> {
|
pub fn chat_id(&self) -> i64 {
|
||||||
pub bot: Arc<Bot>,
|
self.update.chat_id()
|
||||||
pub update: Upd,
|
}
|
||||||
|
|
||||||
|
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.
|
/// 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>
|
for SessionDispatcher<'a, Session, H>
|
||||||
where
|
where
|
||||||
H: AsyncHandler<SessionHandlerCtx<Upd, Session>, SessionState<Session>>,
|
H: AsyncHandler<SessionHandlerCtx<Upd, Session>, SessionState<Session>>,
|
||||||
|
@ -102,8 +118,8 @@ where
|
||||||
/// Dispatches a single `message` from a private chat.
|
/// Dispatches a single `message` from a private chat.
|
||||||
fn handle<'b>(
|
fn handle<'b>(
|
||||||
&'b self,
|
&'b self,
|
||||||
ctx: SessionDispatcherCtx<Upd>,
|
ctx: HandlerCtx<Upd>,
|
||||||
) -> Pin<Box<dyn Future<Output = ()> + 'b>>
|
) -> Pin<Box<dyn Future<Output = Result<(), ()>> + 'b>>
|
||||||
where
|
where
|
||||||
Upd: 'b,
|
Upd: 'b,
|
||||||
{
|
{
|
||||||
|
@ -137,6 +153,8 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
//! Commonly used items.
|
//! Commonly used items.
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
dispatching::{Dispatcher, HandlerCtx},
|
dispatching::{
|
||||||
|
session::{SessionDispatcher, SessionHandlerCtx, SessionState},
|
||||||
|
Dispatcher, HandlerCtx,
|
||||||
|
},
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
types::Message,
|
types::Message,
|
||||||
Bot,
|
Bot, RequestError,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue