mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-14 11:44:04 +01:00
commit
0826893ca3
252 changed files with 1497 additions and 23096 deletions
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -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 -->
|
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -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 -->
|
30
.github/ISSUE_TEMPLATE/parse-error.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/parse-error.md
vendored
Normal file
|
@ -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-->
|
30
.github/ISSUE_TEMPLATE/unknown-telegram-error.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/unknown-telegram-error.md
vendored
Normal file
|
@ -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-->
|
79
.github/workflows/ci.yml
vendored
79
.github/workflows/ci.yml
vendored
|
@ -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
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -3,4 +3,5 @@
|
|||
Cargo.lock
|
||||
.idea/
|
||||
.vscode/
|
||||
examples/*/target
|
||||
examples/*/target
|
||||
*.sqlite
|
||||
|
|
18
CHANGELOG.md
18
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
|
||||
|
|
|
@ -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()`.
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
94
Cargo.toml
94
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"]
|
||||
|
|
202
README.md
202
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).
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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
|
||||
lto = true
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
lto = true
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use teloxide_macros::Transition;
|
||||
use teloxide::dispatching::dialogue::Transition;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
19
examples/sqlite_remember_bot/Cargo.toml
Normal file
19
examples/sqlite_remember_bot/Cargo.toml
Normal file
|
@ -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"
|
55
examples/sqlite_remember_bot/src/main.rs
Normal file
55
examples/sqlite_remember_bot/src/main.rs
Normal file
|
@ -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;
|
||||
}
|
23
examples/sqlite_remember_bot/src/states.rs
Normal file
23
examples/sqlite_remember_bot/src/states.rs
Normal file
|
@ -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,
|
||||
}
|
39
examples/sqlite_remember_bot/src/transitions.rs
Normal file
39
examples/sqlite_remember_bot/src/transitions.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
8
netlify.toml
Normal file
8
netlify.toml
Normal file
|
@ -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"
|
|
@ -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
|
||||
|
|
1538
src/bot/api.rs
1538
src/bot/api.rs
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
}
|
||||
}
|
253
src/bot/mod.rs
253
src/bot/mod.rs
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::types::Message;
|
||||
use teloxide_core::types::Message;
|
||||
|
||||
/// Something that has a chat ID.
|
||||
pub trait GetChatId {
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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>>,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
130
src/dispatching/dialogue/storage/sqlite_storage.rs
Normal file
130
src/dispatching/dialogue/storage/sqlite_storage.rs
Normal file
|
@ -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)))
|
||||
}
|
70
src/dispatching/dialogue/storage/trace_storage.rs
Normal file
70
src/dispatching/dialogue/storage/trace_storage.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>>;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<_>>()
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
515
src/errors.rs
515
src/errors.rs
|
@ -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,
|
||||
}
|
45
src/lib.rs
45
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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}))
|
||||
}
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue