Merge branch 'master' into redis

This commit is contained in:
Temirkhan Myrzamadi 2020-04-19 19:27:03 +03:00 committed by GitHub
commit 478e7038a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 379 additions and 78 deletions

View file

@ -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

View file

@ -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`.

View file

@ -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
```

View file

@ -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&#39;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&#39;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.

View file

@ -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. |

View file

@ -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;

View file

@ -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")
}
}

View 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"

View file

@ -0,0 +1 @@
web: ./target/release/heroku_ping_pong_bot

View 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
```

View 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;
}

View file

@ -0,0 +1,7 @@
##!/bin/sh
for example in */; do
echo Testing $example...
cd $example; cargo check; cd ..;
done

View 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"

View 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;
}

View file

@ -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,

View file

@ -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)
}

View file

@ -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> {}

View file

@ -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();
};

View file

@ -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(

View file

@ -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())),
}

View file

@ -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)
}

View file

@ -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),
}

View file

@ -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 {