mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 14:35:36 +01:00
Refactor
This commit is contained in:
parent
4002d8fbbc
commit
17de4840d7
18 changed files with 220 additions and 181 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -5,4 +5,4 @@ Cargo.lock
|
||||||
.vscode/
|
.vscode/
|
||||||
examples/target
|
examples/target
|
||||||
examples/ping_pong_bot/target
|
examples/ping_pong_bot/target
|
||||||
examples/simple_fsm/target
|
examples/simple_dialogue/target
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "simple_fsm"
|
name = "simple_dialogue"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
|
@ -54,38 +54,28 @@ impl Display for User {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// [FSM - Finite-State Machine]
|
// [States of a dialogue]
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
enum Fsm {
|
enum State {
|
||||||
Start,
|
Start,
|
||||||
FullName,
|
FullName,
|
||||||
Age,
|
Age,
|
||||||
FavouriteMusic,
|
FavouriteMusic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Fsm {
|
impl Default for State {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Start
|
Self::Start
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// [Our Session type]
|
// [Control a dialogue]
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
#[derive(Default)]
|
type Ctx = DialogueHandlerCtx<Message, State, User>;
|
||||||
struct Session {
|
type Res = Result<DialogueStage<State, User>, RequestError>;
|
||||||
user: User,
|
|
||||||
fsm: Fsm,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// [Control our FSM]
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
type Ctx = SessionHandlerCtx<Message, Session>;
|
|
||||||
type Res = Result<SessionState<Session>, RequestError>;
|
|
||||||
|
|
||||||
async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> {
|
async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> {
|
||||||
ctx.bot
|
ctx.bot
|
||||||
|
@ -96,53 +86,53 @@ async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start(ctx: Ctx) -> Res {
|
async fn start(mut ctx: Ctx) -> Res {
|
||||||
ctx.reply("Let's start! First, what's your full name?")
|
ctx.reply("Let's start! First, what's your full name?")
|
||||||
.await?;
|
.await?;
|
||||||
ctx.session.state = Fsm::FullName;
|
ctx.dialogue.state = State::FullName;
|
||||||
Ok(SessionState::Next(ctx.session))
|
Ok(DialogueStage::Next(ctx.dialogue))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn full_name(mut ctx: Ctx) -> Res {
|
async fn full_name(mut ctx: Ctx) -> Res {
|
||||||
ctx.reply("What a wonderful name! Your age?").await?;
|
ctx.reply("What a wonderful name! Your age?").await?;
|
||||||
ctx.session.user.full_name = Some(ctx.update.text().unwrap().to_owned());
|
ctx.dialogue.data.full_name = Some(ctx.update.text().unwrap().to_owned());
|
||||||
ctx.session.fsm = Fsm::Age;
|
ctx.dialogue.state = State::Age;
|
||||||
Ok(SessionState::Next(ctx.session))
|
Ok(DialogueStage::Next(ctx.dialogue))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn age(mut ctx: Ctx) -> Res {
|
async fn age(mut ctx: Ctx) -> Res {
|
||||||
match ctx.update.text().unwrap().parse() {
|
match ctx.update.text().unwrap().parse() {
|
||||||
Ok(ok) => {
|
Ok(ok) => {
|
||||||
send_favourite_music_types(&ctx).await?;
|
send_favourite_music_types(&ctx).await?;
|
||||||
ctx.session.user.age = Some(ok);
|
ctx.dialogue.data.age = Some(ok);
|
||||||
ctx.session.fsm = Fsm::FavouriteMusic;
|
ctx.dialogue.state = State::FavouriteMusic;
|
||||||
}
|
}
|
||||||
Err(_) => ctx.reply("Oh, please, enter a number!").await?,
|
Err(_) => ctx.reply("Oh, please, enter a number!").await?,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(SessionState::Next(ctx.session))
|
Ok(DialogueStage::Next(ctx.dialogue))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn favourite_music(mut ctx: Ctx) -> Res {
|
async fn favourite_music(mut ctx: Ctx) -> Res {
|
||||||
match ctx.update.text().unwrap().parse() {
|
match ctx.update.text().unwrap().parse() {
|
||||||
Ok(ok) => {
|
Ok(ok) => {
|
||||||
ctx.session.user.favourite_music = Some(ok);
|
ctx.dialogue.data.favourite_music = Some(ok);
|
||||||
ctx.reply(format!("Fine. {}", ctx.session.user)).await?;
|
ctx.reply(format!("Fine. {}", ctx.dialogue.data)).await?;
|
||||||
Ok(SessionState::Exit)
|
Ok(DialogueStage::Exit)
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
ctx.reply("Oh, please, enter from the keyboard!").await?;
|
ctx.reply("Oh, please, enter from the keyboard!").await?;
|
||||||
Ok(SessionState::Next(ctx.session))
|
Ok(DialogueStage::Next(ctx.dialogue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_message(ctx: Ctx) -> Res {
|
async fn handle_message(ctx: Ctx) -> Res {
|
||||||
match ctx.session.fsm {
|
match ctx.dialogue.state {
|
||||||
Fsm::Start => start(ctx).await,
|
State::Start => start(ctx).await,
|
||||||
Fsm::FullName => full_name(ctx).await,
|
State::FullName => full_name(ctx).await,
|
||||||
Fsm::Age => age(ctx).await,
|
State::Age => age(ctx).await,
|
||||||
Fsm::FavouriteMusic => favourite_music(ctx).await,
|
State::FavouriteMusic => favourite_music(ctx).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,12 +142,12 @@ async fn handle_message(ctx: Ctx) -> Res {
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
std::env::set_var("RUST_LOG", "simple_fsm=trace");
|
std::env::set_var("RUST_LOG", "simple_dialogue=trace");
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
log::info!("Starting the simple_fsm bot!");
|
log::info!("Starting the simple_dialogue bot!");
|
||||||
|
|
||||||
Dispatcher::new(Bot::new("YourAwesomeToken"))
|
Dispatcher::new(Bot::new("YourAwesomeToken"))
|
||||||
.message_handler(SessionDispatcher::new(|ctx| async move {
|
.message_handler(DialogueDispatcher::new(|ctx| async move {
|
||||||
handle_message(ctx)
|
handle_message(ctx)
|
||||||
.await
|
.await
|
||||||
.expect("Something wrong with the bot!")
|
.expect("Something wrong with the bot!")
|
34
src/dispatching/dialogue/dialogue.rs
Normal file
34
src/dispatching/dialogue/dialogue.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/// A type, encapsulating a dialogue state and arbitrary data.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// use teloxide::dispatching::dialogue::Dialogue;
|
||||||
|
///
|
||||||
|
/// enum MyState {
|
||||||
|
/// FullName,
|
||||||
|
/// Age,
|
||||||
|
/// FavouriteMusic,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[derive(Default)]
|
||||||
|
/// struct User {
|
||||||
|
/// full_name: Option<String>,
|
||||||
|
/// age: Option<u8>,
|
||||||
|
/// favourite_music: Option<String>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let _dialogue = Dialogue::new(MyState::FullName, User::default());
|
||||||
|
/// ```
|
||||||
|
#[derive(Default, Debug, Copy, Clone, Eq, Hash, PartialEq)]
|
||||||
|
pub struct Dialogue<State, T> {
|
||||||
|
pub state: State,
|
||||||
|
pub data: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State, T> Dialogue<State, T> {
|
||||||
|
/// Creates new `Dialogue` with the provided fields.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(state: State, data: T) -> Self {
|
||||||
|
Self { state, data }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +1,33 @@
|
||||||
use crate::dispatching::{
|
use crate::dispatching::{
|
||||||
session::{
|
dialogue::{
|
||||||
GetChatId, InMemStorage, SessionHandlerCtx, SessionState, Storage,
|
Dialogue, DialogueHandlerCtx, DialogueStage, GetChatId, InMemStorage,
|
||||||
|
Storage,
|
||||||
},
|
},
|
||||||
CtxHandler, DispatcherHandlerCtx,
|
CtxHandler, DispatcherHandlerCtx,
|
||||||
};
|
};
|
||||||
use std::{future::Future, pin::Pin};
|
use std::{future::Future, pin::Pin};
|
||||||
|
|
||||||
/// A dispatcher of user sessions.
|
/// A dispatcher of dialogues.
|
||||||
///
|
///
|
||||||
/// Note that `SessionDispatcher` implements `AsyncHandler`, so you can just put
|
/// Note that `DialogueDispatcher` implements `CtxHandler`, so you can just put
|
||||||
/// an instance of this dispatcher into the [`Dispatcher`]'s methods.
|
/// an instance of this dispatcher into the [`Dispatcher`]'s methods.
|
||||||
///
|
///
|
||||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
pub struct SessionDispatcher<'a, Session, H> {
|
pub struct DialogueDispatcher<'a, State, T, H> {
|
||||||
storage: Box<dyn Storage<Session> + 'a>,
|
storage: Box<dyn Storage<State, T> + 'a>,
|
||||||
handler: H,
|
handler: H,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Session, H> SessionDispatcher<'a, Session, H>
|
impl<'a, State, T, H> DialogueDispatcher<'a, State, T, H>
|
||||||
where
|
where
|
||||||
Session: Default + 'a,
|
Dialogue<State, T>: Default + 'a,
|
||||||
|
T: Default + 'a,
|
||||||
|
State: Default + 'a,
|
||||||
{
|
{
|
||||||
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
|
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
|
||||||
/// (a default storage).
|
/// (a default storage).
|
||||||
///
|
///
|
||||||
/// [`InMemStorage`]: crate::dispatching::session::InMemStorage
|
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(handler: H) -> Self {
|
pub fn new(handler: H) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -37,7 +40,7 @@ where
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_storage<Stg>(handler: H, storage: Stg) -> Self
|
pub fn with_storage<Stg>(handler: H, storage: Stg) -> Self
|
||||||
where
|
where
|
||||||
Stg: Storage<Session> + 'a,
|
Stg: Storage<State, T> + 'a,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
storage: Box::new(storage),
|
storage: Box::new(storage),
|
||||||
|
@ -46,14 +49,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Session, H, Upd> CtxHandler<DispatcherHandlerCtx<Upd>, Result<(), ()>>
|
impl<'a, State, T, H, Upd> CtxHandler<DispatcherHandlerCtx<Upd>, Result<(), ()>>
|
||||||
for SessionDispatcher<'a, Session, H>
|
for DialogueDispatcher<'a, State, T, H>
|
||||||
where
|
where
|
||||||
H: CtxHandler<SessionHandlerCtx<Upd, Session>, SessionState<Session>>,
|
H: CtxHandler<DialogueHandlerCtx<Upd, State, T>, DialogueStage<State, T>>,
|
||||||
Upd: GetChatId,
|
Upd: GetChatId,
|
||||||
Session: Default,
|
Dialogue<State, T>: Default,
|
||||||
{
|
{
|
||||||
/// Dispatches a single `message` from a private chat.
|
|
||||||
fn handle_ctx<'b>(
|
fn handle_ctx<'b>(
|
||||||
&'b self,
|
&'b self,
|
||||||
ctx: DispatcherHandlerCtx<Upd>,
|
ctx: DispatcherHandlerCtx<Upd>,
|
||||||
|
@ -64,30 +66,30 @@ where
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let chat_id = ctx.update.chat_id();
|
let chat_id = ctx.update.chat_id();
|
||||||
|
|
||||||
let session = self
|
let dialogue = self
|
||||||
.storage
|
.storage
|
||||||
.remove_session(chat_id)
|
.remove_dialogue(chat_id)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if let SessionState::Next(new_session) = self
|
if let DialogueStage::Next(new_dialogue) = self
|
||||||
.handler
|
.handler
|
||||||
.handle_ctx(SessionHandlerCtx {
|
.handle_ctx(DialogueHandlerCtx {
|
||||||
bot: ctx.bot,
|
bot: ctx.bot,
|
||||||
update: ctx.update,
|
update: ctx.update,
|
||||||
session,
|
dialogue,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
if self
|
if self
|
||||||
.storage
|
.storage
|
||||||
.update_session(chat_id, new_session)
|
.update_dialogue(chat_id, new_dialogue)
|
||||||
.await
|
.await
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
panic!(
|
panic!(
|
||||||
"We previously storage.remove_session() so \
|
"We previously storage.remove_dialogue() so \
|
||||||
storage.update_session() must return None"
|
storage.update_dialogue() must return None"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
38
src/dispatching/dialogue/dialogue_handler_ctx.rs
Normal file
38
src/dispatching/dialogue/dialogue_handler_ctx.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use crate::{
|
||||||
|
dispatching::dialogue::{Dialogue, GetChatId},
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::Message,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A context of a [`DialogueDispatcher`]'s message handler.
|
||||||
|
///
|
||||||
|
/// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher
|
||||||
|
pub struct DialogueHandlerCtx<Upd, State, T> {
|
||||||
|
pub bot: Arc<Bot>,
|
||||||
|
pub update: Upd,
|
||||||
|
pub dialogue: Dialogue<State, T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Upd, State, T> GetChatId for DialogueHandlerCtx<Upd, State, T>
|
||||||
|
where
|
||||||
|
Upd: GetChatId,
|
||||||
|
{
|
||||||
|
fn chat_id(&self) -> i64 {
|
||||||
|
self.update.chat_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State, T> DialogueHandlerCtx<Message, State, T> {
|
||||||
|
pub async fn reply<S>(&self, text: S) -> ResponseResult<()>
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
self.bot
|
||||||
|
.send_message(self.chat_id(), text)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
8
src/dispatching/dialogue/dialogue_state.rs
Normal file
8
src/dispatching/dialogue/dialogue_state.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use crate::dispatching::dialogue::Dialogue;
|
||||||
|
|
||||||
|
/// Continue or terminate a dialogue.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
|
||||||
|
pub enum DialogueStage<State, T> {
|
||||||
|
Next(Dialogue<State, T>),
|
||||||
|
Exit,
|
||||||
|
}
|
|
@ -31,14 +31,19 @@
|
||||||
|
|
||||||
// TODO: examples
|
// TODO: examples
|
||||||
|
|
||||||
|
#![allow(clippy::module_inception)]
|
||||||
|
#![allow(clippy::type_complexity)]
|
||||||
|
|
||||||
|
mod dialogue;
|
||||||
|
mod dialogue_dispatcher;
|
||||||
|
mod dialogue_handler_ctx;
|
||||||
|
mod dialogue_state;
|
||||||
mod get_chat_id;
|
mod get_chat_id;
|
||||||
mod session_dispatcher;
|
|
||||||
mod session_handler_ctx;
|
|
||||||
mod session_state;
|
|
||||||
mod storage;
|
mod storage;
|
||||||
|
|
||||||
|
pub use dialogue::Dialogue;
|
||||||
|
pub use dialogue_dispatcher::DialogueDispatcher;
|
||||||
|
pub use dialogue_handler_ctx::DialogueHandlerCtx;
|
||||||
|
pub use dialogue_state::DialogueStage;
|
||||||
pub use get_chat_id::GetChatId;
|
pub use get_chat_id::GetChatId;
|
||||||
pub use session_dispatcher::SessionDispatcher;
|
|
||||||
pub use session_handler_ctx::SessionHandlerCtx;
|
|
||||||
pub use session_state::SessionState;
|
|
||||||
pub use storage::{InMemStorage, Storage};
|
pub use storage::{InMemStorage, Storage};
|
37
src/dispatching/dialogue/storage/in_mem_storage.rs
Normal file
37
src/dispatching/dialogue/storage/in_mem_storage.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use super::Storage;
|
||||||
|
use crate::dispatching::dialogue::Dialogue;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
/// A memory storage based on a hash map. Stores all the dialogues directly in
|
||||||
|
/// RAM.
|
||||||
|
///
|
||||||
|
/// ## Note
|
||||||
|
/// All the dialogues will be lost after you restart your bot. If you need to
|
||||||
|
/// store them somewhere on a drive, you need to implement a storage
|
||||||
|
/// communicating with a DB.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct InMemStorage<State, T> {
|
||||||
|
map: Mutex<HashMap<i64, Dialogue<State, T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
#[async_trait]
|
||||||
|
impl<State, T> Storage<State, T> for InMemStorage<State, T> {
|
||||||
|
async fn remove_dialogue(
|
||||||
|
&self,
|
||||||
|
chat_id: i64,
|
||||||
|
) -> Option<Dialogue<State, T>> {
|
||||||
|
self.map.lock().await.remove(&chat_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_dialogue(
|
||||||
|
&self,
|
||||||
|
chat_id: i64,
|
||||||
|
dialogue: Dialogue<State, T>,
|
||||||
|
) -> Option<Dialogue<State, T>> {
|
||||||
|
self.map.lock().await.insert(chat_id, dialogue)
|
||||||
|
}
|
||||||
|
}
|
34
src/dispatching/dialogue/storage/mod.rs
Normal file
34
src/dispatching/dialogue/storage/mod.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
mod in_mem_storage;
|
||||||
|
|
||||||
|
use crate::dispatching::dialogue::Dialogue;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
pub use in_mem_storage::InMemStorage;
|
||||||
|
|
||||||
|
/// A storage of dialogues.
|
||||||
|
///
|
||||||
|
/// You can implement this trait for a structure that communicates with a DB and
|
||||||
|
/// be sure that after you restart your bot, all the dialogues won't be lost.
|
||||||
|
///
|
||||||
|
/// For a storage based on a simple hash map, see [`InMemStorage`].
|
||||||
|
///
|
||||||
|
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Storage<State, T> {
|
||||||
|
/// Removes a dialogue with the specified `chat_id`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a
|
||||||
|
/// `dialogue` was deleted.
|
||||||
|
async fn remove_dialogue(&self, chat_id: i64)
|
||||||
|
-> Option<Dialogue<State, T>>;
|
||||||
|
|
||||||
|
/// Updates a dialogue with the specified `chat_id`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a
|
||||||
|
/// `dialogue` was updated.
|
||||||
|
async fn update_dialogue(
|
||||||
|
&self,
|
||||||
|
chat_id: i64,
|
||||||
|
dialogue: Dialogue<State, T>,
|
||||||
|
) -> Option<Dialogue<State, T>>;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
dispatching::session::GetChatId,
|
dispatching::dialogue::GetChatId,
|
||||||
requests::{Request, ResponseResult},
|
requests::{Request, ResponseResult},
|
||||||
types::Message,
|
types::Message,
|
||||||
Bot,
|
Bot,
|
||||||
|
|
|
@ -42,11 +42,11 @@
|
||||||
//! [`SessionDispatcher`]: crate::dispatching::SessionDispatcher
|
//! [`SessionDispatcher`]: crate::dispatching::SessionDispatcher
|
||||||
|
|
||||||
mod ctx_handlers;
|
mod ctx_handlers;
|
||||||
|
pub mod dialogue;
|
||||||
mod dispatcher;
|
mod dispatcher;
|
||||||
mod dispatcher_handler_ctx;
|
mod dispatcher_handler_ctx;
|
||||||
mod error_handlers;
|
mod error_handlers;
|
||||||
mod middleware;
|
mod middleware;
|
||||||
pub mod session;
|
|
||||||
pub mod update_listeners;
|
pub mod update_listeners;
|
||||||
|
|
||||||
pub use ctx_handlers::CtxHandler;
|
pub use ctx_handlers::CtxHandler;
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
use crate::{
|
|
||||||
dispatching::session::GetChatId,
|
|
||||||
requests::{Request, ResponseResult},
|
|
||||||
types::Message,
|
|
||||||
Bot,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// A context of a [`SessionDispatcher`]'s message handler.
|
|
||||||
///
|
|
||||||
/// [`SessionDispatcher`]: crate::dispatching::session::SessionDispatcher
|
|
||||||
pub struct SessionHandlerCtx<Upd, Session> {
|
|
||||||
pub bot: Arc<Bot>,
|
|
||||||
pub update: Upd,
|
|
||||||
pub session: Session,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Upd, Session> GetChatId for SessionHandlerCtx<Upd, Session>
|
|
||||||
where
|
|
||||||
Upd: GetChatId,
|
|
||||||
{
|
|
||||||
fn chat_id(&self) -> i64 {
|
|
||||||
self.update.chat_id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Session> SessionHandlerCtx<Message, Session> {
|
|
||||||
pub async fn reply<T>(&self, text: T) -> ResponseResult<()>
|
|
||||||
where
|
|
||||||
T: Into<String>,
|
|
||||||
{
|
|
||||||
self.bot
|
|
||||||
.send_message(self.chat_id(), text)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
/// Continue or terminate a user session.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
|
|
||||||
pub enum SessionState<Session> {
|
|
||||||
Next(Session),
|
|
||||||
Exit,
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
use super::Storage;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
/// A memory storage based on a hash map. Stores all the sessions directly in
|
|
||||||
/// RAM.
|
|
||||||
///
|
|
||||||
/// ## Note
|
|
||||||
/// All the sessions will be lost after you restart your bot. If you need to
|
|
||||||
/// store them somewhere on a drive, you need to implement a storage
|
|
||||||
/// communicating with a DB.
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct InMemStorage<Session> {
|
|
||||||
map: Mutex<HashMap<i64, Session>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
#[async_trait]
|
|
||||||
impl<Session> Storage<Session> for InMemStorage<Session> {
|
|
||||||
async fn remove_session(&self, chat_id: i64) -> Option<Session> {
|
|
||||||
self.map.lock().await.remove(&chat_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update_session(
|
|
||||||
&self,
|
|
||||||
chat_id: i64,
|
|
||||||
state: Session,
|
|
||||||
) -> Option<Session> {
|
|
||||||
self.map.lock().await.insert(chat_id, state)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
mod in_mem_storage;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
pub use in_mem_storage::InMemStorage;
|
|
||||||
|
|
||||||
/// A storage of sessions.
|
|
||||||
///
|
|
||||||
/// You can implement this trait for a structure that communicates with a DB and
|
|
||||||
/// be sure that after you restart your bot, all the sessions won't be lost.
|
|
||||||
///
|
|
||||||
/// For a storage based on a simple hash map, see [`InMemStorage`].
|
|
||||||
///
|
|
||||||
/// [`InMemStorage`]: crate::dispatching::session::InMemStorage
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
#[async_trait]
|
|
||||||
pub trait Storage<Session> {
|
|
||||||
/// Removes a session with the specified `chat_id`.
|
|
||||||
///
|
|
||||||
/// Returns `None` if there wasn't such a session, `Some(session)` if a
|
|
||||||
/// `session` was deleted.
|
|
||||||
async fn remove_session(&self, chat_id: i64) -> Option<Session>;
|
|
||||||
|
|
||||||
/// Updates a session with the specified `chat_id`.
|
|
||||||
///
|
|
||||||
/// Returns `None` if there wasn't such a session, `Some(session)` if a
|
|
||||||
/// `session` was updated.
|
|
||||||
async fn update_session(
|
|
||||||
&self,
|
|
||||||
chat_id: i64,
|
|
||||||
session: Session,
|
|
||||||
) -> Option<Session>;
|
|
||||||
}
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
dispatching::{
|
dispatching::{
|
||||||
session::{
|
dialogue::{
|
||||||
GetChatId, SessionDispatcher, SessionHandlerCtx, SessionState,
|
DialogueDispatcher, DialogueHandlerCtx, DialogueStage, GetChatId,
|
||||||
},
|
},
|
||||||
Dispatcher, DispatcherHandlerCtx,
|
Dispatcher, DispatcherHandlerCtx,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue