mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 06:25:10 +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/
|
||||
examples/target
|
||||
examples/ping_pong_bot/target
|
||||
examples/simple_fsm/target
|
||||
examples/simple_dialogue/target
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "simple_fsm"
|
||||
name = "simple_dialogue"
|
||||
version = "0.1.0"
|
||||
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
||||
edition = "2018"
|
|
@ -54,38 +54,28 @@ impl Display for User {
|
|||
}
|
||||
|
||||
// ============================================================================
|
||||
// [FSM - Finite-State Machine]
|
||||
// [States of a dialogue]
|
||||
// ============================================================================
|
||||
|
||||
enum Fsm {
|
||||
enum State {
|
||||
Start,
|
||||
FullName,
|
||||
Age,
|
||||
FavouriteMusic,
|
||||
}
|
||||
|
||||
impl Default for Fsm {
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self::Start
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// [Our Session type]
|
||||
// [Control a dialogue]
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Default)]
|
||||
struct Session {
|
||||
user: User,
|
||||
fsm: Fsm,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// [Control our FSM]
|
||||
// ============================================================================
|
||||
|
||||
type Ctx = SessionHandlerCtx<Message, Session>;
|
||||
type Res = Result<SessionState<Session>, RequestError>;
|
||||
type Ctx = DialogueHandlerCtx<Message, State, User>;
|
||||
type Res = Result<DialogueStage<State, User>, RequestError>;
|
||||
|
||||
async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> {
|
||||
ctx.bot
|
||||
|
@ -96,53 +86,53 @@ async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> {
|
|||
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?")
|
||||
.await?;
|
||||
ctx.session.state = Fsm::FullName;
|
||||
Ok(SessionState::Next(ctx.session))
|
||||
ctx.dialogue.state = State::FullName;
|
||||
Ok(DialogueStage::Next(ctx.dialogue))
|
||||
}
|
||||
|
||||
async fn full_name(mut ctx: Ctx) -> Res {
|
||||
ctx.reply("What a wonderful name! Your age?").await?;
|
||||
ctx.session.user.full_name = Some(ctx.update.text().unwrap().to_owned());
|
||||
ctx.session.fsm = Fsm::Age;
|
||||
Ok(SessionState::Next(ctx.session))
|
||||
ctx.dialogue.data.full_name = Some(ctx.update.text().unwrap().to_owned());
|
||||
ctx.dialogue.state = State::Age;
|
||||
Ok(DialogueStage::Next(ctx.dialogue))
|
||||
}
|
||||
|
||||
async fn age(mut ctx: Ctx) -> Res {
|
||||
match ctx.update.text().unwrap().parse() {
|
||||
Ok(ok) => {
|
||||
send_favourite_music_types(&ctx).await?;
|
||||
ctx.session.user.age = Some(ok);
|
||||
ctx.session.fsm = Fsm::FavouriteMusic;
|
||||
ctx.dialogue.data.age = Some(ok);
|
||||
ctx.dialogue.state = State::FavouriteMusic;
|
||||
}
|
||||
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 {
|
||||
match ctx.update.text().unwrap().parse() {
|
||||
Ok(ok) => {
|
||||
ctx.session.user.favourite_music = Some(ok);
|
||||
ctx.reply(format!("Fine. {}", ctx.session.user)).await?;
|
||||
Ok(SessionState::Exit)
|
||||
ctx.dialogue.data.favourite_music = Some(ok);
|
||||
ctx.reply(format!("Fine. {}", ctx.dialogue.data)).await?;
|
||||
Ok(DialogueStage::Exit)
|
||||
}
|
||||
Err(_) => {
|
||||
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 {
|
||||
match ctx.session.fsm {
|
||||
Fsm::Start => start(ctx).await,
|
||||
Fsm::FullName => full_name(ctx).await,
|
||||
Fsm::Age => age(ctx).await,
|
||||
Fsm::FavouriteMusic => favourite_music(ctx).await,
|
||||
match ctx.dialogue.state {
|
||||
State::Start => start(ctx).await,
|
||||
State::FullName => full_name(ctx).await,
|
||||
State::Age => age(ctx).await,
|
||||
State::FavouriteMusic => favourite_music(ctx).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,12 +142,12 @@ async fn handle_message(ctx: Ctx) -> Res {
|
|||
|
||||
#[tokio::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();
|
||||
log::info!("Starting the simple_fsm bot!");
|
||||
log::info!("Starting the simple_dialogue bot!");
|
||||
|
||||
Dispatcher::new(Bot::new("YourAwesomeToken"))
|
||||
.message_handler(SessionDispatcher::new(|ctx| async move {
|
||||
.message_handler(DialogueDispatcher::new(|ctx| async move {
|
||||
handle_message(ctx)
|
||||
.await
|
||||
.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::{
|
||||
session::{
|
||||
GetChatId, InMemStorage, SessionHandlerCtx, SessionState, Storage,
|
||||
dialogue::{
|
||||
Dialogue, DialogueHandlerCtx, DialogueStage, GetChatId, InMemStorage,
|
||||
Storage,
|
||||
},
|
||||
CtxHandler, DispatcherHandlerCtx,
|
||||
};
|
||||
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.
|
||||
///
|
||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||
pub struct SessionDispatcher<'a, Session, H> {
|
||||
storage: Box<dyn Storage<Session> + 'a>,
|
||||
pub struct DialogueDispatcher<'a, State, T, H> {
|
||||
storage: Box<dyn Storage<State, T> + 'a>,
|
||||
handler: H,
|
||||
}
|
||||
|
||||
impl<'a, Session, H> SessionDispatcher<'a, Session, H>
|
||||
impl<'a, State, T, H> DialogueDispatcher<'a, State, T, H>
|
||||
where
|
||||
Session: Default + 'a,
|
||||
Dialogue<State, T>: Default + 'a,
|
||||
T: Default + 'a,
|
||||
State: Default + 'a,
|
||||
{
|
||||
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
|
||||
/// (a default storage).
|
||||
///
|
||||
/// [`InMemStorage`]: crate::dispatching::session::InMemStorage
|
||||
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
|
||||
#[must_use]
|
||||
pub fn new(handler: H) -> Self {
|
||||
Self {
|
||||
|
@ -37,7 +40,7 @@ where
|
|||
#[must_use]
|
||||
pub fn with_storage<Stg>(handler: H, storage: Stg) -> Self
|
||||
where
|
||||
Stg: Storage<Session> + 'a,
|
||||
Stg: Storage<State, T> + 'a,
|
||||
{
|
||||
Self {
|
||||
storage: Box::new(storage),
|
||||
|
@ -46,14 +49,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Session, H, Upd> CtxHandler<DispatcherHandlerCtx<Upd>, Result<(), ()>>
|
||||
for SessionDispatcher<'a, Session, H>
|
||||
impl<'a, State, T, H, Upd> CtxHandler<DispatcherHandlerCtx<Upd>, Result<(), ()>>
|
||||
for DialogueDispatcher<'a, State, T, H>
|
||||
where
|
||||
H: CtxHandler<SessionHandlerCtx<Upd, Session>, SessionState<Session>>,
|
||||
H: CtxHandler<DialogueHandlerCtx<Upd, State, T>, DialogueStage<State, T>>,
|
||||
Upd: GetChatId,
|
||||
Session: Default,
|
||||
Dialogue<State, T>: Default,
|
||||
{
|
||||
/// Dispatches a single `message` from a private chat.
|
||||
fn handle_ctx<'b>(
|
||||
&'b self,
|
||||
ctx: DispatcherHandlerCtx<Upd>,
|
||||
|
@ -64,30 +66,30 @@ where
|
|||
Box::pin(async move {
|
||||
let chat_id = ctx.update.chat_id();
|
||||
|
||||
let session = self
|
||||
let dialogue = self
|
||||
.storage
|
||||
.remove_session(chat_id)
|
||||
.remove_dialogue(chat_id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if let SessionState::Next(new_session) = self
|
||||
if let DialogueStage::Next(new_dialogue) = self
|
||||
.handler
|
||||
.handle_ctx(SessionHandlerCtx {
|
||||
.handle_ctx(DialogueHandlerCtx {
|
||||
bot: ctx.bot,
|
||||
update: ctx.update,
|
||||
session,
|
||||
dialogue,
|
||||
})
|
||||
.await
|
||||
{
|
||||
if self
|
||||
.storage
|
||||
.update_session(chat_id, new_session)
|
||||
.update_dialogue(chat_id, new_dialogue)
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
panic!(
|
||||
"We previously storage.remove_session() so \
|
||||
storage.update_session() must return None"
|
||||
"We previously storage.remove_dialogue() so \
|
||||
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
|
||||
|
||||
#![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 session_dispatcher;
|
||||
mod session_handler_ctx;
|
||||
mod session_state;
|
||||
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 session_dispatcher::SessionDispatcher;
|
||||
pub use session_handler_ctx::SessionHandlerCtx;
|
||||
pub use session_state::SessionState;
|
||||
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::{
|
||||
dispatching::session::GetChatId,
|
||||
dispatching::dialogue::GetChatId,
|
||||
requests::{Request, ResponseResult},
|
||||
types::Message,
|
||||
Bot,
|
||||
|
|
|
@ -42,11 +42,11 @@
|
|||
//! [`SessionDispatcher`]: crate::dispatching::SessionDispatcher
|
||||
|
||||
mod ctx_handlers;
|
||||
pub mod dialogue;
|
||||
mod dispatcher;
|
||||
mod dispatcher_handler_ctx;
|
||||
mod error_handlers;
|
||||
mod middleware;
|
||||
pub mod session;
|
||||
pub mod update_listeners;
|
||||
|
||||
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::{
|
||||
dispatching::{
|
||||
session::{
|
||||
GetChatId, SessionDispatcher, SessionHandlerCtx, SessionState,
|
||||
dialogue::{
|
||||
DialogueDispatcher, DialogueHandlerCtx, DialogueStage, GetChatId,
|
||||
},
|
||||
Dispatcher, DispatcherHandlerCtx,
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue