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 diff --git a/src/adaptors/throttle.rs b/src/adaptors/throttle.rs index 36d6d880..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, } } } @@ -465,9 +470,17 @@ 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 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 < messages_per_min_limit; if limits_not_exceeded { *requests_sent.per_sec.entry(*chat).or_insert(0) += 1; @@ -627,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::*;