From 86a478dd1d86193331307dcd2e052b2360fba3ae Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 16 Sep 2021 00:04:01 +0300 Subject: [PATCH 1/3] Throttle: fix limit check Previously both `messages_per_sec_chat` and `messages_per_min_chat` were checked against last second message count. --- src/adaptors/throttle.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 36d6d880..6eefbf5d 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -465,9 +465,11 @@ async fn worker( while let Some(entry) = queue_removing.next() { let chat = &entry.value().0; - let requests_sent_count = requests_sent.per_sec.get(chat).copied().unwrap_or(0); - let limits_not_exceeded = requests_sent_count < limits.messages_per_sec_chat - && requests_sent_count < limits.messages_per_min_chat; + let requests_sent_per_sec_count = requests_sent.per_sec.get(chat).copied().unwrap_or(0); + let requests_sent_per_min_count = requests_sent.per_min.get(chat).copied().unwrap_or(0); + + let limits_not_exceeded = requests_sent_per_sec_count < limits.messages_per_sec_chat + && requests_sent_per_min_count < limits.messages_per_min_chat; if limits_not_exceeded { *requests_sent.per_sec.entry(*chat).or_insert(0) += 1; From 00b83770a79afca13f5f2e81765b3ab32c447669 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 16 Sep 2021 02:37:38 +0300 Subject: [PATCH 2/3] Throttle: correct limits for channels This commits adds `Limits::messages_per_min_channel` field --- src/adaptors/throttle.rs | 24 ++++++++++++++++++++++-- src/types/chat_id.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 6eefbf5d..7cae78ac 100644 --- a/src/adaptors/throttle.rs +++ b/src/adaptors/throttle.rs @@ -203,11 +203,15 @@ pub struct Limits { /// Allowed messages in one chat per minute. pub messages_per_min_chat: u32, + /// Allowed messages in one channel per minute. + pub messages_per_min_channel: u32, + /// Allowed messages per second. pub messages_per_sec_overall: u32, } -/// Defaults are taken from [telegram documentation][tgdoc]. +/// Defaults are taken from [telegram documentation][tgdoc] (except for +/// `messages_per_min_channel`). /// /// [tgdoc]: https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this impl Default for Limits { @@ -216,6 +220,7 @@ impl Default for Limits { messages_per_sec_chat: 1, messages_per_sec_overall: 30, messages_per_min_chat: 20, + messages_per_min_channel: 10, } } } @@ -468,8 +473,14 @@ async fn worker( let requests_sent_per_sec_count = requests_sent.per_sec.get(chat).copied().unwrap_or(0); let requests_sent_per_min_count = requests_sent.per_min.get(chat).copied().unwrap_or(0); + let messages_per_min_limit = if chat.is_channel() { + limits.messages_per_min_channel + } else { + limits.messages_per_min_chat + }; + let limits_not_exceeded = requests_sent_per_sec_count < limits.messages_per_sec_chat - && requests_sent_per_min_count < limits.messages_per_min_chat; + && requests_sent_per_min_count < messages_per_min_limit; if limits_not_exceeded { *requests_sent.per_sec.entry(*chat).or_insert(0) += 1; @@ -629,6 +640,15 @@ enum ChatIdHash { ChannelUsernameHash(u64), } +impl ChatIdHash { + fn is_channel(&self) -> bool { + match self { + &Self::Id(id) => ChatId::Id(id).is_channel(), + Self::ChannelUsernameHash(_) => true, + } + } +} + impl From<&ChatId> for ChatIdHash { fn from(value: &ChatId) -> Self { match value { diff --git a/src/types/chat_id.rs b/src/types/chat_id.rs index f4e57e98..026715c2 100644 --- a/src/types/chat_id.rs +++ b/src/types/chat_id.rs @@ -15,6 +15,39 @@ pub enum ChatId { ChannelUsername(String), } +impl ChatId { + pub(crate) fn is_channel(&self) -> bool { + matches!(self.unmark(), None | Some(UnmarkedChatId::Channel(_))) + } + + pub(crate) fn unmark(&self) -> Option { + use UnmarkedChatId::*; + + const MAX_CHANNEL_ID: i64 = -(10i64.pow(12)); + const MIN_CHANNEL_ID: i64 = MAX_CHANNEL_ID - (i32::MAX as i64); + const MAX_USER_ID: i64 = i32::MAX as _; + const MIN_CHAT_ID: i64 = -MAX_USER_ID; + + let res = match self { + &Self::Id(id @ MIN_CHAT_ID..=-1) => Chat(-id as _), + &Self::Id(id @ MIN_CHANNEL_ID..=MAX_CHANNEL_ID) => Channel((MAX_CHANNEL_ID - id) as _), + &Self::Id(id) => { + debug_assert!(0 < id && id < MAX_USER_ID, "malformed chat id"); + User(id as _) + } + Self::ChannelUsername(_) => return None, + }; + + Some(res) + } +} + +pub(crate) enum UnmarkedChatId { + User(u32), + Chat(u32), + Channel(u32), +} + #[cfg(test)] mod tests { use super::*; From b18a06403b2768d492e95c7f40285b5413fd9a60 Mon Sep 17 00:00:00 2001 From: Waffle Date: Thu, 16 Sep 2021 02:57:30 +0300 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d146e494..ae179b83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `EditedMessageIsTooLong` error [#109][pr109] - `UntilDate` enum and use it for `{Restricted, Banned}::until_date` ([#116][pr116]) +- `Limits::messages_per_min_channel` ([#121][pr121]) [pr109]: https://github.com/teloxide/teloxide-core/pull/109 [pr116]: https://github.com/teloxide/teloxide-core/pull/116 +[pr121]: https://github.com/teloxide/teloxide-core/pull/121 ### Changed @@ -26,8 +28,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Type of `BanChatMember::until_date`: `u64` -> `chrono::DateTime` ([#116][pr116]) - Type of `Poll::correct_option_id`: `i32` -> `u8` ([#119][pr119]) - Type of `Poll::open_period`: `i32` -> `u16` ([#119][pr119]) +- `Throttle` adaptor not honouring chat/min limits ([#121][pr121]) [pr119]: https://github.com/teloxide/teloxide-core/pull/119 + ## 0.3.3 - 2021-08-03 ### Fixed