Merge branch 'master' into redis

This commit is contained in:
Temirkhan Myrzamadi 2020-06-26 18:42:17 +06:00 committed by GitHub
commit 37a0ac8ef8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 61 additions and 114 deletions

View file

@ -30,9 +30,6 @@
- [Dialogues](https://github.com/teloxide/teloxide#dialogues) - [Dialogues](https://github.com/teloxide/teloxide#dialogues)
- [Recommendations](https://github.com/teloxide/teloxide#recommendations) - [Recommendations](https://github.com/teloxide/teloxide#recommendations)
- [FAQ](https://github.com/teloxide/teloxide#faq) - [FAQ](https://github.com/teloxide/teloxide#faq)
- [Where I can ask questions?](https://github.com/teloxide/teloxide#where-i-can-ask-questions)
- [Why Rust?](https://github.com/teloxide/teloxide#why-rust)
- [Can I use different loggers?](https://github.com/teloxide/teloxide#can-i-use-different-loggers)
- [Community bots](https://github.com/teloxide/teloxide#community-bots) - [Community bots](https://github.com/teloxide/teloxide#community-bots)
- [Contributing](https://github.com/teloxide/teloxide#contributing) - [Contributing](https://github.com/teloxide/teloxide#contributing)
@ -225,11 +222,6 @@ pub type Dialogue = Coprod!(
ReceiveAgeState, ReceiveAgeState,
ReceiveFavouriteMusicState, ReceiveFavouriteMusicState,
); );
wrap_dialogue!(
Wrapper(Dialogue),
default Self(Dialogue::inject(StartState)),
);
``` ```
The [`wrap_dialogue!`](https://docs.rs/teloxide/latest/teloxide/macro.wrap_dialogue.html) macro generates a new-type of `Dialogue` with a default implementation. The [`wrap_dialogue!`](https://docs.rs/teloxide/latest/teloxide/macro.wrap_dialogue.html) macro generates a new-type of `Dialogue` with a default implementation.
@ -348,7 +340,7 @@ async fn main() {
[start, receive_full_name, receive_age, receive_favourite_music] [start, receive_full_name, receive_age, receive_favourite_music]
) )
.expect("Something wrong with the bot!") .expect("Something wrong with the bot!")
})) }, || Dialogue::inject(StartState)))
.dispatch() .dispatch()
.await; .await;
} }
@ -382,24 +374,32 @@ async fn main() {
The second one produces very strange compiler messages because of the `#[tokio::main]` macro. However, the examples in this README use the second variant for brevity. The second one produces very strange compiler messages because of the `#[tokio::main]` macro. However, the examples in this README use the second variant for brevity.
## FAQ ## FAQ
### Where I can ask questions? Q: Where I can ask questions?
[Issues](https://github.com/teloxide/teloxide/issues) is a good place for well-formed questions, for example, about the library design, enhancements, bug reports. But if you can't compile your bot due to compilation errors and need quick help, feel free to ask in [our official group](https://t.me/teloxide).
### Why Rust? A: [Issues](https://github.com/teloxide/teloxide/issues) is a good place for well-formed questions, for example, about the library design, enhancements, bug reports. But if you can't compile your bot due to compilation errors and need quick help, feel free to ask in [our official group](https://t.me/teloxide).
Most programming languages have their own implementations of Telegram bots frameworks, so why not Rust? We think Rust provides enough good ecosystem and the language itself to be suitable for writing bots.
### Can I use webhooks? Q: Why Rust?
teloxide doesn't provide special API for working with webhooks due to their nature with lots of subtle settings. Instead, you setup your webhook by yourself, as shown in [webhook_ping_pong_bot](examples/ngrok_ping_pong_bot/src/main.rs).
A: Most programming languages have their own implementations of Telegram bots frameworks, so why not Rust? We think Rust provides enough good ecosystem and the language itself to be suitable for writing bots.
Q: Can I use webhooks?
A: teloxide doesn't provide special API for working with webhooks due to their nature with lots of subtle settings. Instead, you setup your webhook by yourself, as shown in [webhook_ping_pong_bot](examples/ngrok_ping_pong_bot/src/main.rs).
Associated links: Associated links:
- [Marvin's Marvellous Guide to All Things Webhook](https://core.telegram.org/bots/webhooks) - [Marvin's Marvellous Guide to All Things Webhook](https://core.telegram.org/bots/webhooks)
- [Using self-signed certificates](https://core.telegram.org/bots/self-signed) - [Using self-signed certificates](https://core.telegram.org/bots/self-signed)
### Can I use different loggers? Q: Can I use different loggers?
Of course, you can. The [`enable_logging!`](https://docs.rs/teloxide/latest/teloxide/macro.enable_logging.html) and [`enable_logging_with_filter!`](https://docs.rs/teloxide/latest/teloxide/macro.enable_logging_with_filter.html) macros are just convenient utilities, not necessary to use them. You can setup a different logger, for example, [fern](https://crates.io/crates/fern), as usual, e.g. teloxide has no specific requirements as it depends only on [log](https://crates.io/crates/log).
A: Of course, you can. The [`enable_logging!`](https://docs.rs/teloxide/latest/teloxide/macro.enable_logging.html) and [`enable_logging_with_filter!`](https://docs.rs/teloxide/latest/teloxide/macro.enable_logging_with_filter.html) macros are just convenient utilities, not necessary to use them. You can setup a different logger, for example, [fern](https://crates.io/crates/fern), as usual, e.g. teloxide has no specific requirements as it depends only on [log](https://crates.io/crates/log).
## Community bots ## Community bots
Feel free to push your own bot into our collection: https://github.com/teloxide/community-bots. Later you will be able to play with them right in our official chat: https://t.me/teloxide. Feel free to push your own bot into our collection!
- [Rust subreddit reader](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/subreddit_reader)
- [with_webserver - An example of the teloxide + warp combination](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/with_webserver)
- [vzmuinebot - Telegram bot for food menu navigate](https://github.com/ArtHome12/vzmuinebot)
## Contributing ## Contributing
See [CONRIBUTING.md](https://github.com/teloxide/teloxide/blob/master/CONTRIBUTING.md). See [CONRIBUTING.md](https://github.com/teloxide/teloxide/blob/master/CONTRIBUTING.md).

View file

@ -38,18 +38,21 @@ async fn run() {
let bot = Bot::from_env(); let bot = Bot::from_env();
Dispatcher::new(bot) Dispatcher::new(bot)
.messages_handler(DialogueDispatcher::new(|cx| async move { .messages_handler(DialogueDispatcher::new(
|cx| async move {
let DialogueWithCx { cx, dialogue } = cx; let DialogueWithCx { cx, dialogue } = cx;
// Unwrap without panic because of std::convert::Infallible. // Unwrap without panic because of std::convert::Infallible.
let Wrapper(dialogue) = dialogue.unwrap(); let dialogue = dialogue.unwrap();
dispatch!( dispatch!(
[cx, dialogue] -> [cx, dialogue] ->
[start, receive_full_name, receive_age, receive_favourite_music] [start, receive_full_name, receive_age, receive_favourite_music]
) )
.expect("Something wrong with the bot!") .expect("Something wrong with the bot!")
})) },
|| Dialogue::inject(StartState),
))
.dispatch() .dispatch()
.await; .await;
} }

View file

@ -42,8 +42,3 @@ pub type Dialogue = Coprod!(
ReceiveAgeState, ReceiveAgeState,
ReceiveFavouriteMusicState, ReceiveFavouriteMusicState,
); );
wrap_dialogue!(
Wrapper(Dialogue),
default Self(Dialogue::inject(StartState)),
);

View file

@ -3,7 +3,7 @@ use teloxide::prelude::*;
use super::{favourite_music::FavouriteMusic, states::*}; use super::{favourite_music::FavouriteMusic, states::*};
pub type In<State> = TransitionIn<State, std::convert::Infallible>; pub type In<State> = TransitionIn<State, std::convert::Infallible>;
pub type Out = TransitionOut<Wrapper>; pub type Out = TransitionOut<Dialogue>;
pub async fn start(cx: In<StartState>) -> Out { pub async fn start(cx: In<StartState>) -> Out {
let (cx, dialogue) = cx.unpack(); let (cx, dialogue) = cx.unpack();

View file

@ -26,6 +26,7 @@ use std::sync::{Arc, Mutex};
pub struct DialogueDispatcher<D, S, H, Upd> { pub struct DialogueDispatcher<D, S, H, Upd> {
storage: Arc<S>, storage: Arc<S>,
handler: Arc<H>, handler: Arc<H>,
default: Arc<dyn Fn() -> D + Send + Sync + 'static>,
_phantom: PhantomData<Mutex<D>>, _phantom: PhantomData<Mutex<D>>,
/// A lock-free map to handle updates from the same chat sequentially, but /// A lock-free map to handle updates from the same chat sequentially, but
@ -41,17 +42,22 @@ impl<D, H, Upd> DialogueDispatcher<D, InMemStorage<D>, H, Upd>
where where
H: DialogueDispatcherHandler<Upd, D, Infallible> + Send + Sync + 'static, H: DialogueDispatcherHandler<Upd, D, Infallible> + Send + Sync + 'static,
Upd: GetChatId + Send + 'static, Upd: GetChatId + Send + 'static,
D: Default + Send + 'static, D: Send + 'static,
{ {
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`] /// Creates a dispatcher with the specified `handler, [`InMemStorage`]
/// (a default storage). /// (a default storage), and a function that returns a default dialogue,
/// used when a user initiates a new dialogue.
/// ///
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
#[must_use] #[must_use]
pub fn new(handler: H) -> Self { pub fn new<F>(handler: H, default: F) -> Self
where
F: Fn() -> D + Send + Sync + 'static,
{
Self { Self {
storage: InMemStorage::new(), storage: InMemStorage::new(),
handler: Arc::new(handler), handler: Arc::new(handler),
default: Arc::new(default),
senders: Arc::new(Map::new()), senders: Arc::new(Map::new()),
_phantom: PhantomData, _phantom: PhantomData,
} }
@ -62,16 +68,22 @@ impl<D, S, H, Upd> DialogueDispatcher<D, S, H, Upd>
where where
H: DialogueDispatcherHandler<Upd, D, S::Error> + Send + Sync + 'static, H: DialogueDispatcherHandler<Upd, D, S::Error> + Send + Sync + 'static,
Upd: GetChatId + Send + 'static, Upd: GetChatId + Send + 'static,
D: Default + Send + 'static, D: Send + 'static,
S: Storage<D> + Send + Sync + 'static, S: Storage<D> + Send + Sync + 'static,
S::Error: Send + 'static, S::Error: Send + 'static,
{ {
/// Creates a dispatcher with the specified `handler` and `storage`. /// Creates a dispatcher with the specified `handler`, `storage`, and a
/// function that returns a default dialogue, used when a user initiates a
/// new dialogue.
#[must_use] #[must_use]
pub fn with_storage(handler: H, storage: Arc<S>) -> Self { pub fn with_storage<F>(handler: H, storage: Arc<S>, default: F) -> Self
where
F: Fn() -> D + Send + Sync + 'static,
{
Self { Self {
storage, storage,
handler: Arc::new(handler), handler: Arc::new(handler),
default: Arc::new(default),
senders: Arc::new(Map::new()), senders: Arc::new(Map::new()),
_phantom: PhantomData, _phantom: PhantomData,
} }
@ -83,11 +95,13 @@ where
let storage = Arc::clone(&self.storage); let storage = Arc::clone(&self.storage);
let handler = Arc::clone(&self.handler); let handler = Arc::clone(&self.handler);
let default = Arc::clone(&self.default);
let senders = Arc::clone(&self.senders); let senders = Arc::clone(&self.senders);
tokio::spawn(rx.for_each(move |cx: UpdateWithCx<Upd>| { tokio::spawn(rx.for_each(move |cx: UpdateWithCx<Upd>| {
let storage = Arc::clone(&storage); let storage = Arc::clone(&storage);
let handler = Arc::clone(&handler); let handler = Arc::clone(&handler);
let default = Arc::clone(&default);
let senders = Arc::clone(&senders); let senders = Arc::clone(&senders);
async move { async move {
@ -96,7 +110,7 @@ where
let dialogue = Arc::clone(&storage) let dialogue = Arc::clone(&storage)
.remove_dialogue(chat_id) .remove_dialogue(chat_id)
.await .await
.map(Option::unwrap_or_default); .map(|opt| opt.unwrap_or_else(move || default()));
match handler.handle(DialogueWithCx { cx, dialogue }).await { match handler.handle(DialogueWithCx { cx, dialogue }).await {
DialogueStage::Next(new_dialogue) => { DialogueStage::Next(new_dialogue) => {
@ -131,7 +145,7 @@ impl<D, S, H, Upd> DispatcherHandler<Upd> for DialogueDispatcher<D, S, H, Upd>
where where
H: DialogueDispatcherHandler<Upd, D, S::Error> + Send + Sync + 'static, H: DialogueDispatcherHandler<Upd, D, S::Error> + Send + Sync + 'static,
Upd: GetChatId + Send + 'static, Upd: GetChatId + Send + 'static,
D: Default + Send + 'static, D: Send + 'static,
S: Storage<D> + Send + Sync + 'static, S: Storage<D> + Send + Sync + 'static,
S::Error: Send + 'static, S::Error: Send + 'static,
{ {
@ -189,7 +203,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn updates_from_same_chat_executed_sequentially() { async fn updates_from_same_chat_executed_sequentially() {
#[derive(Debug)] #[derive(Debug, Default)]
struct MyUpdate { struct MyUpdate {
chat_id: i64, chat_id: i64,
unique_number: u32, unique_number: u32,
@ -232,6 +246,7 @@ mod tests {
DialogueStage::Next(()) DialogueStage::Next(())
}, },
|| (),
); );
let updates = stream::iter( let updates = stream::iter(

View file

@ -11,29 +11,21 @@ pub enum DialogueStage<D> {
Exit, Exit,
} }
/// A dialogue wrapper to bypass orphan rules.
pub trait DialogueWrapper<D> {
fn new(dialogue: D) -> Self;
}
/// Returns a new dialogue state. /// Returns a new dialogue state.
/// ///
/// See [the module-level documentation for the design /// See [the module-level documentation for the design
/// overview](crate::dispatching::dialogue). /// overview](crate::dispatching::dialogue).
pub fn next<Dialogue, State, Index, DWrapper>( pub fn next<Dialogue, State, Index>(new_state: State) -> TransitionOut<Dialogue>
new_state: State,
) -> TransitionOut<DWrapper>
where where
Dialogue: CoprodInjector<State, Index>, Dialogue: CoprodInjector<State, Index>,
DWrapper: DialogueWrapper<Dialogue>,
{ {
Ok(DialogueStage::Next(DWrapper::new(Dialogue::inject(new_state)))) Ok(DialogueStage::Next(Dialogue::inject(new_state)))
} }
/// Exits a dialogue. /// Exits a dialogue.
/// ///
/// See [the module-level documentation for the design /// See [the module-level documentation for the design
/// overview](crate::dispatching::dialogue). /// overview](crate::dispatching::dialogue).
pub fn exit<DWrapper>() -> TransitionOut<DWrapper> { pub fn exit<D>() -> TransitionOut<D> {
Ok(DialogueStage::Exit) Ok(DialogueStage::Exit)
} }

View file

@ -52,7 +52,7 @@ mod storage;
use crate::{requests::ResponseResult, types::Message}; use crate::{requests::ResponseResult, types::Message};
pub use dialogue_dispatcher::DialogueDispatcher; pub use dialogue_dispatcher::DialogueDispatcher;
pub use dialogue_dispatcher_handler::DialogueDispatcherHandler; pub use dialogue_dispatcher_handler::DialogueDispatcherHandler;
pub use dialogue_stage::{exit, next, DialogueStage, DialogueWrapper}; pub use dialogue_stage::{exit, next, DialogueStage};
pub use dialogue_with_cx::DialogueWithCx; pub use dialogue_with_cx::DialogueWithCx;
pub use get_chat_id::GetChatId; pub use get_chat_id::GetChatId;
@ -79,13 +79,8 @@ pub use storage::{InMemStorage, Storage};
/// ReceiveNumberState, /// ReceiveNumberState,
/// ); /// );
/// ///
/// wrap_dialogue!(
/// Wrapper(Dialogue),
/// default Self(Dialogue::inject(StartState)),
/// );
///
/// pub type In<State> = TransitionIn<State, std::convert::Infallible>; /// pub type In<State> = TransitionIn<State, std::convert::Infallible>;
/// pub type Out = TransitionOut<Wrapper>; /// pub type Out = TransitionOut<Dialogue>;
/// ///
/// pub async fn start(cx: In<StartState>) -> Out { todo!() } /// pub async fn start(cx: In<StartState>) -> Out { todo!() }
/// pub async fn receive_word(cx: In<ReceiveWordState>) -> Out { todo!() } /// pub async fn receive_word(cx: In<ReceiveWordState>) -> Out { todo!() }
@ -126,59 +121,6 @@ macro_rules! dispatch {
}; };
} }
/// Generates a dialogue wrapper and implements `Default` for it.
///
/// The reason is to bypass orphan rules to be able to pass a user-defined
/// dialogue into [`DialogueDispatcher`]. Since a dialogue is
/// [`frunk::Coproduct`], we cannot directly satisfy the `D: Default`
/// constraint.
///
/// # Examples
/// ```
/// use teloxide::prelude::*;
///
/// struct StartState;
/// struct ReceiveWordState;
/// struct ReceiveNumberState;
/// struct ExitState;
///
/// type Dialogue = Coprod!(
/// StartState,
/// ReceiveWordState,
/// ReceiveNumberState,
/// );
///
/// wrap_dialogue!(
/// Wrapper(Dialogue),
/// default Self(Dialogue::inject(StartState)),
/// );
///
/// let start_state = Wrapper::default();
/// ```
///
/// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher
/// [`frunk::Coproduct`]: https://docs.rs/frunk/0.3.1/frunk/coproduct/enum.Coproduct.html
#[macro_export]
macro_rules! wrap_dialogue {
($name:ident($dialogue:ident), default $default_block:expr, ) => {
pub struct $name(pub $dialogue);
impl teloxide::dispatching::dialogue::DialogueWrapper<$dialogue>
for $name
{
fn new(d: $dialogue) -> Wrapper {
$name(d)
}
}
impl Default for $name {
fn default() -> $name {
$default_block
}
}
};
}
/// Generates `.up(field)` methods for dialogue states. /// Generates `.up(field)` methods for dialogue states.
/// ///
/// Given inductively defined states, this macro generates `.up(field)` methods /// Given inductively defined states, this macro generates `.up(field)` methods
@ -232,4 +174,4 @@ macro_rules! up {
pub type TransitionIn<State, E> = DialogueWithCx<Message, State, E>; pub type TransitionIn<State, E> = DialogueWithCx<Message, State, E>;
// A type returned from a FSM transition function. // A type returned from a FSM transition function.
pub type TransitionOut<DWrapper> = ResponseResult<DialogueStage<DWrapper>>; pub type TransitionOut<D> = ResponseResult<DialogueStage<D>>;

View file

@ -5,14 +5,14 @@ pub use crate::{
dispatching::{ dispatching::{
dialogue::{ dialogue::{
exit, next, DialogueDispatcher, DialogueStage, DialogueWithCx, exit, next, DialogueDispatcher, DialogueStage, DialogueWithCx,
DialogueWrapper, GetChatId, TransitionIn, TransitionOut, GetChatId, TransitionIn, TransitionOut,
}, },
Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx, Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx,
}, },
error_handlers::{LoggingErrorHandler, OnError}, error_handlers::{LoggingErrorHandler, OnError},
requests::{Request, ResponseResult}, requests::{Request, ResponseResult},
types::{Message, Update}, types::{Message, Update},
up, wrap_dialogue, Bot, RequestError, up, Bot, RequestError,
}; };
pub use frunk::{Coprod, Coproduct}; pub use frunk::{Coprod, Coproduct};