diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..bd3d7995 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,25 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +I tried this code: +```rust +<code> +``` +I expected to see this happen: _explanation_ + +Instead, this happened: _explanation_ + +## Meta + +- `teloxide` version: <!-- (e.g.: `0.3.1`) --> +- rustc version: + ``` + <version> + ``` + <!-- use `rustc --version --verbose` to get it --> diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..f6b479f7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: 'Feature Request: <feature>' +labels: feature-request +assignees: Hirrolot, WaffleLapkin + +--- + +<!-- Describe your idea here --> + +### Pros + +<!-- Describe good parts of your idea --> + +### Cons + +<!-- Describe bad parts of your idea --> + +### Alternatives + +<!-- Of any, describe alternatives here --> diff --git a/.github/ISSUE_TEMPLATE/parse-error.md b/.github/ISSUE_TEMPLATE/parse-error.md new file mode 100644 index 00000000..0e3a05b8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/parse-error.md @@ -0,0 +1,30 @@ +--- +name: Parse error +about: Report issue with `teloxide` parsing of telegram response +title: 'Parse Error: <type or error description>' +labels: FIXME, bug +assignees: WaffleLapkin + +--- + +When using `<...>` method I've got `RequestError::InvalidJson` error with the following description: +```text +<Description of the inner serde error here> +``` + +## Steps to reproduce + +<!-- Steps to reproduce the issue - get the same error --> + +## Meta + +- `teloxide` version: <!-- (e.g.: `0.3.1`) --> +- rustc version: + ``` + <version> + ``` + <!-- use `rustc --version --verbose` to get it --> + +### Additional context + +<!-- Describe any additional context here, if needed--> diff --git a/.github/ISSUE_TEMPLATE/unknown-telegram-error.md b/.github/ISSUE_TEMPLATE/unknown-telegram-error.md new file mode 100644 index 00000000..6b5eafe2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/unknown-telegram-error.md @@ -0,0 +1,30 @@ +--- +name: Unknown telegram error +about: You've found telegram error which is not known to teloxide +title: 'Unknown Error: <error description>' +labels: FIXME, bug, good first issue +assignees: '' + +--- + +When using `<...>` method I've got `ApiError::Unknown` error with the following description: +```text +<Description of the error here (an inner string of ApiError::Unknown)> +``` + +## Steps to reproduce + +<!-- Steps to reproduce the issue - get the same error --> + +## Meta + +- `teloxide` version: <!-- (e.g.: `0.3.1`) --> +- rustc version: + ``` + <version> + ``` + <!-- use `rustc --version --verbose` to get it --> + +### Additional context + +<!-- Describe any additional context here, if needed--> diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7b3cda1..f33adb24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,15 +1,15 @@ - on: push: branches: [ master ] pull_request: - branches: [ master ] + branches: [ master, dev ] name: Continuous integration jobs: - code-checks: + style: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -17,33 +17,78 @@ jobs: profile: minimal toolchain: nightly override: true - components: rustfmt, clippy - - name: Cargo fmt - run: cargo +nightly fmt --all -- --check - - uses: actions-rs/toolchain@v1 + components: rustfmt + + - name: fmt + uses: actions-rs/cargo@v1 with: - profile: minimal - toolchain: stable - override: true - - name: Cargo clippy - run: cargo clippy --all --all-targets --all-features -- -D warnings - stable-test: + command: fmt + args: --all -- --check + + clippy: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: nightly override: true + components: clippy + + - name: clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-targets --all-features -- -D warnings + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + - beta + - nightly + + include: + - rust: stable + features: "--features full" + - rust: beta + features: "--features full" + - rust: nightly + features: "--all-features" + + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + + - name: build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose ${{ matrix.features }} + - name: Setup redis run: | sudo apt install redis-server redis-server --port 7777 > /dev/null & redis-server --port 7778 > /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: runs-on: ubuntu-latest strategy: @@ -65,5 +110,5 @@ jobs: profile: minimal toolchain: stable override: true - - name: Test the example + - name: Check the example run: cd examples && cd ${{ matrix.example }} && cargo check diff --git a/.gitignore b/.gitignore index 7967ab0a..b1c241c5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ Cargo.lock .idea/ .vscode/ -examples/*/target \ No newline at end of file +examples/*/target +*.sqlite diff --git a/CHANGELOG.md b/CHANGELOG.md index 735cbe1d..256bc43e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [0.4.0] - 2021-03-19 + ### Added + - Integrate [teloxide-core]. - 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(())`. + - The `sqlite-storage` feature -- enables SQLite support. + - `Dispatcher::{my_chat_members_handler, chat_members_handler}` + +[teloxide-core]: https://github.com/teloxide/teloxide-core + +### Deprecated + + - `UpdateWithCx::answer_str` + +### Fixed + + - Hide `SubtransitionOutputType` from the docs. ### Changed + - Export `teloxide_macros::teloxide` in `prelude`. + - `dispatching::dialogue::serializer::{JSON -> Json, CBOR -> Cbor}` - 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 @@ -21,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `#[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]) + - Remove `ResponseResult` from `prelude`. [issue 253]: https://github.com/teloxide/teloxide/issues/253 [pr 257]: https://github.com/teloxide/teloxide/pull/257 diff --git a/CODE_STYLE.md b/CODE_STYLE.md index 656a5c34..964aea8c 100644 --- a/CODE_STYLE.md +++ b/CODE_STYLE.md @@ -123,3 +123,4 @@ C: Into<String>, { ... } ## Misc 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. + 3. `Box::pin(async [move] { ... })` instead of `async [move] { ... }.boxed()`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b81f9695..c5853790 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,13 @@ # Contributing + Before contributing, please read [our code style](https://github.com/teloxide/teloxide/blob/master/CODE_STYLE.md) and [the license](https://github.com/teloxide/teloxide/blob/master/LICENSE). -To change the source code, fork 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 test --all -cargo doc --open +RUSTDOCFLAGS="--cfg docsrs" cargo doc --open --all-features # Using nightly rustfmt cargo +nightly fmt --all -- --check ``` diff --git a/Cargo.toml b/Cargo.toml index 0146206b..8873a8c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide" -version = "0.3.4" +version = "0.4.0" edition = "2018" description = "An elegant Telegram bots framework for Rust" repository = "https://github.com/teloxide/teloxide" @@ -24,42 +24,90 @@ authors = [ maintenance = { status = "actively-developed" } [features] +sqlite-storage = ["sqlx"] redis-storage = ["redis"] cbor-serializer = ["serde_cbor"] bincode-serializer = ["bincode"] frunk- = ["frunk"] +macros = ["teloxide-macros"] + +native-tls = ["teloxide-core/native-tls"] +rustls = ["teloxide-core/rustls"] +auto-send = ["teloxide-core/auto_send"] +throttle = ["teloxide-core/throttle"] +cache-me = ["teloxide-core/cache_me"] + +# currently used for `README.md` tests, building docs for `docsrs` to add `This is supported on feature="..." only.`, +# and for teloxide-core. +nightly = ["teloxide-core/nightly"] + +full = [ + "sqlite-storage", + "redis-storage", + "cbor-serializer", + "bincode-serializer", + "frunk", + "macros", + "teloxide-core/full", + "native-tls", + "rustls", + "auto-send", + "throttle", + "cache-me" +] [dependencies] -serde_json = "1.0.55" -serde = { version = "1.0.114", features = ["derive"] } +teloxide-core = { version = "0.2.1", default-features = false } +teloxide-macros = { version = "0.4", optional = true } -tokio = { version = "0.2.21", features = ["fs", "stream"] } -tokio-util = "0.3.1" +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } -reqwest = { version = "0.10.6", features = ["json", "stream"] } -log = "0.4.8" +tokio = { version = "1.2", features = ["fs"] } +tokio-util = "0.6" +tokio-stream = "0.1" + +reqwest = { version = "0.11", features = ["json", "stream"] } +log = "0.4" lockfree = "0.5.1" -bytes = "0.5.5" -mime = "0.3.16" +bytes = "1.0" +mime = "0.3" -derive_more = "0.99.9" -thiserror = "1.0.20" -async-trait = "0.1.36" -futures = "0.3.5" -pin-project = "0.4.22" -serde_with_macros = "1.1.0" +derive_more = "0.99" +thiserror = "1.0" +async-trait = "0.1" +futures = "0.3" +pin-project = "1.0" +serde_with_macros = "1.4" -redis = { version = "0.16.0", optional = true } -serde_cbor = { version = "0.11.1", optional = true } -bincode = { version = "1.3.1", optional = true } -frunk = { version = "0.3.1", optional = true } - -teloxide-macros = "0.3.2" +sqlx = { version = "0.5", optional = true, default-features = false, features = [ + "runtime-tokio-native-tls", + "macros", + "sqlite", +] } +redis = { version = "0.20", features = ["tokio-comp"], optional = true } +serde_cbor = { version = "0.11", optional = true } +bincode = { version = "1.3", optional = true } +frunk = { version = "0.3", optional = true } [dev-dependencies] smart-default = "0.6.0" -rand = "0.7.3" +rand = "0.8.3" pretty_env_logger = "0.4.0" lazy_static = "1.4.0" -tokio = { version = "0.2.21", features = ["fs", "stream", "rt-threaded", "macros"] } +tokio = { version = "1.2.0", features = ["fs", "rt-multi-thread", "macros"] } + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[[test]] +name = "redis" +path = "tests/redis.rs" +required-features = ["redis-storage", "cbor-serializer", "bincode-serializer"] + +[[test]] +name = "sqlite" +path = "tests/sqlite.rs" +required-features = ["sqlite-storage", "cbor-serializer", "bincode-serializer"] diff --git a/README.md b/README.md index d487a130..874f0703 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,55 @@ <div align="center"> <img src="ICON.png" width="250"/> <h1>teloxide</h1> - <a href="https://docs.rs/teloxide/"> <img src="https://docs.rs/teloxide/badge.svg"> </a> <a href="https://github.com/teloxide/teloxide/actions"> <img src="https://github.com/teloxide/teloxide/workflows/Continuous%20integration/badge.svg"> </a> + <a href="https://teloxide.netlify.com"> + <img src="https://img.shields.io/badge/docs-dev-blue)"> + </a> <a href="https://crates.io/crates/teloxide"> <img src="https://img.shields.io/crates/v/teloxide.svg"> </a> + <a href="https://core.telegram.org/bots/api"> + <img src="https://img.shields.io/badge/API coverage-Up to 5.1 (inclusively)-green.svg"> + </a> <a href="https://t.me/teloxide"> <img src="https://img.shields.io/badge/official%20chat-t.me%2Fteloxide-blueviolet"> </a> - <a href="https://core.telegram.org/bots/api"> - <img src="https://img.shields.io/badge/API coverage-Up to 0.4.7 (inclusively)-green.svg"> - </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. </div> ## Table of contents - - [Highlights](https://github.com/teloxide/teloxide#highlights) - - [Setting up your environment](https://github.com/teloxide/teloxide#setting-up-your-environment) - - [API overview](https://github.com/teloxide/teloxide#api-overview) - - [The dices bot](https://github.com/teloxide/teloxide#the-dices-bot) - - [Commands](https://github.com/teloxide/teloxide#commands) - - [Dialogues management](https://github.com/teloxide/teloxide#dialogues-management) - - [Recommendations](https://github.com/teloxide/teloxide#recommendations) - - [Cargo features](https://github.com/teloxide/teloxide#cargo-features) - - [FAQ](https://github.com/teloxide/teloxide#faq) - - [Community bots](https://github.com/teloxide/teloxide#community-bots) - - [Contributing](https://github.com/teloxide/teloxide#contributing) + - [Highlights](#highlights) + - [Setting up your environment](#setting-up-your-environment) + - [API overview](#api-overview) + - [The dices bot](#the-dices-bot) + - [Commands](#commands) + - [Dialogues management](#dialogues-management) + - [Recommendations](#recommendations) + - [Cargo features](#cargo-features) + - [FAQ](#faq) + - [Community bots](#community-bots) + - [Contributing](#contributing) ## Highlights - - **Functional 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 [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] and [Sqlite]. -[persistent]: https://en.wikipedia.org/wiki/Persistence_(computer_science) +[persistence]: https://en.wikipedia.org/wiki/Persistence_(computer_science) [Redis]: https://redis.io/ +[Sqlite]: https://www.sqlite.org - - **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 [serde-json]: https://github.com/serde-rs/json @@ -62,7 +65,7 @@ $ export TELOXIDE_TOKEN=<Your token here> # Windows $ 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 # If you're using stable $ rustup update stable @@ -73,25 +76,22 @@ $ rustup update 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 [dependencies] -teloxide = "0.3" -teloxide-macros = "0.3" - +teloxide = "0.4" log = "0.4.8" pretty_env_logger = "0.4.0" - -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } +tokio = { version = "1.3", features = ["rt-threaded", "macros"] } ``` ## API overview ### 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)) -```rust +([Full](./examples/dices_bot/src/main.rs)) +```rust,no_run use teloxide::prelude::*; #[tokio::main] @@ -99,20 +99,19 @@ async fn main() { teloxide::enable_logging!(); log::info!("Starting dices_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); teloxide::repl(bot, |message| async move { - message.answer_dice().send().await?; - ResponseResult::<()>::Ok(()) + message.answer_dice().await?; + respond(()) }) .await; } - ``` <div align="center"> <kbd> - <img src=https://github.com/teloxide/teloxide/raw/master/media/DICES_BOT.gif /> + <img src=../../raw/master/media/DICES_BOT.gif /> </kbd> </div> @@ -126,9 +125,11 @@ Commands are strongly typed and defined declaratively, similar to how we define [structopt]: https://docs.rs/structopt/0.3.9/structopt/ [serde-json]: https://github.com/serde-rs/json -([Full](https://github.com/teloxide/teloxide/blob/master/examples/simple_commands_bot/src/main.rs)) -```rust -// Imports are omitted... +([Full](./examples/simple_commands_bot/src/main.rs)) +```rust,no_run +use teloxide::{prelude::*, utils::command::BotCommand}; + +use std::error::Error; #[derive(BotCommand)] #[command(rename = "lowercase", description = "These commands are supported:")] @@ -141,14 +142,17 @@ enum Command { UsernameAndAge { username: String, age: u8 }, } -async fn answer(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<()> { +async fn answer( + cx: UpdateWithCx<AutoSend<Bot>, Message>, + command: Command, +) -> Result<(), Box<dyn Error + Send + Sync>> { match command { Command::Help => cx.answer(Command::descriptions()).send().await?, Command::Username(username) => { - cx.answer_str(format!("Your username is @{}.", username)).await? + cx.answer(format!("Your username is @{}.", username)).await? } Command::UsernameAndAge { username, age } => { - cx.answer_str(format!("Your username is @{} and age is {}.", username, age)).await? + cx.answer(format!("Your username is @{} and age is {}.", username, age)).await? } }; @@ -160,27 +164,28 @@ async fn main() { teloxide::enable_logging!(); log::info!("Starting simple_commands_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); - teloxide::commands_repl(bot, panic!("Your bot's name here"), answer).await; + let bot_name: String = panic!("Your bot's name here"); + teloxide::commands_repl(bot, bot_name, answer).await; } ``` <div align="center"> <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> </div> ### 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 -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)) -```rust +([dialogue_bot/src/dialogue/mod.rs](./examples/dialogue_bot/src/dialogue/mod.rs)) +```rust,ignore // Imports are omitted... #[derive(Transition, From)] @@ -198,20 +203,24 @@ 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> <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)) -```rust +([dialogue_bot/src/dialogue/states/start.rs](./examples/dialogue_bot/src/dialogue/states/start.rs)) +```rust,ignore // Imports are omitted... pub struct StartState; #[teloxide(subtransition)] -async fn start(_state: StartState, cx: TransitionIn, _ans: String) -> TransitionOut<Dialogue> { - cx.answer_str("Let's start! What's your full name?").await?; +async fn start( + _state: StartState, + cx: TransitionIn<AutoSend<Bot>>, + _ans: String, +) -> TransitionOut<Dialogue> { + cx.answer("Let's start! What's your full name?").await?; next(ReceiveFullNameState) } ``` @@ -221,8 +230,8 @@ async fn start(_state: StartState, cx: TransitionIn, _ans: String) -> Transition <details> <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)) -```rust +([dialogue_bot/src/dialogue/states/receive_full_name.rs](./examples/dialogue_bot/src/dialogue/states/receive_full_name.rs)) +```rust,ignore // Imports are omitted... #[derive(Generic)] @@ -231,10 +240,10 @@ pub struct ReceiveFullNameState; #[teloxide(subtransition)] async fn receive_full_name( state: ReceiveFullNameState, - cx: TransitionIn, + cx: TransitionIn<AutoSend<Bot>>, ans: String, ) -> TransitionOut<Dialogue> { - cx.answer_str("How old are you?").await?; + cx.answer("How old are you?").await?; next(ReceiveAgeState::up(state, ans)) } ``` @@ -244,8 +253,8 @@ async fn receive_full_name( <details> <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)) -```rust +([dialogue_bot/src/dialogue/states/receive_age.rs](./examples/dialogue_bot/src/dialogue/states/receive_age.rs)) +```rust,ignore // Imports are omitted... #[derive(Generic)] @@ -256,16 +265,16 @@ pub struct ReceiveAgeState { #[teloxide(subtransition)] async fn receive_age_state( state: ReceiveAgeState, - cx: TransitionIn, + cx: TransitionIn<AutoSend<Bot>>, ans: String, ) -> TransitionOut<Dialogue> { match ans.parse::<u8>() { Ok(ans) => { - cx.answer_str("What's your location?").await?; + cx.answer("What's your location?").await?; next(ReceiveLocationState::up(state, ans)) } _ => { - cx.answer_str("Send me a number.").await?; + cx.answer("Send me a number.").await?; next(state) } } @@ -277,8 +286,8 @@ async fn receive_age_state( <details> <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)) -```rust +([dialogue_bot/src/dialogue/states/receive_location.rs](./examples/dialogue_bot/src/dialogue/states/receive_location.rs)) +```rust,ignore // Imports are omitted... #[derive(Generic)] @@ -290,10 +299,10 @@ pub struct ReceiveLocationState { #[teloxide(subtransition)] async fn receive_location( state: ReceiveLocationState, - cx: TransitionIn, + cx: TransitionIn<AutoSend<Bot>>, ans: String, ) -> TransitionOut<Dialogue> { - cx.answer_str(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans)) + cx.answer(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans)) .await?; exit() } @@ -301,12 +310,12 @@ async fn receive_location( </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: -([dialogue_bot/src/main.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/main.rs)) -```rust +([dialogue_bot/src/main.rs](./examples/dialogue_bot/src/main.rs)) +```rust,ignore // Imports are omitted... #[tokio::main] @@ -314,7 +323,7 @@ async fn main() { teloxide::enable_logging!(); log::info!("Starting dialogue_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); teloxide::dialogues_repl(bot, |message, dialogue| async move { handle_message(message, dialogue).await.expect("Something wrong with the bot!") @@ -322,10 +331,13 @@ async fn main() { .await; } -async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> { - match cx.update.text_owned() { +async fn handle_message( + cx: UpdateWithCx<AutoSend<Bot>, Message>, + dialogue: Dialogue, +) -> TransitionOut<Dialogue> { + match cx.update.text().map(ToOwned::to_owned) { None => { - cx.answer_str("Send me a text message.").await?; + cx.answer("Send me a text message.").await?; next(dialogue) } Some(ans) => dialogue.react(cx, ans).await, @@ -335,11 +347,11 @@ async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> Transi <div align="center"> <kbd> - <img src=https://github.com/teloxide/teloxide/raw/master/media/DIALOGUE_BOT.gif /> + <img src=../../raw/master/media/DIALOGUE_BOT.gif /> </kbd> </div> -[More examples!](https://github.com/teloxide/teloxide/tree/master/examples) +[More examples!](./examples) ## Recommendations - Use this pattern: @@ -364,21 +376,33 @@ 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 - `redis-storage` -- enables the [Redis] support. + - `sqlite-storage` -- enables the [Sqlite] support. - `cbor-serializer` -- enables the [CBOR] serializer for dialogues. - `bincode-serializer` -- enables the [Bincode] serializer for dialogues. - `frunk` -- enables [`teloxide::utils::UpState`], which allows mapping from a structure of `field1, ..., fieldN` to a structure of `field1, ..., fieldN, fieldN+1`. + - `macros` -- re-exports macros from [`teloxide-macros`]. + - `native-tls` -- enables the [`native-tls`] TLS implementation (enabled by default). + - `rustls` -- enables the [`rustls`] TLS implementation. + - `auto-send` -- enables `AutoSend` bot adaptor. + - `cache-me` -- enables the `CacheMe` bot adaptor. + - `full` -- enables all the features except `nightly`. + - `nightly` -- enables nightly-only features (see the [teloxide-core's features]). [CBOR]: https://en.wikipedia.org/wiki/CBOR [Bincode]: https://github.com/servo/bincode [`teloxide::utils::UpState`]: https://docs.rs/teloxide/latest/teloxide/utils/trait.UpState.html +[`teloxide-macros`]: https://github.com/teloxide/teloxide-macros +[`native-tls`]: https://docs.rs/native-tls +[`rustls`]: https://docs.rs/rustls +[teloxide-core's features]: https://docs.rs/teloxide-core/0.2.1/teloxide_core/#cargo-features ## FAQ -Q: Where I can ask questions? +**Q: Where I can ask questions?** A: [Issues](https://github.com/teloxide/teloxide/issues) is a good place for well-formed questions, for example, about: @@ -389,27 +413,27 @@ A: [Issues](https://github.com/teloxide/teloxide/issues) is a good place for wel If you can't compile your bot due to compilation errors and need quick help, feel free to ask in [our official Telegram group](https://t.me/teloxide). -Q: Do you support the Telegram API for clients? +**Q: Do you support the Telegram API for clients?** 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 spreads 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. What is even more interesting is that [mux-stream] could make a library from teloxide, not a framework, since the design could be defined by just combining streams of updates. +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: - [Marvin's Marvellous Guide to All Things Webhook](https://core.telegram.org/bots/webhooks) - [Using self-signed certificates](https://core.telegram.org/bots/self-signed) -Q: Can I use different loggers? +**Q: Can I use different loggers?** A: Yes. You can setup any logger, for example, [fern], e.g. teloxide has no specific requirements as it depends only on [log]. Remember that [`enable_logging!`] and [`enable_logging_with_filter!`] are just **optional** utilities. @@ -421,9 +445,13 @@ A: Yes. You can setup any logger, for example, [fern], e.g. teloxide has no spec ## Community bots 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) - - [_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) + - [_steadylearner/subreddit_reader_](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/subreddit_reader) + - [_ArtHome12/vzmuinebot -- Telegram bot for food menu navigate_](https://github.com/ArtHome12/vzmuinebot) + - [_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) + - [_sschiz/janitor-bot_ -- A bot that removes users trying to join to a chat that is designed for comments](https://github.com/sschiz/janitor-bot) + - [ myblackbeard/basketball-betting-bot -- The bot lets you bet on NBA games against your buddies](https://github.com/myblackbeard/basketball-betting-bot) ## Contributing See [CONRIBUTING.md](https://github.com/teloxide/teloxide/blob/master/CONTRIBUTING.md). diff --git a/examples/README.md b/examples/README.md index db492ce7..21515200 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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. | 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. | | [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. | -| [guess_a_number_bot](guess_a_number_bot) | The "guess a number" game. | -| [dialogue_bot](dialogue_bot) | Drive a dialogue with a user using a type-safe finite automaton. | -| [admin_bot](admin_bot) | A bot, which can ban, kick, and mute on a command. | -| [shared_state_bot](shared_state_bot) | A bot that shows how to deal with shared state. | +| [redis_remember_bot](redis_remember_bot) | Uses `RedisStorage` instead of `InMemStorage`. | +| [dialogue_bot](dialogue_bot) | How to deal with dialogues. | +| [admin_bot](admin_bot) | Ban, kick, and mute on a command. | +| [shared_state_bot](shared_state_bot) | How to deal with shared state. | diff --git a/examples/admin_bot/Cargo.toml b/examples/admin_bot/Cargo.toml index 3a4950a8..ccaf49c0 100644 --- a/examples/admin_bot/Cargo.toml +++ b/examples/admin_bot/Cargo.toml @@ -7,10 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["macros", "auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } -teloxide = { path = "../../" } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } [profile.release] -lto = true \ No newline at end of file +lto = true diff --git a/examples/admin_bot/src/main.rs b/examples/admin_bot/src/main.rs index ea4f9078..0351f9bd 100644 --- a/examples/admin_bot/src/main.rs +++ b/examples/admin_bot/src/main.rs @@ -1,6 +1,8 @@ -use std::str::FromStr; +use std::{convert::TryInto, error::Error, str::FromStr}; -use teloxide::{prelude::*, types::ChatPermissions, utils::command::BotCommand}; +use teloxide::{prelude::*, utils::command::BotCommand}; + +use teloxide::types::ChatPermissions; // Derive BotCommand to parse text with a command into this enumeration. // @@ -60,20 +62,19 @@ fn calc_restrict_time(time: u32, unit: UnitOfTime) -> u32 { } } -type Cx = UpdateWithCx<Message>; +type Cx = UpdateWithCx<AutoSend<Bot>, Message>; // Mute a user with a replied message. -async fn mute_user(cx: &Cx, time: u32) -> ResponseResult<()> { +async fn mute_user(cx: &Cx, time: u32) -> Result<(), Box<dyn Error + Send + Sync>> { match cx.update.reply_to_message() { Some(msg1) => { - cx.bot + cx.requester .restrict_chat_member( cx.update.chat_id(), msg1.from().expect("Must be MessageKind::Common").id, ChatPermissions::default(), ) - .until_date(cx.update.date + time as i32) - .send() + .until_date((cx.update.date + time as i32).try_into().unwrap()) .await?; } None => { @@ -84,11 +85,14 @@ async fn mute_user(cx: &Cx, time: u32) -> ResponseResult<()> { } // Kick a user with a replied message. -async fn kick_user(cx: &Cx) -> ResponseResult<()> { +async fn kick_user(cx: &Cx) -> Result<(), Box<dyn Error + Send + Sync>> { match cx.update.reply_to_message() { Some(mes) => { // bot.unban_chat_member can also kicks a user from a group chat. - cx.bot.unban_chat_member(cx.update.chat_id(), mes.from().unwrap().id).send().await?; + cx.requester + .unban_chat_member(cx.update.chat_id(), mes.from().unwrap().id) + .send() + .await?; } None => { cx.reply_to("Use this command in reply to another message").send().await?; @@ -98,16 +102,15 @@ async fn kick_user(cx: &Cx) -> ResponseResult<()> { } // Ban a user with replied message. -async fn ban_user(cx: &Cx, time: u32) -> ResponseResult<()> { +async fn ban_user(cx: &Cx, time: u32) -> Result<(), Box<dyn Error + Send + Sync>> { match cx.update.reply_to_message() { Some(message) => { - cx.bot + cx.requester .kick_chat_member( cx.update.chat_id(), message.from().expect("Must be MessageKind::Common").id, ) - .until_date(cx.update.date + time as i32) - .send() + .until_date((cx.update.date + time as i32).try_into().unwrap()) .await?; } None => { @@ -117,7 +120,7 @@ async fn ban_user(cx: &Cx, time: u32) -> ResponseResult<()> { Ok(()) } -async fn action(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<()> { +async fn action(cx: Cx, command: Command) -> Result<(), Box<dyn Error + Send + Sync>> { match command { Command::Help => cx.answer(Command::descriptions()).send().await.map(|_| ())?, Command::Kick => kick_user(&cx).await?, @@ -137,7 +140,8 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting admin_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); - teloxide::commands_repl(bot, panic!("Your bot's name here"), action).await; + let bot_name: String = panic!("Your bot's name here"); + teloxide::commands_repl(bot, bot_name, action).await; } diff --git a/examples/dialogue_bot/Cargo.toml b/examples/dialogue_bot/Cargo.toml index 1f08c16a..45f928ea 100644 --- a/examples/dialogue_bot/Cargo.toml +++ b/examples/dialogue_bot/Cargo.toml @@ -7,19 +7,17 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["frunk", "macros", "auto-send"] } + +futures = "0.3.5" +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } + log = "0.4.8" pretty_env_logger = "0.4.0" +derive_more = "0.99.9" frunk = "0.3.1" frunk_core = "0.3.1" -futures = "0.3.5" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } - -teloxide = { path = "../../", features = ["frunk"] } -teloxide-macros = "0.3.2" - -derive_more = "0.99.9" - [profile.release] lto = true diff --git a/examples/dialogue_bot/src/dialogue/mod.rs b/examples/dialogue_bot/src/dialogue/mod.rs index 176474b6..4bae759a 100644 --- a/examples/dialogue_bot/src/dialogue/mod.rs +++ b/examples/dialogue_bot/src/dialogue/mod.rs @@ -4,7 +4,7 @@ use crate::dialogue::states::{ ReceiveAgeState, ReceiveFullNameState, ReceiveLocationState, StartState, }; use derive_more::From; -use teloxide_macros::Transition; +use teloxide::macros::Transition; #[derive(Transition, From)] pub enum Dialogue { diff --git a/examples/dialogue_bot/src/dialogue/states/receive_age.rs b/examples/dialogue_bot/src/dialogue/states/receive_age.rs index faa42bc7..36c72a23 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_age.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_age.rs @@ -1,6 +1,5 @@ use crate::dialogue::{states::receive_location::ReceiveLocationState, Dialogue}; use teloxide::prelude::*; -use teloxide_macros::teloxide; #[derive(Generic)] pub struct ReceiveAgeState { @@ -10,16 +9,16 @@ pub struct ReceiveAgeState { #[teloxide(subtransition)] async fn receive_age_state( state: ReceiveAgeState, - cx: TransitionIn, + cx: TransitionIn<AutoSend<Bot>>, ans: String, ) -> TransitionOut<Dialogue> { match ans.parse::<u8>() { Ok(ans) => { - cx.answer_str("What's your location?").await?; + cx.answer("What's your location?").await?; next(ReceiveLocationState::up(state, ans)) } _ => { - cx.answer_str("Send me a number.").await?; + cx.answer("Send me a number.").await?; next(state) } } diff --git a/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs b/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs index 85f19ad2..21d3fef2 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs @@ -1,6 +1,5 @@ use crate::dialogue::{states::receive_age::ReceiveAgeState, Dialogue}; use teloxide::prelude::*; -use teloxide_macros::teloxide; #[derive(Generic)] pub struct ReceiveFullNameState; @@ -8,9 +7,9 @@ pub struct ReceiveFullNameState; #[teloxide(subtransition)] async fn receive_full_name( state: ReceiveFullNameState, - cx: TransitionIn, + cx: TransitionIn<AutoSend<Bot>>, ans: String, ) -> TransitionOut<Dialogue> { - cx.answer_str("How old are you?").await?; + cx.answer("How old are you?").await?; next(ReceiveAgeState::up(state, ans)) } diff --git a/examples/dialogue_bot/src/dialogue/states/receive_location.rs b/examples/dialogue_bot/src/dialogue/states/receive_location.rs index d64f35f3..aaa1af2d 100644 --- a/examples/dialogue_bot/src/dialogue/states/receive_location.rs +++ b/examples/dialogue_bot/src/dialogue/states/receive_location.rs @@ -1,6 +1,5 @@ use crate::dialogue::Dialogue; use teloxide::prelude::*; -use teloxide_macros::teloxide; #[derive(Generic)] pub struct ReceiveLocationState { @@ -11,10 +10,10 @@ pub struct ReceiveLocationState { #[teloxide(subtransition)] async fn receive_location( state: ReceiveLocationState, - cx: TransitionIn, + cx: TransitionIn<AutoSend<Bot>>, ans: String, ) -> TransitionOut<Dialogue> { - cx.answer_str(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans)) + cx.answer(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans)) .await?; exit() } diff --git a/examples/dialogue_bot/src/dialogue/states/start.rs b/examples/dialogue_bot/src/dialogue/states/start.rs index e5354159..a4f3c192 100644 --- a/examples/dialogue_bot/src/dialogue/states/start.rs +++ b/examples/dialogue_bot/src/dialogue/states/start.rs @@ -1,11 +1,14 @@ use crate::dialogue::{states::ReceiveFullNameState, Dialogue}; use teloxide::prelude::*; -use teloxide_macros::teloxide; pub struct StartState; #[teloxide(subtransition)] -async fn start(_state: StartState, cx: TransitionIn, _ans: String) -> TransitionOut<Dialogue> { - cx.answer_str("Let's start! What's your full name?").await?; +async fn start( + _state: StartState, + cx: TransitionIn<AutoSend<Bot>>, + _ans: String, +) -> TransitionOut<Dialogue> { + cx.answer("Let's start! What's your full name?").await?; next(ReceiveFullNameState) } diff --git a/examples/dialogue_bot/src/main.rs b/examples/dialogue_bot/src/main.rs index e2c64b07..cc2e6e76 100644 --- a/examples/dialogue_bot/src/main.rs +++ b/examples/dialogue_bot/src/main.rs @@ -34,7 +34,7 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting dialogue_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); teloxide::dialogues_repl(bot, |message, dialogue| async move { handle_message(message, dialogue).await.expect("Something wrong with the bot!") @@ -42,10 +42,13 @@ async fn run() { .await; } -async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> { - match cx.update.text_owned() { +async fn handle_message( + cx: UpdateWithCx<AutoSend<Bot>, Message>, + dialogue: Dialogue, +) -> TransitionOut<Dialogue> { + match cx.update.text().map(ToOwned::to_owned) { None => { - cx.answer_str("Send me a text message.").await?; + cx.answer("Send me a text message.").await?; next(dialogue) } Some(ans) => dialogue.react(cx, ans).await, diff --git a/examples/dices_bot/Cargo.toml b/examples/dices_bot/Cargo.toml index 3c2a6ed8..763f2c8b 100644 --- a/examples/dices_bot/Cargo.toml +++ b/examples/dices_bot/Cargo.toml @@ -7,10 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } -teloxide = { path = "../../" } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } [profile.release] -lto = true \ No newline at end of file +lto = true diff --git a/examples/dices_bot/src/main.rs b/examples/dices_bot/src/main.rs index ea7f4424..99cb370e 100644 --- a/examples/dices_bot/src/main.rs +++ b/examples/dices_bot/src/main.rs @@ -11,11 +11,11 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting dices_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); teloxide::repl(bot, |message| async move { - message.answer_dice().send().await?; - ResponseResult::<()>::Ok(()) + message.answer_dice().await?; + respond(()) }) .await; } diff --git a/examples/heroku_ping_pong_bot/Cargo.toml b/examples/heroku_ping_pong_bot/Cargo.toml index d568e8a8..edcc76f5 100644 --- a/examples/heroku_ping_pong_bot/Cargo.toml +++ b/examples/heroku_ping_pong_bot/Cargo.toml @@ -7,12 +7,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } -teloxide = { path = "../../" } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } +tokio-stream = "0.1.4" # Used to setup a webhook -warp = "0.2.2" +warp = "0.3.0" reqwest = "0.10.4" serde_json = "1.0.50" diff --git a/examples/heroku_ping_pong_bot/src/main.rs b/examples/heroku_ping_pong_bot/src/main.rs index 23bae8f6..8f02734a 100644 --- a/examples/heroku_ping_pong_bot/src/main.rs +++ b/examples/heroku_ping_pong_bot/src/main.rs @@ -1,10 +1,11 @@ // The version of Heroku ping-pong-bot, which uses a webhook to receive updates // from Telegram, instead of long polling. -use teloxide::{dispatching::update_listeners, prelude::*}; +use teloxide::{dispatching::update_listeners, prelude::*, types::Update}; use std::{convert::Infallible, env, net::SocketAddr}; use tokio::sync::mpsc; +use tokio_stream::wrappers::UnboundedReceiverStream; use warp::Filter; use reqwest::StatusCode; @@ -19,7 +20,7 @@ async fn handle_rejection(error: warp::Rejection) -> Result<impl warp::Reply, In Ok(StatusCode::INTERNAL_SERVER_ERROR) } -pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infallible> { +pub async fn webhook<'a>(bot: AutoSend<Bot>) -> impl update_listeners::UpdateListener<Infallible> { // Heroku defines auto defines a port value let teloxide_token = env::var("TELOXIDE_TOKEN").expect("TELOXIDE_TOKEN env variable missing"); let port: u16 = env::var("PORT") @@ -31,7 +32,7 @@ pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infa let path = format!("bot{}", teloxide_token); let url = format!("https://{}/{}", host, path); - bot.set_webhook(url).send().await.expect("Cannot setup a webhook"); + bot.set_webhook(url).await.expect("Cannot setup a webhook"); let (tx, rx) = mpsc::unbounded_channel(); @@ -39,20 +40,7 @@ pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infa .and(warp::path(path)) .and(warp::body::json()) .map(move |json: serde_json::Value| { - let try_parse = match serde_json::from_str(&json.to_string()) { - Ok(update) => Ok(update), - Err(error) => { - log::error!( - "Cannot parse an update.\nError: {:?}\nValue: {}\n\ - This is a bug in teloxide, please open an issue here: \ - https://github.com/teloxide/teloxide/issues.", - error, - json - ); - Err(error) - } - }; - if let Ok(update) = try_parse { + if let Ok(update) = Update::try_parse(&json) { tx.send(Ok(update)).expect("Cannot send an incoming update from the webhook") } @@ -64,21 +52,21 @@ pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infa let address = format!("0.0.0.0:{}", port); tokio::spawn(serve.run(address.parse::<SocketAddr>().unwrap())); - rx + UnboundedReceiverStream::new(rx) } async fn run() { teloxide::enable_logging!(); log::info!("Starting heroku_ping_pong_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); let cloned_bot = bot.clone(); teloxide::repl_with_listener( bot, |message| async move { - message.answer_str("pong").await?; - ResponseResult::<()>::Ok(()) + message.answer("pong").await?; + respond(()) }, webhook(cloned_bot).await, ) diff --git a/examples/ngrok_ping_pong_bot/Cargo.toml b/examples/ngrok_ping_pong_bot/Cargo.toml index 9522426f..571fe1ff 100644 --- a/examples/ngrok_ping_pong_bot/Cargo.toml +++ b/examples/ngrok_ping_pong_bot/Cargo.toml @@ -7,12 +7,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } -teloxide = { path = "../../" } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } +tokio-stream = "0.1.4" # Used to setup a webhook -warp = "0.2.2" +warp = "0.3.0" reqwest = "0.10.4" serde_json = "1.0.50" diff --git a/examples/ngrok_ping_pong_bot/src/main.rs b/examples/ngrok_ping_pong_bot/src/main.rs index 0fb745fb..a00c60ee 100644 --- a/examples/ngrok_ping_pong_bot/src/main.rs +++ b/examples/ngrok_ping_pong_bot/src/main.rs @@ -1,10 +1,11 @@ // The version of ngrok ping-pong-bot, which uses a webhook to receive updates // from Telegram, instead of long polling. -use teloxide::{dispatching::update_listeners, prelude::*}; +use teloxide::{dispatching::update_listeners, prelude::*, types::Update}; use std::{convert::Infallible, net::SocketAddr}; use tokio::sync::mpsc; +use tokio_stream::wrappers::UnboundedReceiverStream; use warp::Filter; use reqwest::StatusCode; @@ -19,11 +20,10 @@ async fn handle_rejection(error: warp::Rejection) -> Result<impl warp::Reply, In Ok(StatusCode::INTERNAL_SERVER_ERROR) } -pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infallible> { +pub async fn webhook<'a>(bot: AutoSend<Bot>) -> impl update_listeners::UpdateListener<Infallible> { // You might want to specify a self-signed certificate via .certificate // method on SetWebhook. bot.set_webhook("Your HTTPS ngrok URL here. Get it by 'ngrok http 80'") - .send() .await .expect("Cannot setup a webhook"); @@ -46,21 +46,21 @@ pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infa // setup a self-signed TLS certificate. tokio::spawn(serve.run("127.0.0.1:80".parse::<SocketAddr>().unwrap())); - rx + UnboundedReceiverStream::new(rx) } async fn run() { teloxide::enable_logging!(); log::info!("Starting ngrok_ping_pong_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); let cloned_bot = bot.clone(); teloxide::repl_with_listener( bot, |message| async move { - message.answer_str("pong").await?; - ResponseResult::<()>::Ok(()) + message.answer("pong").await?; + respond(()) }, webhook(cloned_bot).await, ) diff --git a/examples/redis_remember_bot/Cargo.toml b/examples/redis_remember_bot/Cargo.toml index 6927b9d8..c134f0a4 100644 --- a/examples/redis_remember_bot/Cargo.toml +++ b/examples/redis_remember_bot/Cargo.toml @@ -5,13 +5,12 @@ authors = ["Maximilian Siling <mouse-art@ya.ru>"] edition = "2018" [dependencies] +# You can also choose "cbor-serializer" or built-in JSON serializer +teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer", "macros", "auto-send"] } + 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 -teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer"] } -teloxide-macros = "0.3.2" +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } serde = "1.0.104" futures = "0.3.5" diff --git a/examples/redis_remember_bot/src/main.rs b/examples/redis_remember_bot/src/main.rs index 08f77db9..9fc32f22 100644 --- a/examples/redis_remember_bot/src/main.rs +++ b/examples/redis_remember_bot/src/main.rs @@ -9,6 +9,7 @@ use states::*; use teloxide::{ dispatching::dialogue::{serializer::Bincode, RedisStorage, Storage}, prelude::*, + RequestError, }; use thiserror::Error; @@ -22,7 +23,7 @@ enum Error { StorageError(#[from] StorageError), } -type In = DialogueWithCx<Message, Dialogue, StorageError>; +type In = DialogueWithCx<AutoSend<Bot>, Message, Dialogue, StorageError>; #[tokio::main] async fn main() { @@ -30,7 +31,7 @@ async fn main() { } async fn run() { - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); Dispatcher::new(bot) .messages_handler(DialogueDispatcher::with_storage( |DialogueWithCx { cx, dialogue }: In| async move { @@ -47,10 +48,13 @@ async fn run() { .await; } -async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> { - match cx.update.text_owned() { +async fn handle_message( + cx: UpdateWithCx<AutoSend<Bot>, Message>, + dialogue: Dialogue, +) -> TransitionOut<Dialogue> { + match cx.update.text().map(ToOwned::to_owned) { None => { - cx.answer_str("Send me a text message.").await?; + cx.answer("Send me a text message.").await?; next(dialogue) } Some(ans) => dialogue.react(cx, ans).await, diff --git a/examples/redis_remember_bot/src/states.rs b/examples/redis_remember_bot/src/states.rs index 0bb65bd7..6da08fe8 100644 --- a/examples/redis_remember_bot/src/states.rs +++ b/examples/redis_remember_bot/src/states.rs @@ -1,4 +1,4 @@ -use teloxide_macros::Transition; +use teloxide::dispatching::dialogue::Transition; use serde::{Deserialize, Serialize}; diff --git a/examples/redis_remember_bot/src/transitions.rs b/examples/redis_remember_bot/src/transitions.rs index dcc78db9..7cfccd13 100644 --- a/examples/redis_remember_bot/src/transitions.rs +++ b/examples/redis_remember_bot/src/transitions.rs @@ -1,15 +1,18 @@ use teloxide::prelude::*; -use teloxide_macros::teloxide; use super::states::*; #[teloxide(subtransition)] -async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOut<Dialogue> { +async fn start( + state: StartState, + cx: TransitionIn<AutoSend<Bot>>, + ans: String, +) -> TransitionOut<Dialogue> { if let Ok(number) = ans.parse() { - cx.answer_str(format!("Remembered number {}. Now use /get or /reset", number)).await?; + cx.answer(format!("Remembered number {}. Now use /get or /reset", number)).await?; next(HaveNumberState { number }) } else { - cx.answer_str("Please, send me a number").await?; + cx.answer("Please, send me a number").await?; next(state) } } @@ -17,19 +20,19 @@ async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOu #[teloxide(subtransition)] async fn have_number( state: HaveNumberState, - cx: TransitionIn, + cx: TransitionIn<AutoSend<Bot>>, ans: String, ) -> TransitionOut<Dialogue> { let num = state.number; if ans.starts_with("/get") { - cx.answer_str(format!("Here is your number: {}", num)).await?; + cx.answer(format!("Here is your number: {}", num)).await?; next(state) } else if ans.starts_with("/reset") { - cx.answer_str("Resetted number").await?; + cx.answer("Resetted number").await?; next(StartState) } else { - cx.answer_str("Please, send /get or /reset").await?; + cx.answer("Please, send /get or /reset").await?; next(state) } } diff --git a/examples/shared_state_bot/Cargo.toml b/examples/shared_state_bot/Cargo.toml index be1fbf6b..eb613ba1 100644 --- a/examples/shared_state_bot/Cargo.toml +++ b/examples/shared_state_bot/Cargo.toml @@ -7,8 +7,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } -teloxide = { path = "../../" } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } +tokio-stream = "0.1.3" lazy_static = "1.4.0" diff --git a/examples/shared_state_bot/src/main.rs b/examples/shared_state_bot/src/main.rs index 164f5a7a..123de9dc 100644 --- a/examples/shared_state_bot/src/main.rs +++ b/examples/shared_state_bot/src/main.rs @@ -4,6 +4,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use lazy_static::lazy_static; use teloxide::prelude::*; +use tokio_stream::wrappers::UnboundedReceiverStream; lazy_static! { static ref MESSAGES_TOTAL: AtomicU64 = AtomicU64::new(0); @@ -18,15 +19,15 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting shared_state_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); Dispatcher::new(bot) - .messages_handler(|rx: DispatcherHandlerRx<Message>| { - rx.for_each_concurrent(None, |message| async move { + .messages_handler(|rx: DispatcherHandlerRx<AutoSend<Bot>, Message>| { + UnboundedReceiverStream::new(rx).for_each_concurrent(None, |message| async move { let previous = MESSAGES_TOTAL.fetch_add(1, Ordering::Relaxed); message - .answer_str(format!("I received {} messages in total.", previous)) + .answer(format!("I received {} messages in total.", previous)) .await .log_on_error() .await; diff --git a/examples/simple_commands_bot/Cargo.toml b/examples/simple_commands_bot/Cargo.toml index 7acc2030..7aef1030 100644 --- a/examples/simple_commands_bot/Cargo.toml +++ b/examples/simple_commands_bot/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +teloxide = { path = "../../", features = ["macros", "auto-send"] } log = "0.4.8" pretty_env_logger = "0.4.0" -tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } -teloxide = { path = "../../" } +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } diff --git a/examples/simple_commands_bot/src/main.rs b/examples/simple_commands_bot/src/main.rs index 24b88345..8b34c7b0 100644 --- a/examples/simple_commands_bot/src/main.rs +++ b/examples/simple_commands_bot/src/main.rs @@ -1,5 +1,7 @@ use teloxide::{prelude::*, utils::command::BotCommand}; +use std::error::Error; + #[derive(BotCommand)] #[command(rename = "lowercase", description = "These commands are supported:")] enum Command { @@ -11,14 +13,17 @@ enum Command { UsernameAndAge { username: String, age: u8 }, } -async fn answer(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<()> { +async fn answer( + cx: UpdateWithCx<AutoSend<Bot>, Message>, + command: Command, +) -> Result<(), Box<dyn Error + Send + Sync>> { match command { Command::Help => cx.answer(Command::descriptions()).send().await?, Command::Username(username) => { - cx.answer_str(format!("Your username is @{}.", username)).await? + cx.answer(format!("Your username is @{}.", username)).await? } Command::UsernameAndAge { username, age } => { - cx.answer_str(format!("Your username is @{} and age is {}.", username, age)).await? + cx.answer(format!("Your username is @{} and age is {}.", username, age)).await? } }; @@ -34,7 +39,8 @@ async fn run() { teloxide::enable_logging!(); log::info!("Starting simple_commands_bot..."); - let bot = Bot::from_env(); + let bot = Bot::from_env().auto_send(); - teloxide::commands_repl(bot, panic!("Your bot's name here"), answer).await; + let bot_name: String = panic!("Your bot's name here"); + teloxide::commands_repl(bot, bot_name, answer).await; } diff --git a/examples/sqlite_remember_bot/Cargo.toml b/examples/sqlite_remember_bot/Cargo.toml new file mode 100644 index 00000000..d224d528 --- /dev/null +++ b/examples/sqlite_remember_bot/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sqlite_remember_bot" +version = "0.1.0" +authors = ["Maximilian Siling <mouse-art@ya.ru>", "Sergey Levitin <selevit@gmail.com>"] +edition = "2018" + +[dependencies] +# You can also choose "cbor-serializer" or built-in JSON serializer +teloxide = { path = "../../", features = ["sqlite-storage", "bincode-serializer", "redis-storage", "macros", "auto-send"] } + +log = "0.4.8" +pretty_env_logger = "0.4.0" +tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] } + +serde = "1.0.104" +futures = "0.3.5" + +thiserror = "1.0.15" +derive_more = "0.99.9" diff --git a/examples/sqlite_remember_bot/src/main.rs b/examples/sqlite_remember_bot/src/main.rs new file mode 100644 index 00000000..c8980765 --- /dev/null +++ b/examples/sqlite_remember_bot/src/main.rs @@ -0,0 +1,55 @@ +#[macro_use] +extern crate derive_more; + +mod states; +mod transitions; + +use states::*; + +use teloxide::{ + dispatching::dialogue::{serializer::Json, SqliteStorage, Storage}, + prelude::*, + RequestError, +}; +use thiserror::Error; + +type StorageError = <SqliteStorage<Json> as Storage<Dialogue>>::Error; + +#[derive(Debug, Error)] +enum Error { + #[error("error from Telegram: {0}")] + TelegramError(#[from] RequestError), + #[error("error from storage: {0}")] + StorageError(#[from] StorageError), +} + +type In = DialogueWithCx<AutoSend<Bot>, Message, Dialogue, StorageError>; + +async fn handle_message( + cx: UpdateWithCx<AutoSend<Bot>, Message>, + dialogue: Dialogue, +) -> TransitionOut<Dialogue> { + match cx.update.text().map(ToOwned::to_owned) { + None => { + cx.answer("Send me a text message.").await?; + next(dialogue) + } + Some(ans) => dialogue.react(cx, ans).await, + } +} + +#[tokio::main] +async fn main() { + let bot = Bot::from_env().auto_send(); + + Dispatcher::new(bot) + .messages_handler(DialogueDispatcher::with_storage( + |DialogueWithCx { cx, dialogue }: In| async move { + let dialogue = dialogue.expect("std::convert::Infallible"); + handle_message(cx, dialogue).await.expect("Something wrong with the bot!") + }, + SqliteStorage::open("db.sqlite", Json).await.unwrap(), + )) + .dispatch() + .await; +} diff --git a/examples/sqlite_remember_bot/src/states.rs b/examples/sqlite_remember_bot/src/states.rs new file mode 100644 index 00000000..1c007b5a --- /dev/null +++ b/examples/sqlite_remember_bot/src/states.rs @@ -0,0 +1,23 @@ +use teloxide::macros::Transition; + +use serde::{Deserialize, Serialize}; + +#[derive(Transition, From, Serialize, Deserialize)] +pub enum Dialogue { + Start(StartState), + HaveNumber(HaveNumberState), +} + +impl Default for Dialogue { + fn default() -> Self { + Self::Start(StartState) + } +} + +#[derive(Serialize, Deserialize)] +pub struct StartState; + +#[derive(Serialize, Deserialize)] +pub struct HaveNumberState { + pub number: i32, +} diff --git a/examples/sqlite_remember_bot/src/transitions.rs b/examples/sqlite_remember_bot/src/transitions.rs new file mode 100644 index 00000000..2606e203 --- /dev/null +++ b/examples/sqlite_remember_bot/src/transitions.rs @@ -0,0 +1,39 @@ +use teloxide::prelude::*; +use teloxide::macros::teloxide; + +use super::states::*; + +#[teloxide(subtransition)] +async fn start( + state: StartState, + cx: TransitionIn<AutoSend<Bot>>, + ans: String, +) -> TransitionOut<Dialogue> { + if let Ok(number) = ans.parse() { + cx.answer(format!("Remembered number {}. Now use /get or /reset", number)).await?; + next(HaveNumberState { number }) + } else { + cx.answer("Please, send me a number").await?; + next(state) + } +} + +#[teloxide(subtransition)] +async fn have_number( + state: HaveNumberState, + cx: TransitionIn<AutoSend<Bot>>, + ans: String, +) -> TransitionOut<Dialogue> { + let num = state.number; + + if ans.starts_with("/get") { + cx.answer(format!("Here is your number: {}", num)).await?; + next(state) + } else if ans.starts_with("/reset") { + cx.answer("Resetted number").await?; + next(StartState) + } else { + cx.answer("Please, send /get or /reset").await?; + next(state) + } +} diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000..1197ce45 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,8 @@ +[build] +command = "rustup install nightly --profile minimal && cargo +nightly doc --all-features --no-deps && cp -r target/doc _netlify_out" +environment = { RUSTDOCFLAGS= "--cfg docsrs" } +publish = "_netlify_out" + +[[redirects]] +from = "/" +to = "/teloxide" diff --git a/rustfmt.toml b/rustfmt.toml index c61c5a92..38db4219 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,6 @@ format_code_in_doc_comments = true wrap_comments = true format_strings = true -merge_imports = true +imports_granularity = "Crate" use_small_heuristics = "Max" use_field_init_shorthand = true diff --git a/src/bot/api.rs b/src/bot/api.rs deleted file mode 100644 index 619d0777..00000000 --- a/src/bot/api.rs +++ /dev/null @@ -1,1538 +0,0 @@ -use crate::{ - requests::{ - AddStickerToSet, AnswerCallbackQuery, AnswerInlineQuery, AnswerPreCheckoutQuery, - AnswerShippingQuery, CreateNewStickerSet, DeleteChatPhoto, DeleteChatStickerSet, - DeleteMessage, DeleteStickerFromSet, DeleteWebhook, EditMessageCaption, - EditMessageLiveLocation, EditMessageMedia, EditMessageReplyMarkup, EditMessageText, - ExportChatInviteLink, ForwardMessage, GetChat, GetChatAdministrators, GetChatMember, - GetChatMembersCount, GetFile, GetGameHighScores, GetMe, GetMyCommands, GetStickerSet, - GetUpdates, GetUserProfilePhotos, GetWebhookInfo, KickChatMember, LeaveChat, - PinChatMessage, PromoteChatMember, RestrictChatMember, SendAnimation, SendAudio, - SendChatAction, SendChatActionKind, SendContact, SendDice, SendDocument, SendGame, - SendInvoice, SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendPoll, SendSticker, - SendVenue, SendVideo, SendVideoNote, SendVoice, SetChatAdministratorCustomTitle, - SetChatDescription, SetChatPermissions, SetChatPhoto, SetChatStickerSet, SetChatTitle, - SetGameScore, SetMyCommands, SetStickerPositionInSet, SetStickerSetThumb, SetWebhook, - StopMessageLiveLocation, StopPoll, UnbanChatMember, UnpinChatMessage, UploadStickerFile, - }, - types::{ - BotCommand, ChatId, ChatOrInlineMessage, ChatPermissions, InlineQueryResult, InputFile, - InputMedia, LabeledPrice, ParseMode, StickerType, - }, - Bot, -}; -use std::ops::Deref; - -impl Bot { - /// Use this method to receive incoming updates using long polling ([wiki]). - /// - /// **Notes:** - /// 1. This method will not work if an outgoing webhook is set up. - /// 2. In order to avoid getting duplicate updates, - /// recalculate offset after each server response. - /// - /// [The official docs](https://core.telegram.org/bots/api#getupdates). - /// - /// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling - pub fn get_updates(&self) -> GetUpdates { - GetUpdates::new(self.clone()) - } - - /// Use this method to specify a url and receive incoming updates via an - /// outgoing webhook. - /// - /// Whenever there is an update for the bot, we will send an - /// HTTPS POST request to the specified url, containing a JSON-serialized - /// [`Update`]. In case of an unsuccessful request, we will give up after a - /// reasonable amount of attempts. - /// - /// If you'd like to make sure that the Webhook request comes from Telegram, - /// we recommend using a secret path in the URL, e.g. - /// `https://www.example.com/<token>`. Since nobody else knows your bot‘s - /// token, you can be pretty sure it’s us. - /// - /// [The official docs](https://core.telegram.org/bots/api#setwebhook). - /// - /// # Params - /// - `url`: HTTPS url to send updates to. - /// - /// Use an empty string to remove webhook integration. - /// - /// [`Update`]: crate::types::Update - pub fn set_webhook<U>(&self, url: U) -> SetWebhook - where - U: Into<String>, - { - SetWebhook::new(self.clone(), url) - } - - /// Use this method to remove webhook integration if you decide to switch - /// back to [Bot::get_updates]. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletewebhook). - /// - /// [Bot::get_updates]: crate::Bot::get_updates - pub fn delete_webhook(&self) -> DeleteWebhook { - DeleteWebhook::new(self.clone()) - } - - /// Use this method to get current webhook status. - /// - /// If the bot is using [`Bot::get_updates`], will return an object with the - /// url field empty. - /// - /// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo). - /// - /// [`Bot::get_updates`]: crate::Bot::get_updates - pub fn get_webhook_info(&self) -> GetWebhookInfo { - GetWebhookInfo::new(self.clone()) - } - - /// A simple method for testing your bot's auth token. Requires no - /// parameters. - /// - /// [The official docs](https://core.telegram.org/bots/api#getme). - pub fn get_me(&self) -> GetMe { - GetMe::new(self.clone()) - } - - /// Use this method to send text messages. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendmessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `text`: Text of the message to be sent. - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_message<C, T>(&self, chat_id: C, text: T) -> SendMessage - where - C: Into<ChatId>, - T: Into<String>, - { - self.with_default_parse_mode_if_specified( - SendMessage::new(self.clone(), chat_id, text), - SendMessage::parse_mode, - ) - } - - /// Use this method to forward messages of any kind. - /// - /// [`The official docs`](https://core.telegram.org/bots/api#forwardmessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `from_chat_id`: Unique identifier for the chat where the original - /// message was sent (or channel username in the format - /// `@channelusername`). - /// - `message_id`: Message identifier in the chat specified in - /// [`from_chat_id`]. - /// - /// [`from_chat_id`]: ForwardMessage::from_chat_id - pub fn forward_message<C, F>( - &self, - chat_id: C, - from_chat_id: F, - message_id: i32, - ) -> ForwardMessage - where - C: Into<ChatId>, - F: Into<ChatId>, - { - ForwardMessage::new(self.clone(), chat_id, from_chat_id, message_id) - } - - /// Use this method to send photos. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendphoto). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `photo`: Photo to send. - /// - /// 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 - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_photo<C>(&self, chat_id: C, photo: InputFile) -> SendPhoto - where - C: Into<ChatId>, - { - self.with_default_parse_mode_if_specified( - SendPhoto::new(self.clone(), chat_id, photo), - SendPhoto::parse_mode, - ) - } - - /// - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_audio<C>(&self, chat_id: C, audio: InputFile) -> SendAudio - where - C: Into<ChatId>, - { - self.with_default_parse_mode_if_specified( - SendAudio::new(self.clone(), chat_id, audio), - SendAudio::parse_mode, - ) - } - - /// 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). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `document`: File to send. - /// - /// Pass a file_id as String to send a file that exists on the - /// Telegram servers (recommended), pass an HTTP URL as a String for - /// Telegram to get a file from the Internet, or upload a new one using - /// `multipart/form-data`. [More info on Sending Files »]. - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_document<C>(&self, chat_id: C, document: InputFile) -> SendDocument - where - C: Into<ChatId>, - { - self.with_default_parse_mode_if_specified( - SendDocument::new(self.clone(), chat_id, document), - SendDocument::parse_mode, - ) - } - - /// Use this method to send video files, Telegram clients support mp4 videos - /// (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). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `video`: Video to sent. - /// - /// 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 - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_video<C>(&self, chat_id: C, video: InputFile) -> SendVideo - where - C: Into<ChatId>, - { - self.with_default_parse_mode_if_specified( - SendVideo::new(self.clone(), chat_id, video), - SendVideo::parse_mode, - ) - } - - /// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video - /// 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). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `animation`: Animation to send. - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_animation<C>(&self, chat_id: C, animation: InputFile) -> SendAnimation - where - C: Into<ChatId>, - { - self.with_default_parse_mode_if_specified( - SendAnimation::new(self.clone(), chat_id, animation), - SendAnimation::parse_mode, - ) - } - - /// Use this method to send audio files, if you want Telegram clients to - /// display the file as a playable voice message. - /// - /// 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 - /// 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). - /// - /// [`Audio`]: crate::types::Audio - /// [`Document`]: crate::types::Document - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `voice`: Audio file to send. - /// - /// 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 - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - /// - /// # Notes - /// Uses [a default parse mode] if specified in [`BotBuilder`]. - /// - /// [a default parse mode]: crate::BotBuilder::parse_mode - /// [`BotBuilder`]: crate::BotBuilder - pub fn send_voice<C>(&self, chat_id: C, voice: InputFile) -> SendVoice - where - C: Into<ChatId>, - { - self.with_default_parse_mode_if_specified( - SendVoice::new(self.clone(), chat_id, voice), - SendVoice::parse_mode, - ) - } - - /// As of [v.4.0], Telegram clients support rounded square mp4 videos of up - /// to 1 minute long. Use this method to send video messages. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendvideonote). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `video_note`: Video note to send. - /// - /// 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 - /// [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 - where - C: Into<ChatId>, - { - SendVideoNote::new(self.clone(), chat_id, video_note) - } - - /// Use this method to send a group of photos or videos as an album. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendmediagroup). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `media`: A JSON-serialized array describing photos and videos to be - /// sent, must include 2–10 items. - pub fn send_media_group<C, M>(&self, chat_id: C, media: M) -> SendMediaGroup - where - C: Into<ChatId>, - M: Into<Vec<InputMedia>>, - { - SendMediaGroup::new(self.clone(), chat_id, media) - } - - /// Use this method to send point on the map. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendlocation). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `latitude`: Latitude of the location. - /// - `longitude`: Latitude of the location. - pub fn send_location<C>(&self, chat_id: C, latitude: f32, longitude: f32) -> SendLocation - where - C: Into<ChatId>, - { - SendLocation::new(self.clone(), chat_id, latitude, longitude) - } - - /// Use this method to edit live location messages. - /// - /// A location can be edited until its live_period expires or editing is - /// explicitly disabled by a call to stopMessageLiveLocation. 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#editmessagelivelocation). - /// - /// # Params - /// - `latitude`: Latitude of new location. - /// - `longitude`: Longitude of new location. - /// - /// [`Message`]: crate::types::Message - /// [`True`]: crate::types::True - pub fn edit_message_live_location( - &self, - chat_or_inline_message: ChatOrInlineMessage, - latitude: f32, - longitude: f32, - ) -> EditMessageLiveLocation { - EditMessageLiveLocation::new(self.clone(), chat_or_inline_message, latitude, longitude) - } - - /// Use this method to stop updating a live location message before - /// `live_period` expires. - /// - /// On success, if the message was sent by the bot, the sent [`Message`] is - /// returned, otherwise [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). - /// - /// [`Message`]: crate::types::Message - /// [`True`]: crate::types::True - pub fn stop_message_live_location( - &self, - chat_or_inline_message: ChatOrInlineMessage, - ) -> StopMessageLiveLocation { - StopMessageLiveLocation::new(self.clone(), chat_or_inline_message) - } - - /// Use this method to send information about a venue. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendvenue). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `latitude`: Latitude of the venue. - /// - `longitude`: Longitude of the venue. - /// - `title`: Name of the venue. - /// - `address`: Address of the venue. - pub fn send_venue<C, T, A>( - &self, - chat_id: C, - latitude: f32, - longitude: f32, - title: T, - address: A, - ) -> SendVenue - where - C: Into<ChatId>, - T: Into<String>, - A: Into<String>, - { - SendVenue::new(self.clone(), chat_id, latitude, longitude, title, address) - } - - /// Use this method to send phone contacts. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendcontact). - /// - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `phone_number`: Contact's phone number. - /// - `first_name`: Contact's first name. - pub fn send_contact<C, P, F>(&self, chat_id: C, phone_number: P, first_name: F) -> SendContact - where - C: Into<ChatId>, - P: Into<String>, - F: Into<String>, - { - SendContact::new(self.clone(), chat_id, phone_number, first_name) - } - - /// Use this method to send a native poll. A native poll can't be sent to a - /// private chat. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendpoll). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `question`: Poll question, 1-255 characters. - /// - `options`: List of answer options, 2-10 strings 1-100 characters - /// 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 - where - C: Into<ChatId>, - Q: Into<String>, - O: Into<Vec<String>>, - { - self.with_default_parse_mode_if_specified( - 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 - /// happening on the bot's side. - /// - /// The status is set for 5 seconds or less (when a message arrives from - /// your bot, Telegram clients clear its typing status). - /// - /// ## Note - /// Example: The [ImageBot] needs some time to process a request and upload - /// the image. Instead of sending a text message along the lines of - /// “Retrieving image, please wait…”, the bot may use - /// [`Bot::send_chat_action`] with `action = upload_photo`. The user - /// will see a `sending photo` status for the bot. - /// - /// We only recommend using this method when a response from the bot will - /// take a **noticeable** amount of time to arrive. - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - /// [ImageBot]: https://t.me/imagebot - /// [`Bot::send_chat_action`]: crate::Bot::send_chat_action - pub fn send_chat_action<C>(&self, chat_id: C, action: SendChatActionKind) -> SendChatAction - where - C: Into<ChatId>, - { - SendChatAction::new(self.clone(), chat_id, action) - } - - /// Use this method to get a list of profile pictures for a user. - /// - /// [The official docs](https://core.telegram.org/bots/api#getuserprofilephotos). - /// - /// # Params - /// - `user_id`: Unique identifier of the target user. - pub fn get_user_profile_photos(&self, user_id: i32) -> GetUserProfilePhotos { - GetUserProfilePhotos::new(self.clone(), user_id) - } - - /// Use this method to get basic info about a file and prepare it for - /// downloading. - /// - /// For the moment, bots can download files of up to `20MB` in size. - /// - /// The file can then be downloaded via the link - /// `https://api.telegram.org/file/bot<token>/<file_path>`, where `<file_path>` - /// is taken from the response. It is guaranteed that the link will be valid - /// for at least `1` hour. When the link expires, a new one can be requested - /// by calling [`GetFile`] again. - /// - /// **Note**: This function may not preserve the original file name and MIME - /// type. You should save the file's MIME type and name (if available) when - /// the [`File`] object is received. - /// - /// [The official docs](https://core.telegram.org/bots/api#getfile). - /// - /// # Params - /// - `file_id`: File identifier to get info about. - /// - /// [`File`]: crate::types::File - /// [`GetFile`]: self::GetFile - pub fn get_file<F>(&self, file_id: F) -> GetFile - where - F: Into<String>, - { - GetFile::new(self.clone(), file_id) - } - - /// Use this method to kick a user from a group, a supergroup or a channel. - /// - /// In the case of supergroups and channels, the user will not be able to - /// return to the group on their own using invite links, etc., unless - /// [unbanned] first. The bot must be an administrator in the chat for - /// this to work and must have the appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#kickchatmember). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - /// - /// [unbanned]: crate::Bot::unban_chat_member - pub fn kick_chat_member<C>(&self, chat_id: C, user_id: i32) -> KickChatMember - where - C: Into<ChatId>, - { - KickChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to unban a previously kicked user in a supergroup or - /// channel. The user will **not** return to the group or channel - /// automatically, but will be able to join via link, etc. The bot must - /// be an administrator for this to work. - /// - /// [The official docs](https://core.telegram.org/bots/api#unbanchatmember). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - pub fn unban_chat_member<C>(&self, chat_id: C, user_id: i32) -> UnbanChatMember - where - C: Into<ChatId>, - { - UnbanChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to restrict a user in a supergroup. - /// - /// The bot must be an administrator in the supergroup for this to work and - /// must have the appropriate admin rights. Pass `true` for all - /// permissions to lift restrictions from a user. - /// - /// [The official docs](https://core.telegram.org/bots/api#restrictchatmember). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - /// - `permissions`: New user permissions. - pub fn restrict_chat_member<C>( - &self, - chat_id: C, - user_id: i32, - permissions: ChatPermissions, - ) -> RestrictChatMember - where - C: Into<ChatId>, - { - RestrictChatMember::new(self.clone(), chat_id, user_id, permissions) - } - - /// Use this method to promote or demote a user in a supergroup or a - /// channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. Pass False for all boolean - /// parameters to demote a user. - /// - /// [The official docs](https://core.telegram.org/bots/api#promotechatmember). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - pub fn promote_chat_member<C>(&self, chat_id: C, user_id: i32) -> PromoteChatMember - where - C: Into<ChatId>, - { - PromoteChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to set default chat permissions for all members. - /// - /// The bot must be an administrator in the group or a supergroup for this - /// to work and must have the can_restrict_members admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatpermissions). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `permissions`: New default chat permissions. - pub fn set_chat_permissions<C>( - &self, - chat_id: C, - permissions: ChatPermissions, - ) -> SetChatPermissions - where - C: Into<ChatId>, - { - SetChatPermissions::new(self.clone(), chat_id, permissions) - } - - /// Use this method to generate a new invite link for a chat; any previously - /// generated link is revoked. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. - /// - /// # Note - /// Each administrator in a chat generates their own invite links. Bots - /// can't use invite links generated by other administrators. If you - /// want your bot to work with invite links, it will need to generate - /// its own link using [`Bot::export_chat_invite_link`] – after this the - /// link will become available to the bot via the [`Bot::get_chat`] - /// method. If your bot needs to generate a new invite link replacing - /// its previous one, use [`Bot::export_chat_invite_link`] again. - /// - /// [The official docs](https://core.telegram.org/bots/api#exportchatinvitelink). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - /// [`Bot::export_chat_invite_link`]: crate::Bot::export_chat_invite_link - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub fn export_chat_invite_link<C>(&self, chat_id: C) -> ExportChatInviteLink - where - C: Into<ChatId>, - { - ExportChatInviteLink::new(self.clone(), chat_id) - } - - /// Use this method to set a new profile photo for the chat. - /// - /// Photos can't be changed for private chats. The bot must be an - /// administrator in the chat for this to work and must have the - /// appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatphoto). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `photo`: New chat photo, uploaded using `multipart/form-data`. - pub fn set_chat_photo<C>(&self, chat_id: C, photo: InputFile) -> SetChatPhoto - where - C: Into<ChatId>, - { - SetChatPhoto::new(self.clone(), chat_id, photo) - } - - /// Use this method to delete a chat photo. Photos can't be changed for - /// private chats. The bot must be an administrator in the chat for this - /// to work and must have the appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletechatphoto). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn delete_chat_photo<C>(&self, chat_id: C) -> DeleteChatPhoto - where - C: Into<ChatId>, - { - DeleteChatPhoto::new(self.clone(), chat_id) - } - - /// Use this method to change the title of a chat. - /// - /// Titles can't be changed for private chats. The bot must be an - /// administrator in the chat for this to work and must have the - /// appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchattitle). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `title`: New chat title, 1-255 characters. - pub fn set_chat_title<C, T>(&self, chat_id: C, title: T) -> SetChatTitle - where - C: Into<ChatId>, - T: Into<String>, - { - SetChatTitle::new(self.clone(), chat_id, title) - } - - /// Use this method to change the description of a group, a supergroup or a - /// channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatdescription). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn set_chat_description<C>(&self, chat_id: C) -> SetChatDescription - where - C: Into<ChatId>, - { - SetChatDescription::new(self.clone(), chat_id) - } - - /// Use this method to pin a message in a group, a supergroup, or a channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the `can_pin_messages` admin right in the supergroup or - /// `can_edit_messages` admin right in the channel. - /// - /// [The official docs](https://core.telegram.org/bots/api#pinchatmessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `message_id`: Identifier of a message to pin. - pub fn pin_chat_message<C>(&self, chat_id: C, message_id: i32) -> PinChatMessage - where - C: Into<ChatId>, - { - PinChatMessage::new(self.clone(), chat_id, message_id) - } - - /// Use this method to unpin a message in a group, a supergroup, or a - /// channel. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the `can_pin_messages` admin right in the supergroup or - /// `can_edit_messages` admin right in the channel. - /// - /// [The official docs](https://core.telegram.org/bots/api#unpinchatmessage). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn unpin_chat_message<C>(&self, chat_id: C) -> UnpinChatMessage - where - C: Into<ChatId>, - { - UnpinChatMessage::new(self.clone(), chat_id) - } - - /// Use this method for your bot to leave a group, supergroup or channel. - /// - /// [The official docs](https://core.telegram.org/bots/api#leavechat). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn leave_chat<C>(&self, chat_id: C) -> LeaveChat - where - C: Into<ChatId>, - { - LeaveChat::new(self.clone(), chat_id) - } - - /// Use this method to get up to date information about the chat (current - /// name of the user for one-on-one conversations, current username of a - /// user, group or channel, etc.). - /// - /// [The official docs](https://core.telegram.org/bots/api#getchat). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn get_chat<C>(&self, chat_id: C) -> GetChat - where - C: Into<ChatId>, - { - GetChat::new(self.clone(), chat_id) - } - - /// Use this method to get a list of administrators in a chat. - /// - /// If the chat is a group or a supergroup and no administrators were - /// appointed, only the creator will be returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#getchatadministrators). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn get_chat_administrators<C>(&self, chat_id: C) -> GetChatAdministrators - where - C: Into<ChatId>, - { - GetChatAdministrators::new(self.clone(), chat_id) - } - - /// Use this method to get the number of members in a chat. - /// - /// [The official docs](https://core.telegram.org/bots/api#getchatmemberscount). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - pub fn get_chat_members_count<C>(&self, chat_id: C) -> GetChatMembersCount - where - C: Into<ChatId>, - { - GetChatMembersCount::new(self.clone(), chat_id) - } - - /// Use this method to get information about a member of a chat. - /// - /// [The official docs](https://core.telegram.org/bots/api#getchatmember). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup or channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - pub fn get_chat_member<C>(&self, chat_id: C, user_id: i32) -> GetChatMember - where - C: Into<ChatId>, - { - GetChatMember::new(self.clone(), chat_id, user_id) - } - - /// Use this method to set a new group sticker set for a supergroup. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. Use the field can_set_sticker_set - /// optionally returned in getChat requests to check if the bot can use - /// this method. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatstickerset). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup (in the format `@supergroupusername`). - /// - `sticker_set_name`: Name of the sticker set to be set as the group - /// sticker set. - pub fn set_chat_sticker_set<C, S>(&self, chat_id: C, sticker_set_name: S) -> SetChatStickerSet - where - C: Into<ChatId>, - S: Into<String>, - { - SetChatStickerSet::new(self.clone(), chat_id, sticker_set_name) - } - - /// Use this method to delete a group sticker set from a supergroup. - /// - /// The bot must be an administrator in the chat for this to work and must - /// have the appropriate admin rights. Use the field - /// `can_set_sticker_set` optionally returned in [`Bot::get_chat`] - /// requests to check if the bot can use this method. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletechatstickerset). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target supergroup (in the format `@supergroupusername`). - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub fn delete_chat_sticker_set<C>(&self, chat_id: C) -> DeleteChatStickerSet - where - C: Into<ChatId>, - { - DeleteChatStickerSet::new(self.clone(), chat_id) - } - - /// Use this method to send answers to callback queries sent from [inline - /// keyboards]. - /// - /// The answer will be displayed to the user as a notification at - /// the top of the chat screen or as an alert. - /// - /// [The official docs](https://core.telegram.org/bots/api#answercallbackquery). - /// - /// # Params - /// - `callback_query_id`: Unique identifier for the query to be answered. - /// - /// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn answer_callback_query<C>(&self, callback_query_id: C) -> AnswerCallbackQuery - where - C: Into<String>, - { - AnswerCallbackQuery::new(self.clone(), callback_query_id) - } - - /// Use this method to edit text and game messages. - /// - /// On success, if edited message is sent by the bot, the edited [`Message`] - /// is returned, otherwise [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagetext). - /// - /// # Params - /// - New text of the message. - /// - /// [`Message`]: crate::types::Message - /// [`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_text<T>( - &self, - chat_or_inline_message: ChatOrInlineMessage, - text: T, - ) -> EditMessageText - where - T: Into<String>, - { - match self.parse_mode.deref() { - None => EditMessageText::new(self.clone(), chat_or_inline_message, text), - Some(parse_mode) => EditMessageText::new(self.clone(), chat_or_inline_message, text) - .parse_mode(*parse_mode.deref()), - } - } - - /// Use this method to edit captions of messages. - /// - /// On success, if edited message is sent by the bot, the edited [`Message`] - /// is returned, otherwise [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). - /// - /// [`Message`]: crate::types::Message - /// [`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( - &self, - chat_or_inline_message: ChatOrInlineMessage, - ) -> EditMessageCaption { - match self.parse_mode.deref() { - None => EditMessageCaption::new(self.clone(), chat_or_inline_message), - Some(parse_mode) => EditMessageCaption::new(self.clone(), chat_or_inline_message) - .parse_mode(*parse_mode.deref()), - } - } - - /// Use this method to edit animation, audio, document, photo, or video - /// messages. - /// - /// 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 inline message is edited, new file can't be - /// 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). - /// - /// [`Message`]: crate::types::Message - /// [`True`]: crate::types::True - pub fn edit_message_media( - &self, - chat_or_inline_message: ChatOrInlineMessage, - media: InputMedia, - ) -> EditMessageMedia { - EditMessageMedia::new(self.clone(), chat_or_inline_message, media) - } - - /// 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 returned, otherwise [`True`] is returned. - /// - /// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup). - /// - /// [`Message`]: crate::types::Message - /// [`True`]: crate::types::True - pub fn edit_message_reply_markup( - &self, - chat_or_inline_message: ChatOrInlineMessage, - ) -> EditMessageReplyMarkup { - EditMessageReplyMarkup::new(self.clone(), chat_or_inline_message) - } - - /// Use this method to stop a poll which was sent by the bot. - /// - /// [The official docs](https://core.telegram.org/bots/api#stoppoll). - /// - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `message_id`: Identifier of the original message with the poll. - pub fn stop_poll<C>(&self, chat_id: C, message_id: i32) -> StopPoll - where - C: Into<ChatId>, - { - StopPoll::new(self.clone(), chat_id, message_id) - } - - /// Use this method to delete a message, including service messages. - /// - /// The limitations are: - /// - A message can only be deleted if it was sent less than 48 hours ago. - /// - Bots can delete outgoing messages in private chats, groups, and - /// supergroups. - /// - Bots can delete incoming messages in private chats. - /// - Bots granted can_post_messages permissions can delete outgoing - /// messages in channels. - /// - If the bot is an administrator of a group, it can delete any message - /// there. - /// - If the bot has can_delete_messages permission in a supergroup or a - /// channel, it can delete any message there. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletemessage). - /// - /// # 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 delete. - pub fn delete_message<C>(&self, chat_id: C, message_id: i32) -> DeleteMessage - where - C: Into<ChatId>, - { - DeleteMessage::new(self.clone(), chat_id, message_id) - } - - /// Use this method to send static .WEBP or [animated] .TGS stickers. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendsticker). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `sticker`: Sticker to send. - /// - /// 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 - /// [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 - where - C: Into<ChatId>, - { - SendSticker::new(self.clone(), chat_id, sticker) - } - - /// Use this method to get a sticker set. - /// - /// [The official docs](https://core.telegram.org/bots/api#getstickerset). - /// - /// # Params - /// - `name`: Name of the sticker set. - pub fn get_sticker_set<N>(&self, name: N) -> GetStickerSet - where - N: Into<String>, - { - GetStickerSet::new(self.clone(), name) - } - - /// Use this method to upload a .png file with a sticker for later use in - /// [`Bot::create_new_sticker_set`] and [`Bot::add_sticker_to_set`] methods - /// (can be used multiple times). - /// - /// [The official docs](https://core.telegram.org/bots/api#uploadstickerfile). - /// - /// # Params - /// - `user_id`: User identifier of sticker file owner. - /// - `png_sticker`: **Png** image with the sticker, must be up to 512 - /// kilobytes in size, dimensions must not exceed 512px, and either - /// width or height must be exactly 512px. [More info on 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 { - UploadStickerFile::new(self.clone(), user_id, png_sticker) - } - - /// Use this method to create new sticker set owned by a user. The bot will - /// be able to edit the created sticker set. - /// - /// [The official docs](https://core.telegram.org/bots/api#createnewstickerset). - /// - /// # Params - /// - `user_id`: User identifier of created sticker set owner. - /// - `name`: Short name of sticker set, to be used in `t.me/addstickers/` - /// URLs (e.g., animals). Can contain only english letters, digits and - /// underscores. - /// - /// Must begin with a letter, can't contain consecutive underscores and must - /// end in `_by_<bot username>`. `<bot_username>` is case insensitive. 1-64 - /// characters. - /// - `title`: Sticker set title, 1-64 characters. - pub fn create_new_sticker_set<N, T, E>( - &self, - user_id: i32, - name: N, - title: T, - sticker_type: StickerType, - emojis: E, - ) -> CreateNewStickerSet - where - N: Into<String>, - T: Into<String>, - E: Into<String>, - { - CreateNewStickerSet::new(self.clone(), user_id, name, title, sticker_type, emojis) - } - - /// Use this method to add a new sticker to a set created by the bot. - /// - /// [The official docs](https://core.telegram.org/bots/api#addstickertoset). - /// - /// # Params - /// - `user_id`: User identifier of sticker set owner. - /// - `name`: Sticker set name. - /// - `emojis`: One or more emoji corresponding to the sticker. - pub fn add_sticker_to_set<N, E>( - &self, - user_id: i32, - name: N, - sticker_type: StickerType, - emojis: E, - ) -> AddStickerToSet - where - N: Into<String>, - E: Into<String>, - { - AddStickerToSet::new(self.clone(), user_id, name, sticker_type, emojis) - } - - /// Use this method to move a sticker in a set created by the bot to a - /// specific position. - /// - /// [The official docs](https://core.telegram.org/bots/api#setstickerpositioninset). - /// - /// # Params - /// - `sticker`: File identifier of the sticker. - /// - `position`: New sticker position in the set, zero-based. - pub fn set_sticker_position_in_set<S>( - &self, - sticker: S, - position: i32, - ) -> SetStickerPositionInSet - where - S: Into<String>, - { - SetStickerPositionInSet::new(self.clone(), sticker, position) - } - - /// Use this method to delete a sticker from a set created by the bot. - /// - /// [The official docs](https://core.telegram.org/bots/api#deletestickerfromset). - /// - /// # Params - /// - `sticker`: File identifier of the sticker. - pub fn delete_sticker_from_set<S>(&self, sticker: S) -> DeleteStickerFromSet - where - S: Into<String>, - { - DeleteStickerFromSet::new(self.clone(), sticker) - } - - /// Use this method to send answers to an inline query. - /// - /// No more than **50** results per query are allowed. - /// - /// [The official docs](https://core.telegram.org/bots/api#answerinlinequery). - /// - /// # Params - /// - `inline_query_id`: Unique identifier for the answered query. - /// - `results`: A JSON-serialized array of results for the inline query. - pub fn answer_inline_query<I, R>(&self, inline_query_id: I, results: R) -> AnswerInlineQuery - where - I: Into<String>, - R: Into<Vec<InlineQueryResult>>, - { - AnswerInlineQuery::new(self.clone(), inline_query_id, results) - } - - /// Use this method to send invoices. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendinvoice). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target private chat. - /// - `title`: Product name, 1-32 characters. - /// - `description`: Product description, 1-255 characters. - /// - `payload`: Bot-defined invoice payload, 1-128 bytes. This will not - /// be displayed to the user, use for your internal processes. - /// - `provider_token`: Payments provider token, obtained via - /// [@Botfather]. - /// - `start_parameter`: Unique deep-linking parameter that can be used to - /// generate this invoice when used as a start parameter. - /// - `currency`: Three-letter ISO 4217 currency code, see [more on - /// currencies]. - /// - `prices`: Price breakdown, a list of components (e.g. product price, - /// tax, discount, delivery cost, delivery tax, bonus, etc.). - /// - /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies - /// [@Botfather]: https://t.me/botfather - #[allow(clippy::too_many_arguments)] - pub fn send_invoice<T, D, Pl, Pt, S, C, Pr>( - &self, - chat_id: i32, - title: T, - description: D, - payload: Pl, - provider_token: Pt, - start_parameter: S, - currency: C, - prices: Pr, - ) -> SendInvoice - where - T: Into<String>, - D: Into<String>, - Pl: Into<String>, - Pt: Into<String>, - S: Into<String>, - C: Into<String>, - Pr: Into<Vec<LabeledPrice>>, - { - SendInvoice::new( - self.clone(), - chat_id, - title, - description, - payload, - provider_token, - start_parameter, - currency, - prices, - ) - } - - /// Once the user has confirmed their payment and shipping details, the Bot - /// API sends the final confirmation in the form of an [`Update`] with - /// the field `pre_checkout_query`. Use this method to respond to such - /// pre-checkout queries. Note: The Bot API must receive an answer - /// within 10 seconds after the pre-checkout query was sent. - /// - /// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). - /// - /// # Params - /// - `shipping_query_id`: Unique identifier for the query to be answered. - /// - `ok`: Specify `true` if delivery to the specified address is - /// possible and `false` if there are any problems (for example, if - /// delivery to the specified address is not possible). - /// - /// [`Update`]: crate::types::Update - pub fn answer_shipping_query<S>(&self, shipping_query_id: S, ok: bool) -> AnswerShippingQuery - where - S: Into<String>, - { - AnswerShippingQuery::new(self.clone(), shipping_query_id, ok) - } - - /// Once the user has confirmed their payment and shipping details, the Bot - /// API sends the final confirmation in the form of an [`Update`] with - /// the field `pre_checkout_query`. Use this method to respond to such - /// pre-checkout queries. Note: The Bot API must receive an answer - /// within 10 seconds after the pre-checkout query was sent. - /// - /// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). - /// - /// # Params - /// - `pre_checkout_query_id`: Unique identifier for the query to be - /// answered. - /// - `ok`: Specify `true` if everything is alright (goods are available, - /// etc.) and the bot is ready to proceed with the order. Use False if - /// there are any problems. - /// - /// [`Update`]: crate::types::Update - pub fn answer_pre_checkout_query<P>( - &self, - pre_checkout_query_id: P, - ok: bool, - ) -> AnswerPreCheckoutQuery - where - P: Into<String>, - { - AnswerPreCheckoutQuery::new(self.clone(), pre_checkout_query_id, ok) - } - - /// Use this method to send a game. - /// - /// [The official docs](https://core.telegram.org/bots/api#sendgame). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat. - /// - `game_short_name`: Short name of the game, serves as the unique - /// identifier for the game. Set up your games via [@Botfather]. - /// - /// [@Botfather]: https://t.me/botfather - pub fn send_game<G>(&self, chat_id: i32, game_short_name: G) -> SendGame - where - G: Into<String>, - { - SendGame::new(self.clone(), chat_id, game_short_name) - } - - /// Use this method to set the score of the specified user in a game. - /// - /// On success, if the message was sent by the bot, returns the edited - /// [`Message`], otherwise returns [`True`]. Returns an error, if the new - /// score is not greater than the user's current score in the chat and - /// force is `false`. - /// - /// [The official docs](https://core.telegram.org/bots/api#setgamescore). - /// - /// # Params - /// - `user_id`: User identifier. - /// - `score`: New score, must be non-negative. - /// - /// [`Message`]: crate::types::Message - /// [`True`]: crate::types::True - pub fn set_game_score( - &self, - chat_or_inline_message: ChatOrInlineMessage, - user_id: i32, - score: i32, - ) -> SetGameScore { - SetGameScore::new(self.clone(), chat_or_inline_message, user_id, score) - } - - /// Use this method to get data for high score tables. - /// - /// Will return the score of the specified user and several of his neighbors - /// in a game. - /// - /// # Note - /// This method will currently return scores for the target user, plus two - /// of his closest neighbors on each side. Will also return the top - /// three users if the user and his neighbors are not among them. Please - /// note that this behavior is subject to change. - /// - /// [The official docs](https://core.telegram.org/bots/api#getgamehighscores). - /// - /// # Params - /// - `user_id`: Target user id. - pub fn get_game_high_scores( - &self, - chat_or_inline_message: ChatOrInlineMessage, - user_id: i32, - ) -> GetGameHighScores { - GetGameHighScores::new(self.clone(), chat_or_inline_message, user_id) - } - - /// Use this method to set a custom title for an administrator in a - /// supergroup promoted by the bot. - /// - /// [The official docs](https://core.telegram.org/bots/api#setchatadministratorcustomtitle). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - /// - `user_id`: Unique identifier of the target user. - /// - `custom_title`: New custom title for the administrator; 0-16 - /// characters, emoji are not allowed. - pub fn set_chat_administrator_custom_title<C, CT>( - &self, - chat_id: C, - user_id: i32, - custom_title: CT, - ) -> SetChatAdministratorCustomTitle - where - C: Into<ChatId>, - CT: Into<String>, - { - SetChatAdministratorCustomTitle::new(self.clone(), chat_id, user_id, custom_title) - } - - /// Use this method to send an animated emoji that will display a random - /// value. - /// - /// [The official docs](https://core.telegram.org/bots/api#senddice). - /// - /// # Params - /// - `chat_id`: Unique identifier for the target chat or username of the - /// target channel (in the format `@channelusername`). - pub fn send_dice<C>(&self, chat_id: C) -> SendDice - where - C: Into<ChatId>, - { - SendDice::new(self.clone(), chat_id) - } - - /// Use this method to get the current list of the bot's commands. - /// - /// [The official docs](https://core.telegram.org/bots/api#getmycommands). - pub fn get_my_commands(&self) -> GetMyCommands { - GetMyCommands::new(self.clone()) - } - - /// Use this method to change the list of the bot's commands. - /// - /// [The official docs](https://core.telegram.org/bots/api#setmycommands). - /// - /// # Params - /// - `commands`: A JSON-serialized list of bot commands to be set as the - /// list of the bot's commands. At most 100 commands can be specified. - pub fn set_my_commands<C>(&self, commands: C) -> SetMyCommands - where - C: Into<Vec<BotCommand>>, - { - SetMyCommands::new(self.clone(), commands) - } - - /// Use this method to set the thumbnail of a sticker set. Animated - /// thumbnails can be set for animated sticker sets only. - /// - /// [The official docs](https://core.telegram.org/bots/api#setstickersetthumb). - /// - /// # Params - /// - `name`: Sticker set name. - /// - `user_id`: User identifier of the sticker set owner. - pub fn set_sticker_set_thumb<S>(&self, name: S, user_id: i32) -> SetStickerSetThumb - where - S: Into<String>, - { - 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.deref() { - None => builder, - Some(parse_mode) => f(builder, *parse_mode.deref()), - } - } -} diff --git a/src/bot/download.rs b/src/bot/download.rs deleted file mode 100644 index 7035cfe7..00000000 --- a/src/bot/download.rs +++ /dev/null @@ -1,65 +0,0 @@ -use tokio::io::AsyncWrite; - -#[cfg(feature = "unstable-stream")] -use ::{bytes::Bytes, tokio::stream::Stream}; - -#[cfg(feature = "unstable-stream")] -use crate::net::download_file_stream; -use crate::{bot::Bot, net::download_file, DownloadError}; - -impl Bot { - /// Download a file from Telegram into `destination`. - /// - /// `path` can be obtained from [`Bot::get_file`]. - /// - /// To download as a stream of chunks, see [`Bot::download_file_stream`]. - /// - /// ## Examples - /// - /// ```no_run - /// use teloxide::types::File as TgFile; - /// use tokio::fs::File; - /// # use teloxide::RequestError; - /// use teloxide::{requests::Request, Bot}; - /// - /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { - /// let bot = Bot::new("TOKEN"); - /// let mut file = File::create("/home/waffle/Pictures/test.png").await?; - /// - /// let TgFile { file_path, .. } = bot.get_file("*file_id*").send().await?; - /// bot.download_file(&file_path, &mut file).await?; - /// # Ok(()) } - /// ``` - /// - /// [`Bot::get_file`]: crate::Bot::get_file - /// [`Bot::download_file_stream`]: crate::Bot::download_file_stream - pub async fn download_file<D>( - &self, - path: &str, - destination: &mut D, - ) -> Result<(), DownloadError> - where - D: AsyncWrite + Unpin, - { - download_file(&self.client, &self.token, path, destination).await - } - - /// Download a file from Telegram. - /// - /// `path` can be obtained from the [`Bot::get_file`]. - /// - /// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see - /// [`Bot::download_file`]. - /// - /// [`Bot::get_file`]: crate::bot::Bot::get_file - /// [`AsyncWrite`]: tokio::io::AsyncWrite - /// [`tokio::fs::File`]: tokio::fs::File - /// [`Bot::download_file`]: crate::Bot::download_file - #[cfg(feature = "unstable-stream")] - pub async fn download_file_stream( - &self, - path: &str, - ) -> Result<impl Stream<Item = Result<Bytes, reqwest::Error>>, reqwest::Error> { - download_file_stream(&self.client, &self.token, path).await - } -} diff --git a/src/bot/mod.rs b/src/bot/mod.rs deleted file mode 100644 index 462da3eb..00000000 --- a/src/bot/mod.rs +++ /dev/null @@ -1,253 +0,0 @@ -use crate::types::ParseMode; -use reqwest::{ - header::{HeaderMap, CONNECTION}, - Client, ClientBuilder, -}; -use std::{sync::Arc, time::Duration}; - -mod api; -mod download; - -pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN"; -pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY"; - -/// A requests sender. -/// -/// No need to put it into [`Arc`], because it's already in. -/// -/// [`Arc`]: std::sync::Arc -#[derive(Debug, Clone)] -pub struct Bot { - token: Arc<str>, - client: Client, - parse_mode: Arc<Option<ParseMode>>, -} - -impl Bot { - /// Creates new [`BotBuilder`] see it's [docs] for more - /// - /// [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 - /// - If cannot get the `TELOXIDE_TOKEN` and `TELOXIDE_PROXY` environmental - /// variables. - /// - If it cannot create [`reqwest::Client`]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all - #[must_use] - pub fn from_env() -> Self { - BotBuilder::new().build() - } - - /// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a - /// bot's token) and your [`reqwest::Client`]. - /// - /// # Panics - /// If cannot get the `TELOXIDE_TOKEN` environmental variable. - /// - /// # Caution - /// Your custom client might not be configured correctly to be able to work - /// in long time durations, see [issue 223]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 - #[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \ - (notably default parse_mode)"] - pub fn from_env_with_client(client: Client) -> Self { - #[allow(deprecated)] - Self::with_client(&get_env(TELOXIDE_TOKEN), client) - } - - /// Creates a new `Bot` with the specified token and the default - /// [`reqwest::Client`]. - /// - /// # Panics - /// If it cannot create [`reqwest::Client`]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html - #[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \ - (notably default parse_mode)"] - pub fn new<S>(token: S) -> Self - where - S: Into<String>, - { - #[allow(deprecated)] - Self::with_client(token, build_sound_bot()) - } - - /// Creates a new `Bot` with the specified token and your - /// [`reqwest::Client`]. - /// - /// # Caution - /// Your custom client might not be configured correctly to be able to work - /// in long time durations, see [issue 223]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html - /// [issue 223]: https://github.com/teloxide/teloxide/issues/223 - #[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \ - (notably default parse_mode)"] - pub fn with_client<S>(token: S, client: Client) -> Self - where - S: Into<String>, - { - Self { - token: Into::<Arc<str>>::into(Into::<String>::into(token)), - client, - parse_mode: Arc::new(None), - } - } -} - -/// Returns a builder with safe settings. -/// -/// By "safe settings" I mean that a client will be able to work in long time -/// durations, see the [issue 223]. -/// -/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 -pub(crate) fn sound_bot() -> ClientBuilder { - let mut headers = HeaderMap::new(); - headers.insert(CONNECTION, "keep-alive".parse().unwrap()); - - let connect_timeout = Duration::from_secs(5); - let timeout = 10; - - ClientBuilder::new() - .connect_timeout(connect_timeout) - .timeout(Duration::from_secs(connect_timeout.as_secs() + timeout + 2)) - .tcp_nodelay_(true) - .default_headers(headers) -} - -pub(crate) fn build_sound_bot() -> Client { - sound_bot().build().expect("creating reqwest::Client") -} - -fn get_env(env: &'static str) -> String { - std::env::var(env).unwrap_or_else(|_| panic!("Cannot get the {} env variable", env)) -} - -impl Bot { - // TODO: const fn - pub fn token(&self) -> &str { - &self.token - } - - // TODO: const fn - pub fn client(&self) -> &Client { - &self.client - } -} - -/// A builder of [`Bot`], supporting some extra settings. -/// -/// [`Bot`]: crate::Bot -#[derive(Debug, Default)] -pub struct BotBuilder { - token: Option<String>, - client: Option<Client>, - parse_mode: Option<ParseMode>, -} - -impl BotBuilder { - #[must_use] - pub fn new() -> Self { - Self::default() - } - - /// Specifies a custom HTTPS client. Otherwise, the default will be used. - /// - /// # Caution - /// - Your custom client might not be configured correctly to be able to - /// work - /// 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 - /// [`BotBuilder::build`]: crate::BotBuilder::build - #[must_use] - pub fn client(mut self, client: Client) -> Self { - self.client = Some(client); - self - } - - /// Specified a custom token. - /// - /// Otherwise, a token will be extracted from the `TELOXIDE_TOKEN` - /// environmental variable. - #[must_use] - pub fn token<S>(mut self, token: S) -> Self - where - S: Into<String>, - { - self.token = Some(token.into()); - self - } - - /// Specifies [`ParseMode`], which will be used during all calls to: - /// - /// - [`send_message`] - /// - [`send_photo`] - /// - [`send_video`] - /// - [`send_audio`] - /// - [`send_document`] - /// - [`send_animation`] - /// - [`send_voice`] - /// - [`send_poll`] - /// - [`edit_message_text`] - /// - [`edit_message_caption`] - /// - /// [`send_message`]: crate::Bot::send_message - /// [`send_photo`]: crate::Bot::send_photo - /// [`send_video`]: crate::Bot::send_video - /// [`send_audio`]: crate::Bot::send_audio - /// [`send_document`]: crate::Bot::send_document - /// [`send_animation`]: crate::Bot::send_animation - /// [`send_voice`]: crate::Bot::send_voice - /// [`send_poll`]: crate::Bot::send_poll - /// [`edit_message_text`]: crate::Bot::edit_message_text - /// [`edit_message_caption`]: crate::Bot::edit_message_caption - #[must_use] - pub fn parse_mode(mut self, parse_mode: ParseMode) -> Self { - self.parse_mode = Some(parse_mode); - self - } - - /// 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. - /// - /// # Panics - /// - If cannot get the `TELOXIDE_TOKEN` and `TELOXIDE_PROXY` environmental - /// variables. - /// - If it cannot create [`reqwest::Client`]. - /// - /// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html - /// - /// [`Bot`]: crate::Bot - /// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all - #[must_use] - pub fn build(self) -> Bot { - Bot { - client: self.client.unwrap_or_else(crate::utils::client_from_env), - token: self.token.unwrap_or_else(|| get_env(TELOXIDE_TOKEN)).into(), - parse_mode: Arc::new(self.parse_mode), - } - } -} diff --git a/src/dispatching/dialogue/dialogue_dispatcher.rs b/src/dispatching/dialogue/dialogue_dispatcher.rs index 18565824..706cf952 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher.rs @@ -6,11 +6,13 @@ use crate::dispatching::{ }; use std::{convert::Infallible, marker::PhantomData}; -use futures::{future::BoxFuture, StreamExt}; +use futures::{future::BoxFuture, FutureExt, StreamExt}; use tokio::sync::mpsc; use lockfree::map::Map; use std::sync::{Arc, Mutex}; +use teloxide_core::requests::Requester; +use tokio_stream::wrappers::UnboundedReceiverStream; /// A dispatcher of dialogues. /// @@ -22,7 +24,7 @@ use std::sync::{Arc, Mutex}; /// /// [`Dispatcher`]: crate::dispatching::Dispatcher /// [`DispatcherHandler`]: crate::dispatching::DispatcherHandler -pub struct DialogueDispatcher<D, S, H, Upd> { +pub struct DialogueDispatcher<R, D, S, H, Upd> { storage: Arc<S>, handler: Arc<H>, _phantom: PhantomData<Mutex<D>>, @@ -33,12 +35,12 @@ pub struct DialogueDispatcher<D, S, H, Upd> { /// A value is the TX part of an unbounded asynchronous MPSC channel. A /// handler that executes updates from the same chat ID sequentially /// handles the RX part. - senders: Arc<Map<i64, mpsc::UnboundedSender<UpdateWithCx<Upd>>>>, + senders: Arc<Map<i64, mpsc::UnboundedSender<UpdateWithCx<R, Upd>>>>, } -impl<D, H, Upd> DialogueDispatcher<D, InMemStorage<D>, H, Upd> +impl<R, D, H, Upd> DialogueDispatcher<R, D, InMemStorage<D>, H, Upd> where - H: DialogueDispatcherHandler<Upd, D, Infallible> + Send + Sync + 'static, + H: DialogueDispatcherHandler<R, Upd, D, Infallible> + Send + Sync + 'static, Upd: GetChatId + Send + 'static, D: Default + Send + 'static, { @@ -57,9 +59,9 @@ where } } -impl<D, S, H, Upd> DialogueDispatcher<D, S, H, Upd> +impl<R, D, S, H, Upd> DialogueDispatcher<R, D, S, H, Upd> where - H: DialogueDispatcherHandler<Upd, D, S::Error> + Send + Sync + 'static, + H: DialogueDispatcherHandler<R, Upd, D, S::Error> + Send + Sync + 'static, Upd: GetChatId + Send + 'static, D: Default + Send + 'static, S: Storage<D> + Send + Sync + 'static, @@ -77,14 +79,17 @@ where } #[must_use] - fn new_tx(&self) -> mpsc::UnboundedSender<UpdateWithCx<Upd>> { + fn new_tx(&self) -> mpsc::UnboundedSender<UpdateWithCx<R, Upd>> + where + R: Requester + Send + 'static, + { let (tx, rx) = mpsc::unbounded_channel(); let storage = Arc::clone(&self.storage); let handler = Arc::clone(&self.handler); let senders = Arc::clone(&self.senders); - tokio::spawn(rx.for_each(move |cx: UpdateWithCx<Upd>| { + tokio::spawn(UnboundedReceiverStream::new(rx).for_each(move |cx: UpdateWithCx<R, Upd>| { let storage = Arc::clone(&storage); let handler = Arc::clone(&handler); let senders = Arc::clone(&senders); @@ -123,42 +128,48 @@ where } } -impl<D, S, H, Upd> DispatcherHandler<Upd> for DialogueDispatcher<D, S, H, Upd> +impl<R, D, S, H, Upd> DispatcherHandler<R, Upd> for DialogueDispatcher<R, D, S, H, Upd> where - H: DialogueDispatcherHandler<Upd, D, S::Error> + Send + Sync + 'static, + H: DialogueDispatcherHandler<R, Upd, D, S::Error> + Send + Sync + 'static, Upd: GetChatId + Send + 'static, D: Default + Send + 'static, S: Storage<D> + Send + Sync + 'static, S::Error: Send + 'static, + R: Requester + Send, { - fn handle(self, updates: mpsc::UnboundedReceiver<UpdateWithCx<Upd>>) -> BoxFuture<'static, ()> + fn handle( + self, + updates: mpsc::UnboundedReceiver<UpdateWithCx<R, Upd>>, + ) -> BoxFuture<'static, ()> where - UpdateWithCx<Upd>: 'static, + UpdateWithCx<R, Upd>: 'static, { let this = Arc::new(self); - Box::pin(updates.for_each(move |cx| { - let this = Arc::clone(&this); - let chat_id = cx.update.chat_id(); + UnboundedReceiverStream::new(updates) + .for_each(move |cx| { + let this = Arc::clone(&this); + let chat_id = cx.update.chat_id(); - match this.senders.get(&chat_id) { - // An old dialogue - Some(tx) => { - if tx.1.send(cx).is_err() { - panic!("We are not dropping a receiver or call .close() on it",); + match this.senders.get(&chat_id) { + // An old dialogue + Some(tx) => { + if tx.1.send(cx).is_err() { + 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() } } @@ -166,12 +177,12 @@ where mod tests { use super::*; - use crate::Bot; use futures::{stream, StreamExt}; use lazy_static::lazy_static; + use teloxide_core::Bot; use tokio::{ sync::{mpsc, Mutex}, - time::{delay_for, Duration}, + time::Duration, }; #[tokio::test] @@ -181,7 +192,7 @@ mod tests { struct MyUpdate { chat_id: i64, unique_number: u32, - }; + } impl MyUpdate { fn new(chat_id: i64, unique_number: u32) -> Self { @@ -201,9 +212,9 @@ mod tests { static ref SEQ3: Mutex<Vec<u32>> = Mutex::new(Vec::new()); } - let dispatcher = - DialogueDispatcher::new(|cx: DialogueWithCx<MyUpdate, (), Infallible>| async move { - delay_for(Duration::from_millis(300)).await; + let dispatcher = DialogueDispatcher::new( + |cx: DialogueWithCx<Bot, MyUpdate, (), Infallible>| async move { + tokio::time::sleep(Duration::from_millis(300)).await; match cx.cx.update { MyUpdate { chat_id: 1, unique_number } => { @@ -219,7 +230,8 @@ mod tests { } DialogueStage::Next(()) - }); + }, + ); let updates = stream::iter( vec![ @@ -246,8 +258,8 @@ mod tests { MyUpdate::new(3, 1611), ] .into_iter() - .map(|update| UpdateWithCx { update, bot: Bot::new("Doesn't matter here") }) - .collect::<Vec<UpdateWithCx<MyUpdate>>>(), + .map(|update| UpdateWithCx { update, requester: Bot::new("Doesn't matter here") }) + .collect::<Vec<UpdateWithCx<Bot, MyUpdate>>>(), ); let (tx, rx) = mpsc::unbounded_channel(); @@ -267,7 +279,7 @@ mod tests { dispatcher.handle(rx).await; // Wait until our futures to be finished. - delay_for(Duration::from_millis(3000)).await; + tokio::time::sleep(Duration::from_millis(3000)).await; assert_eq!(*SEQ1.lock().await, vec![174, 125, 2, 193, 104, 7, 7778]); assert_eq!(*SEQ2.lock().await, vec![411, 515, 623, 2222, 737, 10, 55456]); diff --git a/src/dispatching/dialogue/dialogue_dispatcher_handler.rs b/src/dispatching/dialogue/dialogue_dispatcher_handler.rs index 1a596bcc..827809ea 100644 --- a/src/dispatching/dialogue/dialogue_dispatcher_handler.rs +++ b/src/dispatching/dialogue/dialogue_dispatcher_handler.rs @@ -8,24 +8,32 @@ use std::{future::Future, sync::Arc}; /// overview](crate::dispatching::dialogue). /// /// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher -pub trait DialogueDispatcherHandler<Upd, D, E> { +pub trait DialogueDispatcherHandler<R, Upd, D, E> { #[must_use] fn handle( self: Arc<Self>, - cx: DialogueWithCx<Upd, D, E>, + cx: DialogueWithCx<R, Upd, D, E>, ) -> BoxFuture<'static, DialogueStage<D>> where - DialogueWithCx<Upd, D, E>: Send + 'static; + DialogueWithCx<R, Upd, D, E>: Send + 'static, + R: Send, + Upd: Send, + D: Send, + E: Send; } -impl<Upd, D, E, F, Fut> DialogueDispatcherHandler<Upd, D, E> for F +impl<R, Upd, D, E, F, Fut> DialogueDispatcherHandler<R, Upd, D, E> for F where - F: Fn(DialogueWithCx<Upd, D, E>) -> Fut + Send + Sync + 'static, + F: Fn(DialogueWithCx<R, Upd, D, E>) -> Fut + Send + Sync + 'static, Fut: Future<Output = DialogueStage<D>> + Send + 'static, { - fn handle(self: Arc<Self>, cx: DialogueWithCx<Upd, D, E>) -> BoxFuture<'static, Fut::Output> + fn handle(self: Arc<Self>, cx: DialogueWithCx<R, Upd, D, E>) -> BoxFuture<'static, Fut::Output> where - DialogueWithCx<Upd, D, E>: Send + 'static, + DialogueWithCx<R, Upd, D, E>: Send + 'static, + R: Send, + Upd: Send, + D: Send, + E: Send, { Box::pin(async move { self(cx).await }) } diff --git a/src/dispatching/dialogue/dialogue_stage.rs b/src/dispatching/dialogue/dialogue_stage.rs index 7dcafec3..ad731e74 100644 --- a/src/dispatching/dialogue/dialogue_stage.rs +++ b/src/dispatching/dialogue/dialogue_stage.rs @@ -21,7 +21,7 @@ pub enum DialogueStage<D> { /// /// [`From`]: std::convert::From /// [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 Dialogue: From<State>, { @@ -32,6 +32,6 @@ where /// /// See [the module-level documentation for the design /// overview](crate::dispatching::dialogue). -pub fn exit<D>() -> TransitionOut<D> { +pub fn exit<D, E>() -> TransitionOut<D, E> { Ok(DialogueStage::Exit) } diff --git a/src/dispatching/dialogue/dialogue_with_cx.rs b/src/dispatching/dialogue/dialogue_with_cx.rs index 90ab4556..8b718122 100644 --- a/src/dispatching/dialogue/dialogue_with_cx.rs +++ b/src/dispatching/dialogue/dialogue_with_cx.rs @@ -1,5 +1,6 @@ use crate::dispatching::{dialogue::GetChatId, UpdateWithCx}; use std::fmt::Debug; +use teloxide_core::requests::Requester; /// A context of a [`DialogueDispatcher`]'s message handler. /// @@ -8,21 +9,22 @@ use std::fmt::Debug; /// /// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher #[derive(Debug)] -pub struct DialogueWithCx<Upd, D, E> { - pub cx: UpdateWithCx<Upd>, +pub struct DialogueWithCx<R, Upd, D, E> { + pub cx: UpdateWithCx<R, Upd>, pub dialogue: Result<D, E>, } -impl<Upd, D, E> DialogueWithCx<Upd, D, E> { +impl<Upd, R, D, E> DialogueWithCx<R, Upd, D, E> { /// Creates a new instance with the provided fields. - pub fn new(cx: UpdateWithCx<Upd>, dialogue: D) -> Self { + pub fn new(cx: UpdateWithCx<R, Upd>, dialogue: D) -> Self { Self { cx, dialogue: Ok(dialogue) } } } -impl<Upd, D, E> GetChatId for DialogueWithCx<Upd, D, E> +impl<Upd, R, D, E> GetChatId for DialogueWithCx<R, Upd, D, E> where Upd: GetChatId, + R: Requester, { fn chat_id(&self) -> i64 { self.cx.update.chat_id() diff --git a/src/dispatching/dialogue/get_chat_id.rs b/src/dispatching/dialogue/get_chat_id.rs index d7e64206..9d492a43 100644 --- a/src/dispatching/dialogue/get_chat_id.rs +++ b/src/dispatching/dialogue/get_chat_id.rs @@ -1,4 +1,4 @@ -use crate::types::Message; +use teloxide_core::types::Message; /// Something that has a chat ID. pub trait GetChatId { diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 420a755b..303d161c 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -30,29 +30,29 @@ //! skeleton should look like: //! //! ```no_run +//! # #[cfg(feature = "macros")] { //! use std::convert::Infallible; //! -//! use teloxide::prelude::*; -//! use teloxide_macros::{teloxide, Transition}; +//! use teloxide::{dispatching::dialogue::Transition, prelude::*, teloxide, RequestError}; //! //! struct _1State; //! struct _2State; //! struct _3State; //! -//! type Out = TransitionOut<D>; +//! type Out = TransitionOut<D, RequestError>; //! //! #[teloxide(subtransition)] -//! async fn _1_transition(_state: _1State, _cx: TransitionIn) -> Out { +//! async fn _1_transition(_state: _1State, _cx: TransitionIn<AutoSend<Bot>>) -> Out { //! todo!() //! } //! //! #[teloxide(subtransition)] -//! async fn _2_transition(_state: _2State, _cx: TransitionIn) -> Out { +//! async fn _2_transition(_state: _2State, _cx: TransitionIn<AutoSend<Bot>>) -> Out { //! todo!() //! } //! //! #[teloxide(subtransition)] -//! async fn _3_transition(_state: _3State, _cx: TransitionIn) -> Out { +//! async fn _3_transition(_state: _3State, _cx: TransitionIn<AutoSend<Bot>>) -> Out { //! todo!() //! } //! @@ -69,7 +69,7 @@ //! } //! } //! -//! type In = DialogueWithCx<Message, D, Infallible>; +//! type In = DialogueWithCx<AutoSend<Bot>, Message, D, Infallible>; //! //! #[tokio::main] //! async fn main() { @@ -80,7 +80,7 @@ //! teloxide::enable_logging!(); //! log::info!("Starting dialogue_bot!"); //! -//! let bot = Bot::from_env(); +//! let bot = Bot::from_env().auto_send(); //! //! Dispatcher::new(bot) //! .messages_handler(DialogueDispatcher::new( @@ -97,6 +97,7 @@ //! .dispatch() //! .await; //! } +//! # } //! ``` //! //! - `#[teloxide(subtransition)]` implements [`Subtransition`] for the first @@ -156,7 +157,15 @@ pub use transition::{ 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_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))] pub use storage::{RedisStorage, RedisStorageError}; -pub use storage::{serializer, InMemStorage, Serializer, Storage}; +#[cfg(feature = "sqlite-storage")] +pub use storage::{SqliteStorage, SqliteStorageError}; + +pub use storage::{serializer, InMemStorage, Serializer, Storage, TraceStorage}; diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs index 78fc842f..468a305e 100644 --- a/src/dispatching/dialogue/storage/in_mem_storage.rs +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -8,8 +8,11 @@ use tokio::sync::Mutex; /// /// ## Note /// All the dialogues will be lost after you restart your bot. If you need to -/// store them somewhere on a drive, you need to implement a storage -/// communicating with a DB. +/// store them somewhere on a drive, you should use [`SqliteStorage`], +/// [`RedisStorage`] or implement your own. +/// +/// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage +/// [`SqliteStorage`]: crate::dispatching::dialogue::SqliteStorage #[derive(Debug)] pub struct InMemStorage<D> { map: Mutex<HashMap<i64, D>>, diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index ed5319c8..31664425 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -1,26 +1,41 @@ pub mod serializer; mod in_mem_storage; +mod trace_storage; #[cfg(feature = "redis-storage")] mod redis_storage; +#[cfg(feature = "sqlite-storage")] +mod sqlite_storage; + use futures::future::BoxFuture; -pub use in_mem_storage::InMemStorage; +pub use self::{in_mem_storage::InMemStorage, trace_storage::TraceStorage}; + #[cfg(feature = "redis-storage")] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))] pub use redis_storage::{RedisStorage, RedisStorageError}; pub use serializer::Serializer; use std::sync::Arc; +#[cfg(feature = "sqlite-storage")] +pub use sqlite_storage::{SqliteStorage, SqliteStorageError}; + /// A storage of dialogues. /// /// You can implement this trait for a structure that communicates with a DB and /// be sure that after you restart your bot, all the dialogues won't be lost. /// -/// For a storage based on a simple hash map, see [`InMemStorage`]. +/// Currently we support the following storages out of the box: +/// +/// - [`InMemStorage`] - a storage based on a simple hash map +/// - [`RedisStorage`] - a Redis-based storage +/// - [`SqliteStorage`] - an SQLite-based persistent storage /// /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage +/// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage +/// [`SqliteStorage`]: crate::dispatching::dialogue::SqliteStorage pub trait Storage<D> { type Error; diff --git a/src/dispatching/dialogue/storage/redis_storage.rs b/src/dispatching/dialogue/storage/redis_storage.rs index a8514355..31a358e8 100644 --- a/src/dispatching/dialogue/storage/redis_storage.rs +++ b/src/dispatching/dialogue/storage/redis_storage.rs @@ -91,14 +91,13 @@ where Box::pin(async move { let dialogue = self.serializer.serialize(&dialogue).map_err(RedisStorageError::SerdeError)?; - Ok(self - .conn + self.conn .lock() .await .getset::<_, Vec<u8>, Option<Vec<u8>>>(chat_id, dialogue) .await? .map(|d| self.serializer.deserialize(&d).map_err(RedisStorageError::SerdeError)) - .transpose()?) + .transpose() }) } } diff --git a/src/dispatching/dialogue/storage/serializer.rs b/src/dispatching/dialogue/storage/serializer.rs index 20fa4841..2e0aa945 100644 --- a/src/dispatching/dialogue/storage/serializer.rs +++ b/src/dispatching/dialogue/storage/serializer.rs @@ -11,9 +11,9 @@ pub trait Serializer<D> { } /// The JSON serializer for memory storages. -pub struct JSON; +pub struct Json; -impl<D> Serializer<D> for JSON +impl<D> Serializer<D> for Json where D: Serialize + DeserializeOwned, { @@ -32,10 +32,12 @@ where /// /// [CBOR]: https://en.wikipedia.org/wiki/CBOR #[cfg(feature = "cbor-serializer")] -pub struct CBOR; +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))] +pub struct Cbor; #[cfg(feature = "cbor-serializer")] -impl<D> Serializer<D> for CBOR +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))] +impl<D> Serializer<D> for Cbor where D: Serialize + DeserializeOwned, { @@ -54,9 +56,11 @@ where /// /// [Bincode]: https://github.com/servo/bincode #[cfg(feature = "bincode-serializer")] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "bincode-serializer")))] pub struct Bincode; #[cfg(feature = "bincode-serializer")] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "bincode-serializer")))] impl<D> Serializer<D> for Bincode where D: Serialize + DeserializeOwned, diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs new file mode 100644 index 00000000..f4e4d98c --- /dev/null +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -0,0 +1,130 @@ +use super::{serializer::Serializer, Storage}; +use futures::future::BoxFuture; +use serde::{de::DeserializeOwned, Serialize}; +use sqlx::{sqlite::SqlitePool, Executor}; +use std::{ + convert::Infallible, + fmt::{Debug, Display}, + str, + sync::Arc, +}; +use thiserror::Error; + +/// A persistent storage based on [SQLite](https://www.sqlite.org/). +pub struct SqliteStorage<S> { + pool: SqlitePool, + serializer: S, +} + +/// An error returned from [`SqliteStorage`]. +/// +/// [`SqliteStorage`]: struct.SqliteStorage.html +#[derive(Debug, Error)] +pub enum SqliteStorageError<SE> +where + SE: Debug + Display, +{ + #[error("dialogue serialization error: {0}")] + SerdeError(SE), + #[error("sqlite error: {0}")] + SqliteError(#[from] sqlx::Error), +} + +impl<S> SqliteStorage<S> { + pub async fn open( + path: &str, + serializer: S, + ) -> Result<Arc<Self>, SqliteStorageError<Infallible>> { + let pool = SqlitePool::connect(format!("sqlite:{}?mode=rwc", path).as_str()).await?; + let mut conn = pool.acquire().await?; + sqlx::query( + r#" +CREATE TABLE IF NOT EXISTS teloxide_dialogues ( + chat_id BIGINT PRIMARY KEY, + dialogue BLOB NOT NULL +); + "#, + ) + .execute(&mut conn) + .await?; + + Ok(Arc::new(Self { pool, serializer })) + } +} + +impl<S, D> Storage<D> for SqliteStorage<S> +where + S: Send + Sync + Serializer<D> + 'static, + D: Send + Serialize + DeserializeOwned + 'static, + <S as Serializer<D>>::Error: Debug + Display, +{ + type Error = SqliteStorageError<<S as Serializer<D>>::Error>; + + fn remove_dialogue( + self: Arc<Self>, + chat_id: i64, + ) -> BoxFuture<'static, Result<Option<D>, Self::Error>> { + Box::pin(async move { + Ok(match get_dialogue(&self.pool, chat_id).await? { + Some(d) => { + let prev_dialogue = + self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)?; + sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?") + .bind(chat_id) + .execute(&self.pool) + .await?; + Some(prev_dialogue) + } + _ => None, + }) + }) + } + + fn update_dialogue( + self: Arc<Self>, + chat_id: i64, + dialogue: D, + ) -> BoxFuture<'static, Result<Option<D>, Self::Error>> { + Box::pin(async move { + let prev_dialogue = get_dialogue(&self.pool, chat_id) + .await? + .map(|d| self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)) + .transpose()?; + let upd_dialogue = + self.serializer.serialize(&dialogue).map_err(SqliteStorageError::SerdeError)?; + self.pool + .acquire() + .await? + .execute( + sqlx::query( + r#" + INSERT INTO teloxide_dialogues VALUES (?, ?) + ON CONFLICT(chat_id) DO UPDATE SET dialogue=excluded.dialogue + "#, + ) + .bind(chat_id) + .bind(upd_dialogue), + ) + .await?; + Ok(prev_dialogue) + }) + } +} + +#[derive(sqlx::FromRow)] +struct DialogueDbRow { + dialogue: Vec<u8>, +} + +async fn get_dialogue( + pool: &SqlitePool, + chat_id: i64, +) -> Result<Option<Box<Vec<u8>>>, sqlx::Error> { + Ok(sqlx::query_as::<_, DialogueDbRow>( + "SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?", + ) + .bind(chat_id) + .fetch_optional(pool) + .await? + .map(|r| Box::new(r.dialogue))) +} diff --git a/src/dispatching/dialogue/storage/trace_storage.rs b/src/dispatching/dialogue/storage/trace_storage.rs new file mode 100644 index 00000000..2e28263f --- /dev/null +++ b/src/dispatching/dialogue/storage/trace_storage.rs @@ -0,0 +1,70 @@ +use std::{ + fmt::Debug, + marker::{Send, Sync}, + sync::Arc, +}; + +use futures::future::BoxFuture; +use log::{log_enabled, trace, Level::Trace}; + +use crate::dispatching::dialogue::Storage; + +/// Storage wrapper for logging purposes +/// +/// Reports about any dialogue update or removal action on `trace` level +/// using `log` crate. +pub struct TraceStorage<S> { + inner: Arc<S>, +} + +impl<S> TraceStorage<S> { + #[must_use] + pub fn new(inner: Arc<S>) -> Arc<Self> { + Arc::new(Self { inner }) + } + + pub fn into_inner(self) -> Arc<S> { + self.inner + } +} + +impl<S, D> Storage<D> for TraceStorage<S> +where + D: Debug, + S: Storage<D> + Send + Sync + 'static, +{ + type Error = <S as Storage<D>>::Error; + + fn remove_dialogue( + self: Arc<Self>, + chat_id: i64, + ) -> BoxFuture<'static, Result<Option<D>, Self::Error>> + where + D: Send + 'static, + { + trace!("Removing dialogue with {}", chat_id); + <S as Storage<D>>::remove_dialogue(self.inner.clone(), chat_id) + } + + fn update_dialogue( + self: Arc<Self>, + chat_id: i64, + dialogue: D, + ) -> BoxFuture<'static, Result<Option<D>, Self::Error>> + where + D: Send + 'static, + { + if log_enabled!(Trace) { + Box::pin(async move { + let to = format!("{:#?}", dialogue); + let from = + <S as Storage<D>>::update_dialogue(self.inner.clone(), chat_id, dialogue) + .await?; + trace!("Updated dialogue with {}, {:#?} -> {}", chat_id, from, to); + Ok(from) + }) + } else { + <S as Storage<D>>::update_dialogue(self.inner.clone(), chat_id, dialogue) + } + } +} diff --git a/src/dispatching/dialogue/transition.rs b/src/dispatching/dialogue/transition.rs index d15ce303..5674ae3c 100644 --- a/src/dispatching/dialogue/transition.rs +++ b/src/dispatching/dialogue/transition.rs @@ -1,18 +1,21 @@ -use crate::{ - dispatching::{dialogue::DialogueStage, UpdateWithCx}, - requests::ResponseResult, - types::Message, -}; +use crate::dispatching::{dialogue::DialogueStage, UpdateWithCx}; use futures::future::BoxFuture; +use teloxide_core::types::Message; /// Represents a transition function of a dialogue FSM. pub trait Transition: Sized { type Aux; + type Error; + type Requester; /// Turns itself into another state, depending on the input message. /// /// `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<Self::Requester>, + aux: Self::Aux, + ) -> BoxFuture<'static, TransitionOut<Self, Self::Error>>; } /// Like [`Transition`], but from `StateN` -> `Dialogue`. @@ -24,6 +27,8 @@ where { type Aux; type Dialogue; + type Error; + type Requester; /// Turns itself into another state, depending on the input message. /// @@ -31,24 +36,27 @@ where /// message's text. fn react( self, - cx: TransitionIn, + cx: TransitionIn<Self::Requester>, aux: Self::Aux, - ) -> BoxFuture<'static, TransitionOut<Self::Dialogue>>; + ) -> BoxFuture<'static, TransitionOut<Self::Dialogue, Self::Error>>; } /// A type returned from a FSM subtransition function. /// /// Now it is used only inside `#[teloxide(subtransition)]` for type inference. +#[doc(hidden)] pub trait SubtransitionOutputType { type Output; + type Error; } -impl<D> SubtransitionOutputType for TransitionOut<D> { +impl<D, E> SubtransitionOutputType for TransitionOut<D, E> { type Output = D; + type Error = E; } /// An input passed into a FSM (sub)transition function. -pub type TransitionIn = UpdateWithCx<Message>; +pub type TransitionIn<R> = UpdateWithCx<R, Message>; /// 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>; diff --git a/src/dispatching/dispatcher.rs b/src/dispatching/dispatcher.rs index 95b067c2..e7635ebb 100644 --- a/src/dispatching/dispatcher.rs +++ b/src/dispatching/dispatcher.rs @@ -3,34 +3,37 @@ use crate::{ update_listeners, update_listeners::UpdateListener, DispatcherHandler, UpdateWithCx, }, error_handlers::{ErrorHandler, LoggingErrorHandler}, - types::{ - CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, - PreCheckoutQuery, ShippingQuery, UpdateKind, - }, - Bot, }; use futures::StreamExt; use std::{fmt::Debug, sync::Arc}; +use teloxide_core::{ + requests::Requester, + types::{ + CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, + PollAnswer, PreCheckoutQuery, ShippingQuery, UpdateKind, + }, +}; use tokio::sync::mpsc; -type Tx<Upd> = Option<mpsc::UnboundedSender<UpdateWithCx<Upd>>>; +type Tx<Upd, R> = Option<mpsc::UnboundedSender<UpdateWithCx<Upd, R>>>; #[macro_use] mod macros { /// Pushes an update to a queue. macro_rules! send { - ($bot:expr, $tx:expr, $update:expr, $variant:expr) => { - send($bot, $tx, $update, stringify!($variant)); + ($requester:expr, $tx:expr, $update:expr, $variant:expr) => { + send($requester, $tx, $update, stringify!($variant)); }; } } -fn send<'a, Upd>(bot: &'a Bot, tx: &'a Tx<Upd>, update: Upd, variant: &'static str) +fn send<'a, R, Upd>(requester: &'a R, tx: &'a Tx<R, Upd>, update: Upd, variant: &'static str) where Upd: Debug, + R: Requester + Clone, { if let Some(tx) = tx { - if let Err(error) = tx.send(UpdateWithCx { bot: bot.clone(), update }) { + if let Err(error) = tx.send(UpdateWithCx { requester: requester.clone(), update }) { log::error!( "The RX part of the {} channel is closed, but an update is received.\nError:{}\n", variant, @@ -44,28 +47,33 @@ where /// /// See the [module-level documentation](crate::dispatching) for the design /// overview. -pub struct Dispatcher { - bot: Bot, +pub struct Dispatcher<R> { + requester: R, - messages_queue: Tx<Message>, - edited_messages_queue: Tx<Message>, - channel_posts_queue: Tx<Message>, - edited_channel_posts_queue: Tx<Message>, - inline_queries_queue: Tx<InlineQuery>, - chosen_inline_results_queue: Tx<ChosenInlineResult>, - callback_queries_queue: Tx<CallbackQuery>, - shipping_queries_queue: Tx<ShippingQuery>, - pre_checkout_queries_queue: Tx<PreCheckoutQuery>, - polls_queue: Tx<Poll>, - poll_answers_queue: Tx<PollAnswer>, + messages_queue: Tx<R, Message>, + edited_messages_queue: Tx<R, Message>, + channel_posts_queue: Tx<R, Message>, + edited_channel_posts_queue: Tx<R, Message>, + inline_queries_queue: Tx<R, InlineQuery>, + chosen_inline_results_queue: Tx<R, ChosenInlineResult>, + callback_queries_queue: Tx<R, CallbackQuery>, + shipping_queries_queue: Tx<R, ShippingQuery>, + pre_checkout_queries_queue: Tx<R, PreCheckoutQuery>, + polls_queue: Tx<R, Poll>, + poll_answers_queue: Tx<R, PollAnswer>, + my_chat_members_queue: Tx<R, ChatMemberUpdated>, + chat_members_queue: Tx<R, ChatMemberUpdated>, } -impl Dispatcher { - /// Constructs a new dispatcher with the specified `bot`. +impl<R> Dispatcher<R> +where + R: Send + 'static, +{ + /// Constructs a new dispatcher with the specified `requester`. #[must_use] - pub fn new(bot: Bot) -> Self { + pub fn new(requester: R) -> Self { Self { - bot, + requester, messages_queue: None, edited_messages_queue: None, channel_posts_queue: None, @@ -77,14 +85,18 @@ impl Dispatcher { pre_checkout_queries_queue: None, polls_queue: None, poll_answers_queue: None, + my_chat_members_queue: None, + chat_members_queue: None, } } #[must_use] - fn new_tx<H, Upd>(&self, h: H) -> Tx<Upd> + #[allow(clippy::unnecessary_wraps)] + fn new_tx<H, Upd>(&self, h: H) -> Tx<R, Upd> where - H: DispatcherHandler<Upd> + Send + 'static, + H: DispatcherHandler<R, Upd> + Send + 'static, Upd: Send + 'static, + R: Send + 'static, { let (tx, rx) = mpsc::unbounded_channel(); tokio::spawn(async move { @@ -97,7 +109,7 @@ impl Dispatcher { #[must_use] pub fn messages_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<Message> + 'static + Send, + H: DispatcherHandler<R, Message> + 'static + Send, { self.messages_queue = self.new_tx(h); self @@ -106,7 +118,7 @@ impl Dispatcher { #[must_use] pub fn edited_messages_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<Message> + 'static + Send, + H: DispatcherHandler<R, Message> + 'static + Send, { self.edited_messages_queue = self.new_tx(h); self @@ -115,7 +127,7 @@ impl Dispatcher { #[must_use] pub fn channel_posts_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<Message> + 'static + Send, + H: DispatcherHandler<R, Message> + 'static + Send, { self.channel_posts_queue = self.new_tx(h); self @@ -124,7 +136,7 @@ impl Dispatcher { #[must_use] pub fn edited_channel_posts_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<Message> + 'static + Send, + H: DispatcherHandler<R, Message> + 'static + Send, { self.edited_channel_posts_queue = self.new_tx(h); self @@ -133,7 +145,7 @@ impl Dispatcher { #[must_use] pub fn inline_queries_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<InlineQuery> + 'static + Send, + H: DispatcherHandler<R, InlineQuery> + 'static + Send, { self.inline_queries_queue = self.new_tx(h); self @@ -142,7 +154,7 @@ impl Dispatcher { #[must_use] pub fn chosen_inline_results_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<ChosenInlineResult> + 'static + Send, + H: DispatcherHandler<R, ChosenInlineResult> + 'static + Send, { self.chosen_inline_results_queue = self.new_tx(h); self @@ -151,7 +163,7 @@ impl Dispatcher { #[must_use] pub fn callback_queries_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<CallbackQuery> + 'static + Send, + H: DispatcherHandler<R, CallbackQuery> + 'static + Send, { self.callback_queries_queue = self.new_tx(h); self @@ -160,7 +172,7 @@ impl Dispatcher { #[must_use] pub fn shipping_queries_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<ShippingQuery> + 'static + Send, + H: DispatcherHandler<R, ShippingQuery> + 'static + Send, { self.shipping_queries_queue = self.new_tx(h); self @@ -169,7 +181,7 @@ impl Dispatcher { #[must_use] pub fn pre_checkout_queries_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<PreCheckoutQuery> + 'static + Send, + H: DispatcherHandler<R, PreCheckoutQuery> + 'static + Send, { self.pre_checkout_queries_queue = self.new_tx(h); self @@ -178,7 +190,7 @@ impl Dispatcher { #[must_use] pub fn polls_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<Poll> + 'static + Send, + H: DispatcherHandler<R, Poll> + 'static + Send, { self.polls_queue = self.new_tx(h); self @@ -187,19 +199,41 @@ impl Dispatcher { #[must_use] pub fn poll_answers_handler<H>(mut self, h: H) -> Self where - H: DispatcherHandler<PollAnswer> + 'static + Send, + H: DispatcherHandler<R, PollAnswer> + 'static + Send, { self.poll_answers_queue = self.new_tx(h); self } + #[must_use] + pub fn my_chat_members_handler<H>(mut self, h: H) -> Self + where + H: DispatcherHandler<R, ChatMemberUpdated> + 'static + Send, + { + self.my_chat_members_queue = self.new_tx(h); + self + } + + #[must_use] + pub fn chat_members_handler<H>(mut self, h: H) -> Self + where + H: DispatcherHandler<R, ChatMemberUpdated> + 'static + Send, + { + self.chat_members_queue = self.new_tx(h); + self + } + /// Starts your bot with the default parameters. /// /// The default parameters are a long polling update listener and log all /// errors produced by this listener). - pub async fn dispatch(&self) { + pub async fn dispatch(&self) + where + R: Requester + Clone, + <R as Requester>::GetUpdatesFaultTolerant: Send, + { self.dispatch_with_listener( - update_listeners::polling_default(self.bot.clone()), + update_listeners::polling_default(self.requester.clone()), LoggingErrorHandler::with_custom_text("An error from the update listener"), ) .await; @@ -215,6 +249,7 @@ impl Dispatcher { UListener: UpdateListener<ListenerE> + 'a, Eh: ErrorHandler<ListenerE> + 'a, ListenerE: Debug, + R: Requester + Clone, { let update_listener = Box::pin(update_listener); @@ -235,11 +270,16 @@ impl Dispatcher { match update.kind { UpdateKind::Message(message) => { - send!(&self.bot, &self.messages_queue, message, UpdateKind::Message); + send!( + &self.requester, + &self.messages_queue, + message, + UpdateKind::Message + ); } UpdateKind::EditedMessage(message) => { send!( - &self.bot, + &self.requester, &self.edited_messages_queue, message, UpdateKind::EditedMessage @@ -247,7 +287,7 @@ impl Dispatcher { } UpdateKind::ChannelPost(post) => { send!( - &self.bot, + &self.requester, &self.channel_posts_queue, post, UpdateKind::ChannelPost @@ -255,7 +295,7 @@ impl Dispatcher { } UpdateKind::EditedChannelPost(post) => { send!( - &self.bot, + &self.requester, &self.edited_channel_posts_queue, post, UpdateKind::EditedChannelPost @@ -263,7 +303,7 @@ impl Dispatcher { } UpdateKind::InlineQuery(query) => { send!( - &self.bot, + &self.requester, &self.inline_queries_queue, query, UpdateKind::InlineQuery @@ -271,7 +311,7 @@ impl Dispatcher { } UpdateKind::ChosenInlineResult(result) => { send!( - &self.bot, + &self.requester, &self.chosen_inline_results_queue, result, UpdateKind::ChosenInlineResult @@ -279,7 +319,7 @@ impl Dispatcher { } UpdateKind::CallbackQuery(query) => { send!( - &self.bot, + &self.requester, &self.callback_queries_queue, query, UpdateKind::CallbackQuer @@ -287,7 +327,7 @@ impl Dispatcher { } UpdateKind::ShippingQuery(query) => { send!( - &self.bot, + &self.requester, &self.shipping_queries_queue, query, UpdateKind::ShippingQuery @@ -295,23 +335,39 @@ impl Dispatcher { } UpdateKind::PreCheckoutQuery(query) => { send!( - &self.bot, + &self.requester, &self.pre_checkout_queries_queue, query, UpdateKind::PreCheckoutQuery ); } UpdateKind::Poll(poll) => { - send!(&self.bot, &self.polls_queue, poll, UpdateKind::Poll); + send!(&self.requester, &self.polls_queue, poll, UpdateKind::Poll); } UpdateKind::PollAnswer(answer) => { send!( - &self.bot, + &self.requester, &self.poll_answers_queue, answer, UpdateKind::PollAnswer ); } + UpdateKind::MyChatMember(chat_member_updated) => { + send!( + &self.requester, + &self.my_chat_members_queue, + chat_member_updated, + UpdateKind::MyChatMember + ); + } + UpdateKind::ChatMember(chat_member_updated) => { + send!( + &self.requester, + &self.chat_members_queue, + chat_member_updated, + UpdateKind::MyChatMember + ); + } } } }) diff --git a/src/dispatching/dispatcher_handler.rs b/src/dispatching/dispatcher_handler.rs index 94ec6dbd..cd05f3cd 100644 --- a/src/dispatching/dispatcher_handler.rs +++ b/src/dispatching/dispatcher_handler.rs @@ -9,21 +9,21 @@ use futures::future::BoxFuture; /// overview. /// /// [`Dispatcher`]: crate::dispatching::Dispatcher -pub trait DispatcherHandler<Upd> { +pub trait DispatcherHandler<R, Upd> { #[must_use] - fn handle(self, updates: DispatcherHandlerRx<Upd>) -> BoxFuture<'static, ()> + fn handle(self, updates: DispatcherHandlerRx<R, Upd>) -> BoxFuture<'static, ()> where - UpdateWithCx<Upd>: Send + 'static; + UpdateWithCx<R, Upd>: Send + 'static; } -impl<Upd, F, Fut> DispatcherHandler<Upd> for F +impl<R, Upd, F, Fut> DispatcherHandler<R, Upd> for F where - F: FnOnce(DispatcherHandlerRx<Upd>) -> Fut + Send + 'static, + F: FnOnce(DispatcherHandlerRx<R, Upd>) -> Fut + Send + 'static, Fut: Future<Output = ()> + Send + 'static, { - fn handle(self, updates: DispatcherHandlerRx<Upd>) -> BoxFuture<'static, ()> + fn handle(self, updates: DispatcherHandlerRx<R, Upd>) -> BoxFuture<'static, ()> where - UpdateWithCx<Upd>: Send + 'static, + UpdateWithCx<R, Upd>: Send + 'static, { Box::pin(async move { self(updates).await }) } diff --git a/src/dispatching/dispatcher_handler_rx_ext.rs b/src/dispatching/dispatcher_handler_rx_ext.rs index 12e26146..d9616645 100644 --- a/src/dispatching/dispatcher_handler_rx_ext.rs +++ b/src/dispatching/dispatcher_handler_rx_ext.rs @@ -1,5 +1,6 @@ -use crate::{prelude::UpdateWithCx, types::Message, utils::command::BotCommand}; +use crate::{dispatching::UpdateWithCx, utils::command::BotCommand}; use futures::{stream::BoxStream, Stream, StreamExt}; +use teloxide_core::types::Message; /// An extension trait to be used with [`DispatcherHandlerRx`]. /// @@ -7,44 +8,54 @@ use futures::{stream::BoxStream, Stream, StreamExt}; /// overview. /// /// [`DispatcherHandlerRx`]: crate::dispatching::DispatcherHandlerRx -pub trait DispatcherHandlerRxExt { +pub trait DispatcherHandlerRxExt<R> { /// Extracts only text messages from this stream of arbitrary messages. - fn text_messages(self) -> BoxStream<'static, (UpdateWithCx<Message>, String)> + fn text_messages(self) -> BoxStream<'static, (UpdateWithCx<R, Message>, String)> where - Self: Stream<Item = UpdateWithCx<Message>>; + Self: Stream<Item = UpdateWithCx<R, Message>>, + R: Send + 'static; /// Extracts only commands with their arguments from this stream of /// arbitrary messages. - fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<Message>, C)> + fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<R, Message>, C)> where - Self: Stream<Item = UpdateWithCx<Message>>, + Self: Stream<Item = UpdateWithCx<R, Message>>, C: BotCommand, - N: Into<String> + Send; + N: Into<String> + Send, + R: Send + 'static; } -impl<T> DispatcherHandlerRxExt for T +impl<R, T> DispatcherHandlerRxExt<R> for T where T: Send + 'static, { - fn text_messages(self) -> BoxStream<'static, (UpdateWithCx<Message>, String)> + fn text_messages(self) -> BoxStream<'static, (UpdateWithCx<R, Message>, String)> where - Self: Stream<Item = UpdateWithCx<Message>>, + Self: Stream<Item = UpdateWithCx<R, Message>>, + R: Send + 'static, { - Box::pin(self.filter_map(|cx| async move { cx.update.text_owned().map(|text| (cx, text)) })) + self.filter_map(|cx| async move { + let text = cx.update.text().map(ToOwned::to_owned); + text.map(move |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<R, Message>, C)> where - Self: Stream<Item = UpdateWithCx<Message>>, + Self: Stream<Item = UpdateWithCx<R, Message>>, C: BotCommand, N: Into<String> + Send, + R: Send + 'static, { let bot_name = bot_name.into(); - Box::pin(self.text_messages().filter_map(move |(cx, text)| { - let bot_name = bot_name.clone(); + self.text_messages() + .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() } } diff --git a/src/dispatching/mod.rs b/src/dispatching/mod.rs index 05e367dd..651cae53 100644 --- a/src/dispatching/mod.rs +++ b/src/dispatching/mod.rs @@ -9,12 +9,14 @@ //! //! ``` //! use teloxide::prelude::*; +//! use tokio_stream::wrappers::UnboundedReceiverStream; //! -//! async fn handle_messages(rx: DispatcherHandlerRx<Message>) { -//! rx.for_each_concurrent(None, |message| async move { -//! dbg!(message); -//! }) -//! .await; +//! async fn handle_messages(rx: DispatcherHandlerRx<AutoSend<Bot>, Message>) { +//! UnboundedReceiverStream::new(rx) +//! .for_each_concurrent(None, |message| async move { +//! dbg!(message.update); +//! }) +//! .await; //! } //! ``` //! @@ -55,9 +57,9 @@ pub use dispatcher::Dispatcher; pub use dispatcher_handler::DispatcherHandler; pub use dispatcher_handler_rx_ext::DispatcherHandlerRxExt; use tokio::sync::mpsc::UnboundedReceiver; -pub use update_with_cx::UpdateWithCx; +pub use update_with_cx::{UpdateWithCx, UpdateWithCxRequesterType}; /// A type of a stream, consumed by [`Dispatcher`]'s handlers. /// /// [`Dispatcher`]: crate::dispatching::Dispatcher -pub type DispatcherHandlerRx<Upd> = UnboundedReceiver<UpdateWithCx<Upd>>; +pub type DispatcherHandlerRx<R, Upd> = UnboundedReceiver<UpdateWithCx<R, Upd>>; diff --git a/src/dispatching/repls/commands_repl.rs b/src/dispatching/repls/commands_repl.rs index c842fd7a..249d4861 100644 --- a/src/dispatching/repls/commands_repl.rs +++ b/src/dispatching/repls/commands_repl.rs @@ -4,12 +4,12 @@ use crate::{ DispatcherHandlerRxExt, UpdateWithCx, }, error_handlers::{LoggingErrorHandler, OnError}, - types::Message, utils::command::BotCommand, - Bot, }; use futures::StreamExt; use std::{fmt::Debug, future::Future, sync::Arc}; +use teloxide_core::{requests::Requester, types::Message}; +use tokio_stream::wrappers::UnboundedReceiverStream; /// A [REPL] for commands. /// @@ -22,21 +22,24 @@ use std::{fmt::Debug, future::Future, sync::Arc}; /// /// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop /// [`Dispatcher`]: crate::dispatching::Dispatcher -pub async fn commands_repl<Cmd, H, Fut, HandlerE>(bot: Bot, bot_name: &'static str, handler: H) +pub async fn commands_repl<R, Cmd, H, Fut, HandlerE, N>(requester: R, bot_name: N, handler: H) where Cmd: BotCommand + Send + 'static, - H: Fn(UpdateWithCx<Message>, Cmd) -> Fut + Send + Sync + 'static, + H: Fn(UpdateWithCx<R, 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, + R: Requester + Send + Clone + 'static, + <R as Requester>::GetUpdatesFaultTolerant: Send, { - let cloned_bot = bot.clone(); + let cloned_requester = requester.clone(); commands_repl_with_listener( - bot, + requester, bot_name, handler, - update_listeners::polling_default(cloned_bot), + update_listeners::polling_default(cloned_requester), ) .await; } @@ -53,25 +56,27 @@ where /// [`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>( - bot: Bot, - bot_name: &'static str, +pub async fn commands_repl_with_listener<'a, R, Cmd, H, Fut, L, ListenerE, HandlerE, N>( + requester: R, + bot_name: N, handler: H, listener: L, ) where Cmd: BotCommand + Send + 'static, - H: Fn(UpdateWithCx<Message>, Cmd) -> Fut + Send + Sync + 'static, + H: Fn(UpdateWithCx<R, 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, + R: Requester + Clone + Send + 'static, { let handler = Arc::new(handler); - Dispatcher::new(bot) - .messages_handler(move |rx: DispatcherHandlerRx<Message>| { - rx.commands::<Cmd, &'static str>(bot_name).for_each_concurrent( + Dispatcher::<R>::new(requester) + .messages_handler(move |rx: DispatcherHandlerRx<R, Message>| { + UnboundedReceiverStream::new(rx).commands::<Cmd, N>(bot_name).for_each_concurrent( None, move |(cx, cmd)| { let handler = Arc::clone(&handler); diff --git a/src/dispatching/repls/dialogues_repl.rs b/src/dispatching/repls/dialogues_repl.rs index 883e74de..706d26a1 100644 --- a/src/dispatching/repls/dialogues_repl.rs +++ b/src/dispatching/repls/dialogues_repl.rs @@ -6,10 +6,9 @@ use crate::{ Dispatcher, UpdateWithCx, }, error_handlers::LoggingErrorHandler, - types::Message, - Bot, }; use std::{convert::Infallible, fmt::Debug, future::Future, sync::Arc}; +use teloxide_core::{requests::Requester, types::Message}; /// A [REPL] for dialogues. /// @@ -24,15 +23,22 @@ use std::{convert::Infallible, fmt::Debug, future::Future, sync::Arc}; /// [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) +pub async fn dialogues_repl<'a, R, H, D, Fut>(requester: R, handler: H) where - H: Fn(UpdateWithCx<Message>, D) -> Fut + Send + Sync + 'static, + H: Fn(UpdateWithCx<R, Message>, D) -> Fut + Send + Sync + 'static, D: Default + Send + 'static, Fut: Future<Output = DialogueStage<D>> + Send + 'static, + R: Requester + Send + Clone + 'static, + <R as Requester>::GetUpdatesFaultTolerant: Send, { - let cloned_bot = bot.clone(); + let cloned_requester = requester.clone(); - dialogues_repl_with_listener(bot, handler, update_listeners::polling_default(cloned_bot)).await; + dialogues_repl_with_listener( + requester, + handler, + update_listeners::polling_default(cloned_requester), + ) + .await; } /// Like [`dialogues_repl`], but with a custom [`UpdateListener`]. @@ -49,22 +55,23 @@ where /// [`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, +pub async fn dialogues_repl_with_listener<'a, R, H, D, Fut, L, ListenerE>( + requester: R, handler: H, listener: L, ) where - H: Fn(UpdateWithCx<Message>, D) -> Fut + Send + Sync + 'static, + H: Fn(UpdateWithCx<R, 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, + R: Requester + Send + Clone + 'static, { let handler = Arc::new(handler); - Dispatcher::new(bot) + Dispatcher::new(requester) .messages_handler(DialogueDispatcher::new( - move |DialogueWithCx { cx, dialogue }: DialogueWithCx<Message, D, Infallible>| { + move |DialogueWithCx { cx, dialogue }: DialogueWithCx<R, Message, D, Infallible>| { let handler = Arc::clone(&handler); async move { diff --git a/src/dispatching/repls/repl.rs b/src/dispatching/repls/repl.rs index 9a50355f..31075f60 100644 --- a/src/dispatching/repls/repl.rs +++ b/src/dispatching/repls/repl.rs @@ -4,11 +4,11 @@ use crate::{ UpdateWithCx, }, error_handlers::{LoggingErrorHandler, OnError}, - types::Message, - Bot, }; use futures::StreamExt; use std::{fmt::Debug, future::Future, sync::Arc}; +use teloxide_core::{requests::Requester, types::Message}; +use tokio_stream::wrappers::UnboundedReceiverStream; /// A [REPL] for messages. /// @@ -21,15 +21,18 @@ use std::{fmt::Debug, future::Future, sync::Arc}; /// /// [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) +pub async fn repl<R, H, Fut, E>(requester: R, handler: H) where - H: Fn(UpdateWithCx<Message>) -> Fut + Send + Sync + 'static, + H: Fn(UpdateWithCx<R, Message>) -> Fut + Send + Sync + 'static, Fut: Future<Output = Result<(), E>> + Send + 'static, Result<(), E>: OnError<E>, E: Debug + Send, + R: Requester + Send + Clone + 'static, + <R as Requester>::GetUpdatesFaultTolerant: Send, { - let cloned_bot = bot.clone(); - repl_with_listener(bot, handler, update_listeners::polling_default(cloned_bot)).await; + let cloned_requester = requester.clone(); + repl_with_listener(requester, handler, update_listeners::polling_default(cloned_requester)) + .await; } /// Like [`repl`], but with a custom [`UpdateListener`]. @@ -44,20 +47,24 @@ where /// [`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, +pub async fn repl_with_listener<'a, R, H, Fut, E, L, ListenerE>( + requester: R, + handler: H, + listener: L, +) where + H: Fn(UpdateWithCx<R, 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, + R: Requester + Clone + Send + 'static, { let handler = Arc::new(handler); - Dispatcher::new(bot) - .messages_handler(|rx: DispatcherHandlerRx<Message>| { - rx.for_each_concurrent(None, move |message| { + Dispatcher::new(requester) + .messages_handler(|rx: DispatcherHandlerRx<R, Message>| { + UnboundedReceiverStream::new(rx).for_each_concurrent(None, move |message| { let handler = Arc::clone(&handler); async move { diff --git a/src/dispatching/update_listeners.rs b/src/dispatching/update_listeners.rs index 05b30cc7..a3ec5097 100644 --- a/src/dispatching/update_listeners.rs +++ b/src/dispatching/update_listeners.rs @@ -97,7 +97,7 @@ //! [`UpdateListener`]: UpdateListener //! [`polling_default`]: polling_default //! [`polling`]: polling -//! [`Box::get_updates`]: crate::Bot::get_updates +//! [`Box::get_updates`]: crate::requests::Requester::get_updates //! [getting updates]: https://core.telegram.org/bots/api#getting-updates //! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling //! [short]: https://en.wikipedia.org/wiki/Polling_(computer_science) @@ -105,14 +105,11 @@ use futures::{stream, Stream, StreamExt}; -use crate::{ - bot::Bot, - requests::Request, - types::{AllowedUpdate, Update}, - RequestError, -}; - use std::{convert::TryInto, time::Duration}; +use teloxide_core::{ + requests::{HasPayload, Request, Requester}, + types::{AllowedUpdate, SemiparsedVec, Update}, +}; /// A generic update listener. pub trait UpdateListener<E>: Stream<Item = Result<Update, E>> { @@ -123,8 +120,12 @@ impl<S, E> UpdateListener<E> for S where S: Stream<Item = Result<Update, E>> {} /// Returns a long polling update listener with `timeout` of 10 seconds. /// /// See also: [`polling`](polling). -pub fn polling_default(bot: Bot) -> impl UpdateListener<RequestError> { - polling(bot, Some(Duration::from_secs(10)), None, None) +pub fn polling_default<R>(requester: R) -> impl UpdateListener<R::Err> +where + R: Requester, + <R as Requester>::GetUpdatesFaultTolerant: Send, +{ + polling(requester, Some(Duration::from_secs(10)), None, None) } /// Returns a long/short polling update listener with some additional options. @@ -138,26 +139,32 @@ pub fn polling_default(bot: Bot) -> impl UpdateListener<RequestError> { /// /// See also: [`polling_default`](polling_default). /// -/// [`GetUpdates`]: crate::requests::GetUpdates -pub fn polling( - bot: Bot, +/// [`GetUpdates`]: crate::payloads::GetUpdates +pub fn polling<R>( + requester: R, timeout: Option<Duration>, limit: Option<u8>, allowed_updates: Option<Vec<AllowedUpdate>>, -) -> impl UpdateListener<RequestError> { +) -> impl UpdateListener<R::Err> +where + R: Requester, + <R as Requester>::GetUpdatesFaultTolerant: Send, +{ let timeout = timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")); stream::unfold( - (allowed_updates, bot, 0), + (allowed_updates, requester, 0), move |(mut allowed_updates, bot, mut offset)| async move { - let mut req = bot.get_updates().offset(offset); - req.timeout = timeout; - req.limit = limit; - req.allowed_updates = allowed_updates.take(); + let mut req = bot.get_updates_fault_tolerant(); + let payload = &mut req.payload_mut().0; + payload.offset = Some(offset); + payload.timeout = timeout; + payload.limit = limit; + payload.allowed_updates = allowed_updates.take(); let updates = match req.send().await { Err(err) => vec![Err(err)], - Ok(updates) => { + Ok(SemiparsedVec(updates)) => { // Set offset to the last update's id + 1 if let Some(upd) = updates.last() { let id: i32 = match upd { @@ -172,10 +179,19 @@ pub fn polling( offset = id + 1; } - let updates = - updates.into_iter().filter_map(Result::ok).collect::<Vec<Update>>(); + for update in &updates { + if let Err((value, e)) = update { + log::error!( + "Cannot parse an update.\nError: {:?}\nValue: {}\n\ + This is a bug in teloxide-core, please open an issue here: \ + https://github.com/teloxide/teloxide-core/issues.", + e, + value + ); + } + } - updates.into_iter().map(Ok).collect::<Vec<_>>() + updates.into_iter().filter_map(Result::ok).map(Ok).collect::<Vec<_>>() } }; diff --git a/src/dispatching/update_with_cx.rs b/src/dispatching/update_with_cx.rs index 66c0beec..6ac78034 100644 --- a/src/dispatching/update_with_cx.rs +++ b/src/dispatching/update_with_cx.rs @@ -1,13 +1,8 @@ -use crate::{ - dispatching::dialogue::GetChatId, - requests::{ - DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage, PinChatMessage, - Request, ResponseResult, SendAnimation, SendAudio, SendContact, SendDice, SendDocument, - SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker, SendVenue, SendVideo, - SendVideoNote, SendVoice, - }, - types::{ChatId, ChatOrInlineMessage, InputFile, InputMedia, Message}, - Bot, +use crate::dispatching::dialogue::GetChatId; +use teloxide_core::{ + payloads::SendMessageSetters, + requests::{Request, Requester}, + types::{ChatId, InputFile, InputMedia, Message}, }; /// A [`Dispatcher`]'s handler's context of a bot and an update. @@ -17,12 +12,12 @@ use crate::{ /// /// [`Dispatcher`]: crate::dispatching::Dispatcher #[derive(Debug)] -pub struct UpdateWithCx<Upd> { - pub bot: Bot, +pub struct UpdateWithCx<R, Upd> { + pub requester: R, pub update: Upd, } -impl<Upd> GetChatId for UpdateWithCx<Upd> +impl<Upd, R> GetChatId for UpdateWithCx<R, Upd> where Upd: GetChatId, { @@ -31,130 +26,136 @@ where } } -impl UpdateWithCx<Message> { +#[doc(hidden)] +// Now it is used only inside `#[teloxide(subtransition)]` for type inference. +pub trait UpdateWithCxRequesterType { + type Requester; +} + +impl<R, Upd> UpdateWithCxRequesterType for UpdateWithCx<R, Upd> { + type Requester = R; +} + +impl<R> UpdateWithCx<R, Message> +where + R: Requester, +{ /// A shortcut for `.answer(text).send().await`. - pub async fn answer_str<T>(&self, text: T) -> ResponseResult<Message> + #[deprecated(note = "Use .answer(text).await instead")] + pub async fn answer_str<T>(&self, text: T) -> Result<Message, R::Err> where T: Into<String>, + R::SendMessage: std::future::Future, { self.answer(text).send().await } - pub fn answer<T>(&self, text: T) -> SendMessage + pub fn answer<T>(&self, text: T) -> R::SendMessage where T: Into<String>, { - self.bot.send_message(self.chat_id(), text) + self.requester.send_message(self.chat_id(), text) } - pub fn reply_to<T>(&self, text: T) -> SendMessage + pub fn reply_to<T>(&self, text: T) -> R::SendMessage where T: Into<String>, { - self.bot.send_message(self.chat_id(), text).reply_to_message_id(self.update.id) + self.requester.send_message(self.chat_id(), text).reply_to_message_id(self.update.id) } - pub fn answer_photo(&self, photo: InputFile) -> SendPhoto { - self.bot.send_photo(self.update.chat.id, photo) + pub fn answer_photo(&self, photo: InputFile) -> R::SendPhoto { + self.requester.send_photo(self.update.chat.id, photo) } - pub fn answer_audio(&self, audio: InputFile) -> SendAudio { - self.bot.send_audio(self.update.chat.id, audio) + pub fn answer_audio(&self, audio: InputFile) -> R::SendAudio { + self.requester.send_audio(self.update.chat.id, audio) } - pub fn answer_animation(&self, animation: InputFile) -> SendAnimation { - self.bot.send_animation(self.update.chat.id, animation) + pub fn answer_animation(&self, animation: InputFile) -> R::SendAnimation { + self.requester.send_animation(self.update.chat.id, animation) } - pub fn answer_document(&self, document: InputFile) -> SendDocument { - self.bot.send_document(self.update.chat.id, document) + pub fn answer_document(&self, document: InputFile) -> R::SendDocument { + self.requester.send_document(self.update.chat.id, document) } - pub fn answer_video(&self, video: InputFile) -> SendVideo { - self.bot.send_video(self.update.chat.id, video) + pub fn answer_video(&self, video: InputFile) -> R::SendVideo { + self.requester.send_video(self.update.chat.id, video) } - pub fn answer_voice(&self, voice: InputFile) -> SendVoice { - self.bot.send_voice(self.update.chat.id, voice) + pub fn answer_voice(&self, voice: InputFile) -> R::SendVoice { + self.requester.send_voice(self.update.chat.id, voice) } - pub fn answer_media_group<T>(&self, media_group: T) -> SendMediaGroup + pub fn answer_media_group<T>(&self, media_group: T) -> R::SendMediaGroup where - T: Into<Vec<InputMedia>>, + T: IntoIterator<Item = InputMedia>, { - self.bot.send_media_group(self.update.chat.id, media_group) + self.requester.send_media_group(self.update.chat.id, media_group) } - pub fn answer_location(&self, latitude: f32, longitude: f32) -> SendLocation { - self.bot.send_location(self.update.chat.id, latitude, longitude) + pub fn answer_location(&self, latitude: f64, longitude: f64) -> R::SendLocation { + self.requester.send_location(self.update.chat.id, latitude, longitude) } pub fn answer_venue<T, U>( &self, - latitude: f32, - longitude: f32, + latitude: f64, + longitude: f64, title: T, address: U, - ) -> SendVenue + ) -> R::SendVenue where T: Into<String>, U: Into<String>, { - self.bot.send_venue(self.update.chat.id, latitude, longitude, title, address) + self.requester.send_venue(self.update.chat.id, latitude, longitude, title, address) } - pub fn answer_video_note(&self, video_note: InputFile) -> SendVideoNote { - self.bot.send_video_note(self.update.chat.id, video_note) + pub fn answer_video_note(&self, video_note: InputFile) -> R::SendVideoNote { + self.requester.send_video_note(self.update.chat.id, video_note) } - pub fn answer_contact<T, U>(&self, phone_number: T, first_name: U) -> SendContact + pub fn answer_contact<T, U>(&self, phone_number: T, first_name: U) -> R::SendContact where T: Into<String>, U: Into<String>, { - self.bot.send_contact(self.chat_id(), phone_number, first_name) + self.requester.send_contact(self.chat_id(), phone_number, first_name) } - pub fn answer_sticker(&self, sticker: InputFile) -> SendSticker { - self.bot.send_sticker(self.update.chat.id, sticker) + pub fn answer_sticker(&self, sticker: InputFile) -> R::SendSticker { + self.requester.send_sticker(self.update.chat.id, sticker) } - pub fn forward_to<T>(&self, chat_id: T) -> ForwardMessage + pub fn forward_to<T>(&self, chat_id: T) -> R::ForwardMessage where T: Into<ChatId>, { - self.bot.forward_message(chat_id, self.update.chat.id, self.update.id) + self.requester.forward_message(chat_id, self.update.chat.id, self.update.id) } - pub fn edit_message_text<T>(&self, text: T) -> EditMessageText + pub fn edit_message_text<T>(&self, text: T) -> R::EditMessageText where T: Into<String>, { - self.bot.edit_message_text( - ChatOrInlineMessage::Chat { - chat_id: self.update.chat.id.into(), - message_id: self.update.id, - }, - text, - ) + self.requester.edit_message_text(self.update.chat.id, self.update.id, text) } - pub fn edit_message_caption(&self) -> EditMessageCaption { - self.bot.edit_message_caption(ChatOrInlineMessage::Chat { - chat_id: self.update.chat.id.into(), - message_id: self.update.id, - }) + pub fn edit_message_caption(&self) -> R::EditMessageCaption { + self.requester.edit_message_caption(self.update.chat.id, self.update.id) } - pub fn delete_message(&self) -> DeleteMessage { - self.bot.delete_message(self.update.chat.id, self.update.id) + pub fn delete_message(&self) -> R::DeleteMessage { + self.requester.delete_message(self.update.chat.id, self.update.id) } - pub fn pin_message(&self) -> PinChatMessage { - self.bot.pin_chat_message(self.update.chat.id, self.update.id) + pub fn pin_message(&self) -> R::PinChatMessage { + self.requester.pin_chat_message(self.update.chat.id, self.update.id) } - pub fn answer_dice(&self) -> SendDice { - self.bot.send_dice(self.update.chat.id) + pub fn answer_dice(&self) -> R::SendDice { + self.requester.send_dice(self.update.chat.id) } } diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index 466cc501..00000000 --- a/src/errors.rs +++ /dev/null @@ -1,515 +0,0 @@ -use derive_more::From; -use reqwest::StatusCode; -use serde::Deserialize; -use thiserror::Error; - -/// An error caused by downloading a file. -#[derive(Debug, Error, From)] -pub enum DownloadError { - #[error("A network error: {0}")] - NetworkError(#[source] reqwest::Error), - - #[error("An I/O error: {0}")] - Io(#[source] std::io::Error), -} - -/// An error caused by sending a request to Telegram. -#[derive(Debug, Error)] -pub enum RequestError { - #[error("A Telegram's error #{status_code}: {kind:?}")] - ApiError { status_code: StatusCode, kind: ApiErrorKind }, - - /// The group has been migrated to a supergroup with the specified - /// identifier. - #[error("The group has been migrated to a supergroup with ID #{0}")] - MigrateToChatId(i64), - - /// In case of exceeding flood control, the number of seconds left to wait - /// before the request can be repeated. - #[error("Retry after {0} seconds")] - RetryAfter(i32), - - #[error("A network error: {0}")] - NetworkError(#[source] reqwest::Error), - - #[error("An error while parsing JSON: {0}")] - InvalidJson(#[source] serde_json::Error), -} - -/// A kind of an API error. -/// -/// If you receive [`ApiErrorKind::Unknown`], please [open an issue] with -/// the description of the error. -/// -/// [`ApiErrorKind::Unknown`]: crate::ApiErrorKind::Unknown -/// [open an issue]: https://github.com/teloxide/teloxide/issues/new -#[derive(Debug, Deserialize, PartialEq, Hash, Eq, Clone)] -#[serde(untagged)] -pub enum ApiErrorKind { - Known(KnownApiErrorKind), - Unknown(String), -} - -/// A kind of a known API error. -#[derive(Debug, Deserialize, PartialEq, Copy, Hash, Eq, Clone)] -pub enum KnownApiErrorKind { - /// Occurs when the bot tries to send message to user who blocked the bot. - #[serde(rename = "Forbidden: bot was blocked by the user")] - BotBlocked, - - /// Occurs when bot tries to modify a message without modification content. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message is not modified: specified new message content and \ - reply markup are exactly the same as a current content and reply markup \ - of the message")] - MessageNotModified, - - /// Occurs when bot tries to forward or delete a message which was deleted. - /// - /// May happen in methods: - /// 1. [`ForwardMessage`] - /// 2. [`DeleteMessage`] - /// - /// [`ForwardMessage`]: crate::requests::ForwardMessage - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: MESSAGE_ID_INVALID")] - MessageIdInvalid, - - /// Occurs when bot tries to forward a message which does not exists. - /// - /// May happen in methods: - /// 1. [`ForwardMessage`] - /// - /// [`ForwardMessage`]: crate::requests::ForwardMessage - #[serde(rename = "Bad Request: message to forward not found")] - MessageToForwardNotFound, - - /// Occurs when bot tries to delete a message which does not exists. - /// - /// May happen in methods: - /// 1. [`DeleteMessage`] - /// - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: message to delete not found")] - MessageToDeleteNotFound, - - /// Occurs when bot tries to send a text message without text. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: message text is empty")] - MessageTextIsEmpty, - - /// Occurs when bot tries to edit a message after long time. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message can't be edited")] - MessageCantBeEdited, - - /// Occurs when bot tries to delete a someone else's message in group where - /// it does not have enough rights. - /// - /// May happen in methods: - /// 1. [`DeleteMessage`] - /// - /// [`DeleteMessage`]: crate::requests::DeleteMessage - #[serde(rename = "Bad Request: message can't be deleted")] - MessageCantBeDeleted, - - /// Occurs when bot tries to edit a message which does not exists. - /// - /// May happen in methods: - /// 1. [`EditMessageText`] - /// - /// [`EditMessageText`]: crate::requests::EditMessageText - #[serde(rename = "Bad Request: message to edit not found")] - MessageToEditNotFound, - - /// Occurs when bot tries to reply to a message which does not exists. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: reply message not found")] - MessageToReplyNotFound, - - /// Occurs when bot tries to - #[serde(rename = "Bad Request: message identifier is not specified")] - MessageIdentifierNotSpecified, - - /// Occurs when bot tries to send a message with text size greater then - /// 4096 symbols. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: message is too long")] - MessageIsTooLong, - - /// Occurs when bot tries to send media group with more than 10 items. - /// - /// May happen in methods: - /// 1. [`SendMediaGroup`] - /// - /// [`SendMediaGroup`]: crate::requests::SendMediaGroup - #[serde(rename = "Bad Request: Too much messages to send as an album")] - ToMuchMessages, - - /// Occurs when bot tries to stop poll that has already been stopped. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll has already been closed")] - PollHasAlreadyClosed, - - /// Occurs when bot tries to send poll with less than 2 options. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll must have at least 2 option")] - PollMustHaveMoreOptions, - - /// Occurs when bot tries to send poll with more than 10 options. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll can't have more than 10 options")] - PollCantHaveMoreOptions, - - /// Occurs when bot tries to send poll with empty option (without text). - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll options must be non-empty")] - PollOptionsMustBeNonEmpty, - - /// Occurs when bot tries to send poll with empty question (without text). - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll question must be non-empty")] - PollQuestionMustBeNonEmpty, - - /// Occurs when bot tries to send poll with total size of options more than - /// 100 symbols. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll options length must not exceed 100")] - PollOptionsLengthTooLong, - - /// Occurs when bot tries to send poll with question size more than 255 - /// symbols. - /// - /// May happen in methods: - /// 1. [`SendPoll`] - /// - /// [`SendPoll`]: crate::requests::SendPoll - #[serde(rename = "Bad Request: poll question length must not exceed 255")] - PollQuestionLengthTooLong, - - /// Occurs when bot tries to stop poll with message without poll. - /// - /// May happen in methods: - /// 1. [`StopPoll`] - /// - /// [`StopPoll`]: crate::requests::StopPoll - #[serde(rename = "Bad Request: message with poll to stop not found")] - MessageWithPollNotFound, - - /// Occurs when bot tries to stop poll with message without poll. - /// - /// May happen in methods: - /// 1. [`StopPoll`] - /// - /// [`StopPoll`]: crate::requests::StopPoll - #[serde(rename = "Bad Request: message is not a poll")] - MessageIsNotAPoll, - - /// Occurs when bot tries to send a message to chat in which it is not a - /// member. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: chat not found")] - ChatNotFound, - - /// Occurs when bot tries to send method with unknown user_id. - /// - /// May happen in methods: - /// 1. [`getUserProfilePhotos`] - /// - /// [`getUserProfilePhotos`]: - /// crate::requests::GetUserProfilePhotos - #[serde(rename = "Bad Request: user not found")] - UserNotFound, - - /// Occurs when bot tries to send [`SetChatDescription`] with same text as - /// in the current description. - /// - /// May happen in methods: - /// 1. [`SetChatDescription`] - /// - /// [`SetChatDescription`]: crate::requests::SetChatDescription - #[serde(rename = "Bad Request: chat description is not modified")] - ChatDescriptionIsNotModified, - - /// Occurs when bot tries to answer to query after timeout expire. - /// - /// May happen in methods: - /// 1. [`AnswerCallbackQuery`] - /// - /// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery - #[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \ - invalid")] - InvalidQueryID, - - /// Occurs when bot tries to send InlineKeyboardMarkup with invalid button - /// url. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: BUTTON_URL_INVALID")] - ButtonURLInvalid, - - /// Occurs when bot tries to send button with data size more than 64 bytes. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: BUTTON_DATA_INVALID")] - ButtonDataInvalid, - - /// Occurs when bot tries to send button with data size == 0. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \ - unallowed in the inline keyboard")] - TextButtonsAreUnallowed, - - /// Occurs when bot tries to get file by wrong file id. - /// - /// May happen in methods: - /// 1. [`GetFile`] - /// - /// [`GetFile`]: crate::requests::GetFile - #[serde(rename = "Bad Request: wrong file id")] - WrongFileID, - - /// Occurs when bot tries to do some with group which was deactivated. - #[serde(rename = "Bad Request: group is deactivated")] - GroupDeactivated, - - /// Occurs when bot tries to set chat photo from file ID - /// - /// May happen in methods: - /// 1. [`SetChatPhoto`] - /// - /// [`SetChatPhoto`]: crate::requests::SetChatPhoto - #[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")] - PhotoAsInputFileRequired, - - /// Occurs when bot tries to add sticker to stickerset by invalid name. - /// - /// May happen in methods: - /// 1. [`AddStickerToSet`] - /// - /// [`AddStickerToSet`]: crate::requests::AddStickerToSet - #[serde(rename = "Bad Request: STICKERSET_INVALID")] - InvalidStickersSet, - - /// Occurs when bot tries to pin a message without rights to pin in this - /// chat. - /// - /// May happen in methods: - /// 1. [`PinChatMessage`] - /// - /// [`PinChatMessage`]: crate::requests::PinChatMessage - #[serde(rename = "Bad Request: not enough rights to pin a message")] - NotEnoughRightsToPinMessage, - - /// Occurs when bot tries to use method in group which is allowed only in a - /// supergroup or channel. - #[serde(rename = "Bad Request: method is available only for supergroups and channel")] - MethodNotAvailableInPrivateChats, - - /// Occurs when bot tries to demote chat creator. - /// - /// May happen in methods: - /// 1. [`PromoteChatMember`] - /// - /// [`PromoteChatMember`]: crate::requests::PromoteChatMember - #[serde(rename = "Bad Request: can't demote chat creator")] - CantDemoteChatCreator, - - /// Occurs when bot tries to restrict self in group chats. - /// - /// May happen in methods: - /// 1. [`RestrictChatMember`] - /// - /// [`RestrictChatMember`]: crate::requests::RestrictChatMember - #[serde(rename = "Bad Request: can't restrict self")] - CantRestrictSelf, - - /// Occurs when bot tries to restrict chat member without rights to - /// restrict in this chat. - /// - /// May happen in methods: - /// 1. [`RestrictChatMember`] - /// - /// [`RestrictChatMember`]: crate::requests::RestrictChatMember - #[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")] - NotEnoughRightsToRestrict, - - /// Occurs when bot tries set webhook to protocol other than HTTPS. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")] - WebhookRequireHTTPS, - - /// Occurs when bot tries to set webhook to port other than 80, 88, 443 or - /// 8443. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \ - or 8443")] - BadWebhookPort, - - /// Occurs when bot tries to set webhook to unknown host. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")] - UnknownHost, - - /// Occurs when bot tries to set webhook to invalid URL. - /// - /// May happen in methods: - /// 1. [`SetWebhook`] - /// - /// [`SetWebhook`]: crate::requests::SetWebhook - #[serde(rename = "Bad Request: can't parse URL")] - CantParseUrl, - - /// Occurs when bot tries to send message with unfinished entities. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: can't parse entities")] - CantParseEntities, - - /// Occurs when bot tries to use getUpdates while webhook is active. - /// - /// May happen in methods: - /// 1. [`GetUpdates`] - /// - /// [`GetUpdates`]: crate::requests::GetUpdates - #[serde(rename = "can't use getUpdates method while webhook is active")] - CantGetUpdates, - - /// Occurs when bot tries to do some in group where bot was kicked. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot was kicked from a chat")] - BotKicked, - - /// Occurs when bot tries to send message to deactivated user. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: user is deactivated")] - UserDeactivated, - - /// Occurs when you tries to initiate conversation with a user. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot can't initiate conversation with a user")] - CantInitiateConversation, - - /// Occurs when you tries to send message to bot. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Unauthorized: bot can't send messages to bots")] - CantTalkWithBots, - - /// Occurs when bot tries to send button with invalid http url. - /// - /// May happen in methods: - /// 1. [`SendMessage`] - /// - /// [`SendMessage`]: crate::requests::SendMessage - #[serde(rename = "Bad Request: wrong HTTP URL")] - WrongHTTPurl, - - /// Occurs when bot tries GetUpdate before the timeout. Make sure that only - /// one Updater is running. - /// - /// May happen in methods: - /// 1. [`GetUpdates`] - /// - /// [`GetUpdates`]: crate::requests::GetUpdates - #[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \ - bot instance is running")] - TerminatedByOtherGetUpdates, - - /// Occurs when bot tries to get file by invalid file id. - /// - /// May happen in methods: - /// 1. [`GetFile`] - /// - /// [`GetFile`]: crate::requests::GetFile - #[serde(rename = "Bad Request: invalid file id")] - FileIdInvalid, -} diff --git a/src/lib.rs b/src/lib.rs index c3116be2..5ecb64cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,11 +13,11 @@ //! teloxide::enable_logging!(); //! log::info!("Starting dices_bot..."); //! -//! let bot = Bot::from_env(); +//! let bot = Bot::from_env().auto_send(); //! //! teloxide::repl(bot, |message| async move { -//! message.answer_dice().send().await?; -//! ResponseResult::<()>::Ok(()) +//! message.answer_dice().await?; +//! respond(()) //! }) //! .await; //! # } @@ -40,24 +40,45 @@ )] #![allow(clippy::match_bool)] #![forbid(unsafe_code)] +#![cfg_attr(all(feature = "nightly", doctest), feature(external_doc))] +// we pass "--cfg docsrs" when building docs to add `This is supported on feature="..." only.` +// +// To properly build docs of this crate run +// ```console +// $ RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --open --all-features +// ``` +#![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))] -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}; -mod errors; -mod net; +mod logging; -mod bot; pub mod dispatching; pub mod error_handlers; -mod logging; pub mod prelude; -pub mod requests; -pub mod types; pub mod utils; -extern crate teloxide_macros; +#[doc(inline)] +pub use teloxide_core::*; + +#[cfg(feature = "macros")] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +pub use teloxide_macros as macros; + +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +#[cfg(feature = "macros")] +pub use teloxide_macros::teloxide; + +#[cfg(all(feature = "nightly", doctest))] +#[doc(include = "../README.md")] +enum ReadmeDocTests {} + +use teloxide_core::requests::ResponseResult; + +/// A shortcut for `ResponseResult::Ok(val)`. +pub fn respond<T>(val: T) -> ResponseResult<T> { + ResponseResult::Ok(val) +} diff --git a/src/net/download.rs b/src/net/download.rs deleted file mode 100644 index 05f08f5d..00000000 --- a/src/net/download.rs +++ /dev/null @@ -1,49 +0,0 @@ -use reqwest::Client; -use tokio::io::{AsyncWrite, AsyncWriteExt}; - -use crate::errors::DownloadError; - -use super::TELEGRAM_API_URL; - -pub async fn download_file<D>( - client: &Client, - token: &str, - path: &str, - destination: &mut D, -) -> Result<(), DownloadError> -where - D: AsyncWrite + Unpin, -{ - let mut res = client - .get(&super::file_url(TELEGRAM_API_URL, token, path)) - .send() - .await? - .error_for_status()?; - - while let Some(chunk) = res.chunk().await? { - destination.write_all(&chunk).await?; - } - - Ok(()) -} - -#[cfg(feature = "unstable-stream")] -pub async fn download_file_stream( - client: &Client, - token: &str, - path: &str, -) -> Result<impl Stream<Item = reqwest::Result<Bytes>>, reqwest::Error> { - let res = client - .get(&super::file_url(TELEGRAM_API_URL, token, path)) - .send() - .await? - .error_for_status()?; - - Ok(futures::stream::unfold(res, |mut res| async { - match res.chunk().await { - Err(err) => Some((Err(err), res)), - Ok(Some(c)) => Some((Ok(c), res)), - Ok(None) => None, - } - })) -} diff --git a/src/net/mod.rs b/src/net/mod.rs deleted file mode 100644 index b4944959..00000000 --- a/src/net/mod.rs +++ /dev/null @@ -1,61 +0,0 @@ -#[cfg(feature = "unstable-stream")] -pub use download::download_file_stream; - -pub use self::{ - download::download_file, - request::{request_json, request_multipart}, - telegram_response::TelegramResponse, -}; - -mod download; -mod request; -mod telegram_response; - -const TELEGRAM_API_URL: &str = "https://api.telegram.org"; - -/// Creates URL for making HTTPS requests. See the [Telegram documentation]. -/// -/// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests -fn method_url(base: &str, token: &str, method_name: &str) -> String { - format!("{url}/bot{token}/{method}", url = base, token = token, method = method_name,) -} - -/// Creates URL for downloading a file. See the [Telegram documentation]. -/// -/// [Telegram documentation]: https://core.telegram.org/bots/api#file -fn file_url(base: &str, token: &str, file_path: &str) -> String { - format!("{url}/file/bot{token}/{file}", url = base, token = token, file = file_path,) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn method_url_test() { - let url = method_url( - TELEGRAM_API_URL, - "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", - "methodName", - ); - - assert_eq!( - url, - "https://api.telegram.org/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/methodName" - ); - } - - #[test] - fn file_url_test() { - let url = file_url( - TELEGRAM_API_URL, - "535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao", - "AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ", - ); - - assert_eq!( - url, - "https://api.telegram.org/file/bot535362388:AAF7-g0gYncWnm5IyfZlpPRqRRv6kNAGlao/AgADAgADyqoxG2g8aEsu_KjjVsGF4-zetw8ABAEAAwIAA20AA_8QAwABFgQ" - ); - } -} diff --git a/src/net/request.rs b/src/net/request.rs deleted file mode 100644 index e376c3fd..00000000 --- a/src/net/request.rs +++ /dev/null @@ -1,63 +0,0 @@ -use reqwest::{multipart::Form, Client, Response}; -use serde::{de::DeserializeOwned, Serialize}; - -use crate::{requests::ResponseResult, RequestError}; - -use super::{TelegramResponse, TELEGRAM_API_URL}; -use std::time::Duration; - -const DELAY_ON_SERVER_ERROR: Duration = Duration::from_secs(10); - -pub async fn request_multipart<T>( - client: &Client, - token: &str, - method_name: &str, - params: Form, -) -> ResponseResult<T> -where - T: DeserializeOwned, -{ - let response = client - .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) - .multipart(params) - .send() - .await - .map_err(RequestError::NetworkError)?; - - process_response(response).await -} - -pub async fn request_json<T, P>( - client: &Client, - token: &str, - method_name: &str, - params: &P, -) -> ResponseResult<T> -where - T: DeserializeOwned, - P: Serialize, -{ - let response = client - .post(&super::method_url(TELEGRAM_API_URL, token, method_name)) - .json(params) - .send() - .await - .map_err(RequestError::NetworkError)?; - - process_response(response).await -} - -async fn process_response<T>(response: Response) -> ResponseResult<T> -where - T: DeserializeOwned, -{ - if response.status().is_server_error() { - tokio::time::delay_for(DELAY_ON_SERVER_ERROR).await; - } - - serde_json::from_str::<TelegramResponse<T>>( - &response.text().await.map_err(RequestError::NetworkError)?, - ) - .map_err(RequestError::InvalidJson)? - .into() -} diff --git a/src/net/telegram_response.rs b/src/net/telegram_response.rs deleted file mode 100644 index d5062e8f..00000000 --- a/src/net/telegram_response.rs +++ /dev/null @@ -1,70 +0,0 @@ -use reqwest::StatusCode; -use serde::Deserialize; - -use crate::{ - requests::ResponseResult, - types::{False, ResponseParameters, True}, - ApiErrorKind, RequestError, -}; - -#[derive(Deserialize)] -#[serde(untagged)] -pub enum TelegramResponse<R> { - Ok { - /// A dummy field. Used only for deserialization. - #[allow(dead_code)] - ok: True, - - result: R, - }, - Err { - /// A dummy field. Used only for deserialization. - #[allow(dead_code)] - ok: False, - - #[serde(rename = "description")] - kind: ApiErrorKind, - error_code: u16, - response_parameters: Option<ResponseParameters>, - }, -} - -impl<R> Into<ResponseResult<R>> for TelegramResponse<R> { - fn into(self) -> Result<R, RequestError> { - match self { - TelegramResponse::Ok { result, .. } => Ok(result), - TelegramResponse::Err { kind, error_code, response_parameters, .. } => { - if let Some(params) = response_parameters { - match params { - ResponseParameters::RetryAfter(i) => Err(RequestError::RetryAfter(i)), - ResponseParameters::MigrateToChatId(to) => { - Err(RequestError::MigrateToChatId(to)) - } - } - } else { - Err(RequestError::ApiError { - kind, - status_code: StatusCode::from_u16(error_code).unwrap(), - }) - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{errors::KnownApiErrorKind, types::Update}; - - #[test] - fn terminated_by_other_get_updates() { - let expected = ApiErrorKind::Known(KnownApiErrorKind::TerminatedByOtherGetUpdates); - if let TelegramResponse::Err{ kind, .. } = serde_json::from_str::<TelegramResponse<Update>>(r#"{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}"#).unwrap() { - assert_eq!(expected, kind); - } - else { - panic!("Expected ApiErrorKind::TerminatedByOtherGetUpdates"); - } - } -} diff --git a/src/prelude.rs b/src/prelude.rs index 3b2032d9..b7f9a040 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,12 +9,26 @@ pub use crate::{ Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx, }, error_handlers::{LoggingErrorHandler, OnError}, - requests::{Request, ResponseResult}, - types::{Message, Update}, - Bot, RequestError, + respond, }; +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] +#[cfg(feature = "macros")] +pub use crate::teloxide; + +pub use teloxide_core::types::{ + CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, + PreCheckoutQuery, ShippingQuery, +}; + +#[cfg(feature = "auto-send")] +pub use crate::adaptors::AutoSend; + +#[doc(no_inline)] +pub use teloxide_core::prelude::*; + #[cfg(feature = "frunk")] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "frunk")))] pub use crate::utils::UpState; pub use tokio::sync::mpsc::UnboundedReceiver; diff --git a/src/requests/all/add_sticker_to_set.rs b/src/requests/all/add_sticker_to_set.rs deleted file mode 100644 index 8cc0188f..00000000 --- a/src/requests/all/add_sticker_to_set.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::{ - net, - requests::form_builder::FormBuilder, - types::{MaskPosition, True}, - Bot, -}; - -use crate::{ - requests::{RequestWithFile, ResponseResult}, - types::StickerType, -}; - -/// Use this method to add a new sticker to a set created by the bot. -/// -/// [The official docs](https://core.telegram.org/bots/api#addstickertoset). -#[derive(Debug, Clone)] -pub struct AddStickerToSet { - bot: Bot, - user_id: i32, - name: String, - sticker_type: StickerType, - emojis: String, - mask_position: Option<MaskPosition>, -} - -#[async_trait::async_trait] -impl RequestWithFile for AddStickerToSet { - type Output = True; - - async fn send(&self) -> tokio::io::Result<ResponseResult<True>> { - let builder = - FormBuilder::new().add_text("user_id", &self.user_id).add_text("name", &self.name); - - let builder = match &self.sticker_type { - StickerType::Png(file) => builder.add_input_file("png_sticker", &file), - StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file), - } - .await? - .add_text("emojis", &self.emojis) - .add_text("mask_position", &self.mask_position); - - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "addStickerToSet", - builder.build(), - ) - .await) - } -} - -impl AddStickerToSet { - pub(crate) fn new<N, E>( - bot: Bot, - user_id: i32, - name: N, - sticker_type: StickerType, - emojis: E, - ) -> Self - where - N: Into<String>, - E: Into<String>, - { - Self { - bot, - user_id, - name: name.into(), - sticker_type, - emojis: emojis.into(), - mask_position: None, - } - } - - /// User identifier of sticker set owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Sticker set name. - pub fn name<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.name = val.into(); - self - } - - pub fn sticker_type(mut self, val: StickerType) -> Self { - self.sticker_type = val; - self - } - - /// One or more emoji corresponding to the sticker. - pub fn emojis<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.emojis = val.into(); - self - } - - /// A JSON-serialized object for position where the mask should be placed on - /// faces. - pub fn mask_position(mut self, val: MaskPosition) -> Self { - self.mask_position = Some(val); - self - } -} diff --git a/src/requests/all/answer_callback_query.rs b/src/requests/all/answer_callback_query.rs deleted file mode 100644 index c74b1e88..00000000 --- a/src/requests/all/answer_callback_query.rs +++ /dev/null @@ -1,101 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::True, - Bot, -}; - -/// Use this method to send answers to callback queries sent from [inline -/// keyboards]. -/// -/// The answer will be displayed to the user as a notification at -/// the top of the chat screen or as an alert. -/// -/// [The official docs](https://core.telegram.org/bots/api#answercallbackquery). -/// -/// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerCallbackQuery { - #[serde(skip_serializing)] - bot: Bot, - callback_query_id: String, - text: Option<String>, - show_alert: Option<bool>, - url: Option<String>, - cache_time: Option<i32>, -} - -#[async_trait::async_trait] -impl Request for AnswerCallbackQuery { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "answerCallbackQuery", &self).await - } -} - -impl AnswerCallbackQuery { - pub(crate) fn new<C>(bot: Bot, callback_query_id: C) -> Self - where - C: Into<String>, - { - let callback_query_id = callback_query_id.into(); - Self { bot, callback_query_id, text: None, show_alert: None, url: None, cache_time: None } - } - - /// Unique identifier for the query to be answered. - pub fn callback_query_id<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.callback_query_id = val.into(); - self - } - - /// Text of the notification. If not specified, nothing will be shown to the - /// user, 0-200 characters. - pub fn text<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.text = Some(val.into()); - self - } - - /// If `true`, an alert will be shown by the client instead of a - /// notification at the top of the chat screen. Defaults to `false`. - pub fn show_alert(mut self, val: bool) -> Self { - self.show_alert = Some(val); - self - } - - /// URL that will be opened by the user's client. If you have created a - /// [`Game`] and accepted the conditions via [@Botfather], specify the - /// URL that opens your game – note that this will only work if the - /// query comes from a [`callback_game`] button. - /// - /// Otherwise, you may use links like `t.me/your_bot?start=XXXX` that open - /// your bot with a parameter. - /// - /// [@Botfather]: https://t.me/botfather - /// [`callback_game`]: crate::types::InlineKeyboardButton - /// [`Game`]: crate::types::Game - pub fn url<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.url = Some(val.into()); - self - } - - /// The maximum amount of time in seconds that the result of the callback - /// query may be cached client-side. Telegram apps will support caching - /// starting in version 3.14. Defaults to 0. - pub fn cache_time(mut self, val: i32) -> Self { - self.cache_time = Some(val); - self - } -} diff --git a/src/requests/all/answer_inline_query.rs b/src/requests/all/answer_inline_query.rs deleted file mode 100644 index dd1b6a38..00000000 --- a/src/requests/all/answer_inline_query.rs +++ /dev/null @@ -1,146 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineQueryResult, True}, - Bot, -}; - -/// Use this method to send answers to an inline query. -/// -/// No more than **50** results per query are allowed. -/// -/// [The official docs](https://core.telegram.org/bots/api#answerinlinequery). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerInlineQuery { - #[serde(skip_serializing)] - bot: Bot, - inline_query_id: String, - results: Vec<InlineQueryResult>, - cache_time: Option<i32>, - is_personal: Option<bool>, - next_offset: Option<String>, - switch_pm_text: Option<String>, - switch_pm_parameter: Option<String>, -} - -#[async_trait::async_trait] -impl Request for AnswerInlineQuery { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "answerInlineQuery", &self).await - } -} - -impl AnswerInlineQuery { - pub(crate) fn new<I, R>(bot: Bot, inline_query_id: I, results: R) -> Self - where - I: Into<String>, - R: Into<Vec<InlineQueryResult>>, - { - let inline_query_id = inline_query_id.into(); - let results = results.into(); - Self { - bot, - inline_query_id, - results, - cache_time: None, - is_personal: None, - next_offset: None, - switch_pm_text: None, - switch_pm_parameter: None, - } - } - - /// Unique identifier for the answered query. - pub fn inline_query_id<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.inline_query_id = val.into(); - self - } - - /// A JSON-serialized array of results for the inline query. - pub fn results<T>(mut self, val: T) -> Self - where - T: Into<Vec<InlineQueryResult>>, - { - self.results = val.into(); - self - } - - /// The maximum amount of time in seconds that the result of the inline - /// query may be cached on the server. - /// - /// Defaults to 300. - pub fn cache_time(mut self, val: i32) -> Self { - self.cache_time = Some(val); - self - } - - /// Pass `true`, if results may be cached on the server side only for the - /// user that sent the query. - /// - /// By default, results may be returned to any user who sends the same - /// query. - #[allow(clippy::wrong_self_convention)] - pub fn is_personal(mut self, val: bool) -> Self { - self.is_personal = Some(val); - self - } - - /// Pass the offset that a client should send in the next query with the - /// same text to receive more results. - /// - /// Pass an empty string if there are no more results or if you don‘t - /// support pagination. Offset length can’t exceed 64 bytes. - pub fn next_offset<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.next_offset = Some(val.into()); - self - } - - /// If passed, clients will display a button with specified text that - /// switches the user to a private chat with the bot and sends the bot a - /// start message with the parameter [`switch_pm_parameter`]. - /// - /// [`switch_pm_parameter`]: - /// crate::requests::AnswerInlineQuery::switch_pm_parameter - pub fn switch_pm_text<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.switch_pm_text = Some(val.into()); - self - } - - /// [Deep-linking] parameter for the /start message sent to the bot when - /// user presses the switch button. 1-64 characters, only `A-Z`, `a-z`, - /// `0-9`, `_` and `-` are allowed. - /// - /// Example: An inline bot that sends YouTube videos can ask the user to - /// connect the bot to their YouTube account to adapt search results - /// accordingly. To do this, it displays a ‘Connect your YouTube account’ - /// button above the results, or even before showing any. The user presses - /// the button, switches to a private chat with the bot and, in doing so, - /// passes a start parameter that instructs the bot to return an oauth link. - /// Once done, the bot can offer a [`switch_inline`] button so that the user - /// can easily return to the chat where they wanted to use the bot's - /// inline capabilities. - /// - /// [Deep-linking]: https://core.telegram.org/bots#deep-linking - /// [`switch_inline`]: crate::types::InlineKeyboardMarkup - pub fn switch_pm_parameter<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.switch_pm_parameter = Some(val.into()); - self - } -} diff --git a/src/requests/all/answer_pre_checkout_query.rs b/src/requests/all/answer_pre_checkout_query.rs deleted file mode 100644 index 2ac4df42..00000000 --- a/src/requests/all/answer_pre_checkout_query.rs +++ /dev/null @@ -1,82 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::True, - Bot, -}; - -/// Once the user has confirmed their payment and shipping details, the Bot API -/// sends the final confirmation in the form of an [`Update`] with the field -/// `pre_checkout_query`. Use this method to respond to such pre-checkout -/// queries. -/// -/// # Note -/// The Bot API must receive an answer within 10 seconds after the pre-checkout -/// query was sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery). -/// -/// [`Update`]: crate::types::Update -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerPreCheckoutQuery { - #[serde(skip_serializing)] - bot: Bot, - pre_checkout_query_id: String, - ok: bool, - error_message: Option<String>, -} - -#[async_trait::async_trait] -impl Request for AnswerPreCheckoutQuery { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "answerPreCheckoutQuery", &self) - .await - } -} - -impl AnswerPreCheckoutQuery { - pub(crate) fn new<P>(bot: Bot, pre_checkout_query_id: P, ok: bool) -> Self - where - P: Into<String>, - { - let pre_checkout_query_id = pre_checkout_query_id.into(); - Self { bot, pre_checkout_query_id, ok, error_message: None } - } - - /// Unique identifier for the query to be answered. - pub fn pre_checkout_query_id<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.pre_checkout_query_id = val.into(); - self - } - - /// Specify `true` if everything is alright (goods are available, etc.) and - /// the bot is ready to proceed with the order. Use False if there are any - /// problems. - pub fn ok(mut self, val: bool) -> Self { - self.ok = val; - self - } - - /// Required if ok is `false`. Error message in human readable form that - /// explains the reason for failure to proceed with the checkout (e.g. - /// "Sorry, somebody just bought the last of our amazing black T-shirts - /// while you were busy filling out your payment details. Please choose a - /// different color or garment!"). - /// - /// Telegram will display this message to the user. - pub fn error_message<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.error_message = Some(val.into()); - self - } -} diff --git a/src/requests/all/answer_shipping_query.rs b/src/requests/all/answer_shipping_query.rs deleted file mode 100644 index be4d6662..00000000 --- a/src/requests/all/answer_shipping_query.rs +++ /dev/null @@ -1,86 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ShippingOption, True}, - Bot, -}; - -/// If you sent an invoice requesting a shipping address and the parameter -/// `is_flexible` was specified, the Bot API will send an [`Update`] with a -/// shipping_query field to the bot. Use this method to reply to shipping -/// queries. -/// -/// [The official docs](https://core.telegram.org/bots/api#answershippingquery). -/// -/// [`Update`]: crate::types::Update -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct AnswerShippingQuery { - #[serde(skip_serializing)] - bot: Bot, - shipping_query_id: String, - ok: bool, - shipping_options: Option<Vec<ShippingOption>>, - error_message: Option<String>, -} - -#[async_trait::async_trait] -impl Request for AnswerShippingQuery { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "answerShippingQuery", &self).await - } -} - -impl AnswerShippingQuery { - pub(crate) fn new<S>(bot: Bot, shipping_query_id: S, ok: bool) -> Self - where - S: Into<String>, - { - let shipping_query_id = shipping_query_id.into(); - Self { bot, shipping_query_id, ok, shipping_options: None, error_message: None } - } - - /// Unique identifier for the query to be answered. - pub fn shipping_query_id<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.shipping_query_id = val.into(); - self - } - - /// Specify `true` if delivery to the specified address is possible and - /// `false` if there are any problems (for example, if delivery to the - /// specified address is not possible). - pub fn ok(mut self, val: bool) -> Self { - self.ok = val; - self - } - - /// Required if ok is `true`. A JSON-serialized array of available shipping - /// options. - pub fn shipping_options<T>(mut self, val: T) -> Self - where - T: Into<Vec<ShippingOption>>, - { - self.shipping_options = Some(val.into()); - self - } - - /// Required if ok is `false`. Error message in human readable form that - /// explains why it is impossible to complete the order (e.g. "Sorry, - /// delivery to your desired address is unavailable'). - /// - /// Telegram will display this message to the user. - pub fn error_message<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.error_message = Some(val.into()); - self - } -} diff --git a/src/requests/all/create_new_sticker_set.rs b/src/requests/all/create_new_sticker_set.rs deleted file mode 100644 index a49cb08b..00000000 --- a/src/requests/all/create_new_sticker_set.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{MaskPosition, StickerType, True}, - Bot, -}; - -/// Use this method to create new sticker set owned by a user. The bot will be -/// able to edit the created sticker set. -/// -/// [The official docs](https://core.telegram.org/bots/api#createnewstickerset). -#[derive(Debug, Clone)] -pub struct CreateNewStickerSet { - bot: Bot, - user_id: i32, - name: String, - title: String, - sticker_type: StickerType, - emojis: String, - contains_masks: Option<bool>, - mask_position: Option<MaskPosition>, -} - -#[async_trait::async_trait] -impl RequestWithFile for CreateNewStickerSet { - type Output = True; - - async fn send(&self) -> tokio::io::Result<ResponseResult<True>> { - let builder = FormBuilder::new() - .add_text("user_id", &self.user_id) - .add_text("name", &self.name) - .add_text("title", &self.title); - - let builder = match &self.sticker_type { - StickerType::Png(file) => builder.add_input_file("png_sticker", &file), - StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file), - } - .await? - .add_text("emojis", &self.emojis) - .add_text("contains_masks", &self.contains_masks) - .add_text("mask_position", &self.mask_position); - - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "createNewStickerSet", - builder.build(), - ) - .await) - } -} - -impl CreateNewStickerSet { - pub(crate) fn new<N, T, E>( - bot: Bot, - user_id: i32, - name: N, - title: T, - sticker_type: StickerType, - emojis: E, - ) -> Self - where - N: Into<String>, - T: Into<String>, - E: Into<String>, - { - Self { - bot, - user_id, - name: name.into(), - title: title.into(), - sticker_type, - emojis: emojis.into(), - contains_masks: None, - mask_position: None, - } - } - - /// User identifier of created sticker set owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Short name of sticker set, to be used in `t.me/addstickers/` URLs (e.g., - /// animals). Can contain only english letters, digits and underscores. - /// - /// Must begin with a letter, can't contain consecutive underscores and must - /// end in `_by_<bot username>`. `<bot_username>` is case insensitive. - /// 1-64 characters. - pub fn name<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.name = val.into(); - self - } - - /// Sticker set title, 1-64 characters. - pub fn title<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.title = val.into(); - self - } - - pub fn sticker_type(mut self, val: StickerType) -> Self { - self.sticker_type = val; - self - } - - /// One or more emoji corresponding to the sticker. - pub fn emojis<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.emojis = val.into(); - self - } - - /// Pass `true`, if a set of mask stickers should be created. - pub fn contains_masks(mut self, val: bool) -> Self { - self.contains_masks = Some(val); - self - } - - /// A JSON-serialized object for position where the mask should be placed on - /// faces. - pub fn mask_position(mut self, val: MaskPosition) -> Self { - self.mask_position = Some(val); - self - } -} diff --git a/src/requests/all/delete_chat_photo.rs b/src/requests/all/delete_chat_photo.rs deleted file mode 100644 index 01439bfa..00000000 --- a/src/requests/all/delete_chat_photo.rs +++ /dev/null @@ -1,50 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to delete a chat photo. Photos can't be changed for private -/// chats. The bot must be an administrator in the chat for this to work and -/// must have the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletechatphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteChatPhoto { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for DeleteChatPhoto { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "deleteChatPhoto", &self).await - } -} - -impl DeleteChatPhoto { - pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/delete_chat_sticker_set.rs b/src/requests/all/delete_chat_sticker_set.rs deleted file mode 100644 index 9fe228c5..00000000 --- a/src/requests/all/delete_chat_sticker_set.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to delete a group sticker set from a supergroup. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. Use the field `can_set_sticker_set` optionally -/// returned in [`Bot::get_chat`] requests to check if the bot can use this -/// method. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletechatstickerset). -/// -/// [`Bot::get_chat`]: crate::Bot::get_chat -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteChatStickerSet { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for DeleteChatStickerSet { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "deleteChatStickerSet", &self).await - } -} - -impl DeleteChatStickerSet { - pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/delete_message.rs b/src/requests/all/delete_message.rs deleted file mode 100644 index 35ead60a..00000000 --- a/src/requests/all/delete_message.rs +++ /dev/null @@ -1,67 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to delete a message, including service messages. -/// -/// The limitations are: -/// - A message can only be deleted if it was sent less than 48 hours ago. -/// - Bots can delete outgoing messages in private chats, groups, and -/// supergroups. -/// - Bots can delete incoming messages in private chats. -/// - Bots granted can_post_messages permissions can delete outgoing messages -/// in channels. -/// - If the bot is an administrator of a group, it can delete any message -/// there. -/// - If the bot has can_delete_messages permission in a supergroup or a -/// channel, it can delete any message there. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletemessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteMessage { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, -} - -#[async_trait::async_trait] -impl Request for DeleteMessage { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "deleteMessage", &self).await - } -} - -impl DeleteMessage { - pub(crate) fn new<C>(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (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 delete. - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } -} diff --git a/src/requests/all/delete_sticker_from_set.rs b/src/requests/all/delete_sticker_from_set.rs deleted file mode 100644 index 59269f7a..00000000 --- a/src/requests/all/delete_sticker_from_set.rs +++ /dev/null @@ -1,47 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::True, - Bot, -}; - -/// Use this method to delete a sticker from a set created by the bot. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletestickerfromset). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteStickerFromSet { - #[serde(skip_serializing)] - bot: Bot, - sticker: String, -} - -#[async_trait::async_trait] -impl Request for DeleteStickerFromSet { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "deleteStickerFromSet", &self).await - } -} - -impl DeleteStickerFromSet { - pub(crate) fn new<S>(bot: Bot, sticker: S) -> Self - where - S: Into<String>, - { - let sticker = sticker.into(); - Self { bot, sticker } - } - - /// File identifier of the sticker. - pub fn sticker<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.sticker = val.into(); - self - } -} diff --git a/src/requests/all/delete_webhook.rs b/src/requests/all/delete_webhook.rs deleted file mode 100644 index fd3bd3ba..00000000 --- a/src/requests/all/delete_webhook.rs +++ /dev/null @@ -1,37 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::True, - Bot, -}; - -/// Use this method to remove webhook integration if you decide to switch back -/// to [Bot::get_updates]. -/// -/// [The official docs](https://core.telegram.org/bots/api#deletewebhook). -/// -/// [Bot::get_updates]: crate::Bot::get_updates -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct DeleteWebhook { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl Request for DeleteWebhook { - type Output = True; - - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "deleteWebhook", &self).await - } -} - -impl DeleteWebhook { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/edit_message_caption.rs b/src/requests/all/edit_message_caption.rs deleted file mode 100644 index 2b9f2839..00000000 --- a/src/requests/all/edit_message_caption.rs +++ /dev/null @@ -1,78 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode}, - Bot, -}; - -/// Use this method to edit captions of messages. -/// -/// On success, if edited message is sent by the bot, the edited [`Message`] is -/// returned, otherwise [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption). -/// -/// [`Message`]: crate::types::Message -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageCaption { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - chat_or_inline_message: ChatOrInlineMessage, - caption: Option<String>, - parse_mode: Option<ParseMode>, - reply_markup: Option<InlineKeyboardMarkup>, -} - -#[async_trait::async_trait] -impl Request for EditMessageCaption { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "editMessageCaption", &self).await - } -} - -impl EditMessageCaption { - pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage) -> Self { - Self { bot, chat_or_inline_message, caption: None, parse_mode: None, reply_markup: None } - } - - pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { - self.chat_or_inline_message = val; - 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 - } -} diff --git a/src/requests/all/edit_message_live_location.rs b/src/requests/all/edit_message_live_location.rs deleted file mode 100644 index 78fd163e..00000000 --- a/src/requests/all/edit_message_live_location.rs +++ /dev/null @@ -1,77 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to edit live location messages. -/// -/// A location can be edited until its live_period expires or editing is -/// explicitly disabled by a call to stopMessageLiveLocation. 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#editmessagelivelocation). -/// -/// [`Message`]: crate::types::Message -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageLiveLocation { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - chat_or_inline_message: ChatOrInlineMessage, - latitude: f32, - longitude: f32, - reply_markup: Option<InlineKeyboardMarkup>, -} - -#[async_trait::async_trait] -impl Request for EditMessageLiveLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "editMessageLiveLocation", &self) - .await - } -} - -impl EditMessageLiveLocation { - pub(crate) fn new( - bot: Bot, - chat_or_inline_message: ChatOrInlineMessage, - latitude: f32, - longitude: f32, - ) -> Self { - Self { bot, chat_or_inline_message, latitude, longitude, reply_markup: None } - } - - pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { - self.chat_or_inline_message = val; - 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 - } -} diff --git a/src/requests/all/edit_message_media.rs b/src/requests/all/edit_message_media.rs deleted file mode 100644 index fd49c5c0..00000000 --- a/src/requests/all/edit_message_media.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, Request, ResponseResult}, - types::{ChatOrInlineMessage, InlineKeyboardMarkup, InputMedia, Message}, - Bot, -}; - -/// Use this method to edit animation, audio, document, photo, or video -/// messages. -/// -/// 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 -/// inline message is edited, new file can't be 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). -/// -/// [`Message`]: crate::types::Message -/// [`True`]: crate::types::True -#[derive(Debug, Clone)] -pub struct EditMessageMedia { - bot: Bot, - chat_or_inline_message: ChatOrInlineMessage, - media: InputMedia, - reply_markup: Option<InlineKeyboardMarkup>, -} - -#[async_trait::async_trait] -impl Request for EditMessageMedia { - type Output = 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( - self.bot.client(), - self.bot.token(), - "editMessageMedia", - params - .add_text("media", &self.media) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await - } -} - -impl EditMessageMedia { - pub(crate) fn new( - bot: Bot, - chat_or_inline_message: ChatOrInlineMessage, - media: InputMedia, - ) -> Self { - Self { bot, chat_or_inline_message, media, reply_markup: None } - } - - pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { - self.chat_or_inline_message = val; - 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 - } -} diff --git a/src/requests/all/edit_message_reply_markup.rs b/src/requests/all/edit_message_reply_markup.rs deleted file mode 100644 index cdfd9e67..00000000 --- a/src/requests/all/edit_message_reply_markup.rs +++ /dev/null @@ -1,56 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, - Bot, -}; - -/// 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 -/// returned, otherwise [`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 EditMessageReplyMarkup { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - chat_or_inline_message: ChatOrInlineMessage, - reply_markup: Option<InlineKeyboardMarkup>, -} - -#[async_trait::async_trait] -impl Request for EditMessageReplyMarkup { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "editMessageReplyMarkup", &self) - .await - } -} - -impl EditMessageReplyMarkup { - pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage) -> Self { - Self { bot, chat_or_inline_message, reply_markup: None } - } - - pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { - self.chat_or_inline_message = 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 - } -} diff --git a/src/requests/all/edit_message_text.rs b/src/requests/all/edit_message_text.rs deleted file mode 100644 index 71c7aeed..00000000 --- a/src/requests/all/edit_message_text.rs +++ /dev/null @@ -1,94 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode}, - Bot, -}; - -/// Use this method to edit text and game messages. -/// -/// On success, if edited message is sent by the bot, the edited [`Message`] is -/// returned, otherwise [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#editmessagetext). -/// -/// [`Message`]: crate::types::Message -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct EditMessageText { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - chat_or_inline_message: ChatOrInlineMessage, - text: String, - parse_mode: Option<ParseMode>, - disable_web_page_preview: Option<bool>, - reply_markup: Option<InlineKeyboardMarkup>, -} - -#[async_trait::async_trait] -impl Request for EditMessageText { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "editMessageText", &self).await - } -} - -impl EditMessageText { - pub(crate) fn new<T>(bot: Bot, chat_or_inline_message: ChatOrInlineMessage, text: T) -> Self - where - T: Into<String>, - { - Self { - bot, - chat_or_inline_message, - text: text.into(), - parse_mode: None, - disable_web_page_preview: None, - reply_markup: None, - } - } - - pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { - self.chat_or_inline_message = val; - 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 - } -} diff --git a/src/requests/all/export_chat_invite_link.rs b/src/requests/all/export_chat_invite_link.rs deleted file mode 100644 index 51a26437..00000000 --- a/src/requests/all/export_chat_invite_link.rs +++ /dev/null @@ -1,65 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::ChatId, - Bot, -}; - -/// Use this method to generate a new invite link for a chat; any previously -/// generated link is revoked. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. -/// -/// ## Note -/// Each administrator in a chat generates their own invite links. Bots can't -/// use invite links generated by other administrators. If you want your bot to -/// work with invite links, it will need to generate its own link using -/// [`Bot::export_chat_invite_link`] – after this the link will become available -/// to the bot via the [`Bot::get_chat`] method. If your bot needs to generate a -/// new invite link replacing its previous one, use -/// [`Bot::export_chat_invite_link`] again. -/// -/// [The official docs](https://core.telegram.org/bots/api#exportchatinvitelink). -/// -/// [`Bot::export_chat_invite_link`]: crate::Bot::export_chat_invite_link -/// [`Bot::get_chat`]: crate::Bot::get_chat -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct ExportChatInviteLink { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for ExportChatInviteLink { - type Output = String; - - /// Returns the new invite link as `String` on success. - async fn send(&self) -> ResponseResult<String> { - net::request_json(self.bot.client(), self.bot.token(), "exportChatInviteLink", &self).await - } -} - -impl ExportChatInviteLink { - pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/forward_message.rs b/src/requests/all/forward_message.rs deleted file mode 100644 index feb9ec02..00000000 --- a/src/requests/all/forward_message.rs +++ /dev/null @@ -1,81 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message}, - Bot, -}; - -/// Use this method to forward messages of any kind. -/// -/// [`The official docs`](https://core.telegram.org/bots/api#forwardmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct ForwardMessage { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - from_chat_id: ChatId, - disable_notification: Option<bool>, - message_id: i32, -} - -#[async_trait::async_trait] -impl Request for ForwardMessage { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "forwardMessage", &self).await - } -} - -impl ForwardMessage { - pub(crate) fn new<C, F>(bot: Bot, chat_id: C, from_chat_id: F, message_id: i32) -> Self - where - C: Into<ChatId>, - F: Into<ChatId>, - { - let chat_id = chat_id.into(); - let from_chat_id = from_chat_id.into(); - Self { bot, chat_id, from_chat_id, message_id, disable_notification: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier for the chat where the original message was sent (or - /// channel username in the format `@channelusername`). - #[allow(clippy::wrong_self_convention)] - pub fn from_chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.from_chat_id = val.into(); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// Message identifier in the chat specified in [`from_chat_id`]. - /// - /// [`from_chat_id`]: ForwardMessage::from_chat_id - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } -} diff --git a/src/requests/all/get_chat.rs b/src/requests/all/get_chat.rs deleted file mode 100644 index 11406776..00000000 --- a/src/requests/all/get_chat.rs +++ /dev/null @@ -1,50 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{Chat, ChatId}, - Bot, -}; - -/// Use this method to get up to date information about the chat (current name -/// of the user for one-on-one conversations, current username of a user, group -/// or channel, etc.). -/// -/// [The official docs](https://core.telegram.org/bots/api#getchat). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetChat { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for GetChat { - type Output = Chat; - - async fn send(&self) -> ResponseResult<Chat> { - net::request_json(self.bot.client(), self.bot.token(), "getChat", &self).await - } -} - -impl GetChat { - pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/get_chat_administrators.rs b/src/requests/all/get_chat_administrators.rs deleted file mode 100644 index 06657ec5..00000000 --- a/src/requests/all/get_chat_administrators.rs +++ /dev/null @@ -1,53 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, ChatMember}, - Bot, -}; - -/// Use this method to get a list of administrators in a chat. -/// -/// If the chat is a group or a supergroup and no administrators were appointed, -/// only the creator will be returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#getchatadministrators). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetChatAdministrators { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for GetChatAdministrators { - type Output = Vec<ChatMember>; - - /// On success, returns an array that contains information about all chat - /// administrators except other bots. - async fn send(&self) -> ResponseResult<Vec<ChatMember>> { - net::request_json(self.bot.client(), self.bot.token(), "getChatAdministrators", &self).await - } -} - -impl GetChatAdministrators { - pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/get_chat_member.rs b/src/requests/all/get_chat_member.rs deleted file mode 100644 index 4ebe3f56..00000000 --- a/src/requests/all/get_chat_member.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, ChatMember}, - Bot, -}; - -/// Use this method to get information about a member of a chat. -/// -/// [The official docs](https://core.telegram.org/bots/api#getchatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetChatMember { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, -} - -#[async_trait::async_trait] -impl Request for GetChatMember { - type Output = ChatMember; - - async fn send(&self) -> ResponseResult<ChatMember> { - net::request_json(self.bot.client(), self.bot.token(), "getChatMember", &self).await - } -} - -impl GetChatMember { - pub(crate) fn new<C>(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } -} diff --git a/src/requests/all/get_chat_members_count.rs b/src/requests/all/get_chat_members_count.rs deleted file mode 100644 index 5c78596b..00000000 --- a/src/requests/all/get_chat_members_count.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::ChatId, - Bot, -}; - -/// Use this method to get the number of members in a chat. -/// -/// [The official docs](https://core.telegram.org/bots/api#getchatmemberscount). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetChatMembersCount { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for GetChatMembersCount { - type Output = i32; - - async fn send(&self) -> ResponseResult<i32> { - net::request_json(self.bot.client(), self.bot.token(), "getChatMembersCount", &self).await - } -} - -impl GetChatMembersCount { - pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/get_file.rs b/src/requests/all/get_file.rs deleted file mode 100644 index d2d0556c..00000000 --- a/src/requests/all/get_file.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::File, - Bot, -}; - -/// Use this method to get basic info about a file and prepare it for -/// downloading. -/// -/// For the moment, bots can download files of up to `20MB` in size. -/// -/// The file can then be downloaded via the link -/// `https://api.telegram.org/file/bot<token>/<file_path>`, where `<file_path>` -/// is taken from the response. It is guaranteed that the link will be valid -/// for at least `1` hour. When the link expires, a new one can be requested by -/// calling [`GetFile`] again. -/// -/// **Note**: This function may not preserve the original file name and MIME -/// type. You should save the file's MIME type and name (if available) when the -/// [`File`] object is received. -/// -/// [The official docs](https://core.telegram.org/bots/api#getfile). -/// -/// [`File`]: crate::types::File -/// [`GetFile`]: self::GetFile -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetFile { - #[serde(skip_serializing)] - bot: Bot, - file_id: String, -} - -#[async_trait::async_trait] -impl Request for GetFile { - type Output = File; - - async fn send(&self) -> ResponseResult<File> { - net::request_json(self.bot.client(), self.bot.token(), "getFile", &self).await - } -} - -impl GetFile { - pub(crate) fn new<F>(bot: Bot, file_id: F) -> Self - where - F: Into<String>, - { - Self { bot, file_id: file_id.into() } - } - - /// File identifier to get info about. - pub fn file_id<F>(mut self, value: F) -> Self - where - F: Into<String>, - { - self.file_id = value.into(); - self - } -} diff --git a/src/requests/all/get_game_high_scores.rs b/src/requests/all/get_game_high_scores.rs deleted file mode 100644 index 260098c7..00000000 --- a/src/requests/all/get_game_high_scores.rs +++ /dev/null @@ -1,56 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatOrInlineMessage, GameHighScore}, - Bot, -}; - -/// Use this method to get data for high score tables. -/// -/// Will return the score of the specified user and several of his neighbors in -/// a game. -/// -/// ## Note -/// This method will currently return scores for the target user, plus two of -/// his closest neighbors on each side. Will also return the top three users if -/// the user and his neighbors are not among them. Please note that this -/// behavior is subject to change. -/// -/// [The official docs](https://core.telegram.org/bots/api#getgamehighscores). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetGameHighScores { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - chat_or_inline_message: ChatOrInlineMessage, - user_id: i32, -} - -#[async_trait::async_trait] -impl Request for GetGameHighScores { - type Output = Vec<GameHighScore>; - - async fn send(&self) -> ResponseResult<Vec<GameHighScore>> { - net::request_json(self.bot.client(), self.bot.token(), "getGameHighScores", &self).await - } -} - -impl GetGameHighScores { - pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage, user_id: i32) -> Self { - Self { bot, chat_or_inline_message, user_id } - } - - pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { - self.chat_or_inline_message = val; - self - } - - /// Target user id. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } -} diff --git a/src/requests/all/get_me.rs b/src/requests/all/get_me.rs deleted file mode 100644 index 567b4375..00000000 --- a/src/requests/all/get_me.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::{ - net, - requests::{Request, ResponseResult}, - types::Me, - Bot, -}; -use serde::Serialize; - -/// A simple method for testing your bot's auth token. Requires no parameters. -/// -/// [The official docs](https://core.telegram.org/bots/api#getme). -#[derive(Debug, Clone, Serialize)] -pub struct GetMe { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl Request for GetMe { - type Output = Me; - - /// Returns basic information about the bot. - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn send(&self) -> ResponseResult<Me> { - net::request_json(self.bot.client(), self.bot.token(), "getMe", &self).await - } -} - -impl GetMe { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/get_my_commands.rs b/src/requests/all/get_my_commands.rs deleted file mode 100644 index 586bfa28..00000000 --- a/src/requests/all/get_my_commands.rs +++ /dev/null @@ -1,33 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::BotCommand, - Bot, -}; - -/// Use this method to get the current list of the bot's commands. -/// -/// [The official docs](https://core.telegram.org/bots/api#getmycommands). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetMyCommands { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl Request for GetMyCommands { - type Output = Vec<BotCommand>; - - async fn send(&self) -> ResponseResult<Self::Output> { - net::request_json(self.bot.client(), self.bot.token(), "getMyCommands", &self).await - } -} - -impl GetMyCommands { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/get_sticker_set.rs b/src/requests/all/get_sticker_set.rs deleted file mode 100644 index a8a83bea..00000000 --- a/src/requests/all/get_sticker_set.rs +++ /dev/null @@ -1,47 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::StickerSet, - Bot, -}; - -/// Use this method to get a sticker set. -/// -/// [The official docs](https://core.telegram.org/bots/api#getstickerset). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetStickerSet { - #[serde(skip_serializing)] - bot: Bot, - name: String, -} - -#[async_trait::async_trait] -impl Request for GetStickerSet { - type Output = StickerSet; - - async fn send(&self) -> ResponseResult<StickerSet> { - net::request_json(self.bot.client(), self.bot.token(), "getStickerSet", &self).await - } -} - -impl GetStickerSet { - pub(crate) fn new<N>(bot: Bot, name: N) -> Self - where - N: Into<String>, - { - let name = name.into(); - Self { bot, name } - } - - /// Name of the sticker set. - pub fn name<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.name = val.into(); - self - } -} diff --git a/src/requests/all/get_updates.rs b/src/requests/all/get_updates.rs deleted file mode 100644 index 8bc2db59..00000000 --- a/src/requests/all/get_updates.rs +++ /dev/null @@ -1,122 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{AllowedUpdate, Update}, - Bot, RequestError, -}; -use serde_json::Value; - -/// Use this method to receive incoming updates using long polling ([wiki]). -/// -/// **Notes:** -/// 1. This method will not work if an outgoing webhook is set up. -/// 2. In order to avoid getting duplicate updates, -/// recalculate offset after each server response. -/// -/// [The official docs](https://core.telegram.org/bots/api#getupdates). -/// -/// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetUpdates { - #[serde(skip_serializing)] - bot: Bot, - pub(crate) offset: Option<i32>, - pub(crate) limit: Option<u8>, - pub(crate) timeout: Option<u32>, - pub(crate) allowed_updates: Option<Vec<AllowedUpdate>>, -} - -#[async_trait::async_trait] -impl Request for GetUpdates { - type Output = Vec<Result<Update, (Value, serde_json::Error)>>; - - /// Deserialize to `Vec<serde_json::Result<Update>>` instead of - /// `Vec<Update>`, because we want to parse the rest of updates even if our - /// library hasn't parsed one. - async fn send(&self) -> ResponseResult<Vec<Result<Update, (Value, serde_json::Error)>>> { - let value: Value = - net::request_json(self.bot.client(), self.bot.token(), "getUpdates", &self).await?; - - match value { - Value::Array(array) => Ok(array - .into_iter() - .map(|value| Update::try_parse(&value).map_err(|error| (value, error))) - .collect()), - _ => Err(RequestError::InvalidJson( - serde_json::from_value::<Vec<Update>>(value) - .expect_err("get_update must return Value::Array"), - )), - } - } -} - -impl GetUpdates { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot, offset: None, limit: None, timeout: None, allowed_updates: None } - } - - /// Identifier of the first update to be returned. - /// - /// Must be greater by one than the highest among the identifiers of - /// previously received updates. By default, updates starting with the - /// earliest unconfirmed update are returned. An update is considered - /// confirmed as soon as [`GetUpdates`] is called with an [`offset`] - /// higher than its [`id`]. The negative offset can be specified to - /// retrieve updates starting from `-offset` update from the end of the - /// updates queue. All previous updates will forgotten. - /// - /// [`GetUpdates`]: self::GetUpdates - /// [`offset`]: self::GetUpdates::offset - /// [`id`]: crate::types::Update::id - pub fn offset(mut self, value: i32) -> Self { - self.offset = Some(value); - self - } - - /// Limits the number of updates to be retrieved. - /// - /// Values between `1`—`100` are accepted. Defaults to `100`. - pub fn limit(mut self, value: u8) -> Self { - self.limit = Some(value); - self - } - - /// Timeout in seconds for long polling. - /// - /// Defaults to `0`, i.e. usual short polling. Should be positive, short - /// polling should be used for testing purposes only. - pub fn timeout(mut self, value: u32) -> Self { - self.timeout = Some(value); - self - } - - /// List the types of updates you want your bot to receive. - /// - /// For example, specify [[`Message`], [`EditedChannelPost`], - /// [`CallbackQuery`]] to only receive updates of these types. - /// See [`AllowedUpdate`] for a complete list of available update types. - /// - /// Specify an empty list to receive all updates regardless of type - /// (default). If not specified, the previous setting will be used. - /// - /// **Note:** - /// This parameter doesn't affect updates created before the call to the - /// [`Bot::get_updates`], so unwanted updates may be received for a short - /// period of time. - /// - /// [`Message`]: self::AllowedUpdate::Message - /// [`EditedChannelPost`]: self::AllowedUpdate::EditedChannelPost - /// [`CallbackQuery`]: self::AllowedUpdate::CallbackQuery - /// [`AllowedUpdate`]: self::AllowedUpdate - /// [`Bot::get_updates`]: crate::Bot::get_updates - pub fn allowed_updates<T>(mut self, value: T) -> Self - where - T: Into<Vec<AllowedUpdate>>, - { - self.allowed_updates = Some(value.into()); - self - } -} diff --git a/src/requests/all/get_user_profile_photos.rs b/src/requests/all/get_user_profile_photos.rs deleted file mode 100644 index 663a07a4..00000000 --- a/src/requests/all/get_user_profile_photos.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::UserProfilePhotos, - Bot, -}; - -/// Use this method to get a list of profile pictures for a user. -/// -/// [The official docs](https://core.telegram.org/bots/api#getuserprofilephotos). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct GetUserProfilePhotos { - #[serde(skip_serializing)] - bot: Bot, - user_id: i32, - offset: Option<i32>, - limit: Option<i32>, -} - -#[async_trait::async_trait] -impl Request for GetUserProfilePhotos { - type Output = UserProfilePhotos; - - async fn send(&self) -> ResponseResult<UserProfilePhotos> { - net::request_json(self.bot.client(), self.bot.token(), "getUserProfilePhotos", &self).await - } -} - -impl GetUserProfilePhotos { - pub(crate) fn new(bot: Bot, user_id: i32) -> Self { - Self { bot, user_id, offset: None, limit: None } - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Sequential number of the first photo to be returned. By default, all - /// photos are returned. - pub fn offset(mut self, val: i32) -> Self { - self.offset = Some(val); - self - } - - /// Limits the number of photos to be retrieved. Values between 1—100 are - /// accepted. - /// - /// Defaults to 100. - pub fn limit(mut self, val: i32) -> Self { - self.limit = Some(val); - self - } -} diff --git a/src/requests/all/get_webhook_info.rs b/src/requests/all/get_webhook_info.rs deleted file mode 100644 index 51424471..00000000 --- a/src/requests/all/get_webhook_info.rs +++ /dev/null @@ -1,38 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::WebhookInfo, - Bot, -}; - -/// Use this method to get current webhook status. -/// -/// If the bot is using [`Bot::get_updates`], will return an object with the url -/// field empty. -/// -/// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo). -/// -/// [`Bot::get_updates`]: crate::Bot::get_updates -#[derive(Debug, Clone, Serialize)] -pub struct GetWebhookInfo { - #[serde(skip_serializing)] - bot: Bot, -} - -#[async_trait::async_trait] -impl Request for GetWebhookInfo { - type Output = WebhookInfo; - - #[allow(clippy::trivially_copy_pass_by_ref)] - async fn send(&self) -> ResponseResult<WebhookInfo> { - net::request_json(self.bot.client(), self.bot.token(), "getWebhookInfo", &self).await - } -} - -impl GetWebhookInfo { - pub(crate) fn new(bot: Bot) -> Self { - Self { bot } - } -} diff --git a/src/requests/all/kick_chat_member.rs b/src/requests/all/kick_chat_member.rs deleted file mode 100644 index 31953949..00000000 --- a/src/requests/all/kick_chat_member.rs +++ /dev/null @@ -1,72 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to kick a user from a group, a supergroup or a channel. -/// -/// In the case of supergroups and channels, the user will not be able to return -/// to the group on their own using invite links, etc., unless [unbanned] first. -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#kickchatmember). -/// -/// [unbanned]: crate::Bot::unban_chat_member -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct KickChatMember { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, - until_date: Option<i32>, -} - -#[async_trait::async_trait] -impl Request for KickChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "kickChatMember", &self).await - } -} - -impl KickChatMember { - pub(crate) fn new<C>(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id, until_date: None } - } - - /// Unique identifier for the target group or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Date when the user will be unbanned, unix time. - /// - /// If user is banned for more than 366 days or less than 30 seconds from - /// the current time they are considered to be banned forever. - pub fn until_date(mut self, val: i32) -> Self { - self.until_date = Some(val); - self - } -} diff --git a/src/requests/all/leave_chat.rs b/src/requests/all/leave_chat.rs deleted file mode 100644 index d0411efb..00000000 --- a/src/requests/all/leave_chat.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method for your bot to leave a group, supergroup or channel. -/// -/// [The official docs](https://core.telegram.org/bots/api#leavechat). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct LeaveChat { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for LeaveChat { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "leaveChat", &self).await - } -} - -impl LeaveChat { - pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup or channel (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/mod.rs b/src/requests/all/mod.rs deleted file mode 100644 index 9a754103..00000000 --- a/src/requests/all/mod.rs +++ /dev/null @@ -1,140 +0,0 @@ -mod add_sticker_to_set; -mod answer_callback_query; -mod answer_inline_query; -mod answer_pre_checkout_query; -mod answer_shipping_query; -mod create_new_sticker_set; -mod delete_chat_photo; -mod delete_chat_sticker_set; -mod delete_message; -mod delete_sticker_from_set; -mod delete_webhook; -mod edit_message_caption; -mod edit_message_live_location; -mod edit_message_media; -mod edit_message_reply_markup; -mod edit_message_text; -mod export_chat_invite_link; -mod forward_message; -mod get_chat; -mod get_chat_administrators; -mod get_chat_member; -mod get_chat_members_count; -mod get_file; -mod get_game_high_scores; -mod get_me; -mod get_my_commands; -mod get_sticker_set; -mod get_updates; -mod get_user_profile_photos; -mod get_webhook_info; -mod kick_chat_member; -mod leave_chat; -mod pin_chat_message; -mod promote_chat_member; -mod restrict_chat_member; -mod send_animation; -mod send_audio; -mod send_chat_action; -mod send_contact; -mod send_dice; -mod send_document; -mod send_game; -mod send_invoice; -mod send_location; -mod send_media_group; -mod send_message; -mod send_photo; -mod send_poll; -mod send_sticker; -mod send_venue; -mod send_video; -mod send_video_note; -mod send_voice; -mod set_chat_administrator_custom_title; -mod set_chat_description; -mod set_chat_permissions; -mod set_chat_photo; -mod set_chat_sticker_set; -mod set_chat_title; -mod set_game_score; -mod set_my_commands; -mod set_sticker_position_in_set; -mod set_sticker_set_thumb; -mod set_webhook; -mod stop_message_live_location; -mod stop_poll; -mod unban_chat_member; -mod unpin_chat_message; -mod upload_sticker_file; - -pub use add_sticker_to_set::*; -pub use answer_callback_query::*; -pub use answer_inline_query::*; -pub use answer_pre_checkout_query::*; -pub use answer_shipping_query::*; -pub use create_new_sticker_set::*; -pub use delete_chat_photo::*; -pub use delete_chat_sticker_set::*; -pub use delete_message::*; -pub use delete_sticker_from_set::*; -pub use delete_webhook::*; -pub use edit_message_caption::*; -pub use edit_message_live_location::*; -pub use edit_message_media::*; -pub use edit_message_reply_markup::*; -pub use edit_message_text::*; -pub use export_chat_invite_link::*; -pub use forward_message::*; -pub use get_chat::*; -pub use get_chat_administrators::*; -pub use get_chat_member::*; -pub use get_chat_members_count::*; -pub use get_file::*; -pub use get_game_high_scores::*; -pub use get_me::*; -pub use get_my_commands::*; -pub use get_sticker_set::*; -pub use get_updates::*; -pub use get_user_profile_photos::*; -pub use get_webhook_info::*; -pub use kick_chat_member::*; -pub use leave_chat::*; -pub use pin_chat_message::*; -pub use promote_chat_member::*; -pub use restrict_chat_member::*; -pub use send_animation::*; -pub use send_audio::*; -pub use send_chat_action::*; -pub use send_contact::*; -pub use send_dice::*; -pub use send_document::*; -pub use send_game::*; -pub use send_invoice::*; -pub use send_location::*; -pub use send_media_group::*; -pub use send_message::*; -pub use send_photo::*; -pub use send_poll::*; -pub use send_sticker::*; -pub use send_venue::*; -pub use send_video::*; -pub use send_video_note::*; -pub use send_voice::*; -pub use set_chat_administrator_custom_title::*; -pub use set_chat_description::*; -pub use set_chat_permissions::*; -pub use set_chat_photo::*; -pub use set_chat_sticker_set::*; -pub use set_chat_title::*; -pub use set_game_score::*; -pub use set_my_commands::*; -pub use set_sticker_position_in_set::*; -pub use set_sticker_set_thumb::*; -pub use set_webhook::*; -pub use std::pin::Pin; -pub use stop_message_live_location::*; -pub use stop_poll::*; -pub use unban_chat_member::*; -pub use unpin_chat_message::*; -pub use upload_sticker_file::*; diff --git a/src/requests/all/pin_chat_message.rs b/src/requests/all/pin_chat_message.rs deleted file mode 100644 index ae1f3227..00000000 --- a/src/requests/all/pin_chat_message.rs +++ /dev/null @@ -1,69 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to pin a message in a group, a supergroup, or a channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the `can_pin_messages` admin right in the supergroup or `can_edit_messages` -/// admin right in the channel. -/// -/// [The official docs](https://core.telegram.org/bots/api#pinchatmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct PinChatMessage { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, - disable_notification: Option<bool>, -} - -#[async_trait::async_trait] -impl Request for PinChatMessage { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "pinChatMessage", &self).await - } -} - -impl PinChatMessage { - pub(crate) fn new<C>(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, disable_notification: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (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 a message to pin. - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - /// Pass `true`, if it is not necessary to send a notification to all chat - /// members about the new pinned message. - /// - /// Notifications are always disabled in channels. - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } -} diff --git a/src/requests/all/promote_chat_member.rs b/src/requests/all/promote_chat_member.rs deleted file mode 100644 index 468b3c48..00000000 --- a/src/requests/all/promote_chat_member.rs +++ /dev/null @@ -1,134 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to promote or demote a user in a supergroup or a channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. Pass False for all boolean parameters to -/// demote a user. -/// -/// [The official docs](https://core.telegram.org/bots/api#promotechatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct PromoteChatMember { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, - can_change_info: Option<bool>, - can_post_messages: Option<bool>, - can_edit_messages: Option<bool>, - can_delete_messages: Option<bool>, - can_invite_users: Option<bool>, - can_restrict_members: Option<bool>, - can_pin_messages: Option<bool>, - can_promote_members: Option<bool>, -} - -#[async_trait::async_trait] -impl Request for PromoteChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "promoteChatMember", &self).await - } -} - -impl PromoteChatMember { - pub(crate) fn new<C>(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { - bot, - chat_id, - user_id, - can_change_info: None, - can_post_messages: None, - can_edit_messages: None, - can_delete_messages: None, - can_invite_users: None, - can_restrict_members: None, - can_pin_messages: None, - can_promote_members: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// Pass `true`, if the administrator can change chat title, photo and other - /// settings. - pub fn can_change_info(mut self, val: bool) -> Self { - self.can_change_info = Some(val); - self - } - - /// Pass `true`, if the administrator can create channel posts, channels - /// only. - pub fn can_post_messages(mut self, val: bool) -> Self { - self.can_post_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can edit messages of other users and - /// can pin messages, channels only. - pub fn can_edit_messages(mut self, val: bool) -> Self { - self.can_edit_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can delete messages of other users. - pub fn can_delete_messages(mut self, val: bool) -> Self { - self.can_delete_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can invite new users to the chat. - pub fn can_invite_users(mut self, val: bool) -> Self { - self.can_invite_users = Some(val); - self - } - - /// Pass `true`, if the administrator can restrict, ban or unban chat - /// members. - pub fn can_restrict_members(mut self, val: bool) -> Self { - self.can_restrict_members = Some(val); - self - } - - /// Pass `true`, if the administrator can pin messages, supergroups only. - pub fn can_pin_messages(mut self, val: bool) -> Self { - self.can_pin_messages = Some(val); - self - } - - /// Pass `true`, if the administrator can add new administrators with a - /// subset of his own privileges or demote administrators that he has - /// promoted, directly or indirectly (promoted by administrators that were - /// appointed by him). - pub fn can_promote_members(mut self, val: bool) -> Self { - self.can_promote_members = Some(val); - self - } -} diff --git a/src/requests/all/restrict_chat_member.rs b/src/requests/all/restrict_chat_member.rs deleted file mode 100644 index 6b825b91..00000000 --- a/src/requests/all/restrict_chat_member.rs +++ /dev/null @@ -1,76 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, ChatPermissions, True}, - Bot, -}; - -/// Use this method to restrict a user in a supergroup. -/// -/// The bot must be an administrator in the supergroup for this to work and must -/// have the appropriate admin rights. Pass `true` for all permissions to lift -/// restrictions from a user. -/// -/// [The official docs](https://core.telegram.org/bots/api#restrictchatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct RestrictChatMember { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, - permissions: ChatPermissions, - until_date: Option<i32>, -} - -#[async_trait::async_trait] -impl Request for RestrictChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "restrictChatMember", &self).await - } -} - -impl RestrictChatMember { - pub(crate) fn new<C>(bot: Bot, chat_id: C, user_id: i32, permissions: ChatPermissions) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id, permissions, until_date: None } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// New user permissions. - pub fn permissions(mut self, val: ChatPermissions) -> Self { - self.permissions = val; - self - } - - /// Date when restrictions will be lifted for the user, unix time. - /// - /// If user is restricted for more than 366 days or less than 30 seconds - /// from the current time, they are considered to be restricted forever. - pub fn until_date(mut self, val: i32) -> Self { - self.until_date = Some(val); - self - } -} diff --git a/src/requests/all/send_animation.rs b/src/requests/all/send_animation.rs deleted file mode 100644 index 287ebd93..00000000 --- a/src/requests/all/send_animation.rs +++ /dev/null @@ -1,175 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video -/// 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). -#[derive(Debug, Clone)] -pub struct SendAnimation { - bot: Bot, - pub chat_id: ChatId, - pub animation: InputFile, - pub duration: Option<u32>, - pub width: Option<u32>, - pub height: Option<u32>, - pub thumb: Option<InputFile>, - pub caption: Option<String>, - pub parse_mode: Option<ParseMode>, - pub disable_notification: Option<bool>, - pub reply_to_message_id: Option<i32>, - pub reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendAnimation { - type Output = Message; - - async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("animation", &self.animation) - .await? - .add_text("duration", &self.duration) - .add_text("width", &self.width) - .add_text("height", &self.height) - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendAnimation", - builder.build(), - ) - .await) - } -} - -impl SendAnimation { - pub(crate) fn new<C>(bot: Bot, chat_id: C, animation: InputFile) -> Self - where - C: Into<ChatId>, - { - Self { - bot, - chat_id: chat_id.into(), - animation, - duration: None, - width: None, - height: None, - thumb: None, - caption: None, - parse_mode: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, value: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = value.into(); - self - } - - /// Animation to send. - pub fn animation(mut self, val: InputFile) -> Self { - self.animation = val; - self - } - - /// Duration of sent animation in seconds. - pub fn duration(mut self, value: u32) -> Self { - self.duration = Some(value); - self - } - - /// Animation width. - pub fn width(mut self, value: u32) -> Self { - self.width = Some(value); - self - } - - /// Animation height. - pub fn height(mut self, value: u32) -> Self { - self.height = Some(value); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200 kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// file is not uploaded using [`InputFile::File`]. Thumbnails can’t be - /// reused and can be only uploaded as a new file, with - /// [`InputFile::File`]. - /// - /// [`InputFile::File`]: crate::types::InputFile::File - pub fn thumb(mut self, value: InputFile) -> Self { - self.thumb = Some(value); - self - } - - /// Animation caption, `0`-`1024` characters. - pub fn caption<T>(mut self, value: T) -> Self - where - T: Into<String>, - { - self.caption = Some(value.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, value: ParseMode) -> Self { - self.parse_mode = Some(value); - self - } - - /// Sends the message silently. Users will receive a notification with no - /// sound. - pub fn disable_notification(mut self, value: bool) -> Self { - self.disable_notification = Some(value); - self - } - - /// If the message is a reply, [id] of the original message. - /// - /// [id]: crate::types::Message::id - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - /// Additional interface options. - pub fn reply_markup<T>(mut self, value: T) -> Self - where - T: Into<ReplyMarkup>, - { - self.reply_markup = Some(value.into()); - self - } -} diff --git a/src/requests/all/send_audio.rs b/src/requests/all/send_audio.rs deleted file mode 100644 index f2ed209f..00000000 --- a/src/requests/all/send_audio.rs +++ /dev/null @@ -1,200 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send audio files, if you want Telegram clients to display -/// them in the music player. -/// -/// Your audio must be in the .MP3 or .M4A format. Bots can currently send audio -/// files of up to 50 MB in size, this limit may be changed in the future. -/// -/// For sending voice messages, use the [`Bot::send_voice`] method instead. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendaudio). -/// -/// [`Bot::send_voice`]: crate::Bot::send_voice -#[derive(Debug, Clone)] -pub struct SendAudio { - bot: Bot, - chat_id: ChatId, - audio: InputFile, - caption: Option<String>, - parse_mode: Option<ParseMode>, - duration: Option<i32>, - performer: Option<String>, - title: Option<String>, - thumb: Option<InputFile>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendAudio { - type Output = Message; - - async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("audio", &self.audio) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("duration", &self.duration) - .add_text("performer", &self.performer) - .add_text("title", &self.title) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendAudio", - builder.build(), - ) - .await) - } -} - -impl SendAudio { - pub(crate) fn new<C>(bot: Bot, chat_id: C, audio: InputFile) -> Self - where - C: Into<ChatId>, - { - Self { - bot, - chat_id: chat_id.into(), - audio, - caption: None, - parse_mode: None, - duration: None, - performer: None, - title: None, - thumb: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Audio file to send. - /// - /// 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 - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn audio(mut self, val: InputFile) -> Self { - self.audio = val; - self - } - - /// Audio caption, 0-1024 characters. - 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 - } - - /// Duration of the audio in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Performer. - pub fn performer<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.performer = Some(val.into()); - self - } - - /// Track name. - pub fn title<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.title = Some(val.into()); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200 kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// file is not uploaded using `multipart/form-data`. Thumbnails can’t - /// be reused and can be only uploaded as a new file, so you can pass - /// `attach://<file_attach_name>` if the thumbnail was uploaded using - /// `multipart/form-data` under `<file_attach_name>`. [More info on - /// Sending Files »]. - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. A JSON-serialized object for an [inline - /// keyboard], [custom reply keyboard], instructions to remove reply - /// keyboard or to force a reply from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_chat_action.rs b/src/requests/all/send_chat_action.rs deleted file mode 100644 index 5fe853b2..00000000 --- a/src/requests/all/send_chat_action.rs +++ /dev/null @@ -1,105 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method when you need to tell the user that something is happening -/// on the bot's side. -/// -/// The status is set for 5 seconds or less (when a message arrives from your -/// bot, Telegram clients clear its typing status). -/// -/// ## Note -/// Example: The [ImageBot] needs some time to process a request and upload the -/// image. Instead of sending a text message along the lines of “Retrieving -/// image, please wait…”, the bot may use [`Bot::send_chat_action`] with `action -/// = upload_photo`. The user will see a `sending photo` status for the bot. -/// -/// We only recommend using this method when a response from the bot will take a -/// **noticeable** amount of time to arrive. -/// -/// [ImageBot]: https://t.me/imagebot -/// [`Bot::send_chat_action`]: crate::Bot::send_chat_action -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendChatAction { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - action: SendChatActionKind, -} - -/// A type of action used in [`SendChatAction`]. -/// -/// [`SendChatAction`]: crate::requests::SendChatAction -#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum SendChatActionKind { - /// For [text messages](crate::Bot::send_message). - Typing, - - /// For [photos](crate::Bot::send_photo). - UploadPhoto, - - /// For [videos](crate::Bot::send_video). - RecordVideo, - - /// For [videos](crate::Bot::send_video). - UploadVideo, - - /// For [audio files](crate::Bot::send_audio). - RecordAudio, - - /// For [audio files](crate::Bot::send_audio). - UploadAudio, - - /// For [general files](crate::Bot::send_document). - UploadDocument, - - /// For [location data](crate::Bot::send_location). - FindLocation, - - /// For [video notes](crate::Bot::send_video_note). - RecordVideoNote, - - /// For [video notes](crate::Bot::send_video_note). - UploadVideoNote, -} - -#[async_trait::async_trait] -impl Request for SendChatAction { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "sendChatAction", &self).await - } -} - -impl SendChatAction { - pub(crate) fn new<C>(bot: Bot, chat_id: C, action: SendChatActionKind) -> Self - where - C: Into<ChatId>, - { - Self { bot, chat_id: chat_id.into(), action } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Type of action to broadcast. - pub fn action(mut self, val: SendChatActionKind) -> Self { - self.action = val; - self - } -} diff --git a/src/requests/all/send_contact.rs b/src/requests/all/send_contact.rs deleted file mode 100644 index ef3ff034..00000000 --- a/src/requests/all/send_contact.rs +++ /dev/null @@ -1,129 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send phone contacts. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendcontact). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendContact { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - phone_number: String, - first_name: String, - last_name: Option<String>, - vcard: Option<String>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl Request for SendContact { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "sendContact", &self).await - } -} - -impl SendContact { - pub(crate) fn new<C, P, F>(bot: Bot, chat_id: C, phone_number: P, first_name: F) -> Self - where - C: Into<ChatId>, - P: Into<String>, - F: Into<String>, - { - let chat_id = chat_id.into(); - let phone_number = phone_number.into(); - let first_name = first_name.into(); - Self { - bot, - chat_id, - phone_number, - first_name, - last_name: None, - vcard: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Contact's phone number. - pub fn phone_number<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.phone_number = val.into(); - self - } - - /// Contact's first name. - pub fn first_name<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.first_name = val.into(); - self - } - - /// Contact's last name. - pub fn last_name<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.last_name = Some(val.into()); - self - } - - /// Additional data about the contact in the form of a [vCard], 0-2048 - /// bytes. - /// - /// [vCard]: https://en.wikipedia.org/wiki/VCard - pub fn vcard<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.vcard = Some(val.into()); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_dice.rs b/src/requests/all/send_dice.rs deleted file mode 100644 index 663d7001..00000000 --- a/src/requests/all/send_dice.rs +++ /dev/null @@ -1,94 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, DiceEmoji, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send an animated emoji that will display a random value. -/// -/// [The official docs](https://core.telegram.org/bots/api#senddice). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendDice { - #[serde(skip_serializing)] - bot: Bot, - - chat_id: ChatId, - #[serde(flatten)] - emoji: Option<DiceEmoji>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl Request for SendDice { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "sendDice", &self).await - } -} - -impl SendDice { - pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self - where - C: Into<ChatId>, - { - Self { - bot, - chat_id: chat_id.into(), - emoji: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, value: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = value.into(); - self - } - - /// Emoji on which the dice throw animation is based. - pub fn emoji(mut self, val: DiceEmoji) -> Self { - self.emoji = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, value: bool) -> Self { - self.disable_notification = Some(value); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_document.rs b/src/requests/all/send_document.rs deleted file mode 100644 index a3b6ff6f..00000000 --- a/src/requests/all/send_document.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// 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). -#[derive(Debug, Clone)] -pub struct SendDocument { - bot: Bot, - chat_id: ChatId, - document: InputFile, - thumb: Option<InputFile>, - caption: Option<String>, - parse_mode: Option<ParseMode>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendDocument { - type Output = Message; - - async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("document", &self.document) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendDocument", - builder.build(), - ) - .await) - } -} - -impl SendDocument { - pub(crate) fn new<C>(bot: Bot, chat_id: C, document: InputFile) -> Self - where - C: Into<ChatId>, - { - Self { - bot, - chat_id: chat_id.into(), - document, - thumb: None, - caption: None, - parse_mode: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// File to send. - /// - /// Pass a file_id as String to send a file that exists on the - /// Telegram servers (recommended), pass an HTTP URL as a String for - /// Telegram to get a file from the Internet, or upload a new one using - /// `multipart/form-data`. [More info on Sending Files »]. - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn document(mut self, val: InputFile) -> Self { - self.document = val; - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200 kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// file is not uploaded using `multipart/form-data`. Thumbnails can’t - /// be reused and can be only uploaded as a new file, so you can pass - /// “attach://<file_attach_name>” if the thumbnail was uploaded using - /// `multipart/form-data` under `<file_attach_name>`. [More info on - /// Sending Files »]. - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Document caption (may also be used when resending documents by - /// `file_id`), 0-1024 characters. - 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 - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_game.rs b/src/requests/all/send_game.rs deleted file mode 100644 index 03a0b0e2..00000000 --- a/src/requests/all/send_game.rs +++ /dev/null @@ -1,92 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to send a game. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendgame). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendGame { - #[serde(skip_serializing)] - bot: Bot, - chat_id: i32, - game_short_name: String, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<InlineKeyboardMarkup>, -} - -#[async_trait::async_trait] -impl Request for SendGame { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "sendGame", &self).await - } -} - -impl SendGame { - pub(crate) fn new<G>(bot: Bot, chat_id: i32, game_short_name: G) -> Self - where - G: Into<String>, - { - let game_short_name = game_short_name.into(); - Self { - bot, - chat_id, - game_short_name, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat. - pub fn chat_id(mut self, val: i32) -> Self { - self.chat_id = val; - self - } - - /// Short name of the game, serves as the unique identifier for the game. - /// Set up your games via [@Botfather]. - /// - /// [@Botfather]: https://t.me/botfather - pub fn game_short_name<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.game_short_name = val.into(); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. If empty, one `Play - /// game_title` button will be shown. If not empty, the first button must - /// launch the game. - /// - /// [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 - } -} diff --git a/src/requests/all/send_invoice.rs b/src/requests/all/send_invoice.rs deleted file mode 100644 index 8466cdba..00000000 --- a/src/requests/all/send_invoice.rs +++ /dev/null @@ -1,299 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InlineKeyboardMarkup, LabeledPrice, Message}, - Bot, -}; - -/// Use this method to send invoices. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendinvoice). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendInvoice { - #[serde(skip_serializing)] - bot: Bot, - chat_id: i32, - title: String, - description: String, - payload: String, - provider_token: String, - start_parameter: String, - currency: String, - prices: Vec<LabeledPrice>, - provider_data: Option<String>, - photo_url: Option<String>, - photo_size: Option<i32>, - photo_width: Option<i32>, - photo_height: Option<i32>, - need_name: Option<bool>, - need_phone_number: Option<bool>, - need_email: Option<bool>, - need_shipping_address: Option<bool>, - send_phone_number_to_provider: Option<bool>, - send_email_to_provider: Option<bool>, - is_flexible: Option<bool>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<InlineKeyboardMarkup>, -} - -#[async_trait::async_trait] -impl Request for SendInvoice { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "sendInvoice", &self).await - } -} - -impl SendInvoice { - #[allow(clippy::too_many_arguments)] - pub(crate) fn new<T, D, Pl, Pt, S, C, Pr>( - bot: Bot, - chat_id: i32, - title: T, - description: D, - payload: Pl, - provider_token: Pt, - start_parameter: S, - currency: C, - prices: Pr, - ) -> Self - where - T: Into<String>, - D: Into<String>, - Pl: Into<String>, - Pt: Into<String>, - S: Into<String>, - C: Into<String>, - Pr: Into<Vec<LabeledPrice>>, - { - let title = title.into(); - let description = description.into(); - let payload = payload.into(); - let provider_token = provider_token.into(); - let start_parameter = start_parameter.into(); - let currency = currency.into(); - let prices = prices.into(); - Self { - bot, - chat_id, - title, - description, - payload, - provider_token, - start_parameter, - currency, - prices, - provider_data: None, - photo_url: None, - photo_size: None, - photo_width: None, - photo_height: None, - need_name: None, - need_phone_number: None, - need_email: None, - need_shipping_address: None, - send_phone_number_to_provider: None, - send_email_to_provider: None, - is_flexible: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target private chat. - pub fn chat_id(mut self, val: i32) -> Self { - self.chat_id = val; - self - } - - /// Product name, 1-32 characters. - pub fn title<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.title = val.into(); - self - } - - /// Product description, 1-255 characters. - pub fn description<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.description = val.into(); - self - } - - /// Bot-defined invoice payload, 1-128 bytes. This will not be displayed to - /// the user, use for your internal processes. - pub fn payload<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.payload = val.into(); - self - } - - /// Payments provider token, obtained via [@Botfather]. - /// - /// [@Botfather]: https://t.me/botfather - pub fn provider_token<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.provider_token = val.into(); - self - } - - /// Unique deep-linking parameter that can be used to generate this invoice - /// when used as a start parameter. - pub fn start_parameter<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.start_parameter = val.into(); - self - } - - /// Three-letter ISO 4217 currency code, see [more on currencies]. - /// - /// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies - pub fn currency<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.currency = val.into(); - self - } - - /// Price breakdown, a list of components (e.g. product price, tax, - /// discount, delivery cost, delivery tax, bonus, etc.). - pub fn prices<T>(mut self, val: T) -> Self - where - T: Into<Vec<LabeledPrice>>, - { - self.prices = val.into(); - self - } - - /// JSON-encoded data about the invoice, which will be shared with the - /// payment provider. - /// - /// A detailed description of required fields should be provided by the - /// payment provider. - pub fn provider_data<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.provider_data = Some(val.into()); - self - } - - /// URL of the product photo for the invoice. - /// - /// Can be a photo of the goods or a marketing image for a service. People - /// like it better when they see what they are paying for. - pub fn photo_url<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.photo_url = Some(val.into()); - self - } - - /// Photo size. - pub fn photo_size(mut self, val: i32) -> Self { - self.photo_size = Some(val); - self - } - - /// Photo width. - pub fn photo_width(mut self, val: i32) -> Self { - self.photo_width = Some(val); - self - } - - /// Photo height. - pub fn photo_height(mut self, val: i32) -> Self { - self.photo_height = Some(val); - self - } - - /// Pass `true`, if you require the user's full name to complete the order. - pub fn need_name(mut self, val: bool) -> Self { - self.need_name = Some(val); - self - } - - /// Pass `true`, if you require the user's phone number to complete the - /// order. - pub fn need_phone_number(mut self, val: bool) -> Self { - self.need_phone_number = Some(val); - self - } - - /// Pass `true`, if you require the user's email address to complete the - /// order. - pub fn need_email(mut self, val: bool) -> Self { - self.need_email = Some(val); - self - } - - /// Pass `true`, if you require the user's shipping address to complete the - /// order. - pub fn need_shipping_address(mut self, val: bool) -> Self { - self.need_shipping_address = Some(val); - self - } - - /// Pass `true`, if user's phone number should be sent to provider. - pub fn send_phone_number_to_provider(mut self, val: bool) -> Self { - self.send_phone_number_to_provider = Some(val); - self - } - - /// Pass `true`, if user's email address should be sent to provider. - pub fn send_email_to_provider(mut self, val: bool) -> Self { - self.send_email_to_provider = Some(val); - self - } - - /// Pass `true`, if the final price depends on the shipping method. - #[allow(clippy::wrong_self_convention)] - pub fn is_flexible(mut self, val: bool) -> Self { - self.is_flexible = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// If empty, one 'Pay `total price`' button will be shown. If not empty, - /// the first button must be a Pay button. - /// - /// [inlint 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 - } -} diff --git a/src/requests/all/send_location.rs b/src/requests/all/send_location.rs deleted file mode 100644 index 20201ad1..00000000 --- a/src/requests/all/send_location.rs +++ /dev/null @@ -1,110 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send point on the map. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendlocation). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendLocation { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - latitude: f32, - longitude: f32, - live_period: Option<i64>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl Request for SendLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "sendLocation", &self).await - } -} - -impl SendLocation { - pub(crate) fn new<C>(bot: Bot, chat_id: C, latitude: f32, longitude: f32) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { - bot, - chat_id, - latitude, - longitude, - live_period: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Latitude of the location. - pub fn latitude(mut self, val: f32) -> Self { - self.latitude = val; - self - } - - /// Longitude of the location. - pub fn longitude(mut self, val: f32) -> Self { - self.longitude = val; - self - } - - /// Period in seconds for which the location will be updated (see [Live - /// Locations], should be between 60 and 86400). - /// - /// [Live Locations]: https://telegram.org/blog/live-locations - pub fn live_period(mut self, val: i64) -> Self { - self.live_period = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// A JSON-serialized object for an [inline keyboard]. - /// - /// If empty, one 'Pay `total price`' button will be shown. If not empty, - /// the first button must be a Pay button. - /// - /// [inlint keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_media_group.rs b/src/requests/all/send_media_group.rs deleted file mode 100644 index 7484fc62..00000000 --- a/src/requests/all/send_media_group.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, Request, ResponseResult}, - types::{ChatId, InputMedia, Message}, - Bot, -}; - -/// Use this method to send a group of photos or videos as an album. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendmediagroup). -#[derive(Debug, Clone)] -pub struct SendMediaGroup { - bot: Bot, - chat_id: ChatId, - media: Vec<InputMedia>, // TODO: InputMediaPhoto and InputMediaVideo - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, -} - -#[async_trait::async_trait] -impl Request for SendMediaGroup { - type Output = Vec<Message>; - - async fn send(&self) -> ResponseResult<Vec<Message>> { - net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendMediaGroup", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_text("media", &self.media) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .build(), - ) - .await - } -} - -impl SendMediaGroup { - pub(crate) fn new<C, M>(bot: Bot, chat_id: C, media: M) -> Self - where - C: Into<ChatId>, - M: Into<Vec<InputMedia>>, - { - let chat_id = chat_id.into(); - let media = media.into(); - Self { bot, chat_id, media, disable_notification: None, reply_to_message_id: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// A JSON-serialized array describing photos and videos to be sent, must - /// include 2–10 items. - pub fn media<T>(mut self, val: T) -> Self - where - T: Into<Vec<InputMedia>>, - { - self.media = val.into(); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the messages are a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } -} diff --git a/src/requests/all/send_message.rs b/src/requests/all/send_message.rs deleted file mode 100644 index 29525cb9..00000000 --- a/src/requests/all/send_message.rs +++ /dev/null @@ -1,121 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send text messages. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendMessage { - #[serde(skip_serializing)] - bot: Bot, - pub chat_id: ChatId, - pub text: String, - pub parse_mode: Option<ParseMode>, - pub disable_web_page_preview: Option<bool>, - pub disable_notification: Option<bool>, - pub reply_to_message_id: Option<i32>, - pub reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl Request for SendMessage { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "sendMessage", &self).await - } -} - -impl SendMessage { - pub(crate) fn new<C, T>(bot: Bot, chat_id: C, text: T) -> Self - where - C: Into<ChatId>, - T: Into<String>, - { - Self { - bot, - chat_id: chat_id.into(), - text: text.into(), - parse_mode: None, - disable_web_page_preview: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, value: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = value.into(); - self - } - - /// Text of the message to be sent. - pub fn text<T>(mut self, value: T) -> Self - where - T: Into<String>, - { - self.text = value.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, value: ParseMode) -> Self { - self.parse_mode = Some(value); - self - } - - /// Disables link previews for links in this message. - pub fn disable_web_page_preview(mut self, value: bool) -> Self { - self.disable_web_page_preview = Some(value); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, value: bool) -> Self { - self.disable_notification = Some(value); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup<T>(mut self, value: T) -> Self - where - T: Into<ReplyMarkup>, - { - self.reply_markup = Some(value.into()); - self - } -} diff --git a/src/requests/all/send_photo.rs b/src/requests/all/send_photo.rs deleted file mode 100644 index 4027f000..00000000 --- a/src/requests/all/send_photo.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send photos. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendphoto). -#[derive(Debug, Clone)] -pub struct SendPhoto { - bot: Bot, - chat_id: ChatId, - photo: InputFile, - caption: Option<String>, - parse_mode: Option<ParseMode>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendPhoto { - type Output = Message; - - async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> { - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendPhoto", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("photo", &self.photo) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await) - } -} - -impl SendPhoto { - pub(crate) fn new<C>(bot: Bot, chat_id: C, photo: InputFile) -> Self - where - C: Into<ChatId>, - { - Self { - bot, - chat_id: chat_id.into(), - photo, - caption: None, - parse_mode: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Photo to send. - /// - /// 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 - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn photo(mut self, val: InputFile) -> Self { - self.photo = val; - self - } - - ///Photo caption (may also be used when resending photos by file_id), - /// 0-1024 characters. - 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 - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. A JSON-serialized object for an [inline - /// keyboard], [custom reply keyboard], instructions to remove reply - /// keyboard or to force a reply from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_poll.rs b/src/requests/all/send_poll.rs deleted file mode 100644 index 3dd412a1..00000000 --- a/src/requests/all/send_poll.rs +++ /dev/null @@ -1,217 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message, ParseMode, PollType, ReplyMarkup}, - Bot, -}; - -/// Use this method to send a native poll. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendpoll). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendPoll { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - question: String, - options: Vec<String>, - is_anonymous: Option<bool>, - poll_type: Option<PollType>, - allows_multiple_answers: Option<bool>, - correct_option_id: Option<i32>, - explanation: Option<String>, - explanation_parse_mode: Option<ParseMode>, - open_period: Option<i32>, - close_date: Option<i32>, - is_closed: Option<bool>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl Request for SendPoll { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "sendPoll", &self).await - } -} - -impl SendPoll { - pub(crate) fn new<C, Q, O>(bot: Bot, chat_id: C, question: Q, options: O) -> Self - where - C: Into<ChatId>, - Q: Into<String>, - O: Into<Vec<String>>, - { - let chat_id = chat_id.into(); - let question = question.into(); - let options = options.into(); - Self { - bot, - chat_id, - question, - options, - is_anonymous: None, - poll_type: None, - allows_multiple_answers: None, - correct_option_id: None, - explanation: None, - explanation_parse_mode: None, - is_closed: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - close_date: None, - open_period: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - /// - /// A native poll can't be sent to a private chat. - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Poll question, 1-255 characters. - pub fn question<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.question = val.into(); - self - } - - /// List of answer options, 2-10 strings 1-100 characters each. - pub fn options<T>(mut self, val: T) -> Self - where - T: Into<Vec<String>>, - { - self.options = val.into(); - self - } - - /// `true`, if the poll needs to be anonymous, defaults to `true`. - #[allow(clippy::wrong_self_convention)] - pub fn is_anonymous<T>(mut self, val: T) -> Self - where - T: Into<bool>, - { - self.is_anonymous = Some(val.into()); - self - } - - /// Poll type, `quiz` or `regular`, defaults to `regular`. - pub fn poll_type(mut self, val: PollType) -> Self { - self.poll_type = Some(val); - self - } - - /// `true`, if the poll allows multiple answers, ignored for polls in quiz - /// mode. - /// - /// Defaults to `false`. - pub fn allows_multiple_answers<T>(mut self, val: T) -> Self - where - T: Into<bool>, - { - self.allows_multiple_answers = Some(val.into()); - self - } - - /// 0-based identifier of the correct answer option, required for polls in - /// quiz mode. - pub fn correct_option_id<T>(mut self, val: T) -> Self - where - T: Into<i32>, - { - self.correct_option_id = Some(val.into()); - self - } - - /// Text that is shown when a user chooses an incorrect answer or taps on - /// the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line - /// feeds after entities parsing. - pub fn explanation<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.explanation = Some(val.into()); - self - } - - /// Mode for parsing entities in the explanation. See [formatting options] - /// for more details. - /// - /// [formatting options]: https://core.telegram.org/bots/api#formatting-options - pub fn explanation_parse_mode(mut self, val: ParseMode) -> Self { - self.explanation_parse_mode = Some(val); - self - } - - /// Amount of time in seconds the poll will be active after creation, 5-600. - /// Can't be used together with `close_date`. - pub fn open_period(mut self, val: i32) -> Self { - self.open_period = Some(val); - self - } - - /// Point in time (Unix timestamp) when the poll will be automatically - /// closed. Must be at least 5 and no more than 600 seconds in the future. - /// Can't be used together with `open_period`. - pub fn close_date(mut self, val: i32) -> Self { - self.close_date = Some(val); - self - } - - /// Pass `true`, if the poll needs to be immediately closed. - /// - /// This can be useful for poll preview. - #[allow(clippy::wrong_self_convention)] - pub fn is_closed<T>(mut self, val: T) -> Self - where - T: Into<bool>, - { - self.is_closed = Some(val.into()); - self - } - - /// Sends the message [silently]. - /// - /// Users will receive a notification with no sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_sticker.rs b/src/requests/all/send_sticker.rs deleted file mode 100644 index 5e019e19..00000000 --- a/src/requests/all/send_sticker.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send static .WEBP or [animated] .TGS stickers. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendsticker). -/// -/// [animated]: https://telegram.org/blog/animated-stickers -#[derive(Debug, Clone)] -pub struct SendSticker { - bot: Bot, - chat_id: ChatId, - sticker: InputFile, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendSticker { - type Output = Message; - - async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> { - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendSticker", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("sticker", &self.sticker) - .await? - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await) - } -} - -impl SendSticker { - pub(crate) fn new<C>(bot: Bot, chat_id: C, sticker: InputFile) -> Self - where - C: Into<ChatId>, - { - Self { - bot, - chat_id: chat_id.into(), - sticker, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Sticker to send. - /// - /// 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 - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn sticker(mut self, val: InputFile) -> Self { - self.sticker = val; - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_venue.rs b/src/requests/all/send_venue.rs deleted file mode 100644 index 513ece7d..00000000 --- a/src/requests/all/send_venue.rs +++ /dev/null @@ -1,159 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, Message, ReplyMarkup}, - Bot, -}; - -/// Use this method to send information about a venue. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendvenue). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SendVenue { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - latitude: f32, - longitude: f32, - title: String, - address: String, - foursquare_id: Option<String>, - foursquare_type: Option<String>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl Request for SendVenue { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "sendVenue", &self).await - } -} - -impl SendVenue { - pub(crate) fn new<C, T, A>( - bot: Bot, - chat_id: C, - latitude: f32, - longitude: f32, - title: T, - address: A, - ) -> Self - where - C: Into<ChatId>, - T: Into<String>, - A: Into<String>, - { - let chat_id = chat_id.into(); - let title = title.into(); - let address = address.into(); - Self { - bot, - chat_id, - latitude, - longitude, - title, - address, - foursquare_id: None, - foursquare_type: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Latitude of the venue. - pub fn latitude(mut self, val: f32) -> Self { - self.latitude = val; - self - } - - /// Longitude of the venue. - pub fn longitude(mut self, val: f32) -> Self { - self.longitude = val; - self - } - - /// Name of the venue. - pub fn title<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.title = val.into(); - self - } - - /// Address of the venue. - pub fn address<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.address = val.into(); - self - } - - /// Foursquare identifier of the venue. - pub fn foursquare_id<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.foursquare_id = Some(val.into()); - self - } - - /// Foursquare type of the venue, if known. - /// - /// For example, `arts_entertainment/default`, `arts_entertainment/aquarium` - /// or `food/icecream`. - pub fn foursquare_type<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.foursquare_type = Some(val.into()); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_video.rs b/src/requests/all/send_video.rs deleted file mode 100644 index 30ab6a45..00000000 --- a/src/requests/all/send_video.rs +++ /dev/null @@ -1,200 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send video files, Telegram clients support mp4 videos -/// (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). -#[derive(Debug, Clone)] -pub struct SendVideo { - bot: Bot, - chat_id: ChatId, - video: InputFile, - duration: Option<i32>, - width: Option<i32>, - height: Option<i32>, - thumb: Option<InputFile>, - caption: Option<String>, - parse_mode: Option<ParseMode>, - supports_streaming: Option<bool>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendVideo { - type Output = Message; - - async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("video", &self.video) - .await? - .add_text("duration", &self.duration) - .add_text("width", &self.width) - .add_text("height", &self.height) - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("supports_streaming", &self.supports_streaming) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendVideo", - builder.build(), - ) - .await) - } -} - -impl SendVideo { - pub(crate) fn new<C>(bot: Bot, chat_id: C, video: InputFile) -> Self - where - C: Into<ChatId>, - { - Self { - bot, - chat_id: chat_id.into(), - video, - duration: None, - width: None, - height: None, - thumb: None, - caption: None, - parse_mode: None, - supports_streaming: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Video to sent. - /// - /// 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 - pub fn video(mut self, val: InputFile) -> Self { - self.video = val; - self - } - - /// Duration of sent video in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Video width. - pub fn width(mut self, val: i32) -> Self { - self.width = Some(val); - self - } - - /// Video height. - pub fn height(mut self, val: i32) -> Self { - self.height = Some(val); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200 kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// file is not uploaded using `multipart/form-data`. Thumbnails can’t be - /// reused and can be only uploaded as a new file, so you can pass - /// `attach://<file_attach_name>` if the thumbnail was uploaded using - /// `multipart/form-data` under `<file_attach_name>`. [More info on Sending - /// Files »]. - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Video caption (may also be used when resending videos by file_id), - /// 0-1024 characters. - 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 - } - - /// Pass `true`, if the uploaded video is suitable for streaming. - pub fn supports_streaming(mut self, val: bool) -> Self { - self.supports_streaming = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_video_note.rs b/src/requests/all/send_video_note.rs deleted file mode 100644 index 2ac653be..00000000 --- a/src/requests/all/send_video_note.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ReplyMarkup}, - Bot, -}; - -/// As of [v.4.0], Telegram clients support rounded square mp4 videos of up to 1 -/// minute long. Use this method to send video messages. -/// -/// [The official docs](https://core.telegram.org/bots/api#sendvideonote). -/// -/// [v.4.0]: https://telegram.org/blog/video-messages-and-telescope -#[derive(Debug, Clone)] -pub struct SendVideoNote { - bot: Bot, - chat_id: ChatId, - video_note: InputFile, - duration: Option<i32>, - length: Option<i32>, - thumb: Option<InputFile>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendVideoNote { - type Output = Message; - - async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> { - let mut builder = FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("video_note", &self.video_note) - .await? - .add_text("duration", &self.duration) - .add_text("length", &self.length) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup); - if let Some(thumb) = self.thumb.as_ref() { - builder = builder.add_input_file("thumb", thumb).await?; - } - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendVideoNote", - builder.build(), - ) - .await) - } -} - -impl SendVideoNote { - pub(crate) fn new<C>(bot: Bot, chat_id: C, video_note: InputFile) -> Self - where - C: Into<ChatId>, - { - Self { - bot, - chat_id: chat_id.into(), - video_note, - duration: None, - length: None, - thumb: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Video note to send. - /// - /// 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 - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn video_note(mut self, val: InputFile) -> Self { - self.video_note = val; - self - } - - /// Duration of sent video in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Video width and height, i.e. diameter of the video message. - pub fn length(mut self, val: i32) -> Self { - self.length = Some(val); - self - } - - /// Thumbnail of the file sent; can be ignored if thumbnail generation for - /// the file is supported server-side. - /// - /// The thumbnail should be in JPEG format and less than 200 kB in size. A - /// thumbnail‘s width and height should not exceed 320. Ignored if the - /// file is not uploaded using `multipart/form-data`. Thumbnails can’t - /// be reused and can be only uploaded as a new file, so you can pass - /// `attach://<file_attach_name>` if the thumbnail was uploaded using - /// `multipart/form-data` under `<file_attach_name>`. [More info on - /// Sending Files »]. - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/send_voice.rs b/src/requests/all/send_voice.rs deleted file mode 100644 index 1e50bbed..00000000 --- a/src/requests/all/send_voice.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::{ - net, - requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult}, - types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup}, - Bot, -}; - -/// Use this method to send audio files, if you want Telegram clients to display -/// the file as a playable voice message. -/// -/// 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 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). -/// -/// [`Audio`]: crate::types::Audio -/// [`Document`]: crate::types::Document -#[derive(Debug, Clone)] -pub struct SendVoice { - bot: Bot, - chat_id: ChatId, - voice: InputFile, - caption: Option<String>, - parse_mode: Option<ParseMode>, - duration: Option<i32>, - disable_notification: Option<bool>, - reply_to_message_id: Option<i32>, - reply_markup: Option<ReplyMarkup>, -} - -#[async_trait::async_trait] -impl RequestWithFile for SendVoice { - type Output = Message; - - async fn send(&self) -> tokio::io::Result<ResponseResult<Message>> { - Ok(net::request_multipart( - self.bot.client(), - self.bot.token(), - "sendVoice", - FormBuilder::new() - .add_text("chat_id", &self.chat_id) - .add_input_file("voice", &self.voice) - .await? - .add_text("caption", &self.caption) - .add_text("parse_mode", &self.parse_mode) - .add_text("duration", &self.duration) - .add_text("disable_notification", &self.disable_notification) - .add_text("reply_to_message_id", &self.reply_to_message_id) - .add_text("reply_markup", &self.reply_markup) - .build(), - ) - .await) - } -} - -impl SendVoice { - pub(crate) fn new<C>(bot: Bot, chat_id: C, voice: InputFile) -> Self - where - C: Into<ChatId>, - { - Self { - bot, - chat_id: chat_id.into(), - voice, - caption: None, - parse_mode: None, - duration: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Audio file to send. - /// - /// 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 - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn voice(mut self, val: InputFile) -> Self { - self.voice = val; - self - } - - /// Voice message caption, 0-1024 characters. - 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 - } - - /// Duration of the voice message in seconds. - pub fn duration(mut self, val: i32) -> Self { - self.duration = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - /// If the message is a reply, ID of the original message. - pub fn reply_to_message_id(mut self, val: i32) -> Self { - self.reply_to_message_id = Some(val); - self - } - - /// Additional interface options. - /// - /// A JSON-serialized object for an [inline keyboard], [custom reply - /// keyboard], instructions to remove reply keyboard or to force a reply - /// from the user. - /// - /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - /// [custom reply keyboard]: https://core.telegram.org/bots#keyboards - pub fn reply_markup(mut self, val: ReplyMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/requests/all/set_chat_administrator_custom_title.rs b/src/requests/all/set_chat_administrator_custom_title.rs deleted file mode 100644 index a3211811..00000000 --- a/src/requests/all/set_chat_administrator_custom_title.rs +++ /dev/null @@ -1,75 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to set a custom title for an administrator in a supergroup -/// promoted by the bot. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatadministratorcustomtitle). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatAdministratorCustomTitle { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, - custom_title: String, -} - -#[async_trait::async_trait] -impl Request for SetChatAdministratorCustomTitle { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json( - self.bot.client(), - self.bot.token(), - "setChatAdministratorCustomTitle", - &self, - ) - .await - } -} - -impl SetChatAdministratorCustomTitle { - pub(crate) fn new<C, CT>(bot: Bot, chat_id: C, user_id: i32, custom_title: CT) -> Self - where - C: Into<ChatId>, - CT: Into<String>, - { - let chat_id = chat_id.into(); - let custom_title = custom_title.into(); - Self { bot, chat_id, user_id, custom_title } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// New custom title for the administrator; 0-16 characters, emoji are not - /// allowed. - pub fn custom_title<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.custom_title = val.into(); - self - } -} diff --git a/src/requests/all/set_chat_description.rs b/src/requests/all/set_chat_description.rs deleted file mode 100644 index 092c495b..00000000 --- a/src/requests/all/set_chat_description.rs +++ /dev/null @@ -1,62 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to change the description of a group, a supergroup or a -/// channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatdescription). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatDescription { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - description: Option<String>, -} - -#[async_trait::async_trait] -impl Request for SetChatDescription { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "setChatDescription", &self).await - } -} - -impl SetChatDescription { - pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, description: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// New chat description, 0-255 characters. - pub fn description<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.description = Some(val.into()); - self - } -} diff --git a/src/requests/all/set_chat_permissions.rs b/src/requests/all/set_chat_permissions.rs deleted file mode 100644 index aaa1881b..00000000 --- a/src/requests/all/set_chat_permissions.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, ChatPermissions, True}, - Bot, -}; - -/// Use this method to set default chat permissions for all members. -/// -/// The bot must be an administrator in the group or a supergroup for this to -/// work and must have the can_restrict_members admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatpermissions). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatPermissions { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - permissions: ChatPermissions, -} - -#[async_trait::async_trait] -impl Request for SetChatPermissions { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "sendChatPermissions", &self).await - } -} - -impl SetChatPermissions { - pub(crate) fn new<C>(bot: Bot, chat_id: C, permissions: ChatPermissions) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, permissions } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// New default chat permissions. - pub fn permissions(mut self, val: ChatPermissions) -> Self { - self.permissions = val; - self - } -} diff --git a/src/requests/all/set_chat_photo.rs b/src/requests/all/set_chat_photo.rs deleted file mode 100644 index dcc5febc..00000000 --- a/src/requests/all/set_chat_photo.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, InputFile, True}, - Bot, -}; - -/// Use this method to set a new profile photo for the chat. -/// -/// Photos can't be changed for private chats. The bot must be an administrator -/// in the chat for this to work and must have the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatPhoto { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - photo: InputFile, -} - -#[async_trait::async_trait] -impl Request for SetChatPhoto { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "setChatPhoto", &self).await - } -} - -impl SetChatPhoto { - pub(crate) fn new<C>(bot: Bot, chat_id: C, photo: InputFile) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, photo } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// New chat photo, uploaded using `multipart/form-data`. - pub fn photo(mut self, val: InputFile) -> Self { - self.photo = val; - self - } -} diff --git a/src/requests/all/set_chat_sticker_set.rs b/src/requests/all/set_chat_sticker_set.rs deleted file mode 100644 index 010a10f5..00000000 --- a/src/requests/all/set_chat_sticker_set.rs +++ /dev/null @@ -1,64 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to set a new group sticker set for a supergroup. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the appropriate admin rights. Use the field can_set_sticker_set optionally -/// returned in getChat requests to check if the bot can use this method. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchatstickerset). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatStickerSet { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - sticker_set_name: String, -} - -#[async_trait::async_trait] -impl Request for SetChatStickerSet { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "setChatStickerSet", &self).await - } -} - -impl SetChatStickerSet { - pub(crate) fn new<C, S>(bot: Bot, chat_id: C, sticker_set_name: S) -> Self - where - C: Into<ChatId>, - S: Into<String>, - { - let chat_id = chat_id.into(); - let sticker_set_name = sticker_set_name.into(); - Self { bot, chat_id, sticker_set_name } - } - - /// Unique identifier for the target chat or username of the target - /// supergroup (in the format `@supergroupusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Name of the sticker set to be set as the group sticker set. - pub fn sticker_set_name<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.sticker_set_name = val.into(); - self - } -} diff --git a/src/requests/all/set_chat_title.rs b/src/requests/all/set_chat_title.rs deleted file mode 100644 index 7d6ae6d1..00000000 --- a/src/requests/all/set_chat_title.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to change the title of a chat. -/// -/// Titles can't be changed for private chats. The bot must be an administrator -/// in the chat for this to work and must have the appropriate admin rights. -/// -/// [The official docs](https://core.telegram.org/bots/api#setchattitle). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetChatTitle { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - title: String, -} - -#[async_trait::async_trait] -impl Request for SetChatTitle { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "setChatTitle", &self).await - } -} - -impl SetChatTitle { - pub(crate) fn new<C, T>(bot: Bot, chat_id: C, title: T) -> Self - where - C: Into<ChatId>, - T: Into<String>, - { - let chat_id = chat_id.into(); - let title = title.into(); - Self { bot, chat_id, title } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// New chat title, 1-255 characters. - pub fn title<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.title = val.into(); - self - } -} diff --git a/src/requests/all/set_game_score.rs b/src/requests/all/set_game_score.rs deleted file mode 100644 index e3271a77..00000000 --- a/src/requests/all/set_game_score.rs +++ /dev/null @@ -1,93 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatOrInlineMessage, Message}, - Bot, -}; - -/// Use this method to set the score of the specified user in a game. -/// -/// On success, if the message was sent by the bot, returns the edited -/// [`Message`], otherwise returns [`True`]. Returns an error, if the new score -/// is not greater than the user's current score in the chat and force is -/// `false`. -/// -/// [The official docs](https://core.telegram.org/bots/api#setgamescore). -/// -/// [`Message`]: crate::types::Message -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetGameScore { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - chat_or_inline_message: ChatOrInlineMessage, - user_id: i32, - score: i32, - force: Option<bool>, - disable_edit_message: Option<bool>, -} - -#[async_trait::async_trait] -impl Request for SetGameScore { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "setGameScore", &self).await - } -} - -impl SetGameScore { - pub(crate) fn new( - bot: Bot, - chat_or_inline_message: ChatOrInlineMessage, - user_id: i32, - score: i32, - ) -> Self { - 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 { - self.chat_or_inline_message = val; - self - } - - /// User identifier. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// New score, must be non-negative. - pub fn score(mut self, val: i32) -> Self { - self.score = val; - self - } - - /// Pass `true`, if the high score is allowed to decrease. - /// - /// This can be useful when fixing mistakes or banning cheaters. - pub fn force(mut self, val: bool) -> Self { - self.force = Some(val); - self - } - - /// Sends the message [silently]. Users will receive a notification with no - /// sound. - /// - /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages - pub fn disable_edit_message(mut self, val: bool) -> Self { - self.disable_edit_message = Some(val); - self - } -} diff --git a/src/requests/all/set_my_commands.rs b/src/requests/all/set_my_commands.rs deleted file mode 100644 index 831f9705..00000000 --- a/src/requests/all/set_my_commands.rs +++ /dev/null @@ -1,50 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{BotCommand, True}, - Bot, -}; - -/// Use this method to change the list of the bot's commands. -/// -/// [The official docs](https://core.telegram.org/bots/api#setmycommands). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetMyCommands { - #[serde(skip_serializing)] - bot: Bot, - - commands: Vec<BotCommand>, -} - -#[async_trait::async_trait] -impl Request for SetMyCommands { - type Output = True; - - async fn send(&self) -> ResponseResult<Self::Output> { - net::request_json(self.bot.client(), self.bot.token(), "setMyCommands", &self).await - } -} - -impl SetMyCommands { - pub(crate) fn new<C>(bot: Bot, commands: C) -> Self - where - C: Into<Vec<BotCommand>>, - { - Self { bot, commands: commands.into() } - } - - /// A JSON-serialized list of bot commands to be set as the list of the - /// bot's commands. - /// - /// At most 100 commands can be specified. - pub fn commands<C>(mut self, commands: C) -> Self - where - C: Into<Vec<BotCommand>>, - { - self.commands = commands.into(); - self - } -} diff --git a/src/requests/all/set_sticker_position_in_set.rs b/src/requests/all/set_sticker_position_in_set.rs deleted file mode 100644 index 4ed78551..00000000 --- a/src/requests/all/set_sticker_position_in_set.rs +++ /dev/null @@ -1,56 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::True, - Bot, -}; - -/// Use this method to move a sticker in a set created by the bot to a specific -/// position. -/// -/// [The official docs](https://core.telegram.org/bots/api#setstickerpositioninset). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetStickerPositionInSet { - #[serde(skip_serializing)] - bot: Bot, - sticker: String, - position: i32, -} - -#[async_trait::async_trait] -impl Request for SetStickerPositionInSet { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "setStickerPositionInSet", &self) - .await - } -} - -impl SetStickerPositionInSet { - pub(crate) fn new<S>(bot: Bot, sticker: S, position: i32) -> Self - where - S: Into<String>, - { - let sticker = sticker.into(); - Self { bot, sticker, position } - } - - /// File identifier of the sticker. - pub fn sticker<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.sticker = val.into(); - self - } - - /// New sticker position in the set, zero-based. - pub fn position(mut self, val: i32) -> Self { - self.position = val; - self - } -} diff --git a/src/requests/all/set_sticker_set_thumb.rs b/src/requests/all/set_sticker_set_thumb.rs deleted file mode 100644 index 1f84aacd..00000000 --- a/src/requests/all/set_sticker_set_thumb.rs +++ /dev/null @@ -1,73 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{InputFile, True}, - Bot, -}; - -/// Use this method to set the thumbnail of a sticker set. Animated thumbnails -/// can be set for animated sticker sets only. -/// -/// [The official docs](https://core.telegram.org/bots/api#setstickersetthumb). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetStickerSetThumb { - #[serde(skip_serializing)] - bot: Bot, - name: String, - user_id: i32, - thumb: Option<InputFile>, -} - -#[async_trait::async_trait] -impl Request for SetStickerSetThumb { - type Output = True; - - async fn send(&self) -> ResponseResult<Self::Output> { - net::request_json(self.bot.client(), self.bot.token(), "setStickerSetThumb", &self).await - } -} - -impl SetStickerSetThumb { - pub(crate) fn new<S>(bot: Bot, name: S, user_id: i32) -> Self - where - S: Into<String>, - { - Self { bot, name: name.into(), user_id, thumb: None } - } - - /// Sticker set name. - pub fn name<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.name = val.into(); - self - } - - /// User identifier of the sticker set owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// A PNG image with the thumbnail, must be up to 128 kilobytes in size and - /// have width and height exactly 100px, or a TGS animation with the - /// thumbnail up to 32 kilobytes in size; see https://core.telegram.org/animated_stickers#technical-requirements - /// for animated sticker technical requirements. - /// - /// Pass [`InputFile::FileId`] as a String to send a file that already - /// exists on the Telegram servers, pass [`InputFile::Url`] for Telegram - /// to get a file from the Internet, or upload a new one using - /// multipart/form-data. More info on Sending Files ». Animated sticker - /// set thumbnail can't be uploaded via HTTP URL. - /// - /// [`InputFile::FileId`]: crate::types::InputFile::FileId - /// [`InputFile::Url]: crate::types::InputFile::Url - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } -} diff --git a/src/requests/all/set_webhook.rs b/src/requests/all/set_webhook.rs deleted file mode 100644 index c2aff792..00000000 --- a/src/requests/all/set_webhook.rs +++ /dev/null @@ -1,114 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{AllowedUpdate, InputFile, True}, - Bot, -}; - -/// Use this method to specify a url and receive incoming updates via an -/// outgoing webhook. -/// -/// Whenever there is an update for the bot, we will send an -/// HTTPS POST request to the specified url, containing a JSON-serialized -/// [`Update`]. In case of an unsuccessful request, we will give up after a -/// reasonable amount of attempts. -/// -/// If you'd like to make sure that the Webhook request comes from Telegram, -/// we recommend using a secret path in the URL, e.g. -/// `https://www.example.com/<token>`. Since nobody else knows your bot‘s -/// token, you can be pretty sure it’s us. -/// -/// [The official docs](https://core.telegram.org/bots/api#setwebhook). -/// -/// [`Update`]: crate::types::Update -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct SetWebhook { - #[serde(skip_serializing)] - bot: Bot, - url: String, - certificate: Option<InputFile>, - max_connections: Option<i32>, - allowed_updates: Option<Vec<AllowedUpdate>>, -} - -#[async_trait::async_trait] -impl Request for SetWebhook { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "setWebhook", &self).await - } -} - -impl SetWebhook { - pub(crate) fn new<U>(bot: Bot, url: U) -> Self - where - U: Into<String>, - { - let url = url.into(); - Self { bot, url, certificate: None, max_connections: None, allowed_updates: None } - } - - /// HTTPS url to send updates to. - /// - /// Use an empty string to remove webhook integration. - pub fn url<T>(mut self, val: T) -> Self - where - T: Into<String>, - { - self.url = val.into(); - self - } - - /// Upload your public key certificate so that the root certificate in use - /// can be checked. - /// - /// See our [self-signed guide] for details. - /// - /// [self-signed guide]: https://core.telegram.org/bots/self-signed - pub fn certificate(mut self, val: InputFile) -> Self { - self.certificate = Some(val); - self - } - - /// Maximum allowed number of simultaneous HTTPS connections to the webhook - /// for update delivery, 1-100. - /// - /// Defaults to 40. Use lower values to limit the load on your bot‘s server, - /// and higher values to increase your bot’s throughput. - pub fn max_connections(mut self, val: i32) -> Self { - self.max_connections = Some(val); - self - } - - /// List the types of updates you want your bot to receive. - /// - /// For example, specify [`AllowedUpdate::Message`], - /// [`AllowedUpdate::EditedChannelPost`], [`AllowedUpdate::CallbackQuery`] - /// to only receive updates of these types. Specify an empty list to receive - /// all updates regardless of type (default). If not specified, the - /// previous setting will be used. See [`AllowedUpdate`] for a complete list - /// of available update types. - /// - /// Please note that this parameter doesn't affect updates created before - /// the call to the [`Bot::set_webhook`], so unwanted updates may be - /// received for a short period of time. - /// - /// [`Bot::set_webhook`]: crate::Bot::set_webhook - /// [`AllowedUpdate::Message`]: crate::types::AllowedUpdate::Message - /// [`AllowedUpdate::EditedChannelPost`]: - /// crate::types::AllowedUpdate::EditedChannelPost - /// [`AllowedUpdate::CallbackQuery`]: - /// crate::types::AllowedUpdate::CallbackQuery - /// [`AllowedUpdate`]: crate::types::AllowedUpdate - pub fn allowed_updates<T>(mut self, val: T) -> Self - where - T: Into<Vec<AllowedUpdate>>, - { - self.allowed_updates = Some(val.into()); - self - } -} diff --git a/src/requests/all/stop_message_live_location.rs b/src/requests/all/stop_message_live_location.rs deleted file mode 100644 index a0692ec8..00000000 --- a/src/requests/all/stop_message_live_location.rs +++ /dev/null @@ -1,57 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message}, - Bot, -}; - -/// Use this method to stop updating a live location message before -/// `live_period` expires. -/// -/// On success, if the message was sent by the bot, the sent [`Message`] is -/// returned, otherwise [`True`] is returned. -/// -/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation). -/// -/// [`Message`]: crate::types::Message -/// [`True`]: crate::types::True -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct StopMessageLiveLocation { - #[serde(skip_serializing)] - bot: Bot, - #[serde(flatten)] - chat_or_inline_message: ChatOrInlineMessage, - reply_markup: Option<InlineKeyboardMarkup>, -} - -#[async_trait::async_trait] -impl Request for StopMessageLiveLocation { - type Output = Message; - - async fn send(&self) -> ResponseResult<Message> { - net::request_json(self.bot.client(), self.bot.token(), "stopMessageLiveLocation", &self) - .await - } -} - -impl StopMessageLiveLocation { - pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage) -> Self { - Self { bot, chat_or_inline_message, reply_markup: None } - } - - pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self { - self.chat_or_inline_message = 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 - } -} diff --git a/src/requests/all/stop_poll.rs b/src/requests/all/stop_poll.rs deleted file mode 100644 index 946b4760..00000000 --- a/src/requests/all/stop_poll.rs +++ /dev/null @@ -1,66 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, InlineKeyboardMarkup, Poll}, - Bot, -}; - -/// Use this method to stop a poll which was sent by the bot. -/// -/// [The official docs](https://core.telegram.org/bots/api#stoppoll). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct StopPoll { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - message_id: i32, - reply_markup: Option<InlineKeyboardMarkup>, -} - -#[async_trait::async_trait] -impl Request for StopPoll { - type Output = Poll; - - /// On success, the stopped [`Poll`] with the final results is returned. - /// - /// [`Poll`]: crate::types::Poll - async fn send(&self) -> ResponseResult<Poll> { - net::request_json(self.bot.client(), self.bot.token(), "stopPoll", &self).await - } -} -impl StopPoll { - pub(crate) fn new<C>(bot: Bot, chat_id: C, message_id: i32) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, message_id, reply_markup: None } - } - - /// Unique identifier for the target chat or username of the target channel - /// (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 original message with the poll. - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = 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 - } -} diff --git a/src/requests/all/unban_chat_member.rs b/src/requests/all/unban_chat_member.rs deleted file mode 100644 index 5aee625c..00000000 --- a/src/requests/all/unban_chat_member.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to unban a previously kicked user in a supergroup or -/// channel. The user will **not** return to the group or channel automatically, -/// but will be able to join via link, etc. The bot must be an administrator for -/// this to work. -/// -/// [The official docs](https://core.telegram.org/bots/api#unbanchatmember). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct UnbanChatMember { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, - user_id: i32, -} - -#[async_trait::async_trait] -impl Request for UnbanChatMember { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "unbanChatMember", &self).await - } -} - -impl UnbanChatMember { - pub(crate) fn new<C>(bot: Bot, chat_id: C, user_id: i32) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id, user_id } - } - - /// Unique identifier for the target group or username of the target - /// supergroup or channel (in the format `@username`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - /// Unique identifier of the target user. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } -} diff --git a/src/requests/all/unpin_chat_message.rs b/src/requests/all/unpin_chat_message.rs deleted file mode 100644 index 15e76a99..00000000 --- a/src/requests/all/unpin_chat_message.rs +++ /dev/null @@ -1,52 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{ChatId, True}, - Bot, -}; - -/// Use this method to unpin a message in a group, a supergroup, or a channel. -/// -/// The bot must be an administrator in the chat for this to work and must have -/// the `can_pin_messages` admin right in the supergroup or `can_edit_messages` -/// admin right in the channel. -/// -/// [The official docs](https://core.telegram.org/bots/api#unpinchatmessage). -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct UnpinChatMessage { - #[serde(skip_serializing)] - bot: Bot, - chat_id: ChatId, -} - -#[async_trait::async_trait] -impl Request for UnpinChatMessage { - type Output = True; - - async fn send(&self) -> ResponseResult<True> { - net::request_json(self.bot.client(), self.bot.token(), "unpinChatMessage", &self).await - } -} - -impl UnpinChatMessage { - pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self - where - C: Into<ChatId>, - { - let chat_id = chat_id.into(); - Self { bot, chat_id } - } - - /// Unique identifier for the target chat or username of the target channel - /// (in the format `@channelusername`). - pub fn chat_id<T>(mut self, val: T) -> Self - where - T: Into<ChatId>, - { - self.chat_id = val.into(); - self - } -} diff --git a/src/requests/all/upload_sticker_file.rs b/src/requests/all/upload_sticker_file.rs deleted file mode 100644 index b09b143f..00000000 --- a/src/requests/all/upload_sticker_file.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::Serialize; - -use crate::{ - net, - requests::{Request, ResponseResult}, - types::{File, InputFile}, - Bot, -}; - -/// Use this method to upload a .png file with a sticker for later use in -/// [`Bot::create_new_sticker_set`] and [`Bot::add_sticker_to_set`] methods (can -/// be used multiple times). -/// -/// [The official docs](https://core.telegram.org/bots/api#uploadstickerfile). -/// -/// [`Bot::create_new_sticker_set`]: crate::Bot::create_new_sticker_set -/// [`Bot::add_sticker_to_set`]: crate::Bot::add_sticker_to_set -#[serde_with_macros::skip_serializing_none] -#[derive(Debug, Clone, Serialize)] -pub struct UploadStickerFile { - #[serde(skip_serializing)] - bot: Bot, - user_id: i32, - png_sticker: InputFile, -} -#[async_trait::async_trait] -impl Request for UploadStickerFile { - type Output = File; - - async fn send(&self) -> ResponseResult<File> { - net::request_json(self.bot.client(), self.bot.token(), "uploadStickerFile", &self).await - } -} - -impl UploadStickerFile { - pub(crate) fn new(bot: Bot, user_id: i32, png_sticker: InputFile) -> Self { - Self { bot, user_id, png_sticker } - } - - /// User identifier of sticker file owner. - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = val; - self - } - - /// **Png** image with the sticker, must be up to 512 kilobytes in size, - /// dimensions must not exceed 512px, and either width or height must be - /// exactly 512px. [More info on Sending Files »]. - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - pub fn png_sticker(mut self, val: InputFile) -> Self { - self.png_sticker = val; - self - } -} diff --git a/src/requests/form_builder.rs b/src/requests/form_builder.rs deleted file mode 100644 index 3c7f70b9..00000000 --- a/src/requests/form_builder.rs +++ /dev/null @@ -1,156 +0,0 @@ -use std::{borrow::Cow, path::PathBuf}; - -use reqwest::multipart::Form; - -use crate::{ - requests::utils::{file_from_memory_to_part, file_to_part}, - types::{ - ChatId, InlineKeyboardMarkup, InputFile, InputMedia, MaskPosition, ParseMode, ReplyMarkup, - }, -}; - -/// This is a convenient struct that builds `reqwest::multipart::Form` -/// from scratch. -pub(crate) struct FormBuilder { - form: Form, -} - -impl FormBuilder { - pub(crate) fn new() -> Self { - Self { form: Form::new() } - } - - pub fn add_text<'a, T, N>(self, name: N, value: &T) -> Self - where - N: Into<Cow<'a, str>>, - T: IntoFormText, - { - match value.into_form_text() { - Some(val) => Self { form: self.form.text(name.into().into_owned(), val) }, - None => self, - } - } - - pub async fn add_input_file<'a, N>(self, name: N, value: &InputFile) -> tokio::io::Result<Self> - where - N: Into<Cow<'a, str>>, - { - Ok(match value { - InputFile::File(path) => self.add_file(name, path.clone()).await?, - InputFile::Memory { file_name, data } => { - self.add_file_from_memory(name, file_name.clone(), data.clone()) - } - InputFile::Url(url) => self.add_text(name, url), - InputFile::FileId(file_id) => self.add_text(name, file_id), - }) - } - - // used in SendMediaGroup - pub async fn add_file<'a, N>(self, name: N, path_to_file: PathBuf) -> tokio::io::Result<Self> - where - N: Into<Cow<'a, str>>, - { - Ok(Self { - form: self.form.part(name.into().into_owned(), file_to_part(path_to_file).await?), - }) - } - - fn add_file_from_memory<'a, N>( - self, - name: N, - file_name: String, - data: Cow<'static, [u8]>, - ) -> Self - where - N: Into<Cow<'a, str>>, - { - Self { - form: self - .form - .part(name.into().into_owned(), file_from_memory_to_part(data, file_name)), - } - } - - pub fn build(self) -> Form { - self.form - } -} - -pub(crate) trait IntoFormText { - fn into_form_text(&self) -> Option<String>; -} - -macro_rules! impl_for_struct { - ($($name:ty),*) => { - $( - impl IntoFormText for $name { - fn into_form_text(&self) -> Option<String> { - let json = serde_json::to_string(self) - .expect("serde_json::to_string failed"); - Some(json) - } - } - )* - }; -} - -impl_for_struct!(bool, i32, i64, u32, ReplyMarkup, InlineKeyboardMarkup, MaskPosition); - -impl<T> IntoFormText for Option<T> -where - T: IntoFormText, -{ - fn into_form_text(&self) -> Option<String> { - self.as_ref().and_then(IntoFormText::into_form_text) - } -} - -// TODO: fix InputMedia implementation of IntoFormValue (for now it doesn't -// encode files :|) -impl IntoFormText for Vec<InputMedia> { - fn into_form_text(&self) -> Option<String> { - let json = serde_json::to_string(self).expect("serde_json::to_string failed"); - Some(json) - } -} - -impl IntoFormText for InputMedia { - fn into_form_text(&self) -> Option<String> { - let json = serde_json::to_string(self).expect("serde_json::to_string failed"); - Some(json) - } -} - -impl IntoFormText for str { - fn into_form_text(&self) -> Option<String> { - Some(self.to_owned()) - } -} - -impl IntoFormText for ParseMode { - fn into_form_text(&self) -> Option<String> { - let string = match self { - ParseMode::MarkdownV2 => String::from("MarkdownV2"), - ParseMode::HTML => String::from("HTML"), - #[allow(deprecated)] - ParseMode::Markdown => String::from("Markdown"), - }; - Some(string) - } -} - -impl IntoFormText for ChatId { - fn into_form_text(&self) -> Option<String> { - let string = match self { - ChatId::Id(id) => id.to_string(), - ChatId::ChannelUsername(username) => username.clone(), - }; - Some(string) - } -} - -impl IntoFormText for String { - fn into_form_text(&self) -> Option<String> { - Some(self.clone()) - } -} diff --git a/src/requests/mod.rs b/src/requests/mod.rs deleted file mode 100644 index 09f2af6c..00000000 --- a/src/requests/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! API requests. - -mod all; -mod form_builder; -mod utils; - -pub use all::*; - -/// A type that is returned after making a request to Telegram. -pub type ResponseResult<T> = Result<T, crate::RequestError>; - -/// Designates an API request. -#[async_trait::async_trait] -pub trait Request { - /// A data structure returned if success. - type Output; - - /// Asynchronously sends this request to Telegram and returns the result. - async fn send(&self) -> ResponseResult<Self::Output>; -} - -/// Designates an API request with possibly sending file. -#[async_trait::async_trait] -pub trait RequestWithFile { - /// A data structure returned if success. - type Output; - - /// Asynchronously sends this request to Telegram and returns the result. - /// Returns `tokio::io::Result::Err` when trying to send file which does not - /// exists. - async fn send(&self) -> tokio::io::Result<ResponseResult<Self::Output>>; -} diff --git a/src/requests/utils.rs b/src/requests/utils.rs deleted file mode 100644 index f10c9dc5..00000000 --- a/src/requests/utils.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::{borrow::Cow, path::PathBuf}; - -use bytes::{Bytes, BytesMut}; -use reqwest::{multipart::Part, Body}; -use tokio_util::codec::{Decoder, FramedRead}; - -struct FileDecoder; - -impl Decoder for FileDecoder { - type Item = Bytes; - type Error = std::io::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> { - if src.is_empty() { - return Ok(None); - } - Ok(Some(src.split().freeze())) - } -} - -pub async fn file_to_part(path_to_file: PathBuf) -> std::io::Result<Part> { - let file_name = path_to_file.file_name().unwrap().to_string_lossy().into_owned(); - - let file = FramedRead::new(tokio::fs::File::open(path_to_file).await?, FileDecoder); - - Ok(Part::stream(Body::wrap_stream(file)).file_name(file_name)) -} - -pub fn file_from_memory_to_part(data: Cow<'static, [u8]>, name: String) -> Part { - Part::bytes(data).file_name(name) -} diff --git a/src/types/allowed_update.rs b/src/types/allowed_update.rs deleted file mode 100644 index f3b920ac..00000000 --- a/src/types/allowed_update.rs +++ /dev/null @@ -1,14 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum AllowedUpdate { - Message, - EditedMessage, - ChannelPost, - EditedChannelPost, - InlineQuery, - ChosenInlineResult, - CallbackQuery, -} diff --git a/src/types/animation.rs b/src/types/animation.rs deleted file mode 100644 index dc52901c..00000000 --- a/src/types/animation.rs +++ /dev/null @@ -1,165 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{MimeWrapper, PhotoSize}; - -/// This object represents an animation file (GIF or H.264/MPEG-4 AVC video -/// without sound). -/// -/// [The official docs](https://core.telegram.org/bots/api#animation). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Animation { - /// An identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// A video width as defined by a sender. - pub width: u32, - - /// A video height as defined by a sender. - pub height: u32, - - /// A duration of the video in seconds as defined by a sender. - pub duration: u32, - - /// An animation thumbnail as defined by a sender. - pub thumb: Option<PhotoSize>, - - /// An original animation filename as defined by a sender. - pub file_name: Option<String>, - - /// A MIME type of the file as defined by a sender. - pub mime_type: Option<MimeWrapper>, - - /// A size of a file. - pub file_size: Option<u32>, -} - -impl Animation { - pub fn new<S1, S2>( - file_id: S1, - file_unique_id: S2, - width: u32, - height: u32, - duration: u32, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - duration, - thumb: None, - file_name: None, - mime_type: None, - file_size: None, - } - } - - pub fn file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_unique_id = val.into(); - self - } - - pub fn width(mut self, val: u32) -> Self { - self.width = val; - self - } - - pub fn height(mut self, val: u32) -> Self { - self.height = val; - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn file_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_name = Some(val.into()); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let json = r#"{ - "file_id":"id", - "file_unique_id":"", - "width":320, - "height":320, - "duration":59, - "thumb":{ - "file_id":"id", - "file_unique_id":"", - "width":320, - "height":320, - "file_size":3452 - }, - "file_name":"some", - "mime_type":"video/gif", - "file_size":6500}"#; - let expected = Animation { - file_id: "id".to_string(), - file_unique_id: "".to_string(), - width: 320, - height: 320, - duration: 59, - thumb: Some(PhotoSize { - file_id: "id".to_string(), - file_unique_id: "".to_string(), - width: 320, - height: 320, - file_size: Some(3452), - }), - file_name: Some("some".to_string()), - mime_type: Some(MimeWrapper("video/gif".parse().unwrap())), - file_size: Some(6500), - }; - let actual = serde_json::from_str::<Animation>(json).unwrap(); - assert_eq!(actual, expected) - } -} diff --git a/src/types/audio.rs b/src/types/audio.rs deleted file mode 100644 index d5599c20..00000000 --- a/src/types/audio.rs +++ /dev/null @@ -1,152 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{MimeWrapper, PhotoSize}; - -/// This object represents an audio file to be treated as music by the Telegram -/// clients. -/// -/// [The official docs](https://core.telegram.org/bots/api#audio). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Audio { - /// An identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// A duration of the audio in seconds as defined by a sender. - pub duration: u32, - - /// A performer of the audio as defined by a sender or by audio tags. - pub performer: Option<String>, - - /// A title of the audio as defined by sender or by audio tags. - pub title: Option<String>, - - /// A MIME type of the file as defined by a sender. - pub mime_type: Option<MimeWrapper>, - - /// A size of a file. - pub file_size: Option<u32>, - - /// A thumbnail of the album cover to which the music file belongs. - pub thumb: Option<PhotoSize>, -} - -impl Audio { - pub fn new<S1, S2>(file_id: S1, file_unique_id: S2, duration: u32) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - duration, - performer: None, - title: None, - mime_type: None, - file_size: None, - thumb: None, - } - } - - pub fn file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_unique_id = val.into(); - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn performer<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.performer = Some(val.into()); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = Some(val.into()); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let json = r#"{ - "file_id":"id", - "file_unique_id":"", - "duration":60, - "performer":"Performer", - "title":"Title", - "mime_type":"application/zip", - "file_size":123456, - "thumb":{ - "file_id":"id", - "file_unique_id":"", - "width":320, - "height":320, - "file_size":3452 - } - }"#; - let expected = Audio { - file_id: "id".to_string(), - file_unique_id: "".to_string(), - duration: 60, - performer: Some("Performer".to_string()), - title: Some("Title".to_string()), - mime_type: Some(serde_json::from_str("\"application/zip\"").unwrap()), - file_size: Some(123_456), - thumb: Some(PhotoSize { - file_id: "id".to_string(), - file_unique_id: "".to_string(), - width: 320, - height: 320, - file_size: Some(3452), - }), - }; - let actual = serde_json::from_str::<Audio>(&json).unwrap(); - assert_eq!(actual, expected) - } -} diff --git a/src/types/bot_command.rs b/src/types/bot_command.rs deleted file mode 100644 index d3116dff..00000000 --- a/src/types/bot_command.rs +++ /dev/null @@ -1,42 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a bot command. -/// -/// [The official docs](https://core.telegram.org/bots/api#botcommand). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct BotCommand { - /// Text of the command, 1-32 characters. - /// - /// Can contain only lowercase English letters, digits and underscores. - pub command: String, - - /// Description of the command, 3-256 characters. - pub description: String, -} - -impl BotCommand { - pub fn new<S1, S2>(command: S1, description: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { command: command.into(), description: description.into() } - } - - pub fn command<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.command = val.into(); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = val.into(); - self - } -} diff --git a/src/types/callback_game.rs b/src/types/callback_game.rs deleted file mode 100644 index 5b2d6e0f..00000000 --- a/src/types/callback_game.rs +++ /dev/null @@ -1,13 +0,0 @@ -/// A placeholder, currently holds no information. Use [@BotFather](https://t.me/botfather) to set up your game. -/// -/// [The official docs](https://core.telegram.org/bots/api#callbackgame). -use serde::{Deserialize, Serialize}; - -/// A placeholder, currently holds no information. -/// -/// Use [@Botfather] to set up your game. -/// -/// [@Botfather]: https://t.me/botfather -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct CallbackGame; diff --git a/src/types/callback_query.rs b/src/types/callback_query.rs deleted file mode 100644 index d8c1d07c..00000000 --- a/src/types/callback_query.rs +++ /dev/null @@ -1,158 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Message, User}; - -/// This object represents an incoming callback query from a callback button in -/// an [inline keyboard]. -/// -/// If the button that originated the query was attached to a message sent by -/// the bot, the field message will be present. If the button was attached to a -/// message sent via the bot (in [inline mode]), the field `inline_message_id` -/// will be present. Exactly one of the fields data or `game_short_name` will be -/// present. -/// -/// [The official docs](https://core.telegram.org/bots/api#callbackquery). -/// -/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating -/// [inline mode]: https://core.telegram.org/bots/api#inline-mode -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct CallbackQuery { - /// An unique identifier for this query. - pub id: String, - - /// A sender. - pub from: User, - - /// A message with the callback button that originated the query. Note that - /// message content and message date will not be available if the message - /// is too old. - pub message: Option<Message>, - - /// An identifier of the message sent via the bot in inline mode, that - /// originated the query. - pub inline_message_id: Option<String>, - - /// A global identifier, uniquely corresponding to the chat to which the - /// message with the callback button was sent. Useful for high scores in - /// [games]. - /// - /// [games]: https://core.telegram.org/bots/api#games - pub chat_instance: String, - - /// A data associated with the callback button. Be aware that a bad client - /// can send arbitrary data in this field. - pub data: Option<String>, - - /// A short name of a Game to be returned, serves as the unique identifier - /// for the game. - pub game_short_name: Option<String>, -} - -impl CallbackQuery { - pub fn new<S1, S2>(id: S1, from: User, chat_instance: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - id: id.into(), - from, - message: None, - inline_message_id: None, - chat_instance: chat_instance.into(), - data: None, - game_short_name: None, - } - } - - pub fn id<S>(mut self, id: S) -> Self - where - S: Into<String>, - { - self.id = id.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn message(mut self, val: Message) -> Self { - self.message = Some(val); - self - } - - pub fn inline_message_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.inline_message_id = Some(val.into()); - self - } - - pub fn chat_instance<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.chat_instance = val.into(); - self - } - - pub fn data<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.data = Some(val.into()); - self - } - - pub fn game_short_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.game_short_name = Some(val.into()); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let json = r#"{ - "id":"id", - "from":{ - "id":12345, - "is_bot":false, - "first_name":"firstName" - }, - "inline_message_id":"i_m_id", - "chat_instance":"123456", - "data":"some_data", - "game_short_name":"game_name" - }"#; - let expected = CallbackQuery { - id: "id".to_string(), - from: User { - id: 12345, - is_bot: false, - first_name: "firstName".to_string(), - last_name: None, - username: None, - language_code: None, - }, - chat_instance: "123456".to_string(), - message: None, - inline_message_id: Some("i_m_id".to_string()), - data: Some("some_data".to_string()), - game_short_name: Some("game_name".to_string()), - }; - let actual = serde_json::from_str::<CallbackQuery>(json).unwrap(); - assert_eq!(actual, expected); - } -} diff --git a/src/types/chat.rs b/src/types/chat.rs deleted file mode 100644 index 04c1bd49..00000000 --- a/src/types/chat.rs +++ /dev/null @@ -1,353 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{ChatPermissions, ChatPhoto, Message}; - -/// This object represents a chat. -/// -/// [The official docs](https://core.telegram.org/bots/api#chat). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Chat { - /// A unique identifier for this chat. This number may be greater than 32 - /// bits and some programming languages may have difficulty/silent defects - /// in interpreting it. But it is smaller than 52 bits, so a signed 64 bit - /// integer or double-precision float type are safe for storing this - /// identifier. - pub id: i64, - - #[serde(flatten)] - pub kind: ChatKind, - - /// A chat photo. Returned only in [`Bot::get_chat`]. - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub photo: Option<ChatPhoto>, -} - -impl Chat { - pub fn new(id: i64, kind: ChatKind) -> Self { - Self { id, kind, photo: None } - } - - pub fn id(mut self, val: i64) -> Self { - self.id = val; - self - } - - pub fn kind(mut self, val: ChatKind) -> Self { - self.kind = val; - self - } - - pub fn photo(mut self, val: ChatPhoto) -> Self { - self.photo = Some(val); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum ChatKind { - Public(ChatPublic), - Private(ChatPrivate), -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ChatPublic { - /// A title, for supergroups, channels and group chats. - pub title: Option<String>, - - #[serde(flatten)] - pub kind: PublicChatKind, - - /// A description, for groups, supergroups and channel chats. Returned - /// only in [`Bot::get_chat`]. - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub description: Option<String>, - - /// A chat invite link, for groups, supergroups and channel chats. Each - /// administrator in a chat generates their own invite links, so the - /// bot must first generate the link using - /// [`Bot::export_chat_invite_link`]. Returned only in - /// [`Bot::get_chat`]. - /// - /// [`Bot::export_chat_invite_link`]: - /// crate::Bot::export_chat_invite_link - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub invite_link: Option<String>, - - /// Pinned message, for groups, supergroups and channels. Returned only - /// in [`Bot::get_chat`]. - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub pinned_message: Option<Box<Message>>, -} - -impl ChatPublic { - pub fn new(kind: PublicChatKind) -> Self { - Self { title: None, kind, description: None, invite_link: None, pinned_message: None } - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = Some(val.into()); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = Some(val.into()); - self - } - - pub fn invite_link<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.invite_link = Some(val.into()); - self - } - - pub fn pinned_message(mut self, val: Message) -> Self { - self.pinned_message = Some(Box::new(val)); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ChatPrivate { - /// A dummy field. Used to ensure that the `type` field is equal to - /// `private`. - #[serde(rename = "type")] - #[serde(deserialize_with = "assert_private_field")] - pub type_: (), - - /// A username, for private chats, supergroups and channels if - /// available. - pub username: Option<String>, - - /// A first name of the other party in a private chat. - pub first_name: Option<String>, - - /// A last name of the other party in a private chat. - pub last_name: Option<String>, -} - -impl ChatPrivate { - pub fn new() -> Self { - Self::default() - } - - pub fn username<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.username = Some(val.into()); - self - } - - pub fn first_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.first_name = Some(val.into()); - self - } - - pub fn last_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.last_name = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[serde(tag = "type")] -#[non_exhaustive] -pub enum PublicChatKind { - Channel(PublicChatChannel), - Group(PublicChatGroup), - Supergroup(PublicChatSupergroup), -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PublicChatChannel { - /// A username, for private chats, supergroups and channels if available. - pub username: Option<String>, -} - -impl PublicChatChannel { - pub fn new() -> Self { - Self::default() - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PublicChatGroup { - /// A default chat member permissions, for groups and supergroups. Returned - /// only from [`Bot::get_chat`]. - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub permissions: Option<ChatPermissions>, -} - -impl PublicChatGroup { - pub fn new() -> Self { - Self::default() - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PublicChatSupergroup { - /// A username, for private chats, supergroups and channels if - /// available. - pub username: Option<String>, - - /// For supergroups, name of group sticker set. Returned only from - /// [`Bot::get_chat`]. - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub sticker_set_name: Option<String>, - - /// `true`, if the bot can change the group sticker set. Returned only - /// from [`Bot::get_chat`]. - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub can_set_sticker_set: Option<bool>, - - /// A default chat member permissions, for groups and supergroups. - /// Returned only from [`Bot::get_chat`]. - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub permissions: Option<ChatPermissions>, - - /// The minimum allowed delay between consecutive messages sent by each - /// unpriviledged user. Returned only from [`Bot::get_chat`]. - /// - /// [`Bot::get_chat`]: crate::Bot::get_chat - pub slow_mode_delay: Option<i32>, -} - -impl PublicChatSupergroup { - pub fn new() -> Self { - Self::default() - } -} - -struct PrivateChatKindVisitor; - -impl<'de> serde::de::Visitor<'de> for PrivateChatKindVisitor { - type Value = (); - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, r#"field equal to "private""#) - } - - fn visit_borrowed_str<E: serde::de::Error>(self, v: &'de str) -> Result<Self::Value, E> { - match v { - "private" => Ok(()), - _ => Err(E::invalid_value(serde::de::Unexpected::Str(v), &r#""private""#)), - } - } -} - -fn assert_private_field<'de, D>(des: D) -> Result<(), D::Error> -where - D: serde::Deserializer<'de>, -{ - des.deserialize_str(PrivateChatKindVisitor) -} - -impl Chat { - pub fn is_private(&self) -> bool { - matches!(self.kind, ChatKind::Private(_)) - } - pub fn is_group(&self) -> bool { - matches!(self.kind, ChatKind::Public(ChatPublic { kind: PublicChatKind::Group(_), .. })) - } - pub fn is_supergroup(&self) -> bool { - matches!( - self.kind, - ChatKind::Public(ChatPublic { kind: PublicChatKind::Supergroup(_), .. }) - ) - } - pub fn is_channel(&self) -> bool { - matches!(self.kind, ChatKind::Public(ChatPublic { kind: PublicChatKind::Channel(_), .. })) - } - - pub fn is_chat(&self) -> bool { - self.is_private() || self.is_group() || self.is_supergroup() - } -} - -#[cfg(test)] -mod tests { - use serde_json::from_str; - - use crate::types::*; - - #[test] - fn channel_de() { - let expected = Chat { - id: -1, - kind: ChatKind::Public(ChatPublic { - title: None, - kind: PublicChatKind::Channel(PublicChatChannel { - username: Some("channelname".into()), - }), - description: None, - invite_link: None, - pinned_message: None, - }), - photo: None, - }; - let actual = from_str(r#"{"id":-1,"type":"channel","username":"channelname"}"#).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn private_chat_de() { - assert_eq!( - Chat { - id: 0, - kind: ChatKind::Private(ChatPrivate { - type_: (), - username: Some("username".into()), - first_name: Some("Anon".into()), - last_name: None, - }), - photo: None, - }, - from_str(r#"{"id":0,"type":"private","username":"username","first_name":"Anon"}"#) - .unwrap() - ); - } - - #[test] - fn private_chat_de_wrong_type_field() { - assert!(from_str::<Chat>(r#"{"id":0,"type":"WRONG"}"#).is_err()); - } -} diff --git a/src/types/chat_action.rs b/src/types/chat_action.rs deleted file mode 100644 index 9c112a56..00000000 --- a/src/types/chat_action.rs +++ /dev/null @@ -1,17 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum ChatAction { - Typing, - UploadPhoto, - RecordVideo, - UploadVideo, - RecordAudio, - UploadAudio, - UploadDocument, - FindLocation, - RecordVideoNote, - UploadVideoNote, -} diff --git a/src/types/chat_id.rs b/src/types/chat_id.rs deleted file mode 100644 index f5723da0..00000000 --- a/src/types/chat_id.rs +++ /dev/null @@ -1,39 +0,0 @@ -use derive_more::{Display, From}; -use serde::{Deserialize, Serialize}; - -/// A unique identifier for the target chat or username of the target channel -/// (in the format `@channelusername`). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Display, From)] -#[serde(untagged)] -#[non_exhaustive] -pub enum ChatId { - /// A chat identifier. - #[display(fmt = "{}", _0)] - Id(i64), - - /// A channel username (in the format @channelusername). - #[display(fmt = "{}", _0)] - ChannelUsername(String), -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn chat_id_id_serialization() { - let expected_json = String::from(r#"123456"#); - let actual_json = serde_json::to_string(&ChatId::Id(123_456)).unwrap(); - - assert_eq!(expected_json, actual_json) - } - - #[test] - fn chat_id_channel_username_serialization() { - let expected_json = String::from(r#""@username""#); - let actual_json = - serde_json::to_string(&ChatId::ChannelUsername(String::from("@username"))).unwrap(); - - assert_eq!(expected_json, actual_json) - } -} diff --git a/src/types/chat_member.rs b/src/types/chat_member.rs deleted file mode 100644 index b8e38321..00000000 --- a/src/types/chat_member.rs +++ /dev/null @@ -1,262 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::User; - -// TODO: ChatMemberKind?... -/// This object contains information about one member of the chat. -/// -/// [The official docs](https://core.telegram.org/bots/api#chatmember). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ChatMember { - /// Information about the user. - pub user: User, - - /// The member's status in the chat. - pub status: ChatMemberStatus, - - /// Owner and administrators only. Custom title for this user - pub custom_title: Option<String>, - - /// Restricted and kicked only. Date when restrictions will be lifted for - /// this user, unix time. - pub until_date: Option<i32>, - - /// Administrators only. `true`, if the bot is allowed to edit - /// administrator privileges of that user. - pub can_be_edited: Option<bool>, - - /// Administrators only. `true`, if the administrator can change the chat - /// title, photo and other settings. - pub can_change_info: Option<bool>, - - /// Administrators only. `true`, if the administrator can post in the - /// channel, channels only. - pub can_post_messages: Option<bool>, - - /// Administrators only. `true`, if the administrator can edit messages of - /// other users and can pin messages, channels only. - pub can_edit_messages: Option<bool>, - - /// Administrators only. `true`, if the administrator can delete messages - /// of other users. - pub can_delete_messages: Option<bool>, - - /// Administrators only. `true`, if the administrator can invite new users - /// to the chat. - pub can_invite_users: Option<bool>, - - /// Administrators only. `true`, if the administrator can restrict, - /// ban or unban chat members. - pub can_restrict_members: Option<bool>, - - /// Administrators only. `true`, if the administrator can pin messages, - /// supergroups only. - pub can_pin_messages: Option<bool>, - - /// Administrators only. `true`, if the administrator can add new - /// administrators with a subset of his own privileges or demote - /// administrators that he has promoted, directly or indirectly (promoted - /// by administrators that were appointed by the user). - pub can_promote_members: Option<bool>, - - /// Restricted only. `true`, if the user can send text messages, - /// contacts, locations and venues. - pub can_send_messages: Option<bool>, - - /// Restricted only. `true`, if the user is allowed to send audios, - /// documents, photos, videos, video notes and voice notes. - pub can_send_media_messages: Option<bool>, - - /// Restricted only. `true`, if the user is allowed to send animations, - /// games, stickers and use inline bots. - pub can_send_other_messages: Option<bool>, - - /// Restricted only. `true`, if the user is allowed to add web page - /// previews to their messages. - pub can_add_web_page_previews: Option<bool>, -} - -impl ChatMember { - pub fn new(user: User, status: ChatMemberStatus) -> Self { - Self { - user, - status, - custom_title: None, - until_date: None, - can_be_edited: None, - can_change_info: None, - can_post_messages: None, - can_edit_messages: None, - can_delete_messages: None, - can_invite_users: None, - can_restrict_members: None, - can_pin_messages: None, - can_promote_members: None, - can_send_messages: None, - can_send_media_messages: None, - can_send_other_messages: None, - can_add_web_page_previews: None, - } - } - - pub fn user(mut self, val: User) -> Self { - self.user = val; - self - } - - pub fn status(mut self, val: ChatMemberStatus) -> Self { - self.status = val; - self - } - - pub fn custom_title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.custom_title = Some(val.into()); - self - } - - pub fn until_date(mut self, val: i32) -> Self { - self.until_date = Some(val); - self - } - - pub fn can_be_edited(mut self, val: bool) -> Self { - self.can_be_edited = Some(val); - self - } - - pub fn can_change_info(mut self, val: bool) -> Self { - self.can_change_info = Some(val); - self - } - - pub fn can_post_messages(mut self, val: bool) -> Self { - self.can_post_messages = Some(val); - self - } - - pub fn can_edit_messages(mut self, val: bool) -> Self { - self.can_edit_messages = Some(val); - self - } - - pub fn can_delete_messages(mut self, val: bool) -> Self { - self.can_delete_messages = Some(val); - self - } - - pub fn can_invite_users(mut self, val: bool) -> Self { - self.can_invite_users = Some(val); - self - } - - pub fn can_restrict_members(mut self, val: bool) -> Self { - self.can_restrict_members = Some(val); - self - } - - pub fn can_pin_messages(mut self, val: bool) -> Self { - self.can_pin_messages = Some(val); - self - } - - pub fn can_promote_members(mut self, val: bool) -> Self { - self.can_promote_members = Some(val); - self - } - - pub fn can_send_messages(mut self, val: bool) -> Self { - self.can_send_messages = Some(val); - self - } - - pub fn can_send_media_messages(mut self, val: bool) -> Self { - self.can_send_media_messages = Some(val); - self - } - - pub fn can_send_other_messages(mut self, val: bool) -> Self { - self.can_send_other_messages = Some(val); - self - } - - pub fn can_add_web_page_previews(mut self, val: bool) -> Self { - self.can_add_web_page_previews = Some(val); - self - } -} - -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum ChatMemberStatus { - Creator, - Administrator, - Member, - Restricted, - Left, - Kicked, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let json = r#"{ - "user":{ - "id":12345, - "is_bot":false, - "first_name":"firstName" - }, - "status":"creator", - "until_date":123456, - "can_be_edited":true, - "can_post_messages":true, - "can_edit_messages":true, - "can_delete_messages":true, - "can_restrict_members":true, - "can_promote_members":true, - "can_change_info":true, - "can_invite_users":true, - "can_pin_messages":true, - "is_member":true, - "can_send_messages":true, - "can_send_media_messages":true, - "can_send_polls":true, - "can_send_other_messages":true, - "can_add_web_page_previews":true - }"#; - let expected = ChatMember { - user: User { - id: 12345, - is_bot: false, - first_name: "firstName".to_string(), - last_name: None, - username: None, - language_code: None, - }, - status: ChatMemberStatus::Creator, - custom_title: None, - until_date: Some(123_456), - can_be_edited: Some(true), - can_change_info: Some(true), - can_post_messages: Some(true), - can_edit_messages: Some(true), - can_delete_messages: Some(true), - can_invite_users: Some(true), - can_restrict_members: Some(true), - can_pin_messages: Some(true), - can_promote_members: Some(true), - can_send_messages: Some(true), - can_send_media_messages: Some(true), - can_send_other_messages: Some(true), - can_add_web_page_previews: Some(true), - }; - let actual = serde_json::from_str::<ChatMember>(&json).unwrap(); - assert_eq!(actual, expected) - } -} diff --git a/src/types/chat_or_inline_message.rs b/src/types/chat_or_inline_message.rs deleted file mode 100644 index d02a522b..00000000 --- a/src/types/chat_or_inline_message.rs +++ /dev/null @@ -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 }, -} diff --git a/src/types/chat_permissions.rs b/src/types/chat_permissions.rs deleted file mode 100644 index d6db3b6a..00000000 --- a/src/types/chat_permissions.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Describes actions that a non-administrator user is allowed to take in a -/// chat. -/// -/// [The official docs](https://core.telegram.org/bots/api#chatpermissions). -#[serde_with_macros::skip_serializing_none] -#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ChatPermissions { - /// `true`, if the user is allowed to send text messages, contacts, - /// locations and venues. - pub can_send_messages: Option<bool>, - - /// `true`, if the user is allowed to send audios, documents, - /// photos, videos, video notes and voice notes, implies - /// `can_send_messages`. - pub can_send_media_messages: Option<bool>, - - /// `true`, if the user is allowed to send polls, implies - /// `can_send_messages`. - pub can_send_polls: Option<bool>, - - /// `true`, if the user is allowed to send animations, games, stickers and - /// use inline bots, implies `can_send_media_messages`. - pub can_send_other_messages: Option<bool>, - - /// `true`, if the user is allowed to add web page previews to - /// their messages, implies `can_send_media_messages`. - pub can_add_web_page_previews: Option<bool>, - - /// `true`, if the user is allowed to change the chat title, photo and - /// other settings. Ignored in public supergroups. - pub can_change_info: Option<bool>, - - /// `true`, if the user is allowed to invite new users to the chat. - pub can_invite_users: Option<bool>, - - /// `true`, if the user is allowed to pin messages. Ignored in public - /// supergroups. - pub can_pin_messages: Option<bool>, -} - -impl ChatPermissions { - pub fn new() -> Self { - Self::default() - } -} diff --git a/src/types/chat_photo.rs b/src/types/chat_photo.rs deleted file mode 100644 index 7f5ed87d..00000000 --- a/src/types/chat_photo.rs +++ /dev/null @@ -1,82 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a chat photo. -/// -/// [The official docs](https://core.telegram.org/bots/api#chatphoto). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ChatPhoto { - /// A file identifier of small (160x160) chat photo. This file_id can be - /// used only for photo download and only for as long as the photo is - /// not changed. - pub small_file_id: String, - - /// Unique file identifier of small (160x160) chat photo, which is supposed - /// to be the same over time and for different bots. Can't be used to - /// download or reuse the file. - pub small_file_unique_id: String, - - /// A file identifier of big (640x640) chat photo. This file_id can be used - /// only for photo download and only for as long as the photo is not - /// changed. - pub big_file_id: String, - - /// Unique file identifier of big (640x640) chat photo, which is supposed - /// to be the same over time and for different bots. Can't be used to - /// download or reuse the file. - pub big_file_unique_id: String, -} - -impl ChatPhoto { - pub fn new<S1, S2, S3, S4>( - small_file_id: S1, - small_file_unique_id: S2, - big_file_id: S3, - big_file_unique_id: S4, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - S4: Into<String>, - { - Self { - small_file_id: small_file_id.into(), - small_file_unique_id: small_file_unique_id.into(), - big_file_id: big_file_id.into(), - big_file_unique_id: big_file_unique_id.into(), - } - } - - pub fn small_file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.small_file_id = val.into(); - self - } - - pub fn small_file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.small_file_unique_id = val.into(); - self - } - - pub fn big_file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.big_file_id = val.into(); - self - } - - pub fn big_file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.big_file_unique_id = val.into(); - self - } -} diff --git a/src/types/chosen_inline_result.rs b/src/types/chosen_inline_result.rs deleted file mode 100644 index 18139ff0..00000000 --- a/src/types/chosen_inline_result.rs +++ /dev/null @@ -1,85 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Location, User}; - -/// Represents a [result] of an inline query that was chosen by the user and -/// sent to their chat partner. -/// -/// [The official docs](https://core.telegram.org/bots/api#choseninlineresult). -/// -/// [result]: https://core.telegram.org/bots/api#inlinequeryresult -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ChosenInlineResult { - /// The unique identifier for the result that was chosen. - pub result_id: String, - - /// The user that chose the result. - pub from: User, - - /// A sender location, only for bots that require user location. - pub location: Option<Location>, - - /// An identifier of the sent inline message. Available only if - /// there is an [inline keyboard] attached to the message. Will be also - /// received in [callback queries] and can be used to [edit] the message. - /// - /// [inline keyboard]: https://core.telegram.org/bots/api#inlinekeyboardmarkup - /// [callback queries]: https://core.telegram.org/bots/api#callbackquery - /// [edit]: https://core.telegram.org/bots/api#updating-messages - pub inline_message_id: Option<String>, - - /// The query that was used to obtain the result. - pub query: String, -} - -impl ChosenInlineResult { - pub fn new<S1, S2>(result_id: S1, from: User, query: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - result_id: result_id.into(), - from, - location: None, - inline_message_id: None, - query: query.into(), - } - } - - pub fn result_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.result_id = val.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn location<S>(mut self, val: Location) -> Self { - self.location = val.into(); - self - } - - pub fn inline_message_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.inline_message_id = Some(val.into()); - self - } - - pub fn query<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.query = val.into(); - self - } -} diff --git a/src/types/contact.rs b/src/types/contact.rs deleted file mode 100644 index 68a31b38..00000000 --- a/src/types/contact.rs +++ /dev/null @@ -1,79 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a phone contact. -/// -/// [The official docs](https://core.telegram.org/bots/api#contact). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Contact { - /// A contact's phone number. - pub phone_number: String, - - /// A contact's first name. - pub first_name: String, - - /// A contact's last name. - pub last_name: Option<String>, - - /// A contact's user identifier in Telegram. - pub user_id: Option<i32>, - - /// Additional data about the contact in the form of a [vCard]. - /// - /// [vCard]: https://en.wikipedia.org/wiki/VCard - pub vcard: Option<String>, -} - -impl Contact { - pub fn new<S1, S2>(phone_number: S1, first_name: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - phone_number: phone_number.into(), - first_name: first_name.into(), - last_name: None, - user_id: None, - vcard: None, - } - } - - pub fn phone_number<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.phone_number = val.into(); - self - } - - pub fn first_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.first_name = val.into(); - self - } - - pub fn last_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.last_name = Some(val.into()); - self - } - - pub fn user_id(mut self, val: i32) -> Self { - self.user_id = Some(val); - self - } - - pub fn vcard<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.vcard = Some(val.into()); - self - } -} diff --git a/src/types/dice.rs b/src/types/dice.rs deleted file mode 100644 index 1c221166..00000000 --- a/src/types/dice.rs +++ /dev/null @@ -1,38 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::DiceEmoji; - -/// This object represents an animated emoji that displays a random value. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Dice { - /// Emoji on which the dice throw animation is based. - emoji: DiceEmoji, - - /// Value of the dice. - /// - /// 1-6 for [`DiceEmoji::Dice`] and [`DiceEmoji::Darts`], 1-5 for - /// [`DiceEmoji::Basketball`]. - /// - /// [`DiceEmoji::Dice`]: crate::types::DiceEmoji::Dice - /// [`DiceEmoji::Darts`]:crate::types::DiceEmoji::Darts - /// [`DiceEmoji::Basketball`]:crate::types::DiceEmoji::Basketball - value: i32, -} - -impl Dice { - pub fn new(emoji: DiceEmoji, value: i32) -> Self { - Self { emoji, value } - } - - pub fn emoji(mut self, val: DiceEmoji) -> Self { - self.emoji = val; - self - } - - pub fn value<S>(mut self, val: i32) -> Self { - self.value = val; - self - } -} diff --git a/src/types/dice_emoji.rs b/src/types/dice_emoji.rs deleted file mode 100644 index e503c7fa..00000000 --- a/src/types/dice_emoji.rs +++ /dev/null @@ -1,16 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] -pub enum DiceEmoji { - /// Values from 1-6. Defaults to this variant. - #[serde(rename = "🎲")] - Dice, - - /// Values from 1-6. - #[serde(rename = "🎯")] - Darts, - - /// Values from 1-5. - #[serde(rename = "🏀")] - Basketball, -} diff --git a/src/types/document.rs b/src/types/document.rs deleted file mode 100644 index 8a409704..00000000 --- a/src/types/document.rs +++ /dev/null @@ -1,92 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{MimeWrapper, PhotoSize}; - -/// This object represents a general file (as opposed to [photos], [voice -/// messages] and [audio files]). -/// -/// [The official docs](https://core.telegram.org/bots/api#document). -/// -/// [photos]: https://core.telegram.org/bots/api#photosize -/// [voice messages]: https://core.telegram.org/bots/api#voice -/// [audio files]: https://core.telegram.org/bots/api#audio -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Document { - /// An identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// A document thumbnail as defined by a sender. - pub thumb: Option<PhotoSize>, - - /// An original filename as defined by a sender. - pub file_name: Option<String>, - - /// A MIME type of the file as defined by a sender. - pub mime_type: Option<MimeWrapper>, - - /// A size of a file. - pub file_size: Option<u32>, -} - -impl Document { - pub fn new<S1, S2>(file_id: S1, file_unique_id: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - thumb: None, - file_name: None, - mime_type: None, - file_size: None, - } - } - - pub fn file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_unique_id = val.into(); - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn file_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_name = Some(val.into()); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/encrypted_credentials.rs b/src/types/encrypted_credentials.rs deleted file mode 100644 index 543e251c..00000000 --- a/src/types/encrypted_credentials.rs +++ /dev/null @@ -1,94 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Contains data required for decrypting and authenticating -/// [`EncryptedPassportElement`]. -/// -/// See the [Telegram Passport Documentation] for a complete description of the -/// data decryption and authentication processes. -/// -/// [The official docs](https://core.telegram.org/bots/api#encryptedcredentials). -/// -/// [`EncryptedPassportElement`]: -/// crate::types::EncryptedPassportElement -/// [Telegram Passport Documentation]: https://core.telegram.org/passport#receiving-information -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedCredentials { - /// Base64-encoded encrypted JSON-serialized data with unique user's - /// payload, data hashes and secrets required for - /// [`EncryptedPassportElement`] decryption and authentication. - /// - /// [`EncryptedPassportElement`]: - /// crate::types::EncryptedPassportElement - pub data: String, // TODO: check base64 type - - /// Base64-encoded data hash for data authentication. - pub hash: String, - - /// A base64-encoded secret, encrypted with the bot's public RSA key, - /// required for data decryption. - pub secret: String, -} - -impl EncryptedCredentials { - pub fn new<S1, S2, S3>(data: S1, hash: S2, secret: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { data: data.into(), hash: hash.into(), secret: secret.into() } - } - - pub fn data<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.data = val.into(); - self - } - - pub fn hash<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.hash = val.into(); - self - } - - pub fn secret<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.secret = val.into(); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn must_serialize_encrypted_credentials_to_json() { - // given - let expected_json = r#" - { - "data":"someData", - "hash":"1122", - "secret":"secret" - }"# - .replace("\n", "") - .replace(" ", ""); - let encrypted_credentials = EncryptedCredentials { - data: "someData".to_string(), - hash: "1122".to_string(), - secret: "secret".to_string(), - }; - // when - let actual_json = serde_json::to_string(&encrypted_credentials).unwrap(); - //then - assert_eq!(actual_json, expected_json) - } -} diff --git a/src/types/encrypted_passport_element.rs b/src/types/encrypted_passport_element.rs deleted file mode 100644 index 72c0884a..00000000 --- a/src/types/encrypted_passport_element.rs +++ /dev/null @@ -1,794 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use super::PassportFile; - -/// Contains information about documents or other Telegram Passport elements -/// shared with the bot by the user. -/// -/// [The official docs](https://core.telegram.org/bots/api#encryptedpassportelement). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElement { - /// Base64-encoded element hash for using in - /// [`PassportElementErrorKind::Unspecified`]. - /// - /// [`PassportElementErrorKind::Unspecified`]: - /// crate::types::PassportElementErrorKind::Unspecified - pub hash: String, - - #[serde(flatten)] - pub kind: EncryptedPassportElementKind, -} - -impl EncryptedPassportElement { - pub fn new<S>(hash: S, kind: EncryptedPassportElementKind) -> Self - where - S: Into<String>, - { - Self { hash: hash.into(), kind } - } - - pub fn hash<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.hash = val.into(); - self - } - - pub fn kind(mut self, val: EncryptedPassportElementKind) -> Self { - self.kind = val; - self - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[allow(clippy::large_enum_variant)] -#[non_exhaustive] -pub enum EncryptedPassportElementKind { - PersonalDetails(EncryptedPassportElementPersonalDetails), - Passport(EncryptedPassportElementPassport), - DriverLicense(EncryptedPassportElementDriverLicense), - IdentityCard(EncryptedPassportElementIdentityCard), - InternalPassport(EncryptedPassportElementInternalPassport), - Address(EncryptedPassportElementAddress), - UtilityBill(EncryptedPassportElementUtilityBill), - BankStatement(EncryptedPassportElementBankStatement), - RentalAgreement(EncryptedPassportElementRentalAgreement), - PassportRegistration(EncryptedPassportElementPassportRegistration), - EncryptedPassportElement(EncryptedPassportElementTemporaryRegistration), - PhoneNumber(EncryptedPassportElementPhoneNumber), - Email(EncryptedPassportElementEmail), -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementPersonalDetails { - /// Base64-encoded encrypted Telegram Passport element data provided - /// by the user, available for `personal_details`, `passport`, - /// `driver_license`, `identity_card`, `internal_passport` and - /// `address` types. Can be decrypted and verified using the - /// accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub data: String, -} - -impl EncryptedPassportElementPersonalDetails { - pub fn new<S>(data: S) -> Self - where - S: Into<String>, - { - Self { data: data.into() } - } - - pub fn data<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.data = val.into(); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementPassport { - /// Base64-encoded encrypted Telegram Passport element data provided - /// by the user, available for `personal_details`, `passport`, - /// `driver_license`, `identity_card`, `internal_passport` and - /// `address` types. Can be decrypted and verified using the - /// accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub data: String, - - /// Encrypted file with the front side of the document, provided by the - /// user. Available for `passport`, `driver_license`, `identity_card` - /// and `internal_passport`. The file can be decrypted and verified - /// using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub front_side: PassportFile, - - /// Encrypted file with the selfie of the user holding a document, - /// provided by the user; available for `passport`, `driver_license`, - /// `identity_card` and `internal_passport`. The file can be decrypted - /// and verified using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub selfie: PassportFile, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option<Vec<PassportFile>>, -} - -impl EncryptedPassportElementPassport { - pub fn new<S>(data: S, front_side: PassportFile, selfie: PassportFile) -> Self - where - S: Into<String>, - { - Self { data: data.into(), front_side, selfie, translation: None } - } - - pub fn data<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - - pub fn translation<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementDriverLicense { - /// Base64-encoded encrypted Telegram Passport element data provided - /// by the user, available for `personal_details`, `passport`, - /// `driver_license`, `identity_card`, `internal_passport` and - /// `address` types. Can be decrypted and verified using the - /// accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub data: String, - - /// Encrypted file with the front side of the document, provided by the - /// user. Available for `passport`, `driver_license`, `identity_card` - /// and `internal_passport`. The file can be decrypted and verified - /// using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub front_side: PassportFile, - - /// Encrypted file with the reverse side of the document, provided by - /// the user. Available for `driver_license` and `identity_card`. The - /// file can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub reverse_side: PassportFile, - - /// Encrypted file with the selfie of the user holding a document, - /// provided by the user; available for `passport`, `driver_license`, - /// `identity_card` and `internal_passport`. The file can be decrypted - /// and verified using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub selfie: PassportFile, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option<Vec<PassportFile>>, -} - -impl EncryptedPassportElementDriverLicense { - pub fn new<S>( - data: S, - front_side: PassportFile, - reverse_side: PassportFile, - selfie: PassportFile, - ) -> Self - where - S: Into<String>, - { - Self { data: data.into(), front_side, reverse_side, selfie, translation: None } - } - - pub fn data<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn reverse_side(mut self, val: PassportFile) -> Self { - self.reverse_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - pub fn translation<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementIdentityCard { - /// Base64-encoded encrypted Telegram Passport element data provided - /// by the user, available for `personal_details`, `passport`, - /// `driver_license`, `identity_card`, `internal_passport` and - /// `address` types. Can be decrypted and verified using the - /// accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub data: String, - - /// Encrypted file with the front side of the document, provided by the - /// user. Available for `passport`, `driver_license`, `identity_card` - /// and `internal_passport`. The file can be decrypted and verified - /// using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub front_side: PassportFile, - - /// Encrypted file with the reverse side of the document, provided by - /// the user. Available for `driver_license` and `identity_card`. The - /// file can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub reverse_side: PassportFile, - - /// Encrypted file with the selfie of the user holding a document, - /// provided by the user; available for `passport`, `driver_license`, - /// `identity_card` and `internal_passport`. The file can be decrypted - /// and verified using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub selfie: PassportFile, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option<Vec<PassportFile>>, -} - -impl EncryptedPassportElementIdentityCard { - pub fn new<S>( - data: S, - front_side: PassportFile, - reverse_side: PassportFile, - selfie: PassportFile, - ) -> Self - where - S: Into<String>, - { - Self { data: data.into(), front_side, reverse_side, selfie, translation: None } - } - - pub fn data<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn reverse_side(mut self, val: PassportFile) -> Self { - self.reverse_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - pub fn translation<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementInternalPassport { - /// Base64-encoded encrypted Telegram Passport element data provided - /// by the user, available for `personal_details`, `passport`, - /// `driver_license`, `identity_card`, `internal_passport` and - /// `address` types. Can be decrypted and verified using the - /// accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub data: String, - - /// Encrypted file with the front side of the document, provided by the - /// user. Available for `passport`, `driver_license`, `identity_card` - /// and `internal_passport`. The file can be decrypted and verified - /// using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub front_side: PassportFile, - - /// Encrypted file with the selfie of the user holding a document, - /// provided by the user; available for `passport`, `driver_license`, - /// `identity_card` and `internal_passport`. The file can be decrypted - /// and verified using the accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub selfie: PassportFile, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option<Vec<PassportFile>>, -} - -impl EncryptedPassportElementInternalPassport { - pub fn new<S>(data: S, front_side: PassportFile, selfie: PassportFile) -> Self - where - S: Into<String>, - { - Self { data: data.into(), front_side, selfie, translation: None } - } - - pub fn data<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.data = val.into(); - self - } - - pub fn front_side(mut self, val: PassportFile) -> Self { - self.front_side = val; - self - } - - pub fn selfie(mut self, val: PassportFile) -> Self { - self.selfie = val; - self - } - - pub fn translation<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementAddress { - /// Base64-encoded encrypted Telegram Passport element data provided - /// by the user, available for `personal_details`, `passport`, - /// `driver_license`, `identity_card`, `internal_passport` and - /// `address` types. Can be decrypted and verified using the - /// accompanying [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub data: String, -} - -impl EncryptedPassportElementAddress { - pub fn new<S>(data: S) -> Self - where - S: Into<String>, - { - Self { data: data.into() } - } - - pub fn data<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.data = val.into(); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementUtilityBill { - /// Array of encrypted files with documents provided by the user, - /// available for `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub files: Vec<PassportFile>, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option<Vec<PassportFile>>, -} - -impl EncryptedPassportElementUtilityBill { - pub fn new<F>(files: F) -> Self - where - F: Into<Vec<PassportFile>>, - { - Self { files: files.into(), translation: None } - } - - pub fn files<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.files = val.into(); - self - } - - pub fn translation<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementBankStatement { - /// Array of encrypted files with documents provided by the user, - /// available for `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub files: Vec<PassportFile>, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option<Vec<PassportFile>>, -} - -impl EncryptedPassportElementBankStatement { - pub fn new<F>(files: F) -> Self - where - F: Into<Vec<PassportFile>>, - { - Self { files: files.into(), translation: None } - } - - pub fn files<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.files = val.into(); - self - } - - pub fn translation<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementRentalAgreement { - /// Array of encrypted files with documents provided by the user, - /// available for `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub files: Vec<PassportFile>, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option<Vec<PassportFile>>, -} - -impl EncryptedPassportElementRentalAgreement { - pub fn new<F>(files: F) -> Self - where - F: Into<Vec<PassportFile>>, - { - Self { files: files.into(), translation: None } - } - - pub fn files<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.files = val.into(); - self - } - - pub fn translation<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementPassportRegistration { - /// Array of encrypted files with documents provided by the user, - /// available for `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub files: Vec<PassportFile>, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option<Vec<PassportFile>>, -} - -impl EncryptedPassportElementPassportRegistration { - pub fn new<F>(files: F) -> Self - where - F: Into<Vec<PassportFile>>, - { - Self { files: files.into(), translation: None } - } - - pub fn files<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.files = val.into(); - self - } - - pub fn translation<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementTemporaryRegistration { - /// Array of encrypted files with documents provided by the user, - /// available for `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub files: Vec<PassportFile>, - - /// Array of encrypted files with translated versions of documents - /// provided by the user. Available if requested for `passport`, - /// `driver_license`, `identity_card`, `internal_passport`, - /// `utility_bill`, `bank_statement`, `rental_agreement`, - /// `passport_registration` and `temporary_registration` types. Files - /// can be decrypted and verified using the accompanying - /// [`EncryptedCredentials`]. - /// - /// [`EncryptedCredentials`]: - /// crate::types::EncryptedCredentials - pub translation: Option<Vec<PassportFile>>, -} - -impl EncryptedPassportElementTemporaryRegistration { - pub fn new<F>(files: F) -> Self - where - F: Into<Vec<PassportFile>>, - { - Self { files: files.into(), translation: None } - } - - pub fn files<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.files = val.into(); - self - } - - pub fn translation<P>(mut self, val: P) -> Self - where - P: Into<Vec<PassportFile>>, - { - self.translation = Some(val.into()); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementPhoneNumber { - /// User's verified phone number, available only for `phone_number` - /// type. - pub phone_number: String, -} - -impl EncryptedPassportElementPhoneNumber { - pub fn new<S>(phone_number: S) -> Self - where - S: Into<String>, - { - Self { phone_number: phone_number.into() } - } - - pub fn phone_number<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.phone_number = val.into(); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct EncryptedPassportElementEmail { - /// User's verified email address, available only for `email` type. - pub email: String, -} - -impl EncryptedPassportElementEmail { - pub fn new<S>(email: S) -> Self - where - S: Into<String>, - { - Self { email: email.into() } - } - - pub fn email<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.email = val.into(); - self - } -} diff --git a/src/types/file.rs b/src/types/file.rs deleted file mode 100644 index 721f61c0..00000000 --- a/src/types/file.rs +++ /dev/null @@ -1,75 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a file ready to be downloaded. -/// -/// The file can be downloaded via the link `https://api.telegram.org/file/bot<token>/<file_path>`. -/// It is guaranteed that the link will be valid for at least 1 hour. When the -/// link expires, a new one can be requested by calling [`Bot::get_file`]. -/// -/// [The official docs](https://core.telegram.org/bots/api#file). -/// -/// [`Bot::get_file`]: crate::Bot::get_file -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct File { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// File size, if known. - pub file_size: u32, - - // TODO: chacge "Use ..." to use bot.download... - /// File path. Use `https://api.telegram.org/file/bot<token>/<file_path>` - /// to get the file. - pub file_path: String, -} - -impl File { - pub fn new<S1, S2, S3>(file_id: S1, file_unique_id: S2, file_size: u32, file_path: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - file_size, - file_path: file_path.into(), - } - } - - pub fn file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_unique_id = val.into(); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = val; - self - } - - pub fn file_path<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } -} diff --git a/src/types/force_reply.rs b/src/types/force_reply.rs deleted file mode 100644 index a6a74408..00000000 --- a/src/types/force_reply.rs +++ /dev/null @@ -1,41 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::True; - -/// Upon receiving a message with this object, Telegram clients will display a -/// reply interface to the user (act as if the user has selected the bot‘s -/// message and tapped ’Reply'). -/// -/// This can be extremely useful if you want to create user-friendly -/// step-by-step interfaces without having to sacrifice [privacy mode]. -/// -/// [The official docs](https://core.telegram.org/bots/api#forcereply). -/// -/// [privacy mode]: https://core.telegram.org/bots#privacy-mode -#[serde_with_macros::skip_serializing_none] -#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ForceReply { - /// Shows reply interface to the user, as if they manually selected the - /// bot‘s message and tapped ’Reply'. - pub force_reply: True, - - /// Use this parameter if you want to force reply from specific users only. - /// Targets: 1) users that are `@mentioned` in the text of the - /// [`Message`] object; 2) if the bot's message is a reply - /// (has reply_to_message_id), sender of the original message. - /// - /// [`Message`]: crate::types::Message - pub selective: Option<bool>, -} - -impl ForceReply { - pub fn new() -> Self { - Self::default() - } - - pub fn selective(mut self, val: bool) -> Self { - self.selective = Some(val); - self - } -} diff --git a/src/types/game.rs b/src/types/game.rs deleted file mode 100644 index 52fe01b8..00000000 --- a/src/types/game.rs +++ /dev/null @@ -1,106 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Animation, MessageEntity, PhotoSize}; - -/// This object represents a game. -/// -/// Use [@Botfather] to create and edit games, their short names will act as -/// unique identifiers. -/// -/// [@Botfather]: https://t.me/botfather -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Game { - /// Title of the game. - pub title: String, - - /// Description of the game. - pub description: String, - - /// Photo that will be displayed in the game message in chats. - pub photo: Vec<PhotoSize>, - - /// Brief description of the game or high scores included in the game - /// message. Can be automatically edited to include current high scores - /// for the game when the bot calls [`Bot::set_game_score`], or manually - /// edited using [`Bot::edit_message_text`]. 0-4096 characters. - /// - /// [`Bot::set_game_score`]: crate::Bot::set_game_score - /// - /// [`Bot::edit_message_text`]: crate::Bot::edit_message_text - pub text: Option<String>, - - /// Special entities that appear in text, such as usernames, URLs, bot - /// commands, etc. - pub text_entities: Option<Vec<MessageEntity>>, - - /// Animation that will be displayed in the game message in chats. Upload - /// via [@Botfather]. - /// - /// [@Botfather]: https://t.me/botfather - pub animation: Option<Animation>, -} - -impl Game { - pub fn new<S1, S2, P>(title: S1, description: S2, photo: P) -> Self - where - S1: Into<String>, - S2: Into<String>, - P: Into<Vec<PhotoSize>>, - { - Self { - title: title.into(), - description: description.into(), - photo: photo.into(), - text: None, - text_entities: None, - animation: None, - } - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = val.into(); - self - } - - pub fn photo<P>(mut self, val: P) -> Self - where - P: Into<Vec<PhotoSize>>, - { - self.photo = val.into(); - self - } - - pub fn text<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.text = Some(val.into()); - self - } - - pub fn text_entities<T>(mut self, val: T) -> Self - where - T: Into<Vec<MessageEntity>>, - { - self.text_entities = Some(val.into()); - self - } - - pub fn animation(mut self, val: Animation) -> Self { - self.animation = Some(val); - self - } -} diff --git a/src/types/game_high_score.rs b/src/types/game_high_score.rs deleted file mode 100644 index f713b18e..00000000 --- a/src/types/game_high_score.rs +++ /dev/null @@ -1,40 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::user::User; - -/// This object represents one row of the high scores table for a game. -/// -/// [The official docs](https://core.telegram.org/bots/api#gamehighscore). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct GameHighScore { - /// Position in high score table for the game. - pub position: u32, - - /// User. - pub user: User, - - /// Score. - pub score: u32, -} - -impl GameHighScore { - pub fn new(position: u32, user: User, score: u32) -> Self { - Self { position, user, score } - } - - pub fn position(mut self, val: u32) -> Self { - self.position = val; - self - } - - pub fn user(mut self, val: User) -> Self { - self.user = val; - self - } - - pub fn score(mut self, val: u32) -> Self { - self.score = val; - self - } -} diff --git a/src/types/inline_keyboard_button.rs b/src/types/inline_keyboard_button.rs deleted file mode 100644 index 12497463..00000000 --- a/src/types/inline_keyboard_button.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::types::{CallbackGame, LoginUrl}; -use serde::{Deserialize, Serialize}; - -/// This object represents one button of an inline keyboard. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinekeyboardbutton). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineKeyboardButton { - /// Label text on the button. - pub text: String, - - #[serde(flatten)] - pub kind: InlineKeyboardButtonKind, -} - -impl InlineKeyboardButton { - pub fn new<S>(text: S, kind: InlineKeyboardButtonKind) -> Self - where - S: Into<String>, - { - Self { text: text.into(), kind } - } - - pub fn text<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.text = val.into(); - self - } - - pub fn kind(mut self, val: InlineKeyboardButtonKind) -> Self { - self.kind = val; - self - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum InlineKeyboardButtonKind { - /// HTTP or tg:// url to be opened when button is pressed. - Url(String), - - /// An HTTP URL used to automatically authorize the user. Can be used as a - /// replacement for the [Telegram Login Widget](). - /// - /// [Telegram Login Widget]: https://core.telegram.org/widgets/login - LoginUrl(LoginUrl), - - /// Data to be sent in a [`CallbackQuery`] to the bot when button is - /// pressed, 1-64 bytes. - /// - /// [`CallbackQuery`]: crate::types::CallbackQuery - CallbackData(String), - - /// If set, pressing the button will prompt the user to select one of their - /// chats, open that chat and insert the bot‘s username and the specified - /// inline query in the input field. Can be empty, in which case just the - /// bot’s username will be inserted. - /// - /// Note: This offers an easy way for users to start using your bot in - /// [inline mode] when they are currently in a private chat with it. - /// Especially useful when combined with [switch_pm…] actions – in this - /// case the user will be automatically returned to the chat they - /// switched from, skipping the chat selection screen. - /// - /// [inline mode]: https://core.telegram.org/bots/inline - /// [switch_pm…]: https://core.telegram.org/bots/api#answerinlinequery - SwitchInlineQuery(String), - - /// If set, pressing the button will insert the bot‘s username and the - /// specified inline query in the current chat's input field. - /// Can be empty, in which case only the bot’s username will be - /// inserted. - /// - ///This offers a quick way for the user to open your bot in inline mode in - /// the same chat – good for selecting something from multiple options. - SwitchInlineQueryCurrentChat(String), - - /// Description of the game that will be launched when the user presses the - /// button. - /// - /// ## Note - /// This type of button **must** always be the first button in the first - /// row. - CallbackGame(CallbackGame), - - /// Specify True, to send a [Pay button]. - /// - /// ## Note - /// This type of button **must** always be the first button in the first - /// row. - /// - /// [Pay button]: https://core.telegram.org/bots/api#payments - Pay(bool), -} - -/// Build buttons. -/// -/// # Examples -/// ``` -/// use teloxide::types::InlineKeyboardButton; -/// -/// let url_button = InlineKeyboardButton::url("Text".to_string(), "http://url.com".to_string()); -/// ``` -impl InlineKeyboardButton { - pub fn url(text: String, url: String) -> InlineKeyboardButton { - InlineKeyboardButton { text, kind: InlineKeyboardButtonKind::Url(url) } - } - - pub fn callback(text: String, callback_data: String) -> InlineKeyboardButton { - InlineKeyboardButton { text, kind: InlineKeyboardButtonKind::CallbackData(callback_data) } - } - - pub fn switch_inline_query(text: String, switch_inline_query: String) -> InlineKeyboardButton { - InlineKeyboardButton { - text, - kind: InlineKeyboardButtonKind::SwitchInlineQuery(switch_inline_query), - } - } - - pub fn switch_inline_query_current_chat( - text: String, - switch_inline_query_current_chat: String, - ) -> InlineKeyboardButton { - InlineKeyboardButton { - text, - kind: InlineKeyboardButtonKind::SwitchInlineQueryCurrentChat( - switch_inline_query_current_chat, - ), - } - } -} diff --git a/src/types/inline_keyboard_markup.rs b/src/types/inline_keyboard_markup.rs deleted file mode 100644 index b6884a96..00000000 --- a/src/types/inline_keyboard_markup.rs +++ /dev/null @@ -1,109 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::InlineKeyboardButton; - -/// This object represents an [inline keyboard] that appears right next to the -/// message it belongs to. -/// -/// *Note*: This will only work in Telegram versions released after 9 April, -/// 2016. Older clients will display unsupported message. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinekeyboardmarkup). -/// -/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Default)] -#[non_exhaustive] -pub struct InlineKeyboardMarkup { - /// Array of button rows, each represented by an array of - /// [`InlineKeyboardButton`] objects. - /// - /// [`InlineKeyboardButton`]: crate::types::InlineKeyboardButton - pub inline_keyboard: Vec<Vec<InlineKeyboardButton>>, -} - -/// Build `InlineKeyboardMarkup`. -/// -/// # Examples -/// ``` -/// use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup}; -/// -/// let url_button = InlineKeyboardButton::url("text".to_string(), "http://url.com".to_string()); -/// let keyboard = InlineKeyboardMarkup::default().append_row(vec![url_button]); -/// ``` -impl InlineKeyboardMarkup { - pub fn new<I1, I2>(inline_keyboard: I1) -> Self - where - I1: Into<Vec<I2>>, - I2: Into<Vec<InlineKeyboardButton>>, - { - Self { inline_keyboard: inline_keyboard.into().into_iter().map(Into::into).collect() } - } - - pub fn inline_keyboard<I1, I2>(mut self, val: I1) -> Self - where - I1: Into<Vec<I2>>, - I2: Into<Vec<InlineKeyboardButton>>, - { - self.inline_keyboard = val.into().into_iter().map(Into::into).collect(); - self - } - - pub fn append_row(mut self, buttons: Vec<InlineKeyboardButton>) -> Self { - self.inline_keyboard.push(buttons); - self - } - - pub fn append_to_row(mut self, button: InlineKeyboardButton, index: usize) -> Self { - match self.inline_keyboard.get_mut(index) { - Some(buttons) => buttons.push(button), - None => self.inline_keyboard.push(vec![button]), - }; - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn append_row() { - let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); - let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); - - let markup = - InlineKeyboardMarkup::default().append_row(vec![button1.clone(), button2.clone()]); - - let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1, button2]] }; - - assert_eq!(markup, expected); - } - - #[test] - fn append_to_row_existent_row() { - let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); - let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); - - let markup = InlineKeyboardMarkup::default() - .append_row(vec![button1.clone()]) - .append_to_row(button2.clone(), 0); - - let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1, button2]] }; - - assert_eq!(markup, expected); - } - - #[test] - fn append_to_row_nonexistent_row() { - let button1 = InlineKeyboardButton::url("text 1".to_string(), "url 1".to_string()); - let button2 = InlineKeyboardButton::url("text 2".to_string(), "url 2".to_string()); - - let markup = InlineKeyboardMarkup::default() - .append_row(vec![button1.clone()]) - .append_to_row(button2.clone(), 1); - - let expected = InlineKeyboardMarkup { inline_keyboard: vec![vec![button1], vec![button2]] }; - - assert_eq!(markup, expected); - } -} diff --git a/src/types/inline_query.rs b/src/types/inline_query.rs deleted file mode 100644 index a1c15fc8..00000000 --- a/src/types/inline_query.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Location, User}; - -/// This object represents an incoming inline query. -/// -/// When the user sends an empty query, your bot could return some default or -/// trending results. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequery). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQuery { - /// Unique identifier for this query. - pub id: String, - - /// Sender. - pub from: User, - - /// Sender location, only for bots that request user location. - pub location: Option<Location>, - - /// Text of the query (up to 512 characters). - pub query: String, - - /// Offset of the results to be returned, can be controlled by the bot. - pub offset: String, -} - -impl InlineQuery { - pub fn new<S1, S2, S3>(id: S1, from: User, query: S2, offset: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { id: id.into(), from, location: None, query: query.into(), offset: offset.into() } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn location(mut self, val: Location) -> Self { - self.location = Some(val); - self - } - - pub fn query<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.query = val.into(); - self - } - - pub fn offset<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.offset = val.into(); - self - } -} diff --git a/src/types/inline_query_result.rs b/src/types/inline_query_result.rs deleted file mode 100644 index cb9976de..00000000 --- a/src/types/inline_query_result.rs +++ /dev/null @@ -1,102 +0,0 @@ -#![allow(clippy::large_enum_variant)] - -use derive_more::From; -use serde::{Deserialize, Serialize}; - -use crate::types::{ - InlineQueryResultArticle, InlineQueryResultAudio, InlineQueryResultCachedAudio, - InlineQueryResultCachedDocument, InlineQueryResultCachedGif, InlineQueryResultCachedMpeg4Gif, - InlineQueryResultCachedPhoto, InlineQueryResultCachedSticker, InlineQueryResultCachedVideo, - InlineQueryResultCachedVoice, InlineQueryResultContact, InlineQueryResultDocument, - InlineQueryResultGame, InlineQueryResultGif, InlineQueryResultLocation, - InlineQueryResultMpeg4Gif, InlineQueryResultPhoto, InlineQueryResultVenue, - InlineQueryResultVideo, InlineQueryResultVoice, -}; - -/// This object represents one result of an inline query. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresult). -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, From)] -#[serde(tag = "type")] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum InlineQueryResult { - #[serde(rename = "audio")] - CachedAudio(InlineQueryResultCachedAudio), - #[serde(rename = "document")] - CachedDocument(InlineQueryResultCachedDocument), - #[serde(rename = "gif")] - CachedGif(InlineQueryResultCachedGif), - #[serde(rename = "mpeg4_gif")] - CachedMpeg4Gif(InlineQueryResultCachedMpeg4Gif), - #[serde(rename = "photo")] - CachedPhoto(InlineQueryResultCachedPhoto), - #[serde(rename = "sticker")] - CachedSticker(InlineQueryResultCachedSticker), - #[serde(rename = "video")] - CachedVideo(InlineQueryResultCachedVideo), - #[serde(rename = "voice")] - CachedVoice(InlineQueryResultCachedVoice), - - Article(InlineQueryResultArticle), - Audio(InlineQueryResultAudio), - Contact(InlineQueryResultContact), - Game(InlineQueryResultGame), - Document(InlineQueryResultDocument), - Gif(InlineQueryResultGif), - Location(InlineQueryResultLocation), - #[serde(rename = "mpeg4_gif")] - Mpeg4Gif(InlineQueryResultMpeg4Gif), - Photo(InlineQueryResultPhoto), - Venue(InlineQueryResultVenue), - Video(InlineQueryResultVideo), - Voice(InlineQueryResultVoice), -} - -#[cfg(test)] -mod tests { - use crate::types::{ - inline_keyboard_markup::InlineKeyboardMarkup, parse_mode::ParseMode, InlineQueryResult, - InlineQueryResultCachedAudio, InputMessageContent, InputMessageContentText, - }; - - #[test] - fn cached_audio_min_serialize() { - let structure = InlineQueryResult::CachedAudio(InlineQueryResultCachedAudio { - id: String::from("id"), - audio_file_id: String::from("audio_file_id"), - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - }); - - let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id"}"#; - let actual_json = serde_json::to_string(&structure).unwrap(); - - assert_eq!(expected_json, actual_json); - } - - #[test] - fn cached_audio_full_serialize() { - let structure = InlineQueryResult::CachedAudio(InlineQueryResultCachedAudio { - id: String::from("id"), - audio_file_id: String::from("audio_file_id"), - caption: Some(String::from("caption")), - parse_mode: Some(ParseMode::HTML), - reply_markup: Some(InlineKeyboardMarkup::default()), - input_message_content: Some(InputMessageContent::Text(InputMessageContentText { - message_text: String::from("message_text"), - parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), - })), - }); - - let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; - let actual_json = serde_json::to_string(&structure).unwrap(); - - assert_eq!(expected_json, actual_json); - } - - // TODO: Add more tests -} diff --git a/src/types/inline_query_result_article.rs b/src/types/inline_query_result_article.rs deleted file mode 100644 index 2f9bd689..00000000 --- a/src/types/inline_query_result_article.rs +++ /dev/null @@ -1,128 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent}; - -/// Represents a link to an article or web page. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultarticle). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultArticle { - /// Unique identifier for this result, 1-64 Bytes. - pub id: String, - - /// Title of the result. - pub title: String, - - /// Content of the message to be sent. - pub input_message_content: InputMessageContent, - - /// Inline keyboard attached to the message. - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// URL of the result. - pub url: Option<String>, - - /// Pass `true`, if you don't want the URL to be shown in the - /// message. - pub hide_url: Option<bool>, - - /// Short description of the result. - pub description: Option<String>, - - /// Url of the thumbnail for the result. - pub thumb_url: Option<String>, - - /// Thumbnail width. - pub thumb_width: Option<i32>, - - /// Thumbnail height. - pub thumb_height: Option<i32>, -} - -impl InlineQueryResultArticle { - pub fn new<S1, S2>(id: S1, title: S2, input_message_content: InputMessageContent) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - id: id.into(), - title: title.into(), - input_message_content, - reply_markup: None, - url: None, - hide_url: None, - description: None, - thumb_url: None, - thumb_width: None, - thumb_height: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = val; - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.url = Some(val.into()); - self - } - - pub fn hide_url(mut self, val: bool) -> Self { - self.hide_url = Some(val); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = Some(val.into()); - self - } - - pub fn thumb_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.thumb_url = Some(val.into()); - self - } - - pub fn thumb_width(mut self, val: i32) -> Self { - self.thumb_width = Some(val); - self - } - - pub fn thumb_height(mut self, val: i32) -> Self { - self.thumb_height = Some(val); - self - } -} diff --git a/src/types/inline_query_result_audio.rs b/src/types/inline_query_result_audio.rs deleted file mode 100644 index 648c1bee..00000000 --- a/src/types/inline_query_result_audio.rs +++ /dev/null @@ -1,133 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to an MP3 audio file. By default, this audio file will be -/// sent by the user. -/// -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the audio. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultaudio). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultAudio { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL for the audio file. - pub audio_url: String, - - /// Title. - pub title: String, - - /// Caption, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// Performer. - pub performer: Option<String>, - - /// Audio duration in seconds. - pub audio_duration: Option<String>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the audio. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultAudio { - pub fn new<S1, S2, S3>(id: S1, audio_url: S2, title: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - id: id.into(), - audio_url: audio_url.into(), - title: title.into(), - caption: None, - parse_mode: None, - performer: None, - audio_duration: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn audio_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.audio_url = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn performer<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.performer = Some(val.into()); - self - } - - pub fn audio_duration<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.audio_duration = Some(val.into()); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_audio.rs b/src/types/inline_query_result_cached_audio.rs deleted file mode 100644 index 5efec634..00000000 --- a/src/types/inline_query_result_cached_audio.rs +++ /dev/null @@ -1,96 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to an MP3 audio file stored on the Telegram servers. -/// -/// By default, this audio file will be sent by the user. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the audio. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedaudio). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedAudio { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier for the audio file. - pub audio_file_id: String, - - /// Caption, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the audio. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultCachedAudio { - pub fn new<S1, S2>(id: S1, audio_file_id: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - id: id.into(), - audio_file_id: audio_file_id.into(), - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn audio_file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.audio_file_id = val.into(); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_document.rs b/src/types/inline_query_result_cached_document.rs deleted file mode 100644 index 882c5858..00000000 --- a/src/types/inline_query_result_cached_document.rs +++ /dev/null @@ -1,121 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a file stored on the Telegram servers. -/// -/// By default, this file will be sent by the user with an optional caption. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the file. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcacheddocument). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedDocument { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// Title for the result. - pub title: String, - - /// A valid file identifier for the file. - pub document_file_id: String, - - /// Short description of the result. - pub description: Option<String>, - - /// Caption of the document to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the file. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultCachedDocument { - pub fn new<S1, S2, S3>(id: S1, title: S2, document_file_id: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - id: id.into(), - title: title.into(), - document_file_id: document_file_id.into(), - description: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn document_file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.document_file_id = val.into(); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = Some(val.into()); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_gif.rs b/src/types/inline_query_result_cached_gif.rs deleted file mode 100644 index 79fb44db..00000000 --- a/src/types/inline_query_result_cached_gif.rs +++ /dev/null @@ -1,109 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to an animated GIF file stored on the Telegram servers. -/// -/// By default, this animated GIF file will be sent by the user with an optional -/// caption. Alternatively, you can use `input_message_content` to send a -/// message with specified content instead of the animation. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedgif). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedGif { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier for the GIF file. - pub gif_file_id: String, - - /// Title for the result. - pub title: Option<String>, - - /// Caption of the GIF file to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [`ParseMode::Markdown`] or [`ParseMode::HTML`], if you want - /// Telegram apps to show [bold, italic, fixed-width text or inline - /// URLs] in the media caption. - /// - /// [`ParseMode::Markdown`]: crate::types::ParseMode::Markdown - /// [`ParseMode::HTML`]: crate::types::ParseMode::HTML - /// [bold, italic, fixed-width text or inline URLs]: https://core.telegram.org/bots/api#formatting-options - pub parse_mode: Option<ParseMode>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the GIF animation. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultCachedGif { - pub fn new<S1, S2>(id: S1, gif_file_id: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - id: id.into(), - gif_file_id: gif_file_id.into(), - title: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn gif_file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.gif_file_id = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = Some(val.into()); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_mpeg4_gif.rs b/src/types/inline_query_result_cached_mpeg4_gif.rs deleted file mode 100644 index 8f97667d..00000000 --- a/src/types/inline_query_result_cached_mpeg4_gif.rs +++ /dev/null @@ -1,101 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a video animation (H.264/MPEG-4 AVC video without -/// sound) stored on the Telegram servers. -/// -/// By default, this animated MPEG-4 file will be sent by the user with an -/// optional caption. Alternatively, you can use `input_message_content` to send -/// a message with the specified content instead of the animation. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedMpeg4Gif { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier for the MP4 file. - pub mpeg4_file_id: String, - - /// Title for the result. - pub title: Option<String>, - - /// Caption of the MPEG-4 file to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the video animation. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultCachedMpeg4Gif { - pub fn new<S1, S2>(id: S1, mpeg4_file_id: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - id: id.into(), - mpeg4_file_id: mpeg4_file_id.into(), - title: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = Some(val.into()); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_photo.rs b/src/types/inline_query_result_cached_photo.rs deleted file mode 100644 index ac9d28e8..00000000 --- a/src/types/inline_query_result_cached_photo.rs +++ /dev/null @@ -1,120 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a photo stored on the Telegram servers. -/// -/// By default, this photo will be sent by the user with an optional caption. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the photo. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedPhoto { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier of the photo. - pub photo_file_id: String, - - /// Title for the result. - pub title: Option<String>, - - /// Short description of the result. - pub description: Option<String>, - - /// Caption of the photo to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the photo. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultCachedPhoto { - pub fn new<S1, S2>(id: S1, photo_file_id: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - id: id.into(), - photo_file_id: photo_file_id.into(), - title: None, - description: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn photo_file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.photo_file_id = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = Some(val.into()); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = Some(val.into()); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_sticker.rs b/src/types/inline_query_result_cached_sticker.rs deleted file mode 100644 index 0c4e9b88..00000000 --- a/src/types/inline_query_result_cached_sticker.rs +++ /dev/null @@ -1,70 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent}; - -/// Represents a link to a sticker stored on the Telegram servers. -/// -/// By default, this sticker will be sent by the user. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the sticker. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedsticker). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedSticker { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier of the sticker. - pub sticker_file_id: String, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the sticker. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultCachedSticker { - pub fn new<S1, S2>(id: S1, sticker_file_id: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - id: id.into(), - sticker_file_id: sticker_file_id.into(), - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn sticker_file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.sticker_file_id = val.into(); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_video.rs b/src/types/inline_query_result_cached_video.rs deleted file mode 100644 index 645892b0..00000000 --- a/src/types/inline_query_result_cached_video.rs +++ /dev/null @@ -1,121 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a video file stored on the Telegram servers. -/// -/// By default, this video file will be sent by the user with an optional -/// caption. Alternatively, you can use `input_message_content` to send a -/// message with the specified content instead of the video. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedVideo { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier for the video file. - pub video_file_id: String, - - /// Title for each result. - pub title: String, - - /// Short description of the result. - pub description: Option<String>, - - /// Caption of the video to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the video. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultCachedVideo { - pub fn new<S1, S2, S3>(id: S1, video_file_id: S2, title: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - id: id.into(), - video_file_id: video_file_id.into(), - title: title.into(), - description: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn video_file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.video_file_id = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = Some(val.into()); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_cached_voice.rs b/src/types/inline_query_result_cached_voice.rs deleted file mode 100644 index 1f50672c..00000000 --- a/src/types/inline_query_result_cached_voice.rs +++ /dev/null @@ -1,109 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a voice message stored on the Telegram servers. -/// -/// By default, this voice message will be sent by the user. Alternatively, you -/// can use `input_message_content` to send a message with the specified content -/// instead of the voice message. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultCachedVoice { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid file identifier for the voice message. - pub voice_file_id: String, - - /// Voice message title. - pub title: String, - - /// Caption, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the voice message. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultCachedVoice { - pub fn new<S1, S2, S3>(id: S1, voice_file_id: S2, title: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - id: id.into(), - voice_file_id: voice_file_id.into(), - title: title.into(), - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn voice_file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.voice_file_id = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_contact.rs b/src/types/inline_query_result_contact.rs deleted file mode 100644 index 0b4ae0d8..00000000 --- a/src/types/inline_query_result_contact.rs +++ /dev/null @@ -1,140 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent}; - -/// Represents a contact with a phone number. -/// -/// By default, this contact will be sent by the user. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the contact. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultcachedvideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultContact { - /// Unique identifier for this result, 1-64 Bytes. - pub id: String, - - /// Contact's phone number. - pub phone_number: String, - - /// Contact's first name. - pub first_name: String, - - /// Contact's last name. - pub last_name: Option<String>, - - /// Additional data about the contact in the form of a [vCard], 0-2048 - /// bytes. - /// - /// [VCard]: https://en.wikipedia.org/wiki/VCard - pub vcard: Option<String>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the contact. - pub input_message_content: Option<InputMessageContent>, - - /// Url of the thumbnail for the result. - pub thumb_url: Option<String>, - - /// Thumbnail width. - pub thumb_width: Option<i32>, - - /// Thumbnail height. - pub thumb_height: Option<i32>, -} - -impl InlineQueryResultContact { - pub fn new<S1, S2, S3>(id: S1, phone_number: S2, first_name: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - id: id.into(), - phone_number: phone_number.into(), - first_name: first_name.into(), - last_name: None, - vcard: None, - reply_markup: None, - input_message_content: None, - thumb_url: None, - thumb_width: None, - thumb_height: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn phone_number<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.phone_number = val.into(); - self - } - - pub fn first_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.first_name = val.into(); - self - } - - pub fn last_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.last_name = Some(val.into()); - self - } - - pub fn vcard<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.vcard = Some(val.into()); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } - - pub fn thumb_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.thumb_url = Some(val.into()); - self - } - - pub fn thumb_width(mut self, val: i32) -> Self { - self.thumb_width = Some(val); - self - } - - pub fn thumb_height(mut self, val: i32) -> Self { - self.thumb_height = Some(val); - self - } -} diff --git a/src/types/inline_query_result_document.rs b/src/types/inline_query_result_document.rs deleted file mode 100644 index ea66f561..00000000 --- a/src/types/inline_query_result_document.rs +++ /dev/null @@ -1,138 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, ParseMode}; - -/// Represents a link to a file. -/// -/// By default, this file will be sent by the user with an optional caption. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the file. Currently, only **.PDF** and -/// **.ZIP** files can be sent using this method. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultdocument). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultDocument { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// Title for the result. - pub title: String, - - /// Caption of the document to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// A valid URL for the file. - pub document_url: String, - - /// Mime type of the content of the file, either `application/pdf` or - /// `application/zip`. - pub mime_type: MimeWrapper, - - /// Short description of the result. - pub description: Option<String>, - - /// Inline keyboard attached to the message. - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the file. - pub input_message_content: Option<InputMessageContent>, - - /// URL of the thumbnail (jpeg only) for the file. - pub thumb_url: Option<String>, - - /// Thumbnail width. - pub thumb_width: Option<i32>, - - /// Thumbnail height. - pub thumb_height: Option<i32>, -} - -impl InlineQueryResultDocument { - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn document_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.document_url = val.into(); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = val; - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = Some(val.into()); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } - - pub fn thumb_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.thumb_url = Some(val.into()); - self - } - - pub fn thumb_width(mut self, val: i32) -> Self { - self.thumb_width = Some(val); - self - } - - pub fn thumb_height(mut self, val: i32) -> Self { - self.thumb_height = Some(val); - self - } -} diff --git a/src/types/inline_query_result_game.rs b/src/types/inline_query_result_game.rs deleted file mode 100644 index c795547c..00000000 --- a/src/types/inline_query_result_game.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::InlineKeyboardMarkup; - -/// Represents a [game]. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultgame). -/// -/// [game]: https://core.telegram.org/bots/api#games -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultGame { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// Short name of the game. - pub game_short_name: String, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, -} - -impl InlineQueryResultGame { - pub fn new<S1, S2>(id: S1, game_short_name: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { id: id.into(), game_short_name: game_short_name.into(), reply_markup: None } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn game_short_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.game_short_name = val.into(); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/types/inline_query_result_gif.rs b/src/types/inline_query_result_gif.rs deleted file mode 100644 index 71145de3..00000000 --- a/src/types/inline_query_result_gif.rs +++ /dev/null @@ -1,148 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to an animated GIF file. -/// -/// By default, this animated GIF file will be sent by the user with optional -/// caption. Alternatively, you can use `input_message_content` to send a -/// message with the specified content instead of the animation. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultgif). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultGif { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL for the GIF file. File size must not exceed 1MB. - pub gif_url: String, - - /// Width of the GIF. - pub gif_width: Option<i32>, - - /// Height of the GIFv. - pub gif_height: Option<i32>, - - /// Duration of the GIF. - pub gif_duration: Option<i32>, - - /// URL of the static thumbnail for the result (jpeg or gif). - pub thumb_url: String, - - /// Title for the result. - pub title: Option<String>, - - /// Caption of the GIF file to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the GIF animation. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultGif { - pub fn new<S1, S2, S3>(id: S1, gif_url: S2, thumb_url: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - id: id.into(), - gif_url: gif_url.into(), - gif_width: None, - gif_height: None, - gif_duration: None, - thumb_url: thumb_url.into(), - title: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn gif_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.gif_url = val.into(); - self - } - - pub fn gif_width(mut self, val: i32) -> Self { - self.gif_width = Some(val); - self - } - - pub fn gif_height(mut self, val: i32) -> Self { - self.gif_height = Some(val); - self - } - - pub fn gif_duration(mut self, val: i32) -> Self { - self.gif_duration = Some(val); - self - } - - pub fn thumb_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.thumb_url = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = Some(val.into()); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_location.rs b/src/types/inline_query_result_location.rs deleted file mode 100644 index 1d6d9270..00000000 --- a/src/types/inline_query_result_location.rs +++ /dev/null @@ -1,128 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent}; - -/// Represents a location on a map. -/// -/// By default, the location will be sent by the user. Alternatively, you can -/// use `input_message_content` to send a message with the specified content -/// instead of the location. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultlocation). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultLocation { - /// Unique identifier for this result, 1-64 Bytes. - pub id: String, - - /// Location latitude in degrees. - pub latitude: f64, - - /// Location longitude in degrees. - pub longitude: f64, - - /// Location title. - pub title: String, - - /// Period in seconds for which the location can be updated, should be - /// between 60 and 86400. - pub live_period: Option<i32>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the location. - pub input_message_content: Option<InputMessageContent>, - - /// Url of the thumbnail for the result. - pub thumb_url: Option<String>, - - /// Thumbnail width. - pub thumb_width: Option<i32>, - - /// Thumbnail height. - pub thumb_height: Option<i32>, -} - -impl InlineQueryResultLocation { - pub fn new<S1, S2>(id: S1, title: S2, latitude: f64, longitude: f64) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - id: id.into(), - title: title.into(), - latitude, - longitude, - live_period: None, - reply_markup: None, - input_message_content: None, - thumb_url: None, - thumb_width: None, - thumb_height: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn live_period(mut self, val: i32) -> Self { - self.live_period = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } - - pub fn thumb_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.thumb_url = Some(val.into()); - self - } - - pub fn thumb_width(mut self, val: i32) -> Self { - self.thumb_width = Some(val); - self - } - - pub fn thumb_height(mut self, val: i32) -> Self { - self.thumb_height = Some(val); - self - } -} diff --git a/src/types/inline_query_result_mpeg4_gif.rs b/src/types/inline_query_result_mpeg4_gif.rs deleted file mode 100644 index 6c01ceb7..00000000 --- a/src/types/inline_query_result_mpeg4_gif.rs +++ /dev/null @@ -1,149 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a video animation (H.264/MPEG-4 AVC video without -/// sound). -/// -/// By default, this animated MPEG-4 file will be sent by the user with optional -/// caption. Alternatively, you can use `input_message_content` to send -/// a message with the specified content instead of the animation. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultMpeg4Gif { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL for the MP4 file. File size must not exceed 1MB. - pub mpeg4_url: String, - - /// Video width. - pub mpeg4_width: Option<i32>, - - /// Video height. - pub mpeg4_height: Option<i32>, - - /// Video duration. - pub mpeg4_duration: Option<i32>, - - /// URL of the static thumbnail (jpeg or gif) for the result. - pub thumb_url: String, - - /// Title for the result. - pub title: Option<String>, - - /// Caption of the MPEG-4 file to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the video animation. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultMpeg4Gif { - pub fn new<S1, S2, S3>(id: S1, mpeg4_url: S2, thumb_url: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - id: id.into(), - mpeg4_url: mpeg4_url.into(), - thumb_url: thumb_url.into(), - mpeg4_width: None, - mpeg4_height: None, - mpeg4_duration: None, - title: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn mpeg4_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.mpeg4_url = val.into(); - self - } - - pub fn mpeg4_width(mut self, val: i32) -> Self { - self.mpeg4_width = Some(val); - self - } - - pub fn mpeg4_height(mut self, val: i32) -> Self { - self.mpeg4_height = Some(val); - self - } - - pub fn mpeg4_duration(mut self, val: i32) -> Self { - self.mpeg4_duration = Some(val); - self - } - - pub fn thumb_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.thumb_url = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = Some(val.into()); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_photo.rs b/src/types/inline_query_result_photo.rs deleted file mode 100644 index e1d32dc9..00000000 --- a/src/types/inline_query_result_photo.rs +++ /dev/null @@ -1,152 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a photo. -/// -/// By default, this photo will be sent by the user with optional caption. -/// Alternatively, you can use `input_message_content` to send a message with -/// the specified content instead of the photo. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultPhoto { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL of the photo. Photo must be in **jpeg** format. Photo size - /// must not exceed 5MB. - pub photo_url: String, - - /// URL of the thumbnail for the photo. - pub thumb_url: String, - - /// Width of the photo. - pub photo_width: Option<i32>, - - /// Height of the photo. - pub photo_height: Option<i32>, - - /// Title for the result. - pub title: Option<String>, - - /// Short description of the result. - pub description: Option<String>, - - /// Caption of the photo to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the photo. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultPhoto { - pub fn new<S1, S2, S3>(id: S1, photo_url: S2, thumb_url: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - id: id.into(), - photo_url: photo_url.into(), - thumb_url: thumb_url.into(), - photo_width: None, - photo_height: None, - title: None, - description: None, - caption: None, - parse_mode: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn photo_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.photo_url = val.into(); - self - } - - pub fn thumb_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.thumb_url = val.into(); - self - } - - pub fn photo_width<S>(mut self, val: i32) -> Self { - self.photo_width = Some(val); - self - } - - pub fn photo_height<S>(mut self, val: i32) -> Self { - self.photo_height = Some(val); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = Some(val.into()); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = Some(val.into()); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode<S>(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_venue.rs b/src/types/inline_query_result_venue.rs deleted file mode 100644 index c7e67443..00000000 --- a/src/types/inline_query_result_venue.rs +++ /dev/null @@ -1,157 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent}; - -/// Represents a venue. -/// -/// By default, the venue will be sent by the user. Alternatively, you can use -/// `input_message_content` to send a message with the specified content instead -/// of the venue. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvenue). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultVenue { - /// Unique identifier for this result, 1-64 Bytes. - pub id: String, - - /// Latitude of the venue location in degrees. - pub latitude: f64, - - /// Longitude of the venue location in degrees. - pub longitude: f64, - - /// Title of the venue. - pub title: String, - - /// Address of the venue. - pub address: String, - - /// Foursquare identifier of the venue if known. - pub foursquare_id: Option<String>, - - /// Foursquare type of the venue, if known. (For example, - /// `arts_entertainment/default`, `arts_entertainment/aquarium` or - /// `food/icecream`.) - pub foursquare_type: Option<String>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the venue. - pub input_message_content: Option<InputMessageContent>, - - /// Url of the thumbnail for the result. - pub thumb_url: Option<String>, - - /// Thumbnail width. - pub thumb_width: Option<i32>, - - /// Thumbnail height. - pub thumb_height: Option<i32>, -} - -impl InlineQueryResultVenue { - pub fn new<S1, S2, S3>(id: S1, latitude: f64, longitude: f64, title: S2, address: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - id: id.into(), - latitude, - longitude, - title: title.into(), - address: address.into(), - foursquare_id: None, - foursquare_type: None, - reply_markup: None, - input_message_content: None, - thumb_url: None, - thumb_width: None, - thumb_height: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn address<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.address = val.into(); - self - } - - pub fn foursquare_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.foursquare_id = Some(val.into()); - self - } - - pub fn foursquare_type<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.foursquare_type = Some(val.into()); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } - - pub fn thumb_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.thumb_url = Some(val.into()); - self - } - - pub fn thumb_width(mut self, val: i32) -> Self { - self.thumb_width = Some(val); - self - } - - pub fn thumb_height(mut self, val: i32) -> Self { - self.thumb_height = Some(val); - self - } -} diff --git a/src/types/inline_query_result_video.rs b/src/types/inline_query_result_video.rs deleted file mode 100644 index d51258fc..00000000 --- a/src/types/inline_query_result_video.rs +++ /dev/null @@ -1,182 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, MimeWrapper, ParseMode}; - -/// Represents a link to a page containing an embedded video player or a video -/// file. -/// -/// By default, this video file will be sent by the user with an optional -/// caption. Alternatively, you can use `input_messaage_content` to send a -/// message with the specified content instead of the video. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultVideo { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL for the embedded video player or video file. - pub video_url: String, - - /// Mime type of the content of video url, `text/html` or `video/mp4`. - pub mime_type: MimeWrapper, - - /// URL of the thumbnail (jpeg only) for the video. - pub thumb_url: String, - - /// Title for the result. - pub title: String, - - /// Caption of the video to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// Video width. - pub video_width: Option<i32>, - - /// Video height. - pub video_height: Option<i32>, - - /// Video duration in seconds. - pub video_duration: Option<i32>, - - /// Short description of the result. - pub description: Option<String>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the video. This field is - /// **required** if [`InlineQueryResultVideo`] is used to send an HTML-page - /// as a result (e.g., a YouTube video). - /// - /// [`InlineQueryResultVideo`]: - /// crate::types::InlineQueryResultVideo - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultVideo { - pub fn new<S1, S2, S3, S4>( - id: S1, - video_url: S2, - mime_type: MimeWrapper, - thumb_url: S3, - title: S4, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - S4: Into<String>, - { - Self { - id: id.into(), - video_url: video_url.into(), - mime_type, - thumb_url: thumb_url.into(), - title: title.into(), - caption: None, - parse_mode: None, - video_width: None, - video_height: None, - video_duration: None, - description: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn video_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.video_url = val.into(); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = val; - self - } - - pub fn thumb_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.thumb_url = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn video_width(mut self, val: i32) -> Self { - self.video_width = Some(val); - self - } - - pub fn video_height(mut self, val: i32) -> Self { - self.video_height = Some(val); - self - } - - pub fn video_duration(mut self, val: i32) -> Self { - self.video_duration = Some(val); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = Some(val.into()); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/inline_query_result_voice.rs b/src/types/inline_query_result_voice.rs deleted file mode 100644 index db812b7e..00000000 --- a/src/types/inline_query_result_voice.rs +++ /dev/null @@ -1,119 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InlineKeyboardMarkup, InputMessageContent, ParseMode}; - -/// Represents a link to a voice recording in an .ogg container encoded with -/// OPUS. -/// -/// By default, this voice recording will be sent by the user. Alternatively, -/// you can use `input_message_content` to send a message with the specified -/// content instead of the the voice message. -/// -/// [The official docs](https://core.telegram.org/bots/api#inlinequeryresultvoice). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InlineQueryResultVoice { - /// Unique identifier for this result, 1-64 bytes. - pub id: String, - - /// A valid URL for the voice recording. - pub voice_url: String, - - /// Recording title. - pub title: String, - - /// Caption, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// Recording duration in seconds. - pub voice_duration: Option<i32>, - - /// [Inline keyboard] attached to the message. - /// - /// [Inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating - pub reply_markup: Option<InlineKeyboardMarkup>, - - /// Content of the message to be sent instead of the voice recording. - pub input_message_content: Option<InputMessageContent>, -} - -impl InlineQueryResultVoice { - pub fn new<S1, S2, S3>(id: S1, voice_url: S2, title: S3) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - id: id.into(), - voice_url: voice_url.into(), - title: title.into(), - caption: None, - parse_mode: None, - voice_duration: None, - reply_markup: None, - input_message_content: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn voice_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.voice_url = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn voice_duration(mut self, value: i32) -> Self { - self.voice_duration = Some(value); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } - - pub fn input_message_content(mut self, val: InputMessageContent) -> Self { - self.input_message_content = Some(val); - self - } -} diff --git a/src/types/input_file.rs b/src/types/input_file.rs deleted file mode 100644 index 55e15e14..00000000 --- a/src/types/input_file.rs +++ /dev/null @@ -1,101 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use std::{borrow::Cow, path::PathBuf}; - -/// This object represents the contents of a file to be uploaded. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputfile). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize)] -#[non_exhaustive] -pub enum InputFile { - File(PathBuf), - Memory { file_name: String, data: Cow<'static, [u8]> }, - Url(String), - FileId(String), -} - -impl InputFile { - pub fn file<P>(path: P) -> Self - where - P: Into<PathBuf>, - { - Self::File(path.into()) - } - - pub fn memory<S, D>(file_name: S, data: D) -> Self - where - S: Into<String>, - D: Into<Cow<'static, [u8]>>, - { - Self::Memory { file_name: file_name.into(), data: data.into() } - } - - pub fn url<T>(url: T) -> Self - where - T: Into<String>, - { - Self::Url(url.into()) - } - - pub fn file_id<T>(file_id: T) -> Self - where - T: Into<String>, - { - Self::FileId(file_id.into()) - } - - pub fn as_file(&self) -> Option<&PathBuf> { - match self { - Self::File(path) => Some(path), - _ => None, - } - } - - pub fn as_url(&self) -> Option<&String> { - match self { - Self::Url(url) => Some(url), - _ => None, - } - } - - pub fn as_file_id(&self) -> Option<&String> { - match self { - Self::FileId(id) => Some(id), - _ => None, - } - } -} - -impl From<InputFile> for Option<PathBuf> { - fn from(file: InputFile) -> Self { - match file { - InputFile::File(path) => Some(path), - _ => None, - } - } -} - -impl Serialize for InputFile { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - match self { - InputFile::File(path) => { - // NOTE: file should be actually attached with - // multipart/form-data - serializer.serialize_str( - // TODO: remove unwrap (?) - &format!("attach://{}", path.file_name().unwrap().to_string_lossy()), - ) - } - InputFile::Memory { data, .. } => { - // NOTE: file should be actually attached with - // multipart/form-data - serializer.serialize_str(&format!("attach://{}", String::from_utf8_lossy(data))) - } - InputFile::Url(url) => serializer.serialize_str(url), - InputFile::FileId(id) => serializer.serialize_str(id), - } - } -} diff --git a/src/types/input_media.rs b/src/types/input_media.rs deleted file mode 100644 index a37ebda4..00000000 --- a/src/types/input_media.rs +++ /dev/null @@ -1,511 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{InputFile, ParseMode}; - -/// This object represents the content of a media message to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmedia). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(tag = "type")] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum InputMedia { - Photo(InputMediaPhoto), - Video(InputMediaVideo), - Animation(InputMediaAnimation), - Audio(InputMediaAudio), - Document(InputMediaDocument), -} - -/// Represents a photo to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmediaphoto). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMediaPhoto { - /// File to send. - pub media: InputFile, - - /// Caption of the photo to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, -} - -impl InputMediaPhoto { - pub fn new(media: InputFile) -> Self { - Self { media, caption: None, parse_mode: None } - } - - pub fn media(mut self, val: InputFile) -> Self { - self.media = val; - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } -} - -/// Represents a video to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmediavideo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMediaVideo { - // File to send. - pub media: InputFile, - - /// Thumbnail of the file sent; can be ignored if thumbnail generation - /// for the file is supported server-side. The thumbnail should be in - /// JPEG format and less than 200 kB in size. A thumbnail‘s width and - /// height should not exceed 320. Ignored if the file is not uploaded - /// using multipart/form-data. - pub thumb: Option<InputFile>, - - /// Caption of the video to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// Video width. - pub width: Option<u16>, - - /// Video height. - pub height: Option<u16>, - - /// Video duration. - pub duration: Option<u16>, - - /// Pass `true`, if the uploaded video is suitable for streaming. - pub supports_streaming: Option<bool>, -} - -impl InputMediaVideo { - pub fn new(media: InputFile) -> Self { - Self { - media, - thumb: None, - caption: None, - parse_mode: None, - width: None, - height: None, - duration: None, - supports_streaming: None, - } - } - - pub fn media(mut self, val: InputFile) -> Self { - self.media = val; - self - } - - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn width(mut self, val: u16) -> Self { - self.width = Some(val); - self - } - - pub fn height(mut self, val: u16) -> Self { - self.height = Some(val); - self - } - - pub fn duration(mut self, val: u16) -> Self { - self.duration = Some(val); - self - } - - pub fn supports_streaming(mut self, val: bool) -> Self { - self.supports_streaming = Some(val); - self - } -} - -/// Represents an animation file (GIF or H.264/MPEG-4 AVC video without -/// sound) to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmediaanimation). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMediaAnimation { - /// File to send. - pub media: InputFile, - - /// Thumbnail of the file sent; can be ignored if thumbnail generation - /// for the file is supported server-side. The thumbnail should be in - /// JPEG format and less than 200 kB in size. A thumbnail‘s width and - /// height should not exceed 320. Ignored if the file is not uploaded - /// using multipart/form-data. - pub thumb: Option<InputFile>, - - /// Caption of the animation to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// Animation width. - pub width: Option<u16>, - - /// Animation height. - pub height: Option<u16>, - - /// Animation duration. - pub duration: Option<u16>, -} - -impl InputMediaAnimation { - pub fn new(media: InputFile) -> Self { - Self { - media, - thumb: None, - caption: None, - parse_mode: None, - width: None, - height: None, - duration: None, - } - } - - pub fn media(mut self, val: InputFile) -> Self { - self.media = val; - self - } - - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn width(mut self, val: u16) -> Self { - self.width = Some(val); - self - } - - pub fn height(mut self, val: u16) -> Self { - self.height = Some(val); - self - } - - pub fn duration(mut self, val: u16) -> Self { - self.duration = Some(val); - self - } -} - -/// Represents an audio file to be treated as music to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmediaaudio). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMediaAudio { - /// File to send. - pub media: InputFile, - - /// Thumbnail of the file sent; can be ignored if thumbnail generation - /// for the file is supported server-side. The thumbnail should be in - /// JPEG format and less than 200 kB in size. A thumbnail‘s width and - /// height should not exceed 320. Ignored if the file is not uploaded - /// using multipart/form-data. - pub thumb: Option<InputFile>, - - /// Caption of the audio to be sent, 0-1024 characters. - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// Duration of the audio in seconds. - pub duration: Option<u16>, - - /// Performer of the audio. - pub performer: Option<String>, - - /// Title of the audio. - pub title: Option<String>, -} - -impl InputMediaAudio { - pub fn new(media: InputFile) -> Self { - Self { - media, - thumb: None, - caption: None, - parse_mode: None, - performer: None, - title: None, - duration: None, - } - } - - pub fn media(mut self, val: InputFile) -> Self { - self.media = val; - self - } - - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn duration(mut self, val: u16) -> Self { - self.duration = Some(val); - self - } - - pub fn performer<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.performer = Some(val.into()); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = Some(val.into()); - self - } -} - -/// Represents a general file to be sent. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmediadocument). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMediaDocument { - /// File to send. - pub media: InputFile, - - /// Thumbnail of the file sent; can be ignored if thumbnail generation - /// for the file is supported server-side. The thumbnail should be in - /// JPEG format and less than 200 kB in size. A thumbnail‘s width and - /// height should not exceed 320. Ignored if the file is not uploaded - /// using multipart/form-data. - pub thumb: Option<InputFile>, - - /// Caption of the document to be sent, 0-1024 charactersю - pub caption: Option<String>, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, -} - -impl InputMediaDocument { - pub fn new(media: InputFile) -> Self { - Self { media, thumb: None, caption: None, parse_mode: None } - } - - pub fn thumb(mut self, val: InputFile) -> Self { - self.thumb = Some(val); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } -} - -impl InputMedia { - pub fn media(&self) -> &InputFile { - match self { - InputMedia::Photo(InputMediaPhoto { media, .. }) - | InputMedia::Document(InputMediaDocument { media, .. }) - | InputMedia::Audio(InputMediaAudio { media, .. }) - | InputMedia::Animation(InputMediaAnimation { media, .. }) - | InputMedia::Video(InputMediaVideo { media, .. }) => media, - } - } -} - -impl From<InputMedia> for InputFile { - fn from(media: InputMedia) -> InputFile { - match media { - InputMedia::Photo(InputMediaPhoto { media, .. }) - | InputMedia::Document(InputMediaDocument { media, .. }) - | InputMedia::Audio(InputMediaAudio { media, .. }) - | InputMedia::Animation(InputMediaAnimation { media, .. }) - | InputMedia::Video(InputMediaVideo { media, .. }) => media, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn photo_serialize() { - let expected_json = r#"{"type":"photo","media":"123456"}"#; - let photo = InputMedia::Photo(InputMediaPhoto { - media: InputFile::FileId(String::from("123456")), - caption: None, - parse_mode: None, - }); - - let actual_json = serde_json::to_string(&photo).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn video_serialize() { - let expected_json = r#"{"type":"video","media":"123456"}"#; - let video = InputMedia::Video(InputMediaVideo { - media: InputFile::FileId(String::from("123456")), - thumb: None, - caption: None, - parse_mode: None, - width: None, - height: None, - duration: None, - supports_streaming: None, - }); - - let actual_json = serde_json::to_string(&video).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn animation_serialize() { - let expected_json = r#"{"type":"animation","media":"123456"}"#; - let video = InputMedia::Animation(InputMediaAnimation { - media: InputFile::FileId(String::from("123456")), - thumb: None, - caption: None, - parse_mode: None, - width: None, - height: None, - duration: None, - }); - - let actual_json = serde_json::to_string(&video).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn audio_serialize() { - let expected_json = r#"{"type":"audio","media":"123456"}"#; - let video = InputMedia::Audio(InputMediaAudio { - media: InputFile::FileId(String::from("123456")), - thumb: None, - caption: None, - parse_mode: None, - duration: None, - performer: None, - title: None, - }); - - let actual_json = serde_json::to_string(&video).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn document_serialize() { - let expected_json = r#"{"type":"document","media":"123456"}"#; - let video = InputMedia::Document(InputMediaDocument { - media: InputFile::FileId(String::from("123456")), - thumb: None, - caption: None, - parse_mode: None, - }); - - let actual_json = serde_json::to_string(&video).unwrap(); - assert_eq!(expected_json, actual_json); - } -} diff --git a/src/types/input_message_content.rs b/src/types/input_message_content.rs deleted file mode 100644 index caf57650..00000000 --- a/src/types/input_message_content.rs +++ /dev/null @@ -1,318 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::ParseMode; - -/// This object represents the content of a message to be sent as a result of an -/// inline query. -/// -/// [The official docs](https://core.telegram.org/bots/api#inputmessagecontent). -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum InputMessageContent { - Text(InputMessageContentText), - Location(InputMessageContentLocation), - Venue(InputMessageContentVenue), - Contact(InputMessageContentContact), -} -/// Represents the content of a text message to be sent as the result of an -/// inline query. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMessageContentText { - /// Text of the message to be sent, 1-4096 characters. - pub message_text: String, - - /// Send [Markdown] or [HTML], if you want Telegram apps to show [bold, - /// italic, fixed-width text or inline URLs] in the media caption. - /// - /// [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 parse_mode: Option<ParseMode>, - - /// Disables link previews for links in the sent message. - pub disable_web_page_preview: Option<bool>, -} - -impl InputMessageContentText { - pub fn new<S>(message_text: S) -> Self - where - S: Into<String>, - { - Self { message_text: message_text.into(), parse_mode: None, disable_web_page_preview: None } - } - - pub fn message_text<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.message_text = val.into(); - self - } - - pub fn parse_mode(mut self, val: ParseMode) -> Self { - self.parse_mode = Some(val); - self - } - - pub fn disable_web_page_preview(mut self, val: bool) -> Self { - self.disable_web_page_preview = Some(val); - self - } -} - -/// Represents the content of a location message to be sent as the result of an -/// inline query. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMessageContentLocation { - /// Latitude of the location in degrees. - pub latitude: f64, - - /// Longitude of the location in degrees. - pub longitude: f64, - - /// Period in seconds for which the location can be updated, should be - /// between 60 and 86400. - pub live_period: Option<u32>, -} - -impl InputMessageContentLocation { - pub fn new(latitude: f64, longitude: f64) -> Self { - Self { latitude, longitude, live_period: None } - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } - - pub fn live_period(mut self, val: u32) -> Self { - self.live_period = Some(val); - self - } -} - -/// Represents the content of a venue message to be sent as the result of -/// an inline query. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMessageContentVenue { - /// Latitude of the venue in degrees. - pub latitude: f64, - - /// Longitude of the venue in degrees. - pub longitude: f64, - - /// Name of the venue. - pub title: String, - - /// Address of the venue. - pub address: String, - - /// Foursquare identifier of the venue, if known. - pub foursquare_id: Option<String>, - - /// Foursquare type of the venue, if known. (For example, - /// `arts_entertainment/default`, `arts_entertainment/aquarium` - /// or `food/icecream`.) - pub foursquare_type: Option<String>, -} - -impl InputMessageContentVenue { - pub fn new<S1, S2>(latitude: f64, longitude: f64, title: S1, address: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - latitude, - longitude, - title: title.into(), - address: address.into(), - foursquare_id: None, - foursquare_type: None, - } - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn address<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.address = val.into(); - self - } - - pub fn foursquare_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.foursquare_id = Some(val.into()); - self - } - - pub fn foursquare_type<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.foursquare_type = Some(val.into()); - self - } -} - -/// Represents the content of a contact message to be sent as the result of -/// an inline query. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct InputMessageContentContact { - /// Contact's phone number. - pub phone_number: String, - - /// Contact's first name. - pub first_name: String, - - /// Contact's last name. - pub last_name: Option<String>, - - /// Additional data about the contact in the form of a [vCard], 0-2048 - /// bytes. - /// - /// [vCard]: https://en.wikipedia.org/wiki/VCard - pub vcard: Option<String>, -} - -impl InputMessageContentContact { - pub fn new<S1, S2>(phone_number: S1, first_name: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - phone_number: phone_number.into(), - first_name: first_name.into(), - last_name: None, - vcard: None, - } - } - - pub fn phone_number<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.phone_number = val.into(); - self - } - - pub fn first_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.first_name = val.into(); - self - } - - pub fn last_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.last_name = Some(val.into()); - self - } - - pub fn vcard<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.vcard = Some(val.into()); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn text_serialize() { - let expected_json = r#"{"message_text":"text"}"#; - let text_content = InputMessageContent::Text(InputMessageContentText { - message_text: String::from("text"), - parse_mode: None, - disable_web_page_preview: None, - }); - - let actual_json = serde_json::to_string(&text_content).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn location_serialize() { - let expected_json = r#"{"latitude":59.08,"longitude":38.4326}"#; - let location_content = InputMessageContent::Location(InputMessageContentLocation { - latitude: 59.08, - longitude: 38.4326, - live_period: None, - }); - - let actual_json = serde_json::to_string(&location_content).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn venue_serialize() { - let expected_json = r#"{"latitude":59.08,"longitude":38.4326,"title":"some title","address":"some address"}"#; - let venue_content = InputMessageContent::Venue(InputMessageContentVenue { - latitude: 59.08, - longitude: 38.4326, - title: String::from("some title"), - address: String::from("some address"), - foursquare_id: None, - foursquare_type: None, - }); - - let actual_json = serde_json::to_string(&venue_content).unwrap(); - assert_eq!(expected_json, actual_json); - } - - #[test] - fn contact_serialize() { - let expected_json = r#"{"phone_number":"+3800000000","first_name":"jhon"}"#; - let contact_content = InputMessageContent::Contact(InputMessageContentContact { - phone_number: String::from("+3800000000"), - first_name: String::from("jhon"), - last_name: None, - vcard: None, - }); - - let actual_json = serde_json::to_string(&contact_content).unwrap(); - assert_eq!(expected_json, actual_json); - } -} diff --git a/src/types/invoice.rs b/src/types/invoice.rs deleted file mode 100644 index 979881e1..00000000 --- a/src/types/invoice.rs +++ /dev/null @@ -1,91 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object contains basic information about an invoice. -/// -/// [The official docs](https://core.telegram.org/bots/api#invoice). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Invoice { - /// Product name. - pub title: String, - - /// Product description. - pub description: String, - - /// Unique bot deep-linking parameter that can be used to generate this - /// invoice. - pub start_parameter: String, - - /// Three-letter ISO 4217 currency code. - pub currency: String, - - /// Total price in the smallest units of the currency (integer, **not** - /// float/double). For example, for a price of `US$ 1.45` pass `amount = - /// 145`. See the exp parameter in [`currencies.json`], it shows the number - /// of digits past the decimal point for each currency (2 for the - /// majority of currencies). - /// - /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json - pub total_amount: i32, -} - -impl Invoice { - pub fn new<S1, S2, S3, S4>( - title: S1, - description: S2, - start_parameter: S3, - currency: S4, - total_amount: i32, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - S4: Into<String>, - { - Self { - title: title.into(), - description: description.into(), - start_parameter: start_parameter.into(), - currency: currency.into(), - total_amount, - } - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = val.into(); - self - } - - pub fn start_parameter<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.start_parameter = val.into(); - self - } - - pub fn currency<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.currency = val.into(); - self - } - - pub fn total_amount(mut self, val: i32) -> Self { - self.total_amount = val; - self - } -} diff --git a/src/types/keyboard_button.rs b/src/types/keyboard_button.rs deleted file mode 100644 index fc37134a..00000000 --- a/src/types/keyboard_button.rs +++ /dev/null @@ -1,156 +0,0 @@ -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; - -use crate::types::{KeyboardButtonPollType, True}; - -/// This object represents one button of the reply keyboard. -/// -/// For filter text buttons String can be used instead of this object to specify -/// text of the button. -/// -/// [The official docs](https://core.telegram.org/bots/api#keyboardbutton). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct KeyboardButton { - /// Text of the button. If none of the optional fields are used, it will - /// be sent as a message when the button is pressed. - pub text: String, - - /// Request something from user. - /// - If `Some(Contact)`, the user's phone number will be sent as a contact - /// when the button is pressed. Available in private chats only - /// - If `Some(Location)`, the user's current location will be sent when the - /// button is pressed. Available in private chats only - #[serde(flatten)] - pub request: Option<ButtonRequest>, -} - -impl KeyboardButton { - pub fn new<T>(text: T) -> Self - where - T: Into<String>, - { - Self { text: text.into(), request: None } - } - - pub fn request<T>(mut self, val: T) -> Self - where - T: Into<Option<ButtonRequest>>, - { - self.request = val.into(); - self - } -} - -// Serialize + Deserialize are implemented by hand -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[non_exhaustive] -pub enum ButtonRequest { - Location, - Contact, - KeyboardButtonPollType(KeyboardButtonPollType), -} - -/// Helper struct for (de)serializing [`ButtonRequest`](ButtonRequest) -#[serde_with_macros::skip_serializing_none] -#[derive(Serialize, Deserialize)] -#[non_exhaustive] -struct RawRequest { - /// If `true`, the user's phone number will be sent as a contact - /// when the button is pressed. Available in private chats only. - #[serde(rename = "request_contact")] - contact: Option<True>, - - /// If `true`, the user's current location will be sent when the - /// button is pressed. Available in private chats only. - #[serde(rename = "request_location")] - location: Option<True>, - - /// If specified, the user will be asked to create a poll and - /// send it to the bot when the button is pressed. Available in private - /// chats only. - #[serde(rename = "request_poll")] - poll: Option<KeyboardButtonPollType>, -} - -impl<'de> Deserialize<'de> for ButtonRequest { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - let raw = RawRequest::deserialize(deserializer)?; - match raw { - RawRequest { contact: Some(_), location: Some(_), poll: Some(_) } => { - Err(D::Error::custom( - "`request_contact` and `request_location` fields are mutually exclusive, but \ - both were provided", - )) - } - RawRequest { contact: Some(_), .. } => Ok(Self::Contact), - RawRequest { location: Some(_), .. } => Ok(Self::Location), - RawRequest { poll: Some(poll_type), .. } => Ok(Self::KeyboardButtonPollType(poll_type)), - _ => Err(D::Error::custom( - "Either one of `request_contact` and `request_location` fields is required", - )), - } - } -} - -impl Serialize for ButtonRequest { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - match self { - Self::Contact => { - RawRequest { contact: Some(True), location: None, poll: None }.serialize(serializer) - } - Self::Location => { - RawRequest { contact: None, location: Some(True), poll: None }.serialize(serializer) - } - Self::KeyboardButtonPollType(poll_type) => { - RawRequest { contact: None, location: None, poll: Some(poll_type.clone()) } - .serialize(serializer) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialize_no_request() { - let button = KeyboardButton { text: String::from(""), request: None }; - let expected = r#"{"text":""}"#; - let actual = serde_json::to_string(&button).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn serialize_request_contact() { - let button = - KeyboardButton { text: String::from(""), request: Some(ButtonRequest::Contact) }; - let expected = r#"{"text":"","request_contact":true}"#; - let actual = serde_json::to_string(&button).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn deserialize_no_request() { - let json = r#"{"text":""}"#; - let expected = KeyboardButton { text: String::from(""), request: None }; - let actual = serde_json::from_str(json).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn deserialize_request_contact() { - let json = r#"{"text":"","request_contact":true}"#; - let expected = - KeyboardButton { text: String::from(""), request: Some(ButtonRequest::Contact) }; - let actual = serde_json::from_str(json).unwrap(); - assert_eq!(expected, actual); - } -} diff --git a/src/types/keyboard_button_poll_type.rs b/src/types/keyboard_button_poll_type.rs deleted file mode 100644 index 6b24d540..00000000 --- a/src/types/keyboard_button_poll_type.rs +++ /dev/null @@ -1,24 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct KeyboardButtonPollType { - poll_type: String, -} - -impl KeyboardButtonPollType { - pub fn new<S>(poll_type: S) -> Self - where - S: Into<String>, - { - Self { poll_type: poll_type.into() } - } - - pub fn poll_type<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.poll_type = val.into(); - self - } -} diff --git a/src/types/label_price.rs b/src/types/label_price.rs deleted file mode 100644 index 4c6841a9..00000000 --- a/src/types/label_price.rs +++ /dev/null @@ -1,56 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a portion of the price for goods or services. -/// -/// [The official docs](https://core.telegram.org/bots/api#labeledprice). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct LabeledPrice { - /// Portion label. - pub label: String, - - /// Price of the product in the smallest units of the [currency] (integer, - /// **not** float/double). For example, for a price of `US$ 1.45` pass - /// `amount = 145`. See the exp parameter in [`currencies.json`], it shows - /// the number of digits past the decimal point for each currency (2 - /// for the majority of currencies). - /// - /// [currency]: https://core.telegram.org/bots/payments#supported-currencies - /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json - pub amount: i32, -} - -impl LabeledPrice { - pub fn new<S>(label: S, amount: i32) -> Self - where - S: Into<String>, - { - Self { label: label.into(), amount } - } - - pub fn label<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.label = val.into(); - self - } - - pub fn amount(mut self, val: i32) -> Self { - self.amount = val; - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialize() { - let labeled_price = LabeledPrice { label: "Label".to_string(), amount: 60 }; - let expected = r#"{"label":"Label","amount":60}"#; - let actual = serde_json::to_string(&labeled_price).unwrap(); - assert_eq!(actual, expected); - } -} diff --git a/src/types/location.rs b/src/types/location.rs deleted file mode 100644 index e34cee1a..00000000 --- a/src/types/location.rs +++ /dev/null @@ -1,28 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a point on the map. -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Location { - /// Longitude as defined by sender. - pub longitude: f64, - - /// Latitude as defined by sender. - pub latitude: f64, -} - -impl Location { - pub fn new(longitude: f64, latitude: f64) -> Self { - Self { longitude, latitude } - } - - pub fn latitude(mut self, val: f64) -> Self { - self.latitude = val; - self - } - - pub fn longitude(mut self, val: f64) -> Self { - self.longitude = val; - self - } -} diff --git a/src/types/login_url.rs b/src/types/login_url.rs deleted file mode 100644 index 36515cd6..00000000 --- a/src/types/login_url.rs +++ /dev/null @@ -1,61 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a parameter of the inline keyboard button used to -/// automatically authorize a user. -/// -/// Serves as a great replacement for the [Telegram Login Widget] when the user -/// is coming from Telegram. All the user needs to do is tap/click a button and -/// confirm that they want to log in: -/// -/// <div align="center"> -/// <img src="https://core.telegram.org/file/811140015/1734/8VZFkwWXalM.97872/6127fa62d8a0bf2b3c" width=300 /> -/// </div> -/// -/// [Telegram Login Widget]: https://core.telegram.org/widgets/login -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct LoginUrl { - pub url: String, - pub forward_text: Option<String>, - pub bot_username: Option<String>, - pub request_write_access: Option<bool>, -} - -impl LoginUrl { - pub fn new<S>(url: S) -> Self - where - S: Into<String>, - { - Self { url: url.into(), forward_text: None, bot_username: None, request_write_access: None } - } - - pub fn url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.url = val.into(); - self - } - - pub fn forward_text<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.forward_text = Some(val.into()); - self - } - - pub fn bot_username<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.bot_username = Some(val.into()); - self - } - - pub fn request_write_access<S>(mut self, val: bool) -> Self { - self.request_write_access = Some(val); - self - } -} diff --git a/src/types/mask_position.rs b/src/types/mask_position.rs deleted file mode 100644 index 8bca0273..00000000 --- a/src/types/mask_position.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object describes the position on faces where a mask should be placed by -/// default. -/// -/// [The official docs](https://core.telegram.org/bots/api#maskposition). -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MaskPosition { - /// The part of the face relative to which the mask should be placed. One - /// of `forehead`, `eyes`, `mouth`, or `chin`. - pub point: String, - - /// Shift by X-axis measured in widths of the mask scaled to the face size, - /// from left to right. For example, choosing `-1.0` will place mask just - /// to the left of the default mask position. - pub x_shift: f64, - - /// Shift by Y-axis measured in heights of the mask scaled to the face - /// size, from top to bottom. For example, `1.0` will place the mask just - /// below the default mask position. - pub y_shift: f64, - - /// Mask scaling coefficient. For example, `2.0` means double size. - pub scale: f64, -} - -impl MaskPosition { - pub fn new<S>(point: S, x_shift: f64, y_shift: f64, scale: f64) -> Self - where - S: Into<String>, - { - Self { point: point.into(), x_shift, y_shift, scale } - } - - pub fn point<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.point = val.into(); - self - } - - pub fn x_shift(mut self, val: f64) -> Self { - self.x_shift = val; - self - } - - pub fn y_shift(mut self, val: f64) -> Self { - self.y_shift = val; - self - } - - pub fn scale(mut self, val: f64) -> Self { - self.scale = val; - self - } -} diff --git a/src/types/me.rs b/src/types/me.rs deleted file mode 100644 index 0b46a030..00000000 --- a/src/types/me.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::types::User; -use serde::{Deserialize, Serialize}; - -/// Returned only in [`Bot::get_me`]. -/// -/// [`Bot::get_me`]: crate::Bot::get_me -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Me { - #[serde(flatten)] - pub user: User, - - /// `true`, if the bot can be invited to groups. - pub can_join_groups: bool, - - /// `true`, if [privacy mode] is disabled for the bot. - /// - /// [privacy mode]: https://core.telegram.org/bots#privacy-mode - pub can_read_all_group_messages: bool, - - /// `true`, if the bot supports inline queries. - pub supports_inline_queries: bool, -} - -impl Me { - pub fn new( - user: User, - can_join_groups: bool, - can_read_all_group_messages: bool, - supports_inline_queries: bool, - ) -> Self { - Self { user, can_join_groups, can_read_all_group_messages, supports_inline_queries } - } - - pub fn user(mut self, val: User) -> Self { - self.user = val; - self - } - - #[warn(clippy::wrong_self_convention)] - pub fn can_join_groups<S>(mut self, val: bool) -> Self { - self.can_join_groups = val; - self - } - - #[warn(clippy::wrong_self_convention)] - pub fn can_read_all_group_messages<S>(mut self, val: bool) -> Self { - self.can_read_all_group_messages = val; - self - } - - #[warn(clippy::wrong_self_convention)] - pub fn supports_inline_queries<S>(mut self, val: bool) -> Self { - self.supports_inline_queries = val; - self - } -} diff --git a/src/types/message.rs b/src/types/message.rs deleted file mode 100644 index 79aa8366..00000000 --- a/src/types/message.rs +++ /dev/null @@ -1,1753 +0,0 @@ -#![allow(clippy::large_enum_variant)] - -use serde::{Deserialize, Serialize}; - -use crate::types::{ - chat::{ChatKind, PublicChatKind}, - Animation, Audio, Chat, ChatPublic, Contact, Dice, Document, Game, InlineKeyboardMarkup, - Invoice, Location, MessageEntity, PassportData, PhotoSize, Poll, PublicChatChannel, - PublicChatSupergroup, Sticker, SuccessfulPayment, True, User, Venue, Video, VideoNote, Voice, -}; - -/// This object represents a message. -/// -/// [The official docs](https://core.telegram.org/bots/api#message). -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Message { - /// Unique message identifier inside this chat. - #[serde(rename = "message_id")] - pub id: i32, - - /// Date the message was sent in Unix time. - pub date: i32, - - /// Conversation the message belongs to. - pub chat: Chat, - - /// Bot through which the message was sent. - pub via_bot: Option<User>, - - #[serde(flatten)] - pub kind: MessageKind, -} - -impl Message { - pub fn new(id: i32, date: i32, chat: Chat, kind: MessageKind) -> Self { - Self { id, date, chat, kind, via_bot: None } - } - - pub fn id(mut self, val: i32) -> Self { - self.id = val; - self - } - - pub fn date(mut self, val: i32) -> Self { - self.date = val; - self - } - - pub fn chat(mut self, val: Chat) -> Self { - self.chat = val; - self - } - - pub fn kind(mut self, val: MessageKind) -> Self { - self.kind = val; - self - } - - pub fn via_bot(mut self, val: User) -> Self { - self.via_bot = Some(val); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum MessageKind { - Common(MessageCommon), - NewChatMembers(MessageNewChatMembers), - LeftChatMember(MessageLeftChatMember), - NewChatTitle(MessageNewChatTitle), - NewChatPhoto(MessageNewChatPhoto), - DeleteChatPhoto(MessageDeleteChatPhoto), - GroupChatCreated(MessageGroupChatCreated), - SupergroupChatCreated(MessageSupergroupChatCreated), - ChannelChatCreated(MessageChannelChatCreated), - Migrate(MessageMigrate), - Pinned(MessagePinned), - Invoice(MessageInvoice), - SuccessfulPayment(MessageSuccessfulPayment), - ConnectedWebsite(MessageConnectedWebsite), - PassportData(MessagePassportData), - Dice(MessageDice), -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageCommon { - /// Sender, empty for messages sent to channels. - pub from: Option<User>, - - #[serde(flatten)] - pub forward_kind: ForwardKind, - - /// Date the message was last edited in Unix time. - pub edit_date: Option<i32>, - - #[serde(flatten)] - pub media_kind: MediaKind, - - /// Inline keyboard attached to the message. `login_url` buttons are - /// represented as ordinary `url` buttons. - pub reply_markup: Option<InlineKeyboardMarkup>, -} - -impl MessageCommon { - pub fn new(forward_kind: ForwardKind, media_kind: MediaKind) -> Self { - Self { from: None, forward_kind, edit_date: None, media_kind, reply_markup: None } - } - - pub fn from(mut self, val: User) -> Self { - self.from = Some(val); - self - } - - pub fn forward_kind(mut self, val: ForwardKind) -> Self { - self.forward_kind = val; - self - } - - pub fn edit_date(mut self, val: i32) -> Self { - self.edit_date = Some(val); - self - } - - pub fn media_kind(mut self, val: MediaKind) -> Self { - self.media_kind = val; - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageNewChatMembers { - /// New members that were added to the group or supergroup and - /// information about them (the bot itself may be one of these - /// members). - pub new_chat_members: Vec<User>, -} - -impl MessageNewChatMembers { - pub fn new<N>(new_chat_members: N) -> Self - where - N: Into<Vec<User>>, - { - Self { new_chat_members: new_chat_members.into() } - } - - pub fn new_chat_members<N>(mut self, val: N) -> Self - where - N: Into<Vec<User>>, - { - self.new_chat_members = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageLeftChatMember { - /// A member was removed from the group, information about them (this - /// member may be the bot itself). - pub left_chat_member: User, -} - -impl MessageLeftChatMember { - pub fn new<N>(left_chat_member: N) -> Self - where - N: Into<User>, - { - Self { left_chat_member: left_chat_member.into() } - } - - pub fn left_chat_member<N>(mut self, val: N) -> Self - where - N: Into<User>, - { - self.left_chat_member = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageNewChatTitle { - /// A chat title was changed to this value. - pub new_chat_title: String, -} - -impl MessageNewChatTitle { - pub fn new<N>(new_chat_title: N) -> Self - where - N: Into<String>, - { - Self { new_chat_title: new_chat_title.into() } - } - - pub fn new_chat_title<N>(mut self, val: N) -> Self - where - N: Into<String>, - { - self.new_chat_title = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageNewChatPhoto { - /// A chat photo was change to this value. - pub new_chat_photo: Vec<PhotoSize>, -} - -impl MessageNewChatPhoto { - pub fn new<N>(new_chat_photo: N) -> Self - where - N: Into<Vec<PhotoSize>>, - { - Self { new_chat_photo: new_chat_photo.into() } - } - - pub fn new_chat_photo<N>(mut self, val: N) -> Self - where - N: Into<Vec<PhotoSize>>, - { - self.new_chat_photo = val.into(); - self - } -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageDeleteChatPhoto { - /// Service message: the chat photo was deleted. - pub delete_chat_photo: True, -} - -impl MessageDeleteChatPhoto { - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageGroupChatCreated { - /// Service message: the group has been created. - pub group_chat_created: True, -} - -impl MessageGroupChatCreated { - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageSupergroupChatCreated { - /// Service message: the supergroup has been created. This field can‘t - /// be received in a message coming through updates, because bot can’t - /// be a member of a supergroup when it is created. It can only be - /// found in `reply_to_message` if someone replies to a very first - /// message in a directly created supergroup. - pub supergroup_chat_created: True, -} - -impl MessageSupergroupChatCreated { - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageChannelChatCreated { - /// Service message: the channel has been created. This field can‘t be - /// received in a message coming through updates, because bot can’t be - /// a member of a channel when it is created. It can only be found in - /// `reply_to_message` if someone replies to a very first message in a - /// channel. - pub channel_chat_created: True, -} - -impl MessageChannelChatCreated { - pub fn new() -> Self { - Self::default() - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageMigrate { - /// The group has been migrated to a supergroup with the specified - /// identifier. This number may be greater than 32 bits and some - /// programming languages may have difficulty/silent defects in - /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit - /// integer or double-precision float type are safe for storing this - /// identifier. - pub migrate_to_chat_id: i64, - - /// The supergroup has been migrated from a group with the specified - /// identifier. This number may be greater than 32 bits and some - /// programming languages may have difficulty/silent defects in - /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit - /// integer or double-precision float type are safe for storing this - /// identifier. - pub migrate_from_chat_id: i64, -} - -impl MessageMigrate { - pub fn new(migrate_to_chat_id: i64, migrate_from_chat_id: i64) -> Self { - Self { migrate_to_chat_id, migrate_from_chat_id } - } - - pub fn migrate_to_chat_id(mut self, val: i64) -> Self { - self.migrate_to_chat_id = val; - self - } - - pub fn migrate_from_chat_id(mut self, val: i64) -> Self { - self.migrate_from_chat_id = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessagePinned { - /// Specified message was pinned. Note that the Message object in this - /// field will not contain further `reply_to_message` fields even if it - /// is itself a reply. - #[serde(rename = "pinned_message")] - pub pinned: Box<Message>, -} - -impl MessagePinned { - pub fn new(pinned: Message) -> Self { - Self { pinned: Box::new(pinned) } - } - - pub fn pinned(mut self, val: Message) -> Self { - self.pinned = Box::new(val); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageInvoice { - /// Message is an invoice for a [payment], information about the - /// invoice. [More about payments »]. - /// - /// [payment]: https://core.telegram.org/bots/api#payments - /// [More about payments »]: https://core.telegram.org/bots/api#payments - pub invoice: Invoice, -} - -impl MessageInvoice { - pub fn new(invoice: Invoice) -> Self { - Self { invoice } - } - - pub fn invoice(mut self, val: Invoice) -> Self { - self.invoice = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageSuccessfulPayment { - /// Message is a service message about a successful payment, - /// information about the payment. [More about payments »]. - /// - /// [More about payments »]: https://core.telegram.org/bots/api#payments - pub successful_payment: SuccessfulPayment, -} - -impl MessageSuccessfulPayment { - pub fn new(successful_payment: SuccessfulPayment) -> Self { - Self { successful_payment } - } - - pub fn successful_payment(mut self, val: SuccessfulPayment) -> Self { - self.successful_payment = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageConnectedWebsite { - /// The domain name of the website on which the user has logged in. - /// [More about Telegram Login »]. - /// - /// [More about Telegram Login »]: https://core.telegram.org/widgets/login - pub connected_website: String, -} - -impl MessageConnectedWebsite { - pub fn new<S>(connected_website: S) -> Self - where - S: Into<String>, - { - Self { connected_website: connected_website.into() } - } - - pub fn connected_website<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.connected_website = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessagePassportData { - /// Telegram Passport data. - pub passport_data: PassportData, -} - -impl MessagePassportData { - pub fn new(passport_data: PassportData) -> Self { - Self { passport_data } - } - - pub fn passport_data(mut self, val: PassportData) -> Self { - self.passport_data = val; - self - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub enum ForwardedFrom { - #[serde(rename = "forward_from")] - User(User), - #[serde(rename = "forward_sender_name")] - SenderName(String), -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum ForwardKind { - Channel(ForwardChannel), - NonChannel(ForwardNonChannel), - Origin(ForwardOrigin), -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ForwardChannel { - #[serde(rename = "forward_date")] - pub date: i32, - - #[serde(rename = "forward_from_chat")] - pub chat: Chat, - - #[serde(rename = "forward_from_message_id")] - pub message_id: i32, - - #[serde(rename = "forward_signature")] - pub signature: Option<String>, -} - -impl ForwardChannel { - pub fn new(date: i32, chat: Chat, message_id: i32) -> Self { - Self { date, chat, message_id, signature: None } - } - - pub fn date(mut self, val: i32) -> Self { - self.date = val; - self - } - - pub fn chat(mut self, val: Chat) -> Self { - self.chat = val; - self - } - - pub fn message_id(mut self, val: i32) -> Self { - self.message_id = val; - self - } - - pub fn signature<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.signature = Some(val.into()); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ForwardNonChannel { - #[serde(rename = "forward_date")] - pub date: i32, - - #[serde(flatten)] - pub from: ForwardedFrom, -} - -impl ForwardNonChannel { - pub fn new(date: i32, from: ForwardedFrom) -> Self { - Self { date, from } - } - - pub fn date(mut self, val: i32) -> Self { - self.date = val; - self - } - - pub fn from(mut self, val: ForwardedFrom) -> Self { - self.from = val; - self - } -} - -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ForwardOrigin { - pub reply_to_message: Option<Box<Message>>, -} - -impl ForwardOrigin { - pub fn new() -> Self { - Self::default() - } - - pub fn reply_to_message(mut self, val: Message) -> Self { - self.reply_to_message = Some(Box::new(val)); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[non_exhaustive] -pub enum MediaKind { - Animation(MediaAnimation), - Audio(MediaAudio), - Contact(MediaContact), - Document(MediaDocument), - Game(MediaGame), - Location(MediaLocation), - Photo(MediaPhoto), - Poll(MediaPoll), - Sticker(MediaSticker), - Text(MediaText), - Video(MediaVideo), - VideoNote(MediaVideoNote), - Voice(MediaVoice), - Venue(MediaVenue), -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaAnimation { - /// Message is an animation, information about the animation. For - /// backward compatibility, when this field is set, the document field - /// will also be set. - pub animation: Animation, - - #[doc(hidden)] - /// "For backward compatibility" (c) Telegram Docs. - #[serde(skip)] - pub document: (), - - /// Caption for the animation, 0-1024 characters. - pub caption: Option<String>, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec<MessageEntity>, -} - -impl MediaAnimation { - pub fn new<CE>(animation: Animation, caption_entities: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - Self { animation, document: (), caption: None, caption_entities: caption_entities.into() } - } - - pub fn animation(mut self, val: Animation) -> Self { - self.animation = val; - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities<CE>(mut self, val: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - self.caption_entities = val.into(); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaAudio { - /// Message is an audio file, information about the file. - pub audio: Audio, - - /// Caption for the audio, 0-1024 characters. - pub caption: Option<String>, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec<MessageEntity>, -} - -impl MediaAudio { - pub fn new<CE>(audio: Audio, caption_entities: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - Self { audio, caption: None, caption_entities: caption_entities.into() } - } - - pub fn audio(mut self, val: Audio) -> Self { - self.audio = val; - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities<CE>(mut self, val: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - self.caption_entities = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaContact { - /// Message is a shared contact, information about the contact. - contact: Contact, -} - -impl MediaContact { - pub fn new(contact: Contact) -> Self { - Self { contact } - } - - pub fn contact(mut self, val: Contact) -> Self { - self.contact = val; - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaDocument { - /// Message is a general file, information about the file. - pub document: Document, - - /// Caption for the document, 0-1024 characters. - pub caption: Option<String>, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec<MessageEntity>, -} - -impl MediaDocument { - pub fn new<CE>(document: Document, caption_entities: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - Self { document, caption: None, caption_entities: caption_entities.into() } - } - - pub fn document(mut self, val: Document) -> Self { - self.document = val; - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities<CE>(mut self, val: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - self.caption_entities = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaGame { - /// Message is a game, information about the game. [More - /// about games »]. - /// - /// [More about games »]: https://core.telegram.org/bots/api#games - pub game: Game, -} - -impl MediaGame { - pub fn new(game: Game) -> Self { - Self { game } - } - - pub fn game(mut self, val: Game) -> Self { - self.game = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaLocation { - /// Message is a shared location, information about the location. - pub location: Location, -} - -impl MediaLocation { - pub fn new(location: Location) -> Self { - Self { location } - } - - pub fn location(mut self, val: Location) -> Self { - self.location = val; - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaPhoto { - /// Message is a photo, available sizes of the photo. - pub photo: Vec<PhotoSize>, - - /// Caption for the photo, 0-1024 characters. - pub caption: Option<String>, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec<MessageEntity>, - - /// The unique identifier of a media message group this message belongs - /// to. - pub media_group_id: Option<String>, -} - -impl MediaPhoto { - pub fn new<P, CE>(photo: P, caption_entities: CE) -> Self - where - P: Into<Vec<PhotoSize>>, - CE: Into<Vec<MessageEntity>>, - { - Self { - photo: photo.into(), - caption: None, - caption_entities: caption_entities.into(), - media_group_id: None, - } - } - - pub fn photo<P>(mut self, val: P) -> Self - where - P: Into<Vec<PhotoSize>>, - { - self.photo = val.into(); - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities<CE>(mut self, val: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - self.caption_entities = val.into(); - self - } - - pub fn media_group_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.media_group_id = Some(val.into()); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaPoll { - /// Message is a native poll, information about the poll. - pub poll: Poll, -} - -impl MediaPoll { - pub fn new(poll: Poll) -> Self { - Self { poll } - } - - pub fn poll(mut self, val: Poll) -> Self { - self.poll = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaSticker { - /// Message is a sticker, information about the sticker. - pub sticker: Sticker, -} - -impl MediaSticker { - pub fn new(sticker: Sticker) -> Self { - Self { sticker } - } - - pub fn poll(mut self, val: Sticker) -> Self { - self.sticker = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaText { - /// For text messages, the actual UTF-8 text of the message, 0-4096 - /// characters. - pub text: String, - - /// For text messages, special entities like usernames, URLs, bot - /// commands, etc. that appear in the text. - #[serde(default = "Vec::new")] - pub entities: Vec<MessageEntity>, -} - -impl MediaText { - pub fn new<S, E>(text: S, entities: E) -> Self - where - S: Into<String>, - E: Into<Vec<MessageEntity>>, - { - Self { text: text.into(), entities: entities.into() } - } - - pub fn text<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.text = val.into(); - self - } - - pub fn entities<CE>(mut self, val: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - self.entities = val.into(); - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaVideo { - /// Message is a video, information about the video. - pub video: Video, - - /// Caption for the video, 0-1024 characters. - pub caption: Option<String>, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec<MessageEntity>, - - /// The unique identifier of a media message group this message belongs - /// to. - pub media_group_id: Option<String>, -} - -impl MediaVideo { - pub fn new<CE>(video: Video, caption_entities: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - Self { - video, - caption: None, - caption_entities: caption_entities.into(), - media_group_id: None, - } - } - - pub fn video(mut self, val: Video) -> Self { - self.video = val; - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities<CE>(mut self, val: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - self.caption_entities = val.into(); - self - } - - pub fn media_group_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.media_group_id = Some(val.into()); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaVideoNote { - /// Message is a [video note], information about the video message. - /// - /// [video note]: https://telegram.org/blog/video-messages-and-telescope - pub video_note: VideoNote, -} - -impl MediaVideoNote { - pub fn new(video_note: VideoNote) -> Self { - Self { video_note } - } - - pub fn video_note(mut self, val: VideoNote) -> Self { - self.video_note = val; - self - } -} - -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaVoice { - /// Message is a voice message, information about the file. - pub voice: Voice, - - /// Caption for the voice, 0-1024 characters. - pub caption: Option<String>, - - /// For messages with a caption, special entities like usernames, URLs, - /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] - pub caption_entities: Vec<MessageEntity>, -} - -impl MediaVoice { - pub fn new<CE>(voice: Voice, caption_entities: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - Self { voice, caption: None, caption_entities: caption_entities.into() } - } - - pub fn voice(mut self, val: Voice) -> Self { - self.voice = val; - self - } - - pub fn caption<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.caption = Some(val.into()); - self - } - - pub fn caption_entities<CE>(mut self, val: CE) -> Self - where - CE: Into<Vec<MessageEntity>>, - { - self.caption_entities = val.into(); - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MediaVenue { - /// Message is a venue, information about the venue. - pub venue: Venue, -} - -impl MediaVenue { - pub fn new(venue: Venue) -> Self { - Self { venue } - } - - pub fn venue(mut self, val: Venue) -> Self { - self.venue = val; - self - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageDice { - /// Message is a dice with random value from 1 to 6. - pub dice: Dice, -} - -mod getters { - use std::ops::Deref; - - use crate::types::{ - self, - message::{ - ForwardKind::NonChannel, - MessageKind::{ - ChannelChatCreated, Common, ConnectedWebsite, DeleteChatPhoto, GroupChatCreated, - Invoice, LeftChatMember, Migrate, NewChatMembers, NewChatPhoto, NewChatTitle, - PassportData, Pinned, SuccessfulPayment, SupergroupChatCreated, - }, - }, - Chat, ForwardChannel, ForwardKind, ForwardNonChannel, ForwardOrigin, ForwardedFrom, - MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind, - MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaText, MediaVenue, MediaVideo, - MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageCommon, - MessageConnectedWebsite, MessageDeleteChatPhoto, MessageEntity, MessageGroupChatCreated, - MessageInvoice, MessageLeftChatMember, MessageMigrate, MessageNewChatMembers, - MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned, - MessageSuccessfulPayment, MessageSupergroupChatCreated, PhotoSize, True, User, - }; - - /// Getters for [Message] fields from [telegram docs]. - /// - /// [Message]: crate::types::Message - /// [telegram docs]: https://core.telegram.org/bots/api#message - impl Message { - /// NOTE: this is getter for both `from` and `author_signature` - pub fn from(&self) -> Option<&User> { - match &self.kind { - Common(MessageCommon { from, .. }) => from.as_ref(), - _ => None, - } - } - - pub fn chat_id(&self) -> i64 { - self.chat.id - } - - /// NOTE: this is getter for both `forward_from` and - /// `forward_sender_name` - pub fn forward_from(&self) -> Option<&ForwardedFrom> { - match &self.kind { - Common(MessageCommon { - forward_kind: NonChannel(ForwardNonChannel { from, .. }), - .. - }) => Some(from), - _ => None, - } - } - - pub fn forward_from_chat(&self) -> Option<&Chat> { - match &self.kind { - Common(MessageCommon { - forward_kind: ForwardKind::Channel(ForwardChannel { chat, .. }), - .. - }) => Some(chat), - _ => None, - } - } - - pub fn forward_from_message_id(&self) -> Option<&i32> { - match &self.kind { - Common(MessageCommon { - forward_kind: ForwardKind::Channel(ForwardChannel { message_id, .. }), - .. - }) => Some(message_id), - _ => None, - } - } - - pub fn forward_signature(&self) -> Option<&str> { - match &self.kind { - Common(MessageCommon { - forward_kind: ForwardKind::Channel(ForwardChannel { signature, .. }), - .. - }) => signature.as_ref().map(Deref::deref), - _ => None, - } - } - - pub fn forward_date(&self) -> Option<&i32> { - match &self.kind { - Common(MessageCommon { - forward_kind: ForwardKind::Channel(ForwardChannel { date, .. }), - .. - }) - | Common(MessageCommon { - forward_kind: ForwardKind::NonChannel(ForwardNonChannel { date, .. }), - .. - }) => Some(date), - _ => None, - } - } - - pub fn reply_to_message(&self) -> Option<&Message> { - match &self.kind { - Common(MessageCommon { - forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message, .. }), - .. - }) => reply_to_message.as_ref().map(Deref::deref), - _ => None, - } - } - - pub fn edit_date(&self) -> Option<&i32> { - match &self.kind { - Common(MessageCommon { edit_date, .. }) => edit_date.as_ref(), - _ => None, - } - } - - pub fn media_group_id(&self) -> Option<&str> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Video(MediaVideo { media_group_id, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Photo(MediaPhoto { media_group_id, .. }), - .. - }) => media_group_id.as_ref().map(Deref::deref), - _ => None, - } - } - - pub fn text(&self) -> Option<&str> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Text(MediaText { text, .. }), - .. - }) => Some(text), - _ => None, - } - } - - pub fn text_owned(&self) -> Option<String> { - self.text().map(ToOwned::to_owned) - } - - pub fn entities(&self) -> Option<&[MessageEntity]> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Text(MediaText { entities, .. }), - .. - }) => Some(entities), - _ => None, - } - } - - pub fn caption_entities(&self) -> Option<&[MessageEntity]> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Animation(MediaAnimation { caption_entities, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Audio(MediaAudio { caption_entities, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Document(MediaDocument { caption_entities, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Photo(MediaPhoto { caption_entities, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Video(MediaVideo { caption_entities, .. }), - .. - }) - | Common(MessageCommon { - media_kind: MediaKind::Voice(MediaVoice { caption_entities, .. }), - .. - }) => Some(caption_entities), - _ => None, - } - } - - pub fn audio(&self) -> Option<&types::Audio> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Audio(MediaAudio { audio, .. }), - .. - }) => Some(audio), - _ => None, - } - } - - pub fn document(&self) -> Option<&types::Document> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Document(MediaDocument { document, .. }), - .. - }) => Some(document), - _ => None, - } - } - - pub fn animation(&self) -> Option<&types::Animation> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Animation(MediaAnimation { animation, .. }), - .. - }) => Some(animation), - _ => None, - } - } - - pub fn game(&self) -> Option<&types::Game> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Game(MediaGame { game, .. }), - .. - }) => Some(game), - _ => None, - } - } - - pub fn photo(&self) -> Option<&[PhotoSize]> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Photo(MediaPhoto { photo, .. }), - .. - }) => Some(photo), - _ => None, - } - } - - pub fn sticker(&self) -> Option<&types::Sticker> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Sticker(MediaSticker { sticker, .. }), - .. - }) => Some(sticker), - _ => None, - } - } - - pub fn video(&self) -> Option<&types::Video> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Video(MediaVideo { video, .. }), - .. - }) => Some(video), - _ => None, - } - } - - pub fn voice(&self) -> Option<&types::Voice> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Voice(MediaVoice { voice, .. }), - .. - }) => Some(voice), - _ => None, - } - } - - pub fn video_note(&self) -> Option<&types::VideoNote> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::VideoNote(MediaVideoNote { video_note, .. }), - .. - }) => Some(video_note), - _ => None, - } - } - - pub fn caption(&self) -> Option<&str> { - match &self.kind { - Common(MessageCommon { media_kind, .. }) => match media_kind { - MediaKind::Animation(MediaAnimation { caption, .. }) - | MediaKind::Audio(MediaAudio { caption, .. }) - | MediaKind::Document(MediaDocument { caption, .. }) - | MediaKind::Photo(MediaPhoto { caption, .. }) - | MediaKind::Video(MediaVideo { caption, .. }) - | MediaKind::Voice(MediaVoice { caption, .. }) => { - caption.as_ref().map(Deref::deref) - } - _ => None, - }, - _ => None, - } - } - - pub fn contact(&self) -> Option<&types::Contact> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Contact(MediaContact { contact, .. }), - .. - }) => Some(contact), - _ => None, - } - } - - pub fn location(&self) -> Option<&types::Location> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Location(MediaLocation { location, .. }), - .. - }) => Some(location), - _ => None, - } - } - - pub fn venue(&self) -> Option<&types::Venue> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Venue(MediaVenue { venue, .. }), - .. - }) => Some(venue), - _ => None, - } - } - - pub fn poll(&self) -> Option<&types::Poll> { - match &self.kind { - Common(MessageCommon { - media_kind: MediaKind::Poll(MediaPoll { poll, .. }), - .. - }) => Some(poll), - _ => None, - } - } - - pub fn new_chat_members(&self) -> Option<&[User]> { - match &self.kind { - NewChatMembers(MessageNewChatMembers { new_chat_members }) => { - Some(new_chat_members.as_ref()) - } - _ => None, - } - } - - pub fn left_chat_member(&self) -> Option<&User> { - match &self.kind { - LeftChatMember(MessageLeftChatMember { left_chat_member }) => { - Some(left_chat_member) - } - _ => None, - } - } - - pub fn new_chat_title(&self) -> Option<&str> { - match &self.kind { - NewChatTitle(MessageNewChatTitle { new_chat_title }) => Some(new_chat_title), - _ => None, - } - } - - pub fn new_chat_photo(&self) -> Option<&[PhotoSize]> { - match &self.kind { - NewChatPhoto(MessageNewChatPhoto { new_chat_photo }) => Some(new_chat_photo), - _ => None, - } - } - - // TODO: OK, `Option<True>` is weird, can we do something with it? - // mb smt like `is_delete_chat_photo(&self) -> bool`? - pub fn delete_chat_photo(&self) -> Option<True> { - match &self.kind { - DeleteChatPhoto(MessageDeleteChatPhoto { delete_chat_photo }) => { - Some(*delete_chat_photo) - } - _ => None, - } - } - - pub fn group_chat_created(&self) -> Option<True> { - match &self.kind { - GroupChatCreated(MessageGroupChatCreated { group_chat_created }) => { - Some(*group_chat_created) - } - _ => None, - } - } - - pub fn super_group_chat_created(&self) -> Option<True> { - match &self.kind { - SupergroupChatCreated(MessageSupergroupChatCreated { supergroup_chat_created }) => { - Some(*supergroup_chat_created) - } - _ => None, - } - } - - pub fn channel_chat_created(&self) -> Option<True> { - match &self.kind { - ChannelChatCreated(MessageChannelChatCreated { channel_chat_created }) => { - Some(*channel_chat_created) - } - _ => None, - } - } - - pub fn migrate_to_chat_id(&self) -> Option<i64> { - match &self.kind { - Migrate(MessageMigrate { migrate_to_chat_id, .. }) => Some(*migrate_to_chat_id), - _ => None, - } - } - - pub fn migrate_from_chat_id(&self) -> Option<i64> { - match &self.kind { - Migrate(MessageMigrate { migrate_from_chat_id, .. }) => Some(*migrate_from_chat_id), - _ => None, - } - } - - pub fn pinned_message(&self) -> Option<&Message> { - match &self.kind { - Pinned(MessagePinned { pinned }) => Some(pinned), - _ => None, - } - } - - pub fn invoice(&self) -> Option<&types::Invoice> { - match &self.kind { - Invoice(MessageInvoice { invoice }) => Some(invoice), - _ => None, - } - } - - pub fn successful_payment(&self) -> Option<&types::SuccessfulPayment> { - match &self.kind { - SuccessfulPayment(MessageSuccessfulPayment { successful_payment }) => { - Some(successful_payment) - } - _ => None, - } - } - - pub fn connected_website(&self) -> Option<&str> { - match &self.kind { - ConnectedWebsite(MessageConnectedWebsite { connected_website }) => { - Some(connected_website) - } - _ => None, - } - } - - pub fn passport_data(&self) -> Option<&types::PassportData> { - match &self.kind { - PassportData(MessagePassportData { passport_data }) => Some(passport_data), - _ => None, - } - } - - pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> { - match &self.kind { - Common(MessageCommon { reply_markup, .. }) => reply_markup.as_ref(), - _ => None, - } - } - } -} - -impl Message { - pub fn url(&self) -> Option<reqwest::Url> { - match &self.chat.kind { - ChatKind::Public(ChatPublic { - kind: PublicChatKind::Channel(PublicChatChannel { username: Some(username) }), - .. - }) - | ChatKind::Public(ChatPublic { - kind: - PublicChatKind::Supergroup(PublicChatSupergroup { - username: Some(username), .. - }), - .. - }) => Some( - reqwest::Url::parse(format!("https://t.me/{0}/{1}/", username, self.id).as_str()) - .unwrap(), - ), - _ => None, - } - } -} - -#[cfg(test)] -mod tests { - use serde_json::from_str; - - use crate::types::*; - - #[test] - fn de_media_forwarded() { - let json = r#"{ - "message_id": 198283, - "from": { - "id": 250918540, - "is_bot": false, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "language_code": "en" - }, - "chat": { - "id": 250918540, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "type": "private" - }, - "date": 1567927221, - "video": { - "duration": 13, - "width": 512, - "height": 640, - "mime_type": "video/mp4", - "thumb": { - "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE", - "file_unique_id":"", - "file_size": 10339, - "width": 256, - "height": 320 - }, - "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE", - "file_unique_id":"", - "file_size": 1381334 - } - }"#; - let message = from_str::<Message>(json); - assert!(message.is_ok()); - } - - #[test] - fn de_media_group_forwarded() { - let json = r#"{ - "message_id": 198283, - "from": { - "id": 250918540, - "is_bot": false, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "language_code": "en" - }, - "chat": { - "id": 250918540, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "type": "private" - }, - "date": 1567927221, - "media_group_id": "12543417770506682", - "video": { - "duration": 13, - "width": 512, - "height": 640, - "mime_type": "video/mp4", - "thumb": { - "file_id": "AAQCAAOmBAACBf2oS53pByA-I4CWWCObDwAEAQAHbQADMWcAAhYE", - "file_unique_id":"", - "file_size": 10339, - "width": 256, - "height": 320 - }, - "file_id": "BAADAgADpgQAAgX9qEud6QcgPiOAlhYE", - "file_unique_id":"", - "file_size": 1381334 - } - }"#; - let message = from_str::<Message>(json); - assert!(message.is_ok()); - } - - #[test] - fn de_text() { - let json = r#"{ - "message_id": 199785, - "from": { - "id": 250918540, - "is_bot": false, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "language_code": "en" - }, - "chat": { - "id": 250918540, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "type": "private" - }, - "date": 1568289890, - "text": "Лол кек 😂" - }"#; - let message = from_str::<Message>(json); - assert!(message.is_ok()); - } - - #[test] - fn de_sticker() { - let json = r#"{ - "message_id": 199787, - "from": { - "id": 250918540, - "is_bot": false, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "language_code": "en" - }, - "chat": { - "id": 250918540, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "type": "private" - }, - "date": 1568290188, - "sticker": { - "width": 512, - "height": 512, - "emoji": "😡", - "set_name": "AdvenTimeAnim", - "is_animated": true, - "thumb": { - "file_id": "AAQCAAMjAAOw0PgMaabKAcaXKCBLubkPAAQBAAdtAAPGKwACFgQ", - "file_unique_id":"", - "file_size": 4118, - "width": 128, - "height": 128 - }, - "file_id": "CAADAgADIwADsND4DGmmygHGlyggFgQ", - "file_unique_id":"", - "file_size": 16639 - } - }"#; - let message = from_str::<Message>(json); - assert!(message.is_ok()); - } - - #[test] - fn de_image() { - let json = r#"{ - "message_id": 199791, - "from": { - "id": 250918540, - "is_bot": false, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "language_code": "en" - }, - "chat": { - "id": 250918540, - "first_name": "Андрей", - "last_name": "Власов", - "username": "aka_dude", - "type": "private" - }, - "date": 1568290622, - "photo": [ - { - "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA20AAybcBAABFgQ", - "file_unique_id":"", - "file_size": 18188, - "width": 320, - "height": 239 - }, - { - "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3gAAyfcBAABFgQ", - "file_unique_id":"", - "file_size": 62123, - "width": 800, - "height": 598 - }, - { - "file_id": "AgADAgAD36sxG-PX0UvQSXIn9rccdw-ACA4ABAEAAwIAA3kAAyTcBAABFgQ", - "file_unique_id":"", - "file_size": 75245, - "width": 962, - "height": 719 - } - ] - }"#; - let message = from_str::<Message>(json); - assert!(message.is_ok()); - } -} diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs deleted file mode 100644 index c39459be..00000000 --- a/src/types/message_entity.rs +++ /dev/null @@ -1,162 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Message, User}; - -/// This object represents one special entity in a text message. -/// -/// For example, hashtags, usernames, URLs, etc. -/// -/// [The official docs](https://core.telegram.org/bots/api#messageentity). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct MessageEntity { - #[serde(flatten)] - pub kind: MessageEntityKind, - - /// Offset in UTF-16 code units to the start of the entity. - pub offset: usize, - - /// Length of the entity in UTF-16 code units. - pub length: usize, -} - -impl MessageEntity { - pub fn new(kind: MessageEntityKind, offset: usize, length: usize) -> Self { - Self { kind, offset, length } - } - - pub fn kind(mut self, val: MessageEntityKind) -> Self { - self.kind = val; - self - } - - pub fn offset(mut self, val: usize) -> Self { - self.offset = val; - self - } - - pub fn length(mut self, val: usize) -> Self { - self.length = val; - self - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[serde(tag = "type")] -#[non_exhaustive] -pub enum MessageEntityKind { - Mention, - Hashtag, - Cashtag, - BotCommand, - Url, - Email, - PhoneNumber, - Bold, - Italic, - Code, - Pre { language: Option<String> }, - TextLink { url: String }, - TextMention { user: User }, - Underline, - Strikethrough, -} - -impl MessageEntity { - pub fn text_from(&self, message: &Message) -> Option<String> { - let text = message.text(); - Some(String::from(&text?[self.offset..self.offset + self.length])) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::{ - Chat, ChatKind, ChatPrivate, ForwardKind, ForwardOrigin, MediaKind, MediaText, - MessageCommon, MessageKind, - }; - - #[test] - fn recursive_kind() { - use serde_json::from_str; - - assert_eq!( - MessageEntity { - kind: MessageEntityKind::TextLink { url: "ya.ru".into() }, - offset: 1, - length: 2, - }, - from_str::<MessageEntity>( - r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"# - ) - .unwrap() - ); - } - - #[test] - fn pre() { - use serde_json::from_str; - - assert_eq!( - MessageEntity { - kind: MessageEntityKind::Pre { language: Some("rust".to_string()) }, - offset: 1, - length: 2, - }, - from_str::<MessageEntity>( - r#"{"type":"pre","url":"ya.ru","offset":1,"length":2,"language":"rust"}"# - ) - .unwrap() - ); - } - - #[test] - fn text_from() { - let message = message(); - let expected = Some("yes".to_string()); - let entity = message.entities().unwrap()[0].clone(); - let actual = entity.text_from(&message); - assert_eq!(actual, expected); - } - - fn message() -> Message { - Message { - via_bot: None, - id: 0, - date: 0, - chat: Chat { - id: 0, - kind: ChatKind::Private(ChatPrivate { - type_: (), - username: None, - first_name: None, - last_name: None, - }), - photo: None, - }, - kind: MessageKind::Common(MessageCommon { - from: Some(User { - id: 0, - is_bot: false, - first_name: "".to_string(), - last_name: None, - username: None, - language_code: None, - }), - forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message: None }), - edit_date: None, - media_kind: MediaKind::Text(MediaText { - text: "no yes no".to_string(), - entities: vec![MessageEntity { - kind: MessageEntityKind::Mention, - offset: 3, - length: 3, - }], - }), - reply_markup: None, - }), - } - } -} diff --git a/src/types/mod.rs b/src/types/mod.rs deleted file mode 100644 index a8f01b30..00000000 --- a/src/types/mod.rs +++ /dev/null @@ -1,194 +0,0 @@ -//! API types. - -pub use allowed_update::*; -pub use animation::*; -pub use audio::*; -pub use bot_command::*; -pub use callback_game::*; -pub use callback_query::*; -pub use chat::*; -pub use chat_action::*; -pub use chat_id::*; -pub use chat_member::*; -pub use chat_or_inline_message::*; -pub use chat_permissions::*; -pub use chat_photo::*; -pub use chosen_inline_result::*; -pub use contact::*; -pub use dice::*; -pub use dice_emoji::*; -pub use document::*; -pub use encrypted_credentials::*; -pub use encrypted_passport_element::*; -pub use file::*; -pub use force_reply::*; -pub use game::*; -pub use game_high_score::*; -pub use inline_keyboard_button::*; -pub use inline_keyboard_markup::*; -pub use inline_query::*; -pub use inline_query_result::*; -pub use inline_query_result_article::*; -pub use inline_query_result_audio::*; -pub use inline_query_result_cached_audio::*; -pub use inline_query_result_cached_document::*; -pub use inline_query_result_cached_gif::*; -pub use inline_query_result_cached_mpeg4_gif::*; -pub use inline_query_result_cached_photo::*; -pub use inline_query_result_cached_sticker::*; -pub use inline_query_result_cached_video::*; -pub use inline_query_result_cached_voice::*; -pub use inline_query_result_contact::*; -pub use inline_query_result_document::*; -pub use inline_query_result_game::*; -pub use inline_query_result_gif::*; -pub use inline_query_result_location::*; -pub use inline_query_result_mpeg4_gif::*; -pub use inline_query_result_photo::*; -pub use inline_query_result_venue::*; -pub use inline_query_result_video::*; -pub use inline_query_result_voice::*; -pub use input_file::*; -pub use input_media::*; -pub use input_message_content::*; -pub use invoice::*; -pub use keyboard_button::*; -pub use keyboard_button_poll_type::*; -pub use label_price::*; -pub use location::*; -pub use login_url::*; -pub use mask_position::*; -pub use me::*; -pub use message::*; -pub use message_entity::*; -pub use order_info::*; -pub use parse_mode::*; -pub use passport_data::*; -pub use passport_element_error::*; -pub use passport_file::*; -pub use photo_size::*; -pub use poll::*; -pub use poll_answer::*; -pub use poll_type::*; -pub use pre_checkout_query::*; -pub use reply_keyboard_markup::*; -pub use reply_keyboard_remove::*; -pub use reply_markup::*; -pub use response_parameters::*; -pub use send_invoice::*; -pub use shipping_address::*; -pub use shipping_option::*; -pub use shipping_query::*; -pub use sticker::*; -pub use sticker_set::*; -pub use sticker_type::*; -pub use successful_payment::*; -pub use unit_false::*; -pub use unit_true::*; -pub use update::*; -pub use user::*; -pub use user_profile_photos::*; -pub use venue::*; -pub use video::*; -pub use video_note::*; -pub use voice::*; -pub use webhook_info::*; - -mod allowed_update; -mod animation; -mod audio; -mod bot_command; -mod callback_game; -mod callback_query; -mod chat; -mod chat_action; -mod chat_id; -mod chat_member; -mod chat_or_inline_message; -mod chat_permissions; -mod chat_photo; -mod chosen_inline_result; -mod contact; -mod dice; -mod dice_emoji; -mod document; -mod file; -mod force_reply; -mod game; -mod game_high_score; -mod inline_keyboard_button; -mod inline_keyboard_markup; -mod input_file; -mod input_media; -mod input_message_content; -mod invoice; -mod keyboard_button; -mod keyboard_button_poll_type; -mod label_price; -mod location; -mod login_url; -mod mask_position; -mod me; -mod message; -mod message_entity; -mod order_info; -mod parse_mode; -mod photo_size; -mod poll; -mod poll_answer; -mod poll_type; -mod pre_checkout_query; -mod reply_keyboard_markup; -mod reply_keyboard_remove; -mod reply_markup; -mod response_parameters; -mod send_invoice; -mod shipping_address; -mod shipping_option; -mod shipping_query; -mod sticker; -mod sticker_set; -mod sticker_type; -mod successful_payment; -mod unit_false; -mod unit_true; -mod update; -mod user; -mod user_profile_photos; -mod venue; -mod video; -mod video_note; -mod voice; -mod webhook_info; - -mod inline_query; -mod inline_query_result; -mod inline_query_result_article; -mod inline_query_result_audio; -mod inline_query_result_cached_audio; -mod inline_query_result_cached_document; -mod inline_query_result_cached_gif; -mod inline_query_result_cached_mpeg4_gif; -mod inline_query_result_cached_photo; -mod inline_query_result_cached_sticker; -mod inline_query_result_cached_video; -mod inline_query_result_cached_voice; -mod inline_query_result_contact; -mod inline_query_result_document; -mod inline_query_result_game; -mod inline_query_result_gif; -mod inline_query_result_location; -mod inline_query_result_mpeg4_gif; -mod inline_query_result_photo; -mod inline_query_result_venue; -mod inline_query_result_video; -mod inline_query_result_voice; - -mod encrypted_credentials; -mod encrypted_passport_element; -mod passport_data; -mod passport_element_error; -mod passport_file; - -pub use non_telegram_types::*; -mod non_telegram_types; diff --git a/src/types/non_telegram_types/country_code.rs b/src/types/non_telegram_types/country_code.rs deleted file mode 100644 index 4f7705ac..00000000 --- a/src/types/non_telegram_types/country_code.rs +++ /dev/null @@ -1,254 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub enum CountryCode { - AD, - AE, - AF, - AG, - AI, - AL, - AM, - AO, - AQ, - AR, - AS, - AT, - AU, - AW, - AX, - AZ, - BA, - BB, - BD, - BE, - BF, - BG, - BH, - BI, - BJ, - BL, - BM, - BN, - BO, - BQ, - BR, - BS, - BT, - BV, - BW, - BY, - BZ, - CA, - CC, - CD, - CF, - CG, - CH, - CI, - CK, - CL, - CM, - CN, - CO, - CR, - CU, - CV, - CW, - CX, - CY, - CZ, - DE, - DJ, - DK, - DM, - DO, - DZ, - EC, - EE, - EG, - EH, - ER, - ES, - ET, - FI, - FJ, - FK, - FM, - FO, - FR, - GA, - GB, - GD, - GE, - GF, - GG, - GH, - GI, - GL, - GM, - GN, - GP, - GQ, - GR, - GS, - GT, - GU, - GW, - GY, - HK, - HM, - HN, - HR, - HT, - HU, - ID, - IE, - IL, - IM, - IN, - IO, - IQ, - IR, - IS, - IT, - JE, - JM, - JO, - JP, - KE, - KG, - KH, - KI, - KM, - KN, - KP, - KR, - KW, - KY, - KZ, - LA, - LB, - LC, - LI, - LK, - LR, - LS, - LT, - LU, - LV, - LY, - MA, - MC, - MD, - ME, - MF, - MG, - MH, - MK, - ML, - MM, - MN, - MO, - MP, - MQ, - MR, - MS, - MT, - MU, - MV, - MW, - MX, - MY, - MZ, - NA, - NC, - NE, - NF, - NG, - NI, - NL, - NO, - NP, - NR, - NU, - NZ, - OM, - PA, - PE, - PF, - PG, - PH, - PK, - PL, - PM, - PN, - PR, - PS, - PT, - PW, - PY, - QA, - RE, - RO, - RS, - RU, - RW, - SA, - SB, - SC, - SD, - SE, - SG, - SH, - SI, - SJ, - SK, - SL, - SM, - SN, - SO, - SR, - SS, - ST, - SV, - SX, - SY, - SZ, - TC, - TD, - TF, - TG, - TH, - TJ, - TK, - TL, - TM, - TN, - TO, - TR, - TT, - TV, - TW, - TZ, - UA, - UG, - UM, - US, - UY, - UZ, - VA, - VC, - VE, - VG, - VI, - VN, - VU, - WF, - WS, - YE, - YT, - ZA, - ZM, - ZW, -} diff --git a/src/types/non_telegram_types/currency.rs b/src/types/non_telegram_types/currency.rs deleted file mode 100644 index 0cdebce6..00000000 --- a/src/types/non_telegram_types/currency.rs +++ /dev/null @@ -1,89 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub enum Currency { - AED, - AFN, - ALL, - AMD, - ARS, - AUD, - AZN, - BAM, - BDT, - BGN, - BND, - BOB, - BRL, - CAD, - CHF, - CLP, - CNY, - COP, - CRC, - CZK, - DKK, - DOP, - DZD, - EGP, - EUR, - GBP, - GEL, - GTQ, - HKD, - HNL, - HRK, - HUF, - IDR, - ILS, - INR, - ISK, - JMD, - JPY, - KES, - KGS, - KRW, - KZT, - LBP, - LKR, - MAD, - MDL, - MNT, - MUR, - MVR, - MXN, - MYR, - MZN, - NGN, - NIO, - NOK, - NPR, - NZD, - PAB, - PEN, - PHP, - PKR, - PLN, - PYG, - QAR, - RON, - RSD, - RUB, - SAR, - SEK, - SGD, - THB, - TJS, - TRY, - TTD, - TWD, - TZS, - UAH, - UGX, - USD, - UYU, - UZS, - VND, - YER, - ZAR, -} diff --git a/src/types/non_telegram_types/mime_wrapper.rs b/src/types/non_telegram_types/mime_wrapper.rs deleted file mode 100644 index 848b3376..00000000 --- a/src/types/non_telegram_types/mime_wrapper.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::fmt::Formatter; - -use derive_more::From; -use mime::Mime; -use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; - -/// Serializable & deserializable `MIME` wrapper. -#[derive(Clone, Debug, Eq, Hash, PartialEq, From)] -pub struct MimeWrapper(pub Mime); - -impl Serialize for MimeWrapper { - fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> - where - S: Serializer, - { - serializer.serialize_str(self.0.as_ref()) - } -} - -struct MimeVisitor; -impl<'a> Visitor<'a> for MimeVisitor { - type Value = MimeWrapper; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - formatter.write_str("mime type") - } - - fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> - where - E: serde::de::Error, - { - match v.parse::<Mime>() { - Ok(mime_type) => Ok(MimeWrapper(mime_type)), - Err(e) => Err(E::custom(e)), - } - } -} - -impl<'de> Deserialize<'de> for MimeWrapper { - fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(MimeVisitor) - } -} diff --git a/src/types/non_telegram_types/mod.rs b/src/types/non_telegram_types/mod.rs deleted file mode 100644 index 8add54b6..00000000 --- a/src/types/non_telegram_types/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub use country_code::*; -pub use currency::*; -pub use mime_wrapper::*; - -mod country_code; -mod currency; -mod mime_wrapper; diff --git a/src/types/order_info.rs b/src/types/order_info.rs deleted file mode 100644 index 7f41f7a9..00000000 --- a/src/types/order_info.rs +++ /dev/null @@ -1,72 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::ShippingAddress; - -/// This object represents information about an order. -/// -/// [The official docs](https://core.telegram.org/bots/api#orderinfo). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct OrderInfo { - /// User's name. - pub name: String, - - /// User's phone number. - pub phone_number: String, - - /// User's email. - pub email: String, - - /// User's shipping address. - pub shipping_address: ShippingAddress, -} - -impl OrderInfo { - pub fn new<S1, S2, S3>( - name: S1, - phone_number: S2, - email: S3, - shipping_address: ShippingAddress, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - name: name.into(), - phone_number: phone_number.into(), - email: email.into(), - shipping_address, - } - } - - pub fn name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.name = val.into(); - self - } - - pub fn phone_number<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.phone_number = val.into(); - self - } - - pub fn email<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.email = val.into(); - self - } - - pub fn shipping_address(mut self, val: ShippingAddress) -> Self { - self.shipping_address = val; - self - } -} diff --git a/src/types/parse_mode.rs b/src/types/parse_mode.rs deleted file mode 100644 index 44a0ecff..00000000 --- a/src/types/parse_mode.rs +++ /dev/null @@ -1,189 +0,0 @@ -// see https://github.com/rust-lang/rust/issues/38832 -// (for built ins there no warnings, but for (De)Serialize, there are) -#![allow(deprecated)] - -use std::{ - convert::{TryFrom, TryInto}, - str::FromStr, -}; - -use serde::{Deserialize, Serialize}; - -/// Formatting options. -/// -/// The Bot API supports basic formatting for messages. You can use bold, -/// italic, underlined and strikethrough text, as well as inline links and -/// pre-formatted code in your bots' messages. Telegram clients will render -/// them accordingly. You can use either markdown-style or HTML-style -/// formatting. -/// -/// Note that Telegram clients will display an **alert** to the user before -/// opening an inline link (‘Open this link?’ together with the full URL). -/// -/// Links `tg://user?id=<user_id>` can be used to mention a user by their ID -/// without using a username. Please note: -/// -/// - These links will work **only** if they are used inside an inline link. For -/// example, they will not work, when used in an inline keyboard button or in -/// a message text. -/// - These mentions are only guaranteed to work if the user has contacted the -/// bot in the past, has sent a callback query to the bot via inline button or -/// is a member in the group where he was mentioned. -/// -/// ## MarkdownV2 style -/// -/// To use this mode, pass [`MarkdownV2`] in the `parse_mode` field. -/// Use the following syntax in your message: -/// ````text -/// *bold \*text* -/// _italic \*text_ -/// __underline__ -/// ~strikethrough~ -/// *bold _italic bold ~italic bold strikethrough~ __underline italic bold___ bold* -/// [inline URL](http://www.example.com/) -/// [inline mention of a user](tg://user?id=123456789) -/// `inline fixed-width code` -/// ``` -/// pre-formatted fixed-width code block -/// ``` -/// ```rust -/// pre-formatted fixed-width code block written in the Rust programming -/// language ``` -/// ```` -/// -/// Please note: -/// - Any character between 1 and 126 inclusively can be escaped anywhere with a -/// preceding '\' character, in which case it is treated as an ordinary -/// character and not a part of the markup. -/// - Inside `pre` and `code` entities, all '`‘ and ’\‘ characters must be -/// escaped with a preceding ’\' character. -/// - Inside `(...)` part of inline link definition, all ')‘ and ’\‘ must be -/// escaped with a preceding ’\' character. -/// - In all other places characters ’_‘, ’*‘, ’[‘, ’]‘, ’(‘, ’)‘, ’~‘, ’`‘, -/// ’>‘, ’#‘, ’+‘, ’+‘, ’-‘, ’|‘, ’{‘, ’}‘, ’.‘, ’!‘ must be escaped with the -/// preceding character ’\'. -/// - In case of ambiguity between `italic` and `underline` entities ‘__’ is -/// always greadily treated from left to right as beginning or end of -/// `underline` entity, so instead of `___italic underline___` use `___italic -/// underline_\r__`, where `\r` is a character with code `13`, which will be -/// ignored. -/// -/// ## HTML style -/// To use this mode, pass [`HTML`] in the `parse_mode` field. -/// The following tags are currently supported: -/// ````text -/// <b>bold</b>, <strong>bold</strong> -/// <i>italic</i>, <em>italic</em> -/// <u>underline</u>, <ins>underline</ins> -/// <s>strikethrough</s>, <strike>strikethrough</strike>, -/// <del>strikethrough</del> <b>bold <i>italic bold <s>italic bold -/// strikethrough</s> <u>underline italic bold</u></i> bold</b> <a href="http:// www.example.com/">inline URL</a> -/// <a href="tg:// user?id=123456789">inline mention of a user</a> -/// <code>inline fixed-width code</code> -/// <pre>pre-formatted fixed-width code block</pre> -/// <pre><code class="language-rust">pre-formatted fixed-width code block -/// written in the Rust programming language</code></pre> ```` -/// -/// Please note: -/// -/// - Only the tags mentioned above are currently supported. -/// - All `<`, `>` and `&` symbols that are not a part of a tag or an HTML -/// entity must be replaced with the corresponding HTML entities (`<` with -/// `<`, `>` with `>` and `&` with `&`). -/// - All numerical HTML entities are supported. -/// - The API currently supports only the following named HTML entities: `<`, -/// `>`, `&` and `"`. -/// - Use nested `pre` and `code` tags, to define programming language for `pre` -/// entity. -/// - Programming language can't be specified for standalone `code` tags. -/// -/// ## Markdown style -/// This is a legacy mode, retained for backward compatibility. To use this -/// mode, pass [`Markdown`] in the `parse_mode` field. -/// Use the following syntax in your message: -/// ````text -/// *bold text* -/// _italic text_ -/// [inline URL](http://www.example.com/) -/// [inline mention of a user](tg://user?id=123456789) -/// `inline fixed-width code` -/// ```rust -/// pre-formatted fixed-width code block written in the Rust programming -/// language ``` -/// ```` -/// -/// Please note: -/// - Entities must not be nested, use parse mode [`MarkdownV2`] instead. -/// - There is no way to specify underline and strikethrough entities, use parse -/// mode [`MarkdownV2`] instead. -/// - To escape characters ’_‘, ’*‘, ’`‘, ’[‘ outside of an entity, prepend the -/// characters ’\' before them. -/// - Escaping inside entities is not allowed, so entity must be closed first -/// and reopened again: use `_snake_\__case_` for italic `snake_case` and -/// `*2*\**2=4*` for bold `2*2=4`. -/// -/// [`MarkdownV2`]: ParseMode::MarkdownV2 -/// [`HTML`]: ParseMode::HTML -/// [`Markdown`]: ParseMode::Markdown -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub enum ParseMode { - MarkdownV2, - HTML, - #[deprecated = "This is a legacy mode, retained for backward compatibility. Use `MarkdownV2` \ - instead."] - Markdown, -} - -impl TryFrom<&str> for ParseMode { - type Error = (); - - fn try_from(value: &str) -> Result<Self, Self::Error> { - let normalized = value.to_lowercase(); - match normalized.as_ref() { - "html" => Ok(ParseMode::HTML), - "markdown" => Ok(ParseMode::Markdown), - "markdownv2" => Ok(ParseMode::MarkdownV2), - _ => Err(()), - } - } -} - -impl TryFrom<String> for ParseMode { - type Error = (); - - fn try_from(value: String) -> Result<Self, Self::Error> { - value.as_str().try_into() - } -} - -impl FromStr for ParseMode { - type Err = (); - - fn from_str(s: &str) -> Result<Self, Self::Err> { - s.try_into() - } -} - -#[cfg(test)] -mod tests { - #![allow(deprecated)] - - use super::*; - - #[test] - fn html_serialization() { - let expected_json = String::from(r#""HTML""#); - let actual_json = serde_json::to_string(&ParseMode::HTML).unwrap(); - - assert_eq!(expected_json, actual_json) - } - - #[test] - fn markdown_serialization() { - let expected_json = String::from(r#""Markdown""#); - let actual_json = serde_json::to_string(&ParseMode::Markdown).unwrap(); - - assert_eq!(expected_json, actual_json) - } -} diff --git a/src/types/passport_data.rs b/src/types/passport_data.rs deleted file mode 100644 index 3abaef93..00000000 --- a/src/types/passport_data.rs +++ /dev/null @@ -1,40 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use super::{EncryptedCredentials, EncryptedPassportElement}; - -/// Contains information about Telegram Passport data shared with the bot by the -/// user. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportdata). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportData { - /// Array with information about documents and other Telegram Passport - /// elements that was shared with the bot. - pub data: Vec<EncryptedPassportElement>, - - /// Encrypted credentials required to decrypt the data. - pub credentials: EncryptedCredentials, -} - -impl PassportData { - pub fn new<E>(data: E, credentials: EncryptedCredentials) -> Self - where - E: Into<Vec<EncryptedPassportElement>>, - { - Self { data: data.into(), credentials } - } - - pub fn data<E>(mut self, val: E) -> Self - where - E: Into<Vec<EncryptedPassportElement>>, - { - self.data = val.into(); - self - } - - pub fn credentials(mut self, val: EncryptedCredentials) -> Self { - self.credentials = val; - self - } -} diff --git a/src/types/passport_element_error.rs b/src/types/passport_element_error.rs deleted file mode 100644 index 521584e5..00000000 --- a/src/types/passport_element_error.rs +++ /dev/null @@ -1,558 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents an error in the Telegram Passport element which was -/// submitted that should be resolved by the user. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerror). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementError { - /// Error message. - message: String, - - #[serde(flatten)] - kind: PassportElementErrorKind, -} - -impl PassportElementError { - pub fn new<S>(message: S, kind: PassportElementErrorKind) -> Self - where - S: Into<String>, - { - Self { message: message.into(), kind } - } - - pub fn message<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.message = val.into(); - self - } - - pub fn kind(mut self, val: PassportElementErrorKind) -> Self { - self.kind = val; - self - } -} - -#[serde(tag = "source")] -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub enum PassportElementErrorKind { - #[serde(rename = "data")] - DataField(PassportElementErrorDataField), - - #[serde(rename = "snake_case")] - FrontSide(PassportElementErrorFrontSide), - - #[serde(rename = "snake_case")] - ReverseSide(PassportElementErrorReverseSide), - - #[serde(rename = "snake_case")] - Selfie(PassportElementErrorSelfie), - - #[serde(rename = "snake_case")] - File(PassportElementErrorFile), - - #[serde(rename = "snake_case")] - Files(PassportElementErrorFiles), - - #[serde(rename = "snake_case")] - TranslationFile(PassportElementErrorTranslationFile), - - #[serde(rename = "snake_case")] - TranslationFiles(PassportElementErrorTranslationFiles), - - #[serde(rename = "snake_case")] - Unspecified(PassportElementErrorUnspecified), -} - -/// Represents an issue in one of the data fields that was provided by the -/// user. -/// -/// The error is considered resolved when the field's value changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrordatafield). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorDataField { - /// The section of the user's Telegram Passport which has the error. - pub r#type: PassportElementErrorDataFieldType, - - /// Name of the data field which has the error. - pub field_name: String, - - /// Base64-encoded data hash. - pub data_hash: String, -} - -impl PassportElementErrorDataField { - pub fn new<S1, S2>( - r#type: PassportElementErrorDataFieldType, - field_name: S1, - data_hash: S2, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { r#type, field_name: field_name.into(), data_hash: data_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorDataFieldType) -> Self { - self.r#type = val; - self - } - - pub fn field_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.field_name = val.into(); - self - } - - pub fn data_hash<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.data_hash = val.into(); - self - } -} - -/// Represents an issue with the front side of a document. -/// -/// The error is considered resolved when the file with the front side of the -/// document changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfrontside). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorFrontSide { - /// The section of the user's Telegram Passport which has the issue. - pub r#type: PassportElementErrorFrontSideType, - - /// Base64-encoded hash of the file with the front side of the - /// document. - pub file_hash: String, -} - -impl PassportElementErrorFrontSide { - pub fn new<S>(r#type: PassportElementErrorFrontSideType, file_hash: S) -> Self - where - S: Into<String>, - { - Self { r#type, file_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorFrontSideType) -> Self { - self.r#type = val; - self - } - - pub fn file_hash<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_hash = val.into(); - self - } -} - -/// Represents an issue with the reverse side of a document. -/// -/// The error is considered resolved when the file with reverse side of the -/// document changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorreverseside). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorReverseSide { - /// The section of the user's Telegram Passport which has the issue. - pub r#type: PassportElementErrorReverseSideType, - - //// Base64-encoded hash of the file with the reverse side of the - //// document. - pub file_hash: String, -} - -impl PassportElementErrorReverseSide { - pub fn new<S>(r#type: PassportElementErrorReverseSideType, file_hash: S) -> Self - where - S: Into<String>, - { - Self { r#type, file_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorReverseSideType) -> Self { - self.r#type = val; - self - } - - pub fn file_hash<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_hash = val.into(); - self - } -} - -//// Represents an issue with the selfie with a document. -// -/// The error is considered resolved when the file with the selfie changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorselfie). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorSelfie { - /// The section of the user's Telegram Passport which has the issue. - pub r#type: PassportElementErrorSelfieType, - - /// Base64-encoded hash of the file with the selfie. - pub file_hash: String, -} - -impl PassportElementErrorSelfie { - pub fn new<S>(r#type: PassportElementErrorSelfieType, file_hash: S) -> Self - where - S: Into<String>, - { - Self { r#type, file_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorSelfieType) -> Self { - self.r#type = val; - self - } - - pub fn file_hash<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_hash = val.into(); - self - } -} - -/// Represents an issue with a document scan. -/// -/// The error is considered resolved when the file with the document scan -/// changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfile). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorFile { - /// The section of the user's Telegram Passport which has the issue. - pub r#type: PassportElementErrorFileType, - - /// Base64-encoded file hash. - pub file_hash: String, -} - -impl PassportElementErrorFile { - pub fn new<S>(r#type: PassportElementErrorFileType, file_hash: S) -> Self - where - S: Into<String>, - { - Self { r#type, file_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorFileType) -> Self { - self.r#type = val; - self - } - - pub fn file_hash<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_hash = val.into(); - self - } -} - -/// Represents an issue with a list of scans. -/// -/// The error is considered resolved when the list of files containing the scans -/// changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorfiles). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorFiles { - /// The section of the user's Telegram Passport which has the issue. - pub r#type: PassportElementErrorFilesType, - - /// List of base64-encoded file hashes. - pub file_hashes: Vec<String>, -} - -impl PassportElementErrorFiles { - pub fn new<S>(r#type: PassportElementErrorFilesType, file_hashes: S) -> Self - where - S: Into<Vec<String>>, - { - Self { r#type, file_hashes: file_hashes.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorFilesType) -> Self { - self.r#type = val; - self - } - - pub fn file_hashes<S>(mut self, val: S) -> Self - where - S: Into<Vec<String>>, - { - self.file_hashes = val.into(); - self - } -} - -/// Represents an issue with one of the files that constitute the -/// translation of a document. -/// -/// The error is considered resolved when the file changes. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrortranslationfile). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorTranslationFile { - /// Type of element of the user's Telegram Passport which has the - /// issue. - pub r#type: PassportElementErrorTranslationFileType, - - /// Base64-encoded file hash. - pub file_hash: String, -} - -impl PassportElementErrorTranslationFile { - pub fn new<S>(r#type: PassportElementErrorTranslationFileType, file_hash: S) -> Self - where - S: Into<String>, - { - Self { r#type, file_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorTranslationFileType) -> Self { - self.r#type = val; - self - } - - pub fn file_hash<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_hash = val.into(); - self - } -} - -/// Represents an issue with the translated version of a document. -/// -/// The error is considered resolved when a file with the document translation -/// change. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrortranslationfiles). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorTranslationFiles { - /// Type of element of the user's Telegram Passport which has the issue - pub r#type: PassportElementErrorTranslationFilesType, - - /// List of base64-encoded file hashes - pub file_hashes: Vec<String>, -} - -impl PassportElementErrorTranslationFiles { - pub fn new<S>(r#type: PassportElementErrorTranslationFilesType, file_hashes: S) -> Self - where - S: Into<Vec<String>>, - { - Self { r#type, file_hashes: file_hashes.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorTranslationFilesType) -> Self { - self.r#type = val; - self - } - - pub fn file_hashes<S>(mut self, val: S) -> Self - where - S: Into<Vec<String>>, - { - self.file_hashes = val.into(); - self - } -} - -/// Represents an issue in an unspecified place. -/// -/// The error is considered resolved when new data is added. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportelementerrorunspecified). -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportElementErrorUnspecified { - /// Type of element of the user's Telegram Passport which has the - /// issue. - pub r#type: PassportElementErrorUnspecifiedType, - - /// Base64-encoded element hash. - pub element_hash: String, -} - -impl PassportElementErrorUnspecified { - pub fn new<S>(r#type: PassportElementErrorUnspecifiedType, file_hash: S) -> Self - where - S: Into<String>, - { - Self { r#type, element_hash: file_hash.into() } - } - - pub fn r#type(mut self, val: PassportElementErrorUnspecifiedType) -> Self { - self.r#type = val; - self - } - - pub fn element_hash<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.element_hash = val.into(); - self - } -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorDataFieldType { - PersonalDetails, - Passport, - DriverLicense, - IdentityCard, - InternalPassport, - Address, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorFrontSideType { - Passport, - DriverLicense, - IdentityCard, - InternalPassport, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorReverseSideType { - DriverLicense, - IdentityCard, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorSelfieType { - Passport, - DriverLicense, - IdentityCard, - InternalPassport, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorFileType { - UtilityBill, - BankStatement, - RentalAgreement, - PassportRegistration, - TemporaryRegistration, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorFilesType { - UtilityBill, - BankStatement, - RentalAgreement, - PassportRegistration, - TemporaryRegistration, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorTranslationFileType { - Passport, - DriverLicense, - IdentityCard, - InternalPassport, - UtilityBill, - BankStatement, - RentalAgreement, - PassportRegistration, - TemporaryRegistration, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorTranslationFilesType { - Passport, - DriverLicense, - IdentityCard, - InternalPassport, - UtilityBill, - BankStatement, - RentalAgreement, - PassportRegistration, - TemporaryRegistration, -} - -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum PassportElementErrorUnspecifiedType { - DataField, - FrontSide, - ReverseSide, - Selfie, - File, - Files, - TranslationFile, - TranslationFiles, - Unspecified, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialize_data_field() { - let data = PassportElementError { - message: "This is an error message!".to_owned(), - kind: PassportElementErrorKind::DataField(PassportElementErrorDataField { - r#type: PassportElementErrorDataFieldType::InternalPassport, - field_name: "The field name".to_owned(), - data_hash: "This is a data hash".to_owned(), - }), - }; - - assert_eq!( - serde_json::to_string(&data).unwrap(), - r#"{"message":"This is an error message!","source":"data","type":"internal_passport","field_name":"The field name","data_hash":"This is a data hash"}"# - ); - } -} diff --git a/src/types/passport_file.rs b/src/types/passport_file.rs deleted file mode 100644 index 448d0c62..00000000 --- a/src/types/passport_file.rs +++ /dev/null @@ -1,66 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a file uploaded to Telegram Passport. -/// -/// Currently all Telegram Passport files are in JPEG format when decrypted and -/// don't exceed 10MB. -/// -/// [The official docs](https://core.telegram.org/bots/api#passportfile). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PassportFile { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// File size. - pub file_size: u64, - - /// Unix time when the file was uploaded. - pub file_date: u64, -} - -impl PassportFile { - pub fn new<S1, S2>(file_id: S1, file_unique_id: S2, file_size: u64, file_date: u64) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - file_size, - file_date, - } - } - - pub fn file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_unique_id = val.into(); - self - } - - pub fn file_size(mut self, val: u64) -> Self { - self.file_size = val; - self - } - - pub fn file_date(mut self, val: u64) -> Self { - self.file_date = val; - self - } -} diff --git a/src/types/photo_size.rs b/src/types/photo_size.rs deleted file mode 100644 index 64ddfac7..00000000 --- a/src/types/photo_size.rs +++ /dev/null @@ -1,94 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents one size of a photo or a [file]/[sticker] thumbnail. -/// -/// [file]: crate::types::Document -/// [sticker]: crate::types::Sticker -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PhotoSize { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// Photo width. - pub width: i32, - - /// Photo height. - pub height: i32, - - /// File size. - pub file_size: Option<u32>, -} - -impl PhotoSize { - pub fn new<S1, S2>(file_id: S1, file_unique_id: S2, width: i32, height: i32) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - file_size: None, - } - } - - pub fn file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_unique_id = val.into(); - self - } - - pub fn width(mut self, val: i32) -> Self { - self.width = val; - self - } - - pub fn height(mut self, val: i32) -> Self { - self.height = val; - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let json = r#"{"file_id":"id","file_unique_id":"","width":320,"height":320, - "file_size":3452}"#; - let expected = PhotoSize { - file_id: "id".to_string(), - file_unique_id: "".to_string(), - width: 320, - height: 320, - file_size: Some(3452), - }; - let actual = serde_json::from_str::<PhotoSize>(json).unwrap(); - assert_eq!(actual, expected); - } -} diff --git a/src/types/poll.rs b/src/types/poll.rs deleted file mode 100644 index c3646e3b..00000000 --- a/src/types/poll.rs +++ /dev/null @@ -1,250 +0,0 @@ -use crate::types::{MessageEntity, PollType}; -use serde::{Deserialize, Serialize}; - -/// This object contains information about a poll. -/// -/// [The official docs](https://core.telegram.org/bots/api#poll). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Poll { - /// Unique poll identifier. - pub id: String, - - /// Poll question, 1-255 characters. - pub question: String, - - /// List of poll options. - pub options: Vec<PollOption>, - - /// `true`, if the poll is closed. - pub is_closed: bool, - - /// Total number of users that voted in the poll - pub total_voter_count: i32, - - /// True, if the poll is anonymous - pub is_anonymous: bool, - - /// Poll type, currently can be “regular” or “quiz” - #[serde(rename = "type")] - pub poll_type: PollType, - - /// True, if the poll allows multiple answers - pub allows_multiple_answers: bool, - - /// 0-based identifier of the correct answer option. Available only for - /// polls in the quiz mode, which are closed, or was sent (not - /// forwarded) by the bot or to the private chat with the bot. - pub correct_option_id: Option<i32>, - - /// Text that is shown when a user chooses an incorrect answer or taps on - /// the lamp icon in a quiz-style poll, 0-200 characters. - pub explanation: Option<String>, - - /// Special entities like usernames, URLs, bot commands, etc. that appear in - /// the explanation. - pub explanation_entities: Option<Vec<MessageEntity>>, - - /// Amount of time in seconds the poll will be active after creation. - open_period: Option<i32>, - - /// Point in time (Unix timestamp) when the poll will be automatically - /// closed. - close_date: Option<i32>, -} - -impl Poll { - #[allow(clippy::too_many_arguments)] - pub fn new<S1, S2, O>( - id: S1, - question: S2, - options: O, - is_closed: bool, - total_voter_count: i32, - is_anonymous: bool, - poll_type: PollType, - allows_multiple_answers: bool, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - O: Into<Vec<PollOption>>, - { - Self { - id: id.into(), - question: question.into(), - options: options.into(), - is_closed, - total_voter_count, - is_anonymous, - poll_type, - allows_multiple_answers, - correct_option_id: None, - explanation: None, - explanation_entities: None, - open_period: None, - close_date: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn question<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.question = val.into(); - self - } - - pub fn options<P>(mut self, val: P) -> Self - where - P: Into<Vec<PollOption>>, - { - self.options = val.into(); - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_closed(mut self, val: bool) -> Self { - self.is_closed = val; - self - } - - pub fn total_voter_count(mut self, val: i32) -> Self { - self.total_voter_count = val; - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_anonymous(mut self, val: bool) -> Self { - self.is_anonymous = val; - self - } - - pub fn poll_type(mut self, val: PollType) -> Self { - self.poll_type = val; - self - } - - pub fn allows_multiple_answers(mut self, val: bool) -> Self { - self.allows_multiple_answers = val; - self - } - - pub fn correct_option_id(mut self, val: i32) -> Self { - self.correct_option_id = Some(val); - self - } - - pub fn explanation<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.explanation = Some(val.into()); - self - } - - pub fn explanation_entities<S>(mut self, val: S) -> Self - where - S: Into<Vec<MessageEntity>>, - { - self.explanation_entities = Some(val.into()); - self - } - - pub fn open_period(mut self, val: i32) -> Self { - self.open_period = Some(val); - self - } - - pub fn close_date(mut self, val: i32) -> Self { - self.close_date = Some(val); - self - } -} - -/// This object contains information about one answer option in a poll. -/// -/// [The official docs](https://core.telegram.org/bots/api#polloption). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PollOption { - /// Option text, 1-100 characters. - pub text: String, - - /// Number of users that voted for this option. - pub voter_count: i32, -} - -impl PollOption { - pub fn new<S>(text: S, voter_count: i32) -> Self - where - S: Into<String>, - { - Self { text: text.into(), voter_count } - } - - pub fn text<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.text = val.into(); - self - } - - pub fn voter_count(mut self, val: i32) -> Self { - self.voter_count = val; - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let data = r#" - { - "allows_multiple_answers": false, - "id": "5377643193141559299", - "is_anonymous": true, - "is_closed": false, - "options": [ - { - "text": "1", - "voter_count": 1 - }, - { - "text": "2", - "voter_count": 0 - }, - { - "text": "3", - "voter_count": 0 - }, - { - "text": "4", - "voter_count": 0 - }, - { - "text": "5", - "voter_count": 0 - } - ], - "question": "Rate me from 1 to 5.", - "total_voter_count": 1, - "type": "regular" - } - "#; - serde_json::from_str::<Poll>(data).unwrap(); - } -} diff --git a/src/types/poll_answer.rs b/src/types/poll_answer.rs deleted file mode 100644 index 6ddb0b06..00000000 --- a/src/types/poll_answer.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::types::User; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PollAnswer { - /// Unique poll identifier. - pub poll_id: String, - - /// The user, who changed the answer to the poll. - pub user: User, - - /// 0-based identifiers of answer options, chosen by the user. - /// - /// May be empty if the user retracted their vote. - pub option_ids: Vec<i32>, -} - -impl PollAnswer { - pub fn new<S, O>(poll_id: S, user: User, option_ids: O) -> Self - where - S: Into<String>, - O: Into<Vec<i32>>, - { - Self { poll_id: poll_id.into(), user, option_ids: option_ids.into() } - } - - pub fn poll_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.poll_id = val.into(); - self - } - - pub fn user(mut self, val: User) -> Self { - self.user = val; - self - } - - pub fn option_ids<S>(mut self, val: S) -> Self - where - S: Into<Vec<i32>>, - { - self.option_ids = val.into(); - self - } -} diff --git a/src/types/poll_type.rs b/src/types/poll_type.rs deleted file mode 100644 index 0243f7c1..00000000 --- a/src/types/poll_type.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[non_exhaustive] -pub enum PollType { - Quiz, - Regular, -} diff --git a/src/types/pre_checkout_query.rs b/src/types/pre_checkout_query.rs deleted file mode 100644 index 87fd9707..00000000 --- a/src/types/pre_checkout_query.rs +++ /dev/null @@ -1,108 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Currency, OrderInfo, User}; - -/// This object contains information about an incoming pre-checkout query. -/// -/// [The official docs](https://core.telegram.org/bots/api#precheckoutquery). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct PreCheckoutQuery { - /// Unique query identifier. - pub id: String, - - /// User who sent the query. - pub from: User, - - /// Three-letter ISO 4217 [currency] code. - /// - /// [currency]: https://core.telegram.org/bots/payments#supported-currencies - pub currency: Currency, - - /// Total price in the _smallest units_ of the currency (integer, **not** - /// float/double). For example, for a price of `US$ 1.45` pass `amount = - /// 145`. See the exp parameter in [`currencies.json`], it shows the number - /// of digits past the decimal point for each currency (2 for the - /// majority of currencies). - /// - /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json - pub total_amount: i32, - - /// Bot specified invoice payload. - pub invoice_payload: String, - - /// Identifier of the shipping option chosen by the user. - pub shipping_option_id: Option<String>, - - /// Order info provided by the user. - pub order_info: Option<OrderInfo>, -} - -impl PreCheckoutQuery { - pub fn new<S1, S2>( - id: S1, - from: User, - currency: Currency, - total_amount: i32, - invoice_payload: S2, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - id: id.into(), - from, - currency, - total_amount, - invoice_payload: invoice_payload.into(), - shipping_option_id: None, - order_info: None, - } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn from(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn currency<S>(mut self, val: Currency) -> Self { - self.currency = val; - self - } - - pub fn total_amount(mut self, val: i32) -> Self { - self.total_amount = val; - self - } - - pub fn invoice_payload<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.invoice_payload = val.into(); - self - } - - pub fn shipping_option_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.shipping_option_id = Some(val.into()); - self - } - - pub fn order_info(mut self, val: OrderInfo) -> Self { - self.order_info = Some(val); - self - } -} diff --git a/src/types/reply_keyboard_markup.rs b/src/types/reply_keyboard_markup.rs deleted file mode 100644 index a8d5983e..00000000 --- a/src/types/reply_keyboard_markup.rs +++ /dev/null @@ -1,98 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::KeyboardButton; - -/// This object represents a [custom keyboard] with reply options (see -/// [Introduction to bots] for details and examples). -/// -/// [The official docs](https://core.telegram.org/bots/api#replykeyboardmarkup). -/// -/// [custom keyboard]: https://core.telegram.org/bots#keyboards -/// [Introduction to bots]: https://core.telegram.org/bots#keyboards -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, Default)] -#[non_exhaustive] -pub struct ReplyKeyboardMarkup { - /// Array of button rows, each represented by an Array of - /// [`KeyboardButton`] objects - /// - /// [`KeyboardButton`]: crate::types::KeyboardButton - pub keyboard: Vec<Vec<KeyboardButton>>, - - /// Requests clients to resize the keyboard vertically for optimal fit - /// (e.g., make the keyboard smaller if there are just two rows of - /// buttons). Defaults to `false`, in which case the custom keyboard is - /// always of the same height as the app's standard keyboard. - pub resize_keyboard: Option<bool>, - - /// Requests clients to hide the keyboard as soon as it's been used. The - /// keyboard will still be available, but clients will automatically - /// display the usual letter-keyboard in the chat – the user can press a - /// special button in the input field to see the custom keyboard again. - /// Defaults to `false`. - pub one_time_keyboard: Option<bool>, - - /// Use this parameter if you want to show the keyboard to specific users - /// only. Targets: 1) users that are `@mentioned` in the `text` of the - /// [`Message`] object; 2) if the bot's message is a reply (has - /// `reply_to_message_id`), sender of the original message. - /// - /// Example: A user requests to change the bot‘s language, bot replies to - /// the request with a keyboard to select the new language. Other users - /// in the group don’t see the keyboard. - /// - /// [`Message`]: crate::types::Message - pub selective: Option<bool>, -} - -impl ReplyKeyboardMarkup { - pub fn new<K1, K2>(keyboard: K1) -> Self - where - K1: Into<Vec<K2>>, - K2: Into<Vec<KeyboardButton>>, - { - Self { - keyboard: keyboard.into().into_iter().map(Into::into).collect(), - resize_keyboard: None, - one_time_keyboard: None, - selective: None, - } - } - - pub fn append_row(mut self, buttons: Vec<KeyboardButton>) -> Self { - self.keyboard.push(buttons); - self - } - - pub fn append_to_row(mut self, button: KeyboardButton, index: usize) -> Self { - match self.keyboard.get_mut(index) { - Some(buttons) => buttons.push(button), - None => self.keyboard.push(vec![button]), - }; - self - } - - pub fn resize_keyboard<T>(mut self, val: T) -> Self - where - T: Into<Option<bool>>, - { - self.resize_keyboard = val.into(); - self - } - - pub fn one_time_keyboard<T>(mut self, val: T) -> Self - where - T: Into<Option<bool>>, - { - self.one_time_keyboard = val.into(); - self - } - - pub fn selective<T>(mut self, val: T) -> Self - where - T: Into<Option<bool>>, - { - self.selective = val.into(); - self - } -} diff --git a/src/types/reply_keyboard_remove.rs b/src/types/reply_keyboard_remove.rs deleted file mode 100644 index deb8351b..00000000 --- a/src/types/reply_keyboard_remove.rs +++ /dev/null @@ -1,52 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::True; - -/// Upon receiving a message with this object, Telegram clients will remove the -/// current custom keyboard and display the default letter-keyboard. -/// -/// By default, custom keyboards are displayed until a new keyboard is sent by a -/// bot. An exception is made for one-time keyboards that are hidden immediately -/// after the user presses a button (see [`ReplyKeyboardMarkup`]). -/// -/// [The official docs](https://core.telegram.org/bots/api#replykeyboardremove). -/// -/// [`ReplyKeyboardMarkup`]: crate::types::ReplyKeyboardMarkup -#[serde_with_macros::skip_serializing_none] -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ReplyKeyboardRemove { - /// Requests clients to remove the custom keyboard (user will not be able - /// to summon this keyboard; if you want to hide the keyboard from sight - /// but keep it accessible, use one_time_keyboard in - /// [`ReplyKeyboardMarkup`]). - /// - /// [`ReplyKeyboardMarkup`]: crate::types::ReplyKeyboardMarkup - pub remove_keyboard: True, - - /// Use this parameter if you want to remove the keyboard for specific - /// users only. Targets: 1) users that are `@mentioned` in the `text` of - /// the [`Message`] object; 2) if the bot's message is a reply (has - /// `reply_to_message_id`), sender of the original message. - /// - /// Example: A user votes in a poll, bot returns confirmation message in - /// reply to the vote and removes the keyboard for that user, while still - /// showing the keyboard with poll options to users who haven't voted yet. - /// - /// [`Message`]: crate::types::Message - pub selective: Option<bool>, -} - -impl ReplyKeyboardRemove { - pub fn new() -> Self { - Self::default() - } - - pub fn selective<T>(mut self, val: T) -> Self - where - T: Into<bool>, - { - self.selective = Some(val.into()); - self - } -} diff --git a/src/types/reply_markup.rs b/src/types/reply_markup.rs deleted file mode 100644 index 4bddc56c..00000000 --- a/src/types/reply_markup.rs +++ /dev/null @@ -1,27 +0,0 @@ -use derive_more::From; -use serde::{Deserialize, Serialize}; - -use crate::types::{ForceReply, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove}; - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, From)] -#[serde(untagged)] -#[non_exhaustive] -pub enum ReplyMarkup { - InlineKeyboardMarkup(InlineKeyboardMarkup), - ReplyKeyboardMarkup(ReplyKeyboardMarkup), - ReplyKeyboardRemove(ReplyKeyboardRemove), - ForceReply(ForceReply), -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn inline_keyboard_markup() { - let data = InlineKeyboardMarkup::default(); - let expected = ReplyMarkup::InlineKeyboardMarkup(data.clone()); - let actual: ReplyMarkup = data.into(); - assert_eq!(actual, expected) - } -} diff --git a/src/types/response_parameters.rs b/src/types/response_parameters.rs deleted file mode 100644 index 2f0fb43f..00000000 --- a/src/types/response_parameters.rs +++ /dev/null @@ -1,43 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Contains information about why a request was unsuccessful. -/// -/// [The official docs](https://core.telegram.org/bots/api#responseparameters). -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum ResponseParameters { - /// The group has been migrated to a supergroup with the specified - /// identifier. This number may be greater than 32 bits and some - /// programming languages may have difficulty/silent defects in - /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit - /// integer or double-precision float type are safe for storing this - /// identifier. - MigrateToChatId(i64), - - /// In case of exceeding flood control, the number of seconds left to wait - /// before the request can be repeated. - RetryAfter(i32), -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn migrate_to_chat_id_deserialization() { - let expected = ResponseParameters::MigrateToChatId(123_456); - let actual: ResponseParameters = - serde_json::from_str(r#"{"migrate_to_chat_id":123456}"#).unwrap(); - - assert_eq!(expected, actual); - } - - #[test] - fn retry_after_deserialization() { - let expected = ResponseParameters::RetryAfter(123_456); - let actual: ResponseParameters = serde_json::from_str(r#"{"retry_after":123456}"#).unwrap(); - - assert_eq!(expected, actual); - } -} diff --git a/src/types/send_invoice.rs b/src/types/send_invoice.rs deleted file mode 100644 index bc527b45..00000000 --- a/src/types/send_invoice.rs +++ /dev/null @@ -1,229 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{ChatId, InlineKeyboardMarkup, LabeledPrice}; - -// TODO: missing docs -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct SendInvoice { - pub chat_id: ChatId, - pub title: String, - pub description: String, - pub payload: String, - pub provider_token: String, - pub start_parameter: String, - pub currency: String, - pub prices: Vec<LabeledPrice>, - pub provider_data: Option<String>, - pub photo_url: Option<String>, - pub photo_size: Option<i32>, - pub photo_width: Option<i32>, - pub photo_height: Option<i32>, - pub need_name: Option<bool>, - pub need_phone_number: Option<bool>, - pub need_email: Option<bool>, - pub need_shipping_address: Option<bool>, - pub send_phone_number_to_provider: Option<bool>, - pub send_email_to_provider: Option<bool>, - pub is_flexible: Option<bool>, - pub disable_notification: Option<bool>, - pub reply_to_message_id: Option<i32>, - pub reply_markup: Option<InlineKeyboardMarkup>, -} - -impl SendInvoice { - #[allow(clippy::too_many_arguments)] - pub fn new<C, S1, S2, S3, S4, S5, S6, P>( - chat_id: C, - title: S1, - description: S2, - payload: S3, - provider_token: S4, - start_parameter: S5, - currency: S6, - prices: P, - ) -> Self - where - C: Into<ChatId>, - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - S4: Into<String>, - S5: Into<String>, - S6: Into<String>, - P: Into<Vec<LabeledPrice>>, - { - Self { - chat_id: chat_id.into(), - title: title.into(), - description: description.into(), - payload: payload.into(), - provider_token: provider_token.into(), - start_parameter: start_parameter.into(), - currency: currency.into(), - prices: prices.into(), - provider_data: None, - photo_url: None, - photo_size: None, - photo_width: None, - photo_height: None, - need_name: None, - need_phone_number: None, - need_email: None, - need_shipping_address: None, - send_phone_number_to_provider: None, - send_email_to_provider: None, - is_flexible: None, - disable_notification: None, - reply_to_message_id: None, - reply_markup: None, - } - } - - pub fn chat_id<C>(mut self, val: C) -> Self - where - C: Into<ChatId>, - { - self.chat_id = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn description<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.description = val.into(); - self - } - - pub fn payload<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.payload = val.into(); - self - } - - pub fn provider_token<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.provider_token = val.into(); - self - } - - pub fn start_parameter<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.start_parameter = val.into(); - self - } - - pub fn currency<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.currency = val.into(); - self - } - - pub fn prices<P>(mut self, val: P) -> Self - where - P: Into<Vec<LabeledPrice>>, - { - self.prices = val.into(); - self - } - - pub fn provider_data<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.provider_data = Some(val.into()); - self - } - - pub fn photo_url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.photo_url = Some(val.into()); - self - } - - pub fn photo_size(mut self, val: i32) -> Self { - self.photo_size = Some(val); - self - } - - pub fn photo_width(mut self, val: i32) -> Self { - self.photo_width = Some(val); - self - } - - pub fn photo_height(mut self, val: i32) -> Self { - self.photo_height = Some(val); - self - } - - pub fn need_name(mut self, val: bool) -> Self { - self.need_name = Some(val); - self - } - - pub fn need_phone_number(mut self, val: bool) -> Self { - self.need_phone_number = Some(val); - self - } - - pub fn need_email(mut self, val: bool) -> Self { - self.need_email = Some(val); - self - } - - pub fn need_shipping_address(mut self, val: bool) -> Self { - self.need_shipping_address = Some(val); - self - } - - pub fn send_phone_number_to_provider(mut self, val: bool) -> Self { - self.send_phone_number_to_provider = Some(val); - self - } - - pub fn send_email_to_provider(mut self, val: bool) -> Self { - self.send_email_to_provider = Some(val); - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_flexible(mut self, val: bool) -> Self { - self.is_flexible = Some(val); - self - } - - pub fn disable_notification(mut self, val: bool) -> Self { - self.disable_notification = Some(val); - self - } - - pub fn reply_to_message_id(mut self, value: i32) -> Self { - self.reply_to_message_id = Some(value); - self - } - - pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self { - self.reply_markup = Some(val); - self - } -} diff --git a/src/types/shipping_address.rs b/src/types/shipping_address.rs deleted file mode 100644 index 3a0adb58..00000000 --- a/src/types/shipping_address.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::types::CountryCode; -use serde::{Deserialize, Serialize}; - -/// This object represents a shipping address. -/// -/// [The official docs](https://core.telegram.org/bots/api#shippingaddress). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ShippingAddress { - /// ISO 3166-1 alpha-2 country code. - pub country_code: CountryCode, - - /// State, if applicable. - pub state: String, - - /// City. - pub city: String, - - /// First line for the address. - pub street_line1: String, - - /// Second line for the address. - pub street_line2: String, - - /// Address post code. - pub post_code: String, -} - -impl ShippingAddress { - pub fn new<S1, S2, S3, S4, S5>( - country_code: CountryCode, - - state: S1, - city: S2, - street_line1: S3, - street_line2: S4, - post_code: S5, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - S4: Into<String>, - S5: Into<String>, - { - Self { - country_code, - state: state.into(), - city: city.into(), - street_line1: street_line1.into(), - street_line2: street_line2.into(), - post_code: post_code.into(), - } - } - - pub fn country_code(mut self, val: CountryCode) -> Self { - self.country_code = val; - self - } - - pub fn state<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.state = val.into(); - self - } - - pub fn city<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.city = val.into(); - self - } - - pub fn street_line1<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.street_line1 = val.into(); - self - } - - pub fn street_line2<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.street_line2 = val.into(); - self - } - - pub fn post_code<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.post_code = val.into(); - self - } -} diff --git a/src/types/shipping_option.rs b/src/types/shipping_option.rs deleted file mode 100644 index 983c7eef..00000000 --- a/src/types/shipping_option.rs +++ /dev/null @@ -1,71 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::LabeledPrice; - -/// This object represents one shipping option. -/// -/// [The official docs](https://core.telegram.org/bots/api#shippingoption). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ShippingOption { - /// Shipping option identifier. - pub id: String, - - /// Option title. - pub title: String, - - /// List of price portions. - pub prices: Vec<LabeledPrice>, -} - -impl ShippingOption { - pub fn new<S1, S2, P>(id: S1, title: S2, prices: P) -> Self - where - S1: Into<String>, - S2: Into<String>, - P: Into<Vec<LabeledPrice>>, - { - Self { id: id.into(), title: title.into(), prices: prices.into() } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn prices<P>(mut self, val: P) -> Self - where - P: Into<Vec<LabeledPrice>>, - { - self.prices = val.into(); - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialize() { - let shipping_option = ShippingOption { - id: "0".to_string(), - title: "Option".to_string(), - prices: vec![LabeledPrice { label: "Label".to_string(), amount: 60 }], - }; - let expected = r#"{"id":"0","title":"Option","prices":[{"label":"Label","amount":60}]}"#; - let actual = serde_json::to_string(&shipping_option).unwrap(); - assert_eq!(actual, expected); - } -} diff --git a/src/types/shipping_query.rs b/src/types/shipping_query.rs deleted file mode 100644 index bfdf7dc8..00000000 --- a/src/types/shipping_query.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{ShippingAddress, User}; - -/// This object contains information about an incoming shipping query. -/// -/// [The official docs](https://core.telegram.org/bots/api#shippingquery). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ShippingQuery { - /// Unique query identifier. - pub id: String, - - /// User who sent the query. - pub from: User, - - /// Bot specified invoice payload. - pub invoice_payload: String, - - /// User specified shipping address. - pub shipping_address: ShippingAddress, -} - -impl ShippingQuery { - pub fn new<S1, S2>( - id: S1, - from: User, - invoice_payload: S2, - shipping_address: ShippingAddress, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { id: id.into(), from, invoice_payload: invoice_payload.into(), shipping_address } - } - - pub fn id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.id = val.into(); - self - } - - pub fn from<S>(mut self, val: User) -> Self { - self.from = val; - self - } - - pub fn invoice_payload<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.invoice_payload = val.into(); - self - } - - pub fn shipping_address<S>(mut self, val: ShippingAddress) -> Self { - self.shipping_address = val; - self - } -} diff --git a/src/types/sticker.rs b/src/types/sticker.rs deleted file mode 100644 index d133f1cc..00000000 --- a/src/types/sticker.rs +++ /dev/null @@ -1,135 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{MaskPosition, PhotoSize}; - -/// This object represents a sticker. -/// -/// [The official docs](https://core.telegram.org/bots/api#sticker). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Sticker { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// Sticker width. - pub width: u16, - - /// Sticker height. - pub height: u16, - - /// `true`, if the sticker is [animated]. - /// - /// [animated]: https://telegram.org/blog/animated-stickers - pub is_animated: bool, - - /// Sticker thumbnail in the .webp or .jpg format. - pub thumb: Option<PhotoSize>, - - /// Emoji associated with the sticker. - pub emoji: Option<String>, - - /// Name of the sticker set to which the sticker belongs. - pub set_name: Option<String>, - - /// For mask stickers, the position where the mask should be placed. - pub mask_position: Option<MaskPosition>, - - /// File size. - pub file_size: Option<u32>, -} - -impl Sticker { - pub fn new<S1, S2>( - file_id: S1, - file_unique_id: S2, - width: u16, - height: u16, - is_animated: bool, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - is_animated, - thumb: None, - emoji: None, - set_name: None, - mask_position: None, - file_size: None, - } - } - - pub fn file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_unique_id = val.into(); - self - } - - pub fn height(mut self, val: u16) -> Self { - self.height = val; - self - } - - pub fn width(mut self, val: u16) -> Self { - self.width = val; - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_animated(mut self, val: bool) -> Self { - self.is_animated = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn emoji<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.emoji = Some(val.into()); - self - } - - pub fn set_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.set_name = Some(val.into()); - self - } - - pub fn mask_position(mut self, val: MaskPosition) -> Self { - self.mask_position = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/sticker_set.rs b/src/types/sticker_set.rs deleted file mode 100644 index 0c617205..00000000 --- a/src/types/sticker_set.rs +++ /dev/null @@ -1,89 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{PhotoSize, Sticker}; - -/// This object represents a sticker set. -/// -/// [The official docs](https://core.telegram.org/bots/api#stickerset). -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct StickerSet { - /// Sticker set name. - pub name: String, - - /// Sticker set title. - pub title: String, - - /// `true`, if the sticker set contains [animated stickers]. - /// - /// [animates stickers]: https://telegram.org/blog/animated-stickers - pub is_animated: bool, - - /// `true`, if the sticker set contains masks. - pub contains_masks: bool, - - /// List of all set stickers. - pub stickers: Vec<Sticker>, - - /// Sticker set thumbnail in the .WEBP or .TGS format. - thumb: Option<PhotoSize>, -} - -impl StickerSet { - pub fn new<S1, S2, St>( - name: S1, - title: S2, - is_animated: bool, - contains_masks: bool, - stickers: St, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - St: Into<Vec<Sticker>>, - { - Self { - name: name.into(), - title: title.into(), - is_animated, - contains_masks, - stickers: stickers.into(), - thumb: None, - } - } - - pub fn name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.name = val.into(); - self - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_animated(mut self, val: bool) -> Self { - self.is_animated = val; - self - } - - pub fn contains_masks(mut self, val: bool) -> Self { - self.contains_masks = val; - self - } - - pub fn stickers<S>(mut self, val: S) -> Self - where - S: Into<Vec<Sticker>>, - { - self.stickers = val.into(); - self - } -} diff --git a/src/types/sticker_type.rs b/src/types/sticker_type.rs deleted file mode 100644 index b80added..00000000 --- a/src/types/sticker_type.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::types::InputFile; - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -#[non_exhaustive] -pub enum StickerType { - /// PNG image with the sticker, must be up to 512 kilobytes in size, - /// dimensions must not exceed 512px, and either width or height must be - /// exactly 512px. - /// - /// 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 - /// - /// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files - Png(InputFile), - - /// TGS animation with the sticker, uploaded using multipart/form-data. - /// - /// See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements - Tgs(InputFile), -} diff --git a/src/types/successful_payment.rs b/src/types/successful_payment.rs deleted file mode 100644 index 439dbc23..00000000 --- a/src/types/successful_payment.rs +++ /dev/null @@ -1,112 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{Currency, OrderInfo}; - -/// This object contains basic information about a successful payment. -/// -/// [The official docs](https://core.telegram.org/bots/api#successfulpayment). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct SuccessfulPayment { - /// Three-letter ISO 4217 [currency] code. - /// - /// [currency]: https://core.telegram.org/bots/payments#supported-currencies - pub currency: Currency, - - /// Total price in the smallest units of the currency (integer, not - /// float/double). For example, for a price of `US$ 1.45` pass `amount = - /// 145`. See the exp parameter in [`currencies.json`], it shows the - /// number of digits past the decimal point for each currency (2 for - /// the majority of currencies). - /// - /// [`currencies.json`]: https://core.telegram.org/bots/payments/currencies.json - pub total_amount: i32, - - /// Bot specified invoice payload. - pub invoice_payload: String, - - /// Identifier of the shipping option chosen by the user. - pub shipping_option_id: Option<String>, - - /// Order info provided by the user. - pub order_info: Option<OrderInfo>, - - /// Telegram payment identifier. - pub telegram_payment_charge_id: String, - - /// Provider payment identifier. - pub provider_payment_charge_id: String, -} - -impl SuccessfulPayment { - pub fn new<S1, S2, S3>( - currency: Currency, - total_amount: i32, - invoice_payload: S1, - telegram_payment_charge_id: S2, - provider_payment_charge_id: S3, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - S3: Into<String>, - { - Self { - currency, - total_amount, - invoice_payload: invoice_payload.into(), - shipping_option_id: None, - order_info: None, - telegram_payment_charge_id: telegram_payment_charge_id.into(), - provider_payment_charge_id: provider_payment_charge_id.into(), - } - } - - pub fn currency<S>(mut self, val: Currency) -> Self { - self.currency = val; - self - } - - pub fn total_amount(mut self, val: i32) -> Self { - self.total_amount = val; - self - } - - pub fn invoice_payload<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.invoice_payload = val.into(); - self - } - - pub fn shipping_option_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.shipping_option_id = Some(val.into()); - self - } - - pub fn order_info(mut self, val: OrderInfo) -> Self { - self.order_info = Some(val); - self - } - - pub fn telegram_payment_charge_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.telegram_payment_charge_id = val.into(); - self - } - - pub fn provider_payment_charge_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.provider_payment_charge_id = val.into(); - self - } -} diff --git a/src/types/unit_false.rs b/src/types/unit_false.rs deleted file mode 100644 index bde6410b..00000000 --- a/src/types/unit_false.rs +++ /dev/null @@ -1,76 +0,0 @@ -use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; - -/// A type that is always false. -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Default)] -pub struct False; - -impl std::convert::TryFrom<bool> for False { - type Error = (); - - fn try_from(value: bool) -> Result<Self, Self::Error> { - match value { - true => Err(()), - false => Ok(False), - } - } -} - -impl<'de> Deserialize<'de> for False { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - deserializer.deserialize_bool(FalseVisitor) - } -} - -struct FalseVisitor; - -impl<'de> Visitor<'de> for FalseVisitor { - type Value = False; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "bool, equal to `false`") - } - - fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> - where - E: serde::de::Error, - { - match value { - true => Err(E::custom("expected `false`, found `true`")), - false => Ok(False), - } - } -} - -impl Serialize for False { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - serializer.serialize_bool(false) - } -} - -#[cfg(test)] -mod tests { - use serde_json::{from_str, to_string}; - - use super::False; - - #[test] - fn unit_false_de() { - let json = "false"; - let expected = False; - let actual = from_str(json).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn unit_false_se() { - let actual = to_string(&False).unwrap(); - let expected = "false"; - assert_eq!(expected, actual); - } -} diff --git a/src/types/unit_true.rs b/src/types/unit_true.rs deleted file mode 100644 index cd71e5c2..00000000 --- a/src/types/unit_true.rs +++ /dev/null @@ -1,79 +0,0 @@ -use serde::{ - de::{self, Deserialize, Deserializer, Visitor}, - ser::{Serialize, Serializer}, -}; - -/// A type that is always true. -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Default)] -pub struct True; - -impl std::convert::TryFrom<bool> for True { - type Error = (); - - fn try_from(value: bool) -> Result<Self, Self::Error> { - match value { - true => Ok(True), - false => Err(()), - } - } -} - -impl<'de> Deserialize<'de> for True { - fn deserialize<D>(des: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - des.deserialize_bool(TrueVisitor) - } -} - -struct TrueVisitor; - -impl<'de> Visitor<'de> for TrueVisitor { - type Value = True; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "bool, equal to `true`") - } - - fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> - where - E: de::Error, - { - match value { - true => Ok(True), - false => Err(E::custom("expected `true`, found `false`")), - } - } -} - -impl Serialize for True { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - serializer.serialize_bool(true) - } -} - -#[cfg(test)] -mod tests { - use serde_json::{from_str, to_string}; - - use super::True; - - #[test] - fn unit_true_de() { - let json = "true"; - let expected = True; - let actual = from_str(json).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn unit_true_se() { - let actual = to_string(&True).unwrap(); - let expected = "true"; - assert_eq!(expected, actual); - } -} diff --git a/src/types/update.rs b/src/types/update.rs deleted file mode 100644 index 6b047dcf..00000000 --- a/src/types/update.rs +++ /dev/null @@ -1,321 +0,0 @@ -#![allow(clippy::large_enum_variant)] - -use serde::{Deserialize, Serialize}; - -use crate::types::{ - CallbackQuery, Chat, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer, - PreCheckoutQuery, ShippingQuery, User, -}; -use serde_json::Value; - -/// This [object] represents an incoming update. -/// -/// [The official docs](https://core.telegram.org/bots/api#update). -/// -/// [object]: https://core.telegram.org/bots/api#available-types -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Update { - /// The update‘s unique identifier. Update identifiers start from a certain - /// positive number and increase sequentially. This ID becomes especially - /// handy if you’re using [Webhooks], since it allows you to ignore - /// repeated updates or to restore the correct update sequence, should - /// they get out of order. If there are no new updates for at least a - /// week, then identifier of the next update will be chosen randomly - /// instead of sequentially. - /// - /// [Webhooks]: crate::Bot::set_webhook - #[serde(rename = "update_id")] - pub id: i32, - - #[serde(flatten)] - pub kind: UpdateKind, -} - -impl Update { - pub fn new(id: i32, kind: UpdateKind) -> Self { - Self { id, kind } - } - - pub fn id<S>(mut self, val: i32) -> Self { - self.id = val; - self - } - - pub fn kind<S>(mut self, val: UpdateKind) -> Self { - self.kind = val; - self - } -} - -impl Update { - /// Tries to parse `value` into `Update`, logging an error if failed. - /// - /// It is used to implement update listeners. - pub fn try_parse(value: &Value) -> Result<Self, serde_json::Error> { - match serde_json::from_str(&value.to_string()) { - Ok(update) => Ok(update), - Err(error) => { - log::error!( - "Cannot parse an update.\nError: {:?}\nValue: {}\n\ - This is a bug in teloxide, please open an issue here: \ - https://github.com/teloxide/teloxide/issues.", - error, - value - ); - Err(error) - } - } - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum UpdateKind { - /// New incoming message of any kind — text, photo, sticker, etc. - Message(Message), - - /// New version of a message that is known to the bot and was edited. - EditedMessage(Message), - - /// New incoming channel post of any kind — text, photo, sticker, etc. - ChannelPost(Message), - - /// New version of a channel post that is known to the bot and was edited. - EditedChannelPost(Message), - - /// New incoming [inline] query. - /// - /// [inline]: https://core.telegram.org/bots/api#inline-mode - InlineQuery(InlineQuery), - - /// The result of an [inline] query that was chosen by a user and sent to - /// their chat partner. Please see our documentation on the [feedback - /// collecting] for details on how to enable these updates for your bot. - /// - /// [inline]: https://core.telegram.org/bots/api#inline-mode - /// [feedback collecting]: https://core.telegram.org/bots/inline#collecting-feedback - ChosenInlineResult(ChosenInlineResult), - - /// New incoming callback query. - CallbackQuery(CallbackQuery), - - /// New incoming shipping query. Only for invoices with flexible price. - ShippingQuery(ShippingQuery), - - /// New incoming pre-checkout query. Contains full information about - /// checkout. - PreCheckoutQuery(PreCheckoutQuery), - - /// New poll state. Bots receive only updates about stopped polls and - /// polls, which are sent by the bot. - Poll(Poll), - - /// A user changed their answer in a non-anonymous poll. Bots receive new - /// votes only in polls that were sent by the bot itself. - PollAnswer(PollAnswer), -} - -impl Update { - pub fn user(&self) -> Option<&User> { - match &self.kind { - UpdateKind::Message(m) => m.from(), - UpdateKind::EditedMessage(m) => m.from(), - UpdateKind::CallbackQuery(query) => Some(&query.from), - UpdateKind::ChosenInlineResult(chosen) => Some(&chosen.from), - UpdateKind::InlineQuery(query) => Some(&query.from), - UpdateKind::ShippingQuery(query) => Some(&query.from), - UpdateKind::PreCheckoutQuery(query) => Some(&query.from), - UpdateKind::PollAnswer(answer) => Some(&answer.user), - _ => None, - } - } - - pub fn chat(&self) -> Option<&Chat> { - match &self.kind { - UpdateKind::Message(m) => Some(&m.chat), - UpdateKind::EditedMessage(m) => Some(&m.chat), - UpdateKind::ChannelPost(p) => Some(&p.chat), - UpdateKind::EditedChannelPost(p) => Some(&p.chat), - UpdateKind::CallbackQuery(q) => Some(&q.message.as_ref()?.chat), - _ => None, - } - } -} - -#[cfg(test)] -mod test { - use crate::types::{ - Chat, ChatKind, ChatPrivate, ForwardKind, ForwardOrigin, MediaKind, MediaText, Message, - MessageCommon, MessageKind, Update, UpdateKind, User, - }; - - // TODO: more tests for deserialization - #[test] - fn message() { - let json = r#"{ - "update_id":892252934, - "message":{ - "message_id":6557, - "from":{ - "id":218485655, - "is_bot": false, - "first_name":"Waffle", - "username":"WaffleLapkin", - "language_code":"en" - }, - "chat":{ - "id":218485655, - "first_name":"Waffle", - "username":"WaffleLapkin", - "type":"private" - }, - "date":1569518342, - "text":"hello there" - } - }"#; - - let expected = Update { - id: 892_252_934, - kind: UpdateKind::Message(Message { - via_bot: None, - id: 6557, - date: 1_569_518_342, - chat: Chat { - id: 218_485_655, - kind: ChatKind::Private(ChatPrivate { - type_: (), - username: Some(String::from("WaffleLapkin")), - first_name: Some(String::from("Waffle")), - last_name: None, - }), - photo: None, - }, - kind: MessageKind::Common(MessageCommon { - from: Some(User { - id: 218_485_655, - is_bot: false, - first_name: String::from("Waffle"), - last_name: None, - username: Some(String::from("WaffleLapkin")), - language_code: Some(String::from("en")), - }), - forward_kind: ForwardKind::Origin(ForwardOrigin { reply_to_message: None }), - edit_date: None, - media_kind: MediaKind::Text(MediaText { - text: String::from("hello there"), - entities: vec![], - }), - reply_markup: None, - }), - }), - }; - - let actual = serde_json::from_str::<Update>(json).unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn de_private_chat_text_message() { - let text = r#" - { - "message": { - "chat": { - "first_name": "Hirrolot", - "id": 408258968, - "type": "private", - "username": "hirrolot" - }, - "date": 1581448857, - "from": { - "first_name": "Hirrolot", - "id": 408258968, - "is_bot": false, - "language_code": "en", - "username": "hirrolot" - }, - "message_id": 154, - "text": "4" - }, - "update_id": 306197398 - } -"#; - - assert!(serde_json::from_str::<Update>(text).is_ok()); - } - - #[test] - fn pinned_message_works() { - let json = r#"{ - "message": { - "chat": { - "id": -1001276785818, - "title": "teloxide dev", - "type": "supergroup", - "username": "teloxide_dev" - }, - "date": 1582134655, - "from": { - "first_name": "Hirrolot", - "id": 408258968, - "is_bot": false, - "username": "hirrolot" - }, - "message_id": 20225, - "pinned_message": { - "chat": { - "id": -1001276785818, - "title": "teloxide dev", - "type": "supergroup", - "username": "teloxide_dev" - }, - "date": 1582134643, - "from": { - "first_name": "Hirrolot", - "id": 408258968, - "is_bot": false, - "username": "hirrolot" - }, - "message_id": 20224, - "text": "Faster than a bullet" - } - }, - "update_id": 845402291 -}"#; - - serde_json::from_str::<Update>(json).unwrap(); - } - - #[test] - fn dice_works() { - let json = r#" - { - "message": { - "chat": { - "id": -1001276785818, - "title": "bla bla bla chat", - "type": "supergroup", - "username": "teloxide_dev" - }, - "date": 1596014550, - "dice": { - "emoji": "🎲", - "value": 2 - }, - "from": { - "first_name": "Hirrolot", - "id": 408258968, - "is_bot": false, - "language_code": "en", - "username": "hirrolot" - }, - "message_id": 35410 - }, - "update_id": 573255266 -} - "#; - - serde_json::from_str::<Update>(json).unwrap(); - } -} diff --git a/src/types/user.rs b/src/types/user.rs deleted file mode 100644 index d24f5caa..00000000 --- a/src/types/user.rs +++ /dev/null @@ -1,132 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// This object represents a Telegram user or bot. -/// -/// [The official docs](https://core.telegram.org/bots/api#user). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct User { - /// Unique identifier for this user or bot. - pub id: i32, - - /// `true`, if this user is a bot. - pub is_bot: bool, - - /// User‘s or bot’s first name. - pub first_name: String, - - /// User‘s or bot’s last name. - pub last_name: Option<String>, - - /// User‘s or bot’s username. - pub username: Option<String>, - - /// [IETF language tag] of the user's language. - /// - /// [IETF language tag]: https://en.wikipedia.org/wiki/IETF_language_tag - pub language_code: Option<String>, -} - -impl User { - pub fn new<S>(id: i32, is_bot: bool, first_name: S) -> Self - where - S: Into<String>, - { - Self { - id, - is_bot, - first_name: first_name.into(), - last_name: None, - username: None, - language_code: None, - } - } - - pub fn id<S>(mut self, val: i32) -> Self { - self.id = val; - self - } - - #[allow(clippy::wrong_self_convention)] - pub fn is_bot<S>(mut self, val: bool) -> Self { - self.is_bot = val; - self - } - - pub fn first_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.first_name = val.into(); - self - } - - pub fn last_name<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.last_name = Some(val.into()); - self - } - - pub fn username<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.username = Some(val.into()); - self - } - - pub fn language_code<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.language_code = Some(val.into()); - self - } -} - -impl User { - pub fn full_name(&self) -> String { - match &self.last_name { - Some(last_name) => (format!("{0} {1}", self.first_name, last_name)), - None => self.first_name.clone(), - } - } - - pub fn mention(&self) -> Option<String> { - Some(format!("@{}", self.username.as_ref()?)) - } - - pub fn url(&self) -> reqwest::Url { - reqwest::Url::parse(format!("tg://user/?id={}", self.id).as_str()).unwrap() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn deserialize() { - let json = r#"{ - "id":12345, - "is_bot":false, - "first_name":"firstName", - "last_name":"lastName", - "username":"Username", - "language_code":"ru" - }"#; - let expected = User { - id: 12345, - is_bot: false, - first_name: "firstName".to_string(), - last_name: Some("lastName".to_string()), - username: Some("Username".to_string()), - language_code: Some(String::from("ru")), - }; - let actual = serde_json::from_str::<User>(&json).unwrap(); - assert_eq!(actual, expected) - } -} diff --git a/src/types/user_profile_photos.rs b/src/types/user_profile_photos.rs deleted file mode 100644 index 26d8a934..00000000 --- a/src/types/user_profile_photos.rs +++ /dev/null @@ -1,40 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::PhotoSize; - -/// This object represent a user's profile pictures. -/// -/// [The official docs](https://core.telegram.org/bots/api#userprofilephotos). -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct UserProfilePhotos { - /// Total number of profile pictures the target user has. - pub total_count: u32, - - /// Requested profile pictures (in up to 4 sizes each). - pub photos: Vec<Vec<PhotoSize>>, -} - -impl UserProfilePhotos { - pub fn new<P1, P2>(total_count: u32, photos: P1) -> Self - where - P1: Into<Vec<P2>>, - P2: Into<Vec<PhotoSize>>, - { - Self { total_count, photos: photos.into().into_iter().map(Into::into).collect() } - } - - pub fn total_count(mut self, val: u32) -> Self { - self.total_count = val; - self - } - - pub fn photos<P1, P2>(mut self, val: P1) -> Self - where - P1: Into<Vec<P2>>, - P2: Into<Vec<PhotoSize>>, - { - self.photos = val.into().into_iter().map(Into::into).collect(); - self - } -} diff --git a/src/types/venue.rs b/src/types/venue.rs deleted file mode 100644 index 3a8ff38a..00000000 --- a/src/types/venue.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::Location; - -/// This object represents a venue. -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Venue { - /// Venue location. - pub location: Location, - - /// Name of the venue. - pub title: String, - - /// Address of the venue. - pub address: String, - - /// Foursquare identifier of the venue. - pub foursquare_id: Option<String>, - - /// Foursquare type of the venue. (For example, - /// `arts_entertainment/default`, `arts_entertainment/aquarium` or - /// `food/icecream`.) - pub foursquare_type: Option<String>, -} - -impl Venue { - pub fn new<S1, S2>(location: Location, title: S1, address: S2) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - location, - title: title.into(), - address: address.into(), - foursquare_id: None, - foursquare_type: None, - } - } - - pub fn title<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.title = val.into(); - self - } - - pub fn address<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.address = val.into(); - self - } - - pub fn foursquare_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.foursquare_id = Some(val.into()); - self - } - - pub fn foursquare_type<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.foursquare_type = Some(val.into()); - self - } -} diff --git a/src/types/video.rs b/src/types/video.rs deleted file mode 100644 index 84de086a..00000000 --- a/src/types/video.rs +++ /dev/null @@ -1,108 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::{MimeWrapper, PhotoSize}; - -/// This object represents a video file. -/// -/// [The official docs](https://core.telegram.org/bots/api#video). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Video { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// Video width as defined by sender. - pub width: u32, - - /// Video height as defined by sender. - pub height: u32, - - /// Duration of the video in seconds as defined by sender. - pub duration: u32, - - /// Video thumbnail. - pub thumb: Option<PhotoSize>, - - /// Mime type of a file as defined by sender. - pub mime_type: Option<MimeWrapper>, - - /// File size. - pub file_size: Option<u32>, -} - -impl Video { - pub fn new<S1, S2>( - file_id: S1, - file_unique_id: S2, - width: u32, - height: u32, - duration: u32, - ) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - width, - height, - duration, - thumb: None, - mime_type: None, - file_size: None, - } - } - - pub fn file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_unique_id = val.into(); - self - } - - pub fn width(mut self, val: u32) -> Self { - self.width = val; - self - } - - pub fn height(mut self, val: u32) -> Self { - self.height = val; - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/video_note.rs b/src/types/video_note.rs deleted file mode 100644 index 467ace0e..00000000 --- a/src/types/video_note.rs +++ /dev/null @@ -1,89 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::PhotoSize; - -/// This object represents a [video message] (available in Telegram apps as of -/// [v.4.0]). -/// -/// [The official docs](https://core.telegram.org/bots/api#videonote). -/// -/// [video message]: https://telegram.org/blog/video-messages-and-telescope -/// [v4.0]: https://telegram.org/blog/video-messages-and-telescope -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct VideoNote { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// Video width and height (diameter of the video message) as defined by - /// sender. - pub length: u32, - - /// Duration of the video in seconds as defined by sender. - pub duration: u32, - - /// Video thumbnail. - pub thumb: Option<PhotoSize>, - - /// File size. - pub file_size: Option<u32>, -} - -impl VideoNote { - pub fn new<S1, S2>(file_id: S1, file_unique_id: S2, length: u32, duration: u32) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - length, - duration, - thumb: None, - file_size: None, - } - } - - pub fn file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_unique_id = val.into(); - self - } - - pub fn length(mut self, val: u32) -> Self { - self.length = val; - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn thumb(mut self, val: PhotoSize) -> Self { - self.thumb = Some(val); - self - } - - pub fn file_size(mut self, val: u32) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/voice.rs b/src/types/voice.rs deleted file mode 100644 index bd522cbb..00000000 --- a/src/types/voice.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::types::MimeWrapper; -use serde::{Deserialize, Serialize}; - -/// This object represents a voice note. -/// -/// [The official docs](https://core.telegram.org/bots/api#voice). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Voice { - /// Identifier for this file. - pub file_id: String, - - /// Unique identifier for this file, which is supposed to be the same over - /// time and for different bots. Can't be used to download or reuse the - /// file. - pub file_unique_id: String, - - /// Duration of the audio in seconds as defined by sender. - pub duration: u32, - - /// MIME type of the file as defined by sender. - pub mime_type: Option<MimeWrapper>, - - /// File size. - pub file_size: Option<u64>, -} - -impl Voice { - pub fn new<S1, S2>(file_id: S1, file_unique_id: S2, duration: u32) -> Self - where - S1: Into<String>, - S2: Into<String>, - { - Self { - file_id: file_id.into(), - file_unique_id: file_unique_id.into(), - duration, - mime_type: None, - file_size: None, - } - } - - pub fn file_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_id = val.into(); - self - } - - pub fn file_unique_id<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.file_unique_id = val.into(); - self - } - - pub fn duration(mut self, val: u32) -> Self { - self.duration = val; - self - } - - pub fn mime_type(mut self, val: MimeWrapper) -> Self { - self.mime_type = Some(val); - self - } - - pub fn file_size(mut self, val: u64) -> Self { - self.file_size = Some(val); - self - } -} diff --git a/src/types/webhook_info.rs b/src/types/webhook_info.rs deleted file mode 100644 index 782a588d..00000000 --- a/src/types/webhook_info.rs +++ /dev/null @@ -1,98 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Contains information about the current status of a webhook. -/// -/// [The official docs](https://core.telegram.org/bots/api#webhookinfo). -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct WebhookInfo { - /// Webhook URL, may be empty if webhook is not set up. - pub url: String, - - /// `true`, if a custom certificate was provided for webhook certificate - /// checks. - pub has_custom_certificate: bool, - - /// Number of updates awaiting delivery. - pub pending_update_count: u32, - - /// Unix time for the most recent error that happened when trying to - /// deliver an update via webhook. - pub last_error_date: Option<u64>, - - /// Error message in human-readable format for the most recent error that - /// happened when trying to deliver an update via webhook. - pub last_error_message: Option<String>, - - /// Maximum allowed number of simultaneous HTTPS connections to the webhook - /// for update delivery. - pub max_connections: Option<u32>, - - /// A list of update types the bot is subscribed to. Defaults to all update - /// types. - pub allowed_updates: Option<Vec<String>>, -} - -impl WebhookInfo { - pub fn new<S>(url: S, has_custom_certificate: bool, pending_update_count: u32) -> Self - where - S: Into<String>, - { - Self { - url: url.into(), - has_custom_certificate, - pending_update_count, - last_error_date: None, - - last_error_message: None, - max_connections: None, - allowed_updates: None, - } - } - - pub fn url<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.url = val.into(); - self - } - - pub fn has_custom_certificate(mut self, val: bool) -> Self { - self.has_custom_certificate = val; - self - } - - pub fn pending_update_count(mut self, val: u32) -> Self { - self.pending_update_count = val; - self - } - - pub fn last_error_date(mut self, val: u64) -> Self { - self.last_error_date = Some(val); - self - } - - pub fn last_error_message<S>(mut self, val: S) -> Self - where - S: Into<String>, - { - self.last_error_message = Some(val.into()); - self - } - - pub fn max_connections(mut self, val: u32) -> Self { - self.max_connections = Some(val); - self - } - - pub fn allowed_updates<A, S>(mut self, val: A) -> Self - where - A: Into<Vec<S>>, - S: Into<String>, - { - self.allowed_updates = Some(val.into().into_iter().map(Into::into).collect()); - self - } -} diff --git a/src/utils/client_from_env.rs b/src/utils/client_from_env.rs deleted file mode 100644 index 8857e387..00000000 --- a/src/utils/client_from_env.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::bot::{sound_bot, TELOXIDE_PROXY}; - -/// Constructs a client from the `TELOXIDE_PROXY` environmental variable. -/// -/// This function passes the value of `TELOXIDE_PROXY` into -/// [`reqwest::Proxy::all`], if it exists, otherwise returns the default -/// client. -/// -/// # Note -/// The created client will have safe settings, meaning that it will be able to -/// work in long time durations, see the [issue 223]. -/// -/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all -/// [issue 223]: https://github.com/teloxide/teloxide/issues/223 -pub fn client_from_env() -> reqwest::Client { - use reqwest::Proxy; - - let builder = sound_bot(); - - match std::env::var(TELOXIDE_PROXY).ok() { - Some(proxy) => builder.proxy(Proxy::all(&proxy).expect("creating reqwest::Proxy")), - None => builder, - } - .build() - .expect("creating reqwest::Client") -} diff --git a/src/utils/command.rs b/src/utils/command.rs index 8c849ddf..22a795e4 100644 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -6,6 +6,7 @@ //! //! # Using BotCommand //! ``` +//! # #[cfg(feature = "macros")] { //! use teloxide::utils::command::BotCommand; //! //! type UnitOfTime = u8; @@ -19,6 +20,7 @@ //! //! let command = AdminCommand::parse("/ban 5 h", "bot_name").unwrap(); //! assert_eq!(command, AdminCommand::Ban(5, 'h')); +//! # } //! ``` //! //! # Using parse_command @@ -48,12 +50,16 @@ use std::{ error::Error, fmt::{Display, Formatter}, }; + +#[cfg(feature = "macros")] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))] pub use teloxide_macros::BotCommand; /// An enumeration of bot's commands. /// /// # Example /// ``` +/// # #[cfg(feature = "macros")] { /// use teloxide::utils::command::BotCommand; /// /// type UnitOfTime = u8; @@ -67,6 +73,7 @@ pub use teloxide_macros::BotCommand; /// /// let command = AdminCommand::parse("/ban 5 h", "bot_name").unwrap(); /// assert_eq!(command, AdminCommand::Ban(5, 'h')); +/// # } /// ``` /// /// ## Enum attributes @@ -88,6 +95,7 @@ pub use teloxide_macros::BotCommand; /// /// ### Example /// ``` +/// # #[cfg(feature = "macros")] { /// use teloxide::utils::command::BotCommand; /// /// #[derive(BotCommand, PartialEq, Debug)] @@ -98,6 +106,7 @@ pub use teloxide_macros::BotCommand; /// /// let command = Command::parse("/text hello my dear friend!", "").unwrap(); /// assert_eq!(command, Command::Text("hello my dear friend!".to_string())); +/// # } /// ``` /// /// - `split` - separates a messsage by a given separator (the default is the @@ -106,6 +115,7 @@ pub use teloxide_macros::BotCommand; /// /// ### Example /// ``` +/// # #[cfg(feature = "macros")] { /// use teloxide::utils::command::BotCommand; /// /// #[derive(BotCommand, PartialEq, Debug)] @@ -116,6 +126,7 @@ pub use teloxide_macros::BotCommand; /// /// let command = Command::parse("/nums 1 32 -5", "").unwrap(); /// assert_eq!(command, Command::Nums(1, 32, -5)); +/// # } /// ``` /// /// 5. `#[command(separator = "sep")]` @@ -124,6 +135,7 @@ pub use teloxide_macros::BotCommand; /// /// ### Example /// ``` +/// # #[cfg(feature = "macros")] { /// use teloxide::utils::command::BotCommand; /// /// #[derive(BotCommand, PartialEq, Debug)] @@ -134,6 +146,7 @@ pub use teloxide_macros::BotCommand; /// /// let command = Command::parse("/nums 1|32|5", "").unwrap(); /// assert_eq!(command, Command::Nums(1, 32, 5)); +/// # } /// ``` /// /// ## Variant attributes @@ -151,6 +164,7 @@ pub use teloxide_macros::BotCommand; /// /// ### Example /// ``` +/// # #[cfg(feature = "macros")] { /// use teloxide::utils::command::{BotCommand, ParseError}; /// /// fn accept_two_digits(input: String) -> Result<(u8,), ParseError> { @@ -174,6 +188,7 @@ pub use teloxide_macros::BotCommand; /// assert_eq!(command, Command::Num(12)); /// let command = Command::parse("/num 333", ""); /// assert!(command.is_err()); +/// # } /// ``` /// /// 3. `#[command(prefix = "prefix")]` diff --git a/src/utils/html.rs b/src/utils/html.rs index 71c7bec9..cf22acc4 100644 --- a/src/utils/html.rs +++ b/src/utils/html.rs @@ -1,8 +1,8 @@ //! Utils for working with the [HTML message style][spec]. //! //! [spec]: https://core.telegram.org/bots/api#html-style -use crate::types::User; -use std::string::String; + +use teloxide_core::types::User; /// Applies the bold font style to the string. /// diff --git a/src/utils/markdown.rs b/src/utils/markdown.rs index f96236d6..f96fafaa 100644 --- a/src/utils/markdown.rs +++ b/src/utils/markdown.rs @@ -1,8 +1,8 @@ //! Utils for working with the [Markdown V2 message style][spec]. //! //! [spec]: https://core.telegram.org/bots/api#markdownv2-style -use crate::types::User; -use std::string::String; + +use teloxide_core::types::User; /// Applies the bold font style to the string. /// @@ -131,6 +131,7 @@ pub fn user_mention_or_link(user: &User) -> String { #[cfg(test)] mod tests { use super::*; + use teloxide_core::types::User; #[test] fn test_bold() { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6882383b..6e29632d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,12 +1,12 @@ //! Some useful utilities. -mod client_from_env; pub mod command; pub mod html; pub mod markdown; mod up_state; -pub use client_from_env::client_from_env; +pub use teloxide_core::net::client_from_env; #[cfg(feature = "frunk")] +#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "frunk")))] pub use up_state::UpState; diff --git a/tests/command.rs b/tests/command.rs index 82bba9aa..deed8647 100644 --- a/tests/command.rs +++ b/tests/command.rs @@ -1,12 +1,14 @@ +#[cfg(feature = "macros")] use teloxide::utils::command::{BotCommand, ParseError}; // We put tests here because macro expand in unit tests in module // teloxide::utils::command was a failure #[test] +#[cfg(feature = "macros")] fn parse_command_with_args() { - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { Start(String), Help, @@ -19,9 +21,26 @@ fn parse_command_with_args() { } #[test] -fn attribute_prefix() { - #[command(rename = "lowercase")] +#[cfg(feature = "macros")] +fn parse_command_with_non_string_arg() { #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] + 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() { + #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { #[command(prefix = "!")] Start(String), @@ -35,9 +54,10 @@ fn attribute_prefix() { } #[test] +#[cfg(feature = "macros")] fn many_attributes() { - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { #[command(prefix = "!", description = "desc")] Start, @@ -49,9 +69,10 @@ fn many_attributes() { } #[test] +#[cfg(feature = "macros")] fn global_attributes() { - #[command(prefix = "!", rename = "lowercase", description = "Bot commands")] #[derive(BotCommand, Debug, PartialEq)] + #[command(prefix = "!", rename = "lowercase", description = "Bot commands")] enum DefaultCommands { #[command(prefix = "/")] Start, @@ -64,9 +85,10 @@ fn global_attributes() { } #[test] +#[cfg(feature = "macros")] fn parse_command_with_bot_name() { - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { #[command(prefix = "/")] Start, @@ -80,10 +102,11 @@ fn parse_command_with_bot_name() { } #[test] +#[cfg(feature = "macros")] fn parse_with_split() { + #[derive(BotCommand, Debug, PartialEq)] #[command(rename = "lowercase")] #[command(parse_with = "split")] - #[derive(BotCommand, Debug, PartialEq)] enum DefaultCommands { Start(u8, String), Help, @@ -96,10 +119,11 @@ fn parse_with_split() { } #[test] +#[cfg(feature = "macros")] fn parse_with_split2() { + #[derive(BotCommand, Debug, PartialEq)] #[command(rename = "lowercase")] #[command(parse_with = "split", separator = "|")] - #[derive(BotCommand, Debug, PartialEq)] enum DefaultCommands { Start(u8, String), Help, @@ -112,6 +136,7 @@ fn parse_with_split2() { } #[test] +#[cfg(feature = "macros")] fn parse_custom_parser() { fn custom_parse_function(s: String) -> Result<(u8, String), ParseError> { let vec = s.split_whitespace().collect::<Vec<_>>(); @@ -124,8 +149,8 @@ fn parse_custom_parser() { .map_err(|_| ParseError::Custom("First argument must be a integer!".to_owned().into())) } - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { #[command(parse_with = "custom_parse_function")] Start(u8, String), @@ -139,10 +164,11 @@ fn parse_custom_parser() { } #[test] +#[cfg(feature = "macros")] fn parse_named_fields() { + #[derive(BotCommand, Debug, PartialEq)] #[command(rename = "lowercase")] #[command(parse_with = "split")] - #[derive(BotCommand, Debug, PartialEq)] enum DefaultCommands { Start { num: u8, data: String }, Help, @@ -155,9 +181,10 @@ fn parse_named_fields() { } #[test] +#[cfg(feature = "macros")] fn descriptions_off() { - #[command(rename = "lowercase")] #[derive(BotCommand, Debug, PartialEq)] + #[command(rename = "lowercase")] enum DefaultCommands { #[command(description = "off")] Start, diff --git a/tests/redis.rs b/tests/redis.rs index 3d9da600..2b88ad8e 100644 --- a/tests/redis.rs +++ b/tests/redis.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "redis_storage")] - use std::{ fmt::{Debug, Display}, future::Future, @@ -8,18 +6,16 @@ use std::{ use teloxide::dispatching::dialogue::{RedisStorage, Serializer, Storage}; #[tokio::test] -#[cfg(feature = "redis_storage")] async fn test_redis_json() { let storage = RedisStorage::open( "redis://127.0.0.1:7777", - teloxide::dispatching::dialogue::serializer::JSON, + teloxide::dispatching::dialogue::serializer::Json, ) .await .unwrap(); test_redis(storage).await; } -#[cfg(feature = "bincode_serializer")] #[tokio::test] async fn test_redis_bincode() { let storage = RedisStorage::open( @@ -31,12 +27,11 @@ async fn test_redis_bincode() { test_redis(storage).await; } -#[cfg(feature = "cbor_serializer")] #[tokio::test] async fn test_redis_cbor() { let storage = RedisStorage::open( "redis://127.0.0.1:7779", - teloxide::dispatching::dialogue::serializer::CBOR, + teloxide::dispatching::dialogue::serializer::Cbor, ) .await .unwrap(); diff --git a/tests/sqlite.rs b/tests/sqlite.rs new file mode 100644 index 00000000..bf08142d --- /dev/null +++ b/tests/sqlite.rs @@ -0,0 +1,67 @@ +use std::{ + fmt::{Debug, Display}, + future::Future, + sync::Arc, +}; +use teloxide::dispatching::dialogue::{Serializer, SqliteStorage, Storage}; + +#[tokio::test(flavor = "multi_thread")] +async fn test_sqlite_json() { + let storage = + SqliteStorage::open("./test_db1.sqlite", teloxide::dispatching::dialogue::serializer::Json) + .await + .unwrap(); + test_sqlite(storage).await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_sqlite_bincode() { + let storage = SqliteStorage::open( + "./test_db2.sqlite", + teloxide::dispatching::dialogue::serializer::Bincode, + ) + .await + .unwrap(); + test_sqlite(storage).await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_sqlite_cbor() { + let storage = + SqliteStorage::open("./test_db3.sqlite", teloxide::dispatching::dialogue::serializer::Cbor) + .await + .unwrap(); + test_sqlite(storage).await; +} + +type Dialogue = String; + +async fn test_sqlite<S>(storage: Arc<SqliteStorage<S>>) +where + S: Send + Sync + Serializer<Dialogue> + 'static, + <S as Serializer<Dialogue>>::Error: Debug + Display, +{ + check_dialogue(None, Arc::clone(&storage).update_dialogue(1, "ABC".to_owned())).await; + check_dialogue(None, Arc::clone(&storage).update_dialogue(11, "DEF".to_owned())).await; + check_dialogue(None, Arc::clone(&storage).update_dialogue(256, "GHI".to_owned())).await; + + // 1 - ABC, 11 - DEF, 256 - GHI + + check_dialogue("ABC", Arc::clone(&storage).update_dialogue(1, "JKL".to_owned())).await; + check_dialogue("GHI", Arc::clone(&storage).update_dialogue(256, "MNO".to_owned())).await; + + // 1 - GKL, 11 - DEF, 256 - MNO + + check_dialogue("JKL", Arc::clone(&storage).remove_dialogue(1)).await; + check_dialogue("DEF", Arc::clone(&storage).remove_dialogue(11)).await; + check_dialogue("MNO", Arc::clone(&storage).remove_dialogue(256)).await; +} + +async fn check_dialogue<E>( + expected: impl Into<Option<&str>>, + actual: impl Future<Output = Result<Option<Dialogue>, E>>, +) where + E: Debug, +{ + assert_eq!(expected.into().map(ToOwned::to_owned), actual.await.unwrap()) +}