mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 14:35:36 +01:00
Merge remote-tracking branch 'origin/rework-dispatching' into rework-dispatching
This commit is contained in:
commit
6de0c24ed2
13 changed files with 202 additions and 179 deletions
|
@ -1,12 +1,21 @@
|
|||
use teloxide::prelude::*;
|
||||
|
||||
use std::env::{set_var, var};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
std::env::set_var("RUST_LOG", "ping_pong_bot=trace");
|
||||
// Configure a fancy logger. Let this bot print everything, but restrict
|
||||
// teloxide to only log errors.
|
||||
set_var("RUST_LOG", "ping_pong_bot=trace");
|
||||
set_var("RUST_LOG", "teloxide=error");
|
||||
pretty_env_logger::init();
|
||||
log::info!("Starting the ping-pong bot!");
|
||||
|
||||
Dispatcher::<RequestError>::new(Bot::new("MyAwesomeToken"))
|
||||
let bot = Bot::new(var("TELOXIDE_TOKEN").unwrap());
|
||||
|
||||
// Create a dispatcher with a single message handler that answers "pong" to
|
||||
// each incoming message.
|
||||
Dispatcher::<RequestError>::new(bot)
|
||||
.message_handler(&|ctx: DispatcherHandlerCtx<Message>| async move {
|
||||
ctx.answer("pong").send().await?;
|
||||
Ok(())
|
||||
|
|
|
@ -10,9 +10,8 @@ edition = "2018"
|
|||
pretty_env_logger = "0.3.1"
|
||||
log = "0.4.8"
|
||||
tokio = "0.2.9"
|
||||
strum = "0.17.1"
|
||||
smart-default = "0.6.0"
|
||||
strum_macros = "0.17.1"
|
||||
parse-display = "0.1.1"
|
||||
teloxide = { path = "../../" }
|
||||
|
||||
[profile.release]
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
#[macro_use]
|
||||
extern crate strum_macros;
|
||||
#![allow(clippy::trivial_regex)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate smart_default;
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::env::{set_var, var};
|
||||
|
||||
use teloxide::{
|
||||
prelude::*,
|
||||
types::{KeyboardButton, ReplyKeyboardMarkup},
|
||||
};
|
||||
|
||||
use parse_display::{Display, FromStr};
|
||||
|
||||
// ============================================================================
|
||||
// [Favourite music kinds]
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Copy, Clone, Display, EnumString)]
|
||||
#[derive(Copy, Clone, Display, FromStr)]
|
||||
enum FavouriteMusic {
|
||||
Rock,
|
||||
Metal,
|
||||
|
@ -33,96 +36,101 @@ impl FavouriteMusic {
|
|||
}
|
||||
|
||||
// ============================================================================
|
||||
// [A UserInfo's data]
|
||||
// [A type-safe finite automaton]
|
||||
// ============================================================================
|
||||
|
||||
// TODO: implement a type-safe UserInfo without lots of .unwrap
|
||||
#[derive(Default)]
|
||||
struct UserInfo {
|
||||
full_name: Option<String>,
|
||||
age: Option<u8>,
|
||||
favourite_music: Option<FavouriteMusic>,
|
||||
#[derive(Clone)]
|
||||
struct ReceiveAgeState {
|
||||
full_name: String,
|
||||
}
|
||||
|
||||
impl Display for UserInfo {
|
||||
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()
|
||||
)
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct ReceiveFavouriteMusicState {
|
||||
data: ReceiveAgeState,
|
||||
age: u8,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// [States of a dialogue]
|
||||
// ============================================================================
|
||||
#[derive(Display)]
|
||||
#[display(
|
||||
"Your full name: {data.data.full_name}, your age: {data.age}, your \
|
||||
favourite music: {favourite_music}"
|
||||
)]
|
||||
struct ExitState {
|
||||
data: ReceiveFavouriteMusicState,
|
||||
favourite_music: FavouriteMusic,
|
||||
}
|
||||
|
||||
#[derive(SmartDefault)]
|
||||
enum State {
|
||||
enum Dialogue {
|
||||
#[default]
|
||||
Start,
|
||||
FullName,
|
||||
Age,
|
||||
FavouriteMusic,
|
||||
ReceiveFullName,
|
||||
ReceiveAge(ReceiveAgeState),
|
||||
ReceiveFavouriteMusic(ReceiveFavouriteMusicState),
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// [Control a dialogue]
|
||||
// ============================================================================
|
||||
|
||||
type Ctx = DialogueHandlerCtx<Message, State, UserInfo>;
|
||||
type Res = Result<DialogueStage<State, UserInfo>, RequestError>;
|
||||
type Ctx<State> = DialogueHandlerCtx<Message, State>;
|
||||
type Res = Result<DialogueStage<Dialogue>, RequestError>;
|
||||
|
||||
async fn send_favourite_music_types(ctx: &Ctx) -> Result<(), RequestError> {
|
||||
async fn start(ctx: Ctx<()>) -> Res {
|
||||
ctx.answer("Let's start! First, what's your full name?")
|
||||
.send()
|
||||
.await?;
|
||||
next(Dialogue::ReceiveFullName)
|
||||
}
|
||||
|
||||
async fn full_name(ctx: Ctx<()>) -> Res {
|
||||
match ctx.update.text() {
|
||||
None => {
|
||||
ctx.answer("Please, send me a text message!").send().await?;
|
||||
next(Dialogue::ReceiveFullName)
|
||||
}
|
||||
Some(full_name) => {
|
||||
ctx.answer("What a wonderful name! Your age?")
|
||||
.send()
|
||||
.await?;
|
||||
next(Dialogue::ReceiveAge(ReceiveAgeState {
|
||||
full_name: full_name.to_owned(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn age(ctx: Ctx<ReceiveAgeState>) -> Res {
|
||||
match ctx.update.text().unwrap().parse() {
|
||||
Ok(age) => {
|
||||
ctx.answer("Good. Now choose your favourite music:")
|
||||
.reply_markup(FavouriteMusic::markup())
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start(mut ctx: Ctx) -> Res {
|
||||
ctx.answer("Let's start! First, what's your full name?")
|
||||
.send()
|
||||
.await?;
|
||||
state!(ctx, State::FullName);
|
||||
next(ctx.dialogue)
|
||||
}
|
||||
|
||||
async fn full_name(mut ctx: Ctx) -> Res {
|
||||
ctx.answer("What a wonderful name! Your age?")
|
||||
.send()
|
||||
.await?;
|
||||
ctx.dialogue.data.full_name = Some(ctx.update.text().unwrap().to_owned());
|
||||
state!(ctx, State::Age);
|
||||
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.dialogue.data.age = Some(ok);
|
||||
state!(ctx, State::FavouriteMusic);
|
||||
next(Dialogue::ReceiveFavouriteMusic(
|
||||
ReceiveFavouriteMusicState {
|
||||
data: ctx.dialogue,
|
||||
age,
|
||||
},
|
||||
))
|
||||
}
|
||||
Err(_) => {
|
||||
ctx.answer("Oh, please, enter a number!").send().await?;
|
||||
next(Dialogue::ReceiveAge(ctx.dialogue))
|
||||
}
|
||||
Err(_) => ctx
|
||||
.answer("Oh, please, enter a number!")
|
||||
.send()
|
||||
.await
|
||||
.map(|_| ())?,
|
||||
}
|
||||
|
||||
next(ctx.dialogue)
|
||||
}
|
||||
|
||||
async fn favourite_music(mut ctx: Ctx) -> Res {
|
||||
async fn favourite_music(ctx: Ctx<ReceiveFavouriteMusicState>) -> Res {
|
||||
match ctx.update.text().unwrap().parse() {
|
||||
Ok(ok) => {
|
||||
ctx.dialogue.data.favourite_music = Some(ok);
|
||||
ctx.answer(format!("Fine. {}", ctx.dialogue.data))
|
||||
Ok(favourite_music) => {
|
||||
ctx.answer(format!(
|
||||
"Fine. {}",
|
||||
ExitState {
|
||||
data: ctx.dialogue.clone(),
|
||||
favourite_music
|
||||
}
|
||||
))
|
||||
.send()
|
||||
.await?;
|
||||
exit()
|
||||
|
@ -131,17 +139,33 @@ async fn favourite_music(mut ctx: Ctx) -> Res {
|
|||
ctx.answer("Oh, please, enter from the keyboard!")
|
||||
.send()
|
||||
.await?;
|
||||
next(ctx.dialogue)
|
||||
next(Dialogue::ReceiveFavouriteMusic(ctx.dialogue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_message(ctx: Ctx) -> Res {
|
||||
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,
|
||||
async fn handle_message(ctx: Ctx<Dialogue>) -> Res {
|
||||
match ctx {
|
||||
DialogueHandlerCtx {
|
||||
bot,
|
||||
update,
|
||||
dialogue: Dialogue::Start,
|
||||
} => start(DialogueHandlerCtx::new(bot, update, ())).await,
|
||||
DialogueHandlerCtx {
|
||||
bot,
|
||||
update,
|
||||
dialogue: Dialogue::ReceiveFullName,
|
||||
} => full_name(DialogueHandlerCtx::new(bot, update, ())).await,
|
||||
DialogueHandlerCtx {
|
||||
bot,
|
||||
update,
|
||||
dialogue: Dialogue::ReceiveAge(s),
|
||||
} => age(DialogueHandlerCtx::new(bot, update, s)).await,
|
||||
DialogueHandlerCtx {
|
||||
bot,
|
||||
update,
|
||||
dialogue: Dialogue::ReceiveFavouriteMusic(s),
|
||||
} => favourite_music(DialogueHandlerCtx::new(bot, update, s)).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,11 +175,14 @@ async fn handle_message(ctx: Ctx) -> Res {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
std::env::set_var("RUST_LOG", "simple_dialogue=trace");
|
||||
set_var("RUST_LOG", "simple_dialogue=trace");
|
||||
set_var("RUST_LOG", "teloxide=error");
|
||||
pretty_env_logger::init();
|
||||
log::info!("Starting the simple_dialogue bot!");
|
||||
|
||||
Dispatcher::new(Bot::new("YourAwesomeToken"))
|
||||
let bot = Bot::new(var("TELOXIDE_TOKEN").unwrap());
|
||||
|
||||
Dispatcher::new(bot)
|
||||
.message_handler(&DialogueDispatcher::new(|ctx| async move {
|
||||
handle_message(ctx)
|
||||
.await
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
/// A type, encapsulating a dialogue state and arbitrary data.
|
||||
#[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,7 +1,6 @@
|
|||
use crate::dispatching::{
|
||||
dialogue::{
|
||||
Dialogue, DialogueHandlerCtx, DialogueStage, GetChatId, InMemStorage,
|
||||
Storage,
|
||||
DialogueHandlerCtx, DialogueStage, GetChatId, InMemStorage, Storage,
|
||||
},
|
||||
CtxHandler, DispatcherHandlerCtx,
|
||||
};
|
||||
|
@ -13,16 +12,14 @@ use std::{future::Future, pin::Pin};
|
|||
/// an instance of this dispatcher into the [`Dispatcher`]'s methods.
|
||||
///
|
||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||
pub struct DialogueDispatcher<'a, State, T, H> {
|
||||
storage: Box<dyn Storage<State, T> + 'a>,
|
||||
pub struct DialogueDispatcher<'a, D, H> {
|
||||
storage: Box<dyn Storage<D> + 'a>,
|
||||
handler: H,
|
||||
}
|
||||
|
||||
impl<'a, State, T, H> DialogueDispatcher<'a, State, T, H>
|
||||
impl<'a, D, H> DialogueDispatcher<'a, D, H>
|
||||
where
|
||||
Dialogue<State, T>: Default + 'a,
|
||||
T: Default + 'a,
|
||||
State: Default + 'a,
|
||||
D: Default + 'a,
|
||||
{
|
||||
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
|
||||
/// (a default storage).
|
||||
|
@ -40,7 +37,7 @@ where
|
|||
#[must_use]
|
||||
pub fn with_storage<Stg>(handler: H, storage: Stg) -> Self
|
||||
where
|
||||
Stg: Storage<State, T> + 'a,
|
||||
Stg: Storage<D> + 'a,
|
||||
{
|
||||
Self {
|
||||
storage: Box::new(storage),
|
||||
|
@ -49,12 +46,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, State, T, H, Upd> CtxHandler<DispatcherHandlerCtx<Upd>, Result<(), ()>>
|
||||
for DialogueDispatcher<'a, State, T, H>
|
||||
impl<'a, D, H, Upd> CtxHandler<DispatcherHandlerCtx<Upd>, Result<(), ()>>
|
||||
for DialogueDispatcher<'a, D, H>
|
||||
where
|
||||
H: CtxHandler<DialogueHandlerCtx<Upd, State, T>, DialogueStage<State, T>>,
|
||||
H: CtxHandler<DialogueHandlerCtx<Upd, D>, DialogueStage<D>>,
|
||||
Upd: GetChatId,
|
||||
Dialogue<State, T>: Default,
|
||||
D: Default,
|
||||
{
|
||||
fn handle_ctx<'b>(
|
||||
&'b self,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
dispatching::dialogue::{Dialogue, GetChatId},
|
||||
dispatching::dialogue::GetChatId,
|
||||
requests::{
|
||||
DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage,
|
||||
PinChatMessage, SendAnimation, SendAudio, SendContact, SendDocument,
|
||||
|
@ -14,28 +14,37 @@ use std::sync::Arc;
|
|||
/// A context of a [`DialogueDispatcher`]'s message handler.
|
||||
///
|
||||
/// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher
|
||||
pub struct DialogueHandlerCtx<Upd, State, T> {
|
||||
pub struct DialogueHandlerCtx<Upd, D> {
|
||||
pub bot: Arc<Bot>,
|
||||
pub update: Upd,
|
||||
pub dialogue: Dialogue<State, T>,
|
||||
pub dialogue: D,
|
||||
}
|
||||
|
||||
/// Sets a new state.
|
||||
///
|
||||
/// Use it like this: `state!(ctx, State::RequestAge)`, where `ctx` is
|
||||
/// [`DialogueHandlerCtx<Upd, State, T>`] and `State::RequestAge` is of type
|
||||
/// `State`.
|
||||
///
|
||||
/// [`DialogueHandlerCtx<Upd, State, T>`]:
|
||||
/// crate::dispatching::dialogue::DialogueHandlerCtx
|
||||
#[macro_export]
|
||||
macro_rules! state {
|
||||
($ctx:ident, $state:expr) => {
|
||||
$ctx.dialogue.state = $state;
|
||||
};
|
||||
impl<Upd, D> DialogueHandlerCtx<Upd, D> {
|
||||
/// Creates a new instance with the provided fields.
|
||||
pub fn new(bot: Arc<Bot>, update: Upd, dialogue: D) -> Self {
|
||||
Self {
|
||||
bot,
|
||||
update,
|
||||
dialogue,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new instance by substituting a dialogue and preserving
|
||||
/// `self.bot` and `self.update`.
|
||||
pub fn with_new_dialogue<Nd>(
|
||||
self,
|
||||
new_dialogue: Nd,
|
||||
) -> DialogueHandlerCtx<Upd, Nd> {
|
||||
DialogueHandlerCtx {
|
||||
bot: self.bot,
|
||||
update: self.update,
|
||||
dialogue: new_dialogue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Upd, State, T> GetChatId for DialogueHandlerCtx<Upd, State, T>
|
||||
impl<Upd, D> GetChatId for DialogueHandlerCtx<Upd, D>
|
||||
where
|
||||
Upd: GetChatId,
|
||||
{
|
||||
|
@ -44,7 +53,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<State, Data> DialogueHandlerCtx<Message, State, Data> {
|
||||
impl<D> DialogueHandlerCtx<Message, D> {
|
||||
pub fn answer<T>(&self, text: T) -> SendMessage
|
||||
where
|
||||
T: Into<String>,
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
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>),
|
||||
pub enum DialogueStage<D> {
|
||||
Next(D),
|
||||
Exit,
|
||||
}
|
||||
|
||||
/// A shortcut for `Ok(DialogueStage::Next(dialogue))`.
|
||||
pub fn next<E, State, T>(
|
||||
dialogue: Dialogue<State, T>,
|
||||
) -> Result<DialogueStage<State, T>, E> {
|
||||
pub fn next<E, D>(dialogue: D) -> Result<DialogueStage<D>, E> {
|
||||
Ok(DialogueStage::Next(dialogue))
|
||||
}
|
||||
|
||||
/// A shortcut for `Ok(DialogueStage::Exit)`.
|
||||
pub fn exit<E, State, T>() -> Result<DialogueStage<State, T>, E> {
|
||||
pub fn exit<E, D>() -> Result<DialogueStage<D>, E> {
|
||||
Ok(DialogueStage::Exit)
|
||||
}
|
||||
|
|
|
@ -2,22 +2,20 @@
|
|||
//!
|
||||
//! There are four main components:
|
||||
//!
|
||||
//! 1. Your type `State`, which designates a dialogue state at the current
|
||||
//! 1. Your type `D`, which designates a dialogue state at the current
|
||||
//! moment.
|
||||
//! 2. Your type `T`, which represents dialogue data.
|
||||
//! 3. [`Dialogue`], which encapsulates the two types, described above.
|
||||
//! 4. [`Storage`], which encapsulates all the sessions.
|
||||
//! 5. Your handler, which receives an update and turns your session into the
|
||||
//! 2. [`Storage`], which encapsulates all the dialogues.
|
||||
//! 3. Your handler, which receives an update and turns your dialogue into the
|
||||
//! next state.
|
||||
//! 6. [`DialogueDispatcher`], which encapsulates your handler, [`Storage`], and
|
||||
//! implements [`CtxHandler`].
|
||||
//! 4. [`DialogueDispatcher`], which encapsulates your handler, [`Storage`],
|
||||
//! and implements [`CtxHandler`].
|
||||
//!
|
||||
//! You supply [`DialogueDispatcher`] into [`Dispatcher`]. Every time
|
||||
//! [`Dispatcher`] calls `DialogueDispatcher::handle_ctx(...)`, the following
|
||||
//! steps are executed:
|
||||
//!
|
||||
//! 1. If a storage doesn't contain a dialogue from this chat, supply
|
||||
//! `Dialogue::default()` into you handler, otherwise, supply the saved session
|
||||
//! `D::default()` into you handler, otherwise, supply the saved session
|
||||
//! from this chat.
|
||||
//! 3. If a handler has returned [`DialogueStage::Exit`], remove the session
|
||||
//! from the storage, otherwise ([`DialogueStage::Next`]) force the storage to
|
||||
|
@ -37,14 +35,12 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
mod dialogue;
|
||||
mod dialogue_dispatcher;
|
||||
mod dialogue_handler_ctx;
|
||||
mod dialogue_stage;
|
||||
mod get_chat_id;
|
||||
mod storage;
|
||||
|
||||
pub use dialogue::Dialogue;
|
||||
pub use dialogue_dispatcher::DialogueDispatcher;
|
||||
pub use dialogue_handler_ctx::DialogueHandlerCtx;
|
||||
pub use dialogue_stage::{exit, next, DialogueStage};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use super::Storage;
|
||||
use crate::dispatching::dialogue::Dialogue;
|
||||
use std::collections::HashMap;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
|
@ -13,25 +12,18 @@ use tokio::sync::Mutex;
|
|||
/// 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>>>,
|
||||
pub struct InMemStorage<D> {
|
||||
map: Mutex<HashMap<i64, D>>,
|
||||
}
|
||||
|
||||
#[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>> {
|
||||
impl<D> Storage<D> for InMemStorage<D> {
|
||||
async fn remove_dialogue(&self, chat_id: i64) -> Option<D> {
|
||||
self.map.lock().await.remove(&chat_id)
|
||||
}
|
||||
|
||||
async fn update_dialogue(
|
||||
&self,
|
||||
chat_id: i64,
|
||||
dialogue: Dialogue<State, T>,
|
||||
) -> Option<Dialogue<State, T>> {
|
||||
async fn update_dialogue(&self, chat_id: i64, dialogue: D) -> Option<D> {
|
||||
self.map.lock().await.insert(chat_id, dialogue)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
mod in_mem_storage;
|
||||
|
||||
use crate::dispatching::dialogue::Dialogue;
|
||||
use async_trait::async_trait;
|
||||
pub use in_mem_storage::InMemStorage;
|
||||
|
||||
|
@ -14,21 +13,16 @@ pub use in_mem_storage::InMemStorage;
|
|||
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
|
||||
#[async_trait(?Send)]
|
||||
#[async_trait]
|
||||
pub trait Storage<State, T> {
|
||||
pub trait Storage<D> {
|
||||
/// 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>>;
|
||||
async fn remove_dialogue(&self, chat_id: i64) -> Option<D>;
|
||||
|
||||
/// 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>>;
|
||||
async fn update_dialogue(&self, chat_id: i64, dialogue: D) -> Option<D>;
|
||||
}
|
||||
|
|
|
@ -155,13 +155,30 @@ pub fn polling(
|
|||
let updates = match req.send().await {
|
||||
Err(err) => vec![Err(err)],
|
||||
Ok(updates) => {
|
||||
// Set offset to the last update's id + 1
|
||||
if let Some(upd) = updates.last() {
|
||||
let id: i32 = match upd {
|
||||
Ok(ok) => ok.id,
|
||||
Err((value, _)) => value["update_id"]
|
||||
.as_i64()
|
||||
.expect(
|
||||
"The 'update_id' field must always exist in \
|
||||
Update",
|
||||
)
|
||||
.try_into()
|
||||
.expect("update_id must be i32"),
|
||||
};
|
||||
|
||||
offset = id + 1;
|
||||
}
|
||||
|
||||
let updates = updates
|
||||
.into_iter()
|
||||
.filter(|update| match update {
|
||||
Err(error) => {
|
||||
log::error!("Cannot parse an update: {:?}! \
|
||||
Err((value, error)) => {
|
||||
log::error!("Cannot parse an update.\nError: {:?}\nValue: {}\n\
|
||||
This is a bug in teloxide, please open an issue here: \
|
||||
https://github.com/teloxide/teloxide/issues.", error);
|
||||
https://github.com/teloxide/teloxide/issues.", error, value);
|
||||
false
|
||||
}
|
||||
Ok(_) => true,
|
||||
|
@ -171,9 +188,6 @@ pub fn polling(
|
|||
})
|
||||
.collect::<Vec<Update>>();
|
||||
|
||||
if let Some(upd) = updates.last() {
|
||||
offset = upd.id + 1;
|
||||
}
|
||||
updates.into_iter().map(Ok).collect::<Vec<_>>()
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,7 +9,6 @@ pub use crate::{
|
|||
Dispatcher, DispatcherHandlerCtx,
|
||||
},
|
||||
requests::{Request, ResponseResult},
|
||||
state,
|
||||
types::Message,
|
||||
Bot, RequestError,
|
||||
};
|
||||
|
|
|
@ -32,12 +32,14 @@ pub struct GetUpdates {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl Request for GetUpdates {
|
||||
type Output = Vec<serde_json::Result<Update>>;
|
||||
type Output = Vec<Result<Update, (Value, serde_json::Error)>>;
|
||||
|
||||
/// Deserialize to `Vec<serde_json::Result<Update>>` instead of
|
||||
/// `Vec<Update>`, because we want to parse the rest of updates even if our
|
||||
/// library hasn't parsed one.
|
||||
async fn send(&self) -> ResponseResult<Vec<serde_json::Result<Update>>> {
|
||||
async fn send(
|
||||
&self,
|
||||
) -> ResponseResult<Vec<Result<Update, (Value, serde_json::Error)>>> {
|
||||
let value: Value = net::request_json(
|
||||
self.bot.client(),
|
||||
self.bot.token(),
|
||||
|
@ -49,7 +51,10 @@ impl Request for GetUpdates {
|
|||
match value {
|
||||
Value::Array(array) => Ok(array
|
||||
.into_iter()
|
||||
.map(|value| serde_json::from_str(&value.to_string()))
|
||||
.map(|value| {
|
||||
serde_json::from_str(&value.to_string())
|
||||
.map_err(|error| (value, error))
|
||||
})
|
||||
.collect()),
|
||||
_ => Err(RequestError::InvalidJson(
|
||||
serde_json::from_value::<Vec<Update>>(value)
|
||||
|
|
Loading…
Reference in a new issue