Implement chat::Dispatcher

This commit is contained in:
Temirkhan Myrzamadi 2020-01-15 03:46:16 +06:00
parent 5fd6ae3630
commit dedc861523
10 changed files with 63 additions and 44 deletions

View file

@ -4,10 +4,10 @@ version = "0.0.0"
publish = false
edition = "2018"
[dev-dependencies]
[dependencies]
teloxide = { path = "../" }
tokio = "0.2.6"
pretty_env_logger = "0.3"
pretty_env_logger = "0.3.1"
futures = "0.3.1"
log = "0.4.8"

View file

@ -1,22 +1,22 @@
use futures::stream::StreamExt;
use teloxide::{
dispatching::{
private::Dispatcher, update_listeners::polling_default, SessionState,
chat::Dispatcher, update_listeners::polling_default, SessionState,
},
requests::Request,
types::{Update, UpdateKind},
Bot,
};
use teloxide::dispatching::chat::{ChatUpdate, ChatUpdateKind};
#[tokio::main]
async fn main() {
pretty_env_logger::init();
let bot = &Bot::new("1061598315:AAErEDodTsrqD3UxA_EvFyEfXbKA6DT25G0");
let mut updater = Box::pin(polling_default(bot));
let handler = |s, upd: Update| async move {
let handler = |s, upd: ChatUpdate| async move {
match upd.kind {
UpdateKind::Message(m) => {
ChatUpdateKind::Message(m) => {
let msg = bot.send_message(m.chat.id, "pong");
msg.send().await.unwrap();
}

View file

@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};
use crate::types::{CallbackQuery, Message};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ChatUpdate {
pub id: i32,
pub kind: ChatUpdateKind,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ChatUpdateKind {
Message(Message),
EditedMessage(Message),
CallbackQuery(CallbackQuery),
}

View file

@ -3,33 +3,21 @@ use super::{
storage::{InMemStorage, Storage},
};
use crate::{
dispatching::{Handler, SessionState},
types::{ChatKind, Update, UpdateKind},
dispatching::{chat::ChatUpdate, Handler, SessionState},
types::{Update, UpdateKind},
};
use crate::dispatching::chat::ChatUpdateKind;
/// A dispatcher that dispatches updates from 1-to-1 chats.
/// A dispatcher that dispatches updates from chats.
pub struct Dispatcher<'a, Session, H> {
storage: Box<dyn Storage<Session> + 'a>,
handler: H,
}
#[macro_use]
mod macros {
#[macro_export]
macro_rules! private_chat_id {
($msg:expr) => {
match &$msg.chat.kind {
ChatKind::Private { .. } => $msg.chat.id,
_ => return DispatchResult::Unhandled,
}
};
}
}
impl<'a, Session, H> Dispatcher<'a, Session, H>
where
Session: Default + 'a,
H: Handler<Session>,
H: Handler<Session, ChatUpdate>,
{
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
/// (a default storage).
@ -58,18 +46,28 @@ where
/// ## Returns
/// Returns [`DispatchResult::Handled`] if `update` was supplied to a
/// handler, and [`DispatchResult::Unhandled`] if it was an update not
/// from a 1-to-1 chat.
/// from a chat.
///
/// [`DispatchResult::Handled`]: crate::dispatching::DispatchResult::Handled
/// [`DispatchResult::Unhandled`]:
/// crate::dispatching::DispatchResult::Unhandled
pub async fn dispatch(&mut self, update: Update) -> DispatchResult {
let chat_id = match &update.kind {
UpdateKind::Message(msg) => private_chat_id!(msg),
UpdateKind::EditedMessage(msg) => private_chat_id!(msg),
let chat_update = match update.kind {
UpdateKind::Message(msg) => ChatUpdate { id: update.id, kind: ChatUpdateKind::Message(msg) },
UpdateKind::EditedMessage(msg) => ChatUpdate { id: update.id, kind: ChatUpdateKind::EditedMessage(msg) },
UpdateKind::CallbackQuery(query) => ChatUpdate { id: update.id, kind: ChatUpdateKind::CallbackQuery(query) },
_ => return DispatchResult::Unhandled,
};
let chat_id = match &chat_update.kind {
ChatUpdateKind::Message(msg) => msg.chat.id,
ChatUpdateKind::EditedMessage(msg) => msg.chat.id,
ChatUpdateKind::CallbackQuery(query) => match &query.message {
None => return DispatchResult::Unhandled,
Some(msg) => msg.chat.id,
},
};
let session = self
.storage
.remove_session(chat_id)
@ -77,7 +75,7 @@ where
.unwrap_or_default();
if let SessionState::Continue(session) =
self.handler.handle(session, update).await
self.handler.handle(session, chat_update).await
{
if self
.storage

View file

@ -1,4 +1,4 @@
//! Dispatching updates from 1-to-1 chats.
//! Dispatching updates from chats.
//!
//! There are four main components:
//!
@ -14,13 +14,13 @@
//! Every time you call [`.dispatch(update)`] on your dispatcher, the following
//! steps are executed:
//!
//! 1. If a supplied update is not from a 1-to-1 chat, return
//! 1. If a supplied update is not from a chat, return
//! [`DispatchResult::Unhandled`].
//! 2. If a storage doesn't contain a session from this chat, supply
//! `Session::default()` into you handler, otherwise, supply the previous
//! session.
//! 3. If a handler has returned [`SessionState::Terminate`], remove the sesion
//! from a storage, otherwise force the storage to update the session.
//! 3. If a handler has returned [`SessionState::Terminate`], remove the
//! session from a storage, otherwise force the storage to update the session.
//!
//! [`Storage`]: crate::dispatching::private::Storage
//! [`Dispatcher`]: crate::dispatching::private::Dispatcher
@ -32,8 +32,10 @@
// TODO: examples
mod chat_update;
mod dispatcher;
mod storage;
pub use chat_update::*;
pub use dispatcher::*;
pub use storage::*;

View file

@ -3,7 +3,7 @@ mod in_mem_storage;
use async_trait::async_trait;
pub use in_mem_storage::InMemStorage;
/// A storage of user sessions.
/// 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.

View file

@ -1,4 +1,3 @@
use crate::types::Update;
use std::{future::Future, pin::Pin};
/// Continue or terminate a user session.
@ -17,31 +16,33 @@ pub enum SessionState<Session> {
/// [`SessionState::Continue(session)`]:
/// crate::dispatching::SessionState::Continue
/// [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate
pub trait Handler<Session> {
pub trait Handler<Session, U> {
#[must_use]
fn handle<'a>(
&'a self,
session: Session,
update: Update,
update: U,
) -> Pin<Box<dyn Future<Output = SessionState<Session>> + 'a>>
where
Session: 'a;
Session: 'a,
U: 'a;
}
/// The implementation of `Handler` for `Fn(Session, Update) -> Future<Output =
/// SessionState<Session>>`.
impl<Session, F, Fut> Handler<Session> for F
impl<Session, U, F, Fut> Handler<Session, U> for F
where
F: Fn(Session, Update) -> Fut,
F: Fn(Session, U) -> Fut,
Fut: Future<Output = SessionState<Session>>,
{
fn handle<'a>(
&'a self,
session: Session,
update: Update,
update: U,
) -> Pin<Box<dyn Future<Output = Fut::Output> + 'a>>
where
Session: 'a,
U: 'a,
{
Box::pin(async move { self(session, update).await })
}

View file

@ -7,9 +7,9 @@ pub enum DispatchResult {
Unhandled,
}
pub mod chat;
pub mod filters;
mod handler;
pub mod private;
pub mod update_listeners;
pub use filters::Filter;

View file

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use derive_more::From;
use serde::{Deserialize, Serialize};
use crate::types::{
ForceReply, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove,
@ -25,4 +25,4 @@ mod tests {
let actual: ReplyMarkup = data.into();
assert_eq!(actual, expected)
}
}
}