use serde::{Deserialize, Serialize};
use crate::{
entities::{
animation::Animation, audio::Audio, chat::Chat, chat_background::ChatBackground,
chat_boost_added::ChatBoostAdded, chat_shared::ChatShared, contact::Contact, dice::Dice,
document::Document, external_reply_info::ExternalReplyInfo,
forum_topic_closed::ForumTopicClosed, forum_topic_created::ForumTopicCreated,
forum_topic_edited::ForumTopicEdited, forum_topic_reopened::ForumTopicReopened, game::Game,
general_forum_topic_hidden::GeneralForumTopicHidden,
general_forum_topic_unhidden::GeneralForumTopicUnhidden, giveaway::Giveaway,
giveaway_completed::GiveawayCompleted, giveaway_created::GiveawayCreated,
giveaway_winners::GiveawayWinners, inline_keyboard_markup::InlineKeyboardMarkup,
invoice::Invoice, link_preview_options::LinkPreviewOptions, location::Location,
maybe_inaccessible_message::MaybeInaccessibleMessage,
message_auto_delete_timer_changed::MessageAutoDeleteTimerChanged,
message_entity::MessageEntity, message_origin::MessageOrigin,
paid_media_info::PaidMediaInfo, passport_data::PassportData, photo_size::PhotoSize,
poll::Poll, proximity_alert_triggered::ProximityAlertTriggered,
refunded_payment::RefundedPayment, sticker::Sticker, story::Story,
successful_payment::SuccessfulPayment, text_quote::TextQuote, user::User,
users_shared::UsersShared, venue::Venue, video::Video, video_chat_ended::VideoChatEnded,
video_chat_participants_invited::VideoChatParticipantsInvited,
video_chat_scheduled::VideoChatScheduled, video_chat_started::VideoChatStarted,
video_note::VideoNote, voice::Voice, web_app_data::WebAppData,
write_access_allowed::WriteAccessAllowed,
},
utils::deserialize_utils::is_false,
};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Message {
pub message_id: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message_thread_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from: Option<User>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sender_chat: Option<Box<Chat>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sender_boost_count: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sender_business_bot: Option<User>,
pub date: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub business_connection_id: Option<String>,
pub chat: Box<Chat>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub forward_origin: Option<MessageOrigin>,
#[serde(default, skip_serializing_if = "is_false")]
pub is_topic_message: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub is_automatic_forward: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reply_to_message: Option<Box<Message>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub external_reply: Option<ExternalReplyInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub quote: Option<TextQuote>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reply_to_story: Option<Story>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub via_bot: Option<User>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub edit_date: Option<i64>,
#[serde(default, skip_serializing_if = "is_false")]
pub has_protected_content: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub is_from_offline: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub media_group_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub author_signature: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub entities: Vec<MessageEntity>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub link_preview_options: Option<LinkPreviewOptions>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effect_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub animation: Option<Animation>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audio: Option<Audio>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub document: Option<Document>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub paid_media: Option<PaidMediaInfo>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub photo: Vec<PhotoSize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sticker: Option<Sticker>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub story: Option<Story>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video: Option<Video>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video_note: Option<VideoNote>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub voice: Option<Voice>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub caption: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub caption_entities: Vec<MessageEntity>,
#[serde(default, skip_serializing_if = "is_false")]
pub show_caption_above_media: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub has_media_spoiler: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contact: Option<Contact>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dice: Option<Dice>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub game: Option<Game>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub poll: Option<Poll>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub venue: Option<Venue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub location: Option<Location>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub new_chat_members: Vec<User>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub left_chat_member: Option<User>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub new_chat_title: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub new_chat_photo: Vec<PhotoSize>,
#[serde(default, skip_serializing_if = "is_false")]
pub delete_chat_photo: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub group_chat_created: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub supergroup_chat_created: bool,
#[serde(default, skip_serializing_if = "is_false")]
pub channel_chat_created: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message_auto_delete_timer_changed: Option<MessageAutoDeleteTimerChanged>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub migrate_to_chat_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub migrate_from_chat_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pinned_message: Option<Box<MaybeInaccessibleMessage>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub invoice: Option<Invoice>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub successful_payment: Option<SuccessfulPayment>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub refunded_payment: Option<RefundedPayment>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub users_shared: Option<UsersShared>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub chat_shared: Option<ChatShared>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub connected_website: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub write_access_allowed: Option<WriteAccessAllowed>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub passport_data: Option<PassportData>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proximity_alert_triggered: Option<ProximityAlertTriggered>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub boost_added: Option<ChatBoostAdded>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub chat_background_set: Option<ChatBackground>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub forum_topic_created: Option<ForumTopicCreated>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub forum_topic_edited: Option<ForumTopicEdited>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub forum_topic_closed: Option<ForumTopicClosed>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub forum_topic_reopened: Option<ForumTopicReopened>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub general_forum_topic_hidden: Option<GeneralForumTopicHidden>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub general_forum_topic_unhidden: Option<GeneralForumTopicUnhidden>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub giveaway_created: Option<GiveawayCreated>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub giveaway: Option<Giveaway>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub giveaway_winners: Option<GiveawayWinners>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub giveaway_completed: Option<GiveawayCompleted>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video_chat_scheduled: Option<VideoChatScheduled>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video_chat_started: Option<VideoChatStarted>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video_chat_ended: Option<VideoChatEnded>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video_chat_participants_invited: Option<VideoChatParticipantsInvited>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub web_app_data: Option<WebAppData>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reply_markup: Option<InlineKeyboardMarkup>,
}
use std::ops::Range;
use super::{
input_media::InputMedia,
misc::{formatting::FormattedText, input_file::InputFile},
reaction_type::ReactionType,
reply_parameters::ReplyParameters,
};
use crate::{
api::API,
entities::misc::chat_id::ChatId,
errors::ConogramError,
methods::{
copy_message::CopyMessageRequest, delete_message::DeleteMessageRequest,
edit_message_reply_markup::EditMessageReplyMarkupRequest,
edit_message_text::EditMessageTextRequest,
get_custom_emoji_stickers::GetCustomEmojiStickersRequest,
send_document::SendDocumentRequest, send_media_group::SendMediaGroupRequest,
send_message::SendMessageRequest, send_photo::SendPhotoRequest,
send_sticker::SendStickerRequest, set_message_reaction::SetMessageReactionRequest,
},
};
pub enum InputMessageText {
String(String),
FormattedText(FormattedText),
}
impl From<&str> for InputMessageText {
fn from(value: &str) -> Self {
Self::String(value.to_owned())
}
}
impl From<String> for InputMessageText {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<FormattedText> for InputMessageText {
fn from(value: FormattedText) -> Self {
Self::FormattedText(value)
}
}
impl From<MaybeInaccessibleMessage> for Option<Message> {
fn from(value: MaybeInaccessibleMessage) -> Self {
match value {
MaybeInaccessibleMessage::Message(m) => Some(m),
MaybeInaccessibleMessage::InaccessibleMessage(_) => None,
}
}
}
impl<'a> From<&'a MaybeInaccessibleMessage> for Option<&'a Message> {
fn from(value: &'a MaybeInaccessibleMessage) -> Self {
match value {
MaybeInaccessibleMessage::Message(m) => Some(m),
MaybeInaccessibleMessage::InaccessibleMessage(_) => None,
}
}
}
impl Message {
pub fn make_url(chat_id: impl Into<ChatId>, message_id: impl Into<i64>) -> String {
match chat_id.into() {
ChatId::Username(username) => {
format!("https://t.me/{username}/{}", message_id.into())
}
ChatId::Id(id) => format!(
"https://t.me/c/{}/{}",
-id - 1000000000000,
message_id.into()
),
}
}
pub fn get_url(&self) -> String {
if let Some(username) = &self.chat.username {
format!("https://t.me/{username}/{}", self.message_id)
} else {
format!(
"https://t.me/c/{}/{}",
-self.chat.id - 1000000000000,
self.message_id
)
}
}
pub fn from_id(&self) -> i64 {
if let Some(sender_chat) = &self.sender_chat {
sender_chat.id
} else if let Some(from_user) = &self.from {
from_user.id
} else {
0
}
}
pub const fn get_text(&self) -> &Option<String> {
if self.text.is_some() {
&self.text
} else {
&self.caption
}
}
pub fn get_entities(&self) -> &Vec<MessageEntity> {
if self.entities.is_empty() {
&self.caption_entities
} else {
&self.entities
}
}
pub fn get_custom_emoji_ids(&self) -> Vec<String> {
self.get_entities()
.iter()
.filter_map(|ent| ent.custom_emoji_id.as_ref())
.map(String::from)
.collect()
}
pub fn get_custom_emoji_stickers<'a>(
&'a self,
api: &'a API,
) -> GetCustomEmojiStickersRequest<'a> {
api.get_custom_emoji_stickers(self.get_custom_emoji_ids())
}
pub fn file_uid(&self) -> Option<String> {
if let Some(m) = self.photo.first() {
Some(m.file_unique_id.clone())
} else if let Some(m) = &self.animation {
Some(m.file_unique_id.clone())
} else if let Some(m) = &self.audio {
Some(m.file_unique_id.clone())
} else if let Some(m) = &self.document {
Some(m.file_unique_id.clone())
} else if let Some(m) = &self.video {
Some(m.file_unique_id.clone())
} else if let Some(m) = &self.video_note {
Some(m.file_unique_id.clone())
} else if let Some(m) = &self.voice {
Some(m.file_unique_id.clone())
} else {
self.sticker.as_ref().map(|m| m.file_unique_id.clone())
}
}
pub fn file_id(&self) -> Option<String> {
if let Some(m) = self.photo.first() {
Some(m.file_id.clone())
} else if let Some(m) = &self.animation {
Some(m.file_id.clone())
} else if let Some(m) = &self.audio {
Some(m.file_id.clone())
} else if let Some(m) = &self.document {
Some(m.file_id.clone())
} else if let Some(m) = &self.video {
Some(m.file_id.clone())
} else if let Some(m) = &self.video_note {
Some(m.file_id.clone())
} else if let Some(m) = &self.voice {
Some(m.file_id.clone())
} else {
self.sticker.as_ref().map(|m| m.file_id.clone())
}
}
pub fn get_formatted_text(&self) -> Option<FormattedText> {
if let (Some(text), entities) = (self.get_text(), self.get_entities()) {
Some(FormattedText::with_text(text.clone(), entities.clone()))
} else {
None
}
}
pub fn quote_reply<'a>(
&'a self,
api: &'a API,
text: impl Into<String>,
) -> SendMessageRequest<'a> {
self.quote_reply_args(api, text, Option::<Range<usize>>::None, Option::<i64>::None)
}
pub fn quote_reply_partial<'a>(
&'a self,
api: &'a API,
text: impl Into<String>,
quoting_range: impl Into<Range<usize>>,
) -> SendMessageRequest<'a> {
self.quote_reply_args(api, text, Some(quoting_range), Option::<i64>::None)
}
pub fn quote_reply_to<'a>(
&'a self,
api: &'a API,
text: impl Into<String>,
chat_id: impl Into<ChatId>,
) -> SendMessageRequest<'a> {
self.quote_reply_args(api, text, Option::<Range<usize>>::None, Some(chat_id))
}
pub fn quote_reply_partial_to<'a>(
&'a self,
api: &'a API,
text: impl Into<String>,
quoting_range: impl Into<Range<usize>>,
chat_id: impl Into<ChatId>,
) -> SendMessageRequest<'a> {
self.quote_reply_args(api, text, Some(quoting_range), Some(chat_id))
}
pub fn quote_reply_args<'a>(
&'a self,
api: &'a API,
text: impl Into<String>,
quoting_range: Option<impl Into<Range<usize>>>,
chat_id: Option<impl Into<ChatId>>,
) -> SendMessageRequest<'a> {
let chat_id = if let Some(chat_id) = chat_id {
chat_id.into()
} else {
self.chat.id.into()
};
let (quote_text, quote_entities, quote_pos) = if let Some(range) = quoting_range {
let range = range.into();
let range_start = range.start as i64;
let (quote_text, quote_entities) = self
.get_formatted_text()
.unwrap_or_default()
.slice(range)
.build();
(Some(quote_text), quote_entities, Some(range_start))
} else {
(None, vec![], None)
};
let mut req =
SendMessageRequest::new(api, chat_id, text).reply_parameters(ReplyParameters {
message_id: self.message_id,
chat_id: Some(self.chat.id.into()),
quote: quote_text,
quote_entities,
quote_position: quote_pos,
..Default::default()
});
if self.is_topic_message {
if let Some(thread_id) = self.message_thread_id {
req = req.message_thread_id(thread_id);
}
}
req
}
pub fn reply<'a>(
&'a self,
api: &'a API,
text: impl Into<InputMessageText>,
) -> SendMessageRequest<'a> {
let mut req = match text.into() {
InputMessageText::String(v) => api
.send_message(self.chat.id, v)
.reply_parameters(ReplyParameters::new_current_chat(self.message_id)),
InputMessageText::FormattedText(ft) => self.reply_formatted(api, ft),
};
if self.is_topic_message {
if let Some(thread_id) = self.message_thread_id {
req = req.message_thread_id(thread_id);
}
}
req
}
pub fn reply_entities<'a>(
&'a self,
api: &'a API,
text: impl Into<InputMessageText>,
entities: impl IntoIterator<Item = MessageEntity>,
) -> SendMessageRequest<'a> {
self.reply(api, text).entities(entities)
}
pub fn reply_formatted<'a>(
&'a self,
api: &'a API,
formatted_text: FormattedText,
) -> SendMessageRequest<'a> {
let (text, entities) = formatted_text.build();
self.reply(api, text).entities(entities)
}
pub fn answer<'a>(&'a self, api: &'a API, text: impl Into<String>) -> SendMessageRequest<'a> {
let mut req = api.send_message(self.chat.id, text);
if self.is_topic_message {
if let Some(thread_id) = self.message_thread_id {
req = req.message_thread_id(thread_id);
}
}
req
}
pub fn answer_entities<'a>(
&'a self,
api: &'a API,
text: impl Into<String>,
entities: impl IntoIterator<Item = MessageEntity>,
) -> SendMessageRequest<'a> {
self.answer(api, text).entities(entities)
}
pub fn edit_text<'a>(
&'a self,
api: &'a API,
text: impl Into<String>,
) -> EditMessageTextRequest<'a> {
api.edit_message_text(text.into())
.message_id(self.message_id)
.chat_id(self.chat.id)
}
pub fn edit_text_formatted<'a>(
&'a self,
api: &'a API,
ft: impl Into<FormattedText>,
) -> EditMessageTextRequest<'a> {
let (text, entities) = ft.into().build();
self.edit_text(api, text).entities(entities)
}
pub fn copy<'a>(&'a self, api: &'a API, chat_id: impl Into<ChatId>) -> CopyMessageRequest<'a> {
api.copy_message(chat_id, self.chat.id, self.message_id)
}
pub fn edit_reply_markup<'a>(&'a self, api: &'a API) -> EditMessageReplyMarkupRequest<'a> {
api.edit_message_reply_markup()
.message_id(self.message_id)
.chat_id(self.chat.id)
}
pub fn delete_reply_markup<'a>(&'a self, api: &'a API) -> EditMessageReplyMarkupRequest<'a> {
api.edit_message_reply_markup()
.message_id(self.message_id)
.chat_id(self.chat.id)
.reply_markup(InlineKeyboardMarkup::empty())
}
pub fn delete<'a>(&'a self, api: &'a API) -> DeleteMessageRequest<'a> {
api.delete_message(self.chat.id, self.message_id)
}
pub async fn delete_exp<'a>(&'a self, api: &'a API) -> Result<bool, ConogramError> {
api.delete_message_exp(self.chat.id, self.message_id).await
}
pub fn reply_photo<'a>(
&'a self,
api: &'a API,
photo: impl Into<InputFile>,
) -> SendPhotoRequest<'a> {
let mut req = api
.send_photo(self.chat.id, photo)
.reply_parameters(ReplyParameters::new_current_chat(self.message_id));
if self.is_topic_message {
if let Some(thread_id) = self.message_thread_id {
req = req.message_thread_id(thread_id);
}
}
req
}
pub fn reply_media_group<'a>(
&'a self,
api: &'a API,
media: impl IntoIterator<Item = impl Into<InputMedia>>,
) -> SendMediaGroupRequest<'a> {
let mut req = api
.send_media_group(self.chat.id, media)
.reply_parameters(ReplyParameters::new_current_chat(self.message_id));
if self.is_topic_message {
if let Some(thread_id) = self.message_thread_id {
req = req.message_thread_id(thread_id);
}
}
req
}
pub fn reply_document<'a>(
&'a self,
api: &'a API,
document: impl Into<InputFile>,
) -> SendDocumentRequest<'a> {
let mut req = api
.send_document(self.chat.id, document)
.reply_parameters(ReplyParameters::new_current_chat(self.message_id));
if self.is_topic_message {
if let Some(thread_id) = self.message_thread_id {
req = req.message_thread_id(thread_id);
}
}
req
}
pub fn reply_sticker<'a>(
&'a self,
api: &'a API,
sticker: impl Into<InputFile>,
) -> SendStickerRequest<'a> {
let mut req = api
.send_sticker(self.chat.id, sticker)
.reply_parameters(ReplyParameters::new_current_chat(self.message_id));
if self.is_topic_message {
if let Some(thread_id) = self.message_thread_id {
req = req.message_thread_id(thread_id);
}
}
req
}
pub fn copy_to<'a>(
&'a self,
api: &'a API,
chat_id: impl Into<ChatId>,
) -> CopyMessageRequest<'a> {
api.copy_message(chat_id, self.chat.id, self.message_id)
}
pub fn set_reactions<'a>(
&'a self,
api: &'a API,
reactions: impl IntoIterator<Item = impl Into<ReactionType>>,
) -> SetMessageReactionRequest<'a> {
api.set_message_reaction(self.chat.id, self.message_id)
.reaction(reactions)
}
pub fn delete_reactions<'a>(&'a self, api: &'a API) -> SetMessageReactionRequest<'a> {
let reactions: [ReactionType; 0] = [];
self.set_reactions(api, reactions)
}
pub fn react<'a>(
&'a self,
api: &'a API,
reaction: impl Into<ReactionType>,
) -> SetMessageReactionRequest<'a> {
self.set_reactions(api, [reaction.into()])
}
}