Merge branch 'dev' into v0.3.1_release

This commit is contained in:
Waffle Lapkin 2020-09-26 19:01:16 +03:00 committed by GitHub
commit 814adabdb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 1116 additions and 323 deletions

View file

@ -3,7 +3,7 @@ on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
branches: [ master, dev ]
name: Continuous integration
@ -27,23 +27,52 @@ jobs:
override: true
- name: Cargo clippy
run: cargo clippy --all --all-targets --all-features -- -D warnings
stable-test:
test:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- beta
- nightly
include:
- rust: stable
features: "--features \"redis-storage cbor-serializer bincode-serializer frunk-\""
- rust: beta
features: "--features \"redis-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: stable
toolchain: ${{ matrix.rust }}
override: true
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --verbose ${{ matrix.features }}
- name: Setup redis
run: |
sudo apt install redis-server
redis-server --port 7777 > /dev/null &
redis-server --port 7778 > /dev/null &
redis-server --port 7779 > /dev/null &
- name: Cargo test
run: cargo test --all --all-features
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose ${{ matrix.features }}
build-example:
runs-on: ubuntu-latest
strategy:
@ -65,5 +94,5 @@ jobs:
profile: minimal
toolchain: stable
override: true
- name: Test the example
- name: Check the example
run: cd examples && cd ${{ matrix.example }} && cargo check

View file

@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [unreleased]
### Added
- 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(())`.
### Changed
- 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
versions (e.g.: `edit_message_text` and `edit_inline_message_text`). Instead of `ChatOrInlineMessage` common versions
accept `chat_id: impl Into<ChatId>` and `message_id: i32` whereas inline versions accept
`inline_message_id: impl Into<String>`. Also note that return type of inline versions is `True` ([issue 253], [pr 257])
- `ChatOrInlineMessage` is renamed to `TargetMessage`, it's `::Chat` variant is renamed to `::Common`,
`#[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])
[issue 253]: https://github.com/teloxide/teloxide/issues/253
[pr 257]: https://github.com/teloxide/teloxide/pull/257
## [0.3.1] - 2020-08-25
### Added

View file

@ -1,7 +1,8 @@
# Contributing
Before contributing, please read [our code style](https://github.com/teloxide/teloxide/blob/master/CODE_STYLE.md) and [the license](https://github.com/teloxide/teloxide/blob/master/LICENSE).
To change the source code, fork the `master` branch of this repository and work inside your own branch. Then send us a PR into `master` branch and wait for the CI to check everything. However, you'd better check changes first locally:
To change the source code, fork the `dev` branch of this repository and work inside your own branch. Then send us a PR into `dev` branch and wait for the CI to check everything. However, you'd better check changes first locally:
```
cargo clippy --all --all-features --all-targets

View file

@ -30,6 +30,8 @@ bincode-serializer = ["bincode"]
frunk- = ["frunk"]
nightly = [] # currently used only for `README.md` tests
[dependencies]
serde_json = "1.0.55"
serde = { version = "1.0.114", features = ["derive"] }
@ -55,7 +57,7 @@ serde_cbor = { version = "0.11.1", optional = true }
bincode = { version = "1.3.1", optional = true }
frunk = { version = "0.3.1", optional = true }
teloxide-macros = "0.3.2"
teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master" }
[dev-dependencies]
smart-default = "0.6.0"
@ -63,3 +65,11 @@ rand = "0.7.3"
pretty_env_logger = "0.4.0"
lazy_static = "1.4.0"
tokio = { version = "0.2.21", features = ["fs", "stream", "rt-threaded", "macros"] }
[package.metadata."docs.rs"]
all-features = true
[[test]]
name = "redis"
path = "tests/redis.rs"
required-features = ["redis-storage", "cbor-serializer", "bincode-serializer"]

View file

@ -1,52 +1,54 @@
<div align="center">
<img src="ICON.png" width="250"/>
<h1>teloxide</h1>
<a href="https://docs.rs/teloxide/">
<img src="https://docs.rs/teloxide/badge.svg">
</a>
<a href="https://github.com/teloxide/teloxide/actions">
<img src="https://github.com/teloxide/teloxide/workflows/Continuous%20integration/badge.svg">
</a>
<a href="https://teloxide.netlify.com">
<img src="https://img.shields.io/badge/docs-dev-blue)">
</a>
<a href="https://crates.io/crates/teloxide">
<img src="https://img.shields.io/crates/v/teloxide.svg">
</a>
<a href="https://core.telegram.org/bots/api">
<img src="https://img.shields.io/badge/API coverage-Up to 0.4.9 (inclusively)-green.svg">
</a>
<a href="https://t.me/teloxide">
<img src="https://img.shields.io/badge/official%20chat-t.me%2Fteloxide-blueviolet">
</a>
<a href="https://core.telegram.org/bots/api">
<img src="https://img.shields.io/badge/API coverage-Up to 0.4.7 (inclusively)-green.svg">
</a>
A full-featured framework that empowers you to easily build [Telegram bots](https://telegram.org/blog/bot-revolution) using the [`async`/`.await`](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html) syntax in [Rust](https://www.rust-lang.org/). It handles all the difficult stuff so you can focus only on your business logic.
</div>
## Table of contents
- [Highlights](https://github.com/teloxide/teloxide#highlights)
- [Setting up your environment](https://github.com/teloxide/teloxide#setting-up-your-environment)
- [API overview](https://github.com/teloxide/teloxide#api-overview)
- [The dices bot](https://github.com/teloxide/teloxide#the-dices-bot)
- [Commands](https://github.com/teloxide/teloxide#commands)
- [Dialogues management](https://github.com/teloxide/teloxide#dialogues-management)
- [Recommendations](https://github.com/teloxide/teloxide#recommendations)
- [Cargo features](https://github.com/teloxide/teloxide#cargo-features)
- [FAQ](https://github.com/teloxide/teloxide#faq)
- [Community bots](https://github.com/teloxide/teloxide#community-bots)
- [Contributing](https://github.com/teloxide/teloxide#contributing)
- [Highlights](#highlights)
- [Setting up your environment](#setting-up-your-environment)
- [API overview](#api-overview)
- [The dices bot](#the-dices-bot)
- [Commands](#commands)
- [Dialogues management](#dialogues-management)
- [Recommendations](#recommendations)
- [Cargo features](#cargo-features)
- [FAQ](#faq)
- [Community bots](#community-bots)
- [Contributing](#contributing)
## Highlights
- **Functional reactive design.** teloxide has [functional reactive design], allowing you to declaratively manipulate streams of updates from Telegram using filters, maps, folds, zips, and a lot of [other adaptors].
- **Functional reactive design.** teloxide follows [functional reactive design], allowing you to declaratively manipulate streams of updates from Telegram using filters, maps, folds, zips, and a lot of [other adaptors].
[functional reactive design]: https://en.wikipedia.org/wiki/Functional_reactive_programming
[other adaptors]: https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html
- **Persistence.** Dialogues management is independent of how/where dialogues are stored: you can just replace one line and make them [persistent]. Out-of-the-box storages include [Redis].
- **Dialogues management subsystem.** We have designed our dialogues management subsystem to be easy-to-use, and, furthermore, to be independent of how/where dialogues are stored. For example, you can just replace one line to achieve [persistence]. Out-of-the-box storages include [Redis].
[persistent]: https://en.wikipedia.org/wiki/Persistence_(computer_science)
[persistence]: https://en.wikipedia.org/wiki/Persistence_(computer_science)
[Redis]: https://redis.io/
- **Strongly typed bot commands.** You can describe bot commands as enumerations, and then they'll be automatically constructed from strings. Just like you describe JSON structures in [serde-json] and command-line arguments in [structopt].
- **Strongly typed bot commands.** You can describe bot commands as enumerations, and then they'll be automatically constructed from strings — just like JSON structures in [serde-json] and command-line arguments in [structopt].
[structopt]: https://github.com/TeXitoi/structopt
[serde-json]: https://github.com/serde-rs/json
@ -90,8 +92,8 @@ tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
### The dices bot
This bot throws a dice on each incoming message:
([Full](https://github.com/teloxide/teloxide/blob/master/examples/dices_bot/src/main.rs))
```rust
([Full](./examples/dices_bot/src/main.rs))
```rust,no_run
use teloxide::prelude::*;
#[tokio::main]
@ -107,12 +109,11 @@ async fn main() {
})
.await;
}
```
<div align="center">
<kbd>
<img src=https://github.com/teloxide/teloxide/raw/master/media/DICES_BOT.gif />
<img src=../../raw/master/media/DICES_BOT.gif />
</kbd>
</div>
@ -126,9 +127,9 @@ Commands are strongly typed and defined declaratively, similar to how we define
[structopt]: https://docs.rs/structopt/0.3.9/structopt/
[serde-json]: https://github.com/serde-rs/json
([Full](https://github.com/teloxide/teloxide/blob/master/examples/simple_commands_bot/src/main.rs))
```rust
// Imports are omitted...
([Full](./examples/simple_commands_bot/src/main.rs))
```rust,no_run
use teloxide::{utils::command::BotCommand, prelude::*};
#[derive(BotCommand)]
#[command(rename = "lowercase", description = "These commands are supported:")]
@ -162,13 +163,14 @@ async fn main() {
let bot = Bot::from_env();
teloxide::commands_repl(bot, panic!("Your bot's name here"), answer).await;
let bot_name: String = panic!("Your bot's name here");
teloxide::commands_repl(bot, bot_name, answer).await;
}
```
<div align="center">
<kbd>
<img src=https://github.com/teloxide/teloxide/raw/master/media/SIMPLE_COMMANDS_BOT.gif />
<img src=../../raw/master/media/SIMPLE_COMMANDS_BOT.gif />
</kbd>
</div>
@ -179,8 +181,8 @@ A dialogue is described by an enumeration, where each variant is one of possible
Below is a bot, which asks you three questions and then sends the answers back to you. First, let's start with an enumeration (a collection of our dialogue's states):
([dialogue_bot/src/dialogue/mod.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/dialogue/mod.rs))
```rust
([dialogue_bot/src/dialogue/mod.rs](./examples/dialogue_bot/src/dialogue/mod.rs))
```rust,ignore
// Imports are omitted...
#[derive(Transition, From)]
@ -203,8 +205,8 @@ When a user sends a message to our bot, and such a dialogue does not yet exist,
<details>
<summary>Dialogue::Start</summary>
([dialogue_bot/src/dialogue/states/start.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/dialogue/states/start.rs))
```rust
([dialogue_bot/src/dialogue/states/start.rs](./examples/dialogue_bot/src/dialogue/states/start.rs))
```rust,ignore
// Imports are omitted...
pub struct StartState;
@ -221,8 +223,8 @@ async fn start(_state: StartState, cx: TransitionIn, _ans: String) -> Transition
<details>
<summary>Dialogue::ReceiveFullName</summary>
([dialogue_bot/src/dialogue/states/receive_full_name.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/dialogue/states/receive_full_name.rs))
```rust
([dialogue_bot/src/dialogue/states/receive_full_name.rs](./examples/dialogue_bot/src/dialogue/states/receive_full_name.rs))
```rust,ignore
// Imports are omitted...
#[derive(Generic)]
@ -244,8 +246,8 @@ async fn receive_full_name(
<details>
<summary>Dialogue::ReceiveAge</summary>
([dialogue_bot/src/dialogue/states/receive_age.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/dialogue/states/receive_age.rs))
```rust
([dialogue_bot/src/dialogue/states/receive_age.rs](./examples/dialogue_bot/src/dialogue/states/receive_age.rs))
```rust,ignore
// Imports are omitted...
#[derive(Generic)]
@ -277,8 +279,8 @@ async fn receive_age_state(
<details>
<summary>Dialogue::ReceiveLocation</summary>
([dialogue_bot/src/dialogue/states/receive_location.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/dialogue/states/receive_location.rs))
```rust
([dialogue_bot/src/dialogue/states/receive_location.rs](./examples/dialogue_bot/src/dialogue/states/receive_location.rs))
```rust,ignore
// Imports are omitted...
#[derive(Generic)]
@ -305,8 +307,8 @@ All these subtransitions accept a corresponding state (one of the many variants
Finally, the `main` function looks like this:
([dialogue_bot/src/main.rs](https://github.com/teloxide/teloxide/blob/master/examples/dialogue_bot/src/main.rs))
```rust
([dialogue_bot/src/main.rs](./examples/dialogue_bot/src/main.rs))
```rust,ignore
// Imports are omitted...
#[tokio::main]
@ -335,11 +337,11 @@ async fn handle_message(cx: UpdateWithCx<Message>, dialogue: Dialogue) -> Transi
<div align="center">
<kbd>
<img src=https://github.com/teloxide/teloxide/raw/master/media/DIALOGUE_BOT.gif />
<img src=../../raw/master/media/DIALOGUE_BOT.gif />
</kbd>
</div>
[More examples!](https://github.com/teloxide/teloxide/tree/master/examples)
[More examples!](./examples)
## Recommendations
- Use this pattern:
@ -403,7 +405,7 @@ UPD: The current design spreads wide and deep trait bounds, thereby increasing c
Q: Can I use webhooks?
A: teloxide doesn't provide special API for working with webhooks due to their nature with lots of subtle settings. Instead, you setup your webhook by yourself, as shown in [`examples/ngrok_ping_pong_bot`](examples/ngrok_ping_pong_bot/src/main.rs) and [`examples/heroku_ping_pong_bot`](examples/heroku_ping_pong_bot/src/main.rs).
A: teloxide doesn't provide special API for working with webhooks due to their nature with lots of subtle settings. Instead, you setup your webhook by yourself, as shown in [`examples/ngrok_ping_pong_bot`](./examples/ngrok_ping_pong_bot/src/main.rs) and [`examples/heroku_ping_pong_bot`](./examples/heroku_ping_pong_bot/src/main.rs).
Associated links:
- [Marvin's Marvellous Guide to All Things Webhook](https://core.telegram.org/bots/webhooks)
@ -421,9 +423,11 @@ A: Yes. You can setup any logger, for example, [fern], e.g. teloxide has no spec
## Community bots
Feel free to push your own bot into our collection!
- [_Rust subreddit reader_](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/subreddit_reader)
- [_vzmuinebot -- Telegram bot for food menu navigate_](https://github.com/ArtHome12/vzmuinebot)
- [_Tepe -- A CLI to command a bot to send messages and files over Telegram_](https://lib.rs/crates/tepe)
- [_steadylearner/subreddit_reader_](https://github.com/steadylearner/Rust-Full-Stack/tree/master/commits/teloxide/subreddit_reader)
- [_ArtHome12/vzmuinebot -- Telegram bot for food menu navigate_](https://github.com/ArtHome12/vzmuinebot)
- [_Hermitter/tepe -- A CLI to command a bot to send messages and files over Telegram_](https://github.com/Hermitter/tepe)
- [_ArtHome12/cognito_bot -- The bot is designed to anonymize messages to a group_](https://github.com/ArtHome12/cognito_bot)
- [_GoldsteinE/tg-vimhelpbot -- Link `:help` for Vim in Telegram_](https://github.com/GoldsteinE/tg-vimhelpbot)
## Contributing
See [CONRIBUTING.md](https://github.com/teloxide/teloxide/blob/master/CONTRIBUTING.md).

View file

@ -2,11 +2,11 @@
Just enter the directory (for example, `cd dialogue_bot`) and execute `cargo run` to run an example. Don't forget to initialise the `TELOXIDE_TOKEN` environmental variable.
| Bot | Description |
|---|-----------|
| [dices_bot](dices_bot) | This bot throws a dice on each incoming message. |
| [dices_bot](dices_bot) | Throws a dice on each incoming message. |
| [ngrok_ping_pong_bot](ngrok_ping_pong_bot) | The ngrok version of ping-pong-bot that uses webhooks. |
| [heroku_ping_pong_bot](heroku_ping_pong_bot) | The Heroku version of ping-pong-bot that uses webhooks. |
| [simple_commands_bot](simple_commands_bot) | Shows how to deal with bot's commands. |
| [guess_a_number_bot](guess_a_number_bot) | The "guess a number" game. |
| [dialogue_bot](dialogue_bot) | Drive a dialogue with a user using a type-safe finite automaton. |
| [admin_bot](admin_bot) | A bot, which can ban, kick, and mute on a command. |
| [shared_state_bot](shared_state_bot) | A bot that shows how to deal with shared state. |
| [redis_remember_bot](redis_remember_bot) | Uses `RedisStorage` instead of `InMemStorage`. |
| [dialogue_bot](dialogue_bot) | How to deal with dialogues. |
| [admin_bot](admin_bot) | Ban, kick, and mute on a command. |
| [shared_state_bot](shared_state_bot) | How to deal with shared state. |

View file

@ -139,5 +139,6 @@ async fn run() {
let bot = Bot::from_env();
teloxide::commands_repl(bot, panic!("Your bot's name here"), action).await;
let bot_name: String = panic!("Your bot's name here");
teloxide::commands_repl(bot, bot_name, action).await;
}

View file

@ -17,7 +17,7 @@ futures = "0.3.5"
tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
teloxide = { path = "../../", features = ["frunk"] }
teloxide-macros = "0.3.2"
teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master" }
derive_more = "0.99.9"

View file

@ -15,7 +15,7 @@ async fn run() {
teloxide::repl(bot, |message| async move {
message.answer_dice().send().await?;
ResponseResult::<()>::Ok(())
respond(())
})
.await;
}

View file

@ -11,7 +11,7 @@ tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] }
# You can also choose "cbor-serializer" or built-in JSON serializer
teloxide = { path = "../../", features = ["redis-storage", "bincode-serializer"] }
teloxide-macros = "0.3.2"
teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master" }
serde = "1.0.104"
futures = "0.3.5"

View file

@ -36,5 +36,6 @@ async fn run() {
let bot = Bot::from_env();
teloxide::commands_repl(bot, panic!("Your bot's name here"), answer).await;
let bot_name: String = panic!("Your bot's name here");
teloxide::commands_repl(bot, bot_name, answer).await;
}

12
netlify.toml Normal file
View file

@ -0,0 +1,12 @@
[build]
# Directory (relative to root of your repo) that contains the deploy-ready
# HTML files and assets generated by the build. If a base directory has
# been specified, include it in the publish directory path.
publish = "target/doc"
# Default build command.
command = 'curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly --profile minimal && source $HOME/.cargo/env && RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --no-deps --all-features'
[[redirects]]
from = "/*"
to = "/teloxide"

View file

@ -2,26 +2,27 @@ use crate::{
requests::{
AddStickerToSet, AnswerCallbackQuery, AnswerInlineQuery, AnswerPreCheckoutQuery,
AnswerShippingQuery, CreateNewStickerSet, DeleteChatPhoto, DeleteChatStickerSet,
DeleteMessage, DeleteStickerFromSet, DeleteWebhook, EditMessageCaption,
EditMessageLiveLocation, EditMessageMedia, EditMessageReplyMarkup, EditMessageText,
ExportChatInviteLink, ForwardMessage, GetChat, GetChatAdministrators, GetChatMember,
GetChatMembersCount, GetFile, GetGameHighScores, GetMe, GetMyCommands, GetStickerSet,
GetUpdates, GetUserProfilePhotos, GetWebhookInfo, KickChatMember, LeaveChat,
PinChatMessage, PromoteChatMember, RestrictChatMember, SendAnimation, SendAudio,
SendChatAction, SendChatActionKind, SendContact, SendDice, SendDocument, SendGame,
SendInvoice, SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendPoll, SendSticker,
SendVenue, SendVideo, SendVideoNote, SendVoice, SetChatAdministratorCustomTitle,
SetChatDescription, SetChatPermissions, SetChatPhoto, SetChatStickerSet, SetChatTitle,
SetGameScore, SetMyCommands, SetStickerPositionInSet, SetStickerSetThumb, SetWebhook,
StopMessageLiveLocation, StopPoll, UnbanChatMember, UnpinChatMessage, UploadStickerFile,
DeleteMessage, DeleteStickerFromSet, DeleteWebhook, EditInlineMessageCaption,
EditInlineMessageLiveLocation, EditInlineMessageMedia, EditInlineMessageReplyMarkup,
EditInlineMessageText, EditMessageCaption, EditMessageLiveLocation, EditMessageMedia,
EditMessageReplyMarkup, EditMessageText, ExportChatInviteLink, ForwardMessage, GetChat,
GetChatAdministrators, GetChatMember, GetChatMembersCount, GetFile, GetGameHighScores,
GetMe, GetMyCommands, GetStickerSet, GetUpdates, GetUserProfilePhotos, GetWebhookInfo,
KickChatMember, LeaveChat, PinChatMessage, PromoteChatMember, RestrictChatMember,
SendAnimation, SendAudio, SendChatAction, SendChatActionKind, SendContact, SendDice,
SendDocument, SendGame, SendInvoice, SendLocation, SendMediaGroup, SendMessage, SendPhoto,
SendPoll, SendSticker, SendVenue, SendVideo, SendVideoNote, SendVoice,
SetChatAdministratorCustomTitle, SetChatDescription, SetChatPermissions, SetChatPhoto,
SetChatStickerSet, SetChatTitle, SetGameScore, SetMyCommands, SetStickerPositionInSet,
SetStickerSetThumb, SetWebhook, StopInlineMessageLiveLocation, StopMessageLiveLocation,
StopPoll, UnbanChatMember, UnpinChatMessage, UploadStickerFile,
},
types::{
BotCommand, ChatId, ChatOrInlineMessage, ChatPermissions, InlineQueryResult, InputFile,
InputMedia, LabeledPrice, ParseMode, StickerType,
BotCommand, ChatId, ChatPermissions, InlineQueryResult, InputFile, InputMedia,
LabeledPrice, ParseMode, StickerType, TargetMessage,
},
Bot,
};
use std::ops::Deref;
impl Bot {
/// Use this method to receive incoming updates using long polling ([wiki]).
@ -410,42 +411,89 @@ impl Bot {
/// Use this method to edit live location messages.
///
/// A location can be edited until its live_period expires or editing is
/// explicitly disabled by a call to stopMessageLiveLocation. On success, if
/// the edited message was sent by the bot, the edited [`Message`] is
/// returned, otherwise [`True`] is returned.
/// 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
///
/// # Params
/// - `latitude`: Latitude of new location.
/// - `longitude`: Longitude of new location.
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
pub fn edit_message_live_location(
pub fn edit_message_live_location<C>(
&self,
chat_or_inline_message: ChatOrInlineMessage,
chat_id: C,
message_id: i32,
latitude: f32,
longitude: f32,
) -> EditMessageLiveLocation {
EditMessageLiveLocation::new(self.clone(), chat_or_inline_message, latitude, longitude)
) -> EditMessageLiveLocation
where
C: Into<ChatId>,
{
EditMessageLiveLocation::new(self.clone(), chat_id, message_id, latitude, longitude)
}
/// 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
///
/// # Params
/// - `latitude`: Latitude of new location.
/// - `longitude`: Longitude of new location.
pub fn edit_inline_message_live_location<I>(
&self,
inline_message_id: I,
latitude: f32,
longitude: f32,
) -> EditInlineMessageLiveLocation
where
I: Into<String>,
{
EditInlineMessageLiveLocation::new(self.clone(), inline_message_id, latitude, longitude)
}
/// Use this method to stop updating a live location message before
/// `live_period` expires.
///
/// On success, if the message was sent by the bot, the sent [`Message`] is
/// returned, otherwise [`True`] is returned.
/// On success, the sent [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation).
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
pub fn stop_message_live_location(
pub fn stop_message_live_location<C>(
&self,
chat_or_inline_message: ChatOrInlineMessage,
) -> StopMessageLiveLocation {
StopMessageLiveLocation::new(self.clone(), chat_or_inline_message)
chat_id: C,
message_id: i32,
) -> StopMessageLiveLocation
where
C: Into<ChatId>,
{
StopMessageLiveLocation::new(self.clone(), chat_id, message_id)
}
/// Use this method to stop updating a live location message (sent via the
/// bot) before `live_period` expires.
///
/// On success, [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation).
///
/// [`True`]: crate::types::True
pub fn stop_inline_message_live_location<I>(
&self,
inline_message_id: I,
) -> StopInlineMessageLiveLocation
where
I: Into<String>,
{
StopInlineMessageLiveLocation::new(self.clone(), inline_message_id)
}
/// Use this method to send information about a venue.
@ -983,45 +1031,105 @@ impl Bot {
/// Use this method to edit text and game messages.
///
/// On success, if edited message is sent by the bot, the edited [`Message`]
/// is returned, otherwise [`True`] is returned.
/// On success, the edited [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagetext).
///
/// # Params
/// - New text of the message.
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
///
/// # Params
///
/// - `chat_id`: Unique identifier for the target chat or username of the
/// target channel (in the format `@channelusername`).
/// - `message_id`: Identifier of the message to edit.
/// - `text`: New text of the message.
///
/// # Notes
///
/// Uses [a default parse mode] if specified in [`BotBuilder`].
///
/// [a default parse mode]: crate::BotBuilder::parse_mode
/// [`BotBuilder`]: crate::BotBuilder
pub fn edit_message_text<T>(
&self,
chat_or_inline_message: ChatOrInlineMessage,
text: T,
) -> EditMessageText
pub fn edit_message_text<C, T>(&self, chat_id: C, message_id: i32, text: T) -> EditMessageText
where
C: Into<ChatId>,
T: Into<String>,
{
match self.parse_mode.deref() {
None => EditMessageText::new(self.clone(), chat_or_inline_message, text),
Some(parse_mode) => EditMessageText::new(self.clone(), chat_or_inline_message, text)
.parse_mode(*parse_mode.deref()),
match self.parse_mode {
None => EditMessageText::new(self.clone(), chat_id, message_id, text),
Some(parse_mode) => {
EditMessageText::new(self.clone(), chat_id, message_id, text).parse_mode(parse_mode)
}
}
}
/// Use this method to edit captions of messages.
/// Use this method to edit text and game messages sent via the bot.
///
/// On success, if edited message is sent by the bot, the edited [`Message`]
/// is returned, otherwise [`True`] is returned.
/// On success, [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagetext).
///
/// [`True`]: crate::types::True
///
/// # Params
///
/// - `inline_message_id`: Identifier of the inline message.
/// - `text`: New text of the message.
///
/// # Notes
///
/// Uses [a default parse mode] if specified in [`BotBuilder`].
///
/// [a default parse mode]: crate::BotBuilder::parse_mode
/// [`BotBuilder`]: crate::BotBuilder
pub fn edit_inline_message_text<I, T>(
&self,
inline_message_id: I,
text: T,
) -> EditInlineMessageText
where
I: Into<String>,
T: Into<String>,
{
match self.parse_mode {
None => EditInlineMessageText::new(self.clone(), inline_message_id, text),
Some(parse_mode) => EditInlineMessageText::new(self.clone(), inline_message_id, text)
.parse_mode(parse_mode),
}
}
/// 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
///
/// # Notes
///
/// Uses [a default parse mode] if specified in [`BotBuilder`].
///
/// [a default parse mode]: crate::BotBuilder::parse_mode
/// [`BotBuilder`]: crate::BotBuilder
pub fn edit_message_caption<C>(&self, chat_id: C, message_id: i32) -> EditMessageCaption
where
C: Into<ChatId>,
{
match self.parse_mode {
None => EditMessageCaption::new(self.clone(), chat_id, message_id),
Some(parse_mode) => {
EditMessageCaption::new(self.clone(), chat_id, message_id).parse_mode(parse_mode)
}
}
}
/// 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).
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
///
/// # Notes
@ -1029,14 +1137,14 @@ impl Bot {
///
/// [a default parse mode]: crate::BotBuilder::parse_mode
/// [`BotBuilder`]: crate::BotBuilder
pub fn edit_message_caption(
&self,
chat_or_inline_message: ChatOrInlineMessage,
) -> EditMessageCaption {
match self.parse_mode.deref() {
None => EditMessageCaption::new(self.clone(), chat_or_inline_message),
Some(parse_mode) => EditMessageCaption::new(self.clone(), chat_or_inline_message)
.parse_mode(*parse_mode.deref()),
pub fn edit_inline_message_caption<I>(&self, inline_message_id: I) -> EditInlineMessageCaption
where
I: Into<String>,
{
match self.parse_mode {
None => EditInlineMessageCaption::new(self.clone(), inline_message_id),
Some(parse_mode) => EditInlineMessageCaption::new(self.clone(), inline_message_id)
.parse_mode(parse_mode),
}
}
@ -1045,37 +1153,81 @@ impl 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 inline message is edited, new file can't be
/// uploaded. Use previously uploaded file via its `file_id` or specify
/// a URL. On success, if the edited message was sent by the bot, the
/// edited [`Message`] is returned, otherwise [`True`] is returned.
/// arbitrarily. On success, the edited [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagemedia).
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
pub fn edit_message_media(
pub fn edit_message_media<C>(
&self,
chat_or_inline_message: ChatOrInlineMessage,
chat_id: C,
message_id: i32,
media: InputMedia,
) -> EditMessageMedia {
EditMessageMedia::new(self.clone(), chat_or_inline_message, media)
) -> EditMessageMedia
where
C: Into<ChatId>,
{
EditMessageMedia::new(self.clone(), chat_id, message_id, media)
}
/// 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
pub fn edit_inline_message_media<I>(
&self,
inline_message_id: I,
media: InputMedia,
) -> EditInlineMessageMedia
where
I: Into<String>,
{
EditInlineMessageMedia::new(self.clone(), inline_message_id, media)
}
/// Use this method to edit only the reply markup of messages.
///
/// On success, if edited message is sent by the bot, the edited [`Message`]
/// is returned, otherwise [`True`] is returned.
/// On success, the edited [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup).
///
/// [`Message`]: crate::types::Message
pub fn edit_message_reply_markup<C>(
&self,
chat_id: C,
message_id: i32,
) -> EditMessageReplyMarkup
where
C: Into<ChatId>,
{
EditMessageReplyMarkup::new(self.clone(), chat_id, message_id)
}
/// 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
pub fn edit_message_reply_markup(
pub fn edit_inline_message_reply_markup<I>(
&self,
chat_or_inline_message: ChatOrInlineMessage,
) -> EditMessageReplyMarkup {
EditMessageReplyMarkup::new(self.clone(), chat_or_inline_message)
inline_message_id: I,
) -> EditInlineMessageReplyMarkup
where
I: Into<String>,
{
EditInlineMessageReplyMarkup::new(self.clone(), inline_message_id)
}
/// Use this method to stop a poll which was sent by the bot.
@ -1413,18 +1565,18 @@ impl Bot {
/// [The official docs](https://core.telegram.org/bots/api#setgamescore).
///
/// # Params
/// - `target`: Target message, either chat id and message id or inline
/// message id.
/// - `user_id`: User identifier.
/// - `score`: New score, must be non-negative.
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
pub fn set_game_score(
&self,
chat_or_inline_message: ChatOrInlineMessage,
user_id: i32,
score: i32,
) -> SetGameScore {
SetGameScore::new(self.clone(), chat_or_inline_message, user_id, score)
pub fn set_game_score<T>(&self, target: T, user_id: i32, score: i32) -> SetGameScore
where
T: Into<TargetMessage>,
{
SetGameScore::new(self.clone(), target, user_id, score)
}
/// Use this method to get data for high score tables.
@ -1441,13 +1593,14 @@ impl Bot {
/// [The official docs](https://core.telegram.org/bots/api#getgamehighscores).
///
/// # Params
/// - `target`: Target message, either chat id and message id or inline
/// message id.
/// - `user_id`: Target user id.
pub fn get_game_high_scores(
&self,
chat_or_inline_message: ChatOrInlineMessage,
user_id: i32,
) -> GetGameHighScores {
GetGameHighScores::new(self.clone(), chat_or_inline_message, user_id)
pub fn get_game_high_scores<T>(&self, target: T, user_id: i32) -> GetGameHighScores
where
T: Into<TargetMessage>,
{
GetGameHighScores::new(self.clone(), target, user_id)
}
/// Use this method to set a custom title for an administrator in a
@ -1530,9 +1683,9 @@ impl Bot {
builder: Builder,
f: fn(Builder, ParseMode) -> Builder,
) -> Builder {
match self.parse_mode.deref() {
match self.parse_mode {
None => builder,
Some(parse_mode) => f(builder, *parse_mode.deref()),
Some(parse_mode) => f(builder, parse_mode),
}
}
}

View file

@ -20,7 +20,7 @@ pub(crate) const TELOXIDE_PROXY: &str = "TELOXIDE_PROXY";
pub struct Bot {
token: Arc<str>,
client: Client,
parse_mode: Arc<Option<ParseMode>>,
parse_mode: Option<ParseMode>,
}
impl Bot {
@ -41,8 +41,7 @@ impl Bot {
/// client.
///
/// # Panics
/// - If cannot get the `TELOXIDE_TOKEN` and `TELOXIDE_PROXY` environmental
/// variables.
/// - 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
@ -106,7 +105,7 @@ impl Bot {
Self {
token: Into::<Arc<str>>::into(Into::<String>::into(token)),
client,
parse_mode: Arc::new(None),
parse_mode: None,
}
}
}
@ -231,11 +230,11 @@ impl BotBuilder {
///
/// 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.
/// 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` and `TELOXIDE_PROXY` environmental
/// variables.
/// - 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
@ -247,7 +246,7 @@ impl BotBuilder {
Bot {
client: self.client.unwrap_or_else(crate::utils::client_from_env),
token: self.token.unwrap_or_else(|| get_env(TELOXIDE_TOKEN)).into(),
parse_mode: Arc::new(self.parse_mode),
parse_mode: self.parse_mode,
}
}
}

View file

@ -21,7 +21,7 @@ pub enum DialogueStage<D> {
///
/// [`From`]: std::convert::From
/// [derive-more]: https://crates.io/crates/derive_more
pub fn next<Dialogue, State>(new_state: State) -> TransitionOut<Dialogue>
pub fn next<Dialogue, State, E>(new_state: State) -> TransitionOut<Dialogue, E>
where
Dialogue: From<State>,
{
@ -32,6 +32,6 @@ where
///
/// See [the module-level documentation for the design
/// overview](crate::dispatching::dialogue).
pub fn exit<D>() -> TransitionOut<D> {
pub fn exit<D, E>() -> TransitionOut<D, E> {
Ok(DialogueStage::Exit)
}

View file

@ -39,7 +39,7 @@
//! struct _2State;
//! struct _3State;
//!
//! type Out = TransitionOut<D>;
//! type Out = TransitionOut<D, RequestError>;
//!
//! #[teloxide(subtransition)]
//! async fn _1_transition(_state: _1State, _cx: TransitionIn) -> Out {

View file

@ -1,6 +1,5 @@
use crate::{
dispatching::{dialogue::DialogueStage, UpdateWithCx},
requests::ResponseResult,
types::Message,
};
use futures::future::BoxFuture;
@ -8,11 +7,16 @@ use futures::future::BoxFuture;
/// Represents a transition function of a dialogue FSM.
pub trait Transition: Sized {
type Aux;
type Error;
/// Turns itself into another state, depending on the input message.
///
/// `aux` will be passed to each subtransition function.
fn react(self, cx: TransitionIn, aux: Self::Aux) -> BoxFuture<'static, TransitionOut<Self>>;
fn react(
self,
cx: TransitionIn,
aux: Self::Aux,
) -> BoxFuture<'static, TransitionOut<Self, Self::Error>>;
}
/// Like [`Transition`], but from `StateN` -> `Dialogue`.
@ -24,6 +28,7 @@ where
{
type Aux;
type Dialogue;
type Error;
/// Turns itself into another state, depending on the input message.
///
@ -33,7 +38,7 @@ where
self,
cx: TransitionIn,
aux: Self::Aux,
) -> BoxFuture<'static, TransitionOut<Self::Dialogue>>;
) -> BoxFuture<'static, TransitionOut<Self::Dialogue, Self::Error>>;
}
/// A type returned from a FSM subtransition function.
@ -41,14 +46,16 @@ where
/// Now it is used only inside `#[teloxide(subtransition)]` for type inference.
pub trait SubtransitionOutputType {
type Output;
type Error;
}
impl<D> SubtransitionOutputType for TransitionOut<D> {
impl<D, E> SubtransitionOutputType for TransitionOut<D, E> {
type Output = D;
type Error = E;
}
/// An input passed into a FSM (sub)transition function.
pub type TransitionIn = UpdateWithCx<Message>;
/// A type returned from a FSM (sub)transition function.
pub type TransitionOut<D> = ResponseResult<DialogueStage<D>>;
pub type TransitionOut<D, E = crate::RequestError> = Result<DialogueStage<D>, E>;

View file

@ -22,13 +22,14 @@ use std::{fmt::Debug, future::Future, sync::Arc};
///
/// [REPL]: https://en.wikipedia.org/wiki/Read-eval-print_loop
/// [`Dispatcher`]: crate::dispatching::Dispatcher
pub async fn commands_repl<Cmd, H, Fut, HandlerE>(bot: Bot, bot_name: &'static str, handler: H)
pub async fn commands_repl<Cmd, H, Fut, HandlerE, N>(bot: Bot, bot_name: N, handler: H)
where
Cmd: BotCommand + Send + 'static,
H: Fn(UpdateWithCx<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,
{
let cloned_bot = bot.clone();
@ -53,9 +54,9 @@ 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>(
pub async fn commands_repl_with_listener<'a, Cmd, H, Fut, L, ListenerE, HandlerE, N>(
bot: Bot,
bot_name: &'static str,
bot_name: N,
handler: H,
listener: L,
) where
@ -66,21 +67,19 @@ pub async fn commands_repl_with_listener<'a, Cmd, H, Fut, L, ListenerE, HandlerE
ListenerE: Debug + Send + 'a,
Result<(), HandlerE>: OnError<HandlerE>,
HandlerE: Debug + Send,
N: Into<String> + Send + 'static,
{
let handler = Arc::new(handler);
Dispatcher::new(bot)
.messages_handler(move |rx: DispatcherHandlerRx<Message>| {
rx.commands::<Cmd, &'static str>(bot_name).for_each_concurrent(
None,
move |(cx, cmd)| {
let handler = Arc::clone(&handler);
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,7 +6,7 @@ use crate::{
SendLocation, SendMediaGroup, SendMessage, SendPhoto, SendSticker, SendVenue, SendVideo,
SendVideoNote, SendVoice,
},
types::{ChatId, ChatOrInlineMessage, InputFile, InputMedia, Message},
types::{ChatId, InputFile, InputMedia, Message},
Bot,
};
@ -130,20 +130,11 @@ impl UpdateWithCx<Message> {
where
T: Into<String>,
{
self.bot.edit_message_text(
ChatOrInlineMessage::Chat {
chat_id: self.update.chat.id.into(),
message_id: self.update.id,
},
text,
)
self.bot.edit_message_text(self.update.chat.id, self.update.id, text)
}
pub fn edit_message_caption(&self) -> EditMessageCaption {
self.bot.edit_message_caption(ChatOrInlineMessage::Chat {
chat_id: self.update.chat.id.into(),
message_id: self.update.id,
})
self.bot.edit_message_caption(self.update.chat.id, self.update.id)
}
pub fn delete_message(&self) -> DeleteMessage {

View file

@ -40,6 +40,7 @@
)]
#![allow(clippy::match_bool)]
#![forbid(unsafe_code)]
#![cfg_attr(all(feature = "nightly", doctest), feature(external_doc))]
pub use bot::{Bot, BotBuilder};
pub use dispatching::repls::{
@ -61,3 +62,7 @@ pub mod types;
pub mod utils;
extern crate teloxide_macros;
#[cfg(all(feature = "nightly", doctest))]
#[doc(include = "../README.md")]
enum ReadmeDocTests {}

View file

@ -9,7 +9,7 @@ pub use crate::{
Dispatcher, DispatcherHandlerRx, DispatcherHandlerRxExt, UpdateWithCx,
},
error_handlers::{LoggingErrorHandler, OnError},
requests::{Request, ResponseResult},
requests::{respond, Request, ResponseResult},
types::{Message, Update},
Bot, RequestError,
};

View file

@ -0,0 +1,83 @@
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

@ -0,0 +1,77 @@
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

@ -0,0 +1,78 @@
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

@ -0,0 +1,62 @@
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

@ -0,0 +1,98 @@
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

@ -3,26 +3,24 @@ use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode},
types::{ChatId, InlineKeyboardMarkup, Message, ParseMode},
Bot,
};
/// Use this method to edit captions of messages.
/// Use this method to edit captions of messages sent by the bot.
///
/// On success, if edited message is sent by the bot, the edited [`Message`] is
/// returned, otherwise [`True`] is returned.
/// On success, the edited [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagecaption).
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditMessageCaption {
#[serde(skip_serializing)]
bot: Bot,
#[serde(flatten)]
chat_or_inline_message: ChatOrInlineMessage,
chat_id: ChatId,
message_id: i32,
caption: Option<String>,
parse_mode: Option<ParseMode>,
reply_markup: Option<InlineKeyboardMarkup>,
@ -38,12 +36,27 @@ impl Request for EditMessageCaption {
}
impl EditMessageCaption {
pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage) -> Self {
Self { bot, chat_or_inline_message, caption: None, parse_mode: None, reply_markup: None }
pub(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 }
}
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self {
self.chat_or_inline_message = val;
/// 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
}

View file

@ -3,28 +3,26 @@ use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message},
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, if the
/// edited message was sent by the bot, the edited [`Message`] is returned,
/// otherwise [`True`] is returned.
/// 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
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditMessageLiveLocation {
#[serde(skip_serializing)]
bot: Bot,
#[serde(flatten)]
chat_or_inline_message: ChatOrInlineMessage,
chat_id: ChatId,
message_id: i32,
latitude: f32,
longitude: f32,
reply_markup: Option<InlineKeyboardMarkup>,
@ -41,17 +39,33 @@ impl Request for EditMessageLiveLocation {
}
impl EditMessageLiveLocation {
pub(crate) fn new(
pub(crate) fn new<C>(
bot: Bot,
chat_or_inline_message: ChatOrInlineMessage,
chat_id: C,
message_id: i32,
latitude: f32,
longitude: f32,
) -> Self {
Self { bot, chat_or_inline_message, latitude, longitude, reply_markup: None }
) -> Self
where
C: Into<ChatId>,
{
let chat_id = chat_id.into();
Self { bot, chat_id, message_id, latitude, longitude, reply_markup: None }
}
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self {
self.chat_or_inline_message = val;
/// 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
}

View file

@ -1,7 +1,7 @@
use crate::{
net,
requests::{form_builder::FormBuilder, Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, InputMedia, Message},
types::{ChatId, InlineKeyboardMarkup, InputMedia, Message},
Bot,
};
@ -9,20 +9,17 @@ use crate::{
/// messages.
///
/// If a message is a part of a message album, then it can be edited only to a
/// photo or a video. Otherwise, message type can be changed arbitrarily. When
/// inline message is edited, new file can't be uploaded. Use previously
/// uploaded file via its `file_id` or specify a URL. On success, if the edited
/// message was sent by the bot, the edited [`Message`] is returned,
/// otherwise [`True`] is returned.
/// 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
/// [`True`]: crate::types::True
#[derive(Debug, Clone)]
pub struct EditMessageMedia {
bot: Bot,
chat_or_inline_message: ChatOrInlineMessage,
chat_id: ChatId,
message_id: i32,
media: InputMedia,
reply_markup: Option<InlineKeyboardMarkup>,
}
@ -32,22 +29,13 @@ impl Request for EditMessageMedia {
type Output = Message;
async fn send(&self) -> ResponseResult<Message> {
let mut params = FormBuilder::new();
match &self.chat_or_inline_message {
ChatOrInlineMessage::Chat { chat_id, message_id } => {
params = params.add_text("chat_id", chat_id).add_text("message_id", message_id);
}
ChatOrInlineMessage::Inline { inline_message_id } => {
params = params.add_text("inline_message_id", inline_message_id);
}
}
net::request_multipart(
self.bot.client(),
self.bot.token(),
"editMessageMedia",
params
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(),
@ -57,16 +45,27 @@ impl Request for EditMessageMedia {
}
impl EditMessageMedia {
pub(crate) fn new(
bot: Bot,
chat_or_inline_message: ChatOrInlineMessage,
media: InputMedia,
) -> Self {
Self { bot, chat_or_inline_message, media, reply_markup: None }
pub(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 }
}
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self {
self.chat_or_inline_message = val;
/// 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
}

View file

@ -3,26 +3,24 @@ use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message},
types::{ChatId, InlineKeyboardMarkup, Message},
Bot,
};
/// Use this method to edit only the reply markup of messages.
///
/// On success, if edited message is sent by the bot, the edited [`Message`] is
/// returned, otherwise [`True`] is returned.
/// On success, the edited [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagereplymarkup).
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditMessageReplyMarkup {
#[serde(skip_serializing)]
bot: Bot,
#[serde(flatten)]
chat_or_inline_message: ChatOrInlineMessage,
chat_id: ChatId,
message_id: i32,
reply_markup: Option<InlineKeyboardMarkup>,
}
@ -37,12 +35,27 @@ impl Request for EditMessageReplyMarkup {
}
impl EditMessageReplyMarkup {
pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage) -> Self {
Self { bot, chat_or_inline_message, reply_markup: None }
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 }
}
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self {
self.chat_or_inline_message = val;
/// 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
}

View file

@ -3,26 +3,24 @@ use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message, ParseMode},
types::{ChatId, InlineKeyboardMarkup, Message, ParseMode},
Bot,
};
/// Use this method to edit text and game messages.
///
/// On success, if edited message is sent by the bot, the edited [`Message`] is
/// returned, otherwise [`True`] is returned.
/// On success, the edited [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#editmessagetext).
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct EditMessageText {
#[serde(skip_serializing)]
bot: Bot,
#[serde(flatten)]
chat_or_inline_message: ChatOrInlineMessage,
chat_id: ChatId,
message_id: i32,
text: String,
parse_mode: Option<ParseMode>,
disable_web_page_preview: Option<bool>,
@ -39,22 +37,37 @@ impl Request for EditMessageText {
}
impl EditMessageText {
pub(crate) fn new<T>(bot: Bot, chat_or_inline_message: ChatOrInlineMessage, text: T) -> Self
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_or_inline_message,
text: text.into(),
chat_id,
message_id,
text,
parse_mode: None,
disable_web_page_preview: None,
reply_markup: None,
}
}
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self {
self.chat_or_inline_message = val;
/// 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
}

View file

@ -3,7 +3,7 @@ use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, GameHighScore},
types::{GameHighScore, TargetMessage},
Bot,
};
@ -18,14 +18,13 @@ use crate::{
/// the user and his neighbors are not among them. Please note that this
/// behavior is subject to change.
///
/// [The official docs](https://core.telegram.org/bots/api#getgamehighscores).
#[serde_with_macros::skip_serializing_none]
/// [The official docs](https://core.telegram.org/bots/api#getgamehighscores)
#[derive(Debug, Clone, Serialize)]
pub struct GetGameHighScores {
#[serde(skip_serializing)]
bot: Bot,
#[serde(flatten)]
chat_or_inline_message: ChatOrInlineMessage,
target: TargetMessage,
user_id: i32,
}
@ -39,12 +38,20 @@ impl Request for GetGameHighScores {
}
impl GetGameHighScores {
pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage, user_id: i32) -> Self {
Self { bot, chat_or_inline_message, user_id }
pub(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 }
}
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self {
self.chat_or_inline_message = val;
/// 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
}

View file

@ -9,6 +9,11 @@ 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;
@ -62,6 +67,7 @@ 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;
@ -79,6 +85,11 @@ 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::*;
@ -133,6 +144,7 @@ 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::*;

View file

@ -3,7 +3,7 @@ use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, Message},
types::{Message, TargetMessage},
Bot,
};
@ -24,7 +24,7 @@ pub struct SetGameScore {
#[serde(skip_serializing)]
bot: Bot,
#[serde(flatten)]
chat_or_inline_message: ChatOrInlineMessage,
target: TargetMessage,
user_id: i32,
score: i32,
force: Option<bool>,
@ -41,24 +41,20 @@ impl Request for SetGameScore {
}
impl SetGameScore {
pub(crate) fn new(
bot: Bot,
chat_or_inline_message: ChatOrInlineMessage,
user_id: i32,
score: i32,
) -> Self {
Self {
bot,
chat_or_inline_message,
user_id,
score,
force: None,
disable_edit_message: None,
}
pub(crate) fn new<T>(bot: Bot, target: T, user_id: i32, score: i32) -> Self
where
T: Into<TargetMessage>,
{
let target = target.into();
Self { bot, target, user_id, score, force: None, disable_edit_message: None }
}
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self {
self.chat_or_inline_message = val;
/// 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
}

View file

@ -0,0 +1,62 @@
use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{InlineKeyboardMarkup, Message},
Bot,
};
/// Use this method to stop updating a live location message (sent via the bot)
/// before `live_period` expires.
///
/// On success, [`True`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation).
///
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct StopInlineMessageLiveLocation {
#[serde(skip_serializing)]
bot: Bot,
inline_message_id: String,
reply_markup: Option<InlineKeyboardMarkup>,
}
#[async_trait::async_trait]
impl Request for StopInlineMessageLiveLocation {
type Output = Message;
async fn send(&self) -> ResponseResult<Message> {
net::request_json(self.bot.client(), self.bot.token(), "stopMessageLiveLocation", &self)
.await
}
}
impl StopInlineMessageLiveLocation {
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 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

@ -3,27 +3,25 @@ use serde::Serialize;
use crate::{
net,
requests::{Request, ResponseResult},
types::{ChatOrInlineMessage, InlineKeyboardMarkup, Message},
types::{ChatId, InlineKeyboardMarkup, Message},
Bot,
};
/// Use this method to stop updating a live location message before
/// `live_period` expires.
///
/// On success, if the message was sent by the bot, the sent [`Message`] is
/// returned, otherwise [`True`] is returned.
/// On success, the sent [`Message`] is returned.
///
/// [The official docs](https://core.telegram.org/bots/api#stopmessagelivelocation).
///
/// [`Message`]: crate::types::Message
/// [`True`]: crate::types::True
#[serde_with_macros::skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct StopMessageLiveLocation {
#[serde(skip_serializing)]
bot: Bot,
#[serde(flatten)]
chat_or_inline_message: ChatOrInlineMessage,
chat_id: ChatId,
message_id: i32,
reply_markup: Option<InlineKeyboardMarkup>,
}
@ -38,12 +36,27 @@ impl Request for StopMessageLiveLocation {
}
impl StopMessageLiveLocation {
pub(crate) fn new(bot: Bot, chat_or_inline_message: ChatOrInlineMessage) -> Self {
Self { bot, chat_or_inline_message, reply_markup: None }
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 }
}
pub fn chat_or_inline_message(mut self, val: ChatOrInlineMessage) -> Self {
self.chat_or_inline_message = val;
/// 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
}

View file

@ -9,6 +9,11 @@ pub use all::*;
/// A type that is returned after making a request to Telegram.
pub type ResponseResult<T> = Result<T, crate::RequestError>;
/// A shortcut for `ResponseResult::Ok(val)`.
pub fn respond<T>(val: T) -> ResponseResult<T> {
ResponseResult::Ok(val)
}
/// Designates an API request.
#[async_trait::async_trait]
pub trait Request {

View file

@ -1,12 +0,0 @@
use crate::types::ChatId;
use serde::{Deserialize, Serialize};
/// A chat message or inline message.
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum ChatOrInlineMessage {
Chat { chat_id: ChatId, message_id: i32 },
Inline { inline_message_id: i32 },
}

View file

@ -10,7 +10,6 @@ pub use chat::*;
pub use chat_action::*;
pub use chat_id::*;
pub use chat_member::*;
pub use chat_or_inline_message::*;
pub use chat_permissions::*;
pub use chat_photo::*;
pub use chosen_inline_result::*;
@ -83,6 +82,7 @@ pub use sticker::*;
pub use sticker_set::*;
pub use sticker_type::*;
pub use successful_payment::*;
pub use target_message::*;
pub use unit_false::*;
pub use unit_true::*;
pub use update::*;
@ -104,7 +104,6 @@ mod chat;
mod chat_action;
mod chat_id;
mod chat_member;
mod chat_or_inline_message;
mod chat_permissions;
mod chat_photo;
mod chosen_inline_result;
@ -150,6 +149,7 @@ mod sticker;
mod sticker_set;
mod sticker_type;
mod successful_payment;
mod target_message;
mod unit_false;
mod unit_true;
mod update;

View file

@ -0,0 +1,17 @@
use crate::types::ChatId;
use serde::{Deserialize, Serialize};
/// A message in chat or inline message.
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TargetMessage {
Common { chat_id: ChatId, message_id: i32 },
Inline { inline_message_id: String },
}
impl From<String> for TargetMessage {
fn from(inline_message_id: String) -> Self {
Self::Inline { inline_message_id }
}
}

View file

@ -18,6 +18,21 @@ fn parse_command_with_args() {
assert_eq!(actual, expected)
}
#[test]
fn parse_command_with_non_string_arg() {
#[command(rename = "lowercase")]
#[derive(BotCommand, Debug, PartialEq)]
enum DefaultCommands {
Start(i32),
Help,
}
let data = "/start -50";
let expected = DefaultCommands::Start("-50".parse().unwrap());
let actual = DefaultCommands::parse(data, "").unwrap();
assert_eq!(actual, expected)
}
#[test]
fn attribute_prefix() {
#[command(rename = "lowercase")]

View file

@ -1,5 +1,3 @@
#![cfg(feature = "redis_storage")]
use std::{
fmt::{Debug, Display},
future::Future,
@ -8,7 +6,6 @@ use std::{
use teloxide::dispatching::dialogue::{RedisStorage, Serializer, Storage};
#[tokio::test]
#[cfg(feature = "redis_storage")]
async fn test_redis_json() {
let storage = RedisStorage::open(
"redis://127.0.0.1:7777",
@ -19,7 +16,6 @@ async fn test_redis_json() {
test_redis(storage).await;
}
#[cfg(feature = "bincode_serializer")]
#[tokio::test]
async fn test_redis_bincode() {
let storage = RedisStorage::open(
@ -31,7 +27,6 @@ async fn test_redis_bincode() {
test_redis(storage).await;
}
#[cfg(feature = "cbor_serializer")]
#[tokio::test]
async fn test_redis_cbor() {
let storage = RedisStorage::open(