diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index e2078f7e6..7b0d52e5f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -677,6 +677,8 @@ PRIVATE history/view/media/history_view_game.h history/view/media/history_view_gif.cpp history/view/media/history_view_gif.h + history/view/media/history_view_giveaway.cpp + history/view/media/history_view_giveaway.h history/view/media/history_view_invoice.cpp history/view/media/history_view_invoice.h history/view/media/history_view_large_emoji.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5a7c0a7a1..1572839ca 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2076,13 +2076,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prize_title" = "Congratulations!"; "lng_prize_about" = "You won a prize in a giveaway organized by {channel}."; -"lng_prize_duration" = "Your prize is **Telegram Premium** subscription for {duration}."; +"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription for {duration}."; +"lng_prize_gift_about" = "You've received a gift from {channel}."; +"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription for {duration}."; "lng_prize_open" = "Open Gift Link"; "lng_prizes_title#one" = "Giveaway Prize"; "lng_prizes_title#other" = "Giveaway Prizes"; -"lng_prizes_about#one" = "{count} Telegram Premium Subscription for {duration}."; -"lng_prizes_about#other" = "{count} Telegram Premium Subscriptions for {duration}."; +"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription for {duration}."; +"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions for {duration}."; "lng_prizes_participants" = "Participants"; "lng_prizes_participants_all#one" = "All subscribers of the channel:"; "lng_prizes_participants_all#other" = "All subscribers of the channels:"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 31b478af9..affdd5524 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2134,15 +2134,16 @@ void ApiWrap::saveDraftsToCloud() { } auto flags = MTPmessages_SaveDraft::Flags(0); + auto replyFlags = MTPDinputReplyToMessage::Flags(0); auto &textWithTags = cloudDraft->textWithTags; if (cloudDraft->previewState != Data::PreviewState::Allowed) { flags |= MTPmessages_SaveDraft::Flag::f_no_webpage; } - if (cloudDraft->msgId) { - flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id; - } - if (cloudDraft->topicRootId) { - flags |= MTPmessages_SaveDraft::Flag::f_top_msg_id; + if (cloudDraft->msgId || cloudDraft->topicRootId) { + flags |= MTPmessages_SaveDraft::Flag::f_reply_to; + if (cloudDraft->topicRootId) { + replyFlags |= MTPDinputReplyToMessage::Flag::f_top_msg_id; + } } if (!textWithTags.tags.isEmpty()) { flags |= MTPmessages_SaveDraft::Flag::f_entities; @@ -2155,8 +2156,15 @@ void ApiWrap::saveDraftsToCloud() { history->startSavingCloudDraft(topicRootId); cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( MTP_flags(flags), - MTP_int(cloudDraft->msgId), - MTP_int(cloudDraft->topicRootId), + MTP_inputReplyToMessage( + MTP_flags(replyFlags), + MTP_int(cloudDraft->msgId + ? cloudDraft->msgId + : cloudDraft->topicRootId), + MTP_int(cloudDraft->topicRootId), + MTPInputPeer(), // reply_to_peer_id + MTPstring(), // quote_text + MTPVector()), // quote_entities history->peer->input, MTP_string(textWithTags.text), entities diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 00c9ba02d..f0b937ca8 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -64,7 +64,18 @@ void ApplyPeerCloudDraft( session, draft.ventities().value_or_empty())) }; - const auto replyTo = draft.vreply_to_msg_id().value_or_empty(); + auto replyTo = MsgId(); + if (const auto reply = draft.vreply_to()) { + reply->match([&](const MTPDmessageReplyHeader &data) { + if (!data.vreply_to_peer_id() + || (peerFromMTP(*data.vreply_to_peer_id()) == peerId)) { + replyTo = data.vreply_to_msg_id().value_or_empty(); + } else { + // #TODO replies + } + }, [&](const MTPDmessageReplyStoryHeader &data) { + }); + } auto cloudDraft = std::make_unique( textWithTags, replyTo, diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 4ca3cb635..7443aab62 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -45,12 +45,15 @@ MTPInputReplyTo ReplyToForMTP( } } else if (replyTo.msgId || replyTo.topicRootId) { using Flag = MTPDinputReplyToMessage::Flag; - return MTP_inputReplyToMessage( + return MTP_inputReplyToMessage( // #TODO replies (replyTo.topicRootId ? MTP_flags(Flag::f_top_msg_id) : MTP_flags(0)), MTP_int(replyTo.msgId ? replyTo.msgId : replyTo.topicRootId), - MTP_int(replyTo.topicRootId)); + MTP_int(replyTo.topicRootId), + MTPInputPeer(), // reply_to_peer_id + MTPstring(), // quote_text + MTPVector()); // quote_entities } return MTPInputReplyTo(); } diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 8d87fb77a..ca947219d 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_contact.h" #include "history/view/media/history_view_location.h" #include "history/view/media/history_view_game.h" +#include "history/view/media/history_view_giveaway.h" #include "history/view/media/history_view_invoice.h" #include "history/view/media/history_view_call.h" #include "history/view/media/history_view_web_page.h" @@ -361,6 +362,22 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) { return result; } +Giveaway ComputeGiveawayData( + not_null item, + const MTPDmessageMediaGiveaway &data) { + auto result = Giveaway{ + .untilDate = data.vuntil_date().v, + .quantity = data.vquantity().v, + .months = data.vmonths().v, + }; + result.channels.reserve(data.vchannels().v.size()); + const auto owner = &item->history()->owner(); + for (const auto &id : data.vchannels().v) { + result.channels.push_back(owner->channel(ChannelId(id))); + } + return result; +} + Media::Media(not_null parent) : _parent(parent) { } @@ -420,6 +437,10 @@ bool Media::storyMention() const { return false; } +const Giveaway *Media::giveaway() const { + return nullptr; +} + bool Media::uploading() const { return false; } @@ -2144,4 +2165,50 @@ std::unique_ptr MediaStory::createView( } } +MediaGiveaway::MediaGiveaway( + not_null parent, + const Giveaway &data) +: Media(parent) +, _giveaway(data) { +} + +std::unique_ptr MediaGiveaway::clone(not_null parent) { + return std::make_unique(parent, _giveaway); +} + +const Giveaway *MediaGiveaway::giveaway() const { + return &_giveaway; +} + +TextWithEntities MediaGiveaway::notificationText() const { + return { + .text = tr::lng_prizes_title(tr::now, lt_count, _giveaway.quantity), + }; +} + +QString MediaGiveaway::pinnedTextSubstring() const { + return QString::fromUtf8("\xC2\xAB") + + notificationText().text + + QString::fromUtf8("\xC2\xBB"); +} + +TextForMimeData MediaGiveaway::clipboardText() const { + return TextForMimeData(); +} + +bool MediaGiveaway::updateInlineResultMedia(const MTPMessageMedia &media) { + return true; +} + +bool MediaGiveaway::updateSentMedia(const MTPMessageMedia &media) { + return true; +} + +std::unique_ptr MediaGiveaway::createView( + not_null message, + not_null realParent, + HistoryView::Element *replacing) { + return std::make_unique(message, &_giveaway); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 7649a37ed..0ab5f51a8 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -90,6 +90,14 @@ struct Invoice { bool isTest = false; }; +struct Giveaway { + std::vector> channels; + TimeId untilDate = 0; + int quantity = 0; + int months = 0; + bool all = false; +}; + class Media { public: Media(not_null parent); @@ -116,6 +124,7 @@ public: virtual FullStoryId storyId() const; virtual bool storyExpired(bool revalidate = false); virtual bool storyMention() const; + virtual const Giveaway *giveaway() const; virtual bool uploading() const; virtual Storage::SharedMediaTypesMask sharedMediaTypes() const; @@ -605,6 +614,32 @@ private: }; +class MediaGiveaway final : public Media { +public: + MediaGiveaway( + not_null parent, + const Giveaway &data); + + std::unique_ptr clone(not_null parent) override; + + const Giveaway *giveaway() const override; + + TextWithEntities notificationText() const override; + QString pinnedTextSubstring() const override; + TextForMimeData clipboardText() const override; + + bool updateInlineResultMedia(const MTPMessageMedia &media) override; + bool updateSentMedia(const MTPMessageMedia &media) override; + std::unique_ptr createView( + not_null message, + not_null realParent, + HistoryView::Element *replacing = nullptr) override; + +private: + Giveaway _giveaway; + +}; + [[nodiscard]] TextForMimeData WithCaptionClipboardText( const QString &attachType, TextForMimeData &&caption); @@ -615,4 +650,8 @@ private: [[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call); +[[nodiscard]] Giveaway ComputeGiveawayData( + not_null item, + const MTPDmessageMediaGiveaway &data); + } // namespace Data diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 723eeae9b..6420c3759 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -576,6 +576,18 @@ Poll ParsePoll(const MTPDmessageMediaPoll &data) { return result; } +Giveaway ParseGiveaway(const MTPDmessageMediaGiveaway &data) { + auto result = Giveaway{ + .untilDate = data.vuntil_date().v, + .quantity = data.vquantity().v, + .months = data.vmonths().v, + }; + for (const auto &id : data.vchannels().v) { + result.channels.push_back(ChannelId(id)); + } + return result; +} + UserpicsSlice ParseUserpicsSlice( const MTPVector &data, int baseIndex) { @@ -1057,7 +1069,9 @@ Media ParseMedia( }, [](const MTPDmessageMediaDice &data) { // #TODO dice }, [](const MTPDmessageMediaStory &data) { - // #TODO stories export + // #TODO export stories + }, [&](const MTPDmessageMediaGiveaway &data) { + result.content = ParseGiveaway(data); }, [](const MTPDmessageMediaEmpty &data) {}); return result; } @@ -1298,6 +1312,14 @@ ServiceAction ParseServiceAction( content.peerId = ParsePeerId(data.vpeer()); content.buttonId = data.vbutton_id().v; result.content = content; + }, [&](const MTPDmessageActionGiftCode &data) { + auto content = ActionGiftCode(); + content.boostPeerId = data.vboost_peer() + ? peerFromMTP(*data.vboost_peer()) + : PeerId(); + content.viaGiveaway = data.is_via_giveaway(); + content.months = data.vmonths().v; + content.code = data.vslug().v; }, [](const MTPDmessageActionEmpty &data) {}); return result; } @@ -1358,15 +1380,19 @@ Message ParseMessage( } if (const auto replyTo = data.vreply_to()) { replyTo->match([&](const MTPDmessageReplyHeader &data) { - result.replyToMsgId = data.vreply_to_msg_id().v; - result.replyToPeerId = data.vreply_to_peer_id() - ? ParsePeerId(*data.vreply_to_peer_id()) - : 0; - if (result.replyToPeerId == result.peerId) { - result.replyToPeerId = 0; + if (const auto replyToMsg = data.vreply_to_msg_id()) { + result.replyToMsgId = replyToMsg->v; + result.replyToPeerId = data.vreply_to_peer_id() + ? ParsePeerId(*data.vreply_to_peer_id()) + : 0; + if (result.replyToPeerId == result.peerId) { + result.replyToPeerId = 0; + } + } else { + // #TODO export replies } }, [&](const MTPDmessageReplyStoryHeader &data) { - // #TODO stories export + // #TODO export stories }); } } @@ -1410,12 +1436,16 @@ Message ParseMessage( } if (const auto replyTo = data.vreply_to()) { replyTo->match([&](const MTPDmessageReplyHeader &data) { - result.replyToMsgId = data.vreply_to_msg_id().v; - result.replyToPeerId = data.vreply_to_peer_id() - ? ParsePeerId(*data.vreply_to_peer_id()) - : PeerId(0); + if (const auto replyToMsg = data.vreply_to_msg_id()) { + result.replyToMsgId = replyToMsg->v; + result.replyToPeerId = data.vreply_to_peer_id() + ? ParsePeerId(*data.vreply_to_peer_id()) + : PeerId(0); + } else { + // #TODO export replies + } }, [&](const MTPDmessageReplyStoryHeader &data) { - // #TODO stories export + // #TODO export stories }); } if (const auto viaBotId = data.vvia_bot_id()) { diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 4635ea269..a4fd372f4 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -196,6 +196,13 @@ struct Poll { bool closed = false; }; +struct Giveaway { + std::vector channels; + TimeId untilDate = 0; + int quantity = 0; + int months = 0; +}; + struct UserpicsSlice { std::vector list; }; @@ -325,6 +332,7 @@ struct Media { Game, Invoice, Poll, + Giveaway, UnsupportedMedia> content; TimeId ttl = 0; @@ -527,6 +535,13 @@ struct ActionSetChatWallPaper { struct ActionSetSameChatWallPaper { }; +struct ActionGiftCode { + QByteArray code; + PeerId boostPeerId = 0; + int months = 0; + bool viaGiveaway = false; +}; + struct ActionRequestedPeer { PeerId peerId = 0; int buttonId = 0; @@ -570,7 +585,8 @@ struct ServiceAction { ActionSuggestProfilePhoto, ActionRequestedPeer, ActionSetChatWallPaper, - ActionSetSameChatWallPaper> content; + ActionSetSameChatWallPaper, + ActionGiftCode> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index d4581c737..54dd8e33f 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -611,6 +611,9 @@ private: const Data::Photo &data, const QString &basePath); [[nodiscard]] QByteArray pushPoll(const Data::Poll &data); + [[nodiscard]] QByteArray pushGiveaway( + const PeersMap &peers, + const Data::Giveaway &data); File _file; QByteArray _composedStart; @@ -1276,6 +1279,16 @@ auto HtmlWriter::Wrap::pushMessage( + " set " + wrapReplyToLink("the same background") + " for this chat"; + }, [&](const ActionGiftCode &data) { + return data.viaGiveaway + ? ("You won a Telegram Premium for " + + NumberToString(data.months) + + (data.months > 1 ? " months" : "month") + + " prize in a giveaway organized by a channel.") + : ("You've received a Telegram Premium for " + + NumberToString(data.months) + + (data.months > 1 ? " months" : "month") + + " gift from a channel."); }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { @@ -1459,8 +1472,9 @@ QByteArray HtmlWriter::Wrap::pushMedia( if (!data.classes.isEmpty()) { return pushGenericMedia(data); } + using namespace Data; const auto &content = message.media.content; - if (const auto document = std::get_if(&content)) { + if (const auto document = std::get_if(&content)) { Assert(!message.media.ttl); if (document->isSticker) { return pushStickerMedia(*document, basePath); @@ -1470,11 +1484,13 @@ QByteArray HtmlWriter::Wrap::pushMedia( return pushVideoFileMedia(*document, basePath); } Unexpected("Non generic document in HtmlWriter::Wrap::pushMedia."); - } else if (const auto photo = std::get_if(&content)) { + } else if (const auto photo = std::get_if(&content)) { Assert(!message.media.ttl); return pushPhotoMedia(*photo, basePath); - } else if (const auto poll = std::get_if(&content)) { + } else if (const auto poll = std::get_if(&content)) { return pushPoll(*poll); + } else if (const auto giveaway = std::get_if(&content)) { + return pushGiveaway(peers, *giveaway); } Assert(v::is_null(content)); return QByteArray(); @@ -1796,6 +1812,52 @@ QByteArray HtmlWriter::Wrap::pushPoll(const Data::Poll &data) { return result; } +QByteArray HtmlWriter::Wrap::pushGiveaway( + const PeersMap &peers, + const Data::Giveaway &data) { + auto result = pushDiv("media_wrap clearfix"); + result.append(pushDiv("media_giveaway")); + + result.append(pushDiv("section_title bold")); + result.append(SerializeString("Giveaway Prizes")); + result.append(popTag()); + result.append(pushDiv("section_body")); + result.append("" + + Data::NumberToString(data.quantity) + + " " + + SerializeString((data.quantity > 1) + ? "Telegram Premium Subscriptions" + : "Telegram Premium Subscription") + + " for " + Data::NumberToString(data.months) + " " + + (data.months > 1 ? "months." : "month.")); + result.append(popTag()); + + result.append(pushDiv("section_title bold")); + result.append(SerializeString("Participants")); + result.append(popTag()); + result.append(pushDiv("section_body")); + auto channels = QByteArrayList(); + for (const auto &channel : data.channels) { + channels.append("" + peers.wrapPeerName(channel) + ""); + } + result.append(SerializeString((channels.size() > 1) + ? "All subscribers of those channels: " + : "All subscribers of the channel: ") + + channels.join(", ")); + result.append(popTag()); + + result.append(pushDiv("section_title bold")); + result.append(SerializeString("Winners Selection Date")); + result.append(popTag()); + result.append(pushDiv("section_body")); + result.append(Data::FormatDateTime(data.untilDate)); + result.append(popTag()); + + result.append(popTag()); + result.append(popTag()); + return result; +} + MediaData HtmlWriter::Wrap::prepareMediaData( const Data::Message &message, const QString &basePath, @@ -1954,6 +2016,7 @@ MediaData HtmlWriter::Wrap::prepareMediaData( result.description = data.description; result.status = Data::FormatMoneyAmount(data.amount, data.currency); }, [](const Poll &data) { + }, [](const Giveaway &data) { }, [](const UnsupportedMedia &data) { Unexpected("Unsupported message."); }, [](v::null_t) {}); diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index a232b2788..8655eeeec 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -287,11 +287,12 @@ QByteArray SerializeMessage( } const auto push = [&](const QByteArray &key, const auto &value) { - if constexpr (std::is_arithmetic_v>) { + using V = std::decay_t; + if constexpr (std::is_same_v) { + pushBare(key, value ? "true" : "false"); + } else if constexpr (std::is_arithmetic_v) { pushBare(key, Data::NumberToString(value)); - } else if constexpr (std::is_same_v< - std::decay_t, - PeerId>) { + } else if constexpr (std::is_same_v) { if (const auto chat = peerToChat(value)) { pushBare( key, @@ -592,6 +593,14 @@ QByteArray SerializeMessage( pushAction("requested_peer"); push("button_id", data.buttonId); push("peer_id", data.peerId.value); + }, [&](const ActionGiftCode &data) { + pushAction("gift_code_prize"); + push("gift_code", data.code); + if (data.boostPeerId) { + push("boost_peer_id", data.boostPeerId); + } + push("months", data.months); + push("via_giveaway", data.viaGiveaway); }, [&](const ActionSetChatWallPaper &data) { pushActor(); pushAction("set_chat_wallpaper"); @@ -738,6 +747,22 @@ QByteArray SerializeMessage( { "total_voters", NumberToString(data.totalVotes) }, { "answers", serialized } })); + }, [&](const Giveaway &data) { + context.nesting.push_back(Context::kObject); + const auto channels = ranges::views::all( + data.channels + ) | ranges::views::transform([&](ChannelId id) { + return NumberToString(id.bare); + }) | ranges::to_vector; + const auto serialized = SerializeArray(context, channels); + context.nesting.pop_back(); + + push("giveaway_information", SerializeObject(context, { + { "quantity", NumberToString(data.quantity) }, + { "months", NumberToString(data.months) }, + { "until_date", SerializeDate(data.untilDate) }, + { "channels", serialized }, + })); }, [](const UnsupportedMedia &data) { Unexpected("Unsupported message."); }, [](v::null_t) {}); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 5b75925d8..6ff35aea5 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1127,8 +1127,8 @@ void History::applyServiceChanges( }, [&](const MTPDmessageActionPinMessage &data) { if (replyTo) { replyTo->match([&](const MTPDmessageReplyHeader &data) { - const auto id = data.vreply_to_msg_id().v; - if (item) { + const auto id = data.vreply_to_msg_id().value_or_empty(); + if (id && item) { session().storage().add(Storage::SharedMediaAddSlice( peer->id, MsgId(0), diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index f5d04718d..e84631f23 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -291,6 +291,10 @@ std::unique_ptr HistoryItem::CreateMedia( peerFromMTP(media.vpeer()), media.vid().v, }, media.is_via_mention()); + }, [&](const MTPDmessageMediaGiveaway &media) -> Result { + return std::make_unique( + item, + Data::ComputeGiveawayData(item, media)); }, [](const MTPDmessageMediaEmpty &) -> Result { return nullptr; }, [](const MTPDmessageMediaUnsupported &) -> Result { @@ -1621,11 +1625,18 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) { setForwardsCount(data.vforwards().value_or(-1)); if (const auto reply = data.vreply_to()) { reply->match([&](const MTPDmessageReplyHeader &data) { - setReplyFields( - data.vreply_to_msg_id().v, - data.vreply_to_top_id().value_or( - data.vreply_to_msg_id().v), - data.is_forum_topic()); + // #TODO replies + const auto replyToPeer = data.vreply_to_peer_id() + ? peerFromMTP(*data.vreply_to_peer_id()) + : PeerId(); + if (!replyToPeer || replyToPeer == history()->peer->id) { + if (const auto replyToId = data.vreply_to_msg_id()) { + setReplyFields( + replyToId->v, + data.vreply_to_top_id().value_or(replyToId->v), + data.is_forum_topic()); + } + } }, [](const MTPDmessageReplyStoryHeader &data) { }); } @@ -3382,18 +3393,21 @@ void HistoryItem::createComponents(const MTPDmessage &data) { } if (const auto reply = data.vreply_to()) { reply->match([&](const MTPDmessageReplyHeader &data) { - if (const auto peer = data.vreply_to_peer_id()) { - config.replyToPeer = peerFromMTP(*peer); - if (config.replyToPeer == _history->peer->id) { - config.replyToPeer = 0; + // #TODO replies + if (const auto id = data.vreply_to_msg_id().value_or_empty()) { + if (const auto peer = data.vreply_to_peer_id()) { + config.replyToPeer = peerFromMTP(*peer); + if (config.replyToPeer == _history->peer->id) { + config.replyToPeer = 0; + } } + const auto owner = &_history->owner(); + config.replyTo = data.is_reply_to_scheduled() + ? owner->scheduledMessages().localMessageId(id) + : id; + config.replyToTop = data.vreply_to_top_id().value_or(id); + config.replyIsTopicPost = data.is_forum_topic(); } - const auto id = data.vreply_to_msg_id().v; - config.replyTo = data.is_reply_to_scheduled() - ? _history->owner().scheduledMessages().localMessageId(id) - : id; - config.replyToTop = data.vreply_to_top_id().value_or(id); - config.replyIsTopicPost = data.is_forum_topic(); }, [&](const MTPDmessageReplyStoryHeader &data) { config.replyToPeer = peerFromUser(data.vuser_id()); config.replyToStory = data.vstory_id().v; @@ -3631,21 +3645,23 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { ? peerFromMTP(*data.vreply_to_peer_id()) : _history->peer->id; if (const auto dependent = GetServiceDependentData()) { - dependent->peerId = (peerId != _history->peer->id) - ? peerId - : 0; - const auto id = data.vreply_to_msg_id().v; - dependent->msgId = id; - dependent->topId = data.vreply_to_top_id().value_or(id); - dependent->topicPost = data.is_forum_topic() - || Has(); - if (!updateServiceDependent()) { - RequestDependentMessageItem( - this, - (dependent->peerId - ? dependent->peerId - : _history->peer->id), - dependent->msgId); + const auto id = data.vreply_to_msg_id().value_or_empty(); + if (id) { + dependent->peerId = (peerId != _history->peer->id) + ? peerId + : 0; + dependent->msgId = id; + dependent->topId = data.vreply_to_top_id().value_or(id); + dependent->topicPost = data.is_forum_topic() + || Has(); + if (!updateServiceDependent()) { + RequestDependentMessageItem( + this, + (dependent->peerId + ? dependent->peerId + : _history->peer->id), + dependent->msgId); + } } } }, [](const MTPDmessageReplyStoryHeader &data) { @@ -4426,6 +4442,25 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto prepareGiftCode = [&](const MTPDmessageActionGiftCode &action) { + auto result = PreparedServiceText(); + _history->session().giftBoxStickersPacks().load(); + const auto months = action.vmonths().v; + + result.text = { + (action.is_via_giveaway() + ? tr::lng_prize_about + : tr::lng_prize_gift_about)( + tr::now, + lt_channel, + (action.vboost_peer() + ? _from->owner().peer( + peerFromMTP(*action.vboost_peer()))->name() + : "a channel")), + }; + return result; + }; + setServiceText(action.match([&]( const MTPDmessageActionChatAddUser &data) { return prepareChatAddUserText(data); @@ -4506,6 +4541,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return prepareSetChatWallPaper(data); }, [&](const MTPDmessageActionSetSameChatWallPaper &data) { return prepareSetSameChatWallPaper(data); + }, [&](const MTPDmessageActionGiftCode &data) { + return prepareGiftCode(data); }, [](const MTPDmessageActionEmpty &) { return PreparedServiceText{ { tr::lng_message_empty(tr::now) } }; })); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index c89788248..4129df0ad 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -370,13 +370,19 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { : Flag(0))), MTP_int(replyTo.msgId), MTPPeer(), - MTP_int(replyToTop)); + MTPMessageFwdHeader(), // reply_header + MTP_int(replyToTop), + MTPstring(), // quote_text + MTPVector()); // quote_entities } return MTP_messageReplyHeader( MTP_flags(0), MTP_int(replyTo.msgId), - MTPPeer(), - MTPint()); + MTPPeer(), // reply_to_peer_id + MTPMessageFwdHeader(), // reply_header + MTPint(), // reply_to_top_id + MTPstring(), // quote_text + MTPVector()); // quote_entities } return MTPMessageReplyHeader(); } @@ -453,6 +459,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { return data.is_via_mention() ? Result::HasStoryMention : Result::Good; + }, [](const MTPDmessageMediaGiveaway &) { + return Result::Good; }, [](const MTPDmessageMediaUnsupported &) { return Result::Unsupported; }); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp new file mode 100644 index 000000000..3835a1a3b --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -0,0 +1,298 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/media/history_view_giveaway.h" + +#include "base/unixtime.h" +#include "data/data_channel.h" +#include "data/data_media_types.h" +#include "lang/lang_keys.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "ui/chat/chat_style.h" +#include "ui/text/text_utilities.h" +#include "styles/style_chat.h" + +namespace HistoryView { + +void TextRows::add(Ui::Text::String text, int skipTop) { +} + +bool TextRows::isEmpty() const { + return _rows.empty() + || (_rows.size() == 1 && _rows.front().text.isEmpty()); +} + +int TextRows::maxWidth() const { + return 0; +} + +int TextRows::minHeight() const { + return 0; +} + +int TextRows::countHeight(int newWidth) const { + return 0; +} + +int TextRows::length() const { + return 0; +} + +TextSelection UnshiftItemSelection( + TextSelection selection, + const TextRows &byText) { + return UnshiftItemSelection(selection, byText.length()); +} + +TextSelection ShiftItemSelection( + TextSelection selection, + const TextRows &byText) { + return ShiftItemSelection(selection, byText.length()); +} + +Giveaway::Giveaway( + not_null parent, + not_null giveaway) +: Media(parent) { + fillFromData(giveaway); +} + +Giveaway::~Giveaway() = default; + +void Giveaway::fillFromData(not_null giveaway) { + _rows.add(Ui::Text::String( + st::semiboldTextStyle, + tr::lng_prizes_title(tr::now, lt_count, giveaway->quantity), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayPrizesTop); + + const auto duration = (giveaway->months < 12) + ? tr::lng_months(tr::now, lt_count, giveaway->months) + : tr::lng_years(tr::now, lt_count, giveaway->months / 12); + _rows.add(Ui::Text::String( + st::defaultTextStyle, + tr::lng_prizes_about( + tr::now, + lt_count, + giveaway->quantity, + lt_duration, + Ui::Text::Bold(duration), + Ui::Text::RichLangValue), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayPrizesSkip); + + _rows.add(Ui::Text::String( + st::semiboldTextStyle, + tr::lng_prizes_participants(tr::now), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayParticipantsTop); + + for (const auto &channel : giveaway->channels) { + _channels.push_back({ Ui::Text::String( + st::semiboldTextStyle, + channel->name(), + kDefaultTextOptions, + st::msgMinWidth) + }); + } + + const auto channels = int(_channels.size()); + _rows.add(Ui::Text::String( + st::defaultTextStyle, + (giveaway->all + ? tr::lng_prizes_participants_all + : tr::lng_prizes_participants_new)(tr::now, lt_count, channels), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayParticipantsSkip); + + _date.add(Ui::Text::String( + st::semiboldTextStyle, + tr::lng_prizes_date(tr::now), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayDateTop); + + _rows.add(Ui::Text::String( + st::defaultTextStyle, + langDateTime(base::unixtime::parse(giveaway->untilDate)), + kDefaultTextOptions, + st::msgMinWidth), st::chatGiveawayDateSkip); +} + +QSize Giveaway::countOptimalSize() { + // init dimensions + auto skipBlockWidth = _parent->skipBlockWidth(); + auto maxWidth = skipBlockWidth; + auto minHeight = 0; + + accumulate_max(maxWidth, _rows.maxWidth()); + minHeight += _rows.minHeight(); + + //minHeight += + + accumulate_max(maxWidth, _date.maxWidth()); + minHeight += _date.minHeight(); + + auto padding = inBubblePadding(); + maxWidth += padding.left() + padding.right(); + minHeight += padding.top() + padding.bottom(); + return { maxWidth, minHeight }; +} + +QSize Giveaway::countCurrentSize(int newWidth) { + accumulate_min(newWidth, maxWidth()); + auto innerWidth = newWidth + - st::msgPadding.left() + - st::msgPadding.right(); + + auto newHeight = 0; + _rowsHeight = _rows.countHeight(innerWidth); + newHeight += _rowsHeight; + + //newHeight += + + newHeight += _date.countHeight(innerWidth); + _dateHeight = _date.minHeight(); + newHeight += _dateHeight; + + auto padding = inBubblePadding(); + newHeight += padding.top() + padding.bottom(); + + return { newWidth, newHeight }; +} + +TextSelection Giveaway::toDateSelection(TextSelection selection) const { + return UnshiftItemSelection(selection, _rows); +} + +TextSelection Giveaway::fromDateSelection(TextSelection selection) const { + return ShiftItemSelection(selection, _rows); +} + +void Giveaway::draw(Painter &p, const PaintContext &context) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + const auto st = context.st; + const auto sti = context.imageStyle(); + const auto stm = context.messageStyle(); + + auto &semibold = stm->msgServiceFg; + + auto padding = inBubblePadding(); + auto tshift = padding.top(); + + //_rows.draw(p, { + // .position = { padding.left(), tshift }, + // .outerWidth = width(), + // .availableWidth = paintw, + // .now = context.now, + // .selection = context.selection, + //}); + //tshift += _rows.countHeight(paintw); + + //_date.draw(p, { + // .position = { padding.left(), tshift }, + // .outerWidth = width(), + // .availableWidth = paintw, + // .now = context.now, + // .selection = toDateSelection(context.selection), + //}); +} + +TextState Giveaway::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + + auto padding = inBubblePadding(); + auto tshift = padding.top(); + auto bshift = padding.bottom(); + + auto symbolAdd = 0; + if (_rowsHeight > 0) { + if (point.y() >= tshift && point.y() < tshift + _rowsHeight) { + //result = TextState(_parent, _rows.getState( + // point - QPoint(padding.left(), tshift), + // paintw, + // width(), + // request.forText())); + } else if (point.y() >= tshift + _rowsHeight) { + symbolAdd += _rows.length(); + } + tshift += _rowsHeight; + } + if (_channelsHeight > 0) { + tshift += _channelsHeight; + } + if (_dateHeight > 0) { + if (point.y() >= tshift && point.y() < tshift + _dateHeight) { + //result = TextState(_parent, _date.getState( + // point - QPoint(padding.left(), tshift), + // paintw, + // width(), + // request.forText())); + } else if (point.y() >= tshift + _dateHeight) { + symbolAdd += _date.length(); + } + tshift += _dateHeight; + } + result.symbol += symbolAdd; + return result; +} + +TextSelection Giveaway::adjustSelection( + TextSelection selection, + TextSelectType type) const { + //if (_date.isEmpty() || selection.to <= _rows.length()) { + // return _rows.adjustSelection(selection, type); + //} + //const auto dateSelection = _date.adjustSelection( + // toDateSelection(selection), + // type); + //if (selection.from >= _rows.length()) { + // return fromDateSelection(dateSelection); + //} + //const auto rowsSelection = _rows.adjustSelection(selection, type); + //return { rowsSelection.from, fromDateSelection(dateSelection).to }; + return selection; +} + +bool Giveaway::hasHeavyPart() const { + return false; +} + +void Giveaway::unloadHeavyPart() { +} + +uint16 Giveaway::fullSelectionLength() const { + return 0; +} + +TextForMimeData Giveaway::selectedText(TextSelection selection) const { + //auto rowsResult = _rows.toTextForMimeData(selection); + //auto dateResult = _date.toTextForMimeData(toDateSelection(selection)); + //if (rowsResult.empty()) { + // return dateResult; + //} else if (dateResult.empty()) { + // return rowsResult; + //} + //return rowsResult.append('\n').append(std::move(dateResult)); + return {}; +} + +QMargins Giveaway::inBubblePadding() const { + auto lshift = st::msgPadding.left(); + auto rshift = st::msgPadding.right(); + auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip; + auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip; + return QMargins(lshift, tshift, rshift, bshift); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h new file mode 100644 index 000000000..17dcdbebf --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -0,0 +1,102 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "history/view/media/history_view_media.h" + +namespace Data { +struct Giveaway; +} // namespace Data + +namespace HistoryView { + +class TextRows final { +public: + void add(Ui::Text::String text, int skipTop); + + [[nodiscard]] bool isEmpty() const; + [[nodiscard]] int maxWidth() const; + [[nodiscard]] int minHeight() const; + + [[nodiscard]] int countHeight(int newWidth) const; + + [[nodiscard]] int length() const; + +private: + struct Row { + Ui::Text::String text; + int skipTop = 0; + }; + std::vector _rows; +}; + +[[nodiscard]] TextSelection UnshiftItemSelection( + TextSelection selection, + const TextRows &byText); +[[nodiscard]] TextSelection ShiftItemSelection( + TextSelection selection, + const TextRows &byText); + +class Giveaway final : public Media { +public: + Giveaway( + not_null parent, + not_null giveaway); + ~Giveaway(); + + void draw(Painter &p, const PaintContext &context) const override; + TextState textState(QPoint point, StateRequest request) const override; + + bool needsBubble() const override { + return true; + } + bool customInfoLayout() const override { + return false; + } + + bool toggleSelectionByHandlerClick( + const ClickHandlerPtr &p) const override { + return true; + } + bool dragItemByHandler(const ClickHandlerPtr &p) const override { + return true; + } + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override; + uint16 fullSelectionLength() const override; + TextForMimeData selectedText(TextSelection selection) const override; + + void unloadHeavyPart() override; + bool hasHeavyPart() const override; + +private: + struct Channel { + Ui::Text::String name; + }; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + void fillFromData(not_null giveaway); + + TextSelection toDateSelection(TextSelection selection) const; + TextSelection fromDateSelection(TextSelection selection) const; + QMargins inBubblePadding() const; + + TextRows _rows; + std::vector _channels; + TextRows _date; + int _rowsHeight = 0; + int _channelsHeight = 0; + int _dateHeight = 0; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 4aede409a..8d720264c 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -130,6 +130,7 @@ messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia; +messageMediaGiveaway#4291677c flags:# only_new_subscribers:flags.0?true channels:Vector quantity:int months:int until_date:int = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; @@ -170,6 +171,7 @@ messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; messageActionSetChatWallPaper#bc44a927 wallpaper:WallPaper = MessageAction; messageActionSetSameChatWallPaper#c0787d6d wallpaper:WallPaper = MessageAction; +messageActionGiftCode#d2cfdb0e flags:# via_giveaway:flags.0?true boost_peer:flags.1?Peer months:int slug:string = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -755,7 +757,7 @@ contacts.topPeers#70b772a8 categories:Vector chats:Vector< contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; -draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; +draftMessage#95b0528b flags:# no_webpage:flags.1?true reply_to:flags.4?MessageReplyHeader message:string entities:flags.3?Vector date:int = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; @@ -1256,7 +1258,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; -messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; +messageReplyHeader#3d5c1693 flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_header:flags.5?MessageFwdHeader reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector = MessageReplyHeader; messageReplyStoryHeader#9c98bfc1 user_id:long story_id:int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1406,6 +1408,7 @@ attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType; inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice; inputInvoiceSlug#c326caef slug:string = InputInvoice; +inputInvoicePremiumGiftCode#98986c0d purpose:InputStorePaymentPurpose option:PremiumGiftCodeOption = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1416,6 +1419,7 @@ help.premiumPromo#5334759c status_text:string status_entities:Vector boost_peer:flags.0?InputPeer currency:string amount:long = InputStorePaymentPurpose; +inputStorePaymentPremiumGiveaway#e94a2529 flags:# only_new_subscribers:flags.0?true boost_peer:InputPeer additional_peers:flags.1?Vector random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1547,7 +1551,7 @@ stories.storyViewsList#46e9b9ec flags:# count:int reactions_count:int views:Vect stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews; -inputReplyToMessage#9c5386e4 flags:# reply_to_msg_id:int top_msg_id:flags.0?int = InputReplyTo; +inputReplyToMessage#73ec805 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector = InputReplyTo; inputReplyToStory#15b0f283 user_id:InputUser story_id:int = InputReplyTo; exportedStoryLink#3fc9053b link:string = ExportedStoryLink; @@ -1576,9 +1580,12 @@ stories.boostersList#f3dd3d1d flags:# count:int boosters:Vector next_of messages.webPage#fd5e12bd webpage:WebPage chats:Vector users:Vector = messages.WebPage; -premiumGiftCodeOption#d579436c flags:# users:int months:int store_product:flags.0?string = PremiumGiftCodeOption; +premiumGiftCodeOption#257e962b flags:# users:int months:int store_product:flags.0?string store_quantity:flags.1?int currency:string amount:long = PremiumGiftCodeOption; -payments.checkedGiftCode#ff70298c flags:# from_id:Peer to_id:flags.0?long date:int months:int used_date:flags.1?int chats:Vector users:Vector = payments.CheckedGiftCode; +payments.checkedGiftCode#b722f158 flags:# via_giveaway:flags.2?true from_id:Peer giveaway_msg_id:flags.3?int to_id:flags.0?long date:int months:int used_date:flags.1?int chats:Vector users:Vector = payments.CheckedGiftCode; + +payments.giveawayInfo#7a7bdc5a flags:# participating:flags.0?true preparing_results:flags.3?true joined_too_early_date:flags.1?int admin_disallowed_chat_id:flags.2?long = payments.GiveawayInfo; +payments.giveawayInfoResults#38c32424 flags:# winner:flags.0?true refunded:flags.1?true gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int = payments.GiveawayInfo; ---functions--- @@ -1793,7 +1800,7 @@ messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputB messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; -messages.saveDraft#b4331e3f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int top_msg_id:flags.2?int peer:InputPeer message:string entities:flags.3?Vector = Bool; +messages.saveDraft#64a3026c flags:# no_webpage:flags.1?true reply_to:flags.0?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; @@ -2051,6 +2058,7 @@ payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool; payments.getPremiumGiftCodeOptions#2757ba54 flags:# boost_peer:flags.0?InputPeer = Vector; payments.checkGiftCode#8e51b4c1 slug:string = payments.CheckedGiftCode; payments.applyGiftCode#f6e26854 slug:string = Updates; +payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayInfo; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index f13781bfe..2b16826ce 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -931,3 +931,10 @@ storyMentionUnreadStrokeTwice: 6px; storyMentionReadSkipTwice: 7px; storyMentionReadStrokeTwice: 3px; storyMentionButtonSkip: 5px; + +chatGiveawayPrizesTop: 4px; +chatGiveawayPrizesSkip: 4px; +chatGiveawayParticipantsTop: 16px; +chatGiveawayParticipantsSkip: 4px; +chatGiveawayDateTop: 16px; +chatGiveawayDateSkip: 4px;