"Fossies" - the Fresh Open Source Software Archive 
Member "tdesktop-2.6.1/Telegram/SourceFiles/history/history_item.cpp" (24 Feb 2021, 29399 Bytes) of package /linux/misc/tdesktop-2.6.1.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "history_item.cpp" see the
Fossies "Dox" file reference documentation and the last
Fossies "Diffs" side-by-side code changes report:
2.5.9_vs_2.6.0.
1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "history/history_item.h"
9
10 #include "lang/lang_keys.h"
11 #include "mainwidget.h"
12 #include "layout.h"
13 #include "history/view/history_view_element.h"
14 #include "history/view/history_view_service_message.h"
15 #include "history/history_item_components.h"
16 #include "history/view/media/history_view_media_grouped.h"
17 #include "history/history_service.h"
18 #include "history/history_message.h"
19 #include "history/history.h"
20 #include "mtproto/mtproto_config.h"
21 #include "media/clip/media_clip_reader.h"
22 #include "ui/effects/ripple_animation.h"
23 #include "ui/text/text_isolated_emoji.h"
24 #include "ui/text/text_options.h"
25 #include "storage/file_upload.h"
26 #include "storage/storage_facade.h"
27 #include "storage/storage_shared_media.h"
28 //#include "storage/storage_feed_messages.h" // #feed
29 #include "main/main_session.h"
30 #include "apiwrap.h"
31 #include "media/audio/media_audio.h"
32 #include "core/application.h"
33 #include "mainwindow.h"
34 #include "window/window_session_controller.h"
35 #include "core/crash_reports.h"
36 #include "base/unixtime.h"
37 #include "api/api_text_entities.h"
38 #include "data/data_scheduled_messages.h" // kScheduledUntilOnlineTimestamp
39 #include "data/data_changes.h"
40 #include "data/data_session.h"
41 #include "data/data_messages.h"
42 #include "data/data_media_types.h"
43 #include "data/data_folder.h"
44 #include "data/data_channel.h"
45 #include "data/data_chat.h"
46 #include "data/data_user.h"
47 #include "styles/style_dialogs.h"
48 #include "styles/style_chat.h"
49
50 namespace {
51
52 constexpr auto kNotificationTextLimit = 255;
53
54 enum class MediaCheckResult {
55 Good,
56 Unsupported,
57 Empty,
58 HasTimeToLive,
59 };
60
61 not_null<HistoryItem*> CreateUnsupportedMessage(
62 not_null<History*> history,
63 MsgId msgId,
64 MTPDmessage::Flags flags,
65 MTPDmessage_ClientFlags clientFlags,
66 MsgId replyTo,
67 UserId viaBotId,
68 TimeId date,
69 PeerId from) {
70 const auto siteLink = qsl("https://desktop.telegram.org");
71 auto text = TextWithEntities{
72 tr::lng_message_unsupported(tr::now, lt_link, siteLink)
73 };
74 TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
75 text.entities.push_front(
76 EntityInText(EntityType::Italic, 0, text.text.size()));
77 flags &= ~MTPDmessage::Flag::f_post_author;
78 flags |= MTPDmessage::Flag::f_legacy;
79 return history->makeMessage(
80 msgId,
81 flags,
82 clientFlags,
83 replyTo,
84 viaBotId,
85 date,
86 from,
87 QString(),
88 text);
89 }
90
91 MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
92 using Result = MediaCheckResult;
93 return media.match([](const MTPDmessageMediaEmpty &) {
94 return Result::Good;
95 }, [](const MTPDmessageMediaContact &) {
96 return Result::Good;
97 }, [](const MTPDmessageMediaGeo &data) {
98 return data.vgeo().match([](const MTPDgeoPoint &) {
99 return Result::Good;
100 }, [](const MTPDgeoPointEmpty &) {
101 return Result::Empty;
102 });
103 }, [](const MTPDmessageMediaVenue &data) {
104 return data.vgeo().match([](const MTPDgeoPoint &) {
105 return Result::Good;
106 }, [](const MTPDgeoPointEmpty &) {
107 return Result::Empty;
108 });
109 }, [](const MTPDmessageMediaGeoLive &data) {
110 return data.vgeo().match([](const MTPDgeoPoint &) {
111 return Result::Good;
112 }, [](const MTPDgeoPointEmpty &) {
113 return Result::Empty;
114 });
115 }, [](const MTPDmessageMediaPhoto &data) {
116 const auto photo = data.vphoto();
117 if (data.vttl_seconds()) {
118 return Result::HasTimeToLive;
119 } else if (!photo) {
120 return Result::Empty;
121 }
122 return photo->match([](const MTPDphoto &) {
123 return Result::Good;
124 }, [](const MTPDphotoEmpty &) {
125 return Result::Empty;
126 });
127 }, [](const MTPDmessageMediaDocument &data) {
128 const auto document = data.vdocument();
129 if (data.vttl_seconds()) {
130 return Result::HasTimeToLive;
131 } else if (!document) {
132 return Result::Empty;
133 }
134 return document->match([](const MTPDdocument &) {
135 return Result::Good;
136 }, [](const MTPDdocumentEmpty &) {
137 return Result::Empty;
138 });
139 }, [](const MTPDmessageMediaWebPage &data) {
140 return data.vwebpage().match([](const MTPDwebPage &) {
141 return Result::Good;
142 }, [](const MTPDwebPageEmpty &) {
143 return Result::Good;
144 }, [](const MTPDwebPagePending &) {
145 return Result::Good;
146 }, [](const MTPDwebPageNotModified &) {
147 return Result::Unsupported;
148 });
149 }, [](const MTPDmessageMediaGame &data) {
150 return data.vgame().match([](const MTPDgame &) {
151 return Result::Good;
152 });
153 }, [](const MTPDmessageMediaInvoice &) {
154 return Result::Good;
155 }, [](const MTPDmessageMediaPoll &) {
156 return Result::Good;
157 }, [](const MTPDmessageMediaDice &) {
158 return Result::Good;
159 }, [](const MTPDmessageMediaUnsupported &) {
160 return Result::Unsupported;
161 });
162 }
163
164 } // namespace
165
166 void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
167 if (value) {
168 value->destroy();
169 }
170 }
171
172 HistoryItem::HistoryItem(
173 not_null<History*> history,
174 MsgId id,
175 MTPDmessage::Flags flags,
176 MTPDmessage_ClientFlags clientFlags,
177 TimeId date,
178 PeerId from)
179 : id(id)
180 , _history(history)
181 , _from(from ? history->owner().peer(from) : history->peer)
182 , _flags(flags)
183 , _clientFlags(clientFlags)
184 , _date(date) {
185 if (isHistoryEntry() && IsClientMsgId(id)) {
186 _history->registerLocalMessage(this);
187 }
188 }
189
190 TimeId HistoryItem::date() const {
191 return _date;
192 }
193
194 TimeId HistoryItem::NewMessageDate(TimeId scheduled) {
195 return scheduled ? scheduled : base::unixtime::now();
196 }
197
198 void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) {
199 const auto date = data.vdate().v;
200 if (_date == date) {
201 return;
202 }
203 _date = date;
204 }
205
206 void HistoryItem::finishEdition(int oldKeyboardTop) {
207 _history->owner().requestItemViewRefresh(this);
208 invalidateChatListEntry();
209 if (const auto group = _history->owner().groups().find(this)) {
210 const auto leader = group->items.front();
211 if (leader != this) {
212 _history->owner().requestItemViewRefresh(leader);
213 leader->invalidateChatListEntry();
214 }
215 }
216
217 // Should be completely redesigned as the oldTop no longer exists.
218 //if (oldKeyboardTop >= 0) { // #TODO edit bot message
219 // if (auto keyboard = Get<HistoryMessageReplyMarkup>()) {
220 // keyboard->oldTop = oldKeyboardTop;
221 // }
222 //}
223
224 _history->owner().updateDependentMessages(this);
225 }
226
227 void HistoryItem::setGroupId(MessageGroupId groupId) {
228 Expects(!_groupId);
229
230 _groupId = groupId;
231 _history->owner().groups().registerMessage(this);
232 }
233
234 HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() {
235 if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
236 if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
237 return markup;
238 }
239 }
240 return nullptr;
241 }
242
243 ReplyKeyboard *HistoryItem::inlineReplyKeyboard() {
244 if (const auto markup = inlineReplyMarkup()) {
245 return markup->inlineKeyboard.get();
246 }
247 return nullptr;
248 }
249
250 ChannelData *HistoryItem::discussionPostOriginalSender() const {
251 if (!history()->peer->isMegagroup()) {
252 return nullptr;
253 }
254 if (const auto forwarded = Get<HistoryMessageForwarded>()) {
255 const auto from = forwarded->savedFromPeer;
256 if (const auto result = from ? from->asChannel() : nullptr) {
257 return result;
258 }
259 }
260 return nullptr;
261 }
262
263 bool HistoryItem::isDiscussionPost() const {
264 return (discussionPostOriginalSender() != nullptr);
265 }
266
267 HistoryItem *HistoryItem::lookupDiscussionPostOriginal() const {
268 if (!history()->peer->isMegagroup()) {
269 return nullptr;
270 }
271 const auto forwarded = Get<HistoryMessageForwarded>();
272 if (!forwarded
273 || !forwarded->savedFromPeer
274 || !forwarded->savedFromMsgId) {
275 return nullptr;
276 }
277 return _history->owner().message(
278 forwarded->savedFromPeer->asChannel(),
279 forwarded->savedFromMsgId);
280 }
281
282 PeerData *HistoryItem::displayFrom() const {
283 if (const auto sender = discussionPostOriginalSender()) {
284 return sender;
285 } else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
286 if (history()->peer->isSelf() || history()->peer->isRepliesChat() || forwarded->imported) {
287 return forwarded->originalSender;
288 }
289 }
290 return author().get();
291 }
292
293 void HistoryItem::invalidateChatListEntry() {
294 history()->session().changes().messageUpdated(
295 this,
296 Data::MessageUpdate::Flag::DialogRowRefresh);
297
298 // invalidate cache for drawInDialog
299 if (history()->textCachedFor == this) {
300 history()->textCachedFor = nullptr;
301 }
302 //if (const auto feed = history()->peer->feed()) { // #TODO archive
303 // if (feed->textCachedFor == this) {
304 // feed->textCachedFor = nullptr;
305 // feed->updateChatListEntry();
306 // }
307 //}
308 }
309
310 void HistoryItem::finishEditionToEmpty() {
311 finishEdition(-1);
312 _history->itemVanished(this);
313 }
314
315 bool HistoryItem::hasUnreadMediaFlag() const {
316 if (_history->peer->isChannel()) {
317 const auto passed = base::unixtime::now() - date();
318 const auto &config = _history->session().serverConfig();
319 if (passed >= config.channelsReadMediaPeriod) {
320 return false;
321 }
322 }
323 return _flags & MTPDmessage::Flag::f_media_unread;
324 }
325
326 bool HistoryItem::isUnreadMention() const {
327 return mentionsMe() && (_flags & MTPDmessage::Flag::f_media_unread);
328 }
329
330 bool HistoryItem::mentionsMe() const {
331 if (Has<HistoryServicePinned>()
332 && !Core::App().settings().notifyAboutPinned()) {
333 return false;
334 }
335 return _flags & MTPDmessage::Flag::f_mentioned;
336 }
337
338 bool HistoryItem::isUnreadMedia() const {
339 if (!hasUnreadMediaFlag()) {
340 return false;
341 } else if (const auto media = this->media()) {
342 if (const auto document = media->document()) {
343 if (document->isVoiceMessage() || document->isVideoMessage()) {
344 return (media->webpage() == nullptr);
345 }
346 }
347 }
348 return false;
349 }
350
351 void HistoryItem::markMediaRead() {
352 _flags &= ~MTPDmessage::Flag::f_media_unread;
353
354 if (mentionsMe()) {
355 history()->updateChatListEntry();
356 history()->eraseFromUnreadMentions(id);
357 }
358 }
359
360 void HistoryItem::setIsPinned(bool pinned) {
361 const auto changed = (isPinned() != pinned);
362 if (pinned) {
363 _flags |= MTPDmessage::Flag::f_pinned;
364 history()->session().storage().add(Storage::SharedMediaAddExisting(
365 history()->peer->id,
366 Storage::SharedMediaType::Pinned,
367 id,
368 { id, id }));
369 history()->peer->setHasPinnedMessages(true);
370 } else {
371 _flags &= ~MTPDmessage::Flag::f_pinned;
372 history()->session().storage().remove(Storage::SharedMediaRemoveOne(
373 history()->peer->id,
374 Storage::SharedMediaType::Pinned,
375 id));
376 }
377 if (changed) {
378 history()->owner().requestItemResize(this);
379 }
380 }
381
382 bool HistoryItem::definesReplyKeyboard() const {
383 if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
384 if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
385 return false;
386 }
387 return true;
388 }
389
390 // optimization: don't create markup component for the case
391 // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
392 return (_flags & MTPDmessage::Flag::f_reply_markup);
393 }
394
395 MTPDreplyKeyboardMarkup::Flags HistoryItem::replyKeyboardFlags() const {
396 Expects(definesReplyKeyboard());
397
398 if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
399 return markup->flags;
400 }
401
402 // optimization: don't create markup component for the case
403 // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
404 return MTPDreplyKeyboardMarkup_ClientFlag::f_zero | 0;
405 }
406
407 void HistoryItem::addLogEntryOriginal(
408 WebPageId localId,
409 const QString &label,
410 const TextWithEntities &content) {
411 Expects(isAdminLogEntry());
412
413 AddComponents(HistoryMessageLogEntryOriginal::Bit());
414 Get<HistoryMessageLogEntryOriginal>()->page = _history->owner().webpage(
415 localId,
416 label,
417 content);
418 }
419
420 PeerData *HistoryItem::specialNotificationPeer() const {
421 return (mentionsMe() && !_history->peer->isUser())
422 ? from().get()
423 : nullptr;
424 }
425
426 UserData *HistoryItem::viaBot() const {
427 if (const auto via = Get<HistoryMessageVia>()) {
428 return via->bot;
429 }
430 return nullptr;
431 }
432
433 UserData *HistoryItem::getMessageBot() const {
434 if (const auto bot = viaBot()) {
435 return bot;
436 }
437 auto bot = from()->asUser();
438 if (!bot) {
439 bot = history()->peer->asUser();
440 }
441 return (bot && bot->isBot()) ? bot : nullptr;
442 }
443
444 bool HistoryItem::isHistoryEntry() const {
445 return IsServerMsgId(id)
446 || (_clientFlags & MTPDmessage_ClientFlag::f_local_history_entry);
447 }
448
449 bool HistoryItem::isAdminLogEntry() const {
450 return (_clientFlags & MTPDmessage_ClientFlag::f_admin_log_entry);
451 }
452
453 bool HistoryItem::isFromScheduled() const {
454 return isHistoryEntry()
455 && (_flags & MTPDmessage::Flag::f_from_scheduled);
456 }
457
458 bool HistoryItem::isScheduled() const {
459 return !isHistoryEntry()
460 && !isAdminLogEntry()
461 && (_flags & MTPDmessage::Flag::f_from_scheduled);
462 }
463
464 void HistoryItem::destroy() {
465 _history->destroyMessage(this);
466 }
467
468 void HistoryItem::refreshMainView() {
469 if (const auto view = mainView()) {
470 _history->owner().notifyHistoryChangeDelayed(_history);
471 view->refreshInBlock();
472 }
473 }
474
475 void HistoryItem::removeMainView() {
476 if (const auto view = mainView()) {
477 _history->owner().notifyHistoryChangeDelayed(_history);
478 view->removeFromBlock();
479 }
480 }
481
482 void HistoryItem::clearMainView() {
483 _mainView = nullptr;
484 }
485
486 void HistoryItem::addToUnreadMentions(UnreadMentionType type) {
487 }
488
489 void HistoryItem::applyEditionToHistoryCleared() {
490 applyEdition(
491 MTP_messageService(
492 MTP_flags(0),
493 MTP_int(id),
494 peerToMTP(PeerId(0)), // from_id
495 peerToMTP(history()->peer->id),
496 MTPMessageReplyHeader(),
497 MTP_int(date()),
498 MTP_messageActionHistoryClear(),
499 MTPint() // ttl_period
500 ).c_messageService());
501 }
502
503 void HistoryItem::applySentMessage(const MTPDmessage &data) {
504 updateSentContent({
505 qs(data.vmessage()),
506 Api::EntitiesFromMTP(
507 &history()->session(),
508 data.ventities().value_or_empty())
509 }, data.vmedia());
510 updateReplyMarkup(data.vreply_markup());
511 updateForwardedInfo(data.vfwd_from());
512 setViewsCount(data.vviews().value_or(-1));
513 if (const auto replies = data.vreplies()) {
514 setReplies(*replies);
515 } else {
516 clearReplies();
517 }
518 setForwardsCount(data.vforwards().value_or(-1));
519 if (const auto reply = data.vreply_to()) {
520 reply->match([&](const MTPDmessageReplyHeader &data) {
521 setReplyToTop(
522 data.vreply_to_top_id().value_or(
523 data.vreply_to_msg_id().v));
524 });
525 }
526 setPostAuthor(data.vpost_author().value_or_empty());
527 contributeToSlowmode(data.vdate().v);
528 indexAsNewItem();
529 history()->owner().requestItemTextRefresh(this);
530 history()->owner().updateDependentMessages(this);
531 }
532
533 void HistoryItem::applySentMessage(
534 const QString &text,
535 const MTPDupdateShortSentMessage &data,
536 bool wasAlready) {
537 updateSentContent({
538 text,
539 Api::EntitiesFromMTP(
540 &history()->session(),
541 data.ventities().value_or_empty())
542 }, data.vmedia());
543 contributeToSlowmode(data.vdate().v);
544 if (!wasAlready) {
545 indexAsNewItem();
546 }
547 }
548
549 void HistoryItem::indexAsNewItem() {
550 if (IsServerMsgId(id)) {
551 addToUnreadMentions(UnreadMentionType::New);
552 if (const auto types = sharedMediaTypes()) {
553 _history->session().storage().add(Storage::SharedMediaAddNew(
554 _history->peer->id,
555 types,
556 id));
557 if (types.test(Storage::SharedMediaType::Pinned)) {
558 _history->peer->setHasPinnedMessages(true);
559 }
560 }
561 //if (const auto channel = history()->peer->asChannel()) { // #feed
562 // if (const auto feed = channel->feed()) {
563 // _history->session().storage().add(Storage::FeedMessagesAddNew(
564 // feed->id(),
565 // position()));
566 // }
567 //}
568 }
569 }
570
571 void HistoryItem::setRealId(MsgId newId) {
572 Expects(_clientFlags & MTPDmessage_ClientFlag::f_sending);
573 Expects(IsClientMsgId(id));
574
575 const auto oldId = std::exchange(id, newId);
576 _clientFlags &= ~MTPDmessage_ClientFlag::f_sending;
577 if (IsServerMsgId(id)) {
578 _history->unregisterLocalMessage(this);
579 }
580 _history->owner().notifyItemIdChange({ this, oldId });
581
582 // We don't fire MessageUpdate::Flag::ReplyMarkup and update keyboard
583 // in history widget, because it can't exist for an outgoing message.
584 // Only inline keyboards can be in outgoing messages.
585 if (const auto markup = inlineReplyMarkup()) {
586 if (markup->inlineKeyboard) {
587 markup->inlineKeyboard->updateMessageId();
588 }
589 }
590
591 _history->owner().requestItemRepaint(this);
592 }
593
594 bool HistoryItem::canPin() const {
595 if (id < 0 || !toHistoryMessage()) {
596 return false;
597 } else if (const auto m = media(); m && m->call()) {
598 return false;
599 }
600 return _history->peer->canPinMessages();
601 }
602
603 bool HistoryItem::allowsSendNow() const {
604 return false;
605 }
606
607 bool HistoryItem::allowsForward() const {
608 return false;
609 }
610
611 bool HistoryItem::allowsEdit(TimeId now) const {
612 return false;
613 }
614
615 bool HistoryItem::canStopPoll() const {
616 if (id < 0
617 || Has<HistoryMessageVia>()
618 || Has<HistoryMessageForwarded>()) {
619 return false;
620 }
621
622 const auto peer = _history->peer;
623 if (peer->isSelf()) {
624 return true;
625 } else if (const auto channel = peer->asChannel()) {
626 if (isPost() && channel->canEditMessages()) {
627 return true;
628 } else if (out()) {
629 return isPost() ? channel->canPublish() : channel->canWrite();
630 } else {
631 return false;
632 }
633 }
634 return out();
635 }
636
637 bool HistoryItem::canDelete() const {
638 if (!IsServerMsgId(id) && serviceMsg()) {
639 return false;
640 } else if (!isHistoryEntry() && !isScheduled()) {
641 return false;
642 }
643 auto channel = _history->peer->asChannel();
644 if (!channel) {
645 return !isGroupMigrate();
646 }
647
648 if (id == 1) {
649 return false;
650 }
651 if (channel->canDeleteMessages()) {
652 return true;
653 }
654 if (out() && toHistoryMessage()) {
655 return isPost() ? channel->canPublish() : true;
656 }
657 return false;
658 }
659
660 bool HistoryItem::canDeleteForEveryone(TimeId now) const {
661 const auto peer = history()->peer;
662 const auto &config = history()->session().serverConfig();
663 const auto messageToMyself = peer->isSelf();
664 const auto messageTooOld = messageToMyself
665 ? false
666 : peer->isUser()
667 ? (now - date() >= config.revokePrivateTimeLimit)
668 : (now - date() >= config.revokeTimeLimit);
669 if (id < 0 || messageToMyself || messageTooOld || isPost()) {
670 return false;
671 }
672 if (peer->isChannel()) {
673 return false;
674 } else if (const auto user = peer->asUser()) {
675 // Bots receive all messages and there is no sense in revoking them.
676 // See https://github.com/telegramdesktop/tdesktop/issues/3818
677 if (user->isBot() && !user->isSupport()) {
678 return false;
679 }
680 }
681 if (const auto media = this->media()) {
682 if (!media->allowsRevoke(now)) {
683 return false;
684 }
685 }
686 if (!out()) {
687 if (const auto chat = peer->asChat()) {
688 if (!chat->canDeleteMessages()) {
689 return false;
690 }
691 } else if (peer->isUser()) {
692 return config.revokePrivateInbox;
693 } else {
694 return false;
695 }
696 }
697 return true;
698 }
699
700 bool HistoryItem::suggestReport() const {
701 if (out() || serviceMsg() || !IsServerMsgId(id)) {
702 return false;
703 } else if (const auto channel = history()->peer->asChannel()) {
704 return true;
705 } else if (const auto user = history()->peer->asUser()) {
706 return user->isBot();
707 }
708 return false;
709 }
710
711 bool HistoryItem::suggestBanReport() const {
712 auto channel = history()->peer->asChannel();
713 auto fromUser = from()->asUser();
714 if (!channel || !fromUser || !channel->canRestrictUser(fromUser)) {
715 return false;
716 }
717 return !isPost() && !out();
718 }
719
720 bool HistoryItem::suggestDeleteAllReport() const {
721 auto channel = history()->peer->asChannel();
722 if (!channel || !channel->canDeleteMessages()) {
723 return false;
724 }
725 return !isPost() && !out() && from()->isUser();
726 }
727
728 bool HistoryItem::hasDirectLink() const {
729 return IsServerMsgId(id) && _history->peer->isChannel();
730 }
731
732 ChannelId HistoryItem::channelId() const {
733 return _history->channelId();
734 }
735
736 Data::MessagePosition HistoryItem::position() const {
737 return { .fullId = fullId(), .date = date() };
738 }
739
740 MsgId HistoryItem::replyToId() const {
741 if (const auto reply = Get<HistoryMessageReply>()) {
742 return reply->replyToId();
743 }
744 return 0;
745 }
746
747 MsgId HistoryItem::replyToTop() const {
748 if (const auto reply = Get<HistoryMessageReply>()) {
749 return reply->replyToTop();
750 }
751 return 0;
752 }
753
754 not_null<PeerData*> HistoryItem::author() const {
755 return isPost() ? history()->peer : from();
756 }
757
758 TimeId HistoryItem::dateOriginal() const {
759 if (const auto forwarded = Get<HistoryMessageForwarded>()) {
760 return forwarded->originalDate;
761 }
762 return date();
763 }
764
765 PeerData *HistoryItem::senderOriginal() const {
766 if (const auto forwarded = Get<HistoryMessageForwarded>()) {
767 return forwarded->originalSender;
768 }
769 const auto peer = history()->peer;
770 return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
771 }
772
773 const HiddenSenderInfo *HistoryItem::hiddenForwardedInfo() const {
774 if (const auto forwarded = Get<HistoryMessageForwarded>()) {
775 return forwarded->hiddenSenderInfo.get();
776 }
777 return nullptr;
778 }
779
780 not_null<PeerData*> HistoryItem::fromOriginal() const {
781 if (const auto forwarded = Get<HistoryMessageForwarded>()) {
782 if (forwarded->originalSender) {
783 if (const auto user = forwarded->originalSender->asUser()) {
784 return user;
785 }
786 }
787 }
788 return from();
789 }
790
791 QString HistoryItem::authorOriginal() const {
792 if (const auto forwarded = Get<HistoryMessageForwarded>()) {
793 return forwarded->originalAuthor;
794 } else if (const auto msgsigned = Get<HistoryMessageSigned>()) {
795 if (!msgsigned->isAnonymousRank) {
796 return msgsigned->author;
797 }
798 }
799 return QString();
800 }
801
802 MsgId HistoryItem::idOriginal() const {
803 if (const auto forwarded = Get<HistoryMessageForwarded>()) {
804 return forwarded->originalId;
805 }
806 return id;
807 }
808
809 void HistoryItem::updateDate(TimeId newDate) {
810 if (canUpdateDate() && _date != newDate) {
811 _date = newDate;
812 _history->owner().requestItemViewRefresh(this);
813 }
814 }
815
816 bool HistoryItem::canUpdateDate() const {
817 return isScheduled();
818 }
819
820 void HistoryItem::applyTTL(const MTPDmessage &data) {
821 if (const auto period = data.vttl_period()) {
822 if (period->v > 0) {
823 applyTTL(data.vdate().v + period->v);
824 }
825 }
826 }
827
828 void HistoryItem::applyTTL(const MTPDmessageService &data) {
829 if (const auto period = data.vttl_period()) {
830 if (period->v > 0) {
831 applyTTL(data.vdate().v + period->v);
832 }
833 }
834 }
835
836 void HistoryItem::applyTTL(TimeId destroyAt) {
837 const auto previousDestroyAt = std::exchange(_ttlDestroyAt, destroyAt);
838 if (previousDestroyAt) {
839 history()->owner().unregisterMessageTTL(previousDestroyAt, this);
840 }
841 if (!_ttlDestroyAt) {
842 return;
843 } else if (base::unixtime::now() >= _ttlDestroyAt) {
844 const auto session = &history()->session();
845 crl::on_main(session, [session, id = fullId()]{
846 if (const auto item = session->data().message(id)) {
847 item->destroy();
848 }
849 });
850 } else {
851 history()->owner().registerMessageTTL(_ttlDestroyAt, this);
852 }
853 }
854
855 void HistoryItem::sendFailed() {
856 Expects(_clientFlags & MTPDmessage_ClientFlag::f_sending);
857 Expects(!(_clientFlags & MTPDmessage_ClientFlag::f_failed));
858
859 _clientFlags = (_clientFlags | MTPDmessage_ClientFlag::f_failed)
860 & ~MTPDmessage_ClientFlag::f_sending;
861 history()->session().changes().historyUpdated(
862 history(),
863 Data::HistoryUpdate::Flag::LocalMessages);
864 }
865
866 bool HistoryItem::needCheck() const {
867 return (out() && !isEmpty()) || (id < 0 && history()->peer->isSelf());
868 }
869
870 bool HistoryItem::unread() const {
871 // Messages from myself are always read, unless scheduled.
872 if (history()->peer->isSelf() && !isFromScheduled()) {
873 return false;
874 }
875
876 if (out()) {
877 // Outgoing messages in converted chats are always read.
878 if (history()->peer->migrateTo()) {
879 return false;
880 }
881
882 if (IsServerMsgId(id)) {
883 if (!history()->isServerSideUnread(this)) {
884 return false;
885 }
886 if (const auto user = history()->peer->asUser()) {
887 if (user->isBot()) {
888 return false;
889 }
890 } else if (const auto channel = history()->peer->asChannel()) {
891 if (!channel->isMegagroup()) {
892 return false;
893 }
894 }
895 }
896 return true;
897 }
898
899 if (IsServerMsgId(id)) {
900 if (!history()->isServerSideUnread(this)) {
901 return false;
902 }
903 return true;
904 }
905 return (_clientFlags & MTPDmessage_ClientFlag::f_clientside_unread);
906 }
907
908 bool HistoryItem::showNotification() const {
909 const auto channel = _history->peer->asChannel();
910 if (channel && !channel->amIn()) {
911 return false;
912 }
913 return (out() || _history->peer->isSelf())
914 ? isFromScheduled()
915 : unread();
916 }
917
918 void HistoryItem::markClientSideAsRead() {
919 _clientFlags &= ~MTPDmessage_ClientFlag::f_clientside_unread;
920 }
921
922 MessageGroupId HistoryItem::groupId() const {
923 return _groupId;
924 }
925
926 bool HistoryItem::isEmpty() const {
927 return _text.isEmpty()
928 && !_media
929 && !Has<HistoryMessageLogEntryOriginal>();
930 }
931
932 QString HistoryItem::notificationText() const {
933 const auto result = [&] {
934 if (_media) {
935 return _media->notificationText();
936 } else if (!emptyText()) {
937 return _text.toString();
938 }
939 return QString();
940 }();
941 return (result.size() <= kNotificationTextLimit)
942 ? result
943 : result.mid(0, kNotificationTextLimit) + qsl("...");
944 }
945
946 QString HistoryItem::inDialogsText(DrawInDialog way) const {
947 auto getText = [this]() {
948 if (_media) {
949 if (_groupId) {
950 return textcmdLink(1, TextUtilities::Clean(tr::lng_in_dlg_album(tr::now)));
951 }
952 return _media->chatListText();
953 } else if (!emptyText()) {
954 return TextUtilities::Clean(_text.toString());
955 }
956 return QString();
957 };
958 const auto plainText = getText();
959 const auto sender = [&]() -> PeerData* {
960 if (isPost() || isEmpty() || (way == DrawInDialog::WithoutSender)) {
961 return nullptr;
962 } else if (!_history->peer->isUser() || out()) {
963 return displayFrom();
964 } else if (_history->peer->isSelf() && !Has<HistoryMessageForwarded>()) {
965 return senderOriginal();
966 }
967 return nullptr;
968 }();
969 if (sender) {
970 auto fromText = sender->isSelf() ? tr::lng_from_you(tr::now) : sender->shortName();
971 auto fromWrapped = textcmdLink(1, tr::lng_dialogs_text_from_wrapped(tr::now, lt_from, TextUtilities::Clean(fromText)));
972 return tr::lng_dialogs_text_with_from(tr::now, lt_from_part, fromWrapped, lt_message, plainText);
973 }
974 return plainText;
975 }
976
977 Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
978 return Ui::Text::IsolatedEmoji();
979 }
980
981 void HistoryItem::drawInDialog(
982 Painter &p,
983 const QRect &r,
984 bool active,
985 bool selected,
986 DrawInDialog way,
987 const HistoryItem *&cacheFor,
988 Ui::Text::String &cache) const {
989 if (r.isEmpty()) {
990 return;
991 }
992 if (cacheFor != this) {
993 cacheFor = this;
994 cache.setText(st::dialogsTextStyle, inDialogsText(way), Ui::DialogTextOptions());
995 }
996 p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
997 p.setFont(st::dialogsTextFont);
998 p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
999 cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dialogsTextFont->height);
1000 p.restoreTextPalette();
1001 }
1002
1003 HistoryItem::~HistoryItem() {
1004 applyTTL(0);
1005 }
1006
1007 QDateTime ItemDateTime(not_null<const HistoryItem*> item) {
1008 return base::unixtime::parse(item->date());
1009 }
1010
1011 QString ItemDateText(not_null<const HistoryItem*> item, bool isUntilOnline) {
1012 const auto dateText = langDayOfMonthFull(ItemDateTime(item).date());
1013 return !item->isScheduled()
1014 ? dateText
1015 : isUntilOnline
1016 ? tr::lng_scheduled_date_until_online(tr::now)
1017 : tr::lng_scheduled_date(tr::now, lt_date, dateText);
1018 }
1019
1020 bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item) {
1021 return item->isScheduled()
1022 && (item->date() ==
1023 Data::ScheduledMessages::kScheduledUntilOnlineTimestamp);
1024 }
1025
1026 ClickHandlerPtr goToMessageClickHandler(
1027 not_null<HistoryItem*> item,
1028 FullMsgId returnToId) {
1029 return goToMessageClickHandler(
1030 item->history()->peer,
1031 item->id,
1032 returnToId);
1033 }
1034
1035 ClickHandlerPtr goToMessageClickHandler(
1036 not_null<PeerData*> peer,
1037 MsgId msgId,
1038 FullMsgId returnToId) {
1039 return std::make_shared<LambdaClickHandler>([=] {
1040 if (const auto main = App::main()) { // multi good
1041 if (&main->session() == &peer->session()) {
1042 auto params = Window::SectionShow{
1043 Window::SectionShow::Way::Forward
1044 };
1045 params.origin = Window::SectionShow::OriginMessage{
1046 returnToId
1047 };
1048 main->controller()->showPeerHistory(peer, params, msgId);
1049 }
1050 }
1051 });
1052 }
1053
1054 not_null<HistoryItem*> HistoryItem::Create(
1055 not_null<History*> history,
1056 const MTPMessage &message,
1057 MTPDmessage_ClientFlags clientFlags) {
1058 return message.match([&](const MTPDmessage &data) -> HistoryItem* {
1059 const auto media = data.vmedia();
1060 const auto checked = media
1061 ? CheckMessageMedia(*media)
1062 : MediaCheckResult::Good;
1063 if (checked == MediaCheckResult::Unsupported) {
1064 return CreateUnsupportedMessage(
1065 history,
1066 data.vid().v,
1067 data.vflags().v,
1068 clientFlags,
1069 MsgId(0), // No need to pass reply_to data here.
1070 data.vvia_bot_id().value_or_empty(),
1071 data.vdate().v,
1072 data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
1073 } else if (checked == MediaCheckResult::Empty) {
1074 const auto text = HistoryService::PreparedText{
1075 tr::lng_message_empty(tr::now)
1076 };
1077 return history->makeServiceMessage(
1078 data.vid().v,
1079 clientFlags,
1080 data.vdate().v,
1081 text,
1082 data.vflags().v,
1083 data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
1084 } else if (checked == MediaCheckResult::HasTimeToLive) {
1085 return history->makeServiceMessage(data, clientFlags);
1086 }
1087 return history->makeMessage(data, clientFlags);
1088 }, [&](const MTPDmessageService &data) -> HistoryItem* {
1089 if (data.vaction().type() == mtpc_messageActionPhoneCall) {
1090 return history->makeMessage(data, clientFlags);
1091 }
1092 return history->makeServiceMessage(data, clientFlags);
1093 }, [&](const MTPDmessageEmpty &data) -> HistoryItem* {
1094 const auto text = HistoryService::PreparedText{
1095 tr::lng_message_empty(tr::now)
1096 };
1097 return history->makeServiceMessage(
1098 data.vid().v,
1099 clientFlags,
1100 TimeId(0),
1101 text);
1102 });
1103 }