diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index bd5ecb94f..351d397de 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -1516,8 +1516,8 @@ inputTextQuote text:formattedText position:int32 = InputTextQuote; //@origin_send_date Point in time (Unix timestamp) when the message was sent if the message was from another chat or topic; 0 for messages from the same chat //@content Media content of the message if the message was from another chat or topic; may be null for messages from the same chat and messages without media. //-Can be only one of the following types: messageAnimation, messageAudio, messageContact, messageDice, messageDocument, messageGame, messageInvoice, messageLocation, -//-messagePhoto, messagePoll, messagePremiumGiveaway, messagePremiumGiveawayWinners, messageSticker, messageStory, messageText (for link preview), messageVenue, -//-messageVideo, messageVideoNote, or messageVoiceNote +//-messagePaidMedia, messagePhoto, messagePoll, messagePremiumGiveaway, messagePremiumGiveawayWinners, messageSticker, messageStory, messageText (for link preview), +//-messageVenue, messageVideo, messageVideoNote, or messageVoiceNote messageReplyToMessage chat_id:int53 message_id:int53 quote:textQuote origin:MessageOrigin origin_send_date:int32 content:MessageContent = MessageReplyTo; //@description Describes a story replied by a given message @story_sender_chat_id The identifier of the sender of the story @story_id The identifier of the story @@ -3117,6 +3117,12 @@ messageAudio audio:audio caption:formattedText = MessageContent; //@description A document message (general file) @document The document description @caption Document caption messageDocument document:document caption:formattedText = MessageContent; +//@description A message with paid media +//@star_count Number of stars needed to buy access to the media in the message +//@media Information about the media +//@caption Media caption +messagePaidMedia star_count:int53 media:vector caption:formattedText = MessageContent; + //@description A photo message //@photo The photo //@caption Photo caption @@ -3507,7 +3513,7 @@ messageSelfDestructTypeImmediately = MessageSelfDestructType; //@only_preview Pass true to get a fake message instead of actually sending them messageSendOptions disable_notification:Bool from_background:Bool protect_content:Bool update_order_of_installed_sticker_sets:Bool scheduling_state:MessageSchedulingState effect_id:int64 sending_id:int32 only_preview:Bool = MessageSendOptions; -//@description Options to be used when a message content is copied without reference to the original sender. Service messages, messages with messageInvoice, messagePremiumGiveaway, or messagePremiumGiveawayWinners content can't be copied +//@description Options to be used when a message content is copied without reference to the original sender. Service messages, messages with messageInvoice, messagePaidMedia, messagePremiumGiveaway, or messagePremiumGiveawayWinners content can't be copied //@send_copy True, if content of the message needs to be copied without reference to the original sender. Always true if the message is forwarded to a secret chat or is local //@replace_caption True, if media caption of the message copy needs to be replaced. Ignored if send_copy is false //@new_caption New message caption; pass null to copy message without caption. Ignored if replace_caption is false diff --git a/td/telegram/BusinessConnectionManager.cpp b/td/telegram/BusinessConnectionManager.cpp index 623728ab6..7bb00ae60 100644 --- a/td/telegram/BusinessConnectionManager.cpp +++ b/td/telegram/BusinessConnectionManager.cpp @@ -1047,8 +1047,8 @@ void BusinessConnectionManager::complete_upload_media(unique_ptr bool need_update = false; unique_ptr &old_content = message->content_; - MessageContentType old_content_type = old_content->get_type(); - MessageContentType new_content_type = new_content->get_type(); + auto old_content_type = old_content->get_type(); + auto new_content_type = new_content->get_type(); auto old_file_id = get_message_file_id(message); if (old_content_type != new_content_type) { diff --git a/td/telegram/DialogAction.cpp b/td/telegram/DialogAction.cpp index 3d217dd8c..c725a0068 100644 --- a/td/telegram/DialogAction.cpp +++ b/td/telegram/DialogAction.cpp @@ -373,6 +373,7 @@ bool DialogAction::is_canceled_by_message_of_type(MessageContentType message_con return type_ == Type::ChoosingSticker; case MessageContentType::Game: case MessageContentType::Invoice: + case MessageContentType::PaidMedia: case MessageContentType::Text: case MessageContentType::Unsupported: case MessageContentType::ChatCreate: @@ -434,6 +435,7 @@ DialogAction DialogAction::get_uploading_action(MessageContentType message_conte case MessageContentType::Animation: case MessageContentType::Audio: case MessageContentType::Document: + case MessageContentType::PaidMedia: return DialogAction(Type::UploadingDocument, progress); case MessageContentType::Photo: return DialogAction(Type::UploadingPhoto, progress); diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index 82af1d3ad..58fb391f4 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -51,6 +51,7 @@ #include "td/telegram/Location.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageEntity.hpp" +#include "td/telegram/MessageExtendedMedia.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessageSearchFilter.h" #include "td/telegram/MessageSender.h" @@ -72,6 +73,7 @@ #include "td/telegram/ServerMessageId.h" #include "td/telegram/SharedDialog.h" #include "td/telegram/SharedDialog.hpp" +#include "td/telegram/StarManager.h" #include "td/telegram/StickerFormat.h" #include "td/telegram/StickersManager.h" #include "td/telegram/StickersManager.hpp" @@ -502,7 +504,7 @@ class MessageChatSetTtl final : public MessageContent { class MessageUnsupported final : public MessageContent { public: - static constexpr int32 CURRENT_VERSION = 31; + static constexpr int32 CURRENT_VERSION = 32; int32 version = CURRENT_VERSION; MessageUnsupported() = default; @@ -1120,6 +1122,22 @@ class MessageDialogShared final : public MessageContent { } }; +class MessagePaidMedia final : public MessageContent { + public: + vector media; + FormattedText caption; + int64 star_count = 0; + + MessagePaidMedia() = default; + MessagePaidMedia(vector &&media, FormattedText &&caption, int64 star_count) + : media(std::move(media)), caption(std::move(caption)), star_count(star_count) { + } + + MessageContentType get_type() const final { + return MessageContentType::PaidMedia; + } +}; + template static void store(const MessageContent *content, StorerT &storer) { CHECK(content != nullptr); @@ -1684,6 +1702,19 @@ static void store(const MessageContent *content, StorerT &storer) { store(m->button_id, storer); break; } + case MessageContentType::PaidMedia: { + const auto *m = static_cast(content); + bool has_caption = !m->caption.text.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_caption); + END_STORE_FLAGS(); + store(m->media, storer); + if (has_caption) { + store(m->caption, storer); + } + store(m->star_count, storer); + break; + } default: UNREACHABLE(); } @@ -2433,6 +2464,26 @@ static void parse(unique_ptr &content, ParserT &parser) { content = std::move(m); break; } + case MessageContentType::PaidMedia: { + auto m = make_unique(); + bool has_caption; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_caption); + END_PARSE_FLAGS(); + parse(m->media, parser); + if (has_caption) { + parse(m->caption, parser); + } + parse(m->star_count, parser); + + for (auto &media : m->media) { + if (media.is_empty()) { + is_bad = true; + } + } + content = std::move(m); + break; + } default: is_bad = true; @@ -3158,6 +3209,7 @@ bool can_have_input_media(const Td *td, const MessageContent *content, bool is_s case MessageContentType::Video: case MessageContentType::VideoNote: case MessageContentType::VoiceNote: + case MessageContentType::PaidMedia: return true; default: UNREACHABLE(); @@ -3284,6 +3336,7 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: case MessageContentType::DialogShared: + case MessageContentType::PaidMedia: break; default: UNREACHABLE(); @@ -3342,6 +3395,11 @@ static tl_object_ptr get_input_media_impl( const auto *m = static_cast(content); return m->location.get_input_media_geo_point(); } + case MessageContentType::PaidMedia: { + // const auto *m = static_cast(content); + // TODO get_input_media + return nullptr; + } case MessageContentType::Photo: { const auto *m = static_cast(content); return photo_get_input_media(td->file_manager_.get(), m->photo, std::move(input_file), ttl.get_input_ttl(), @@ -3559,6 +3617,9 @@ void delete_message_content_thumbnail(MessageContent *content, Td *td) { auto *m = static_cast(content); return m->input_invoice.delete_thumbnail(td); } + case MessageContentType::PaidMedia: + // TODO delete_message_content_thumbnail + break; case MessageContentType::Photo: { auto *m = static_cast(content); return photo_delete_thumbnail(m->photo); @@ -3739,6 +3800,14 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten return Status::Error(400, "Not enough rights to send locations to the chat"); } break; + case MessageContentType::PaidMedia: + if (!permissions.can_send_photos() || !permissions.can_send_videos()) { + return Status::Error(400, "Not enough rights to send paid media to the chat"); + } + if (dialog_type == DialogType::SecretChat) { + return Status::Error(400, "Paid media can't be sent to secret chats"); + } + break; case MessageContentType::Photo: if (!permissions.can_send_photos()) { return Status::Error(400, "Not enough rights to send photos to the chat"); @@ -4005,6 +4074,7 @@ static int32 get_message_content_media_index_mask(const MessageContent *content, case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: case MessageContentType::DialogShared: + case MessageContentType::PaidMedia: return 0; default: UNREACHABLE(); @@ -4290,6 +4360,8 @@ vector get_message_content_min_user_ids(const Td *td, const MessageConte break; case MessageContentType::DialogShared: break; + case MessageContentType::PaidMedia: + break; default: UNREACHABLE(); break; @@ -4598,6 +4670,14 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo } break; } + case MessageContentType::PaidMedia: { + //const auto *old_ = static_cast(old_content); + //const auto *new_ = static_cast(new_content); + if (need_merge_files) { + // TODO merge extended media + } + break; + } case MessageContentType::Photo: { const auto *old_ = static_cast(old_content); auto *new_ = static_cast(new_content); @@ -4795,6 +4875,7 @@ bool merge_message_content_file_id(Td *td, MessageContent *message_content, File case MessageContentType::Invoice: case MessageContentType::LiveLocation: case MessageContentType::Location: + case MessageContentType::PaidMedia: case MessageContentType::Story: case MessageContentType::Text: case MessageContentType::Venue: @@ -5391,6 +5472,14 @@ void compare_message_contents(Td *td, const MessageContent *old_content, const M } break; } + case MessageContentType::PaidMedia: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->caption != rhs->caption || lhs->star_count != rhs->star_count) { + need_update = true; + } + break; + } default: UNREACHABLE(); break; @@ -6313,8 +6402,14 @@ unique_ptr get_message_content(Td *td, FormattedText message, std::move(media->prize_description_), media->until_date_, media->only_new_subscribers_, media->refunded_, media->winners_count_, media->unclaimed_count_, std::move(winner_user_ids)); } - case telegram_api::messageMediaPaidMedia::ID: - return make_unique(); + case telegram_api::messageMediaPaidMedia::ID: { + auto media = telegram_api::move_object_as(media_ptr); + auto extended_media = transform(std::move(media->extended_media_), [&](auto &&extended_media) { + return MessageExtendedMedia(td, std::move(extended_media), FormattedText(), owner_dialog_id); + }); + return td::make_unique(std::move(extended_media), std::move(message), + StarManager::get_star_count(media->stars_amount_)); + } case telegram_api::messageMediaUnsupported::ID: return make_unique(); default: @@ -6425,6 +6520,17 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const } case MessageContentType::Location: return make_unique(*static_cast(content)); + case MessageContentType::PaidMedia: { + if (type == MessageContentDupType::Copy || type == MessageContentDupType::ServerCopy) { + return nullptr; + } + auto result = make_unique(*static_cast(content)); + if (type != MessageContentDupType::Forward) { + // TODO support PaidMedia sending + return nullptr; + } + return result; + } case MessageContentType::Photo: { auto result = make_unique(*static_cast(content)); if (replace_caption) { @@ -6437,7 +6543,7 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const // having remote location is not enough to have InputMedia, because the file may not have valid file_reference // also file_id needs to be duped, because upload can be called to repair the file_reference and every upload // request must have unique file_id - if (!td->auth_manager_->is_bot()) { + if (!td->auth_manager_->is_bot() && type != MessageContentDupType::Forward) { result->photo.photos.back().file_id = fix_file_id(result->photo.photos.back().file_id); } return std::move(result); @@ -7499,6 +7605,13 @@ tl_object_ptr get_message_content_object(const MessageCo return td_api::make_object(m->shared_dialogs[0].get_shared_chat_object(td), m->button_id); } + case MessageContentType::PaidMedia: { + const auto *m = static_cast(content); + return td_api::make_object( + m->star_count, + transform(m->media, [&](const auto &media) { return media.get_message_extended_media_object(td); }), + get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp)); + } default: UNREACHABLE(); return nullptr; @@ -7532,6 +7645,8 @@ const FormattedText *get_message_content_caption(const MessageContent *content) return &static_cast(content)->caption; case MessageContentType::Invoice: return static_cast(content)->input_invoice.get_caption(); + case MessageContentType::PaidMedia: + return &static_cast(content)->caption; case MessageContentType::Photo: return &static_cast(content)->caption; case MessageContentType::Video: @@ -7585,6 +7700,13 @@ int32 get_message_content_duration(const MessageContent *content, const Td *td) } case MessageContentType::Invoice: return static_cast(content)->input_invoice.get_duration(td); + case MessageContentType::PaidMedia: { + int32 result = -1; + for (auto &media : static_cast(content)->media) { + result = max(result, media.get_duration(td)); + } + return result; + } case MessageContentType::Video: { auto video_file_id = static_cast(content)->file_id; return td->videos_manager_->get_video_duration(video_file_id); @@ -7611,6 +7733,13 @@ int32 get_message_content_media_duration(const MessageContent *content, const Td } case MessageContentType::Invoice: return static_cast(content)->input_invoice.get_duration(td); + case MessageContentType::PaidMedia: { + int32 result = -1; + for (const auto &media : static_cast(content)->media) { + result = max(result, media.get_duration(td)); + } + return result; + } case MessageContentType::Story: { auto story_full_id = static_cast(content)->story_full_id; return td->story_manager_->get_story_duration(story_full_id); @@ -7814,6 +7943,13 @@ vector get_message_content_file_ids(const MessageContent *content, const case MessageContentType::Story: // story file references are repaired independently return {}; + case MessageContentType::PaidMedia: { + vector result; + for (const auto &media : static_cast(content)->media) { + media.append_file_ids(td, result); + } + return result; + } default: return {}; } @@ -7858,6 +7994,10 @@ string get_message_content_search_text(const Td *td, const MessageContent *conte const auto *invoice = static_cast(content); return invoice->input_invoice.get_caption()->text; } + case MessageContentType::PaidMedia: { + const auto *paid_media = static_cast(content); + return paid_media->caption.text; + } case MessageContentType::Photo: { const auto *photo = static_cast(content); return photo->caption.text; @@ -8004,6 +8144,15 @@ bool need_reget_message_content(const MessageContent *content) { const auto *m = static_cast(content); return m->input_invoice.need_reget(); } + case MessageContentType::PaidMedia: { + const auto *m = static_cast(content); + for (const auto &media : m->media) { + if (media.need_reget()) { + return true; + } + } + return false; + } default: return false; } @@ -8285,6 +8434,8 @@ void add_message_content_dependencies(Dependencies &dependencies, const MessageC break; case MessageContentType::DialogShared: break; + case MessageContentType::PaidMedia: + break; default: UNREACHABLE(); break; diff --git a/td/telegram/MessageContentType.cpp b/td/telegram/MessageContentType.cpp index 2675ea38d..39e44aadb 100644 --- a/td/telegram/MessageContentType.cpp +++ b/td/telegram/MessageContentType.cpp @@ -146,6 +146,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType cont return string_builder << "BoostApply"; case MessageContentType::DialogShared: return string_builder << "ChatShared"; + case MessageContentType::PaidMedia: + return string_builder << "PaidMedia"; default: return string_builder << "Invalid type " << static_cast(content_type); } @@ -232,6 +234,7 @@ bool is_allowed_media_group_content(MessageContentType content_type) { case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: case MessageContentType::DialogShared: + case MessageContentType::PaidMedia: return false; default: UNREACHABLE(); @@ -313,6 +316,7 @@ bool can_be_secret_message_content(MessageContentType content_type) { case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: case MessageContentType::DialogShared: + case MessageContentType::PaidMedia: return false; default: UNREACHABLE(); @@ -390,6 +394,7 @@ bool can_be_local_message_content(MessageContentType content_type) { case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: case MessageContentType::DialogShared: + case MessageContentType::PaidMedia: return false; default: UNREACHABLE(); @@ -414,6 +419,7 @@ bool is_service_message_content(MessageContentType content_type) { case MessageContentType::Invoice: case MessageContentType::LiveLocation: case MessageContentType::Location: + case MessageContentType::PaidMedia: case MessageContentType::Photo: case MessageContentType::Poll: case MessageContentType::Sticker: @@ -480,6 +486,7 @@ bool is_editable_message_content(MessageContentType content_type) { case MessageContentType::Audio: case MessageContentType::Document: case MessageContentType::Game: + case MessageContentType::PaidMedia: case MessageContentType::Photo: case MessageContentType::Text: case MessageContentType::Video: @@ -563,6 +570,7 @@ bool is_supported_reply_message_content(MessageContentType content_type) { case MessageContentType::GiveawayWinners: case MessageContentType::Invoice: case MessageContentType::Location: + case MessageContentType::PaidMedia: case MessageContentType::Photo: case MessageContentType::Poll: case MessageContentType::Sticker: @@ -619,6 +627,7 @@ bool can_have_message_content_caption(MessageContentType content_type) { case MessageContentType::Animation: case MessageContentType::Audio: case MessageContentType::Document: + case MessageContentType::PaidMedia: case MessageContentType::Photo: case MessageContentType::Video: case MessageContentType::VoiceNote: @@ -697,6 +706,7 @@ uint64 get_message_content_chain_id(MessageContentType content_type) { case MessageContentType::Audio: case MessageContentType::Document: case MessageContentType::Invoice: + case MessageContentType::PaidMedia: case MessageContentType::Photo: case MessageContentType::Sticker: case MessageContentType::Video: diff --git a/td/telegram/MessageContentType.h b/td/telegram/MessageContentType.h index 0818ad2d2..a8b67dc43 100644 --- a/td/telegram/MessageContentType.h +++ b/td/telegram/MessageContentType.h @@ -80,7 +80,8 @@ enum class MessageContentType : int32 { ExpiredVideoNote, ExpiredVoiceNote, BoostApply, - DialogShared + DialogShared, + PaidMedia }; // increase MessageUnsupported::CURRENT_VERSION each time a new message content type is added diff --git a/td/telegram/QuickReplyManager.cpp b/td/telegram/QuickReplyManager.cpp index a293b22af..33c38f39d 100644 --- a/td/telegram/QuickReplyManager.cpp +++ b/td/telegram/QuickReplyManager.cpp @@ -1092,7 +1092,8 @@ unique_ptr QuickReplyManager::create_messa auto content_type = content->get_type(); if (is_service_message_content(content_type) || content_type == MessageContentType::LiveLocation || - is_expired_message_content(content_type) || content_type == MessageContentType::Poll) { + is_expired_message_content(content_type) || content_type == MessageContentType::Poll || + content_type == MessageContentType::PaidMedia) { LOG(ERROR) << "Receive " << content_type << " from " << source; break; }