Merge branch 'dev' of github.com:teloxide/teloxide into sqlite

This commit is contained in:
Sergey Levitin 2020-10-19 00:15:21 +03:00
commit 900ae4f630
70 changed files with 1942 additions and 712 deletions

View file

@ -3,7 +3,7 @@ on:
push: push:
branches: [ master ] branches: [ master ]
pull_request: pull_request:
branches: [ master ] branches: [ master, dev ]
name: Continuous integration name: Continuous integration
@ -27,23 +27,52 @@ jobs:
override: true override: true
- name: Cargo clippy - name: Cargo clippy
run: cargo clippy --all --all-targets --all-features -- -D warnings run: cargo clippy --all --all-targets --all-features -- -D warnings
stable-test:
test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- beta
- nightly
include:
- rust: stable
features: "--features \"redis-storage cbor-serializer bincode-serializer frunk-\""
- rust: beta
features: "--features \"redis-storage cbor-serializer bincode-serializer frunk-\""
- rust: nightly
features: "--all-features"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: stable toolchain: ${{ matrix.rust }}
override: true override: true
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --verbose ${{ matrix.features }}
- name: Setup redis - name: Setup redis
run: | run: |
sudo apt install redis-server sudo apt install redis-server
redis-server --port 7777 > /dev/null & redis-server --port 7777 > /dev/null &
redis-server --port 7778 > /dev/null & redis-server --port 7778 > /dev/null &
redis-server --port 7779 > /dev/null & redis-server --port 7779 > /dev/null &
- name: Cargo test
run: cargo test --all --all-features - name: Test
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose ${{ matrix.features }}
build-example: build-example:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -56,6 +85,7 @@ jobs:
dices_bot, dices_bot,
shared_state_bot, shared_state_bot,
simple_commands_bot, simple_commands_bot,
redis_remember_bot,
] ]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -64,5 +94,5 @@ jobs:
profile: minimal profile: minimal
toolchain: stable toolchain: stable
override: true override: true
- name: Test the example - name: Check the example
run: cd examples && cd ${{ matrix.example }} && cargo check run: cd examples && cd ${{ matrix.example }} && cargo check

View file

@ -4,19 +4,68 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.0] - ??? ## [unreleased]
### Added ### Added
- Allow arbitrary error types to be returned from (sub)transitions ([issue 242](https://github.com/teloxide/teloxide/issues/242)).
- The `respond` function, a shortcut for `ResponseResult::Ok(())`.
### Changed
- Allow `bot_name` be `N`, where `N: Into<String> + ...` in `commands_repl` & `commands_repl_with_listener`.
- 'Edit methods' (namely `edit_message_live_location`, `stop_message_live_location`, `edit_message_text`,
`edit_message_caption`, `edit_message_media` and `edit_message_reply_markup`) are split into common and inline
versions (e.g.: `edit_message_text` and `edit_inline_message_text`). Instead of `ChatOrInlineMessage` common versions
accept `chat_id: impl Into<ChatId>` and `message_id: i32` whereas inline versions accept
`inline_message_id: impl Into<String>`. Also note that return type of inline versions is `True` ([issue 253], [pr 257])
- `ChatOrInlineMessage` is renamed to `TargetMessage`, it's `::Chat` variant is renamed to `::Common`,
`#[non_exhaustive]` annotation is removed from the enum, type of `TargetMessage::Inline::inline_message_id` changed
`i32` => `String`. `TargetMessage` now implements `From<String>`, `get_game_high_scores` and `set_game_score` use
`Into<TargetMessage>` to accept `String`s. ([issue 253], [pr 257])
[issue 253]: https://github.com/teloxide/teloxide/issues/253
[pr 257]: https://github.com/teloxide/teloxide/pull/257
## [0.3.1] - 2020-08-25
### Added
- `Bot::builder` method ([PR 269](https://github.com/teloxide/teloxide/pull/269)).
## [0.3.0] - 2020-07-31
### Added
- Support for typed bot commands ([issue 152](https://github.com/teloxide/teloxide/issues/152)).
- `BotBuilder`, which allows setting a default `ParseMode`. - `BotBuilder`, which allows setting a default `ParseMode`.
- The `Transition`, `Subtransition`, `SubtransitionOutputType` traits. - The `Transition`, `Subtransition`, `SubtransitionOutputType` traits.
- A nicer approach to manage dialogues via `#[derive(Transition)]` + `#[teloxide(subtransition)]` (see [`examples/dialogue_bot`](https://github.com/teloxide/teloxide/tree/af2aa218e7bfc442ab4475023a1c661834f576fc/examples/dialogue_bot)). - A nicer approach to manage dialogues via `#[derive(Transition)]` + `#[teloxide(subtransition)]` (see [`examples/dialogue_bot`](https://github.com/teloxide/teloxide/tree/af2aa218e7bfc442ab4475023a1c661834f576fc/examples/dialogue_bot)).
- The `redis-storage` feature -- enables the Redis support.
- The `cbor-serializer` feature -- enables the `CBOR` serializer for dialogues.
- The `bincode-serializer` feature -- enables the `Bincode` serializer for dialogues.
- The `frunk` feature -- enables `teloxide::utils::UpState`, which allows mapping from a structure of `field1, ..., fieldN` to a structure of `field1, ..., fieldN, fieldN+1`.
- Upgrade to v4.9 Telegram bots API.
- `teloxide::utils::client_from_env` -- constructs a client from the `TELOXIDE_TOKEN` environmental variable.
- Import `Transition`, `TransitionIn`, `TransitionOut`, `UpState` to `teloxide::prelude`.
- Import `repl`, `commands_repl` to `teloxide`.
- Let users inspect an unknown API error using `ApiErrorKind::Unknown(String)`. All the known API errors are placed into `KnownApiErrorKind`.
- Setters to all the API types.
- `teloxide::dispatching::dialogue::serializer` -- various serializers for memory storages. The `Serializer` trait, `Bincode`, `CBOR`, `JSON`.
- `teloxide::{repl, repl_with_listener, commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener}`
- `InputFile::Memory`
- Option to hide a command from description ([issue 217](https://github.com/teloxide/teloxide/issues/217)).
- Respect the `TELOXIDE_PROXY` environment variable in `Bot::from_env`.
### Deprecated ### Deprecated
- `Bot::{from_env_with_client, new, with_client}`. - `Bot::{from_env_with_client, new, with_client}`
### Changed ### Changed
- Now methods which can send file to Telegram returns tokio::io::Result<T>. Early its could panic. ([issue 216](https://github.com/teloxide/teloxide/issues/216)) - `DialogueDispatcherHandlerCx` -> `DialogueWithCx`.
- Now provided description of unknown telegram error, by splitting ApiErrorKind at `ApiErrorKind` and `ApiErrorKindKnown` enums. ([issue 199](https://github.com/teloxide/teloxide/issues/199)) - `DispatcherHandlerCx` -> `UpdateWithCx`.
- Now provided description of unknown telegram error, by splitting ApiErrorKind at `ApiErrorKind` and `ApiErrorKindKnown` enums ([issue 199](https://github.com/teloxide/teloxide/issues/199)).
- Extract `Bot` from `Arc` ([issue 216](https://github.com/teloxide/teloxide/issues/230)). - Extract `Bot` from `Arc` ([issue 216](https://github.com/teloxide/teloxide/issues/230)).
- Mark all the API types as `#[non_exhaustive]`.
- Replace all `mime_type: String` with `MimeWrapper`.
### Fixed
- Now methods which can send file to Telegram returns `tokio::io::Result<T>`. Early its could panic ([issue 216](https://github.com/teloxide/teloxide/issues/216)).
- If a bot wasn't triggered for several days, it stops responding ([issue 223](https://github.com/teloxide/teloxide/issues/223)).
## [0.2.0] - 2020-02-25 ## [0.2.0] - 2020-02-25
### Added ### Added

View file

@ -123,3 +123,4 @@ C: Into<String>, { ... }
## Misc ## Misc
1. Use `Into<...>` only where there exists at least one conversion **and** it will be logically to use. 1. Use `Into<...>` only where there exists at least one conversion **and** it will be logically to use.
2. Always mark a function as `#[must_use]` if its return value **must** be used. 2. Always mark a function as `#[must_use]` if its return value **must** be used.
3. `Box::pin(async [move] { ... })` instead of `async [move] { ... }.boxed()`.

View file

@ -1,7 +1,8 @@
# Contributing # 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). 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 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: To change the source code, fork the `dev` branch of this repository and work inside your own branch. Then send us a PR into `dev` 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 clippy --all --all-features --all-targets

View file

@ -1,6 +1,6 @@
[package] [package]
name = "teloxide" name = "teloxide"
version = "0.2.0" version = "0.3.1"
edition = "2018" edition = "2018"
description = "An elegant Telegram bots framework for Rust" description = "An elegant Telegram bots framework for Rust"
repository = "https://github.com/teloxide/teloxide" repository = "https://github.com/teloxide/teloxide"
@ -31,12 +31,16 @@ bincode-serializer = ["bincode"]
frunk- = ["frunk"] frunk- = ["frunk"]
macros = ["teloxide-macros"]
nightly = [] # currently used only for `README.md` tests
[dependencies] [dependencies]
serde_json = "1.0.55" serde_json = "1.0.55"
serde = { version = "1.0.114", features = ["derive"] } serde = { version = "1.0.114", features = ["derive"] }
tokio = { version = "0.2.21", features = ["full"] } tokio = { version = "0.2.21", features = ["fs", "stream"] }
tokio-util = { version = "0.3.1", features = ["full"] } tokio-util = "0.3.1"
reqwest = { version = "0.10.6", features = ["json", "stream"] } reqwest = { version = "0.10.6", features = ["json", "stream"] }
log = "0.4.8" log = "0.4.8"
@ -61,10 +65,19 @@ serde_cbor = { version = "0.11.1", optional = true }
bincode = { version = "1.3.1", optional = true } bincode = { version = "1.3.1", optional = true }
frunk = { version = "0.3.1", optional = true } frunk = { version = "0.3.1", optional = true }
teloxide-macros = "0.3.2" teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master", optional = true }
[dev-dependencies] [dev-dependencies]
smart-default = "0.6.0" smart-default = "0.6.0"
rand = "0.7.3" rand = "0.7.3"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
tokio = { version = "0.2.21", features = ["fs", "stream", "rt-threaded", "macros"] }
[package.metadata."docs.rs"]
all-features = true
[[test]]
name = "redis"
path = "tests/redis.rs"
required-features = ["redis-storage", "cbor-serializer", "bincode-serializer"]

172
README.md
View file

@ -1,52 +1,54 @@
<div align="center"> <div align="center">
<img src="ICON.png" width="250"/> <img src="ICON.png" width="250"/>
<h1>teloxide</h1> <h1>teloxide</h1>
<a href="https://docs.rs/teloxide/"> <a href="https://docs.rs/teloxide/">
<img src="https://img.shields.io/badge/docs.rs-v0.2.0-blue.svg"> <img src="https://docs.rs/teloxide/badge.svg">
</a> </a>
<a href="https://github.com/teloxide/teloxide/actions"> <a href="https://github.com/teloxide/teloxide/actions">
<img src="https://github.com/teloxide/teloxide/workflows/Continuous%20integration/badge.svg"> <img src="https://github.com/teloxide/teloxide/workflows/Continuous%20integration/badge.svg">
</a> </a>
<a href="https://crates.io/crates/teloxide"> <a href="https://teloxide.netlify.com">
<img src="https://img.shields.io/badge/crates.io-v0.2.0-orange.svg"> <img src="https://img.shields.io/badge/docs-dev-blue)">
</a> </a>
<a href="https://t.me/teloxide"> <a href="https://crates.io/crates/teloxide">
<img src="https://img.shields.io/badge/official%20chat-t.me%2Fteloxide-blueviolet"> <img src="https://img.shields.io/crates/v/teloxide.svg">
</a> </a>
<a href="https://core.telegram.org/bots/api"> <a href="https://core.telegram.org/bots/api">
<img src="https://img.shields.io/badge/API coverage-Up to 0.4.9 (inclusively)-green.svg"> <img src="https://img.shields.io/badge/API coverage-Up to 0.4.9 (inclusively)-green.svg">
</a> </a>
<a href="https://t.me/teloxide">
<img src="https://img.shields.io/badge/official%20chat-t.me%2Fteloxide-blueviolet">
</a>
A full-featured framework that empowers you to easily build [Telegram bots](https://telegram.org/blog/bot-revolution) using the [`async`/`.await`](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html) syntax in [Rust](https://www.rust-lang.org/). It handles all the difficult stuff so you can focus only on your business logic. A full-featured framework that empowers you to easily build [Telegram bots](https://telegram.org/blog/bot-revolution) using the [`async`/`.await`](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html) syntax in [Rust](https://www.rust-lang.org/). It handles all the difficult stuff so you can focus only on your business logic.
</div> </div>
## Table of contents ## Table of contents
- [Highlights](https://github.com/teloxide/teloxide#highlights) - [Highlights](#highlights)
- [Setting up your environment](https://github.com/teloxide/teloxide#setting-up-your-environment) - [Setting up your environment](#setting-up-your-environment)
- [API overview](https://github.com/teloxide/teloxide#api-overview) - [API overview](#api-overview)
- [The dices bot](https://github.com/teloxide/teloxide#the-dices-bot) - [The dices bot](#the-dices-bot)
- [Commands](https://github.com/teloxide/teloxide#commands) - [Commands](#commands)
- [Dialogues management](https://github.com/teloxide/teloxide#dialogues-management) - [Dialogues management](#dialogues-management)
- [Recommendations](https://github.com/teloxide/teloxide#recommendations) - [Recommendations](#recommendations)
- [Cargo features](https://github.com/teloxide/teloxide#cargo-features) - [Cargo features](#cargo-features)
- [FAQ](https://github.com/teloxide/teloxide#faq) - [FAQ](#faq)
- [Community bots](https://github.com/teloxide/teloxide#community-bots) - [Community bots](#community-bots)
- [Contributing](https://github.com/teloxide/teloxide#contributing) - [Contributing](#contributing)
## Highlights ## Highlights
- **Functioal reactive design.** teloxide has [functional reactive design], allowing you to declaratively manipulate streams of updates from Telegram using filters, maps, folds, zips, and a lot of [other adaptors]. - **Functional reactive design.** teloxide follows [functional reactive design], allowing you to declaratively manipulate streams of updates from Telegram using filters, maps, folds, zips, and a lot of [other adaptors].
[functional reactive design]: https://en.wikipedia.org/wiki/Functional_reactive_programming [functional reactive design]: https://en.wikipedia.org/wiki/Functional_reactive_programming
[other adaptors]: https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html [other adaptors]: https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html
- **Persistence.** Dialogues management is independent of how/where dialogues are stored: you can just replace one line and make them [persistent]. Out-of-the-box storages include [Redis]. - **Dialogues management subsystem.** We have designed our dialogues management subsystem to be easy-to-use, and, furthermore, to be agnostic of how/where dialogues are stored. For example, you can just replace a one line to achieve [persistence]. Out-of-the-box storages include [Redis].
[persistent]: https://en.wikipedia.org/wiki/Persistence_(computer_science) [persistence]: https://en.wikipedia.org/wiki/Persistence_(computer_science)
[Redis]: https://redis.io/ [Redis]: https://redis.io/
- **Strongly typed bot commands.** You can describe bot commands as enumerations, and then they'll be automatically constructed from strings. Just like you describe JSON structures in [serde-json] and command-line arguments in [structopt]. - **Strongly typed bot commands.** You can describe bot commands as enumerations, and then they'll be automatically constructed from strings — just like JSON structures in [serde-json] and command-line arguments in [structopt].
[structopt]: https://github.com/TeXitoi/structopt [structopt]: https://github.com/TeXitoi/structopt
[serde-json]: https://github.com/serde-rs/json [serde-json]: https://github.com/serde-rs/json
@ -62,7 +64,7 @@ $ export TELOXIDE_TOKEN=<Your token here>
# Windows # Windows
$ set TELOXIDE_TOKEN=<Your token here> $ set TELOXIDE_TOKEN=<Your token here>
``` ```
4. Be sure that you are up to date: 4. Make sure that your Rust compiler is up to date:
```bash ```bash
# If you're using stable # If you're using stable
$ rustup update stable $ rustup update stable
@ -73,26 +75,25 @@ $ rustup update nightly
$ rustup override set nightly $ rustup override set nightly
``` ```
5. Execute `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`: 5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`:
```toml ```toml
[dependencies] [dependencies]
teloxide = "0.2.0" teloxide = "0.3.1"
teloxide-macros = "0.3.2" teloxide-macros = "0.3.2"
log = "0.4.8" log = "0.4.8"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
tokio = "0.2.11" tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
futures = "0.3.5"
``` ```
## API overview ## API overview
### The dices bot ### The dices bot
This bot throws a dice on each incoming message: This bot replies with a dice throw to each received message:
([Full](https://github.com/teloxide/teloxide/blob/master/examples/dices_bot/src/main.rs)) ([Full](./examples/dices_bot/src/main.rs))
```rust ```rust,no_run
use teloxide::prelude::*; use teloxide::prelude::*;
#[tokio::main] #[tokio::main]
@ -102,21 +103,17 @@ async fn main() {
let bot = Bot::from_env(); let bot = Bot::from_env();
Dispatcher::new(bot) teloxide::repl(bot, |message| async move {
.messages_handler(|rx: DispatcherHandlerRx<Message>| { message.answer_dice().send().await?;
rx.for_each(|message| async move { ResponseResult::<()>::Ok(())
message.send_dice().send().await.log_on_error().await; })
}) .await;
})
.dispatch()
.await;
} }
``` ```
<div align="center"> <div align="center">
<kbd> <kbd>
<img src=https://github.com/teloxide/teloxide/raw/master/media/DICES_BOT.gif /> <img src=../../raw/master/media/DICES_BOT.gif />
</kbd> </kbd>
</div> </div>
@ -130,9 +127,9 @@ Commands are strongly typed and defined declaratively, similar to how we define
[structopt]: https://docs.rs/structopt/0.3.9/structopt/ [structopt]: https://docs.rs/structopt/0.3.9/structopt/
[serde-json]: https://github.com/serde-rs/json [serde-json]: https://github.com/serde-rs/json
([Full](https://github.com/teloxide/teloxide/blob/master/examples/simple_commands_bot/src/main.rs)) ([Full](./examples/simple_commands_bot/src/main.rs))
```rust ```rust,no_run
// Imports are omitted... use teloxide::{utils::command::BotCommand, prelude::*};
#[derive(BotCommand)] #[derive(BotCommand)]
#[command(rename = "lowercase", description = "These commands are supported:")] #[command(rename = "lowercase", description = "These commands are supported:")]
@ -159,36 +156,33 @@ async fn answer(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<(
Ok(()) Ok(())
} }
async fn handle_commands(rx: DispatcherHandlerRx<Message>) {
rx.commands::<Command, &str>(panic!("Insert here your bot's name"))
.for_each_concurrent(None, |(cx, command)| async move {
answer(cx, command).await.log_on_error().await;
})
.await;
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
// Setup is omitted... teloxide::enable_logging!();
} log::info!("Starting simple_commands_bot...");
let bot = Bot::from_env();
let bot_name: String = panic!("Your bot's name here");
teloxide::commands_repl(bot, bot_name, answer).await;
}
``` ```
<div align="center"> <div align="center">
<kbd> <kbd>
<img src=https://github.com/teloxide/teloxide/raw/master/media/SIMPLE_COMMANDS_BOT.gif /> <img src=../../raw/master/media/SIMPLE_COMMANDS_BOT.gif />
</kbd> </kbd>
</div> </div>
### Dialogues management ### Dialogues management
A dialogue is described by an enumeration, where each variant is one of possible dialogue's states. There are also _subtransition functions_, which turn a dialogue from one state to another, thereby forming a [FSM]. A dialogue is described by an enumeration where each variant is one of possible dialogue's states. There are also _subtransition functions_, which turn a dialogue from one state to another, thereby forming a [FSM].
[FSM]: https://en.wikipedia.org/wiki/Finite-state_machine [FSM]: https://en.wikipedia.org/wiki/Finite-state_machine
Below is a bot, which asks you three questions and then sends the answers back to you. First, let's start with an enumeration (a collection of our dialogue's states): Below is a bot that asks you three questions and then sends the answers back to you. First, let's start with an enumeration (a collection of our dialogue's states):
([dialogue_bot/src/dialogue/mod.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/dialogue/mod.rs)) ([dialogue_bot/src/dialogue/mod.rs](./examples/dialogue_bot/src/dialogue/mod.rs))
```rust ```rust,ignore
// Imports are omitted... // Imports are omitted...
#[derive(Transition, From)] #[derive(Transition, From)]
@ -206,13 +200,13 @@ impl Default for Dialogue {
} }
``` ```
When a user sends a message to our bot, and such a dialogue does not yet exist, `Dialogue::default()` is invoked, which is `Dialogue::Start`. Every time a message is received, an associated dialogue is extracted, and then passed to a corresponding subtransition function: When a user sends a message to our bot and such a dialogue does not exist yet, a `Dialogue::default()` is invoked, which is a `Dialogue::Start` in this case. Every time a message is received, an associated dialogue is extracted and then passed to a corresponding subtransition function:
<details> <details>
<summary>Dialogue::Start</summary> <summary>Dialogue::Start</summary>
([dialogue_bot/src/dialogue/states/start.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/dialogue/states/start.rs)) ([dialogue_bot/src/dialogue/states/start.rs](./examples/dialogue_bot/src/dialogue/states/start.rs))
```rust ```rust,ignore
// Imports are omitted... // Imports are omitted...
pub struct StartState; pub struct StartState;
@ -229,8 +223,8 @@ async fn start(_state: StartState, cx: TransitionIn, _ans: String) -> Transition
<details> <details>
<summary>Dialogue::ReceiveFullName</summary> <summary>Dialogue::ReceiveFullName</summary>
([dialogue_bot/src/dialogue/states/receive_full_name.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs)) ([dialogue_bot/src/dialogue/states/receive_full_name.rs](./examples/dialogue_bot/src/dialogue/states/receive_full_name.rs))
```rust ```rust,ignore
// Imports are omitted... // Imports are omitted...
#[derive(Generic)] #[derive(Generic)]
@ -252,8 +246,8 @@ async fn receive_full_name(
<details> <details>
<summary>Dialogue::ReceiveAge</summary> <summary>Dialogue::ReceiveAge</summary>
([dialogue_bot/src/dialogue/states/receive_age.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/dialogue/states/receive_age.rs)) ([dialogue_bot/src/dialogue/states/receive_age.rs](./examples/dialogue_bot/src/dialogue/states/receive_age.rs))
```rust ```rust,ignore
// Imports are omitted... // Imports are omitted...
#[derive(Generic)] #[derive(Generic)]
@ -285,8 +279,8 @@ async fn receive_age_state(
<details> <details>
<summary>Dialogue::ReceiveLocation</summary> <summary>Dialogue::ReceiveLocation</summary>
([dialogue_bot/src/dialogue/states/receive_location.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/dialogue/states/receive_location.rs)) ([dialogue_bot/src/dialogue/states/receive_location.rs](./examples/dialogue_bot/src/dialogue/states/receive_location.rs))
```rust ```rust,ignore
// Imports are omitted... // Imports are omitted...
#[derive(Generic)] #[derive(Generic)]
@ -309,16 +303,14 @@ async fn receive_location(
</details> </details>
All these subtransitions accept a corresponding state (one of the many variants of `Dialogue`), a context, and a textual message. They return `TransitionOut<Dialogue>`, e.g. a mapping from `<your state type>` to `Dialogue`. All these subtransition functions accept a corresponding state (one of the many variants of `Dialogue`), a context, and a textual message. They return `TransitionOut<Dialogue>`, e.g. a mapping from `<your state type>` to `Dialogue`.
Finally, the `main` function looks like this: Finally, the `main` function looks like this:
([dialogue_bot/src/main.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/main.rs)) ([dialogue_bot/src/main.rs](./examples/dialogue_bot/src/main.rs))
```rust ```rust,ignore
// Imports are omitted... // Imports are omitted...
type In = DialogueWithCx<Message, Dialogue, Infallible>;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
teloxide::enable_logging!(); teloxide::enable_logging!();
@ -326,15 +318,10 @@ async fn main() {
let bot = Bot::from_env(); let bot = Bot::from_env();
Dispatcher::new(bot) teloxide::dialogues_repl(bot, |message, dialogue| async move {
.messages_handler(DialogueDispatcher::new( handle_message(message, dialogue).await.expect("Something wrong with the bot!")
|DialogueWithCx { cx, dialogue }: In| async move { })
let dialogue = dialogue.expect("std::convert::Infallible"); .await;
handle_message(cx, dialogue).await.expect("Something wrong with the bot!")
},
))
.dispatch()
.await;
} }
async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> { async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> {
@ -346,16 +333,15 @@ async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> Transi
Some(ans) => dialogue.react(cx, ans).await, Some(ans) => dialogue.react(cx, ans).await,
} }
} }
``` ```
<div align="center"> <div align="center">
<kbd> <kbd>
<img src=https://github.com/teloxide/teloxide/raw/master/media/DIALOGUE_BOT.gif /> <img src=../../raw/master/media/DIALOGUE_BOT.gif />
</kbd> </kbd>
</div> </div>
[More examples!](https://github.com/teloxide/teloxide/tree/master/examples) [More examples!](./examples)
## Recommendations ## Recommendations
- Use this pattern: - Use this pattern:
@ -380,7 +366,7 @@ async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> Transi
} }
``` ```
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 due to the `#[tokio::main]` macro. However, the examples in this README use the second variant for brevity.
## Cargo features ## Cargo features
@ -411,11 +397,15 @@ A: No, only the bots API.
Q: Why Rust? 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. A: Most programming languages have their own implementations of Telegram bots frameworks, so why not Rust? We think Rust provides a good enough ecosystem and the language for it to be suitable for writing bots.
UPD: The current design relies on wide and deep trait bounds, thereby increasing cognitive complexity. It can be avoided using [mux-stream], but currently the stable Rust channel doesn't support necessary features to use [mux-stream] conveniently. Furthermore, the [mux-stream] could help to make a library out of teloxide, not a framework, since the design in this case could be defined by just combining streams of updates.
[mux-stream]: https://github.com/Hirrolot/mux-stream
Q: Can I use webhooks? 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 [`examples/ngrok_ping_pong_bot`](examples/ngrok_ping_pong_bot/src/main.rs) and [`examples/heroku_ping_pong_bot`](examples/heroku_ping_pong_bot/src/main.rs). A: teloxide doesn't provide special API for working with webhooks due to their nature with lots of subtle settings. Instead, you should setup your webhook by yourself, as shown in [`examples/ngrok_ping_pong_bot`](./examples/ngrok_ping_pong_bot/src/main.rs) and [`examples/heroku_ping_pong_bot`](./examples/heroku_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)
@ -433,9 +423,11 @@ A: Yes. You can setup any logger, for example, [fern], e.g. teloxide has no spec
## Community bots ## Community bots
Feel free to push your own bot into our collection! 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) - [_steadylearner/subreddit_reader_](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/subreddit_reader)
- [_vzmuinebot -- Telegram bot for food menu navigate_](https://github.com/ArtHome12/vzmuinebot) - [_ArtHome12/vzmuinebot -- Telegram bot for food menu navigate_](https://github.com/ArtHome12/vzmuinebot)
- [_Tepe -- A CLI to command a bot to send messages and files over Telegram_](https://lib.rs/crates/tepe) - [_Hermitter/tepe -- A CLI to command a bot to send messages and files over Telegram_](https://github.com/Hermitter/tepe)
- [_ArtHome12/cognito_bot -- The bot is designed to anonymize messages to a group_](https://github.com/ArtHome12/cognito_bot)
- [_GoldsteinE/tg-vimhelpbot -- Link `:help` for Vim in Telegram_](https://github.com/GoldsteinE/tg-vimhelpbot)
## 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

@ -2,11 +2,11 @@
Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run` to run an example. Don't forget to initialise the `TELOXIDE_TOKEN` environmental variable. Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run` to run an example. Don't forget to initialise the `TELOXIDE_TOKEN` environmental variable.
| Bot | Description | | Bot | Description |
|---|-----------| |---|-----------|
| [dices_bot](dices_bot) | This bot throws a dice on each incoming message. | | [dices_bot](dices_bot) | Throws a dice on each incoming message. |
| [ngrok_ping_pong_bot](ngrok_ping_pong_bot) | The ngrok version of ping-pong-bot that uses webhooks. | | [ngrok_ping_pong_bot](ngrok_ping_pong_bot) | The ngrok version of ping-pong-bot that uses webhooks. |
| [heroku_ping_pong_bot](heroku_ping_pong_bot) | The Heroku version of ping-pong-bot that uses webhooks. | | [heroku_ping_pong_bot](heroku_ping_pong_bot) | The Heroku version of ping-pong-bot that uses webhooks. |
| [simple_commands_bot](simple_commands_bot) | Shows how to deal with bot's commands. | | [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. | | [redis_remember_bot](redis_remember_bot) | Uses `RedisStorage` instead of `InMemStorage`. |
| [dialogue_bot](dialogue_bot) | Drive a dialogue with a user using a type-safe finite automaton. | | [dialogue_bot](dialogue_bot) | How to deal with dialogues. |
| [admin_bot](admin_bot) | A bot, which can ban, kick, and mute on a command. | | [admin_bot](admin_bot) | 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) | How to deal with shared state. |

View file

@ -8,10 +8,9 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
futures = "0.3.4"
tokio = "0.2.9"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
teloxide = { path = "../../" } tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../", features = ["macros"] }
[profile.release] [profile.release]
lto = true lto = true

View file

@ -2,8 +2,6 @@ use std::str::FromStr;
use teloxide::{prelude::*, types::ChatPermissions, utils::command::BotCommand}; use teloxide::{prelude::*, types::ChatPermissions, utils::command::BotCommand};
use futures::future;
// Derive BotCommand to parse text with a command into this enumeration. // Derive BotCommand to parse text with a command into this enumeration.
// //
// 1. rename = "lowercase" turns all the commands into lowercase letters. // 1. rename = "lowercase" turns all the commands into lowercase letters.
@ -130,16 +128,6 @@ async fn action(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<(
Ok(()) Ok(())
} }
async fn handle_commands(rx: DispatcherHandlerRx<Message>) {
rx.filter(|cx| future::ready(cx.update.chat.is_group()))
.commands::<Command, &str>(panic!("Insert here your bot's name"))
// Execute all incoming commands concurrently:
.for_each_concurrent(None, |(cx, command)| async move {
action(cx, command).await.log_on_error().await;
})
.await;
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
run().await; run().await;
@ -151,5 +139,6 @@ async fn run() {
let bot = Bot::from_env(); let bot = Bot::from_env();
Dispatcher::new(bot).messages_handler(handle_commands).dispatch().await let bot_name: String = panic!("Your bot's name here");
teloxide::commands_repl(bot, bot_name, action).await;
} }

View file

@ -8,16 +8,18 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
tokio = "0.2.9" pretty_env_logger = "0.4.0"
frunk = "0.3.1" frunk = "0.3.1"
frunk_core = "0.3.1" frunk_core = "0.3.1"
pretty_env_logger = "0.4.0"
futures = "0.3.5" futures = "0.3.5"
derive_more = "0.99.9" tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../", features = ["frunk"] } teloxide = { path = "../../", features = ["frunk"] }
teloxide-macros = "0.3.2" teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master" }
derive_more = "0.99.9"
[profile.release] [profile.release]
lto = true lto = true

View file

@ -23,11 +23,8 @@ extern crate frunk;
mod dialogue; mod dialogue;
use crate::dialogue::Dialogue; use crate::dialogue::Dialogue;
use std::convert::Infallible;
use teloxide::prelude::*; use teloxide::prelude::*;
type In = DialogueWithCx<Message, Dialogue, Infallible>;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
run().await; run().await;
@ -39,15 +36,10 @@ async fn run() {
let bot = Bot::from_env(); let bot = Bot::from_env();
Dispatcher::new(bot) teloxide::dialogues_repl(bot, |message, dialogue| async move {
.messages_handler(DialogueDispatcher::new( handle_message(message, dialogue).await.expect("Something wrong with the bot!")
|DialogueWithCx { cx, dialogue }: In| async move { })
let dialogue = dialogue.expect("std::convert::Infallible"); .await;
handle_message(cx, dialogue).await.expect("Something wrong with the bot!")
},
))
.dispatch()
.await;
} }
async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> { async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> {

View file

@ -8,8 +8,8 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
tokio = "0.2.9"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../" } teloxide = { path = "../../" }
[profile.release] [profile.release]

View file

@ -13,12 +13,9 @@ async fn run() {
let bot = Bot::from_env(); let bot = Bot::from_env();
Dispatcher::new(bot) teloxide::repl(bot, |message| async move {
.messages_handler(|rx: DispatcherHandlerRx<Message>| { message.answer_dice().send().await?;
rx.for_each(|message| async move { respond(())
message.send_dice().send().await.log_on_error().await; })
}) .await;
})
.dispatch()
.await;
} }

View file

@ -8,10 +8,9 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
futures = "0.3.4"
tokio = "0.2.9"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
teloxide = { path = "../../" } tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../" }
# Used to setup a webhook # Used to setup a webhook
warp = "0.2.2" warp = "0.2.2"

View file

@ -73,15 +73,14 @@ async fn run() {
let bot = Bot::from_env(); let bot = Bot::from_env();
Dispatcher::new(bot.clone()) let cloned_bot = bot.clone();
.messages_handler(|rx: DispatcherHandlerRx<Message>| { teloxide::repl_with_listener(
rx.for_each(|message| async move { bot,
message.answer_str("pong").await.log_on_error().await; |message| async move {
}) message.answer_str("pong").await?;
}) ResponseResult::<()>::Ok(())
.dispatch_with_listener( },
webhook(bot).await, webhook(cloned_bot).await,
LoggingErrorHandler::with_custom_text("An error from the update listener"), )
) .await;
.await;
} }

View file

@ -8,10 +8,8 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
futures = "0.3.4"
tokio = "0.2.9"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../" } teloxide = { path = "../../" }
# Used to setup a webhook # Used to setup a webhook

View file

@ -55,15 +55,14 @@ async fn run() {
let bot = Bot::from_env(); let bot = Bot::from_env();
Dispatcher::new(bot.clone()) let cloned_bot = bot.clone();
.messages_handler(|rx: DispatcherHandlerRx<Message>| { teloxide::repl_with_listener(
rx.for_each(|message| async move { bot,
message.answer_str("pong").await.log_on_error().await; |message| async move {
}) message.answer_str("pong").await?;
}) ResponseResult::<()>::Ok(())
.dispatch_with_listener( },
webhook(bot).await, webhook(cloned_bot).await,
LoggingErrorHandler::with_custom_text("An error from the update listener"), )
) .await;
.await;
} }

View file

@ -5,11 +5,13 @@ authors = ["Maximilian Siling <mouse-art@ya.ru>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
tokio = "0.2.9" log = "0.4.8"
pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
# You can also choose "cbor-serializer" or built-in JSON serializer # You can also choose "cbor-serializer" or built-in JSON serializer
teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer"] } teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer"] }
teloxide-macros = "0.3.2" teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master" }
serde = "1.0.104" serde = "1.0.104"
futures = "0.3.5" futures = "0.3.5"

View file

@ -8,7 +8,7 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
tokio = "0.2.9"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../" }
lazy_static = "1.4.0" lazy_static = "1.4.0"
teloxide = { path = "../../" }

View file

@ -8,7 +8,6 @@ edition = "2018"
[dependencies] [dependencies]
log = "0.4.8" log = "0.4.8"
futures = "0.3.4"
tokio = "0.2.9"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
teloxide = { path = "../../" } tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../", features = ["macros"] }

View file

@ -25,14 +25,6 @@ async fn answer(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<(
Ok(()) Ok(())
} }
async fn handle_commands(rx: DispatcherHandlerRx<Message>) {
rx.commands::<Command, &str>(panic!("Insert here your bot's name"))
.for_each_concurrent(None, |(cx, command)| async move {
answer(cx, command).await.log_on_error().await;
})
.await;
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
run().await; run().await;
@ -44,5 +36,6 @@ async fn run() {
let bot = Bot::from_env(); let bot = Bot::from_env();
Dispatcher::new(bot).messages_handler(handle_commands).dispatch().await; let bot_name: String = panic!("Your bot's name here");
teloxide::commands_repl(bot, bot_name, answer).await;
} }

12
netlify.toml Normal file
View file

@ -0,0 +1,12 @@
[build]
# Directory (relative to root of your repo) that contains the deploy-ready
# HTML files and assets generated by the build. If a base directory has
# been specified, include it in the publish directory path.
publish = "target/doc"
# Default build command.
command = 'curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly --profile minimal && source $HOME/.cargo/env && RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --no-deps --all-features'
[[redirects]]
from = "/*"
to = "/teloxide"

View file

@ -2,26 +2,27 @@ use crate::{
requests::{ requests::{
AddStickerToSet, AnswerCallbackQuery, AnswerInlineQuery, AnswerPreCheckoutQuery, AddStickerToSet, AnswerCallbackQuery, AnswerInlineQuery, AnswerPreCheckoutQuery,
AnswerShippingQuery, CreateNewStickerSet, DeleteChatPhoto, DeleteChatStickerSet, AnswerShippingQuery, CreateNewStickerSet, DeleteChatPhoto, DeleteChatStickerSet,
DeleteMessage, DeleteStickerFromSet, DeleteWebhook, EditMessageCaption, DeleteMessage, DeleteStickerFromSet, DeleteWebhook, EditInlineMessageCaption,
EditMessageLiveLocation, EditMessageMedia, EditMessageReplyMarkup, EditMessageText, EditInlineMessageLiveLocation, EditInlineMessageMedia, EditInlineMessageReplyMarkup,
ExportChatInviteLink, ForwardMessage, GetChat, GetChatAdministrators, GetChatMember, EditInlineMessageText, EditMessageCaption, EditMessageLiveLocation, EditMessageMedia,
GetChatMembersCount, GetFile, GetGameHighScores, GetMe, GetMyCommands, GetStickerSet, EditMessageReplyMarkup, EditMessageText, ExportChatInviteLink, ForwardMessage, GetChat,
GetUpdates, GetUserProfilePhotos, GetWebhookInfo, KickChatMember, LeaveChat, GetChatAdministrators, GetChatMember, GetChatMembersCount, GetFile, GetGameHighScores,
PinChatMessage, PromoteChatMember, RestrictChatMember, SendAnimation, SendAudio, GetMe, GetMyCommands, GetStickerSet, GetUpdates, GetUserProfilePhotos, GetWebhookInfo,
SendChatAction, SendChatActionKind, SendContact, SendDice, SendDocument, SendGame, KickChatMember, LeaveChat, PinChatMessage, PromoteChatMember, RestrictChatMember,
SendInvoice, SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendPoll, SendSticker, SendAnimation, SendAudio, SendChatAction, SendChatActionKind, SendContact, SendDice,
SendVenue, SendVideo, SendVideoNote, SendVoice, SetChatAdministratorCustomTitle, SendDocument, SendGame, SendInvoice, SendLocation, SendMediaGroup, SendMessage, SendPhoto,
SetChatDescription, SetChatPermissions, SetChatPhoto, SetChatStickerSet, SetChatTitle, SendPoll, SendSticker, SendVenue, SendVideo, SendVideoNote, SendVoice,
SetGameScore, SetMyCommands, SetStickerPositionInSet, SetStickerSetThumb, SetWebhook, SetChatAdministratorCustomTitle, SetChatDescription, SetChatPermissions, SetChatPhoto,
StopMessageLiveLocation, StopPoll, UnbanChatMember, UnpinChatMessage, UploadStickerFile, SetChatStickerSet, SetChatTitle, SetGameScore, SetMyCommands, SetStickerPositionInSet,
SetStickerSetThumb, SetWebhook, StopInlineMessageLiveLocation, StopMessageLiveLocation,
StopPoll, UnbanChatMember, UnpinChatMessage, UploadStickerFile,
}, },
types::{ types::{
BotCommand, ChatId, ChatOrInlineMessage, ChatPermissions, InlineQueryResult, InputFile, BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia,
InputMedia, LabeledPrice, StickerType, LabeledPrice, ParseMode, StickerType, TargetMessage,
}, },
Bot, Bot,
}; };
use std::ops::Deref;
impl Bot { impl Bot {
/// Use this method to receive incoming updates using long polling ([wiki]). /// Use this method to receive incoming updates using long polling ([wiki]).
@ -115,12 +116,10 @@ impl Bot {
C: Into<ChatId>, C: Into<ChatId>,
T: Into<String>, T: Into<String>,
{ {
match self.parse_mode.deref() { self.with_default_parse_mode_if_specified(
None => SendMessage::new(self.clone(), chat_id, text), SendMessage::new(self.clone(), chat_id, text),
Some(parse_mode) => { SendMessage::parse_mode,
SendMessage::new(self.clone(), chat_id, text).parse_mode(*parse_mode.deref()) )
}
}
} }
/// Use this method to forward messages of any kind. /// Use this method to forward messages of any kind.
@ -157,16 +156,17 @@ impl Bot {
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target supergroup or channel (in the format `@channelusername`). /// target supergroup or channel (in the format `@channelusername`).
/// - `photo`: Photo to send. /// - `photo`: Pass [`InputFile::FileId`] to send a photo that exists on
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
/// Telegram to get a photo from the Internet (5MB max.), pass
/// [`InputFile::File`] to upload a picture from the file system or
/// [`InputFile::Memory`] to upload a photo from memory (10MB max.
/// each). [More info on Sending Files »].
/// ///
/// Pass [`InputFile::File`] to send a photo that exists on
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
/// Telegram to get a .webp file from the Internet, or upload a new one
/// using [`InputFile::FileId`]. [More info on Sending Files »].
///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
/// ///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
/// ///
@ -179,19 +179,32 @@ impl Bot {
where where
C: Into<ChatId>, C: Into<ChatId>,
{ {
match self.parse_mode.deref() { self.with_default_parse_mode_if_specified(
None => SendPhoto::new(self.clone(), chat_id, photo), SendPhoto::new(self.clone(), chat_id, photo),
Some(parse_mode) => { SendPhoto::parse_mode,
SendPhoto::new(self.clone(), chat_id, photo).parse_mode(*parse_mode.deref()) )
}
}
} }
/// Use this method to send audio files
/// ///
/// [The official docs](https://core.telegram.org/bots/api#sendaudio).
/// ///
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target supergroup or channel (in the format `@channelusername`). /// target supergroup or channel (in the format `@channelusername`).
/// - `audio`: Pass [`InputFile::FileId`] to send an audio file that
/// exists on the Telegram servers (recommended), pass an
/// [`InputFile::Url`] for Telegram to get a file from the Internet
/// (20MB max.), pass [`InputFile::File`] to upload a file from the file
/// system or [`InputFile::Memory`] to upload a file from memory (50MB
/// max. each). [More info on Sending Files »].
///
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
/// ///
/// # Notes /// # Notes
/// Uses [a default parse mode] if specified in [`BotBuilder`]. /// Uses [a default parse mode] if specified in [`BotBuilder`].
@ -202,30 +215,30 @@ impl Bot {
where where
C: Into<ChatId>, C: Into<ChatId>,
{ {
match self.parse_mode.deref() { self.with_default_parse_mode_if_specified(
None => SendAudio::new(self.clone(), chat_id, audio), SendAudio::new(self.clone(), chat_id, audio),
Some(parse_mode) => { SendAudio::parse_mode,
SendAudio::new(self.clone(), chat_id, audio).parse_mode(*parse_mode.deref()) )
}
}
} }
/// Use this method to send general files. /// Use this method to send general files.
/// ///
/// Bots can currently send files of any type of up to 50 MB in size, this
/// limit may be changed in the future.
///
/// [The official docs](https://core.telegram.org/bots/api#senddocument). /// [The official docs](https://core.telegram.org/bots/api#senddocument).
/// ///
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target supergroup or channel (in the format `@channelusername`). /// target supergroup or channel (in the format `@channelusername`).
/// - `document`: File to send. /// - `document`: Pass [`InputFile::FileId`] to send a file that exists on
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
/// Telegram to get a file from the Internet (20MB max.), pass
/// [`InputFile::File`] to upload a file from the file system or
/// [`InputFile::Memory`] to upload a file from memory (50MB max. each).
/// [More info on Sending Files »].
/// ///
/// Pass a file_id as String to send a file that exists on the /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// Telegram servers (recommended), pass an HTTP URL as a String for /// [`InputFile::Url`]: crate::types::InputFile::Url
/// Telegram to get a file from the Internet, or upload a new one using /// [`InputFile::File`]: crate::types::InputFile::File
/// `multipart/form-data`. [More info on Sending Files »]. /// [`InputFile::Memory`]: crate::types::InputFile::Memory
/// ///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
/// ///
@ -238,35 +251,33 @@ impl Bot {
where where
C: Into<ChatId>, C: Into<ChatId>,
{ {
match self.parse_mode.deref() { self.with_default_parse_mode_if_specified(
None => SendDocument::new(self.clone(), chat_id, document), SendDocument::new(self.clone(), chat_id, document),
Some(parse_mode) => { SendDocument::parse_mode,
SendDocument::new(self.clone(), chat_id, document).parse_mode(*parse_mode.deref()) )
}
}
} }
/// Use this method to send video files, Telegram clients support mp4 videos /// Use this method to send video files, Telegram clients support mp4 videos
/// (other formats may be sent as Document). /// (other formats may be sent as Document).
/// ///
/// Bots can currently send video files of up to 50 MB in size, this
/// limit may be changed in the future.
///
/// [The official docs](https://core.telegram.org/bots/api#sendvideo). /// [The official docs](https://core.telegram.org/bots/api#sendvideo).
/// ///
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target supergroup or channel (in the format `@channelusername`). /// target supergroup or channel (in the format `@channelusername`).
/// - `video`: Video to sent. /// - `video`: Pass [`InputFile::FileId`] to send a file that exists on
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
/// Telegram to get a file from the Internet (20MB max.), pass
/// [`InputFile::File`] to upload a file from the file system or
/// [`InputFile::Memory`] to upload a file from memory (50MB max. each).
/// [More info on Sending Files »].
/// ///
/// Pass [`InputFile::File`] to send a file that exists on
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
/// Telegram to get a .webp file from the Internet, or upload a new one
/// using [`InputFile::FileId`]. [More info on Sending Files »].
///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
/// ///
/// # Notes /// # Notes
/// Uses [a default parse mode] if specified in [`BotBuilder`]. /// Uses [a default parse mode] if specified in [`BotBuilder`].
@ -277,26 +288,33 @@ impl Bot {
where where
C: Into<ChatId>, C: Into<ChatId>,
{ {
match self.parse_mode.deref() { self.with_default_parse_mode_if_specified(
None => SendVideo::new(self.clone(), chat_id, video), SendVideo::new(self.clone(), chat_id, video),
Some(parse_mode) => { SendVideo::parse_mode,
SendVideo::new(self.clone(), chat_id, video).parse_mode(*parse_mode.deref()) )
}
}
} }
/// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video
/// without sound). /// without sound).
/// ///
/// Bots can currently send animation files of up to 50 MB in size, this
/// limit may be changed in the future.
///
/// [The official docs](https://core.telegram.org/bots/api#sendanimation). /// [The official docs](https://core.telegram.org/bots/api#sendanimation).
/// ///
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target supergroup or channel (in the format `@channelusername`). /// target supergroup or channel (in the format `@channelusername`).
/// - `animation`: Animation to send. /// - `animation`: Pass [`InputFile::FileId`] to send a file that exists
/// on the Telegram servers (recommended), pass an [`InputFile::Url`]
/// for Telegram to get a file from the Internet (20MB max.), pass
/// [`InputFile::File`] to upload a file from the file system or
/// [`InputFile::Memory`] to upload a file from memory (50MB max. each).
/// [More info on Sending Files »].
///
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
/// ///
/// # Notes /// # Notes
/// Uses [a default parse mode] if specified in [`BotBuilder`]. /// Uses [a default parse mode] if specified in [`BotBuilder`].
@ -307,21 +325,17 @@ impl Bot {
where where
C: Into<ChatId>, C: Into<ChatId>,
{ {
match self.parse_mode.deref() { self.with_default_parse_mode_if_specified(
None => SendAnimation::new(self.clone(), chat_id, animation), SendAnimation::new(self.clone(), chat_id, animation),
Some(parse_mode) => { SendAnimation::parse_mode,
SendAnimation::new(self.clone(), chat_id, animation).parse_mode(*parse_mode.deref()) )
}
}
} }
/// Use this method to send audio files, if you want Telegram clients to /// Use this method to send audio files, if you want Telegram clients to
/// display the file as a playable voice message. /// display the file as a playable voice message.
/// ///
/// For this to work, your audio must be in an .ogg file encoded with OPUS /// For this to work, your audio must be in an .ogg file encoded with OPUS
/// (other formats may be sent as [`Audio`] or [`Document`]). Bots can /// (other formats may be sent as [`Audio`] or [`Document`]).
/// currently send voice messages of up to 50 MB in size, this limit may
/// be changed in the future.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#sendvoice). /// [The official docs](https://core.telegram.org/bots/api#sendvoice).
/// ///
@ -331,16 +345,18 @@ impl Bot {
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target supergroup or channel (in the format `@channelusername`). /// target supergroup or channel (in the format `@channelusername`).
/// - `voice`: Audio file to send. /// - `voice`: Pass [`InputFile::FileId`] to send a file that exists on
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
/// Telegram to get a file from the Internet (20MB max.), pass
/// [`InputFile::File`] to upload a file from the file system or
/// [`InputFile::Memory`] to upload a file from memory (50MB max. each).
/// [More info on Sending Files »].
/// ///
/// Pass [`InputFile::File`] to send a file that exists on
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
/// Telegram to get a .webp file from the Internet, or upload a new one
/// using [`InputFile::FileId`]. [More info on Sending Files »].
///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
/// ///
/// # Notes /// # Notes
@ -352,12 +368,10 @@ impl Bot {
where where
C: Into<ChatId>, C: Into<ChatId>,
{ {
match self.parse_mode.deref() { self.with_default_parse_mode_if_specified(
None => SendVoice::new(self.clone(), chat_id, voice), SendVoice::new(self.clone(), chat_id, voice),
Some(parse_mode) => { SendVoice::parse_mode,
SendVoice::new(self.clone(), chat_id, voice).parse_mode(*parse_mode.deref()) )
}
}
} }
/// As of [v.4.0], Telegram clients support rounded square mp4 videos of up /// As of [v.4.0], Telegram clients support rounded square mp4 videos of up
@ -365,20 +379,23 @@ impl Bot {
/// ///
/// [The official docs](https://core.telegram.org/bots/api#sendvideonote). /// [The official docs](https://core.telegram.org/bots/api#sendvideonote).
/// ///
/// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope
///
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target supergroup or channel (in the format `@channelusername`). /// target supergroup or channel (in the format `@channelusername`).
/// - `video_note`: Video note to send. /// - `video_note`: Pass [`InputFile::FileId`] to send a file that exists
/// on the Telegram servers (recommended), pass an [`InputFile::Url`]
/// for Telegram to get a file from the Internet (20MB max.), pass
/// [`InputFile::File`] to upload a file from the file system or
/// [`InputFile::Memory`] to upload a file from memory (50MB max. each).
/// [More info on Sending Files »].
/// ///
/// Pass [`InputFile::File`] to send a file that exists on the Telegram
/// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a
/// .webp file from the Internet, or upload a new one using
/// [`InputFile::FileId`]. [More info on Sending Files »].
///
/// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn send_video_note<C>(&self, chat_id: C, video_note: InputFile) -> SendVideoNote pub fn send_video_note<C>(&self, chat_id: C, video_note: InputFile) -> SendVideoNote
@ -395,8 +412,8 @@ impl Bot {
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target supergroup or channel (in the format `@channelusername`). /// target supergroup or channel (in the format `@channelusername`).
/// - `media`: A JSON-serialized array describing photos and videos to be /// - `media`: A vector of photos and videos as [`InputMedia`] to be sent,
/// sent, must include 210 items. /// must include 210 items.
pub fn send_media_group<C, M>(&self, chat_id: C, media: M) -> SendMediaGroup pub fn send_media_group<C, M>(&self, chat_id: C, media: M) -> SendMediaGroup
where where
C: Into<ChatId>, C: Into<ChatId>,
@ -424,42 +441,89 @@ impl Bot {
/// Use this method to edit live location messages. /// Use this method to edit live location messages.
/// ///
/// A location can be edited until its live_period expires or editing is /// A location can be edited until its live_period expires or editing is
/// explicitly disabled by a call to stopMessageLiveLocation. On success, if /// explicitly disabled by a call to stopMessageLiveLocation. On success,
/// the edited message was sent by the bot, the edited [`Message`] is /// the edited [`Message`] is returned.
/// returned, otherwise [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). /// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation).
/// ///
/// [`Message`]: crate::types::Message
///
/// # Params /// # Params
/// - `latitude`: Latitude of new location. /// - `latitude`: Latitude of new location.
/// - `longitude`: Longitude of new location. /// - `longitude`: Longitude of new location.
/// pub fn edit_message_live_location<C>(
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
pub fn edit_message_live_location(
&self, &self,
chat_or_inline_message: ChatOrInlineMessage, chat_id: C,
message_id: i32,
latitude: f32, latitude: f32,
longitude: f32, longitude: f32,
) -> EditMessageLiveLocation { ) -> EditMessageLiveLocation
EditMessageLiveLocation::new(self.clone(), chat_or_inline_message, latitude, longitude) where
C: Into<ChatId>,
{
EditMessageLiveLocation::new(self.clone(), chat_id, message_id, latitude, longitude)
}
/// Use this method to edit live location messages sent via the bot.
///
/// A location can be edited until its live_period expires or editing is
/// explicitly disabled by a call to stopMessageLiveLocation. On success,
/// [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation).
///
/// [`True`]: crate::types::True
///
/// # Params
/// - `latitude`: Latitude of new location.
/// - `longitude`: Longitude of new location.
pub fn edit_inline_message_live_location<I>(
&self,
inline_message_id: I,
latitude: f32,
longitude: f32,
) -> EditInlineMessageLiveLocation
where
I: Into<String>,
{
EditInlineMessageLiveLocation::new(self.clone(), inline_message_id, latitude, longitude)
} }
/// Use this method to stop updating a live location message before /// Use this method to stop updating a live location message before
/// `live_period` expires. /// `live_period` expires.
/// ///
/// On success, if the message was sent by the bot, the sent [`Message`] is /// On success, the sent [`Message`] is returned.
/// returned, otherwise [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). /// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation).
/// ///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True pub fn stop_message_live_location<C>(
pub fn stop_message_live_location(
&self, &self,
chat_or_inline_message: ChatOrInlineMessage, chat_id: C,
) -> StopMessageLiveLocation { message_id: i32,
StopMessageLiveLocation::new(self.clone(), chat_or_inline_message) ) -> StopMessageLiveLocation
where
C: Into<ChatId>,
{
StopMessageLiveLocation::new(self.clone(), chat_id, message_id)
}
/// Use this method to stop updating a live location message (sent via the
/// bot) before `live_period` expires.
///
/// On success, [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation).
///
/// [`True`]: crate::types::True
pub fn stop_inline_message_live_location<I>(
&self,
inline_message_id: I,
) -> StopInlineMessageLiveLocation
where
I: Into<String>,
{
StopInlineMessageLiveLocation::new(self.clone(), inline_message_id)
} }
/// Use this method to send information about a venue. /// Use this method to send information about a venue.
@ -513,21 +577,32 @@ impl Bot {
/// ///
/// [The official docs](https://core.telegram.org/bots/api#sendpoll). /// [The official docs](https://core.telegram.org/bots/api#sendpoll).
/// ///
///
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target supergroup or channel (in the format `@channelusername`). /// target supergroup or channel (in the format `@channelusername`).
/// - `question`: Poll question, 1-255 characters. /// - `question`: Poll question, 1-255 characters.
/// - `options`: List of answer options, 2-10 strings 1-100 characters /// - `options`: List of answer options, 2-10 strings 1-100 characters
/// each. /// each.
///
/// # Notes
/// Uses [a default parse mode] ([`SendPoll::explanation_parse_mode`]) if
/// specified in [`BotBuilder`].
///
/// [a default parse mode]: crate::BotBuilder::parse_mode
/// [`BotBuilder`]: crate::BotBuilder
/// [`SendPoll::explanation_parse_mode`]:
/// [`SendPoll::explanation_parse_mode`]:
/// crate::types::SendPoll::explanation_parse_mode
pub fn send_poll<C, Q, O>(&self, chat_id: C, question: Q, options: O) -> SendPoll pub fn send_poll<C, Q, O>(&self, chat_id: C, question: Q, options: O) -> SendPoll
where where
C: Into<ChatId>, C: Into<ChatId>,
Q: Into<String>, Q: Into<String>,
O: Into<Vec<String>>, O: Into<Vec<String>>,
{ {
// FIXME: parse_mode self.with_default_parse_mode_if_specified(
SendPoll::new(self.clone(), chat_id, question, options) SendPoll::new(self.clone(), chat_id, question, options),
SendPoll::explanation_parse_mode,
)
} }
/// Use this method when you need to tell the user that something is /// Use this method when you need to tell the user that something is
@ -746,7 +821,14 @@ impl Bot {
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target supergroup or channel (in the format `@channelusername`). /// target supergroup or channel (in the format `@channelusername`).
/// - `photo`: New chat photo, uploaded using `multipart/form-data`. /// - `photo`: New chat photo, pass [`InputFile::File`] to upload a file
/// from the file system or [`InputFile::Memory`] to upload a file from
/// memory (10MB max. each). [More info on Sending Files »].
///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn set_chat_photo<C>(&self, chat_id: C, photo: InputFile) -> SetChatPhoto pub fn set_chat_photo<C>(&self, chat_id: C, photo: InputFile) -> SetChatPhoto
where where
C: Into<ChatId>, C: Into<ChatId>,
@ -986,45 +1068,105 @@ impl Bot {
/// Use this method to edit text and game messages. /// Use this method to edit text and game messages.
/// ///
/// On success, if edited message is sent by the bot, the edited [`Message`] /// On success, the edited [`Message`] is returned.
/// is returned, otherwise [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagetext). /// [The official docs](https://core.telegram.org/bots/api#editmessagetext).
/// ///
/// # Params
/// - New text of the message.
///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True ///
/// # Params
///
/// - `chat_id`: Unique identifier for the target chat or username of the
/// target channel (in the format `@channelusername`).
/// - `message_id`: Identifier of the message to edit.
/// - `text`: New text of the message.
/// ///
/// # Notes /// # Notes
///
/// Uses [a default parse mode] if specified in [`BotBuilder`]. /// Uses [a default parse mode] if specified in [`BotBuilder`].
/// ///
/// [a default parse mode]: crate::BotBuilder::parse_mode /// [a default parse mode]: crate::BotBuilder::parse_mode
/// [`BotBuilder`]: crate::BotBuilder /// [`BotBuilder`]: crate::BotBuilder
pub fn edit_message_text<T>( pub fn edit_message_text<C, T>(&self, chat_id: C, message_id: i32, text: T) -> EditMessageText
&self,
chat_or_inline_message: ChatOrInlineMessage,
text: T,
) -> EditMessageText
where where
C: Into<ChatId>,
T: Into<String>, T: Into<String>,
{ {
match self.parse_mode.deref() { match self.parse_mode {
None => EditMessageText::new(self.clone(), chat_or_inline_message, text), None => EditMessageText::new(self.clone(), chat_id, message_id, text),
Some(parse_mode) => EditMessageText::new(self.clone(), chat_or_inline_message, text) Some(parse_mode) => {
.parse_mode(*parse_mode.deref()), EditMessageText::new(self.clone(), chat_id, message_id, text).parse_mode(parse_mode)
}
} }
} }
/// Use this method to edit captions of messages. /// Use this method to edit text and game messages sent via the bot.
/// ///
/// On success, if edited message is sent by the bot, the edited [`Message`] /// On success, [`True`] is returned.
/// is returned, otherwise [`True`] is returned. ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagetext).
///
/// [`True`]: crate::types::True
///
/// # Params
///
/// - `inline_message_id`: Identifier of the inline message.
/// - `text`: New text of the message.
///
/// # Notes
///
/// Uses [a default parse mode] if specified in [`BotBuilder`].
///
/// [a default parse mode]: crate::BotBuilder::parse_mode
/// [`BotBuilder`]: crate::BotBuilder
pub fn edit_inline_message_text<I, T>(
&self,
inline_message_id: I,
text: T,
) -> EditInlineMessageText
where
I: Into<String>,
T: Into<String>,
{
match self.parse_mode {
None => EditInlineMessageText::new(self.clone(), inline_message_id, text),
Some(parse_mode) => EditInlineMessageText::new(self.clone(), inline_message_id, text)
.parse_mode(parse_mode),
}
}
/// Use this method to edit captions of messages sent via the bot.
///
/// On success, [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption).
///
/// [`True`]: crate::types::True
///
/// # Notes
///
/// Uses [a default parse mode] if specified in [`BotBuilder`].
///
/// [a default parse mode]: crate::BotBuilder::parse_mode
/// [`BotBuilder`]: crate::BotBuilder
pub fn edit_message_caption<C>(&self, chat_id: C, message_id: i32) -> EditMessageCaption
where
C: Into<ChatId>,
{
match self.parse_mode {
None => EditMessageCaption::new(self.clone(), chat_id, message_id),
Some(parse_mode) => {
EditMessageCaption::new(self.clone(), chat_id, message_id).parse_mode(parse_mode)
}
}
}
/// Use this method to edit captions of messages sent via the bot.
///
/// On success, [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption).
/// ///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True /// [`True`]: crate::types::True
/// ///
/// # Notes /// # Notes
@ -1032,14 +1174,14 @@ impl Bot {
/// ///
/// [a default parse mode]: crate::BotBuilder::parse_mode /// [a default parse mode]: crate::BotBuilder::parse_mode
/// [`BotBuilder`]: crate::BotBuilder /// [`BotBuilder`]: crate::BotBuilder
pub fn edit_message_caption( pub fn edit_inline_message_caption<I>(&self, inline_message_id: I) -> EditInlineMessageCaption
&self, where
chat_or_inline_message: ChatOrInlineMessage, I: Into<String>,
) -> EditMessageCaption { {
match self.parse_mode.deref() { match self.parse_mode {
None => EditMessageCaption::new(self.clone(), chat_or_inline_message), None => EditInlineMessageCaption::new(self.clone(), inline_message_id),
Some(parse_mode) => EditMessageCaption::new(self.clone(), chat_or_inline_message) Some(parse_mode) => EditInlineMessageCaption::new(self.clone(), inline_message_id)
.parse_mode(*parse_mode.deref()), .parse_mode(parse_mode),
} }
} }
@ -1048,37 +1190,81 @@ impl Bot {
/// ///
/// If a message is a part of a message album, then it can be edited only to /// If a message is a part of a message album, then it can be edited only to
/// a photo or a video. Otherwise, message type can be changed /// a photo or a video. Otherwise, message type can be changed
/// arbitrarily. When inline message is edited, new file can't be /// arbitrarily. On success, the edited [`Message`] is returned.
/// uploaded. Use previously uploaded file via its `file_id` or specify
/// a URL. On success, if the edited message was sent by the bot, the
/// edited [`Message`] is returned, otherwise [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). /// [The official docs](https://core.telegram.org/bots/api#editmessagemedia).
/// ///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True pub fn edit_message_media<C>(
pub fn edit_message_media(
&self, &self,
chat_or_inline_message: ChatOrInlineMessage, chat_id: C,
message_id: i32,
media: InputMedia, media: InputMedia,
) -> EditMessageMedia { ) -> EditMessageMedia
EditMessageMedia::new(self.clone(), chat_or_inline_message, media) where
C: Into<ChatId>,
{
EditMessageMedia::new(self.clone(), chat_id, message_id, media)
}
/// Use this method to edit animation, audio, document, photo, or video
/// messages sent via the bot.
///
/// If a message is a part of a message album, then it can be edited only to
/// a photo or a video. Otherwise, message type can be changed
/// arbitrarily. When this method is used, new file can't be uploaded.
/// Use previously uploaded file via its `file_id` or specify a URL. On
/// success, [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia).
///
/// [`True`]: crate::types::True
pub fn edit_inline_message_media<I>(
&self,
inline_message_id: I,
media: InputMedia,
) -> EditInlineMessageMedia
where
I: Into<String>,
{
EditInlineMessageMedia::new(self.clone(), inline_message_id, media)
} }
/// Use this method to edit only the reply markup of messages. /// Use this method to edit only the reply markup of messages.
/// ///
/// On success, if edited message is sent by the bot, the edited [`Message`] /// On success, the edited [`Message`] is returned.
/// is returned, otherwise [`True`] is returned. ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup).
///
/// [`Message`]: crate::types::Message
pub fn edit_message_reply_markup<C>(
&self,
chat_id: C,
message_id: i32,
) -> EditMessageReplyMarkup
where
C: Into<ChatId>,
{
EditMessageReplyMarkup::new(self.clone(), chat_id, message_id)
}
/// Use this method to edit only the reply markup of messages sent via the
/// bot.
///
/// On success, [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). /// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup).
/// ///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True /// [`True`]: crate::types::True
pub fn edit_message_reply_markup( pub fn edit_inline_message_reply_markup<I>(
&self, &self,
chat_or_inline_message: ChatOrInlineMessage, inline_message_id: I,
) -> EditMessageReplyMarkup { ) -> EditInlineMessageReplyMarkup
EditMessageReplyMarkup::new(self.clone(), chat_or_inline_message) where
I: Into<String>,
{
EditInlineMessageReplyMarkup::new(self.clone(), inline_message_id)
} }
/// Use this method to stop a poll which was sent by the bot. /// Use this method to stop a poll which was sent by the bot.
@ -1128,20 +1314,23 @@ impl Bot {
/// ///
/// [The official docs](https://core.telegram.org/bots/api#sendsticker). /// [The official docs](https://core.telegram.org/bots/api#sendsticker).
/// ///
/// [animated]: https://telegram.org/blog/animated-stickers
///
/// # Params /// # Params
/// - `chat_id`: Unique identifier for the target chat or username of the /// - `chat_id`: Unique identifier for the target chat or username of the
/// target channel (in the format `@channelusername`). /// target channel (in the format `@channelusername`).
/// - `sticker`: Sticker to send. /// - `sticker`: Pass [`InputFile::FileId`] to send a sticker that exists
/// on the Telegram servers (recommended), pass an [`InputFile::Url`]
/// for Telegram to get a sticker (.WEBP file) from the Internet, pass
/// [`InputFile::File`] to upload a sticker from the file system or
/// [`InputFile::Memory`] to upload a sticker from memory [More info on
/// Sending Files »].
/// ///
/// Pass [`InputFile::File`] to send a file that exists on the Telegram
/// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a
/// .webp file from the Internet, or upload a new one using
/// [`InputFile::FileId`]. [More info on Sending Files »].
///
/// [animated]: https://telegram.org/blog/animated-stickers
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn send_sticker<C>(&self, chat_id: C, sticker: InputFile) -> SendSticker pub fn send_sticker<C>(&self, chat_id: C, sticker: InputFile) -> SendSticker
where where
@ -1173,12 +1362,14 @@ impl Bot {
/// - `user_id`: User identifier of sticker file owner. /// - `user_id`: User identifier of sticker file owner.
/// - `png_sticker`: **Png** image with the sticker, must be up to 512 /// - `png_sticker`: **Png** image with the sticker, must be up to 512
/// kilobytes in size, dimensions must not exceed 512px, and either /// kilobytes in size, dimensions must not exceed 512px, and either
/// width or height must be exactly 512px. [More info on Sending Files /// width or height must be exactly 512px. Pass [`InputFile::File`] to
/// »]. /// upload a file from the file system or [`InputFile::Memory`] to
/// upload a file from memory. [More info on Sending Files »].
///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
/// ///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
/// [`Bot::create_new_sticker_set`]: crate::Bot::create_new_sticker_set
/// [`Bot::add_sticker_to_set`]: crate::Bot::add_sticker_to_set
pub fn upload_sticker_file(&self, user_id: i32, png_sticker: InputFile) -> UploadStickerFile { pub fn upload_sticker_file(&self, user_id: i32, png_sticker: InputFile) -> UploadStickerFile {
UploadStickerFile::new(self.clone(), user_id, png_sticker) UploadStickerFile::new(self.clone(), user_id, png_sticker)
} }
@ -1416,18 +1607,18 @@ impl Bot {
/// [The official docs](https://core.telegram.org/bots/api#setgamescore). /// [The official docs](https://core.telegram.org/bots/api#setgamescore).
/// ///
/// # Params /// # Params
/// - `target`: Target message, either chat id and message id or inline
/// message id.
/// - `user_id`: User identifier. /// - `user_id`: User identifier.
/// - `score`: New score, must be non-negative. /// - `score`: New score, must be non-negative.
/// ///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True /// [`True`]: crate::types::True
pub fn set_game_score( pub fn set_game_score<T>(&self, target: T, user_id: i32, score: i32) -> SetGameScore
&self, where
chat_or_inline_message: ChatOrInlineMessage, T: Into<TargetMessage>,
user_id: i32, {
score: i32, SetGameScore::new(self.clone(), target, user_id, score)
) -> SetGameScore {
SetGameScore::new(self.clone(), chat_or_inline_message, user_id, score)
} }
/// Use this method to get data for high score tables. /// Use this method to get data for high score tables.
@ -1444,13 +1635,14 @@ impl Bot {
/// [The official docs](https://core.telegram.org/bots/api#getgamehighscores). /// [The official docs](https://core.telegram.org/bots/api#getgamehighscores).
/// ///
/// # Params /// # Params
/// - `target`: Target message, either chat id and message id or inline
/// message id.
/// - `user_id`: Target user id. /// - `user_id`: Target user id.
pub fn get_game_high_scores( pub fn get_game_high_scores<T>(&self, target: T, user_id: i32) -> GetGameHighScores
&self, where
chat_or_inline_message: ChatOrInlineMessage, T: Into<TargetMessage>,
user_id: i32, {
) -> GetGameHighScores { GetGameHighScores::new(self.clone(), target, user_id)
GetGameHighScores::new(self.clone(), chat_or_inline_message, user_id)
} }
/// Use this method to set a custom title for an administrator in a /// Use this method to set a custom title for an administrator in a
@ -1527,4 +1719,15 @@ impl Bot {
{ {
SetStickerSetThumb::new(self.clone(), name, user_id) SetStickerSetThumb::new(self.clone(), name, user_id)
} }
fn with_default_parse_mode_if_specified<Builder>(
&self,
builder: Builder,
f: fn(Builder, ParseMode) -> Builder,
) -> Builder {
match self.parse_mode {
None => builder,
Some(parse_mode) => f(builder, parse_mode),
}
}
} }

View file

@ -8,6 +8,9 @@ use std::{sync::Arc, time::Duration};
mod api; mod api;
mod download; mod download;
pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN";
pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY";
/// A requests sender. /// A requests sender.
/// ///
/// No need to put it into [`Arc`], because it's already in. /// No need to put it into [`Arc`], because it's already in.
@ -17,21 +20,35 @@ mod download;
pub struct Bot { pub struct Bot {
token: Arc<str>, token: Arc<str>,
client: Client, client: Client,
parse_mode: Arc<Option<ParseMode>>, parse_mode: Option<ParseMode>,
} }
impl Bot { impl Bot {
/// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a /// Creates new [`BotBuilder`] see it's [docs] for more
/// bot's token) and the default [`reqwest::Client`]. ///
/// [docs]: BotBuilder
#[must_use]
pub fn builder() -> BotBuilder {
BotBuilder::new()
}
/// Creates a new `Bot` with the `TELOXIDE_TOKEN` & `TELOXIDE_PROXY`
/// environmental variables (a bot's token & a proxy) and the default
/// [`reqwest::Client`].
///
/// This function passes the value of `TELOXIDE_PROXY` into
/// [`reqwest::Proxy::all`], if it exists, otherwise returns the default
/// client.
/// ///
/// # Panics /// # Panics
/// - If cannot get the `TELOXIDE_TOKEN` environmental variable. /// - If cannot get the `TELOXIDE_TOKEN` environmental variable.
/// - If it cannot create [`reqwest::Client`]. /// - If it cannot create [`reqwest::Client`].
/// ///
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
#[allow(deprecated)] /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all
#[must_use]
pub fn from_env() -> Self { pub fn from_env() -> Self {
Self::from_env_with_client(build_sound_bot()) BotBuilder::new().build()
} }
/// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a
@ -46,10 +63,11 @@ impl Bot {
/// ///
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 /// [issue 223]: https://github.com/teloxide/teloxide/issues/223
#[deprecated] #[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \
#[allow(deprecated)] (notably default parse_mode)"]
pub fn from_env_with_client(client: Client) -> Self { pub fn from_env_with_client(client: Client) -> Self {
Self::with_client(&get_token_from_env(), client) #[allow(deprecated)]
Self::with_client(&get_env(TELOXIDE_TOKEN), client)
} }
/// Creates a new `Bot` with the specified token and the default /// Creates a new `Bot` with the specified token and the default
@ -59,12 +77,13 @@ impl Bot {
/// If it cannot create [`reqwest::Client`]. /// If it cannot create [`reqwest::Client`].
/// ///
/// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
#[deprecated] #[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \
#[allow(deprecated)] (notably default parse_mode)"]
pub fn new<S>(token: S) -> Self pub fn new<S>(token: S) -> Self
where where
S: Into<String>, S: Into<String>,
{ {
#[allow(deprecated)]
Self::with_client(token, build_sound_bot()) Self::with_client(token, build_sound_bot())
} }
@ -77,8 +96,8 @@ impl Bot {
/// ///
/// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 /// [issue 223]: https://github.com/teloxide/teloxide/issues/223
#[deprecated] #[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \
#[allow(deprecated)] (notably default parse_mode)"]
pub fn with_client<S>(token: S, client: Client) -> Self pub fn with_client<S>(token: S, client: Client) -> Self
where where
S: Into<String>, S: Into<String>,
@ -86,7 +105,7 @@ impl Bot {
Self { Self {
token: Into::<Arc<str>>::into(Into::<String>::into(token)), token: Into::<Arc<str>>::into(Into::<String>::into(token)),
client, client,
parse_mode: Arc::new(None), parse_mode: None,
} }
} }
} }
@ -115,8 +134,8 @@ pub(crate) fn build_sound_bot() -> Client {
sound_bot().build().expect("creating reqwest::Client") sound_bot().build().expect("creating reqwest::Client")
} }
fn get_token_from_env() -> String { fn get_env(env: &'static str) -> String {
std::env::var("TELOXIDE_TOKEN").expect("Cannot get the TELOXIDE_TOKEN env variable") std::env::var(env).unwrap_or_else(|_| panic!("Cannot get the {} env variable", env))
} }
impl Bot { impl Bot {
@ -133,7 +152,7 @@ impl Bot {
/// A builder of [`Bot`], supporting some extra settings. /// A builder of [`Bot`], supporting some extra settings.
/// ///
/// [`Bot`] crate::Bot /// [`Bot`]: crate::Bot
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct BotBuilder { pub struct BotBuilder {
token: Option<String>, token: Option<String>,
@ -150,10 +169,15 @@ impl BotBuilder {
/// Specifies a custom HTTPS client. Otherwise, the default will be used. /// Specifies a custom HTTPS client. Otherwise, the default will be used.
/// ///
/// # Caution /// # Caution
/// Your custom client might not be configured correctly to be able to work /// - Your custom client might not be configured correctly to be able to
/// work
/// in long time durations, see [issue 223]. /// in long time durations, see [issue 223].
/// ///
/// - If this method is used, the `TELOXIDE_PROXY` environmental variable
/// won't be extracted in [`BotBuilder::build`].
///
/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 /// [issue 223]: https://github.com/teloxide/teloxide/issues/223
/// [`BotBuilder::build`]: crate::BotBuilder::build
#[must_use] #[must_use]
pub fn client(mut self, client: Client) -> Self { pub fn client(mut self, client: Client) -> Self {
self.client = Some(client); self.client = Some(client);
@ -204,6 +228,11 @@ impl BotBuilder {
/// Builds [`Bot`]. /// Builds [`Bot`].
/// ///
/// This method will attempt to build a new client with a proxy, specified
/// in the `TELOXIDE_PROXY` (passed into [`reqwest::Proxy::all`])
/// environmental variable, if a client haven't been specified. If
/// `TELOXIDE_PROXY` is unspecified, it'll use no proxy.
///
/// # Panics /// # Panics
/// - If cannot get the `TELOXIDE_TOKEN` environmental variable. /// - If cannot get the `TELOXIDE_TOKEN` environmental variable.
/// - If it cannot create [`reqwest::Client`]. /// - If it cannot create [`reqwest::Client`].
@ -211,12 +240,13 @@ impl BotBuilder {
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
/// ///
/// [`Bot`]: crate::Bot /// [`Bot`]: crate::Bot
/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all
#[must_use] #[must_use]
pub fn build(self) -> Bot { pub fn build(self) -> Bot {
Bot { Bot {
client: self.client.unwrap_or_else(build_sound_bot), client: self.client.unwrap_or_else(crate::utils::client_from_env),
token: self.token.unwrap_or_else(get_token_from_env).into(), token: self.token.unwrap_or_else(|| get_env(TELOXIDE_TOKEN)).into(),
parse_mode: Arc::new(self.parse_mode), parse_mode: self.parse_mode,
} }
} }
} }

View file

@ -6,7 +6,7 @@ use crate::dispatching::{
}; };
use std::{convert::Infallible, marker::PhantomData}; use std::{convert::Infallible, marker::PhantomData};
use futures::{future::BoxFuture, StreamExt}; use futures::{future::BoxFuture, FutureExt, StreamExt};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use lockfree::map::Map; use lockfree::map::Map;
@ -137,28 +137,30 @@ where
{ {
let this = Arc::new(self); let this = Arc::new(self);
Box::pin(updates.for_each(move |cx| { updates
let this = Arc::clone(&this); .for_each(move |cx| {
let chat_id = cx.update.chat_id(); let this = Arc::clone(&this);
let chat_id = cx.update.chat_id();
match this.senders.get(&chat_id) { match this.senders.get(&chat_id) {
// An old dialogue // An old dialogue
Some(tx) => { Some(tx) => {
if tx.1.send(cx).is_err() { if tx.1.send(cx).is_err() {
panic!("We are not dropping a receiver or call .close() on it",); panic!("We are not dropping a receiver or call .close() on it",);
}
}
None => {
let tx = this.new_tx();
if tx.send(cx).is_err() {
panic!("We are not dropping a receiver or call .close() on it",);
}
this.senders.insert(chat_id, tx);
} }
} }
None => {
let tx = this.new_tx();
if tx.send(cx).is_err() {
panic!("We are not dropping a receiver or call .close() on it",);
}
this.senders.insert(chat_id, tx);
}
}
async {} async {}
})) })
.boxed()
} }
} }

View file

@ -21,7 +21,7 @@ pub enum DialogueStage<D> {
/// ///
/// [`From`]: std::convert::From /// [`From`]: std::convert::From
/// [derive-more]: https://crates.io/crates/derive_more /// [derive-more]: https://crates.io/crates/derive_more
pub fn next<Dialogue, State>(new_state: State) -> TransitionOut<Dialogue> pub fn next<Dialogue, State, E>(new_state: State) -> TransitionOut<Dialogue, E>
where where
Dialogue: From<State>, Dialogue: From<State>,
{ {
@ -32,6 +32,6 @@ where
/// ///
/// 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<D>() -> TransitionOut<D> { pub fn exit<D, E>() -> TransitionOut<D, E> {
Ok(DialogueStage::Exit) Ok(DialogueStage::Exit)
} }

View file

@ -30,16 +30,16 @@
//! skeleton should look like: //! skeleton should look like:
//! //!
//! ```no_run //! ```no_run
//! # #[cfg(feature = "macros")] {
//! use std::convert::Infallible; //! use std::convert::Infallible;
//! //!
//! use teloxide::prelude::*; //! use teloxide::{dispatching::dialogue::Transition, prelude::*, teloxide};
//! use teloxide_macros::{teloxide, Transition};
//! //!
//! struct _1State; //! struct _1State;
//! struct _2State; //! struct _2State;
//! struct _3State; //! struct _3State;
//! //!
//! type Out = TransitionOut<D>; //! type Out = TransitionOut<D, RequestError>;
//! //!
//! #[teloxide(subtransition)] //! #[teloxide(subtransition)]
//! async fn _1_transition(_state: _1State, _cx: TransitionIn) -> Out { //! async fn _1_transition(_state: _1State, _cx: TransitionIn) -> Out {
@ -97,6 +97,7 @@
//! .dispatch() //! .dispatch()
//! .await; //! .await;
//! } //! }
//! # }
//! ``` //! ```
//! //!
//! - `#[teloxide(subtransition)]` implements [`Subtransition`] for the first //! - `#[teloxide(subtransition)]` implements [`Subtransition`] for the first
@ -156,6 +157,10 @@ pub use transition::{
Subtransition, SubtransitionOutputType, Transition, TransitionIn, TransitionOut, Subtransition, SubtransitionOutputType, Transition, TransitionIn, TransitionOut,
}; };
#[cfg(feature = "macros")]
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
pub use teloxide_macros::Transition;
#[cfg(feature = "redis-storage")] #[cfg(feature = "redis-storage")]
pub use storage::{RedisStorage, RedisStorageError}; pub use storage::{RedisStorage, RedisStorageError};

View file

@ -1,6 +1,5 @@
use crate::{ use crate::{
dispatching::{dialogue::DialogueStage, UpdateWithCx}, dispatching::{dialogue::DialogueStage, UpdateWithCx},
requests::ResponseResult,
types::Message, types::Message,
}; };
use futures::future::BoxFuture; use futures::future::BoxFuture;
@ -8,11 +7,16 @@ use futures::future::BoxFuture;
/// Represents a transition function of a dialogue FSM. /// Represents a transition function of a dialogue FSM.
pub trait Transition: Sized { pub trait Transition: Sized {
type Aux; type Aux;
type Error;
/// Turns itself into another state, depending on the input message. /// Turns itself into another state, depending on the input message.
/// ///
/// `aux` will be passed to each subtransition function. /// `aux` will be passed to each subtransition function.
fn react(self, cx: TransitionIn, aux: Self::Aux) -> BoxFuture<'static, TransitionOut<Self>>; fn react(
self,
cx: TransitionIn,
aux: Self::Aux,
) -> BoxFuture<'static, TransitionOut<Self, Self::Error>>;
} }
/// Like [`Transition`], but from `StateN` -> `Dialogue`. /// Like [`Transition`], but from `StateN` -> `Dialogue`.
@ -24,6 +28,7 @@ where
{ {
type Aux; type Aux;
type Dialogue; type Dialogue;
type Error;
/// Turns itself into another state, depending on the input message. /// Turns itself into another state, depending on the input message.
/// ///
@ -33,7 +38,7 @@ where
self, self,
cx: TransitionIn, cx: TransitionIn,
aux: Self::Aux, aux: Self::Aux,
) -> BoxFuture<'static, TransitionOut<Self::Dialogue>>; ) -> BoxFuture<'static, TransitionOut<Self::Dialogue, Self::Error>>;
} }
/// A type returned from a FSM subtransition function. /// A type returned from a FSM subtransition function.
@ -41,14 +46,16 @@ where
/// Now it is used only inside `#[teloxide(subtransition)]` for type inference. /// Now it is used only inside `#[teloxide(subtransition)]` for type inference.
pub trait SubtransitionOutputType { pub trait SubtransitionOutputType {
type Output; type Output;
type Error;
} }
impl<D> SubtransitionOutputType for TransitionOut<D> { impl<D, E> SubtransitionOutputType for TransitionOut<D, E> {
type Output = D; type Output = D;
type Error = E;
} }
/// An input passed into a FSM (sub)transition function. /// An input passed into a FSM (sub)transition function.
pub type TransitionIn = UpdateWithCx<Message>; pub type TransitionIn = UpdateWithCx<Message>;
/// A type returned from a FSM (sub)transition function. /// A type returned from a FSM (sub)transition function.
pub type TransitionOut<D> = ResponseResult<DialogueStage<D>>; pub type TransitionOut<D, E = crate::RequestError> = Result<DialogueStage<D>, E>;

View file

@ -30,7 +30,7 @@ where
where where
Self: Stream<Item = UpdateWithCx<Message>>, Self: Stream<Item = UpdateWithCx<Message>>,
{ {
Box::pin(self.filter_map(|cx| async move { cx.update.text_owned().map(|text| (cx, text)) })) self.filter_map(|cx| async move { cx.update.text_owned().map(|text| (cx, text)) }).boxed()
} }
fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<Message>, C)> fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<Message>, C)>
@ -41,10 +41,12 @@ where
{ {
let bot_name = bot_name.into(); let bot_name = bot_name.into();
Box::pin(self.text_messages().filter_map(move |(cx, text)| { self.text_messages()
let bot_name = bot_name.clone(); .filter_map(move |(cx, text)| {
let bot_name = bot_name.clone();
async move { C::parse(&text, &bot_name).map(|command| (cx, command)).ok() } async move { C::parse(&text, &bot_name).map(|command| (cx, command)).ok() }
})) })
.boxed()
} }
} }

View file

@ -30,38 +30,7 @@
//! //!
//! Since they implement [`DispatcherHandler`] too. //! Since they implement [`DispatcherHandler`] too.
//! //!
//! # The dices bot //! [See the examples](https://github.com/teloxide/teloxide/tree/master/examples).
//! This bot throws a dice on each incoming message:
//!
//! ([Full](https://github.com/teloxide/teloxide/blob/master/examples/dices_bot/src/main.rs))
//! ```no_run
//! use teloxide::prelude::*;
//!
//! # #[tokio::main]
//! # async fn main_() {
//! teloxide::enable_logging!();
//! log::info!("Starting dices_bot...");
//!
//! let bot = Bot::from_env();
//!
//! Dispatcher::new(bot)
//! .messages_handler(|rx: DispatcherHandlerRx<Message>| {
//! rx.for_each(|message| async move {
//! message.send_dice().send().await.log_on_error().await;
//! })
//! })
//! .dispatch()
//! .await;
//! # }
//! ```
//!
//! <div align="center">
//! <kbd>
//! <img src=https://github.com/teloxide/teloxide/raw/master/media/DICES_BOT.gif />
//! </kbd>
//! </div>
//!
//! [See more examples](https://github.com/teloxide/teloxide/tree/master/examples).
//! //!
//! [`Dispatcher`]: crate::dispatching::Dispatcher //! [`Dispatcher`]: crate::dispatching::Dispatcher
//! [all the update kinds]: crate::types::UpdateKind //! [all the update kinds]: crate::types::UpdateKind
@ -78,6 +47,7 @@ pub mod dialogue;
mod dispatcher; mod dispatcher;
mod dispatcher_handler; mod dispatcher_handler;
mod dispatcher_handler_rx_ext; mod dispatcher_handler_rx_ext;
pub(crate) mod repls;
pub mod update_listeners; pub mod update_listeners;
mod update_with_cx; mod update_with_cx;

View file

@ -0,0 +1,89 @@
use crate::{
dispatching::{
update_listeners, update_listeners::UpdateListener, Dispatcher, DispatcherHandlerRx,
DispatcherHandlerRxExt, UpdateWithCx,
},
error_handlers::{LoggingErrorHandler, OnError},
types::Message,
utils::command::BotCommand,
Bot,
};
use futures::StreamExt;
use std::{fmt::Debug, future::Future, sync::Arc};
/// A [REPL] for commands.
///
/// All errors from an update listener and handler will be logged.
///
/// # Caution
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
/// because Telegram disallow multiple requests at the same time from the same
/// bot.
///
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
/// [`Dispatcher`]: crate::dispatching::Dispatcher
pub async fn commands_repl<Cmd, H, Fut, HandlerE, N>(bot: Bot, bot_name: N, handler: H)
where
Cmd: BotCommand + Send + 'static,
H: Fn(UpdateWithCx<Message>, Cmd) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), HandlerE>> + Send + 'static,
Result<(), HandlerE>: OnError<HandlerE>,
HandlerE: Debug + Send,
N: Into<String> + Send + 'static,
{
let cloned_bot = bot.clone();
commands_repl_with_listener(
bot,
bot_name,
handler,
update_listeners::polling_default(cloned_bot),
)
.await;
}
/// Like [`commands_repl`], but with a custom [`UpdateListener`].
///
/// All errors from an update listener and handler will be logged.
///
/// # Caution
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
/// because Telegram disallow multiple requests at the same time from the same
/// bot.
///
/// [`Dispatcher`]: crate::dispatching::Dispatcher
/// [`commands_repl`]: crate::dispatching::repls::commands_repl()
/// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener
pub async fn commands_repl_with_listener<'a, Cmd, H, Fut, L, ListenerE, HandlerE, N>(
bot: Bot,
bot_name: N,
handler: H,
listener: L,
) where
Cmd: BotCommand + Send + 'static,
H: Fn(UpdateWithCx<Message>, Cmd) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), HandlerE>> + Send + 'static,
L: UpdateListener<ListenerE> + Send + 'a,
ListenerE: Debug + Send + 'a,
Result<(), HandlerE>: OnError<HandlerE>,
HandlerE: Debug + Send,
N: Into<String> + Send + 'static,
{
let handler = Arc::new(handler);
Dispatcher::new(bot)
.messages_handler(move |rx: DispatcherHandlerRx<Message>| {
rx.commands::<Cmd, N>(bot_name).for_each_concurrent(None, move |(cx, cmd)| {
let handler = Arc::clone(&handler);
async move {
handler(cx, cmd).await.log_on_error().await;
}
})
})
.dispatch_with_listener(
listener,
LoggingErrorHandler::with_custom_text("An error from the update listener"),
)
.await
}

View file

@ -0,0 +1,81 @@
use crate::{
dispatching::{
dialogue::{DialogueDispatcher, DialogueStage, DialogueWithCx},
update_listeners,
update_listeners::UpdateListener,
Dispatcher, UpdateWithCx,
},
error_handlers::LoggingErrorHandler,
types::Message,
Bot,
};
use std::{convert::Infallible, fmt::Debug, future::Future, sync::Arc};
/// A [REPL] for dialogues.
///
/// All errors from an update listener and handler will be logged. This function
/// uses [`InMemStorage`].
///
/// # Caution
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
/// because Telegram disallow multiple requests at the same time from the same
/// bot.
///
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
/// [`Dispatcher`]: crate::dispatching::Dispatcher
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
pub async fn dialogues_repl<'a, H, D, Fut>(bot: Bot, handler: H)
where
H: Fn(UpdateWithCx<Message>, D) -> Fut + Send + Sync + 'static,
D: Default + Send + 'static,
Fut: Future<Output = DialogueStage<D>> + Send + 'static,
{
let cloned_bot = bot.clone();
dialogues_repl_with_listener(bot, handler, update_listeners::polling_default(cloned_bot)).await;
}
/// Like [`dialogues_repl`], but with a custom [`UpdateListener`].
///
/// All errors from an update listener and handler will be logged. This function
/// uses [`InMemStorage`].
///
/// # Caution
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
/// because Telegram disallow multiple requests at the same time from the same
/// bot.
///
/// [`Dispatcher`]: crate::dispatching::Dispatcher
/// [`dialogues_repl`]: crate::dispatching::repls::dialogues_repl()
/// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
pub async fn dialogues_repl_with_listener<'a, H, D, Fut, L, ListenerE>(
bot: Bot,
handler: H,
listener: L,
) where
H: Fn(UpdateWithCx<Message>, D) -> Fut + Send + Sync + 'static,
D: Default + Send + 'static,
Fut: Future<Output = DialogueStage<D>> + Send + 'static,
L: UpdateListener<ListenerE> + Send + 'a,
ListenerE: Debug + Send + 'a,
{
let handler = Arc::new(handler);
Dispatcher::new(bot)
.messages_handler(DialogueDispatcher::new(
move |DialogueWithCx { cx, dialogue }: DialogueWithCx<Message, D, Infallible>| {
let handler = Arc::clone(&handler);
async move {
let dialogue = dialogue.expect("std::convert::Infallible");
handler(cx, dialogue).await
}
},
))
.dispatch_with_listener(
listener,
LoggingErrorHandler::with_custom_text("An error from the update listener"),
)
.await;
}

View file

@ -0,0 +1,7 @@
mod commands_repl;
mod dialogues_repl;
mod repl;
pub use commands_repl::{commands_repl, commands_repl_with_listener};
pub use dialogues_repl::{dialogues_repl, dialogues_repl_with_listener};
pub use repl::{repl, repl_with_listener};

View file

@ -0,0 +1,73 @@
use crate::{
dispatching::{
update_listeners, update_listeners::UpdateListener, Dispatcher, DispatcherHandlerRx,
UpdateWithCx,
},
error_handlers::{LoggingErrorHandler, OnError},
types::Message,
Bot,
};
use futures::StreamExt;
use std::{fmt::Debug, future::Future, sync::Arc};
/// A [REPL] for messages.
///
/// All errors from an update listener and a handler will be logged.
///
/// # Caution
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
/// because Telegram disallow multiple requests at the same time from the same
/// bot.
///
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
/// [`Dispatcher`]: crate::dispatching::Dispatcher
pub async fn repl<H, Fut, E>(bot: Bot, handler: H)
where
H: Fn(UpdateWithCx<Message>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), E>> + Send + 'static,
Result<(), E>: OnError<E>,
E: Debug + Send,
{
let cloned_bot = bot.clone();
repl_with_listener(bot, handler, update_listeners::polling_default(cloned_bot)).await;
}
/// Like [`repl`], but with a custom [`UpdateListener`].
///
/// All errors from an update listener and handler will be logged.
///
/// # Caution
/// **DO NOT** use this function together with [`Dispatcher`] and other REPLs,
/// because Telegram disallow multiple requests at the same time from the same
/// bot.
///
/// [`Dispatcher`]: crate::dispatching::Dispatcher
/// [`repl`]: crate::dispatching::repls::repl()
/// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener
pub async fn repl_with_listener<'a, H, Fut, E, L, ListenerE>(bot: Bot, handler: H, listener: L)
where
H: Fn(UpdateWithCx<Message>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), E>> + Send + 'static,
L: UpdateListener<ListenerE> + Send + 'a,
ListenerE: Debug,
Result<(), E>: OnError<E>,
E: Debug + Send,
{
let handler = Arc::new(handler);
Dispatcher::new(bot)
.messages_handler(|rx: DispatcherHandlerRx<Message>| {
rx.for_each_concurrent(None, move |message| {
let handler = Arc::clone(&handler);
async move {
handler(message).await.log_on_error().await;
}
})
})
.dispatch_with_listener(
listener,
LoggingErrorHandler::with_custom_text("An error from the update listener"),
)
.await;
}

View file

@ -6,7 +6,7 @@ use crate::{
SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker, SendVenue, SendVideo, SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker, SendVenue, SendVideo,
SendVideoNote, SendVoice, SendVideoNote, SendVoice,
}, },
types::{ChatId, ChatOrInlineMessage, InputFile, InputMedia, Message}, types::{ChatId, InputFile, InputMedia, Message},
Bot, Bot,
}; };
@ -130,20 +130,11 @@ impl UpdateWithCx<Message> {
where where
T: Into<String>, T: Into<String>,
{ {
self.bot.edit_message_text( self.bot.edit_message_text(self.update.chat.id, self.update.id, text)
ChatOrInlineMessage::Chat {
chat_id: self.update.chat.id.into(),
message_id: self.update.id,
},
text,
)
} }
pub fn edit_message_caption(&self) -> EditMessageCaption { pub fn edit_message_caption(&self) -> EditMessageCaption {
self.bot.edit_message_caption(ChatOrInlineMessage::Chat { self.bot.edit_message_caption(self.update.chat.id, self.update.id)
chat_id: self.update.chat.id.into(),
message_id: self.update.id,
})
} }
pub fn delete_message(&self) -> DeleteMessage { pub fn delete_message(&self) -> DeleteMessage {
@ -154,7 +145,7 @@ impl UpdateWithCx<Message> {
self.bot.pin_chat_message(self.update.chat.id, self.update.id) self.bot.pin_chat_message(self.update.chat.id, self.update.id)
} }
pub fn send_dice(&self) -> SendDice { pub fn answer_dice(&self) -> SendDice {
self.bot.send_dice(self.update.chat.id) self.bot.send_dice(self.update.chat.id)
} }
} }

View file

@ -4,6 +4,31 @@
//! //!
//! For a high-level overview, see [our GitHub repository](https://github.com/teloxide/teloxide). //! For a high-level overview, see [our GitHub repository](https://github.com/teloxide/teloxide).
//! //!
//! ([Full](https://github.com/teloxide/teloxide/blob/master/examples/dices_bot/src/main.rs))
//! ```no_run
//! use teloxide::prelude::*;
//!
//! # #[tokio::main]
//! # async fn main_() {
//! teloxide::enable_logging!();
//! log::info!("Starting dices_bot...");
//!
//! let bot = Bot::from_env();
//!
//! teloxide::repl(bot, |message| async move {
//! message.answer_dice().send().await?;
//! ResponseResult::<()>::Ok(())
//! })
//! .await;
//! # }
//! ```
//!
//! <div align="center">
//! <kbd>
//! <img src=https://github.com/teloxide/teloxide/raw/master/media/DICES_BOT.gif />
//! </kbd>
//! </div>
//!
//! [Telegram bots]: https://telegram.org/blog/bot-revolution //! [Telegram bots]: https://telegram.org/blog/bot-revolution
//! [`async`/`.await`]: https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html //! [`async`/`.await`]: https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html
//! [Rust]: https://www.rust-lang.org/ //! [Rust]: https://www.rust-lang.org/
@ -15,8 +40,14 @@
)] )]
#![allow(clippy::match_bool)] #![allow(clippy::match_bool)]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![cfg_attr(all(feature = "nightly", doctest), feature(external_doc))]
#![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))]
pub use bot::{Bot, BotBuilder}; pub use bot::{Bot, BotBuilder};
pub use dispatching::repls::{
commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener, repl,
repl_with_listener,
};
pub use errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError}; pub use errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError};
mod errors; mod errors;
@ -31,4 +62,13 @@ pub mod requests;
pub mod types; pub mod types;
pub mod utils; pub mod utils;
#[cfg(feature = "macros")]
extern crate teloxide_macros; extern crate teloxide_macros;
#[cfg(feature = "macros")]
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
pub use teloxide_macros::teloxide;
#[cfg(all(feature = "nightly", doctest))]
#[doc(include = "../README.md")]
enum ReadmeDocTests {}

View file

@ -9,7 +9,7 @@ pub use crate::{
Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx, Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx,
}, },
error_handlers::{LoggingErrorHandler, OnError}, error_handlers::{LoggingErrorHandler, OnError},
requests::{Request, ResponseResult}, requests::{respond, Request, ResponseResult},
types::{Message, Update}, types::{Message, Update},
Bot, RequestError, Bot, RequestError,
}; };

View file

@ -0,0 +1,83 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{InlineKeyboardMarkup, ParseMode, True},
Bot,
};
/// Use this method to edit captions of messages sent via the bot.
///
/// On success, [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption).
///
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditInlineMessageCaption {
#[serde(skip_serializing)]
bot: Bot,
inline_message_id: String,
caption: Option<String>,
parse_mode: Option<ParseMode>,
reply_markup: Option<InlineKeyboardMarkup>,
}
#[async_trait::async_trait]
impl Request for EditInlineMessageCaption {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "editMessageCaption", &self).await
}
}
impl EditInlineMessageCaption {
pub(crate) fn new<I>(bot: Bot, inline_message_id: I) -> Self
where
I: Into<String>,
{
let inline_message_id = inline_message_id.into();
Self { bot, inline_message_id, caption: None, parse_mode: None, reply_markup: None }
}
/// Identifier of the inline message.
pub fn inline_message_id<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.inline_message_id = val.into();
self
}
/// New caption of the message.
pub fn caption<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.caption = Some(val.into());
self
}
/// Send [Markdown] or [HTML], if you want Telegram apps to show
/// [bold, italic, fixed-width text or inline URLs] in the media caption.
///
/// [Markdown]: crate::types::ParseMode::Markdown
/// [HTML]: crate::types::ParseMode::HTML
/// [bold, italic, fixed-width text or inline URLs]:
/// crate::types::ParseMode
pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val);
self
}
/// A JSON-serialized object for an [inline keyboard].
///
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self {
self.reply_markup = Some(val);
self
}
}

View file

@ -0,0 +1,77 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{InlineKeyboardMarkup, True},
Bot,
};
/// Use this method to edit live location messages sent via the bot.
///
/// A location can be edited until its live_period expires or editing is
/// explicitly disabled by a call to stopMessageLiveLocation. On success,
/// [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation).
///
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditInlineMessageLiveLocation {
#[serde(skip_serializing)]
bot: Bot,
inline_message_id: String,
latitude: f32,
longitude: f32,
reply_markup: Option<InlineKeyboardMarkup>,
}
#[async_trait::async_trait]
impl Request for EditInlineMessageLiveLocation {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "editMessageLiveLocation", &self)
.await
}
}
impl EditInlineMessageLiveLocation {
pub(crate) fn new<I>(bot: Bot, inline_message_id: I, latitude: f32, longitude: f32) -> Self
where
I: Into<String>,
{
let inline_message_id = inline_message_id.into();
Self { bot, inline_message_id, latitude, longitude, reply_markup: None }
}
/// Identifier of the inline message.
pub fn inline_message_id<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.inline_message_id = val.into();
self
}
/// Latitude of new location.
pub fn latitude(mut self, val: f32) -> Self {
self.latitude = val;
self
}
/// Longitude of new location.
pub fn longitude(mut self, val: f32) -> Self {
self.longitude = val;
self
}
/// A JSON-serialized object for a new [inline keyboard].
///
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self {
self.reply_markup = Some(val);
self
}
}

View file

@ -0,0 +1,78 @@
use crate::{
net,
requests::{form_builder::FormBuilder, Request, ResponseResult},
types::{InlineKeyboardMarkup, InputMedia, True},
Bot,
};
/// Use this method to edit animation, audio, document, photo, or video
/// messages sent via the bot.
///
/// If a message is a part of a message album, then it can be edited only to a
/// photo or a video. Otherwise, message type can be changed arbitrarily. When
/// this method is used, new file can't be uploaded. Use previously
/// uploaded file via its `file_id` or specify a URL. On success, [`True`] is
/// returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia).
///
/// [`True`]: crate::types::True
#[derive(Debug, Clone)]
pub struct EditInlineMessageMedia {
bot: Bot,
inline_message_id: String,
media: InputMedia,
reply_markup: Option<InlineKeyboardMarkup>,
}
#[async_trait::async_trait]
impl Request for EditInlineMessageMedia {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_multipart(
self.bot.client(),
self.bot.token(),
"editMessageMedia",
FormBuilder::new()
.add_text("media", &self.media)
.add_text("reply_markup", &self.reply_markup)
.add_text("inline_message_id", &self.inline_message_id)
.build(),
)
.await
}
}
impl EditInlineMessageMedia {
pub(crate) fn new<I>(bot: Bot, inline_message_id: I, media: InputMedia) -> Self
where
I: Into<String>,
{
let inline_message_id = inline_message_id.into();
Self { bot, inline_message_id, media, reply_markup: None }
}
/// Identifier of the inline message.
pub fn inline_message_id<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.inline_message_id = val.into();
self
}
/// A JSON-serialized object for a new media content of the message.
pub fn media(mut self, val: InputMedia) -> Self {
self.media = val;
self
}
/// A JSON-serialized object for a new [inline keyboard].
///
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self {
self.reply_markup = Some(val);
self
}
}

View file

@ -0,0 +1,62 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{InlineKeyboardMarkup, True},
Bot,
};
/// Use this method to edit only the reply markup of messages sent via the bot.
///
/// On success, [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup).
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditInlineMessageReplyMarkup {
#[serde(skip_serializing)]
bot: Bot,
inline_message_id: String,
reply_markup: Option<InlineKeyboardMarkup>,
}
#[async_trait::async_trait]
impl Request for EditInlineMessageReplyMarkup {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "editMessageReplyMarkup", &self)
.await
}
}
impl EditInlineMessageReplyMarkup {
pub(crate) fn new<I>(bot: Bot, inline_message_id: I) -> Self
where
I: Into<String>,
{
let inline_message_id = inline_message_id.into();
Self { bot, inline_message_id, reply_markup: None }
}
/// Identifier of the inline message.
pub fn inline_message_id<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.inline_message_id = val.into();
self
}
/// A JSON-serialized object for an [inline keyboard].
///
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self {
self.reply_markup = Some(val);
self
}
}

View file

@ -0,0 +1,98 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{InlineKeyboardMarkup, Message, ParseMode},
Bot,
};
/// Use this method to edit text and game messages sent via the bot.
///
/// On success, [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagetext).
///
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditInlineMessageText {
#[serde(skip_serializing)]
bot: Bot,
inline_message_id: String,
text: String,
parse_mode: Option<ParseMode>,
disable_web_page_preview: Option<bool>,
reply_markup: Option<InlineKeyboardMarkup>,
}
#[async_trait::async_trait]
impl Request for EditInlineMessageText {
type Output = Message;
async fn send(&self) -> ResponseResult<Message> {
net::request_json(self.bot.client(), self.bot.token(), "editMessageText", &self).await
}
}
impl EditInlineMessageText {
pub(crate) fn new<I, T>(bot: Bot, inline_message_id: I, text: T) -> Self
where
I: Into<String>,
T: Into<String>,
{
let inline_message_id = inline_message_id.into();
let text = text.into();
Self {
bot,
inline_message_id,
text,
parse_mode: None,
disable_web_page_preview: None,
reply_markup: None,
}
}
/// Identifier of the inline message.
pub fn inline_message_id<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.inline_message_id = val.into();
self
}
/// New text of the message.
pub fn text<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.text = val.into();
self
}
/// Send [Markdown] or [HTML], if you want Telegram apps to show [bold,
/// italic, fixed-width text or inline URLs] in your bot's message.
///
/// [Markdown]: https://core.telegram.org/bots/api#markdown-style
/// [HTML]: https://core.telegram.org/bots/api#html-style
/// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options
pub fn parse_mode(mut self, val: ParseMode) -> Self {
self.parse_mode = Some(val);
self
}
/// Disables link previews for links in this message.
pub fn disable_web_page_preview(mut self, val: bool) -> Self {
self.disable_web_page_preview = Some(val);
self
}
/// A JSON-serialized object for an [inline keyboard].
///
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self {
self.reply_markup = Some(val);
self
}
}

View file

@ -3,26 +3,24 @@ use serde::Serialize;
use crate::{ use crate::{
net, net,
requests::{Request, ResponseResult}, requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode}, types::{ChatId, InlineKeyboardMarkup, Message, ParseMode},
Bot, Bot,
}; };
/// Use this method to edit captions of messages. /// Use this method to edit captions of messages sent by the bot.
/// ///
/// On success, if edited message is sent by the bot, the edited [`Message`] is /// On success, the edited [`Message`] is returned.
/// returned, otherwise [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption).
/// ///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none] #[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct EditMessageCaption { pub struct EditMessageCaption {
#[serde(skip_serializing)] #[serde(skip_serializing)]
bot: Bot, bot: Bot,
#[serde(flatten)] chat_id: ChatId,
chat_or_inline_message: ChatOrInlineMessage, message_id: i32,
caption: Option<String>, caption: Option<String>,
parse_mode: Option<ParseMode>, parse_mode: Option<ParseMode>,
reply_markup: Option<InlineKeyboardMarkup>, reply_markup: Option<InlineKeyboardMarkup>,
@ -38,12 +36,27 @@ impl Request for EditMessageCaption {
} }
impl EditMessageCaption { impl EditMessageCaption {
pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage) -> Self { pub(crate) fn new<C>(bot: Bot, chat_id: C, message_id: i32) -> Self
Self { bot, chat_or_inline_message, caption: None, parse_mode: None, reply_markup: None } where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, message_id, caption: None, parse_mode: None, reply_markup: None }
} }
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { /// Unique identifier for the target chat or username of the target channel
self.chat_or_inline_message = val; /// (in the format `@channelusername`)
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Identifier of the message to edit
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = val;
self self
} }

View file

@ -3,28 +3,26 @@ use serde::Serialize;
use crate::{ use crate::{
net, net,
requests::{Request, ResponseResult}, requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, types::{ChatId, InlineKeyboardMarkup, Message},
Bot, Bot,
}; };
/// Use this method to edit live location messages. /// Use this method to edit live location messages.
/// ///
/// A location can be edited until its live_period expires or editing is /// A location can be edited until its live_period expires or editing is
/// explicitly disabled by a call to stopMessageLiveLocation. On success, if the /// explicitly disabled by a call to stopMessageLiveLocation. On success, the
/// edited message was sent by the bot, the edited [`Message`] is returned, /// edited [`Message`] is returned.
/// otherwise [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation). /// [The official docs](https://core.telegram.org/bots/api#editmessagelivelocation).
/// ///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none] #[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct EditMessageLiveLocation { pub struct EditMessageLiveLocation {
#[serde(skip_serializing)] #[serde(skip_serializing)]
bot: Bot, bot: Bot,
#[serde(flatten)] chat_id: ChatId,
chat_or_inline_message: ChatOrInlineMessage, message_id: i32,
latitude: f32, latitude: f32,
longitude: f32, longitude: f32,
reply_markup: Option<InlineKeyboardMarkup>, reply_markup: Option<InlineKeyboardMarkup>,
@ -41,17 +39,33 @@ impl Request for EditMessageLiveLocation {
} }
impl EditMessageLiveLocation { impl EditMessageLiveLocation {
pub(crate) fn new( pub(crate) fn new<C>(
bot: Bot, bot: Bot,
chat_or_inline_message: ChatOrInlineMessage, chat_id: C,
message_id: i32,
latitude: f32, latitude: f32,
longitude: f32, longitude: f32,
) -> Self { ) -> Self
Self { bot, chat_or_inline_message, latitude, longitude, reply_markup: None } where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, message_id, latitude, longitude, reply_markup: None }
} }
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { /// Unique identifier for the target chat or username of the target channel
self.chat_or_inline_message = val; /// (in the format `@channelusername`)
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Identifier of the message to edit
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = val;
self self
} }

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
net, net,
requests::{form_builder::FormBuilder, Request, ResponseResult}, requests::{form_builder::FormBuilder, Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, InputMedia, Message}, types::{ChatId, InlineKeyboardMarkup, InputMedia, Message},
Bot, Bot,
}; };
@ -9,20 +9,17 @@ use crate::{
/// messages. /// messages.
/// ///
/// If a message is a part of a message album, then it can be edited only to a /// If a message is a part of a message album, then it can be edited only to a
/// photo or a video. Otherwise, message type can be changed arbitrarily. When /// photo or a video. Otherwise, message type can be changed arbitrarily. On
/// inline message is edited, new file can't be uploaded. Use previously /// success, the edited [`Message`] is returned.
/// uploaded file via its `file_id` or specify a URL. On success, if the edited
/// message was sent by the bot, the edited [`Message`] is returned,
/// otherwise [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia). /// [The official docs](https://core.telegram.org/bots/api#editmessagemedia).
/// ///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EditMessageMedia { pub struct EditMessageMedia {
bot: Bot, bot: Bot,
chat_or_inline_message: ChatOrInlineMessage, chat_id: ChatId,
message_id: i32,
media: InputMedia, media: InputMedia,
reply_markup: Option<InlineKeyboardMarkup>, reply_markup: Option<InlineKeyboardMarkup>,
} }
@ -32,22 +29,13 @@ impl Request for EditMessageMedia {
type Output = Message; type Output = Message;
async fn send(&self) -> ResponseResult<Message> { async fn send(&self) -> ResponseResult<Message> {
let mut params = FormBuilder::new();
match &self.chat_or_inline_message {
ChatOrInlineMessage::Chat { chat_id, message_id } => {
params = params.add_text("chat_id", chat_id).add_text("message_id", message_id);
}
ChatOrInlineMessage::Inline { inline_message_id } => {
params = params.add_text("inline_message_id", inline_message_id);
}
}
net::request_multipart( net::request_multipart(
self.bot.client(), self.bot.client(),
self.bot.token(), self.bot.token(),
"editMessageMedia", "editMessageMedia",
params FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_text("message_id", &self.message_id)
.add_text("media", &self.media) .add_text("media", &self.media)
.add_text("reply_markup", &self.reply_markup) .add_text("reply_markup", &self.reply_markup)
.build(), .build(),
@ -57,16 +45,27 @@ impl Request for EditMessageMedia {
} }
impl EditMessageMedia { impl EditMessageMedia {
pub(crate) fn new( pub(crate) fn new<C>(bot: Bot, chat_id: C, message_id: i32, media: InputMedia) -> Self
bot: Bot, where
chat_or_inline_message: ChatOrInlineMessage, C: Into<ChatId>,
media: InputMedia, {
) -> Self { let chat_id = chat_id.into();
Self { bot, chat_or_inline_message, media, reply_markup: None } Self { bot, chat_id, message_id, media, reply_markup: None }
} }
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { /// Unique identifier for the target chat or username of the target channel
self.chat_or_inline_message = val; /// (in the format `@channelusername`)
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Identifier of the message to edit
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = val;
self self
} }

View file

@ -3,26 +3,24 @@ use serde::Serialize;
use crate::{ use crate::{
net, net,
requests::{Request, ResponseResult}, requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, types::{ChatId, InlineKeyboardMarkup, Message},
Bot, Bot,
}; };
/// Use this method to edit only the reply markup of messages. /// Use this method to edit only the reply markup of messages.
/// ///
/// On success, if edited message is sent by the bot, the edited [`Message`] is /// On success, the edited [`Message`] is returned.
/// returned, otherwise [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). /// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup).
/// ///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none] #[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct EditMessageReplyMarkup { pub struct EditMessageReplyMarkup {
#[serde(skip_serializing)] #[serde(skip_serializing)]
bot: Bot, bot: Bot,
#[serde(flatten)] chat_id: ChatId,
chat_or_inline_message: ChatOrInlineMessage, message_id: i32,
reply_markup: Option<InlineKeyboardMarkup>, reply_markup: Option<InlineKeyboardMarkup>,
} }
@ -37,12 +35,27 @@ impl Request for EditMessageReplyMarkup {
} }
impl EditMessageReplyMarkup { impl EditMessageReplyMarkup {
pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage) -> Self { pub(crate) fn new<C>(bot: Bot, chat_id: C, message_id: i32) -> Self
Self { bot, chat_or_inline_message, reply_markup: None } where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, message_id, reply_markup: None }
} }
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { /// Unique identifier for the target chat or username of the target channel
self.chat_or_inline_message = val; /// (in the format `@channelusername`)
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Identifier of the message to edit
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = val;
self self
} }

View file

@ -3,26 +3,24 @@ use serde::Serialize;
use crate::{ use crate::{
net, net,
requests::{Request, ResponseResult}, requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode}, types::{ChatId, InlineKeyboardMarkup, Message, ParseMode},
Bot, Bot,
}; };
/// Use this method to edit text and game messages. /// Use this method to edit text and game messages.
/// ///
/// On success, if edited message is sent by the bot, the edited [`Message`] is /// On success, the edited [`Message`] is returned.
/// returned, otherwise [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#editmessagetext). /// [The official docs](https://core.telegram.org/bots/api#editmessagetext).
/// ///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none] #[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct EditMessageText { pub struct EditMessageText {
#[serde(skip_serializing)] #[serde(skip_serializing)]
bot: Bot, bot: Bot,
#[serde(flatten)] chat_id: ChatId,
chat_or_inline_message: ChatOrInlineMessage, message_id: i32,
text: String, text: String,
parse_mode: Option<ParseMode>, parse_mode: Option<ParseMode>,
disable_web_page_preview: Option<bool>, disable_web_page_preview: Option<bool>,
@ -39,22 +37,37 @@ impl Request for EditMessageText {
} }
impl EditMessageText { impl EditMessageText {
pub(crate) fn new<T>(bot: Bot, chat_or_inline_message: ChatOrInlineMessage, text: T) -> Self pub(crate) fn new<C, T>(bot: Bot, chat_id: C, message_id: i32, text: T) -> Self
where where
C: Into<ChatId>,
T: Into<String>, T: Into<String>,
{ {
let chat_id = chat_id.into();
let text = text.into();
Self { Self {
bot, bot,
chat_or_inline_message, chat_id,
text: text.into(), message_id,
text,
parse_mode: None, parse_mode: None,
disable_web_page_preview: None, disable_web_page_preview: None,
reply_markup: None, reply_markup: None,
} }
} }
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { /// Unique identifier for the target chat or username of the target channel
self.chat_or_inline_message = val; /// (in the format `@channelusername`)
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Identifier of the message to edit
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = val;
self self
} }

View file

@ -3,7 +3,7 @@ use serde::Serialize;
use crate::{ use crate::{
net, net,
requests::{Request, ResponseResult}, requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, GameHighScore}, types::{GameHighScore, TargetMessage},
Bot, Bot,
}; };
@ -18,14 +18,13 @@ use crate::{
/// the user and his neighbors are not among them. Please note that this /// the user and his neighbors are not among them. Please note that this
/// behavior is subject to change. /// behavior is subject to change.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#getgamehighscores). /// [The official docs](https://core.telegram.org/bots/api#getgamehighscores)
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct GetGameHighScores { pub struct GetGameHighScores {
#[serde(skip_serializing)] #[serde(skip_serializing)]
bot: Bot, bot: Bot,
#[serde(flatten)] #[serde(flatten)]
chat_or_inline_message: ChatOrInlineMessage, target: TargetMessage,
user_id: i32, user_id: i32,
} }
@ -39,12 +38,20 @@ impl Request for GetGameHighScores {
} }
impl GetGameHighScores { impl GetGameHighScores {
pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage, user_id: i32) -> Self { pub(crate) fn new<T>(bot: Bot, target: T, user_id: i32) -> Self
Self { bot, chat_or_inline_message, user_id } where
T: Into<TargetMessage>,
{
let target = target.into();
Self { bot, target, user_id }
} }
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { /// Target message, either chat id and message id or inline message id.
self.chat_or_inline_message = val; pub fn target<T>(mut self, val: T) -> Self
where
T: Into<TargetMessage>,
{
self.target = val.into();
self self
} }

View file

@ -9,6 +9,11 @@ mod delete_chat_sticker_set;
mod delete_message; mod delete_message;
mod delete_sticker_from_set; mod delete_sticker_from_set;
mod delete_webhook; mod delete_webhook;
mod edit_inline_message_caption;
mod edit_inline_message_live_location;
mod edit_inline_message_media;
mod edit_inline_message_reply_markup;
mod edit_inline_message_text;
mod edit_message_caption; mod edit_message_caption;
mod edit_message_live_location; mod edit_message_live_location;
mod edit_message_media; mod edit_message_media;
@ -62,6 +67,7 @@ mod set_my_commands;
mod set_sticker_position_in_set; mod set_sticker_position_in_set;
mod set_sticker_set_thumb; mod set_sticker_set_thumb;
mod set_webhook; mod set_webhook;
mod stop_inline_message_live_location;
mod stop_message_live_location; mod stop_message_live_location;
mod stop_poll; mod stop_poll;
mod unban_chat_member; mod unban_chat_member;
@ -79,6 +85,11 @@ pub use delete_chat_sticker_set::*;
pub use delete_message::*; pub use delete_message::*;
pub use delete_sticker_from_set::*; pub use delete_sticker_from_set::*;
pub use delete_webhook::*; pub use delete_webhook::*;
pub use edit_inline_message_caption::*;
pub use edit_inline_message_live_location::*;
pub use edit_inline_message_media::*;
pub use edit_inline_message_reply_markup::*;
pub use edit_inline_message_text::*;
pub use edit_message_caption::*; pub use edit_message_caption::*;
pub use edit_message_live_location::*; pub use edit_message_live_location::*;
pub use edit_message_media::*; pub use edit_message_media::*;
@ -133,6 +144,7 @@ pub use set_sticker_position_in_set::*;
pub use set_sticker_set_thumb::*; pub use set_sticker_set_thumb::*;
pub use set_webhook::*; pub use set_webhook::*;
pub use std::pin::Pin; pub use std::pin::Pin;
pub use stop_inline_message_live_location::*;
pub use stop_message_live_location::*; pub use stop_message_live_location::*;
pub use stop_poll::*; pub use stop_poll::*;
pub use unban_chat_member::*; pub use unban_chat_member::*;

View file

@ -90,6 +90,19 @@ impl SendAnimation {
} }
/// Animation to send. /// Animation to send.
///
/// Pass [`InputFile::FileId`] to send a file that exists on the Telegram
/// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a
/// file from the Internet (20MB max.), pass [`InputFile::File`] to upload
/// a file from the file system or [`InputFile::Memory`] to upload a file
/// from memory (50MB max. each). [More info on Sending Files »].
///
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn animation(mut self, val: InputFile) -> Self { pub fn animation(mut self, val: InputFile) -> Self {
self.animation = val; self.animation = val;
self self
@ -116,13 +129,18 @@ impl SendAnimation {
/// Thumbnail of the file sent; can be ignored if thumbnail generation for /// Thumbnail of the file sent; can be ignored if thumbnail generation for
/// the file is supported server-side. /// the file is supported server-side.
/// ///
/// The thumbnail should be in JPEG format and less than 200 kB in size. A /// The thumbnail should be in JPEG format and less than 200kB in size. A
/// thumbnails width and height should not exceed 320. Ignored if the /// thumbnails width and height should not exceed 320. Ignored if the
/// file is not uploaded using [`InputFile::File`]. Thumbnails cant be /// animation is not uploaded using [`InputFile::File`] or
/// reused and can be only uploaded as a new file, with /// [`InputFile::Memory`]. Thumbnails cant be reused and can be only
/// [`InputFile::File`]. /// uploaded as a new file. Pass [`InputFile::File`] to upload a file from
/// the file system or [`InputFile::Memory`] to upload a file from memory.
/// [More info on Sending Files »].
/// ///
/// [`InputFile::File`]: crate::types::InputFile::File /// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn thumb(mut self, value: InputFile) -> Self { pub fn thumb(mut self, value: InputFile) -> Self {
self.thumb = Some(value); self.thumb = Some(value);
self self

View file

@ -95,14 +95,17 @@ impl SendAudio {
/// Audio file to send. /// Audio file to send.
/// ///
/// Pass [`InputFile::File`] to send a file that exists on /// Pass [`InputFile::FileId`] to send an audio file that exists on the
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for /// Telegram servers (recommended), pass an [`InputFile::Url`] for Telegram
/// Telegram to get a .webp file from the Internet, or upload a new one /// to get a file from the Internet (20MB max.), pass [`InputFile::File`]
/// using [`InputFile::FileId`]. [More info on Sending Files »]. /// to upload a file from the file system or [`InputFile::Memory`] to
/// upload a file from memory (50MB max. each).
/// [More info on Sending Files »].
/// ///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
/// ///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn audio(mut self, val: InputFile) -> Self { pub fn audio(mut self, val: InputFile) -> Self {
@ -158,13 +161,16 @@ impl SendAudio {
/// Thumbnail of the file sent; can be ignored if thumbnail generation for /// Thumbnail of the file sent; can be ignored if thumbnail generation for
/// the file is supported server-side. /// the file is supported server-side.
/// ///
/// The thumbnail should be in JPEG format and less than 200 kB in size. A /// The thumbnail should be in JPEG format and less than 200kB in size. A
/// thumbnails width and height should not exceed 320. Ignored if the /// thumbnails width and height should not exceed 320. Ignored if the
/// file is not uploaded using `multipart/form-data`. Thumbnails cant /// audio file is not uploaded using [`InputFile::File`] or
/// be reused and can be only uploaded as a new file, so you can pass /// [`InputFile::Memory`]. Thumbnails cant be reused and can be only
/// `attach://<file_attach_name>` if the thumbnail was uploaded using /// uploaded as a new file. Pass [`InputFile::File`] to upload a file from
/// `multipart/form-data` under `<file_attach_name>`. [More info on /// the file system or [`InputFile::Memory`] to upload a file from memory.
/// Sending Files »]. /// [More info on Sending Files »].
///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
/// ///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn thumb(mut self, val: InputFile) -> Self { pub fn thumb(mut self, val: InputFile) -> Self {

View file

@ -81,10 +81,16 @@ impl SendDocument {
/// File to send. /// File to send.
/// ///
/// Pass a file_id as String to send a file that exists on the /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram
/// Telegram servers (recommended), pass an HTTP URL as a String for /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a
/// Telegram to get a file from the Internet, or upload a new one using /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload
/// `multipart/form-data`. [More info on Sending Files »]. /// a file from the file system or [`InputFile::Memory`] to upload a file
/// from memory (50MB max. each). [More info on Sending Files »].
///
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
/// ///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn document(mut self, val: InputFile) -> Self { pub fn document(mut self, val: InputFile) -> Self {
@ -95,13 +101,16 @@ impl SendDocument {
/// Thumbnail of the file sent; can be ignored if thumbnail generation for /// Thumbnail of the file sent; can be ignored if thumbnail generation for
/// the file is supported server-side. /// the file is supported server-side.
/// ///
/// The thumbnail should be in JPEG format and less than 200 kB in size. A /// The thumbnail should be in JPEG format and less than 200kB in size. A
/// thumbnails width and height should not exceed 320. Ignored if the /// thumbnails width and height should not exceed 320. Ignored if the
/// file is not uploaded using `multipart/form-data`. Thumbnails cant /// document is not uploaded using [`InputFile::File`] or
/// be reused and can be only uploaded as a new file, so you can pass /// [`InputFile::Memory`]. Thumbnails cant be reused and can be only
/// “attach://<file_attach_name>” if the thumbnail was uploaded using /// uploaded as a new file. Pass [`InputFile::File`] to upload a file from
/// `multipart/form-data` under `<file_attach_name>`. [More info on /// the file system or [`InputFile::Memory`] to upload a file from memory.
/// Sending Files »]. /// [More info on Sending Files »].
///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
/// ///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn thumb(mut self, val: InputFile) -> Self { pub fn thumb(mut self, val: InputFile) -> Self {

View file

@ -73,14 +73,16 @@ impl SendPhoto {
/// Photo to send. /// Photo to send.
/// ///
/// Pass [`InputFile::File`] to send a photo that exists on /// Pass [`InputFile::FileId`] to send a photo that exists on the Telegram
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a
/// Telegram to get a .webp file from the Internet, or upload a new one /// photo from the Internet (5MB max.), pass [`InputFile::File`] to upload
/// using [`InputFile::FileId`]. [More info on Sending Files »]. /// a picture from the file system or [`InputFile::Memory`] to upload a
/// photo from memory (10MB max. each). [More info on Sending Files »].
/// ///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
/// ///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn photo(mut self, val: InputFile) -> Self { pub fn photo(mut self, val: InputFile) -> Self {

View file

@ -69,14 +69,18 @@ impl SendSticker {
/// Sticker to send. /// Sticker to send.
/// ///
/// Pass [`InputFile::File`] to send a file that exists on /// Pass [`InputFile::FileId`] to send a sticker that exists on the
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for /// Telegram servers (recommended), pass an [`InputFile::Url`] for Telegram
/// Telegram to get a .webp file from the Internet, or upload a new one /// to get a sticker (.WEBP file) from the Internet, pass
/// using [`InputFile::FileId`]. [More info on Sending Files »]. /// [`InputFile::File`] to upload a sticker from the file system or
/// [`InputFile::Memory`] to upload a sticker from memory
/// [More info on Sending Files »].
/// ///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn sticker(mut self, val: InputFile) -> Self { pub fn sticker(mut self, val: InputFile) -> Self {
self.sticker = val; self.sticker = val;

View file

@ -92,16 +92,20 @@ impl SendVideo {
self self
} }
/// Video to sent. /// Video to send.
/// ///
/// Pass [`InputFile::File`] to send a file that exists on /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a
/// Telegram to get a .webp file from the Internet, or upload a new one /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload
/// using [`InputFile::FileId`]. [More info on Sending Files »]. /// a file from the file system or [`InputFile::Memory`] to upload a file
/// from memory (50MB max. each). [More info on Sending Files »].
/// ///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn video(mut self, val: InputFile) -> Self { pub fn video(mut self, val: InputFile) -> Self {
self.video = val; self.video = val;
self self
@ -128,13 +132,16 @@ impl SendVideo {
/// Thumbnail of the file sent; can be ignored if thumbnail generation for /// Thumbnail of the file sent; can be ignored if thumbnail generation for
/// the file is supported server-side. /// the file is supported server-side.
/// ///
/// The thumbnail should be in JPEG format and less than 200 kB in size. A /// The thumbnail should be in JPEG format and less than 200kB in size. A
/// thumbnails width and height should not exceed 320. Ignored if the /// thumbnails width and height should not exceed 320. Ignored if the
/// file is not uploaded using `multipart/form-data`. Thumbnails cant be /// video file is not uploaded using [`InputFile::File`] or
/// reused and can be only uploaded as a new file, so you can pass /// [`InputFile::Memory`]. Thumbnails cant be reused and can be only
/// `attach://<file_attach_name>` if the thumbnail was uploaded using /// uploaded as a new file. Pass [`InputFile::File`] to upload a file from
/// `multipart/form-data` under `<file_attach_name>`. [More info on Sending /// the file system or [`InputFile::Memory`] to upload a file from memory.
/// Files »]. /// [More info on Sending Files »].
///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
/// ///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn thumb(mut self, val: InputFile) -> Self { pub fn thumb(mut self, val: InputFile) -> Self {

View file

@ -81,14 +81,17 @@ impl SendVideoNote {
/// Video note to send. /// Video note to send.
/// ///
/// Pass [`InputFile::File`] to send a file that exists on /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a
/// Telegram to get a .webp file from the Internet, or upload a new one /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload
/// using [`InputFile::FileId`]. [More info on Sending Files »]. /// a file from the file system or [`InputFile::Memory`] to upload a file
/// from memory (50MB max. each). [More info on Sending Files »].
/// ///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn video_note(mut self, val: InputFile) -> Self { pub fn video_note(mut self, val: InputFile) -> Self {
self.video_note = val; self.video_note = val;
@ -110,13 +113,18 @@ impl SendVideoNote {
/// Thumbnail of the file sent; can be ignored if thumbnail generation for /// Thumbnail of the file sent; can be ignored if thumbnail generation for
/// the file is supported server-side. /// the file is supported server-side.
/// ///
/// The thumbnail should be in JPEG format and less than 200 kB in size. A /// The thumbnail should be in JPEG format and less than 200kB in size. A
/// thumbnails width and height should not exceed 320. Ignored if the /// thumbnails width and height should not exceed 320. Ignored if the
/// file is not uploaded using `multipart/form-data`. Thumbnails cant /// video note is not uploaded using [`InputFile::File`] or
/// be reused and can be only uploaded as a new file, so you can pass /// [`InputFile::Memory`]. Thumbnails cant be reused and can be only
/// `attach://<file_attach_name>` if the thumbnail was uploaded using /// uploaded as a new file. Pass [`InputFile::File`] to upload a file from
/// `multipart/form-data` under `<file_attach_name>`. [More info on /// the file system or [`InputFile::Memory`] to upload a file from memory.
/// Sending Files »]. /// [More info on Sending Files »].
///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn thumb(mut self, val: InputFile) -> Self { pub fn thumb(mut self, val: InputFile) -> Self {
self.thumb = Some(val); self.thumb = Some(val);
self self

View file

@ -85,14 +85,17 @@ impl SendVoice {
/// Audio file to send. /// Audio file to send.
/// ///
/// Pass [`InputFile::File`] to send a file that exists on /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a
/// Telegram to get a .webp file from the Internet, or upload a new one /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload
/// using [`InputFile::FileId`]. [More info on Sending Files »]. /// a file from the file system or [`InputFile::Memory`] to upload a file
/// from memory (50MB max. each). [More info on Sending Files »].
/// ///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
pub fn voice(mut self, val: InputFile) -> Self { pub fn voice(mut self, val: InputFile) -> Self {
self.voice = val; self.voice = val;

View file

@ -3,7 +3,7 @@ use serde::Serialize;
use crate::{ use crate::{
net, net,
requests::{Request, ResponseResult}, requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, Message}, types::{Message, TargetMessage},
Bot, Bot,
}; };
@ -24,7 +24,7 @@ pub struct SetGameScore {
#[serde(skip_serializing)] #[serde(skip_serializing)]
bot: Bot, bot: Bot,
#[serde(flatten)] #[serde(flatten)]
chat_or_inline_message: ChatOrInlineMessage, target: TargetMessage,
user_id: i32, user_id: i32,
score: i32, score: i32,
force: Option<bool>, force: Option<bool>,
@ -41,24 +41,20 @@ impl Request for SetGameScore {
} }
impl SetGameScore { impl SetGameScore {
pub(crate) fn new( pub(crate) fn new<T>(bot: Bot, target: T, user_id: i32, score: i32) -> Self
bot: Bot, where
chat_or_inline_message: ChatOrInlineMessage, T: Into<TargetMessage>,
user_id: i32, {
score: i32, let target = target.into();
) -> Self { Self { bot, target, user_id, score, force: None, disable_edit_message: None }
Self {
bot,
chat_or_inline_message,
user_id,
score,
force: None,
disable_edit_message: None,
}
} }
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { /// Target message, either chat id and message id or inline message id.
self.chat_or_inline_message = val; pub fn target<T>(mut self, val: T) -> Self
where
T: Into<TargetMessage>,
{
self.target = val.into();
self self
} }

View file

@ -58,11 +58,12 @@ impl SetStickerSetThumb {
/// thumbnail up to 32 kilobytes in size; see https://core.telegram.org/animated_stickers#technical-requirements /// thumbnail up to 32 kilobytes in size; see https://core.telegram.org/animated_stickers#technical-requirements
/// for animated sticker technical requirements. /// for animated sticker technical requirements.
/// ///
/// Pass [`InputFile::FileId`] as a String to send a file that already /// Pass [`InputFile::FileId`] to send a file that exists on the Telegram
/// exists on the Telegram servers, pass [`InputFile::Url`] for Telegram /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a
/// to get a file from the Internet, or upload a new one using /// file from the Internet (20MB max.), pass [`InputFile::File`] to upload
/// multipart/form-data. More info on Sending Files ». Animated sticker /// a file from the file system or [`InputFile::Memory`] to upload a file
/// set thumbnail can't be uploaded via HTTP URL. /// from memory (50MB max. each). [More info on Sending Files »]. Animated
/// sticker set thumbnail can't be uploaded via HTTP URL.
/// ///
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url]: crate::types::InputFile::Url /// [`InputFile::Url]: crate::types::InputFile::Url

View file

@ -0,0 +1,62 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{InlineKeyboardMarkup, Message},
Bot,
};
/// Use this method to stop updating a live location message (sent via the bot)
/// before `live_period` expires.
///
/// On success, [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation).
///
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct StopInlineMessageLiveLocation {
#[serde(skip_serializing)]
bot: Bot,
inline_message_id: String,
reply_markup: Option<InlineKeyboardMarkup>,
}
#[async_trait::async_trait]
impl Request for StopInlineMessageLiveLocation {
type Output = Message;
async fn send(&self) -> ResponseResult<Message> {
net::request_json(self.bot.client(), self.bot.token(), "stopMessageLiveLocation", &self)
.await
}
}
impl StopInlineMessageLiveLocation {
pub(crate) fn new<I>(bot: Bot, inline_message_id: I) -> Self
where
I: Into<String>,
{
let inline_message_id = inline_message_id.into();
Self { bot, inline_message_id, reply_markup: None }
}
/// Identifier of the inline message.
pub fn inline_message_id<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.inline_message_id = val.into();
self
}
/// A JSON-serialized object for a new [inline keyboard].
///
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self {
self.reply_markup = Some(val);
self
}
}

View file

@ -3,27 +3,25 @@ use serde::Serialize;
use crate::{ use crate::{
net, net,
requests::{Request, ResponseResult}, requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, types::{ChatId, InlineKeyboardMarkup, Message},
Bot, Bot,
}; };
/// Use this method to stop updating a live location message before /// Use this method to stop updating a live location message before
/// `live_period` expires. /// `live_period` expires.
/// ///
/// On success, if the message was sent by the bot, the sent [`Message`] is /// On success, the sent [`Message`] is returned.
/// returned, otherwise [`True`] is returned.
/// ///
/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). /// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation).
/// ///
/// [`Message`]: crate::types::Message /// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none] #[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct StopMessageLiveLocation { pub struct StopMessageLiveLocation {
#[serde(skip_serializing)] #[serde(skip_serializing)]
bot: Bot, bot: Bot,
#[serde(flatten)] chat_id: ChatId,
chat_or_inline_message: ChatOrInlineMessage, message_id: i32,
reply_markup: Option<InlineKeyboardMarkup>, reply_markup: Option<InlineKeyboardMarkup>,
} }
@ -38,12 +36,27 @@ impl Request for StopMessageLiveLocation {
} }
impl StopMessageLiveLocation { impl StopMessageLiveLocation {
pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage) -> Self { pub(crate) fn new<C>(bot: Bot, chat_id: C, message_id: i32) -> Self
Self { bot, chat_or_inline_message, reply_markup: None } where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, message_id, reply_markup: None }
} }
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { /// Unique identifier for the target chat or username of the target channel
self.chat_or_inline_message = val; /// (in the format `@channelusername`)
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Identifier of the message to edit
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = val;
self self
} }

View file

@ -9,6 +9,11 @@ pub use all::*;
/// A type that is returned after making a request to Telegram. /// A type that is returned after making a request to Telegram.
pub type ResponseResult<T> = Result<T, crate::RequestError>; pub type ResponseResult<T> = Result<T, crate::RequestError>;
/// A shortcut for `ResponseResult::Ok(val)`.
pub fn respond<T>(val: T) -> ResponseResult<T> {
ResponseResult::Ok(val)
}
/// Designates an API request. /// Designates an API request.
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait Request { pub trait Request {

View file

@ -1,12 +0,0 @@
use crate::types::ChatId;
use serde::{Deserialize, Serialize};
/// A chat message or inline message.
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum ChatOrInlineMessage {
Chat { chat_id: ChatId, message_id: i32 },
Inline { inline_message_id: i32 },
}

View file

@ -10,7 +10,6 @@ pub use chat::*;
pub use chat_action::*; pub use chat_action::*;
pub use chat_id::*; pub use chat_id::*;
pub use chat_member::*; pub use chat_member::*;
pub use chat_or_inline_message::*;
pub use chat_permissions::*; pub use chat_permissions::*;
pub use chat_photo::*; pub use chat_photo::*;
pub use chosen_inline_result::*; pub use chosen_inline_result::*;
@ -83,6 +82,7 @@ pub use sticker::*;
pub use sticker_set::*; pub use sticker_set::*;
pub use sticker_type::*; pub use sticker_type::*;
pub use successful_payment::*; pub use successful_payment::*;
pub use target_message::*;
pub use unit_false::*; pub use unit_false::*;
pub use unit_true::*; pub use unit_true::*;
pub use update::*; pub use update::*;
@ -104,7 +104,6 @@ mod chat;
mod chat_action; mod chat_action;
mod chat_id; mod chat_id;
mod chat_member; mod chat_member;
mod chat_or_inline_message;
mod chat_permissions; mod chat_permissions;
mod chat_photo; mod chat_photo;
mod chosen_inline_result; mod chosen_inline_result;
@ -150,6 +149,7 @@ mod sticker;
mod sticker_set; mod sticker_set;
mod sticker_type; mod sticker_type;
mod successful_payment; mod successful_payment;
mod target_message;
mod unit_false; mod unit_false;
mod unit_true; mod unit_true;
mod update; mod update;

View file

@ -7,14 +7,16 @@ pub enum StickerType {
/// dimensions must not exceed 512px, and either width or height must be /// dimensions must not exceed 512px, and either width or height must be
/// exactly 512px. /// exactly 512px.
/// ///
/// Pass [`InputFile::File`] to send a file that exists on /// Pass [`InputFile::FileId`] to send a sticker that exists on the Telegram
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for /// servers (recommended), pass an [`InputFile::Url`] for Telegram to get a
/// Telegram to get a .webp file from the Internet, or upload a new one /// sticker (.WEBP file) from the Internet, pass [`InputFile::File`] to
/// using [`InputFile::FileId`]. [More info on Sending Files »]. /// upload a sticker from the file system or [`InputFile::Memory`] to upload
/// a sticker from memory [More info on Sending Files »].
/// ///
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::FileId`]: crate::types::InputFile::FileId /// [`InputFile::FileId`]: crate::types::InputFile::FileId
/// [`InputFile::Url`]: crate::types::InputFile::Url
/// [`InputFile::File`]: crate::types::InputFile::File
/// [`InputFile::Memory`]: crate::types::InputFile::Memory
/// ///
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
Png(InputFile), Png(InputFile),

View file

@ -0,0 +1,17 @@
use crate::types::ChatId;
use serde::{Deserialize, Serialize};
/// A message in chat or inline message.
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TargetMessage {
Common { chat_id: ChatId, message_id: i32 },
Inline { inline_message_id: String },
}
impl From<String> for TargetMessage {
fn from(inline_message_id: String) -> Self {
Self::Inline { inline_message_id }
}
}

View file

@ -1,4 +1,4 @@
use crate::bot::sound_bot; use crate::bot::{sound_bot, TELOXIDE_PROXY};
/// Constructs a client from the `TELOXIDE_PROXY` environmental variable. /// Constructs a client from the `TELOXIDE_PROXY` environmental variable.
/// ///
@ -13,13 +13,13 @@ use crate::bot::sound_bot;
/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all
/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 /// [issue 223]: https://github.com/teloxide/teloxide/issues/223
pub fn client_from_env() -> reqwest::Client { pub fn client_from_env() -> reqwest::Client {
use reqwest::{Client, Proxy}; use reqwest::Proxy;
match std::env::var("TELOXIDE_PROXY").ok() { let builder = sound_bot();
Some(proxy) => {
Client::builder().proxy(Proxy::all(&proxy).expect("creating reqwest::Proxy")) match std::env::var(TELOXIDE_PROXY).ok() {
} Some(proxy) => builder.proxy(Proxy::all(&proxy).expect("creating reqwest::Proxy")),
None => sound_bot(), None => builder,
} }
.build() .build()
.expect("creating reqwest::Client") .expect("creating reqwest::Client")

View file

@ -6,6 +6,7 @@
//! //!
//! # Using BotCommand //! # Using BotCommand
//! ``` //! ```
//! # #[cfg(feature = "macros")] {
//! use teloxide::utils::command::BotCommand; //! use teloxide::utils::command::BotCommand;
//! //!
//! type UnitOfTime = u8; //! type UnitOfTime = u8;
@ -19,6 +20,7 @@
//! //!
//! let command = AdminCommand::parse("/ban 5 h", "bot_name").unwrap(); //! let command = AdminCommand::parse("/ban 5 h", "bot_name").unwrap();
//! assert_eq!(command, AdminCommand::Ban(5, 'h')); //! assert_eq!(command, AdminCommand::Ban(5, 'h'));
//! # }
//! ``` //! ```
//! //!
//! # Using parse_command //! # Using parse_command
@ -46,12 +48,16 @@
use serde::export::Formatter; use serde::export::Formatter;
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
#[cfg(feature = "macros")]
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
pub use teloxide_macros::BotCommand; pub use teloxide_macros::BotCommand;
/// An enumeration of bot's commands. /// An enumeration of bot's commands.
/// ///
/// # Example /// # Example
/// ``` /// ```
/// # #[cfg(feature = "macros")] {
/// use teloxide::utils::command::BotCommand; /// use teloxide::utils::command::BotCommand;
/// ///
/// type UnitOfTime = u8; /// type UnitOfTime = u8;
@ -65,6 +71,7 @@ pub use teloxide_macros::BotCommand;
/// ///
/// let command = AdminCommand::parse("/ban 5 h", "bot_name").unwrap(); /// let command = AdminCommand::parse("/ban 5 h", "bot_name").unwrap();
/// assert_eq!(command, AdminCommand::Ban(5, 'h')); /// assert_eq!(command, AdminCommand::Ban(5, 'h'));
/// # }
/// ``` /// ```
/// ///
/// ## Enum attributes /// ## Enum attributes
@ -86,6 +93,7 @@ pub use teloxide_macros::BotCommand;
/// ///
/// ### Example /// ### Example
/// ``` /// ```
/// # #[cfg(feature = "macros")] {
/// use teloxide::utils::command::BotCommand; /// use teloxide::utils::command::BotCommand;
/// ///
/// #[derive(BotCommand, PartialEq, Debug)] /// #[derive(BotCommand, PartialEq, Debug)]
@ -96,6 +104,7 @@ pub use teloxide_macros::BotCommand;
/// ///
/// let command = Command::parse("/text hello my dear friend!", "").unwrap(); /// let command = Command::parse("/text hello my dear friend!", "").unwrap();
/// assert_eq!(command, Command::Text("hello my dear friend!".to_string())); /// assert_eq!(command, Command::Text("hello my dear friend!".to_string()));
/// # }
/// ``` /// ```
/// ///
/// - `split` - separates a messsage by a given separator (the default is the /// - `split` - separates a messsage by a given separator (the default is the
@ -104,6 +113,7 @@ pub use teloxide_macros::BotCommand;
/// ///
/// ### Example /// ### Example
/// ``` /// ```
/// # #[cfg(feature = "macros")] {
/// use teloxide::utils::command::BotCommand; /// use teloxide::utils::command::BotCommand;
/// ///
/// #[derive(BotCommand, PartialEq, Debug)] /// #[derive(BotCommand, PartialEq, Debug)]
@ -114,6 +124,7 @@ pub use teloxide_macros::BotCommand;
/// ///
/// let command = Command::parse("/nums 1 32 -5", "").unwrap(); /// let command = Command::parse("/nums 1 32 -5", "").unwrap();
/// assert_eq!(command, Command::Nums(1, 32, -5)); /// assert_eq!(command, Command::Nums(1, 32, -5));
/// # }
/// ``` /// ```
/// ///
/// 5. `#[command(separator = "sep")]` /// 5. `#[command(separator = "sep")]`
@ -122,6 +133,7 @@ pub use teloxide_macros::BotCommand;
/// ///
/// ### Example /// ### Example
/// ``` /// ```
/// # #[cfg(feature = "macros")] {
/// use teloxide::utils::command::BotCommand; /// use teloxide::utils::command::BotCommand;
/// ///
/// #[derive(BotCommand, PartialEq, Debug)] /// #[derive(BotCommand, PartialEq, Debug)]
@ -132,6 +144,7 @@ pub use teloxide_macros::BotCommand;
/// ///
/// let command = Command::parse("/nums 1|32|5", "").unwrap(); /// let command = Command::parse("/nums 1|32|5", "").unwrap();
/// assert_eq!(command, Command::Nums(1, 32, 5)); /// assert_eq!(command, Command::Nums(1, 32, 5));
/// # }
/// ``` /// ```
/// ///
/// ## Variant attributes /// ## Variant attributes
@ -149,6 +162,7 @@ pub use teloxide_macros::BotCommand;
/// ///
/// ### Example /// ### Example
/// ``` /// ```
/// # #[cfg(feature = "macros")] {
/// use teloxide::utils::command::{BotCommand, ParseError}; /// use teloxide::utils::command::{BotCommand, ParseError};
/// ///
/// fn accept_two_digits(input: String) -> Result<(u8,), ParseError> { /// fn accept_two_digits(input: String) -> Result<(u8,), ParseError> {
@ -172,6 +186,7 @@ pub use teloxide_macros::BotCommand;
/// assert_eq!(command, Command::Num(12)); /// assert_eq!(command, Command::Num(12));
/// let command = Command::parse("/num 333", ""); /// let command = Command::parse("/num 333", "");
/// assert!(command.is_err()); /// assert!(command.is_err());
/// # }
/// ``` /// ```
/// ///
/// 3. `#[command(prefix = "prefix")]` /// 3. `#[command(prefix = "prefix")]`

View file

@ -1,9 +1,11 @@
#[cfg(feature = "macros")]
use teloxide::utils::command::{BotCommand, ParseError}; use teloxide::utils::command::{BotCommand, ParseError};
// We put tests here because macro expand in unit tests in module // We put tests here because macro expand in unit tests in module
// teloxide::utils::command was a failure // teloxide::utils::command was a failure
#[test] #[test]
#[cfg(feature = "macros")]
fn parse_command_with_args() { fn parse_command_with_args() {
#[command(rename = "lowercase")] #[command(rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)] #[derive(BotCommand, Debug, PartialEq)]
@ -19,6 +21,23 @@ fn parse_command_with_args() {
} }
#[test] #[test]
#[cfg(feature = "macros")]
fn parse_command_with_non_string_arg() {
#[command(rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)]
enum DefaultCommands {
Start(i32),
Help,
}
let data = "/start -50";
let expected = DefaultCommands::Start("-50".parse().unwrap());
let actual = DefaultCommands::parse(data, "").unwrap();
assert_eq!(actual, expected)
}
#[test]
#[cfg(feature = "macros")]
fn attribute_prefix() { fn attribute_prefix() {
#[command(rename = "lowercase")] #[command(rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)] #[derive(BotCommand, Debug, PartialEq)]
@ -35,6 +54,7 @@ fn attribute_prefix() {
} }
#[test] #[test]
#[cfg(feature = "macros")]
fn many_attributes() { fn many_attributes() {
#[command(rename = "lowercase")] #[command(rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)] #[derive(BotCommand, Debug, PartialEq)]
@ -49,6 +69,7 @@ fn many_attributes() {
} }
#[test] #[test]
#[cfg(feature = "macros")]
fn global_attributes() { fn global_attributes() {
#[command(prefix = "!", rename = "lowercase", description = "Bot commands")] #[command(prefix = "!", rename = "lowercase", description = "Bot commands")]
#[derive(BotCommand, Debug, PartialEq)] #[derive(BotCommand, Debug, PartialEq)]
@ -64,6 +85,7 @@ fn global_attributes() {
} }
#[test] #[test]
#[cfg(feature = "macros")]
fn parse_command_with_bot_name() { fn parse_command_with_bot_name() {
#[command(rename = "lowercase")] #[command(rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)] #[derive(BotCommand, Debug, PartialEq)]
@ -80,6 +102,7 @@ fn parse_command_with_bot_name() {
} }
#[test] #[test]
#[cfg(feature = "macros")]
fn parse_with_split() { fn parse_with_split() {
#[command(rename = "lowercase")] #[command(rename = "lowercase")]
#[command(parse_with = "split")] #[command(parse_with = "split")]
@ -96,6 +119,7 @@ fn parse_with_split() {
} }
#[test] #[test]
#[cfg(feature = "macros")]
fn parse_with_split2() { fn parse_with_split2() {
#[command(rename = "lowercase")] #[command(rename = "lowercase")]
#[command(parse_with = "split", separator = "|")] #[command(parse_with = "split", separator = "|")]
@ -112,6 +136,7 @@ fn parse_with_split2() {
} }
#[test] #[test]
#[cfg(feature = "macros")]
fn parse_custom_parser() { fn parse_custom_parser() {
fn custom_parse_function(s: String) -> Result<(u8, String), ParseError> { fn custom_parse_function(s: String) -> Result<(u8, String), ParseError> {
let vec = s.split_whitespace().collect::<Vec<_>>(); let vec = s.split_whitespace().collect::<Vec<_>>();
@ -139,6 +164,7 @@ fn parse_custom_parser() {
} }
#[test] #[test]
#[cfg(feature = "macros")]
fn parse_named_fields() { fn parse_named_fields() {
#[command(rename = "lowercase")] #[command(rename = "lowercase")]
#[command(parse_with = "split")] #[command(parse_with = "split")]
@ -155,6 +181,7 @@ fn parse_named_fields() {
} }
#[test] #[test]
#[cfg(feature = "macros")]
fn descriptions_off() { fn descriptions_off() {
#[command(rename = "lowercase")] #[command(rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)] #[derive(BotCommand, Debug, PartialEq)]

View file

@ -3,26 +3,38 @@ use std::{
future::Future, future::Future,
sync::Arc, sync::Arc,
}; };
use teloxide::dispatching::dialogue::{ use teloxide::dispatching::dialogue::{RedisStorage, Serializer, Storage};
serializer::{Bincode, CBOR, JSON},
RedisStorage, Serializer, Storage,
};
#[tokio::test] #[tokio::test]
async fn test_redis_json() { async fn test_redis_json() {
let storage = RedisStorage::open("redis://127.0.0.1:7777", JSON).await.unwrap(); let storage = RedisStorage::open(
"redis://127.0.0.1:7777",
teloxide::dispatching::dialogue::serializer::JSON,
)
.await
.unwrap();
test_redis(storage).await; test_redis(storage).await;
} }
#[tokio::test] #[tokio::test]
async fn test_redis_bincode() { async fn test_redis_bincode() {
let storage = RedisStorage::open("redis://127.0.0.1:7778", Bincode).await.unwrap(); let storage = RedisStorage::open(
"redis://127.0.0.1:7778",
teloxide::dispatching::dialogue::serializer::Bincode,
)
.await
.unwrap();
test_redis(storage).await; test_redis(storage).await;
} }
#[tokio::test] #[tokio::test]
async fn test_redis_cbor() { async fn test_redis_cbor() {
let storage = RedisStorage::open("redis://127.0.0.1:7779", CBOR).await.unwrap(); let storage = RedisStorage::open(
"redis://127.0.0.1:7779",
teloxide::dispatching::dialogue::serializer::CBOR,
)
.await
.unwrap();
test_redis(storage).await; test_redis(storage).await;
} }