mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 14:35:36 +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
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -1,4 +1,11 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
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]
|
||||
name = "async-telegram-bot"
|
||||
name = "teloxide"
|
||||
version = "0.1.0"
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
futures-preview = { version = "0.3.0-alpha.14", features = ["compat"] }
|
||||
reqwest = "0.9.20"
|
||||
serde_json = "1.0.39"
|
||||
serde = {version = "1.0.92", features = ["derive"] }
|
||||
lazy_static = "1.3"
|
||||
serde_json = "1.0.44"
|
||||
serde = { version = "1.0.101", features = ["derive"] }
|
||||
|
||||
tokio = { version = "0.2.6", features = ["full"] }
|
||||
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
|
||||
|
||||
Copyright (c) 2019 async-telegram-bot
|
||||
Copyright (c) 2019 teloxide
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
246
README.md
246
README.md
|
@ -1,2 +1,244 @@
|
|||
# async-telegram-bot
|
||||
An asynchronous full-featured Telegram bot framework for Rust
|
||||
<div align="center">
|
||||
<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]
|
||||
extern crate lazy_static;
|
||||
#![doc(
|
||||
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