mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-23 06:51:01 +01:00
Merge branch 'master' into redis
This commit is contained in:
commit
37a0ac8ef8
8 changed files with 61 additions and 114 deletions
36
README.md
36
README.md
|
@ -30,9 +30,6 @@
|
|||
- [Dialogues](https://github.com/teloxide/teloxide#dialogues)
|
||||
- [Recommendations](https://github.com/teloxide/teloxide#recommendations)
|
||||
- [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)
|
||||
- [Contributing](https://github.com/teloxide/teloxide#contributing)
|
||||
|
||||
|
@ -225,11 +222,6 @@ pub type Dialogue = Coprod!(
|
|||
ReceiveAgeState,
|
||||
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.
|
||||
|
@ -348,7 +340,7 @@ async fn main() {
|
|||
[start, receive_full_name, receive_age, receive_favourite_music]
|
||||
)
|
||||
.expect("Something wrong with the bot!")
|
||||
}))
|
||||
}, || Dialogue::inject(StartState)))
|
||||
.dispatch()
|
||||
.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.
|
||||
|
||||
## FAQ
|
||||
### 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).
|
||||
Q: Where I can ask questions?
|
||||
|
||||
### Why Rust?
|
||||
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.
|
||||
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).
|
||||
|
||||
### Can I use webhooks?
|
||||
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).
|
||||
Q: Why Rust?
|
||||
|
||||
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:
|
||||
- [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)
|
||||
|
||||
### 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).
|
||||
Q: Can I use different loggers?
|
||||
|
||||
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
|
||||
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
|
||||
See [CONRIBUTING.md](https://github.com/teloxide/teloxide/blob/master/CONTRIBUTING.md).
|
||||
|
|
|
@ -38,18 +38,21 @@ async fn run() {
|
|||
let bot = Bot::from_env();
|
||||
|
||||
Dispatcher::new(bot)
|
||||
.messages_handler(DialogueDispatcher::new(|cx| async move {
|
||||
.messages_handler(DialogueDispatcher::new(
|
||||
|cx| async move {
|
||||
let DialogueWithCx { cx, dialogue } = cx;
|
||||
|
||||
// Unwrap without panic because of std::convert::Infallible.
|
||||
let Wrapper(dialogue) = dialogue.unwrap();
|
||||
let dialogue = dialogue.unwrap();
|
||||
|
||||
dispatch!(
|
||||
[cx, dialogue] ->
|
||||
[start, receive_full_name, receive_age, receive_favourite_music]
|
||||
)
|
||||
.expect("Something wrong with the bot!")
|
||||
}))
|
||||
},
|
||||
|| Dialogue::inject(StartState),
|
||||
))
|
||||
.dispatch()
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -42,8 +42,3 @@ pub type Dialogue = Coprod!(
|
|||
ReceiveAgeState,
|
||||
ReceiveFavouriteMusicState,
|
||||
);
|
||||
|
||||
wrap_dialogue!(
|
||||
Wrapper(Dialogue),
|
||||
default Self(Dialogue::inject(StartState)),
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@ use teloxide::prelude::*;
|
|||
use super::{favourite_music::FavouriteMusic, states::*};
|
||||
|
||||
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 {
|
||||
let (cx, dialogue) = cx.unpack();
|
||||
|
|
|
@ -26,6 +26,7 @@ use std::sync::{Arc, Mutex};
|
|||
pub struct DialogueDispatcher<D, S, H, Upd> {
|
||||
storage: Arc<S>,
|
||||
handler: Arc<H>,
|
||||
default: Arc<dyn Fn() -> D + Send + Sync + 'static>,
|
||||
_phantom: PhantomData<Mutex<D>>,
|
||||
|
||||
/// 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
|
||||
H: DialogueDispatcherHandler<Upd, D, Infallible> + Send + Sync + 'static,
|
||||
Upd: GetChatId + Send + 'static,
|
||||
D: Default + Send + 'static,
|
||||
D: Send + 'static,
|
||||
{
|
||||
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
|
||||
/// (a default storage).
|
||||
/// Creates a dispatcher with the specified `handler, [`InMemStorage`]
|
||||
/// (a default storage), and a function that returns a default dialogue,
|
||||
/// used when a user initiates a new dialogue.
|
||||
///
|
||||
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
|
||||
#[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 {
|
||||
storage: InMemStorage::new(),
|
||||
handler: Arc::new(handler),
|
||||
default: Arc::new(default),
|
||||
senders: Arc::new(Map::new()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
|
@ -62,16 +68,22 @@ impl<D, S, H, Upd> DialogueDispatcher<D, S, H, Upd>
|
|||
where
|
||||
H: DialogueDispatcherHandler<Upd, D, S::Error> + Send + Sync + 'static,
|
||||
Upd: GetChatId + Send + 'static,
|
||||
D: Default + Send + 'static,
|
||||
D: Send + 'static,
|
||||
S: Storage<D> + Send + Sync + '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]
|
||||
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 {
|
||||
storage,
|
||||
handler: Arc::new(handler),
|
||||
default: Arc::new(default),
|
||||
senders: Arc::new(Map::new()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
|
@ -83,11 +95,13 @@ where
|
|||
|
||||
let storage = Arc::clone(&self.storage);
|
||||
let handler = Arc::clone(&self.handler);
|
||||
let default = Arc::clone(&self.default);
|
||||
let senders = Arc::clone(&self.senders);
|
||||
|
||||
tokio::spawn(rx.for_each(move |cx: UpdateWithCx<Upd>| {
|
||||
let storage = Arc::clone(&storage);
|
||||
let handler = Arc::clone(&handler);
|
||||
let default = Arc::clone(&default);
|
||||
let senders = Arc::clone(&senders);
|
||||
|
||||
async move {
|
||||
|
@ -96,7 +110,7 @@ where
|
|||
let dialogue = Arc::clone(&storage)
|
||||
.remove_dialogue(chat_id)
|
||||
.await
|
||||
.map(Option::unwrap_or_default);
|
||||
.map(|opt| opt.unwrap_or_else(move || default()));
|
||||
|
||||
match handler.handle(DialogueWithCx { cx, dialogue }).await {
|
||||
DialogueStage::Next(new_dialogue) => {
|
||||
|
@ -131,7 +145,7 @@ impl<D, S, H, Upd> DispatcherHandler<Upd> for DialogueDispatcher<D, S, H, Upd>
|
|||
where
|
||||
H: DialogueDispatcherHandler<Upd, D, S::Error> + Send + Sync + 'static,
|
||||
Upd: GetChatId + Send + 'static,
|
||||
D: Default + Send + 'static,
|
||||
D: Send + 'static,
|
||||
S: Storage<D> + Send + Sync + 'static,
|
||||
S::Error: Send + 'static,
|
||||
{
|
||||
|
@ -189,7 +203,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn updates_from_same_chat_executed_sequentially() {
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
struct MyUpdate {
|
||||
chat_id: i64,
|
||||
unique_number: u32,
|
||||
|
@ -232,6 +246,7 @@ mod tests {
|
|||
|
||||
DialogueStage::Next(())
|
||||
},
|
||||
|| (),
|
||||
);
|
||||
|
||||
let updates = stream::iter(
|
||||
|
|
|
@ -11,29 +11,21 @@ pub enum DialogueStage<D> {
|
|||
Exit,
|
||||
}
|
||||
|
||||
/// A dialogue wrapper to bypass orphan rules.
|
||||
pub trait DialogueWrapper<D> {
|
||||
fn new(dialogue: D) -> Self;
|
||||
}
|
||||
|
||||
/// Returns a new dialogue state.
|
||||
///
|
||||
/// See [the module-level documentation for the design
|
||||
/// overview](crate::dispatching::dialogue).
|
||||
pub fn next<Dialogue, State, Index, DWrapper>(
|
||||
new_state: State,
|
||||
) -> TransitionOut<DWrapper>
|
||||
pub fn next<Dialogue, State, Index>(new_state: State) -> TransitionOut<Dialogue>
|
||||
where
|
||||
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.
|
||||
///
|
||||
/// See [the module-level documentation for the design
|
||||
/// overview](crate::dispatching::dialogue).
|
||||
pub fn exit<DWrapper>() -> TransitionOut<DWrapper> {
|
||||
pub fn exit<D>() -> TransitionOut<D> {
|
||||
Ok(DialogueStage::Exit)
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ mod storage;
|
|||
use crate::{requests::ResponseResult, types::Message};
|
||||
pub use dialogue_dispatcher::DialogueDispatcher;
|
||||
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 get_chat_id::GetChatId;
|
||||
|
||||
|
@ -79,13 +79,8 @@ pub use storage::{InMemStorage, Storage};
|
|||
/// ReceiveNumberState,
|
||||
/// );
|
||||
///
|
||||
/// wrap_dialogue!(
|
||||
/// Wrapper(Dialogue),
|
||||
/// default Self(Dialogue::inject(StartState)),
|
||||
/// );
|
||||
///
|
||||
/// 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 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.
|
||||
///
|
||||
/// 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>;
|
||||
|
||||
// A type returned from a FSM transition function.
|
||||
pub type TransitionOut<DWrapper> = ResponseResult<DialogueStage<DWrapper>>;
|
||||
pub type TransitionOut<D> = ResponseResult<DialogueStage<D>>;
|
||||
|
|
|
@ -5,14 +5,14 @@ pub use crate::{
|
|||
dispatching::{
|
||||
dialogue::{
|
||||
exit, next, DialogueDispatcher, DialogueStage, DialogueWithCx,
|
||||
DialogueWrapper, GetChatId, TransitionIn, TransitionOut,
|
||||
GetChatId, TransitionIn, TransitionOut,
|
||||
},
|
||||
Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx,
|
||||
},
|
||||
error_handlers::{LoggingErrorHandler, OnError},
|
||||
requests::{Request, ResponseResult},
|
||||
types::{Message, Update},
|
||||
up, wrap_dialogue, Bot, RequestError,
|
||||
up, Bot, RequestError,
|
||||
};
|
||||
|
||||
pub use frunk::{Coprod, Coproduct};
|
||||
|
|
Loading…
Reference in a new issue