Add mentioned_users to update types where it makes sense

This commit is contained in:
Maybe Waffle 2023-02-13 14:11:06 +04:00
parent 16c20e371c
commit 35471a4a0b
10 changed files with 227 additions and 3 deletions

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

@ -1406,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

@ -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> {

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,
})
}