Merge branch 'dev' into clarify-storage-purpose

This commit is contained in:
Temirkhan Myrzamadi 2021-03-22 01:35:14 +06:00
commit 580dfaeae6
246 changed files with 794 additions and 23875 deletions

View file

@ -1,4 +1,3 @@
on:
push:
branches: [ master ]
@ -8,8 +7,9 @@ on:
name: Continuous integration
jobs:
code-checks:
style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
@ -17,49 +17,65 @@ jobs:
profile: minimal
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Cargo fmt
run: cargo +nightly fmt --all -- --check
- uses: actions-rs/toolchain@v1
components: rustfmt
- name: fmt
uses: actions-rs/cargo@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Cargo clippy
run: cargo clippy --all --all-targets --all-features -- -D warnings
command: fmt
args: --all -- --check
test:
clippy:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- beta
- nightly
include:
- rust: stable
features: "--features \"redis-storage sqlite-storage cbor-serializer bincode-serializer frunk-\""
- rust: beta
features: "--features \"redis-storage sqlite-storage cbor-serializer bincode-serializer frunk-\""
- rust: nightly
features: "--all-features"
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: clippy
- name: clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --all-features -- -D warnings
test:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- beta
- nightly
include:
- rust: stable
features: "--features full"
- rust: beta
features: "--features full"
- rust: nightly
features: "--all-features"
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
- name: Build
- name: build
uses: actions-rs/cargo@v1
with:
command: build
args: --verbose ${{ matrix.features }}
- name: Setup redis
run: |
sudo apt install redis-server
@ -67,7 +83,7 @@ jobs:
redis-server --port 7778 > /dev/null &
redis-server --port 7779 > /dev/null &
- name: Test
- name: test
uses: actions-rs/cargo@v1
with:
command: test

View file

@ -6,12 +6,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased]
## [0.4.0] - 2021-03-19
### Added
- Integrate [teloxide-core].
- Allow arbitrary error types to be returned from (sub)transitions ([issue 242](https://github.com/teloxide/teloxide/issues/242)).
- The `respond` function, a shortcut for `ResponseResult::Ok(())`.
- The `sqlite-storage` feature -- enables SQLite support.
- `Dispatcher::{my_chat_members_handler, chat_members_handler}`
[teloxide-core]: https://github.com/teloxide/teloxide-core
### Deprecated
- `UpdateWithCx::answer_str`
### Fixed
- Hide `SubtransitionOutputType` from the docs.
### Changed
- Export `teloxide_macros::teloxide` in `prelude`.
- `dispatching::dialogue::serializer::{JSON -> Json, CBOR -> Cbor}`
- Allow `bot_name` be `N`, where `N: Into<String> + ...` in `commands_repl` & `commands_repl_with_listener`.
- 'Edit methods' (namely `edit_message_live_location`, `stop_message_live_location`, `edit_message_text`,
`edit_message_caption`, `edit_message_media` and `edit_message_reply_markup`) are split into common and inline
@ -22,10 +38,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`#[non_exhaustive]` annotation is removed from the enum, type of `TargetMessage::Inline::inline_message_id` changed
`i32` => `String`. `TargetMessage` now implements `From<String>`, `get_game_high_scores` and `set_game_score` use
`Into<TargetMessage>` to accept `String`s. ([issue 253], [pr 257])
- Remove `ResponseResult` from `prelude`.
[issue 253]: https://github.com/teloxide/teloxide/issues/253
[pr 257]: https://github.com/teloxide/teloxide/pull/257
## [0.3.4] - 2020-01-13
### Fixed
- Failing compilation with `serde::export` ([issue 328](https://github.com/teloxide/teloxide/issues/328)).
## [0.3.3] - 2020-10-30
### Fixed

View file

@ -1,6 +1,6 @@
[package]
name = "teloxide"
version = "0.3.3"
version = "0.4.0"
edition = "2018"
description = "An elegant Telegram bots framework for Rust"
repository = "https://github.com/teloxide/teloxide"
@ -30,49 +30,73 @@ cbor-serializer = ["serde_cbor"]
bincode-serializer = ["bincode"]
frunk- = ["frunk"]
macros = ["teloxide-macros"]
nightly = [] # currently used for `README.md` tests and building docs for `docsrs` to add `This is supported on feature="..." only.`
native-tls = ["teloxide-core/native-tls"]
rustls = ["teloxide-core/rustls"]
auto-send = ["teloxide-core/auto_send"]
throttle = ["teloxide-core/throttle"]
cache-me = ["teloxide-core/cache_me"]
# currently used for `README.md` tests, building docs for `docsrs` to add `This is supported on feature="..." only.`,
# and for teloxide-core.
nightly = ["teloxide-core/nightly"]
full = [
"sqlite-storage",
"redis-storage",
"cbor-serializer",
"bincode-serializer",
"frunk",
"macros",
"teloxide-core/full",
"native-tls",
"rustls",
"auto-send",
"throttle",
"cache-me"
]
[dependencies]
serde_json = "1.0.55"
serde = { version = "1.0.114", features = ["derive"] }
teloxide-core = { version = "0.2.1", default-features = false }
teloxide-macros = { version = "0.4", optional = true }
tokio = { version = "0.2.21", features = ["fs", "stream"] }
tokio-util = "0.3.1"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.10.6", features = ["json", "stream"] }
log = "0.4.8"
tokio = { version = "1.2", features = ["fs"] }
tokio-util = "0.6"
tokio-stream = "0.1"
reqwest = { version = "0.11", features = ["json", "stream"] }
log = "0.4"
lockfree = "0.5.1"
bytes = "0.5.5"
mime = "0.3.16"
bytes = "1.0"
mime = "0.3"
derive_more = "0.99.9"
thiserror = "1.0.20"
async-trait = "0.1.36"
futures = "0.3.5"
pin-project = "0.4.22"
serde_with_macros = "1.1.0"
derive_more = "0.99"
thiserror = "1.0"
async-trait = "0.1"
futures = "0.3"
pin-project = "1.0"
serde_with_macros = "1.4"
sqlx = { version = "0.4.0-beta.1", optional = true, default-features = false, features = [
sqlx = { version = "0.5", optional = true, default-features = false, features = [
"runtime-tokio-native-tls",
"macros",
"sqlite",
] }
redis = { version = "0.16.0", optional = true }
serde_cbor = { version = "0.11.1", optional = true }
bincode = { version = "1.3.1", optional = true }
frunk = { version = "0.3.1", optional = true }
teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master", optional = true }
redis = { version = "0.20", features = ["tokio-comp"], optional = true }
serde_cbor = { version = "0.11", optional = true }
bincode = { version = "1.3", optional = true }
frunk = { version = "0.3", optional = true }
[dev-dependencies]
smart-default = "0.6.0"
rand = "0.7.3"
rand = "0.8.3"
pretty_env_logger = "0.4.0"
lazy_static = "1.4.0"
tokio = { version = "0.2.21", features = ["fs", "stream", "rt-threaded", "macros"] }
tokio = { version = "1.2.0", features = ["fs", "rt-multi-thread", "macros"] }
[package.metadata.docs.rs]
all-features = true

View file

@ -14,7 +14,7 @@
<img src="https://img.shields.io/crates/v/teloxide.svg">
</a>
<a href="https://core.telegram.org/bots/api">
<img src="https://img.shields.io/badge/API coverage-Up to 0.4.9 (inclusively)-green.svg">
<img src="https://img.shields.io/badge/API coverage-Up to 5.1 (inclusively)-green.svg">
</a>
<a href="https://t.me/teloxide">
<img src="https://img.shields.io/badge/official%20chat-t.me%2Fteloxide-blueviolet">
@ -79,13 +79,10 @@ $ rustup override set nightly
5. Run `cargo new my_bot`, enter the directory and put these lines into your `Cargo.toml`:
```toml
[dependencies]
teloxide = "0.3"
teloxide-macros = "0.3"
teloxide = "0.4"
log = "0.4.8"
pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
tokio = { version = "1.3", features = ["rt-threaded", "macros"] }
```
## API overview
@ -102,11 +99,11 @@ async fn main() {
teloxide::enable_logging!();
log::info!("Starting dices_bot...");
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
teloxide::repl(bot, |message| async move {
message.answer_dice().send().await?;
ResponseResult::<()>::Ok(())
message.answer_dice().await?;
respond(())
})
.await;
}
@ -130,7 +127,9 @@ Commands are strongly typed and defined declaratively, similar to how we define
([Full](./examples/simple_commands_bot/src/main.rs))
```rust,no_run
use teloxide::{utils::command::BotCommand, prelude::*};
use teloxide::{prelude::*, utils::command::BotCommand};
use std::error::Error;
#[derive(BotCommand)]
#[command(rename = "lowercase", description = "These commands are supported:")]
@ -143,14 +142,17 @@ enum Command {
UsernameAndAge { username: String, age: u8 },
}
async fn answer(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<()> {
async fn answer(
cx: UpdateWithCx<AutoSend<Bot>, Message>,
command: Command,
) -> Result<(), Box<dyn Error + Send + Sync>> {
match command {
Command::Help => cx.answer(Command::descriptions()).send().await?,
Command::Username(username) => {
cx.answer_str(format!("Your username is @{}.", username)).await?
cx.answer(format!("Your username is @{}.", username)).await?
}
Command::UsernameAndAge { username, age } => {
cx.answer_str(format!("Your username is @{} and age is {}.", username, age)).await?
cx.answer(format!("Your username is @{} and age is {}.", username, age)).await?
}
};
@ -162,7 +164,7 @@ async fn main() {
teloxide::enable_logging!();
log::info!("Starting simple_commands_bot...");
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
let bot_name: String = panic!("Your bot's name here");
teloxide::commands_repl(bot, bot_name, answer).await;
@ -213,8 +215,12 @@ When a user sends a message to our bot and such a dialogue does not exist yet, a
pub struct StartState;
#[teloxide(subtransition)]
async fn start(_state: StartState, cx: TransitionIn, _ans: String) -> TransitionOut<Dialogue> {
cx.answer_str("Let's start! What's your full name?").await?;
async fn start(
_state: StartState,
cx: TransitionIn<AutoSend<Bot>>,
_ans: String,
) -> TransitionOut<Dialogue> {
cx.answer("Let's start! What's your full name?").await?;
next(ReceiveFullNameState)
}
```
@ -234,10 +240,10 @@ pub struct ReceiveFullNameState;
#[teloxide(subtransition)]
async fn receive_full_name(
state: ReceiveFullNameState,
cx: TransitionIn,
cx: TransitionIn<AutoSend<Bot>>,
ans: String,
) -> TransitionOut<Dialogue> {
cx.answer_str("How old are you?").await?;
cx.answer("How old are you?").await?;
next(ReceiveAgeState::up(state, ans))
}
```
@ -259,16 +265,16 @@ pub struct ReceiveAgeState {
#[teloxide(subtransition)]
async fn receive_age_state(
state: ReceiveAgeState,
cx: TransitionIn,
cx: TransitionIn<AutoSend<Bot>>,
ans: String,
) -> TransitionOut<Dialogue> {
match ans.parse::<u8>() {
Ok(ans) => {
cx.answer_str("What's your location?").await?;
cx.answer("What's your location?").await?;
next(ReceiveLocationState::up(state, ans))
}
_ => {
cx.answer_str("Send me a number.").await?;
cx.answer("Send me a number.").await?;
next(state)
}
}
@ -293,10 +299,10 @@ pub struct ReceiveLocationState {
#[teloxide(subtransition)]
async fn receive_location(
state: ReceiveLocationState,
cx: TransitionIn,
cx: TransitionIn<AutoSend<Bot>>,
ans: String,
) -> TransitionOut<Dialogue> {
cx.answer_str(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans))
cx.answer(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans))
.await?;
exit()
}
@ -317,7 +323,7 @@ async fn main() {
teloxide::enable_logging!();
log::info!("Starting dialogue_bot...");
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
teloxide::dialogues_repl(bot, |message, dialogue| async move {
handle_message(message, dialogue).await.expect("Something wrong with the bot!")
@ -325,10 +331,13 @@ async fn main() {
.await;
}
async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> {
match cx.update.text_owned() {
async fn handle_message(
cx: UpdateWithCx<AutoSend<Bot>, Message>,
dialogue: Dialogue,
) -> TransitionOut<Dialogue> {
match cx.update.text().map(ToOwned::to_owned) {
None => {
cx.answer_str("Send me a text message.").await?;
cx.answer("Send me a text message.").await?;
next(dialogue)
}
Some(ans) => dialogue.react(cx, ans).await,
@ -376,10 +385,21 @@ The second one produces very strange compiler messages due to the `#[tokio::main
- `cbor-serializer` -- enables the [CBOR] serializer for dialogues.
- `bincode-serializer` -- enables the [Bincode] serializer for dialogues.
- `frunk` -- enables [`teloxide::utils::UpState`], which allows mapping from a structure of `field1, ..., fieldN` to a structure of `field1, ..., fieldN, fieldN+1`.
- `macros` -- re-exports macros from [`teloxide-macros`].
- `native-tls` -- enables the [`native-tls`] TLS implementation (enabled by default).
- `rustls` -- enables the [`rustls`] TLS implementation.
- `auto-send` -- enables `AutoSend` bot adaptor.
- `cache-me` -- enables the `CacheMe` bot adaptor.
- `full` -- enables all the features except `nightly`.
- `nightly` -- enables nightly-only features (see the [teloxide-core's features]).
[CBOR]: https://en.wikipedia.org/wiki/CBOR
[Bincode]: https://github.com/servo/bincode
[`teloxide::utils::UpState`]: https://docs.rs/teloxide/latest/teloxide/utils/trait.UpState.html
[`teloxide-macros`]: https://github.com/teloxide/teloxide-macros
[`native-tls`]: https://docs.rs/native-tls
[`rustls`]: https://docs.rs/rustls
[teloxide-core's features]: https://docs.rs/teloxide-core/0.2.1/teloxide_core/#cargo-features
## FAQ
**Q: Where I can ask questions?**

View file

@ -7,10 +7,10 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
teloxide = { path = "../../", features = ["macros", "auto-send"] }
log = "0.4.8"
pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../", features = ["macros"] }
tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] }
[profile.release]
lto = true
lto = true

View file

@ -1,6 +1,8 @@
use std::str::FromStr;
use std::{convert::TryInto, error::Error, str::FromStr};
use teloxide::{prelude::*, types::ChatPermissions, utils::command::BotCommand};
use teloxide::{prelude::*, utils::command::BotCommand};
use teloxide::types::ChatPermissions;
// Derive BotCommand to parse text with a command into this enumeration.
//
@ -60,20 +62,19 @@ fn calc_restrict_time(time: u32, unit: UnitOfTime) -> u32 {
}
}
type Cx = UpdateWithCx<Message>;
type Cx = UpdateWithCx<AutoSend<Bot>, Message>;
// Mute a user with a replied message.
async fn mute_user(cx: &Cx, time: u32) -> ResponseResult<()> {
async fn mute_user(cx: &Cx, time: u32) -> Result<(), Box<dyn Error + Send + Sync>> {
match cx.update.reply_to_message() {
Some(msg1) => {
cx.bot
cx.requester
.restrict_chat_member(
cx.update.chat_id(),
msg1.from().expect("Must be MessageKind::Common").id,
ChatPermissions::default(),
)
.until_date(cx.update.date + time as i32)
.send()
.until_date((cx.update.date + time as i32).try_into().unwrap())
.await?;
}
None => {
@ -84,11 +85,14 @@ async fn mute_user(cx: &Cx, time: u32) -> ResponseResult<()> {
}
// Kick a user with a replied message.
async fn kick_user(cx: &Cx) -> ResponseResult<()> {
async fn kick_user(cx: &Cx) -> Result<(), Box<dyn Error + Send + Sync>> {
match cx.update.reply_to_message() {
Some(mes) => {
// bot.unban_chat_member can also kicks a user from a group chat.
cx.bot.unban_chat_member(cx.update.chat_id(), mes.from().unwrap().id).send().await?;
cx.requester
.unban_chat_member(cx.update.chat_id(), mes.from().unwrap().id)
.send()
.await?;
}
None => {
cx.reply_to("Use this command in reply to another message").send().await?;
@ -98,16 +102,15 @@ async fn kick_user(cx: &Cx) -> ResponseResult<()> {
}
// Ban a user with replied message.
async fn ban_user(cx: &Cx, time: u32) -> ResponseResult<()> {
async fn ban_user(cx: &Cx, time: u32) -> Result<(), Box<dyn Error + Send + Sync>> {
match cx.update.reply_to_message() {
Some(message) => {
cx.bot
cx.requester
.kick_chat_member(
cx.update.chat_id(),
message.from().expect("Must be MessageKind::Common").id,
)
.until_date(cx.update.date + time as i32)
.send()
.until_date((cx.update.date + time as i32).try_into().unwrap())
.await?;
}
None => {
@ -117,7 +120,7 @@ async fn ban_user(cx: &Cx, time: u32) -> ResponseResult<()> {
Ok(())
}
async fn action(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<()> {
async fn action(cx: Cx, command: Command) -> Result<(), Box<dyn Error + Send + Sync>> {
match command {
Command::Help => cx.answer(Command::descriptions()).send().await.map(|_| ())?,
Command::Kick => kick_user(&cx).await?,
@ -137,7 +140,7 @@ async fn run() {
teloxide::enable_logging!();
log::info!("Starting admin_bot...");
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
let bot_name: String = panic!("Your bot's name here");
teloxide::commands_repl(bot, bot_name, action).await;

View file

@ -7,19 +7,17 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
teloxide = { path = "../../", features = ["frunk", "macros", "auto-send"] }
futures = "0.3.5"
tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] }
log = "0.4.8"
pretty_env_logger = "0.4.0"
derive_more = "0.99.9"
frunk = "0.3.1"
frunk_core = "0.3.1"
futures = "0.3.5"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../", features = ["frunk"] }
teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master" }
derive_more = "0.99.9"
[profile.release]
lto = true

View file

@ -4,7 +4,7 @@ use crate::dialogue::states::{
ReceiveAgeState, ReceiveFullNameState, ReceiveLocationState, StartState,
};
use derive_more::From;
use teloxide_macros::Transition;
use teloxide::macros::Transition;
#[derive(Transition, From)]
pub enum Dialogue {

View file

@ -1,6 +1,5 @@
use crate::dialogue::{states::receive_location::ReceiveLocationState, Dialogue};
use teloxide::prelude::*;
use teloxide_macros::teloxide;
#[derive(Generic)]
pub struct ReceiveAgeState {
@ -10,16 +9,16 @@ pub struct ReceiveAgeState {
#[teloxide(subtransition)]
async fn receive_age_state(
state: ReceiveAgeState,
cx: TransitionIn,
cx: TransitionIn<AutoSend<Bot>>,
ans: String,
) -> TransitionOut<Dialogue> {
match ans.parse::<u8>() {
Ok(ans) => {
cx.answer_str("What's your location?").await?;
cx.answer("What's your location?").await?;
next(ReceiveLocationState::up(state, ans))
}
_ => {
cx.answer_str("Send me a number.").await?;
cx.answer("Send me a number.").await?;
next(state)
}
}

View file

@ -1,6 +1,5 @@
use crate::dialogue::{states::receive_age::ReceiveAgeState, Dialogue};
use teloxide::prelude::*;
use teloxide_macros::teloxide;
#[derive(Generic)]
pub struct ReceiveFullNameState;
@ -8,9 +7,9 @@ pub struct ReceiveFullNameState;
#[teloxide(subtransition)]
async fn receive_full_name(
state: ReceiveFullNameState,
cx: TransitionIn,
cx: TransitionIn<AutoSend<Bot>>,
ans: String,
) -> TransitionOut<Dialogue> {
cx.answer_str("How old are you?").await?;
cx.answer("How old are you?").await?;
next(ReceiveAgeState::up(state, ans))
}

View file

@ -1,6 +1,5 @@
use crate::dialogue::Dialogue;
use teloxide::prelude::*;
use teloxide_macros::teloxide;
#[derive(Generic)]
pub struct ReceiveLocationState {
@ -11,10 +10,10 @@ pub struct ReceiveLocationState {
#[teloxide(subtransition)]
async fn receive_location(
state: ReceiveLocationState,
cx: TransitionIn,
cx: TransitionIn<AutoSend<Bot>>,
ans: String,
) -> TransitionOut<Dialogue> {
cx.answer_str(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans))
cx.answer(format!("Full name: {}\nAge: {}\nLocation: {}", state.full_name, state.age, ans))
.await?;
exit()
}

View file

@ -1,11 +1,14 @@
use crate::dialogue::{states::ReceiveFullNameState, Dialogue};
use teloxide::prelude::*;
use teloxide_macros::teloxide;
pub struct StartState;
#[teloxide(subtransition)]
async fn start(_state: StartState, cx: TransitionIn, _ans: String) -> TransitionOut<Dialogue> {
cx.answer_str("Let's start! What's your full name?").await?;
async fn start(
_state: StartState,
cx: TransitionIn<AutoSend<Bot>>,
_ans: String,
) -> TransitionOut<Dialogue> {
cx.answer("Let's start! What's your full name?").await?;
next(ReceiveFullNameState)
}

View file

@ -34,7 +34,7 @@ async fn run() {
teloxide::enable_logging!();
log::info!("Starting dialogue_bot...");
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
teloxide::dialogues_repl(bot, |message, dialogue| async move {
handle_message(message, dialogue).await.expect("Something wrong with the bot!")
@ -42,10 +42,13 @@ async fn run() {
.await;
}
async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> {
match cx.update.text_owned() {
async fn handle_message(
cx: UpdateWithCx<AutoSend<Bot>, Message>,
dialogue: Dialogue,
) -> TransitionOut<Dialogue> {
match cx.update.text().map(ToOwned::to_owned) {
None => {
cx.answer_str("Send me a text message.").await?;
cx.answer("Send me a text message.").await?;
next(dialogue)
}
Some(ans) => dialogue.react(cx, ans).await,

View file

@ -7,10 +7,10 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
teloxide = { path = "../../", features = ["auto-send"] }
log = "0.4.8"
pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../" }
tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] }
[profile.release]
lto = true
lto = true

View file

@ -11,10 +11,10 @@ async fn run() {
teloxide::enable_logging!();
log::info!("Starting dices_bot...");
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
teloxide::repl(bot, |message| async move {
message.answer_dice().send().await?;
message.answer_dice().await?;
respond(())
})
.await;

View file

@ -7,12 +7,13 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
teloxide = { path = "../../", features = ["auto-send"] }
log = "0.4.8"
pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../" }
tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] }
tokio-stream = "0.1.4"
# Used to setup a webhook
warp = "0.2.2"
warp = "0.3.0"
reqwest = "0.10.4"
serde_json = "1.0.50"

View file

@ -1,10 +1,11 @@
// The version of Heroku ping-pong-bot, which uses a webhook to receive updates
// from Telegram, instead of long polling.
use teloxide::{dispatching::update_listeners, prelude::*};
use teloxide::{dispatching::update_listeners, prelude::*, types::Update};
use std::{convert::Infallible, env, net::SocketAddr};
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
use warp::Filter;
use reqwest::StatusCode;
@ -19,7 +20,7 @@ async fn handle_rejection(error: warp::Rejection) -> Result<impl warp::Reply, In
Ok(StatusCode::INTERNAL_SERVER_ERROR)
}
pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infallible> {
pub async fn webhook<'a>(bot: AutoSend<Bot>) -> impl update_listeners::UpdateListener<Infallible> {
// Heroku defines auto defines a port value
let teloxide_token = env::var("TELOXIDE_TOKEN").expect("TELOXIDE_TOKEN env variable missing");
let port: u16 = env::var("PORT")
@ -31,7 +32,7 @@ pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infa
let path = format!("bot{}", teloxide_token);
let url = format!("https://{}/{}", host, path);
bot.set_webhook(url).send().await.expect("Cannot setup a webhook");
bot.set_webhook(url).await.expect("Cannot setup a webhook");
let (tx, rx) = mpsc::unbounded_channel();
@ -39,20 +40,7 @@ pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infa
.and(warp::path(path))
.and(warp::body::json())
.map(move |json: serde_json::Value| {
let try_parse = match serde_json::from_str(&json.to_string()) {
Ok(update) => Ok(update),
Err(error) => {
log::error!(
"Cannot parse an update.\nError: {:?}\nValue: {}\n\
This is a bug in teloxide, please open an issue here: \
https://github.com/teloxide/teloxide/issues.",
error,
json
);
Err(error)
}
};
if let Ok(update) = try_parse {
if let Ok(update) = Update::try_parse(&json) {
tx.send(Ok(update)).expect("Cannot send an incoming update from the webhook")
}
@ -64,21 +52,21 @@ pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infa
let address = format!("0.0.0.0:{}", port);
tokio::spawn(serve.run(address.parse::<SocketAddr>().unwrap()));
rx
UnboundedReceiverStream::new(rx)
}
async fn run() {
teloxide::enable_logging!();
log::info!("Starting heroku_ping_pong_bot...");
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
let cloned_bot = bot.clone();
teloxide::repl_with_listener(
bot,
|message| async move {
message.answer_str("pong").await?;
ResponseResult::<()>::Ok(())
message.answer("pong").await?;
respond(())
},
webhook(cloned_bot).await,
)

View file

@ -7,12 +7,13 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
teloxide = { path = "../../", features = ["auto-send"] }
log = "0.4.8"
pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../" }
tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] }
tokio-stream = "0.1.4"
# Used to setup a webhook
warp = "0.2.2"
warp = "0.3.0"
reqwest = "0.10.4"
serde_json = "1.0.50"

View file

@ -1,10 +1,11 @@
// The version of ngrok ping-pong-bot, which uses a webhook to receive updates
// from Telegram, instead of long polling.
use teloxide::{dispatching::update_listeners, prelude::*};
use teloxide::{dispatching::update_listeners, prelude::*, types::Update};
use std::{convert::Infallible, net::SocketAddr};
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
use warp::Filter;
use reqwest::StatusCode;
@ -19,11 +20,10 @@ async fn handle_rejection(error: warp::Rejection) -> Result<impl warp::Reply, In
Ok(StatusCode::INTERNAL_SERVER_ERROR)
}
pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infallible> {
pub async fn webhook<'a>(bot: AutoSend<Bot>) -> impl update_listeners::UpdateListener<Infallible> {
// You might want to specify a self-signed certificate via .certificate
// method on SetWebhook.
bot.set_webhook("Your HTTPS ngrok URL here. Get it by 'ngrok http 80'")
.send()
.await
.expect("Cannot setup a webhook");
@ -46,21 +46,21 @@ pub async fn webhook<'a>(bot: Bot) -> impl update_listeners::UpdateListener<Infa
// setup a self-signed TLS certificate.
tokio::spawn(serve.run("127.0.0.1:80".parse::<SocketAddr>().unwrap()));
rx
UnboundedReceiverStream::new(rx)
}
async fn run() {
teloxide::enable_logging!();
log::info!("Starting ngrok_ping_pong_bot...");
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
let cloned_bot = bot.clone();
teloxide::repl_with_listener(
bot,
|message| async move {
message.answer_str("pong").await?;
ResponseResult::<()>::Ok(())
message.answer("pong").await?;
respond(())
},
webhook(cloned_bot).await,
)

View file

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

View file

@ -9,6 +9,7 @@ use states::*;
use teloxide::{
dispatching::dialogue::{serializer::Bincode, RedisStorage, Storage},
prelude::*,
RequestError,
};
use thiserror::Error;
@ -22,7 +23,7 @@ enum Error {
StorageError(#[from] StorageError),
}
type In = DialogueWithCx<Message, Dialogue, StorageError>;
type In = DialogueWithCx<AutoSend<Bot>, Message, Dialogue, StorageError>;
#[tokio::main]
async fn main() {
@ -30,7 +31,7 @@ async fn main() {
}
async fn run() {
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
Dispatcher::new(bot)
.messages_handler(DialogueDispatcher::with_storage(
|DialogueWithCx { cx, dialogue }: In| async move {
@ -47,10 +48,13 @@ async fn run() {
.await;
}
async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> {
match cx.update.text_owned() {
async fn handle_message(
cx: UpdateWithCx<AutoSend<Bot>, Message>,
dialogue: Dialogue,
) -> TransitionOut<Dialogue> {
match cx.update.text().map(ToOwned::to_owned) {
None => {
cx.answer_str("Send me a text message.").await?;
cx.answer("Send me a text message.").await?;
next(dialogue)
}
Some(ans) => dialogue.react(cx, ans).await,

View file

@ -1,4 +1,4 @@
use teloxide_macros::Transition;
use teloxide::dispatching::dialogue::Transition;
use serde::{Deserialize, Serialize};

View file

@ -1,15 +1,18 @@
use teloxide::prelude::*;
use teloxide_macros::teloxide;
use super::states::*;
#[teloxide(subtransition)]
async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOut<Dialogue> {
async fn start(
state: StartState,
cx: TransitionIn<AutoSend<Bot>>,
ans: String,
) -> TransitionOut<Dialogue> {
if let Ok(number) = ans.parse() {
cx.answer_str(format!("Remembered number {}. Now use /get or /reset", number)).await?;
cx.answer(format!("Remembered number {}. Now use /get or /reset", number)).await?;
next(HaveNumberState { number })
} else {
cx.answer_str("Please, send me a number").await?;
cx.answer("Please, send me a number").await?;
next(state)
}
}
@ -17,19 +20,19 @@ async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOu
#[teloxide(subtransition)]
async fn have_number(
state: HaveNumberState,
cx: TransitionIn,
cx: TransitionIn<AutoSend<Bot>>,
ans: String,
) -> TransitionOut<Dialogue> {
let num = state.number;
if ans.starts_with("/get") {
cx.answer_str(format!("Here is your number: {}", num)).await?;
cx.answer(format!("Here is your number: {}", num)).await?;
next(state)
} else if ans.starts_with("/reset") {
cx.answer_str("Resetted number").await?;
cx.answer("Resetted number").await?;
next(StartState)
} else {
cx.answer_str("Please, send /get or /reset").await?;
cx.answer("Please, send /get or /reset").await?;
next(state)
}
}

View file

@ -7,8 +7,9 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
teloxide = { path = "../../", features = ["auto-send"] }
log = "0.4.8"
pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../" }
tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] }
tokio-stream = "0.1.3"
lazy_static = "1.4.0"

View file

@ -4,6 +4,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
use lazy_static::lazy_static;
use teloxide::prelude::*;
use tokio_stream::wrappers::UnboundedReceiverStream;
lazy_static! {
static ref MESSAGES_TOTAL: AtomicU64 = AtomicU64::new(0);
@ -18,15 +19,15 @@ async fn run() {
teloxide::enable_logging!();
log::info!("Starting shared_state_bot...");
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
Dispatcher::new(bot)
.messages_handler(|rx: DispatcherHandlerRx<Message>| {
rx.for_each_concurrent(None, |message| async move {
.messages_handler(|rx: DispatcherHandlerRx<AutoSend<Bot>, Message>| {
UnboundedReceiverStream::new(rx).for_each_concurrent(None, |message| async move {
let previous = MESSAGES_TOTAL.fetch_add(1, Ordering::Relaxed);
message
.answer_str(format!("I received {} messages in total.", previous))
.answer(format!("I received {} messages in total.", previous))
.await
.log_on_error()
.await;

View file

@ -7,7 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
teloxide = { path = "../../", features = ["macros", "auto-send"] }
log = "0.4.8"
pretty_env_logger = "0.4.0"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../", features = ["macros"] }
tokio = { version = "1.3.0", features = ["rt-multi-thread", "macros"] }

View file

@ -1,5 +1,7 @@
use teloxide::{prelude::*, utils::command::BotCommand};
use std::error::Error;
#[derive(BotCommand)]
#[command(rename = "lowercase", description = "These commands are supported:")]
enum Command {
@ -11,14 +13,17 @@ enum Command {
UsernameAndAge { username: String, age: u8 },
}
async fn answer(cx: UpdateWithCx<Message>, command: Command) -> ResponseResult<()> {
async fn answer(
cx: UpdateWithCx<AutoSend<Bot>, Message>,
command: Command,
) -> Result<(), Box<dyn Error + Send + Sync>> {
match command {
Command::Help => cx.answer(Command::descriptions()).send().await?,
Command::Username(username) => {
cx.answer_str(format!("Your username is @{}.", username)).await?
cx.answer(format!("Your username is @{}.", username)).await?
}
Command::UsernameAndAge { username, age } => {
cx.answer_str(format!("Your username is @{} and age is {}.", username, age)).await?
cx.answer(format!("Your username is @{} and age is {}.", username, age)).await?
}
};
@ -34,7 +39,7 @@ async fn run() {
teloxide::enable_logging!();
log::info!("Starting simple_commands_bot...");
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
let bot_name: String = panic!("Your bot's name here");
teloxide::commands_repl(bot, bot_name, answer).await;

View file

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

View file

@ -7,12 +7,13 @@ mod transitions;
use states::*;
use teloxide::{
dispatching::dialogue::{serializer::JSON, SqliteStorage, Storage},
dispatching::dialogue::{serializer::Json, SqliteStorage, Storage},
prelude::*,
RequestError,
};
use thiserror::Error;
type StorageError = <SqliteStorage<JSON> as Storage<Dialogue>>::Error;
type StorageError = <SqliteStorage<Json> as Storage<Dialogue>>::Error;
#[derive(Debug, Error)]
enum Error {
@ -22,12 +23,15 @@ enum Error {
StorageError(#[from] StorageError),
}
type In = DialogueWithCx<Message, Dialogue, StorageError>;
type In = DialogueWithCx<AutoSend<Bot>, Message, Dialogue, StorageError>;
async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> TransitionOut<Dialogue> {
match cx.update.text_owned() {
async fn handle_message(
cx: UpdateWithCx<AutoSend<Bot>, Message>,
dialogue: Dialogue,
) -> TransitionOut<Dialogue> {
match cx.update.text().map(ToOwned::to_owned) {
None => {
cx.answer_str("Send me a text message.").await?;
cx.answer("Send me a text message.").await?;
next(dialogue)
}
Some(ans) => dialogue.react(cx, ans).await,
@ -36,14 +40,15 @@ async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> Transi
#[tokio::main]
async fn main() {
let bot = Bot::from_env();
let bot = Bot::from_env().auto_send();
Dispatcher::new(bot)
.messages_handler(DialogueDispatcher::with_storage(
|DialogueWithCx { cx, dialogue }: In| async move {
let dialogue = dialogue.expect("std::convert::Infallible");
handle_message(cx, dialogue).await.expect("Something wrong with the bot!")
},
SqliteStorage::open("db.sqlite", JSON).await.unwrap(),
SqliteStorage::open("db.sqlite", Json).await.unwrap(),
))
.dispatch()
.await;

View file

@ -1,4 +1,4 @@
use teloxide_macros::Transition;
use teloxide::macros::Transition;
use serde::{Deserialize, Serialize};

View file

@ -1,15 +1,19 @@
use teloxide::prelude::*;
use teloxide_macros::teloxide;
use teloxide::macros::teloxide;
use super::states::*;
#[teloxide(subtransition)]
async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOut<Dialogue> {
async fn start(
state: StartState,
cx: TransitionIn<AutoSend<Bot>>,
ans: String,
) -> TransitionOut<Dialogue> {
if let Ok(number) = ans.parse() {
cx.answer_str(format!("Remembered number {}. Now use /get or /reset", number)).await?;
cx.answer(format!("Remembered number {}. Now use /get or /reset", number)).await?;
next(HaveNumberState { number })
} else {
cx.answer_str("Please, send me a number").await?;
cx.answer("Please, send me a number").await?;
next(state)
}
}
@ -17,19 +21,19 @@ async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOu
#[teloxide(subtransition)]
async fn have_number(
state: HaveNumberState,
cx: TransitionIn,
cx: TransitionIn<AutoSend<Bot>>,
ans: String,
) -> TransitionOut<Dialogue> {
let num = state.number;
if ans.starts_with("/get") {
cx.answer_str(format!("Here is your number: {}", num)).await?;
cx.answer(format!("Here is your number: {}", num)).await?;
next(state)
} else if ans.starts_with("/reset") {
cx.answer_str("Resetted number").await?;
cx.answer("Resetted number").await?;
next(StartState)
} else {
cx.answer_str("Please, send /get or /reset").await?;
cx.answer("Please, send /get or /reset").await?;
next(state)
}
}

View file

@ -1,6 +1,6 @@
format_code_in_doc_comments = true
wrap_comments = true
format_strings = true
merge_imports = true
imports_granularity = "Crate"
use_small_heuristics = "Max"
use_field_init_shorthand = true

File diff suppressed because it is too large Load diff

View file

@ -1,67 +0,0 @@
use tokio::io::AsyncWrite;
#[cfg(feature = "unstable-stream")]
use ::{bytes::Bytes, tokio::stream::Stream};
#[cfg(feature = "unstable-stream")]
use crate::net::download_file_stream;
use crate::{bot::Bot, net::download_file, DownloadError};
impl Bot {
/// Download a file from Telegram into `destination`.
///
/// `path` can be obtained from [`Bot::get_file`].
///
/// To download as a stream of chunks, see [`Bot::download_file_stream`].
///
/// ## Examples
///
/// ```no_run
/// use teloxide::types::File as TgFile;
/// use tokio::fs::File;
/// # use teloxide::RequestError;
/// use teloxide::{requests::Request, Bot};
///
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let bot = Bot::new("TOKEN");
/// let mut file = File::create("/home/waffle/Pictures/test.png").await?;
///
/// let TgFile { file_path, .. } = bot.get_file("*file_id*").send().await?;
/// bot.download_file(&file_path, &mut file).await?;
/// # Ok(()) }
/// ```
///
/// [`Bot::get_file`]: crate::Bot::get_file
/// [`Bot::download_file_stream`]: crate::Bot::download_file_stream
pub async fn download_file<D>(
&self,
path: &str,
destination: &mut D,
) -> Result<(), DownloadError>
where
D: AsyncWrite + Unpin,
{
download_file(&self.client, &self.token, path, destination).await
}
/// Download a file from Telegram.
///
/// `path` can be obtained from the [`Bot::get_file`].
///
/// To download into [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see
/// [`Bot::download_file`].
///
/// [`Bot::get_file`]: crate::bot::Bot::get_file
/// [`AsyncWrite`]: tokio::io::AsyncWrite
/// [`tokio::fs::File`]: tokio::fs::File
/// [`Bot::download_file`]: crate::Bot::download_file
#[cfg(feature = "unstable-stream")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(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
}
}

View file

@ -1,252 +0,0 @@
use crate::types::ParseMode;
use reqwest::{
header::{HeaderMap, CONNECTION},
Client, ClientBuilder,
};
use std::{sync::Arc, time::Duration};
mod api;
mod download;
pub(crate) const TELOXIDE_TOKEN: &str = "TELOXIDE_TOKEN";
pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY";
/// A requests sender.
///
/// No need to put it into [`Arc`], because it's already in.
///
/// [`Arc`]: std::sync::Arc
#[derive(Debug, Clone)]
pub struct Bot {
token: Arc<str>,
client: Client,
parse_mode: Option<ParseMode>,
}
impl Bot {
/// Creates new [`BotBuilder`] see it's [docs] for more
///
/// [docs]: BotBuilder
#[must_use]
pub fn builder() -> BotBuilder {
BotBuilder::new()
}
/// Creates a new `Bot` with the `TELOXIDE_TOKEN` & `TELOXIDE_PROXY`
/// environmental variables (a bot's token & a proxy) and the default
/// [`reqwest::Client`].
///
/// This function passes the value of `TELOXIDE_PROXY` into
/// [`reqwest::Proxy::all`], if it exists, otherwise returns the default
/// client.
///
/// # Panics
/// - If cannot get the `TELOXIDE_TOKEN` environmental variable.
/// - If it cannot create [`reqwest::Client`].
///
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all
#[must_use]
pub fn from_env() -> Self {
BotBuilder::new().build()
}
/// Creates a new `Bot` with the `TELOXIDE_TOKEN` environmental variable (a
/// bot's token) and your [`reqwest::Client`].
///
/// # Panics
/// If cannot get the `TELOXIDE_TOKEN` environmental variable.
///
/// # Caution
/// Your custom client might not be configured correctly to be able to work
/// in long time durations, see [issue 223].
///
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
/// [issue 223]: https://github.com/teloxide/teloxide/issues/223
#[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \
(notably default parse_mode)"]
pub fn from_env_with_client(client: Client) -> Self {
#[allow(deprecated)]
Self::with_client(&get_env(TELOXIDE_TOKEN), client)
}
/// Creates a new `Bot` with the specified token and the default
/// [`reqwest::Client`].
///
/// # Panics
/// If it cannot create [`reqwest::Client`].
///
/// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
#[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \
(notably default parse_mode)"]
pub fn new<S>(token: S) -> Self
where
S: Into<String>,
{
#[allow(deprecated)]
Self::with_client(token, build_sound_bot())
}
/// Creates a new `Bot` with the specified token and your
/// [`reqwest::Client`].
///
/// # Caution
/// Your custom client might not be configured correctly to be able to work
/// in long time durations, see [issue 223].
///
/// [`reqwest::Client`]: https://docs.rs/reqwest/latest/reqwest/struct.Client.html
/// [issue 223]: https://github.com/teloxide/teloxide/issues/223
#[deprecated = "Deprecated in favour of BotBuilder because the later provides more options \
(notably default parse_mode)"]
pub fn with_client<S>(token: S, client: Client) -> Self
where
S: Into<String>,
{
Self {
token: Into::<Arc<str>>::into(Into::<String>::into(token)),
client,
parse_mode: None,
}
}
}
/// Returns a builder with safe settings.
///
/// By "safe settings" I mean that a client will be able to work in long time
/// durations, see the [issue 223].
///
/// [issue 223]: https://github.com/teloxide/teloxide/issues/223
pub(crate) fn sound_bot() -> ClientBuilder {
let mut headers = HeaderMap::new();
headers.insert(CONNECTION, "keep-alive".parse().unwrap());
let connect_timeout = Duration::from_secs(5);
let timeout = 10;
ClientBuilder::new()
.connect_timeout(connect_timeout)
.timeout(Duration::from_secs(connect_timeout.as_secs() + timeout + 2))
.tcp_nodelay_(true)
.default_headers(headers)
}
pub(crate) fn build_sound_bot() -> Client {
sound_bot().build().expect("creating reqwest::Client")
}
fn get_env(env: &'static str) -> String {
std::env::var(env).unwrap_or_else(|_| panic!("Cannot get the {} env variable", env))
}
impl Bot {
// TODO: const fn
pub fn token(&self) -> &str {
&self.token
}
// TODO: const fn
pub fn client(&self) -> &Client {
&self.client
}
}
/// A builder of [`Bot`], supporting some extra settings.
///
/// [`Bot`]: crate::Bot
#[derive(Debug, Default)]
pub struct BotBuilder {
token: Option<String>,
client: Option<Client>,
parse_mode: Option<ParseMode>,
}
impl BotBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// Specifies a custom HTTPS client. Otherwise, the default will be used.
///
/// # Caution
/// - Your custom client might not be configured correctly to be able to
/// work
/// in long time durations, see [issue 223].
///
/// - If this method is used, the `TELOXIDE_PROXY` environmental variable
/// won't be extracted in [`BotBuilder::build`].
///
/// [issue 223]: https://github.com/teloxide/teloxide/issues/223
/// [`BotBuilder::build`]: crate::BotBuilder::build
#[must_use]
pub fn client(mut self, client: Client) -> Self {
self.client = Some(client);
self
}
/// Specified a custom token.
///
/// Otherwise, a token will be extracted from the `TELOXIDE_TOKEN`
/// environmental variable.
#[must_use]
pub fn token<S>(mut self, token: S) -> Self
where
S: Into<String>,
{
self.token = Some(token.into());
self
}
/// Specifies [`ParseMode`], which will be used during all calls to:
///
/// - [`send_message`]
/// - [`send_photo`]
/// - [`send_video`]
/// - [`send_audio`]
/// - [`send_document`]
/// - [`send_animation`]
/// - [`send_voice`]
/// - [`send_poll`]
/// - [`edit_message_text`]
/// - [`edit_message_caption`]
///
/// [`send_message`]: crate::Bot::send_message
/// [`send_photo`]: crate::Bot::send_photo
/// [`send_video`]: crate::Bot::send_video
/// [`send_audio`]: crate::Bot::send_audio
/// [`send_document`]: crate::Bot::send_document
/// [`send_animation`]: crate::Bot::send_animation
/// [`send_voice`]: crate::Bot::send_voice
/// [`send_poll`]: crate::Bot::send_poll
/// [`edit_message_text`]: crate::Bot::edit_message_text
/// [`edit_message_caption`]: crate::Bot::edit_message_caption
#[must_use]
pub fn parse_mode(mut self, parse_mode: ParseMode) -> Self {
self.parse_mode = Some(parse_mode);
self
}
/// Builds [`Bot`].
///
/// This method will attempt to build a new client with a proxy, specified
/// in the `TELOXIDE_PROXY` (passed into [`reqwest::Proxy::all`])
/// environmental variable, if a client haven't been specified. If
/// `TELOXIDE_PROXY` is unspecified, it'll use no proxy.
///
/// # Panics
/// - If cannot get the `TELOXIDE_TOKEN` environmental variable.
/// - If it cannot create [`reqwest::Client`].
///
/// [`reqwest::Client`]: https://docs.rs/reqwest/0.10.1/reqwest/struct.Client.html
///
/// [`Bot`]: crate::Bot
/// [`reqwest::Proxy::all`]: https://docs.rs/reqwest/latest/reqwest/struct.Proxy.html#method.all
#[must_use]
pub fn build(self) -> Bot {
Bot {
client: self.client.unwrap_or_else(crate::utils::client_from_env),
token: self.token.unwrap_or_else(|| get_env(TELOXIDE_TOKEN)).into(),
parse_mode: self.parse_mode,
}
}
}

View file

@ -11,6 +11,8 @@ use tokio::sync::mpsc;
use lockfree::map::Map;
use std::sync::{Arc, Mutex};
use teloxide_core::requests::Requester;
use tokio_stream::wrappers::UnboundedReceiverStream;
/// A dispatcher of dialogues.
///
@ -22,7 +24,7 @@ use std::sync::{Arc, Mutex};
///
/// [`Dispatcher`]: crate::dispatching::Dispatcher
/// [`DispatcherHandler`]: crate::dispatching::DispatcherHandler
pub struct DialogueDispatcher<D, S, H, Upd> {
pub struct DialogueDispatcher<R, D, S, H, Upd> {
storage: Arc<S>,
handler: Arc<H>,
_phantom: PhantomData<Mutex<D>>,
@ -33,12 +35,12 @@ pub struct DialogueDispatcher<D, S, H, Upd> {
/// A value is the TX part of an unbounded asynchronous MPSC channel. A
/// handler that executes updates from the same chat ID sequentially
/// handles the RX part.
senders: Arc<Map<i64, mpsc::UnboundedSender<UpdateWithCx<Upd>>>>,
senders: Arc<Map<i64, mpsc::UnboundedSender<UpdateWithCx<R, Upd>>>>,
}
impl<D, H, Upd> DialogueDispatcher<D, InMemStorage<D>, H, Upd>
impl<R, D, H, Upd> DialogueDispatcher<R, D, InMemStorage<D>, H, Upd>
where
H: DialogueDispatcherHandler<Upd, D, Infallible> + Send + Sync + 'static,
H: DialogueDispatcherHandler<R, Upd, D, Infallible> + Send + Sync + 'static,
Upd: GetChatId + Send + 'static,
D: Default + Send + 'static,
{
@ -57,9 +59,9 @@ where
}
}
impl<D, S, H, Upd> DialogueDispatcher<D, S, H, Upd>
impl<R, D, S, H, Upd> DialogueDispatcher<R, D, S, H, Upd>
where
H: DialogueDispatcherHandler<Upd, D, S::Error> + Send + Sync + 'static,
H: DialogueDispatcherHandler<R, Upd, D, S::Error> + Send + Sync + 'static,
Upd: GetChatId + Send + 'static,
D: Default + Send + 'static,
S: Storage<D> + Send + Sync + 'static,
@ -77,14 +79,17 @@ where
}
#[must_use]
fn new_tx(&self) -> mpsc::UnboundedSender<UpdateWithCx<Upd>> {
fn new_tx(&self) -> mpsc::UnboundedSender<UpdateWithCx<R, Upd>>
where
R: Requester + Send + 'static,
{
let (tx, rx) = mpsc::unbounded_channel();
let storage = Arc::clone(&self.storage);
let handler = Arc::clone(&self.handler);
let senders = Arc::clone(&self.senders);
tokio::spawn(rx.for_each(move |cx: UpdateWithCx<Upd>| {
tokio::spawn(UnboundedReceiverStream::new(rx).for_each(move |cx: UpdateWithCx<R, Upd>| {
let storage = Arc::clone(&storage);
let handler = Arc::clone(&handler);
let senders = Arc::clone(&senders);
@ -123,21 +128,25 @@ where
}
}
impl<D, S, H, Upd> DispatcherHandler<Upd> for DialogueDispatcher<D, S, H, Upd>
impl<R, D, S, H, Upd> DispatcherHandler<R, Upd> for DialogueDispatcher<R, D, S, H, Upd>
where
H: DialogueDispatcherHandler<Upd, D, S::Error> + Send + Sync + 'static,
H: DialogueDispatcherHandler<R, Upd, D, S::Error> + Send + Sync + 'static,
Upd: GetChatId + Send + 'static,
D: Default + Send + 'static,
S: Storage<D> + Send + Sync + 'static,
S::Error: Send + 'static,
R: Requester + Send,
{
fn handle(self, updates: mpsc::UnboundedReceiver<UpdateWithCx<Upd>>) -> BoxFuture<'static, ()>
fn handle(
self,
updates: mpsc::UnboundedReceiver<UpdateWithCx<R, Upd>>,
) -> BoxFuture<'static, ()>
where
UpdateWithCx<Upd>: 'static,
UpdateWithCx<R, Upd>: 'static,
{
let this = Arc::new(self);
updates
UnboundedReceiverStream::new(updates)
.for_each(move |cx| {
let this = Arc::clone(&this);
let chat_id = cx.update.chat_id();
@ -168,12 +177,12 @@ where
mod tests {
use super::*;
use crate::Bot;
use futures::{stream, StreamExt};
use lazy_static::lazy_static;
use teloxide_core::Bot;
use tokio::{
sync::{mpsc, Mutex},
time::{delay_for, Duration},
time::Duration,
};
#[tokio::test]
@ -183,7 +192,7 @@ mod tests {
struct MyUpdate {
chat_id: i64,
unique_number: u32,
};
}
impl MyUpdate {
fn new(chat_id: i64, unique_number: u32) -> Self {
@ -203,9 +212,9 @@ mod tests {
static ref SEQ3: Mutex<Vec<u32>> = Mutex::new(Vec::new());
}
let dispatcher =
DialogueDispatcher::new(|cx: DialogueWithCx<MyUpdate, (), Infallible>| async move {
delay_for(Duration::from_millis(300)).await;
let dispatcher = DialogueDispatcher::new(
|cx: DialogueWithCx<Bot, MyUpdate, (), Infallible>| async move {
tokio::time::sleep(Duration::from_millis(300)).await;
match cx.cx.update {
MyUpdate { chat_id: 1, unique_number } => {
@ -221,7 +230,8 @@ mod tests {
}
DialogueStage::Next(())
});
},
);
let updates = stream::iter(
vec![
@ -248,8 +258,8 @@ mod tests {
MyUpdate::new(3, 1611),
]
.into_iter()
.map(|update| UpdateWithCx { update, bot: Bot::new("Doesn't matter here") })
.collect::<Vec<UpdateWithCx<MyUpdate>>>(),
.map(|update| UpdateWithCx { update, requester: Bot::new("Doesn't matter here") })
.collect::<Vec<UpdateWithCx<Bot, MyUpdate>>>(),
);
let (tx, rx) = mpsc::unbounded_channel();
@ -269,7 +279,7 @@ mod tests {
dispatcher.handle(rx).await;
// Wait until our futures to be finished.
delay_for(Duration::from_millis(3000)).await;
tokio::time::sleep(Duration::from_millis(3000)).await;
assert_eq!(*SEQ1.lock().await, vec![174, 125, 2, 193, 104, 7, 7778]);
assert_eq!(*SEQ2.lock().await, vec![411, 515, 623, 2222, 737, 10, 55456]);

View file

@ -8,24 +8,32 @@ use std::{future::Future, sync::Arc};
/// overview](crate::dispatching::dialogue).
///
/// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher
pub trait DialogueDispatcherHandler<Upd, D, E> {
pub trait DialogueDispatcherHandler<R, Upd, D, E> {
#[must_use]
fn handle(
self: Arc<Self>,
cx: DialogueWithCx<Upd, D, E>,
cx: DialogueWithCx<R, Upd, D, E>,
) -> BoxFuture<'static, DialogueStage<D>>
where
DialogueWithCx<Upd, D, E>: Send + 'static;
DialogueWithCx<R, Upd, D, E>: Send + 'static,
R: Send,
Upd: Send,
D: Send,
E: Send;
}
impl<Upd, D, E, F, Fut> DialogueDispatcherHandler<Upd, D, E> for F
impl<R, Upd, D, E, F, Fut> DialogueDispatcherHandler<R, Upd, D, E> for F
where
F: Fn(DialogueWithCx<Upd, D, E>) -> Fut + Send + Sync + 'static,
F: Fn(DialogueWithCx<R, Upd, D, E>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = DialogueStage<D>> + Send + 'static,
{
fn handle(self: Arc<Self>, cx: DialogueWithCx<Upd, D, E>) -> BoxFuture<'static, Fut::Output>
fn handle(self: Arc<Self>, cx: DialogueWithCx<R, Upd, D, E>) -> BoxFuture<'static, Fut::Output>
where
DialogueWithCx<Upd, D, E>: Send + 'static,
DialogueWithCx<R, Upd, D, E>: Send + 'static,
R: Send,
Upd: Send,
D: Send,
E: Send,
{
Box::pin(async move { self(cx).await })
}

View file

@ -1,5 +1,6 @@
use crate::dispatching::{dialogue::GetChatId, UpdateWithCx};
use std::fmt::Debug;
use teloxide_core::requests::Requester;
/// A context of a [`DialogueDispatcher`]'s message handler.
///
@ -8,21 +9,22 @@ use std::fmt::Debug;
///
/// [`DialogueDispatcher`]: crate::dispatching::dialogue::DialogueDispatcher
#[derive(Debug)]
pub struct DialogueWithCx<Upd, D, E> {
pub cx: UpdateWithCx<Upd>,
pub struct DialogueWithCx<R, Upd, D, E> {
pub cx: UpdateWithCx<R, Upd>,
pub dialogue: Result<D, E>,
}
impl<Upd, D, E> DialogueWithCx<Upd, D, E> {
impl<Upd, R, D, E> DialogueWithCx<R, Upd, D, E> {
/// Creates a new instance with the provided fields.
pub fn new(cx: UpdateWithCx<Upd>, dialogue: D) -> Self {
pub fn new(cx: UpdateWithCx<R, Upd>, dialogue: D) -> Self {
Self { cx, dialogue: Ok(dialogue) }
}
}
impl<Upd, D, E> GetChatId for DialogueWithCx<Upd, D, E>
impl<Upd, R, D, E> GetChatId for DialogueWithCx<R, Upd, D, E>
where
Upd: GetChatId,
R: Requester,
{
fn chat_id(&self) -> i64 {
self.cx.update.chat_id()

View file

@ -1,4 +1,4 @@
use crate::types::Message;
use teloxide_core::types::Message;
/// Something that has a chat ID.
pub trait GetChatId {

View file

@ -33,7 +33,7 @@
//! # #[cfg(feature = "macros")] {
//! use std::convert::Infallible;
//!
//! use teloxide::{dispatching::dialogue::Transition, prelude::*, teloxide};
//! use teloxide::{dispatching::dialogue::Transition, prelude::*, teloxide, RequestError};
//!
//! struct _1State;
//! struct _2State;
@ -42,17 +42,17 @@
//! type Out = TransitionOut<D, RequestError>;
//!
//! #[teloxide(subtransition)]
//! async fn _1_transition(_state: _1State, _cx: TransitionIn) -> Out {
//! async fn _1_transition(_state: _1State, _cx: TransitionIn<AutoSend<Bot>>) -> Out {
//! todo!()
//! }
//!
//! #[teloxide(subtransition)]
//! async fn _2_transition(_state: _2State, _cx: TransitionIn) -> Out {
//! async fn _2_transition(_state: _2State, _cx: TransitionIn<AutoSend<Bot>>) -> Out {
//! todo!()
//! }
//!
//! #[teloxide(subtransition)]
//! async fn _3_transition(_state: _3State, _cx: TransitionIn) -> Out {
//! async fn _3_transition(_state: _3State, _cx: TransitionIn<AutoSend<Bot>>) -> Out {
//! todo!()
//! }
//!
@ -69,7 +69,7 @@
//! }
//! }
//!
//! type In = DialogueWithCx<Message, D, Infallible>;
//! type In = DialogueWithCx<AutoSend<Bot>, Message, D, Infallible>;
//!
//! #[tokio::main]
//! async fn main() {
@ -80,7 +80,7 @@
//! teloxide::enable_logging!();
//! log::info!("Starting dialogue_bot!");
//!
//! let bot = Bot::from_env();
//! let bot = Bot::from_env().auto_send();
//!
//! Dispatcher::new(bot)
//! .messages_handler(DialogueDispatcher::new(
@ -158,13 +158,11 @@ pub use transition::{
};
#[cfg(feature = "macros")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
pub use teloxide_macros::Transition;
#[cfg(feature = "redis-storage")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))]
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))]
pub use storage::{RedisStorage, RedisStorageError};
#[cfg(feature = "sqlite-storage")]

View file

@ -14,8 +14,7 @@ use futures::future::BoxFuture;
pub use self::{in_mem_storage::InMemStorage, trace_storage::TraceStorage};
#[cfg(feature = "redis-storage")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))]
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "redis-storage")))]
pub use redis_storage::{RedisStorage, RedisStorageError};
pub use serializer::Serializer;
use std::sync::Arc;

View file

@ -91,14 +91,13 @@ where
Box::pin(async move {
let dialogue =
self.serializer.serialize(&dialogue).map_err(RedisStorageError::SerdeError)?;
Ok(self
.conn
self.conn
.lock()
.await
.getset::<_, Vec<u8>, Option<Vec<u8>>>(chat_id, dialogue)
.await?
.map(|d| self.serializer.deserialize(&d).map_err(RedisStorageError::SerdeError))
.transpose()?)
.transpose()
})
}
}

View file

@ -11,9 +11,9 @@ pub trait Serializer<D> {
}
/// The JSON serializer for memory storages.
pub struct JSON;
pub struct Json;
impl<D> Serializer<D> for JSON
impl<D> Serializer<D> for Json
where
D: Serialize + DeserializeOwned,
{
@ -32,14 +32,12 @@ where
///
/// [CBOR]: https://en.wikipedia.org/wiki/CBOR
#[cfg(feature = "cbor-serializer")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))]
pub struct CBOR;
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))]
pub struct Cbor;
#[cfg(feature = "cbor-serializer")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))]
impl<D> Serializer<D> for CBOR
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "cbor-serializer")))]
impl<D> Serializer<D> for Cbor
where
D: Serialize + DeserializeOwned,
{
@ -58,13 +56,11 @@ where
///
/// [Bincode]: https://github.com/servo/bincode
#[cfg(feature = "bincode-serializer")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "bincode-serializer")))]
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "bincode-serializer")))]
pub struct Bincode;
#[cfg(feature = "bincode-serializer")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "bincode-serializer")))]
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "bincode-serializer")))]
impl<D> Serializer<D> for Bincode
where
D: Serialize + DeserializeOwned,

View file

@ -86,12 +86,10 @@ where
dialogue: D,
) -> BoxFuture<'static, Result<Option<D>, Self::Error>> {
Box::pin(async move {
let prev_dialogue = match get_dialogue(&self.pool, chat_id).await? {
Some(d) => {
Some(self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)?)
}
_ => None,
};
let prev_dialogue = get_dialogue(&self.pool, chat_id)
.await?
.map(|d| self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError))
.transpose()?;
let upd_dialogue =
self.serializer.serialize(&dialogue).map_err(SqliteStorageError::SerdeError)?;
self.pool
@ -114,7 +112,7 @@ where
}
#[derive(sqlx::FromRow)]
struct DialogueDBRow {
struct DialogueDbRow {
dialogue: Vec<u8>,
}
@ -122,16 +120,11 @@ async fn get_dialogue(
pool: &SqlitePool,
chat_id: i64,
) -> Result<Option<Box<Vec<u8>>>, sqlx::Error> {
Ok(
match sqlx::query_as::<_, DialogueDBRow>(
"SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?",
)
.bind(chat_id)
.fetch_optional(pool)
.await?
{
Some(r) => Some(Box::new(r.dialogue)),
_ => None,
},
Ok(sqlx::query_as::<_, DialogueDbRow>(
"SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?",
)
.bind(chat_id)
.fetch_optional(pool)
.await?
.map(|r| Box::new(r.dialogue)))
}

View file

@ -1,20 +1,19 @@
use crate::{
dispatching::{dialogue::DialogueStage, UpdateWithCx},
types::Message,
};
use crate::dispatching::{dialogue::DialogueStage, UpdateWithCx};
use futures::future::BoxFuture;
use teloxide_core::types::Message;
/// Represents a transition function of a dialogue FSM.
pub trait Transition: Sized {
type Aux;
type Error;
type Requester;
/// Turns itself into another state, depending on the input message.
///
/// `aux` will be passed to each subtransition function.
fn react(
self,
cx: TransitionIn,
cx: TransitionIn<Self::Requester>,
aux: Self::Aux,
) -> BoxFuture<'static, TransitionOut<Self, Self::Error>>;
}
@ -29,6 +28,7 @@ where
type Aux;
type Dialogue;
type Error;
type Requester;
/// Turns itself into another state, depending on the input message.
///
@ -36,7 +36,7 @@ where
/// message's text.
fn react(
self,
cx: TransitionIn,
cx: TransitionIn<Self::Requester>,
aux: Self::Aux,
) -> BoxFuture<'static, TransitionOut<Self::Dialogue, Self::Error>>;
}
@ -44,6 +44,7 @@ where
/// A type returned from a FSM subtransition function.
///
/// Now it is used only inside `#[teloxide(subtransition)]` for type inference.
#[doc(hidden)]
pub trait SubtransitionOutputType {
type Output;
type Error;
@ -55,7 +56,7 @@ impl<D, E> SubtransitionOutputType for TransitionOut<D, E> {
}
/// An input passed into a FSM (sub)transition function.
pub type TransitionIn = UpdateWithCx<Message>;
pub type TransitionIn<R> = UpdateWithCx<R, Message>;
/// A type returned from a FSM (sub)transition function.
pub type TransitionOut<D, E = crate::RequestError> = Result<DialogueStage<D>, E>;

View file

@ -3,34 +3,37 @@ use crate::{
update_listeners, update_listeners::UpdateListener, DispatcherHandler, UpdateWithCx,
},
error_handlers::{ErrorHandler, LoggingErrorHandler},
types::{
CallbackQuery, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer,
PreCheckoutQuery, ShippingQuery, UpdateKind,
},
Bot,
};
use futures::StreamExt;
use std::{fmt::Debug, sync::Arc};
use teloxide_core::{
requests::Requester,
types::{
CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll,
PollAnswer, PreCheckoutQuery, ShippingQuery, UpdateKind,
},
};
use tokio::sync::mpsc;
type Tx<Upd> = Option<mpsc::UnboundedSender<UpdateWithCx<Upd>>>;
type Tx<Upd, R> = Option<mpsc::UnboundedSender<UpdateWithCx<Upd, R>>>;
#[macro_use]
mod macros {
/// Pushes an update to a queue.
macro_rules! send {
($bot:expr, $tx:expr, $update:expr, $variant:expr) => {
send($bot, $tx, $update, stringify!($variant));
($requester:expr, $tx:expr, $update:expr, $variant:expr) => {
send($requester, $tx, $update, stringify!($variant));
};
}
}
fn send<'a, Upd>(bot: &'a Bot, tx: &'a Tx<Upd>, update: Upd, variant: &'static str)
fn send<'a, R, Upd>(requester: &'a R, tx: &'a Tx<R, Upd>, update: Upd, variant: &'static str)
where
Upd: Debug,
R: Requester + Clone,
{
if let Some(tx) = tx {
if let Err(error) = tx.send(UpdateWithCx { bot: bot.clone(), update }) {
if let Err(error) = tx.send(UpdateWithCx { requester: requester.clone(), update }) {
log::error!(
"The RX part of the {} channel is closed, but an update is received.\nError:{}\n",
variant,
@ -44,28 +47,33 @@ where
///
/// See the [module-level documentation](crate::dispatching) for the design
/// overview.
pub struct Dispatcher {
bot: Bot,
pub struct Dispatcher<R> {
requester: R,
messages_queue: Tx<Message>,
edited_messages_queue: Tx<Message>,
channel_posts_queue: Tx<Message>,
edited_channel_posts_queue: Tx<Message>,
inline_queries_queue: Tx<InlineQuery>,
chosen_inline_results_queue: Tx<ChosenInlineResult>,
callback_queries_queue: Tx<CallbackQuery>,
shipping_queries_queue: Tx<ShippingQuery>,
pre_checkout_queries_queue: Tx<PreCheckoutQuery>,
polls_queue: Tx<Poll>,
poll_answers_queue: Tx<PollAnswer>,
messages_queue: Tx<R, Message>,
edited_messages_queue: Tx<R, Message>,
channel_posts_queue: Tx<R, Message>,
edited_channel_posts_queue: Tx<R, Message>,
inline_queries_queue: Tx<R, InlineQuery>,
chosen_inline_results_queue: Tx<R, ChosenInlineResult>,
callback_queries_queue: Tx<R, CallbackQuery>,
shipping_queries_queue: Tx<R, ShippingQuery>,
pre_checkout_queries_queue: Tx<R, PreCheckoutQuery>,
polls_queue: Tx<R, Poll>,
poll_answers_queue: Tx<R, PollAnswer>,
my_chat_members_queue: Tx<R, ChatMemberUpdated>,
chat_members_queue: Tx<R, ChatMemberUpdated>,
}
impl Dispatcher {
/// Constructs a new dispatcher with the specified `bot`.
impl<R> Dispatcher<R>
where
R: Send + 'static,
{
/// Constructs a new dispatcher with the specified `requester`.
#[must_use]
pub fn new(bot: Bot) -> Self {
pub fn new(requester: R) -> Self {
Self {
bot,
requester,
messages_queue: None,
edited_messages_queue: None,
channel_posts_queue: None,
@ -77,14 +85,18 @@ impl Dispatcher {
pre_checkout_queries_queue: None,
polls_queue: None,
poll_answers_queue: None,
my_chat_members_queue: None,
chat_members_queue: None,
}
}
#[must_use]
fn new_tx<H, Upd>(&self, h: H) -> Tx<Upd>
#[allow(clippy::unnecessary_wraps)]
fn new_tx<H, Upd>(&self, h: H) -> Tx<R, Upd>
where
H: DispatcherHandler<Upd> + Send + 'static,
H: DispatcherHandler<R, Upd> + Send + 'static,
Upd: Send + 'static,
R: Send + 'static,
{
let (tx, rx) = mpsc::unbounded_channel();
tokio::spawn(async move {
@ -97,7 +109,7 @@ impl Dispatcher {
#[must_use]
pub fn messages_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<Message> + 'static + Send,
H: DispatcherHandler<R, Message> + 'static + Send,
{
self.messages_queue = self.new_tx(h);
self
@ -106,7 +118,7 @@ impl Dispatcher {
#[must_use]
pub fn edited_messages_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<Message> + 'static + Send,
H: DispatcherHandler<R, Message> + 'static + Send,
{
self.edited_messages_queue = self.new_tx(h);
self
@ -115,7 +127,7 @@ impl Dispatcher {
#[must_use]
pub fn channel_posts_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<Message> + 'static + Send,
H: DispatcherHandler<R, Message> + 'static + Send,
{
self.channel_posts_queue = self.new_tx(h);
self
@ -124,7 +136,7 @@ impl Dispatcher {
#[must_use]
pub fn edited_channel_posts_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<Message> + 'static + Send,
H: DispatcherHandler<R, Message> + 'static + Send,
{
self.edited_channel_posts_queue = self.new_tx(h);
self
@ -133,7 +145,7 @@ impl Dispatcher {
#[must_use]
pub fn inline_queries_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<InlineQuery> + 'static + Send,
H: DispatcherHandler<R, InlineQuery> + 'static + Send,
{
self.inline_queries_queue = self.new_tx(h);
self
@ -142,7 +154,7 @@ impl Dispatcher {
#[must_use]
pub fn chosen_inline_results_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<ChosenInlineResult> + 'static + Send,
H: DispatcherHandler<R, ChosenInlineResult> + 'static + Send,
{
self.chosen_inline_results_queue = self.new_tx(h);
self
@ -151,7 +163,7 @@ impl Dispatcher {
#[must_use]
pub fn callback_queries_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<CallbackQuery> + 'static + Send,
H: DispatcherHandler<R, CallbackQuery> + 'static + Send,
{
self.callback_queries_queue = self.new_tx(h);
self
@ -160,7 +172,7 @@ impl Dispatcher {
#[must_use]
pub fn shipping_queries_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<ShippingQuery> + 'static + Send,
H: DispatcherHandler<R, ShippingQuery> + 'static + Send,
{
self.shipping_queries_queue = self.new_tx(h);
self
@ -169,7 +181,7 @@ impl Dispatcher {
#[must_use]
pub fn pre_checkout_queries_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<PreCheckoutQuery> + 'static + Send,
H: DispatcherHandler<R, PreCheckoutQuery> + 'static + Send,
{
self.pre_checkout_queries_queue = self.new_tx(h);
self
@ -178,7 +190,7 @@ impl Dispatcher {
#[must_use]
pub fn polls_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<Poll> + 'static + Send,
H: DispatcherHandler<R, Poll> + 'static + Send,
{
self.polls_queue = self.new_tx(h);
self
@ -187,19 +199,41 @@ impl Dispatcher {
#[must_use]
pub fn poll_answers_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<PollAnswer> + 'static + Send,
H: DispatcherHandler<R, PollAnswer> + 'static + Send,
{
self.poll_answers_queue = self.new_tx(h);
self
}
#[must_use]
pub fn my_chat_members_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<R, ChatMemberUpdated> + 'static + Send,
{
self.my_chat_members_queue = self.new_tx(h);
self
}
#[must_use]
pub fn chat_members_handler<H>(mut self, h: H) -> Self
where
H: DispatcherHandler<R, ChatMemberUpdated> + 'static + Send,
{
self.chat_members_queue = self.new_tx(h);
self
}
/// Starts your bot with the default parameters.
///
/// The default parameters are a long polling update listener and log all
/// errors produced by this listener).
pub async fn dispatch(&self) {
pub async fn dispatch(&self)
where
R: Requester + Clone,
<R as Requester>::GetUpdatesFaultTolerant: Send,
{
self.dispatch_with_listener(
update_listeners::polling_default(self.bot.clone()),
update_listeners::polling_default(self.requester.clone()),
LoggingErrorHandler::with_custom_text("An error from the update listener"),
)
.await;
@ -215,6 +249,7 @@ impl Dispatcher {
UListener: UpdateListener<ListenerE> + 'a,
Eh: ErrorHandler<ListenerE> + 'a,
ListenerE: Debug,
R: Requester + Clone,
{
let update_listener = Box::pin(update_listener);
@ -235,11 +270,16 @@ impl Dispatcher {
match update.kind {
UpdateKind::Message(message) => {
send!(&self.bot, &self.messages_queue, message, UpdateKind::Message);
send!(
&self.requester,
&self.messages_queue,
message,
UpdateKind::Message
);
}
UpdateKind::EditedMessage(message) => {
send!(
&self.bot,
&self.requester,
&self.edited_messages_queue,
message,
UpdateKind::EditedMessage
@ -247,7 +287,7 @@ impl Dispatcher {
}
UpdateKind::ChannelPost(post) => {
send!(
&self.bot,
&self.requester,
&self.channel_posts_queue,
post,
UpdateKind::ChannelPost
@ -255,7 +295,7 @@ impl Dispatcher {
}
UpdateKind::EditedChannelPost(post) => {
send!(
&self.bot,
&self.requester,
&self.edited_channel_posts_queue,
post,
UpdateKind::EditedChannelPost
@ -263,7 +303,7 @@ impl Dispatcher {
}
UpdateKind::InlineQuery(query) => {
send!(
&self.bot,
&self.requester,
&self.inline_queries_queue,
query,
UpdateKind::InlineQuery
@ -271,7 +311,7 @@ impl Dispatcher {
}
UpdateKind::ChosenInlineResult(result) => {
send!(
&self.bot,
&self.requester,
&self.chosen_inline_results_queue,
result,
UpdateKind::ChosenInlineResult
@ -279,7 +319,7 @@ impl Dispatcher {
}
UpdateKind::CallbackQuery(query) => {
send!(
&self.bot,
&self.requester,
&self.callback_queries_queue,
query,
UpdateKind::CallbackQuer
@ -287,7 +327,7 @@ impl Dispatcher {
}
UpdateKind::ShippingQuery(query) => {
send!(
&self.bot,
&self.requester,
&self.shipping_queries_queue,
query,
UpdateKind::ShippingQuery
@ -295,23 +335,39 @@ impl Dispatcher {
}
UpdateKind::PreCheckoutQuery(query) => {
send!(
&self.bot,
&self.requester,
&self.pre_checkout_queries_queue,
query,
UpdateKind::PreCheckoutQuery
);
}
UpdateKind::Poll(poll) => {
send!(&self.bot, &self.polls_queue, poll, UpdateKind::Poll);
send!(&self.requester, &self.polls_queue, poll, UpdateKind::Poll);
}
UpdateKind::PollAnswer(answer) => {
send!(
&self.bot,
&self.requester,
&self.poll_answers_queue,
answer,
UpdateKind::PollAnswer
);
}
UpdateKind::MyChatMember(chat_member_updated) => {
send!(
&self.requester,
&self.my_chat_members_queue,
chat_member_updated,
UpdateKind::MyChatMember
);
}
UpdateKind::ChatMember(chat_member_updated) => {
send!(
&self.requester,
&self.chat_members_queue,
chat_member_updated,
UpdateKind::MyChatMember
);
}
}
}
})

View file

@ -9,21 +9,21 @@ use futures::future::BoxFuture;
/// overview.
///
/// [`Dispatcher`]: crate::dispatching::Dispatcher
pub trait DispatcherHandler<Upd> {
pub trait DispatcherHandler<R, Upd> {
#[must_use]
fn handle(self, updates: DispatcherHandlerRx<Upd>) -> BoxFuture<'static, ()>
fn handle(self, updates: DispatcherHandlerRx<R, Upd>) -> BoxFuture<'static, ()>
where
UpdateWithCx<Upd>: Send + 'static;
UpdateWithCx<R, Upd>: Send + 'static;
}
impl<Upd, F, Fut> DispatcherHandler<Upd> for F
impl<R, Upd, F, Fut> DispatcherHandler<R, Upd> for F
where
F: FnOnce(DispatcherHandlerRx<Upd>) -> Fut + Send + 'static,
F: FnOnce(DispatcherHandlerRx<R, Upd>) -> Fut + Send + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
fn handle(self, updates: DispatcherHandlerRx<Upd>) -> BoxFuture<'static, ()>
fn handle(self, updates: DispatcherHandlerRx<R, Upd>) -> BoxFuture<'static, ()>
where
UpdateWithCx<Upd>: Send + 'static,
UpdateWithCx<R, Upd>: Send + 'static,
{
Box::pin(async move { self(updates).await })
}

View file

@ -1,5 +1,6 @@
use crate::{prelude::UpdateWithCx, types::Message, utils::command::BotCommand};
use crate::{dispatching::UpdateWithCx, utils::command::BotCommand};
use futures::{stream::BoxStream, Stream, StreamExt};
use teloxide_core::types::Message;
/// An extension trait to be used with [`DispatcherHandlerRx`].
///
@ -7,37 +8,45 @@ use futures::{stream::BoxStream, Stream, StreamExt};
/// overview.
///
/// [`DispatcherHandlerRx`]: crate::dispatching::DispatcherHandlerRx
pub trait DispatcherHandlerRxExt {
pub trait DispatcherHandlerRxExt<R> {
/// Extracts only text messages from this stream of arbitrary messages.
fn text_messages(self) -> BoxStream<'static, (UpdateWithCx<Message>, String)>
fn text_messages(self) -> BoxStream<'static, (UpdateWithCx<R, Message>, String)>
where
Self: Stream<Item = UpdateWithCx<Message>>;
Self: Stream<Item = UpdateWithCx<R, Message>>,
R: Send + 'static;
/// Extracts only commands with their arguments from this stream of
/// arbitrary messages.
fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<Message>, C)>
fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<R, Message>, C)>
where
Self: Stream<Item = UpdateWithCx<Message>>,
Self: Stream<Item = UpdateWithCx<R, Message>>,
C: BotCommand,
N: Into<String> + Send;
N: Into<String> + Send,
R: Send + 'static;
}
impl<T> DispatcherHandlerRxExt for T
impl<R, T> DispatcherHandlerRxExt<R> for T
where
T: Send + 'static,
{
fn text_messages(self) -> BoxStream<'static, (UpdateWithCx<Message>, String)>
fn text_messages(self) -> BoxStream<'static, (UpdateWithCx<R, Message>, String)>
where
Self: Stream<Item = UpdateWithCx<Message>>,
Self: Stream<Item = UpdateWithCx<R, Message>>,
R: Send + 'static,
{
self.filter_map(|cx| async move { cx.update.text_owned().map(|text| (cx, text)) }).boxed()
self.filter_map(|cx| async move {
let text = cx.update.text().map(ToOwned::to_owned);
text.map(move |text| (cx, text))
})
.boxed()
}
fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<Message>, C)>
fn commands<C, N>(self, bot_name: N) -> BoxStream<'static, (UpdateWithCx<R, Message>, C)>
where
Self: Stream<Item = UpdateWithCx<Message>>,
Self: Stream<Item = UpdateWithCx<R, Message>>,
C: BotCommand,
N: Into<String> + Send,
R: Send + 'static,
{
let bot_name = bot_name.into();

View file

@ -9,12 +9,14 @@
//!
//! ```
//! use teloxide::prelude::*;
//! use tokio_stream::wrappers::UnboundedReceiverStream;
//!
//! async fn handle_messages(rx: DispatcherHandlerRx<Message>) {
//! rx.for_each_concurrent(None, |message| async move {
//! dbg!(message);
//! })
//! .await;
//! async fn handle_messages(rx: DispatcherHandlerRx<AutoSend<Bot>, Message>) {
//! UnboundedReceiverStream::new(rx)
//! .for_each_concurrent(None, |message| async move {
//! dbg!(message.update);
//! })
//! .await;
//! }
//! ```
//!
@ -55,9 +57,9 @@ pub use dispatcher::Dispatcher;
pub use dispatcher_handler::DispatcherHandler;
pub use dispatcher_handler_rx_ext::DispatcherHandlerRxExt;
use tokio::sync::mpsc::UnboundedReceiver;
pub use update_with_cx::UpdateWithCx;
pub use update_with_cx::{UpdateWithCx, UpdateWithCxRequesterType};
/// A type of a stream, consumed by [`Dispatcher`]'s handlers.
///
/// [`Dispatcher`]: crate::dispatching::Dispatcher
pub type DispatcherHandlerRx<Upd> = UnboundedReceiver<UpdateWithCx<Upd>>;
pub type DispatcherHandlerRx<R, Upd> = UnboundedReceiver<UpdateWithCx<R, Upd>>;

View file

@ -4,12 +4,12 @@ use crate::{
DispatcherHandlerRxExt, UpdateWithCx,
},
error_handlers::{LoggingErrorHandler, OnError},
types::Message,
utils::command::BotCommand,
Bot,
};
use futures::StreamExt;
use std::{fmt::Debug, future::Future, sync::Arc};
use teloxide_core::{requests::Requester, types::Message};
use tokio_stream::wrappers::UnboundedReceiverStream;
/// A [REPL] for commands.
///
@ -22,22 +22,24 @@ use std::{fmt::Debug, future::Future, sync::Arc};
///
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
/// [`Dispatcher`]: crate::dispatching::Dispatcher
pub async fn commands_repl<Cmd, H, Fut, HandlerE, N>(bot: Bot, bot_name: N, handler: H)
pub async fn commands_repl<R, Cmd, H, Fut, HandlerE, N>(requester: R, bot_name: N, handler: H)
where
Cmd: BotCommand + Send + 'static,
H: Fn(UpdateWithCx<Message>, Cmd) -> Fut + Send + Sync + 'static,
H: Fn(UpdateWithCx<R, Message>, Cmd) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), HandlerE>> + Send + 'static,
Result<(), HandlerE>: OnError<HandlerE>,
HandlerE: Debug + Send,
N: Into<String> + Send + 'static,
R: Requester + Send + Clone + 'static,
<R as Requester>::GetUpdatesFaultTolerant: Send,
{
let cloned_bot = bot.clone();
let cloned_requester = requester.clone();
commands_repl_with_listener(
bot,
requester,
bot_name,
handler,
update_listeners::polling_default(cloned_bot),
update_listeners::polling_default(cloned_requester),
)
.await;
}
@ -54,32 +56,36 @@ where
/// [`Dispatcher`]: crate::dispatching::Dispatcher
/// [`commands_repl`]: crate::dispatching::repls::commands_repl()
/// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener
pub async fn commands_repl_with_listener<'a, Cmd, H, Fut, L, ListenerE, HandlerE, N>(
bot: Bot,
pub async fn commands_repl_with_listener<'a, R, Cmd, H, Fut, L, ListenerE, HandlerE, N>(
requester: R,
bot_name: N,
handler: H,
listener: L,
) where
Cmd: BotCommand + Send + 'static,
H: Fn(UpdateWithCx<Message>, Cmd) -> Fut + Send + Sync + 'static,
H: Fn(UpdateWithCx<R, Message>, Cmd) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), HandlerE>> + Send + 'static,
L: UpdateListener<ListenerE> + Send + 'a,
ListenerE: Debug + Send + 'a,
Result<(), HandlerE>: OnError<HandlerE>,
HandlerE: Debug + Send,
N: Into<String> + Send + 'static,
R: Requester + Clone + Send + 'static,
{
let handler = Arc::new(handler);
Dispatcher::new(bot)
.messages_handler(move |rx: DispatcherHandlerRx<Message>| {
rx.commands::<Cmd, N>(bot_name).for_each_concurrent(None, move |(cx, cmd)| {
let handler = Arc::clone(&handler);
Dispatcher::<R>::new(requester)
.messages_handler(move |rx: DispatcherHandlerRx<R, Message>| {
UnboundedReceiverStream::new(rx).commands::<Cmd, N>(bot_name).for_each_concurrent(
None,
move |(cx, cmd)| {
let handler = Arc::clone(&handler);
async move {
handler(cx, cmd).await.log_on_error().await;
}
})
async move {
handler(cx, cmd).await.log_on_error().await;
}
},
)
})
.dispatch_with_listener(
listener,

View file

@ -6,10 +6,9 @@ use crate::{
Dispatcher, UpdateWithCx,
},
error_handlers::LoggingErrorHandler,
types::Message,
Bot,
};
use std::{convert::Infallible, fmt::Debug, future::Future, sync::Arc};
use teloxide_core::{requests::Requester, types::Message};
/// A [REPL] for dialogues.
///
@ -24,15 +23,22 @@ use std::{convert::Infallible, fmt::Debug, future::Future, sync::Arc};
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
/// [`Dispatcher`]: crate::dispatching::Dispatcher
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
pub async fn dialogues_repl<'a, H, D, Fut>(bot: Bot, handler: H)
pub async fn dialogues_repl<'a, R, H, D, Fut>(requester: R, handler: H)
where
H: Fn(UpdateWithCx<Message>, D) -> Fut + Send + Sync + 'static,
H: Fn(UpdateWithCx<R, Message>, D) -> Fut + Send + Sync + 'static,
D: Default + Send + 'static,
Fut: Future<Output = DialogueStage<D>> + Send + 'static,
R: Requester + Send + Clone + 'static,
<R as Requester>::GetUpdatesFaultTolerant: Send,
{
let cloned_bot = bot.clone();
let cloned_requester = requester.clone();
dialogues_repl_with_listener(bot, handler, update_listeners::polling_default(cloned_bot)).await;
dialogues_repl_with_listener(
requester,
handler,
update_listeners::polling_default(cloned_requester),
)
.await;
}
/// Like [`dialogues_repl`], but with a custom [`UpdateListener`].
@ -49,22 +55,23 @@ where
/// [`dialogues_repl`]: crate::dispatching::repls::dialogues_repl()
/// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener
/// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage
pub async fn dialogues_repl_with_listener<'a, H, D, Fut, L, ListenerE>(
bot: Bot,
pub async fn dialogues_repl_with_listener<'a, R, H, D, Fut, L, ListenerE>(
requester: R,
handler: H,
listener: L,
) where
H: Fn(UpdateWithCx<Message>, D) -> Fut + Send + Sync + 'static,
H: Fn(UpdateWithCx<R, Message>, D) -> Fut + Send + Sync + 'static,
D: Default + Send + 'static,
Fut: Future<Output = DialogueStage<D>> + Send + 'static,
L: UpdateListener<ListenerE> + Send + 'a,
ListenerE: Debug + Send + 'a,
R: Requester + Send + Clone + 'static,
{
let handler = Arc::new(handler);
Dispatcher::new(bot)
Dispatcher::new(requester)
.messages_handler(DialogueDispatcher::new(
move |DialogueWithCx { cx, dialogue }: DialogueWithCx<Message, D, Infallible>| {
move |DialogueWithCx { cx, dialogue }: DialogueWithCx<R, Message, D, Infallible>| {
let handler = Arc::clone(&handler);
async move {

View file

@ -4,11 +4,11 @@ use crate::{
UpdateWithCx,
},
error_handlers::{LoggingErrorHandler, OnError},
types::Message,
Bot,
};
use futures::StreamExt;
use std::{fmt::Debug, future::Future, sync::Arc};
use teloxide_core::{requests::Requester, types::Message};
use tokio_stream::wrappers::UnboundedReceiverStream;
/// A [REPL] for messages.
///
@ -21,15 +21,18 @@ use std::{fmt::Debug, future::Future, sync::Arc};
///
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
/// [`Dispatcher`]: crate::dispatching::Dispatcher
pub async fn repl<H, Fut, E>(bot: Bot, handler: H)
pub async fn repl<R, H, Fut, E>(requester: R, handler: H)
where
H: Fn(UpdateWithCx<Message>) -> Fut + Send + Sync + 'static,
H: Fn(UpdateWithCx<R, Message>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), E>> + Send + 'static,
Result<(), E>: OnError<E>,
E: Debug + Send,
R: Requester + Send + Clone + 'static,
<R as Requester>::GetUpdatesFaultTolerant: Send,
{
let cloned_bot = bot.clone();
repl_with_listener(bot, handler, update_listeners::polling_default(cloned_bot)).await;
let cloned_requester = requester.clone();
repl_with_listener(requester, handler, update_listeners::polling_default(cloned_requester))
.await;
}
/// Like [`repl`], but with a custom [`UpdateListener`].
@ -44,20 +47,24 @@ where
/// [`Dispatcher`]: crate::dispatching::Dispatcher
/// [`repl`]: crate::dispatching::repls::repl()
/// [`UpdateListener`]: crate::dispatching::update_listeners::UpdateListener
pub async fn repl_with_listener<'a, H, Fut, E, L, ListenerE>(bot: Bot, handler: H, listener: L)
where
H: Fn(UpdateWithCx<Message>) -> Fut + Send + Sync + 'static,
pub async fn repl_with_listener<'a, R, H, Fut, E, L, ListenerE>(
requester: R,
handler: H,
listener: L,
) where
H: Fn(UpdateWithCx<R, Message>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), E>> + Send + 'static,
L: UpdateListener<ListenerE> + Send + 'a,
ListenerE: Debug,
Result<(), E>: OnError<E>,
E: Debug + Send,
R: Requester + Clone + Send + 'static,
{
let handler = Arc::new(handler);
Dispatcher::new(bot)
.messages_handler(|rx: DispatcherHandlerRx<Message>| {
rx.for_each_concurrent(None, move |message| {
Dispatcher::new(requester)
.messages_handler(|rx: DispatcherHandlerRx<R, Message>| {
UnboundedReceiverStream::new(rx).for_each_concurrent(None, move |message| {
let handler = Arc::clone(&handler);
async move {

View file

@ -97,7 +97,7 @@
//! [`UpdateListener`]: UpdateListener
//! [`polling_default`]: polling_default
//! [`polling`]: polling
//! [`Box::get_updates`]: crate::Bot::get_updates
//! [`Box::get_updates`]: crate::requests::Requester::get_updates
//! [getting updates]: https://core.telegram.org/bots/api#getting-updates
//! [long]: https://en.wikipedia.org/wiki/Push_technology#Long_polling
//! [short]: https://en.wikipedia.org/wiki/Polling_(computer_science)
@ -105,14 +105,11 @@
use futures::{stream, Stream, StreamExt};
use crate::{
bot::Bot,
requests::Request,
types::{AllowedUpdate, Update},
RequestError,
};
use std::{convert::TryInto, time::Duration};
use teloxide_core::{
requests::{HasPayload, Request, Requester},
types::{AllowedUpdate, SemiparsedVec, Update},
};
/// A generic update listener.
pub trait UpdateListener<E>: Stream<Item = Result<Update, E>> {
@ -123,8 +120,12 @@ impl<S, E> UpdateListener<E> for S where S: Stream<Item = Result<Update, E>> {}
/// Returns a long polling update listener with `timeout` of 10 seconds.
///
/// See also: [`polling`](polling).
pub fn polling_default(bot: Bot) -> impl UpdateListener<RequestError> {
polling(bot, Some(Duration::from_secs(10)), None, None)
pub fn polling_default<R>(requester: R) -> impl UpdateListener<R::Err>
where
R: Requester,
<R as Requester>::GetUpdatesFaultTolerant: Send,
{
polling(requester, Some(Duration::from_secs(10)), None, None)
}
/// Returns a long/short polling update listener with some additional options.
@ -138,26 +139,32 @@ pub fn polling_default(bot: Bot) -> impl UpdateListener<RequestError> {
///
/// See also: [`polling_default`](polling_default).
///
/// [`GetUpdates`]: crate::requests::GetUpdates
pub fn polling(
bot: Bot,
/// [`GetUpdates`]: crate::payloads::GetUpdates
pub fn polling<R>(
requester: R,
timeout: Option<Duration>,
limit: Option<u8>,
allowed_updates: Option<Vec<AllowedUpdate>>,
) -> impl UpdateListener<RequestError> {
) -> impl UpdateListener<R::Err>
where
R: Requester,
<R as Requester>::GetUpdatesFaultTolerant: Send,
{
let timeout = timeout.map(|t| t.as_secs().try_into().expect("timeout is too big"));
stream::unfold(
(allowed_updates, bot, 0),
(allowed_updates, requester, 0),
move |(mut allowed_updates, bot, mut offset)| async move {
let mut req = bot.get_updates().offset(offset);
req.timeout = timeout;
req.limit = limit;
req.allowed_updates = allowed_updates.take();
let mut req = bot.get_updates_fault_tolerant();
let payload = &mut req.payload_mut().0;
payload.offset = Some(offset);
payload.timeout = timeout;
payload.limit = limit;
payload.allowed_updates = allowed_updates.take();
let updates = match req.send().await {
Err(err) => vec![Err(err)],
Ok(updates) => {
Ok(SemiparsedVec(updates)) => {
// Set offset to the last update's id + 1
if let Some(upd) = updates.last() {
let id: i32 = match upd {
@ -172,10 +179,19 @@ pub fn polling(
offset = id + 1;
}
let updates =
updates.into_iter().filter_map(Result::ok).collect::<Vec<Update>>();
for update in &updates {
if let Err((value, e)) = update {
log::error!(
"Cannot parse an update.\nError: {:?}\nValue: {}\n\
This is a bug in teloxide-core, please open an issue here: \
https://github.com/teloxide/teloxide-core/issues.",
e,
value
);
}
}
updates.into_iter().map(Ok).collect::<Vec<_>>()
updates.into_iter().filter_map(Result::ok).map(Ok).collect::<Vec<_>>()
}
};

View file

@ -1,13 +1,8 @@
use crate::{
dispatching::dialogue::GetChatId,
requests::{
DeleteMessage, EditMessageCaption, EditMessageText, ForwardMessage, PinChatMessage,
Request, ResponseResult, SendAnimation, SendAudio, SendContact, SendDice, SendDocument,
SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker, SendVenue, SendVideo,
SendVideoNote, SendVoice,
},
use crate::dispatching::dialogue::GetChatId;
use teloxide_core::{
payloads::SendMessageSetters,
requests::{Request, Requester},
types::{ChatId, InputFile, InputMedia, Message},
Bot,
};
/// A [`Dispatcher`]'s handler's context of a bot and an update.
@ -17,12 +12,12 @@ use crate::{
///
/// [`Dispatcher`]: crate::dispatching::Dispatcher
#[derive(Debug)]
pub struct UpdateWithCx<Upd> {
pub bot: Bot,
pub struct UpdateWithCx<R, Upd> {
pub requester: R,
pub update: Upd,
}
impl<Upd> GetChatId for UpdateWithCx<Upd>
impl<Upd, R> GetChatId for UpdateWithCx<R, Upd>
where
Upd: GetChatId,
{
@ -31,121 +26,136 @@ where
}
}
impl UpdateWithCx<Message> {
#[doc(hidden)]
// Now it is used only inside `#[teloxide(subtransition)]` for type inference.
pub trait UpdateWithCxRequesterType {
type Requester;
}
impl<R, Upd> UpdateWithCxRequesterType for UpdateWithCx<R, Upd> {
type Requester = R;
}
impl<R> UpdateWithCx<R, Message>
where
R: Requester,
{
/// A shortcut for `.answer(text).send().await`.
pub async fn answer_str<T>(&self, text: T) -> ResponseResult<Message>
#[deprecated(note = "Use .answer(text).await instead")]
pub async fn answer_str<T>(&self, text: T) -> Result<Message, R::Err>
where
T: Into<String>,
R::SendMessage: std::future::Future,
{
self.answer(text).send().await
}
pub fn answer<T>(&self, text: T) -> SendMessage
pub fn answer<T>(&self, text: T) -> R::SendMessage
where
T: Into<String>,
{
self.bot.send_message(self.chat_id(), text)
self.requester.send_message(self.chat_id(), text)
}
pub fn reply_to<T>(&self, text: T) -> SendMessage
pub fn reply_to<T>(&self, text: T) -> R::SendMessage
where
T: Into<String>,
{
self.bot.send_message(self.chat_id(), text).reply_to_message_id(self.update.id)
self.requester.send_message(self.chat_id(), text).reply_to_message_id(self.update.id)
}
pub fn answer_photo(&self, photo: InputFile) -> SendPhoto {
self.bot.send_photo(self.update.chat.id, photo)
pub fn answer_photo(&self, photo: InputFile) -> R::SendPhoto {
self.requester.send_photo(self.update.chat.id, photo)
}
pub fn answer_audio(&self, audio: InputFile) -> SendAudio {
self.bot.send_audio(self.update.chat.id, audio)
pub fn answer_audio(&self, audio: InputFile) -> R::SendAudio {
self.requester.send_audio(self.update.chat.id, audio)
}
pub fn answer_animation(&self, animation: InputFile) -> SendAnimation {
self.bot.send_animation(self.update.chat.id, animation)
pub fn answer_animation(&self, animation: InputFile) -> R::SendAnimation {
self.requester.send_animation(self.update.chat.id, animation)
}
pub fn answer_document(&self, document: InputFile) -> SendDocument {
self.bot.send_document(self.update.chat.id, document)
pub fn answer_document(&self, document: InputFile) -> R::SendDocument {
self.requester.send_document(self.update.chat.id, document)
}
pub fn answer_video(&self, video: InputFile) -> SendVideo {
self.bot.send_video(self.update.chat.id, video)
pub fn answer_video(&self, video: InputFile) -> R::SendVideo {
self.requester.send_video(self.update.chat.id, video)
}
pub fn answer_voice(&self, voice: InputFile) -> SendVoice {
self.bot.send_voice(self.update.chat.id, voice)
pub fn answer_voice(&self, voice: InputFile) -> R::SendVoice {
self.requester.send_voice(self.update.chat.id, voice)
}
pub fn answer_media_group<T>(&self, media_group: T) -> SendMediaGroup
pub fn answer_media_group<T>(&self, media_group: T) -> R::SendMediaGroup
where
T: Into<Vec<InputMedia>>,
T: IntoIterator<Item = InputMedia>,
{
self.bot.send_media_group(self.update.chat.id, media_group)
self.requester.send_media_group(self.update.chat.id, media_group)
}
pub fn answer_location(&self, latitude: f32, longitude: f32) -> SendLocation {
self.bot.send_location(self.update.chat.id, latitude, longitude)
pub fn answer_location(&self, latitude: f64, longitude: f64) -> R::SendLocation {
self.requester.send_location(self.update.chat.id, latitude, longitude)
}
pub fn answer_venue<T, U>(
&self,
latitude: f32,
longitude: f32,
latitude: f64,
longitude: f64,
title: T,
address: U,
) -> SendVenue
) -> R::SendVenue
where
T: Into<String>,
U: Into<String>,
{
self.bot.send_venue(self.update.chat.id, latitude, longitude, title, address)
self.requester.send_venue(self.update.chat.id, latitude, longitude, title, address)
}
pub fn answer_video_note(&self, video_note: InputFile) -> SendVideoNote {
self.bot.send_video_note(self.update.chat.id, video_note)
pub fn answer_video_note(&self, video_note: InputFile) -> R::SendVideoNote {
self.requester.send_video_note(self.update.chat.id, video_note)
}
pub fn answer_contact<T, U>(&self, phone_number: T, first_name: U) -> SendContact
pub fn answer_contact<T, U>(&self, phone_number: T, first_name: U) -> R::SendContact
where
T: Into<String>,
U: Into<String>,
{
self.bot.send_contact(self.chat_id(), phone_number, first_name)
self.requester.send_contact(self.chat_id(), phone_number, first_name)
}
pub fn answer_sticker(&self, sticker: InputFile) -> SendSticker {
self.bot.send_sticker(self.update.chat.id, sticker)
pub fn answer_sticker(&self, sticker: InputFile) -> R::SendSticker {
self.requester.send_sticker(self.update.chat.id, sticker)
}
pub fn forward_to<T>(&self, chat_id: T) -> ForwardMessage
pub fn forward_to<T>(&self, chat_id: T) -> R::ForwardMessage
where
T: Into<ChatId>,
{
self.bot.forward_message(chat_id, self.update.chat.id, self.update.id)
self.requester.forward_message(chat_id, self.update.chat.id, self.update.id)
}
pub fn edit_message_text<T>(&self, text: T) -> EditMessageText
pub fn edit_message_text<T>(&self, text: T) -> R::EditMessageText
where
T: Into<String>,
{
self.bot.edit_message_text(self.update.chat.id, self.update.id, text)
self.requester.edit_message_text(self.update.chat.id, self.update.id, text)
}
pub fn edit_message_caption(&self) -> EditMessageCaption {
self.bot.edit_message_caption(self.update.chat.id, self.update.id)
pub fn edit_message_caption(&self) -> R::EditMessageCaption {
self.requester.edit_message_caption(self.update.chat.id, self.update.id)
}
pub fn delete_message(&self) -> DeleteMessage {
self.bot.delete_message(self.update.chat.id, self.update.id)
pub fn delete_message(&self) -> R::DeleteMessage {
self.requester.delete_message(self.update.chat.id, self.update.id)
}
pub fn pin_message(&self) -> PinChatMessage {
self.bot.pin_chat_message(self.update.chat.id, self.update.id)
pub fn pin_message(&self) -> R::PinChatMessage {
self.requester.pin_chat_message(self.update.chat.id, self.update.id)
}
pub fn answer_dice(&self) -> SendDice {
self.bot.send_dice(self.update.chat.id)
pub fn answer_dice(&self) -> R::SendDice {
self.requester.send_dice(self.update.chat.id)
}
}

View file

@ -1,515 +0,0 @@
use derive_more::From;
use reqwest::StatusCode;
use serde::Deserialize;
use thiserror::Error;
/// An error caused by downloading a file.
#[derive(Debug, Error, From)]
pub enum DownloadError {
#[error("A network error: {0}")]
NetworkError(#[source] reqwest::Error),
#[error("An I/O error: {0}")]
Io(#[source] std::io::Error),
}
/// An error caused by sending a request to Telegram.
#[derive(Debug, Error)]
pub enum RequestError {
#[error("A Telegram's error #{status_code}: {kind:?}")]
ApiError { status_code: StatusCode, kind: ApiErrorKind },
/// The group has been migrated to a supergroup with the specified
/// identifier.
#[error("The group has been migrated to a supergroup with ID #{0}")]
MigrateToChatId(i64),
/// In case of exceeding flood control, the number of seconds left to wait
/// before the request can be repeated.
#[error("Retry after {0} seconds")]
RetryAfter(i32),
#[error("A network error: {0}")]
NetworkError(#[source] reqwest::Error),
#[error("An error while parsing JSON: {0}")]
InvalidJson(#[source] serde_json::Error),
}
/// A kind of an API error.
///
/// If you receive [`ApiErrorKind::Unknown`], please [open an issue] with
/// the description of the error.
///
/// [`ApiErrorKind::Unknown`]: crate::ApiErrorKind::Unknown
/// [open an issue]: https://github.com/teloxide/teloxide/issues/new
#[derive(Debug, Deserialize, PartialEq, Hash, Eq, Clone)]
#[serde(untagged)]
pub enum ApiErrorKind {
Known(KnownApiErrorKind),
Unknown(String),
}
/// A kind of a known API error.
#[derive(Debug, Deserialize, PartialEq, Copy, Hash, Eq, Clone)]
pub enum KnownApiErrorKind {
/// Occurs when the bot tries to send message to user who blocked the bot.
#[serde(rename = "Forbidden: bot was blocked by the user")]
BotBlocked,
/// Occurs when bot tries to modify a message without modification content.
///
/// May happen in methods:
/// 1. [`EditMessageText`]
///
/// [`EditMessageText`]: crate::requests::EditMessageText
#[serde(rename = "Bad Request: message is not modified: specified new message content and \
reply markup are exactly the same as a current content and reply markup \
of the message")]
MessageNotModified,
/// Occurs when bot tries to forward or delete a message which was deleted.
///
/// May happen in methods:
/// 1. [`ForwardMessage`]
/// 2. [`DeleteMessage`]
///
/// [`ForwardMessage`]: crate::requests::ForwardMessage
/// [`DeleteMessage`]: crate::requests::DeleteMessage
#[serde(rename = "Bad Request: MESSAGE_ID_INVALID")]
MessageIdInvalid,
/// Occurs when bot tries to forward a message which does not exists.
///
/// May happen in methods:
/// 1. [`ForwardMessage`]
///
/// [`ForwardMessage`]: crate::requests::ForwardMessage
#[serde(rename = "Bad Request: message to forward not found")]
MessageToForwardNotFound,
/// Occurs when bot tries to delete a message which does not exists.
///
/// May happen in methods:
/// 1. [`DeleteMessage`]
///
/// [`DeleteMessage`]: crate::requests::DeleteMessage
#[serde(rename = "Bad Request: message to delete not found")]
MessageToDeleteNotFound,
/// Occurs when bot tries to send a text message without text.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Bad Request: message text is empty")]
MessageTextIsEmpty,
/// Occurs when bot tries to edit a message after long time.
///
/// May happen in methods:
/// 1. [`EditMessageText`]
///
/// [`EditMessageText`]: crate::requests::EditMessageText
#[serde(rename = "Bad Request: message can't be edited")]
MessageCantBeEdited,
/// Occurs when bot tries to delete a someone else's message in group where
/// it does not have enough rights.
///
/// May happen in methods:
/// 1. [`DeleteMessage`]
///
/// [`DeleteMessage`]: crate::requests::DeleteMessage
#[serde(rename = "Bad Request: message can't be deleted")]
MessageCantBeDeleted,
/// Occurs when bot tries to edit a message which does not exists.
///
/// May happen in methods:
/// 1. [`EditMessageText`]
///
/// [`EditMessageText`]: crate::requests::EditMessageText
#[serde(rename = "Bad Request: message to edit not found")]
MessageToEditNotFound,
/// Occurs when bot tries to reply to a message which does not exists.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Bad Request: reply message not found")]
MessageToReplyNotFound,
/// Occurs when bot tries to
#[serde(rename = "Bad Request: message identifier is not specified")]
MessageIdentifierNotSpecified,
/// Occurs when bot tries to send a message with text size greater then
/// 4096 symbols.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Bad Request: message is too long")]
MessageIsTooLong,
/// Occurs when bot tries to send media group with more than 10 items.
///
/// May happen in methods:
/// 1. [`SendMediaGroup`]
///
/// [`SendMediaGroup`]: crate::requests::SendMediaGroup
#[serde(rename = "Bad Request: Too much messages to send as an album")]
ToMuchMessages,
/// Occurs when bot tries to stop poll that has already been stopped.
///
/// May happen in methods:
/// 1. [`SendPoll`]
///
/// [`SendPoll`]: crate::requests::SendPoll
#[serde(rename = "Bad Request: poll has already been closed")]
PollHasAlreadyClosed,
/// Occurs when bot tries to send poll with less than 2 options.
///
/// May happen in methods:
/// 1. [`SendPoll`]
///
/// [`SendPoll`]: crate::requests::SendPoll
#[serde(rename = "Bad Request: poll must have at least 2 option")]
PollMustHaveMoreOptions,
/// Occurs when bot tries to send poll with more than 10 options.
///
/// May happen in methods:
/// 1. [`SendPoll`]
///
/// [`SendPoll`]: crate::requests::SendPoll
#[serde(rename = "Bad Request: poll can't have more than 10 options")]
PollCantHaveMoreOptions,
/// Occurs when bot tries to send poll with empty option (without text).
///
/// May happen in methods:
/// 1. [`SendPoll`]
///
/// [`SendPoll`]: crate::requests::SendPoll
#[serde(rename = "Bad Request: poll options must be non-empty")]
PollOptionsMustBeNonEmpty,
/// Occurs when bot tries to send poll with empty question (without text).
///
/// May happen in methods:
/// 1. [`SendPoll`]
///
/// [`SendPoll`]: crate::requests::SendPoll
#[serde(rename = "Bad Request: poll question must be non-empty")]
PollQuestionMustBeNonEmpty,
/// Occurs when bot tries to send poll with total size of options more than
/// 100 symbols.
///
/// May happen in methods:
/// 1. [`SendPoll`]
///
/// [`SendPoll`]: crate::requests::SendPoll
#[serde(rename = "Bad Request: poll options length must not exceed 100")]
PollOptionsLengthTooLong,
/// Occurs when bot tries to send poll with question size more than 255
/// symbols.
///
/// May happen in methods:
/// 1. [`SendPoll`]
///
/// [`SendPoll`]: crate::requests::SendPoll
#[serde(rename = "Bad Request: poll question length must not exceed 255")]
PollQuestionLengthTooLong,
/// Occurs when bot tries to stop poll with message without poll.
///
/// May happen in methods:
/// 1. [`StopPoll`]
///
/// [`StopPoll`]: crate::requests::StopPoll
#[serde(rename = "Bad Request: message with poll to stop not found")]
MessageWithPollNotFound,
/// Occurs when bot tries to stop poll with message without poll.
///
/// May happen in methods:
/// 1. [`StopPoll`]
///
/// [`StopPoll`]: crate::requests::StopPoll
#[serde(rename = "Bad Request: message is not a poll")]
MessageIsNotAPoll,
/// Occurs when bot tries to send a message to chat in which it is not a
/// member.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Bad Request: chat not found")]
ChatNotFound,
/// Occurs when bot tries to send method with unknown user_id.
///
/// May happen in methods:
/// 1. [`getUserProfilePhotos`]
///
/// [`getUserProfilePhotos`]:
/// crate::requests::GetUserProfilePhotos
#[serde(rename = "Bad Request: user not found")]
UserNotFound,
/// Occurs when bot tries to send [`SetChatDescription`] with same text as
/// in the current description.
///
/// May happen in methods:
/// 1. [`SetChatDescription`]
///
/// [`SetChatDescription`]: crate::requests::SetChatDescription
#[serde(rename = "Bad Request: chat description is not modified")]
ChatDescriptionIsNotModified,
/// Occurs when bot tries to answer to query after timeout expire.
///
/// May happen in methods:
/// 1. [`AnswerCallbackQuery`]
///
/// [`AnswerCallbackQuery`]: crate::requests::AnswerCallbackQuery
#[serde(rename = "Bad Request: query is too old and response timeout expired or query id is \
invalid")]
InvalidQueryID,
/// Occurs when bot tries to send InlineKeyboardMarkup with invalid button
/// url.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Bad Request: BUTTON_URL_INVALID")]
ButtonURLInvalid,
/// Occurs when bot tries to send button with data size more than 64 bytes.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Bad Request: BUTTON_DATA_INVALID")]
ButtonDataInvalid,
/// Occurs when bot tries to send button with data size == 0.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Bad Request: can't parse inline keyboard button: Text buttons are \
unallowed in the inline keyboard")]
TextButtonsAreUnallowed,
/// Occurs when bot tries to get file by wrong file id.
///
/// May happen in methods:
/// 1. [`GetFile`]
///
/// [`GetFile`]: crate::requests::GetFile
#[serde(rename = "Bad Request: wrong file id")]
WrongFileID,
/// Occurs when bot tries to do some with group which was deactivated.
#[serde(rename = "Bad Request: group is deactivated")]
GroupDeactivated,
/// Occurs when bot tries to set chat photo from file ID
///
/// May happen in methods:
/// 1. [`SetChatPhoto`]
///
/// [`SetChatPhoto`]: crate::requests::SetChatPhoto
#[serde(rename = "Bad Request: Photo should be uploaded as an InputFile")]
PhotoAsInputFileRequired,
/// Occurs when bot tries to add sticker to stickerset by invalid name.
///
/// May happen in methods:
/// 1. [`AddStickerToSet`]
///
/// [`AddStickerToSet`]: crate::requests::AddStickerToSet
#[serde(rename = "Bad Request: STICKERSET_INVALID")]
InvalidStickersSet,
/// Occurs when bot tries to pin a message without rights to pin in this
/// chat.
///
/// May happen in methods:
/// 1. [`PinChatMessage`]
///
/// [`PinChatMessage`]: crate::requests::PinChatMessage
#[serde(rename = "Bad Request: not enough rights to pin a message")]
NotEnoughRightsToPinMessage,
/// Occurs when bot tries to use method in group which is allowed only in a
/// supergroup or channel.
#[serde(rename = "Bad Request: method is available only for supergroups and channel")]
MethodNotAvailableInPrivateChats,
/// Occurs when bot tries to demote chat creator.
///
/// May happen in methods:
/// 1. [`PromoteChatMember`]
///
/// [`PromoteChatMember`]: crate::requests::PromoteChatMember
#[serde(rename = "Bad Request: can't demote chat creator")]
CantDemoteChatCreator,
/// Occurs when bot tries to restrict self in group chats.
///
/// May happen in methods:
/// 1. [`RestrictChatMember`]
///
/// [`RestrictChatMember`]: crate::requests::RestrictChatMember
#[serde(rename = "Bad Request: can't restrict self")]
CantRestrictSelf,
/// Occurs when bot tries to restrict chat member without rights to
/// restrict in this chat.
///
/// May happen in methods:
/// 1. [`RestrictChatMember`]
///
/// [`RestrictChatMember`]: crate::requests::RestrictChatMember
#[serde(rename = "Bad Request: not enough rights to restrict/unrestrict chat member")]
NotEnoughRightsToRestrict,
/// Occurs when bot tries set webhook to protocol other than HTTPS.
///
/// May happen in methods:
/// 1. [`SetWebhook`]
///
/// [`SetWebhook`]: crate::requests::SetWebhook
#[serde(rename = "Bad Request: bad webhook: HTTPS url must be provided for webhook")]
WebhookRequireHTTPS,
/// Occurs when bot tries to set webhook to port other than 80, 88, 443 or
/// 8443.
///
/// May happen in methods:
/// 1. [`SetWebhook`]
///
/// [`SetWebhook`]: crate::requests::SetWebhook
#[serde(rename = "Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 \
or 8443")]
BadWebhookPort,
/// Occurs when bot tries to set webhook to unknown host.
///
/// May happen in methods:
/// 1. [`SetWebhook`]
///
/// [`SetWebhook`]: crate::requests::SetWebhook
#[serde(rename = "Bad Request: bad webhook: Failed to resolve host: Name or service not known")]
UnknownHost,
/// Occurs when bot tries to set webhook to invalid URL.
///
/// May happen in methods:
/// 1. [`SetWebhook`]
///
/// [`SetWebhook`]: crate::requests::SetWebhook
#[serde(rename = "Bad Request: can't parse URL")]
CantParseUrl,
/// Occurs when bot tries to send message with unfinished entities.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Bad Request: can't parse entities")]
CantParseEntities,
/// Occurs when bot tries to use getUpdates while webhook is active.
///
/// May happen in methods:
/// 1. [`GetUpdates`]
///
/// [`GetUpdates`]: crate::requests::GetUpdates
#[serde(rename = "can't use getUpdates method while webhook is active")]
CantGetUpdates,
/// Occurs when bot tries to do some in group where bot was kicked.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Unauthorized: bot was kicked from a chat")]
BotKicked,
/// Occurs when bot tries to send message to deactivated user.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Unauthorized: user is deactivated")]
UserDeactivated,
/// Occurs when you tries to initiate conversation with a user.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Unauthorized: bot can't initiate conversation with a user")]
CantInitiateConversation,
/// Occurs when you tries to send message to bot.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Unauthorized: bot can't send messages to bots")]
CantTalkWithBots,
/// Occurs when bot tries to send button with invalid http url.
///
/// May happen in methods:
/// 1. [`SendMessage`]
///
/// [`SendMessage`]: crate::requests::SendMessage
#[serde(rename = "Bad Request: wrong HTTP URL")]
WrongHTTPurl,
/// Occurs when bot tries GetUpdate before the timeout. Make sure that only
/// one Updater is running.
///
/// May happen in methods:
/// 1. [`GetUpdates`]
///
/// [`GetUpdates`]: crate::requests::GetUpdates
#[serde(rename = "Conflict: terminated by other getUpdates request; make sure that only one \
bot instance is running")]
TerminatedByOtherGetUpdates,
/// Occurs when bot tries to get file by invalid file id.
///
/// May happen in methods:
/// 1. [`GetFile`]
///
/// [`GetFile`]: crate::requests::GetFile
#[serde(rename = "Bad Request: invalid file id")]
FileIdInvalid,
}

View file

@ -13,11 +13,11 @@
//! teloxide::enable_logging!();
//! log::info!("Starting dices_bot...");
//!
//! let bot = Bot::from_env();
//! let bot = Bot::from_env().auto_send();
//!
//! teloxide::repl(bot, |message| async move {
//! message.answer_dice().send().await?;
//! ResponseResult::<()>::Ok(())
//! message.answer_dice().await?;
//! respond(())
//! })
//! .await;
//! # }
@ -45,36 +45,40 @@
//
// To properly build docs of this crate run
// ```console
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
// $ RUSTDOCFLAGS="--cfg teloxide_docsrs" cargo +nightly doc --open --all-features
// $ RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --open --all-features
// ```
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#![cfg_attr(all(teloxide_docsrs, feature = "nightly"), feature(doc_cfg))]
#![cfg_attr(all(docsrs, feature = "nightly"), feature(doc_cfg))]
pub use bot::{Bot, BotBuilder};
pub use dispatching::repls::{
commands_repl, commands_repl_with_listener, dialogues_repl, dialogues_repl_with_listener, repl,
repl_with_listener,
};
pub use errors::{ApiErrorKind, DownloadError, KnownApiErrorKind, RequestError};
mod errors;
mod net;
mod logging;
mod bot;
pub mod dispatching;
pub mod error_handlers;
mod logging;
pub mod prelude;
pub mod requests;
pub mod types;
pub mod utils;
#[doc(inline)]
pub use teloxide_core::*;
#[cfg(feature = "macros")]
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
pub use teloxide_macros as macros;
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
#[cfg(feature = "macros")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
pub use teloxide_macros::teloxide;
#[cfg(all(feature = "nightly", doctest))]
#[doc(include = "../README.md")]
enum ReadmeDocTests {}
use teloxide_core::requests::ResponseResult;
/// A shortcut for `ResponseResult::Ok(val)`.
pub fn respond<T>(val: T) -> ResponseResult<T> {
ResponseResult::Ok(val)
}

View file

@ -1,51 +0,0 @@
use reqwest::Client;
use tokio::io::{AsyncWrite, AsyncWriteExt};
use crate::errors::DownloadError;
use super::TELEGRAM_API_URL;
pub async fn download_file<D>(
client: &Client,
token: &str,
path: &str,
destination: &mut D,
) -> Result<(), DownloadError>
where
D: AsyncWrite + Unpin,
{
let mut res = client
.get(&super::file_url(TELEGRAM_API_URL, token, path))
.send()
.await?
.error_for_status()?;
while let Some(chunk) = res.chunk().await? {
destination.write_all(&chunk).await?;
}
Ok(())
}
#[cfg(feature = "unstable-stream")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(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,
}
}))
}

View file

@ -1,63 +0,0 @@
#[cfg(feature = "unstable-stream")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(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"
);
}
}

View file

@ -1,63 +0,0 @@
use reqwest::{multipart::Form, Client, Response};
use serde::{de::DeserializeOwned, Serialize};
use crate::{requests::ResponseResult, RequestError};
use super::{TelegramResponse, TELEGRAM_API_URL};
use std::time::Duration;
const DELAY_ON_SERVER_ERROR: Duration = Duration::from_secs(10);
pub async fn request_multipart<T>(
client: &Client,
token: &str,
method_name: &str,
params: Form,
) -> ResponseResult<T>
where
T: DeserializeOwned,
{
let response = client
.post(&super::method_url(TELEGRAM_API_URL, token, method_name))
.multipart(params)
.send()
.await
.map_err(RequestError::NetworkError)?;
process_response(response).await
}
pub async fn request_json<T, P>(
client: &Client,
token: &str,
method_name: &str,
params: &P,
) -> ResponseResult<T>
where
T: DeserializeOwned,
P: Serialize,
{
let response = client
.post(&super::method_url(TELEGRAM_API_URL, token, method_name))
.json(params)
.send()
.await
.map_err(RequestError::NetworkError)?;
process_response(response).await
}
async fn process_response<T>(response: Response) -> ResponseResult<T>
where
T: DeserializeOwned,
{
if response.status().is_server_error() {
tokio::time::delay_for(DELAY_ON_SERVER_ERROR).await;
}
serde_json::from_str::<TelegramResponse<T>>(
&response.text().await.map_err(RequestError::NetworkError)?,
)
.map_err(RequestError::InvalidJson)?
.into()
}

View file

@ -1,70 +0,0 @@
use reqwest::StatusCode;
use serde::Deserialize;
use crate::{
requests::ResponseResult,
types::{False, ResponseParameters, True},
ApiErrorKind, RequestError,
};
#[derive(Deserialize)]
#[serde(untagged)]
pub enum TelegramResponse<R> {
Ok {
/// A dummy field. Used only for deserialization.
#[allow(dead_code)]
ok: True,
result: R,
},
Err {
/// A dummy field. Used only for deserialization.
#[allow(dead_code)]
ok: False,
#[serde(rename = "description")]
kind: ApiErrorKind,
error_code: u16,
response_parameters: Option<ResponseParameters>,
},
}
impl<R> Into<ResponseResult<R>> for TelegramResponse<R> {
fn into(self) -> Result<R, RequestError> {
match self {
TelegramResponse::Ok { result, .. } => Ok(result),
TelegramResponse::Err { kind, error_code, response_parameters, .. } => {
if let Some(params) = response_parameters {
match params {
ResponseParameters::RetryAfter(i) => Err(RequestError::RetryAfter(i)),
ResponseParameters::MigrateToChatId(to) => {
Err(RequestError::MigrateToChatId(to))
}
}
} else {
Err(RequestError::ApiError {
kind,
status_code: StatusCode::from_u16(error_code).unwrap(),
})
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{errors::KnownApiErrorKind, types::Update};
#[test]
fn terminated_by_other_get_updates() {
let expected = ApiErrorKind::Known(KnownApiErrorKind::TerminatedByOtherGetUpdates);
if let TelegramResponse::Err{ kind, .. } = serde_json::from_str::<TelegramResponse<Update>>(r#"{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request; make sure that only one bot instance is running"}"#).unwrap() {
assert_eq!(expected, kind);
}
else {
panic!("Expected ApiErrorKind::TerminatedByOtherGetUpdates");
}
}
}

View file

@ -9,14 +9,26 @@ pub use crate::{
Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx,
},
error_handlers::{LoggingErrorHandler, OnError},
requests::{respond, Request, ResponseResult},
types::{Message, Update},
Bot, RequestError,
respond,
};
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "macros")))]
#[cfg(feature = "macros")]
pub use crate::teloxide;
pub use teloxide_core::types::{
CallbackQuery, ChatMemberUpdated, ChosenInlineResult, InlineQuery, Message, Poll, PollAnswer,
PreCheckoutQuery, ShippingQuery,
};
#[cfg(feature = "auto-send")]
pub use crate::adaptors::AutoSend;
#[doc(no_inline)]
pub use teloxide_core::prelude::*;
#[cfg(feature = "frunk")]
// FIXME(waffle): use `docsrs` here when issue with combine is resolved <https://github.com/teloxide/teloxide/pull/305#issuecomment-716172103>
#[cfg_attr(all(teloxide_docsrs, feature = "nightly"), doc(cfg(feature = "frunk")))]
#[cfg_attr(all(docsrs, feature = "nightly"), doc(cfg(feature = "frunk")))]
pub use crate::utils::UpState;
pub use tokio::sync::mpsc::UnboundedReceiver;

View file

@ -1,109 +0,0 @@
use crate::{
net,
requests::form_builder::FormBuilder,
types::{MaskPosition, True},
Bot,
};
use crate::{
requests::{RequestWithFile, ResponseResult},
types::StickerType,
};
/// Use this method to add a new sticker to a set created by the bot.
///
/// [The official docs](https://core.telegram.org/bots/api#addstickertoset).
#[derive(Debug, Clone)]
pub struct AddStickerToSet {
bot: Bot,
user_id: i32,
name: String,
sticker_type: StickerType,
emojis: String,
mask_position: Option<MaskPosition>,
}
#[async_trait::async_trait]
impl RequestWithFile for AddStickerToSet {
type Output = True;
async fn send(&self) -> tokio::io::Result<ResponseResult<True>> {
let builder =
FormBuilder::new().add_text("user_id", &self.user_id).add_text("name", &self.name);
let builder = match &self.sticker_type {
StickerType::Png(file) => builder.add_input_file("png_sticker", &file),
StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file),
}
.await?
.add_text("emojis", &self.emojis)
.add_text("mask_position", &self.mask_position);
Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"addStickerToSet",
builder.build(),
)
.await)
}
}
impl AddStickerToSet {
pub(crate) fn new<N, E>(
bot: Bot,
user_id: i32,
name: N,
sticker_type: StickerType,
emojis: E,
) -> Self
where
N: Into<String>,
E: Into<String>,
{
Self {
bot,
user_id,
name: name.into(),
sticker_type,
emojis: emojis.into(),
mask_position: None,
}
}
/// User identifier of sticker set owner.
pub fn user_id(mut self, val: i32) -> Self {
self.user_id = val;
self
}
/// Sticker set name.
pub fn name<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.name = val.into();
self
}
pub fn sticker_type(mut self, val: StickerType) -> Self {
self.sticker_type = val;
self
}
/// One or more emoji corresponding to the sticker.
pub fn emojis<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.emojis = val.into();
self
}
/// A JSON-serialized object for position where the mask should be placed on
/// faces.
pub fn mask_position(mut self, val: MaskPosition) -> Self {
self.mask_position = Some(val);
self
}
}

View file

@ -1,101 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::True,
Bot,
};
/// Use this method to send answers to callback queries sent from [inline
/// keyboards].
///
/// The answer will be displayed to the user as a notification at
/// the top of the chat screen or as an alert.
///
/// [The official docs](https://core.telegram.org/bots/api#answercallbackquery).
///
/// [inline keyboards]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct AnswerCallbackQuery {
#[serde(skip_serializing)]
bot: Bot,
callback_query_id: String,
text: Option<String>,
show_alert: Option<bool>,
url: Option<String>,
cache_time: Option<i32>,
}
#[async_trait::async_trait]
impl Request for AnswerCallbackQuery {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "answerCallbackQuery", &self).await
}
}
impl AnswerCallbackQuery {
pub(crate) fn new<C>(bot: Bot, callback_query_id: C) -> Self
where
C: Into<String>,
{
let callback_query_id = callback_query_id.into();
Self { bot, callback_query_id, text: None, show_alert: None, url: None, cache_time: None }
}
/// Unique identifier for the query to be answered.
pub fn callback_query_id<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.callback_query_id = val.into();
self
}
/// Text of the notification. If not specified, nothing will be shown to the
/// user, 0-200 characters.
pub fn text<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.text = Some(val.into());
self
}
/// If `true`, an alert will be shown by the client instead of a
/// notification at the top of the chat screen. Defaults to `false`.
pub fn show_alert(mut self, val: bool) -> Self {
self.show_alert = Some(val);
self
}
/// URL that will be opened by the user's client. If you have created a
/// [`Game`] and accepted the conditions via [@Botfather], specify the
/// URL that opens your game note that this will only work if the
/// query comes from a [`callback_game`] button.
///
/// Otherwise, you may use links like `t.me/your_bot?start=XXXX` that open
/// your bot with a parameter.
///
/// [@Botfather]: https://t.me/botfather
/// [`callback_game`]: crate::types::InlineKeyboardButton
/// [`Game`]: crate::types::Game
pub fn url<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.url = Some(val.into());
self
}
/// The maximum amount of time in seconds that the result of the callback
/// query may be cached client-side. Telegram apps will support caching
/// starting in version 3.14. Defaults to 0.
pub fn cache_time(mut self, val: i32) -> Self {
self.cache_time = Some(val);
self
}
}

View file

@ -1,146 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{InlineQueryResult, True},
Bot,
};
/// Use this method to send answers to an inline query.
///
/// No more than **50** results per query are allowed.
///
/// [The official docs](https://core.telegram.org/bots/api#answerinlinequery).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct AnswerInlineQuery {
#[serde(skip_serializing)]
bot: Bot,
inline_query_id: String,
results: Vec<InlineQueryResult>,
cache_time: Option<i32>,
is_personal: Option<bool>,
next_offset: Option<String>,
switch_pm_text: Option<String>,
switch_pm_parameter: Option<String>,
}
#[async_trait::async_trait]
impl Request for AnswerInlineQuery {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "answerInlineQuery", &self).await
}
}
impl AnswerInlineQuery {
pub(crate) fn new<I, R>(bot: Bot, inline_query_id: I, results: R) -> Self
where
I: Into<String>,
R: Into<Vec<InlineQueryResult>>,
{
let inline_query_id = inline_query_id.into();
let results = results.into();
Self {
bot,
inline_query_id,
results,
cache_time: None,
is_personal: None,
next_offset: None,
switch_pm_text: None,
switch_pm_parameter: None,
}
}
/// Unique identifier for the answered query.
pub fn inline_query_id<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.inline_query_id = val.into();
self
}
/// A JSON-serialized array of results for the inline query.
pub fn results<T>(mut self, val: T) -> Self
where
T: Into<Vec<InlineQueryResult>>,
{
self.results = val.into();
self
}
/// The maximum amount of time in seconds that the result of the inline
/// query may be cached on the server.
///
/// Defaults to 300.
pub fn cache_time(mut self, val: i32) -> Self {
self.cache_time = Some(val);
self
}
/// Pass `true`, if results may be cached on the server side only for the
/// user that sent the query.
///
/// By default, results may be returned to any user who sends the same
/// query.
#[allow(clippy::wrong_self_convention)]
pub fn is_personal(mut self, val: bool) -> Self {
self.is_personal = Some(val);
self
}
/// Pass the offset that a client should send in the next query with the
/// same text to receive more results.
///
/// Pass an empty string if there are no more results or if you dont
/// support pagination. Offset length cant 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
}
}

View file

@ -1,82 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::True,
Bot,
};
/// Once the user has confirmed their payment and shipping details, the Bot API
/// sends the final confirmation in the form of an [`Update`] with the field
/// `pre_checkout_query`. Use this method to respond to such pre-checkout
/// queries.
///
/// # Note
/// The Bot API must receive an answer within 10 seconds after the pre-checkout
/// query was sent.
///
/// [The official docs](https://core.telegram.org/bots/api#answerprecheckoutquery).
///
/// [`Update`]: crate::types::Update
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct AnswerPreCheckoutQuery {
#[serde(skip_serializing)]
bot: Bot,
pre_checkout_query_id: String,
ok: bool,
error_message: Option<String>,
}
#[async_trait::async_trait]
impl Request for AnswerPreCheckoutQuery {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "answerPreCheckoutQuery", &self)
.await
}
}
impl AnswerPreCheckoutQuery {
pub(crate) fn new<P>(bot: Bot, pre_checkout_query_id: P, ok: bool) -> Self
where
P: Into<String>,
{
let pre_checkout_query_id = pre_checkout_query_id.into();
Self { bot, pre_checkout_query_id, ok, error_message: None }
}
/// Unique identifier for the query to be answered.
pub fn pre_checkout_query_id<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.pre_checkout_query_id = val.into();
self
}
/// Specify `true` if everything is alright (goods are available, etc.) and
/// the bot is ready to proceed with the order. Use False if there are any
/// problems.
pub fn ok(mut self, val: bool) -> Self {
self.ok = val;
self
}
/// Required if ok is `false`. Error message in human readable form that
/// explains the reason for failure to proceed with the checkout (e.g.
/// "Sorry, somebody just bought the last of our amazing black T-shirts
/// while you were busy filling out your payment details. Please choose a
/// different color or garment!").
///
/// Telegram will display this message to the user.
pub fn error_message<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.error_message = Some(val.into());
self
}
}

View file

@ -1,86 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ShippingOption, True},
Bot,
};
/// If you sent an invoice requesting a shipping address and the parameter
/// `is_flexible` was specified, the Bot API will send an [`Update`] with a
/// shipping_query field to the bot. Use this method to reply to shipping
/// queries.
///
/// [The official docs](https://core.telegram.org/bots/api#answershippingquery).
///
/// [`Update`]: crate::types::Update
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct AnswerShippingQuery {
#[serde(skip_serializing)]
bot: Bot,
shipping_query_id: String,
ok: bool,
shipping_options: Option<Vec<ShippingOption>>,
error_message: Option<String>,
}
#[async_trait::async_trait]
impl Request for AnswerShippingQuery {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "answerShippingQuery", &self).await
}
}
impl AnswerShippingQuery {
pub(crate) fn new<S>(bot: Bot, shipping_query_id: S, ok: bool) -> Self
where
S: Into<String>,
{
let shipping_query_id = shipping_query_id.into();
Self { bot, shipping_query_id, ok, shipping_options: None, error_message: None }
}
/// Unique identifier for the query to be answered.
pub fn shipping_query_id<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.shipping_query_id = val.into();
self
}
/// Specify `true` if delivery to the specified address is possible and
/// `false` if there are any problems (for example, if delivery to the
/// specified address is not possible).
pub fn ok(mut self, val: bool) -> Self {
self.ok = val;
self
}
/// Required if ok is `true`. A JSON-serialized array of available shipping
/// options.
pub fn shipping_options<T>(mut self, val: T) -> Self
where
T: Into<Vec<ShippingOption>>,
{
self.shipping_options = Some(val.into());
self
}
/// Required if ok is `false`. Error message in human readable form that
/// explains why it is impossible to complete the order (e.g. "Sorry,
/// delivery to your desired address is unavailable').
///
/// Telegram will display this message to the user.
pub fn error_message<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.error_message = Some(val.into());
self
}
}

View file

@ -1,134 +0,0 @@
use crate::{
net,
requests::{form_builder::FormBuilder, RequestWithFile, ResponseResult},
types::{MaskPosition, StickerType, True},
Bot,
};
/// Use this method to create new sticker set owned by a user. The bot will be
/// able to edit the created sticker set.
///
/// [The official docs](https://core.telegram.org/bots/api#createnewstickerset).
#[derive(Debug, Clone)]
pub struct CreateNewStickerSet {
bot: Bot,
user_id: i32,
name: String,
title: String,
sticker_type: StickerType,
emojis: String,
contains_masks: Option<bool>,
mask_position: Option<MaskPosition>,
}
#[async_trait::async_trait]
impl RequestWithFile for CreateNewStickerSet {
type Output = True;
async fn send(&self) -> tokio::io::Result<ResponseResult<True>> {
let builder = FormBuilder::new()
.add_text("user_id", &self.user_id)
.add_text("name", &self.name)
.add_text("title", &self.title);
let builder = match &self.sticker_type {
StickerType::Png(file) => builder.add_input_file("png_sticker", &file),
StickerType::Tgs(file) => builder.add_input_file("tgs_sticker", &file),
}
.await?
.add_text("emojis", &self.emojis)
.add_text("contains_masks", &self.contains_masks)
.add_text("mask_position", &self.mask_position);
Ok(net::request_multipart(
self.bot.client(),
self.bot.token(),
"createNewStickerSet",
builder.build(),
)
.await)
}
}
impl CreateNewStickerSet {
pub(crate) fn new<N, T, E>(
bot: Bot,
user_id: i32,
name: N,
title: T,
sticker_type: StickerType,
emojis: E,
) -> Self
where
N: Into<String>,
T: Into<String>,
E: Into<String>,
{
Self {
bot,
user_id,
name: name.into(),
title: title.into(),
sticker_type,
emojis: emojis.into(),
contains_masks: None,
mask_position: None,
}
}
/// User identifier of created sticker set owner.
pub fn user_id(mut self, val: i32) -> Self {
self.user_id = val;
self
}
/// Short name of sticker set, to be used in `t.me/addstickers/` URLs (e.g.,
/// animals). Can contain only english letters, digits and underscores.
///
/// Must begin with a letter, can't contain consecutive underscores and must
/// end in `_by_<bot username>`. `<bot_username>` is case insensitive.
/// 1-64 characters.
pub fn name<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.name = val.into();
self
}
/// Sticker set title, 1-64 characters.
pub fn title<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.title = val.into();
self
}
pub fn sticker_type(mut self, val: StickerType) -> Self {
self.sticker_type = val;
self
}
/// One or more emoji corresponding to the sticker.
pub fn emojis<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.emojis = val.into();
self
}
/// Pass `true`, if a set of mask stickers should be created.
pub fn contains_masks(mut self, val: bool) -> Self {
self.contains_masks = Some(val);
self
}
/// A JSON-serialized object for position where the mask should be placed on
/// faces.
pub fn mask_position(mut self, val: MaskPosition) -> Self {
self.mask_position = Some(val);
self
}
}

View file

@ -1,50 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, True},
Bot,
};
/// Use this method to delete a chat photo. Photos can't be changed for private
/// chats. The bot must be an administrator in the chat for this to work and
/// must have the appropriate admin rights.
///
/// [The official docs](https://core.telegram.org/bots/api#deletechatphoto).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct DeleteChatPhoto {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
}
#[async_trait::async_trait]
impl Request for DeleteChatPhoto {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "deleteChatPhoto", &self).await
}
}
impl DeleteChatPhoto {
pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id }
}
/// Unique identifier for the target chat or username of the target channel
/// (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
}

View file

@ -1,55 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, True},
Bot,
};
/// Use this method to delete a group sticker set from a supergroup.
///
/// The bot must be an administrator in the chat for this to work and must have
/// the appropriate admin rights. Use the field `can_set_sticker_set` optionally
/// returned in [`Bot::get_chat`] requests to check if the bot can use this
/// method.
///
/// [The official docs](https://core.telegram.org/bots/api#deletechatstickerset).
///
/// [`Bot::get_chat`]: crate::Bot::get_chat
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct DeleteChatStickerSet {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
}
#[async_trait::async_trait]
impl Request for DeleteChatStickerSet {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "deleteChatStickerSet", &self).await
}
}
impl DeleteChatStickerSet {
pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id }
}
/// Unique identifier for the target chat or username of the target
/// supergroup (in the format `@supergroupusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
}

View file

@ -1,67 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, True},
Bot,
};
/// Use this method to delete a message, including service messages.
///
/// The limitations are:
/// - A message can only be deleted if it was sent less than 48 hours ago.
/// - Bots can delete outgoing messages in private chats, groups, and
/// supergroups.
/// - Bots can delete incoming messages in private chats.
/// - Bots granted can_post_messages permissions can delete outgoing messages
/// in channels.
/// - If the bot is an administrator of a group, it can delete any message
/// there.
/// - If the bot has can_delete_messages permission in a supergroup or a
/// channel, it can delete any message there.
///
/// [The official docs](https://core.telegram.org/bots/api#deletemessage).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct DeleteMessage {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
message_id: i32,
}
#[async_trait::async_trait]
impl Request for DeleteMessage {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "deleteMessage", &self).await
}
}
impl DeleteMessage {
pub(crate) fn new<C>(bot: Bot, chat_id: C, message_id: i32) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, message_id }
}
/// Unique identifier for the target chat or username of the target channel
/// (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Identifier of the message to delete.
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = val;
self
}
}

View file

@ -1,47 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::True,
Bot,
};
/// Use this method to delete a sticker from a set created by the bot.
///
/// [The official docs](https://core.telegram.org/bots/api#deletestickerfromset).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct DeleteStickerFromSet {
#[serde(skip_serializing)]
bot: Bot,
sticker: String,
}
#[async_trait::async_trait]
impl Request for DeleteStickerFromSet {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "deleteStickerFromSet", &self).await
}
}
impl DeleteStickerFromSet {
pub(crate) fn new<S>(bot: Bot, sticker: S) -> Self
where
S: Into<String>,
{
let sticker = sticker.into();
Self { bot, sticker }
}
/// File identifier of the sticker.
pub fn sticker<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.sticker = val.into();
self
}
}

View file

@ -1,37 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::True,
Bot,
};
/// Use this method to remove webhook integration if you decide to switch back
/// to [Bot::get_updates].
///
/// [The official docs](https://core.telegram.org/bots/api#deletewebhook).
///
/// [Bot::get_updates]: crate::Bot::get_updates
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct DeleteWebhook {
#[serde(skip_serializing)]
bot: Bot,
}
#[async_trait::async_trait]
impl Request for DeleteWebhook {
type Output = True;
#[allow(clippy::trivially_copy_pass_by_ref)]
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "deleteWebhook", &self).await
}
}
impl DeleteWebhook {
pub(crate) fn new(bot: Bot) -> Self {
Self { bot }
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,91 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, InlineKeyboardMarkup, Message, ParseMode},
Bot,
};
/// Use this method to edit captions of messages sent by the bot.
///
/// On success, the edited [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption).
///
/// [`Message`]: crate::types::Message
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditMessageCaption {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
message_id: i32,
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<C>(bot: Bot, chat_id: C, message_id: i32) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, message_id, caption: None, parse_mode: 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
}
/// Identifier of the message to edit
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = 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
}
}

View file

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

View file

@ -1,85 +0,0 @@
use crate::{
net,
requests::{form_builder::FormBuilder, Request, ResponseResult},
types::{ChatId, InlineKeyboardMarkup, InputMedia, Message},
Bot,
};
/// Use this method to edit animation, audio, document, photo, or video
/// messages.
///
/// If a message is a part of a message album, then it can be edited only to a
/// photo or a video. Otherwise, message type can be changed arbitrarily. On
/// success, the edited [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia).
///
/// [`Message`]: crate::types::Message
#[derive(Debug, Clone)]
pub struct EditMessageMedia {
bot: Bot,
chat_id: ChatId,
message_id: i32,
media: InputMedia,
reply_markup: Option<InlineKeyboardMarkup>,
}
#[async_trait::async_trait]
impl Request for EditMessageMedia {
type Output = Message;
async fn send(&self) -> ResponseResult<Message> {
net::request_multipart(
self.bot.client(),
self.bot.token(),
"editMessageMedia",
FormBuilder::new()
.add_text("chat_id", &self.chat_id)
.add_text("message_id", &self.message_id)
.add_text("media", &self.media)
.add_text("reply_markup", &self.reply_markup)
.build(),
)
.await
}
}
impl EditMessageMedia {
pub(crate) fn new<C>(bot: Bot, chat_id: C, message_id: i32, media: InputMedia) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, message_id, media, reply_markup: None }
}
/// Unique identifier for the target chat or username of the target channel
/// (in the format `@channelusername`)
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Identifier of the message to edit
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = 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
}
}

View file

@ -1,69 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, InlineKeyboardMarkup, Message},
Bot,
};
/// Use this method to edit only the reply markup of messages.
///
/// On success, the edited [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup).
///
/// [`Message`]: crate::types::Message
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditMessageReplyMarkup {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
message_id: i32,
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<C>(bot: Bot, chat_id: C, message_id: i32) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, message_id, reply_markup: None }
}
/// Unique identifier for the target chat or username of the target channel
/// (in the format `@channelusername`)
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Identifier of the message to edit
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = val;
self
}
/// A JSON-serialized object for an [inline keyboard].
///
/// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating
pub fn reply_markup(mut self, val: InlineKeyboardMarkup) -> Self {
self.reply_markup = Some(val);
self
}
}

View file

@ -1,107 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, InlineKeyboardMarkup, Message, ParseMode},
Bot,
};
/// Use this method to edit text and game messages.
///
/// On success, the edited [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagetext).
///
/// [`Message`]: crate::types::Message
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditMessageText {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
message_id: i32,
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<C, T>(bot: Bot, chat_id: C, message_id: i32, text: T) -> Self
where
C: Into<ChatId>,
T: Into<String>,
{
let chat_id = chat_id.into();
let text = text.into();
Self {
bot,
chat_id,
message_id,
text,
parse_mode: None,
disable_web_page_preview: 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
}
/// Identifier of the message to edit
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = 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
}
}

View file

@ -1,65 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::ChatId,
Bot,
};
/// Use this method to generate a new invite link for a chat; any previously
/// generated link is revoked.
///
/// The bot must be an administrator in the chat for this to work and must have
/// the appropriate admin rights.
///
/// ## Note
/// Each administrator in a chat generates their own invite links. Bots can't
/// use invite links generated by other administrators. If you want your bot to
/// work with invite links, it will need to generate its own link using
/// [`Bot::export_chat_invite_link`] after this the link will become available
/// to the bot via the [`Bot::get_chat`] method. If your bot needs to generate a
/// new invite link replacing its previous one, use
/// [`Bot::export_chat_invite_link`] again.
///
/// [The official docs](https://core.telegram.org/bots/api#exportchatinvitelink).
///
/// [`Bot::export_chat_invite_link`]: crate::Bot::export_chat_invite_link
/// [`Bot::get_chat`]: crate::Bot::get_chat
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct ExportChatInviteLink {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
}
#[async_trait::async_trait]
impl Request for ExportChatInviteLink {
type Output = String;
/// Returns the new invite link as `String` on success.
async fn send(&self) -> ResponseResult<String> {
net::request_json(self.bot.client(), self.bot.token(), "exportChatInviteLink", &self).await
}
}
impl ExportChatInviteLink {
pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id }
}
/// Unique identifier for the target chat or username of the target channel
/// (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
}

View file

@ -1,81 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, Message},
Bot,
};
/// Use this method to forward messages of any kind.
///
/// [`The official docs`](https://core.telegram.org/bots/api#forwardmessage).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct ForwardMessage {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
from_chat_id: ChatId,
disable_notification: Option<bool>,
message_id: i32,
}
#[async_trait::async_trait]
impl Request for ForwardMessage {
type Output = Message;
async fn send(&self) -> ResponseResult<Message> {
net::request_json(self.bot.client(), self.bot.token(), "forwardMessage", &self).await
}
}
impl ForwardMessage {
pub(crate) fn new<C, F>(bot: Bot, chat_id: C, from_chat_id: F, message_id: i32) -> Self
where
C: Into<ChatId>,
F: Into<ChatId>,
{
let chat_id = chat_id.into();
let from_chat_id = from_chat_id.into();
Self { bot, chat_id, from_chat_id, message_id, disable_notification: None }
}
/// Unique identifier for the target chat or username of the target channel
/// (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Unique identifier for the chat where the original message was sent (or
/// channel username in the format `@channelusername`).
#[allow(clippy::wrong_self_convention)]
pub fn from_chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.from_chat_id = val.into();
self
}
/// Sends the message [silently]. Users will receive a notification with no
/// sound.
///
/// [silently]: https://telegram.org/blog/channels-2-0#silent-messages
pub fn disable_notification(mut self, val: bool) -> Self {
self.disable_notification = Some(val);
self
}
/// Message identifier in the chat specified in [`from_chat_id`].
///
/// [`from_chat_id`]: ForwardMessage::from_chat_id
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = val;
self
}
}

View file

@ -1,50 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{Chat, ChatId},
Bot,
};
/// Use this method to get up to date information about the chat (current name
/// of the user for one-on-one conversations, current username of a user, group
/// or channel, etc.).
///
/// [The official docs](https://core.telegram.org/bots/api#getchat).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct GetChat {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
}
#[async_trait::async_trait]
impl Request for GetChat {
type Output = Chat;
async fn send(&self) -> ResponseResult<Chat> {
net::request_json(self.bot.client(), self.bot.token(), "getChat", &self).await
}
}
impl GetChat {
pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id }
}
/// Unique identifier for the target chat or username of the target
/// supergroup or channel (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
}

View file

@ -1,53 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, ChatMember},
Bot,
};
/// Use this method to get a list of administrators in a chat.
///
/// If the chat is a group or a supergroup and no administrators were appointed,
/// only the creator will be returned.
///
/// [The official docs](https://core.telegram.org/bots/api#getchatadministrators).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct GetChatAdministrators {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
}
#[async_trait::async_trait]
impl Request for GetChatAdministrators {
type Output = Vec<ChatMember>;
/// On success, returns an array that contains information about all chat
/// administrators except other bots.
async fn send(&self) -> ResponseResult<Vec<ChatMember>> {
net::request_json(self.bot.client(), self.bot.token(), "getChatAdministrators", &self).await
}
}
impl GetChatAdministrators {
pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id }
}
/// Unique identifier for the target chat or username of the target
/// supergroup or channel (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
}

View file

@ -1,55 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, ChatMember},
Bot,
};
/// Use this method to get information about a member of a chat.
///
/// [The official docs](https://core.telegram.org/bots/api#getchatmember).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct GetChatMember {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
user_id: i32,
}
#[async_trait::async_trait]
impl Request for GetChatMember {
type Output = ChatMember;
async fn send(&self) -> ResponseResult<ChatMember> {
net::request_json(self.bot.client(), self.bot.token(), "getChatMember", &self).await
}
}
impl GetChatMember {
pub(crate) fn new<C>(bot: Bot, chat_id: C, user_id: i32) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, user_id }
}
/// Unique identifier for the target chat or username of the target
/// supergroup or channel (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Unique identifier of the target user.
pub fn user_id(mut self, val: i32) -> Self {
self.user_id = val;
self
}
}

View file

@ -1,48 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::ChatId,
Bot,
};
/// Use this method to get the number of members in a chat.
///
/// [The official docs](https://core.telegram.org/bots/api#getchatmemberscount).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct GetChatMembersCount {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
}
#[async_trait::async_trait]
impl Request for GetChatMembersCount {
type Output = i32;
async fn send(&self) -> ResponseResult<i32> {
net::request_json(self.bot.client(), self.bot.token(), "getChatMembersCount", &self).await
}
}
impl GetChatMembersCount {
pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id }
}
/// Unique identifier for the target chat or username of the target
/// supergroup or channel (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
}

View file

@ -1,62 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::File,
Bot,
};
/// Use this method to get basic info about a file and prepare it for
/// downloading.
///
/// For the moment, bots can download files of up to `20MB` in size.
///
/// The file can then be downloaded via the link
/// `https://api.telegram.org/file/bot<token>/<file_path>`, where `<file_path>`
/// is taken from the response. It is guaranteed that the link will be valid
/// for at least `1` hour. When the link expires, a new one can be requested by
/// calling [`GetFile`] again.
///
/// **Note**: This function may not preserve the original file name and MIME
/// type. You should save the file's MIME type and name (if available) when the
/// [`File`] object is received.
///
/// [The official docs](https://core.telegram.org/bots/api#getfile).
///
/// [`File`]: crate::types::File
/// [`GetFile`]: self::GetFile
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct GetFile {
#[serde(skip_serializing)]
bot: Bot,
file_id: String,
}
#[async_trait::async_trait]
impl Request for GetFile {
type Output = File;
async fn send(&self) -> ResponseResult<File> {
net::request_json(self.bot.client(), self.bot.token(), "getFile", &self).await
}
}
impl GetFile {
pub(crate) fn new<F>(bot: Bot, file_id: F) -> Self
where
F: Into<String>,
{
Self { bot, file_id: file_id.into() }
}
/// File identifier to get info about.
pub fn file_id<F>(mut self, value: F) -> Self
where
F: Into<String>,
{
self.file_id = value.into();
self
}
}

View file

@ -1,63 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{GameHighScore, TargetMessage},
Bot,
};
/// Use this method to get data for high score tables.
///
/// Will return the score of the specified user and several of his neighbors in
/// a game.
///
/// ## Note
/// This method will currently return scores for the target user, plus two of
/// his closest neighbors on each side. Will also return the top three users if
/// the user and his neighbors are not among them. Please note that this
/// behavior is subject to change.
///
/// [The official docs](https://core.telegram.org/bots/api#getgamehighscores)
#[derive(Debug, Clone, Serialize)]
pub struct GetGameHighScores {
#[serde(skip_serializing)]
bot: Bot,
#[serde(flatten)]
target: TargetMessage,
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<T>(bot: Bot, target: T, user_id: i32) -> Self
where
T: Into<TargetMessage>,
{
let target = target.into();
Self { bot, target, user_id }
}
/// Target message, either chat id and message id or inline message id.
pub fn target<T>(mut self, val: T) -> Self
where
T: Into<TargetMessage>,
{
self.target = val.into();
self
}
/// Target user id.
pub fn user_id(mut self, val: i32) -> Self {
self.user_id = val;
self
}
}

View file

@ -1,33 +0,0 @@
use crate::{
net,
requests::{Request, ResponseResult},
types::Me,
Bot,
};
use serde::Serialize;
/// A simple method for testing your bot's auth token. Requires no parameters.
///
/// [The official docs](https://core.telegram.org/bots/api#getme).
#[derive(Debug, Clone, Serialize)]
pub struct GetMe {
#[serde(skip_serializing)]
bot: Bot,
}
#[async_trait::async_trait]
impl Request for GetMe {
type Output = Me;
/// Returns basic information about the bot.
#[allow(clippy::trivially_copy_pass_by_ref)]
async fn send(&self) -> ResponseResult<Me> {
net::request_json(self.bot.client(), self.bot.token(), "getMe", &self).await
}
}
impl GetMe {
pub(crate) fn new(bot: Bot) -> Self {
Self { bot }
}
}

View file

@ -1,33 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::BotCommand,
Bot,
};
/// Use this method to get the current list of the bot's commands.
///
/// [The official docs](https://core.telegram.org/bots/api#getmycommands).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct GetMyCommands {
#[serde(skip_serializing)]
bot: Bot,
}
#[async_trait::async_trait]
impl Request for GetMyCommands {
type Output = Vec<BotCommand>;
async fn send(&self) -> ResponseResult<Self::Output> {
net::request_json(self.bot.client(), self.bot.token(), "getMyCommands", &self).await
}
}
impl GetMyCommands {
pub(crate) fn new(bot: Bot) -> Self {
Self { bot }
}
}

View file

@ -1,47 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::StickerSet,
Bot,
};
/// Use this method to get a sticker set.
///
/// [The official docs](https://core.telegram.org/bots/api#getstickerset).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct GetStickerSet {
#[serde(skip_serializing)]
bot: Bot,
name: String,
}
#[async_trait::async_trait]
impl Request for GetStickerSet {
type Output = StickerSet;
async fn send(&self) -> ResponseResult<StickerSet> {
net::request_json(self.bot.client(), self.bot.token(), "getStickerSet", &self).await
}
}
impl GetStickerSet {
pub(crate) fn new<N>(bot: Bot, name: N) -> Self
where
N: Into<String>,
{
let name = name.into();
Self { bot, name }
}
/// Name of the sticker set.
pub fn name<T>(mut self, val: T) -> Self
where
T: Into<String>,
{
self.name = val.into();
self
}
}

View file

@ -1,122 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{AllowedUpdate, Update},
Bot, RequestError,
};
use serde_json::Value;
/// Use this method to receive incoming updates using long polling ([wiki]).
///
/// **Notes:**
/// 1. This method will not work if an outgoing webhook is set up.
/// 2. In order to avoid getting duplicate updates,
/// recalculate offset after each server response.
///
/// [The official docs](https://core.telegram.org/bots/api#getupdates).
///
/// [wiki]: https://en.wikipedia.org/wiki/Push_technology#Long_polling
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct GetUpdates {
#[serde(skip_serializing)]
bot: Bot,
pub(crate) offset: Option<i32>,
pub(crate) limit: Option<u8>,
pub(crate) timeout: Option<u32>,
pub(crate) allowed_updates: Option<Vec<AllowedUpdate>>,
}
#[async_trait::async_trait]
impl Request for GetUpdates {
type Output = Vec<Result<Update, (Value, serde_json::Error)>>;
/// Deserialize to `Vec<serde_json::Result<Update>>` instead of
/// `Vec<Update>`, because we want to parse the rest of updates even if our
/// library hasn't parsed one.
async fn send(&self) -> ResponseResult<Vec<Result<Update, (Value, serde_json::Error)>>> {
let value: Value =
net::request_json(self.bot.client(), self.bot.token(), "getUpdates", &self).await?;
match value {
Value::Array(array) => Ok(array
.into_iter()
.map(|value| Update::try_parse(&value).map_err(|error| (value, error)))
.collect()),
_ => Err(RequestError::InvalidJson(
serde_json::from_value::<Vec<Update>>(value)
.expect_err("get_update must return Value::Array"),
)),
}
}
}
impl GetUpdates {
pub(crate) fn new(bot: Bot) -> Self {
Self { bot, offset: None, limit: None, timeout: None, allowed_updates: None }
}
/// Identifier of the first update to be returned.
///
/// Must be greater by one than the highest among the identifiers of
/// previously received updates. By default, updates starting with the
/// earliest unconfirmed update are returned. An update is considered
/// confirmed as soon as [`GetUpdates`] is called with an [`offset`]
/// higher than its [`id`]. The negative offset can be specified to
/// retrieve updates starting from `-offset` update from the end of the
/// updates queue. All previous updates will forgotten.
///
/// [`GetUpdates`]: self::GetUpdates
/// [`offset`]: self::GetUpdates::offset
/// [`id`]: crate::types::Update::id
pub fn offset(mut self, value: i32) -> Self {
self.offset = Some(value);
self
}
/// Limits the number of updates to be retrieved.
///
/// Values between `1`—`100` are accepted. Defaults to `100`.
pub fn limit(mut self, value: u8) -> Self {
self.limit = Some(value);
self
}
/// Timeout in seconds for long polling.
///
/// Defaults to `0`, i.e. usual short polling. Should be positive, short
/// polling should be used for testing purposes only.
pub fn timeout(mut self, value: u32) -> Self {
self.timeout = Some(value);
self
}
/// List the types of updates you want your bot to receive.
///
/// For example, specify [[`Message`], [`EditedChannelPost`],
/// [`CallbackQuery`]] to only receive updates of these types.
/// See [`AllowedUpdate`] for a complete list of available update types.
///
/// Specify an empty list to receive all updates regardless of type
/// (default). If not specified, the previous setting will be used.
///
/// **Note:**
/// This parameter doesn't affect updates created before the call to the
/// [`Bot::get_updates`], so unwanted updates may be received for a short
/// period of time.
///
/// [`Message`]: self::AllowedUpdate::Message
/// [`EditedChannelPost`]: self::AllowedUpdate::EditedChannelPost
/// [`CallbackQuery`]: self::AllowedUpdate::CallbackQuery
/// [`AllowedUpdate`]: self::AllowedUpdate
/// [`Bot::get_updates`]: crate::Bot::get_updates
pub fn allowed_updates<T>(mut self, value: T) -> Self
where
T: Into<Vec<AllowedUpdate>>,
{
self.allowed_updates = Some(value.into());
self
}
}

View file

@ -1,58 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::UserProfilePhotos,
Bot,
};
/// Use this method to get a list of profile pictures for a user.
///
/// [The official docs](https://core.telegram.org/bots/api#getuserprofilephotos).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct GetUserProfilePhotos {
#[serde(skip_serializing)]
bot: Bot,
user_id: i32,
offset: Option<i32>,
limit: Option<i32>,
}
#[async_trait::async_trait]
impl Request for GetUserProfilePhotos {
type Output = UserProfilePhotos;
async fn send(&self) -> ResponseResult<UserProfilePhotos> {
net::request_json(self.bot.client(), self.bot.token(), "getUserProfilePhotos", &self).await
}
}
impl GetUserProfilePhotos {
pub(crate) fn new(bot: Bot, user_id: i32) -> Self {
Self { bot, user_id, offset: None, limit: None }
}
/// Unique identifier of the target user.
pub fn user_id(mut self, val: i32) -> Self {
self.user_id = val;
self
}
/// Sequential number of the first photo to be returned. By default, all
/// photos are returned.
pub fn offset(mut self, val: i32) -> Self {
self.offset = Some(val);
self
}
/// Limits the number of photos to be retrieved. Values between 1—100 are
/// accepted.
///
/// Defaults to 100.
pub fn limit(mut self, val: i32) -> Self {
self.limit = Some(val);
self
}
}

View file

@ -1,38 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::WebhookInfo,
Bot,
};
/// Use this method to get current webhook status.
///
/// If the bot is using [`Bot::get_updates`], will return an object with the url
/// field empty.
///
/// [The official docs](https://core.telegram.org/bots/api#getwebhookinfo).
///
/// [`Bot::get_updates`]: crate::Bot::get_updates
#[derive(Debug, Clone, Serialize)]
pub struct GetWebhookInfo {
#[serde(skip_serializing)]
bot: Bot,
}
#[async_trait::async_trait]
impl Request for GetWebhookInfo {
type Output = WebhookInfo;
#[allow(clippy::trivially_copy_pass_by_ref)]
async fn send(&self) -> ResponseResult<WebhookInfo> {
net::request_json(self.bot.client(), self.bot.token(), "getWebhookInfo", &self).await
}
}
impl GetWebhookInfo {
pub(crate) fn new(bot: Bot) -> Self {
Self { bot }
}
}

View file

@ -1,72 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, True},
Bot,
};
/// Use this method to kick a user from a group, a supergroup or a channel.
///
/// In the case of supergroups and channels, the user will not be able to return
/// to the group on their own using invite links, etc., unless [unbanned] first.
/// The bot must be an administrator in the chat for this to work and must have
/// the appropriate admin rights.
///
/// [The official docs](https://core.telegram.org/bots/api#kickchatmember).
///
/// [unbanned]: crate::Bot::unban_chat_member
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct KickChatMember {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
user_id: i32,
until_date: Option<i32>,
}
#[async_trait::async_trait]
impl Request for KickChatMember {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "kickChatMember", &self).await
}
}
impl KickChatMember {
pub(crate) fn new<C>(bot: Bot, chat_id: C, user_id: i32) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, user_id, until_date: None }
}
/// Unique identifier for the target group or username of the target
/// supergroup or channel (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Unique identifier of the target user.
pub fn user_id(mut self, val: i32) -> Self {
self.user_id = val;
self
}
/// Date when the user will be unbanned, unix time.
///
/// If user is banned for more than 366 days or less than 30 seconds from
/// the current time they are considered to be banned forever.
pub fn until_date(mut self, val: i32) -> Self {
self.until_date = Some(val);
self
}
}

View file

@ -1,48 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, True},
Bot,
};
/// Use this method for your bot to leave a group, supergroup or channel.
///
/// [The official docs](https://core.telegram.org/bots/api#leavechat).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct LeaveChat {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
}
#[async_trait::async_trait]
impl Request for LeaveChat {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "leaveChat", &self).await
}
}
impl LeaveChat {
pub(crate) fn new<C>(bot: Bot, chat_id: C) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id }
}
/// Unique identifier for the target chat or username of the target
/// supergroup or channel (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
}

View file

@ -1,152 +0,0 @@
mod add_sticker_to_set;
mod answer_callback_query;
mod answer_inline_query;
mod answer_pre_checkout_query;
mod answer_shipping_query;
mod create_new_sticker_set;
mod delete_chat_photo;
mod delete_chat_sticker_set;
mod delete_message;
mod delete_sticker_from_set;
mod delete_webhook;
mod edit_inline_message_caption;
mod edit_inline_message_live_location;
mod edit_inline_message_media;
mod edit_inline_message_reply_markup;
mod edit_inline_message_text;
mod edit_message_caption;
mod edit_message_live_location;
mod edit_message_media;
mod edit_message_reply_markup;
mod edit_message_text;
mod export_chat_invite_link;
mod forward_message;
mod get_chat;
mod get_chat_administrators;
mod get_chat_member;
mod get_chat_members_count;
mod get_file;
mod get_game_high_scores;
mod get_me;
mod get_my_commands;
mod get_sticker_set;
mod get_updates;
mod get_user_profile_photos;
mod get_webhook_info;
mod kick_chat_member;
mod leave_chat;
mod pin_chat_message;
mod promote_chat_member;
mod restrict_chat_member;
mod send_animation;
mod send_audio;
mod send_chat_action;
mod send_contact;
mod send_dice;
mod send_document;
mod send_game;
mod send_invoice;
mod send_location;
mod send_media_group;
mod send_message;
mod send_photo;
mod send_poll;
mod send_sticker;
mod send_venue;
mod send_video;
mod send_video_note;
mod send_voice;
mod set_chat_administrator_custom_title;
mod set_chat_description;
mod set_chat_permissions;
mod set_chat_photo;
mod set_chat_sticker_set;
mod set_chat_title;
mod set_game_score;
mod set_my_commands;
mod set_sticker_position_in_set;
mod set_sticker_set_thumb;
mod set_webhook;
mod stop_inline_message_live_location;
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_inline_message_caption::*;
pub use edit_inline_message_live_location::*;
pub use edit_inline_message_media::*;
pub use edit_inline_message_reply_markup::*;
pub use edit_inline_message_text::*;
pub use edit_message_caption::*;
pub use edit_message_live_location::*;
pub use edit_message_media::*;
pub use edit_message_reply_markup::*;
pub use edit_message_text::*;
pub use export_chat_invite_link::*;
pub use forward_message::*;
pub use get_chat::*;
pub use get_chat_administrators::*;
pub use get_chat_member::*;
pub use get_chat_members_count::*;
pub use get_file::*;
pub use get_game_high_scores::*;
pub use get_me::*;
pub use get_my_commands::*;
pub use get_sticker_set::*;
pub use get_updates::*;
pub use get_user_profile_photos::*;
pub use get_webhook_info::*;
pub use kick_chat_member::*;
pub use leave_chat::*;
pub use pin_chat_message::*;
pub use promote_chat_member::*;
pub use restrict_chat_member::*;
pub use send_animation::*;
pub use send_audio::*;
pub use send_chat_action::*;
pub use send_contact::*;
pub use send_dice::*;
pub use send_document::*;
pub use send_game::*;
pub use send_invoice::*;
pub use send_location::*;
pub use send_media_group::*;
pub use send_message::*;
pub use send_photo::*;
pub use send_poll::*;
pub use send_sticker::*;
pub use send_venue::*;
pub use send_video::*;
pub use send_video_note::*;
pub use send_voice::*;
pub use set_chat_administrator_custom_title::*;
pub use set_chat_description::*;
pub use set_chat_permissions::*;
pub use set_chat_photo::*;
pub use set_chat_sticker_set::*;
pub use set_chat_title::*;
pub use set_game_score::*;
pub use set_my_commands::*;
pub use set_sticker_position_in_set::*;
pub use set_sticker_set_thumb::*;
pub use set_webhook::*;
pub use std::pin::Pin;
pub use stop_inline_message_live_location::*;
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::*;

View file

@ -1,69 +0,0 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatId, True},
Bot,
};
/// Use this method to pin a message in a group, a supergroup, or a channel.
///
/// The bot must be an administrator in the chat for this to work and must have
/// the `can_pin_messages` admin right in the supergroup or `can_edit_messages`
/// admin right in the channel.
///
/// [The official docs](https://core.telegram.org/bots/api#pinchatmessage).
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct PinChatMessage {
#[serde(skip_serializing)]
bot: Bot,
chat_id: ChatId,
message_id: i32,
disable_notification: Option<bool>,
}
#[async_trait::async_trait]
impl Request for PinChatMessage {
type Output = True;
async fn send(&self) -> ResponseResult<True> {
net::request_json(self.bot.client(), self.bot.token(), "pinChatMessage", &self).await
}
}
impl PinChatMessage {
pub(crate) fn new<C>(bot: Bot, chat_id: C, message_id: i32) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, message_id, disable_notification: None }
}
/// Unique identifier for the target chat or username of the target channel
/// (in the format `@channelusername`).
pub fn chat_id<T>(mut self, val: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = val.into();
self
}
/// Identifier of a message to pin.
pub fn message_id(mut self, val: i32) -> Self {
self.message_id = val;
self
}
/// Pass `true`, if it is not necessary to send a notification to all chat
/// members about the new pinned message.
///
/// Notifications are always disabled in channels.
pub fn disable_notification(mut self, val: bool) -> Self {
self.disable_notification = Some(val);
self
}
}

Some files were not shown because too many files have changed in this diff Show more