Update API scheme on layer 166.

This commit is contained in:
John Preston 2023-10-03 17:52:33 +04:00
parent 744c1b925e
commit 926aae6847
18 changed files with 801 additions and 75 deletions

View File

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

View File

@ -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:";

View File

@ -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<MTPMessageEntity>()), // quote_entities
history->peer->input,
MTP_string(textWithTags.text),
entities

View File

@ -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<Draft>(
textWithTags,
replyTo,

View File

@ -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<MTPMessageEntity>()); // quote_entities
}
return MTPInputReplyTo();
}

View File

@ -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<HistoryItem*> 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<HistoryItem*> 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<HistoryView::Media> MediaStory::createView(
}
}
MediaGiveaway::MediaGiveaway(
not_null<HistoryItem*> parent,
const Giveaway &data)
: Media(parent)
, _giveaway(data) {
}
std::unique_ptr<Media> MediaGiveaway::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaGiveaway>(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<HistoryView::Media> MediaGiveaway::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
return std::make_unique<HistoryView::Giveaway>(message, &_giveaway);
}
} // namespace Data

View File

@ -90,6 +90,14 @@ struct Invoice {
bool isTest = false;
};
struct Giveaway {
std::vector<not_null<ChannelData*>> channels;
TimeId untilDate = 0;
int quantity = 0;
int months = 0;
bool all = false;
};
class Media {
public:
Media(not_null<HistoryItem*> 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<HistoryItem*> parent,
const Giveaway &data);
std::unique_ptr<Media> clone(not_null<HistoryItem*> 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<HistoryView::Media> createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> 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<HistoryItem*> item,
const MTPDmessageMediaGiveaway &data);
} // namespace Data

View File

@ -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<MTPPhoto> &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()) {

View File

@ -196,6 +196,13 @@ struct Poll {
bool closed = false;
};
struct Giveaway {
std::vector<ChannelId> channels;
TimeId untilDate = 0;
int quantity = 0;
int months = 0;
};
struct UserpicsSlice {
std::vector<Photo> 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(

View File

@ -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<Data::Document>(&content)) {
if (const auto document = std::get_if<Document>(&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<Data::Photo>(&content)) {
} else if (const auto photo = std::get_if<Photo>(&content)) {
Assert(!message.media.ttl);
return pushPhotoMedia(*photo, basePath);
} else if (const auto poll = std::get_if<Data::Poll>(&content)) {
} else if (const auto poll = std::get_if<Poll>(&content)) {
return pushPoll(*poll);
} else if (const auto giveaway = std::get_if<Giveaway>(&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("<b>"
+ Data::NumberToString(data.quantity)
+ "</b> "
+ SerializeString((data.quantity > 1)
? "Telegram Premium Subscriptions"
: "Telegram Premium Subscription")
+ " for <b>" + Data::NumberToString(data.months) + "</b> "
+ (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("<b>" + peers.wrapPeerName(channel) + "</b>");
}
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) {});

View File

@ -287,11 +287,12 @@ QByteArray SerializeMessage(
}
const auto push = [&](const QByteArray &key, const auto &value) {
if constexpr (std::is_arithmetic_v<std::decay_t<decltype(value)>>) {
using V = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<V, bool>) {
pushBare(key, value ? "true" : "false");
} else if constexpr (std::is_arithmetic_v<V>) {
pushBare(key, Data::NumberToString(value));
} else if constexpr (std::is_same_v<
std::decay_t<decltype(value)>,
PeerId>) {
} else if constexpr (std::is_same_v<V, PeerId>) {
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) {});

View File

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

View File

@ -291,6 +291,10 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
peerFromMTP(media.vpeer()),
media.vid().v,
}, media.is_via_mention());
}, [&](const MTPDmessageMediaGiveaway &media) -> Result {
return std::make_unique<Data::MediaGiveaway>(
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<HistoryServiceTopicInfo>();
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<HistoryServiceTopicInfo>();
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) } };
}));

View File

@ -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<MTPMessageEntity>()); // 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<MTPMessageEntity>()); // 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;
});

View File

@ -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<Element*> parent,
not_null<Data::Giveaway*> giveaway)
: Media(parent) {
fillFromData(giveaway);
}
Giveaway::~Giveaway() = default;
void Giveaway::fillFromData(not_null<Data::Giveaway*> 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

View File

@ -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<Row> _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<Element*> parent,
not_null<Data::Giveaway*> 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<Data::Giveaway*> giveaway);
TextSelection toDateSelection(TextSelection selection) const;
TextSelection fromDateSelection(TextSelection selection) const;
QMargins inBubblePadding() const;
TextRows _rows;
std::vector<Channel> _channels;
TextRows _date;
int _rowsHeight = 0;
int _channelsHeight = 0;
int _dateHeight = 0;
};
} // namespace HistoryView

View File

@ -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<long> quantity:int months:int until_date:int = MessageMedia;
messageActionEmpty#b6aef7b0 = MessageAction;
messageActionChatCreate#bd47cbad title:string users:Vector<long> = 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<TopPeerCategoryPeers> 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<MessageEntity> date:int = DraftMessage;
draftMessage#95b0528b flags:# no_webpage:flags.1?true reply_to:flags.4?MessageReplyHeader message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage;
messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers;
messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
@ -1256,7 +1258,7 @@ messages.messageViews#b6c4f543 views:Vector<MessageViews> chats:Vector<Chat> use
messages.discussionMessage#a6341782 flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector<Chat> users:Vector<User> = 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<MessageEntity> = 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<Peer> 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<MessageEnti
inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true upgrade:flags.1?true = InputStorePaymentPurpose;
inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose;
inputStorePaymentPremiumGiftCode#a3805f3f flags:# users:Vector<InputUser> 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<InputPeer> 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<StoryViews> users:Vector<User> = 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<MessageEntity> = 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<Booster> next_of
messages.webPage#fd5e12bd webpage:WebPage chats:Vector<Chat> users:Vector<User> = 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<Chat> users:Vector<User> = 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<Chat> users:Vector<User> = 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<InputDialogPeer> = 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<MessageEntity> = Bool;
messages.saveDraft#64a3026c flags:# no_webpage:flags.1?true reply_to:flags.0?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector<MessageEntity> = Bool;
messages.getAllDrafts#6a3f8d65 = Updates;
messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers;
messages.readFeaturedStickers#5b118126 id:Vector<long> = Bool;
@ -2051,6 +2058,7 @@ payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool;
payments.getPremiumGiftCodeOptions#2757ba54 flags:# boost_peer:flags.0?InputPeer = Vector<PremiumGiftCodeOption>;
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<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;

View File

@ -931,3 +931,10 @@ storyMentionUnreadStrokeTwice: 6px;
storyMentionReadSkipTwice: 7px;
storyMentionReadStrokeTwice: 3px;
storyMentionButtonSkip: 5px;
chatGiveawayPrizesTop: 4px;
chatGiveawayPrizesSkip: 4px;
chatGiveawayParticipantsTop: 16px;
chatGiveawayParticipantsSkip: 4px;
chatGiveawayDateTop: 16px;
chatGiveawayDateSkip: 4px;