mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-31 16:40:37 +01:00
commit
75cff40eb8
223 changed files with 21373 additions and 78 deletions
72
.github/workflows/ci.yml
vendored
Normal file
72
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
name: Continuous integration
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
- nightly
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
override: true
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: stable/beta build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
if: matrix.rust == 'stable' || matrix.rust == 'beta'
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --verbose --features ""
|
||||||
|
|
||||||
|
- name: nightly build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
if: matrix.rust == 'nightly'
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --verbose --all-features
|
||||||
|
|
||||||
|
- name: stable/beta test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
if: matrix.rust == 'stable' || matrix.rust == 'beta'
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
args: --verbose --features ""
|
||||||
|
|
||||||
|
- name: nightly test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
if: matrix.rust == 'nightly'
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
args: --verbose --all-features
|
||||||
|
|
||||||
|
- name: fmt
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
if: matrix.rust == 'nightly'
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check
|
||||||
|
|
||||||
|
- name: stable/beta clippy
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
if: matrix.rust == 'stable' || matrix.rust == 'beta'
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
args: --all-targets --features "" -- -D warnings
|
||||||
|
|
||||||
|
- name: nightly clippy
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
if: matrix.rust == 'nightly'
|
||||||
|
with:
|
||||||
|
command: clippy
|
||||||
|
args: --all-targets --all-features -- -D warnings
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -2,3 +2,10 @@
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
.idea/
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
examples/ping_pong_bot/target
|
||||||
|
examples/dialogue_bot/target
|
||||||
|
examples/multiple_handlers_bot/target
|
||||||
|
examples/admin_bot/target
|
||||||
|
examples/guess_a_number_bot/target
|
||||||
|
examples/simple_commands_bot/target
|
124
CODE_STYLE.md
Normal file
124
CODE_STYLE.md
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
# Code style
|
||||||
|
This is a description of a coding style that every contributor must follow. Please, read the whole document before you start pushing code.
|
||||||
|
|
||||||
|
## Generics
|
||||||
|
Generics are always written with `where`.
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn new<N: Into<String>,
|
||||||
|
T: Into<String>,
|
||||||
|
P: Into<InputFile>,
|
||||||
|
E: Into<String>>
|
||||||
|
(user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn new<N, T, P, E>(user_id: i32, name: N, title: T, png_sticker: P, emojis: E) -> Self
|
||||||
|
where
|
||||||
|
N: Into<String>,
|
||||||
|
T: Into<String>,
|
||||||
|
P: Into<InputFile>,
|
||||||
|
E: Into<String> { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
1. Comments must describe what your code does and mustn't describe how your code does it and bla-bla-bla. Be sure that your comments follow the grammar, including punctuation, the first capital letter and so on.
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// this function make request to telegram
|
||||||
|
pub fn make_request(url: &str) -> String { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// This function makes a request to Telegram.
|
||||||
|
pub fn make_request(url: &str) -> String { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Also, link resources in your comments when possible:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Self where possible
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl ErrorKind {
|
||||||
|
fn print(&self) {
|
||||||
|
ErrorKind::Io => println!("Io"),
|
||||||
|
ErrorKind::Network => println!("Network"),
|
||||||
|
ErrorKind::Json => println!("Json"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Good:
|
||||||
|
```rust
|
||||||
|
impl ErrorKind {
|
||||||
|
fn print(&self) {
|
||||||
|
Self::Io => println!("Io"),
|
||||||
|
Self::Network => println!("Network"),
|
||||||
|
Self::Json => println!("Json"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>More examples</summary>
|
||||||
|
|
||||||
|
Bad:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl<'a> AnswerCallbackQuery<'a> {
|
||||||
|
pub(crate) fn new<C>(bot: &'a Bot, callback_query_id: C) -> AnswerCallbackQuery<'a>
|
||||||
|
where
|
||||||
|
C: Into<String>, { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Good:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl<'a> AnswerCallbackQuery<'a> {
|
||||||
|
pub(crate) fn new<C>(bot: &'a Bot, callback_query_id: C) -> Self
|
||||||
|
where
|
||||||
|
C: Into<String>, { ... }
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Naming
|
||||||
|
1. Avoid unnecessary duplication (`Message::message_id` -> `Message::id` using `#[serde(rename = "message_id")]`).
|
||||||
|
2. Use a generic parameter name `S` for streams, `Fut` for futures, `F` for functions (where possible).
|
||||||
|
|
||||||
|
## Deriving
|
||||||
|
1. Derive `Copy`, `Eq`, `Hash`, `PartialEq`, `Clone`, `Debug` for public types when possible (note: if the default `Debug` implementation is weird, you should manually implement it by yourself).
|
||||||
|
2. Derive `Default` when there is an algorithm to get a default value for your type.
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
1. Use `Into<...>` only where there exists at least one conversion **and** it will be logically to use.
|
13
CONTRIBUTING.md
Normal file
13
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Contributing
|
||||||
|
Before contributing, please read our [the code style](https://github.com/teloxide/teloxide/blob/dev/CODE_STYLE.md).
|
||||||
|
|
||||||
|
To change the source code, fork this repository and work inside your own branch. Then send us a PR and wait for the CI to check everything. However, you'd better check changes first locally:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo clippy --all --all-features --all-targets
|
||||||
|
cargo test --all
|
||||||
|
cargo doc --open
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
```
|
||||||
|
|
||||||
|
To report a bug, suggest new functionality, or ask a question, go to [Issues](https://github.com/teloxide/teloxide/issues). Try to make MRE (**M**inimal **R**eproducible **E**xample) and specify your teloxide version to let others help you.
|
41
Cargo.toml
41
Cargo.toml
|
@ -1,13 +1,42 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-telegram-bot"
|
name = "teloxide"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
description = "An elegant Telegram bots framework for Rust"
|
||||||
|
repository = "https://github.com/teloxide/teloxide"
|
||||||
|
documentation = "https://docs.rs/teloxide/"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["teloxide", "telegram", "telegram-bot-framework", "telegram-bot-api"]
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures-preview = { version = "0.3.0-alpha.14", features = ["compat"] }
|
serde_json = "1.0.44"
|
||||||
reqwest = "0.9.20"
|
serde = { version = "1.0.101", features = ["derive"] }
|
||||||
serde_json = "1.0.39"
|
|
||||||
serde = {version = "1.0.92", features = ["derive"] }
|
tokio = { version = "0.2.6", features = ["full"] }
|
||||||
lazy_static = "1.3"
|
tokio-util = { version = "0.2.0", features = ["full"] }
|
||||||
|
|
||||||
|
reqwest = { version = "0.10", features = ["json", "stream", "native-tls-vendored"] }
|
||||||
|
log = "0.4.8"
|
||||||
|
bytes = "0.5.3"
|
||||||
|
mime = "0.3.16"
|
||||||
|
|
||||||
|
derive_more = "0.99.2"
|
||||||
|
thiserror = "1.0.9"
|
||||||
|
async-trait = "0.1.22"
|
||||||
|
futures = "0.3.1"
|
||||||
|
pin-project = "0.4.6"
|
||||||
|
serde_with_macros = "1.0.1"
|
||||||
|
either = "1.5.3"
|
||||||
|
|
||||||
|
teloxide-macros = { path = "teloxide-macros" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
smart-default = "0.6.0"
|
||||||
|
rand = "0.7.3"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
|
BIN
ICON.png
Normal file
BIN
ICON.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 async-telegram-bot
|
Copyright (c) 2019 teloxide
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
246
README.md
246
README.md
|
@ -1,2 +1,244 @@
|
||||||
# async-telegram-bot
|
<div align="center">
|
||||||
An asynchronous full-featured Telegram bot framework for Rust
|
<img src="ICON.png" width="250"/>
|
||||||
|
<h1>teloxide</h1>
|
||||||
|
|
||||||
|
<a href="https://docs.rs/teloxide/">
|
||||||
|
<img src="https://img.shields.io/badge/docs.rs-v0.1.0-blue.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://crates.io/crates/teloxide">
|
||||||
|
<img src="https://img.shields.io/badge/crates.io-v0.1.0-orange.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>
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- **Type-safe.** teloxide leverages the Rust's type system with two serious implications: resistance to human mistakes and tight integration with IDEs. Write fast, avoid debugging as possible.
|
||||||
|
|
||||||
|
- **Persistency.** By default, teloxide stores all user dialogues in RAM, but you can store them somewhere else (for example, in DB) just by implementing 2 functions.
|
||||||
|
|
||||||
|
- **Convenient dialogues system.** Define a type-safe [finite automaton](https://en.wikipedia.org/wiki/Finite-state_machine)
|
||||||
|
and transition functions to drive a user dialogue with ease (see the examples below).
|
||||||
|
|
||||||
|
- **Convenient API.** Automatic conversions are used to avoid boilerplate. For example, functions accept `Into<String>`, rather than `&str` or `String`, so you can call them without `.to_string()`/`.as_str()`/etc.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
1. Create a new bot using [@Botfather](https://t.me/botfather) to get a token in the format `123456789:blablabla`.
|
||||||
|
2. Initialise the `TELOXIDE_TOKEN` environmental variable to your token:
|
||||||
|
```bash
|
||||||
|
# Unix
|
||||||
|
$ export TELOXIDE_TOKEN=MyAwesomeToken
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
$ set TELOXITE_TOKEN=MyAwesomeToken
|
||||||
|
```
|
||||||
|
3. Be sure that you are up to date:
|
||||||
|
```bash
|
||||||
|
$ rustup update stable
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Execute `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`:
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
teloxide = "0.1.0"
|
||||||
|
log = "0.4.8"
|
||||||
|
tokio = "0.2.11"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## The ping-pong bot
|
||||||
|
This bot has a single message handler, which answers "pong" to each incoming message:
|
||||||
|
|
||||||
|
([Full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/src/main.rs))
|
||||||
|
```rust
|
||||||
|
use teloxide::prelude::*;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
teloxide::enable_logging!();
|
||||||
|
log::info!("Starting the ping-pong bot!");
|
||||||
|
|
||||||
|
let bot = Bot::from_env();
|
||||||
|
|
||||||
|
Dispatcher::<RequestError>::new(bot)
|
||||||
|
.message_handler(&|ctx: DispatcherHandlerCtx<Message>| async move {
|
||||||
|
ctx.answer("pong").send().await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
Commands are defined similar to how we define CLI using [structopt](https://docs.rs/structopt/0.3.9/structopt/). This bot says "I am a cat! Meow!" on `/meow`, generates a random number within [0; 1) on `/generate`, and shows the usage guide on `/help`:
|
||||||
|
|
||||||
|
([Full](https://github.com/teloxide/teloxide/blob/dev/examples/simple_commands_bot/src/main.rs))
|
||||||
|
```rust
|
||||||
|
// Imports are omitted...
|
||||||
|
|
||||||
|
#[derive(BotCommand)]
|
||||||
|
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||||
|
enum Command {
|
||||||
|
#[command(description = "display this text.")]
|
||||||
|
Help,
|
||||||
|
#[command(description = "be a cat.")]
|
||||||
|
Meow,
|
||||||
|
#[command(description = "generate a random number within [0; 1).")]
|
||||||
|
Generate,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_command(
|
||||||
|
ctx: DispatcherHandlerCtx<Message>,
|
||||||
|
) -> Result<(), RequestError> {
|
||||||
|
let text = match ctx.update.text() {
|
||||||
|
Some(text) => text,
|
||||||
|
None => {
|
||||||
|
log::info!("Received a message, but not text.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let command = match Command::parse(text) {
|
||||||
|
Some((command, _)) => command,
|
||||||
|
None => {
|
||||||
|
log::info!("Received a text message, but not a command.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match command {
|
||||||
|
Command::Help => ctx.answer(Command::descriptions()).send().await?,
|
||||||
|
Command::Generate => {
|
||||||
|
ctx.answer(thread_rng().gen_range(0.0, 1.0).to_string())
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
Command::Meow => ctx.answer("I am a cat! Meow!").send().await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// Setup is omitted...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guess a number
|
||||||
|
Wanna see more? This is a bot, which starts a game on each incoming message. You must guess a number from 1 to 10 (inclusively):
|
||||||
|
|
||||||
|
([Full](https://github.com/teloxide/teloxide/blob/dev/examples/guess_a_number_bot/src/main.rs))
|
||||||
|
```rust
|
||||||
|
// Imports are omitted...
|
||||||
|
|
||||||
|
#[derive(SmartDefault)]
|
||||||
|
enum Dialogue {
|
||||||
|
#[default]
|
||||||
|
Start,
|
||||||
|
ReceiveAttempt(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_message(
|
||||||
|
ctx: DialogueHandlerCtx<Message, Dialogue>,
|
||||||
|
) -> Result<DialogueStage<Dialogue>, RequestError> {
|
||||||
|
match ctx.dialogue {
|
||||||
|
Dialogue::Start => {
|
||||||
|
ctx.answer(
|
||||||
|
"Let's play a game! Guess a number from 1 to 10 (inclusively).",
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(Dialogue::ReceiveAttempt(thread_rng().gen_range(1, 11)))
|
||||||
|
}
|
||||||
|
Dialogue::ReceiveAttempt(secret) => match ctx.update.text() {
|
||||||
|
None => {
|
||||||
|
ctx.answer("Oh, please, send me a text message!")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(ctx.dialogue)
|
||||||
|
}
|
||||||
|
Some(text) => match text.parse::<u8>() {
|
||||||
|
Ok(attempt) => match attempt {
|
||||||
|
x if !(1..=10).contains(&x) => {
|
||||||
|
ctx.answer(
|
||||||
|
"Oh, please, send me a number in the range [1; \
|
||||||
|
10]!",
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(ctx.dialogue)
|
||||||
|
}
|
||||||
|
x if x == secret => {
|
||||||
|
ctx.answer("Congratulations! You won!").send().await?;
|
||||||
|
exit()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
ctx.answer("No.").send().await?;
|
||||||
|
next(ctx.dialogue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
ctx.answer(
|
||||||
|
"Oh, please, send me a number in the range [1; 10]!",
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(ctx.dialogue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// Setup is omitted...
|
||||||
|
|
||||||
|
Dispatcher::new(bot)
|
||||||
|
.message_handler(&DialogueDispatcher::new(|ctx| async move {
|
||||||
|
handle_message(ctx)
|
||||||
|
.await
|
||||||
|
.expect("Something wrong with the bot!")
|
||||||
|
}))
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Our [finite automaton](https://en.wikipedia.org/wiki/Finite-state_machine), designating a user dialogue, cannot be in an invalid state. See [examples/dialogue_bot](https://github.com/teloxide/teloxide/blob/dev/examples/dialogue_bot/src/main.rs) to see a bit more complicated bot with dialogues.
|
||||||
|
|
||||||
|
[See more examples](https://github.com/teloxide/teloxide/tree/dev/examples).
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
- Use this pattern:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
run().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run() {
|
||||||
|
// Your logic here...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// Your logic here...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
See [CONRIBUTING.md](https://github.com/teloxide/teloxide/blob/dev/CONTRIBUTING.md).
|
||||||
|
|
9
examples/README.md
Normal file
9
examples/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Examples
|
||||||
|
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.
|
||||||
|
|
||||||
|
- [ping_pong_bot](ping_pong_bot) - Answers "pong" to each incoming message.
|
||||||
|
- [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.
|
||||||
|
- [multiple_handlers_bot](multiple_handlers_bot) - Shows how multiple dispatcher's handlers relate to each other.
|
16
examples/admin_bot/Cargo.toml
Normal file
16
examples/admin_bot/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "admin_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["p0lunin <dmytro.polunin@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.8"
|
||||||
|
tokio = "0.2.9"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
teloxide = { path = "../../" }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
203
examples/admin_bot/src/main.rs
Normal file
203
examples/admin_bot/src/main.rs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
// TODO: simplify this and use typed command variants (see https://github.com/teloxide/teloxide/issues/152).
|
||||||
|
|
||||||
|
use teloxide::{
|
||||||
|
prelude::*, types::ChatPermissions, utils::command::BotCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Derive BotCommand to parse text with a command into this enumeration.
|
||||||
|
//
|
||||||
|
// 1. rename = "lowercase" turns all the commands into lowercase letters.
|
||||||
|
// 2. `description = "..."` specifies a text before all the commands.
|
||||||
|
//
|
||||||
|
// That is, you can just call Command::descriptions() to get a description of
|
||||||
|
// your commands in this format:
|
||||||
|
// %GENERAL-DESCRIPTION%
|
||||||
|
// %PREFIX%%COMMAND% - %DESCRIPTION%
|
||||||
|
#[derive(BotCommand)]
|
||||||
|
#[command(
|
||||||
|
rename = "lowercase",
|
||||||
|
description = "Use commands in format /%command% %num% %unit%"
|
||||||
|
)]
|
||||||
|
enum Command {
|
||||||
|
#[command(description = "kick user from chat.")]
|
||||||
|
Kick,
|
||||||
|
#[command(description = "ban user in chat.")]
|
||||||
|
Ban,
|
||||||
|
#[command(description = "mute user in chat.")]
|
||||||
|
Mute,
|
||||||
|
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates time of user restriction.
|
||||||
|
fn calc_restrict_time(num: i32, unit: &str) -> Result<i32, &str> {
|
||||||
|
match unit {
|
||||||
|
"h" | "hours" => Ok(num * 3600),
|
||||||
|
"m" | "minutes" => Ok(num * 60),
|
||||||
|
"s" | "seconds" => Ok(num),
|
||||||
|
_ => Err("Allowed units: h, m, s"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse arguments after a command.
|
||||||
|
fn parse_args(args: Vec<&str>) -> Result<(i32, &str), &str> {
|
||||||
|
let num = match args.get(0) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Err("Use command in format /%command% %num% %unit%"),
|
||||||
|
};
|
||||||
|
let unit = match args.get(1) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Err("Use command in format /%command% %num% %unit%"),
|
||||||
|
};
|
||||||
|
|
||||||
|
match num.parse::<i32>() {
|
||||||
|
Ok(n) => Ok((n, unit)),
|
||||||
|
Err(_) => Err("input positive number!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse arguments into a user restriction duration.
|
||||||
|
fn parse_time_restrict(args: Vec<&str>) -> Result<i32, &str> {
|
||||||
|
let (num, unit) = parse_args(args)?;
|
||||||
|
calc_restrict_time(num, unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ctx = DispatcherHandlerCtx<Message>;
|
||||||
|
|
||||||
|
// Mute a user with a replied message.
|
||||||
|
async fn mute_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> {
|
||||||
|
match ctx.update.reply_to_message() {
|
||||||
|
Some(msg1) => match parse_time_restrict(args) {
|
||||||
|
// Mute user temporarily...
|
||||||
|
Ok(time) => {
|
||||||
|
ctx.bot
|
||||||
|
.restrict_chat_member(
|
||||||
|
ctx.update.chat_id(),
|
||||||
|
msg1.from().expect("Must be MessageKind::Common").id,
|
||||||
|
ChatPermissions::default(),
|
||||||
|
)
|
||||||
|
.until_date(ctx.update.date + time)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
// ...or permanently
|
||||||
|
Err(_) => {
|
||||||
|
ctx.bot
|
||||||
|
.restrict_chat_member(
|
||||||
|
ctx.update.chat_id(),
|
||||||
|
msg1.from().unwrap().id,
|
||||||
|
ChatPermissions::default(),
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
ctx.reply_to("Use this command in reply to another message")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kick a user with a replied message.
|
||||||
|
async fn kick_user(ctx: &Ctx) -> Result<(), RequestError> {
|
||||||
|
match ctx.update.reply_to_message() {
|
||||||
|
Some(mes) => {
|
||||||
|
// bot.unban_chat_member can also kicks a user from a group chat.
|
||||||
|
ctx.bot
|
||||||
|
.unban_chat_member(ctx.update.chat_id(), mes.from().unwrap().id)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
ctx.reply_to("Use this command in reply to another message")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ban a user with replied message.
|
||||||
|
async fn ban_user(ctx: &Ctx, args: Vec<&str>) -> Result<(), RequestError> {
|
||||||
|
match ctx.update.reply_to_message() {
|
||||||
|
Some(message) => match parse_time_restrict(args) {
|
||||||
|
// Mute user temporarily...
|
||||||
|
Ok(time) => {
|
||||||
|
ctx.bot
|
||||||
|
.kick_chat_member(
|
||||||
|
ctx.update.chat_id(),
|
||||||
|
message.from().expect("Must be MessageKind::Common").id,
|
||||||
|
)
|
||||||
|
.until_date(ctx.update.date + time)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
// ...or permanently
|
||||||
|
Err(_) => {
|
||||||
|
ctx.bot
|
||||||
|
.kick_chat_member(
|
||||||
|
ctx.update.chat_id(),
|
||||||
|
message.from().unwrap().id,
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
ctx.reply_to("Use this command in a reply to another message!")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle all messages.
|
||||||
|
async fn handle_command(ctx: Ctx) -> Result<(), RequestError> {
|
||||||
|
if ctx.update.chat.is_group() {
|
||||||
|
// The same as DispatcherHandlerResult::exit(Ok(())). If you have more
|
||||||
|
// handlers, use DispatcherHandlerResult::next(...)
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(text) = ctx.update.text() {
|
||||||
|
// Parse text into a command with args.
|
||||||
|
let (command, args): (Command, Vec<&str>) = match Command::parse(text) {
|
||||||
|
Some(tuple) => tuple,
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match command {
|
||||||
|
Command::Help => {
|
||||||
|
ctx.answer(Command::descriptions()).send().await?;
|
||||||
|
}
|
||||||
|
Command::Kick => {
|
||||||
|
kick_user(&ctx).await?;
|
||||||
|
}
|
||||||
|
Command::Ban => {
|
||||||
|
ban_user(&ctx, args).await?;
|
||||||
|
}
|
||||||
|
Command::Mute => {
|
||||||
|
mute_user(&ctx, args).await?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
teloxide::enable_logging!();
|
||||||
|
log::info!("Starting admin_bot!");
|
||||||
|
|
||||||
|
let bot = Bot::from_env();
|
||||||
|
|
||||||
|
Dispatcher::new(bot)
|
||||||
|
.message_handler(&handle_command)
|
||||||
|
.dispatch()
|
||||||
|
.await
|
||||||
|
}
|
18
examples/dialogue_bot/Cargo.toml
Normal file
18
examples/dialogue_bot/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "dialogue_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.8"
|
||||||
|
tokio = "0.2.9"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
smart-default = "0.6.0"
|
||||||
|
parse-display = "0.1.1"
|
||||||
|
teloxide = { path = "../../" }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
209
examples/dialogue_bot/src/main.rs
Normal file
209
examples/dialogue_bot/src/main.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
// This is a bot that asks your full name, your age, your favourite kind of
|
||||||
|
// music and sends all the gathered information back.
|
||||||
|
//
|
||||||
|
// # Example
|
||||||
|
// ```
|
||||||
|
// - Let's start! First, what's your full name?
|
||||||
|
// - Luke Skywalker
|
||||||
|
// - What a wonderful name! Your age?
|
||||||
|
// - 26
|
||||||
|
// - Good. Now choose your favourite music
|
||||||
|
// *A keyboard of music kinds is displayed*
|
||||||
|
// *You select Metal*
|
||||||
|
// - Metal
|
||||||
|
// - Fine. Your full name: Luke Skywalker, your age: 26, your favourite music: Metal
|
||||||
|
// ```
|
||||||
|
|
||||||
|
#![allow(clippy::trivial_regex)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate smart_default;
|
||||||
|
|
||||||
|
use teloxide::{
|
||||||
|
prelude::*,
|
||||||
|
types::{KeyboardButton, ReplyKeyboardMarkup},
|
||||||
|
};
|
||||||
|
|
||||||
|
use parse_display::{Display, FromStr};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// [Favourite music kinds]
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Display, FromStr)]
|
||||||
|
enum FavouriteMusic {
|
||||||
|
Rock,
|
||||||
|
Metal,
|
||||||
|
Pop,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FavouriteMusic {
|
||||||
|
fn markup() -> ReplyKeyboardMarkup {
|
||||||
|
ReplyKeyboardMarkup::default().append_row(vec![
|
||||||
|
KeyboardButton::new("Rock"),
|
||||||
|
KeyboardButton::new("Metal"),
|
||||||
|
KeyboardButton::new("Pop"),
|
||||||
|
KeyboardButton::new("Other"),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// [A type-safe finite automaton]
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ReceiveAgeState {
|
||||||
|
full_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ReceiveFavouriteMusicState {
|
||||||
|
data: ReceiveAgeState,
|
||||||
|
age: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Display)]
|
||||||
|
#[display(
|
||||||
|
"Your full name: {data.data.full_name}, your age: {data.age}, your \
|
||||||
|
favourite music: {favourite_music}"
|
||||||
|
)]
|
||||||
|
struct ExitState {
|
||||||
|
data: ReceiveFavouriteMusicState,
|
||||||
|
favourite_music: FavouriteMusic,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SmartDefault)]
|
||||||
|
enum Dialogue {
|
||||||
|
#[default]
|
||||||
|
Start,
|
||||||
|
ReceiveFullName,
|
||||||
|
ReceiveAge(ReceiveAgeState),
|
||||||
|
ReceiveFavouriteMusic(ReceiveFavouriteMusicState),
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// [Control a dialogue]
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
type Ctx<State> = DialogueHandlerCtx<Message, State>;
|
||||||
|
type Res = Result<DialogueStage<Dialogue>, RequestError>;
|
||||||
|
|
||||||
|
async fn start(ctx: Ctx<()>) -> Res {
|
||||||
|
ctx.answer("Let's start! First, what's your full name?")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(Dialogue::ReceiveFullName)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn full_name(ctx: Ctx<()>) -> Res {
|
||||||
|
match ctx.update.text() {
|
||||||
|
None => {
|
||||||
|
ctx.answer("Please, send me a text message!").send().await?;
|
||||||
|
next(Dialogue::ReceiveFullName)
|
||||||
|
}
|
||||||
|
Some(full_name) => {
|
||||||
|
ctx.answer("What a wonderful name! Your age?")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(Dialogue::ReceiveAge(ReceiveAgeState {
|
||||||
|
full_name: full_name.to_owned(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn age(ctx: Ctx<ReceiveAgeState>) -> Res {
|
||||||
|
match ctx.update.text().unwrap().parse() {
|
||||||
|
Ok(age) => {
|
||||||
|
ctx.answer("Good. Now choose your favourite music:")
|
||||||
|
.reply_markup(FavouriteMusic::markup())
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(Dialogue::ReceiveFavouriteMusic(
|
||||||
|
ReceiveFavouriteMusicState {
|
||||||
|
data: ctx.dialogue,
|
||||||
|
age,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
ctx.answer("Oh, please, enter a number!").send().await?;
|
||||||
|
next(Dialogue::ReceiveAge(ctx.dialogue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn favourite_music(ctx: Ctx<ReceiveFavouriteMusicState>) -> Res {
|
||||||
|
match ctx.update.text().unwrap().parse() {
|
||||||
|
Ok(favourite_music) => {
|
||||||
|
ctx.answer(format!(
|
||||||
|
"Fine. {}",
|
||||||
|
ExitState {
|
||||||
|
data: ctx.dialogue.clone(),
|
||||||
|
favourite_music
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
exit()
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
ctx.answer("Oh, please, enter from the keyboard!")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(Dialogue::ReceiveFavouriteMusic(ctx.dialogue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_message(ctx: Ctx<Dialogue>) -> Res {
|
||||||
|
match ctx {
|
||||||
|
DialogueHandlerCtx {
|
||||||
|
bot,
|
||||||
|
update,
|
||||||
|
dialogue: Dialogue::Start,
|
||||||
|
} => start(DialogueHandlerCtx::new(bot, update, ())).await,
|
||||||
|
DialogueHandlerCtx {
|
||||||
|
bot,
|
||||||
|
update,
|
||||||
|
dialogue: Dialogue::ReceiveFullName,
|
||||||
|
} => full_name(DialogueHandlerCtx::new(bot, update, ())).await,
|
||||||
|
DialogueHandlerCtx {
|
||||||
|
bot,
|
||||||
|
update,
|
||||||
|
dialogue: Dialogue::ReceiveAge(s),
|
||||||
|
} => age(DialogueHandlerCtx::new(bot, update, s)).await,
|
||||||
|
DialogueHandlerCtx {
|
||||||
|
bot,
|
||||||
|
update,
|
||||||
|
dialogue: Dialogue::ReceiveFavouriteMusic(s),
|
||||||
|
} => favourite_music(DialogueHandlerCtx::new(bot, update, s)).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// [Run!]
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
run().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run() {
|
||||||
|
teloxide::enable_logging!();
|
||||||
|
log::info!("Starting dialogue_bot!");
|
||||||
|
|
||||||
|
let bot = Bot::from_env();
|
||||||
|
|
||||||
|
Dispatcher::new(bot)
|
||||||
|
.message_handler(&DialogueDispatcher::new(|ctx| async move {
|
||||||
|
handle_message(ctx)
|
||||||
|
.await
|
||||||
|
.expect("Something wrong with the bot!")
|
||||||
|
}))
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
}
|
15
examples/guess_a_number_bot/Cargo.toml
Normal file
15
examples/guess_a_number_bot/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "guess_a_number_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.8"
|
||||||
|
tokio = "0.2.9"
|
||||||
|
smart-default = "0.6.0"
|
||||||
|
rand = "0.7.3"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
teloxide = { path = "../../" }
|
116
examples/guess_a_number_bot/src/main.rs
Normal file
116
examples/guess_a_number_bot/src/main.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
// This is a guess-a-number game!
|
||||||
|
//
|
||||||
|
// # Example
|
||||||
|
// ```
|
||||||
|
// - Hello
|
||||||
|
// - Let's play a game! Guess a number from 1 to 10 (inclusively).
|
||||||
|
// - 4
|
||||||
|
// - No.
|
||||||
|
// - 3
|
||||||
|
// - No.
|
||||||
|
// - Blablabla
|
||||||
|
// - Oh, please, send me a text message!
|
||||||
|
// - 111
|
||||||
|
// - Oh, please, send me a number in the range [1; 10]!
|
||||||
|
// - 5
|
||||||
|
// - Congratulations! You won!
|
||||||
|
// ```
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate smart_default;
|
||||||
|
|
||||||
|
use teloxide::prelude::*;
|
||||||
|
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// [A type-safe finite automaton]
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[derive(SmartDefault)]
|
||||||
|
enum Dialogue {
|
||||||
|
#[default]
|
||||||
|
Start,
|
||||||
|
ReceiveAttempt(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// [Control a dialogue]
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
async fn handle_message(
|
||||||
|
ctx: DialogueHandlerCtx<Message, Dialogue>,
|
||||||
|
) -> Result<DialogueStage<Dialogue>, RequestError> {
|
||||||
|
match ctx.dialogue {
|
||||||
|
Dialogue::Start => {
|
||||||
|
ctx.answer(
|
||||||
|
"Let's play a game! Guess a number from 1 to 10 (inclusively).",
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(Dialogue::ReceiveAttempt(thread_rng().gen_range(1, 11)))
|
||||||
|
}
|
||||||
|
Dialogue::ReceiveAttempt(secret) => match ctx.update.text() {
|
||||||
|
None => {
|
||||||
|
ctx.answer("Oh, please, send me a text message!")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(ctx.dialogue)
|
||||||
|
}
|
||||||
|
Some(text) => match text.parse::<u8>() {
|
||||||
|
Ok(attempt) => match attempt {
|
||||||
|
x if !(1..=10).contains(&x) => {
|
||||||
|
ctx.answer(
|
||||||
|
"Oh, please, send me a number in the range [1; \
|
||||||
|
10]!",
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(ctx.dialogue)
|
||||||
|
}
|
||||||
|
x if x == secret => {
|
||||||
|
ctx.answer("Congratulations! You won!").send().await?;
|
||||||
|
exit()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
ctx.answer("No.").send().await?;
|
||||||
|
next(ctx.dialogue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
ctx.answer(
|
||||||
|
"Oh, please, send me a number in the range [1; 10]!",
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
next(ctx.dialogue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// [Run!]
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
run().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run() {
|
||||||
|
teloxide::enable_logging!();
|
||||||
|
log::info!("Starting guess_a_number_bot!");
|
||||||
|
|
||||||
|
let bot = Bot::from_env();
|
||||||
|
|
||||||
|
Dispatcher::new(bot)
|
||||||
|
.message_handler(&DialogueDispatcher::new(|ctx| async move {
|
||||||
|
handle_message(ctx)
|
||||||
|
.await
|
||||||
|
.expect("Something wrong with the bot!")
|
||||||
|
}))
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
}
|
13
examples/multiple_handlers_bot/Cargo.toml
Normal file
13
examples/multiple_handlers_bot/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "multiple_handlers_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.8"
|
||||||
|
tokio = "0.2.9"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
teloxide = { path = "../../" }
|
49
examples/multiple_handlers_bot/src/main.rs
Normal file
49
examples/multiple_handlers_bot/src/main.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// This example demonstrates the ability of Dispatcher to deal with multiple
|
||||||
|
// handlers.
|
||||||
|
|
||||||
|
use teloxide::prelude::*;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
run().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run() {
|
||||||
|
teloxide::enable_logging!();
|
||||||
|
log::info!("Starting multiple_handlers_bot!");
|
||||||
|
|
||||||
|
let bot = Bot::from_env();
|
||||||
|
|
||||||
|
// Create a dispatcher with multiple handlers of different types. This will
|
||||||
|
// print One! and Two! on every incoming UpdateKind::Message.
|
||||||
|
Dispatcher::<RequestError>::new(bot)
|
||||||
|
// This is the first UpdateKind::Message handler, which will be called
|
||||||
|
// after the Update handler below.
|
||||||
|
.message_handler(&|ctx: DispatcherHandlerCtx<Message>| async move {
|
||||||
|
log::info!("Two!");
|
||||||
|
DispatcherHandlerResult::next(ctx.update, Ok(()))
|
||||||
|
})
|
||||||
|
// Remember: handler of Update are called first.
|
||||||
|
.update_handler(&|ctx: DispatcherHandlerCtx<Update>| async move {
|
||||||
|
log::info!("One!");
|
||||||
|
DispatcherHandlerResult::next(ctx.update, Ok(()))
|
||||||
|
})
|
||||||
|
// This handler will be called right after the first UpdateKind::Message
|
||||||
|
// handler, because it is registered after.
|
||||||
|
.message_handler(&|_ctx: DispatcherHandlerCtx<Message>| async move {
|
||||||
|
// The same as DispatcherHandlerResult::exit(Ok(()))
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
// This handler will never be called, because the UpdateKind::Message
|
||||||
|
// handler above terminates the pipeline.
|
||||||
|
.message_handler(&|ctx: DispatcherHandlerCtx<Message>| async move {
|
||||||
|
log::info!("This will never be printed!");
|
||||||
|
DispatcherHandlerResult::next(ctx.update, Ok(()))
|
||||||
|
})
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Note: if this bot receive, for example, UpdateKind::ChannelPost, it will
|
||||||
|
// only print "One!", because the UpdateKind::Message handlers will not be
|
||||||
|
// called.
|
||||||
|
}
|
16
examples/ping_pong_bot/Cargo.toml
Normal file
16
examples/ping_pong_bot/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "ping_pong_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.8"
|
||||||
|
tokio = "0.2.9"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
teloxide = { path = "../../" }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
23
examples/ping_pong_bot/src/main.rs
Normal file
23
examples/ping_pong_bot/src/main.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use teloxide::prelude::*;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
run().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run() {
|
||||||
|
teloxide::enable_logging!();
|
||||||
|
log::info!("Starting ping_pong_bot!");
|
||||||
|
|
||||||
|
let bot = Bot::from_env();
|
||||||
|
|
||||||
|
// Create a dispatcher with a single message handler that answers "pong" to
|
||||||
|
// each incoming message.
|
||||||
|
Dispatcher::<RequestError>::new(bot)
|
||||||
|
.message_handler(&|ctx: DispatcherHandlerCtx<Message>| async move {
|
||||||
|
ctx.answer("pong").send().await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
}
|
14
examples/simple_commands_bot/Cargo.toml
Normal file
14
examples/simple_commands_bot/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "simple_commands_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Temirkhan Myrzamadi <hirrolot@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.8"
|
||||||
|
tokio = "0.2.9"
|
||||||
|
rand = "0.7.3"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
teloxide = { path = "../../" }
|
63
examples/simple_commands_bot/src/main.rs
Normal file
63
examples/simple_commands_bot/src/main.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use teloxide::{prelude::*, utils::command::BotCommand};
|
||||||
|
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
|
#[derive(BotCommand)]
|
||||||
|
#[command(rename = "lowercase", description = "These commands are supported:")]
|
||||||
|
enum Command {
|
||||||
|
#[command(description = "display this text.")]
|
||||||
|
Help,
|
||||||
|
#[command(description = "be a cat.")]
|
||||||
|
Meow,
|
||||||
|
#[command(description = "generate a random number within [0; 1).")]
|
||||||
|
Generate,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_command(
|
||||||
|
ctx: DispatcherHandlerCtx<Message>,
|
||||||
|
) -> Result<(), RequestError> {
|
||||||
|
let text = match ctx.update.text() {
|
||||||
|
Some(text) => text,
|
||||||
|
None => {
|
||||||
|
log::info!("Received a message, but not text.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let command = match Command::parse(text) {
|
||||||
|
Some((command, _)) => command,
|
||||||
|
None => {
|
||||||
|
log::info!("Received a text message, but not a command.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match command {
|
||||||
|
Command::Help => ctx.answer(Command::descriptions()).send().await?,
|
||||||
|
Command::Generate => {
|
||||||
|
ctx.answer(thread_rng().gen_range(0.0, 1.0).to_string())
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
Command::Meow => ctx.answer("I am a cat! Meow!").send().await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
run().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run() {
|
||||||
|
teloxide::enable_logging!();
|
||||||
|
log::info!("Starting simple_commands_bot!");
|
||||||
|
|
||||||
|
let bot = Bot::from_env();
|
||||||
|
|
||||||
|
Dispatcher::<RequestError>::new(bot)
|
||||||
|
.message_handler(&handle_command)
|
||||||
|
.dispatch()
|
||||||
|
.await;
|
||||||
|
}
|
405
logo.svg
Normal file
405
logo.svg
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
id="svg8"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 1000.1765 1000.1688"
|
||||||
|
height="1000.1688mm"
|
||||||
|
width="1000.1688mm">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<radialGradient
|
||||||
|
id="radialGradient2098"
|
||||||
|
cx="257.63312"
|
||||||
|
cy="346.10947"
|
||||||
|
r="1837.1556"
|
||||||
|
fx="323.34329"
|
||||||
|
fy="313.63162"
|
||||||
|
gradientTransform="matrix(0.25369962,0,0,0.25369962,-147.14212,-160.64302)"
|
||||||
|
gradientUnits="userSpaceOnUse">
|
||||||
|
<stop
|
||||||
|
offset="0"
|
||||||
|
style="stop-color:#FF980E"
|
||||||
|
id="stop2086" />
|
||||||
|
<stop
|
||||||
|
offset="0.295012"
|
||||||
|
style="stop-color:#FF7139"
|
||||||
|
id="stop2088" />
|
||||||
|
<stop
|
||||||
|
offset="0.4846462"
|
||||||
|
style="stop-color:#FF5B51"
|
||||||
|
id="stop2090" />
|
||||||
|
<stop
|
||||||
|
offset="0.6260016"
|
||||||
|
style="stop-color:#FF4F5E"
|
||||||
|
id="stop2092" />
|
||||||
|
<stop
|
||||||
|
offset="0.73652"
|
||||||
|
style="stop-color:#f6374e;stop-opacity:0.97254902"
|
||||||
|
id="stop2094" />
|
||||||
|
<stop
|
||||||
|
offset="0.8428285"
|
||||||
|
style="stop-color:#f52d44;stop-opacity:1"
|
||||||
|
id="stop2096" />
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient
|
||||||
|
id="radialGradient7778"
|
||||||
|
cx="624.28052"
|
||||||
|
cy="138.58418"
|
||||||
|
r="3105.1294"
|
||||||
|
gradientTransform="matrix(0.9588647 0 0 0.9588647 1293.9r906006 17.7235451)"
|
||||||
|
gradientUnits="userSpaceOnUse">
|
||||||
|
<stop
|
||||||
|
offset="0.0535657"
|
||||||
|
style="stop-color:#FFF44F"
|
||||||
|
id="stop7768" />
|
||||||
|
<stop
|
||||||
|
offset="0.4572717"
|
||||||
|
style="stop-color:#FF980E"
|
||||||
|
id="stop7770" />
|
||||||
|
<stop
|
||||||
|
offset="0.5210502"
|
||||||
|
style="stop-color:#FF8424"
|
||||||
|
id="stop7772" />
|
||||||
|
<stop
|
||||||
|
offset="0.5831793"
|
||||||
|
style="stop-color:#FF7634"
|
||||||
|
id="stop7774" />
|
||||||
|
<stop
|
||||||
|
offset="0.639343"
|
||||||
|
style="stop-color:#FF7139"
|
||||||
|
id="stop7776" />
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient
|
||||||
|
y2="180"
|
||||||
|
x2="100.008"
|
||||||
|
y1="40.007999"
|
||||||
|
x1="160.008"
|
||||||
|
id="linearGradient7176"
|
||||||
|
gradientUnits="userSpaceOnUse">
|
||||||
|
<stop
|
||||||
|
id="stop7172"
|
||||||
|
offset="0"
|
||||||
|
stop-color="#37aee2"
|
||||||
|
style="stop-color:#ff3750;stop-opacity:0" />
|
||||||
|
<stop
|
||||||
|
id="stop7174"
|
||||||
|
offset="1"
|
||||||
|
stop-color="#1e96c8"
|
||||||
|
style="stop-color:#ffd865;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
id="b"
|
||||||
|
x1="160.008"
|
||||||
|
y1="40.007999"
|
||||||
|
x2="100.008"
|
||||||
|
y2="180">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#37aee2;stop-opacity:0"
|
||||||
|
stop-color="#37aee2"
|
||||||
|
offset="0"
|
||||||
|
id="stop4892" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#1e96c8;stop-opacity:0"
|
||||||
|
stop-color="#1e96c8"
|
||||||
|
offset="1"
|
||||||
|
id="stop4894" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="scale(1.0919081,0.91582798)"
|
||||||
|
id="w"
|
||||||
|
x1="140.86748"
|
||||||
|
y1="147.14627"
|
||||||
|
x2="88.524704"
|
||||||
|
y2="112.05341">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#a047bf;stop-opacity:1"
|
||||||
|
stop-color="#eff7fc"
|
||||||
|
offset="0"
|
||||||
|
id="stop4897" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ee7e40;stop-opacity:1"
|
||||||
|
stop-color="#fff"
|
||||||
|
offset="1"
|
||||||
|
id="stop4899" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
y2="180"
|
||||||
|
x2="100.008"
|
||||||
|
y1="40.007999"
|
||||||
|
x1="160.008"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
id="linearGradient7166"
|
||||||
|
xlink:href="#b" />
|
||||||
|
<radialGradient
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.25369962,0,0,0.25369962,116.95639,-406.02996)"
|
||||||
|
r="3105.1294"
|
||||||
|
cy="138.58418"
|
||||||
|
cx="624.28052"
|
||||||
|
id="SVGID_11_">
|
||||||
|
<stop
|
||||||
|
id="stop135"
|
||||||
|
style="stop-color:#FFF44F"
|
||||||
|
offset="0.0535657" />
|
||||||
|
<stop
|
||||||
|
id="stop137"
|
||||||
|
style="stop-color:#FF980E"
|
||||||
|
offset="0.4572717" />
|
||||||
|
<stop
|
||||||
|
id="stop139"
|
||||||
|
style="stop-color:#FF8424"
|
||||||
|
offset="0.5210502" />
|
||||||
|
<stop
|
||||||
|
id="stop141"
|
||||||
|
style="stop-color:#FF7634"
|
||||||
|
offset="0.587052" />
|
||||||
|
<stop
|
||||||
|
id="stop143"
|
||||||
|
style="stop-color:#FF7139"
|
||||||
|
offset="0.639343" />
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(4.4292543,-0.00937413,0.00863853,4.0816693,-654.99926,-176.9423)"
|
||||||
|
r="65.246368"
|
||||||
|
fy="60.625607"
|
||||||
|
fx="198.56102"
|
||||||
|
cy="60.625607"
|
||||||
|
cx="198.56102"
|
||||||
|
id="radialGradient1819"
|
||||||
|
xlink:href="#SVGID_7_" />
|
||||||
|
<radialGradient
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.25369962,0,0,0.25369962,-147.14212,-160.64302)"
|
||||||
|
fy="313.63162"
|
||||||
|
fx="323.34329"
|
||||||
|
r="1837.1556"
|
||||||
|
cy="346.10947"
|
||||||
|
cx="257.63312"
|
||||||
|
id="SVGID_7_">
|
||||||
|
<stop
|
||||||
|
id="stop85"
|
||||||
|
style="stop-color:#FF980E"
|
||||||
|
offset="0" />
|
||||||
|
<stop
|
||||||
|
id="stop87"
|
||||||
|
style="stop-color:#FF7139"
|
||||||
|
offset="0.295012" />
|
||||||
|
<stop
|
||||||
|
id="stop89"
|
||||||
|
style="stop-color:#FF5B51"
|
||||||
|
offset="0.4846462" />
|
||||||
|
<stop
|
||||||
|
id="stop91"
|
||||||
|
style="stop-color:#FF4F5E"
|
||||||
|
offset="0.6260016" />
|
||||||
|
<stop
|
||||||
|
id="stop93"
|
||||||
|
style="stop-color:#FF4055"
|
||||||
|
offset="0.73652" />
|
||||||
|
<stop
|
||||||
|
id="stop95"
|
||||||
|
style="stop-color:#FF3750"
|
||||||
|
offset="0.8428285" />
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.26458333,0,0,0.26458333,-232.53772,-527.76884)"
|
||||||
|
fy="-153.06329"
|
||||||
|
fx="389.09302"
|
||||||
|
r="1876.7874"
|
||||||
|
cy="-119.88482"
|
||||||
|
cx="321.9653"
|
||||||
|
id="SVGID_1_">
|
||||||
|
<stop
|
||||||
|
id="stop3"
|
||||||
|
style="stop-color:#FFF44F"
|
||||||
|
offset="0" />
|
||||||
|
<stop
|
||||||
|
id="stop5"
|
||||||
|
style="stop-color:#FF980E"
|
||||||
|
offset="0.2948518" />
|
||||||
|
<stop
|
||||||
|
id="stop7"
|
||||||
|
style="stop-color:#FF5D36"
|
||||||
|
offset="0.4315208" />
|
||||||
|
<stop
|
||||||
|
id="stop9"
|
||||||
|
style="stop-color:#FF3750"
|
||||||
|
offset="0.5302083" />
|
||||||
|
<stop
|
||||||
|
id="stop11"
|
||||||
|
style="stop-color:#F5156C"
|
||||||
|
offset="0.7493189" />
|
||||||
|
<stop
|
||||||
|
id="stop13"
|
||||||
|
style="stop-color:#F1136E"
|
||||||
|
offset="0.7647903" />
|
||||||
|
<stop
|
||||||
|
id="stop15"
|
||||||
|
style="stop-color:#DA057A"
|
||||||
|
offset="0.8800957" />
|
||||||
|
<stop
|
||||||
|
id="stop17"
|
||||||
|
style="stop-color:#D2007F"
|
||||||
|
offset="0.9527844" />
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient
|
||||||
|
gradientTransform="matrix(0.49700557,0,0,0.49700557,7.2354759,-1184.0654)"
|
||||||
|
y2="1708.0002"
|
||||||
|
x2="477.68073"
|
||||||
|
y1="246.00212"
|
||||||
|
x1="1321.7657"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
id="SVGID_12_">
|
||||||
|
<stop
|
||||||
|
id="stop2743"
|
||||||
|
style="stop-color:#FFF44F;stop-opacity:0.8"
|
||||||
|
offset="0" />
|
||||||
|
<stop
|
||||||
|
id="stop2745"
|
||||||
|
style="stop-color:#FFF44F;stop-opacity:0"
|
||||||
|
offset="0.75" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.25369962,0,0,0.25369962,-147.14212,-160.64302)"
|
||||||
|
fy="313.63162"
|
||||||
|
fx="323.34329"
|
||||||
|
r="1837.1556"
|
||||||
|
cy="346.10947"
|
||||||
|
cx="257.63312"
|
||||||
|
id="SVGID_7_-2">
|
||||||
|
<stop
|
||||||
|
id="stop85-9"
|
||||||
|
style="stop-color:#FF980E"
|
||||||
|
offset="0" />
|
||||||
|
<stop
|
||||||
|
id="stop87-1"
|
||||||
|
style="stop-color:#FF7139"
|
||||||
|
offset="0.295012" />
|
||||||
|
<stop
|
||||||
|
id="stop89-2"
|
||||||
|
style="stop-color:#FF5B51"
|
||||||
|
offset="0.4846462" />
|
||||||
|
<stop
|
||||||
|
id="stop91-7"
|
||||||
|
style="stop-color:#FF4F5E"
|
||||||
|
offset="0.6260016" />
|
||||||
|
<stop
|
||||||
|
id="stop93-0"
|
||||||
|
style="stop-color:#FF4055"
|
||||||
|
offset="0.73652" />
|
||||||
|
<stop
|
||||||
|
id="stop95-9"
|
||||||
|
style="stop-color:#FF3750"
|
||||||
|
offset="0.8428285" />
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient
|
||||||
|
gradientTransform="translate(-290.96154,-5.268689)"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
y2="220.18013"
|
||||||
|
x2="24.136377"
|
||||||
|
y1="-7.9157548"
|
||||||
|
x1="238.16116"
|
||||||
|
id="linearGradient1127"
|
||||||
|
xlink:href="#SVGID_12_" />
|
||||||
|
</defs>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
style="display:inline"
|
||||||
|
transform="translate(-25.485081,874.03927)"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
ry="122.74884"
|
||||||
|
y="-873.87427"
|
||||||
|
x="25.485081"
|
||||||
|
height="1000.0077"
|
||||||
|
width="1000.0077"
|
||||||
|
id="rect99"
|
||||||
|
style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke-width:0.24541904" />
|
||||||
|
<g
|
||||||
|
transform="matrix(3.4097882,0,0,3.4097882,-58.261564,-771.89541)"
|
||||||
|
id="g4557">
|
||||||
|
<path
|
||||||
|
transform="scale(0.26458333)"
|
||||||
|
style="fill:#f1f1f1;fill-opacity:1;stroke:#f1f1f1;stroke-width:10.54206085;stroke-opacity:1"
|
||||||
|
d="m 647.14258,-57.232422 c -274.72614,0 -498.32227,223.703512 -498.32227,498.324222 0,274.6207 223.70155,498.32226 498.32227,498.32226 274.6207,0 498.32422,-223.70156 498.32422,-498.32226 0,-274.62071 -223.70352,-498.324222 -498.32422,-498.324222 z m -0.74219,44.277344 a 32.785812,32.785812 0 0 1 0.004,0 32.680391,32.785812 0 0 1 31.83789,32.785156 32.786135,32.786135 0 0 1 -65.57227,0 32.785812,32.785812 0 0 1 33.73047,-32.785156 z m -74.10547,53.974609 50.4961,52.921875 c 11.38541,11.912534 30.25542,12.440064 42.16796,0.949219 l 56.50586,-53.871094 a 403.44469,403.44469 0 0 1 66.1543,18.777344 c 1.97576,0.407587 3.93673,0.970989 5.87695,1.730469 13.99934,5.479892 27.61358,11.724081 40.80079,18.671875 a 403.44469,403.44469 0 0 1 2.36523,1.269531 c 10.36827,5.551273 20.46754,11.537743 30.26953,17.943359 a 403.44469,403.44469 0 0 1 6.39844,4.269531 c 8.32464,5.66907 16.42445,11.64272 24.28711,17.90234 a 403.44469,403.44469 0 0 1 9.8125,8.05664 c 6.45209,5.47512 12.72445,11.15359 18.81836,17.01758 a 403.44469,403.44469 0 0 1 11.94922,11.9668 c 5.13237,5.3678 10.11852,10.87734 14.95117,16.52148 a 403.44469,403.44469 0 0 1 12.51172,15.37696 c 3.37107,4.35061 6.66483,8.76435 9.85937,13.2539 0.23859,0.33532 0.45279,0.6794 0.67383,1.02149 a 403.44469,403.44469 0 0 1 21.47266,32.93555 l -38.68946,87.28906 c -6.6415,15.07515 0.21195,32.78628 15.18164,39.5332 l 74.4258,32.99609 a 403.44469,403.44469 0 0 1 0.8437,69.99805 h -0.5 c -5.7455,78.10094 -33.7008,150.02515 -77.59567,209.47266 h 0.7168 a 403.44469,403.44469 0 0 1 -37.31836,43.2207 l -69.26172,-14.86328 c -16.12936,-3.47887 -32.04848,6.85115 -35.52734,22.98047 l -16.44532,76.74805 a 403.44469,403.44469 0 0 1 -36.45312,14.36328 c -0.0902,0.0369 -0.18261,0.0712 -0.27344,0.10742 a 403.44469,403.44469 0 0 1 -1.01367,0.375 c -0.24074,0.087 -0.47392,0.18338 -0.71875,0.26562 -40.39438,13.56934 -83.64335,20.92188 -128.60938,20.92188 -40.73374,0 -80.05693,-6.03481 -117.12695,-17.25781 -2.64112,-0.79961 -5.08556,-1.88567 -7.35742,-3.22071 A 403.44469,403.44469 0 0 1 477.09961,803.52734 L 460.6543,726.78125 c -3.47891,-16.12936 -19.29252,-26.46129 -35.42188,-22.98242 l -67.78515,14.54883 a 403.44469,403.44469 0 0 1 -21.21289,-24.19141 c -1.98,-1.33851 -3.76227,-2.94462 -5.30469,-4.875 -55.15082,-69.02256 -88.12305,-156.54162 -88.12305,-251.76172 0,-2.92892 0.34165,-5.66443 0.98047,-8.23242 a 403.44469,403.44469 0 0 1 2.23047,-35.21289 l 70.63086,-31.41602 c 15.07515,-6.74692 21.92856,-24.35258 15.18164,-39.42773 l -14.54883,-32.78516 h 0.26563 c -13.3252,-23.74604 -27.38086,-44.84375 -27.38086,-44.84375 10.41967,-18.87574 21.81773,-35.1234 34.7871,-52.17773 51.04777,-67.12628 122.64108,-117.779826 205.4043,-142.587892 2.17055,-0.650615 4.32668,-0.947976 6.47266,-0.953126 a 403.44469,403.44469 0 0 1 35.46484,-8.863281 z M 255.5,283.69727 a 32.785812,32.785812 0 0 1 0.006,0 32.785812,32.785812 0 0 1 31.83594,32.78711 32.785812,32.785812 0 0 1 -65.57032,0 A 32.785812,32.785812 0 0 1 255.5,283.69727 Z m 781.6934,1.47656 a 32.785812,32.785812 0 0 1 0.01,0 32.785812,32.785812 0 0 1 31.8379,32.78515 32.78615,32.78615 0 0 1 -65.5723,0 32.785812,32.785812 0 0 1 33.7286,-32.78515 z M 403.61523,745.33398 a 32.785812,32.785812 0 0 1 0.006,0.002 32.785812,32.785812 0 0 1 31.83789,32.78515 32.78613,32.78613 0 0 1 -65.57226,0 32.785812,32.785812 0 0 1 33.72851,-32.78711 z m 485.46289,1.47657 a 32.785812,32.785812 0 0 1 0.006,0 32.785812,32.785812 0 0 1 31.83594,32.78711 32.78613,32.78613 0 0 1 -65.57226,0 32.785812,32.785812 0 0 1 33.73046,-32.78711 z"
|
||||||
|
id="path4504" />
|
||||||
|
<path
|
||||||
|
style="fill:#f1f1f1;fill-opacity:1;fill-rule:evenodd;stroke:#f1f1f1;stroke-width:8.36776161;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 295.70761,116.70536 A 124.4844,124.4844 0 0 1 171.22322,241.18976 124.4844,124.4844 0 0 1 46.738819,116.70536 124.4844,124.4844 0 0 1 171.22322,-7.779039 124.4844,124.4844 0 0 1 295.70761,116.70536 Z m -2.34297,-12.02169 19.41321,12.02169 -19.41321,12.02168 16.67974,15.59193 -21.36569,8.00516 13.33264,18.54854 -22.56507,3.68181 9.48347,20.80784 -22.84399,-0.80889 5.2438,22.25825 -22.25825,-5.2438 0.80888,22.84399 -20.80783,-9.48346 -3.68181,22.56506 -18.54854,-13.33263 -8.00516,21.36568 -15.59193,-16.67974 -12.02168,19.41321 -12.02169,-19.41321 -15.59193,16.67974 -8.00515,-21.36568 -18.54854,13.33263 -3.68182,-22.56506 -20.807831,9.48346 0.808884,-22.84399 -22.258245,5.2438 5.243797,-22.25825 -22.843989,0.80889 9.483463,-20.80784 L 40.435106,170.87267 53.767739,152.32413 32.402055,144.31897 49.081793,128.72704 29.668586,116.70536 49.081793,104.68367 32.402055,89.091745 53.767739,81.086587 40.435106,62.538049 63.000169,58.856234 53.516706,38.048401 l 22.843989,0.808884 -5.243797,-22.258246 22.258245,5.243797 -0.808884,-22.8439882 20.807831,9.4834629 3.68182,-22.5650637 18.54854,13.33263359 8.00515,-21.36568459 15.59193,16.6797382 12.02169,-19.4132062 12.02168,19.4132062 15.59193,-16.6797382 8.00516,21.36568459 18.54854,-13.33263359 3.68181,22.5650637 20.80783,-9.4834629 -0.80888,22.8439882 22.25825,-5.243797 -5.2438,22.258246 22.84399,-0.808884 -9.48347,20.807833 22.56507,3.681815 -13.33264,18.548538 21.36569,8.005158 z"
|
||||||
|
id="path4506" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(2.9858713,0,0,2.9858713,167.26874,-732.2594)"
|
||||||
|
id="g4928">
|
||||||
|
<circle
|
||||||
|
style="fill:url(#linearGradient7166);fill-opacity:1"
|
||||||
|
cx="120"
|
||||||
|
cy="120"
|
||||||
|
r="120"
|
||||||
|
id="circle4904" />
|
||||||
|
<path
|
||||||
|
style="fill:#bf2543;fill-opacity:1"
|
||||||
|
d="m 98,175 c -3.8876,0 -3.227,-1.4679 -4.5678,-5.1695 L 82,132.2059 170,80"
|
||||||
|
id="path4906" />
|
||||||
|
<path
|
||||||
|
style="fill:#a92543;fill-opacity:1"
|
||||||
|
d="m 98,175 c 3,0 4.3255,-1.372 6,-3 l 16,-15.558 -19.958,-12.035"
|
||||||
|
id="path4908" />
|
||||||
|
<path
|
||||||
|
style="fill:url(#radialGradient1819);fill-opacity:1"
|
||||||
|
d="m 100.04,144.41 48.36,35.729 c 5.5185,3.0449 9.5014,1.4684 10.876,-5.1235 l 19.685,-92.763 c 2.0154,-8.0802 -3.0801,-11.745 -8.3594,-9.3482 l -115.59,44.571 c -7.8901,3.1647 -7.8441,7.5666 -1.4382,9.528 l 29.663,9.2583 68.673,-43.325 c 3.2419,-1.9659 6.2173,-0.90899 3.7752,1.2584"
|
||||||
|
id="path4910" />
|
||||||
|
</g>
|
||||||
|
<rect
|
||||||
|
y="-44.385658"
|
||||||
|
x="34.240719"
|
||||||
|
height="159.17772"
|
||||||
|
width="164.35423"
|
||||||
|
id="rect2241"
|
||||||
|
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke-width:0.25622422" />
|
||||||
|
<g
|
||||||
|
transform="matrix(0.53235487,0,0,0.53235487,-27.283341,10.578064)"
|
||||||
|
id="g4557-3"
|
||||||
|
style="display:inline">
|
||||||
|
<path
|
||||||
|
style="fill:#282828;fill-opacity:1;stroke:#282828;stroke-width:2.78925347;stroke-opacity:1"
|
||||||
|
d="m -136.20958,-18.218258 c -72.68796,0 -131.84776,59.18822 -131.84776,131.848288 0,72.66006 59.1877,131.84776 131.84776,131.84776 72.660061,0 131.8482833,-59.1877 131.8482833,-131.84776 0,-72.660068 -59.1882223,-131.848288 -131.8482833,-131.848288 z m -0.19637,11.7150471 a 8.6745793,8.6745793 0 0 1 10e-4,0 8.6466867,8.6745793 0 0 1 8.42377,8.6744058 8.674665,8.674665 0 0 1 -17.34933,0 8.6745793,8.6745793 0 0 1 8.92452,-8.6744058 z m -19.60707,14.2807817 13.36042,14.0022462 c 3.01239,3.151858 8.00508,3.291434 11.15694,0.251148 l 14.95051,-14.2533942 a 106.74474,106.74474 0 0 1 17.503328,4.9681722 c 0.522753,0.107841 1.041593,0.256908 1.554943,0.457854 3.703992,1.449888 7.306093,3.101996 10.795209,4.940266 a 106.74474,106.74474 0 0 1 0.6258,0.335897 c 2.743271,1.468774 5.41537,3.052695 8.008813,4.747514 a 106.74474,106.74474 0 0 1 1.692921,1.129647 c 2.202561,1.499941 4.345635,3.080469 6.425964,4.73666 a 106.74474,106.74474 0 0 1 2.596224,2.131653 c 1.707115,1.448625 3.366677,2.951054 4.979024,4.502568 a 106.74474,106.74474 0 0 1 3.161565,3.166216 c 1.357939,1.42023 2.677192,2.877963 3.95583,4.371308 a 106.74474,106.74474 0 0 1 3.310393,4.068487 c 0.891929,1.151099 1.763403,2.318901 2.608625,3.506761 0.06313,0.08872 0.1198,0.179758 0.178284,0.270269 a 106.74474,106.74474 0 0 1 5.681308,8.714198 l -10.236586,23.09523 c -1.757231,3.988633 0.05608,8.674703 4.016808,10.459826 l 19.691826,8.730223 a 106.74474,106.74474 0 0 1 0.223229,18.52031 h -0.132291 c -1.520164,20.66421 -8.91667,39.69416 -20.530521,55.42298 h 0.189653 a 106.74474,106.74474 0 0 1 -9.873816,11.43547 l -18.325496,-3.93257 c -4.26756,-0.92045 -8.479494,1.8127 -9.399942,6.08025 l -4.351158,20.30625 a 106.74474,106.74474 0 0 1 -9.644887,3.80029 c -0.0239,0.01 -0.0483,0.0188 -0.0724,0.0284 a 106.74474,106.74474 0 0 1 -0.2682,0.0992 c -0.0637,0.023 -0.12539,0.0485 -0.19017,0.0703 -10.68768,3.59023 -22.13063,5.53558 -34.0279,5.53558 -10.77746,0 -21.18172,-1.59671 -30.98983,-4.56612 -0.6988,-0.21157 -1.34556,-0.49892 -1.94665,-0.85215 a 106.74474,106.74474 0 0 1 -11.86491,-4.53409 l -4.35116,-20.30574 c -0.92046,-4.26756 -5.10448,-7.00121 -9.37204,-6.08076 l -17.93482,3.84938 a 106.74474,106.74474 0 0 1 -5.61258,-6.40065 c -0.52387,-0.35415 -0.99543,-0.7791 -1.40353,-1.28984 -14.59199,-18.26222 -23.31589,-41.4183 -23.31589,-66.61196 0,-0.77494 0.0904,-1.49871 0.25942,-2.17816 a 106.74474,106.74474 0 0 1 0.59014,-9.31674 l 18.68775,-8.312159 c 3.98863,-1.785122 5.80193,-6.443287 4.01681,-10.43192 l -3.84938,-8.674407 h 0.0703 c -3.52562,-6.282806 -7.24452,-11.864908 -7.24452,-11.864908 2.75688,-4.994207 5.77261,-9.293067 9.20409,-13.805358 13.50639,-17.760495 32.44879,-31.162579 54.34656,-37.726379 0.57429,-0.172142 1.14476,-0.250819 1.71255,-0.252181 a 106.74474,106.74474 0 0 1 9.38341,-2.3450772 z m -83.81866,64.2084842 a 8.6745793,8.6745793 0 0 1 0.002,0 8.6745793,8.6745793 0 0 1 8.42326,8.674923 8.6745793,8.6745793 0 0 1 -17.34881,0 8.6745793,8.6745793 0 0 1 8.92396,-8.674923 z m 206.823047,0.390674 a 8.6745793,8.6745793 0 0 1 0.0026,0 8.6745793,8.6745793 0 0 1 8.423778,8.674404 8.674669,8.674669 0 0 1 -17.349338,0 8.6745793,8.6745793 0 0 1 8.924026,-8.674404 z M -200.64286,194.12744 a 8.6745793,8.6745793 0 0 1 0.002,5.3e-4 8.6745793,8.6745793 0 0 1 8.42378,8.6744 8.674665,8.674665 0 0 1 -17.34933,0 8.6745793,8.6745793 0 0 1 8.924,-8.67492 z m 128.445393,0.39067 a 8.6745793,8.6745793 0 0 1 0.0016,0 8.6745793,8.6745793 0 0 1 8.423259,8.67492 8.6746635,8.6746635 0 0 1 -17.349327,0 8.6745793,8.6745793 0 0 1 8.924518,-8.67492 z"
|
||||||
|
id="path4504-6" />
|
||||||
|
<path
|
||||||
|
style="fill:#282828;fill-opacity:1;fill-rule:evenodd;stroke:#282828;stroke-width:8.36776161;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="m -11.724928,113.62977 a 124.4844,124.4844 0 0 1 -124.484392,124.4844 124.4844,124.4844 0 0 1 -124.4844,-124.4844 124.4844,124.4844 0 0 1 124.4844,-124.484403 124.4844,124.4844 0 0 1 124.484392,124.484403 z m -2.34297,-12.02169 19.4132097,12.02169 -19.4132097,12.02168 16.6797397,15.59193 -21.3656897,8.00516 13.3326397,18.54854 -22.5650697,3.68181 9.48347,20.80784 -22.84399,-0.80889 5.2438,22.25825 -22.25825,-5.2438 0.80888,22.84399 -20.80783,-9.48346 -3.68181,22.56506 -18.548542,-13.33263 -8.00516,21.36568 -15.59193,-16.67974 -12.02168,19.41321 -12.02169,-19.41321 -15.59193,16.67974 -8.00515,-21.36568 -18.54854,13.33263 -3.68182,-22.56506 -20.80783,9.48346 0.80888,-22.84399 -22.25824,5.2438 5.2438,-22.25825 -22.84399,0.80889 9.48346,-20.80784 -22.56506,-3.68181 13.33263,-18.54854 -21.36568,-8.00516 16.67973,-15.59193 -19.4132,-12.02168 19.4132,-12.02169 -16.67973,-15.591929 21.36568,-8.005158 -13.33263,-18.548538 22.56506,-3.681815 -9.48346,-20.807833 22.84399,0.808884 -5.2438,-22.258246 22.25824,5.243797 -0.80888,-22.8439878 20.80783,9.4834629 3.68182,-22.5650641 18.54854,13.332634 8.00515,-21.365685 15.59193,16.6797386 12.02169,-19.4132066 12.02168,19.4132066 15.59193,-16.6797386 8.00516,21.365685 18.548542,-13.332634 3.68181,22.5650641 20.80783,-9.4834629 -0.80888,22.8439878 22.25825,-5.243797 -5.2438,22.258246 22.84399,-0.808884 -9.48347,20.807833 22.5650697,3.681815 -13.3326397,18.548538 21.3656897,8.005158 z"
|
||||||
|
id="path4506-7" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="matrix(2.9858713,0,0,2.9858713,1047.9069,-718.22402)"
|
||||||
|
id="g4928-3"
|
||||||
|
style="display:inline">
|
||||||
|
<path
|
||||||
|
id="path4906-0"
|
||||||
|
d="m -121.42796,67.483064 c -0.93128,0.03404 -1.91559,0.271144 -2.90546,0.720544 l -115.58979,44.570682 c -7.8901,3.1647 -7.84366,7.56642 -1.43777,9.52782 l 29.65763,9.25734 10.20069,33.56963 c 1.34081,3.70159 0.68066,5.17018 4.56826,5.17018 3,0 4.32487,-1.37279 5.99937,-3.00079 l 14.41311,-14.01404 29.98686,22.15398 c 5.51849,3.0449 9.5023,1.46827 10.87689,-5.12363 l 19.68418,-92.762936 c 1.63751,-6.565163 -1.41841,-10.216308 -5.45397,-10.06878 z"
|
||||||
|
style="fill:url(#linearGradient1127);fill-opacity:1;stroke-width:1" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 22 KiB |
BIN
media/FILTER_DP_FLOWCHART.png
Normal file
BIN
media/FILTER_DP_FLOWCHART.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
5
rustfmt.toml
Normal file
5
rustfmt.toml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
format_code_in_doc_comments = true
|
||||||
|
wrap_comments = true
|
||||||
|
format_strings = true
|
||||||
|
max_width = 80
|
||||||
|
merge_imports = true
|
1590
src/bot/api.rs
Normal file
1590
src/bot/api.rs
Normal file
File diff suppressed because it is too large
Load diff
66
src/bot/download.rs
Normal file
66
src/bot/download.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
80
src/bot/mod.rs
Normal file
80
src/bot/mod.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
use reqwest::Client;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
mod download;
|
||||||
|
|
||||||
|
/// A Telegram bot used to send requests.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Bot {
|
||||||
|
token: String,
|
||||||
|
client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bot {
|
||||||
|
/// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a
|
||||||
|
/// bot's token) and the default [`reqwest::Client`].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If cannot get the `TELOXIDE_TOKEN` environmental variable.
|
||||||
|
///
|
||||||
|
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
|
||||||
|
pub fn from_env() -> Arc<Self> {
|
||||||
|
Self::new(
|
||||||
|
std::env::var("TELOXIDE_TOKEN")
|
||||||
|
.expect("Cannot get the TELOXIDE_TOKEN env variable"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
|
||||||
|
pub fn from_env_with_client(client: Client) -> Arc<Self> {
|
||||||
|
Self::with_client(
|
||||||
|
std::env::var("TELOXIDE_TOKEN")
|
||||||
|
.expect("Cannot get the TELOXIDE_TOKEN env variable"),
|
||||||
|
client,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `Bot` with the specified token and the default
|
||||||
|
/// [`reqwest::Client`].
|
||||||
|
///
|
||||||
|
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
|
||||||
|
pub fn new<S>(token: S) -> Arc<Self>
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
Self::with_client(token, Client::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `Bot` with the specified token and your
|
||||||
|
/// [`reqwest::Client`].
|
||||||
|
///
|
||||||
|
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
|
||||||
|
pub fn with_client<S>(token: S, client: Client) -> Arc<Self>
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
Arc::new(Self {
|
||||||
|
token: token.into(),
|
||||||
|
client,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bot {
|
||||||
|
// TODO: const fn
|
||||||
|
pub fn token(&self) -> &str {
|
||||||
|
&self.token
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: const fn
|
||||||
|
pub fn client(&self) -> &Client {
|
||||||
|
&self.client
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,64 +0,0 @@
|
||||||
use futures::compat::Future01CompatExt;
|
|
||||||
use reqwest::r#async::Client;
|
|
||||||
use reqwest::StatusCode;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref REQWEST_CLIENT: Client = Client::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
const TELEGRAM_URL_START: &str = "https://api.telegram.org/bot";
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
Api {
|
|
||||||
status_code: StatusCode,
|
|
||||||
description: Option<String>,
|
|
||||||
},
|
|
||||||
Send(reqwest::Error),
|
|
||||||
InvalidJson(reqwest::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Response<T> = Result<T, Error>;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct User {
|
|
||||||
id: i64,
|
|
||||||
is_bot: bool,
|
|
||||||
first_name: String,
|
|
||||||
last_name: Option<String>,
|
|
||||||
username: Option<String>,
|
|
||||||
language_code: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_me(bot_token: &str) -> Response<User> {
|
|
||||||
let mut response = REQWEST_CLIENT
|
|
||||||
.get(&format!(
|
|
||||||
"{}{bot_token}/getMe",
|
|
||||||
TELEGRAM_URL_START,
|
|
||||||
bot_token = bot_token
|
|
||||||
))
|
|
||||||
.send()
|
|
||||||
.compat()
|
|
||||||
.await
|
|
||||||
.map_err(Error::Send)?;
|
|
||||||
|
|
||||||
let response_json = response
|
|
||||||
.json::<Value>()
|
|
||||||
.compat()
|
|
||||||
.await
|
|
||||||
.map_err(Error::InvalidJson)?;
|
|
||||||
|
|
||||||
if response_json["ok"] == "false" {
|
|
||||||
return Err(Error::Api {
|
|
||||||
status_code: response.status(),
|
|
||||||
description: match response_json.get("description") {
|
|
||||||
None => None,
|
|
||||||
Some(description) => Some(description.to_string()),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(serde_json::from_value(response_json["result"].clone()).unwrap())
|
|
||||||
}
|
|
31
src/dispatching/ctx_handlers.rs
Normal file
31
src/dispatching/ctx_handlers.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use std::{future::Future, pin::Pin};
|
||||||
|
|
||||||
|
/// An asynchronous handler of a context.
|
||||||
|
///
|
||||||
|
/// See [the module-level documentation for the design
|
||||||
|
/// overview](crate::dispatching).
|
||||||
|
pub trait CtxHandler<Ctx, Output> {
|
||||||
|
#[must_use]
|
||||||
|
fn handle_ctx<'a>(
|
||||||
|
&'a self,
|
||||||
|
ctx: Ctx,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Output> + 'a>>
|
||||||
|
where
|
||||||
|
Ctx: 'a;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx, Output, F, Fut> CtxHandler<Ctx, Output> for F
|
||||||
|
where
|
||||||
|
F: Fn(Ctx) -> Fut,
|
||||||
|
Fut: Future<Output = Output>,
|
||||||
|
{
|
||||||
|
fn handle_ctx<'a>(
|
||||||
|
&'a self,
|
||||||
|
ctx: Ctx,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Fut::Output> + 'a>>
|
||||||
|
where
|
||||||
|
Ctx: 'a,
|
||||||
|
{
|
||||||
|
Box::pin(async move { self(ctx).await })
|
||||||
|
}
|
||||||
|
}
|
97
src/dispatching/dialogue/dialogue_dispatcher.rs
Normal file
97
src/dispatching/dialogue/dialogue_dispatcher.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use crate::dispatching::{
|
||||||
|
dialogue::{
|
||||||
|
DialogueHandlerCtx, DialogueStage, GetChatId, InMemStorage, Storage,
|
||||||
|
},
|
||||||
|
CtxHandler, DispatcherHandlerCtx,
|
||||||
|
};
|
||||||
|
use std::{future::Future, pin::Pin};
|
||||||
|
|
||||||
|
/// A dispatcher of dialogues.
|
||||||
|
///
|
||||||
|
/// Note that `DialogueDispatcher` implements `CtxHandler`, so you can just put
|
||||||
|
/// an instance of this dispatcher into the [`Dispatcher`]'s methods.
|
||||||
|
///
|
||||||
|
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
|
pub struct DialogueDispatcher<'a, D, H> {
|
||||||
|
storage: Box<dyn Storage<D> + 'a>,
|
||||||
|
handler: H,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, D, H> DialogueDispatcher<'a, D, H>
|
||||||
|
where
|
||||||
|
D: Default + 'a,
|
||||||
|
{
|
||||||
|
/// Creates a dispatcher with the specified `handler` and [`InMemStorage`]
|
||||||
|
/// (a default storage).
|
||||||
|
///
|
||||||
|
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(handler: H) -> Self {
|
||||||
|
Self {
|
||||||
|
storage: Box::new(InMemStorage::default()),
|
||||||
|
handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a dispatcher with the specified `handler` and `storage`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_storage<Stg>(handler: H, storage: Stg) -> Self
|
||||||
|
where
|
||||||
|
Stg: Storage<D> + 'a,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
storage: Box::new(storage),
|
||||||
|
handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, D, H, Upd> CtxHandler<DispatcherHandlerCtx<Upd>, Result<(), ()>>
|
||||||
|
for DialogueDispatcher<'a, D, H>
|
||||||
|
where
|
||||||
|
H: CtxHandler<DialogueHandlerCtx<Upd, D>, DialogueStage<D>>,
|
||||||
|
Upd: GetChatId,
|
||||||
|
D: Default,
|
||||||
|
{
|
||||||
|
fn handle_ctx<'b>(
|
||||||
|
&'b self,
|
||||||
|
ctx: DispatcherHandlerCtx<Upd>,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<(), ()>> + 'b>>
|
||||||
|
where
|
||||||
|
Upd: 'b,
|
||||||
|
{
|
||||||
|
Box::pin(async move {
|
||||||
|
let chat_id = ctx.update.chat_id();
|
||||||
|
|
||||||
|
let dialogue = self
|
||||||
|
.storage
|
||||||
|
.remove_dialogue(chat_id)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if let DialogueStage::Next(new_dialogue) = self
|
||||||
|
.handler
|
||||||
|
.handle_ctx(DialogueHandlerCtx {
|
||||||
|
bot: ctx.bot,
|
||||||
|
update: ctx.update,
|
||||||
|
dialogue,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if self
|
||||||
|
.storage
|
||||||
|
.update_dialogue(chat_id, new_dialogue)
|
||||||
|
.await
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
panic!(
|
||||||
|
"We previously storage.remove_dialogue() so \
|
||||||
|
storage.update_dialogue() must return None"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
190
src/dispatching/dialogue/dialogue_handler_ctx.rs
Normal file
190
src/dispatching/dialogue/dialogue_handler_ctx.rs
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
use crate::{
|
||||||
|
dispatching::dialogue::GetChatId,
|
||||||
|
requests::{
|
||||||
|
DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage,
|
||||||
|
PinChatMessage, SendAnimation, SendAudio, SendContact, SendDocument,
|
||||||
|
SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker,
|
||||||
|
SendVenue, SendVideo, SendVideoNote, SendVoice,
|
||||||
|
},
|
||||||
|
types::{ChatId, ChatOrInlineMessage, InputFile, InputMedia, Message},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A context of a [`DialogueDispatcher`]'s message handler.
|
||||||
|
///
|
||||||
|
/// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher
|
||||||
|
pub struct DialogueHandlerCtx<Upd, D> {
|
||||||
|
pub bot: Arc<Bot>,
|
||||||
|
pub update: Upd,
|
||||||
|
pub dialogue: D,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Upd, D> DialogueHandlerCtx<Upd, D> {
|
||||||
|
/// Creates a new instance with the provided fields.
|
||||||
|
pub fn new(bot: Arc<Bot>, update: Upd, dialogue: D) -> Self {
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
update,
|
||||||
|
dialogue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new instance by substituting a dialogue and preserving
|
||||||
|
/// `self.bot` and `self.update`.
|
||||||
|
pub fn with_new_dialogue<Nd>(
|
||||||
|
self,
|
||||||
|
new_dialogue: Nd,
|
||||||
|
) -> DialogueHandlerCtx<Upd, Nd> {
|
||||||
|
DialogueHandlerCtx {
|
||||||
|
bot: self.bot,
|
||||||
|
update: self.update,
|
||||||
|
dialogue: new_dialogue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Upd, D> GetChatId for DialogueHandlerCtx<Upd, D>
|
||||||
|
where
|
||||||
|
Upd: GetChatId,
|
||||||
|
{
|
||||||
|
fn chat_id(&self) -> i64 {
|
||||||
|
self.update.chat_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> DialogueHandlerCtx<Message, D> {
|
||||||
|
pub fn answer<T>(&self, text: T) -> SendMessage
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.bot.send_message(self.chat_id(), text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reply_to<T>(&self, text: T) -> SendMessage
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.bot
|
||||||
|
.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_audio(&self, audio: InputFile) -> SendAudio {
|
||||||
|
self.bot.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_document(&self, document: InputFile) -> SendDocument {
|
||||||
|
self.bot.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_voice(&self, voice: InputFile) -> SendVoice {
|
||||||
|
self.bot.send_voice(self.update.chat.id, voice)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn answer_media_group<T>(&self, media_group: T) -> SendMediaGroup
|
||||||
|
where
|
||||||
|
T: Into<Vec<InputMedia>>,
|
||||||
|
{
|
||||||
|
self.bot.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_venue<T, U>(
|
||||||
|
&self,
|
||||||
|
latitude: f32,
|
||||||
|
longitude: f32,
|
||||||
|
title: T,
|
||||||
|
address: U,
|
||||||
|
) -> SendVenue
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
U: Into<String>,
|
||||||
|
{
|
||||||
|
self.bot.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_contact<T, U>(
|
||||||
|
&self,
|
||||||
|
phone_number: T,
|
||||||
|
first_name: U,
|
||||||
|
) -> SendContact
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
U: Into<String>,
|
||||||
|
{
|
||||||
|
self.bot
|
||||||
|
.send_contact(self.chat_id(), phone_number, first_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn answer_sticker<T>(&self, sticker: InputFile) -> SendSticker {
|
||||||
|
self.bot.send_sticker(self.update.chat.id, sticker)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn forward_to<T>(&self, chat_id: T) -> ForwardMessage
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.bot
|
||||||
|
.forward_message(chat_id, self.update.chat.id, self.update.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn edit_message_text<T>(&self, text: T) -> 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 delete_message(&self) -> DeleteMessage {
|
||||||
|
self.bot.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)
|
||||||
|
}
|
||||||
|
}
|
16
src/dispatching/dialogue/dialogue_stage.rs
Normal file
16
src/dispatching/dialogue/dialogue_stage.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/// Continue or terminate a dialogue.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
|
||||||
|
pub enum DialogueStage<D> {
|
||||||
|
Next(D),
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shortcut for `Ok(DialogueStage::Next(dialogue))`.
|
||||||
|
pub fn next<E, D>(dialogue: D) -> Result<DialogueStage<D>, E> {
|
||||||
|
Ok(DialogueStage::Next(dialogue))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shortcut for `Ok(DialogueStage::Exit)`.
|
||||||
|
pub fn exit<E, D>() -> Result<DialogueStage<D>, E> {
|
||||||
|
Ok(DialogueStage::Exit)
|
||||||
|
}
|
13
src/dispatching/dialogue/get_chat_id.rs
Normal file
13
src/dispatching/dialogue/get_chat_id.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use crate::types::Message;
|
||||||
|
|
||||||
|
/// Something that has a chat ID.
|
||||||
|
pub trait GetChatId {
|
||||||
|
#[must_use]
|
||||||
|
fn chat_id(&self) -> i64;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetChatId for Message {
|
||||||
|
fn chat_id(&self) -> i64 {
|
||||||
|
self.chat.id
|
||||||
|
}
|
||||||
|
}
|
48
src/dispatching/dialogue/mod.rs
Normal file
48
src/dispatching/dialogue/mod.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
//! Dealing with dialogues.
|
||||||
|
//!
|
||||||
|
//! There are four main components:
|
||||||
|
//!
|
||||||
|
//! 1. Your type `D`, which designates a dialogue state at the current
|
||||||
|
//! moment.
|
||||||
|
//! 2. [`Storage`], which encapsulates all the dialogues.
|
||||||
|
//! 3. Your handler, which receives an update and turns your dialogue into the
|
||||||
|
//! next state.
|
||||||
|
//! 4. [`DialogueDispatcher`], which encapsulates your handler, [`Storage`],
|
||||||
|
//! and implements [`CtxHandler`].
|
||||||
|
//!
|
||||||
|
//! You supply [`DialogueDispatcher`] into [`Dispatcher`]. Every time
|
||||||
|
//! [`Dispatcher`] calls `DialogueDispatcher::handle_ctx(...)`, the following
|
||||||
|
//! steps are executed:
|
||||||
|
//!
|
||||||
|
//! 1. If a storage doesn't contain a dialogue from this chat, supply
|
||||||
|
//! `D::default()` into you handler, otherwise, supply the saved session
|
||||||
|
//! from this chat.
|
||||||
|
//! 2. If a handler has returned [`DialogueStage::Exit`], remove the session
|
||||||
|
//! from the storage, otherwise ([`DialogueStage::Next`]) force the storage to
|
||||||
|
//! update the session.
|
||||||
|
//!
|
||||||
|
//! Please, see [examples/dialogue_bot] as an example.
|
||||||
|
//!
|
||||||
|
//! [`Storage`]: crate::dispatching::dialogue::Storage
|
||||||
|
//! [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher
|
||||||
|
//! [`DialogueStage::Exit`]:
|
||||||
|
//! crate::dispatching::dialogue::DialogueStage::Exit
|
||||||
|
//! [`DialogueStage::Next`]: crate::dispatching::dialogue::DialogueStage::Next
|
||||||
|
//! [`CtxHandler`]: crate::dispatching::CtxHandler
|
||||||
|
//! [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
|
//! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/tree/dev/examples/dialogue_bot
|
||||||
|
|
||||||
|
#![allow(clippy::module_inception)]
|
||||||
|
#![allow(clippy::type_complexity)]
|
||||||
|
|
||||||
|
mod dialogue_dispatcher;
|
||||||
|
mod dialogue_handler_ctx;
|
||||||
|
mod dialogue_stage;
|
||||||
|
mod get_chat_id;
|
||||||
|
mod storage;
|
||||||
|
|
||||||
|
pub use dialogue_dispatcher::DialogueDispatcher;
|
||||||
|
pub use dialogue_handler_ctx::DialogueHandlerCtx;
|
||||||
|
pub use dialogue_stage::{exit, next, DialogueStage};
|
||||||
|
pub use get_chat_id::GetChatId;
|
||||||
|
pub use storage::{InMemStorage, Storage};
|
29
src/dispatching/dialogue/storage/in_mem_storage.rs
Normal file
29
src/dispatching/dialogue/storage/in_mem_storage.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use super::Storage;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
/// A memory storage based on a hash map. Stores all the dialogues directly in
|
||||||
|
/// RAM.
|
||||||
|
///
|
||||||
|
/// ## 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.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct InMemStorage<D> {
|
||||||
|
map: Mutex<HashMap<i64, D>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
#[async_trait]
|
||||||
|
impl<D> Storage<D> for InMemStorage<D> {
|
||||||
|
async fn remove_dialogue(&self, chat_id: i64) -> Option<D> {
|
||||||
|
self.map.lock().await.remove(&chat_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_dialogue(&self, chat_id: i64, dialogue: D) -> Option<D> {
|
||||||
|
self.map.lock().await.insert(chat_id, dialogue)
|
||||||
|
}
|
||||||
|
}
|
28
src/dispatching/dialogue/storage/mod.rs
Normal file
28
src/dispatching/dialogue/storage/mod.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
mod in_mem_storage;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
pub use in_mem_storage::InMemStorage;
|
||||||
|
|
||||||
|
/// 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`].
|
||||||
|
///
|
||||||
|
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Storage<D> {
|
||||||
|
/// Removes a dialogue with the specified `chat_id`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a
|
||||||
|
/// `dialogue` was deleted.
|
||||||
|
async fn remove_dialogue(&self, chat_id: i64) -> Option<D>;
|
||||||
|
|
||||||
|
/// Updates a dialogue with the specified `chat_id`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if there wasn't such a dialogue, `Some(dialogue)` if a
|
||||||
|
/// `dialogue` was updated.
|
||||||
|
async fn update_dialogue(&self, chat_id: i64, dialogue: D) -> Option<D>;
|
||||||
|
}
|
381
src/dispatching/dispatcher.rs
Normal file
381
src/dispatching/dispatcher.rs
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
use crate::{
|
||||||
|
dispatching::{
|
||||||
|
error_handlers::ErrorHandler, update_listeners,
|
||||||
|
update_listeners::UpdateListener, CtxHandler, DispatcherHandlerCtx,
|
||||||
|
DispatcherHandlerResult, LoggingErrorHandler,
|
||||||
|
},
|
||||||
|
types::{
|
||||||
|
CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll,
|
||||||
|
PollAnswer, PreCheckoutQuery, ShippingQuery, Update, UpdateKind,
|
||||||
|
},
|
||||||
|
Bot, RequestError,
|
||||||
|
};
|
||||||
|
use futures::{stream, StreamExt};
|
||||||
|
use std::{fmt::Debug, future::Future, sync::Arc};
|
||||||
|
|
||||||
|
type Handlers<'a, Upd, HandlerE> = Vec<
|
||||||
|
Box<
|
||||||
|
dyn CtxHandler<
|
||||||
|
DispatcherHandlerCtx<Upd>,
|
||||||
|
DispatcherHandlerResult<Upd, HandlerE>,
|
||||||
|
> + 'a,
|
||||||
|
>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// One dispatcher to rule them all.
|
||||||
|
///
|
||||||
|
/// See [the module-level documentation for the design
|
||||||
|
/// overview](crate::dispatching).
|
||||||
|
// HandlerE=RequestError doesn't work now, because of very poor type inference.
|
||||||
|
// See https://github.com/rust-lang/rust/issues/27336 for more details.
|
||||||
|
pub struct Dispatcher<'a, HandlerE = RequestError> {
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
|
||||||
|
handlers_error_handler: Box<dyn ErrorHandler<HandlerE> + 'a>,
|
||||||
|
|
||||||
|
update_handlers: Handlers<'a, Update, HandlerE>,
|
||||||
|
message_handlers: Handlers<'a, Message, HandlerE>,
|
||||||
|
edited_message_handlers: Handlers<'a, Message, HandlerE>,
|
||||||
|
channel_post_handlers: Handlers<'a, Message, HandlerE>,
|
||||||
|
edited_channel_post_handlers: Handlers<'a, Message, HandlerE>,
|
||||||
|
inline_query_handlers: Handlers<'a, InlineQuery, HandlerE>,
|
||||||
|
chosen_inline_result_handlers: Handlers<'a, ChosenInlineResult, HandlerE>,
|
||||||
|
callback_query_handlers: Handlers<'a, CallbackQuery, HandlerE>,
|
||||||
|
shipping_query_handlers: Handlers<'a, ShippingQuery, HandlerE>,
|
||||||
|
pre_checkout_query_handlers: Handlers<'a, PreCheckoutQuery, HandlerE>,
|
||||||
|
poll_handlers: Handlers<'a, Poll, HandlerE>,
|
||||||
|
poll_answer_handlers: Handlers<'a, PollAnswer, HandlerE>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, HandlerE> Dispatcher<'a, HandlerE>
|
||||||
|
where
|
||||||
|
HandlerE: Debug + 'a,
|
||||||
|
{
|
||||||
|
/// Constructs a new dispatcher with this `bot`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(bot: Arc<Bot>) -> Self {
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
handlers_error_handler: Box::new(LoggingErrorHandler::new(
|
||||||
|
"An error from a Dispatcher's handler",
|
||||||
|
)),
|
||||||
|
update_handlers: Vec::new(),
|
||||||
|
message_handlers: Vec::new(),
|
||||||
|
edited_message_handlers: Vec::new(),
|
||||||
|
channel_post_handlers: Vec::new(),
|
||||||
|
edited_channel_post_handlers: Vec::new(),
|
||||||
|
inline_query_handlers: Vec::new(),
|
||||||
|
chosen_inline_result_handlers: Vec::new(),
|
||||||
|
callback_query_handlers: Vec::new(),
|
||||||
|
shipping_query_handlers: Vec::new(),
|
||||||
|
pre_checkout_query_handlers: Vec::new(),
|
||||||
|
poll_handlers: Vec::new(),
|
||||||
|
poll_answer_handlers: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a handler of errors, produced by other handlers.
|
||||||
|
#[must_use]
|
||||||
|
pub fn handlers_error_handler<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: ErrorHandler<HandlerE> + 'a,
|
||||||
|
{
|
||||||
|
self.handlers_error_handler = Box::new(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn update_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<Update>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<Update, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.update_handlers = register_handler(self.update_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn message_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<Message>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<Message, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.message_handlers = register_handler(self.message_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn edited_message_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<Message>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<Message, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.edited_message_handlers =
|
||||||
|
register_handler(self.edited_message_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn channel_post_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<Message>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<Message, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.channel_post_handlers =
|
||||||
|
register_handler(self.channel_post_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn edited_channel_post_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<Message>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<Message, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.edited_channel_post_handlers =
|
||||||
|
register_handler(self.edited_channel_post_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn inline_query_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<InlineQuery>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<InlineQuery, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.inline_query_handlers =
|
||||||
|
register_handler(self.inline_query_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn chosen_inline_result_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<ChosenInlineResult>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<ChosenInlineResult, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.chosen_inline_result_handlers =
|
||||||
|
register_handler(self.chosen_inline_result_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn callback_query_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<CallbackQuery>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<CallbackQuery, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.callback_query_handlers =
|
||||||
|
register_handler(self.callback_query_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn shipping_query_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<ShippingQuery>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<ShippingQuery, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.shipping_query_handlers =
|
||||||
|
register_handler(self.shipping_query_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn pre_checkout_query_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<PreCheckoutQuery>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<PreCheckoutQuery, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.pre_checkout_query_handlers =
|
||||||
|
register_handler(self.pre_checkout_query_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn poll_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<Poll>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<Poll, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.poll_handlers = register_handler(self.poll_handlers, h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn poll_answer_handler<H, I>(mut self, h: &'a H) -> Self
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<PollAnswer>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<PollAnswer, HandlerE>> + 'a,
|
||||||
|
{
|
||||||
|
self.poll_answer_handlers =
|
||||||
|
register_handler(self.poll_answer_handlers, 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(&'a self) {
|
||||||
|
self.dispatch_with_listener(
|
||||||
|
update_listeners::polling_default(Arc::clone(&self.bot)),
|
||||||
|
&LoggingErrorHandler::new("An error from the update listener"),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts your bot with custom `update_listener` and
|
||||||
|
/// `update_listener_error_handler`.
|
||||||
|
pub async fn dispatch_with_listener<UListener, ListenerE, Eh>(
|
||||||
|
&'a self,
|
||||||
|
update_listener: UListener,
|
||||||
|
update_listener_error_handler: &'a Eh,
|
||||||
|
) where
|
||||||
|
UListener: UpdateListener<ListenerE> + 'a,
|
||||||
|
Eh: ErrorHandler<ListenerE> + 'a,
|
||||||
|
ListenerE: Debug,
|
||||||
|
{
|
||||||
|
let update_listener = Box::pin(update_listener);
|
||||||
|
|
||||||
|
update_listener
|
||||||
|
.for_each_concurrent(None, move |update| async move {
|
||||||
|
log::trace!("Dispatcher received an update: {:?}", update);
|
||||||
|
|
||||||
|
let update = match update {
|
||||||
|
Ok(update) => update,
|
||||||
|
Err(error) => {
|
||||||
|
update_listener_error_handler.handle_error(error).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let update =
|
||||||
|
match self.handle(&self.update_handlers, update).await {
|
||||||
|
Some(update) => update,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
match update.kind {
|
||||||
|
UpdateKind::Message(message) => {
|
||||||
|
self.handle(&self.message_handlers, message).await;
|
||||||
|
}
|
||||||
|
UpdateKind::EditedMessage(message) => {
|
||||||
|
self.handle(&self.edited_message_handlers, message)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
UpdateKind::ChannelPost(post) => {
|
||||||
|
self.handle(&self.channel_post_handlers, post).await;
|
||||||
|
}
|
||||||
|
UpdateKind::EditedChannelPost(post) => {
|
||||||
|
self.handle(&self.edited_channel_post_handlers, post)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
UpdateKind::InlineQuery(query) => {
|
||||||
|
self.handle(&self.inline_query_handlers, query).await;
|
||||||
|
}
|
||||||
|
UpdateKind::ChosenInlineResult(result) => {
|
||||||
|
self.handle(
|
||||||
|
&self.chosen_inline_result_handlers,
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
UpdateKind::CallbackQuery(query) => {
|
||||||
|
self.handle(&self.callback_query_handlers, query).await;
|
||||||
|
}
|
||||||
|
UpdateKind::ShippingQuery(query) => {
|
||||||
|
self.handle(&self.shipping_query_handlers, query).await;
|
||||||
|
}
|
||||||
|
UpdateKind::PreCheckoutQuery(query) => {
|
||||||
|
self.handle(&self.pre_checkout_query_handlers, query)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
UpdateKind::Poll(poll) => {
|
||||||
|
self.handle(&self.poll_handlers, poll).await;
|
||||||
|
}
|
||||||
|
UpdateKind::PollAnswer(answer) => {
|
||||||
|
self.handle(&self.poll_answer_handlers, answer).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles a single update.
|
||||||
|
#[allow(clippy::ptr_arg)]
|
||||||
|
async fn handle<Upd>(
|
||||||
|
&self,
|
||||||
|
handlers: &Handlers<'a, Upd, HandlerE>,
|
||||||
|
update: Upd,
|
||||||
|
) -> Option<Upd> {
|
||||||
|
stream::iter(handlers)
|
||||||
|
.fold(Some(update), |acc, handler| {
|
||||||
|
async move {
|
||||||
|
// Option::and_then is not working here, because
|
||||||
|
// Middleware::handle is asynchronous.
|
||||||
|
match acc {
|
||||||
|
Some(update) => {
|
||||||
|
let DispatcherHandlerResult { next, result } =
|
||||||
|
handler
|
||||||
|
.handle_ctx(DispatcherHandlerCtx {
|
||||||
|
bot: Arc::clone(&self.bot),
|
||||||
|
update,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Err(error) = result {
|
||||||
|
self.handlers_error_handler
|
||||||
|
.handle_error(error)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
next
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms Future<Output = T> into Future<Output = U> by applying an Into
|
||||||
|
/// conversion.
|
||||||
|
async fn intermediate_fut0<T, U>(fut: impl Future<Output = T>) -> U
|
||||||
|
where
|
||||||
|
T: Into<U>,
|
||||||
|
{
|
||||||
|
fut.await.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms CtxHandler with Into<DispatcherHandlerResult<...>> as a return
|
||||||
|
/// value into CtxHandler with DispatcherHandlerResult return value.
|
||||||
|
fn intermediate_fut1<'a, Upd, HandlerE, H, I>(
|
||||||
|
h: &'a H,
|
||||||
|
) -> impl CtxHandler<
|
||||||
|
DispatcherHandlerCtx<Upd>,
|
||||||
|
DispatcherHandlerResult<Upd, HandlerE>,
|
||||||
|
> + 'a
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<Upd>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<Upd, HandlerE>> + 'a,
|
||||||
|
Upd: 'a,
|
||||||
|
{
|
||||||
|
move |ctx| intermediate_fut0(h.handle_ctx(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a single handler.
|
||||||
|
fn register_handler<'a, Upd, H, I, HandlerE>(
|
||||||
|
mut handlers: Handlers<'a, Upd, HandlerE>,
|
||||||
|
h: &'a H,
|
||||||
|
) -> Handlers<'a, Upd, HandlerE>
|
||||||
|
where
|
||||||
|
H: CtxHandler<DispatcherHandlerCtx<Upd>, I> + 'a,
|
||||||
|
I: Into<DispatcherHandlerResult<Upd, HandlerE>> + 'a,
|
||||||
|
HandlerE: 'a,
|
||||||
|
Upd: 'a,
|
||||||
|
{
|
||||||
|
handlers.push(Box::new(intermediate_fut1(h)));
|
||||||
|
handlers
|
||||||
|
}
|
168
src/dispatching/dispatcher_handler_ctx.rs
Normal file
168
src/dispatching/dispatcher_handler_ctx.rs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
use crate::{
|
||||||
|
dispatching::dialogue::GetChatId,
|
||||||
|
requests::{
|
||||||
|
DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage,
|
||||||
|
PinChatMessage, SendAnimation, SendAudio, SendContact, SendDocument,
|
||||||
|
SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker,
|
||||||
|
SendVenue, SendVideo, SendVideoNote, SendVoice,
|
||||||
|
},
|
||||||
|
types::{ChatId, ChatOrInlineMessage, InputFile, InputMedia, Message},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A [`Dispatcher`]'s handler's context of a bot and an update.
|
||||||
|
///
|
||||||
|
/// See [the module-level documentation for the design
|
||||||
|
/// overview](crate::dispatching).
|
||||||
|
///
|
||||||
|
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
|
pub struct DispatcherHandlerCtx<Upd> {
|
||||||
|
pub bot: Arc<Bot>,
|
||||||
|
pub update: Upd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Upd> GetChatId for DispatcherHandlerCtx<Upd>
|
||||||
|
where
|
||||||
|
Upd: GetChatId,
|
||||||
|
{
|
||||||
|
fn chat_id(&self) -> i64 {
|
||||||
|
self.update.chat_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DispatcherHandlerCtx<Message> {
|
||||||
|
pub fn answer<T>(&self, text: T) -> SendMessage
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.bot.send_message(self.chat_id(), text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reply_to<T>(&self, text: T) -> SendMessage
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.bot
|
||||||
|
.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_audio(&self, audio: InputFile) -> SendAudio {
|
||||||
|
self.bot.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_document(&self, document: InputFile) -> SendDocument {
|
||||||
|
self.bot.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_voice(&self, voice: InputFile) -> SendVoice {
|
||||||
|
self.bot.send_voice(self.update.chat.id, voice)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn answer_media_group<T>(&self, media_group: T) -> SendMediaGroup
|
||||||
|
where
|
||||||
|
T: Into<Vec<InputMedia>>,
|
||||||
|
{
|
||||||
|
self.bot.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_venue<T, U>(
|
||||||
|
&self,
|
||||||
|
latitude: f32,
|
||||||
|
longitude: f32,
|
||||||
|
title: T,
|
||||||
|
address: U,
|
||||||
|
) -> SendVenue
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
U: Into<String>,
|
||||||
|
{
|
||||||
|
self.bot.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_contact<T, U>(
|
||||||
|
&self,
|
||||||
|
phone_number: T,
|
||||||
|
first_name: U,
|
||||||
|
) -> SendContact
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
U: Into<String>,
|
||||||
|
{
|
||||||
|
self.bot
|
||||||
|
.send_contact(self.chat_id(), phone_number, first_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn answer_sticker<T>(&self, sticker: InputFile) -> SendSticker {
|
||||||
|
self.bot.send_sticker(self.update.chat.id, sticker)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn forward_to<T>(&self, chat_id: T) -> ForwardMessage
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.bot
|
||||||
|
.forward_message(chat_id, self.update.chat.id, self.update.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn edit_message_text<T>(&self, text: T) -> 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 delete_message(&self) -> DeleteMessage {
|
||||||
|
self.bot.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)
|
||||||
|
}
|
||||||
|
}
|
31
src/dispatching/dispatcher_handler_result.rs
Normal file
31
src/dispatching/dispatcher_handler_result.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/// A result of a handler in [`Dispatcher`].
|
||||||
|
///
|
||||||
|
/// See [the module-level documentation for the design
|
||||||
|
/// overview](crate::dispatching).
|
||||||
|
///
|
||||||
|
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
|
pub struct DispatcherHandlerResult<Upd, E> {
|
||||||
|
pub next: Option<Upd>,
|
||||||
|
pub result: Result<(), E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Upd, E> DispatcherHandlerResult<Upd, E> {
|
||||||
|
/// Creates new `DispatcherHandlerResult` that continues the pipeline.
|
||||||
|
pub fn next(update: Upd, result: Result<(), E>) -> Self {
|
||||||
|
Self {
|
||||||
|
next: Some(update),
|
||||||
|
result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new `DispatcherHandlerResult` that terminates the pipeline.
|
||||||
|
pub fn exit(result: Result<(), E>) -> Self {
|
||||||
|
Self { next: None, result }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Upd, E> From<Result<(), E>> for DispatcherHandlerResult<Upd, E> {
|
||||||
|
fn from(result: Result<(), E>) -> Self {
|
||||||
|
Self::exit(result)
|
||||||
|
}
|
||||||
|
}
|
151
src/dispatching/error_handlers.rs
Normal file
151
src/dispatching/error_handlers.rs
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
use std::{convert::Infallible, fmt::Debug, future::Future, pin::Pin};
|
||||||
|
|
||||||
|
/// An asynchronous handler of an error.
|
||||||
|
///
|
||||||
|
/// See [the module-level documentation for the design
|
||||||
|
/// overview](crate::dispatching).
|
||||||
|
pub trait ErrorHandler<E> {
|
||||||
|
#[must_use]
|
||||||
|
fn handle_error<'a>(
|
||||||
|
&'a self,
|
||||||
|
error: E,
|
||||||
|
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
|
||||||
|
where
|
||||||
|
E: 'a;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, F, Fut> ErrorHandler<E> for F
|
||||||
|
where
|
||||||
|
F: Fn(E) -> Fut,
|
||||||
|
Fut: Future<Output = ()>,
|
||||||
|
{
|
||||||
|
fn handle_error<'a>(
|
||||||
|
&'a self,
|
||||||
|
error: E,
|
||||||
|
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
|
||||||
|
where
|
||||||
|
E: 'a,
|
||||||
|
{
|
||||||
|
Box::pin(async move { self(error).await })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handler that silently ignores all errors.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// # #[tokio::main]
|
||||||
|
/// # async fn main_() {
|
||||||
|
/// use teloxide::dispatching::{ErrorHandler, IgnoringErrorHandler};
|
||||||
|
///
|
||||||
|
/// IgnoringErrorHandler.handle_error(()).await;
|
||||||
|
/// IgnoringErrorHandler.handle_error(404).await;
|
||||||
|
/// IgnoringErrorHandler.handle_error("error").await;
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub struct IgnoringErrorHandler;
|
||||||
|
|
||||||
|
impl<E> ErrorHandler<E> for IgnoringErrorHandler {
|
||||||
|
fn handle_error<'a>(
|
||||||
|
&'a self,
|
||||||
|
_: E,
|
||||||
|
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
|
||||||
|
where
|
||||||
|
E: 'a,
|
||||||
|
{
|
||||||
|
Box::pin(async {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handler that silently ignores all errors that can never happen (e.g.:
|
||||||
|
/// [`!`] or [`Infallible`]).
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
/// ```
|
||||||
|
/// # #[tokio::main]
|
||||||
|
/// # async fn main_() {
|
||||||
|
/// use std::convert::{Infallible, TryInto};
|
||||||
|
///
|
||||||
|
/// use teloxide::dispatching::{ErrorHandler, IgnoringErrorHandlerSafe};
|
||||||
|
///
|
||||||
|
/// let result: Result<String, Infallible> = "str".try_into();
|
||||||
|
/// match result {
|
||||||
|
/// Ok(string) => println!("{}", string),
|
||||||
|
/// Err(inf) => IgnoringErrorHandlerSafe.handle_error(inf).await,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// IgnoringErrorHandlerSafe.handle_error(return).await; // return type of `return` is `!` (aka never)
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```compile_fail
|
||||||
|
/// use teloxide::dispatching::{ErrorHandler, IgnoringErrorHandlerSafe};
|
||||||
|
///
|
||||||
|
/// IgnoringErrorHandlerSafe.handle_error(0);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`!`]: https://doc.rust-lang.org/std/primitive.never.html
|
||||||
|
/// [`Infallible`]: std::convert::Infallible
|
||||||
|
pub struct IgnoringErrorHandlerSafe;
|
||||||
|
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
impl ErrorHandler<Infallible> for IgnoringErrorHandlerSafe {
|
||||||
|
fn handle_error<'a>(
|
||||||
|
&'a self,
|
||||||
|
_: Infallible,
|
||||||
|
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
|
||||||
|
where
|
||||||
|
Infallible: 'a,
|
||||||
|
{
|
||||||
|
Box::pin(async {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handler that log all errors passed into it.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```
|
||||||
|
/// # #[tokio::main]
|
||||||
|
/// # async fn main_() {
|
||||||
|
/// use teloxide::dispatching::{ErrorHandler, LoggingErrorHandler};
|
||||||
|
///
|
||||||
|
/// LoggingErrorHandler::default().handle_error(()).await;
|
||||||
|
/// LoggingErrorHandler::new("error").handle_error(404).await;
|
||||||
|
/// LoggingErrorHandler::new("error")
|
||||||
|
/// .handle_error("Invalid data type!")
|
||||||
|
/// .await;
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct LoggingErrorHandler {
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoggingErrorHandler {
|
||||||
|
/// Creates `LoggingErrorHandler` with a meta text before a log.
|
||||||
|
///
|
||||||
|
/// The logs will be printed in this format: `{text}: {:?}`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new<T>(text: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
Self { text: text.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> ErrorHandler<E> for LoggingErrorHandler
|
||||||
|
where
|
||||||
|
E: Debug,
|
||||||
|
{
|
||||||
|
fn handle_error<'a>(
|
||||||
|
&'a self,
|
||||||
|
error: E,
|
||||||
|
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
|
||||||
|
where
|
||||||
|
E: 'a,
|
||||||
|
{
|
||||||
|
log::error!("{text}: {:?}", error, text = self.text);
|
||||||
|
Box::pin(async {})
|
||||||
|
}
|
||||||
|
}
|
120
src/dispatching/mod.rs
Normal file
120
src/dispatching/mod.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
//! Updates dispatching.
|
||||||
|
//!
|
||||||
|
//! The key type here is [`Dispatcher`]. It encapsulates [`Bot`], handlers for
|
||||||
|
//! [11 update kinds] (+ for [`Update`]) and [`ErrorHandler`] for them. When
|
||||||
|
//! [`Update`] is received from Telegram, the following steps are executed:
|
||||||
|
//!
|
||||||
|
//! 1. It is supplied into an appropriate handler (the first ones is those who
|
||||||
|
//! accept [`Update`]).
|
||||||
|
//! 2. If a handler failed, invoke [`ErrorHandler`] with the corresponding
|
||||||
|
//! error.
|
||||||
|
//! 3. If a handler has returned [`DispatcherHandlerResult`] with `None`,
|
||||||
|
//! terminate the pipeline, otherwise supply an update into the next handler
|
||||||
|
//! (back to step 1).
|
||||||
|
//!
|
||||||
|
//! The pipeline is executed until either all the registered handlers were
|
||||||
|
//! executed, or one of handlers has terminated the pipeline. That's simple!
|
||||||
|
//!
|
||||||
|
//! 1. Note that handlers implement [`CtxHandler`], which means that you are
|
||||||
|
//! able to supply [`DialogueDispatcher`] as a handler, since it implements
|
||||||
|
//! [`CtxHandler`] too!
|
||||||
|
//! 2. Note that you don't always need to return [`DispatcherHandlerResult`]
|
||||||
|
//! explicitly, because of automatic conversions. Just return `Result<(), E>` if
|
||||||
|
//! you want to terminate the pipeline (see the example below).
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//! ### The ping-pong bot
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! # #[tokio::main]
|
||||||
|
//! # async fn main_() {
|
||||||
|
//! use teloxide::prelude::*;
|
||||||
|
//!
|
||||||
|
//! // Setup logging here...
|
||||||
|
//!
|
||||||
|
//! // Create a dispatcher with a single message handler that answers "pong"
|
||||||
|
//! // to each incoming message.
|
||||||
|
//! Dispatcher::<RequestError>::new(Bot::from_env())
|
||||||
|
//! .message_handler(&|ctx: DispatcherHandlerCtx<Message>| async move {
|
||||||
|
//! ctx.answer("pong").send().await?;
|
||||||
|
//! Ok(())
|
||||||
|
//! })
|
||||||
|
//! .dispatch()
|
||||||
|
//! .await;
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [Full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/)
|
||||||
|
//!
|
||||||
|
//! ### Multiple handlers
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! # #[tokio::main]
|
||||||
|
//! # async fn main_() {
|
||||||
|
//! use teloxide::prelude::*;
|
||||||
|
//!
|
||||||
|
//! // Create a dispatcher with multiple handlers of different types. This will
|
||||||
|
//! // print One! and Two! on every incoming UpdateKind::Message.
|
||||||
|
//! Dispatcher::<RequestError>::new(Bot::from_env())
|
||||||
|
//! // This is the first UpdateKind::Message handler, which will be called
|
||||||
|
//! // after the Update handler below.
|
||||||
|
//! .message_handler(&|ctx: DispatcherHandlerCtx<Message>| async move {
|
||||||
|
//! log::info!("Two!");
|
||||||
|
//! DispatcherHandlerResult::next(ctx.update, Ok(()))
|
||||||
|
//! })
|
||||||
|
//! // Remember: handler of Update are called first.
|
||||||
|
//! .update_handler(&|ctx: DispatcherHandlerCtx<Update>| async move {
|
||||||
|
//! log::info!("One!");
|
||||||
|
//! DispatcherHandlerResult::next(ctx.update, Ok(()))
|
||||||
|
//! })
|
||||||
|
//! // This handler will be called right after the first UpdateKind::Message
|
||||||
|
//! // handler, because it is registered after.
|
||||||
|
//! .message_handler(&|_ctx: DispatcherHandlerCtx<Message>| async move {
|
||||||
|
//! // The same as DispatcherHandlerResult::exit(Ok(()))
|
||||||
|
//! Ok(())
|
||||||
|
//! })
|
||||||
|
//! // This handler will never be called, because the UpdateKind::Message
|
||||||
|
//! // handler above terminates the pipeline.
|
||||||
|
//! .message_handler(&|ctx: DispatcherHandlerCtx<Message>| async move {
|
||||||
|
//! log::info!("This will never be printed!");
|
||||||
|
//! DispatcherHandlerResult::next(ctx.update, Ok(()))
|
||||||
|
//! })
|
||||||
|
//! .dispatch()
|
||||||
|
//! .await;
|
||||||
|
//!
|
||||||
|
//! // Note: if this bot receive, for example, UpdateKind::ChannelPost, it will
|
||||||
|
//! // only print "One!", because the UpdateKind::Message handlers will not be
|
||||||
|
//! // called.
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [Full](https://github.com/teloxide/teloxide/blob/dev/examples/miltiple_handlers_bot/)
|
||||||
|
//!
|
||||||
|
//! For a bit more complicated example, please see [examples/dialogue_bot].
|
||||||
|
//!
|
||||||
|
//! [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
|
//! [11 update kinds]: crate::types::UpdateKind
|
||||||
|
//! [`Update`]: crate::types::Update
|
||||||
|
//! [`ErrorHandler`]: crate::dispatching::ErrorHandler
|
||||||
|
//! [`CtxHandler`]: crate::dispatching::CtxHandler
|
||||||
|
//! [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher
|
||||||
|
//! [`DispatcherHandlerResult`]: crate::dispatching::DispatcherHandlerResult
|
||||||
|
//! [`Bot`]: crate::Bot
|
||||||
|
//! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/tree/dev/examples/dialogue_bot
|
||||||
|
|
||||||
|
mod ctx_handlers;
|
||||||
|
pub mod dialogue;
|
||||||
|
mod dispatcher;
|
||||||
|
mod dispatcher_handler_ctx;
|
||||||
|
mod dispatcher_handler_result;
|
||||||
|
mod error_handlers;
|
||||||
|
pub mod update_listeners;
|
||||||
|
|
||||||
|
pub use ctx_handlers::CtxHandler;
|
||||||
|
pub use dispatcher::Dispatcher;
|
||||||
|
pub use dispatcher_handler_ctx::DispatcherHandlerCtx;
|
||||||
|
pub use dispatcher_handler_result::DispatcherHandlerResult;
|
||||||
|
pub use error_handlers::{
|
||||||
|
ErrorHandler, IgnoringErrorHandler, IgnoringErrorHandlerSafe,
|
||||||
|
LoggingErrorHandler,
|
||||||
|
};
|
204
src/dispatching/update_listeners.rs
Normal file
204
src/dispatching/update_listeners.rs
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
//! Receiving updates from Telegram.
|
||||||
|
//!
|
||||||
|
//! The key trait here is [`UpdateListener`]. You can get it by these functions:
|
||||||
|
//!
|
||||||
|
//! - [`polling_default`], which returns a default long polling listener.
|
||||||
|
//! - [`polling`], which returns a long/short polling listener with your
|
||||||
|
//! configuration.
|
||||||
|
//!
|
||||||
|
//! And then you can extract updates from it and pass them directly to a
|
||||||
|
//! dispatcher.
|
||||||
|
//!
|
||||||
|
//! Telegram supports two ways of [getting updates]: [long]/[short] polling and
|
||||||
|
//! [webhook].
|
||||||
|
//!
|
||||||
|
//! # Long Polling
|
||||||
|
//!
|
||||||
|
//! In long polling, you just call [`Box::get_updates`] every N seconds.
|
||||||
|
//!
|
||||||
|
//! ## Example
|
||||||
|
//!
|
||||||
|
//! <pre>
|
||||||
|
//! tg bot
|
||||||
|
//! | |
|
||||||
|
//! |<---------------------------| Updates? (Bot::get_updates call)
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | timeout<a id="1b" href="#1">^1</a> |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! Nope |--------------------------->|
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | delay between Bot::get_updates<a id="2b" href="#2">^2</a> |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! |<---------------------------| Updates?
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | timeout<a id="3b" href="#3">^3</a> |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! Yes |-------[updates 0, 1]------>|
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | delay |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! |<-------[offset = 1]--------| Updates?<a id="4b" href="#4">^4</a>
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | timeout |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! Yes |---------[update 2]-------->|
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | delay |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! |<-------[offset = 2]--------| Updates?
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | timeout |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! Nope |--------------------------->|
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | delay |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! |<-------[offset = 2]--------| Updates?
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | timeout |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! Nope |--------------------------->|
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | delay |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! |<-------[offset = 2]--------| Updates?
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | timeout |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! Yes |-------[updates 2..5]------>|
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | delay |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! |<-------[offset = 5]--------| Updates?
|
||||||
|
//! ↑ ↑
|
||||||
|
//! | timeout |
|
||||||
|
//! ↓ ↓
|
||||||
|
//! Nope |--------------------------->|
|
||||||
|
//! | |
|
||||||
|
//! ~ and so on, and so on ~
|
||||||
|
//! </pre>
|
||||||
|
//!
|
||||||
|
//! <a id="1" href="#1b">^1</a> A timeout can be even 0
|
||||||
|
//! (this is also called short polling),
|
||||||
|
//! but you should use it **only** for testing purposes.
|
||||||
|
//!
|
||||||
|
//! <a id="2" href="#2b">^2</a> Large delays will cause in bot lags,
|
||||||
|
//! so delay shouldn't exceed second.
|
||||||
|
//!
|
||||||
|
//! <a id="3" href="#3b">^3</a> Note that if Telegram already have updates for
|
||||||
|
//! you it will answer you **without** waiting for a timeout.
|
||||||
|
//!
|
||||||
|
//! <a id="4" href="#4b">^4</a> `offset = N` means that we've already received
|
||||||
|
//! updates `0..=N`.
|
||||||
|
//!
|
||||||
|
//! [`UpdateListener`]: UpdateListener
|
||||||
|
//! [`polling_default`]: polling_default
|
||||||
|
//! [`polling`]: polling
|
||||||
|
//! [`Box::get_updates`]: crate::Bot::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)
|
||||||
|
//! [webhook]: https://en.wikipedia.org/wiki/Webhook
|
||||||
|
|
||||||
|
use futures::{stream, Stream, StreamExt};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bot::Bot,
|
||||||
|
requests::Request,
|
||||||
|
types::{AllowedUpdate, Update},
|
||||||
|
RequestError,
|
||||||
|
};
|
||||||
|
use std::{convert::TryInto, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
/// A generic update listener.
|
||||||
|
pub trait UpdateListener<E>: Stream<Item = Result<Update, E>> {
|
||||||
|
// TODO: add some methods here (.shutdown(), etc).
|
||||||
|
}
|
||||||
|
impl<S, E> UpdateListener<E> for S where S: Stream<Item = Result<Update, E>> {}
|
||||||
|
|
||||||
|
/// Returns a long polling update listener with the default configuration.
|
||||||
|
///
|
||||||
|
/// See also: [`polling`](polling).
|
||||||
|
pub fn polling_default(bot: Arc<Bot>) -> impl UpdateListener<RequestError> {
|
||||||
|
polling(bot, None, None, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a long/short polling update listener with some additional options.
|
||||||
|
///
|
||||||
|
/// - `bot`: Using this bot, the returned update listener will receive updates.
|
||||||
|
/// - `timeout`: A timeout for polling.
|
||||||
|
/// - `limit`: Limits the number of updates to be retrieved at once. Values
|
||||||
|
/// between 1—100 are accepted.
|
||||||
|
/// - `allowed_updates`: A list the types of updates you want to receive.
|
||||||
|
/// See [`GetUpdates`] for defaults.
|
||||||
|
///
|
||||||
|
/// See also: [`polling_default`](polling_default).
|
||||||
|
///
|
||||||
|
/// [`GetUpdates`]: crate::requests::GetUpdates
|
||||||
|
pub fn polling(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
timeout: Option<Duration>,
|
||||||
|
limit: Option<u8>,
|
||||||
|
allowed_updates: Option<Vec<AllowedUpdate>>,
|
||||||
|
) -> impl UpdateListener<RequestError> {
|
||||||
|
let timeout =
|
||||||
|
timeout.map(|t| t.as_secs().try_into().expect("timeout is too big"));
|
||||||
|
|
||||||
|
stream::unfold(
|
||||||
|
(allowed_updates, bot, 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 updates = match req.send().await {
|
||||||
|
Err(err) => vec![Err(err)],
|
||||||
|
Ok(updates) => {
|
||||||
|
// Set offset to the last update's id + 1
|
||||||
|
if let Some(upd) = updates.last() {
|
||||||
|
let id: i32 = match upd {
|
||||||
|
Ok(ok) => ok.id,
|
||||||
|
Err((value, _)) => value["update_id"]
|
||||||
|
.as_i64()
|
||||||
|
.expect(
|
||||||
|
"The 'update_id' field must always exist in \
|
||||||
|
Update",
|
||||||
|
)
|
||||||
|
.try_into()
|
||||||
|
.expect("update_id must be i32"),
|
||||||
|
};
|
||||||
|
|
||||||
|
offset = id + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updates = updates
|
||||||
|
.into_iter()
|
||||||
|
.filter(|update| match update {
|
||||||
|
Err((value, error)) => {
|
||||||
|
log::error!("Cannot parse an update.\nError: {:?}\nValue: {}\n\
|
||||||
|
This is a bug in teloxide, please open an issue here: \
|
||||||
|
https://github.com/teloxide/teloxide/issues.", error, value);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Ok(_) => true,
|
||||||
|
})
|
||||||
|
.map(|update| {
|
||||||
|
update.expect("See the previous .filter() call")
|
||||||
|
})
|
||||||
|
.collect::<Vec<Update>>();
|
||||||
|
|
||||||
|
updates.into_iter().map(Ok).collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((stream::iter(updates), (allowed_updates, bot, offset)))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO implement webhook (this actually require webserver and probably we
|
||||||
|
// should add cargo feature that adds webhook)
|
||||||
|
//pub fn webhook<'a>(bot: &'a cfg: WebhookConfig) -> Updater<impl
|
||||||
|
// Stream<Item=Result<Update, ???>> + 'a> {}
|
519
src/errors.rs
Normal file
519
src/errors.rs
Normal file
|
@ -0,0 +1,519 @@
|
||||||
|
use derive_more::From;
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
//<editor-fold desc="download">
|
||||||
|
/// An error occurred after 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),
|
||||||
|
}
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
|
//<editor-fold desc="request">
|
||||||
|
/// An error occurred after making 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),
|
||||||
|
}
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
|
/// A kind of an API error returned from Telegram.
|
||||||
|
#[derive(Debug, Deserialize, PartialEq, Copy, Hash, Eq, Clone)]
|
||||||
|
pub enum ApiErrorKind {
|
||||||
|
/// 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,
|
||||||
|
|
||||||
|
#[serde(other)]
|
||||||
|
Other,
|
||||||
|
}
|
301
src/lib.rs
301
src/lib.rs
|
@ -1,6 +1,299 @@
|
||||||
#![feature(async_await)]
|
//! A full-featured framework that empowers you to easily build [Telegram bots]
|
||||||
|
//! using the [`async`/`.await`] syntax in [Rust]. It handles all the difficult
|
||||||
|
//! stuff so you can focus only on your business logic.
|
||||||
|
//!
|
||||||
|
//! ## Features
|
||||||
|
//! - **Type-safe.** teloxide leverages the Rust's type system with two serious
|
||||||
|
//! implications: resistance to human mistakes and tight integration with
|
||||||
|
//! IDEs. Write fast, avoid debugging as possible.
|
||||||
|
//!
|
||||||
|
//! - **Persistency.** By default, teloxide stores all user dialogues in RAM,
|
||||||
|
//! but you can store them somewhere else (for example, in DB) just by
|
||||||
|
//! implementing 2 functions.
|
||||||
|
//!
|
||||||
|
//! - **Convenient dialogues system.** Define a type-safe [finite automaton]
|
||||||
|
//! and transition functions to drive a user dialogue with ease (see the
|
||||||
|
//! examples below).
|
||||||
|
//!
|
||||||
|
//! - **Convenient API.** Automatic conversions are used to avoid boilerplate.
|
||||||
|
//! For example, functions accept `Into<String>`, rather than `&str` or
|
||||||
|
//! `String`, so you can call them without `.to_string()`/`.as_str()`/etc.
|
||||||
|
//!
|
||||||
|
//! ## Getting started
|
||||||
|
//! 1. Create a new bot using [@Botfather] to get a token in the format
|
||||||
|
//! `123456789:blablabla`. 2. Initialise the `TELOXIDE_TOKEN` environmental
|
||||||
|
//! variable to your token:
|
||||||
|
//! ```bash
|
||||||
|
//! # Unix
|
||||||
|
//! $ export TELOXIDE_TOKEN=MyAwesomeToken
|
||||||
|
//!
|
||||||
|
//! # Windows
|
||||||
|
//! $ set TELOXITE_TOKEN=MyAwesomeToken
|
||||||
|
//! ```
|
||||||
|
//! 3. Be sure that you are up to date:
|
||||||
|
//! ```bash
|
||||||
|
//! $ rustup update stable
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! 4. Execute `cargo new my_bot`, enter the directory and put these lines into
|
||||||
|
//! your `Cargo.toml`:
|
||||||
|
//! ```toml
|
||||||
|
//! [dependencies]
|
||||||
|
//! teloxide = "0.1.0"
|
||||||
|
//! log = "0.4.8"
|
||||||
|
//! tokio = "0.2.11"
|
||||||
|
//! pretty_env_logger = "0.4.0"
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## The ping-pong bot
|
||||||
|
//! This bot has a single message handler, which answers "pong" to each incoming
|
||||||
|
//! message:
|
||||||
|
//!
|
||||||
|
//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/ping_pong_bot/src/main.rs))
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use teloxide::prelude::*;
|
||||||
|
//!
|
||||||
|
//! # #[tokio::main]
|
||||||
|
//! # async fn main() {
|
||||||
|
//! teloxide::enable_logging!();
|
||||||
|
//! log::info!("Starting the ping-pong bot!");
|
||||||
|
//!
|
||||||
|
//! let bot = Bot::from_env();
|
||||||
|
//!
|
||||||
|
//! Dispatcher::<RequestError>::new(bot)
|
||||||
|
//! .message_handler(&|ctx: DispatcherHandlerCtx<Message>| async move {
|
||||||
|
//! ctx.answer("pong").send().await?;
|
||||||
|
//! Ok(())
|
||||||
|
//! })
|
||||||
|
//! .dispatch()
|
||||||
|
//! .await;
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Commands
|
||||||
|
//! Commands are defined similar to how we define CLI using [structopt]. This
|
||||||
|
//! bot says "I am a cat! Meow!" on `/meow`, generates a random number within
|
||||||
|
//! [0; 1) on `/generate`, and shows the usage guide on `/help`:
|
||||||
|
//!
|
||||||
|
//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/simple_commands_bot/src/main.rs))
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! # use teloxide::{prelude::*, utils::command::BotCommand};
|
||||||
|
//! # use rand::{thread_rng, Rng};
|
||||||
|
//! // Imports are omitted...
|
||||||
|
//!
|
||||||
|
//! #[derive(BotCommand)]
|
||||||
|
//! #[command(
|
||||||
|
//! rename = "lowercase",
|
||||||
|
//! description = "These commands are supported:"
|
||||||
|
//! )]
|
||||||
|
//! enum Command {
|
||||||
|
//! #[command(description = "display this text.")]
|
||||||
|
//! Help,
|
||||||
|
//! #[command(description = "be a cat.")]
|
||||||
|
//! Meow,
|
||||||
|
//! #[command(description = "generate a random number within [0; 1).")]
|
||||||
|
//! Generate,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn handle_command(
|
||||||
|
//! ctx: DispatcherHandlerCtx<Message>,
|
||||||
|
//! ) -> Result<(), RequestError> {
|
||||||
|
//! let text = match ctx.update.text() {
|
||||||
|
//! Some(text) => text,
|
||||||
|
//! None => {
|
||||||
|
//! log::info!("Received a message, but not text.");
|
||||||
|
//! return Ok(());
|
||||||
|
//! }
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! let command = match Command::parse(text) {
|
||||||
|
//! Some((command, _)) => command,
|
||||||
|
//! None => {
|
||||||
|
//! log::info!("Received a text message, but not a command.");
|
||||||
|
//! return Ok(());
|
||||||
|
//! }
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! match command {
|
||||||
|
//! Command::Help => ctx.answer(Command::descriptions()).send().await?,
|
||||||
|
//! Command::Generate => {
|
||||||
|
//! ctx.answer(thread_rng().gen_range(0.0, 1.0).to_string())
|
||||||
|
//! .send()
|
||||||
|
//! .await?
|
||||||
|
//! }
|
||||||
|
//! Command::Meow => ctx.answer("I am a cat! Meow!").send().await?,
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! async fn main() {
|
||||||
|
//! // Setup is omitted...
|
||||||
|
//! # teloxide::enable_logging!();
|
||||||
|
//! # log::info!("Starting simple_commands_bot!");
|
||||||
|
//! #
|
||||||
|
//! # let bot = Bot::from_env();
|
||||||
|
//! #
|
||||||
|
//! # Dispatcher::<RequestError>::new(bot)
|
||||||
|
//! # .message_handler(&handle_command)
|
||||||
|
//! # .dispatch()
|
||||||
|
//! # .await;
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Guess a number
|
||||||
|
//! Wanna see more? This is a bot, which starts a game on each incoming message.
|
||||||
|
//! You must guess a number from 1 to 10 (inclusively):
|
||||||
|
//!
|
||||||
|
//! ([Full](https://github.com/teloxide/teloxide/blob/dev/examples/guess_a_number_bot/src/main.rs))
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! # #[macro_use]
|
||||||
|
//! # extern crate smart_default;
|
||||||
|
//! # use teloxide::prelude::*;
|
||||||
|
//! # use rand::{thread_rng, Rng};
|
||||||
|
//! // Imports are omitted...
|
||||||
|
//!
|
||||||
|
//! #[derive(SmartDefault)]
|
||||||
|
//! enum Dialogue {
|
||||||
|
//! #[default]
|
||||||
|
//! Start,
|
||||||
|
//! ReceiveAttempt(u8),
|
||||||
|
//! }
|
||||||
|
//! async fn handle_message(
|
||||||
|
//! ctx: DialogueHandlerCtx<Message, Dialogue>,
|
||||||
|
//! ) -> Result<DialogueStage<Dialogue>, RequestError> {
|
||||||
|
//! match ctx.dialogue {
|
||||||
|
//! Dialogue::Start => {
|
||||||
|
//! ctx.answer(
|
||||||
|
//! "Let's play a game! Guess a number from 1 to 10
|
||||||
|
//! (inclusively).",
|
||||||
|
//! )
|
||||||
|
//! .send()
|
||||||
|
//! .await?;
|
||||||
|
//! next(Dialogue::ReceiveAttempt(thread_rng().gen_range(1, 11)))
|
||||||
|
//! }
|
||||||
|
//! Dialogue::ReceiveAttempt(secret) => match ctx.update.text() {
|
||||||
|
//! None => {
|
||||||
|
//! ctx.answer("Oh, please, send me a text message!")
|
||||||
|
//! .send()
|
||||||
|
//! .await?;
|
||||||
|
//! next(ctx.dialogue)
|
||||||
|
//! }
|
||||||
|
//! Some(text) => match text.parse::<u8>() {
|
||||||
|
//! Ok(attempt) => match attempt {
|
||||||
|
//! x if !(1..=10).contains(&x) => {
|
||||||
|
//! ctx.answer(
|
||||||
|
//! "Oh, please, send me a number in the range \
|
||||||
|
//! [1; 10]!",
|
||||||
|
//! )
|
||||||
|
//! .send()
|
||||||
|
//! .await?;
|
||||||
|
//! next(ctx.dialogue)
|
||||||
|
//! }
|
||||||
|
//! x if x == secret => {
|
||||||
|
//! ctx.answer("Congratulations! You won!")
|
||||||
|
//! .send()
|
||||||
|
//! .await?;
|
||||||
|
//! exit()
|
||||||
|
//! }
|
||||||
|
//! _ => {
|
||||||
|
//! ctx.answer("No.").send().await?;
|
||||||
|
//! next(ctx.dialogue)
|
||||||
|
//! }
|
||||||
|
//! },
|
||||||
|
//! Err(_) => {
|
||||||
|
//! ctx.answer(
|
||||||
|
//! "Oh, please, send me a number in the range [1; \
|
||||||
|
//! 10]!",
|
||||||
|
//! )
|
||||||
|
//! .send()
|
||||||
|
//! .await?;
|
||||||
|
//! next(ctx.dialogue)
|
||||||
|
//! }
|
||||||
|
//! },
|
||||||
|
//! },
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! async fn main() {
|
||||||
|
//! # teloxide::enable_logging!();
|
||||||
|
//! # log::info!("Starting guess_a_number_bot!");
|
||||||
|
//! # let bot = Bot::from_env();
|
||||||
|
//! // Setup is omitted...
|
||||||
|
//!
|
||||||
|
//! Dispatcher::new(bot)
|
||||||
|
//! .message_handler(&DialogueDispatcher::new(|ctx| async move {
|
||||||
|
//! handle_message(ctx)
|
||||||
|
//! .await
|
||||||
|
//! .expect("Something wrong with the bot!")
|
||||||
|
//! }))
|
||||||
|
//! .dispatch()
|
||||||
|
//! .await;
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Our [finite automaton], designating a user dialogue, cannot be in an invalid
|
||||||
|
//! state. See [examples/dialogue_bot] to see a bit more complicated bot with
|
||||||
|
//! dialogues.
|
||||||
|
//!
|
||||||
|
//! [See more examples](https://github.com/teloxide/teloxide/tree/dev/examples).
|
||||||
|
//!
|
||||||
|
//! ## Recommendations
|
||||||
|
//!
|
||||||
|
//! - Use this pattern:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! async fn main() {
|
||||||
|
//! run().await;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn run() {
|
||||||
|
//! // Your logic here...
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Instead of this:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! async fn main() {
|
||||||
|
//! // Your logic here...
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The second one produces very strange compiler messages because of the
|
||||||
|
//! `#[tokio::main]` macro. However, the examples above use the second one for
|
||||||
|
//! brevity.
|
||||||
|
//!
|
||||||
|
//! [Telegram bots]: https://telegram.org/blog/bot-revolution
|
||||||
|
//! [`async`/`.await`]: https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html
|
||||||
|
//! [Rust]: https://www.rust-lang.org/
|
||||||
|
//! [finite automaton]: https://en.wikipedia.org/wiki/Finite-state_machine
|
||||||
|
//! [examples/dialogue_bot]: https://github.com/teloxide/teloxide/blob/dev/examples/dialogue_bot/src/main.rs
|
||||||
|
//! [structopt]: https://docs.rs/structopt/0.3.9/structopt/
|
||||||
|
//! [@Botfather]: https://t.me/botfather
|
||||||
|
|
||||||
#[macro_use]
|
#![doc(
|
||||||
extern crate lazy_static;
|
html_logo_url = "https://github.com/teloxide/teloxide/raw/dev/logo.svg",
|
||||||
|
html_favicon_url = "https://github.com/teloxide/teloxide/raw/dev/ICON.png"
|
||||||
|
)]
|
||||||
|
#![allow(clippy::match_bool)]
|
||||||
|
|
||||||
mod core;
|
pub use bot::Bot;
|
||||||
|
pub use errors::{ApiErrorKind, DownloadError, RequestError};
|
||||||
|
|
||||||
|
mod errors;
|
||||||
|
mod net;
|
||||||
|
|
||||||
|
mod bot;
|
||||||
|
pub mod dispatching;
|
||||||
|
mod logging;
|
||||||
|
pub mod prelude;
|
||||||
|
pub mod requests;
|
||||||
|
pub mod types;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
extern crate teloxide_macros;
|
||||||
|
|
52
src/logging.rs
Normal file
52
src/logging.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/// Enables logging through [pretty-env-logger].
|
||||||
|
///
|
||||||
|
/// A logger will **only** print errors from teloxide and **all** logs from
|
||||||
|
/// your program.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```no_compile
|
||||||
|
/// teloxide::enable_logging!();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// Calling this macro **is not mandatory**; you can setup if your own logger if
|
||||||
|
/// you want.
|
||||||
|
///
|
||||||
|
/// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! enable_logging {
|
||||||
|
() => {
|
||||||
|
teloxide::enable_logging_with_filter!(log::LevelFilter::Trace);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables logging through [pretty-env-logger] with a custom filter for your
|
||||||
|
/// program.
|
||||||
|
///
|
||||||
|
/// A logger will **only** print errors from teloxide and restrict logs from
|
||||||
|
/// your program by the specified filter.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// Allow printing all logs from your program up to [`LevelFilter::Debug`] (i.e.
|
||||||
|
/// do not print traces):
|
||||||
|
///
|
||||||
|
/// ```no_compile
|
||||||
|
/// teloxide::enable_logging_with_filter!(log::LevelFilter::Debug);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// Calling this macro **is not mandatory**; you can setup if your own logger if
|
||||||
|
/// you want.
|
||||||
|
///
|
||||||
|
/// [pretty-env-logger]: https://crates.io/crates/pretty_env_logger
|
||||||
|
/// [`LevelFilter::Debug`]: https://docs.rs/log/0.4.10/log/enum.LevelFilter.html
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! enable_logging_with_filter {
|
||||||
|
($filter:expr) => {
|
||||||
|
pretty_env_logger::formatted_builder()
|
||||||
|
.write_style(pretty_env_logger::env_logger::WriteStyle::Auto)
|
||||||
|
.filter(Some(env!("CARGO_PKG_NAME")), $filter)
|
||||||
|
.filter(Some("teloxide"), log::LevelFilter::Error)
|
||||||
|
.init();
|
||||||
|
};
|
||||||
|
}
|
49
src/net/download.rs
Normal file
49
src/net/download.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
71
src/net/mod.rs
Normal file
71
src/net/mod.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#[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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
56
src/net/request.rs
Normal file
56
src/net/request.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use reqwest::{multipart::Form, Client, Response};
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
|
use crate::{requests::ResponseResult, RequestError};
|
||||||
|
|
||||||
|
use super::{TelegramResponse, TELEGRAM_API_URL};
|
||||||
|
|
||||||
|
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,
|
||||||
|
{
|
||||||
|
serde_json::from_str::<TelegramResponse<T>>(
|
||||||
|
&response.text().await.map_err(RequestError::NetworkError)?,
|
||||||
|
)
|
||||||
|
.map_err(RequestError::InvalidJson)?
|
||||||
|
.into()
|
||||||
|
}
|
77
src/net/telegram_response.rs
Normal file
77
src/net/telegram_response.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
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::types::Update;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn terminated_by_other_get_updates() {
|
||||||
|
let expected = ApiErrorKind::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!("Этой херни здесь не должно быть");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/prelude.rs
Normal file
14
src/prelude.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
//! Commonly used items.
|
||||||
|
|
||||||
|
pub use crate::{
|
||||||
|
dispatching::{
|
||||||
|
dialogue::{
|
||||||
|
exit, next, DialogueDispatcher, DialogueHandlerCtx, DialogueStage,
|
||||||
|
GetChatId,
|
||||||
|
},
|
||||||
|
Dispatcher, DispatcherHandlerCtx, DispatcherHandlerResult,
|
||||||
|
},
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{Message, Update},
|
||||||
|
Bot, RequestError,
|
||||||
|
};
|
119
src/requests/all/add_sticker_to_set.rs
Normal file
119
src/requests/all/add_sticker_to_set.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::form_builder::FormBuilder,
|
||||||
|
types::{InputFile, MaskPosition, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::requests::{Request, ResponseResult};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<Bot>,
|
||||||
|
user_id: i32,
|
||||||
|
name: String,
|
||||||
|
png_sticker: InputFile,
|
||||||
|
emojis: String,
|
||||||
|
mask_position: Option<MaskPosition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for AddStickerToSet {
|
||||||
|
type Output = True;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<True> {
|
||||||
|
net::request_multipart(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"addStickerToSet",
|
||||||
|
FormBuilder::new()
|
||||||
|
.add("user_id", &self.user_id)
|
||||||
|
.await
|
||||||
|
.add("name", &self.name)
|
||||||
|
.await
|
||||||
|
.add("png_sticker", &self.png_sticker)
|
||||||
|
.await
|
||||||
|
.add("emojis", &self.emojis)
|
||||||
|
.await
|
||||||
|
.add("mask_position", &self.mask_position)
|
||||||
|
.await
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddStickerToSet {
|
||||||
|
pub(crate) fn new<N, E>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
user_id: i32,
|
||||||
|
name: N,
|
||||||
|
png_sticker: InputFile,
|
||||||
|
emojis: E,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
N: Into<String>,
|
||||||
|
E: Into<String>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
user_id,
|
||||||
|
name: name.into(),
|
||||||
|
png_sticker,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **Png** image with the sticker, must be up to 512 kilobytes in size,
|
||||||
|
/// dimensions must not exceed 512px, and either width or height must be
|
||||||
|
/// exactly 512px.
|
||||||
|
///
|
||||||
|
/// Pass [`InputFile::File`] to send a file that exists on
|
||||||
|
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
|
||||||
|
/// Telegram to get a .webp file from the Internet, or upload a new one
|
||||||
|
/// using [`InputFile::FileId`]. [More info on Sending Files »].
|
||||||
|
///
|
||||||
|
/// [`InputFile::File`]: crate::types::InputFile::File
|
||||||
|
/// [`InputFile::Url`]: crate::types::InputFile::Url
|
||||||
|
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
|
||||||
|
pub fn png_sticker(mut self, val: InputFile) -> Self {
|
||||||
|
self.png_sticker = 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
|
||||||
|
}
|
||||||
|
}
|
115
src/requests/all/answer_callback_query.rs
Normal file
115
src/requests/all/answer_callback_query.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::True,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
157
src/requests/all/answer_inline_query.rs
Normal file
157
src/requests/all/answer_inline_query.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{InlineQueryResult, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
94
src/requests/all/answer_pre_checkout_query.rs
Normal file
94
src/requests/all/answer_pre_checkout_query.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::True,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
99
src/requests/all/answer_shipping_query.rs
Normal file
99
src/requests/all/answer_shipping_query.rs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ShippingOption, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
148
src/requests/all/create_new_sticker_set.rs
Normal file
148
src/requests/all/create_new_sticker_set.rs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||||
|
types::{InputFile, MaskPosition, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<Bot>,
|
||||||
|
user_id: i32,
|
||||||
|
name: String,
|
||||||
|
title: String,
|
||||||
|
png_sticker: InputFile,
|
||||||
|
emojis: String,
|
||||||
|
contains_masks: Option<bool>,
|
||||||
|
mask_position: Option<MaskPosition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for CreateNewStickerSet {
|
||||||
|
type Output = True;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<True> {
|
||||||
|
net::request_multipart(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"createNewStickerSet",
|
||||||
|
FormBuilder::new()
|
||||||
|
.add("user_id", &self.user_id)
|
||||||
|
.await
|
||||||
|
.add("name", &self.name)
|
||||||
|
.await
|
||||||
|
.add("title", &self.title)
|
||||||
|
.await
|
||||||
|
.add("png_sticker", &self.png_sticker)
|
||||||
|
.await
|
||||||
|
.add("emojis", &self.emojis)
|
||||||
|
.await
|
||||||
|
.add("contains_masks", &self.contains_masks)
|
||||||
|
.await
|
||||||
|
.add("mask_position", &self.mask_position)
|
||||||
|
.await
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateNewStickerSet {
|
||||||
|
pub(crate) fn new<N, T, E>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
user_id: i32,
|
||||||
|
name: N,
|
||||||
|
title: T,
|
||||||
|
png_sticker: InputFile,
|
||||||
|
emojis: E,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
N: Into<String>,
|
||||||
|
T: Into<String>,
|
||||||
|
E: Into<String>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
user_id,
|
||||||
|
name: name.into(),
|
||||||
|
title: title.into(),
|
||||||
|
png_sticker,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **Png** image with the sticker, must be up to 512 kilobytes in size,
|
||||||
|
/// dimensions must not exceed 512px, and either width or height must be
|
||||||
|
/// exactly 512px.
|
||||||
|
///
|
||||||
|
/// Pass [`InputFile::File`] to send a file that exists on
|
||||||
|
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
|
||||||
|
/// Telegram to get a .webp file from the Internet, or upload a new one
|
||||||
|
/// using [`InputFile::FileId`]. [More info on Sending Files »].
|
||||||
|
///
|
||||||
|
/// [`InputFile::File`]: crate::types::InputFile::File
|
||||||
|
/// [`InputFile::Url`]: crate::types::InputFile::Url
|
||||||
|
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
|
||||||
|
pub fn png_sticker(mut self, val: InputFile) -> Self {
|
||||||
|
self.png_sticker = 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
|
||||||
|
}
|
||||||
|
}
|
57
src/requests/all/delete_chat_photo.rs
Normal file
57
src/requests/all/delete_chat_photo.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
62
src/requests/all/delete_chat_sticker_set.rs
Normal file
62
src/requests/all/delete_chat_sticker_set.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
78
src/requests/all/delete_message.rs
Normal file
78
src/requests/all/delete_message.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
54
src/requests/all/delete_sticker_from_set.rs
Normal file
54
src/requests/all/delete_sticker_from_set.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::True,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
44
src/requests/all/delete_webhook.rs
Normal file
44
src/requests/all/delete_webhook.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::True,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<Bot>) -> Self {
|
||||||
|
Self { bot }
|
||||||
|
}
|
||||||
|
}
|
94
src/requests/all/edit_message_caption.rs
Normal file
94
src/requests/all/edit_message_caption.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
89
src/requests/all/edit_message_live_location.rs
Normal file
89
src/requests/all/edit_message_live_location.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
102
src/requests/all/edit_message_media.rs
Normal file
102
src/requests/all/edit_message_media.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||||
|
types::{ChatOrInlineMessage, InlineKeyboardMarkup, InputMedia, Message},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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("chat_id", chat_id)
|
||||||
|
.await
|
||||||
|
.add("message_id", message_id)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ChatOrInlineMessage::Inline { inline_message_id } => {
|
||||||
|
params =
|
||||||
|
params.add("inline_message_id", inline_message_id).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
net::request_multipart(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"editMessageMedia",
|
||||||
|
params
|
||||||
|
.add("media", &self.media)
|
||||||
|
.await
|
||||||
|
.add("reply_markup", &self.reply_markup)
|
||||||
|
.await
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditMessageMedia {
|
||||||
|
pub(crate) fn new(
|
||||||
|
bot: Arc<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
|
||||||
|
}
|
||||||
|
}
|
69
src/requests/all/edit_message_reply_markup.rs
Normal file
69
src/requests/all/edit_message_reply_markup.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
105
src/requests/all/edit_message_text.rs
Normal file
105
src/requests/all/edit_message_text.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
72
src/requests/all/export_chat_invite_link.rs
Normal file
72
src/requests/all/export_chat_invite_link.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::ChatId,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
99
src/requests/all/forward_message.rs
Normal file
99
src/requests/all/forward_message.rs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, Message},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
52
src/requests/all/get_chat.rs
Normal file
52
src/requests/all/get_chat.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{Chat, ChatId},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
60
src/requests/all/get_chat_administrators.rs
Normal file
60
src/requests/all/get_chat_administrators.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, ChatMember},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
66
src/requests/all/get_chat_member.rs
Normal file
66
src/requests/all/get_chat_member.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, ChatMember},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
55
src/requests/all/get_chat_members_count.rs
Normal file
55
src/requests/all/get_chat_members_count.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::ChatId,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
67
src/requests/all/get_file.rs
Normal file
67
src/requests/all/get_file.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::File,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
71
src/requests/all/get_game_high_scores.rs
Normal file
71
src/requests/all/get_game_high_scores.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatOrInlineMessage, GameHighScore},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
35
src/requests/all/get_me.rs
Normal file
35
src/requests/all/get_me.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::Me,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<Bot>) -> Self {
|
||||||
|
Self { bot }
|
||||||
|
}
|
||||||
|
}
|
54
src/requests/all/get_sticker_set.rs
Normal file
54
src/requests/all/get_sticker_set.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::StickerSet,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// 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: Arc<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: Arc<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
|
||||||
|
}
|
||||||
|
}
|
139
src/requests/all/get_updates.rs
Normal file
139
src/requests/all/get_updates.rs
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{AllowedUpdate, Update},
|
||||||
|
Bot, RequestError,
|
||||||
|
};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to receive incoming updates using long polling ([wiki]).
|
||||||
|
///
|
||||||
|
/// **Notes:**
|
||||||
|
/// 1. This method will not work if an outgoing webhook is set up.
|
||||||
|
/// 2. In order to avoid getting duplicate updates,
|
||||||
|
/// recalculate offset after each server response.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#getupdates).
|
||||||
|
///
|
||||||
|
/// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct GetUpdates {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
pub(crate) offset: Option<i32>,
|
||||||
|
pub(crate) limit: Option<u8>,
|
||||||
|
pub(crate) timeout: Option<u32>,
|
||||||
|
pub(crate) allowed_updates: Option<Vec<AllowedUpdate>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for GetUpdates {
|
||||||
|
type Output = Vec<Result<Update, (Value, serde_json::Error)>>;
|
||||||
|
|
||||||
|
/// Deserialize to `Vec<serde_json::Result<Update>>` instead of
|
||||||
|
/// `Vec<Update>`, because we want to parse the rest of updates even if our
|
||||||
|
/// library hasn't parsed one.
|
||||||
|
async fn send(
|
||||||
|
&self,
|
||||||
|
) -> ResponseResult<Vec<Result<Update, (Value, serde_json::Error)>>> {
|
||||||
|
let value: Value = net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"getUpdates",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Value::Array(array) => Ok(array
|
||||||
|
.into_iter()
|
||||||
|
.map(|value| {
|
||||||
|
serde_json::from_str(&value.to_string())
|
||||||
|
.map_err(|error| (value, error))
|
||||||
|
})
|
||||||
|
.collect()),
|
||||||
|
_ => Err(RequestError::InvalidJson(
|
||||||
|
serde_json::from_value::<Vec<Update>>(value)
|
||||||
|
.expect_err("get_update must return Value::Array"),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetUpdates {
|
||||||
|
pub(crate) fn new(bot: Arc<Bot>) -> Self {
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
offset: None,
|
||||||
|
limit: None,
|
||||||
|
timeout: None,
|
||||||
|
allowed_updates: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Identifier of the first update to be returned.
|
||||||
|
///
|
||||||
|
/// Must be greater by one than the highest among the identifiers of
|
||||||
|
/// previously received updates. By default, updates starting with the
|
||||||
|
/// earliest unconfirmed update are returned. An update is considered
|
||||||
|
/// confirmed as soon as [`GetUpdates`] is called with an [`offset`]
|
||||||
|
/// higher than its [`id`]. The negative offset can be specified to
|
||||||
|
/// retrieve updates starting from `-offset` update from the end of the
|
||||||
|
/// updates queue. All previous updates will forgotten.
|
||||||
|
///
|
||||||
|
/// [`GetUpdates`]: self::GetUpdates
|
||||||
|
/// [`offset`]: self::GetUpdates::offset
|
||||||
|
/// [`id`]: crate::types::Update::id
|
||||||
|
pub fn offset(mut self, value: i32) -> Self {
|
||||||
|
self.offset = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Limits the number of updates to be retrieved.
|
||||||
|
///
|
||||||
|
/// Values between `1`—`100` are accepted. Defaults to `100`.
|
||||||
|
pub fn limit(mut self, value: u8) -> Self {
|
||||||
|
self.limit = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Timeout in seconds for long polling.
|
||||||
|
///
|
||||||
|
/// Defaults to `0`, i.e. usual short polling. Should be positive, short
|
||||||
|
/// polling should be used for testing purposes only.
|
||||||
|
pub fn timeout(mut self, value: u32) -> Self {
|
||||||
|
self.timeout = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List the types of updates you want your bot to receive.
|
||||||
|
///
|
||||||
|
/// For example, specify [[`Message`], [`EditedChannelPost`],
|
||||||
|
/// [`CallbackQuery`]] to only receive updates of these types.
|
||||||
|
/// See [`AllowedUpdate`] for a complete list of available update types.
|
||||||
|
///
|
||||||
|
/// Specify an empty list to receive all updates regardless of type
|
||||||
|
/// (default). If not specified, the previous setting will be used.
|
||||||
|
///
|
||||||
|
/// **Note:**
|
||||||
|
/// This parameter doesn't affect updates created before the call to the
|
||||||
|
/// [`Bot::get_updates`], so unwanted updates may be received for a short
|
||||||
|
/// period of time.
|
||||||
|
///
|
||||||
|
/// [`Message`]: self::AllowedUpdate::Message
|
||||||
|
/// [`EditedChannelPost`]: self::AllowedUpdate::EditedChannelPost
|
||||||
|
/// [`CallbackQuery`]: self::AllowedUpdate::CallbackQuery
|
||||||
|
/// [`AllowedUpdate`]: self::AllowedUpdate
|
||||||
|
/// [`Bot::get_updates`]: crate::Bot::get_updates
|
||||||
|
pub fn allowed_updates<T>(mut self, value: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<Vec<AllowedUpdate>>,
|
||||||
|
{
|
||||||
|
self.allowed_updates = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
70
src/requests/all/get_user_profile_photos.rs
Normal file
70
src/requests/all/get_user_profile_photos.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::UserProfilePhotos,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to get a list of profile pictures for a user.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#getuserprofilephotos).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct GetUserProfilePhotos {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
user_id: i32,
|
||||||
|
offset: Option<i32>,
|
||||||
|
limit: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for GetUserProfilePhotos {
|
||||||
|
type Output = UserProfilePhotos;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<UserProfilePhotos> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"getUserProfilePhotos",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetUserProfilePhotos {
|
||||||
|
pub(crate) fn new(bot: Arc<Bot>, user_id: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
user_id,
|
||||||
|
offset: None,
|
||||||
|
limit: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier of the target user.
|
||||||
|
pub fn user_id(mut self, val: i32) -> Self {
|
||||||
|
self.user_id = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sequential number of the first photo to be returned. By default, all
|
||||||
|
/// photos are returned.
|
||||||
|
pub fn offset(mut self, val: i32) -> Self {
|
||||||
|
self.offset = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Limits the number of photos to be retrieved. Values between 1—100 are
|
||||||
|
/// accepted.
|
||||||
|
///
|
||||||
|
/// Defaults to 100.
|
||||||
|
pub fn limit(mut self, val: i32) -> Self {
|
||||||
|
self.limit = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
45
src/requests/all/get_webhook_info.rs
Normal file
45
src/requests/all/get_webhook_info.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::WebhookInfo,
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to get current webhook status.
|
||||||
|
///
|
||||||
|
/// If the bot is using [`Bot::get_updates`], will return an object with the url
|
||||||
|
/// field empty.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo).
|
||||||
|
///
|
||||||
|
/// [`Bot::get_updates`]: crate::Bot::get_updates
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct GetWebhookInfo {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for GetWebhookInfo {
|
||||||
|
type Output = WebhookInfo;
|
||||||
|
|
||||||
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
async fn send(&self) -> ResponseResult<WebhookInfo> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"getWebhookInfo",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetWebhookInfo {
|
||||||
|
pub(crate) fn new(bot: Arc<Bot>) -> Self {
|
||||||
|
Self { bot }
|
||||||
|
}
|
||||||
|
}
|
84
src/requests/all/kick_chat_member.rs
Normal file
84
src/requests/all/kick_chat_member.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to kick a user from a group, a supergroup or a channel.
|
||||||
|
///
|
||||||
|
/// In the case of supergroups and channels, the user will not be able to return
|
||||||
|
/// to the group on their own using invite links, etc., unless [unbanned] first.
|
||||||
|
/// The bot must be an administrator in the chat for this to work and must have
|
||||||
|
/// the appropriate admin rights.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#kickchatmember).
|
||||||
|
///
|
||||||
|
/// [unbanned]: crate::Bot::unban_chat_member
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct KickChatMember {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
user_id: i32,
|
||||||
|
until_date: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for KickChatMember {
|
||||||
|
type Output = True;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<True> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"kickChatMember",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KickChatMember {
|
||||||
|
pub(crate) fn new<C>(bot: Arc<Bot>, chat_id: C, user_id: i32) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
let chat_id = chat_id.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
user_id,
|
||||||
|
until_date: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target group or username of the target
|
||||||
|
/// supergroup or channel (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier of the target user.
|
||||||
|
pub fn user_id(mut self, val: i32) -> Self {
|
||||||
|
self.user_id = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Date when the user will be unbanned, unix time.
|
||||||
|
///
|
||||||
|
/// If user is banned for more than 366 days or less than 30 seconds from
|
||||||
|
/// the current time they are considered to be banned forever.
|
||||||
|
pub fn until_date(mut self, val: i32) -> Self {
|
||||||
|
self.until_date = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
55
src/requests/all/leave_chat.rs
Normal file
55
src/requests/all/leave_chat.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method for your bot to leave a group, supergroup or channel.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#leavechat).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct LeaveChat {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for LeaveChat {
|
||||||
|
type Output = True;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<True> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"leaveChat",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LeaveChat {
|
||||||
|
pub(crate) fn new<C>(bot: Arc<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
|
||||||
|
}
|
||||||
|
}
|
132
src/requests/all/mod.rs
Normal file
132
src/requests/all/mod.rs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
mod add_sticker_to_set;
|
||||||
|
mod answer_callback_query;
|
||||||
|
mod answer_inline_query;
|
||||||
|
mod answer_pre_checkout_query;
|
||||||
|
mod answer_shipping_query;
|
||||||
|
mod create_new_sticker_set;
|
||||||
|
mod delete_chat_photo;
|
||||||
|
mod delete_chat_sticker_set;
|
||||||
|
mod delete_message;
|
||||||
|
mod delete_sticker_from_set;
|
||||||
|
mod delete_webhook;
|
||||||
|
mod edit_message_caption;
|
||||||
|
mod edit_message_live_location;
|
||||||
|
mod edit_message_media;
|
||||||
|
mod edit_message_reply_markup;
|
||||||
|
mod edit_message_text;
|
||||||
|
mod export_chat_invite_link;
|
||||||
|
mod forward_message;
|
||||||
|
mod get_chat;
|
||||||
|
mod get_chat_administrators;
|
||||||
|
mod get_chat_member;
|
||||||
|
mod get_chat_members_count;
|
||||||
|
mod get_file;
|
||||||
|
mod get_game_high_scores;
|
||||||
|
mod get_me;
|
||||||
|
mod get_sticker_set;
|
||||||
|
mod get_updates;
|
||||||
|
mod get_user_profile_photos;
|
||||||
|
mod get_webhook_info;
|
||||||
|
mod kick_chat_member;
|
||||||
|
mod leave_chat;
|
||||||
|
mod pin_chat_message;
|
||||||
|
mod promote_chat_member;
|
||||||
|
mod restrict_chat_member;
|
||||||
|
mod send_animation;
|
||||||
|
mod send_audio;
|
||||||
|
mod send_chat_action;
|
||||||
|
mod send_contact;
|
||||||
|
mod send_document;
|
||||||
|
mod send_game;
|
||||||
|
mod send_invoice;
|
||||||
|
mod send_location;
|
||||||
|
mod send_media_group;
|
||||||
|
mod send_message;
|
||||||
|
mod send_photo;
|
||||||
|
mod send_poll;
|
||||||
|
mod send_sticker;
|
||||||
|
mod send_venue;
|
||||||
|
mod send_video;
|
||||||
|
mod send_video_note;
|
||||||
|
mod send_voice;
|
||||||
|
mod set_chat_administrator_custom_title;
|
||||||
|
mod set_chat_description;
|
||||||
|
mod set_chat_permissions;
|
||||||
|
mod set_chat_photo;
|
||||||
|
mod set_chat_sticker_set;
|
||||||
|
mod set_chat_title;
|
||||||
|
mod set_game_score;
|
||||||
|
mod set_sticker_position_in_set;
|
||||||
|
mod set_webhook;
|
||||||
|
mod stop_message_live_location;
|
||||||
|
mod stop_poll;
|
||||||
|
mod unban_chat_member;
|
||||||
|
mod unpin_chat_message;
|
||||||
|
mod upload_sticker_file;
|
||||||
|
|
||||||
|
pub use add_sticker_to_set::*;
|
||||||
|
pub use answer_callback_query::*;
|
||||||
|
pub use answer_inline_query::*;
|
||||||
|
pub use answer_pre_checkout_query::*;
|
||||||
|
pub use answer_shipping_query::*;
|
||||||
|
pub use create_new_sticker_set::*;
|
||||||
|
pub use delete_chat_photo::*;
|
||||||
|
pub use delete_chat_sticker_set::*;
|
||||||
|
pub use delete_message::*;
|
||||||
|
pub use delete_sticker_from_set::*;
|
||||||
|
pub use delete_webhook::*;
|
||||||
|
pub use edit_message_caption::*;
|
||||||
|
pub use edit_message_live_location::*;
|
||||||
|
pub use edit_message_media::*;
|
||||||
|
pub use edit_message_reply_markup::*;
|
||||||
|
pub use edit_message_text::*;
|
||||||
|
pub use export_chat_invite_link::*;
|
||||||
|
pub use forward_message::*;
|
||||||
|
pub use get_chat::*;
|
||||||
|
pub use get_chat_administrators::*;
|
||||||
|
pub use get_chat_member::*;
|
||||||
|
pub use get_chat_members_count::*;
|
||||||
|
pub use get_file::*;
|
||||||
|
pub use get_game_high_scores::*;
|
||||||
|
pub use get_me::*;
|
||||||
|
pub use get_sticker_set::*;
|
||||||
|
pub use get_updates::*;
|
||||||
|
pub use get_user_profile_photos::*;
|
||||||
|
pub use get_webhook_info::*;
|
||||||
|
pub use kick_chat_member::*;
|
||||||
|
pub use leave_chat::*;
|
||||||
|
pub use pin_chat_message::*;
|
||||||
|
pub use promote_chat_member::*;
|
||||||
|
pub use restrict_chat_member::*;
|
||||||
|
pub use send_animation::*;
|
||||||
|
pub use send_audio::*;
|
||||||
|
pub use send_chat_action::*;
|
||||||
|
pub use send_contact::*;
|
||||||
|
pub use send_document::*;
|
||||||
|
pub use send_game::*;
|
||||||
|
pub use send_invoice::*;
|
||||||
|
pub use send_location::*;
|
||||||
|
pub use send_media_group::*;
|
||||||
|
pub use send_message::*;
|
||||||
|
pub use send_photo::*;
|
||||||
|
pub use send_poll::*;
|
||||||
|
pub use send_sticker::*;
|
||||||
|
pub use send_venue::*;
|
||||||
|
pub use send_video::*;
|
||||||
|
pub use send_video_note::*;
|
||||||
|
pub use send_voice::*;
|
||||||
|
pub use set_chat_administrator_custom_title::*;
|
||||||
|
pub use set_chat_description::*;
|
||||||
|
pub use set_chat_permissions::*;
|
||||||
|
pub use set_chat_photo::*;
|
||||||
|
pub use set_chat_sticker_set::*;
|
||||||
|
pub use set_chat_title::*;
|
||||||
|
pub use set_game_score::*;
|
||||||
|
pub use set_sticker_position_in_set::*;
|
||||||
|
pub use set_webhook::*;
|
||||||
|
pub use std::pin::Pin;
|
||||||
|
pub use stop_message_live_location::*;
|
||||||
|
pub use stop_poll::*;
|
||||||
|
pub use unban_chat_member::*;
|
||||||
|
pub use unpin_chat_message::*;
|
||||||
|
pub use upload_sticker_file::*;
|
81
src/requests/all/pin_chat_message.rs
Normal file
81
src/requests/all/pin_chat_message.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to pin a message in a group, a supergroup, or a channel.
|
||||||
|
///
|
||||||
|
/// The bot must be an administrator in the chat for this to work and must have
|
||||||
|
/// the `can_pin_messages` admin right in the supergroup or `can_edit_messages`
|
||||||
|
/// admin right in the channel.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#pinchatmessage).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct PinChatMessage {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
message_id: i32,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for PinChatMessage {
|
||||||
|
type Output = True;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<True> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"pinChatMessage",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinChatMessage {
|
||||||
|
pub(crate) fn new<C>(bot: Arc<Bot>, chat_id: C, message_id: i32) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
let chat_id = chat_id.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
message_id,
|
||||||
|
disable_notification: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Identifier of a message to pin.
|
||||||
|
pub fn message_id(mut self, val: i32) -> Self {
|
||||||
|
self.message_id = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if it is not necessary to send a notification to all chat
|
||||||
|
/// members about the new pinned message.
|
||||||
|
///
|
||||||
|
/// Notifications are always disabled in channels.
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
141
src/requests/all/promote_chat_member.rs
Normal file
141
src/requests/all/promote_chat_member.rs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to promote or demote a user in a supergroup or a channel.
|
||||||
|
///
|
||||||
|
/// The bot must be an administrator in the chat for this to work and must have
|
||||||
|
/// the appropriate admin rights. Pass False for all boolean parameters to
|
||||||
|
/// demote a user.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#promotechatmember).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct PromoteChatMember {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
user_id: i32,
|
||||||
|
can_change_info: Option<bool>,
|
||||||
|
can_post_messages: Option<bool>,
|
||||||
|
can_edit_messages: Option<bool>,
|
||||||
|
can_delete_messages: Option<bool>,
|
||||||
|
can_invite_users: Option<bool>,
|
||||||
|
can_restrict_members: Option<bool>,
|
||||||
|
can_pin_messages: Option<bool>,
|
||||||
|
can_promote_members: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for PromoteChatMember {
|
||||||
|
type Output = True;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<True> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"promoteChatMember",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PromoteChatMember {
|
||||||
|
pub(crate) fn new<C>(bot: Arc<Bot>, chat_id: C, user_id: i32) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
let chat_id = chat_id.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
user_id,
|
||||||
|
can_change_info: None,
|
||||||
|
can_post_messages: None,
|
||||||
|
can_edit_messages: None,
|
||||||
|
can_delete_messages: None,
|
||||||
|
can_invite_users: None,
|
||||||
|
can_restrict_members: None,
|
||||||
|
can_pin_messages: None,
|
||||||
|
can_promote_members: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier of the target user.
|
||||||
|
pub fn user_id(mut self, val: i32) -> Self {
|
||||||
|
self.user_id = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the administrator can change chat title, photo and other
|
||||||
|
/// settings.
|
||||||
|
pub fn can_change_info(mut self, val: bool) -> Self {
|
||||||
|
self.can_change_info = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the administrator can create channel posts, channels
|
||||||
|
/// only.
|
||||||
|
pub fn can_post_messages(mut self, val: bool) -> Self {
|
||||||
|
self.can_post_messages = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the administrator can edit messages of other users and
|
||||||
|
/// can pin messages, channels only.
|
||||||
|
pub fn can_edit_messages(mut self, val: bool) -> Self {
|
||||||
|
self.can_edit_messages = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the administrator can delete messages of other users.
|
||||||
|
pub fn can_delete_messages(mut self, val: bool) -> Self {
|
||||||
|
self.can_delete_messages = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the administrator can invite new users to the chat.
|
||||||
|
pub fn can_invite_users(mut self, val: bool) -> Self {
|
||||||
|
self.can_invite_users = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the administrator can restrict, ban or unban chat
|
||||||
|
/// members.
|
||||||
|
pub fn can_restrict_members(mut self, val: bool) -> Self {
|
||||||
|
self.can_restrict_members = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the administrator can pin messages, supergroups only.
|
||||||
|
pub fn can_pin_messages(mut self, val: bool) -> Self {
|
||||||
|
self.can_pin_messages = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the administrator can add new administrators with a
|
||||||
|
/// subset of his own privileges or demote administrators that he has
|
||||||
|
/// promoted, directly or indirectly (promoted by administrators that were
|
||||||
|
/// appointed by him).
|
||||||
|
pub fn can_promote_members(mut self, val: bool) -> Self {
|
||||||
|
self.can_promote_members = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
94
src/requests/all/restrict_chat_member.rs
Normal file
94
src/requests/all/restrict_chat_member.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, ChatPermissions, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to restrict a user in a supergroup.
|
||||||
|
///
|
||||||
|
/// The bot must be an administrator in the supergroup for this to work and must
|
||||||
|
/// have the appropriate admin rights. Pass `true` for all permissions to lift
|
||||||
|
/// restrictions from a user.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#restrictchatmember).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct RestrictChatMember {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
user_id: i32,
|
||||||
|
permissions: ChatPermissions,
|
||||||
|
until_date: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for RestrictChatMember {
|
||||||
|
type Output = True;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<True> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"restrictChatMember",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RestrictChatMember {
|
||||||
|
pub(crate) fn new<C>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: C,
|
||||||
|
user_id: i32,
|
||||||
|
permissions: ChatPermissions,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
let chat_id = chat_id.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
user_id,
|
||||||
|
permissions,
|
||||||
|
until_date: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target
|
||||||
|
/// supergroup (in the format `@supergroupusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier of the target user.
|
||||||
|
pub fn user_id(mut self, val: i32) -> Self {
|
||||||
|
self.user_id = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New user permissions.
|
||||||
|
pub fn permissions(mut self, val: ChatPermissions) -> Self {
|
||||||
|
self.permissions = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Date when restrictions will be lifted for the user, unix time.
|
||||||
|
///
|
||||||
|
/// If user is restricted for more than 366 days or less than 30 seconds
|
||||||
|
/// from the current time, they are considered to be restricted forever.
|
||||||
|
pub fn until_date(mut self, val: i32) -> Self {
|
||||||
|
self.until_date = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
188
src/requests/all/send_animation.rs
Normal file
188
src/requests/all/send_animation.rs
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||||
|
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send animation files (GIF or H.264/MPEG-4 AVC video
|
||||||
|
/// without sound).
|
||||||
|
///
|
||||||
|
/// Bots can currently send animation files of up to 50 MB in size, this limit
|
||||||
|
/// may be changed in the future.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendanimation).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SendAnimation {
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
pub chat_id: ChatId,
|
||||||
|
pub animation: InputFile,
|
||||||
|
pub duration: Option<u32>,
|
||||||
|
pub width: Option<u32>,
|
||||||
|
pub height: Option<u32>,
|
||||||
|
pub thumb: Option<InputFile>,
|
||||||
|
pub caption: Option<String>,
|
||||||
|
pub parse_mode: Option<ParseMode>,
|
||||||
|
pub disable_notification: Option<bool>,
|
||||||
|
pub reply_to_message_id: Option<i32>,
|
||||||
|
pub reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendAnimation {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_multipart(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendAnimation",
|
||||||
|
FormBuilder::new()
|
||||||
|
.add("chat_id", &self.chat_id)
|
||||||
|
.await
|
||||||
|
.add("animation", &self.animation)
|
||||||
|
.await
|
||||||
|
.add("duration", &self.duration)
|
||||||
|
.await
|
||||||
|
.add("width", &self.width)
|
||||||
|
.await
|
||||||
|
.add("height", &self.height)
|
||||||
|
.await
|
||||||
|
.add("thumb", &self.thumb)
|
||||||
|
.await
|
||||||
|
.add("caption", &self.caption)
|
||||||
|
.await
|
||||||
|
.add("parse_mode", &self.parse_mode)
|
||||||
|
.await
|
||||||
|
.add("disable_notification", &self.disable_notification)
|
||||||
|
.await
|
||||||
|
.add("reply_to_message_id", &self.reply_to_message_id)
|
||||||
|
.await
|
||||||
|
.add("reply_markup", &self.reply_markup)
|
||||||
|
.await
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendAnimation {
|
||||||
|
pub(crate) fn new<C>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: C,
|
||||||
|
animation: InputFile,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id: chat_id.into(),
|
||||||
|
animation,
|
||||||
|
duration: None,
|
||||||
|
width: None,
|
||||||
|
height: None,
|
||||||
|
thumb: None,
|
||||||
|
caption: None,
|
||||||
|
parse_mode: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, value: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = value.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animation to send.
|
||||||
|
pub fn animation(mut self, val: InputFile) -> Self {
|
||||||
|
self.animation = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Duration of sent animation in seconds.
|
||||||
|
pub fn duration(mut self, value: u32) -> Self {
|
||||||
|
self.duration = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animation width.
|
||||||
|
pub fn width(mut self, value: u32) -> Self {
|
||||||
|
self.width = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animation height.
|
||||||
|
pub fn height(mut self, value: u32) -> Self {
|
||||||
|
self.height = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thumbnail of the file sent; can be ignored if thumbnail generation for
|
||||||
|
/// the file is supported server-side.
|
||||||
|
///
|
||||||
|
/// The thumbnail should be in JPEG format and less than 200 kB in size. A
|
||||||
|
/// thumbnail‘s width and height should not exceed 320. Ignored if the
|
||||||
|
/// file is not uploaded using [`InputFile::File`]. Thumbnails can’t be
|
||||||
|
/// reused and can be only uploaded as a new file, with
|
||||||
|
/// [`InputFile::File`].
|
||||||
|
///
|
||||||
|
/// [`InputFile::File`]: crate::types::InputFile::File
|
||||||
|
pub fn thumb(mut self, value: InputFile) -> Self {
|
||||||
|
self.thumb = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animation caption, `0`-`1024` characters.
|
||||||
|
pub fn caption<T>(mut self, value: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.caption = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send [Markdown] or [HTML], if you want Telegram apps to show
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs] in the media caption.
|
||||||
|
///
|
||||||
|
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||||
|
/// [HTML]: crate::types::ParseMode::HTML
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs]:
|
||||||
|
/// crate::types::ParseMode
|
||||||
|
pub fn parse_mode(mut self, value: ParseMode) -> Self {
|
||||||
|
self.parse_mode = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message silently. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
pub fn disable_notification(mut self, value: bool) -> Self {
|
||||||
|
self.disable_notification = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, [id] of the original message.
|
||||||
|
///
|
||||||
|
/// [id]: crate::types::Message::id
|
||||||
|
pub fn reply_to_message_id(mut self, value: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional interface options.
|
||||||
|
pub fn reply_markup<T>(mut self, value: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ReplyMarkup>,
|
||||||
|
{
|
||||||
|
self.reply_markup = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
209
src/requests/all/send_audio.rs
Normal file
209
src/requests/all/send_audio.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||||
|
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send audio files, if you want Telegram clients to display
|
||||||
|
/// them in the music player.
|
||||||
|
///
|
||||||
|
/// Your audio must be in the .MP3 or .M4A format. Bots can currently send audio
|
||||||
|
/// files of up to 50 MB in size, this limit may be changed in the future.
|
||||||
|
///
|
||||||
|
/// For sending voice messages, use the [`Bot::send_voice`] method instead.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendaudio).
|
||||||
|
///
|
||||||
|
/// [`Bot::send_voice`]: crate::Bot::send_voice
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SendAudio {
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
audio: InputFile,
|
||||||
|
caption: Option<String>,
|
||||||
|
parse_mode: Option<ParseMode>,
|
||||||
|
duration: Option<i32>,
|
||||||
|
performer: Option<String>,
|
||||||
|
title: Option<String>,
|
||||||
|
thumb: Option<InputFile>,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendAudio {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_multipart(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendAudio",
|
||||||
|
FormBuilder::new()
|
||||||
|
.add("chat_id", &self.chat_id)
|
||||||
|
.await
|
||||||
|
.add("audio", &self.audio)
|
||||||
|
.await
|
||||||
|
.add("caption", &self.caption)
|
||||||
|
.await
|
||||||
|
.add("parse_mode", &self.parse_mode)
|
||||||
|
.await
|
||||||
|
.add("duration", &self.duration)
|
||||||
|
.await
|
||||||
|
.add("performer", &self.performer)
|
||||||
|
.await
|
||||||
|
.add("title", &self.title)
|
||||||
|
.await
|
||||||
|
.add("thumb", &self.thumb)
|
||||||
|
.await
|
||||||
|
.add("disable_notification", &self.disable_notification)
|
||||||
|
.await
|
||||||
|
.add("reply_to_message_id", &self.reply_to_message_id)
|
||||||
|
.await
|
||||||
|
.add("reply_markup", &self.reply_markup)
|
||||||
|
.await
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendAudio {
|
||||||
|
pub(crate) fn new<C>(bot: Arc<Bot>, chat_id: C, audio: InputFile) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id: chat_id.into(),
|
||||||
|
audio,
|
||||||
|
caption: None,
|
||||||
|
parse_mode: None,
|
||||||
|
duration: None,
|
||||||
|
performer: None,
|
||||||
|
title: None,
|
||||||
|
thumb: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio file to send.
|
||||||
|
///
|
||||||
|
/// Pass [`InputFile::File`] to send a file that exists on
|
||||||
|
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
|
||||||
|
/// Telegram to get a .webp file from the Internet, or upload a new one
|
||||||
|
/// using [`InputFile::FileId`]. [More info on Sending Files »].
|
||||||
|
///
|
||||||
|
/// [`InputFile::File`]: crate::types::InputFile::File
|
||||||
|
/// [`InputFile::Url`]: crate::types::InputFile::Url
|
||||||
|
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
|
||||||
|
///
|
||||||
|
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
|
||||||
|
pub fn audio(mut self, val: InputFile) -> Self {
|
||||||
|
self.audio = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio caption, 0-1024 characters.
|
||||||
|
pub fn caption<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.caption = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send [Markdown] or [HTML], if you want Telegram apps to show
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs] in the media caption.
|
||||||
|
///
|
||||||
|
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||||
|
/// [HTML]: crate::types::ParseMode::HTML
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs]:
|
||||||
|
/// crate::types::ParseMode
|
||||||
|
pub fn parse_mode(mut self, val: ParseMode) -> Self {
|
||||||
|
self.parse_mode = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Duration of the audio in seconds.
|
||||||
|
pub fn duration(mut self, val: i32) -> Self {
|
||||||
|
self.duration = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performer.
|
||||||
|
pub fn performer<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.performer = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Track name.
|
||||||
|
pub fn title<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.title = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thumbnail of the file sent; can be ignored if thumbnail generation for
|
||||||
|
/// the file is supported server-side.
|
||||||
|
///
|
||||||
|
/// The thumbnail should be in JPEG format and less than 200 kB in size. A
|
||||||
|
/// thumbnail‘s width and height should not exceed 320. Ignored if the
|
||||||
|
/// file is not uploaded using `multipart/form-data`. Thumbnails can’t
|
||||||
|
/// be reused and can be only uploaded as a new file, so you can pass
|
||||||
|
/// `attach://<file_attach_name>` if the thumbnail was uploaded using
|
||||||
|
/// `multipart/form-data` under `<file_attach_name>`. [More info on
|
||||||
|
/// Sending Files »].
|
||||||
|
///
|
||||||
|
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
|
||||||
|
pub fn thumb(mut self, val: InputFile) -> Self {
|
||||||
|
self.thumb = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional interface options. A JSON-serialized object for an [inline
|
||||||
|
/// keyboard], [custom reply keyboard], instructions to remove reply
|
||||||
|
/// keyboard or to force a reply from the user.
|
||||||
|
///
|
||||||
|
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
|
||||||
|
/// [custom reply keyboard]: https://core.telegram.org/bots#keyboards
|
||||||
|
pub fn reply_markup(mut self, val: ReplyMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
120
src/requests/all/send_chat_action.rs
Normal file
120
src/requests/all/send_chat_action.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, True},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method when you need to tell the user that something is happening
|
||||||
|
/// on the bot's side.
|
||||||
|
///
|
||||||
|
/// The status is set for 5 seconds or less (when a message arrives from your
|
||||||
|
/// bot, Telegram clients clear its typing status).
|
||||||
|
///
|
||||||
|
/// ## Note
|
||||||
|
/// Example: The [ImageBot] needs some time to process a request and upload the
|
||||||
|
/// image. Instead of sending a text message along the lines of “Retrieving
|
||||||
|
/// image, please wait…”, the bot may use [`Bot::send_chat_action`] with `action
|
||||||
|
/// = upload_photo`. The user will see a `sending photo` status for the bot.
|
||||||
|
///
|
||||||
|
/// We only recommend using this method when a response from the bot will take a
|
||||||
|
/// **noticeable** amount of time to arrive.
|
||||||
|
///
|
||||||
|
/// [ImageBot]: https://t.me/imagebot
|
||||||
|
/// [`Bot::send_chat_action`]: crate::Bot::send_chat_action
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct SendChatAction {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
action: SendChatActionKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type of action used in [`SendChatAction`].
|
||||||
|
///
|
||||||
|
/// [`SendChatAction`]: crate::requests::SendChatAction
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum SendChatActionKind {
|
||||||
|
/// For [text messages](crate::Bot::send_message).
|
||||||
|
Typing,
|
||||||
|
|
||||||
|
/// For [photos](crate::Bot::send_photo).
|
||||||
|
UploadPhoto,
|
||||||
|
|
||||||
|
/// For [videos](crate::Bot::send_video).
|
||||||
|
RecordVideo,
|
||||||
|
|
||||||
|
/// For [videos](crate::Bot::send_video).
|
||||||
|
UploadVideo,
|
||||||
|
|
||||||
|
/// For [audio files](crate::Bot::send_audio).
|
||||||
|
RecordAudio,
|
||||||
|
|
||||||
|
/// For [audio files](crate::Bot::send_audio).
|
||||||
|
UploadAudio,
|
||||||
|
|
||||||
|
/// For [general files](crate::Bot::send_document).
|
||||||
|
UploadDocument,
|
||||||
|
|
||||||
|
/// For [location data](crate::Bot::send_location).
|
||||||
|
FindLocation,
|
||||||
|
|
||||||
|
/// For [video notes](crate::Bot::send_video_note).
|
||||||
|
RecordVideoNote,
|
||||||
|
|
||||||
|
/// For [video notes](crate::Bot::send_video_note).
|
||||||
|
UploadVideoNote,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendChatAction {
|
||||||
|
type Output = True;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<True> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendChatAction",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendChatAction {
|
||||||
|
pub(crate) fn new<C>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: C,
|
||||||
|
action: SendChatActionKind,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id: chat_id.into(),
|
||||||
|
action,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type of action to broadcast.
|
||||||
|
pub fn action(mut self, val: SendChatActionKind) -> Self {
|
||||||
|
self.action = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
141
src/requests/all/send_contact.rs
Normal file
141
src/requests/all/send_contact.rs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, Message, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send phone contacts.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendcontact).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct SendContact {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
phone_number: String,
|
||||||
|
first_name: String,
|
||||||
|
last_name: Option<String>,
|
||||||
|
vcard: Option<String>,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendContact {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendContact",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendContact {
|
||||||
|
pub(crate) fn new<C, P, F>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: C,
|
||||||
|
phone_number: P,
|
||||||
|
first_name: F,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
P: Into<String>,
|
||||||
|
F: Into<String>,
|
||||||
|
{
|
||||||
|
let chat_id = chat_id.into();
|
||||||
|
let phone_number = phone_number.into();
|
||||||
|
let first_name = first_name.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
phone_number,
|
||||||
|
first_name,
|
||||||
|
last_name: None,
|
||||||
|
vcard: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contact's phone number.
|
||||||
|
pub fn phone_number<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.phone_number = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contact's first name.
|
||||||
|
pub fn first_name<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.first_name = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contact's last name.
|
||||||
|
pub fn last_name<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.last_name = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional data about the contact in the form of a [vCard], 0-2048
|
||||||
|
/// bytes.
|
||||||
|
///
|
||||||
|
/// [vCard]: https://en.wikipedia.org/wiki/VCard
|
||||||
|
pub fn vcard<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.vcard = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional interface options.
|
||||||
|
pub fn reply_markup(mut self, val: ReplyMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
160
src/requests/all/send_document.rs
Normal file
160
src/requests/all/send_document.rs
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||||
|
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send general files.
|
||||||
|
///
|
||||||
|
/// Bots can currently send files of any type of up to 50 MB in size, this limit
|
||||||
|
/// may be changed in the future.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#senddocument).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SendDocument {
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
document: InputFile,
|
||||||
|
thumb: Option<InputFile>,
|
||||||
|
caption: Option<String>,
|
||||||
|
parse_mode: Option<ParseMode>,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendDocument {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_multipart(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendDocument",
|
||||||
|
FormBuilder::new()
|
||||||
|
.add("chat_id", &self.chat_id)
|
||||||
|
.await
|
||||||
|
.add("document", &self.document)
|
||||||
|
.await
|
||||||
|
.add("thumb", &self.thumb)
|
||||||
|
.await
|
||||||
|
.add("caption", &self.caption)
|
||||||
|
.await
|
||||||
|
.add("parse_mode", &self.parse_mode)
|
||||||
|
.await
|
||||||
|
.add("disable_notification", &self.disable_notification)
|
||||||
|
.await
|
||||||
|
.add("reply_to_message_id", &self.reply_to_message_id)
|
||||||
|
.await
|
||||||
|
.add("reply_markup", &self.reply_markup)
|
||||||
|
.await
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendDocument {
|
||||||
|
pub(crate) fn new<C>(bot: Arc<Bot>, chat_id: C, document: InputFile) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id: chat_id.into(),
|
||||||
|
document,
|
||||||
|
thumb: None,
|
||||||
|
caption: None,
|
||||||
|
parse_mode: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// File to send.
|
||||||
|
///
|
||||||
|
/// Pass a file_id as String to send a file that exists on the
|
||||||
|
/// Telegram servers (recommended), pass an HTTP URL as a String for
|
||||||
|
/// Telegram to get a file from the Internet, or upload a new one using
|
||||||
|
/// `multipart/form-data`. [More info on Sending Files »].
|
||||||
|
///
|
||||||
|
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
|
||||||
|
pub fn document(mut self, val: InputFile) -> Self {
|
||||||
|
self.document = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thumbnail of the file sent; can be ignored if thumbnail generation for
|
||||||
|
/// the file is supported server-side.
|
||||||
|
///
|
||||||
|
/// The thumbnail should be in JPEG format and less than 200 kB in size. A
|
||||||
|
/// thumbnail‘s width and height should not exceed 320. Ignored if the
|
||||||
|
/// file is not uploaded using `multipart/form-data`. Thumbnails can’t
|
||||||
|
/// be reused and can be only uploaded as a new file, so you can pass
|
||||||
|
/// “attach://<file_attach_name>” if the thumbnail was uploaded using
|
||||||
|
/// `multipart/form-data` under `<file_attach_name>`. [More info on
|
||||||
|
/// Sending Files »].
|
||||||
|
///
|
||||||
|
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
|
||||||
|
pub fn thumb(mut self, val: InputFile) -> Self {
|
||||||
|
self.thumb = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Document caption (may also be used when resending documents by
|
||||||
|
/// `file_id`), 0-1024 characters.
|
||||||
|
pub fn caption<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.caption = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send [Markdown] or [HTML], if you want Telegram apps to show
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs] in the media caption.
|
||||||
|
///
|
||||||
|
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||||
|
/// [HTML]: crate::types::ParseMode::HTML
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs]:
|
||||||
|
/// crate::types::ParseMode
|
||||||
|
pub fn parse_mode(mut self, val: ParseMode) -> Self {
|
||||||
|
self.parse_mode = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional interface options.
|
||||||
|
pub fn reply_markup(mut self, val: ReplyMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
103
src/requests/all/send_game.rs
Normal file
103
src/requests/all/send_game.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{InlineKeyboardMarkup, Message},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send a game.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendgame).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct SendGame {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: i32,
|
||||||
|
game_short_name: String,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<InlineKeyboardMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendGame {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendGame",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendGame {
|
||||||
|
pub(crate) fn new<G>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: i32,
|
||||||
|
game_short_name: G,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
G: Into<String>,
|
||||||
|
{
|
||||||
|
let game_short_name = game_short_name.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
game_short_name,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat.
|
||||||
|
pub fn chat_id(mut self, val: i32) -> Self {
|
||||||
|
self.chat_id = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Short name of the game, serves as the unique identifier for the game.
|
||||||
|
/// Set up your games via [@Botfather].
|
||||||
|
///
|
||||||
|
/// [@Botfather]: https://t.me/botfather
|
||||||
|
pub fn game_short_name<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.game_short_name = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A JSON-serialized object for an [inline keyboard]. If empty, one `Play
|
||||||
|
/// game_title` button will be shown. If not empty, the first button must
|
||||||
|
/// launch the game.
|
||||||
|
///
|
||||||
|
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
|
||||||
|
pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
306
src/requests/all/send_invoice.rs
Normal file
306
src/requests/all/send_invoice.rs
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{InlineKeyboardMarkup, LabeledPrice, Message},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send invoices.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendinvoice).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct SendInvoice {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: i32,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
payload: String,
|
||||||
|
provider_token: String,
|
||||||
|
start_parameter: String,
|
||||||
|
currency: String,
|
||||||
|
prices: Vec<LabeledPrice>,
|
||||||
|
provider_data: Option<String>,
|
||||||
|
photo_url: Option<String>,
|
||||||
|
photo_size: Option<i32>,
|
||||||
|
photo_width: Option<i32>,
|
||||||
|
photo_height: Option<i32>,
|
||||||
|
need_name: Option<bool>,
|
||||||
|
need_phone_number: Option<bool>,
|
||||||
|
need_email: Option<bool>,
|
||||||
|
need_shipping_address: Option<bool>,
|
||||||
|
send_phone_number_to_provider: Option<bool>,
|
||||||
|
send_email_to_provider: Option<bool>,
|
||||||
|
is_flexible: Option<bool>,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<InlineKeyboardMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendInvoice {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendInvoice",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendInvoice {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub(crate) fn new<T, D, Pl, Pt, S, C, Pr>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: i32,
|
||||||
|
title: T,
|
||||||
|
description: D,
|
||||||
|
payload: Pl,
|
||||||
|
provider_token: Pt,
|
||||||
|
start_parameter: S,
|
||||||
|
currency: C,
|
||||||
|
prices: Pr,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
D: Into<String>,
|
||||||
|
Pl: Into<String>,
|
||||||
|
Pt: Into<String>,
|
||||||
|
S: Into<String>,
|
||||||
|
C: Into<String>,
|
||||||
|
Pr: Into<Vec<LabeledPrice>>,
|
||||||
|
{
|
||||||
|
let title = title.into();
|
||||||
|
let description = description.into();
|
||||||
|
let payload = payload.into();
|
||||||
|
let provider_token = provider_token.into();
|
||||||
|
let start_parameter = start_parameter.into();
|
||||||
|
let currency = currency.into();
|
||||||
|
let prices = prices.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
payload,
|
||||||
|
provider_token,
|
||||||
|
start_parameter,
|
||||||
|
currency,
|
||||||
|
prices,
|
||||||
|
provider_data: None,
|
||||||
|
photo_url: None,
|
||||||
|
photo_size: None,
|
||||||
|
photo_width: None,
|
||||||
|
photo_height: None,
|
||||||
|
need_name: None,
|
||||||
|
need_phone_number: None,
|
||||||
|
need_email: None,
|
||||||
|
need_shipping_address: None,
|
||||||
|
send_phone_number_to_provider: None,
|
||||||
|
send_email_to_provider: None,
|
||||||
|
is_flexible: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target private chat.
|
||||||
|
pub fn chat_id(mut self, val: i32) -> Self {
|
||||||
|
self.chat_id = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Product name, 1-32 characters.
|
||||||
|
pub fn title<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.title = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Product description, 1-255 characters.
|
||||||
|
pub fn description<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.description = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bot-defined invoice payload, 1-128 bytes. This will not be displayed to
|
||||||
|
/// the user, use for your internal processes.
|
||||||
|
pub fn payload<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.payload = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Payments provider token, obtained via [@Botfather].
|
||||||
|
///
|
||||||
|
/// [@Botfather]: https://t.me/botfather
|
||||||
|
pub fn provider_token<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.provider_token = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique deep-linking parameter that can be used to generate this invoice
|
||||||
|
/// when used as a start parameter.
|
||||||
|
pub fn start_parameter<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.start_parameter = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Three-letter ISO 4217 currency code, see [more on currencies].
|
||||||
|
///
|
||||||
|
/// [more on currencies]: https://core.telegram.org/bots/payments#supported-currencies
|
||||||
|
pub fn currency<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.currency = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Price breakdown, a list of components (e.g. product price, tax,
|
||||||
|
/// discount, delivery cost, delivery tax, bonus, etc.).
|
||||||
|
pub fn prices<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<Vec<LabeledPrice>>,
|
||||||
|
{
|
||||||
|
self.prices = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JSON-encoded data about the invoice, which will be shared with the
|
||||||
|
/// payment provider.
|
||||||
|
///
|
||||||
|
/// A detailed description of required fields should be provided by the
|
||||||
|
/// payment provider.
|
||||||
|
pub fn provider_data<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.provider_data = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// URL of the product photo for the invoice.
|
||||||
|
///
|
||||||
|
/// Can be a photo of the goods or a marketing image for a service. People
|
||||||
|
/// like it better when they see what they are paying for.
|
||||||
|
pub fn photo_url<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.photo_url = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Photo size.
|
||||||
|
pub fn photo_size(mut self, val: i32) -> Self {
|
||||||
|
self.photo_size = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Photo width.
|
||||||
|
pub fn photo_width(mut self, val: i32) -> Self {
|
||||||
|
self.photo_width = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Photo height.
|
||||||
|
pub fn photo_height(mut self, val: i32) -> Self {
|
||||||
|
self.photo_height = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if you require the user's full name to complete the order.
|
||||||
|
pub fn need_name(mut self, val: bool) -> Self {
|
||||||
|
self.need_name = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if you require the user's phone number to complete the
|
||||||
|
/// order.
|
||||||
|
pub fn need_phone_number(mut self, val: bool) -> Self {
|
||||||
|
self.need_phone_number = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if you require the user's email address to complete the
|
||||||
|
/// order.
|
||||||
|
pub fn need_email(mut self, val: bool) -> Self {
|
||||||
|
self.need_email = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if you require the user's shipping address to complete the
|
||||||
|
/// order.
|
||||||
|
pub fn need_shipping_address(mut self, val: bool) -> Self {
|
||||||
|
self.need_shipping_address = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if user's phone number should be sent to provider.
|
||||||
|
pub fn send_phone_number_to_provider(mut self, val: bool) -> Self {
|
||||||
|
self.send_phone_number_to_provider = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if user's email address should be sent to provider.
|
||||||
|
pub fn send_email_to_provider(mut self, val: bool) -> Self {
|
||||||
|
self.send_email_to_provider = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the final price depends on the shipping method.
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
pub fn is_flexible(mut self, val: bool) -> Self {
|
||||||
|
self.is_flexible = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A JSON-serialized object for an [inline keyboard].
|
||||||
|
///
|
||||||
|
/// If empty, one 'Pay `total price`' button will be shown. If not empty,
|
||||||
|
/// the first button must be a Pay button.
|
||||||
|
///
|
||||||
|
/// [inlint keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
|
||||||
|
pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
122
src/requests/all/send_location.rs
Normal file
122
src/requests/all/send_location.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, Message, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send point on the map.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendlocation).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct SendLocation {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
latitude: f32,
|
||||||
|
longitude: f32,
|
||||||
|
live_period: Option<i64>,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendLocation {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendLocation",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendLocation {
|
||||||
|
pub(crate) fn new<C>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: C,
|
||||||
|
latitude: f32,
|
||||||
|
longitude: f32,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
let chat_id = chat_id.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
live_period: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Latitude of the location.
|
||||||
|
pub fn latitude(mut self, val: f32) -> Self {
|
||||||
|
self.latitude = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Longitude of the location.
|
||||||
|
pub fn longitude(mut self, val: f32) -> Self {
|
||||||
|
self.longitude = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Period in seconds for which the location will be updated (see [Live
|
||||||
|
/// Locations], should be between 60 and 86400).
|
||||||
|
///
|
||||||
|
/// [Live Locations]: https://telegram.org/blog/live-locations
|
||||||
|
pub fn live_period(mut self, val: i64) -> Self {
|
||||||
|
self.live_period = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A JSON-serialized object for an [inline keyboard].
|
||||||
|
///
|
||||||
|
/// If empty, one 'Pay `total price`' button will be shown. If not empty,
|
||||||
|
/// the first button must be a Pay button.
|
||||||
|
///
|
||||||
|
/// [inlint keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
|
||||||
|
pub fn reply_markup(mut self, val: ReplyMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
96
src/requests/all/send_media_group.rs
Normal file
96
src/requests/all/send_media_group.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||||
|
types::{ChatId, InputMedia, Message},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send a group of photos or videos as an album.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendmediagroup).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SendMediaGroup {
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
media: Vec<InputMedia>, // TODO: InputMediaPhoto and InputMediaVideo
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendMediaGroup {
|
||||||
|
type Output = Vec<Message>;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Vec<Message>> {
|
||||||
|
net::request_multipart(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendMediaGroup",
|
||||||
|
FormBuilder::new()
|
||||||
|
.add("chat_id", &self.chat_id)
|
||||||
|
.await
|
||||||
|
.add("media", &self.media)
|
||||||
|
.await
|
||||||
|
.add("disable_notification", &self.disable_notification)
|
||||||
|
.await
|
||||||
|
.add("reply_to_message_id", &self.reply_to_message_id)
|
||||||
|
.await
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendMediaGroup {
|
||||||
|
pub(crate) fn new<C, M>(bot: Arc<Bot>, chat_id: C, media: M) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
M: Into<Vec<InputMedia>>,
|
||||||
|
{
|
||||||
|
let chat_id = chat_id.into();
|
||||||
|
let media = media.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
media,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A JSON-serialized array describing photos and videos to be sent, must
|
||||||
|
/// include 2–10 items.
|
||||||
|
pub fn media<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<Vec<InputMedia>>,
|
||||||
|
{
|
||||||
|
self.media = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the messages are a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
128
src/requests/all/send_message.rs
Normal file
128
src/requests/all/send_message.rs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, Message, ParseMode, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send text messages.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendmessage).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct SendMessage {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
pub chat_id: ChatId,
|
||||||
|
pub text: String,
|
||||||
|
pub parse_mode: Option<ParseMode>,
|
||||||
|
pub disable_web_page_preview: Option<bool>,
|
||||||
|
pub disable_notification: Option<bool>,
|
||||||
|
pub reply_to_message_id: Option<i32>,
|
||||||
|
pub reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendMessage {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendMessage",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendMessage {
|
||||||
|
pub(crate) fn new<C, T>(bot: Arc<Bot>, chat_id: C, text: T) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id: chat_id.into(),
|
||||||
|
text: text.into(),
|
||||||
|
parse_mode: None,
|
||||||
|
disable_web_page_preview: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, value: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = value.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Text of the message to be sent.
|
||||||
|
pub fn text<T>(mut self, value: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.text = value.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send [Markdown] or [HTML], if you want Telegram apps to show
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs] in the media caption.
|
||||||
|
///
|
||||||
|
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||||
|
/// [HTML]: crate::types::ParseMode::HTML
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs]:
|
||||||
|
/// crate::types::ParseMode
|
||||||
|
pub fn parse_mode(mut self, value: ParseMode) -> Self {
|
||||||
|
self.parse_mode = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disables link previews for links in this message.
|
||||||
|
pub fn disable_web_page_preview(mut self, value: bool) -> Self {
|
||||||
|
self.disable_web_page_preview = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, value: bool) -> Self {
|
||||||
|
self.disable_notification = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, value: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional interface options.
|
||||||
|
///
|
||||||
|
/// A JSON-serialized object for an [inline keyboard], [custom reply
|
||||||
|
/// keyboard], instructions to remove reply keyboard or to force a reply
|
||||||
|
/// from the user.
|
||||||
|
///
|
||||||
|
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
|
||||||
|
/// [custom reply keyboard]: https://core.telegram.org/bots#keyboards
|
||||||
|
pub fn reply_markup<T>(mut self, value: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ReplyMarkup>,
|
||||||
|
{
|
||||||
|
self.reply_markup = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
145
src/requests/all/send_photo.rs
Normal file
145
src/requests/all/send_photo.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||||
|
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send photos.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendphoto).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SendPhoto {
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
photo: InputFile,
|
||||||
|
caption: Option<String>,
|
||||||
|
parse_mode: Option<ParseMode>,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendPhoto {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_multipart(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendPhoto",
|
||||||
|
FormBuilder::new()
|
||||||
|
.add("chat_id", &self.chat_id)
|
||||||
|
.await
|
||||||
|
.add("photo", &self.photo)
|
||||||
|
.await
|
||||||
|
.add("caption", &self.caption)
|
||||||
|
.await
|
||||||
|
.add("parse_mode", &self.parse_mode)
|
||||||
|
.await
|
||||||
|
.add("disable_notification", &self.disable_notification)
|
||||||
|
.await
|
||||||
|
.add("reply_to_message_id", &self.reply_to_message_id)
|
||||||
|
.await
|
||||||
|
.add("reply_markup", &self.reply_markup)
|
||||||
|
.await
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendPhoto {
|
||||||
|
pub(crate) fn new<C>(bot: Arc<Bot>, chat_id: C, photo: InputFile) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id: chat_id.into(),
|
||||||
|
photo,
|
||||||
|
caption: None,
|
||||||
|
parse_mode: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Photo to send.
|
||||||
|
///
|
||||||
|
/// Pass [`InputFile::File`] to send a photo that exists on
|
||||||
|
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
|
||||||
|
/// Telegram to get a .webp file from the Internet, or upload a new one
|
||||||
|
/// using [`InputFile::FileId`]. [More info on Sending Files »].
|
||||||
|
///
|
||||||
|
/// [`InputFile::File`]: crate::types::InputFile::File
|
||||||
|
/// [`InputFile::Url`]: crate::types::InputFile::Url
|
||||||
|
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
|
||||||
|
///
|
||||||
|
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
|
||||||
|
pub fn photo(mut self, val: InputFile) -> Self {
|
||||||
|
self.photo = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
///Photo caption (may also be used when resending photos by file_id),
|
||||||
|
/// 0-1024 characters.
|
||||||
|
pub fn caption<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.caption = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send [Markdown] or [HTML], if you want Telegram apps to show
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs] in the media caption.
|
||||||
|
///
|
||||||
|
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||||
|
/// [HTML]: crate::types::ParseMode::HTML
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs]:
|
||||||
|
/// crate::types::ParseMode
|
||||||
|
pub fn parse_mode(mut self, val: ParseMode) -> Self {
|
||||||
|
self.parse_mode = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional interface options. A JSON-serialized object for an [inline
|
||||||
|
/// keyboard], [custom reply keyboard], instructions to remove reply
|
||||||
|
/// keyboard or to force a reply from the user.
|
||||||
|
///
|
||||||
|
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
|
||||||
|
/// [custom reply keyboard]: https://core.telegram.org/bots#keyboards
|
||||||
|
pub fn reply_markup(mut self, val: ReplyMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
186
src/requests/all/send_poll.rs
Normal file
186
src/requests/all/send_poll.rs
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, Message, PollType, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send a native poll.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendpoll).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct SendPoll {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
question: String,
|
||||||
|
options: Vec<String>,
|
||||||
|
is_anonymous: Option<bool>,
|
||||||
|
poll_type: Option<PollType>,
|
||||||
|
allows_multiple_answers: Option<bool>,
|
||||||
|
correct_option_id: Option<i32>,
|
||||||
|
is_closed: Option<bool>,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendPoll {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendPoll",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendPoll {
|
||||||
|
pub(crate) fn new<C, Q, O>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: C,
|
||||||
|
question: Q,
|
||||||
|
options: O,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
Q: Into<String>,
|
||||||
|
O: Into<Vec<String>>,
|
||||||
|
{
|
||||||
|
let chat_id = chat_id.into();
|
||||||
|
let question = question.into();
|
||||||
|
let options = options.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
question,
|
||||||
|
options,
|
||||||
|
is_anonymous: None,
|
||||||
|
poll_type: None,
|
||||||
|
allows_multiple_answers: None,
|
||||||
|
correct_option_id: None,
|
||||||
|
is_closed: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
///
|
||||||
|
/// A native poll can't be sent to a private chat.
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poll question, 1-255 characters.
|
||||||
|
pub fn question<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.question = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List of answer options, 2-10 strings 1-100 characters each.
|
||||||
|
pub fn options<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<Vec<String>>,
|
||||||
|
{
|
||||||
|
self.options = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `true`, if the poll needs to be anonymous, defaults to `true`.
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
pub fn is_anonymous<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<bool>,
|
||||||
|
{
|
||||||
|
self.is_anonymous = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poll type, `quiz` or `regular`, defaults to `regular`.
|
||||||
|
pub fn poll_type(mut self, val: PollType) -> Self {
|
||||||
|
self.poll_type = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `true`, if the poll allows multiple answers, ignored for polls in quiz
|
||||||
|
/// mode.
|
||||||
|
///
|
||||||
|
/// Defaults to `false`.
|
||||||
|
pub fn allows_multiple_answers<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<bool>,
|
||||||
|
{
|
||||||
|
self.allows_multiple_answers = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0-based identifier of the correct answer option, required for polls in
|
||||||
|
/// quiz mode.
|
||||||
|
pub fn correct_option_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<i32>,
|
||||||
|
{
|
||||||
|
self.correct_option_id = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the poll needs to be immediately closed.
|
||||||
|
///
|
||||||
|
/// This can be useful for poll preview.
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
|
pub fn is_closed<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<bool>,
|
||||||
|
{
|
||||||
|
self.is_closed = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently].
|
||||||
|
///
|
||||||
|
/// Users will receive a notification with no sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional interface options.
|
||||||
|
///
|
||||||
|
/// A JSON-serialized object for an [inline keyboard], [custom reply
|
||||||
|
/// keyboard], instructions to remove reply keyboard or to force a reply
|
||||||
|
/// from the user.
|
||||||
|
///
|
||||||
|
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
|
||||||
|
/// [custom reply keyboard]: https://core.telegram.org/bots#keyboards
|
||||||
|
pub fn reply_markup(mut self, val: ReplyMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
118
src/requests/all/send_sticker.rs
Normal file
118
src/requests/all/send_sticker.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||||
|
types::{ChatId, InputFile, Message, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send static .WEBP or [animated] .TGS stickers.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendsticker).
|
||||||
|
///
|
||||||
|
/// [animated]: https://telegram.org/blog/animated-stickers
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SendSticker {
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
sticker: InputFile,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendSticker {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_multipart(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendSticker",
|
||||||
|
FormBuilder::new()
|
||||||
|
.add("chat_id", &self.chat_id)
|
||||||
|
.await
|
||||||
|
.add("sticker", &self.sticker)
|
||||||
|
.await
|
||||||
|
.add("disable_notification", &self.disable_notification)
|
||||||
|
.await
|
||||||
|
.add("reply_to_message_id", &self.reply_to_message_id)
|
||||||
|
.await
|
||||||
|
.add("reply_markup", &self.reply_markup)
|
||||||
|
.await
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendSticker {
|
||||||
|
pub(crate) fn new<C>(bot: Arc<Bot>, chat_id: C, sticker: InputFile) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id: chat_id.into(),
|
||||||
|
sticker,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sticker to send.
|
||||||
|
///
|
||||||
|
/// Pass [`InputFile::File`] to send a file that exists on
|
||||||
|
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
|
||||||
|
/// Telegram to get a .webp file from the Internet, or upload a new one
|
||||||
|
/// using [`InputFile::FileId`]. [More info on Sending Files »].
|
||||||
|
///
|
||||||
|
/// [`InputFile::File`]: crate::types::InputFile::File
|
||||||
|
/// [`InputFile::Url`]: crate::types::InputFile::Url
|
||||||
|
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
|
||||||
|
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
|
||||||
|
pub fn sticker(mut self, val: InputFile) -> Self {
|
||||||
|
self.sticker = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional interface options.
|
||||||
|
///
|
||||||
|
/// A JSON-serialized object for an [inline keyboard], [custom reply
|
||||||
|
/// keyboard], instructions to remove reply keyboard or to force a reply
|
||||||
|
/// from the user.
|
||||||
|
///
|
||||||
|
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
|
||||||
|
/// [custom reply keyboard]: https://core.telegram.org/bots#keyboards
|
||||||
|
pub fn reply_markup(mut self, val: ReplyMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
166
src/requests/all/send_venue.rs
Normal file
166
src/requests/all/send_venue.rs
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{Request, ResponseResult},
|
||||||
|
types::{ChatId, Message, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send information about a venue.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendvenue).
|
||||||
|
#[serde_with_macros::skip_serializing_none]
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct SendVenue {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
latitude: f32,
|
||||||
|
longitude: f32,
|
||||||
|
title: String,
|
||||||
|
address: String,
|
||||||
|
foursquare_id: Option<String>,
|
||||||
|
foursquare_type: Option<String>,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendVenue {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_json(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendVenue",
|
||||||
|
&self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendVenue {
|
||||||
|
pub(crate) fn new<C, T, A>(
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: C,
|
||||||
|
latitude: f32,
|
||||||
|
longitude: f32,
|
||||||
|
title: T,
|
||||||
|
address: A,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
T: Into<String>,
|
||||||
|
A: Into<String>,
|
||||||
|
{
|
||||||
|
let chat_id = chat_id.into();
|
||||||
|
let title = title.into();
|
||||||
|
let address = address.into();
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
title,
|
||||||
|
address,
|
||||||
|
foursquare_id: None,
|
||||||
|
foursquare_type: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Latitude of the venue.
|
||||||
|
pub fn latitude(mut self, val: f32) -> Self {
|
||||||
|
self.latitude = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Longitude of the venue.
|
||||||
|
pub fn longitude(mut self, val: f32) -> Self {
|
||||||
|
self.longitude = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Name of the venue.
|
||||||
|
pub fn title<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.title = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Address of the venue.
|
||||||
|
pub fn address<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.address = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Foursquare identifier of the venue.
|
||||||
|
pub fn foursquare_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.foursquare_id = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Foursquare type of the venue, if known.
|
||||||
|
///
|
||||||
|
/// For example, `arts_entertainment/default`, `arts_entertainment/aquarium`
|
||||||
|
/// or `food/icecream`.
|
||||||
|
pub fn foursquare_type<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.foursquare_type = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional interface options.
|
||||||
|
///
|
||||||
|
/// A JSON-serialized object for an [inline keyboard], [custom reply
|
||||||
|
/// keyboard], instructions to remove reply keyboard or to force a reply
|
||||||
|
/// from the user.
|
||||||
|
///
|
||||||
|
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
|
||||||
|
/// [custom reply keyboard]: https://core.telegram.org/bots#keyboards
|
||||||
|
pub fn reply_markup(mut self, val: ReplyMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
210
src/requests/all/send_video.rs
Normal file
210
src/requests/all/send_video.rs
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
use crate::{
|
||||||
|
net,
|
||||||
|
requests::{form_builder::FormBuilder, Request, ResponseResult},
|
||||||
|
types::{ChatId, InputFile, Message, ParseMode, ReplyMarkup},
|
||||||
|
Bot,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Use this method to send video files, Telegram clients support mp4 videos
|
||||||
|
/// (other formats may be sent as Document).
|
||||||
|
///
|
||||||
|
/// Bots can currently send video files of up to 50 MB in size, this
|
||||||
|
/// limit may be changed in the future.
|
||||||
|
///
|
||||||
|
/// [The official docs](https://core.telegram.org/bots/api#sendvideo).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SendVideo {
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
chat_id: ChatId,
|
||||||
|
video: InputFile,
|
||||||
|
duration: Option<i32>,
|
||||||
|
width: Option<i32>,
|
||||||
|
height: Option<i32>,
|
||||||
|
thumb: Option<InputFile>,
|
||||||
|
caption: Option<String>,
|
||||||
|
parse_mode: Option<ParseMode>,
|
||||||
|
supports_streaming: Option<bool>,
|
||||||
|
disable_notification: Option<bool>,
|
||||||
|
reply_to_message_id: Option<i32>,
|
||||||
|
reply_markup: Option<ReplyMarkup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Request for SendVideo {
|
||||||
|
type Output = Message;
|
||||||
|
|
||||||
|
async fn send(&self) -> ResponseResult<Message> {
|
||||||
|
net::request_multipart(
|
||||||
|
self.bot.client(),
|
||||||
|
self.bot.token(),
|
||||||
|
"sendVideo",
|
||||||
|
FormBuilder::new()
|
||||||
|
.add("chat_id", &self.chat_id)
|
||||||
|
.await
|
||||||
|
.add("video", &self.video)
|
||||||
|
.await
|
||||||
|
.add("duration", &self.duration)
|
||||||
|
.await
|
||||||
|
.add("width", &self.width)
|
||||||
|
.await
|
||||||
|
.add("height", &self.height)
|
||||||
|
.await
|
||||||
|
.add("thumb", &self.thumb)
|
||||||
|
.await
|
||||||
|
.add("caption", &self.caption)
|
||||||
|
.await
|
||||||
|
.add("parse_mode", &self.parse_mode)
|
||||||
|
.await
|
||||||
|
.add("supports_streaming", &self.supports_streaming)
|
||||||
|
.await
|
||||||
|
.add("disable_notification", &self.disable_notification)
|
||||||
|
.await
|
||||||
|
.add("reply_to_message_id", &self.reply_to_message_id)
|
||||||
|
.await
|
||||||
|
.add("reply_markup", &self.reply_markup)
|
||||||
|
.await
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendVideo {
|
||||||
|
pub(crate) fn new<C>(bot: Arc<Bot>, chat_id: C, video: InputFile) -> Self
|
||||||
|
where
|
||||||
|
C: Into<ChatId>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
bot,
|
||||||
|
chat_id: chat_id.into(),
|
||||||
|
video,
|
||||||
|
duration: None,
|
||||||
|
width: None,
|
||||||
|
height: None,
|
||||||
|
thumb: None,
|
||||||
|
caption: None,
|
||||||
|
parse_mode: None,
|
||||||
|
supports_streaming: None,
|
||||||
|
disable_notification: None,
|
||||||
|
reply_to_message_id: None,
|
||||||
|
reply_markup: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unique identifier for the target chat or username of the target channel
|
||||||
|
/// (in the format `@channelusername`).
|
||||||
|
pub fn chat_id<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<ChatId>,
|
||||||
|
{
|
||||||
|
self.chat_id = val.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Video to sent.
|
||||||
|
///
|
||||||
|
/// Pass [`InputFile::File`] to send a file that exists on
|
||||||
|
/// the Telegram servers (recommended), pass an [`InputFile::Url`] for
|
||||||
|
/// Telegram to get a .webp file from the Internet, or upload a new one
|
||||||
|
/// using [`InputFile::FileId`]. [More info on Sending Files »].
|
||||||
|
///
|
||||||
|
/// [`InputFile::File`]: crate::types::InputFile::File
|
||||||
|
/// [`InputFile::Url`]: crate::types::InputFile::Url
|
||||||
|
/// [`InputFile::FileId`]: crate::types::InputFile::FileId
|
||||||
|
pub fn video(mut self, val: InputFile) -> Self {
|
||||||
|
self.video = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Duration of sent video in seconds.
|
||||||
|
pub fn duration(mut self, val: i32) -> Self {
|
||||||
|
self.duration = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Video width.
|
||||||
|
pub fn width(mut self, val: i32) -> Self {
|
||||||
|
self.width = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Video height.
|
||||||
|
pub fn height(mut self, val: i32) -> Self {
|
||||||
|
self.height = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thumbnail of the file sent; can be ignored if thumbnail generation for
|
||||||
|
/// the file is supported server-side.
|
||||||
|
///
|
||||||
|
/// The thumbnail should be in JPEG format and less than 200 kB in size. A
|
||||||
|
/// thumbnail‘s width and height should not exceed 320. Ignored if the
|
||||||
|
/// file is not uploaded using `multipart/form-data`. Thumbnails can’t be
|
||||||
|
/// reused and can be only uploaded as a new file, so you can pass
|
||||||
|
/// `attach://<file_attach_name>` if the thumbnail was uploaded using
|
||||||
|
/// `multipart/form-data` under `<file_attach_name>`. [More info on Sending
|
||||||
|
/// Files »].
|
||||||
|
///
|
||||||
|
/// [More info on Sending Files »]: https://core.telegram.org/bots/api#sending-files
|
||||||
|
pub fn thumb(mut self, val: InputFile) -> Self {
|
||||||
|
self.thumb = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Video caption (may also be used when resending videos by file_id),
|
||||||
|
/// 0-1024 characters.
|
||||||
|
pub fn caption<T>(mut self, val: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
self.caption = Some(val.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send [Markdown] or [HTML], if you want Telegram apps to show
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs] in the media caption.
|
||||||
|
///
|
||||||
|
/// [Markdown]: crate::types::ParseMode::Markdown
|
||||||
|
/// [HTML]: crate::types::ParseMode::HTML
|
||||||
|
/// [bold, italic, fixed-width text or inline URLs]:
|
||||||
|
/// crate::types::ParseMode
|
||||||
|
pub fn parse_mode(mut self, val: ParseMode) -> Self {
|
||||||
|
self.parse_mode = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass `true`, if the uploaded video is suitable for streaming.
|
||||||
|
pub fn supports_streaming(mut self, val: bool) -> Self {
|
||||||
|
self.supports_streaming = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends the message [silently]. Users will receive a notification with no
|
||||||
|
/// sound.
|
||||||
|
///
|
||||||
|
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
|
||||||
|
pub fn disable_notification(mut self, val: bool) -> Self {
|
||||||
|
self.disable_notification = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the message is a reply, ID of the original message.
|
||||||
|
pub fn reply_to_message_id(mut self, val: i32) -> Self {
|
||||||
|
self.reply_to_message_id = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional interface options.
|
||||||
|
///
|
||||||
|
/// A JSON-serialized object for an [inline keyboard], [custom reply
|
||||||
|
/// keyboard], instructions to remove reply keyboard or to force a reply
|
||||||
|
/// from the user.
|
||||||
|
///
|
||||||
|
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
|
||||||
|
/// [custom reply keyboard]: https://core.telegram.org/bots#keyboards
|
||||||
|
pub fn reply_markup(mut self, val: ReplyMarkup) -> Self {
|
||||||
|
self.reply_markup = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue