Merge pull request #1162 from LasterAlex/add-syntactic-sugar

Added syntactic sugar
This commit is contained in:
Tima Kinsart 2024-09-24 19:38:14 +00:00 committed by GitHub
commit cfedb585d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 373 additions and 14 deletions

View file

@ -8,13 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Add `filter_boost_added` and `filter_reply_to_story` filters to `MessageFilterExt` trait - `filter_boost_added` and `filter_reply_to_story` filters to the `MessageFilterExt` trait ([PR 1131](https://github.com/teloxide/teloxide/pull/1131))
- Add `filter_mention_command` filter to `HandlerExt` trait ([issue #494](https://github.com/teloxide/teloxide/issues/494)) - `filter_mention_command` filter to the `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_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 ### 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` - 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` - 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` - `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 - 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 ## 0.13.0 - 2024-08-16
### Added ### Added

View file

@ -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)] #[cfg(test)]
mod tests { mod tests {
use chrono::DateTime; use chrono::DateTime;

View file

@ -2,6 +2,7 @@ use std::error::Error;
use teloxide::{ use teloxide::{
payloads::SendMessageSetters, payloads::SendMessageSetters,
prelude::*, prelude::*,
sugar::bot::BotMessagesExt,
types::{ types::{
InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputMessageContent, InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputMessageContent,
InputMessageContentText, Me, InputMessageContentText, Me,
@ -107,17 +108,17 @@ async fn inline_query_handler(
/// **IMPORTANT**: do not send privacy-sensitive data this way!!! /// **IMPORTANT**: do not send privacy-sensitive data this way!!!
/// Anyone can read data stored in the callback button. /// Anyone can read data stored in the callback button.
async fn callback_handler(bot: Bot, q: CallbackQuery) -> Result<(), Box<dyn Error + Send + Sync>> { 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}"); let text = format!("You chose: {version}");
// Tell telegram that we've seen this query, to remove 🕑 icons from the // Tell telegram that we've seen this query, to remove 🕑 icons from the
// clients. You could also use `answer_callback_query`'s optional // clients. You could also use `answer_callback_query`'s optional
// parameters to tweak what happens on the client side. // 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 // Edit text of the message to which the buttons were attached
if let Some(message) = q.message { if let Some(message) = q.regular_message() {
bot.edit_message_text(message.chat().id, message.id(), text).await?; bot.edit_text(message, text).await?;
} else if let Some(id) = q.inline_message_id { } else if let Some(id) = q.inline_message_id {
bot.edit_message_text_inline(id, text).await?; bot.edit_message_text_inline(id, text).await?;
} }

View file

@ -4,9 +4,7 @@
use rand::Rng; use rand::Rng;
use teloxide::{ use teloxide::{
dispatching::HandlerExt, dispatching::HandlerExt, prelude::*, sugar::request::RequestReplyExt, types::Dice,
prelude::*,
types::{Dice, ReplyParameters},
utils::command::BotCommands, utils::command::BotCommands,
}; };
@ -84,7 +82,7 @@ async fn main() {
// filter only messages with dices. // filter only messages with dices.
Message::filter_dice().endpoint(|bot: Bot, msg: Message, dice: Dice| async move { Message::filter_dice().endpoint(|bot: Bot, msg: Message, dice: Dice| async move {
bot.send_message(msg.chat.id, format!("Dice value: {}", dice.value)) bot.send_message(msg.chat.id, format!("Dice value: {}", dice.value))
.reply_parameters(ReplyParameters::new(msg.id)) .reply_to(msg)
.await?; .await?;
Ok(()) Ok(())
}), }),

View file

@ -140,6 +140,7 @@ pub mod prelude;
#[cfg(feature = "ctrlc_handler")] #[cfg(feature = "ctrlc_handler")]
pub mod repls; pub mod repls;
pub mod stop; pub mod stop;
pub mod sugar;
pub mod update_listeners; pub mod update_listeners;
pub mod utils; pub mod utils;

View file

@ -0,0 +1,4 @@
//! Some syntax sugar support for TBA functionality.
pub mod bot;
pub mod request;

View 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)
}
}

View 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())
}
}