mirror of
https://github.com/teloxide/teloxide.git
synced 2024-10-24 09:57:18 +02:00
Merge branch 'master' into redis
This commit is contained in:
commit
478e7038a6
23 changed files with 379 additions and 78 deletions
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
|
@ -50,13 +50,6 @@ jobs:
|
|||
command: test
|
||||
args: --verbose --all-features
|
||||
|
||||
- name: fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
if: matrix.rust == 'nightly'
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: stable/beta clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
if: matrix.rust == 'stable' || matrix.rust == 'beta'
|
||||
|
@ -70,3 +63,13 @@ jobs:
|
|||
with:
|
||||
command: clippy
|
||||
args: --all-targets --all-features -- -D warnings
|
||||
|
||||
- name: Test the examples
|
||||
run: cd examples && bash test_examples.sh
|
||||
|
||||
- name: fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
if: matrix.rust == 'nightly'
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -6,14 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [0.2.0] - 2020-02-25
|
||||
### Added
|
||||
- The functionality to parse commands only with a correct bot's name (breaks backwards compatibility) (https://github.com/teloxide/teloxide/issues/168).
|
||||
- The functionality to parse commands only with a correct bot's name (breaks backwards compatibility) ([Issue 168](https://github.com/teloxide/teloxide/issues/168)).
|
||||
- This `CHANGELOG.md`.
|
||||
|
||||
### Fixed
|
||||
- Fix parsing a pinned message (https://github.com/teloxide/teloxide/issues/167).
|
||||
- Replace `LanguageCode` with `String`, Because [the official Telegram documentation](https://core.telegram.org/bots/api#getchat) doesn't specify a concrete version of IETF language tag.
|
||||
- Problems with the `poll_type` field (https://github.com/teloxide/teloxide/issues/178).
|
||||
- Make `polling_default` actually a long polling update listener (https://github.com/teloxide/teloxide/pull/182).
|
||||
- Fix parsing a pinned message ([Issue 167](https://github.com/teloxide/teloxide/issues/167)).
|
||||
- Replace `LanguageCode` with `String`, because [the official Telegram documentation](https://core.telegram.org/bots/api#getchat) doesn't specify a concrete version of IETF language tag.
|
||||
- Problems with the `poll_type` field ([Issue 178](https://github.com/teloxide/teloxide/issues/178)).
|
||||
- Make `polling_default` actually a long polling update listener ([PR 182](https://github.com/teloxide/teloxide/pull/182)).
|
||||
|
||||
### Removed
|
||||
- [either](https://crates.io/crates/either) from the dependencies in `Cargo.toml`.
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
# Contributing
|
||||
Before contributing, please read [our code style](https://github.com/teloxide/teloxide/blob/master/CODE_STYLE.md) and [the license](https://github.com/teloxide/teloxide/blob/master/LICENSE).
|
||||
|
||||
To change the source code, fork this repository and work inside your own branch. Then send us a PR into the [dev](https://github.com/teloxide/teloxide/tree/dev) branch and wait for the CI to check everything. However, you'd better check changes first locally:
|
||||
To change the source code, fork the `master` branch of this repository and work inside your own branch. Then send us a PR into `master` branch and wait for the CI to check everything. However, you'd better check changes first locally:
|
||||
|
||||
```
|
||||
cargo clippy --all --all-features --all-targets
|
||||
cargo test --all
|
||||
cargo doc --open
|
||||
cargo fmt --all -- --check
|
||||
# Using nightly rustfmt
|
||||
cargo +nightly fmt --all -- --check
|
||||
```
|
||||
|
||||
To report a bug, suggest new functionality, or ask a question, go to [Issues](https://github.com/teloxide/teloxide/issues). Try to make MRE (**M**inimal **R**eproducible **E**xample) and specify your teloxide version to let others help you.
|
||||
|
||||
|
||||
And don't forget to switch to the nightly channel in order to be able to run `cargo fmt` (because our [rustfmt.toml](https://github.com/teloxide/teloxide/blob/master/rustfmt.toml) requires some nightly-only functionality):
|
||||
|
||||
```bash
|
||||
$ rustup override set nightly
|
||||
```
|
||||
|
|
42
README.md
42
README.md
|
@ -34,17 +34,32 @@
|
|||
- [Contributing](https://github.com/teloxide/teloxide#contributing)
|
||||
|
||||
## Features
|
||||
- **Declarative API.** You tell teloxide what you want instead of describing what to do.
|
||||
<h3 align="center">Higher-order design</h3>
|
||||
<p align="center">
|
||||
teloxide supports <a href="https://en.wikipedia.org/wiki/Higher-order_programming">higher-order programming</a> by making <a href="https://docs.rs/futures/latest/futures/prelude/trait.Stream.html">streams</a> a <a href="https://en.wikipedia.org/wiki/First-class_citizen">first-class citizen</a>: feel free to (de)multiplex them, apply arbitrary transformations, pass to/return from other functions, <a href="https://en.wikipedia.org/wiki/Lazy_evaluation">lazily evaluate them</a>, concurrently process their items, and much more, thereby achieving extremely flexible design.
|
||||
</p>
|
||||
|
||||
- **Type-safe.** All the [API types and methods](https://core.telegram.org/bots/api) are implemented with heavy use of [**ADT**s](https://en.wikipedia.org/wiki/Algebraic_data_type) to enforce type-safety and tight integration with IDEs.
|
||||
<hr>
|
||||
|
||||
- **Flexible API.** Updates are represented as [streams](https://docs.rs/futures/0.3.4/futures/stream/index.html): you can express your business logic using [all 30+ adaptors](https://docs.rs/futures/0.3.4/futures/stream/trait.StreamExt.html), each having distinct semantics (see [simple-commands-bot](#commands) below).
|
||||
<h3 align="center">Type safety</h3>
|
||||
<p align="center">
|
||||
All the API <a href="https://docs.rs/teloxide/latest/teloxide/types/index.html">types</a> and <a href="https://docs.rs/teloxide/0.2.0/teloxide/requests/index.html">methods</a> are implemented with heavy use of <a href="https://en.wikipedia.org/wiki/Algebraic_data_type"><strong>ADT</strong>s</a> to enforce type safety and tight integration with IDEs. Bot's commands <a href="https://github.com/teloxide/teloxide#commands">have precise types too</a>, thereby serving as a self-documenting code and respecting the <a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">parse, don't validate</a> programming idiom.
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 align="center">Persistency</h3>
|
||||
<p align="center">
|
||||
By default, teloxide stores all user dialogues in RAM, but you can store them somewhere else (for example, in a database) just by implementing <a href="https://docs.rs/teloxide/latest/teloxide/dispatching/dialogue/trait.Storage.html">2 functions</a>.
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 align="center">Convenient dialogues management</h3>
|
||||
<p align="center">
|
||||
Define a type-safe <a href="https://en.wikipedia.org/wiki/Finite-state_machine">finite automaton</a> and transition functions to drive a user dialogue with ease (see <a href="#guess-a-number">the guess-a-number example</a> below).
|
||||
</p>
|
||||
|
||||
- **Persistency.** By default, teloxide stores all user dialogues in RAM, but you can store them somewhere else (for example, in DB) just by implementing 2 functions.
|
||||
|
||||
- **Convenient dialogues system.** Define a type-safe [finite automaton](https://en.wikipedia.org/wiki/Finite-state_machine)
|
||||
and transition functions to drive a user dialogue with ease (see [the guess-a-number example](#guess-a-number) below).
|
||||
|
||||
## Getting started
|
||||
1. Create a new bot using [@Botfather](https://t.me/botfather) to get a token in the format `123456789:blablabla`.
|
||||
2. Initialise the `TELOXIDE_TOKEN` environmental variable to your token:
|
||||
|
@ -244,7 +259,7 @@ async fn main() {
|
|||
<br/><br/>
|
||||
</div>
|
||||
|
||||
Our [finite automaton](https://en.wikipedia.org/wiki/Finite-state_machine), designating a user dialogue, cannot be in an invalid state, and this is why it is called "type-safe". We could use `enum` + `Option`s instead, but it will lead is to lots of unpleasure `.unwrap()`s.
|
||||
Our [finite automaton](https://en.wikipedia.org/wiki/Finite-state_machine), designating a user dialogue, cannot be in an invalid state, and this is why it is called "type-safe". We could use `enum` + `Option`s instead, but it would lead us to lots of unpleasant `.unwrap()`s.
|
||||
|
||||
Remember that a classical [finite automaton](https://en.wikipedia.org/wiki/Finite-state_machine) is defined by its initial state, a list of its possible states and a transition function? We can think that `Dialogue` is a finite automaton with a context type at each state (`Dialogue::Start` has `()`, `Dialogue::ReceiveAttempt` has `u8`).
|
||||
|
||||
|
@ -279,11 +294,18 @@ The second one produces very strange compiler messages because of the `#[tokio::
|
|||
|
||||
## 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.
|
||||
[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?
|
||||
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?
|
||||
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/webhook_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)
|
||||
|
||||
## 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.
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@ Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run
|
|||
| Bot | Description |
|
||||
|---|-----------|
|
||||
| [ping_pong_bot](ping_pong_bot) | Answers "pong" to each incoming message. |
|
||||
| [webhook_ping_pong_bot](webhook_ping_pong_bot) | How to use webhooks with teloxide. |
|
||||
| [simple_commands_bot](simple_commands_bot) | Shows how to deal with bot's commands. |
|
||||
| [guess_a_number_bot](guess_a_number_bot) | The "guess a number" game. |
|
||||
| [dialogue_bot](dialogue_bot) | Drive a dialogue with a user using a type-safe finite automaton. |
|
||||
| [admin_bot](admin_bot) | A bot, which can ban, kick, and mute on a command. |
|
||||
| [shared_state_bot](shared_state_bot) | A bot that shows how to deal with shared state. |
|
||||
| [shared_state_bot](shared_state_bot) | A bot that shows how to deal with shared state. |
|
||||
|
|
|
@ -153,7 +153,10 @@ async fn favourite_music(cx: Cx<ReceiveFavouriteMusicState>) -> Res {
|
|||
|
||||
async fn handle_message(cx: Cx<Dialogue>) -> Res {
|
||||
let DialogueDispatcherHandlerCx { bot, update, dialogue } = cx;
|
||||
match dialogue.unwrap() {
|
||||
|
||||
// You need handle the error instead of panicking in real-world code, maybe
|
||||
// send diagnostics to a development chat.
|
||||
match dialogue.expect("Failed to get dialogue info from storage") {
|
||||
Dialogue::Start => {
|
||||
start(DialogueDispatcherHandlerCx::new(bot, update, ())).await
|
||||
}
|
||||
|
@ -186,10 +189,8 @@ async fn run() {
|
|||
let bot = Bot::from_env();
|
||||
|
||||
Dispatcher::new(bot)
|
||||
.messages_handler(DialogueDispatcher::new(|cx| {
|
||||
async move {
|
||||
handle_message(cx).await.expect("Something wrong with the bot!")
|
||||
}
|
||||
.messages_handler(DialogueDispatcher::new(|cx| async move {
|
||||
handle_message(cx).await.expect("Something wrong with the bot!")
|
||||
}))
|
||||
.dispatch()
|
||||
.await;
|
||||
|
|
|
@ -21,8 +21,8 @@ extern crate smart_default;
|
|||
|
||||
use teloxide::prelude::*;
|
||||
|
||||
use std::convert::Infallible;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::convert::Infallible;
|
||||
|
||||
// ============================================================================
|
||||
// [A type-safe finite automaton]
|
||||
|
@ -80,23 +80,20 @@ async fn receive_attempt(cx: Cx<u8>) -> Res {
|
|||
async fn handle_message(
|
||||
cx: DialogueDispatcherHandlerCx<Message, Dialogue, Infallible>,
|
||||
) -> Res {
|
||||
match cx {
|
||||
DialogueDispatcherHandlerCx {
|
||||
bot,
|
||||
update,
|
||||
dialogue: Ok(Dialogue::Start),
|
||||
} => start(DialogueDispatcherHandlerCx::new(bot, update, ())).await,
|
||||
DialogueDispatcherHandlerCx {
|
||||
bot,
|
||||
update,
|
||||
dialogue: Ok(Dialogue::ReceiveAttempt(secret)),
|
||||
} => {
|
||||
let DialogueDispatcherHandlerCx { bot, update, dialogue } = cx;
|
||||
|
||||
// You need handle the error instead of panicking in real-world code, maybe
|
||||
// send diagnostics to a development chat.
|
||||
match dialogue.expect("Failed to get dialogue info from storage") {
|
||||
Dialogue::Start => {
|
||||
start(DialogueDispatcherHandlerCx::new(bot, update, ())).await
|
||||
}
|
||||
Dialogue::ReceiveAttempt(secret) => {
|
||||
receive_attempt(DialogueDispatcherHandlerCx::new(
|
||||
bot, update, secret,
|
||||
))
|
||||
.await
|
||||
}
|
||||
_ => panic!("Failed to get dialogue info from storage")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
19
examples/heroku_ping_pong_bot/Cargo.toml
Normal file
19
examples/heroku_ping_pong_bot/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "heroku_ping_pong_bot"
|
||||
version = "0.1.0"
|
||||
authors = ["Pedro Lopes <ordepi@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
futures = "0.3.4"
|
||||
tokio = "0.2.9"
|
||||
pretty_env_logger = "0.4.0"
|
||||
teloxide = "0.2.0"
|
||||
|
||||
# Used to setup a webhook
|
||||
warp = "0.2.2"
|
||||
reqwest = "0.10.4"
|
||||
serde_json = "1.0.50"
|
1
examples/heroku_ping_pong_bot/Procfile
Normal file
1
examples/heroku_ping_pong_bot/Procfile
Normal file
|
@ -0,0 +1 @@
|
|||
web: ./target/release/heroku_ping_pong_bot
|
15
examples/heroku_ping_pong_bot/README.md
Normal file
15
examples/heroku_ping_pong_bot/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Heroku example
|
||||
|
||||
This is an example project on how to deploy `webhook_ping_pong_bot` to heroku.
|
||||
|
||||
You will need to configure the buildpack for heroku. We will be using [Heroku rust buildpack](https://github.com/emk/heroku-buildpack-rust). Configuration was done by using `heroku` CLI.
|
||||
|
||||
If you're creating a new Heroku application, run this command inside example
|
||||
```
|
||||
heroku create --buildpack emk/rust
|
||||
```
|
||||
|
||||
To set buildpack for existing applicaton:
|
||||
```
|
||||
heroku buildpacks:set emk/rust
|
||||
```
|
91
examples/heroku_ping_pong_bot/src/main.rs
Normal file
91
examples/heroku_ping_pong_bot/src/main.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
// The version of ping-pong-bot, which uses a webhook to receive updates from
|
||||
// Telegram, instead of long polling.
|
||||
|
||||
use teloxide::{dispatching::update_listeners, prelude::*};
|
||||
|
||||
use std::{convert::Infallible, env, net::SocketAddr, sync::Arc};
|
||||
use tokio::sync::mpsc;
|
||||
use warp::Filter;
|
||||
|
||||
use reqwest::StatusCode;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
run().await;
|
||||
}
|
||||
|
||||
async fn handle_rejection(error: warp::Rejection) -> Result<impl warp::Reply, Infallible> {
|
||||
log::error!("Cannot process the request due to: {:?}", error);
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
pub async fn webhook<'a>(bot: Arc<Bot>) -> impl update_listeners::UpdateListener<Infallible> {
|
||||
// Heroku defines auto defines a port value
|
||||
let teloxide_token = env::var("TELOXIDE_TOKEN").expect("TELOXIDE_TOKEN env variable missing");
|
||||
let port: u16 = env::var("PORT")
|
||||
.expect("PORT env variable missing")
|
||||
.parse()
|
||||
.expect("PORT value to be integer");
|
||||
// Heroku host example .: "heroku-ping-pong-bot.herokuapp.com"
|
||||
let host = env::var("HOST").expect("have HOST env variable");
|
||||
let path = format!("bot{}", teloxide_token);
|
||||
let url = format!("https://{}/{}", host, path);
|
||||
|
||||
bot.set_webhook(url)
|
||||
.send()
|
||||
.await
|
||||
.expect("Cannot setup a webhook");
|
||||
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let server = warp::post()
|
||||
.and(warp::path(path))
|
||||
.and(warp::body::json())
|
||||
.map(move |json: serde_json::Value| {
|
||||
let try_parse = match serde_json::from_str(&json.to_string()) {
|
||||
Ok(update) => Ok(update),
|
||||
Err(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,
|
||||
json
|
||||
);
|
||||
Err(error)
|
||||
}
|
||||
};
|
||||
if let Ok(update) = try_parse {
|
||||
tx.send(Ok(update))
|
||||
.expect("Cannot send an incoming update from the webhook")
|
||||
}
|
||||
|
||||
StatusCode::OK
|
||||
})
|
||||
.recover(handle_rejection);
|
||||
|
||||
let serve = warp::serve(server);
|
||||
|
||||
let address = format!("0.0.0.0:{}", port);
|
||||
tokio::spawn(serve.run(address.parse::<SocketAddr>().unwrap()));
|
||||
rx
|
||||
}
|
||||
|
||||
async fn run() {
|
||||
teloxide::enable_logging!();
|
||||
log::info!("Starting ping_pong_bot!");
|
||||
|
||||
let bot = Bot::from_env();
|
||||
|
||||
Dispatcher::new(Arc::clone(&bot))
|
||||
.messages_handler(|rx: DispatcherHandlerRx<Message>| {
|
||||
rx.for_each(|message| async move {
|
||||
message.answer("pong").send().await.log_on_error().await;
|
||||
})
|
||||
})
|
||||
.dispatch_with_listener(
|
||||
webhook(bot).await,
|
||||
LoggingErrorHandler::with_custom_text("An error from the update listener"),
|
||||
)
|
||||
.await;
|
||||
}
|
7
examples/test_examples.sh
Normal file
7
examples/test_examples.sh
Normal file
|
@ -0,0 +1,7 @@
|
|||
##!/bin/sh
|
||||
|
||||
for example in */; do
|
||||
echo Testing $example...
|
||||
cd $example; cargo check; cd ..;
|
||||
done
|
||||
|
20
examples/webhook_ping_pong_bot/Cargo.toml
Normal file
20
examples/webhook_ping_pong_bot/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "webhook_ping_pong_bot"
|
||||
version = "0.1.0"
|
||||
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
futures = "0.3.4"
|
||||
tokio = "0.2.9"
|
||||
pretty_env_logger = "0.4.0"
|
||||
|
||||
teloxide = { path = "../../" }
|
||||
|
||||
# Used to setup a webhook
|
||||
warp = "0.2.2"
|
||||
reqwest = "0.10.4"
|
||||
serde_json = "1.0.50"
|
76
examples/webhook_ping_pong_bot/src/main.rs
Normal file
76
examples/webhook_ping_pong_bot/src/main.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
// The version of ping-pong-bot, which uses a webhook to receive updates from
|
||||
// Telegram, instead of long polling.
|
||||
|
||||
use teloxide::{dispatching::update_listeners, prelude::*};
|
||||
|
||||
use std::{convert::Infallible, net::SocketAddr, sync::Arc};
|
||||
use tokio::sync::mpsc;
|
||||
use warp::Filter;
|
||||
|
||||
use reqwest::StatusCode;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
run().await;
|
||||
}
|
||||
|
||||
async fn handle_rejection(
|
||||
error: warp::Rejection,
|
||||
) -> Result<impl warp::Reply, Infallible> {
|
||||
log::error!("Cannot process the request due to: {:?}", error);
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
pub async fn webhook<'a>(
|
||||
bot: Arc<Bot>,
|
||||
) -> impl update_listeners::UpdateListener<Infallible> {
|
||||
// You might want to specify a self-signed certificate via .certificate
|
||||
// method on SetWebhook.
|
||||
bot.set_webhook("Your HTTPS ngrok URL here. Get it by 'ngrok http 80'")
|
||||
.send()
|
||||
.await
|
||||
.expect("Cannot setup a webhook");
|
||||
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
|
||||
let server = warp::post()
|
||||
.and(warp::body::json())
|
||||
.map(move |json: serde_json::Value| {
|
||||
if let Ok(update) = Update::try_parse(&json) {
|
||||
tx.send(Ok(update))
|
||||
.expect("Cannot send an incoming update from the webhook")
|
||||
}
|
||||
|
||||
StatusCode::OK
|
||||
})
|
||||
.recover(handle_rejection);
|
||||
|
||||
let serve = warp::serve(server);
|
||||
|
||||
// You might want to use serve.key_path/serve.cert_path methods here to
|
||||
// setup a self-signed TLS certificate.
|
||||
|
||||
tokio::spawn(serve.run("127.0.0.1:80".parse::<SocketAddr>().unwrap()));
|
||||
rx
|
||||
}
|
||||
|
||||
async fn run() {
|
||||
teloxide::enable_logging!();
|
||||
log::info!("Starting ping_pong_bot!");
|
||||
|
||||
let bot = Bot::from_env();
|
||||
|
||||
Dispatcher::new(Arc::clone(&bot))
|
||||
.messages_handler(|rx: DispatcherHandlerRx<Message>| {
|
||||
rx.for_each(|message| async move {
|
||||
message.answer("pong").send().await.log_on_error().await;
|
||||
})
|
||||
})
|
||||
.dispatch_with_listener(
|
||||
webhook(bot).await,
|
||||
LoggingErrorHandler::with_custom_text(
|
||||
"An error from the update listener",
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
|
@ -21,12 +21,12 @@ mod macros {
|
|||
/// Pushes an update to a queue.
|
||||
macro_rules! send {
|
||||
($bot:expr, $tx:expr, $update:expr, $variant:expr) => {
|
||||
send($bot, $tx, $update, stringify!($variant)).await;
|
||||
send($bot, $tx, $update, stringify!($variant));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async fn send<'a, Upd>(
|
||||
fn send<'a, Upd>(
|
||||
bot: &'a Arc<Bot>,
|
||||
tx: &'a Tx<Upd>,
|
||||
update: Upd,
|
||||
|
|
|
@ -124,7 +124,7 @@ impl DispatcherHandlerCx<Message> {
|
|||
self.bot.send_contact(self.chat_id(), phone_number, first_name)
|
||||
}
|
||||
|
||||
pub fn answer_sticker<T>(&self, sticker: InputFile) -> SendSticker {
|
||||
pub fn answer_sticker(&self, sticker: InputFile) -> SendSticker {
|
||||
self.bot.send_sticker(self.update.chat.id, sticker)
|
||||
}
|
||||
|
||||
|
|
|
@ -91,6 +91,9 @@
|
|||
//! <a id="4" href="#4b">^4</a> `offset = N` means that we've already received
|
||||
//! updates `0..=N`.
|
||||
//!
|
||||
//! # Webhooks
|
||||
//! See the [README FAQ about webhooks](https://github.com/teloxide/teloxide/blob/master/README.md#can-i-use-webhooks).
|
||||
//!
|
||||
//! [`UpdateListener`]: UpdateListener
|
||||
//! [`polling_default`]: polling_default
|
||||
//! [`polling`]: polling
|
||||
|
@ -108,6 +111,7 @@ use crate::{
|
|||
types::{AllowedUpdate, Update},
|
||||
RequestError,
|
||||
};
|
||||
|
||||
use std::{convert::TryInto, sync::Arc, time::Duration};
|
||||
|
||||
/// A generic update listener.
|
||||
|
@ -162,8 +166,8 @@ pub fn polling(
|
|||
Err((value, _)) => value["update_id"]
|
||||
.as_i64()
|
||||
.expect(
|
||||
"The 'update_id' field must always exist in \
|
||||
Update",
|
||||
"The 'update_id' field must always exist \
|
||||
in Update",
|
||||
)
|
||||
.try_into()
|
||||
.expect("update_id must be i32"),
|
||||
|
@ -174,18 +178,7 @@ pub fn polling(
|
|||
|
||||
let updates = updates
|
||||
.into_iter()
|
||||
.filter(|update| match 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, value);
|
||||
false
|
||||
}
|
||||
Ok(_) => true,
|
||||
})
|
||||
.map(|update| {
|
||||
update.expect("See the previous .filter() call")
|
||||
})
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<Update>>();
|
||||
|
||||
updates.into_iter().map(Ok).collect::<Vec<_>>()
|
||||
|
@ -197,8 +190,3 @@ pub fn polling(
|
|||
)
|
||||
.flatten()
|
||||
}
|
||||
|
||||
// TODO implement webhook (this actually require webserver and probably we
|
||||
// should add cargo feature that adds webhook)
|
||||
//pub fn webhook<'a>(bot: &'a cfg: WebhookConfig) -> Updater<impl
|
||||
// Stream<Item=Result<Update, ???>> + 'a> {}
|
||||
|
|
|
@ -45,7 +45,7 @@ macro_rules! enable_logging_with_filter {
|
|||
($filter:expr) => {
|
||||
pretty_env_logger::formatted_builder()
|
||||
.write_style(pretty_env_logger::env_logger::WriteStyle::Auto)
|
||||
.filter(Some(env!("CARGO_PKG_NAME")), $filter)
|
||||
.filter(Some(&env!("CARGO_PKG_NAME").replace("-", "_")), $filter)
|
||||
.filter(Some("teloxide"), log::LevelFilter::Error)
|
||||
.init();
|
||||
};
|
||||
|
|
|
@ -52,8 +52,7 @@ impl Request for GetUpdates {
|
|||
Value::Array(array) => Ok(array
|
||||
.into_iter()
|
||||
.map(|value| {
|
||||
serde_json::from_str(&value.to_string())
|
||||
.map_err(|error| (value, error))
|
||||
Update::try_parse(&value).map_err(|error| (value, error))
|
||||
})
|
||||
.collect()),
|
||||
_ => Err(RequestError::InvalidJson(
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{borrow::Cow, path::PathBuf};
|
|||
use reqwest::multipart::Form;
|
||||
|
||||
use crate::{
|
||||
requests::utils::file_to_part,
|
||||
requests::utils::{file_from_memory_to_part, file_to_part},
|
||||
types::{
|
||||
ChatId, InlineKeyboardMarkup, InputFile, InputMedia, MaskPosition,
|
||||
ParseMode, ReplyMarkup,
|
||||
|
@ -33,6 +33,9 @@ impl FormBuilder {
|
|||
Self { form: self.form.text(name, string) }
|
||||
}
|
||||
Some(FormValue::File(path)) => self.add_file(name, path).await,
|
||||
Some(FormValue::Memory { file_name, data }) => {
|
||||
self.add_file_from_memory(name, file_name, data)
|
||||
}
|
||||
None => self,
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +53,23 @@ impl FormBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_file_from_memory<'a, N>(
|
||||
self,
|
||||
name: N,
|
||||
file_name: String,
|
||||
data: Cow<'static, [u8]>,
|
||||
) -> Self
|
||||
where
|
||||
N: Into<Cow<'a, str>>,
|
||||
{
|
||||
Self {
|
||||
form: self.form.part(
|
||||
name.into().into_owned(),
|
||||
file_from_memory_to_part(data, file_name),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(self) -> Form {
|
||||
self.form
|
||||
}
|
||||
|
@ -57,6 +77,7 @@ impl FormBuilder {
|
|||
|
||||
pub(crate) enum FormValue {
|
||||
File(PathBuf),
|
||||
Memory { file_name: String, data: Cow<'static, [u8]> },
|
||||
Str(String),
|
||||
}
|
||||
|
||||
|
@ -153,6 +174,10 @@ impl IntoFormValue for InputFile {
|
|||
fn into_form_value(&self) -> Option<FormValue> {
|
||||
match self {
|
||||
InputFile::File(path) => Some(FormValue::File(path.clone())),
|
||||
InputFile::Memory { file_name, data } => Some(FormValue::Memory {
|
||||
file_name: file_name.clone(),
|
||||
data: data.clone(),
|
||||
}),
|
||||
InputFile::Url(url) => Some(FormValue::Str(url.clone())),
|
||||
InputFile::FileId(file_id) => Some(FormValue::Str(file_id.clone())),
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::path::PathBuf;
|
||||
use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use reqwest::{multipart::Part, Body};
|
||||
|
@ -34,3 +34,10 @@ pub async fn file_to_part(path_to_file: PathBuf) -> Part {
|
|||
|
||||
Part::stream(Body::wrap_stream(file)).file_name(file_name)
|
||||
}
|
||||
|
||||
pub fn file_from_memory_to_part(
|
||||
data: Cow<'static, [u8]>,
|
||||
name: String,
|
||||
) -> Part {
|
||||
Part::bytes(data).file_name(name)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
/// This object represents the contents of a file to be uploaded.
|
||||
///
|
||||
|
@ -8,6 +8,7 @@ use std::path::PathBuf;
|
|||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize)]
|
||||
pub enum InputFile {
|
||||
File(PathBuf),
|
||||
Memory { file_name: String, data: Cow<'static, [u8]> },
|
||||
Url(String),
|
||||
FileId(String),
|
||||
}
|
||||
|
@ -20,6 +21,14 @@ impl InputFile {
|
|||
Self::File(path.into())
|
||||
}
|
||||
|
||||
pub fn memory<S, D>(file_name: S, data: D) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
D: Into<Cow<'static, [u8]>>,
|
||||
{
|
||||
Self::Memory { file_name: file_name.into(), data: data.into() }
|
||||
}
|
||||
|
||||
pub fn url<T>(url: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
|
@ -82,6 +91,14 @@ impl Serialize for InputFile {
|
|||
),
|
||||
)
|
||||
}
|
||||
InputFile::Memory { data, .. } => {
|
||||
// NOTE: file should be actually attached with
|
||||
// multipart/form-data
|
||||
serializer.serialize_str(&format!(
|
||||
"attach://{}",
|
||||
String::from_utf8_lossy(data)
|
||||
))
|
||||
}
|
||||
InputFile::Url(url) => serializer.serialize_str(url),
|
||||
InputFile::FileId(id) => serializer.serialize_str(id),
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::types::{
|
|||
CallbackQuery, Chat, ChosenInlineResult, InlineQuery, Message, Poll,
|
||||
PollAnswer, PreCheckoutQuery, ShippingQuery, User,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
/// This [object] represents an incoming update.
|
||||
///
|
||||
|
@ -30,6 +31,23 @@ pub struct Update {
|
|||
pub kind: UpdateKind,
|
||||
}
|
||||
|
||||
impl Update {
|
||||
/// Tries to parse `value` into `Update`, logging an error if failed.
|
||||
///
|
||||
/// It is used to implement update listeners.
|
||||
pub fn try_parse(value: &Value) -> Result<Self, serde_json::Error> {
|
||||
match serde_json::from_str(&value.to_string()) {
|
||||
Ok(update) => Ok(update),
|
||||
Err(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, value);
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum UpdateKind {
|
||||
|
|
Loading…
Reference in a new issue