Merge pull request #850 from teloxide/update_users

Rename `Update::{user -> from}`, add `Update::mentioned_users` & co
This commit is contained in:
Waffle Maybe 2023-03-11 17:28:55 +00:00 committed by GitHub
commit 6bbf0ed386
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 256 additions and 6 deletions

View file

@ -10,9 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- `ChatPermission::can_*` helper functions ([#851][pr851]) - `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 [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 ## 0.9.1 - 2023-02-15
### Fixed ### Fixed

View file

@ -117,6 +117,7 @@ mod bot;
// implementation details // implementation details
mod serde_multipart; mod serde_multipart;
mod util;
#[cfg(test)] #[cfg(test)]
mod codegen; mod codegen;

View file

@ -49,6 +49,20 @@ pub struct CallbackQuery {
pub game_short_name: Option<String>, pub game_short_name: Option<String>,
} }
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<Item = &User> {
use crate::util::flatten;
use std::iter::once;
once(&self.from).chain(flatten(self.message.as_ref().map(Message::mentioned_users)))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::types::UserId; use crate::types::UserId;

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; 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. /// This object represents a chat.
/// ///
@ -493,6 +493,23 @@ impl Chat {
_ => None, _ => 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<Item = &User> {
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<Item = &User> {
crate::util::flatten(self.pinned_message.as_ref().map(|m| m.mentioned_users_rec()))
}
} }
mod serde_helper { mod serde_helper {

View file

@ -18,3 +18,15 @@ pub struct ChatJoinRequest {
/// Chat invite link that was used by the user to send the join request /// Chat invite link that was used by the user to send the join request
pub invite_link: Option<ChatInviteLink>, pub invite_link: Option<ChatInviteLink>,
} }
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<Item = &User> {
std::iter::once(&self.from).chain(self.chat.mentioned_users())
}
}

View file

@ -20,3 +20,21 @@ pub struct ChatMemberUpdated {
/// joining by invite link events only. /// joining by invite link events only.
pub invite_link: Option<ChatInviteLink>, pub invite_link: Option<ChatInviteLink>,
} }
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<Item = &User> {
[
&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())
}
}

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::{Animation, MessageEntity, PhotoSize}; use crate::types::{Animation, MessageEntity, PhotoSize, User};
/// This object represents a game. /// This object represents a game.
/// ///
@ -39,3 +39,17 @@ pub struct Game {
/// [@Botfather]: https://t.me/botfather /// [@Botfather]: https://t.me/botfather
pub animation: Option<Animation>, pub animation: Option<Animation>,
} }
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<Item = &User> {
use crate::util::{flatten, mentioned_users_from_entities};
flatten(self.text_entities.as_deref().map(mentioned_users_from_entities))
}
}

View file

@ -615,7 +615,7 @@ mod getters {
MessageGroupChatCreated, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers, MessageGroupChatCreated, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers,
MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned, MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned,
MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated, MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated,
PhotoSize, True, User, MessageVideoChatParticipantsInvited, PhotoSize, True, User,
}; };
/// Getters for [Message] fields from [telegram docs]. /// 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] #[must_use]
pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> { pub fn reply_markup(&self) -> Option<&types::InlineKeyboardMarkup> {
match &self.kind { match &self.kind {
@ -1394,6 +1406,42 @@ impl Message {
pub fn parse_caption_entities(&self) -> Option<Vec<MessageEntityRef<'_>>> { pub fn parse_caption_entities(&self) -> Option<Vec<MessageEntityRef<'_>>> {
self.caption().zip(self.caption_entities()).map(|(t, e)| MessageEntityRef::parse(t, e)) 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<Item = &User> {
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<dyn Iterator<Item = &User> + Send + Sync + '_> {
Box::new(self.mentioned_users())
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,4 +1,4 @@
use crate::types::{MessageEntity, PollType}; use crate::types::{MessageEntity, PollType, User};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -67,6 +67,20 @@ pub struct PollOption {
pub voter_count: i32, 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<Item = &User> {
use crate::util::{flatten, mentioned_users_from_entities};
flatten(self.explanation_entities.as_deref().map(mentioned_users_from_entities))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -29,7 +29,7 @@ pub struct Update {
} }
impl 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 /// Returns the user that performed the action that caused this update, if
/// known. /// known.
@ -37,7 +37,7 @@ impl Update {
/// This is generally the `from` field (except for `PollAnswer` where it's /// 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). /// `user` and `Poll` with `Error` which don't have such field at all).
#[must_use] #[must_use]
pub fn user(&self) -> Option<&User> { pub fn from(&self) -> Option<&User> {
use UpdateKind::*; use UpdateKind::*;
let from = match &self.kind { let from = match &self.kind {
@ -59,6 +59,48 @@ impl Update {
Some(from) 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<Item = &User> {
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. /// Returns the chat in which is update has happened, if any.
#[must_use] #[must_use]
pub fn chat(&self) -> Option<&Chat> { pub fn chat(&self) -> Option<&Chat> {
@ -82,6 +124,11 @@ impl Update {
Some(chat) Some(chat)
} }
#[deprecated(note = "renamed to `from`", since = "0.10.0")]
pub fn user(&self) -> Option<&User> {
self.from()
}
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]

View file

@ -0,0 +1,56 @@
use crate::types::{MessageEntity, User};
/// Converts an optional iterator to a flattened iterator.
pub(crate) fn flatten<I>(opt: Option<I>) -> impl Iterator<Item = I::Item>
where
I: IntoIterator,
{
struct Flat<I>(Option<I>);
impl<I> Iterator for Flat<I>
where
I: Iterator,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
self.0.as_mut()?.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
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<Item = &User> {
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,
})
}