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 "history/view/history_view_element.h"
13 #include "history/view/history_view_service_message.h"
14 #include "history/history_item_components.h"
15 #include "history/view/media/history_view_media_grouped.h"
16 #include "history/history_service.h"
17 #include "history/history_message.h"
18 #include "history/history.h"
19 #include "mtproto/mtproto_config.h"
20 #include "media/clip/media_clip_reader.h"
21 #include "ui/effects/ripple_animation.h"
22 #include "ui/text/text_isolated_emoji.h"
23 #include "ui/text/text_options.h"
24 #include "storage/file_upload.h"
25 #include "storage/storage_facade.h"
26 #include "storage/storage_shared_media.h"
27 #include "main/main_session.h"
28 #include "apiwrap.h"
29 #include "media/audio/media_audio.h"
30 #include "core/application.h"
31 #include "mainwindow.h"
32 #include "window/window_session_controller.h"
33 #include "core/crash_reports.h"
34 #include "base/unixtime.h"
35 #include "api/api_text_entities.h"
36 #include "dialogs/ui/dialogs_message_view.h"
37 #include "data/data_scheduled_messages.h" // kScheduledUntilOnlineTimestamp
38 #include "data/data_changes.h"
39 #include "data/data_session.h"
40 #include "data/data_messages.h"
41 #include "data/data_media_types.h"
42 #include "data/data_folder.h"
43 #include "data/data_channel.h"
44 #include "data/data_chat.h"
45 #include "data/data_user.h"
46 #include "styles/style_dialogs.h"
47 #include "styles/style_chat.h"
48 
49 namespace {
50 
51 constexpr auto kNotificationTextLimit = 255;
52 
53 using ItemPreview = HistoryView::ItemPreview;
54 
55 enum class MediaCheckResult {
56 	Good,
57 	Unsupported,
58 	Empty,
59 	HasTimeToLive,
60 };
61 
CreateUnsupportedMessage(not_null<History * > history,MsgId msgId,MessageFlags flags,MsgId replyTo,UserId viaBotId,TimeId date,PeerId from)62 not_null<HistoryItem*> CreateUnsupportedMessage(
63 		not_null<History*> history,
64 		MsgId msgId,
65 		MessageFlags flags,
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 &= ~MessageFlag::HasPostAuthor;
78 	flags |= MessageFlag::Legacy;
79 	const auto groupedId = uint64();
80 	return history->makeMessage(
81 		msgId,
82 		flags,
83 		replyTo,
84 		viaBotId,
85 		date,
86 		from,
87 		QString(),
88 		text,
89 		MTP_messageMediaEmpty(),
90 		HistoryMessageMarkupData(),
91 		groupedId);
92 }
93 
CheckMessageMedia(const MTPMessageMedia & media)94 MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
95 	using Result = MediaCheckResult;
96 	return media.match([](const MTPDmessageMediaEmpty &) {
97 		return Result::Good;
98 	}, [](const MTPDmessageMediaContact &) {
99 		return Result::Good;
100 	}, [](const MTPDmessageMediaGeo &data) {
101 		return data.vgeo().match([](const MTPDgeoPoint &) {
102 			return Result::Good;
103 		}, [](const MTPDgeoPointEmpty &) {
104 			return Result::Empty;
105 		});
106 	}, [](const MTPDmessageMediaVenue &data) {
107 		return data.vgeo().match([](const MTPDgeoPoint &) {
108 			return Result::Good;
109 		}, [](const MTPDgeoPointEmpty &) {
110 			return Result::Empty;
111 		});
112 	}, [](const MTPDmessageMediaGeoLive &data) {
113 		return data.vgeo().match([](const MTPDgeoPoint &) {
114 			return Result::Good;
115 		}, [](const MTPDgeoPointEmpty &) {
116 			return Result::Empty;
117 		});
118 	}, [](const MTPDmessageMediaPhoto &data) {
119 		const auto photo = data.vphoto();
120 		if (data.vttl_seconds()) {
121 			return Result::HasTimeToLive;
122 		} else if (!photo) {
123 			return Result::Empty;
124 		}
125 		return photo->match([](const MTPDphoto &) {
126 			return Result::Good;
127 		}, [](const MTPDphotoEmpty &) {
128 			return Result::Empty;
129 		});
130 	}, [](const MTPDmessageMediaDocument &data) {
131 		const auto document = data.vdocument();
132 		if (data.vttl_seconds()) {
133 			return Result::HasTimeToLive;
134 		} else if (!document) {
135 			return Result::Empty;
136 		}
137 		return document->match([](const MTPDdocument &) {
138 			return Result::Good;
139 		}, [](const MTPDdocumentEmpty &) {
140 			return Result::Empty;
141 		});
142 	}, [](const MTPDmessageMediaWebPage &data) {
143 		return data.vwebpage().match([](const MTPDwebPage &) {
144 			return Result::Good;
145 		}, [](const MTPDwebPageEmpty &) {
146 			return Result::Good;
147 		}, [](const MTPDwebPagePending &) {
148 			return Result::Good;
149 		}, [](const MTPDwebPageNotModified &) {
150 			return Result::Unsupported;
151 		});
152 	}, [](const MTPDmessageMediaGame &data) {
153 		return data.vgame().match([](const MTPDgame &) {
154 			return Result::Good;
155 		});
156 	}, [](const MTPDmessageMediaInvoice &) {
157 		return Result::Good;
158 	}, [](const MTPDmessageMediaPoll &) {
159 		return Result::Good;
160 	}, [](const MTPDmessageMediaDice &) {
161 		return Result::Good;
162 	}, [](const MTPDmessageMediaUnsupported &) {
163 		return Result::Unsupported;
164 	});
165 }
166 
FinalizeMessageFlags(MessageFlags flags)167 [[nodiscard]] MessageFlags FinalizeMessageFlags(MessageFlags flags) {
168 	if (!(flags & MessageFlag::FakeHistoryItem)
169 		&& !(flags & MessageFlag::IsOrWasScheduled)
170 		&& !(flags & MessageFlag::AdminLogEntry)) {
171 		flags |= MessageFlag::HistoryEntry;
172 	}
173 	return flags;
174 }
175 
176 } // namespace
177 
operator ()(HistoryItem * value)178 void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
179 	if (value) {
180 		value->destroy();
181 	}
182 }
183 
HistoryItem(not_null<History * > history,MsgId id,MessageFlags flags,TimeId date,PeerId from)184 HistoryItem::HistoryItem(
185 	not_null<History*> history,
186 	MsgId id,
187 	MessageFlags flags,
188 	TimeId date,
189 	PeerId from)
190 : id(id)
191 , _history(history)
192 , _from(from ? history->owner().peer(from) : history->peer)
193 , _flags(FinalizeMessageFlags(flags))
194 , _date(date) {
195 	if (isHistoryEntry() && IsClientMsgId(id)) {
196 		_history->registerClientSideMessage(this);
197 	}
198 }
199 
date() const200 TimeId HistoryItem::date() const {
201 	return _date;
202 }
203 
NewMessageDate(TimeId scheduled)204 TimeId HistoryItem::NewMessageDate(TimeId scheduled) {
205 	return scheduled ? scheduled : base::unixtime::now();
206 }
207 
applyServiceDateEdition(const MTPDmessageService & data)208 void HistoryItem::applyServiceDateEdition(const MTPDmessageService &data) {
209 	const auto date = data.vdate().v;
210 	if (_date == date) {
211 		return;
212 	}
213 	_date = date;
214 }
215 
finishEdition(int oldKeyboardTop)216 void HistoryItem::finishEdition(int oldKeyboardTop) {
217 	if (const auto group = _history->owner().groups().find(this)) {
218 		for (const auto &item : group->items) {
219 			_history->owner().requestItemViewRefresh(item);
220 			item->invalidateChatListEntry();
221 		}
222 	} else {
223 		_history->owner().requestItemViewRefresh(this);
224 		invalidateChatListEntry();
225 	}
226 
227 	// Should be completely redesigned as the oldTop no longer exists.
228 	//if (oldKeyboardTop >= 0) { // #TODO edit bot message
229 	//	if (auto keyboard = Get<HistoryMessageReplyMarkup>()) {
230 	//		keyboard->oldTop = oldKeyboardTop;
231 	//	}
232 	//}
233 
234 	_history->owner().updateDependentMessages(this);
235 }
236 
setGroupId(MessageGroupId groupId)237 void HistoryItem::setGroupId(MessageGroupId groupId) {
238 	Expects(!_groupId);
239 
240 	_groupId = groupId;
241 	_history->owner().groups().registerMessage(this);
242 }
243 
inlineReplyMarkup()244 HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() {
245 	if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
246 		if (markup->data.flags & ReplyMarkupFlag::Inline) {
247 			return markup;
248 		}
249 	}
250 	return nullptr;
251 }
252 
inlineReplyKeyboard()253 ReplyKeyboard *HistoryItem::inlineReplyKeyboard() {
254 	if (const auto markup = inlineReplyMarkup()) {
255 		return markup->inlineKeyboard.get();
256 	}
257 	return nullptr;
258 }
259 
discussionPostOriginalSender() const260 ChannelData *HistoryItem::discussionPostOriginalSender() const {
261 	if (!history()->peer->isMegagroup()) {
262 		return nullptr;
263 	}
264 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
265 		const auto from = forwarded->savedFromPeer;
266 		if (const auto result = from ? from->asChannel() : nullptr) {
267 			return result;
268 		}
269 	}
270 	return nullptr;
271 }
272 
isDiscussionPost() const273 bool HistoryItem::isDiscussionPost() const {
274 	return (discussionPostOriginalSender() != nullptr);
275 }
276 
lookupDiscussionPostOriginal() const277 HistoryItem *HistoryItem::lookupDiscussionPostOriginal() const {
278 	if (!history()->peer->isMegagroup()) {
279 		return nullptr;
280 	}
281 	const auto forwarded = Get<HistoryMessageForwarded>();
282 	if (!forwarded
283 		|| !forwarded->savedFromPeer
284 		|| !forwarded->savedFromMsgId) {
285 		return nullptr;
286 	}
287 	return _history->owner().message(
288 		forwarded->savedFromPeer->asChannel(),
289 		forwarded->savedFromMsgId);
290 }
291 
displayFrom() const292 PeerData *HistoryItem::displayFrom() const {
293 	if (const auto sender = discussionPostOriginalSender()) {
294 		return sender;
295 	} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
296 		if (history()->peer->isSelf() || history()->peer->isRepliesChat() || forwarded->imported) {
297 			return forwarded->originalSender;
298 		}
299 	}
300 	return author().get();
301 }
302 
invalidateChatListEntry()303 void HistoryItem::invalidateChatListEntry() {
304 	history()->session().changes().messageUpdated(
305 		this,
306 		Data::MessageUpdate::Flag::DialogRowRefresh);
307 	history()->lastItemDialogsView.itemInvalidated(this);
308 }
309 
finishEditionToEmpty()310 void HistoryItem::finishEditionToEmpty() {
311 	finishEdition(-1);
312 	_history->itemVanished(this);
313 }
314 
hasUnreadMediaFlag() const315 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 & MessageFlag::MediaIsUnread;
324 }
325 
isUnreadMention() const326 bool HistoryItem::isUnreadMention() const {
327 	return mentionsMe() && (_flags & MessageFlag::MediaIsUnread);
328 }
329 
mentionsMe() const330 bool HistoryItem::mentionsMe() const {
331 	if (Has<HistoryServicePinned>()
332 		&& !Core::App().settings().notifyAboutPinned()) {
333 		return false;
334 	}
335 	return _flags & MessageFlag::MentionsMe;
336 }
337 
isUnreadMedia() const338 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 
markMediaRead()351 void HistoryItem::markMediaRead() {
352 	_flags &= ~MessageFlag::MediaIsUnread;
353 
354 	if (mentionsMe()) {
355 		history()->updateChatListEntry();
356 		history()->eraseFromUnreadMentions(id);
357 	}
358 }
359 
setIsPinned(bool pinned)360 void HistoryItem::setIsPinned(bool pinned) {
361 	const auto changed = (isPinned() != pinned);
362 	if (pinned) {
363 		_flags |= MessageFlag::Pinned;
364 		history()->session().storage().add(Storage::SharedMediaAddExisting(
365 			history()->peer->id,
366 			Storage::SharedMediaType::Pinned,
367 			id,
368 			{ id, id }));
369 		history()->setHasPinnedMessages(true);
370 	} else {
371 		_flags &= ~MessageFlag::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 
definesReplyKeyboard() const382 bool HistoryItem::definesReplyKeyboard() const {
383 	if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
384 		if (markup->data.flags & ReplyMarkupFlag::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 & MessageFlag::HasReplyMarkup);
393 }
394 
replyKeyboardFlags() const395 ReplyMarkupFlags HistoryItem::replyKeyboardFlags() const {
396 	Expects(definesReplyKeyboard());
397 
398 	if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
399 		return markup->data.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 ReplyMarkupFlag::None;
405 }
406 
addLogEntryOriginal(WebPageId localId,const QString & label,const TextWithEntities & content)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 
specialNotificationPeer() const420 PeerData *HistoryItem::specialNotificationPeer() const {
421 	return (mentionsMe() && !_history->peer->isUser())
422 		? from().get()
423 		: nullptr;
424 }
425 
viaBot() const426 UserData *HistoryItem::viaBot() const {
427 	if (const auto via = Get<HistoryMessageVia>()) {
428 		return via->bot;
429 	}
430 	return nullptr;
431 }
432 
getMessageBot() const433 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 
isHistoryEntry() const444 bool HistoryItem::isHistoryEntry() const {
445 	return (_flags & MessageFlag::HistoryEntry);
446 }
447 
isAdminLogEntry() const448 bool HistoryItem::isAdminLogEntry() const {
449 	return (_flags & MessageFlag::AdminLogEntry);
450 }
451 
isFromScheduled() const452 bool HistoryItem::isFromScheduled() const {
453 	return isHistoryEntry()
454 		&& (_flags & MessageFlag::IsOrWasScheduled);
455 }
456 
isScheduled() const457 bool HistoryItem::isScheduled() const {
458 	return !isHistoryEntry()
459 		&& !isAdminLogEntry()
460 		&& (_flags & MessageFlag::IsOrWasScheduled);
461 }
462 
isSponsored() const463 bool HistoryItem::isSponsored() const {
464 	return (_flags & MessageFlag::IsSponsored);
465 }
466 
skipNotification() const467 bool HistoryItem::skipNotification() const {
468 	if (isSilent() && (_flags & MessageFlag::IsContactSignUp)) {
469 		return true;
470 	} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
471 		if (forwarded->imported) {
472 			return true;
473 		}
474 	}
475 	return false;
476 }
477 
destroy()478 void HistoryItem::destroy() {
479 	_history->destroyMessage(this);
480 }
481 
refreshMainView()482 void HistoryItem::refreshMainView() {
483 	if (const auto view = mainView()) {
484 		_history->owner().notifyHistoryChangeDelayed(_history);
485 		view->refreshInBlock();
486 	}
487 }
488 
removeMainView()489 void HistoryItem::removeMainView() {
490 	if (const auto view = mainView()) {
491 		_history->owner().notifyHistoryChangeDelayed(_history);
492 		view->removeFromBlock();
493 	}
494 }
495 
clearMainView()496 void HistoryItem::clearMainView() {
497 	_mainView = nullptr;
498 }
499 
addToUnreadMentions(UnreadMentionType type)500 void HistoryItem::addToUnreadMentions(UnreadMentionType type) {
501 }
502 
applyEditionToHistoryCleared()503 void HistoryItem::applyEditionToHistoryCleared() {
504 	applyEdition(
505 		MTP_messageService(
506 			MTP_flags(0),
507 			MTP_int(id),
508 			peerToMTP(PeerId(0)), // from_id
509 			peerToMTP(history()->peer->id),
510 			MTPMessageReplyHeader(),
511 			MTP_int(date()),
512 			MTP_messageActionHistoryClear(),
513 			MTPint() // ttl_period
514 		).c_messageService());
515 }
516 
applySentMessage(const MTPDmessage & data)517 void HistoryItem::applySentMessage(const MTPDmessage &data) {
518 	updateSentContent({
519 		qs(data.vmessage()),
520 		Api::EntitiesFromMTP(
521 			&history()->session(),
522 			data.ventities().value_or_empty())
523 	}, data.vmedia());
524 	updateReplyMarkup(HistoryMessageMarkupData(data.vreply_markup()));
525 	updateForwardedInfo(data.vfwd_from());
526 	setViewsCount(data.vviews().value_or(-1));
527 	if (const auto replies = data.vreplies()) {
528 		setReplies(HistoryMessageRepliesData(replies));
529 	} else {
530 		clearReplies();
531 	}
532 	setForwardsCount(data.vforwards().value_or(-1));
533 	if (const auto reply = data.vreply_to()) {
534 		reply->match([&](const MTPDmessageReplyHeader &data) {
535 			setReplyToTop(
536 				data.vreply_to_top_id().value_or(
537 					data.vreply_to_msg_id().v));
538 		});
539 	}
540 	setPostAuthor(data.vpost_author().value_or_empty());
541 	contributeToSlowmode(data.vdate().v);
542 	indexAsNewItem();
543 	history()->owner().requestItemTextRefresh(this);
544 	history()->owner().updateDependentMessages(this);
545 }
546 
applySentMessage(const QString & text,const MTPDupdateShortSentMessage & data,bool wasAlready)547 void HistoryItem::applySentMessage(
548 		const QString &text,
549 		const MTPDupdateShortSentMessage &data,
550 		bool wasAlready) {
551 	updateSentContent({
552 		text,
553 		Api::EntitiesFromMTP(
554 			&history()->session(),
555 			data.ventities().value_or_empty())
556 		}, data.vmedia());
557 	contributeToSlowmode(data.vdate().v);
558 	if (!wasAlready) {
559 		indexAsNewItem();
560 	}
561 }
562 
indexAsNewItem()563 void HistoryItem::indexAsNewItem() {
564 	if (isRegular()) {
565 		addToUnreadMentions(UnreadMentionType::New);
566 		if (const auto types = sharedMediaTypes()) {
567 			_history->session().storage().add(Storage::SharedMediaAddNew(
568 				_history->peer->id,
569 				types,
570 				id));
571 			if (types.test(Storage::SharedMediaType::Pinned)) {
572 				_history->setHasPinnedMessages(true);
573 			}
574 		}
575 	}
576 }
577 
setRealId(MsgId newId)578 void HistoryItem::setRealId(MsgId newId) {
579 	Expects(_flags & MessageFlag::BeingSent);
580 	Expects(IsClientMsgId(id));
581 
582 	const auto oldId = std::exchange(id, newId);
583 	_flags &= ~(MessageFlag::BeingSent | MessageFlag::Local);
584 	if (isRegular()) {
585 		_history->unregisterClientSideMessage(this);
586 	}
587 	_history->owner().notifyItemIdChange({ this, oldId });
588 
589 	// We don't fire MessageUpdate::Flag::ReplyMarkup and update keyboard
590 	// in history widget, because it can't exist for an outgoing message.
591 	// Only inline keyboards can be in outgoing messages.
592 	if (const auto markup = inlineReplyMarkup()) {
593 		if (markup->inlineKeyboard) {
594 			markup->inlineKeyboard->updateMessageId();
595 		}
596 	}
597 
598 	_history->owner().requestItemRepaint(this);
599 }
600 
canPin() const601 bool HistoryItem::canPin() const {
602 	if (!isRegular() || isService()) {
603 		return false;
604 	} else if (const auto m = media(); m && m->call()) {
605 		return false;
606 	}
607 	return _history->peer->canPinMessages();
608 }
609 
allowsSendNow() const610 bool HistoryItem::allowsSendNow() const {
611 	return false;
612 }
613 
allowsForward() const614 bool HistoryItem::allowsForward() const {
615 	return false;
616 }
617 
allowsEdit(TimeId now) const618 bool HistoryItem::allowsEdit(TimeId now) const {
619 	return false;
620 }
621 
canBeEdited() const622 bool HistoryItem::canBeEdited() const {
623 	if ((!isRegular() && !isScheduled())
624 		|| Has<HistoryMessageVia>()
625 		|| Has<HistoryMessageForwarded>()) {
626 		return false;
627 	}
628 
629 	const auto peer = _history->peer;
630 	if (peer->isSelf()) {
631 		return true;
632 	} else if (const auto channel = peer->asChannel()) {
633 		if (isPost() && channel->canEditMessages()) {
634 			return true;
635 		} else if (out()) {
636 			return isPost() ? channel->canPublish() : channel->canWrite();
637 		} else {
638 			return false;
639 		}
640 	}
641 	return out();
642 }
643 
canStopPoll() const644 bool HistoryItem::canStopPoll() const {
645 	return canBeEdited() && isRegular();
646 }
647 
canDelete() const648 bool HistoryItem::canDelete() const {
649 	if (isSponsored()) {
650 		return false;
651 	} else if (isService() && !isRegular()) {
652 		return false;
653 	} else if (!isHistoryEntry() && !isScheduled()) {
654 		return false;
655 	}
656 	auto channel = _history->peer->asChannel();
657 	if (!channel) {
658 		return !isGroupMigrate();
659 	}
660 
661 	if (id == 1) {
662 		return false;
663 	}
664 	if (channel->canDeleteMessages()) {
665 		return true;
666 	}
667 	if (out() && !isService()) {
668 		return isPost() ? channel->canPublish() : true;
669 	}
670 	return false;
671 }
672 
canDeleteForEveryone(TimeId now) const673 bool HistoryItem::canDeleteForEveryone(TimeId now) const {
674 	const auto peer = history()->peer;
675 	const auto &config = history()->session().serverConfig();
676 	const auto messageToMyself = peer->isSelf();
677 	const auto messageTooOld = messageToMyself
678 		? false
679 		: peer->isUser()
680 		? (now - date() >= config.revokePrivateTimeLimit)
681 		: (now - date() >= config.revokeTimeLimit);
682 	if (!isRegular() || messageToMyself || messageTooOld || isPost()) {
683 		return false;
684 	}
685 	if (peer->isChannel()) {
686 		return false;
687 	} else if (const auto user = peer->asUser()) {
688 		// Bots receive all messages and there is no sense in revoking them.
689 		// See https://github.com/telegramdesktop/tdesktop/issues/3818
690 		if (user->isBot() && !user->isSupport()) {
691 			return false;
692 		}
693 	}
694 	if (const auto media = this->media()) {
695 		if (!media->allowsRevoke(now)) {
696 			return false;
697 		}
698 	}
699 	if (!out()) {
700 		if (const auto chat = peer->asChat()) {
701 			if (!chat->canDeleteMessages()) {
702 				return false;
703 			}
704 		} else if (peer->isUser()) {
705 			return config.revokePrivateInbox;
706 		} else {
707 			return false;
708 		}
709 	}
710 	return true;
711 }
712 
suggestReport() const713 bool HistoryItem::suggestReport() const {
714 	if (out() || isService() || !isRegular()) {
715 		return false;
716 	} else if (const auto channel = history()->peer->asChannel()) {
717 		return true;
718 	} else if (const auto user = history()->peer->asUser()) {
719 		return user->isBot();
720 	}
721 	return false;
722 }
723 
suggestBanReport() const724 bool HistoryItem::suggestBanReport() const {
725 	const auto channel = history()->peer->asChannel();
726 	const auto fromUser = from()->asUser();
727 	if (!channel
728 		|| !fromUser
729 		|| !channel->canRestrictParticipant(fromUser)) {
730 		return false;
731 	}
732 	return !isPost() && !out();
733 }
734 
suggestDeleteAllReport() const735 bool HistoryItem::suggestDeleteAllReport() const {
736 	auto channel = history()->peer->asChannel();
737 	if (!channel || !channel->canDeleteMessages()) {
738 		return false;
739 	}
740 	return !isPost() && !out() && from()->isUser();
741 }
742 
hasDirectLink() const743 bool HistoryItem::hasDirectLink() const {
744 	return isRegular() && _history->peer->isChannel();
745 }
746 
channelId() const747 ChannelId HistoryItem::channelId() const {
748 	return _history->channelId();
749 }
750 
position() const751 Data::MessagePosition HistoryItem::position() const {
752 	return { .fullId = fullId(), .date = date() };
753 }
754 
replyToId() const755 MsgId HistoryItem::replyToId() const {
756 	if (const auto reply = Get<HistoryMessageReply>()) {
757 		return reply->replyToId();
758 	}
759 	return 0;
760 }
761 
replyToTop() const762 MsgId HistoryItem::replyToTop() const {
763 	if (const auto reply = Get<HistoryMessageReply>()) {
764 		return reply->replyToTop();
765 	}
766 	return 0;
767 }
768 
author() const769 not_null<PeerData*> HistoryItem::author() const {
770 	return (isPost() && !isSponsored()) ? history()->peer : from();
771 }
772 
dateOriginal() const773 TimeId HistoryItem::dateOriginal() const {
774 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
775 		return forwarded->originalDate;
776 	}
777 	return date();
778 }
779 
senderOriginal() const780 PeerData *HistoryItem::senderOriginal() const {
781 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
782 		return forwarded->originalSender;
783 	}
784 	const auto peer = history()->peer;
785 	return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
786 }
787 
hiddenForwardedInfo() const788 const HiddenSenderInfo *HistoryItem::hiddenForwardedInfo() const {
789 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
790 		return forwarded->hiddenSenderInfo.get();
791 	}
792 	return nullptr;
793 }
794 
fromOriginal() const795 not_null<PeerData*> HistoryItem::fromOriginal() const {
796 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
797 		if (forwarded->originalSender) {
798 			if (const auto user = forwarded->originalSender->asUser()) {
799 				return user;
800 			}
801 		}
802 	}
803 	return from();
804 }
805 
authorOriginal() const806 QString HistoryItem::authorOriginal() const {
807 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
808 		return forwarded->originalAuthor;
809 	} else if (const auto msgsigned = Get<HistoryMessageSigned>()) {
810 		if (!msgsigned->isAnonymousRank) {
811 			return msgsigned->author;
812 		}
813 	}
814 	return QString();
815 }
816 
idOriginal() const817 MsgId HistoryItem::idOriginal() const {
818 	if (const auto forwarded = Get<HistoryMessageForwarded>()) {
819 		return forwarded->originalId;
820 	}
821 	return id;
822 }
823 
updateDate(TimeId newDate)824 void HistoryItem::updateDate(TimeId newDate) {
825 	if (canUpdateDate() && _date != newDate) {
826 		_date = newDate;
827 		_history->owner().requestItemViewRefresh(this);
828 	}
829 }
830 
canUpdateDate() const831 bool HistoryItem::canUpdateDate() const {
832 	return isScheduled();
833 }
834 
applyTTL(const MTPDmessage & data)835 void HistoryItem::applyTTL(const MTPDmessage &data) {
836 	if (const auto period = data.vttl_period()) {
837 		if (period->v > 0) {
838 			applyTTL(data.vdate().v + period->v);
839 		}
840 	}
841 }
842 
applyTTL(const MTPDmessageService & data)843 void HistoryItem::applyTTL(const MTPDmessageService &data) {
844 	if (const auto period = data.vttl_period()) {
845 		if (period->v > 0) {
846 			applyTTL(data.vdate().v + period->v);
847 		}
848 	}
849 }
850 
applyTTL(TimeId destroyAt)851 void HistoryItem::applyTTL(TimeId destroyAt) {
852 	const auto previousDestroyAt = std::exchange(_ttlDestroyAt, destroyAt);
853 	if (previousDestroyAt) {
854 		history()->owner().unregisterMessageTTL(previousDestroyAt, this);
855 	}
856 	if (!_ttlDestroyAt) {
857 		return;
858 	} else if (base::unixtime::now() >= _ttlDestroyAt) {
859 		const auto session = &history()->session();
860 		crl::on_main(session, [session, id = fullId()]{
861 			if (const auto item = session->data().message(id)) {
862 				item->destroy();
863 			}
864 		});
865 	} else {
866 		history()->owner().registerMessageTTL(_ttlDestroyAt, this);
867 	}
868 }
869 
isUploading() const870 bool HistoryItem::isUploading() const {
871 	return _media && _media->uploading();
872 }
873 
isRegular() const874 bool HistoryItem::isRegular() const {
875 	return isHistoryEntry() && !isLocal();
876 }
877 
sendFailed()878 void HistoryItem::sendFailed() {
879 	Expects(_flags & MessageFlag::BeingSent);
880 	Expects(!(_flags & MessageFlag::SendingFailed));
881 
882 	_flags = (_flags | MessageFlag::SendingFailed) & ~MessageFlag::BeingSent;
883 	history()->session().changes().historyUpdated(
884 		history(),
885 		Data::HistoryUpdate::Flag::ClientSideMessages);
886 }
887 
needCheck() const888 bool HistoryItem::needCheck() const {
889 	return (out() && !isEmpty()) || (!isRegular() && history()->peer->isSelf());
890 }
891 
unread() const892 bool HistoryItem::unread() const {
893 	// Messages from myself are always read, unless scheduled.
894 	if (history()->peer->isSelf() && !isFromScheduled()) {
895 		return false;
896 	}
897 
898 	if (out()) {
899 		// Outgoing messages in converted chats are always read.
900 		if (history()->peer->migrateTo()) {
901 			return false;
902 		}
903 
904 		if (isRegular()) {
905 			if (!history()->isServerSideUnread(this)) {
906 				return false;
907 			}
908 			if (const auto user = history()->peer->asUser()) {
909 				if (user->isBot() && !user->isSupport()) {
910 					return false;
911 				}
912 			} else if (const auto channel = history()->peer->asChannel()) {
913 				if (!channel->isMegagroup()) {
914 					return false;
915 				}
916 			}
917 		}
918 		return true;
919 	}
920 
921 	if (isRegular()) {
922 		if (!history()->isServerSideUnread(this)) {
923 			return false;
924 		}
925 		return true;
926 	}
927 	return (_flags & MessageFlag::ClientSideUnread);
928 }
929 
showNotification() const930 bool HistoryItem::showNotification() const {
931 	const auto channel = _history->peer->asChannel();
932 	if (channel && !channel->amIn()) {
933 		return false;
934 	}
935 	return (out() || _history->peer->isSelf())
936 		? isFromScheduled()
937 		: unread();
938 }
939 
markClientSideAsRead()940 void HistoryItem::markClientSideAsRead() {
941 	_flags &= ~MessageFlag::ClientSideUnread;
942 }
943 
groupId() const944 MessageGroupId HistoryItem::groupId() const {
945 	return _groupId;
946 }
947 
isEmpty() const948 bool HistoryItem::isEmpty() const {
949 	return _text.isEmpty()
950 		&& !_media
951 		&& !Has<HistoryMessageLogEntryOriginal>();
952 }
953 
notificationText() const954 QString HistoryItem::notificationText() const {
955 	const auto result = [&] {
956 		if (_media && !isService()) {
957 			return _media->notificationText();
958 		} else if (!emptyText()) {
959 			return _text.toString();
960 		}
961 		return QString();
962 	}();
963 	return (result.size() <= kNotificationTextLimit)
964 		? result
965 		: result.mid(0, kNotificationTextLimit) + qsl("...");
966 }
967 
toPreview(ToPreviewOptions options) const968 ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
969 	auto result = [&]() -> ItemPreview {
970 		if (_media) {
971 			return _media->toPreview(options);
972 		} else if (!emptyText()) {
973 			return { .text = TextUtilities::Clean(_text.toString()) };
974 		}
975 		return {};
976 	}();
977 	const auto sender = [&]() -> std::optional<QString> {
978 		const auto fromSender = [](not_null<PeerData*> sender) {
979 			return sender->isSelf()
980 				? tr::lng_from_you(tr::now)
981 				: sender->shortName();
982 		};
983 		if (options.hideSender || isPost() || isEmpty()) {
984 			return {};
985 		} else if (!_history->peer->isUser() || _history->peer->isSelf()) {
986 			if (const auto forwarded = Get<HistoryMessageForwarded>()) {
987 				return forwarded->originalSender
988 					? fromSender(forwarded->originalSender)
989 					: forwarded->hiddenSenderInfo->name;
990 			} else if (!_history->peer->isUser()) {
991 				return fromSender(displayFrom());
992 			}
993 		}
994 		return {};
995 	}();
996 	if (!sender) {
997 		return result;
998 	}
999 	const auto fromWrapped = textcmdLink(
1000 		1,
1001 		tr::lng_dialogs_text_from_wrapped(
1002 			tr::now,
1003 			lt_from,
1004 			TextUtilities::Clean(*sender)));
1005 	return Dialogs::Ui::PreviewWithSender(std::move(result), fromWrapped);
1006 }
1007 
isolatedEmoji() const1008 Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
1009 	return Ui::Text::IsolatedEmoji();
1010 }
1011 
~HistoryItem()1012 HistoryItem::~HistoryItem() {
1013 	applyTTL(0);
1014 }
1015 
ItemDateTime(not_null<const HistoryItem * > item)1016 QDateTime ItemDateTime(not_null<const HistoryItem*> item) {
1017 	return base::unixtime::parse(item->date());
1018 }
1019 
ItemDateText(not_null<const HistoryItem * > item,bool isUntilOnline)1020 QString ItemDateText(not_null<const HistoryItem*> item, bool isUntilOnline) {
1021 	const auto dateText = langDayOfMonthFull(ItemDateTime(item).date());
1022 	return !item->isScheduled()
1023 		? dateText
1024 		: isUntilOnline
1025 			? tr::lng_scheduled_date_until_online(tr::now)
1026 			: tr::lng_scheduled_date(tr::now, lt_date, dateText);
1027 }
1028 
IsItemScheduledUntilOnline(not_null<const HistoryItem * > item)1029 bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item) {
1030 	return item->isScheduled()
1031 		&& (item->date() ==
1032 			Data::ScheduledMessages::kScheduledUntilOnlineTimestamp);
1033 }
1034 
goToMessageClickHandler(not_null<HistoryItem * > item,FullMsgId returnToId)1035 ClickHandlerPtr goToMessageClickHandler(
1036 		not_null<HistoryItem*> item,
1037 		FullMsgId returnToId) {
1038 	return goToMessageClickHandler(
1039 		item->history()->peer,
1040 		item->id,
1041 		returnToId);
1042 }
1043 
goToMessageClickHandler(not_null<PeerData * > peer,MsgId msgId,FullMsgId returnToId)1044 ClickHandlerPtr goToMessageClickHandler(
1045 		not_null<PeerData*> peer,
1046 		MsgId msgId,
1047 		FullMsgId returnToId) {
1048 	return std::make_shared<LambdaClickHandler>([=] {
1049 		if (const auto main = App::main()) { // multi good
1050 			if (&main->session() == &peer->session()) {
1051 				auto params = Window::SectionShow{
1052 					Window::SectionShow::Way::Forward
1053 				};
1054 				params.origin = Window::SectionShow::OriginMessage{
1055 					returnToId
1056 				};
1057 				main->controller()->showPeerHistory(peer, params, msgId);
1058 			}
1059 		}
1060 	});
1061 }
1062 
FlagsFromMTP(MsgId id,MTPDmessage::Flags flags,MessageFlags localFlags)1063 MessageFlags FlagsFromMTP(
1064 		MsgId id,
1065 		MTPDmessage::Flags flags,
1066 		MessageFlags localFlags) {
1067 	using Flag = MessageFlag;
1068 	using MTP = MTPDmessage::Flag;
1069 	return localFlags
1070 		| (IsServerMsgId(id) ? Flag::HistoryEntry : Flag())
1071 		| ((flags & MTP::f_out) ? Flag::Outgoing : Flag())
1072 		| ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag())
1073 		| ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag())
1074 		| ((flags & MTP::f_silent) ? Flag::Silent : Flag())
1075 		| ((flags & MTP::f_post) ? Flag::Post : Flag())
1076 		| ((flags & MTP::f_legacy) ? Flag::Legacy : Flag())
1077 		| ((flags & MTP::f_edit_hide) ? Flag::HideEdited : Flag())
1078 		| ((flags & MTP::f_pinned) ? Flag::Pinned : Flag())
1079 		| ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag())
1080 		| ((flags & MTP::f_via_bot_id) ? Flag::HasViaBot : Flag())
1081 		| ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag())
1082 		| ((flags & MTP::f_reply_markup) ? Flag::HasReplyMarkup : Flag())
1083 		| ((flags & MTP::f_from_scheduled) ? Flag::IsOrWasScheduled : Flag())
1084 		| ((flags & MTP::f_views) ? Flag::HasViews : Flag());
1085 }
1086 
FlagsFromMTP(MsgId id,MTPDmessageService::Flags flags,MessageFlags localFlags)1087 MessageFlags FlagsFromMTP(
1088 		MsgId id,
1089 		MTPDmessageService::Flags flags,
1090 		MessageFlags localFlags) {
1091 	using Flag = MessageFlag;
1092 	using MTP = MTPDmessageService::Flag;
1093 	return localFlags
1094 		| (IsServerMsgId(id) ? Flag::HistoryEntry : Flag())
1095 		| ((flags & MTP::f_out) ? Flag::Outgoing : Flag())
1096 		| ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag())
1097 		| ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag())
1098 		| ((flags & MTP::f_silent) ? Flag::Silent : Flag())
1099 		| ((flags & MTP::f_post) ? Flag::Post : Flag())
1100 		| ((flags & MTP::f_legacy) ? Flag::Legacy : Flag())
1101 		| ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag())
1102 		| ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag());
1103 }
1104 
Create(not_null<History * > history,MsgId id,const MTPMessage & message,MessageFlags localFlags)1105 not_null<HistoryItem*> HistoryItem::Create(
1106 		not_null<History*> history,
1107 		MsgId id,
1108 		const MTPMessage &message,
1109 		MessageFlags localFlags) {
1110 	return message.match([&](const MTPDmessage &data) -> HistoryItem* {
1111 		const auto media = data.vmedia();
1112 		const auto checked = media
1113 			? CheckMessageMedia(*media)
1114 			: MediaCheckResult::Good;
1115 		if (checked == MediaCheckResult::Unsupported) {
1116 			return CreateUnsupportedMessage(
1117 				history,
1118 				id,
1119 				FlagsFromMTP(id, data.vflags().v, localFlags),
1120 				MsgId(0), // No need to pass reply_to data here.
1121 				data.vvia_bot_id().value_or_empty(),
1122 				data.vdate().v,
1123 				data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
1124 		} else if (checked == MediaCheckResult::Empty) {
1125 			const auto text = HistoryService::PreparedText{
1126 				tr::lng_message_empty(tr::now)
1127 			};
1128 			return history->makeServiceMessage(
1129 				id,
1130 				FlagsFromMTP(id, data.vflags().v, localFlags),
1131 				data.vdate().v,
1132 				text,
1133 				data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
1134 		} else if (checked == MediaCheckResult::HasTimeToLive) {
1135 			return history->makeServiceMessage(id, data, localFlags);
1136 		}
1137 		return history->makeMessage(id, data, localFlags);
1138 	}, [&](const MTPDmessageService &data) -> HistoryItem* {
1139 		if (data.vaction().type() == mtpc_messageActionPhoneCall) {
1140 			return history->makeMessage(id, data, localFlags);
1141 		}
1142 		return history->makeServiceMessage(id, data, localFlags);
1143 	}, [&](const MTPDmessageEmpty &data) -> HistoryItem* {
1144 		const auto text = HistoryService::PreparedText{
1145 			tr::lng_message_empty(tr::now)
1146 		};
1147 		return history->makeServiceMessage(id, localFlags, TimeId(0), text);
1148 	});
1149 }
1150