mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-23 06:51:01 +01:00
Merge branch 'dev' of https://github.com/teloxide/teloxide into useful_functions
This commit is contained in:
commit
50c29227ce
16 changed files with 439 additions and 609 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,4 +1,6 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
.idea/
|
||||
.idea/
|
||||
.vscode/
|
||||
examples/target
|
||||
|
|
18
src/dispatching/chat/chat_update.rs
Normal file
18
src/dispatching/chat/chat_update.rs
Normal 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),
|
||||
}
|
106
src/dispatching/chat/dispatcher.rs
Normal file
106
src/dispatching/chat/dispatcher.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
use super::{
|
||||
super::DispatchResult,
|
||||
storage::{InMemStorage, Storage},
|
||||
};
|
||||
use crate::{
|
||||
dispatching::{
|
||||
chat::{ChatUpdate, ChatUpdateKind},
|
||||
Handler, SessionState,
|
||||
},
|
||||
types::{Update, UpdateKind},
|
||||
};
|
||||
|
||||
/// A dispatcher that dispatches updates from chats.
|
||||
pub struct Dispatcher<'a, Session, H> {
|
||||
storage: Box<dyn Storage<Session> + 'a>,
|
||||
handler: H,
|
||||
}
|
||||
|
||||
impl<'a, Session, H> Dispatcher<'a, Session, H>
|
||||
where
|
||||
Session: Default + 'a,
|
||||
H: Handler<Session, ChatUpdate>,
|
||||
{
|
||||
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
|
||||
/// (a default storage).
|
||||
///
|
||||
/// [`InMemStorage`]: crate::dispatching::private::InMemStorage
|
||||
pub fn new(handler: H) -> Self {
|
||||
Self {
|
||||
storage: Box::new(InMemStorage::default()),
|
||||
handler,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a dispatcher with the specified `handler` and `storage`.
|
||||
pub fn with_storage<Stg>(handler: H, storage: Stg) -> Self
|
||||
where
|
||||
Stg: Storage<Session> + 'a,
|
||||
{
|
||||
Self {
|
||||
storage: Box::new(storage),
|
||||
handler,
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatches a single `update`.
|
||||
///
|
||||
/// ## Returns
|
||||
/// Returns [`DispatchResult::Handled`] if `update` was supplied to a
|
||||
/// handler, and [`DispatchResult::Unhandled`] if it was an update not
|
||||
/// 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_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)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if let SessionState::Continue(session) =
|
||||
self.handler.handle(session, chat_update).await
|
||||
{
|
||||
if self
|
||||
.storage
|
||||
.update_session(chat_id, session)
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
panic!(
|
||||
"We previously storage.remove_session() so \
|
||||
storage.update_session() must return None"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DispatchResult::Handled
|
||||
}
|
||||
}
|
41
src/dispatching/chat/mod.rs
Normal file
41
src/dispatching/chat/mod.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
//! Dispatching updates from chats.
|
||||
//!
|
||||
//! There are four main components:
|
||||
//!
|
||||
//! 1. Your session type `Session`, which designates a dialogue state at the
|
||||
//! current moment.
|
||||
//! 2. [`Storage`] that encapsulates all the sessions.
|
||||
//! 3. Your handler of type `H: async Fn(Session, Update) ->
|
||||
//! HandleResult<Session>` that receives an update and turns your session into
|
||||
//! the next state.
|
||||
//! 4. [`Dispatcher`], which encapsulates your handler and [`Storage`], and has
|
||||
//! the [`dispatch(Update) -> DispatchResult`] function.
|
||||
//!
|
||||
//! Every time you call [`.dispatch(update)`] on your dispatcher, the following
|
||||
//! steps are executed:
|
||||
//!
|
||||
//! 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
|
||||
//! session from a storage, otherwise force the storage to update the session.
|
||||
//!
|
||||
//! [`Storage`]: crate::dispatching::private::Storage
|
||||
//! [`Dispatcher`]: crate::dispatching::private::Dispatcher
|
||||
//! [`dispatch(Update) -> DispatchResult`]:
|
||||
//! crate::dispatching::private::Dispatcher::dispatch
|
||||
//! [`.dispatch(update)`]: crate::dispatching::private::Dispatcher::dispatch
|
||||
//! [`DispatchResult::Unhandled`]: crate::dispatching::DispatchResult::Unhandled
|
||||
//! [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate
|
||||
|
||||
// TODO: examples
|
||||
|
||||
mod chat_update;
|
||||
mod dispatcher;
|
||||
mod storage;
|
||||
|
||||
pub use chat_update::*;
|
||||
pub use dispatcher::*;
|
||||
pub use storage::*;
|
32
src/dispatching/chat/storage/in_mem_storage.rs
Normal file
32
src/dispatching/chat/storage/in_mem_storage.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
use super::Storage;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 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(Clone, Debug, Eq, PartialEq, Default)]
|
||||
pub struct InMemStorage<Session> {
|
||||
map: HashMap<i64, Session>,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
#[async_trait]
|
||||
impl<Session> Storage<Session> for InMemStorage<Session> {
|
||||
async fn remove_session(&mut self, chat_id: i64) -> Option<Session> {
|
||||
self.map.remove(&chat_id)
|
||||
}
|
||||
|
||||
async fn update_session(
|
||||
&mut self,
|
||||
chat_id: i64,
|
||||
state: Session,
|
||||
) -> Option<Session> {
|
||||
self.map.insert(chat_id, state)
|
||||
}
|
||||
}
|
32
src/dispatching/chat/storage/mod.rs
Normal file
32
src/dispatching/chat/storage/mod.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
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::private::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(&mut 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(
|
||||
&mut self,
|
||||
chat_id: i64,
|
||||
session: Session,
|
||||
) -> Option<Session>;
|
||||
}
|
|
@ -1,348 +0,0 @@
|
|||
//! A dispatcher based on filters.
|
||||
|
||||
use futures::StreamExt;
|
||||
|
||||
use crate::{
|
||||
dispatching::{filters::Filter, ErrorHandler, Handler, Updater},
|
||||
types::{
|
||||
CallbackQuery, ChosenInlineResult, InlineQuery, Message, Update,
|
||||
UpdateKind,
|
||||
},
|
||||
};
|
||||
use either::Either;
|
||||
|
||||
type FilterWithHandler<'a, T, E> =
|
||||
(Box<dyn Filter<T> + 'a>, Box<dyn Handler<T, E> + 'a>);
|
||||
type FiltersWithHandlers<'a, T, E> = Vec<FilterWithHandler<'a, T, E>>;
|
||||
|
||||
/// A dispatcher based on filters.
|
||||
///
|
||||
/// It consists of:
|
||||
/// 1. [`ErrorHandler`] than handles errors both from [`Updater`] and
|
||||
/// [`Handler`].
|
||||
/// 2. Filters and handlers.
|
||||
///
|
||||
/// First you call [`FilterDispatcher::new(eh)`] to create this dispatcher. `eh`
|
||||
/// stands for **E**rror **H**andler, you can simply provide a closure that
|
||||
/// takes [`Either<UpdaterE, HandlerE>`]:
|
||||
///
|
||||
/// ```
|
||||
/// use either::Either;
|
||||
/// use std::convert::Infallible;
|
||||
/// use teloxide::{dispatching::FilterDispatcher, RequestError};
|
||||
///
|
||||
/// let _ = FilterDispatcher::new(|err: Either<RequestError, Infallible>| {
|
||||
/// async {
|
||||
/// dbg!(err);
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Or you can do it even simpler by providing the built-in error handler
|
||||
/// [`Print`]:
|
||||
///
|
||||
/// ```
|
||||
/// use std::convert::Infallible;
|
||||
/// use teloxide::{
|
||||
/// dispatching::{error_handlers::Print, FilterDispatcher},
|
||||
/// RequestError,
|
||||
/// };
|
||||
///
|
||||
/// let _ = FilterDispatcher::<'_, Infallible, _>::new::<RequestError>(Print);
|
||||
/// ```
|
||||
///
|
||||
/// And then you register filters and handlers using the methods defined below,
|
||||
/// and then you call [`.dispatch(updater)`]. Filters and handlers are executed
|
||||
/// in order of registering. The following flowchart represents how this
|
||||
/// dispatcher acts:
|
||||
///
|
||||
/// <div align="center">
|
||||
/// <img src="https://raw.githubusercontent.com/teloxide/teloxide/dev/media/FILTER_DP_FLOWCHART.png" />
|
||||
/// </div>
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// The simplest example:
|
||||
/// ```no_run
|
||||
/// # async fn run() {
|
||||
/// use std::convert::Infallible;
|
||||
///
|
||||
/// use teloxide::{
|
||||
/// dispatching::{updaters::polling_default, FilterDispatcher},
|
||||
/// types::Message,
|
||||
/// Bot,
|
||||
/// };
|
||||
///
|
||||
/// async fn handle_edited_message(mes: Message) -> Result<(), Infallible> {
|
||||
/// println!("Edited message: {:?}", mes);
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// let bot = Bot::new("TOKEN");
|
||||
///
|
||||
/// // Create a dispatcher which handlers can't fail with the
|
||||
/// // error handler that just ignores all errors (that can't ever happen).
|
||||
/// let mut dp = FilterDispatcher::<Infallible, _>::new(|_| async {})
|
||||
/// // Add a handler, which handles all messages sent to the bot.
|
||||
/// .message_handler(true, |mes: Message| {
|
||||
/// async move {
|
||||
/// println!("New message: {:?}", mes);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// })
|
||||
/// // Add a handler, which handles all messages edited in a chat
|
||||
/// // with the bot.
|
||||
/// .edited_message_handler(true, handle_edited_message);
|
||||
///
|
||||
/// // Start dispatching updates using long polling.
|
||||
/// dp.dispatch(polling_default(&bot)).await;
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`std::fmt::Debug`]: std::fmt::Debug
|
||||
/// [`.dispatch(updater)`]: FilterDispatcher::dispatch
|
||||
/// [`ErrorHandler`]: crate::dispatching::error_handlers::ErrorHandler
|
||||
/// [`Updater`]: crate::dispatching::updaters::Updater
|
||||
/// [`Handler`]: crate::dispatching::Handler
|
||||
/// [`FilterDispatcher::new(eh)`]: FilterDispatcher::new
|
||||
/// [`Either<UpdaterE, HandlerE>`]: either::Either
|
||||
/// [`Print`]: crate::dispatching::error_handlers::Print
|
||||
pub struct FilterDispatcher<'a, HandlerE> {
|
||||
message_handlers: FiltersWithHandlers<'a, Message, HandlerE>,
|
||||
edited_message_handlers: FiltersWithHandlers<'a, Message, HandlerE>,
|
||||
channel_post_handlers: FiltersWithHandlers<'a, Message, HandlerE>,
|
||||
edited_channel_post_handlers: FiltersWithHandlers<'a, Message, HandlerE>,
|
||||
inline_query_handlers: FiltersWithHandlers<'a, InlineQuery, HandlerE>,
|
||||
chosen_inline_result_handlers:
|
||||
FiltersWithHandlers<'a, ChosenInlineResult, HandlerE>,
|
||||
callback_query_handlers: FiltersWithHandlers<'a, CallbackQuery, HandlerE>,
|
||||
}
|
||||
|
||||
impl<'a, HandlerE> FilterDispatcher<'a, HandlerE> {
|
||||
pub fn new() -> Self {
|
||||
FilterDispatcher {
|
||||
message_handlers: Vec::new(),
|
||||
edited_message_handlers: Vec::new(),
|
||||
channel_post_handlers: Vec::new(),
|
||||
edited_channel_post_handlers: Vec::new(),
|
||||
inline_query_handlers: Vec::new(),
|
||||
chosen_inline_result_handlers: Vec::new(),
|
||||
callback_query_handlers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message_handler<F, H>(mut self, filter: F, handler: H) -> Self
|
||||
where
|
||||
F: Filter<Message> + 'a,
|
||||
H: Handler<Message, HandlerE> + 'a,
|
||||
{
|
||||
self.message_handlers
|
||||
.push((Box::new(filter), Box::new(handler)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn edited_message_handler<F, H>(mut self, filter: F, handler: H) -> Self
|
||||
where
|
||||
F: Filter<Message> + 'a,
|
||||
H: Handler<Message, HandlerE> + 'a,
|
||||
{
|
||||
self.edited_message_handlers
|
||||
.push((Box::new(filter), Box::new(handler)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn channel_post_handler<F, H>(mut self, filter: F, handler: H) -> Self
|
||||
where
|
||||
F: Filter<Message> + 'a,
|
||||
H: Handler<Message, HandlerE> + 'a,
|
||||
{
|
||||
self.channel_post_handlers
|
||||
.push((Box::new(filter), Box::new(handler)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn edited_channel_post_handler<F, H>(
|
||||
mut self,
|
||||
filter: F,
|
||||
handler: H,
|
||||
) -> Self
|
||||
where
|
||||
F: Filter<Message> + 'a,
|
||||
H: Handler<Message, HandlerE> + 'a,
|
||||
{
|
||||
self.edited_channel_post_handlers
|
||||
.push((Box::new(filter), Box::new(handler)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn inline_query_handler<F, H>(mut self, filter: F, handler: H) -> Self
|
||||
where
|
||||
F: Filter<InlineQuery> + 'a,
|
||||
H: Handler<InlineQuery, HandlerE> + 'a,
|
||||
{
|
||||
self.inline_query_handlers
|
||||
.push((Box::new(filter), Box::new(handler)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn chosen_inline_result_handler<F, H>(
|
||||
mut self,
|
||||
filter: F,
|
||||
handler: H,
|
||||
) -> Self
|
||||
where
|
||||
F: Filter<ChosenInlineResult> + 'a,
|
||||
H: Handler<ChosenInlineResult, HandlerE> + 'a,
|
||||
{
|
||||
self.chosen_inline_result_handlers
|
||||
.push((Box::new(filter), Box::new(handler)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn callback_query_handler<F, H>(mut self, filter: F, handler: H) -> Self
|
||||
where
|
||||
F: Filter<CallbackQuery> + 'a,
|
||||
H: Handler<CallbackQuery, HandlerE> + 'a,
|
||||
{
|
||||
self.callback_query_handlers
|
||||
.push((Box::new(filter), Box::new(handler)));
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn dispatch<U>(
|
||||
&mut self,
|
||||
update: Update,
|
||||
) -> Result<(), HandlerE> {
|
||||
let Update { kind, id } = update;
|
||||
|
||||
let res = match kind.clone() {
|
||||
UpdateKind::Message(mes) => {
|
||||
Self::handle(mes, &self.message_handlers).await
|
||||
}
|
||||
UpdateKind::EditedMessage(mes) => {
|
||||
Self::handle(mes, &self.edited_message_handlers).await
|
||||
}
|
||||
UpdateKind::ChannelPost(post) => {
|
||||
Self::handle(post, &self.channel_post_handlers).await
|
||||
}
|
||||
UpdateKind::EditedChannelPost(post) => {
|
||||
Self::handle(post, &self.edited_channel_post_handlers).await
|
||||
}
|
||||
UpdateKind::InlineQuery(query) => {
|
||||
Self::handle(query, &self.inline_query_handlers).await
|
||||
}
|
||||
UpdateKind::ChosenInlineResult(result) => {
|
||||
Self::handle(result, &self.chosen_inline_result_handlers).await
|
||||
}
|
||||
UpdateKind::CallbackQuery(callback) => {
|
||||
Self::handle(callback, &self.callback_query_handlers).await
|
||||
}
|
||||
};
|
||||
|
||||
log::debug!("dispatched update#{id:?}: {kind:?}", id = id, kind = kind);
|
||||
res
|
||||
}
|
||||
|
||||
async fn handle<T>(
|
||||
update: T,
|
||||
handlers: &[FilterWithHandler<'a, T, HandlerE>],
|
||||
) -> Result<(), HandlerE>
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
for x in handlers {
|
||||
if x.0.test(&update) {
|
||||
return x.1.handle(update).await;
|
||||
}
|
||||
}
|
||||
|
||||
log::warn!("unhandled update {:?}", update);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
sync::atomic::{AtomicI32, Ordering},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
dispatching::{FilterDispatcher, Updater},
|
||||
types::{
|
||||
Chat, ChatKind, ForwardKind, MediaKind, Message, MessageKind,
|
||||
Sender, Update, UpdateKind, User,
|
||||
},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn first_handler_executes_1_time() {
|
||||
let counter = &AtomicI32::new(0);
|
||||
let counter2 = &AtomicI32::new(0);
|
||||
|
||||
let mut dp = FilterDispatcher::<Infallible, _>::new(|_| async {})
|
||||
.message_handler(true, |_mes: Message| async move {
|
||||
counter.fetch_add(1, Ordering::SeqCst);
|
||||
Ok::<_, Infallible>(())
|
||||
})
|
||||
.message_handler(true, |_mes: Message| async move {
|
||||
counter2.fetch_add(1, Ordering::SeqCst);
|
||||
Ok::<_, Infallible>(())
|
||||
});
|
||||
|
||||
dp.dispatch(one_message_updater()).await;
|
||||
|
||||
assert_eq!(counter.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(counter2.load(Ordering::SeqCst), 0);
|
||||
}
|
||||
|
||||
fn message() -> Message {
|
||||
Message {
|
||||
id: 6534,
|
||||
date: 1_567_898_953,
|
||||
chat: Chat {
|
||||
id: 218_485_655,
|
||||
photo: None,
|
||||
kind: ChatKind::Private {
|
||||
type_: (),
|
||||
first_name: Some("W".to_string()),
|
||||
last_name: None,
|
||||
username: Some("WaffleLapkin".to_string()),
|
||||
},
|
||||
},
|
||||
kind: MessageKind::Common {
|
||||
from: Sender::User(User {
|
||||
id: 457_569_668,
|
||||
is_bot: true,
|
||||
first_name: "BT".to_string(),
|
||||
last_name: None,
|
||||
username: Some("BloodyTestBot".to_string()),
|
||||
language_code: None,
|
||||
}),
|
||||
forward_kind: ForwardKind::Origin {
|
||||
reply_to_message: None,
|
||||
},
|
||||
edit_date: None,
|
||||
media_kind: MediaKind::Text {
|
||||
text: "text".to_string(),
|
||||
entities: vec![],
|
||||
},
|
||||
reply_markup: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn message_update() -> Update {
|
||||
Update {
|
||||
id: 0,
|
||||
kind: UpdateKind::Message(message()),
|
||||
}
|
||||
}
|
||||
|
||||
fn one_message_updater() -> impl Updater<Infallible> {
|
||||
use futures::{future::ready, stream};
|
||||
|
||||
stream::once(ready(Ok(message_update())))
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
pub mod filter;
|
|
@ -1,149 +0,0 @@
|
|||
//! Error handlers.
|
||||
|
||||
// Infallible used here instead of `!` to be compatible with rust <1.41.
|
||||
use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin};
|
||||
|
||||
/// An asynchronous handler of an error.
|
||||
pub trait ErrorHandler<E> {
|
||||
#[must_use]
|
||||
fn handle_error<'a>(
|
||||
&'a self,
|
||||
error: E,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
|
||||
where
|
||||
E: 'a;
|
||||
}
|
||||
|
||||
/// A handler that silently ignores all errors.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main_() {
|
||||
/// use teloxide::dispatching::error_handlers::{ErrorHandler, Ignore};
|
||||
///
|
||||
/// Ignore.handle_error(()).await;
|
||||
/// Ignore.handle_error(404).await;
|
||||
/// Ignore.handle_error(String::from("error")).await;
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct Ignore;
|
||||
|
||||
impl<E> ErrorHandler<E> for Ignore {
|
||||
#[must_use]
|
||||
fn handle_error<'a>(
|
||||
&'a self,
|
||||
_: E,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
|
||||
where
|
||||
E: 'a,
|
||||
{
|
||||
Box::pin(async {})
|
||||
}
|
||||
}
|
||||
|
||||
/// An error handler that silently ignores all errors that can never happen
|
||||
/// (e.g.: [`!`] or [`Infallible`]).
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main_() {
|
||||
/// use std::convert::{Infallible, TryInto};
|
||||
///
|
||||
/// use teloxide::dispatching::error_handlers::{ErrorHandler, IgnoreSafe};
|
||||
///
|
||||
/// let result: Result<String, Infallible> = "str".try_into();
|
||||
/// match result {
|
||||
/// Ok(string) => println!("{}", string),
|
||||
/// Err(inf) => IgnoreSafe.handle_error(inf).await,
|
||||
/// }
|
||||
///
|
||||
/// IgnoreSafe.handle_error(return).await; // return type of `return` is `!` (aka never)
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use teloxide::dispatching::dispatchers::filter::error_policy::{
|
||||
/// ErrorPolicy, IgnoreSafe,
|
||||
/// };
|
||||
///
|
||||
/// IgnoreSafe.handle_error(0);
|
||||
/// ```
|
||||
///
|
||||
/// [`!`]: https://doc.rust-lang.org/std/primitive.never.html
|
||||
/// [`Infallible`]: std::convert::Infallible
|
||||
pub struct IgnoreSafe;
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
impl ErrorHandler<Infallible> for IgnoreSafe {
|
||||
fn handle_error<'a>(
|
||||
&'a self,
|
||||
_: Infallible,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
|
||||
where
|
||||
Infallible: 'a,
|
||||
{
|
||||
Box::pin(async {})
|
||||
}
|
||||
}
|
||||
|
||||
/// An error handler that prints all errors passed into it.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main_() {
|
||||
/// use teloxide::dispatching::error_handlers::{ErrorHandler, Print};
|
||||
///
|
||||
/// Print.handle_error(()).await;
|
||||
/// Print.handle_error(404).await;
|
||||
/// Print.handle_error(String::from("error")).await;
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct Print;
|
||||
|
||||
impl<E> ErrorHandler<E> for Print
|
||||
where
|
||||
E: Debug,
|
||||
{
|
||||
fn handle_error<'a>(
|
||||
&'a self,
|
||||
error: E,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
|
||||
where
|
||||
E: 'a,
|
||||
{
|
||||
log::debug!("error: {:?}", error);
|
||||
Box::pin(async {})
|
||||
}
|
||||
}
|
||||
|
||||
/// The implementation of `ErrorHandler` for `Fn(error) -> Future<Output = ()>`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main_() {
|
||||
/// use teloxide::dispatching::error_handlers::ErrorHandler;
|
||||
///
|
||||
/// let mut closure = |e: i32| async move { eprintln!("Error code{}", e) };
|
||||
///
|
||||
/// closure.handle_error(404).await;
|
||||
/// # }
|
||||
/// ```
|
||||
impl<E, F, Fut> ErrorHandler<E> for F
|
||||
where
|
||||
F: Fn(E) -> Fut,
|
||||
Fut: Future<Output = ()>,
|
||||
{
|
||||
fn handle_error<'a>(
|
||||
&'a self,
|
||||
error: E,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
|
||||
where
|
||||
E: 'a,
|
||||
{
|
||||
Box::pin(async move { self(error).await })
|
||||
}
|
||||
}
|
|
@ -1,30 +1,49 @@
|
|||
use std::{future::Future, pin::Pin};
|
||||
|
||||
/// An asynchronous handler of a value.
|
||||
pub trait Handler<T, E> {
|
||||
/// Continue or terminate a user session.
|
||||
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
|
||||
pub enum SessionState<Session> {
|
||||
Continue(Session),
|
||||
Terminate,
|
||||
}
|
||||
|
||||
/// A handler of a user session and an update.
|
||||
///
|
||||
/// ## Returns
|
||||
/// Returns [`SessionState::Continue(session)`] if it wants to be called again
|
||||
/// after a new update, or [`SessionState::Terminate`] if not.
|
||||
///
|
||||
/// [`SessionState::Continue(session)`]:
|
||||
/// crate::dispatching::SessionState::Continue
|
||||
/// [`SessionState::Terminate`]: crate::dispatching::SessionState::Terminate
|
||||
pub trait Handler<Session, U> {
|
||||
#[must_use]
|
||||
fn handle<'a>(
|
||||
&'a self,
|
||||
value: T,
|
||||
) -> Pin<Box<dyn Future<Output = Result<(), E>> + 'a>>
|
||||
session: Session,
|
||||
update: U,
|
||||
) -> Pin<Box<dyn Future<Output = SessionState<Session>> + 'a>>
|
||||
where
|
||||
T: 'a;
|
||||
Session: 'a,
|
||||
U: 'a;
|
||||
}
|
||||
|
||||
/// The implementation of `Handler` for `Fn(U) -> Future<Output = Result<(),
|
||||
/// E>>`.
|
||||
impl<T, E, F, Fut> Handler<T, E> for F
|
||||
/// The implementation of `Handler` for `Fn(Session, Update) -> Future<Output =
|
||||
/// SessionState<Session>>`.
|
||||
impl<Session, U, F, Fut> Handler<Session, U> for F
|
||||
where
|
||||
F: Fn(T) -> Fut,
|
||||
Fut: Future<Output = Result<(), E>>,
|
||||
F: Fn(Session, U) -> Fut,
|
||||
Fut: Future<Output = SessionState<Session>>,
|
||||
{
|
||||
fn handle<'a>(
|
||||
&'a self,
|
||||
value: T,
|
||||
session: Session,
|
||||
update: U,
|
||||
) -> Pin<Box<dyn Future<Output = Fut::Output> + 'a>>
|
||||
where
|
||||
T: 'a,
|
||||
Session: 'a,
|
||||
U: 'a,
|
||||
{
|
||||
Box::pin(async move { self(value).await })
|
||||
Box::pin(async move { self(session, update).await })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
//! Update dispatching.
|
||||
|
||||
mod dispatchers;
|
||||
pub mod error_handlers;
|
||||
/// If an update was handled by a dispatcher or not.
|
||||
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
|
||||
pub enum DispatchResult {
|
||||
Handled,
|
||||
Unhandled,
|
||||
}
|
||||
|
||||
pub mod chat;
|
||||
pub mod filters;
|
||||
mod handler;
|
||||
pub mod updaters;
|
||||
pub mod update_listeners;
|
||||
|
||||
pub use dispatchers::filter::FilterDispatcher;
|
||||
pub use error_handlers::ErrorHandler;
|
||||
pub use filters::Filter;
|
||||
pub use handler::Handler;
|
||||
pub use updaters::Updater;
|
||||
pub use handler::*;
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
//! Receiving updates from Telegram.
|
||||
//!
|
||||
//! The key trait here is [`Updater`]. You can get it by these functions:
|
||||
//! The key trait here is [`UpdateListener`]. You can get it by these functions:
|
||||
//!
|
||||
//! - [`polling_default`], which returns a default long polling updater.
|
||||
//! - [`polling`], which returns a long/short polling updater with your
|
||||
//! - [`polling_default`], which returns a default long polling listener.
|
||||
//! - [`polling`], which returns a long/short polling listener with your
|
||||
//! configuration.
|
||||
//!
|
||||
//! And then you can pass it directly to a dispatcher.
|
||||
//! And then you can extract updates from it and pass them directly to a
|
||||
//! dispatcher.
|
||||
//!
|
||||
//! Telegram supports two ways of [getting updates]: [long]/[short] polling and
|
||||
//! [webhook].
|
||||
|
@ -90,10 +91,9 @@
|
|||
//! <a id="4" href="#4b">^4</a> `offset = N` means that we've already received
|
||||
//! updates `0..=N`.
|
||||
//!
|
||||
//! [`Updater`]: Updater
|
||||
//! [`UpdateListener`]: UpdateListener
|
||||
//! [`polling_default`]: polling_default
|
||||
//! [`polling`]: polling
|
||||
//! [`Dispatcher`]: crate::dispatching::Dispatcher::dispatch
|
||||
//! [`Box::get_updates`]: crate::Bot::get_updates
|
||||
//! [getting updates]: https://core.telegram.org/bots/api#getting-updates
|
||||
//! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling
|
||||
|
@ -110,22 +110,22 @@ use crate::{
|
|||
};
|
||||
use std::{convert::TryInto, time::Duration};
|
||||
|
||||
/// A generic updater.
|
||||
pub trait Updater<E>: Stream<Item = Result<Update, E>> {
|
||||
/// A generic update listener.
|
||||
pub trait UpdateListener<E>: Stream<Item = Result<Update, E>> {
|
||||
// TODO: add some methods here (.shutdown(), etc).
|
||||
}
|
||||
impl<S, E> Updater<E> for S where S: Stream<Item = Result<Update, E>> {}
|
||||
impl<S, E> UpdateListener<E> for S where S: Stream<Item = Result<Update, E>> {}
|
||||
|
||||
/// Returns a long polling updater with the default configuration.
|
||||
/// Returns a long polling update listener with the default configuration.
|
||||
///
|
||||
/// See also: [`polling`](polling).
|
||||
pub fn polling_default(bot: &Bot) -> impl Updater<RequestError> + '_ {
|
||||
pub fn polling_default(bot: &Bot) -> impl UpdateListener<RequestError> + '_ {
|
||||
polling(bot, None, None, None)
|
||||
}
|
||||
|
||||
/// Returns a long/short polling updater with some additional options.
|
||||
/// Returns a long/short polling update listener with some additional options.
|
||||
///
|
||||
/// - `bot`: Using this bot, the returned updater will receive updates.
|
||||
/// - `bot`: Using this bot, the returned update listener will receive updates.
|
||||
/// - `timeout`: A timeout for polling.
|
||||
/// - `limit`: Limits the number of updates to be retrieved at once. Values
|
||||
/// between 1—100 are accepted.
|
||||
|
@ -140,7 +140,7 @@ pub fn polling(
|
|||
timeout: Option<Duration>,
|
||||
limit: Option<u8>,
|
||||
allowed_updates: Option<Vec<AllowedUpdate>>,
|
||||
) -> impl Updater<RequestError> + '_ {
|
||||
) -> impl UpdateListener<RequestError> + '_ {
|
||||
let timeout =
|
||||
timeout.map(|t| t.as_secs().try_into().expect("timeout is too big"));
|
||||
|
|
@ -57,7 +57,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`EditMessageText`]
|
||||
///
|
||||
/// [`EditMessageText`]: crate::requests::payloads::EditMessageText
|
||||
/// [`EditMessageText`]: crate::requests::EditMessageText
|
||||
#[serde(rename = "Bad Request: message is not modified: specified new \
|
||||
message content and reply markup are exactly the same \
|
||||
as a current content and reply markup of the message")]
|
||||
|
@ -69,8 +69,8 @@ pub enum ApiErrorKind {
|
|||
/// 1. [`ForwardMessage`]
|
||||
/// 2. [`DeleteMessage`]
|
||||
///
|
||||
/// [`ForwardMessage`]: crate::requests::payloads::ForwardMessage
|
||||
/// [`DeleteMessage`]: crate::requests::payloads::DeleteMessage
|
||||
/// [`ForwardMessage`]: crate::requests::ForwardMessage
|
||||
/// [`DeleteMessage`]: crate::requests::DeleteMessage
|
||||
#[serde(rename = "Bad Request: MESSAGE_ID_INVALID")]
|
||||
MessageIdInvalid,
|
||||
|
||||
|
@ -79,7 +79,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`ForwardMessage`]
|
||||
///
|
||||
/// [`ForwardMessage`]: crate::requests::payloads::ForwardMessage
|
||||
/// [`ForwardMessage`]: crate::requests::ForwardMessage
|
||||
#[serde(rename = "Bad Request: message to forward not found")]
|
||||
MessageToForwardNotFound,
|
||||
|
||||
|
@ -88,7 +88,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`DeleteMessage`]
|
||||
///
|
||||
/// [`DeleteMessage`]: crate::requests::payloads::DeleteMessage
|
||||
/// [`DeleteMessage`]: crate::requests::DeleteMessage
|
||||
#[serde(rename = "Bad Request: message to delete not found")]
|
||||
MessageToDeleteNotFound,
|
||||
|
||||
|
@ -97,7 +97,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Bad Request: message text is empty")]
|
||||
MessageTextIsEmpty,
|
||||
|
||||
|
@ -106,7 +106,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`EditMessageText`]
|
||||
///
|
||||
/// [`EditMessageText`]: crate::requests::payloads::EditMessageText
|
||||
/// [`EditMessageText`]: crate::requests::EditMessageText
|
||||
#[serde(rename = "Bad Request: message can't be edited")]
|
||||
MessageCantBeEdited,
|
||||
|
||||
|
@ -116,7 +116,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`DeleteMessage`]
|
||||
///
|
||||
/// [`DeleteMessage`]: crate::requests::payloads::DeleteMessage
|
||||
/// [`DeleteMessage`]: crate::requests::DeleteMessage
|
||||
#[serde(rename = "Bad Request: message can't be deleted")]
|
||||
MessageCantBeDeleted,
|
||||
|
||||
|
@ -125,7 +125,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`EditMessageText`]
|
||||
///
|
||||
/// [`EditMessageText`]: crate::requests::payloads::EditMessageText
|
||||
/// [`EditMessageText`]: crate::requests::EditMessageText
|
||||
#[serde(rename = "Bad Request: message to edit not found")]
|
||||
MessageToEditNotFound,
|
||||
|
||||
|
@ -134,7 +134,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Bad Request: reply message not found")]
|
||||
MessageToReplyNotFound,
|
||||
|
||||
|
@ -148,7 +148,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Bad Request: message is too long")]
|
||||
MessageIsTooLong,
|
||||
|
||||
|
@ -157,7 +157,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMediaGroup`]
|
||||
///
|
||||
/// [`SendMediaGroup`]: crate::requests::payloads::SendMediaGroup
|
||||
/// [`SendMediaGroup`]: crate::requests::SendMediaGroup
|
||||
#[serde(rename = "Bad Request: Too much messages to send as an album")]
|
||||
ToMuchMessages,
|
||||
|
||||
|
@ -166,7 +166,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendPoll`]
|
||||
///
|
||||
/// [`SendPoll`]: crate::requests::payloads::SendPoll
|
||||
/// [`SendPoll`]: crate::requests::SendPoll
|
||||
#[serde(rename = "Bad Request: poll has already been closed")]
|
||||
PollHasAlreadyClosed,
|
||||
|
||||
|
@ -175,7 +175,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendPoll`]
|
||||
///
|
||||
/// [`SendPoll`]: crate::requests::payloads::SendPoll
|
||||
/// [`SendPoll`]: crate::requests::SendPoll
|
||||
#[serde(rename = "Bad Request: poll must have at least 2 option")]
|
||||
PollMustHaveMoreOptions,
|
||||
|
||||
|
@ -184,7 +184,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendPoll`]
|
||||
///
|
||||
/// [`SendPoll`]: crate::requests::payloads::SendPoll
|
||||
/// [`SendPoll`]: crate::requests::SendPoll
|
||||
#[serde(rename = "Bad Request: poll can't have more than 10 options")]
|
||||
PollCantHaveMoreOptions,
|
||||
|
||||
|
@ -193,7 +193,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendPoll`]
|
||||
///
|
||||
/// [`SendPoll`]: crate::requests::payloads::SendPoll
|
||||
/// [`SendPoll`]: crate::requests::SendPoll
|
||||
#[serde(rename = "Bad Request: poll options must be non-empty")]
|
||||
PollOptionsMustBeNonEmpty,
|
||||
|
||||
|
@ -202,7 +202,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendPoll`]
|
||||
///
|
||||
/// [`SendPoll`]: crate::requests::payloads::SendPoll
|
||||
/// [`SendPoll`]: crate::requests::SendPoll
|
||||
#[serde(rename = "Bad Request: poll question must be non-empty")]
|
||||
PollQuestionMustBeNonEmpty,
|
||||
|
||||
|
@ -212,7 +212,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendPoll`]
|
||||
///
|
||||
/// [`SendPoll`]: crate::requests::payloads::SendPoll
|
||||
/// [`SendPoll`]: crate::requests::SendPoll
|
||||
#[serde(rename = "Bad Request: poll options length must not exceed 100")]
|
||||
PollOptionsLengthTooLong,
|
||||
|
||||
|
@ -222,7 +222,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendPoll`]
|
||||
///
|
||||
/// [`SendPoll`]: crate::requests::payloads::SendPoll
|
||||
/// [`SendPoll`]: crate::requests::SendPoll
|
||||
#[serde(rename = "Bad Request: poll question length must not exceed 255")]
|
||||
PollQuestionLengthTooLong,
|
||||
|
||||
|
@ -231,7 +231,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`StopPoll`]
|
||||
///
|
||||
/// [`StopPoll`]: crate::requests::payloads::StopPoll
|
||||
/// [`StopPoll`]: crate::requests::StopPoll
|
||||
#[serde(rename = "Bad Request: message with poll to stop not found")]
|
||||
MessageWithPollNotFound,
|
||||
|
||||
|
@ -240,7 +240,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`StopPoll`]
|
||||
///
|
||||
/// [`StopPoll`]: crate::requests::payloads::StopPoll
|
||||
/// [`StopPoll`]: crate::requests::StopPoll
|
||||
#[serde(rename = "Bad Request: message is not a poll")]
|
||||
MessageIsNotAPoll,
|
||||
|
||||
|
@ -250,7 +250,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Bad Request: chat not found")]
|
||||
ChatNotFound,
|
||||
|
||||
|
@ -260,7 +260,7 @@ pub enum ApiErrorKind {
|
|||
/// 1. [`getUserProfilePhotos`]
|
||||
///
|
||||
/// [`getUserProfilePhotos`]:
|
||||
/// crate::requests::payloads::getUserProfilePhotos
|
||||
/// crate::requests::GetUserProfilePhotos
|
||||
#[serde(rename = "Bad Request: user not found")]
|
||||
UserNotFound,
|
||||
|
||||
|
@ -270,7 +270,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SetChatDescription`]
|
||||
///
|
||||
/// [`SetChatDescription`]: crate::requests::payloads::SetChatDescription
|
||||
/// [`SetChatDescription`]: crate::requests::SetChatDescription
|
||||
#[serde(rename = "Bad Request: chat description is not modified")]
|
||||
ChatDescriptionIsNotModified,
|
||||
|
||||
|
@ -279,7 +279,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`AnswerCallbackQuery`]
|
||||
///
|
||||
/// [`AnswerCallbackQuery`]: crate::requests::payloads::AnswerCallbackQuery
|
||||
/// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery
|
||||
#[serde(rename = "Bad Request: query is too old and response timeout \
|
||||
expired or query id is invalid")]
|
||||
InvalidQueryID,
|
||||
|
@ -290,7 +290,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Bad Request: BUTTON_URL_INVALID")]
|
||||
ButtonURLInvalid,
|
||||
|
||||
|
@ -299,7 +299,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Bad Request: BUTTON_DATA_INVALID")]
|
||||
ButtonDataInvalid,
|
||||
|
||||
|
@ -308,7 +308,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Bad Request: can't parse inline keyboard button: Text \
|
||||
buttons are unallowed in the inline keyboard")]
|
||||
TextButtonsAreUnallowed,
|
||||
|
@ -318,7 +318,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`GetFile`]
|
||||
///
|
||||
/// [`GetFile`]: crate::requests::payloads::GetFile
|
||||
/// [`GetFile`]: crate::requests::GetFile
|
||||
#[serde(rename = "Bad Request: wrong file id")]
|
||||
WrongFileID,
|
||||
|
||||
|
@ -331,7 +331,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SetChatPhoto`]
|
||||
///
|
||||
/// [`SetChatPhoto`]: crate::requests::payloads::SetChatPhoto
|
||||
/// [`SetChatPhoto`]: crate::requests::SetChatPhoto
|
||||
#[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")]
|
||||
PhotoAsInputFileRequired,
|
||||
|
||||
|
@ -340,7 +340,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`AddStickerToSet`]
|
||||
///
|
||||
/// [`AddStickerToSet`]: crate::requests::payloads::AddStickerToSet
|
||||
/// [`AddStickerToSet`]: crate::requests::AddStickerToSet
|
||||
#[serde(rename = "Bad Request: STICKERSET_INVALID")]
|
||||
InvalidStickersSet,
|
||||
|
||||
|
@ -350,7 +350,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`PinMessage`]
|
||||
///
|
||||
/// [`PinMessage`]: crate::requests::payloads::PinMessage
|
||||
/// [`PinMessage`]: crate::requests::PinMessage
|
||||
#[serde(rename = "Bad Request: not enough rights to pin a message")]
|
||||
NotEnoughRightsToPinMessage,
|
||||
|
||||
|
@ -365,7 +365,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`PromoteChatMember`]
|
||||
///
|
||||
/// [`PromoteChatMember`]: crate::requests::payloads::PromoteChatMember
|
||||
/// [`PromoteChatMember`]: crate::requests::PromoteChatMember
|
||||
#[serde(rename = "Bad Request: can't demote chat creator")]
|
||||
CantDemoteChatCreator,
|
||||
|
||||
|
@ -374,7 +374,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`RestrictChatMember`]
|
||||
///
|
||||
/// [`RestrictChatMember`]: crate::requests::payloads::RestrictChatMember
|
||||
/// [`RestrictChatMember`]: crate::requests::RestrictChatMember
|
||||
#[serde(rename = "Bad Request: can't restrict self")]
|
||||
CantRestrictSelf,
|
||||
|
||||
|
@ -384,7 +384,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`RestrictChatMember`]
|
||||
///
|
||||
/// [`RestrictChatMember`]: crate::requests::payloads::RestrictChatMember
|
||||
/// [`RestrictChatMember`]: crate::requests::RestrictChatMember
|
||||
#[serde(rename = "Bad Request: not enough rights to restrict/unrestrict \
|
||||
chat member")]
|
||||
NotEnoughRightsToRestrict,
|
||||
|
@ -394,7 +394,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SetWebhook`]
|
||||
///
|
||||
/// [`SetWebhook`]: crate::requests::payloads::SetWebhook
|
||||
/// [`SetWebhook`]: crate::requests::SetWebhook
|
||||
#[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided \
|
||||
for webhook")]
|
||||
WebhookRequireHTTPS,
|
||||
|
@ -405,7 +405,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SetWebhook`]
|
||||
///
|
||||
/// [`SetWebhook`]: crate::requests::payloads::SetWebhook
|
||||
/// [`SetWebhook`]: crate::requests::SetWebhook
|
||||
#[serde(rename = "Bad Request: bad webhook: Webhook can be set up only \
|
||||
on ports 80, 88, 443 or 8443")]
|
||||
BadWebhookPort,
|
||||
|
@ -415,7 +415,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SetWebhook`]
|
||||
///
|
||||
/// [`SetWebhook`]: crate::requests::payloads::SetWebhook
|
||||
/// [`SetWebhook`]: crate::requests::SetWebhook
|
||||
#[serde(rename = "Bad Request: bad webhook: Failed to resolve host: \
|
||||
Name or service not known")]
|
||||
UnknownHost,
|
||||
|
@ -425,7 +425,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SetWebhook`]
|
||||
///
|
||||
/// [`SetWebhook`]: crate::requests::payloads::SetWebhook
|
||||
/// [`SetWebhook`]: crate::requests::SetWebhook
|
||||
#[serde(rename = "Bad Request: can't parse URL")]
|
||||
CantParseUrl,
|
||||
|
||||
|
@ -434,7 +434,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Bad Request: can't parse entities")]
|
||||
CantParseEntities,
|
||||
|
||||
|
@ -443,7 +443,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`GetUpdates`]
|
||||
///
|
||||
/// [`GetUpdates`]: crate::requests::payloads::GetUpdates
|
||||
/// [`GetUpdates`]: crate::requests::GetUpdates
|
||||
#[serde(rename = "can't use getUpdates method while webhook is active")]
|
||||
CantGetUpdates,
|
||||
|
||||
|
@ -452,7 +452,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Unauthorized: bot was kicked from a chat")]
|
||||
BotKicked,
|
||||
|
||||
|
@ -461,7 +461,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Unauthorized: user is deactivated")]
|
||||
UserDeactivated,
|
||||
|
||||
|
@ -470,7 +470,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(
|
||||
rename = "Unauthorized: bot can't initiate conversation with a user"
|
||||
)]
|
||||
|
@ -481,7 +481,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Unauthorized: bot can't send messages to bots")]
|
||||
CantTalkWithBots,
|
||||
|
||||
|
@ -490,7 +490,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`SendMessage`]
|
||||
///
|
||||
/// [`SendMessage`]: crate::requests::payloads::SendMessage
|
||||
/// [`SendMessage`]: crate::requests::SendMessage
|
||||
#[serde(rename = "Bad Request: wrong HTTP URL")]
|
||||
WrongHTTPurl,
|
||||
|
||||
|
@ -500,7 +500,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`GetUpdates`]
|
||||
///
|
||||
/// [`GetUpdates`]: crate::requests::payloads::GetUpdates
|
||||
/// [`GetUpdates`]: crate::requests::GetUpdates
|
||||
#[serde(rename = "Conflict: terminated by other getUpdates request; \
|
||||
make sure that only one bot instance is running")]
|
||||
TerminatedByOtherGetUpdates,
|
||||
|
@ -510,7 +510,7 @@ pub enum ApiErrorKind {
|
|||
/// May happen in methods:
|
||||
/// 1. [`GetFile`]
|
||||
///
|
||||
/// [`GetFile`]: crate::requests::payloads::GetFile
|
||||
/// [`GetFile`]: crate::requests::GetFile
|
||||
#[serde(rename = "Bad Request: invalid file id")]
|
||||
FileIdInvalid,
|
||||
|
||||
|
|
|
@ -39,13 +39,11 @@ pub async fn download_file_stream(
|
|||
.await?
|
||||
.error_for_status()?;
|
||||
|
||||
Ok(futures::stream::unfold(res, |mut res| {
|
||||
async {
|
||||
match res.chunk().await {
|
||||
Err(err) => Some((Err(err), res)),
|
||||
Ok(Some(c)) => Some((Ok(c), res)),
|
||||
Ok(None) => None,
|
||||
}
|
||||
Ok(futures::stream::unfold(res, |mut res| async {
|
||||
match res.chunk().await {
|
||||
Err(err) => Some((Err(err), res)),
|
||||
Ok(Some(c)) => Some((Ok(c), res)),
|
||||
Ok(None) => None,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::User;
|
||||
use crate::types::{Message, User};
|
||||
|
||||
/// This object represents one special entity in a text message. For example,
|
||||
/// hashtags, usernames, URLs, etc.
|
||||
|
@ -39,21 +39,85 @@ pub enum MessageEntityKind {
|
|||
Strikethrough,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_kind() {
|
||||
use serde_json::from_str;
|
||||
|
||||
assert_eq!(
|
||||
MessageEntity {
|
||||
kind: MessageEntityKind::TextLink {
|
||||
url: "ya.ru".into()
|
||||
},
|
||||
offset: 1,
|
||||
length: 2,
|
||||
},
|
||||
from_str::<MessageEntity>(
|
||||
r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"#
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
impl MessageEntity {
|
||||
pub fn text_from(&self, message: &Message) -> Option<String> {
|
||||
let text = message.text();
|
||||
Some(String::from(&text?[self.offset..self.offset + self.length]))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::{
|
||||
Chat, ChatKind, ForwardKind, MediaKind, MessageKind, Sender,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn recursive_kind() {
|
||||
use serde_json::from_str;
|
||||
|
||||
assert_eq!(
|
||||
MessageEntity {
|
||||
kind: MessageEntityKind::TextLink {
|
||||
url: "ya.ru".into()
|
||||
},
|
||||
offset: 1,
|
||||
length: 2,
|
||||
},
|
||||
from_str::<MessageEntity>(
|
||||
r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"#
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_from() {
|
||||
let message = message();
|
||||
let expected = Some("yes".to_string());
|
||||
let entity = message.entities().unwrap()[0].clone();
|
||||
let actual = entity.text_from(&message);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
fn message() -> Message {
|
||||
Message {
|
||||
id: 0,
|
||||
date: 0,
|
||||
chat: Chat {
|
||||
id: 0,
|
||||
kind: ChatKind::Private {
|
||||
type_: (),
|
||||
username: None,
|
||||
first_name: None,
|
||||
last_name: None,
|
||||
},
|
||||
photo: None,
|
||||
},
|
||||
kind: MessageKind::Common {
|
||||
from: Sender::User(User {
|
||||
id: 0,
|
||||
is_bot: false,
|
||||
first_name: "".to_string(),
|
||||
last_name: None,
|
||||
username: None,
|
||||
language_code: None,
|
||||
}),
|
||||
forward_kind: ForwardKind::Origin {
|
||||
reply_to_message: None,
|
||||
},
|
||||
edit_date: None,
|
||||
media_kind: MediaKind::Text {
|
||||
text: "no yes no".to_string(),
|
||||
entities: vec![MessageEntity {
|
||||
kind: MessageEntityKind::Mention,
|
||||
offset: 3,
|
||||
length: 3,
|
||||
}],
|
||||
},
|
||||
reply_markup: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::types::{CallbackQuery, ChosenInlineResult, InlineQuery, Message};
|
||||
use crate::types::{
|
||||
CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll,
|
||||
PreCheckoutQuery, ShippingQuery,
|
||||
};
|
||||
|
||||
/// This [object] represents an incoming update.
|
||||
///
|
||||
|
@ -57,7 +60,17 @@ pub enum UpdateKind {
|
|||
|
||||
/// New incoming callback query.
|
||||
CallbackQuery(CallbackQuery),
|
||||
// TODO: Add more variants
|
||||
|
||||
/// New incoming shipping query. Only for invoices with flexible price.
|
||||
ShippingQuery(ShippingQuery),
|
||||
|
||||
/// New incoming pre-checkout query. Contains full information about
|
||||
/// checkout.
|
||||
PreCheckoutQuery(PreCheckoutQuery),
|
||||
|
||||
/// New poll state. Bots receive only updates about stopped polls and
|
||||
/// polls, which are sent by the bot.
|
||||
Poll(Poll),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -92,7 +105,7 @@ mod test {
|
|||
}
|
||||
}"#;
|
||||
|
||||
let expected: Update = Update {
|
||||
let expected = Update {
|
||||
id: 892_252_934,
|
||||
kind: UpdateKind::Message(Message {
|
||||
id: 6557,
|
||||
|
|
Loading…
Reference in a new issue