diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index 7c3cd15cd..ffc0194ca 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -533,6 +533,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_action_created_chat" = "{from} created group «{title}»"; "lng_action_created_channel" = "Channel «{title}» created"; "lng_action_group_migrate" = "The group was upgraded to a supergroup"; +"lng_action_pinned_message" = "{from} pinned «{text}»"; +"lng_action_pinned_media" = "{from} pinned {media}"; +"lng_action_pinned_media_photo" = "a photo"; +"lng_action_pinned_media_video" = "a video file"; +"lng_action_pinned_media_music" = "a music file"; +"lng_action_pinned_media_voice" = "a voice message"; +"lng_action_pinned_media_file" = "a file"; +"lng_action_pinned_media_gif" = "a GIF animation"; +"lng_action_pinned_media_contact" = "a contact information"; +"lng_action_pinned_media_location" = "a location mark"; +"lng_action_pinned_media_sticker" = "a sticker"; "lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached"; "lng_profile_migrate_about" = "If you'd like to go over this limit, you can upgrade your group to a supergroup. In supergroups:"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 1d006822c..c6108e8e0 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -32,7 +32,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org ApiWrap::ApiWrap(QObject *parent) : QObject(parent) { App::initBackground(); - connect(&_replyToTimer, SIGNAL(timeout()), this, SLOT(resolveReplyTo())); + connect(&_dependencyTimer, SIGNAL(timeout()), this, SLOT(resolveDependencyItems())); connect(&_webPagesTimer, SIGNAL(timeout()), this, SLOT(resolveWebPages())); } @@ -40,78 +40,78 @@ void ApiWrap::init() { } void ApiWrap::itemRemoved(HistoryItem *item) { - if (HistoryReply *reply = item->toHistoryReply()) { - ChannelData *channel = reply->history()->peer->asChannel(); - ReplyToRequests *requests(replyToRequests(channel, true)); + if (MsgId dependencyMsgId = item->dependencyMsgId()) { + ChannelData *channel = item->history()->peer->asChannel(); + DependencyRequests *requests(dependencyRequests(channel, true)); if (requests) { - ReplyToRequests::iterator i = requests->find(reply->replyToId()); + DependencyRequests::iterator i = requests->find(dependencyMsgId); if (i != requests->cend()) { - for (QList::iterator j = i->replies.begin(); j != i->replies.end();) { - if ((*j) == reply) { - j = i->replies.erase(j); + for (QList::iterator j = i->dependentItems.begin(); j != i->dependentItems.end();) { + if ((*j) == item) { + j = i->dependentItems.erase(j); } else { ++j; } } - if (i->replies.isEmpty()) { + if (i->dependentItems.isEmpty()) { requests->erase(i); } } if (channel && requests->isEmpty()) { - _channelReplyToRequests.remove(channel); + _channelDependencyRequests.remove(channel); } } } } -void ApiWrap::requestReplyTo(HistoryReply *reply, ChannelData *channel, MsgId id) { - ReplyToRequest &req(channel ? _channelReplyToRequests[channel][id] : _replyToRequests[id]); - req.replies.append(reply); - if (!req.req) _replyToTimer.start(1); +void ApiWrap::requestDependencyItem(HistoryItem *dependency, ChannelData *channel, MsgId id) { + DependencyRequest &req(channel ? _channelDependencyRequests[channel][id] : _dependencyRequests[id]); + req.dependentItems.append(dependency); + if (!req.req) _dependencyTimer.start(1); } -ApiWrap::MessageIds ApiWrap::collectMessageIds(const ReplyToRequests &requests) { +ApiWrap::MessageIds ApiWrap::collectMessageIds(const DependencyRequests &requests) { MessageIds result; result.reserve(requests.size()); - for (ReplyToRequests::const_iterator i = requests.cbegin(), e = requests.cend(); i != e; ++i) { + for (DependencyRequests::const_iterator i = requests.cbegin(), e = requests.cend(); i != e; ++i) { if (i.value().req > 0) continue; result.push_back(MTP_int(i.key())); } return result; } -ApiWrap::ReplyToRequests *ApiWrap::replyToRequests(ChannelData *channel, bool onlyExisting) { +ApiWrap::DependencyRequests *ApiWrap::dependencyRequests(ChannelData *channel, bool onlyExisting) { if (channel) { - ChannelReplyToRequests::iterator i = _channelReplyToRequests.find(channel); - if (i == _channelReplyToRequests.cend()) { + ChannelDependencyRequests::iterator i = _channelDependencyRequests.find(channel); + if (i == _channelDependencyRequests.cend()) { if (onlyExisting) return 0; - i = _channelReplyToRequests.insert(channel, ReplyToRequests()); + i = _channelDependencyRequests.insert(channel, DependencyRequests()); } return &i.value(); } - return &_replyToRequests; + return &_dependencyRequests; } -void ApiWrap::resolveReplyTo() { - if (_replyToRequests.isEmpty() && _channelReplyToRequests.isEmpty()) return; +void ApiWrap::resolveDependencyItems() { + if (_dependencyRequests.isEmpty() && _channelDependencyRequests.isEmpty()) return; - MessageIds ids = collectMessageIds(_replyToRequests); + MessageIds ids = collectMessageIds(_dependencyRequests); if (!ids.isEmpty()) { - mtpRequestId req = MTP::send(MTPmessages_GetMessages(MTP_vector(ids)), rpcDone(&ApiWrap::gotReplyTo, (ChannelData*)0), RPCFailHandlerPtr(), 0, 5); - for (ReplyToRequests::iterator i = _replyToRequests.begin(); i != _replyToRequests.cend(); ++i) { + mtpRequestId req = MTP::send(MTPmessages_GetMessages(MTP_vector(ids)), rpcDone(&ApiWrap::gotDependencyItem, (ChannelData*)0), RPCFailHandlerPtr(), 0, 5); + for (DependencyRequests::iterator i = _dependencyRequests.begin(); i != _dependencyRequests.cend(); ++i) { if (i.value().req > 0) continue; i.value().req = req; } } - for (ChannelReplyToRequests::iterator j = _channelReplyToRequests.begin(); j != _channelReplyToRequests.cend();) { + for (ChannelDependencyRequests::iterator j = _channelDependencyRequests.begin(); j != _channelDependencyRequests.cend();) { if (j->isEmpty()) { - j = _channelReplyToRequests.erase(j); + j = _channelDependencyRequests.erase(j); continue; } MessageIds ids = collectMessageIds(j.value()); if (!ids.isEmpty()) { - mtpRequestId req = MTP::send(MTPchannels_GetMessages(j.key()->inputChannel, MTP_vector(ids)), rpcDone(&ApiWrap::gotReplyTo, j.key()), RPCFailHandlerPtr(), 0, 5); - for (ReplyToRequests::iterator i = j->begin(); i != j->cend(); ++i) { + mtpRequestId req = MTP::send(MTPchannels_GetMessages(j.key()->inputChannel, MTP_vector(ids)), rpcDone(&ApiWrap::gotDependencyItem, j.key()), RPCFailHandlerPtr(), 0, 5); + for (DependencyRequests::iterator i = j->begin(); i != j->cend(); ++i) { if (i.value().req > 0) continue; i.value().req = req; } @@ -120,7 +120,7 @@ void ApiWrap::resolveReplyTo() { } } -void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) { +void ApiWrap::gotDependencyItem(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) { switch (msgs.type()) { case mtpc_messages_messages: { const MTPDmessages_messages &d(msgs.c_messages_messages()); @@ -141,10 +141,10 @@ void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs, if (channel) { channel->ptsReceived(d.vpts.v); } else { - LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotReplyTo)")); + LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotDependencyItem)")); } if (d.has_collapsed()) { // should not be returned - LOG(("API Error: channels.getMessages and messages.getMessages should not return collapsed groups! (ApiWrap::gotReplyTo)")); + LOG(("API Error: channels.getMessages and messages.getMessages should not return collapsed groups! (ApiWrap::gotDependencyItem)")); } App::feedUsers(d.vusers); @@ -152,15 +152,15 @@ void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs, App::feedMsgs(d.vmessages, NewMessageExisting); } break; } - ReplyToRequests *requests(replyToRequests(channel, true)); + DependencyRequests *requests(dependencyRequests(channel, true)); if (requests) { - for (ReplyToRequests::iterator i = requests->begin(); i != requests->cend();) { + for (DependencyRequests::iterator i = requests->begin(); i != requests->cend();) { if (i.value().req == req) { - for (QList::const_iterator j = i.value().replies.cbegin(), e = i.value().replies.cend(); j != e; ++j) { + for (QList::const_iterator j = i.value().dependentItems.cbegin(), e = i.value().dependentItems.cend(); j != e; ++j) { if (*j) { - (*j)->updateReplyTo(true); - } else { - App::main()->updateReplyTo(); + (*j)->updateDependencyItem(); + } else if (App::main()) { + App::main()->updateDependencyItem(); } } i = requests->erase(i); @@ -169,7 +169,7 @@ void ApiWrap::gotReplyTo(ChannelData *channel, const MTPmessages_Messages &msgs, } } if (channel && requests->isEmpty()) { - _channelReplyToRequests.remove(channel); + _channelDependencyRequests.remove(channel); } } } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 521bb05b2..d15fb88f7 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -29,8 +29,8 @@ public: void init(); void itemRemoved(HistoryItem *item); - - void requestReplyTo(HistoryReply *reply, ChannelData *channel, MsgId id); + + void requestDependencyItem(HistoryItem *dependent, ChannelData *channel, MsgId id); void requestFullPeer(PeerData *peer); void requestPeer(PeerData *peer); @@ -59,35 +59,35 @@ signals: public slots: - void resolveReplyTo(); + void resolveDependencyItems(); void resolveWebPages(); void delayedRequestParticipantsCount(); private: - void gotReplyTo(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req); - struct ReplyToRequest { - ReplyToRequest() : req(0) { + void gotDependencyItem(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req); + struct DependencyRequest { + DependencyRequest() : req(0) { } mtpRequestId req; - QList replies; + QList dependentItems; }; - typedef QMap ReplyToRequests; - ReplyToRequests _replyToRequests; - typedef QMap ChannelReplyToRequests; - ChannelReplyToRequests _channelReplyToRequests; - SingleTimer _replyToTimer; + typedef QMap DependencyRequests; + DependencyRequests _dependencyRequests; + typedef QMap ChannelDependencyRequests; + ChannelDependencyRequests _channelDependencyRequests; + SingleTimer _dependencyTimer; typedef QVector MessageIds; - MessageIds collectMessageIds(const ReplyToRequests &requests); - ReplyToRequests *replyToRequests(ChannelData *channel, bool onlyExisting = false); + MessageIds collectMessageIds(const DependencyRequests &requests); + DependencyRequests *dependencyRequests(ChannelData *channel, bool onlyExisting = false); void gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mtpRequestId req); void gotUserFull(PeerData *peer, const MTPUserFull &result, mtpRequestId req); bool gotPeerFullFailed(PeerData *peer, const RPCError &err); typedef QMap PeerRequests; PeerRequests _fullPeerRequests; - + void gotChat(PeerData *peer, const MTPmessages_Chats &result); void gotUser(PeerData *peer, const MTPVector &result); void gotChats(const MTPmessages_Chats &result); diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 85a4000ac..5543956a5 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -67,8 +67,8 @@ namespace { SharedContactItems sharedContactItems; GifItems gifItems; - typedef QMap > RepliesTo; - RepliesTo repliesTo; + typedef QMap > DependentItems; + DependentItems dependentItems; Histories histories; @@ -1784,12 +1784,12 @@ namespace App { } } historyItemDetached(item); - RepliesTo::iterator j = ::repliesTo.find(item); - if (j != ::repliesTo.cend()) { - for (QMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) { - k.key()->replyToReplaced(item, 0); + DependentItems::iterator j = ::dependentItems.find(item); + if (j != ::dependentItems.cend()) { + for (OrderedSet::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) { + k.key()->dependencyItemRemoved(item); } - ::repliesTo.erase(j); + ::dependentItems.erase(j); } if (App::main() && !App::quitting()) { App::main()->itemRemoved(item); @@ -1797,7 +1797,7 @@ namespace App { } void historyClearMsgs() { - ::repliesTo.clear(); + ::dependentItems.clear(); QVector toDelete; for (MsgsData::const_iterator i = msgsData.cbegin(), e = msgsData.cend(); i != e; ++i) { @@ -1869,16 +1869,16 @@ namespace App { if (App::wnd()) App::wnd()->updateGlobalMenu(); } - void historyRegReply(HistoryReply *reply, HistoryItem *to) { - ::repliesTo[to].insert(reply, true); + void historyRegDependency(HistoryItem *dependent, HistoryItem *dependency) { + ::dependentItems[dependency].insert(dependent); } - void historyUnregReply(HistoryReply *reply, HistoryItem *to) { - RepliesTo::iterator i = ::repliesTo.find(to); - if (i != ::repliesTo.cend()) { - i.value().remove(reply); + void historyUnregDependency(HistoryItem *dependent, HistoryItem *dependency) { + DependentItems::iterator i = ::dependentItems.find(dependency); + if (i != ::dependentItems.cend()) { + i.value().remove(dependent); if (i.value().isEmpty()) { - ::repliesTo.erase(i); + ::dependentItems.erase(i); } } } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 20e51a806..f3228811c 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -152,8 +152,8 @@ namespace App { void historyUnregItem(HistoryItem *item); void historyClearMsgs(); void historyClearItems(); - void historyRegReply(HistoryReply *reply, HistoryItem *to); - void historyUnregReply(HistoryReply *reply, HistoryItem *to); + void historyRegDependency(HistoryItem *dependent, HistoryItem *dependency); + void historyUnregDependency(HistoryItem *dependent, HistoryItem *dependency); void historyRegRandom(uint64 randomId, const FullMsgId &itemId); void historyUnregRandom(uint64 randomId); diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 83969d1c6..2f488d590 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -171,6 +171,8 @@ enum { ChoosePeerByDragTimeout = 1000, // 1 second mouse not moved to choose dialog when dragging a file ReloadChannelMembersTimeout = 1000, // 1 second wait before reload members in channel after adding + + PinnedMessageTextLimit = 16, }; inline bool isNotificationsUser(uint64 id) { diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index d582b39bd..e164d9da3 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -1509,6 +1509,13 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo //const MTPDmessageActionChannelMigrateFrom &d(action.c_messageActionChannelMigrateFrom()); //PeerData *chat = App::peerLoaded(peerFromChat(d.vchat_id)); } break; + + case mtpc_messageActionPinMessage: { + if (d.has_reply_to_msg_id() && result && result->history()->peer->isMegagroup()) { + result->history()->peer->asChannel()->mgInfo->pinnedMsgId = d.vreply_to_msg_id.v; + if (App::main()) emit App::main()->peerUpdated(result->history()->peer); + } + } break; } } } break; @@ -1540,7 +1547,7 @@ HistoryItem *History::createItemDocument(HistoryBlock *block, MsgId id, int32 fl HistoryItem *History::createItemPhoto(HistoryBlock *block, MsgId id, int32 flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption) { HistoryItem *result = 0; - if (flags & MTPDmessage::flag_reply_to_msg_id && replyTo > 0) { + if ((flags & MTPDmessage::flag_reply_to_msg_id) && replyTo > 0) { result = new HistoryReply(this, block, id, flags, viaBotId, replyTo, date, from, photo, caption); } else { result = new HistoryMessage(this, block, id, flags, viaBotId, date, from, photo, caption); @@ -6891,7 +6898,7 @@ void HistoryMessage::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 bool inText = false; bool breakEverywhere = (fwd->_text.countHeight(trect.width()) > 2 * st::semiboldFont->height); textstyleSet(&st::inFwdTextStyle); - fwd->_text.getState(lnk, inText, x - trect.left(), y - trect.top(), trect.right() - trect.left(), style::al_left, breakEverywhere); + fwd->_text.getState(lnk, inText, x - trect.left(), y - trect.top(), trect.width(), style::al_left, breakEverywhere); textstyleRestore(); if (breakEverywhere) { state = HistoryInForwardedCursorState; @@ -7035,7 +7042,7 @@ HistoryReply::HistoryReply(History *history, HistoryBlock *block, const MTPDmess , _maxReplyWidth(0) , _replyToVia(0) { if (!updateReplyTo() && App::api()) { - App::api()->requestReplyTo(this, history->peer->asChannel(), replyToMsgId); + App::api()->requestDependencyItem(this, history->peer->asChannel(), replyToMsgId); } } @@ -7047,7 +7054,7 @@ HistoryReply::HistoryReply(History *history, HistoryBlock *block, MsgId msgId, i , _maxReplyWidth(0) , _replyToVia(0) { if (!updateReplyTo() && App::api()) { - App::api()->requestReplyTo(this, history->peer->asChannel(), replyToMsgId); + App::api()->requestDependencyItem(this, history->peer->asChannel(), replyToMsgId); } } @@ -7059,7 +7066,7 @@ HistoryReply::HistoryReply(History *history, HistoryBlock *block, MsgId msgId, i , _maxReplyWidth(0) , _replyToVia(0) { if (!updateReplyTo() && App::api()) { - App::api()->requestReplyTo(this, history->peer->asChannel(), replyToMsgId); + App::api()->requestDependencyItem(this, history->peer->asChannel(), replyToMsgId); } replyToNameUpdated(); } @@ -7089,7 +7096,7 @@ bool HistoryReply::updateReplyTo(bool force) { replyToMsg = App::histItemById(channelId(), replyToMsgId); if (replyToMsg) { - App::historyRegReply(this, replyToMsg); + App::historyRegDependency(this, replyToMsg); replyToText.setText(st::msgFont, replyToMsg->inReplyText(), _textDlgOptions); replyToNameUpdated(); @@ -7146,20 +7153,14 @@ HistoryItem *HistoryReply::replyToMessage() const { return replyToMsg; } -void HistoryReply::replyToReplaced(HistoryItem *oldItem, HistoryItem *newItem) { - if (replyToMsg == oldItem) { +void HistoryReply::dependencyItemRemoved(HistoryItem *dependency) { + if (replyToMsg == dependency) { delete _replyToVia; _replyToVia = 0; - replyToMsg = newItem; - if (!newItem) { - replyToMsgId = 0; - initDimensions(); - } else if (!replyToMsg->Is()) { - if (UserData *bot = replyToMsg->viaBot()) { - _replyToVia = new HistoryMessageVia(0); - _replyToVia->create(peerToUser(bot->id)); - } - } + + replyToMsg = nullptr; + replyToMsgId = 0; + initDimensions(); } } @@ -7301,7 +7302,7 @@ void HistoryReply::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); if (y >= trect.top() && y < trect.top() + h) { - if (replyToMsg && y >= trect.top() + st::msgReplyPadding.top() && y < trect.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() && x >= trect.left() && x < trect.right()) { + if (replyToMsg && y >= trect.top() + st::msgReplyPadding.top() && y < trect.top() + st::msgReplyPadding.top() + st::msgReplyBarSize.height() && x >= trect.left() && x < trect.left() + trect.width()) { lnk = replyToLnk; } return; @@ -7347,13 +7348,18 @@ void HistoryReply::getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, i HistoryReply::~HistoryReply() { if (replyToMsg) { - App::historyUnregReply(this, replyToMsg); + App::historyUnregDependency(this, replyToMsg); } else if (replyToMsgId && App::api()) { App::api()->itemRemoved(this); } deleteAndMark(_replyToVia); } +HistoryServicePinned::HistoryServicePinned(Interfaces *) +: msgId(0) +, msg(0) { +} + void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { QList links; LangString text = lang(lng_message_empty); @@ -7486,6 +7492,15 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { } } break; + case mtpc_messageActionPinMessage: { + if (updatePinnedText(&from, &text)) { + HistoryServicePinned *pinned = Get(); + t_assert(pinned != nullptr); + + links.push_back(pinned->lnk); + } + } break; + default: from = QString(); break; } @@ -7500,19 +7515,106 @@ void HistoryServiceMsg::setMessageByAction(const MTPmessageAction &action) { } } +bool HistoryServiceMsg::updatePinned(bool force) { + HistoryServicePinned *pinned = Get(); + t_assert(pinned != nullptr); + + if (!pinned->msgId || pinned->msg) return true; + + if (!pinned->lnk) { + pinned->lnk = TextLinkPtr(new MessageLink(history()->peer->id, pinned->msgId)); + } + pinned->msg = App::histItemById(channelId(), pinned->msgId); + if (pinned->msg) { + App::historyRegDependency(this, pinned->msg); + updatePinnedText(); + } else if (force) { + pinned->msgId = 0; + updatePinnedText(); + } + if (force) { + initDimensions(); + Notify::historyItemResized(this); + } + return (pinned->msg || !pinned->msgId); +} + +bool HistoryServiceMsg::updatePinnedText(const QString *pfrom, QString *ptext) { + bool result = false; + QString from, text; + if (pfrom) { + from = *pfrom; + } else { + from = textcmdLink(1, _from->name); + } + + HistoryServicePinned *pinned = Get(); + if (pinned && pinned->msg) { + HistoryMedia *media = pinned->msg->getMedia(); + QString mediaText; + switch (media ? media->type() : MediaTypeCount) { + case MediaTypePhoto: mediaText = lang(lng_action_pinned_media_photo); break; + case MediaTypeVideo: mediaText = lang(lng_action_pinned_media_video); break; + case MediaTypeContact: mediaText = lang(lng_action_pinned_media_contact); break; + case MediaTypeFile: mediaText = lang(lng_action_pinned_media_file); break; + case MediaTypeGif: mediaText = lang(lng_action_pinned_media_gif); break; + case MediaTypeSticker: mediaText = lang(lng_action_pinned_media_sticker); break; + case MediaTypeLocation: mediaText = lang(lng_action_pinned_media_location); break; + case MediaTypeMusicFile: mediaText = lang(lng_action_pinned_media_music); break; + case MediaTypeVoiceFile: mediaText = lang(lng_action_pinned_media_voice); break; + } + if (mediaText.isEmpty()) { + QString original = pinned->msg->originalText(); + int32 cutat = 0, limit = PinnedMessageTextLimit, size = original.size(); + for (; limit > 0;) { + --limit; + if (cutat >= size) break; + if (original.at(cutat).isLowSurrogate() && cutat + 1 < size && original.at(cutat + 1).isHighSurrogate()) { + cutat += 2; + } else { + ++cutat; + } + } + if (!limit && cutat + 5 < size) { + original = original.mid(0, cutat) + qstr(".."); + } + text = lng_action_pinned_message(lt_from, from, lt_text, textcmdLink(2, original)); + } else { + text = lng_action_pinned_media(lt_from, from, lt_media, textcmdLink(2, mediaText)); + } + result = true; + } else if (pinned && pinned->msgId) { + text = lng_action_pinned_media(lt_from, from, lt_media, textcmdLink(2, lang(lng_contacts_loading))); + result = true; + } else { + text = lng_action_pinned_media(lt_from, from, lt_media, lang(lng_deleted_message)); + } + if (ptext) { + *ptext = text; + } else { + setServiceText(text); + } + return result; +} + HistoryServiceMsg::HistoryServiceMsg(History *history, HistoryBlock *block, const MTPDmessageService &msg) : HistoryItem(history, block, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) , _text(st::msgMinWidth) -, _media(0) -{ +, _media(0) { + if (msg.has_reply_to_msg_id()) { + UpdateInterfaces(HistoryServicePinned::Bit()); + Get()->msgId = msg.vreply_to_msg_id.v; + if (!updatePinned() && App::api()) { + App::api()->requestDependencyItem(this, history->peer->asChannel(), Get()->msgId); + } + } setMessageByAction(msg.vaction); } HistoryServiceMsg::HistoryServiceMsg(History *history, HistoryBlock *block, MsgId msgId, QDateTime date, const QString &msg, int32 flags, HistoryMedia *media, int32 from) : HistoryItem(history, block, msgId, flags, date, from) , _text(st::msgServiceFont, msg, _historySrvOptions, st::dlgMinWidth) -, _media(media) -{ +, _media(media) { } void HistoryServiceMsg::initDimensions() { diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 436997fbe..fb4df056b 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -115,7 +115,6 @@ struct FakeDialogRow { enum HistoryMediaType { MediaTypePhoto, MediaTypeVideo, - MediaTypeGeo, MediaTypeContact, MediaTypeFile, MediaTypeGif, @@ -919,6 +918,15 @@ public: virtual int32 resize(int32 width) = 0; // return new height virtual void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const = 0; + virtual void dependencyItemRemoved(HistoryItem *dependency) { + } + virtual bool updateDependencyItem() { + return true; + } + virtual MsgId dependencyMsgId() const { + return 0; + } + virtual UserData *viaBot() const { return 0; } @@ -2229,15 +2237,19 @@ public: void initDimensions(); - bool updateReplyTo(bool force = false); - void replyToNameUpdated() const; + bool updateDependencyItem() override { + return updateReplyTo(true); + } + MsgId dependencyMsgId() const override { + return replyToId(); + } int32 replyToWidth() const; TextLinkPtr replyToLink() const; MsgId replyToId() const; HistoryItem *replyToMessage() const; - void replyToReplaced(HistoryItem *oldItem, HistoryItem *newItem); + void dependencyItemRemoved(HistoryItem *dependency) override; void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const; void drawReplyTo(Painter &p, int32 x, int32 y, int32 w, bool selected, bool likeService = false) const; @@ -2265,6 +2277,9 @@ public: protected: + bool updateReplyTo(bool force = false); + void replyToNameUpdated() const; + MsgId replyToMsgId; HistoryItem *replyToMsg; TextLinkPtr replyToLnk; @@ -2296,6 +2311,14 @@ inline int32 newForwardedFlags(PeerData *p, int32 from, HistoryMessage *fwd) { return result; } +struct HistoryServicePinned : public BasicInterface { + HistoryServicePinned(Interfaces *); + + MsgId msgId; + HistoryItem *msg; + TextLinkPtr lnk; +}; + class HistoryServiceMsg : public HistoryItem { public: @@ -2304,6 +2327,16 @@ public: void initDimensions(); + bool updateDependencyItem() override { + return updatePinned(true); + } + MsgId dependencyMsgId() const override { + if (const HistoryServicePinned *pinned = Get()) { + return pinned->msgId; + } + return 0; + } + void countPositionAndSize(int32 &left, int32 &width) const; void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const; @@ -2344,6 +2377,8 @@ public: protected: void setMessageByAction(const MTPmessageAction &action); + bool updatePinned(bool force = false); + bool updatePinnedText(const QString *pfrom = nullptr, QString *ptext = nullptr); Text _text; HistoryMedia *_media; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 6ab5eef10..6c72f29bf 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3500,7 +3500,7 @@ void HistoryWidget::applyDraft(bool parseLinks) { if (_editMsgId || _replyToId) { updateReplyEditTexts(); if (!_replyEditMsg && App::api()) { - App::api()->requestReplyTo(0, _peer->asChannel(), _editMsgId ? _editMsgId : _replyToId); + App::api()->requestDependencyItem(0, _peer->asChannel(), _editMsgId ? _editMsgId : _replyToId); } } } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 6e49f6758..9ef400e1c 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -2056,7 +2056,7 @@ ApiWrap *MainWidget::api() { return _api; } -void MainWidget::updateReplyTo() { +void MainWidget::updateDependencyItem() { history.updateReplyEditTexts(true); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index f0318a1a4..08c053f5b 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -373,7 +373,7 @@ public: ImagePtr newBackgroundThumb(); ApiWrap *api(); - void updateReplyTo(); + void updateDependencyItem(); void updateBotKeyboard(History *h); void pushReplyReturn(HistoryItem *item); diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.cpp b/Telegram/SourceFiles/mtproto/mtpScheme.cpp index 40ec2b60e..5a4af0ce6 100644 --- a/Telegram/SourceFiles/mtproto/mtpScheme.cpp +++ b/Telegram/SourceFiles/mtproto/mtpScheme.cpp @@ -1385,8 +1385,9 @@ void _serialize_messageService(MTPStringLogger &to, int32 stage, int32 lev, Type case 7: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 8: to.add(" from_id: "); ++stages.back(); if (flag & MTPDmessageService::flag_from_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 8 IN FIELD flags ]"); } break; case 9: to.add(" to_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 10: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 11: to.add(" action: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 10: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPDmessageService::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 11: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 12: to.add(" action: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -1613,6 +1614,10 @@ void _serialize_messageActionChannelMigrateFrom(MTPStringLogger &to, int32 stage } } +void _serialize_messageActionPinMessage(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + to.add("{ messageActionPinMessage }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + void _serialize_dialog(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -6958,8 +6963,10 @@ void _serialize_channels_updatePinnedMessage(MTPStringLogger &to, int32 stage, i to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" channel: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" silent: "); ++stages.back(); if (flag & MTPchannels_updatePinnedMessage::flag_silent) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" channel: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -7683,6 +7690,7 @@ namespace { _serializers.insert(mtpc_messageActionChannelCreate, _serialize_messageActionChannelCreate); _serializers.insert(mtpc_messageActionChatMigrateTo, _serialize_messageActionChatMigrateTo); _serializers.insert(mtpc_messageActionChannelMigrateFrom, _serialize_messageActionChannelMigrateFrom); + _serializers.insert(mtpc_messageActionPinMessage, _serialize_messageActionPinMessage); _serializers.insert(mtpc_dialog, _serialize_dialog); _serializers.insert(mtpc_dialogChannel, _serialize_dialogChannel); _serializers.insert(mtpc_photoEmpty, _serialize_photoEmpty); diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.h b/Telegram/SourceFiles/mtproto/mtpScheme.h index 8ffca9b89..577f80f59 100644 --- a/Telegram/SourceFiles/mtproto/mtpScheme.h +++ b/Telegram/SourceFiles/mtproto/mtpScheme.h @@ -146,7 +146,7 @@ enum { mtpc_chatPhoto = 0x6153276a, mtpc_messageEmpty = 0x83e5de54, mtpc_message = 0xc09be45f, - mtpc_messageService = 0xc06b9607, + mtpc_messageService = 0x9e19a1f6, mtpc_messageMediaEmpty = 0x3ded6320, mtpc_messageMediaPhoto = 0x3d8ce53d, mtpc_messageMediaGeo = 0x56e0d474, @@ -166,6 +166,7 @@ enum { mtpc_messageActionChannelCreate = 0x95d2ac92, mtpc_messageActionChatMigrateTo = 0x51bdb021, mtpc_messageActionChannelMigrateFrom = 0xb055eaee, + mtpc_messageActionPinMessage = 0x94bd38ed, mtpc_dialog = 0xc1dd804a, mtpc_dialogChannel = 0x5b8496b2, mtpc_photoEmpty = 0x2331b22d, @@ -613,7 +614,7 @@ enum { mtpc_channels_toggleSignatures = 0x1f69b606, mtpc_channels_getMessageEditData = 0x27ea3a28, mtpc_channels_editMessage = 0xdcda80ed, - mtpc_channels_updatePinnedMessage = 0x84a41867 + mtpc_channels_updatePinnedMessage = 0xa72ded52 }; // Type forward declarations @@ -3535,7 +3536,7 @@ private: friend MTPmessage MTP_messageEmpty(MTPint _id); friend MTPmessage MTP_message(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, const MTPMessageFwdHeader &_fwd_from, MTPint _via_bot_id, MTPint _reply_to_msg_id, MTPint _date, const MTPstring &_message, const MTPMessageMedia &_media, const MTPReplyMarkup &_reply_markup, const MTPVector &_entities, MTPint _views, MTPint _edit_date); - friend MTPmessage MTP_messageService(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, MTPint _date, const MTPMessageAction &_action); + friend MTPmessage MTP_messageService(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, MTPint _reply_to_msg_id, MTPint _date, const MTPMessageAction &_action); mtpTypeId _type; }; @@ -3796,6 +3797,7 @@ private: friend MTPmessageAction MTP_messageActionChannelCreate(const MTPstring &_title); friend MTPmessageAction MTP_messageActionChatMigrateTo(MTPint _channel_id); friend MTPmessageAction MTP_messageActionChannelMigrateFrom(const MTPstring &_title, MTPint _chat_id); + friend MTPmessageAction MTP_messageActionPinMessage(); mtpTypeId _type; }; @@ -10199,13 +10201,14 @@ class MTPDmessageService : public mtpDataImpl { public: MTPDmessageService() { } - MTPDmessageService(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, MTPint _date, const MTPMessageAction &_action) : vflags(_flags), vid(_id), vfrom_id(_from_id), vto_id(_to_id), vdate(_date), vaction(_action) { + MTPDmessageService(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, MTPint _reply_to_msg_id, MTPint _date, const MTPMessageAction &_action) : vflags(_flags), vid(_id), vfrom_id(_from_id), vto_id(_to_id), vreply_to_msg_id(_reply_to_msg_id), vdate(_date), vaction(_action) { } MTPint vflags; MTPint vid; MTPint vfrom_id; MTPPeer vto_id; + MTPint vreply_to_msg_id; MTPint vdate; MTPMessageAction vaction; @@ -10217,6 +10220,7 @@ public: flag_silent = (1 << 13), flag_post = (1 << 14), flag_from_id = (1 << 8), + flag_reply_to_msg_id = (1 << 3), }; bool is_unread() const { return vflags.v & flag_unread; } @@ -10226,6 +10230,7 @@ public: bool is_silent() const { return vflags.v & flag_silent; } bool is_post() const { return vflags.v & flag_post; } bool has_from_id() const { return vflags.v & flag_from_id; } + bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } }; class MTPDmessageMediaPhoto : public mtpDataImpl { @@ -20603,6 +20608,7 @@ public: class MTPchannels_updatePinnedMessage { // RPC method 'channels.updatePinnedMessage' public: + MTPint vflags; MTPInputChannel vchannel; MTPint vid; @@ -20611,20 +20617,28 @@ public: MTPchannels_updatePinnedMessage(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_channels_updatePinnedMessage) { read(from, end, cons); } - MTPchannels_updatePinnedMessage(const MTPInputChannel &_channel, MTPint _id) : vchannel(_channel), vid(_id) { + MTPchannels_updatePinnedMessage(MTPint _flags, const MTPInputChannel &_channel, MTPint _id) : vflags(_flags), vchannel(_channel), vid(_id) { } + enum { + flag_silent = (1 << 0), + }; + + bool is_silent() const { return vflags.v & flag_silent; } + uint32 innerLength() const { - return vchannel.innerLength() + vid.innerLength(); + return vflags.innerLength() + vchannel.innerLength() + vid.innerLength(); } mtpTypeId type() const { return mtpc_channels_updatePinnedMessage; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_channels_updatePinnedMessage) { + vflags.read(from, end); vchannel.read(from, end); vid.read(from, end); } void write(mtpBuffer &to) const { + vflags.write(to); vchannel.write(to); vid.write(to); } @@ -20639,7 +20653,7 @@ public: } MTPchannels_UpdatePinnedMessage(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPchannels_UpdatePinnedMessage(const MTPInputChannel &_channel, MTPint _id) : MTPBoxed(MTPchannels_updatePinnedMessage(_channel, _id)) { + MTPchannels_UpdatePinnedMessage(MTPint _flags, const MTPInputChannel &_channel, MTPint _id) : MTPBoxed(MTPchannels_updatePinnedMessage(_flags, _channel, _id)) { } }; @@ -23401,7 +23415,7 @@ inline uint32 MTPmessage::innerLength() const { } case mtpc_messageService: { const MTPDmessageService &v(c_messageService()); - return v.vflags.innerLength() + v.vid.innerLength() + (v.has_from_id() ? v.vfrom_id.innerLength() : 0) + v.vto_id.innerLength() + v.vdate.innerLength() + v.vaction.innerLength(); + return v.vflags.innerLength() + v.vid.innerLength() + (v.has_from_id() ? v.vfrom_id.innerLength() : 0) + v.vto_id.innerLength() + (v.has_reply_to_msg_id() ? v.vreply_to_msg_id.innerLength() : 0) + v.vdate.innerLength() + v.vaction.innerLength(); } } return 0; @@ -23443,6 +23457,7 @@ inline void MTPmessage::read(const mtpPrime *&from, const mtpPrime *end, mtpType v.vid.read(from, end); if (v.has_from_id()) { v.vfrom_id.read(from, end); } else { v.vfrom_id = MTPint(); } v.vto_id.read(from, end); + if (v.has_reply_to_msg_id()) { v.vreply_to_msg_id.read(from, end); } else { v.vreply_to_msg_id = MTPint(); } v.vdate.read(from, end); v.vaction.read(from, end); } break; @@ -23478,6 +23493,7 @@ inline void MTPmessage::write(mtpBuffer &to) const { v.vid.write(to); if (v.has_from_id()) v.vfrom_id.write(to); v.vto_id.write(to); + if (v.has_reply_to_msg_id()) v.vreply_to_msg_id.write(to); v.vdate.write(to); v.vaction.write(to); } break; @@ -23503,8 +23519,8 @@ inline MTPmessage MTP_messageEmpty(MTPint _id) { inline MTPmessage MTP_message(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, const MTPMessageFwdHeader &_fwd_from, MTPint _via_bot_id, MTPint _reply_to_msg_id, MTPint _date, const MTPstring &_message, const MTPMessageMedia &_media, const MTPReplyMarkup &_reply_markup, const MTPVector &_entities, MTPint _views, MTPint _edit_date) { return MTPmessage(new MTPDmessage(_flags, _id, _from_id, _to_id, _fwd_from, _via_bot_id, _reply_to_msg_id, _date, _message, _media, _reply_markup, _entities, _views, _edit_date)); } -inline MTPmessage MTP_messageService(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, MTPint _date, const MTPMessageAction &_action) { - return MTPmessage(new MTPDmessageService(_flags, _id, _from_id, _to_id, _date, _action)); +inline MTPmessage MTP_messageService(MTPint _flags, MTPint _id, MTPint _from_id, const MTPPeer &_to_id, MTPint _reply_to_msg_id, MTPint _date, const MTPMessageAction &_action) { + return MTPmessage(new MTPDmessageService(_flags, _id, _from_id, _to_id, _reply_to_msg_id, _date, _action)); } inline uint32 MTPmessageMedia::innerLength() const { @@ -23771,6 +23787,7 @@ inline void MTPmessageAction::read(const mtpPrime *&from, const mtpPrime *end, m v.vtitle.read(from, end); v.vchat_id.read(from, end); } break; + case mtpc_messageActionPinMessage: _type = cons; break; default: throw mtpErrorUnexpected(cons, "MTPmessageAction"); } } @@ -23829,6 +23846,7 @@ inline MTPmessageAction::MTPmessageAction(mtpTypeId type) : mtpDataOwner(0), _ty case mtpc_messageActionChannelCreate: setData(new MTPDmessageActionChannelCreate()); break; case mtpc_messageActionChatMigrateTo: setData(new MTPDmessageActionChatMigrateTo()); break; case mtpc_messageActionChannelMigrateFrom: setData(new MTPDmessageActionChannelMigrateFrom()); break; + case mtpc_messageActionPinMessage: break; default: throw mtpErrorBadTypeId(type, "MTPmessageAction"); } } @@ -23883,6 +23901,9 @@ inline MTPmessageAction MTP_messageActionChatMigrateTo(MTPint _channel_id) { inline MTPmessageAction MTP_messageActionChannelMigrateFrom(const MTPstring &_title, MTPint _chat_id) { return MTPmessageAction(new MTPDmessageActionChannelMigrateFrom(_title, _chat_id)); } +inline MTPmessageAction MTP_messageActionPinMessage() { + return MTPmessageAction(mtpc_messageActionPinMessage); +} inline uint32 MTPdialog::innerLength() const { switch (_type) { diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 876080a4b..5588272f7 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -227,7 +227,7 @@ chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto; messageEmpty#83e5de54 id:int = Message; message#c09be45f flags:# unread:flags.0?true out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int edit_date:flags.15?int = Message; -messageService#c06b9607 flags:# unread:flags.0?true out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer date:int action:MessageAction = Message; +messageService#9e19a1f6 flags:# unread:flags.0?true out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message; messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#3d8ce53d photo:Photo caption:string = MessageMedia; @@ -249,6 +249,7 @@ messageActionChatJoinedByLink#f89cf5e8 inviter_id:int = MessageAction; messageActionChannelCreate#95d2ac92 title:string = MessageAction; messageActionChatMigrateTo#51bdb021 channel_id:int = MessageAction; messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageAction; +messageActionPinMessage#94bd38ed = MessageAction; dialog#c1dd804a peer:Peer top_message:int read_inbox_max_id:int unread_count:int notify_settings:PeerNotifySettings = Dialog; dialogChannel#5b8496b2 peer:Peer top_message:int top_important_message:int read_inbox_max_id:int unread_count:int unread_important_count:int notify_settings:PeerNotifySettings pts:int = Dialog; @@ -821,4 +822,4 @@ channels.exportMessageLink#c846d22d channel:InputChannel id:int = ExportedMessag channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; channels.getMessageEditData#27ea3a28 channel:InputChannel id:int = channels.MessageEditData; channels.editMessage#dcda80ed flags:# no_webpage:flags.1?true channel:InputChannel id:int message:string entities:flags.3?Vector = Updates; -channels.updatePinnedMessage#84a41867 channel:InputChannel id:int = Updates; +channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates;