mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-21 22:15:13 +01:00
Merge pull request #1162 from LasterAlex/add-syntactic-sugar
Added syntactic sugar
This commit is contained in:
commit
cfedb585d3
8 changed files with 373 additions and 14 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -8,13 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
|
||||
- Add `filter_boost_added` and `filter_reply_to_story` filters to `MessageFilterExt` trait
|
||||
- Add `filter_mention_command` filter to `HandlerExt` trait ([issue #494](https://github.com/teloxide/teloxide/issues/494))
|
||||
- Add `filter_business_connection`, `filter_business_message`, `filter_edited_business_message`, and `filter_deleted_business_messages` filters to update filters ([PR 1146](https://github.com/teloxide/teloxide/pull/1146))
|
||||
- `filter_boost_added` and `filter_reply_to_story` filters to the `MessageFilterExt` trait ([PR 1131](https://github.com/teloxide/teloxide/pull/1131))
|
||||
- `filter_mention_command` filter to the `HandlerExt` trait ([issue 494](https://github.com/teloxide/teloxide/issues/494))
|
||||
- `filter_business_connection`, `filter_business_message`, `filter_edited_business_message`, and `filter_deleted_business_messages` filters to the `UpdateFilterExt` trait ([PR 1146](https://github.com/teloxide/teloxide/pull/1146))
|
||||
- Syntax sugar for making requests ([issue 1143](https://github.com/teloxide/teloxide/issues/1143)):
|
||||
- `bot.forward`, `bot.edit_live_location`, `bot.stop_live_location`, `bot.set_reaction`, `bot.pin`, `bot.unpin`, `bot.edit_text`, `bot.edit_caption`, `bot.edit_media`, `bot.edit_reply_markup`, `bot.stop_poll_message`, `bot.delete` and `bot.copy` methods to the new `crate::sugar::bot::BotMessagesExt` trait
|
||||
- `req.reply_to` method to the new `crate::sugar::request::RequestReplyExt` trait
|
||||
- `req.disable_link_preview` method to the new `crate::sugar::request::RequestLinkPreviewExt` trait
|
||||
|
||||
### Changed
|
||||
|
||||
- Environment bumps: ([#1147][pr1147])
|
||||
- Environment bumps: ([PR 1147](https://github.com/teloxide/teloxide/pull/1147))
|
||||
- MSRV (Minimal Supported Rust Version) was bumped from `1.70.0` to `1.80.0`
|
||||
- Some dependencies was bumped: `sqlx` to `0.8.1`, `tower` to `0.5.0`, `reqwest` to `0.12.7`
|
||||
- `tokio` version was explicitly specified as `1.39`
|
||||
|
@ -23,8 +27,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Now Vec<MessageId> in requests serializes into [number] instead of [ {message_id: number} ], `forward_messages`, `copy_messages` and `delete_messages` now work properly
|
||||
|
||||
[pr1147]: https://github.com/teloxide/teloxide/pull/1147
|
||||
|
||||
## 0.13.0 - 2024-08-16
|
||||
|
||||
### Added
|
||||
|
|
|
@ -1816,6 +1816,13 @@ impl Message {
|
|||
}
|
||||
}
|
||||
|
||||
/// Implemented for syntax sugar, see issue <https://github.com/teloxide/teloxide/issues/1143>
|
||||
impl From<Message> for MessageId {
|
||||
fn from(message: Message) -> MessageId {
|
||||
message.id
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::DateTime;
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::error::Error;
|
|||
use teloxide::{
|
||||
payloads::SendMessageSetters,
|
||||
prelude::*,
|
||||
sugar::bot::BotMessagesExt,
|
||||
types::{
|
||||
InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputMessageContent,
|
||||
InputMessageContentText, Me,
|
||||
|
@ -107,17 +108,17 @@ async fn inline_query_handler(
|
|||
/// **IMPORTANT**: do not send privacy-sensitive data this way!!!
|
||||
/// Anyone can read data stored in the callback button.
|
||||
async fn callback_handler(bot: Bot, q: CallbackQuery) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
if let Some(version) = q.data {
|
||||
if let Some(ref version) = q.data {
|
||||
let text = format!("You chose: {version}");
|
||||
|
||||
// Tell telegram that we've seen this query, to remove 🕑 icons from the
|
||||
// clients. You could also use `answer_callback_query`'s optional
|
||||
// parameters to tweak what happens on the client side.
|
||||
bot.answer_callback_query(q.id).await?;
|
||||
bot.answer_callback_query(&q.id).await?;
|
||||
|
||||
// Edit text of the message to which the buttons were attached
|
||||
if let Some(message) = q.message {
|
||||
bot.edit_message_text(message.chat().id, message.id(), text).await?;
|
||||
if let Some(message) = q.regular_message() {
|
||||
bot.edit_text(message, text).await?;
|
||||
} else if let Some(id) = q.inline_message_id {
|
||||
bot.edit_message_text_inline(id, text).await?;
|
||||
}
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
use rand::Rng;
|
||||
|
||||
use teloxide::{
|
||||
dispatching::HandlerExt,
|
||||
prelude::*,
|
||||
types::{Dice, ReplyParameters},
|
||||
dispatching::HandlerExt, prelude::*, sugar::request::RequestReplyExt, types::Dice,
|
||||
utils::command::BotCommands,
|
||||
};
|
||||
|
||||
|
@ -84,7 +82,7 @@ async fn main() {
|
|||
// filter only messages with dices.
|
||||
Message::filter_dice().endpoint(|bot: Bot, msg: Message, dice: Dice| async move {
|
||||
bot.send_message(msg.chat.id, format!("Dice value: {}", dice.value))
|
||||
.reply_parameters(ReplyParameters::new(msg.id))
|
||||
.reply_to(msg)
|
||||
.await?;
|
||||
Ok(())
|
||||
}),
|
||||
|
|
|
@ -140,6 +140,7 @@ pub mod prelude;
|
|||
#[cfg(feature = "ctrlc_handler")]
|
||||
pub mod repls;
|
||||
pub mod stop;
|
||||
pub mod sugar;
|
||||
pub mod update_listeners;
|
||||
pub mod utils;
|
||||
|
||||
|
|
4
crates/teloxide/src/sugar.rs
Normal file
4
crates/teloxide/src/sugar.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
//! Some syntax sugar support for TBA functionality.
|
||||
|
||||
pub mod bot;
|
||||
pub mod request;
|
191
crates/teloxide/src/sugar/bot.rs
Normal file
191
crates/teloxide/src/sugar/bot.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
//! Additions to [`Bot`].
|
||||
//!
|
||||
//! [`Bot`]: crate::Bot
|
||||
use crate::{prelude::*, types::*};
|
||||
use teloxide_core::{
|
||||
payloads::*,
|
||||
requests::{JsonRequest, MultipartRequest},
|
||||
};
|
||||
|
||||
/// Syntax sugar for [`Message`] manipulations.
|
||||
///
|
||||
/// [`Message`]: crate::types::Message
|
||||
pub trait BotMessagesExt {
|
||||
/// This function is the same as [`Bot::forward_message`],
|
||||
/// but can take in [`Message`] to forward it.
|
||||
///
|
||||
/// [`Bot::forward_message`]: crate::Bot::forward_message
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn forward<C>(&self, to_chat_id: C, message: &Message) -> JsonRequest<ForwardMessage>
|
||||
where
|
||||
C: Into<Recipient>;
|
||||
|
||||
/// This function is the same as [`Bot::edit_message_live_location`],
|
||||
/// but can take in [`Message`] to edit it.
|
||||
///
|
||||
/// [`Bot::edit_message_live_location`]: crate::Bot::edit_message_live_location
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn edit_live_location(
|
||||
&self,
|
||||
message: &Message,
|
||||
latitude: f64,
|
||||
longitude: f64,
|
||||
) -> JsonRequest<EditMessageLiveLocation>;
|
||||
|
||||
/// This function is the same as [`Bot::stop_message_live_location`],
|
||||
/// but can take in [`Message`] to stop the live location in it.
|
||||
///
|
||||
/// [`Bot::stop_message_live_location`]: crate::Bot::stop_message_live_location
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn stop_live_location(&self, message: &Message) -> JsonRequest<StopMessageLiveLocation>;
|
||||
|
||||
/// This function is the same as [`Bot::set_message_reaction`],
|
||||
/// but can take in [`Message`] to set a reaction on it.
|
||||
///
|
||||
/// [`Bot::set_message_reaction`]: crate::Bot::set_message_reaction
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn set_reaction(&self, message: &Message) -> JsonRequest<SetMessageReaction>;
|
||||
|
||||
/// This function is the same as [`Bot::pin_chat_message`],
|
||||
/// but can take in [`Message`] to pin it.
|
||||
///
|
||||
/// [`Bot::pin_chat_message`]: crate::Bot::pin_chat_message
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn pin(&self, message: &Message) -> JsonRequest<PinChatMessage>;
|
||||
|
||||
/// This function is the same as [`Bot::unpin_chat_message`],
|
||||
/// but can take in [`Message`] to unpin it.
|
||||
///
|
||||
/// [`Bot::unpin_chat_message`]: crate::Bot::unpin_chat_message
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn unpin(&self, message: &Message) -> JsonRequest<UnpinChatMessage>;
|
||||
|
||||
/// This function is the same as [`Bot::edit_message_text`],
|
||||
/// but can take in [`Message`] to edit it.
|
||||
///
|
||||
/// [`Bot::edit_message_text`]: crate::Bot::edit_message_text
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn edit_text<T>(&self, message: &Message, text: T) -> JsonRequest<EditMessageText>
|
||||
where
|
||||
T: Into<String>;
|
||||
|
||||
/// This function is the same as [`Bot::edit_message_caption`],
|
||||
/// but can take in [`Message`] to edit it.
|
||||
///
|
||||
/// [`Bot::edit_message_caption`]: crate::Bot::edit_message_caption
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn edit_caption(&self, message: &Message) -> JsonRequest<EditMessageCaption>;
|
||||
|
||||
/// This function is the same as [`Bot::edit_message_media`],
|
||||
/// but can take in [`Message`] to edit it.
|
||||
///
|
||||
/// [`Bot::edit_message_media`]: crate::Bot::edit_message_media
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn edit_media(
|
||||
&self,
|
||||
message: &Message,
|
||||
media: InputMedia,
|
||||
) -> MultipartRequest<EditMessageMedia>;
|
||||
|
||||
/// This function is the same as [`Bot::edit_message_reply_markup`],
|
||||
/// but can take in [`Message`] to edit it.
|
||||
///
|
||||
/// [`Bot::edit_message_reply_markup`]: crate::Bot::edit_message_reply_markup
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn edit_reply_markup(&self, message: &Message) -> JsonRequest<EditMessageReplyMarkup>;
|
||||
|
||||
/// This function is the same as [`Bot::stop_poll`],
|
||||
/// but can take in [`Message`] to stop the poll in it.
|
||||
///
|
||||
/// [`Bot::stop_poll`]: crate::Bot::stop_poll
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn stop_poll_message(&self, message: &Message) -> JsonRequest<StopPoll>;
|
||||
|
||||
/// This function is the same as [`Bot::delete_message`],
|
||||
/// but can take in [`Message`] to delete it.
|
||||
///
|
||||
/// [`Bot::delete_message`]: crate::Bot::delete_message
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn delete(&self, message: &Message) -> JsonRequest<DeleteMessage>;
|
||||
|
||||
/// This function is the same as [`Bot::copy_message`],
|
||||
/// but can take in [`Message`] to copy it.
|
||||
///
|
||||
/// [`Bot::copy_messages`]: crate::Bot::copy_message
|
||||
/// [`Message`]: crate::types::Message
|
||||
fn copy<C>(&self, to_chat_id: C, message: &Message) -> JsonRequest<CopyMessage>
|
||||
where
|
||||
C: Into<Recipient>;
|
||||
}
|
||||
|
||||
impl BotMessagesExt for Bot {
|
||||
fn forward<C>(&self, to_chat_id: C, message: &Message) -> JsonRequest<ForwardMessage>
|
||||
where
|
||||
C: Into<Recipient>,
|
||||
{
|
||||
self.forward_message(to_chat_id, message.chat.id, message.id)
|
||||
}
|
||||
|
||||
fn edit_live_location(
|
||||
&self,
|
||||
message: &Message,
|
||||
latitude: f64,
|
||||
longitude: f64,
|
||||
) -> JsonRequest<EditMessageLiveLocation> {
|
||||
self.edit_message_live_location(message.chat.id, message.id, latitude, longitude)
|
||||
}
|
||||
|
||||
fn stop_live_location(&self, message: &Message) -> JsonRequest<StopMessageLiveLocation> {
|
||||
self.stop_message_live_location(message.chat.id, message.id)
|
||||
}
|
||||
|
||||
fn set_reaction(&self, message: &Message) -> JsonRequest<SetMessageReaction> {
|
||||
self.set_message_reaction(message.chat.id, message.id)
|
||||
}
|
||||
|
||||
fn pin(&self, message: &Message) -> JsonRequest<PinChatMessage> {
|
||||
self.pin_chat_message(message.chat.id, message.id)
|
||||
}
|
||||
|
||||
fn unpin(&self, message: &Message) -> JsonRequest<UnpinChatMessage> {
|
||||
self.unpin_chat_message(message.chat.id).message_id(message.id)
|
||||
}
|
||||
|
||||
fn edit_text<T>(&self, message: &Message, text: T) -> JsonRequest<EditMessageText>
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
self.edit_message_text(message.chat.id, message.id, text)
|
||||
}
|
||||
|
||||
fn edit_caption(&self, message: &Message) -> JsonRequest<EditMessageCaption> {
|
||||
self.edit_message_caption(message.chat.id, message.id)
|
||||
}
|
||||
|
||||
fn edit_media(
|
||||
&self,
|
||||
message: &Message,
|
||||
media: InputMedia,
|
||||
) -> MultipartRequest<EditMessageMedia> {
|
||||
self.edit_message_media(message.chat.id, message.id, media)
|
||||
}
|
||||
|
||||
fn edit_reply_markup(&self, message: &Message) -> JsonRequest<EditMessageReplyMarkup> {
|
||||
self.edit_message_reply_markup(message.chat.id, message.id)
|
||||
}
|
||||
|
||||
fn stop_poll_message(&self, message: &Message) -> JsonRequest<StopPoll> {
|
||||
self.stop_poll(message.chat.id, message.id)
|
||||
}
|
||||
|
||||
fn delete(&self, message: &Message) -> JsonRequest<DeleteMessage> {
|
||||
self.delete_message(message.chat.id, message.id)
|
||||
}
|
||||
|
||||
fn copy<C>(&self, to_chat_id: C, message: &Message) -> JsonRequest<CopyMessage>
|
||||
where
|
||||
C: Into<Recipient>,
|
||||
{
|
||||
self.copy_message(to_chat_id, message.chat.id, message.id)
|
||||
}
|
||||
}
|
155
crates/teloxide/src/sugar/request.rs
Normal file
155
crates/teloxide/src/sugar/request.rs
Normal file
|
@ -0,0 +1,155 @@
|
|||
//! Additions to [`JsonRequest`] and [`MultipartRequest`].
|
||||
//!
|
||||
//! [`JsonRequest`]: teloxide_core::requests::JsonRequest
|
||||
//! [`MultipartRequest`]: teloxide_core::requests::MultipartRequest
|
||||
|
||||
use teloxide_core::{
|
||||
payloads::*,
|
||||
requests::{JsonRequest, MultipartRequest},
|
||||
types::*,
|
||||
};
|
||||
|
||||
macro_rules! impl_request_reply_ext {
|
||||
($($t:ty),*) => {
|
||||
$(
|
||||
impl RequestReplyExt for $t {
|
||||
fn reply_to<M>(self, message_id: M) -> Self
|
||||
where
|
||||
M: Into<MessageId>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.reply_parameters(ReplyParameters::new(message_id.into()))
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_request_link_preview_ext {
|
||||
($($t:ty),*) => {
|
||||
$(
|
||||
impl RequestLinkPreviewExt for $t {
|
||||
fn disable_link_preview(self, is_disabled: bool) -> Self
|
||||
where
|
||||
Self: Sized
|
||||
{
|
||||
let link_preview_options = LinkPreviewOptions {
|
||||
is_disabled,
|
||||
url: None,
|
||||
prefer_small_media: false,
|
||||
prefer_large_media: false,
|
||||
show_above_text: false,
|
||||
};
|
||||
self.link_preview_options(link_preview_options)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
/// `.reply_to(msg)` syntax sugar for requests.
|
||||
pub trait RequestReplyExt {
|
||||
/// Replaces `.reply_parameters(ReplyParameters::new(msg.id))`
|
||||
/// with `.reply_to(msg.id)` or `.reply_to(msg)`
|
||||
fn reply_to<M>(self, message_id: M) -> Self
|
||||
where
|
||||
M: Into<MessageId>,
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// `.disable_link_preview(is_disabled)` syntax sugar for requests.
|
||||
pub trait RequestLinkPreviewExt {
|
||||
/// Replaces
|
||||
/// `.link_preview_options(LinkPreviewOptions {
|
||||
/// is_disabled: true,
|
||||
/// url: None,
|
||||
/// prefer_small_media: false,
|
||||
/// prefer_large_media: false,
|
||||
/// show_above_text: false
|
||||
/// };)`
|
||||
///
|
||||
/// with `.disable_link_preview(true)`.
|
||||
fn disable_link_preview(self, is_disabled: bool) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl_request_reply_ext! {
|
||||
JsonRequest<SendDice>,
|
||||
JsonRequest<SendInvoice>,
|
||||
JsonRequest<SendPoll>,
|
||||
JsonRequest<SendContact>,
|
||||
JsonRequest<SendGame>,
|
||||
JsonRequest<SendVenue>,
|
||||
JsonRequest<SendLocation>,
|
||||
JsonRequest<CopyMessage>,
|
||||
JsonRequest<SendMessage>,
|
||||
MultipartRequest<SendSticker>,
|
||||
MultipartRequest<SendMediaGroup>,
|
||||
MultipartRequest<SendAnimation>,
|
||||
MultipartRequest<SendVideoNote>,
|
||||
MultipartRequest<SendVideo>,
|
||||
MultipartRequest<SendDocument>,
|
||||
MultipartRequest<SendAudio>,
|
||||
MultipartRequest<SendVoice>,
|
||||
MultipartRequest<SendPhoto>
|
||||
}
|
||||
|
||||
impl_request_link_preview_ext! {
|
||||
JsonRequest<SendMessage>,
|
||||
JsonRequest<EditMessageText>
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Deref;
|
||||
|
||||
use super::*;
|
||||
use teloxide_core::{prelude::Requester, Bot};
|
||||
|
||||
#[test]
|
||||
fn test_reply_to() {
|
||||
let bot = Bot::new("TOKEN");
|
||||
|
||||
let real_reply_req = bot
|
||||
.send_message(ChatId(1234), "test")
|
||||
.reply_parameters(ReplyParameters::new(MessageId(1)));
|
||||
let sugar_reply_req = bot.send_message(ChatId(1234), "test").reply_to(MessageId(1));
|
||||
|
||||
assert_eq!(real_reply_req.deref(), sugar_reply_req.deref())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reply_to_multipart() {
|
||||
let bot = Bot::new("TOKEN");
|
||||
let document = InputFile::memory("hello world!");
|
||||
|
||||
let real_reply_req = bot
|
||||
.send_document(ChatId(1234), document.clone())
|
||||
.reply_parameters(ReplyParameters::new(MessageId(1)));
|
||||
let sugar_reply_req = bot.send_document(ChatId(1234), document).reply_to(MessageId(1));
|
||||
|
||||
assert_eq!(
|
||||
real_reply_req.deref().reply_parameters,
|
||||
sugar_reply_req.deref().reply_parameters
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disable_link_preview() {
|
||||
let link_preview_options = LinkPreviewOptions {
|
||||
is_disabled: true,
|
||||
url: None,
|
||||
prefer_small_media: false,
|
||||
prefer_large_media: false,
|
||||
show_above_text: false,
|
||||
};
|
||||
let bot = Bot::new("TOKEN");
|
||||
|
||||
let real_link_req =
|
||||
bot.send_message(ChatId(1234), "test").link_preview_options(link_preview_options);
|
||||
let sugar_link_req = bot.send_message(ChatId(1234), "test").disable_link_preview(true);
|
||||
|
||||
assert_eq!(real_link_req.deref(), sugar_link_req.deref())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue