From 6fc6664be9dd5031cdc760cf9520b0610ca41507 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 5 Nov 2023 22:49:55 +0100 Subject: [PATCH 01/19] Re-implement `DefaultParseMode` adaptor, to apply parse mode at `send` --- .../teloxide-core/src/adaptors/parse_mode.rs | 150 +++++++++++++++--- 1 file changed, 131 insertions(+), 19 deletions(-) diff --git a/crates/teloxide-core/src/adaptors/parse_mode.rs b/crates/teloxide-core/src/adaptors/parse_mode.rs index 94d923d4..b609e1eb 100644 --- a/crates/teloxide-core/src/adaptors/parse_mode.rs +++ b/crates/teloxide-core/src/adaptors/parse_mode.rs @@ -1,8 +1,15 @@ +use std::future::IntoFuture; + use url::Url; use crate::{ + payloads::{ + EditMessageCaption, EditMessageCaptionInline, EditMessageText, EditMessageTextInline, + SendAnimation, SendAudio, SendDocument, SendMessage, SendPhoto, SendPoll, SendVideo, + SendVoice, + }, prelude::Requester, - requests::HasPayload, + requests::{HasPayload, Output, Request}, types::{InputFile, ParseMode, Recipient, *}, }; @@ -14,6 +21,13 @@ pub struct DefaultParseMode { mode: ParseMode, } +/// Request returned by [`DefaultParseMode`] methods. +#[derive(Clone)] +pub struct DefaultParseModeRequest { + req: R, + mode: ParseMode, +} + impl DefaultParseMode { /// Creates new [`DefaultParseMode`]. /// @@ -40,17 +54,70 @@ impl DefaultParseMode { } } +impl Request for DefaultParseModeRequest +where + R: Request + Clone, + R::Payload: VisitParseModes, +{ + type Err = R::Err; + type Send = R::Send; + type SendRef = R::Send; + + // Required methods + fn send(mut self) -> Self::Send { + self.req.payload_mut().visit_parse_modes(|mode| _ = mode.get_or_insert(self.mode)); + self.req.send() + } + + fn send_ref(&self) -> Self::SendRef { + // There is no other way to change the payload, given a `&self` :( + self.clone().send() + } +} + +impl IntoFuture for DefaultParseModeRequest +where + Self: Request, +{ + type Output = Result, ::Err>; + type IntoFuture = ::Send; + + fn into_future(self) -> Self::IntoFuture { + self.send() + } +} + +impl HasPayload for DefaultParseModeRequest +where + R: Request, +{ + type Payload = R::Payload; + + fn payload_mut(&mut self) -> &mut Self::Payload { + self.req.payload_mut() + } + + fn payload_ref(&self) -> &Self::Payload { + self.req.payload_ref() + } +} + macro_rules! f { ($m:ident $this:ident ($($arg:ident : $T:ty),*)) => { { - let mut req = $this.inner().$m($($arg),*); - req.payload_mut().parse_mode = Some($this.mode); - req + let req = $this.inner().$m($($arg),*); + DefaultParseModeRequest { req, mode: $this.mode } } }; } macro_rules! fty { + ($T:ident) => { + DefaultParseModeRequest + }; +} + +macro_rules! ftyid { ($T:ident) => { B::$T }; @@ -62,7 +129,22 @@ macro_rules! fid { }; } -impl Requester for DefaultParseMode { +impl Requester for DefaultParseMode +where + B: Requester, + B::SendMessage: Clone, + B::SendPhoto: Clone, + B::SendVideo: Clone, + B::SendAudio: Clone, + B::SendDocument: Clone, + B::SendAnimation: Clone, + B::SendVoice: Clone, + B::EditMessageText: Clone, + B::EditMessageTextInline: Clone, + B::EditMessageCaption: Clone, + B::EditMessageCaptionInline: Clone, + B::SendPoll: Clone, +{ type Err = B::Err; requester_forward! { @@ -73,25 +155,13 @@ impl Requester for DefaultParseMode { send_document, send_animation, send_voice, + send_poll, edit_message_text, edit_message_text_inline, edit_message_caption, edit_message_caption_inline => f, fty } - type SendPoll = B::SendPoll; - - fn send_poll(&self, chat_id: C, question: Q, options: O) -> Self::SendPoll - where - C: Into, - Q: Into, - O: IntoIterator, - { - let mut req = self.inner().send_poll(chat_id, question, options); - req.payload_mut().explanation_parse_mode = Some(self.mode); - req - } - requester_forward! { get_me, log_out, @@ -191,7 +261,7 @@ impl Requester for DefaultParseMode { get_game_high_scores, approve_chat_join_request, decline_chat_join_request - => fid, fty + => fid, ftyid } } @@ -200,3 +270,45 @@ download_forward! { DefaultParseMode { this => this.inner() } } + +trait VisitParseModes { + fn visit_parse_modes(&mut self, visitor: impl FnMut(&mut Option)); +} + +macro_rules! impl_visit_parse_modes { + ( + $( + $T:ty => [ + $( + $field:ident + ),* + ] + , + )* + ) => { + $( + impl VisitParseModes for $T { + fn visit_parse_modes(&mut self, mut visitor: impl FnMut(&mut Option)) { + $( + visitor(&mut self.$field); + )* + } + } + )* + } +} + +impl_visit_parse_modes! { + SendMessage => [parse_mode], + SendPhoto => [parse_mode], + SendVideo => [parse_mode], + SendAudio => [parse_mode], + SendDocument => [parse_mode], + SendAnimation => [parse_mode], + SendVoice => [parse_mode], + EditMessageText => [parse_mode], + EditMessageTextInline => [parse_mode], + EditMessageCaption => [parse_mode], + EditMessageCaptionInline => [parse_mode], + SendPoll => [explanation_parse_mode], +} From 746302f8519eb0e81bd6c5a177396d48bca5b7b1 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 5 Nov 2023 22:58:15 +0100 Subject: [PATCH 02/19] Support `CopyMessage` by `DefaultParseMode` --- crates/teloxide-core/src/adaptors/parse_mode.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/teloxide-core/src/adaptors/parse_mode.rs b/crates/teloxide-core/src/adaptors/parse_mode.rs index b609e1eb..5d0ccfe9 100644 --- a/crates/teloxide-core/src/adaptors/parse_mode.rs +++ b/crates/teloxide-core/src/adaptors/parse_mode.rs @@ -4,9 +4,9 @@ use url::Url; use crate::{ payloads::{ - EditMessageCaption, EditMessageCaptionInline, EditMessageText, EditMessageTextInline, - SendAnimation, SendAudio, SendDocument, SendMessage, SendPhoto, SendPoll, SendVideo, - SendVoice, + CopyMessage, EditMessageCaption, EditMessageCaptionInline, EditMessageText, + EditMessageTextInline, SendAnimation, SendAudio, SendDocument, SendMessage, SendPhoto, + SendPoll, SendVideo, SendVoice, }, prelude::Requester, requests::{HasPayload, Output, Request}, @@ -144,6 +144,7 @@ where B::EditMessageCaption: Clone, B::EditMessageCaptionInline: Clone, B::SendPoll: Clone, + B::CopyMessage: Clone, { type Err = B::Err; @@ -159,7 +160,8 @@ where edit_message_text, edit_message_text_inline, edit_message_caption, - edit_message_caption_inline => f, fty + edit_message_caption_inline, + copy_message => f, fty } requester_forward! { @@ -171,7 +173,6 @@ where delete_webhook, get_webhook_info, forward_message, - copy_message, send_video_note, send_media_group, send_location, @@ -310,5 +311,8 @@ impl_visit_parse_modes! { EditMessageTextInline => [parse_mode], EditMessageCaption => [parse_mode], EditMessageCaptionInline => [parse_mode], + // FIXME: check if `parse_mode` changes anything if `.caption` is not set + // (and if it does, maybe not call visitor if `self.caption.is_none()`) + CopyMessage => [parse_mode], SendPoll => [explanation_parse_mode], } From 88eb35ad9540385dbdfb1839074f6d0d50f8db12 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 5 Nov 2023 23:19:59 +0100 Subject: [PATCH 03/19] Add proper support for `answer_inline_query` and `answer_web_app_query` in `DefaultParseMode` --- .../teloxide-core/src/adaptors/parse_mode.rs | 81 +++++++++++++++++-- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/crates/teloxide-core/src/adaptors/parse_mode.rs b/crates/teloxide-core/src/adaptors/parse_mode.rs index 5d0ccfe9..44de63c4 100644 --- a/crates/teloxide-core/src/adaptors/parse_mode.rs +++ b/crates/teloxide-core/src/adaptors/parse_mode.rs @@ -4,9 +4,9 @@ use url::Url; use crate::{ payloads::{ - CopyMessage, EditMessageCaption, EditMessageCaptionInline, EditMessageText, - EditMessageTextInline, SendAnimation, SendAudio, SendDocument, SendMessage, SendPhoto, - SendPoll, SendVideo, SendVoice, + AnswerInlineQuery, AnswerWebAppQuery, CopyMessage, EditMessageCaption, + EditMessageCaptionInline, EditMessageText, EditMessageTextInline, SendAnimation, SendAudio, + SendDocument, SendMessage, SendPhoto, SendPoll, SendVideo, SendVoice, }, prelude::Requester, requests::{HasPayload, Output, Request}, @@ -145,6 +145,8 @@ where B::EditMessageCaptionInline: Clone, B::SendPoll: Clone, B::CopyMessage: Clone, + B::AnswerInlineQuery: Clone, + B::AnswerWebAppQuery: Clone, { type Err = B::Err; @@ -161,7 +163,10 @@ where edit_message_text_inline, edit_message_caption, edit_message_caption_inline, - copy_message => f, fty + copy_message, + answer_inline_query, + answer_web_app_query, + => f, fty } requester_forward! { @@ -234,8 +239,6 @@ where set_my_default_administrator_rights, get_my_default_administrator_rights, delete_my_commands, - answer_inline_query, - answer_web_app_query, edit_message_media, edit_message_media_inline, edit_message_reply_markup, @@ -316,3 +319,69 @@ impl_visit_parse_modes! { CopyMessage => [parse_mode], SendPoll => [explanation_parse_mode], } + +impl VisitParseModes for AnswerInlineQuery { + fn visit_parse_modes(&mut self, mut visitor: impl FnMut(&mut Option)) { + for result in &mut self.results { + visit_parse_modes_in_inline_query_result(result, &mut visitor); + } + } +} + +impl VisitParseModes for AnswerWebAppQuery { + fn visit_parse_modes(&mut self, mut visitor: impl FnMut(&mut Option)) { + visit_parse_modes_in_inline_query_result(&mut self.result, &mut visitor); + } +} + +fn visit_parse_modes_in_inline_query_result( + result: &mut InlineQueryResult, + visitor: &mut impl FnMut(&mut Option), +) { + use InlineQueryResult::*; + + let parse_mode = match result { + // Simply contain `parse_mode` + CachedAudio(r) => &mut r.parse_mode, + CachedDocument(r) => &mut r.parse_mode, + CachedGif(r) => &mut r.parse_mode, + CachedMpeg4Gif(r) => &mut r.parse_mode, + CachedPhoto(r) => &mut r.parse_mode, + CachedVideo(r) => &mut r.parse_mode, + CachedVoice(r) => &mut r.parse_mode, + Audio(r) => &mut r.parse_mode, + Document(r) => &mut r.parse_mode, + Gif(r) => &mut r.parse_mode, + Mpeg4Gif(r) => &mut r.parse_mode, + Photo(r) => &mut r.parse_mode, + Video(r) => &mut r.parse_mode, + Voice(r) => &mut r.parse_mode, + + // Can contain parse mode if `InputMessageContent::Text` + CachedSticker(r) => match &mut r.input_message_content { + Some(InputMessageContent::Text(t)) => &mut t.parse_mode, + _ => return, + }, + Article(r) => match &mut r.input_message_content { + InputMessageContent::Text(t) => &mut t.parse_mode, + _ => return, + }, + Contact(r) => match &mut r.input_message_content { + Some(InputMessageContent::Text(t)) => &mut t.parse_mode, + _ => return, + }, + Location(r) => match &mut r.input_message_content { + Some(InputMessageContent::Text(t)) => &mut t.parse_mode, + _ => return, + }, + Venue(r) => match &mut r.input_message_content { + Some(InputMessageContent::Text(t)) => &mut t.parse_mode, + _ => return, + }, + + // Can't contain `parse_mode` at all + Game(_r) => return, + }; + + visitor(parse_mode); +} From f4dd70604c9795057213bced5fa0f28e01218e23 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 5 Nov 2023 23:37:23 +0100 Subject: [PATCH 04/19] Add proper support for `send_media_group` and `edit_message_media{,_inline}` in `DefaultParseMode` --- .../teloxide-core/src/adaptors/parse_mode.rs | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/crates/teloxide-core/src/adaptors/parse_mode.rs b/crates/teloxide-core/src/adaptors/parse_mode.rs index 44de63c4..0dd50024 100644 --- a/crates/teloxide-core/src/adaptors/parse_mode.rs +++ b/crates/teloxide-core/src/adaptors/parse_mode.rs @@ -5,8 +5,9 @@ use url::Url; use crate::{ payloads::{ AnswerInlineQuery, AnswerWebAppQuery, CopyMessage, EditMessageCaption, - EditMessageCaptionInline, EditMessageText, EditMessageTextInline, SendAnimation, SendAudio, - SendDocument, SendMessage, SendPhoto, SendPoll, SendVideo, SendVoice, + EditMessageCaptionInline, EditMessageMedia, EditMessageMediaInline, EditMessageText, + EditMessageTextInline, SendAnimation, SendAudio, SendDocument, SendMediaGroup, SendMessage, + SendPhoto, SendPoll, SendVideo, SendVoice, }, prelude::Requester, requests::{HasPayload, Output, Request}, @@ -147,6 +148,9 @@ where B::CopyMessage: Clone, B::AnswerInlineQuery: Clone, B::AnswerWebAppQuery: Clone, + B::EditMessageMedia: Clone, + B::EditMessageMediaInline: Clone, + B::SendMediaGroup: Clone, { type Err = B::Err; @@ -166,6 +170,9 @@ where copy_message, answer_inline_query, answer_web_app_query, + send_media_group, + edit_message_media, + edit_message_media_inline, => f, fty } @@ -179,7 +186,6 @@ where get_webhook_info, forward_message, send_video_note, - send_media_group, send_location, edit_message_live_location, edit_message_live_location_inline, @@ -239,8 +245,6 @@ where set_my_default_administrator_rights, get_my_default_administrator_rights, delete_my_commands, - edit_message_media, - edit_message_media_inline, edit_message_reply_markup, edit_message_reply_markup_inline, stop_poll, @@ -334,6 +338,26 @@ impl VisitParseModes for AnswerWebAppQuery { } } +impl VisitParseModes for SendMediaGroup { + fn visit_parse_modes(&mut self, mut visitor: impl FnMut(&mut Option)) { + for media in &mut self.media { + visit_parse_modes_in_input_media(media, &mut visitor); + } + } +} + +impl VisitParseModes for EditMessageMedia { + fn visit_parse_modes(&mut self, mut visitor: impl FnMut(&mut Option)) { + visit_parse_modes_in_input_media(&mut self.media, &mut visitor); + } +} + +impl VisitParseModes for EditMessageMediaInline { + fn visit_parse_modes(&mut self, mut visitor: impl FnMut(&mut Option)) { + visit_parse_modes_in_input_media(&mut self.media, &mut visitor); + } +} + fn visit_parse_modes_in_inline_query_result( result: &mut InlineQueryResult, visitor: &mut impl FnMut(&mut Option), @@ -385,3 +409,20 @@ fn visit_parse_modes_in_inline_query_result( visitor(parse_mode); } + +fn visit_parse_modes_in_input_media( + media: &mut InputMedia, + visitor: &mut impl FnMut(&mut Option), +) { + use InputMedia::*; + + let parse_mode = match media { + Photo(m) => &mut m.parse_mode, + Video(m) => &mut m.parse_mode, + Animation(m) => &mut m.parse_mode, + Audio(m) => &mut m.parse_mode, + Document(m) => &mut m.parse_mode, + }; + + visitor(parse_mode); +} From 0017480467f4468ab6bb3bc63223bb59f92f6994 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 5 Nov 2023 23:39:36 +0100 Subject: [PATCH 05/19] Replace a couple for loops with `.for_each` I will not be explaining why (maybe unless you ask me) --- crates/teloxide-core/src/adaptors/parse_mode.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/teloxide-core/src/adaptors/parse_mode.rs b/crates/teloxide-core/src/adaptors/parse_mode.rs index 0dd50024..c929cfbd 100644 --- a/crates/teloxide-core/src/adaptors/parse_mode.rs +++ b/crates/teloxide-core/src/adaptors/parse_mode.rs @@ -326,9 +326,9 @@ impl_visit_parse_modes! { impl VisitParseModes for AnswerInlineQuery { fn visit_parse_modes(&mut self, mut visitor: impl FnMut(&mut Option)) { - for result in &mut self.results { - visit_parse_modes_in_inline_query_result(result, &mut visitor); - } + self.results + .iter_mut() + .for_each(|result| visit_parse_modes_in_inline_query_result(result, &mut visitor)) } } @@ -340,9 +340,9 @@ impl VisitParseModes for AnswerWebAppQuery { impl VisitParseModes for SendMediaGroup { fn visit_parse_modes(&mut self, mut visitor: impl FnMut(&mut Option)) { - for media in &mut self.media { - visit_parse_modes_in_input_media(media, &mut visitor); - } + self.media + .iter_mut() + .for_each(|media| visit_parse_modes_in_input_media(media, &mut visitor)) } } From ad2d28176f373b103b8f5a1aacb922e385805751 Mon Sep 17 00:00:00 2001 From: Waffle Maybe Date: Sun, 5 Nov 2023 23:45:43 +0100 Subject: [PATCH 06/19] Don't auto-add `C-main` label to all PRs it looks like triagebot uses `starts_with`, which makes `crate/teloxide` trigger on `crates/teloxide-core`, lmao. --- triagebot.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/triagebot.toml b/triagebot.toml index 1a9f3b3f..c5a1e994 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -20,13 +20,13 @@ new_pr = true #new_issue = true [autolabel."C-core"] -trigger_files = ["crates/teloxide-core"] +trigger_files = ["crates/teloxide-core/"] [autolabel."C-main"] -trigger_files = ["crates/teloxide"] +trigger_files = ["crates/teloxide/"] [autolabel."C-macros"] -trigger_files = ["crates/teloxide-macros"] +trigger_files = ["crates/teloxide-macros/"] [relabel] From d26bd0629b4be2299c264af58625e54acf59291f Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sun, 5 Nov 2023 23:50:54 +0100 Subject: [PATCH 07/19] Update changelog --- crates/teloxide-core/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index 085e5995..b202658e 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -67,6 +67,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `SendGame::reply_to_message_id`, `SendSticker::reply_to_message_id` and `SendInvoice::reply_to_message_id` now use `MessageId` instead of `i32` ([#887][pr887]) - Use `UpdateId` for `Update::id` ([#892][pr892]) - MSRV (Minimal Supported Rust Version) was bumped from `1.64.0` to `1.68.0` ([#950][pr950]) +- Add proper support for `edit_message_caption_inline`, `copy_message`, `answer_inline_query`, `answer_web_app_query`, `send_media_group`, `edit_message_media`, and `edit_message_media_inline` to `DefaultParseMode` adaptor ([#961][pr961]) + - Note that now `DefaultParseMode` sets the default on `send`, instead of request creation + - `DefaultParseMode` now also requires that the supported requests implement `Clone` (as a user you should not notice anything changing) [pr852]: https://github.com/teloxide/teloxide/pull/853 [pr859]: https://github.com/teloxide/teloxide/pull/859 @@ -74,6 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr885]: https://github.com/teloxide/teloxide/pull/885 [pr892]: https://github.com/teloxide/teloxide/pull/892 [pr950]: https://github.com/teloxide/teloxide/pull/950 +[pr961]: https://github.com/teloxide/teloxide/pull/961 ### Deprecated From f7fd84ac32d2f028c1a25acda233735b76256dbf Mon Sep 17 00:00:00 2001 From: Zagzuz Date: Fri, 8 Dec 2023 14:26:16 +0600 Subject: [PATCH 08/19] add `#[must_use]` to bot adaptors --- crates/teloxide-core/src/requests/requester_ext.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/teloxide-core/src/requests/requester_ext.rs b/crates/teloxide-core/src/requests/requester_ext.rs index 35509409..4f5c5751 100644 --- a/crates/teloxide-core/src/requests/requester_ext.rs +++ b/crates/teloxide-core/src/requests/requester_ext.rs @@ -20,6 +20,7 @@ use crate::adaptors::throttle::{Limits, Throttle}; pub trait RequesterExt: Requester { /// Add `get_me` caching ability, see [`CacheMe`] for more. #[cfg(feature = "cache_me")] + #[must_use] fn cache_me(self) -> CacheMe where Self: Sized, @@ -29,6 +30,7 @@ pub trait RequesterExt: Requester { /// Send requests automatically, see [`AutoSend`] for more. #[cfg(feature = "auto_send")] + #[must_use] #[deprecated( since = "0.8.0", note = "`AutoSend` is no longer required to `.await` requests and is now noop" @@ -43,6 +45,7 @@ pub trait RequesterExt: Requester { /// Erase requester type. #[cfg(feature = "erased")] + #[must_use] fn erase<'a>(self) -> ErasedRequester<'a, Self::Err> where Self: 'a, @@ -53,6 +56,7 @@ pub trait RequesterExt: Requester { /// Trace requests, see [`Trace`] for more. #[cfg(feature = "trace_adaptor")] + #[must_use] fn trace(self, settings: Settings) -> Trace where Self: Sized, @@ -64,6 +68,7 @@ pub trait RequesterExt: Requester { /// /// Note: this spawns the worker, just as [`Throttle::new_spawn`]. #[cfg(feature = "throttle")] + #[must_use] fn throttle(self, limits: Limits) -> Throttle where Self: Sized + Clone + Send + Sync + 'static, @@ -101,6 +106,7 @@ pub trait RequesterExt: Requester { /// crate::requests::Requester::edit_message_caption /// [`edit_message_caption_inline`]: /// crate::requests::Requester::edit_message_caption_inline + #[must_use] fn parse_mode(self, parse_mode: ParseMode) -> DefaultParseMode where Self: Sized, From 1cc5a2d4feebd7eb7afdaf29eb4efb6e4501f5a5 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Mon, 25 Sep 2023 14:30:40 +0300 Subject: [PATCH 09/19] Add array value to `AttrValue` --- crates/teloxide-macros/src/attr.rs | 37 +++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/crates/teloxide-macros/src/attr.rs b/crates/teloxide-macros/src/attr.rs index c581b91c..e275fbc7 100644 --- a/crates/teloxide-macros/src/attr.rs +++ b/crates/teloxide-macros/src/attr.rs @@ -83,6 +83,7 @@ pub(crate) struct Attr { pub(crate) enum AttrValue { Path(Path), Lit(Lit), + Array(Vec, Span), None(Span), } @@ -169,6 +170,20 @@ impl AttrValue { } } + /// Unwraps this value if it's a vector of `T`. + /// ## Example + /// ```text + /// #[command(some = [1, 2, 3])] + /// ^^^^^^^^^ + /// this value will be parsed as a vector of integers + /// ``` + pub fn expect_array(self) -> Result> { + self.expect("an array", |this| match this { + AttrValue::Array(a, _) => Ok(a), + _ => Err(this), + }) + } + // /// Unwraps this value if it's a path. // pub fn expect_path(self) -> Result { // self.expect("a path", |this| match this { @@ -196,6 +211,7 @@ impl AttrValue { Bool(_) => "a boolean", Verbatim(_) => ":shrug:", }, + Self::Array(_, _) => "an array", Self::Path(_) => "a path", } } @@ -211,17 +227,26 @@ impl AttrValue { Self::Path(p) => p.span(), Self::Lit(l) => l.span(), Self::None(sp) => *sp, + Self::Array(_, sp) => *sp, } } } impl Parse for AttrValue { fn parse(input: ParseStream) -> syn::Result { - let this = match input.peek(Lit) { - true => Self::Lit(input.parse()?), - false => Self::Path(input.parse()?), - }; - - Ok(this) + if input.peek(Lit) { + input.parse::().map(AttrValue::Lit) + } else if input.peek(syn::token::Bracket) { + let content; + let array_span = syn::bracketed!(content in input).span; + let array = content.parse_terminated::<_, Token![,]>(AttrValue::parse)?; + Ok(AttrValue::Array(array.into_iter().collect(), array_span)) + } else { + Ok(AttrValue::Path( + input + .parse::() + .map_err(|_| syn::Error::new(input.span(), "Unexpected token"))?, + )) + } } } From d13d3e2b2aca80f4184f90e7d0614bd9ae78a7d9 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Mon, 25 Sep 2023 14:39:16 +0300 Subject: [PATCH 10/19] Support command aliases --- crates/teloxide-macros/src/bot_commands.rs | 9 ++++++-- crates/teloxide-macros/src/command.rs | 12 ++++++++++- crates/teloxide-macros/src/command_attr.rs | 16 +++++++++++++-- crates/teloxide-macros/src/command_enum.rs | 8 +++++++- crates/teloxide/src/utils/command.rs | 24 +++++++++++++++------- 5 files changed, 56 insertions(+), 13 deletions(-) diff --git a/crates/teloxide-macros/src/bot_commands.rs b/crates/teloxide-macros/src/bot_commands.rs index 6cebfdeb..ae9b9f96 100644 --- a/crates/teloxide-macros/src/bot_commands.rs +++ b/crates/teloxide-macros/src/bot_commands.rs @@ -61,9 +61,10 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> proc_macro2::To let command_descriptions = infos .iter() .filter(|command| command.description_is_enabled()) - .map(|command @ Command { prefix, name, ..}| { + .map(|command @ Command { prefix, name, aliases, ..}| { let description = command.description().unwrap_or_default(); - quote! { CommandDescription { prefix: #prefix, command: #name, description: #description } } + let aliases = aliases.clone().map(|(aliases, _)| aliases).unwrap_or_default(); + quote! { CommandDescription { prefix: #prefix, command: #name, description: #description, aliases: &[#(#aliases),*]} } }); let warnings = infos.iter().filter_map(|command| command.deprecated_description_off_span()).map(|span| { @@ -102,6 +103,7 @@ fn impl_parse( command_separator: &str, ) -> proc_macro2::TokenStream { let matching_values = infos.iter().map(|c| c.get_prefixed_command()); + let aliases = infos.iter().map(|c| c.get_prefixed_aliases().unwrap_or_default()); quote! { fn parse(s: &str, bot_name: &str) -> ::std::result::Result { @@ -129,6 +131,9 @@ fn impl_parse( #( #matching_values => Ok(#variants_initialization), )* + #( + c if [#(#aliases),*].contains(&c) => Ok(#variants_initialization), + )* _ => ::std::result::Result::Err(ParseError::UnknownCommand(command.to_owned())), } } diff --git a/crates/teloxide-macros/src/command.rs b/crates/teloxide-macros/src/command.rs index 794d42d8..9cca6987 100644 --- a/crates/teloxide-macros/src/command.rs +++ b/crates/teloxide-macros/src/command.rs @@ -13,6 +13,8 @@ pub(crate) struct Command { pub description: Option<(String, bool, Span)>, /// Name of the command, with all renames already applied. pub name: String, + /// The aliases of the command. + pub aliases: Option<(Vec, Span)>, /// Parser for arguments of this command. pub parser: ParserType, /// Whether the command is hidden from the help message. @@ -31,6 +33,7 @@ impl Command { description, rename_rule, rename, + aliases, parser, // FIXME: error on/do not ignore separator separator: _, @@ -55,7 +58,7 @@ impl Command { let parser = parser.map(|(p, _)| p).unwrap_or_else(|| global_options.parser_type.clone()); let hidden = hide.is_some(); - Ok(Self { prefix, description, parser, name, hidden }) + Ok(Self { prefix, description, parser, name, aliases, hidden }) } pub fn get_prefixed_command(&self) -> String { @@ -63,6 +66,13 @@ impl Command { format!("{prefix}{name}") } + pub fn get_prefixed_aliases(&self) -> Option> { + let Self { prefix, aliases, .. } = self; + aliases + .as_ref() + .map(|(aliases, _)| aliases.iter().map(|alias| format!("{prefix}{alias}")).collect()) + } + pub fn description(&self) -> Option<&str> { self.description.as_ref().map(|(d, ..)| &**d) } diff --git a/crates/teloxide-macros/src/command_attr.rs b/crates/teloxide-macros/src/command_attr.rs index 91f87bc9..cf5af10d 100644 --- a/crates/teloxide-macros/src/command_attr.rs +++ b/crates/teloxide-macros/src/command_attr.rs @@ -1,5 +1,5 @@ use crate::{ - attr::{fold_attrs, Attr}, + attr::{fold_attrs, Attr, AttrValue}, error::compile_error_at, fields_parse::ParserType, rename_rules::RenameRule, @@ -20,6 +20,7 @@ pub(crate) struct CommandAttrs { pub description: Option<(String, bool, Span)>, pub rename_rule: Option<(RenameRule, Span)>, pub rename: Option<(String, Span)>, + pub aliases: Option<(Vec, Span)>, pub parser: Option<(ParserType, Span)>, pub separator: Option<(String, Span)>, pub command_separator: Option<(String, Span)>, @@ -47,6 +48,7 @@ enum CommandAttrKind { Description(String, bool), RenameRule(RenameRule), Rename(String), + Aliases(Vec), ParseWith(ParserType), Separator(String), CommandSeparator(String), @@ -66,6 +68,7 @@ impl CommandAttrs { description: None, rename_rule: None, rename: None, + aliases: None, parser: None, separator: None, command_separator: None, @@ -111,6 +114,7 @@ impl CommandAttrs { } RenameRule(r) => insert(&mut this.rename_rule, r, attr.sp), Rename(r) => insert(&mut this.rename, r, attr.sp), + Aliases(a) => insert(&mut this.aliases, a, attr.sp), ParseWith(p) => insert(&mut this.parser, p, attr.sp), Separator(s) => insert(&mut this.separator, s, attr.sp), CommandSeparator(s) => insert(&mut this.command_separator, s, attr.sp), @@ -170,10 +174,18 @@ impl CommandAttr { "separator" => Separator(value.expect_string()?), "command_separator" => CommandSeparator(value.expect_string()?), "hide" => value.expect_none("hide").map(|_| Hide)?, + "alias" => Aliases(vec![value.expect_string()?]), + "aliases" => Aliases( + value + .expect_array()? + .into_iter() + .map(AttrValue::expect_string) + .collect::>()?, + ), _ => { return Err(compile_error_at( "unexpected attribute name (expected one of `prefix`, `description`, \ - `rename`, `parse_with`, `separator` and `hide`", + `rename`, `parse_with`, `separator`, `hide`, `alias` and `aliases`", attr.span(), )) } diff --git a/crates/teloxide-macros/src/command_enum.rs b/crates/teloxide-macros/src/command_enum.rs index ff2a4960..4a3f0efa 100644 --- a/crates/teloxide-macros/src/command_enum.rs +++ b/crates/teloxide-macros/src/command_enum.rs @@ -21,8 +21,9 @@ impl CommandEnum { rename_rule, rename, parser, - separator, + aliases, command_separator, + separator, hide, } = attrs; @@ -36,6 +37,11 @@ impl CommandEnum { "`hide` attribute can only be applied to enums *variants*", sp, )); + } else if let Some((_aliases, sp)) = aliases { + return Err(compile_error_at( + "`aliases` attribute can only be applied to enums *variants*", + sp, + )); } let mut parser = parser.map(|(p, _)| p).unwrap_or(ParserType::Default); diff --git a/crates/teloxide/src/utils/command.rs b/crates/teloxide/src/utils/command.rs index 79c9dc7a..e9eb7f68 100644 --- a/crates/teloxide/src/utils/command.rs +++ b/crates/teloxide/src/utils/command.rs @@ -317,6 +317,8 @@ pub struct CommandDescription<'a> { pub prefix: &'a str, /// The command itself, e.g. `start`. pub command: &'a str, + /// The command aliases, e.g. `["help", "h"]`. + pub aliases: &'a [&'a str], /// Human-readable description of the command. pub description: &'a str, } @@ -478,17 +480,25 @@ impl Display for CommandDescriptions<'_> { f.write_str("\n\n")?; } - let mut write = |&CommandDescription { prefix, command, description }, nls| { + let format_command = |command: &str, prefix: &str, formater: &mut fmt::Formatter<'_>| { + formater.write_str(prefix)?; + formater.write_str(command)?; + if let Some(username) = self.bot_username { + formater.write_char('@')?; + formater.write_str(username)?; + } + fmt::Result::Ok(()) + }; + + let mut write = |&CommandDescription { prefix, command, aliases, description }, nls| { if nls { f.write_char('\n')?; } - f.write_str(prefix)?; - f.write_str(command)?; - - if let Some(username) = self.bot_username { - f.write_char('@')?; - f.write_str(username)?; + format_command(command, prefix, f)?; + for alias in aliases { + f.write_str(", ")?; + format_command(alias, prefix, f)?; } if !description.is_empty() { From eeb0ef663f62f6995d56b9708e1c1ab2dfc7022e Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Mon, 25 Sep 2023 14:40:03 +0300 Subject: [PATCH 11/19] Command aliases tests --- crates/teloxide/tests/command.rs | 97 ++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/crates/teloxide/tests/command.rs b/crates/teloxide/tests/command.rs index ce6ccf75..1d7784a4 100644 --- a/crates/teloxide/tests/command.rs +++ b/crates/teloxide/tests/command.rs @@ -392,6 +392,103 @@ fn rename_rules() { ); } +#[test] +#[cfg(feature = "macros")] +fn alias() { + #[derive(BotCommands, Debug, PartialEq)] + #[command(rename_rule = "snake_case")] + enum DefaultCommands { + #[command(alias = "s")] + Start, + #[command(alias = "h")] + Help, + #[command(alias = "привет_мир")] + HelloWorld(String), + } + + assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/start", "").unwrap()); + assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/s", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/help", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/h", "").unwrap()); + assert_eq!( + DefaultCommands::HelloWorld("username".to_owned()), + DefaultCommands::parse("/hello_world username", "").unwrap() + ); + assert_eq!( + DefaultCommands::HelloWorld("username".to_owned()), + DefaultCommands::parse("/привет_мир username", "").unwrap() + ); +} + +#[test] +#[cfg(feature = "macros")] +fn alias_help_message() { + #[derive(BotCommands, Debug, PartialEq)] + #[command(rename_rule = "snake_case")] + enum DefaultCommands { + /// Start command + Start, + /// Help command + #[command(alias = "h")] + Help, + #[command(alias = "привет_мир")] + HelloWorld(String), + } + + assert_eq!( + "/start — Start command\n/help, /h — Help command\n/hello_world, /привет_мир", + DefaultCommands::descriptions().to_string() + ); +} + +#[test] +#[cfg(feature = "macros")] +fn aliases() { + #[derive(BotCommands, Debug, PartialEq)] + #[command(rename_rule = "snake_case")] + enum DefaultCommands { + Start, + #[command(aliases = ["h", "помощь"])] + Help, + #[command(aliases = ["привет_мир"])] + HelloWorld(String), + } + + assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/start", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/help", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/h", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/помощь", "").unwrap()); + assert_eq!( + DefaultCommands::HelloWorld("username".to_owned()), + DefaultCommands::parse("/hello_world username", "").unwrap() + ); + assert_eq!( + DefaultCommands::HelloWorld("username".to_owned()), + DefaultCommands::parse("/привет_мир username", "").unwrap() + ); +} + +#[test] +#[cfg(feature = "macros")] +fn aliases_help_message() { + #[derive(BotCommands, Debug, PartialEq)] + #[command(rename_rule = "snake_case")] + enum DefaultCommands { + /// Start command + Start, + /// Help command + #[command(aliases = ["h", "помощь"])] + Help, + #[command(aliases = ["привет_мир"])] + HelloWorld(String), + } + + assert_eq!( + "/start — Start command\n/help, /h, /помощь — Help command\n/hello_world, /привет_мир", + DefaultCommands::descriptions().to_string() + ); +} + #[test] #[cfg(feature = "macros")] fn custom_result() { From 3d050eaf43a872598dcf3a5f6dc9a7da4e410bba Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Mon, 25 Sep 2023 15:21:18 +0300 Subject: [PATCH 12/19] Fix doc test --- crates/teloxide/src/utils/command.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/teloxide/src/utils/command.rs b/crates/teloxide/src/utils/command.rs index e9eb7f68..50e653f4 100644 --- a/crates/teloxide/src/utils/command.rs +++ b/crates/teloxide/src/utils/command.rs @@ -348,8 +348,18 @@ impl<'a> CommandDescriptions<'a> { /// use teloxide::utils::command::{CommandDescription, CommandDescriptions}; /// /// let descriptions = CommandDescriptions::new(&[ - /// CommandDescription { prefix: "/", command: "start", description: "start this bot" }, - /// CommandDescription { prefix: "/", command: "help", description: "show this message" }, + /// CommandDescription { + /// prefix: "/", + /// command: "start", + /// description: "start this bot", + /// aliases: &[], + /// }, + /// CommandDescription { + /// prefix: "/", + /// command: "help", + /// description: "show this message", + /// aliases: &[], + /// }, /// ]); /// /// assert_eq!(descriptions.to_string(), "/start — start this bot\n/help — show this message"); From ed90485fdf2bfc15e850003a7c041aa3b45d3b7c Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Mon, 25 Sep 2023 16:19:32 +0300 Subject: [PATCH 13/19] Ability to hide the command aliases from the help message --- crates/teloxide-macros/src/bot_commands.rs | 2 +- crates/teloxide-macros/src/command.rs | 6 +++- crates/teloxide-macros/src/command_attr.rs | 5 ++++ crates/teloxide-macros/src/command_enum.rs | 33 +++++++++++----------- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/crates/teloxide-macros/src/bot_commands.rs b/crates/teloxide-macros/src/bot_commands.rs index ae9b9f96..c51b2ed3 100644 --- a/crates/teloxide-macros/src/bot_commands.rs +++ b/crates/teloxide-macros/src/bot_commands.rs @@ -63,7 +63,7 @@ fn impl_descriptions(infos: &[Command], global: &CommandEnum) -> proc_macro2::To .filter(|command| command.description_is_enabled()) .map(|command @ Command { prefix, name, aliases, ..}| { let description = command.description().unwrap_or_default(); - let aliases = aliases.clone().map(|(aliases, _)| aliases).unwrap_or_default(); + let aliases = (!command.hidden_aliases).then(|| aliases.clone().map(|(aliases, _)| aliases).unwrap_or_default()).unwrap_or_default(); quote! { CommandDescription { prefix: #prefix, command: #name, description: #description, aliases: &[#(#aliases),*]} } }); diff --git a/crates/teloxide-macros/src/command.rs b/crates/teloxide-macros/src/command.rs index 9cca6987..80b763a9 100644 --- a/crates/teloxide-macros/src/command.rs +++ b/crates/teloxide-macros/src/command.rs @@ -19,6 +19,8 @@ pub(crate) struct Command { pub parser: ParserType, /// Whether the command is hidden from the help message. pub hidden: bool, + /// Whether the aliases of the command are hidden from the help message. + pub hidden_aliases: bool, } impl Command { @@ -40,6 +42,7 @@ impl Command { // FIXME: error on/do not ignore command separator command_separator: _, hide, + hide_aliases, } = attrs; let name = match (rename, rename_rule) { @@ -57,8 +60,9 @@ impl Command { let prefix = prefix.map(|(p, _)| p).unwrap_or_else(|| global_options.prefix.clone()); let parser = parser.map(|(p, _)| p).unwrap_or_else(|| global_options.parser_type.clone()); let hidden = hide.is_some(); + let hidden_aliases = hide_aliases.is_some(); - Ok(Self { prefix, description, parser, name, aliases, hidden }) + Ok(Self { prefix, description, parser, name, aliases, hidden, hidden_aliases }) } pub fn get_prefixed_command(&self) -> String { diff --git a/crates/teloxide-macros/src/command_attr.rs b/crates/teloxide-macros/src/command_attr.rs index cf5af10d..2569cae8 100644 --- a/crates/teloxide-macros/src/command_attr.rs +++ b/crates/teloxide-macros/src/command_attr.rs @@ -25,6 +25,7 @@ pub(crate) struct CommandAttrs { pub separator: Option<(String, Span)>, pub command_separator: Option<(String, Span)>, pub hide: Option<((), Span)>, + pub hide_aliases: Option<((), Span)>, } /// A single k/v attribute for `BotCommands` derive macro. @@ -53,6 +54,7 @@ enum CommandAttrKind { Separator(String), CommandSeparator(String), Hide, + HideAliases, } impl CommandAttrs { @@ -73,6 +75,7 @@ impl CommandAttrs { separator: None, command_separator: None, hide: None, + hide_aliases: None, }, |mut this, attr| { fn insert(opt: &mut Option<(T, Span)>, x: T, sp: Span) -> Result<()> { @@ -119,6 +122,7 @@ impl CommandAttrs { Separator(s) => insert(&mut this.separator, s, attr.sp), CommandSeparator(s) => insert(&mut this.command_separator, s, attr.sp), Hide => insert(&mut this.hide, (), attr.sp), + HideAliases => insert(&mut this.hide_aliases, (), attr.sp), }?; Ok(this) @@ -174,6 +178,7 @@ impl CommandAttr { "separator" => Separator(value.expect_string()?), "command_separator" => CommandSeparator(value.expect_string()?), "hide" => value.expect_none("hide").map(|_| Hide)?, + "hide_aliases" => value.expect_none("hide_aliases").map(|_| HideAliases)?, "alias" => Aliases(vec![value.expect_string()?]), "aliases" => Aliases( value diff --git a/crates/teloxide-macros/src/command_enum.rs b/crates/teloxide-macros/src/command_enum.rs index 4a3f0efa..7c1fbf4f 100644 --- a/crates/teloxide-macros/src/command_enum.rs +++ b/crates/teloxide-macros/src/command_enum.rs @@ -3,6 +3,21 @@ use crate::{ rename_rules::RenameRule, Result, }; +/// Create a if block that checks if the given attribute is applied to a enum +/// itself, if so, return an error +macro_rules! variants_only_attr { + ($($attr: ident),+) => { + $( + if let Some((_, sp)) = $attr { + return Err(compile_error_at( + concat!("`", stringify!($attr), "` attribute can only be applied to enums *variants*"), + sp, + )); + } + )+ + }; +} + pub(crate) struct CommandEnum { pub prefix: String, /// The bool is true if the description contains a doc comment @@ -25,24 +40,10 @@ impl CommandEnum { command_separator, separator, hide, + hide_aliases, } = attrs; - if let Some((_rename, sp)) = rename { - return Err(compile_error_at( - "`rename` attribute can only be applied to enums *variants*", - sp, - )); - } else if let Some((_hide, sp)) = hide { - return Err(compile_error_at( - "`hide` attribute can only be applied to enums *variants*", - sp, - )); - } else if let Some((_aliases, sp)) = aliases { - return Err(compile_error_at( - "`aliases` attribute can only be applied to enums *variants*", - sp, - )); - } + variants_only_attr![rename, hide, hide_aliases, aliases]; let mut parser = parser.map(|(p, _)| p).unwrap_or(ParserType::Default); From f3281b6eac7932b0fd412e402600bdd90fab11f6 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Mon, 25 Sep 2023 16:19:58 +0300 Subject: [PATCH 14/19] `hide_aliases` tests --- crates/teloxide/tests/command.rs | 87 ++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/crates/teloxide/tests/command.rs b/crates/teloxide/tests/command.rs index 1d7784a4..c1417c4f 100644 --- a/crates/teloxide/tests/command.rs +++ b/crates/teloxide/tests/command.rs @@ -489,6 +489,93 @@ fn aliases_help_message() { ); } +#[test] +#[cfg(feature = "macros")] +fn hide_aliases_for_unaliases_command() { + #[derive(BotCommands, Debug, PartialEq)] + #[command(rename_rule = "snake_case")] + enum DefaultCommands { + /// Start command. + Start, + /// Show help message. + #[command(hide_aliases)] + Help, + } + + assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/start", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/help", "").unwrap()); + + assert_eq!( + "/start — Start command.\n/help — Show help message.", + DefaultCommands::descriptions().to_string() + ); +} + +#[test] +#[cfg(feature = "macros")] +fn hide_aliases_with_alias() { + #[derive(BotCommands, Debug, PartialEq)] + #[command(rename_rule = "snake_case")] + enum DefaultCommands { + /// Start. + #[command(alias = "s")] + Start, + /// Help. + #[command(alias = "h", hide_aliases)] + Help, + } + + assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/start", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/help", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/h", "").unwrap()); + + assert_eq!("/start, /s — Start.\n/help — Help.", DefaultCommands::descriptions().to_string()); +} + +#[test] +#[cfg(feature = "macros")] +fn hide_command_with_aliases() { + #[derive(BotCommands, Debug, PartialEq)] + #[command(rename_rule = "snake_case")] + enum DefaultCommands { + /// Start. + #[command(alias = "s", hide)] + Start, + /// Help. + #[command(alias = "h")] + Help, + } + + assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/start", "").unwrap()); + assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/s", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/help", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/h", "").unwrap()); + + assert_eq!("/help, /h — Help.", DefaultCommands::descriptions().to_string()); +} + +#[test] +#[cfg(feature = "macros")] +fn hide_aliases_with_aliases() { + #[derive(BotCommands, Debug, PartialEq)] + #[command(rename_rule = "snake_case")] + enum DefaultCommands { + #[command(aliases = ["s", "старт"])] + Start, + #[command(aliases = ["h", "помощь"], hide_aliases)] + Help, + } + + assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/start", "").unwrap()); + assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/s", "").unwrap()); + assert_eq!(DefaultCommands::Start, DefaultCommands::parse("/старт", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/help", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/h", "").unwrap()); + assert_eq!(DefaultCommands::Help, DefaultCommands::parse("/помощь", "").unwrap()); + + assert_eq!("/start, /s, /старт\n/help", DefaultCommands::descriptions().to_string()); +} + #[test] #[cfg(feature = "macros")] fn custom_result() { From 019f9e9247f736eff7d37aa463aa3998022892ec Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Mon, 25 Sep 2023 16:26:05 +0300 Subject: [PATCH 15/19] Update changelogs for #937 --- crates/teloxide-macros/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/teloxide-macros/CHANGELOG.md b/crates/teloxide-macros/CHANGELOG.md index e91ac50d..1f464682 100644 --- a/crates/teloxide-macros/CHANGELOG.md +++ b/crates/teloxide-macros/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Now you can use `#[command(command_separator="sep")]` (default is a whitespace character) to set the separator between command and its arguments ([issue #897](https://github.com/teloxide/teloxide/issues/897)) - Now you can use `/// doc comment` for the command help message ([PR #861](https://github.com/teloxide/teloxide/pull/861)). - Now you can use `#[command(hide)]` to hide a command from the help message ([PR #862](https://github.com/teloxide/teloxide/pull/862)) +- `#[command(alias = "...")]` and `#[command(aliases = "...")]` to specify command aliases ([PR #937](https://github.com/teloxide/teloxide/pull/937)) +- `#[command(hide_aliases)]` to hide aliases from the help message ([PR #937](https://github.com/teloxide/teloxide/pull/937)) ### Fixed From 67670e19d34c5730f244cb77329fc58d41f1be37 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Mon, 25 Sep 2023 21:09:59 +0300 Subject: [PATCH 16/19] Add `alias`, `aliases` and `hide_aliases` to docs --- crates/teloxide/src/utils/command.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/teloxide/src/utils/command.rs b/crates/teloxide/src/utils/command.rs index 50e653f4..32aba462 100644 --- a/crates/teloxide/src/utils/command.rs +++ b/crates/teloxide/src/utils/command.rs @@ -202,6 +202,15 @@ pub use teloxide_macros::BotCommands; /// 5. `#[command(hide)]` /// Hide a command from the help message. It will still be parsed. /// +/// 6. `#[command(alias = "alias")]` +/// Add an alias to a command. It will be shown in the help message. +/// +/// 7. `#[command(aliases = ["alias1", "alias2"])]` +/// Add multiple aliases to a command. They will be shown in the help message. +/// +/// 8. `#[command(hide_aliases)]` +/// Hide all aliases of a command from the help message. +/// /// ## Example /// ``` /// # #[cfg(feature = "macros")] { From 232c9c7d773aa43d414064f3eb43ade7e2a9e652 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Mon, 25 Sep 2023 21:12:27 +0300 Subject: [PATCH 17/19] Add `alias`, `aliases` and `hide_aliases` to examples --- crates/teloxide/examples/command.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/teloxide/examples/command.rs b/crates/teloxide/examples/command.rs index c1dc2d94..1a460b8a 100644 --- a/crates/teloxide/examples/command.rs +++ b/crates/teloxide/examples/command.rs @@ -15,11 +15,13 @@ async fn main() { #[command(rename_rule = "lowercase")] enum Command { /// Display this text. + #[command(aliases = ["h", "?"])] Help, /// Handle a username. + #[command(alias = "u")] Username(String), /// Handle a username and an age. - #[command(parse_with = "split")] + #[command(parse_with = "split", alias = "ua", hide_aliases)] UsernameAndAge { username: String, age: u8 }, } From 08912042af6749e8baf73885d7ae2fabe5424225 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Sun, 7 Jan 2024 11:07:04 +0300 Subject: [PATCH 18/19] Update triagebot configuration - Add the `[review-requested]` table to add and remove status labels when requesting a review - Add `contributing_url` to `[assign]` table --- triagebot.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/triagebot.toml b/triagebot.toml index c5a1e994..3ff64fb3 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1,6 +1,6 @@ [assign] warn_non_default_branch = true -# contributing_url = "https://rustc-dev-guide.rust-lang.org/contributing.html" # FIXME: configure +contributing_url = "https://github.com/teloxide/teloxide/blob/master/CONTRIBUTING.md" [assign.adhoc_groups] # This is a special group that will be used if none of the `owners` entries matches. @@ -57,4 +57,10 @@ reviewed_label = "S-waiting-on-author" # These labels are removed when a review is submitted. review_labels = ["S-waiting-on-review"] +[review-requested] +# Those labels are removed when PR author requests a review from an assignee +remove_labels = ["S-waiting-on-author"] +# Those labels are added when PR author requests a review from an assignee +add_labels = ["S-waiting-on-review"] + [shortcut] From 93a2711476495d8243bb50d266f60c311a6bc054 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Sun, 7 Jan 2024 11:18:05 +0300 Subject: [PATCH 19/19] Update license year to `2019-2024` --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 8e5427af..0e2c4063 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2022 teloxide +Copyright (c) 2019-2024 teloxide Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal