From b91c79e524b9f58fdde8ce4a1ef4d856005b84b4 Mon Sep 17 00:00:00 2001 From: puh Date: Sat, 4 Feb 2023 15:04:20 +0300 Subject: [PATCH 01/33] fix split parser for tuple struct with len <2 --- crates/teloxide-macros/src/fields_parse.rs | 8 +++--- crates/teloxide/tests/command.rs | 30 +++++++++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/crates/teloxide-macros/src/fields_parse.rs b/crates/teloxide-macros/src/fields_parse.rs index 5d9d320a..b06e1d47 100644 --- a/crates/teloxide-macros/src/fields_parse.rs +++ b/crates/teloxide-macros/src/fields_parse.rs @@ -125,8 +125,8 @@ fn parser_with_separator<'a>( })?; <#types>::from_str(s).map_err(|e| teloxide::utils::command::ParseError::IncorrectFormat(e.into()))? - } - ),* + }, + )* ) } }; @@ -139,12 +139,12 @@ fn parser_with_separator<'a>( let res = #res; match splitted.next() { - Some(d) => ::std::result::Result::Err(teloxide::utils::command::ParseError::TooManyArguments { + Some(d) if !s.is_empty() => ::std::result::Result::Err(teloxide::utils::command::ParseError::TooManyArguments { expected: #expected, found: #expected + 1, message: format!("Excess argument: {}", d), }), - None => ::std::result::Result::Ok(res) + _ => ::std::result::Result::Ok(res) } } ) diff --git a/crates/teloxide/tests/command.rs b/crates/teloxide/tests/command.rs index cc4cd8a1..245b4c33 100644 --- a/crates/teloxide/tests/command.rs +++ b/crates/teloxide/tests/command.rs @@ -117,7 +117,7 @@ fn parse_with_split() { assert_eq!( DefaultCommands::Start(10, "hello".to_string()), - DefaultCommands::parse("/start 10 hello", "").unwrap() + DefaultCommands::parse("/start 10 hello", "").unwrap(), ); } @@ -138,6 +138,34 @@ fn parse_with_split2() { ); } +#[test] +#[cfg(feature = "macros")] +fn parse_with_split3() { + #[derive(BotCommands, Debug, PartialEq)] + #[command(rename_rule = "lowercase")] + #[command(parse_with = "split")] + enum DefaultCommands { + Start(u8), + Help, + } + + assert_eq!(DefaultCommands::Start(10), DefaultCommands::parse("/start 10", "").unwrap(),); +} + +#[test] +#[cfg(feature = "macros")] +fn parse_with_split4() { + #[derive(BotCommands, Debug, PartialEq)] + #[command(rename_rule = "lowercase")] + #[command(parse_with = "split")] + enum DefaultCommands { + Start(), + Help, + } + + assert_eq!(DefaultCommands::Start(), DefaultCommands::parse("/start", "").unwrap(),); +} + #[test] #[cfg(feature = "macros")] fn parse_custom_parser() { From cbbfc5b3d1dfcec06fbafabb6d57d01bfe772379 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 31 Jan 2023 13:25:58 +0400 Subject: [PATCH 02/33] Rewrite hacks for reliable `Update` deserialization --- crates/teloxide-core/CHANGELOG.md | 3 + crates/teloxide-core/src/bot.rs | 6 +- crates/teloxide-core/src/net/request.rs | 46 ++++++- crates/teloxide-core/src/types/update.rs | 126 +++++++----------- .../src/update_listeners/webhooks/axum.rs | 12 +- 5 files changed, 108 insertions(+), 85 deletions(-) diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index eab2daed..dadb1ec3 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -10,8 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `Update::user` now handles channel posts, chat member changes and chat join request updates correctly ([#835][pr835]) +- In cases when `teloxide` can't deserialize an update, error now includes the full json value ([#826][pr826]) + [pr835]: https://github.com/teloxide/teloxide/pull/835 +[pr826]: https://github.com/teloxide/teloxide/pull/826 ## 0.9.0 - 2023-01-17 diff --git a/crates/teloxide-core/src/bot.rs b/crates/teloxide-core/src/bot.rs index deea3566..65f2770c 100644 --- a/crates/teloxide-core/src/bot.rs +++ b/crates/teloxide-core/src/bot.rs @@ -206,7 +206,7 @@ impl Bot { ) -> impl Future> + 'static where P: Payload + Serialize, - P::Output: DeserializeOwned, + P::Output: DeserializeOwned + 'static, { let client = self.client.clone(); let token = Arc::clone(&self.token); @@ -237,7 +237,7 @@ impl Bot { ) -> impl Future> where P: MultipartPayload + Serialize, - P::Output: DeserializeOwned, + P::Output: DeserializeOwned + 'static, { let client = self.client.clone(); let token = Arc::clone(&self.token); @@ -267,7 +267,7 @@ impl Bot { ) -> impl Future> where P: MultipartPayload + Serialize, - P::Output: DeserializeOwned, + P::Output: DeserializeOwned + 'static, { let client = self.client.clone(); let token = Arc::clone(&self.token); diff --git a/crates/teloxide-core/src/net/request.rs b/crates/teloxide-core/src/net/request.rs index 7fa345ec..7e4ca1bb 100644 --- a/crates/teloxide-core/src/net/request.rs +++ b/crates/teloxide-core/src/net/request.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{any::TypeId, time::Duration}; use reqwest::{ header::{HeaderValue, CONTENT_TYPE}, @@ -19,7 +19,7 @@ pub async fn request_multipart( _timeout_hint: Option, ) -> ResponseResult where - T: DeserializeOwned, + T: DeserializeOwned + 'static, { // Workaround for [#460] // @@ -58,7 +58,7 @@ pub async fn request_json( _timeout_hint: Option, ) -> ResponseResult where - T: DeserializeOwned, + T: DeserializeOwned + 'static, { // Workaround for [#460] // @@ -91,7 +91,7 @@ where async fn process_response(response: Response) -> ResponseResult where - T: DeserializeOwned, + T: DeserializeOwned + 'static, { if response.status().is_server_error() { tokio::time::sleep(DELAY_ON_SERVER_ERROR).await; @@ -100,6 +100,44 @@ where let text = response.text().await?; serde_json::from_str::>(&text) + .map(|mut response| { + use crate::types::{Update, UpdateKind}; + use std::any::Any; + + // HACK: Fill-in error information into `UpdateKind::Error`. + // + // Why? Well, we need `Update` deserialization to be reliable, + // even if Telegram breaks something in their Bot API, we want + // 1. Deserialization to """succeed""" + // 2. Get the `update.id` + // + // Both of these points are required for `get_updates(...) -> Vec` + // to behave well after Telegram introduces updates that we can't parse. + // (1.) makes it so only some of the updates in a butch need to be skipped + // (otherwise serde'll stop on the first error). (2.) allows us to issue + // the next `get_updates` call with the right offset, even if the last + // update in the batch didn't deserialize well. + // + // serde's interface doesn't allows us to implement `Deserialize` in such + // a way, that we could keep the data we couldn't parse, so our + // `Deserialize` impl for `UpdateKind` just returns + // `UpdateKind::Error(/* some empty-ish value */)`. Here, through some + // terrible hacks and downcasting, we fill-in the data we couldn't parse + // so that our users can make actionable bug reports. + if TypeId::of::() == TypeId::of::() { + if let TelegramResponse::Ok { response, .. } = &mut response { + if let Some(update) = + (response as &mut T as &mut dyn Any).downcast_mut::() + { + if let UpdateKind::Error(value) = &mut update.kind { + *value = serde_json::from_str(&text).unwrap_or_default(); + } + } + } + } + + response + }) .map_err(|source| RequestError::InvalidJson { source, raw: text.into() })? .into() } diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index 7cd59ff6..24d92778 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -156,7 +156,10 @@ pub enum UpdateKind { /// An error that happened during deserialization. /// /// This allows `teloxide` to continue working even if telegram adds a new - /// kind of updates. + /// kinds of updates. + /// + /// **Note that deserialize implementation always returns an empty value**, + /// teloxide fills in the data when doing deserialization. Error(Value), } @@ -182,94 +185,63 @@ impl<'de> Deserialize<'de> for UpdateKind { // Try to deserialize a borrowed-str key, or else try deserializing an owned // string key - let k = map.next_key::<&str>().or_else(|_| { + let key = map.next_key::<&str>().or_else(|_| { map.next_key::().map(|k| { tmp = k; tmp.as_deref() }) }); - if let Ok(Some(k)) = k { - let res = match k { - "message" => { - map.next_value::().map(UpdateKind::Message).map_err(|_| false) + let this = key + .ok() + .flatten() + .and_then(|key| match key { + "message" => map.next_value::().ok().map(UpdateKind::Message), + "edited_message" => { + map.next_value::().ok().map(UpdateKind::EditedMessage) + } + "channel_post" => { + map.next_value::().ok().map(UpdateKind::ChannelPost) + } + "edited_channel_post" => { + map.next_value::().ok().map(UpdateKind::EditedChannelPost) + } + "inline_query" => { + map.next_value::().ok().map(UpdateKind::InlineQuery) } - "edited_message" => map - .next_value::() - .map(UpdateKind::EditedMessage) - .map_err(|_| false), - "channel_post" => map - .next_value::() - .map(UpdateKind::ChannelPost) - .map_err(|_| false), - "edited_channel_post" => map - .next_value::() - .map(UpdateKind::EditedChannelPost) - .map_err(|_| false), - "inline_query" => map - .next_value::() - .map(UpdateKind::InlineQuery) - .map_err(|_| false), "chosen_inline_result" => map .next_value::() - .map(UpdateKind::ChosenInlineResult) - .map_err(|_| false), - "callback_query" => map - .next_value::() - .map(UpdateKind::CallbackQuery) - .map_err(|_| false), - "shipping_query" => map - .next_value::() - .map(UpdateKind::ShippingQuery) - .map_err(|_| false), + .ok() + .map(UpdateKind::ChosenInlineResult), + "callback_query" => { + map.next_value::().ok().map(UpdateKind::CallbackQuery) + } + "shipping_query" => { + map.next_value::().ok().map(UpdateKind::ShippingQuery) + } "pre_checkout_query" => map .next_value::() - .map(UpdateKind::PreCheckoutQuery) - .map_err(|_| false), - "poll" => map.next_value::().map(UpdateKind::Poll).map_err(|_| false), - "poll_answer" => map - .next_value::() - .map(UpdateKind::PollAnswer) - .map_err(|_| false), - "my_chat_member" => map - .next_value::() - .map(UpdateKind::MyChatMember) - .map_err(|_| false), - "chat_member" => map - .next_value::() - .map(UpdateKind::ChatMember) - .map_err(|_| false), + .ok() + .map(UpdateKind::PreCheckoutQuery), + "poll" => map.next_value::().ok().map(UpdateKind::Poll), + "poll_answer" => { + map.next_value::().ok().map(UpdateKind::PollAnswer) + } + "my_chat_member" => { + map.next_value::().ok().map(UpdateKind::MyChatMember) + } + "chat_member" => { + map.next_value::().ok().map(UpdateKind::ChatMember) + } "chat_join_request" => map .next_value::() - .map(UpdateKind::ChatJoinRequest) - .map_err(|_| false), + .ok() + .map(UpdateKind::ChatJoinRequest), + _ => Some(empty_error()), + }) + .unwrap_or_else(empty_error); - _ => Err(true), - }; - - let value_available = match res { - Ok(ok) => return Ok(ok), - Err(e) => e, - }; - - let mut value = serde_json::Map::new(); - value.insert( - k.to_owned(), - if value_available { - map.next_value::().unwrap_or(Value::Null) - } else { - Value::Null - }, - ); - - while let Ok(Some((k, v))) = map.next_entry::<_, Value>() { - value.insert(k, v); - } - - return Ok(UpdateKind::Error(Value::Object(value))); - } - - Ok(UpdateKind::Error(Value::Object(<_>::default()))) + Ok(this) } } @@ -319,6 +291,10 @@ impl Serialize for UpdateKind { } } +fn empty_error() -> UpdateKind { + UpdateKind::Error(Value::Object(<_>::default())) +} + #[cfg(test)] mod test { use crate::types::{ diff --git a/crates/teloxide/src/update_listeners/webhooks/axum.rs b/crates/teloxide/src/update_listeners/webhooks/axum.rs index ba2c930f..2a915a55 100644 --- a/crates/teloxide/src/update_listeners/webhooks/axum.rs +++ b/crates/teloxide/src/update_listeners/webhooks/axum.rs @@ -9,7 +9,7 @@ use tokio::sync::mpsc; use crate::{ requests::Requester, stop::StopFlag, - types::Update, + types::{Update, UpdateKind}, update_listeners::{webhooks::Options, UpdateListener}, }; @@ -186,8 +186,14 @@ pub fn axum_no_setup( Some(tx) => tx, }; - match serde_json::from_str(&input) { - Ok(update) => { + match serde_json::from_str::(&input) { + Ok(mut update) => { + // See HACK comment in + // `teloxide_core::net::request::process_response::{closure#0}` + if let UpdateKind::Error(value) = &mut update.kind { + *value = serde_json::from_str(&input).unwrap_or_default(); + } + tx.send(Ok(update)).expect("Cannot send an incoming update from the webhook") } Err(error) => { From 2221116d9c7639b55542c5eaa262bf5380a48c3d Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 1 Feb 2023 17:11:07 +0400 Subject: [PATCH 03/33] Actually provide meaningful errors --- crates/teloxide-core/src/net/request.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/teloxide-core/src/net/request.rs b/crates/teloxide-core/src/net/request.rs index 7e4ca1bb..6c0580fe 100644 --- a/crates/teloxide-core/src/net/request.rs +++ b/crates/teloxide-core/src/net/request.rs @@ -102,7 +102,7 @@ where serde_json::from_str::>(&text) .map(|mut response| { use crate::types::{Update, UpdateKind}; - use std::any::Any; + use std::{any::Any, iter::zip}; // HACK: Fill-in error information into `UpdateKind::Error`. // @@ -124,13 +124,24 @@ where // `UpdateKind::Error(/* some empty-ish value */)`. Here, through some // terrible hacks and downcasting, we fill-in the data we couldn't parse // so that our users can make actionable bug reports. - if TypeId::of::() == TypeId::of::() { + // + // We specifically handle `Vec` here, because that's the return + // type of the only method that returns updates. + if TypeId::of::() == TypeId::of::>() { if let TelegramResponse::Ok { response, .. } = &mut response { - if let Some(update) = - (response as &mut T as &mut dyn Any).downcast_mut::() + if let Some(updates) = + (response as &mut T as &mut dyn Any).downcast_mut::>() { - if let UpdateKind::Error(value) = &mut update.kind { - *value = serde_json::from_str(&text).unwrap_or_default(); + if updates.iter().any(|u| matches!(u.kind, UpdateKind::Error(_))) { + let re_parsed = serde_json::from_str(&text); + + if let Ok(TelegramResponse::Ok { response: values, .. }) = re_parsed { + for (update, value) in zip::<_, Vec<_>>(updates, values) { + if let UpdateKind::Error(dest) = &mut update.kind { + *dest = value; + } + } + } } } } From 74e32a725da8067b6daca88451023c0cab5c1961 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 6 Feb 2023 12:17:55 +0400 Subject: [PATCH 04/33] Add tests for response processing --- crates/teloxide-core/src/errors.rs | 1 + crates/teloxide-core/src/net/request.rs | 120 ++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/crates/teloxide-core/src/errors.rs b/crates/teloxide-core/src/errors.rs index 41acffdf..45be0f70 100644 --- a/crates/teloxide-core/src/errors.rs +++ b/crates/teloxide-core/src/errors.rs @@ -17,6 +17,7 @@ pub enum RequestError { /// The group has been migrated to a supergroup with the specified /// identifier. #[error("The group has been migrated to a supergroup with ID #{0}")] + // FIXME: change to `ChatId` :| MigrateToChatId(i64), /// In case of exceeding flood control, the number of seconds left to wait diff --git a/crates/teloxide-core/src/net/request.rs b/crates/teloxide-core/src/net/request.rs index 6c0580fe..b990e588 100644 --- a/crates/teloxide-core/src/net/request.rs +++ b/crates/teloxide-core/src/net/request.rs @@ -99,6 +99,13 @@ where let text = response.text().await?; + deserialize_response(text) +} + +fn deserialize_response(text: String) -> Result +where + T: DeserializeOwned + 'static, +{ serde_json::from_str::>(&text) .map(|mut response| { use crate::types::{Update, UpdateKind}; @@ -152,3 +159,116 @@ where .map_err(|source| RequestError::InvalidJson { source, raw: text.into() })? .into() } + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use cool_asserts::assert_matches; + + use crate::{ + net::request::deserialize_response, + types::{True, Update, UpdateKind}, + ApiError, RequestError, + }; + + #[test] + fn smoke_ok() { + let json = r#"{"ok":true,"result":true}"#.to_owned(); + + let res = deserialize_response::(json); + assert_matches!(res, Ok(True)); + } + + #[test] + fn smoke_err() { + let json = + r#"{"ok":false,"description":"Forbidden: bot was blocked by the user"}"#.to_owned(); + + let res = deserialize_response::(json); + assert_matches!(res, Err(RequestError::Api(ApiError::BotBlocked))); + } + + #[test] + fn migrate() { + let json = r#"{"ok":false,"description":"this string is ignored","parameters":{"migrate_to_chat_id":123456}}"#.to_owned(); + + let res = deserialize_response::(json); + assert_matches!(res, Err(RequestError::MigrateToChatId(123456))); + } + + #[test] + fn retry_after() { + let json = r#"{"ok":false,"description":"this string is ignored","parameters":{"retry_after":123456}}"#.to_owned(); + + let res = deserialize_response::(json); + assert_matches!(res, Err(RequestError::RetryAfter(duration)) if duration == Duration::from_secs(123456)); + } + + #[test] + fn update_ok() { + let json = r#"{ + "ok":true, + "result":[ + { + "update_id":0, + "poll_answer":{ + "poll_id":"POLL_ID", + "user": {"id":42,"is_bot":false,"first_name":"blah"}, + "option_ids": [] + } + } + ] + }"# + .to_owned(); + + let res = deserialize_response::>(json).unwrap(); + assert_matches!(res, [Update { id: 0, kind: UpdateKind::PollAnswer(_) }]); + } + + /// Check that `get_updates` can work with malformed updates. + #[test] + fn update_err() { + let json = r#"{ + "ok":true, + "result":[ + { + "update_id":0, + "poll_answer":{ + "poll_id":"POLL_ID", + "user": {"id":42,"is_bot":false,"first_name":"blah"}, + "option_ids": [] + } + }, + { + "update_id":1, + "something unknown to us":17 + }, + { + "update_id":2, + "poll_answer":{ + "poll_id":"POLL_ID", + "user": {"id":42,"is_bot":false,"first_name":"blah"}, + "option_ids": [3, 4, 8] + } + }, + { + "update_id":3, + "message":{"some fields are missing":true} + } + ] + }"# + .to_owned(); + + let res = deserialize_response::>(json).unwrap(); + assert_matches!( + res, + [ + Update { id: 0, kind: UpdateKind::PollAnswer(_) }, + Update { id: 1, kind: UpdateKind::Error(v) } if v.is_object(), + Update { id: 2, kind: UpdateKind::PollAnswer(_) }, + Update { id: 3, kind: UpdateKind::Error(v) } if v.is_object(), + ] + ); + } +} From d961cf79f40509c7f4730f3d94fa18c95808293b Mon Sep 17 00:00:00 2001 From: puh Date: Tue, 7 Feb 2023 10:17:30 +0300 Subject: [PATCH 05/33] Update changelogs --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b35e27..51150963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Allow `ChatJoinRequest` updates +- Fix `split` parser for tuple variants with len < 2 ([issue #834](https://github.com/teloxide/teloxide/issues/834)) ### Added From 560812dfccf8162705e8e372b52bf5551287f3ed Mon Sep 17 00:00:00 2001 From: puh Date: Tue, 7 Feb 2023 12:55:20 +0300 Subject: [PATCH 06/33] Fix changelogs and TooManyArguments --- CHANGELOG.md | 1 - crates/teloxide-macros/CHANGELOG.md | 4 ++++ crates/teloxide-macros/src/fields_parse.rs | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51150963..a7b35e27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Allow `ChatJoinRequest` updates -- Fix `split` parser for tuple variants with len < 2 ([issue #834](https://github.com/teloxide/teloxide/issues/834)) ### Added diff --git a/crates/teloxide-macros/CHANGELOG.md b/crates/teloxide-macros/CHANGELOG.md index 19edca63..10b1109e 100644 --- a/crates/teloxide-macros/CHANGELOG.md +++ b/crates/teloxide-macros/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +### Fixed + +- Fix `split` parser for tuple variants with len < 2 ([issue #834](https://github.com/teloxide/teloxide/issues/834)) + ## 0.7.1 - 2023-01-17 ### Fixed diff --git a/crates/teloxide-macros/src/fields_parse.rs b/crates/teloxide-macros/src/fields_parse.rs index b06e1d47..896aaaa6 100644 --- a/crates/teloxide-macros/src/fields_parse.rs +++ b/crates/teloxide-macros/src/fields_parse.rs @@ -138,10 +138,13 @@ fn parser_with_separator<'a>( let res = #res; + if !s.is_empty() && splitted.count() { + } + match splitted.next() { Some(d) if !s.is_empty() => ::std::result::Result::Err(teloxide::utils::command::ParseError::TooManyArguments { expected: #expected, - found: #expected + 1, + found: #expected + 1 + splitted.count(), message: format!("Excess argument: {}", d), }), _ => ::std::result::Result::Ok(res) From f5ff145a7ce4aca2c870ad0220c1062dbc5e7257 Mon Sep 17 00:00:00 2001 From: Boyd Kane <33420535+beyarkay@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:50:28 +0200 Subject: [PATCH 07/33] Add docstring to Message::from --- crates/teloxide-core/src/types/message.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index e121ee1f..6b8922cb 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -620,6 +620,7 @@ mod getters { /// [Message]: crate::types::Message /// [telegram docs]: https://core.telegram.org/bots/api#message impl Message { + /// Returns the user who sent the message. #[must_use] pub fn from(&self) -> Option<&User> { match &self.kind { From 650216d3735fec65285c76b5ec1900b9b632586c Mon Sep 17 00:00:00 2001 From: elpekenin Date: Thu, 9 Feb 2023 23:18:18 +0100 Subject: [PATCH 08/33] Fix broken example links --- crates/teloxide/src/dispatching.rs | 4 ++-- crates/teloxide/src/dispatching/dialogue.rs | 2 +- crates/teloxide/src/lib.rs | 2 +- crates/teloxide/src/utils/command.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/teloxide/src/dispatching.rs b/crates/teloxide/src/dispatching.rs index d456aabc..bb1e9d94 100644 --- a/crates/teloxide/src/dispatching.rs +++ b/crates/teloxide/src/dispatching.rs @@ -207,12 +207,12 @@ //! [default]: DispatcherBuilder#method.default_handler //! [error]: DispatcherBuilder#method.error_handler //! [dialogues]: dialogue -//! [`examples/purchase.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs +//! [`examples/purchase.rs`]: https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/purchase.rs //! [`Update::filter_message`]: crate::types::Update::filter_message //! [`Update::filter_callback_query`]: crate::types::Update::filter_callback_query //! [chain of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern //! [dependency injection (DI)]: https://en.wikipedia.org/wiki/Dependency_injection -//! [`examples/dispatching_features.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/dispatching_features.rs +//! [`examples/dispatching_features.rs`]: https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/dispatching_features.rs //! [`Update`]: crate::types::Update pub mod dialogue; diff --git a/crates/teloxide/src/dispatching/dialogue.rs b/crates/teloxide/src/dispatching/dialogue.rs index a577b999..b6135cc4 100644 --- a/crates/teloxide/src/dispatching/dialogue.rs +++ b/crates/teloxide/src/dispatching/dialogue.rs @@ -91,7 +91,7 @@ //! } //! ``` //! -//! [`examples/dialogue.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/dialogue.rs +//! [`examples/dialogue.rs`]: https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/dialogue.rs #[cfg(feature = "redis-storage")] pub use self::{RedisStorage, RedisStorageError}; diff --git a/crates/teloxide/src/lib.rs b/crates/teloxide/src/lib.rs index 1e86d521..ffcd8f65 100644 --- a/crates/teloxide/src/lib.rs +++ b/crates/teloxide/src/lib.rs @@ -4,7 +4,7 @@ //! //! For a high-level overview, see [our GitHub repository](https://github.com/teloxide/teloxide). //! -//! [[`examples/throw_dice.rs`](https://github.com/teloxide/teloxide/blob/master/examples/throw_dice.rs)] +//! [[`examples/throw_dice.rs`](https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/throw_dice.rs)] //! ```no_run //! # #[cfg(feature = "ctrlc_handler")] //! use teloxide::prelude::*; diff --git a/crates/teloxide/src/utils/command.rs b/crates/teloxide/src/utils/command.rs index 2626d28c..763f1f7a 100644 --- a/crates/teloxide/src/utils/command.rs +++ b/crates/teloxide/src/utils/command.rs @@ -45,9 +45,9 @@ //! assert_eq!(args, vec!["3", "hours"]); //! ``` //! -//! See [examples/admin_bot] as a more complicated examples. +//! See [examples/admin] as a more complicated examples. //! -//! [examples/admin_bot]: https://github.com/teloxide/teloxide/blob/master/examples/admin_bot/ +//! [examples/admin]: https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/admin.rs use core::fmt; use std::{ From 7fc398d418d6a1750cf795daddcb2d136bfd0641 Mon Sep 17 00:00:00 2001 From: elpekenin Date: Fri, 10 Feb 2023 12:04:28 +0100 Subject: [PATCH 09/33] Log changes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b35e27..6f6c9969 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Allow `ChatJoinRequest` updates +- Fix broken links to examples in the documentation ### Added From 83acf7d9481e1958bf939929f33946ff4e03dd4d Mon Sep 17 00:00:00 2001 From: elpekenin Date: Fri, 10 Feb 2023 12:05:38 +0100 Subject: [PATCH 10/33] Text according to review, hadnt seen it --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f6c9969..b4575c5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Allow `ChatJoinRequest` updates -- Fix broken links to examples in the documentation +- Some example links in documentation ### Added From 626edd713057a84ce29b7adb1d718770739ff335 Mon Sep 17 00:00:00 2001 From: Waffle Maybe Date: Mon, 13 Feb 2023 11:55:16 +0400 Subject: [PATCH 11/33] Run CI in merge queue --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40b4d815..4dd54800 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,7 @@ on: branches: [ master ] pull_request: branches: [ master ] + merge_group: name: Continuous integration From 119e305729ea6329b56b3a1270bdf58757dd261a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 13 Feb 2023 11:49:56 +0400 Subject: [PATCH 12/33] Add `sqlite-storage-rustls` feature --- .github/workflows/ci.yml | 4 ++-- CHANGELOG.md | 1 + crates/teloxide/Cargo.toml | 10 +++++++--- crates/teloxide/src/features.md | 3 ++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40b4d815..72499188 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: features: "--features full" - rust: nightly toolchain: nightly-2022-12-23 - features: "--all-features" + features: "--features full nightly" - rust: msrv toolchain: 1.64.0 features: "--features full" @@ -187,7 +187,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --all-targets --all-features + args: --all-targets --features "full nightly" doc: name: check docs diff --git a/CHANGELOG.md b/CHANGELOG.md index b4575c5f..ffe3154e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `Update::filter_chat_join_request` +- `sqlite-storage-rustls` feature, that allows using sqlite storage without `native-tls` ## 0.12.0 - 2023-01-17 diff --git a/crates/teloxide/Cargo.toml b/crates/teloxide/Cargo.toml index e4034f62..b38e5418 100644 --- a/crates/teloxide/Cargo.toml +++ b/crates/teloxide/Cargo.toml @@ -22,7 +22,9 @@ default = ["native-tls", "ctrlc_handler", "teloxide-core/default", "auto-send"] webhooks = ["rand"] webhooks-axum = ["webhooks", "axum", "tower", "tower-http"] -sqlite-storage = ["sqlx"] +# FIXME: rename `sqlite-storage` -> `sqlite-storage-nativetls` +sqlite-storage = ["sqlx", "sqlx/runtime-tokio-native-tls", "native-tls"] +sqlite-storage-rustls = ["sqlx", "sqlx/runtime-tokio-rustls", "rustls"] redis-storage = ["redis"] cbor-serializer = ["serde_cbor"] bincode-serializer = ["bincode"] @@ -35,7 +37,7 @@ native-tls = ["teloxide-core/native-tls"] rustls = ["teloxide-core/rustls"] auto-send = ["teloxide-core/auto_send"] throttle = ["teloxide-core/throttle"] -cache-me = ["teloxide-core/cache_me"] +cache-me = ["teloxide-core/cache_me"] # FIXME: why teloxide and core use - _ differently? trace-adaptor = ["teloxide-core/trace_adaptor"] erased = ["teloxide-core/erased"] @@ -44,8 +46,11 @@ erased = ["teloxide-core/erased"] nightly = ["teloxide-core/nightly"] full = [ + "webhooks", "webhooks-axum", "sqlite-storage", + # "sqlite-storage-rustls" is explicitly ommited here, + # since it conflicts with "sqlite-storage" "redis-storage", "cbor-serializer", "bincode-serializer", @@ -91,7 +96,6 @@ serde_with_macros = "1.4" aquamarine = "0.1.11" sqlx = { version = "0.6", optional = true, default-features = false, features = [ - "runtime-tokio-native-tls", "macros", "sqlite", ] } diff --git a/crates/teloxide/src/features.md b/crates/teloxide/src/features.md index fb1680e9..a871ee1b 100644 --- a/crates/teloxide/src/features.md +++ b/crates/teloxide/src/features.md @@ -16,7 +16,8 @@ | `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). | | `rustls` | Enables the [`rustls`] TLS implementation. | | `redis-storage` | Enables the [Redis] storage support for dialogues. | -| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. | +| `sqlite-storage` | Enables the [Sqlite] storage support for dialogues (depends on `native-tls`). | +| `sqlite-storage-rustls` | Enables the [Sqlite] storage support for dialogues (depends on `rustls`, conflicts with `sqlite-storage`). | | `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | | `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | From ef49094d2841ab38fcaa6b096d6364813b9605b1 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 13 Feb 2023 21:42:13 +0400 Subject: [PATCH 13/33] Remove a useless allocation when attaching files --- crates/teloxide-core/src/types/input_file.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/teloxide-core/src/types/input_file.rs b/crates/teloxide-core/src/types/input_file.rs index 631cac82..00948f8b 100644 --- a/crates/teloxide-core/src/types/input_file.rs +++ b/crates/teloxide-core/src/types/input_file.rs @@ -123,8 +123,8 @@ impl InputFile { /// /// This is used to coordinate with `attach://`. pub(crate) fn id(&self) -> &str { - // FIXME: remove extra alloc - self.id.get_or_init(|| uuid::Uuid::new_v4().to_string().into()) + let random = || Arc::from(&*uuid::Uuid::new_v4().as_simple().encode_lower(&mut [0; 32])); + self.id.get_or_init(random) } /// Returns `true` if this file needs an attachment i.e. it's not a file_id From c29aac96a7056924daa88bd3b138bc326eb59b6b Mon Sep 17 00:00:00 2001 From: puh Date: Wed, 15 Feb 2023 03:35:03 +0300 Subject: [PATCH 14/33] cleanup --- crates/teloxide-macros/src/fields_parse.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/teloxide-macros/src/fields_parse.rs b/crates/teloxide-macros/src/fields_parse.rs index 896aaaa6..d93de04f 100644 --- a/crates/teloxide-macros/src/fields_parse.rs +++ b/crates/teloxide-macros/src/fields_parse.rs @@ -138,9 +138,6 @@ fn parser_with_separator<'a>( let res = #res; - if !s.is_empty() && splitted.count() { - } - match splitted.next() { Some(d) if !s.is_empty() => ::std::result::Result::Err(teloxide::utils::command::ParseError::TooManyArguments { expected: #expected, From b7c34f8bdbcb92beba58b07d52d1d1e0bd370113 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 1 Feb 2023 16:53:56 +0400 Subject: [PATCH 15/33] Fix & test rgb deserialization --- crates/teloxide-core/src/types.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 420e8fee..fc7a47b2 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -461,6 +461,13 @@ pub(crate) mod serde_rgb { { Ok(from_u32(v)) } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + self.visit_u32(v.try_into().map_err(|_| E::custom("rgb value doesn't fit u32"))?) + } } d.deserialize_u32(V) } @@ -481,5 +488,16 @@ pub(crate) mod serde_rgb { } #[test] - fn json() {} + fn json() { + #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + struct Struct { + #[serde(with = "self")] + color: [u8; 3], + } + + let json = format!("{}", 0x00AABBCC); + let Struct { color } = serde_json::from_str(&json).unwrap(); + + assert_eq!(color, [0xAA, 0xBB, 0xCC]) + } } From 6a2f5d7f2fde73f8c6e16790f9c75f9070273175 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 1 Feb 2023 16:57:10 +0400 Subject: [PATCH 16/33] Correctly deserialize `Message::thread_id` --- crates/teloxide-core/src/types/message.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 6b8922cb..64afdea1 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -26,6 +26,7 @@ pub struct Message { /// Unique identifier of a message thread to which the message belongs; for /// supergroups only. // FIXME: MessageThreadId or such + #[serde(rename = "message_thread_id")] pub thread_id: Option, /// Date the message was sent in Unix time. From 0b83007954c49b5d206f32359f6d89e9f9337a23 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 1 Feb 2023 16:57:42 +0400 Subject: [PATCH 17/33] Add some deserialization tests for topic-related types/values --- crates/teloxide-core/src/types.rs | 2 +- .../src/types/forum_topic_created.rs | 17 ++++++++++ crates/teloxide-core/src/types/message.rs | 34 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index fc7a47b2..8737244a 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -496,7 +496,7 @@ pub(crate) mod serde_rgb { } let json = format!("{}", 0x00AABBCC); - let Struct { color } = serde_json::from_str(&json).unwrap(); + let Struct { color } = serde_json::from_str(&json).unwrap(); assert_eq!(color, [0xAA, 0xBB, 0xCC]) } diff --git a/crates/teloxide-core/src/types/forum_topic_created.rs b/crates/teloxide-core/src/types/forum_topic_created.rs index cf4a91b6..fe209f62 100644 --- a/crates/teloxide-core/src/types/forum_topic_created.rs +++ b/crates/teloxide-core/src/types/forum_topic_created.rs @@ -19,3 +19,20 @@ pub struct ForumTopicCreated { // FIXME: CustomEmojiId pub icon_custom_emoji_id: Option, } + +#[cfg(test)] +mod tests { + use crate::types::ForumTopicCreated; + + #[test] + fn deserialization() { + let json = + r#"{"icon_color":9367192,"icon_custom_emoji_id":"5312536423851630001","name":"???"}"#; + + let event = serde_json::from_str::(json).unwrap(); + + assert_eq!(event.name, "???"); + assert_eq!(event.icon_color, [0x8E, 0xEE, 0x98]); + assert_eq!(event.icon_custom_emoji_id.as_deref(), Some("5312536423851630001")); + } +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 64afdea1..0fcbc1a8 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -117,6 +117,8 @@ pub struct MessageCommon { pub reply_markup: Option, /// `true`, if the message is sent to a forum topic. + // FIXME: `is_topic_message` is included even in service messages, like ForumTopicCreated. + // more this to `Message` #[serde(default)] pub is_topic_message: bool, @@ -1838,4 +1840,36 @@ mod tests { assert!(!entities.is_empty()); assert_eq!(entities[0].kind().clone(), MessageEntityKind::Url); } + + #[test] + fn topic_created() { + let json = r#"{ + "chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"}, + "date":1675229139, + "forum_topic_created":{ + "icon_color":9367192, + "icon_custom_emoji_id":"5312536423851630001", + "name":"???" + }, + "from":{ + "first_name":"вафель'", + "id":1253681278, + "is_bot":false, + "language_code":"en", + "username":"wafflelapkin" + }, + "is_topic_message":true, + "message_id":4, + "message_thread_id":4 + }"#; + + let message: Message = serde_json::from_str(json).unwrap(); + } + + #[test] + fn topic_message() { + let json = r#"{"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},"date":1675229140,"from":{"first_name":"вафель'","id":1253681278,"is_bot":false,"language_code":"en","username":"wafflelapkin"},"is_topic_message":true,"message_id":5,"message_thread_id":4,"reply_to_message":{"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},"date":1675229139,"forum_topic_created":{"icon_color":9367192,"icon_custom_emoji_id":"5312536423851630001","name":"???"},"from":{"first_name":"вафель'","id":1253681278,"is_bot":false,"language_code":"en","username":"wafflelapkin"},"is_topic_message":true,"message_id":4,"message_thread_id":4},"text":"blah"}"#; + + let message: Message = serde_json::from_str(json).unwrap(); + } } From c3a3fc4ee2c4ad8d9d2da5043b6fd9aa6d3dceff Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 1 Feb 2023 17:17:30 +0400 Subject: [PATCH 18/33] Remove unused variables in tests --- crates/teloxide-core/src/types/message.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 0fcbc1a8..706e371b 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -1863,13 +1863,13 @@ mod tests { "message_thread_id":4 }"#; - let message: Message = serde_json::from_str(json).unwrap(); + let _: Message = serde_json::from_str(json).unwrap(); } #[test] fn topic_message() { let json = r#"{"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},"date":1675229140,"from":{"first_name":"вафель'","id":1253681278,"is_bot":false,"language_code":"en","username":"wafflelapkin"},"is_topic_message":true,"message_id":5,"message_thread_id":4,"reply_to_message":{"chat":{"id":-1001847508954,"is_forum":true,"title":"twest","type":"supergroup"},"date":1675229139,"forum_topic_created":{"icon_color":9367192,"icon_custom_emoji_id":"5312536423851630001","name":"???"},"from":{"first_name":"вафель'","id":1253681278,"is_bot":false,"language_code":"en","username":"wafflelapkin"},"is_topic_message":true,"message_id":4,"message_thread_id":4},"text":"blah"}"#; - let message: Message = serde_json::from_str(json).unwrap(); + let _: Message = serde_json::from_str(json).unwrap(); } } From 3c5370548aaf293460bb03c35f04237524212f6a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 2 Feb 2023 12:12:37 +0400 Subject: [PATCH 19/33] fix a test --- crates/teloxide-core/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 8737244a..fd2a428c 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -495,7 +495,7 @@ pub(crate) mod serde_rgb { color: [u8; 3], } - let json = format!("{}", 0x00AABBCC); + let json = format!(r#"{{"color":{}}}"#, 0x00AABBCC); let Struct { color } = serde_json::from_str(&json).unwrap(); assert_eq!(color, [0xAA, 0xBB, 0xCC]) From 6a9183a53b288422af5882fc235f562c0ffb211a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 15 Feb 2023 18:14:28 +0400 Subject: [PATCH 20/33] Update changelog --- crates/teloxide-core/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index dadb1ec3..d42b4067 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -11,10 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Update::user` now handles channel posts, chat member changes and chat join request updates correctly ([#835][pr835]) - In cases when `teloxide` can't deserialize an update, error now includes the full json value ([#826][pr826]) - +- Deserialization of topic messages ([#830][pr830]) [pr835]: https://github.com/teloxide/teloxide/pull/835 [pr826]: https://github.com/teloxide/teloxide/pull/826 +[pr830]: https://github.com/teloxide/teloxide/pull/830 ## 0.9.0 - 2023-01-17 From bb35c2c155528e832a6d8896e7243bc333f2dbbf Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 15 Feb 2023 22:53:08 +0400 Subject: [PATCH 21/33] Update changelog --- crates/teloxide-core/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index d42b4067..4cbae73b 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -17,6 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr826]: https://github.com/teloxide/teloxide/pull/826 [pr830]: https://github.com/teloxide/teloxide/pull/830 +### Added + +- `ApiError::ImageProcessFailed` ([#825][pr825]) + +[pr825]: https://github.com/teloxide/teloxide/pull/825 + ## 0.9.0 - 2023-01-17 ### Changed From a7be39f966d8f4e99f9c643010295b32885069b0 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 15 Feb 2023 23:08:42 +0400 Subject: [PATCH 22/33] Fix version in teloxide core docs --- crates/teloxide-core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/lib.rs b/crates/teloxide-core/src/lib.rs index d76dac3b..bfc94ddf 100644 --- a/crates/teloxide-core/src/lib.rs +++ b/crates/teloxide-core/src/lib.rs @@ -5,7 +5,7 @@ //! asynchronous and built using [`tokio`]. //! //!```toml -//! teloxide_core = "0.8" +//! teloxide_core = "0.9" //! ``` //! _Compiler support: requires rustc 1.64+_. //! From 750d8c3209122c58e2b0e78e5d8e01eb3b66f8ce Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 15 Feb 2023 22:29:52 +0400 Subject: [PATCH 23/33] Release `teloxide-core` v0.9.1 --- crates/teloxide-core/CHANGELOG.md | 2 ++ crates/teloxide-core/Cargo.toml | 2 +- crates/teloxide/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index 4cbae73b..2dda03bf 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +## 0.9.1 - 2023-02-15 + ### Fixed - `Update::user` now handles channel posts, chat member changes and chat join request updates correctly ([#835][pr835]) diff --git a/crates/teloxide-core/Cargo.toml b/crates/teloxide-core/Cargo.toml index f81d1ef3..4df1fc7b 100644 --- a/crates/teloxide-core/Cargo.toml +++ b/crates/teloxide-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide-core" -version = "0.9.0" +version = "0.9.1" description = "Core part of the `teloxide` library - telegram bot API client" rust-version.workspace = true diff --git a/crates/teloxide/Cargo.toml b/crates/teloxide/Cargo.toml index b38e5418..5aadc2b3 100644 --- a/crates/teloxide/Cargo.toml +++ b/crates/teloxide/Cargo.toml @@ -68,7 +68,7 @@ full = [ [dependencies] -teloxide-core = { version = "0.9.0", path = "../teloxide-core", default-features = false } +teloxide-core = { version = "0.9.1", path = "../teloxide-core", default-features = false } teloxide-macros = { version = "0.7.1", path = "../teloxide-macros", optional = true } serde_json = "1.0" From e54acb76ea0a826daa5273bb89877f323c20d767 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 15 Feb 2023 23:48:50 +0400 Subject: [PATCH 24/33] Release `teloxide` `v0.12.1` --- CHANGELOG.md | 6 ++++++ crates/teloxide/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffe3154e..ffd72b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +## 0.12.1 - 2023-02-15 + ### Fixed - Allow `ChatJoinRequest` updates @@ -16,6 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Update::filter_chat_join_request` - `sqlite-storage-rustls` feature, that allows using sqlite storage without `native-tls` +### Changed + +- Updated `teloxide-core` to v0.9.1; see its [changelog](https://github.com/teloxide/teloxide/blob/master/crates/teloxide-core/CHANGELOG.md#091---2023-02-15) for more + ## 0.12.0 - 2023-01-17 ### Changed diff --git a/crates/teloxide/Cargo.toml b/crates/teloxide/Cargo.toml index 5aadc2b3..a0ee5364 100644 --- a/crates/teloxide/Cargo.toml +++ b/crates/teloxide/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide" -version = "0.12.0" +version = "0.12.1" description = "An elegant Telegram bots framework for Rust" rust-version.workspace = true From 0fb3cdd6b4836bdb8c06bcc12a5e89e17a6e1519 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 16 Feb 2023 00:26:48 +0400 Subject: [PATCH 25/33] Fix `docs.rs` build --- CHANGELOG.md | 6 ++++++ crates/teloxide/Cargo.toml | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffd72b41..801294ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +## 0.12.2 - 2023-02-15 + +### Fixed + +- `docs.rs` documentation build + ## 0.12.1 - 2023-02-15 ### Fixed diff --git a/crates/teloxide/Cargo.toml b/crates/teloxide/Cargo.toml index a0ee5364..92703ecd 100644 --- a/crates/teloxide/Cargo.toml +++ b/crates/teloxide/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teloxide" -version = "0.12.1" +version = "0.12.2" description = "An elegant Telegram bots framework for Rust" rust-version.workspace = true @@ -120,7 +120,8 @@ tokio-stream = "0.1" [package.metadata.docs.rs] -all-features = true +# NB: can't use `all-features = true`, because `sqlite-storage` conflicts with `sqlite-storage-rustls` +features = ["full", "nightly"] # FIXME: Add back "-Znormalize-docs" when https://github.com/rust-lang/rust/issues/93703 is fixed rustdoc-args = ["--cfg", "docsrs"] rustc-args = ["--cfg", "dep_docsrs"] From 96c4c83daf400a02177d5e2927b68bb34fbd883a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 13 Feb 2023 21:21:19 +0400 Subject: [PATCH 26/33] Add `ChatPermissions::can_*` helper functions --- crates/teloxide-core/CHANGELOG.md | 6 ++ .../src/types/chat_permissions.rs | 83 ++++++++++++++++--- 2 files changed, 79 insertions(+), 10 deletions(-) diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index 2dda03bf..8e3e02cf 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +### Added + +- `ChatPermission::can_*` helper functions ([#851][pr851]) + +[pr851]: https://github.com/teloxide/teloxide/pull/851 + ## 0.9.1 - 2023-02-15 ### Fixed diff --git a/crates/teloxide-core/src/types/chat_permissions.rs b/crates/teloxide-core/src/types/chat_permissions.rs index 2fe560c4..2a634c35 100644 --- a/crates/teloxide-core/src/types/chat_permissions.rs +++ b/crates/teloxide-core/src/types/chat_permissions.rs @@ -84,7 +84,70 @@ bitflags::bitflags! { } } -// FIXME: add `can_*` methods for convinience +impl ChatPermissions { + /// Checks for [`SEND_MESSAGES`] permission. + /// + /// [`SEND_MESSAGES`]: ChatPermissions::SEND_MESSAGES + pub fn can_send_messages(&self) -> bool { + self.contains(ChatPermissions::SEND_MESSAGES) + } + + /// Checks for [`SEND_MEDIA_MESSAGES`] permission. + /// + /// [`SEND_MEDIA_MESSAGES`]: ChatPermissions::SEND_MEDIA_MESSAGES + pub fn can_send_media_messages(&self) -> bool { + self.contains(ChatPermissions::SEND_MEDIA_MESSAGES) + } + + /// Checks for [`SEND_POLLS`] permission. + /// + /// [`SEND_POLLS`]: ChatPermissions::SEND_POLLS + pub fn can_send_polls(&self) -> bool { + self.contains(ChatPermissions::SEND_POLLS) + } + + /// Checks for [`SEND_OTHER_MESSAGES`] permission. + /// + /// [`SEND_OTHER_MESSAGES`]: ChatPermissions::SEND_OTHER_MESSAGES + pub fn can_send_other_messages(&self) -> bool { + self.contains(ChatPermissions::SEND_OTHER_MESSAGES) + } + + /// Checks for [`ADD_WEB_PAGE_PREVIEWS`] permission. + /// + /// [`ADD_WEB_PAGE_PREVIEWS`]: ChatPermissions::ADD_WEB_PAGE_PREVIEWS + pub fn can_add_web_page_previews(&self) -> bool { + self.contains(ChatPermissions::ADD_WEB_PAGE_PREVIEWS) + } + + /// Checks for [`CHANGE_INFO`] permission. + /// + /// [`CHANGE_INFO`]: ChatPermissions::CHANGE_INFO + pub fn can_change_info(&self) -> bool { + self.contains(ChatPermissions::CHANGE_INFO) + } + + /// Checks for [`INVITE_USERS`] permission. + /// + /// [`INVITE_USERS`]: ChatPermissions::INVITE_USERS + pub fn can_invite_users(&self) -> bool { + self.contains(ChatPermissions::INVITE_USERS) + } + + /// Checks for [`PIN_MESSAGES`] permission. + /// + /// [`PIN_MESSAGES`]: ChatPermissions::PIN_MESSAGES + pub fn can_pin_messages(&self) -> bool { + self.contains(ChatPermissions::PIN_MESSAGES) + } + + /// Checks for [`MANAGE_TOPICS`] permission. + /// + /// [`MANAGE_TOPICS`]: ChatPermissions::MANAGE_TOPICS + pub fn can_manage_topics(&self) -> bool { + self.contains(ChatPermissions::MANAGE_TOPICS) + } +} /// Helper for (de)serialization #[derive(Serialize, Deserialize)] @@ -124,15 +187,15 @@ struct ChatPermissionsRaw { impl From for ChatPermissionsRaw { fn from(this: ChatPermissions) -> Self { Self { - can_send_messages: this.contains(ChatPermissions::SEND_MESSAGES), - can_send_media_messages: this.contains(ChatPermissions::SEND_MEDIA_MESSAGES), - can_send_polls: this.contains(ChatPermissions::SEND_POLLS), - can_send_other_messages: this.contains(ChatPermissions::SEND_OTHER_MESSAGES), - can_add_web_page_previews: this.contains(ChatPermissions::ADD_WEB_PAGE_PREVIEWS), - can_change_info: this.contains(ChatPermissions::CHANGE_INFO), - can_invite_users: this.contains(ChatPermissions::INVITE_USERS), - can_pin_messages: this.contains(ChatPermissions::PIN_MESSAGES), - can_manage_topics: this.contains(ChatPermissions::MANAGE_TOPICS), + can_send_messages: this.can_send_messages(), + can_send_media_messages: this.can_send_media_messages(), + can_send_polls: this.can_send_polls(), + can_send_other_messages: this.can_send_other_messages(), + can_add_web_page_previews: this.can_add_web_page_previews(), + can_change_info: this.can_change_info(), + can_invite_users: this.can_invite_users(), + can_pin_messages: this.can_pin_messages(), + can_manage_topics: this.can_manage_topics(), } } } From a411bff31cffc7bf719975b772ddab17fc589d1b Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 13 Feb 2023 14:08:50 +0400 Subject: [PATCH 27/33] Rename `Update::user` -> `Update::from` --- crates/teloxide-core/src/types/update.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index 24d92778..e63a42f5 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -29,7 +29,7 @@ pub struct Update { } impl Update { - // FIXME: rename user => from, add mentioned_users -> impl Iterator<&User> + // FIXME: add mentioned_users -> impl Iterator<&User> /// Returns the user that performed the action that caused this update, if /// known. @@ -37,7 +37,7 @@ impl Update { /// This is generally the `from` field (except for `PollAnswer` where it's /// `user` and `Poll` with `Error` which don't have such field at all). #[must_use] - pub fn user(&self) -> Option<&User> { + pub fn from(&self) -> Option<&User> { use UpdateKind::*; let from = match &self.kind { @@ -82,6 +82,11 @@ impl Update { Some(chat) } + + #[deprecated(note = "renamed to `from`", since = "0.13.0")] + pub fn user(&self) -> Option<&User> { + self.from() + } } #[derive(Clone, Debug, PartialEq)] From 16c20e371c0e80c14b6e588417d56f9580de3a03 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 13 Feb 2023 14:10:14 +0400 Subject: [PATCH 28/33] Add `Message::video_chat_participants_invited` --- crates/teloxide-core/src/types/message.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 706e371b..0138be1a 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -615,7 +615,7 @@ mod getters { MessageGroupChatCreated, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned, MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated, - PhotoSize, True, User, + MessageVideoChatParticipantsInvited, PhotoSize, True, User, }; /// Getters for [Message] fields from [telegram docs]. @@ -1178,6 +1178,18 @@ mod getters { } } + #[must_use] + pub fn video_chat_participants_invited( + &self, + ) -> Option<&types::VideoChatParticipantsInvited> { + match &self.kind { + VideoChatParticipantsInvited(MessageVideoChatParticipantsInvited { + video_chat_participants_invited, + }) => Some(video_chat_participants_invited), + _ => None, + } + } + #[must_use] pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> { match &self.kind { From 35471a4a0be367d183f4b853d68f8c06685f9e98 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 13 Feb 2023 14:11:06 +0400 Subject: [PATCH 29/33] Add `mentioned_users` to update types where it makes sense --- crates/teloxide-core/src/lib.rs | 1 + .../teloxide-core/src/types/callback_query.rs | 14 +++++ crates/teloxide-core/src/types/chat.rs | 19 ++++++- .../src/types/chat_join_request.rs | 12 ++++ .../src/types/chat_member_updated.rs | 18 ++++++ crates/teloxide-core/src/types/game.rs | 16 +++++- crates/teloxide-core/src/types/message.rs | 36 ++++++++++++ crates/teloxide-core/src/types/poll.rs | 16 +++++- crates/teloxide-core/src/types/update.rs | 42 ++++++++++++++ crates/teloxide-core/src/util.rs | 56 +++++++++++++++++++ 10 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 crates/teloxide-core/src/util.rs diff --git a/crates/teloxide-core/src/lib.rs b/crates/teloxide-core/src/lib.rs index bfc94ddf..6127ad54 100644 --- a/crates/teloxide-core/src/lib.rs +++ b/crates/teloxide-core/src/lib.rs @@ -117,6 +117,7 @@ mod bot; // implementation details mod serde_multipart; +mod util; #[cfg(test)] mod codegen; diff --git a/crates/teloxide-core/src/types/callback_query.rs b/crates/teloxide-core/src/types/callback_query.rs index 5cfa0fa8..2d1f5362 100644 --- a/crates/teloxide-core/src/types/callback_query.rs +++ b/crates/teloxide-core/src/types/callback_query.rs @@ -49,6 +49,20 @@ pub struct CallbackQuery { pub game_short_name: Option, } +impl CallbackQuery { + /// Returns all users that are "contained" in this `CallbackQuery` + /// structure. + /// + /// This might be useful to track information about users. + /// Note that this function can return duplicate users. + pub fn mentioned_users(&self) -> impl Iterator { + use crate::util::flatten; + use std::iter::once; + + once(&self.from).chain(flatten(self.message.as_ref().map(Message::mentioned_users))) + } +} + #[cfg(test)] mod tests { use crate::types::UserId; diff --git a/crates/teloxide-core/src/types/chat.rs b/crates/teloxide-core/src/types/chat.rs index 38da326b..8dbb4d8d 100644 --- a/crates/teloxide-core/src/types/chat.rs +++ b/crates/teloxide-core/src/types/chat.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::{ChatId, ChatLocation, ChatPermissions, ChatPhoto, Message, True}; +use crate::types::{ChatId, ChatLocation, ChatPermissions, ChatPhoto, Message, True, User}; /// This object represents a chat. /// @@ -493,6 +493,23 @@ impl Chat { _ => None, } } + + /// Returns all users that are "contained" in this `Chat` + /// structure. + /// + /// This might be useful to track information about users. + /// + /// Note that this function can return duplicate users. + pub fn mentioned_users(&self) -> impl Iterator { + crate::util::flatten(self.pinned_message.as_ref().map(|m| m.mentioned_users())) + } + + /// `{Message, Chat}::mentioned_users` are mutually recursive, as such we + /// can't use `->impl Iterator` everywhere, as it would make an infinite + /// type. So we need to box somewhere. + pub(crate) fn mentioned_users_rec(&self) -> impl Iterator { + crate::util::flatten(self.pinned_message.as_ref().map(|m| m.mentioned_users_rec())) + } } mod serde_helper { diff --git a/crates/teloxide-core/src/types/chat_join_request.rs b/crates/teloxide-core/src/types/chat_join_request.rs index ec5210d7..455d922e 100644 --- a/crates/teloxide-core/src/types/chat_join_request.rs +++ b/crates/teloxide-core/src/types/chat_join_request.rs @@ -18,3 +18,15 @@ pub struct ChatJoinRequest { /// Chat invite link that was used by the user to send the join request pub invite_link: Option, } + +impl ChatJoinRequest { + /// Returns all users that are "contained" in this `ChatJoinRequest` + /// structure. + /// + /// This might be useful to track information about users. + /// + /// Note that this function can return duplicate users. + pub fn mentioned_users(&self) -> impl Iterator { + std::iter::once(&self.from).chain(self.chat.mentioned_users()) + } +} diff --git a/crates/teloxide-core/src/types/chat_member_updated.rs b/crates/teloxide-core/src/types/chat_member_updated.rs index 86094d2e..61ec402d 100644 --- a/crates/teloxide-core/src/types/chat_member_updated.rs +++ b/crates/teloxide-core/src/types/chat_member_updated.rs @@ -20,3 +20,21 @@ pub struct ChatMemberUpdated { /// joining by invite link events only. pub invite_link: Option, } + +impl ChatMemberUpdated { + /// Returns all users that are "contained" in this `ChatMemberUpdated` + /// structure. + /// + /// This might be useful to track information about users. + /// + /// Note that this function can return duplicate users. + pub fn mentioned_users(&self) -> impl Iterator { + [ + &self.from, + /* ignore `old_chat_member.user`, it should always be the same as the new one */ + &self.new_chat_member.user, + ] + .into_iter() + .chain(self.chat.mentioned_users()) + } +} diff --git a/crates/teloxide-core/src/types/game.rs b/crates/teloxide-core/src/types/game.rs index fa9c5f33..5fdc06a4 100644 --- a/crates/teloxide-core/src/types/game.rs +++ b/crates/teloxide-core/src/types/game.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::{Animation, MessageEntity, PhotoSize}; +use crate::types::{Animation, MessageEntity, PhotoSize, User}; /// This object represents a game. /// @@ -39,3 +39,17 @@ pub struct Game { /// [@Botfather]: https://t.me/botfather pub animation: Option, } + +impl Game { + /// Returns all users that are "contained" in this `Game` + /// structure. + /// + /// This might be useful to track information about users. + /// + /// Note that this function can return duplicate users. + pub fn mentioned_users(&self) -> impl Iterator { + use crate::util::{flatten, mentioned_users_from_entities}; + + flatten(self.text_entities.as_deref().map(mentioned_users_from_entities)) + } +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 0138be1a..2c6403d8 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -1406,6 +1406,42 @@ impl Message { pub fn parse_caption_entities(&self) -> Option>> { self.caption().zip(self.caption_entities()).map(|(t, e)| MessageEntityRef::parse(t, e)) } + + /// Returns all users that are "contained" in this `Message` structure. + /// + /// This might be useful to track information about users. + /// + /// Note that this function may return quite a few users as it scans + /// replies, pinned messages, message entities and more. Also note that this + /// function can return duplicate users. + pub fn mentioned_users(&self) -> impl Iterator { + use crate::util::{flatten, mentioned_users_from_entities}; + + // Lets just hope we didn't forget something here... + + self.from() + .into_iter() + .chain(self.via_bot.as_ref()) + .chain(self.chat.mentioned_users_rec()) + .chain(flatten(self.reply_to_message().map(Self::mentioned_users_rec))) + .chain(flatten(self.new_chat_members())) + .chain(self.left_chat_member()) + .chain(self.forward_from_user()) + .chain(flatten(self.forward_from_chat().map(Chat::mentioned_users_rec))) + .chain(flatten(self.game().map(Game::mentioned_users))) + .chain(flatten(self.entities().map(mentioned_users_from_entities))) + .chain(flatten(self.caption_entities().map(mentioned_users_from_entities))) + .chain(flatten(self.poll().map(Poll::mentioned_users))) + .chain(flatten(self.proximity_alert_triggered().map(|a| [&a.traveler, &a.watcher]))) + .chain(flatten(self.video_chat_participants_invited().and_then(|i| i.users.as_deref()))) + } + + /// `Message::mentioned_users` is recursive (due to replies), as such we + /// can't use `->impl Iterator` everywhere, as it would make an infinite + /// type. So we need to box somewhere. + pub(crate) fn mentioned_users_rec(&self) -> Box + Send + Sync + '_> { + Box::new(self.mentioned_users()) + } } #[cfg(test)] diff --git a/crates/teloxide-core/src/types/poll.rs b/crates/teloxide-core/src/types/poll.rs index 4a35a7d5..7725c961 100644 --- a/crates/teloxide-core/src/types/poll.rs +++ b/crates/teloxide-core/src/types/poll.rs @@ -1,4 +1,4 @@ -use crate::types::{MessageEntity, PollType}; +use crate::types::{MessageEntity, PollType, User}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -67,6 +67,20 @@ pub struct PollOption { pub voter_count: i32, } +impl Poll { + /// Returns all users that are "contained" in this `Poll` + /// structure. + /// + /// This might be useful to track information about users. + /// + /// Note that this function can return duplicate users. + pub fn mentioned_users(&self) -> impl Iterator { + use crate::util::{flatten, mentioned_users_from_entities}; + + flatten(self.explanation_entities.as_deref().map(mentioned_users_from_entities)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index e63a42f5..67c35862 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -59,6 +59,48 @@ impl Update { Some(from) } + /// Returns all users that are "contained" in this `Update` structure. + /// + /// This might be useful to track information about users. + /// + /// Note that this function may return quite a few users as it scans + /// replies, pinned messages, message entities, "via bot" fields and more. + /// Also note that this function can return duplicate users. + pub fn mentioned_users(&self) -> impl Iterator { + use either::Either::{Left, Right}; + use std::iter::{empty, once}; + + let i0 = Left; + let i1 = |x| Right(Left(x)); + let i2 = |x| Right(Right(Left(x))); + let i3 = |x| Right(Right(Right(Left(x)))); + let i4 = |x| Right(Right(Right(Right(Left(x))))); + let i5 = |x| Right(Right(Right(Right(Right(Left(x)))))); + let i6 = |x| Right(Right(Right(Right(Right(Right(x)))))); + + match &self.kind { + UpdateKind::Message(message) + | UpdateKind::EditedMessage(message) + | UpdateKind::ChannelPost(message) + | UpdateKind::EditedChannelPost(message) => i0(message.mentioned_users()), + + UpdateKind::InlineQuery(query) => i1(once(&query.from)), + UpdateKind::ChosenInlineResult(query) => i1(once(&query.from)), + UpdateKind::CallbackQuery(query) => i2(query.mentioned_users()), + UpdateKind::ShippingQuery(query) => i1(once(&query.from)), + UpdateKind::PreCheckoutQuery(query) => i1(once(&query.from)), + UpdateKind::Poll(poll) => i3(poll.mentioned_users()), + + UpdateKind::PollAnswer(answer) => i1(once(&answer.user)), + + UpdateKind::MyChatMember(member) | UpdateKind::ChatMember(member) => { + i4(member.mentioned_users()) + } + UpdateKind::ChatJoinRequest(request) => i5(request.mentioned_users()), + UpdateKind::Error(_) => i6(empty()), + } + } + /// Returns the chat in which is update has happened, if any. #[must_use] pub fn chat(&self) -> Option<&Chat> { diff --git a/crates/teloxide-core/src/util.rs b/crates/teloxide-core/src/util.rs new file mode 100644 index 00000000..a917428b --- /dev/null +++ b/crates/teloxide-core/src/util.rs @@ -0,0 +1,56 @@ +use crate::types::{MessageEntity, User}; + +/// Converts an optional iterator to a flattened iterator. +pub(crate) fn flatten(opt: Option) -> impl Iterator +where + I: IntoIterator, +{ + struct Flat(Option); + + impl Iterator for Flat + where + I: Iterator, + { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.0.as_mut()?.next() + } + + fn size_hint(&self) -> (usize, Option) { + match &self.0 { + None => (0, Some(0)), + Some(i) => i.size_hint(), + } + } + } + + Flat(opt.map(<_>::into_iter)) +} + +pub(crate) fn mentioned_users_from_entities( + entities: &[MessageEntity], +) -> impl Iterator { + use crate::types::MessageEntityKind::*; + + entities.iter().filter_map(|entity| match &entity.kind { + TextMention { user } => Some(user), + + Mention + | Hashtag + | Cashtag + | BotCommand + | Url + | Email + | PhoneNumber + | Bold + | Italic + | Underline + | Strikethrough + | Spoiler + | Code + | Pre { language: _ } + | TextLink { url: _ } + | CustomEmoji { custom_emoji_id: _ } => None, + }) +} From 48c8f935a53e6f6208697f657da969ba3e1f82e8 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 20 Feb 2023 18:44:08 +0400 Subject: [PATCH 30/33] Update changelog --- crates/teloxide-core/CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index 8e3e02cf..42fec72a 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -10,9 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `ChatPermission::can_*` helper functions ([#851][pr851]) +- `mentioned_users` functions for `CallbackQuery`, `Chat`, `ChatJoinRequest`, `ChatMemberUpdated`, `Game`, `Message`, `Poll`, `Update` which return all contained `User` instances ([#850][pr850]) +- `Message::video_chat_participants_invited` ([#850][pr850]) +- `Update::from`, a replacement for `Update::user` ([#850][pr850]) [pr851]: https://github.com/teloxide/teloxide/pull/851 +### Deprecated + +- `Update::user`, use `Update::from` instead ([#850][pr850]) + +[pr850]: https://github.com/teloxide/teloxide/pull/850 + ## 0.9.1 - 2023-02-15 ### Fixed From b8f3e0e005bd22a6da3b287b98142bb8ff31230f Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 20 Feb 2023 18:45:22 +0400 Subject: [PATCH 31/33] Fix deprecation version (core and main crates have different versions..) --- crates/teloxide-core/src/types/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index 67c35862..82a77e78 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -125,7 +125,7 @@ impl Update { Some(chat) } - #[deprecated(note = "renamed to `from`", since = "0.13.0")] + #[deprecated(note = "renamed to `from`", since = "0.10.0")] pub fn user(&self) -> Option<&User> { self.from() } From 98c0fd2187a19ceafafb508c5074f8dd8dcf257a Mon Sep 17 00:00:00 2001 From: jeorji <118723195+jeorji@users.noreply.github.com> Date: Mon, 27 Mar 2023 00:16:29 +0700 Subject: [PATCH 32/33] Update outdated documentation --- crates/teloxide-core/src/types/callback_query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/teloxide-core/src/types/callback_query.rs b/crates/teloxide-core/src/types/callback_query.rs index 2d1f5362..189c1f9b 100644 --- a/crates/teloxide-core/src/types/callback_query.rs +++ b/crates/teloxide-core/src/types/callback_query.rs @@ -40,8 +40,8 @@ pub struct CallbackQuery { /// [games]: https://core.telegram.org/bots/api#games pub chat_instance: String, - /// A data associated with the callback button. Be aware that a bad client - /// can send arbitrary data in this field. + /// A data associated with the callback button. Be aware that the message + /// originated the query can contain no callback buttons with this data. pub data: Option, /// A short name of a Game to be returned, serves as the unique identifier From d0365763972377eaa197467f5f37e01eef614090 Mon Sep 17 00:00:00 2001 From: jeorji <118723195+jeorji@users.noreply.github.com> Date: Mon, 27 Mar 2023 23:42:27 +0700 Subject: [PATCH 33/33] Remove outdated sentence --- crates/teloxide-core/src/types/callback_query.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/teloxide-core/src/types/callback_query.rs b/crates/teloxide-core/src/types/callback_query.rs index 189c1f9b..7dc8a422 100644 --- a/crates/teloxide-core/src/types/callback_query.rs +++ b/crates/teloxide-core/src/types/callback_query.rs @@ -40,8 +40,7 @@ pub struct CallbackQuery { /// [games]: https://core.telegram.org/bots/api#games pub chat_instance: String, - /// A data associated with the callback button. Be aware that the message - /// originated the query can contain no callback buttons with this data. + /// A data associated with the callback button. pub data: Option, /// A short name of a Game to be returned, serves as the unique identifier