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 "data/data_session.h"
9
10 #include "main/main_session.h"
11 #include "main/main_session_settings.h"
12 #include "main/main_account.h"
13 #include "apiwrap.h"
14 #include "mainwidget.h"
15 #include "api/api_text_entities.h"
16 #include "core/application.h"
17 #include "core/mime_type.h" // Core::IsMimeSticker
18 #include "core/crash_reports.h" // CrashReports::SetAnnotation
19 #include "ui/image/image.h"
20 #include "ui/image/image_location_factory.h" // Images::FromPhotoSize
21 #include "ui/text/format_values.h" // Ui::FormatPhone
22 #include "export/export_manager.h"
23 #include "export/view/export_view_panel_controller.h"
24 #include "mtproto/mtproto_config.h"
25 #include "window/notifications_manager.h"
26 #include "history/history.h"
27 #include "history/history_item_components.h"
28 #include "history/view/media/history_view_media.h"
29 #include "history/view/history_view_element.h"
30 #include "inline_bots/inline_bot_layout_item.h"
31 #include "storage/storage_account.h"
32 #include "storage/storage_encrypted_file.h"
33 #include "media/player/media_player_instance.h" // instance()->play()
34 #include "media/audio/media_audio.h"
35 #include "boxes/abstract_box.h"
36 #include "passport/passport_form_controller.h"
37 #include "lang/lang_keys.h" // tr::lng_deleted(tr::now) in user name
38 #include "data/stickers/data_stickers.h"
39 #include "data/data_changes.h"
40 #include "data/data_group_call.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 "data/data_file_origin.h"
47 #include "data/data_photo.h"
48 #include "data/data_document.h"
49 #include "data/data_web_page.h"
50 #include "data/data_wall_paper.h"
51 #include "data/data_game.h"
52 #include "data/data_poll.h"
53 #include "data/data_chat_filters.h"
54 #include "data/data_scheduled_messages.h"
55 #include "data/data_send_action.h"
56 #include "data/data_sponsored_messages.h"
57 #include "data/data_cloud_themes.h"
58 #include "data/data_streaming.h"
59 #include "data/data_media_rotation.h"
60 #include "data/data_histories.h"
61 #include "base/platform/base_platform_info.h"
62 #include "base/unixtime.h"
63 #include "base/call_delayed.h"
64 #include "base/random.h"
65 #include "facades.h" // Notify::switchInlineBotButtonReceived
66 #include "app.h"
67 #include "styles/style_boxes.h" // st::backgroundSize
68
69 namespace Data {
70 namespace {
71
72 constexpr auto kMaxNotifyCheckDelay = 24 * 3600 * crl::time(1000);
73
74 using ViewElement = HistoryView::Element;
75
76 // s: box 100x100
77 // m: box 320x320
78 // x: box 800x800
79 // y: box 1280x1280
80 // w: box 2560x2560 // if loading this fix HistoryPhoto::updateFrom
81 // a: crop 160x160
82 // b: crop 320x320
83 // c: crop 640x640
84 // d: crop 1280x1280
85 const auto InlineLevels = "i"_q;
86 const auto SmallLevels = "sa"_q;
87 const auto ThumbnailLevels = "mbsa"_q;
88 const auto LargeLevels = "ydxcwmbsa"_q;
89
CheckForSwitchInlineButton(not_null<HistoryItem * > item)90 void CheckForSwitchInlineButton(not_null<HistoryItem*> item) {
91 if (item->out() || !item->hasSwitchInlineButton()) {
92 return;
93 }
94 if (const auto user = item->history()->peer->asUser()) {
95 if (!user->isBot() || !user->botInfo->inlineReturnTo.key) {
96 return;
97 }
98 if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
99 for (const auto &row : markup->data.rows) {
100 for (const auto &button : row) {
101 using ButtonType = HistoryMessageMarkupButton::Type;
102 if (button.type == ButtonType::SwitchInline) {
103 Notify::switchInlineBotButtonReceived(
104 &item->history()->session(),
105 QString::fromUtf8(button.data));
106 return;
107 }
108 }
109 }
110 }
111 }
112 }
113
114 // We should get a full restriction in "{full}: {reason}" format and we
115 // need to find an "-all" tag in {full}, otherwise ignore this restriction.
ExtractUnavailableReasons(const QVector<MTPRestrictionReason> & restrictions)116 std::vector<UnavailableReason> ExtractUnavailableReasons(
117 const QVector<MTPRestrictionReason> &restrictions) {
118 return ranges::views::all(
119 restrictions
120 ) | ranges::views::filter([](const MTPRestrictionReason &restriction) {
121 return restriction.match([&](const MTPDrestrictionReason &data) {
122 const auto platform = qs(data.vplatform());
123 return false
124 #ifdef OS_MAC_STORE
125 || (platform == qstr("ios"))
126 #elif defined OS_WIN_STORE // OS_MAC_STORE
127 || (platform == qstr("ms"))
128 #endif // OS_MAC_STORE || OS_WIN_STORE
129 || (platform == qstr("all"));
130 });
131 }) | ranges::views::transform([](const MTPRestrictionReason &restriction) {
132 return restriction.match([&](const MTPDrestrictionReason &data) {
133 return UnavailableReason{ qs(data.vreason()), qs(data.vtext()) };
134 });
135 }) | ranges::to_vector;
136 }
137
FindInlineThumbnail(const QVector<MTPPhotoSize> & sizes)138 [[nodiscard]] InlineImageLocation FindInlineThumbnail(
139 const QVector<MTPPhotoSize> &sizes) {
140 const auto i = ranges::find(
141 sizes,
142 mtpc_photoStrippedSize,
143 &MTPPhotoSize::type);
144 const auto j = ranges::find(
145 sizes,
146 mtpc_photoPathSize,
147 &MTPPhotoSize::type);
148 return (i != sizes.end())
149 ? InlineImageLocation{ i->c_photoStrippedSize().vbytes().v, false }
150 : (j != sizes.end())
151 ? InlineImageLocation{ j->c_photoPathSize().vbytes().v, true }
152 : InlineImageLocation();
153 }
154
FindDocumentInlineThumbnail(const MTPDdocument & data)155 [[nodiscard]] InlineImageLocation FindDocumentInlineThumbnail(
156 const MTPDdocument &data) {
157 return FindInlineThumbnail(data.vthumbs().value_or_empty());
158 }
159
FindDocumentThumbnail(const MTPDdocument & data)160 [[nodiscard]] MTPPhotoSize FindDocumentThumbnail(const MTPDdocument &data) {
161 const auto area = [](const MTPPhotoSize &size) {
162 static constexpr auto kInvalid = 0;
163 return size.match([](const MTPDphotoSizeEmpty &) {
164 return kInvalid;
165 }, [](const MTPDphotoStrippedSize &) {
166 return kInvalid;
167 }, [](const MTPDphotoPathSize &) {
168 return kInvalid;
169 }, [](const auto &data) {
170 return (data.vw().v * data.vh().v);
171 });
172 };
173 const auto thumbs = data.vthumbs();
174 if (!thumbs) {
175 return MTP_photoSizeEmpty(MTP_string());
176 }
177 const auto &list = thumbs->v;
178 const auto i = ranges::max_element(list, std::less<>(), area);
179 return (i != list.end() && area(*i) > 0)
180 ? (*i)
181 : MTPPhotoSize(MTP_photoSizeEmpty(MTP_string()));
182 }
183
FindDocumentVideoThumbnail(const MTPDdocument & data)184 [[nodiscard]] std::optional<MTPVideoSize> FindDocumentVideoThumbnail(
185 const MTPDdocument &data) {
186 const auto area = [](const MTPVideoSize &size) {
187 return size.match([](const MTPDvideoSize &data) {
188 return (data.vw().v * data.vh().v);
189 });
190 };
191 const auto thumbs = data.vvideo_thumbs();
192 if (!thumbs) {
193 return std::nullopt;
194 }
195 const auto &list = thumbs->v;
196 const auto i = ranges::max_element(list, std::less<>(), area);
197 return (i != list.end() && area(*i) > 0)
198 ? std::make_optional(*i)
199 : std::nullopt;
200 }
201
FindPhotoInlineThumbnail(const MTPDphoto & data)202 [[nodiscard]] QByteArray FindPhotoInlineThumbnail(const MTPDphoto &data) {
203 const auto thumbnail = FindInlineThumbnail(data.vsizes().v);
204 return !thumbnail.isPath ? thumbnail.bytes : QByteArray();
205 }
206
VideoStartTime(const MTPDvideoSize & data)207 [[nodiscard]] int VideoStartTime(const MTPDvideoSize &data) {
208 return int(
209 std::clamp(
210 std::floor(data.vvideo_start_ts().value_or_empty() * 1000),
211 0.,
212 double(std::numeric_limits<int>::max())));
213 }
214
215 } // namespace
216
Session(not_null<Main::Session * > session)217 Session::Session(not_null<Main::Session*> session)
218 : _session(session)
219 , _cache(Core::App().databases().get(
220 _session->local().cachePath(),
221 _session->local().cacheSettings()))
222 , _bigFileCache(Core::App().databases().get(
223 _session->local().cacheBigFilePath(),
224 _session->local().cacheBigFileSettings()))
225 , _chatsList(
226 session,
227 FilterId(),
228 session->serverConfig().pinnedDialogsCountMax.value())
229 , _contactsList(Dialogs::SortMode::Name)
230 , _contactsNoChatsList(Dialogs::SortMode::Name)
231 , _ttlCheckTimer([=] { checkTTLs(); })
__anona11d63740e02null232 , _selfDestructTimer([=] { checkSelfDestructItems(); })
__anona11d63740f02null233 , _pollsClosingTimer([=] { checkPollsClosings(); })
__anona11d63741002null234 , _unmuteByFinishedTimer([=] { unmuteByFinished(); })
235 , _groups(this)
236 , _chatsFilters(std::make_unique<ChatFilters>(this))
237 , _scheduledMessages(std::make_unique<ScheduledMessages>(this))
238 , _cloudThemes(std::make_unique<CloudThemes>(session))
239 , _sendActionManager(std::make_unique<SendActionManager>())
240 , _streaming(std::make_unique<Streaming>(this))
241 , _mediaRotation(std::make_unique<MediaRotation>())
242 , _histories(std::make_unique<Histories>(this))
243 , _stickers(std::make_unique<Stickers>(this))
244 , _sponsoredMessages(std::make_unique<SponsoredMessages>(this)) {
245 _cache->open(_session->local().cacheKey());
246 _bigFileCache->open(_session->local().cacheBigFileKey());
247
248 if constexpr (Platform::IsLinux()) {
249 const auto wasVersion = _session->local().oldMapVersion();
250 if (wasVersion >= 1007011 && wasVersion < 1007015) {
251 _bigFileCache->clear();
252 _cache->clearByTag(Data::kImageCacheTag);
253 }
254 }
255
256 setupMigrationViewer();
257 setupChannelLeavingViewer();
258 setupPeerNameViewer();
259 setupUserIsContactViewer();
260
261 _chatsList.unreadStateChanges(
__anona11d63741102null262 ) | rpl::start_with_next([=] {
263 notifyUnreadBadgeChanged();
264 }, _lifetime);
265
266 _chatsFilters->changed(
__anona11d63741202null267 ) | rpl::start_with_next([=] {
268 const auto enabled = !_chatsFilters->list().empty();
269 if (enabled != session->settings().dialogsFiltersEnabled()) {
270 session->settings().setDialogsFiltersEnabled(enabled);
271 session->saveSettingsDelayed();
272 }
273 }, _lifetime);
274
275 }
276
clear()277 void Session::clear() {
278 // Optimization: clear notifications before destroying items.
279 Core::App().notifications().clearFromSession(_session);
280
281 _sendActionManager->clear();
282
283 _histories->unloadAll();
284 _scheduledMessages = nullptr;
285 _sponsoredMessages = nullptr;
286 _dependentMessages.clear();
287 base::take(_messages);
288 base::take(_channelMessages);
289 _messageByRandomId.clear();
290 _sentMessagesData.clear();
291 cSetRecentInlineBots(RecentInlineBots());
292 cSetRecentStickers(RecentStickerPack());
293 App::clearMousedItems();
294 _histories->clearAll();
295 _webpages.clear();
296 _locations.clear();
297 _polls.clear();
298 _games.clear();
299 _documents.clear();
300 _photos.clear();
301 }
302
keepAlive(std::shared_ptr<PhotoMedia> media)303 void Session::keepAlive(std::shared_ptr<PhotoMedia> media) {
304 // NB! This allows PhotoMedia to outlive Main::Session!
305 // In case this is a problem this code should be rewritten.
306 crl::on_main(&session(), [media = std::move(media)]{});
307 }
308
keepAlive(std::shared_ptr<DocumentMedia> media)309 void Session::keepAlive(std::shared_ptr<DocumentMedia> media) {
310 // NB! This allows DocumentMedia to outlive Main::Session!
311 // In case this is a problem this code should be rewritten.
312 crl::on_main(&session(), [media = std::move(media)] {});
313 }
314
peer(PeerId id)315 not_null<PeerData*> Session::peer(PeerId id) {
316 const auto i = _peers.find(id);
317 if (i != _peers.cend()) {
318 return i->second.get();
319 }
320 auto result = [&]() -> std::unique_ptr<PeerData> {
321 if (peerIsUser(id)) {
322 return std::make_unique<UserData>(this, id);
323 } else if (peerIsChat(id)) {
324 return std::make_unique<ChatData>(this, id);
325 } else if (peerIsChannel(id)) {
326 return std::make_unique<ChannelData>(this, id);
327 }
328 Unexpected("Peer id type.");
329 }();
330
331 result->input = MTPinputPeer(MTP_inputPeerEmpty());
332 return _peers.emplace(id, std::move(result)).first->second.get();
333 }
334
user(UserId id)335 not_null<UserData*> Session::user(UserId id) {
336 return peer(peerFromUser(id))->asUser();
337 }
338
chat(ChatId id)339 not_null<ChatData*> Session::chat(ChatId id) {
340 return peer(peerFromChat(id))->asChat();
341 }
342
channel(ChannelId id)343 not_null<ChannelData*> Session::channel(ChannelId id) {
344 return peer(peerFromChannel(id))->asChannel();
345 }
346
peerLoaded(PeerId id) const347 PeerData *Session::peerLoaded(PeerId id) const {
348 const auto i = _peers.find(id);
349 if (i == end(_peers)) {
350 return nullptr;
351 } else if (!i->second->isLoaded()) {
352 return nullptr;
353 }
354 return i->second.get();
355 }
356
userLoaded(UserId id) const357 UserData *Session::userLoaded(UserId id) const {
358 if (const auto peer = peerLoaded(peerFromUser(id))) {
359 return peer->asUser();
360 }
361 return nullptr;
362 }
363
chatLoaded(ChatId id) const364 ChatData *Session::chatLoaded(ChatId id) const {
365 if (const auto peer = peerLoaded(peerFromChat(id))) {
366 return peer->asChat();
367 }
368 return nullptr;
369 }
370
channelLoaded(ChannelId id) const371 ChannelData *Session::channelLoaded(ChannelId id) const {
372 if (const auto peer = peerLoaded(peerFromChannel(id))) {
373 return peer->asChannel();
374 }
375 return nullptr;
376 }
377
processUser(const MTPUser & data)378 not_null<UserData*> Session::processUser(const MTPUser &data) {
379 const auto result = user(data.match([](const auto &data) {
380 return data.vid().v;
381 }));
382 auto minimal = false;
383 const MTPUserStatus *status = nullptr;
384 const MTPUserStatus emptyStatus = MTP_userStatusEmpty();
385
386 using UpdateFlag = PeerUpdate::Flag;
387 auto flags = UpdateFlag::None | UpdateFlag::None;
388 data.match([&](const MTPDuserEmpty &data) {
389 const auto canShareThisContact = result->canShareThisContactFast();
390
391 result->input = MTP_inputPeerUser(data.vid(), MTP_long(0));
392 result->inputUser = MTP_inputUser(data.vid(), MTP_long(0));
393 result->setName(tr::lng_deleted(tr::now), QString(), QString(), QString());
394 result->setPhoto(MTP_userProfilePhotoEmpty());
395 result->setFlags(UserDataFlag::Deleted);
396 if (!result->phone().isEmpty()) {
397 result->setPhone(QString());
398 flags |= UpdateFlag::PhoneNumber;
399 }
400 result->setBotInfoVersion(-1);
401 status = &emptyStatus;
402 result->setIsContact(false);
403 if (canShareThisContact != result->canShareThisContactFast()) {
404 flags |= UpdateFlag::CanShareContact;
405 }
406 }, [&](const MTPDuser &data) {
407 minimal = data.is_min();
408
409 const auto canShareThisContact = result->canShareThisContactFast();
410
411 using Flag = UserDataFlag;
412 const auto flagsMask = Flag::Deleted
413 | Flag::Verified
414 | Flag::Scam
415 | Flag::Fake
416 | Flag::BotInlineGeo
417 | Flag::Support
418 | (!minimal
419 ? Flag::Contact
420 | Flag::MutualContact
421 | Flag::DiscardMinPhoto
422 : Flag());
423 const auto flagsSet = (data.is_deleted() ? Flag::Deleted : Flag())
424 | (data.is_verified() ? Flag::Verified : Flag())
425 | (data.is_scam() ? Flag::Scam : Flag())
426 | (data.is_fake() ? Flag::Fake : Flag())
427 | (data.is_bot_inline_geo() ? Flag::BotInlineGeo : Flag())
428 | (data.is_support() ? Flag::Support : Flag())
429 | (!minimal
430 ? (data.is_contact() ? Flag::Contact : Flag())
431 | (data.is_mutual_contact() ? Flag::MutualContact : Flag())
432 | (data.is_apply_min_photo() ? Flag() : Flag::DiscardMinPhoto)
433 : Flag());
434 result->setFlags((result->flags() & ~flagsMask) | flagsSet);
435 if (minimal) {
436 if (result->input.type() == mtpc_inputPeerEmpty) {
437 result->input = MTP_inputPeerUser(
438 data.vid(),
439 MTP_long(data.vaccess_hash().value_or_empty()));
440 }
441 if (result->inputUser.type() == mtpc_inputUserEmpty) {
442 result->inputUser = MTP_inputUser(
443 data.vid(),
444 MTP_long(data.vaccess_hash().value_or_empty()));
445 }
446 } else {
447 if (data.is_self()) {
448 result->input = MTP_inputPeerSelf();
449 result->inputUser = MTP_inputUserSelf();
450 } else if (const auto accessHash = data.vaccess_hash()) {
451 result->input = MTP_inputPeerUser(data.vid(), *accessHash);
452 result->inputUser = MTP_inputUser(data.vid(), *accessHash);
453 } else {
454 result->input = MTP_inputPeerUser(data.vid(), MTP_long(result->accessHash()));
455 result->inputUser = MTP_inputUser(data.vid(), MTP_long(result->accessHash()));
456 }
457 if (const auto restriction = data.vrestriction_reason()) {
458 result->setUnavailableReasons(
459 ExtractUnavailableReasons(restriction->v));
460 } else {
461 result->setUnavailableReasons({});
462 }
463 }
464 if (data.is_deleted()) {
465 if (!result->phone().isEmpty()) {
466 result->setPhone(QString());
467 flags |= UpdateFlag::PhoneNumber;
468 }
469 result->setName(tr::lng_deleted(tr::now), QString(), QString(), QString());
470 result->setPhoto(MTP_userProfilePhotoEmpty());
471 status = &emptyStatus;
472 } else {
473 // apply first_name and last_name from minimal user only if we don't have
474 // local values for first name and last name already, otherwise skip
475 bool noLocalName = result->firstName.isEmpty() && result->lastName.isEmpty();
476 QString fname = (!minimal || noLocalName) ? TextUtilities::SingleLine(qs(data.vfirst_name().value_or_empty())) : result->firstName;
477 QString lname = (!minimal || noLocalName) ? TextUtilities::SingleLine(qs(data.vlast_name().value_or_empty())) : result->lastName;
478
479 QString phone = minimal ? result->phone() : qs(data.vphone().value_or_empty());
480 QString uname = minimal ? result->username : TextUtilities::SingleLine(qs(data.vusername().value_or_empty()));
481
482 const auto phoneChanged = (result->phone() != phone);
483 if (phoneChanged) {
484 result->setPhone(phone);
485 flags |= UpdateFlag::PhoneNumber;
486 }
487 const auto nameChanged = (result->firstName != fname)
488 || (result->lastName != lname);
489
490 auto showPhone = !result->isServiceUser()
491 && !data.is_support()
492 && !data.is_self()
493 && !data.is_contact()
494 && !data.is_mutual_contact();
495 auto showPhoneChanged = !result->isServiceUser()
496 && !data.is_self()
497 && ((showPhone && result->isContact())
498 || (!showPhone
499 && !result->isContact()
500 && !result->phone().isEmpty()));
501 if (minimal) {
502 showPhoneChanged = false;
503 showPhone = !result->isServiceUser()
504 && !result->isContact()
505 && !result->phone().isEmpty()
506 && (result->id != _session->userPeerId());
507 }
508
509 // see also Serialize::readPeer
510
511 const auto pname = (showPhoneChanged || phoneChanged || nameChanged)
512 ? ((showPhone && !phone.isEmpty())
513 ? Ui::FormatPhone(phone)
514 : QString())
515 : result->nameOrPhone;
516
517 result->setName(fname, lname, pname, uname);
518 if (!minimal || result->applyMinPhoto()) {
519 if (const auto photo = data.vphoto()) {
520 result->setPhoto(*photo);
521 } else {
522 result->setPhoto(MTP_userProfilePhotoEmpty());
523 }
524 }
525 if (const auto accessHash = data.vaccess_hash()) {
526 result->setAccessHash(accessHash->v);
527 }
528 status = data.vstatus();
529 }
530 if (!minimal) {
531 if (const auto botInfoVersion = data.vbot_info_version()) {
532 result->setBotInfoVersion(botInfoVersion->v);
533 result->botInfo->readsAllHistory = data.is_bot_chat_history();
534 if (result->botInfo->cantJoinGroups != data.is_bot_nochats()) {
535 result->botInfo->cantJoinGroups = data.is_bot_nochats();
536 flags |= UpdateFlag::BotCanBeInvited;
537 }
538 if (const auto placeholder = data.vbot_inline_placeholder()) {
539 result->botInfo->inlinePlaceholder = '_' + qs(*placeholder);
540 } else {
541 result->botInfo->inlinePlaceholder = QString();
542 }
543 } else {
544 result->setBotInfoVersion(-1);
545 }
546 result->setIsContact(data.is_contact()
547 || data.is_mutual_contact());
548 }
549
550 if (canShareThisContact != result->canShareThisContactFast()) {
551 flags |= UpdateFlag::CanShareContact;
552 }
553 });
554
555 if (minimal) {
556 if (!result->isMinimalLoaded()) {
557 result->setLoadedStatus(PeerData::LoadedStatus::Minimal);
558 }
559 } else if (!result->isLoaded()
560 && (!result->isSelf() || !result->phone().isEmpty())) {
561 result->setLoadedStatus(PeerData::LoadedStatus::Normal);
562 }
563
564 if (status && !minimal) {
565 const auto oldOnlineTill = result->onlineTill;
566 const auto newOnlineTill = ApiWrap::OnlineTillFromStatus(
567 *status,
568 oldOnlineTill);
569 if (oldOnlineTill != newOnlineTill) {
570 result->onlineTill = newOnlineTill;
571 flags |= UpdateFlag::OnlineStatus;
572 }
573 }
574
575 if (flags) {
576 session().changes().peerUpdated(result, flags);
577 }
578 return result;
579 }
580
processChat(const MTPChat & data)581 not_null<PeerData*> Session::processChat(const MTPChat &data) {
582 const auto result = data.match([&](const MTPDchat &data) {
583 return peer(peerFromChat(data.vid().v));
584 }, [&](const MTPDchatForbidden &data) {
585 return peer(peerFromChat(data.vid().v));
586 }, [&](const MTPDchatEmpty &data) {
587 return peer(peerFromChat(data.vid().v));
588 }, [&](const MTPDchannel &data) {
589 return peer(peerFromChannel(data.vid().v));
590 }, [&](const MTPDchannelForbidden &data) {
591 return peer(peerFromChannel(data.vid().v));
592 });
593 auto minimal = false;
594
595 using UpdateFlag = Data::PeerUpdate::Flag;
596 auto flags = UpdateFlag::None | UpdateFlag::None;
597 data.match([&](const MTPDchat &data) {
598 const auto chat = result->asChat();
599
600 const auto canAddMembers = chat->canAddMembers();
601 if (chat->version() < data.vversion().v) {
602 chat->setVersion(data.vversion().v);
603 chat->invalidateParticipants();
604 }
605
606 chat->input = MTP_inputPeerChat(data.vid());
607 chat->setName(qs(data.vtitle()));
608 chat->setPhoto(data.vphoto());
609 chat->date = data.vdate().v;
610
611 if (const auto rights = data.vadmin_rights()) {
612 chat->setAdminRights(ChatAdminRightsInfo(*rights).flags);
613 } else {
614 chat->setAdminRights(ChatAdminRights());
615 }
616 if (const auto rights = data.vdefault_banned_rights()) {
617 chat->setDefaultRestrictions(ChatRestrictionsInfo(*rights).flags);
618 } else {
619 chat->setDefaultRestrictions(ChatRestrictions());
620 }
621
622 if (const auto migratedTo = data.vmigrated_to()) {
623 migratedTo->match([&](const MTPDinputChannel &input) {
624 const auto channel = this->channel(input.vchannel_id().v);
625 channel->addFlags(ChannelDataFlag::Megagroup);
626 if (!channel->access) {
627 channel->setAccessHash(input.vaccess_hash().v);
628 }
629 ApplyMigration(chat, channel);
630 }, [](const MTPDinputChannelFromMessage &) {
631 LOG(("API Error: "
632 "migrated_to contains channel from message."));
633 }, [](const MTPDinputChannelEmpty &) {
634 });
635 }
636
637 using Flag = ChatDataFlag;
638 const auto flagsMask = Flag::Left
639 | Flag::Kicked
640 | Flag::Creator
641 | Flag::Deactivated
642 | Flag::Forbidden
643 | Flag::CallActive
644 | Flag::CallNotEmpty;
645 const auto flagsSet = (data.is_left() ? Flag::Left : Flag())
646 | (data.is_kicked() ? Flag::Kicked : Flag())
647 | (data.is_creator() ? Flag::Creator : Flag())
648 | (data.is_deactivated() ? Flag::Deactivated : Flag())
649 | (data.is_call_active() ? Flag::CallActive : Flag())
650 | ((data.is_call_not_empty()
651 || (chat->groupCall()
652 && chat->groupCall()->fullCount() > 0))
653 ? Flag::CallNotEmpty
654 : Flag());
655 chat->setFlags((chat->flags() & ~flagsMask) | flagsSet);
656 chat->count = data.vparticipants_count().v;
657
658 if (canAddMembers != chat->canAddMembers()) {
659 flags |= UpdateFlag::Rights;
660 }
661 }, [&](const MTPDchatForbidden &data) {
662 const auto chat = result->asChat();
663
664 const auto canAddMembers = chat->canAddMembers();
665
666 chat->input = MTP_inputPeerChat(data.vid());
667 chat->setName(qs(data.vtitle()));
668 chat->setPhoto(MTP_chatPhotoEmpty());
669 chat->date = 0;
670 chat->count = -1;
671 chat->invalidateParticipants();
672 chat->setFlags(ChatDataFlag::Forbidden);
673 chat->setAdminRights(ChatAdminRights());
674 chat->setDefaultRestrictions(ChatRestrictions());
675
676 if (canAddMembers != chat->canAddMembers()) {
677 flags |= UpdateFlag::Rights;
678 }
679 }, [&](const MTPDchannel &data) {
680 const auto channel = result->asChannel();
681
682 minimal = data.is_min();
683 if (minimal && !result->isLoaded()) {
684 LOG(("API Warning: not loaded minimal channel applied."));
685 }
686
687 const auto wasInChannel = channel->amIn();
688 const auto canViewAdmins = channel->canViewAdmins();
689 const auto canViewMembers = channel->canViewMembers();
690 const auto canAddMembers = channel->canAddMembers();
691
692 if (const auto count = data.vparticipants_count()) {
693 channel->setMembersCount(count->v);
694 }
695 if (const auto rights = data.vdefault_banned_rights()) {
696 channel->setDefaultRestrictions(ChatRestrictionsInfo(*rights).flags);
697 } else {
698 channel->setDefaultRestrictions(ChatRestrictions());
699 }
700
701 if (minimal) {
702 if (channel->input.type() == mtpc_inputPeerEmpty
703 || channel->inputChannel.type() == mtpc_inputChannelEmpty) {
704 channel->setAccessHash(data.vaccess_hash().value_or_empty());
705 }
706 } else {
707 if (const auto rights = data.vadmin_rights()) {
708 channel->setAdminRights(ChatAdminRightsInfo(*rights).flags);
709 } else if (channel->hasAdminRights()) {
710 channel->setAdminRights(ChatAdminRights());
711 }
712 if (const auto rights = data.vbanned_rights()) {
713 channel->setRestrictions(ChatRestrictionsInfo(*rights));
714 } else if (channel->hasRestrictions()) {
715 channel->setRestrictions(ChatRestrictionsInfo());
716 }
717 channel->setAccessHash(
718 data.vaccess_hash().value_or(channel->access));
719 channel->date = data.vdate().v;
720 if (const auto restriction = data.vrestriction_reason()) {
721 channel->setUnavailableReasons(
722 ExtractUnavailableReasons(restriction->v));
723 } else {
724 channel->setUnavailableReasons({});
725 }
726 }
727
728 using Flag = ChannelDataFlag;
729 const auto flagsMask = Flag::Broadcast
730 | Flag::Verified
731 | Flag::Scam
732 | Flag::Fake
733 | Flag::Megagroup
734 | Flag::Gigagroup
735 | Flag::Username
736 | Flag::Signatures
737 | Flag::HasLink
738 | Flag::SlowmodeEnabled
739 | Flag::CallActive
740 | Flag::CallNotEmpty
741 | Flag::Forbidden
742 | (!minimal
743 ? Flag::Left
744 | Flag::Creator
745 : Flag());
746 const auto flagsSet = (data.is_broadcast() ? Flag::Broadcast : Flag())
747 | (data.is_verified() ? Flag::Verified : Flag())
748 | (data.is_scam() ? Flag::Scam : Flag())
749 | (data.is_fake() ? Flag::Fake : Flag())
750 | (data.is_megagroup() ? Flag::Megagroup : Flag())
751 | (data.is_gigagroup() ? Flag::Gigagroup : Flag())
752 | (data.vusername() ? Flag::Username : Flag())
753 | (data.is_signatures() ? Flag::Signatures : Flag())
754 | (data.is_has_link() ? Flag::HasLink : Flag())
755 | (data.is_slowmode_enabled() ? Flag::SlowmodeEnabled : Flag())
756 | (data.is_call_active() ? Flag::CallActive : Flag())
757 | ((data.is_call_not_empty()
758 || (channel->groupCall()
759 && channel->groupCall()->fullCount() > 0))
760 ? Flag::CallNotEmpty
761 : Flag())
762 | (!minimal
763 ? (data.is_left() ? Flag::Left : Flag())
764 | (data.is_creator() ? Flag::Creator : Flag())
765 : Flag());
766 channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
767
768 channel->setName(
769 qs(data.vtitle()),
770 TextUtilities::SingleLine(qs(data.vusername().value_or_empty())));
771
772 channel->setPhoto(data.vphoto());
773
774 if (wasInChannel != channel->amIn()) {
775 flags |= UpdateFlag::ChannelAmIn;
776 }
777 if (canViewAdmins != channel->canViewAdmins()
778 || canViewMembers != channel->canViewMembers()
779 || canAddMembers != channel->canAddMembers()) {
780 flags |= UpdateFlag::Rights;
781 }
782 }, [&](const MTPDchannelForbidden &data) {
783 const auto channel = result->asChannel();
784
785 auto wasInChannel = channel->amIn();
786 auto canViewAdmins = channel->canViewAdmins();
787 auto canViewMembers = channel->canViewMembers();
788 auto canAddMembers = channel->canAddMembers();
789
790 using Flag = ChannelDataFlag;
791 const auto flagsMask = Flag::Broadcast
792 | Flag::Megagroup
793 | Flag::Forbidden;
794 const auto flagsSet = (data.is_broadcast() ? Flag::Broadcast : Flag())
795 | (data.is_megagroup() ? Flag::Megagroup : Flag())
796 | Flag::Forbidden;
797 channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
798
799 if (channel->hasAdminRights()) {
800 channel->setAdminRights(ChatAdminRights());
801 }
802 if (channel->hasRestrictions()) {
803 channel->setRestrictions(ChatRestrictionsInfo());
804 }
805
806 channel->setName(qs(data.vtitle()), QString());
807
808 channel->setAccessHash(data.vaccess_hash().v);
809 channel->setPhoto(MTP_chatPhotoEmpty());
810 channel->date = 0;
811 channel->setMembersCount(0);
812
813 if (wasInChannel != channel->amIn()) {
814 flags |= UpdateFlag::ChannelAmIn;
815 }
816 if (canViewAdmins != channel->canViewAdmins()
817 || canViewMembers != channel->canViewMembers()
818 || canAddMembers != channel->canAddMembers()) {
819 flags |= UpdateFlag::Rights;
820 }
821 }, [](const MTPDchatEmpty &) {
822 });
823
824 if (minimal) {
825 if (!result->isMinimalLoaded()) {
826 result->setLoadedStatus(PeerData::LoadedStatus::Minimal);
827 }
828 } else if (!result->isLoaded()) {
829 result->setLoadedStatus(PeerData::LoadedStatus::Normal);
830 }
831 if (flags) {
832 session().changes().peerUpdated(result, flags);
833 }
834 return result;
835 }
836
processUsers(const MTPVector<MTPUser> & data)837 UserData *Session::processUsers(const MTPVector<MTPUser> &data) {
838 auto result = (UserData*)nullptr;
839 for (const auto &user : data.v) {
840 result = processUser(user);
841 }
842 return result;
843 }
844
processChats(const MTPVector<MTPChat> & data)845 PeerData *Session::processChats(const MTPVector<MTPChat> &data) {
846 auto result = (PeerData*)nullptr;
847 for (const auto &chat : data.v) {
848 result = processChat(chat);
849 }
850 return result;
851 }
852
applyMaximumChatVersions(const MTPVector<MTPChat> & data)853 void Session::applyMaximumChatVersions(const MTPVector<MTPChat> &data) {
854 for (const auto &chat : data.v) {
855 chat.match([&](const MTPDchat &data) {
856 if (const auto chat = chatLoaded(data.vid().v)) {
857 if (data.vversion().v < chat->version()) {
858 chat->setVersion(data.vversion().v);
859 }
860 }
861 }, [](const auto &) {
862 });
863 }
864 }
865
registerGroupCall(not_null<GroupCall * > call)866 void Session::registerGroupCall(not_null<GroupCall*> call) {
867 _groupCalls.emplace(call->id(), call);
868 }
869
unregisterGroupCall(not_null<GroupCall * > call)870 void Session::unregisterGroupCall(not_null<GroupCall*> call) {
871 _groupCalls.remove(call->id());
872 }
873
groupCall(CallId callId) const874 GroupCall *Session::groupCall(CallId callId) const {
875 const auto i = _groupCalls.find(callId);
876 return (i != end(_groupCalls)) ? i->second.get() : nullptr;
877 }
878
invitedToCallUsers(CallId callId) const879 auto Session::invitedToCallUsers(CallId callId) const
880 -> const base::flat_set<not_null<UserData*>> & {
881 static const base::flat_set<not_null<UserData*>> kEmpty;
882 const auto i = _invitedToCallUsers.find(callId);
883 return (i != _invitedToCallUsers.end()) ? i->second : kEmpty;
884 }
885
registerInvitedToCallUser(CallId callId,not_null<PeerData * > peer,not_null<UserData * > user)886 void Session::registerInvitedToCallUser(
887 CallId callId,
888 not_null<PeerData*> peer,
889 not_null<UserData*> user) {
890 const auto call = peer->groupCall();
891 if (call && call->id() == callId) {
892 const auto inCall = ranges::contains(
893 call->participants(),
894 user,
895 &Data::GroupCallParticipant::peer);
896 if (inCall) {
897 return;
898 }
899 }
900 _invitedToCallUsers[callId].emplace(user);
901 _invitesToCalls.fire({ callId, user });
902 }
903
unregisterInvitedToCallUser(CallId callId,not_null<UserData * > user)904 void Session::unregisterInvitedToCallUser(
905 CallId callId,
906 not_null<UserData*> user) {
907 const auto i = _invitedToCallUsers.find(callId);
908 if (i != _invitedToCallUsers.end()) {
909 i->second.remove(user);
910 if (i->second.empty()) {
911 _invitedToCallUsers.erase(i);
912 }
913 }
914 }
915
peerByUsername(const QString & username) const916 PeerData *Session::peerByUsername(const QString &username) const {
917 const auto uname = username.trimmed();
918 for (const auto &[peerId, peer] : _peers) {
919 if (!peer->userName().compare(uname, Qt::CaseInsensitive)) {
920 return peer.get();
921 }
922 }
923 return nullptr;
924 }
925
enumerateUsers(Fn<void (not_null<UserData * >)> action) const926 void Session::enumerateUsers(Fn<void(not_null<UserData*>)> action) const {
927 for (const auto &[peerId, peer] : _peers) {
928 if (const auto user = peer->asUser()) {
929 action(user);
930 }
931 }
932 }
933
enumerateGroups(Fn<void (not_null<PeerData * >)> action) const934 void Session::enumerateGroups(Fn<void(not_null<PeerData*>)> action) const {
935 for (const auto &[peerId, peer] : _peers) {
936 if (peer->isChat() || peer->isMegagroup()) {
937 action(peer.get());
938 }
939 }
940 }
941
enumerateChannels(Fn<void (not_null<ChannelData * >)> action) const942 void Session::enumerateChannels(
943 Fn<void(not_null<ChannelData*>)> action) const {
944 for (const auto &[peerId, peer] : _peers) {
945 if (const auto channel = peer->asChannel()) {
946 if (!channel->isMegagroup()) {
947 action(channel);
948 }
949 }
950 }
951 }
952
history(PeerId peerId)953 not_null<History*> Session::history(PeerId peerId) {
954 return _histories->findOrCreate(peerId);
955 }
956
historyLoaded(PeerId peerId) const957 History *Session::historyLoaded(PeerId peerId) const {
958 return _histories->find(peerId);
959 }
960
history(not_null<const PeerData * > peer)961 not_null<History*> Session::history(not_null<const PeerData*> peer) {
962 return history(peer->id);
963 }
964
historyLoaded(const PeerData * peer)965 History *Session::historyLoaded(const PeerData *peer) {
966 return peer ? historyLoaded(peer->id) : nullptr;
967 }
968
deleteConversationLocally(not_null<PeerData * > peer)969 void Session::deleteConversationLocally(not_null<PeerData*> peer) {
970 const auto history = historyLoaded(peer);
971 if (history) {
972 if (history->folderKnown()) {
973 setChatPinned(history, FilterId(), false);
974 }
975 removeChatListEntry(history);
976 history->clear(peer->isChannel()
977 ? History::ClearType::Unload
978 : History::ClearType::DeleteChat);
979 }
980 if (const auto channel = peer->asMegagroup()) {
981 channel->addFlags(ChannelDataFlag::Left);
982 if (const auto from = channel->getMigrateFromChat()) {
983 if (const auto migrated = historyLoaded(from)) {
984 migrated->updateChatListExistence();
985 }
986 }
987 }
988 }
989
cancelForwarding(not_null<History * > history)990 void Session::cancelForwarding(not_null<History*> history) {
991 history->setForwardDraft({});
992 session().changes().historyUpdated(
993 history,
994 Data::HistoryUpdate::Flag::ForwardDraft);
995 }
996
chatsListLoaded(Data::Folder * folder)997 bool Session::chatsListLoaded(Data::Folder *folder) {
998 return chatsList(folder)->loaded();
999 }
1000
chatsListChanged(FolderId folderId)1001 void Session::chatsListChanged(FolderId folderId) {
1002 chatsListChanged(folderId ? folder(folderId).get() : nullptr);
1003 }
1004
chatsListChanged(Data::Folder * folder)1005 void Session::chatsListChanged(Data::Folder *folder) {
1006 _chatsListChanged.fire_copy(folder);
1007 }
1008
chatsListDone(Data::Folder * folder)1009 void Session::chatsListDone(Data::Folder *folder) {
1010 if (folder) {
1011 folder->chatsList()->setLoaded();
1012 } else {
1013 _chatsList.setLoaded();
1014 }
1015 _chatsListLoadedEvents.fire_copy(folder);
1016 }
1017
userIsBotChanged(not_null<UserData * > user)1018 void Session::userIsBotChanged(not_null<UserData*> user) {
1019 if (const auto history = this->history(user)) {
1020 chatsFilters().refreshHistory(history);
1021 }
1022 _userIsBotChanges.fire_copy(user);
1023 }
1024
userIsBotChanges() const1025 rpl::producer<not_null<UserData*>> Session::userIsBotChanges() const {
1026 return _userIsBotChanges.events();
1027 }
1028
botCommandsChanged(not_null<PeerData * > peer)1029 void Session::botCommandsChanged(not_null<PeerData*> peer) {
1030 _botCommandsChanges.fire_copy(peer);
1031 }
1032
botCommandsChanges() const1033 rpl::producer<not_null<PeerData*>> Session::botCommandsChanges() const {
1034 return _botCommandsChanges.events();
1035 }
1036
cache()1037 Storage::Cache::Database &Session::cache() {
1038 return *_cache;
1039 }
1040
cacheBigFile()1041 Storage::Cache::Database &Session::cacheBigFile() {
1042 return *_bigFileCache;
1043 }
1044
suggestStartExport(TimeId availableAt)1045 void Session::suggestStartExport(TimeId availableAt) {
1046 _exportAvailableAt = availableAt;
1047 suggestStartExport();
1048 }
1049
clearExportSuggestion()1050 void Session::clearExportSuggestion() {
1051 _exportAvailableAt = 0;
1052 if (_exportSuggestion) {
1053 _exportSuggestion->closeBox();
1054 }
1055 }
1056
suggestStartExport()1057 void Session::suggestStartExport() {
1058 if (_exportAvailableAt <= 0) {
1059 return;
1060 }
1061
1062 const auto now = base::unixtime::now();
1063 const auto left = (_exportAvailableAt <= now)
1064 ? 0
1065 : (_exportAvailableAt - now);
1066 if (left) {
1067 base::call_delayed(
1068 std::min(left + 5, 3600) * crl::time(1000),
1069 _session,
1070 [=] { suggestStartExport(); });
1071 } else if (Core::App().exportManager().inProgress()) {
1072 Export::View::ClearSuggestStart(&session());
1073 } else {
1074 _exportSuggestion = Export::View::SuggestStart(&session());
1075 }
1076 }
1077
passportCredentials() const1078 const Passport::SavedCredentials *Session::passportCredentials() const {
1079 return _passportCredentials ? &_passportCredentials->first : nullptr;
1080 }
1081
rememberPassportCredentials(Passport::SavedCredentials data,crl::time rememberFor)1082 void Session::rememberPassportCredentials(
1083 Passport::SavedCredentials data,
1084 crl::time rememberFor) {
1085 Expects(rememberFor > 0);
1086
1087 static auto generation = 0;
1088 _passportCredentials = std::make_unique<CredentialsWithGeneration>(
1089 std::move(data),
1090 ++generation);
1091 base::call_delayed(rememberFor, _session, [=, check = generation] {
1092 if (_passportCredentials && _passportCredentials->second == check) {
1093 forgetPassportCredentials();
1094 }
1095 });
1096 }
1097
forgetPassportCredentials()1098 void Session::forgetPassportCredentials() {
1099 _passportCredentials = nullptr;
1100 }
1101
nameSortKey(const QString & name) const1102 QString Session::nameSortKey(const QString &name) const {
1103 return TextUtilities::RemoveAccents(name).toLower();
1104 }
1105
setupMigrationViewer()1106 void Session::setupMigrationViewer() {
1107 session().changes().peerUpdates(
1108 PeerUpdate::Flag::Migration
1109 ) | rpl::map([](const PeerUpdate &update) {
1110 return update.peer->asChat();
1111 }) | rpl::filter([=](ChatData *chat) {
1112 return (chat != nullptr);
1113 }) | rpl::start_with_next([=](not_null<ChatData*> chat) {
1114 const auto channel = chat->migrateTo();
1115 if (!channel) {
1116 return;
1117 }
1118
1119 chat->clearGroupCall();
1120 if (const auto from = historyLoaded(chat)) {
1121 if (const auto to = historyLoaded(channel)) {
1122 if (to->inChatList() && from->inChatList()) {
1123 removeChatListEntry(from);
1124 }
1125 }
1126 }
1127 }, _lifetime);
1128 }
1129
setupChannelLeavingViewer()1130 void Session::setupChannelLeavingViewer() {
1131 session().changes().peerUpdates(
1132 PeerUpdate::Flag::ChannelAmIn
1133 ) | rpl::map([](const PeerUpdate &update) {
1134 return update.peer->asChannel();
1135 }) | rpl::start_with_next([=](not_null<ChannelData*> channel) {
1136 if (channel->amIn()) {
1137 channel->clearInvitePeek();
1138 } else {
1139 if (const auto history = historyLoaded(channel->id)) {
1140 history->removeJoinedMessage();
1141 history->updateChatListExistence();
1142 history->updateChatListSortPosition();
1143 }
1144 }
1145 }, _lifetime);
1146 }
1147
setupPeerNameViewer()1148 void Session::setupPeerNameViewer() {
1149 session().changes().realtimeNameUpdates(
1150 ) | rpl::start_with_next([=](const NameUpdate &update) {
1151 const auto peer = update.peer;
1152 if (const auto history = historyLoaded(peer)) {
1153 history->refreshChatListNameSortKey();
1154 }
1155 const auto &oldLetters = update.oldFirstLetters;
1156 _contactsNoChatsList.peerNameChanged(peer, oldLetters);
1157 _contactsList.peerNameChanged(peer, oldLetters);
1158
1159 }, _lifetime);
1160 }
1161
setupUserIsContactViewer()1162 void Session::setupUserIsContactViewer() {
1163 session().changes().peerUpdates(
1164 PeerUpdate::Flag::IsContact
1165 ) | rpl::map([](const PeerUpdate &update) {
1166 return update.peer->asUser();
1167 }) | rpl::start_with_next([=](not_null<UserData*> user) {
1168 const auto i = _contactViews.find(peerToUser(user->id));
1169 if (i != _contactViews.end()) {
1170 for (const auto &view : i->second) {
1171 requestViewResize(view);
1172 }
1173 }
1174 if (!user->isLoaded()) {
1175 LOG(("API Error: "
1176 "userIsContactChanged() called for a not loaded user!"));
1177 return;
1178 }
1179 if (user->isContact()) {
1180 const auto history = this->history(user->id);
1181 _contactsList.addByName(history);
1182 if (!history->inChatList()) {
1183 _contactsNoChatsList.addByName(history);
1184 }
1185 } else if (const auto history = historyLoaded(user)) {
1186 _contactsNoChatsList.del(history);
1187 _contactsList.del(history);
1188 }
1189 }, _lifetime);
1190 }
1191
1192 Session::~Session() = default;
1193
1194 template <typename Method>
enumerateItemViews(not_null<const HistoryItem * > item,Method method)1195 void Session::enumerateItemViews(
1196 not_null<const HistoryItem*> item,
1197 Method method) {
1198 if (const auto i = _views.find(item); i != _views.end()) {
1199 for (const auto view : i->second) {
1200 method(view);
1201 }
1202 }
1203 }
1204
photoLoadSettingsChanged()1205 void Session::photoLoadSettingsChanged() {
1206 for (const auto &[id, photo] : _photos) {
1207 photo->automaticLoadSettingsChanged();
1208 }
1209 }
1210
documentLoadSettingsChanged()1211 void Session::documentLoadSettingsChanged() {
1212 for (const auto &[id, document] : _documents) {
1213 document->automaticLoadSettingsChanged();
1214 }
1215 }
1216
notifyPhotoLayoutChanged(not_null<const PhotoData * > photo)1217 void Session::notifyPhotoLayoutChanged(not_null<const PhotoData*> photo) {
1218 if (const auto i = _photoItems.find(photo); i != end(_photoItems)) {
1219 for (const auto &item : i->second) {
1220 notifyItemLayoutChange(item);
1221 }
1222 }
1223 }
1224
requestPhotoViewRepaint(not_null<const PhotoData * > photo)1225 void Session::requestPhotoViewRepaint(not_null<const PhotoData*> photo) {
1226 const auto i = _photoItems.find(photo);
1227 if (i != end(_photoItems)) {
1228 for (const auto &item : i->second) {
1229 requestItemRepaint(item);
1230 }
1231 }
1232 }
1233
notifyDocumentLayoutChanged(not_null<const DocumentData * > document)1234 void Session::notifyDocumentLayoutChanged(
1235 not_null<const DocumentData*> document) {
1236 const auto i = _documentItems.find(document);
1237 if (i != end(_documentItems)) {
1238 for (const auto &item : i->second) {
1239 notifyItemLayoutChange(item);
1240 }
1241 }
1242 if (const auto items = InlineBots::Layout::documentItems()) {
1243 if (const auto i = items->find(document); i != items->end()) {
1244 for (const auto &item : i->second) {
1245 item->layoutChanged();
1246 }
1247 }
1248 }
1249 }
1250
requestDocumentViewRepaint(not_null<const DocumentData * > document)1251 void Session::requestDocumentViewRepaint(
1252 not_null<const DocumentData*> document) {
1253 const auto i = _documentItems.find(document);
1254 if (i != end(_documentItems)) {
1255 for (const auto &item : i->second) {
1256 requestItemRepaint(item);
1257 }
1258 }
1259 }
1260
requestPollViewRepaint(not_null<const PollData * > poll)1261 void Session::requestPollViewRepaint(not_null<const PollData*> poll) {
1262 if (const auto i = _pollViews.find(poll); i != _pollViews.end()) {
1263 for (const auto &view : i->second) {
1264 requestViewResize(view);
1265 }
1266 }
1267 }
1268
documentLoadProgress(not_null<DocumentData * > document)1269 void Session::documentLoadProgress(not_null<DocumentData*> document) {
1270 requestDocumentViewRepaint(document);
1271 session().documentUpdated.notify(document, true);
1272
1273 if (document->isAudioFile()) {
1274 ::Media::Player::instance()->documentLoadProgress(document);
1275 }
1276 }
1277
documentLoadDone(not_null<DocumentData * > document)1278 void Session::documentLoadDone(not_null<DocumentData*> document) {
1279 notifyDocumentLayoutChanged(document);
1280 }
1281
documentLoadFail(not_null<DocumentData * > document,bool started)1282 void Session::documentLoadFail(
1283 not_null<DocumentData*> document,
1284 bool started) {
1285 notifyDocumentLayoutChanged(document);
1286 }
1287
photoLoadProgress(not_null<PhotoData * > photo)1288 void Session::photoLoadProgress(not_null<PhotoData*> photo) {
1289 requestPhotoViewRepaint(photo);
1290 }
1291
photoLoadDone(not_null<PhotoData * > photo)1292 void Session::photoLoadDone(not_null<PhotoData*> photo) {
1293 notifyPhotoLayoutChanged(photo);
1294 }
1295
photoLoadFail(not_null<PhotoData * > photo,bool started)1296 void Session::photoLoadFail(
1297 not_null<PhotoData*> photo,
1298 bool started) {
1299 notifyPhotoLayoutChanged(photo);
1300 }
1301
markMediaRead(not_null<const DocumentData * > document)1302 void Session::markMediaRead(not_null<const DocumentData*> document) {
1303 const auto i = _documentItems.find(document);
1304 if (i != end(_documentItems)) {
1305 _session->api().markMediaRead({ begin(i->second), end(i->second) });
1306 }
1307 }
1308
notifyItemLayoutChange(not_null<const HistoryItem * > item)1309 void Session::notifyItemLayoutChange(not_null<const HistoryItem*> item) {
1310 _itemLayoutChanges.fire_copy(item);
1311 enumerateItemViews(item, [&](not_null<ViewElement*> view) {
1312 notifyViewLayoutChange(view);
1313 });
1314 }
1315
itemLayoutChanged() const1316 rpl::producer<not_null<const HistoryItem*>> Session::itemLayoutChanged() const {
1317 return _itemLayoutChanges.events();
1318 }
1319
notifyViewLayoutChange(not_null<const ViewElement * > view)1320 void Session::notifyViewLayoutChange(not_null<const ViewElement*> view) {
1321 _viewLayoutChanges.fire_copy(view);
1322 }
1323
viewLayoutChanged() const1324 rpl::producer<not_null<const ViewElement*>> Session::viewLayoutChanged() const {
1325 return _viewLayoutChanges.events();
1326 }
1327
notifyNewItemAdded(not_null<HistoryItem * > item)1328 void Session::notifyNewItemAdded(not_null<HistoryItem*> item) {
1329 _newItemAdded.fire_copy(item);
1330 }
1331
newItemAdded() const1332 rpl::producer<not_null<HistoryItem*>> Session::newItemAdded() const {
1333 return _newItemAdded.events();
1334 }
1335
changeMessageId(ChannelId channel,MsgId wasId,MsgId nowId)1336 void Session::changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId) {
1337 const auto list = messagesListForInsert(channel);
1338 auto i = list->find(wasId);
1339 Assert(i != list->end());
1340 auto owned = std::move(i->second);
1341 list->erase(i);
1342 const auto [j, ok] = list->emplace(nowId, std::move(owned));
1343
1344 Ensures(ok);
1345 }
1346
notifyItemIdChange(IdChange event)1347 void Session::notifyItemIdChange(IdChange event) {
1348 const auto item = event.item;
1349 changeMessageId(item->history()->channelId(), event.oldId, item->id);
1350
1351 _itemIdChanges.fire_copy(event);
1352
1353 const auto refreshViewDataId = [](not_null<ViewElement*> view) {
1354 view->refreshDataId();
1355 };
1356 enumerateItemViews(item, refreshViewDataId);
1357 if (const auto group = groups().find(item)) {
1358 const auto leader = group->items.front();
1359 if (leader != item) {
1360 enumerateItemViews(leader, refreshViewDataId);
1361 }
1362 }
1363 }
1364
itemIdChanged() const1365 rpl::producer<Session::IdChange> Session::itemIdChanged() const {
1366 return _itemIdChanges.events();
1367 }
1368
requestItemRepaint(not_null<const HistoryItem * > item)1369 void Session::requestItemRepaint(not_null<const HistoryItem*> item) {
1370 _itemRepaintRequest.fire_copy(item);
1371 auto repaintGroupLeader = false;
1372 auto repaintView = [&](not_null<const ViewElement*> view) {
1373 if (view->isHiddenByGroup()) {
1374 repaintGroupLeader = true;
1375 } else {
1376 requestViewRepaint(view);
1377 }
1378 };
1379 enumerateItemViews(item, repaintView);
1380 if (repaintGroupLeader) {
1381 if (const auto group = groups().find(item)) {
1382 const auto leader = group->items.front();
1383 if (leader != item) {
1384 enumerateItemViews(leader, repaintView);
1385 }
1386 }
1387 }
1388 const auto history = item->history();
1389 if (history->lastItemDialogsView.dependsOn(item)) {
1390 history->updateChatListEntry();
1391 }
1392 }
1393
itemRepaintRequest() const1394 rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const {
1395 return _itemRepaintRequest.events();
1396 }
1397
requestViewRepaint(not_null<const ViewElement * > view)1398 void Session::requestViewRepaint(not_null<const ViewElement*> view) {
1399 _viewRepaintRequest.fire_copy(view);
1400 }
1401
viewRepaintRequest() const1402 rpl::producer<not_null<const ViewElement*>> Session::viewRepaintRequest() const {
1403 return _viewRepaintRequest.events();
1404 }
1405
requestItemResize(not_null<const HistoryItem * > item)1406 void Session::requestItemResize(not_null<const HistoryItem*> item) {
1407 _itemResizeRequest.fire_copy(item);
1408 enumerateItemViews(item, [&](not_null<ViewElement*> view) {
1409 requestViewResize(view);
1410 });
1411 }
1412
itemResizeRequest() const1413 rpl::producer<not_null<const HistoryItem*>> Session::itemResizeRequest() const {
1414 return _itemResizeRequest.events();
1415 }
1416
requestViewResize(not_null<ViewElement * > view)1417 void Session::requestViewResize(not_null<ViewElement*> view) {
1418 view->setPendingResize();
1419 _viewResizeRequest.fire_copy(view);
1420 notifyViewLayoutChange(view);
1421 }
1422
viewResizeRequest() const1423 rpl::producer<not_null<ViewElement*>> Session::viewResizeRequest() const {
1424 return _viewResizeRequest.events();
1425 }
1426
requestItemViewRefresh(not_null<HistoryItem * > item)1427 void Session::requestItemViewRefresh(not_null<HistoryItem*> item) {
1428 if (const auto view = item->mainView()) {
1429 notifyHistoryChangeDelayed(item->history());
1430 view->refreshInBlock();
1431 }
1432 _itemViewRefreshRequest.fire_copy(item);
1433 }
1434
itemViewRefreshRequest() const1435 rpl::producer<not_null<HistoryItem*>> Session::itemViewRefreshRequest() const {
1436 return _itemViewRefreshRequest.events();
1437 }
1438
requestItemTextRefresh(not_null<HistoryItem * > item)1439 void Session::requestItemTextRefresh(not_null<HistoryItem*> item) {
1440 if (const auto i = _views.find(item); i != _views.end()) {
1441 for (const auto view : i->second) {
1442 if (const auto media = view->media()) {
1443 media->parentTextUpdated();
1444 }
1445 }
1446 }
1447 }
1448
requestAnimationPlayInline(not_null<HistoryItem * > item)1449 void Session::requestAnimationPlayInline(not_null<HistoryItem*> item) {
1450 _animationPlayInlineRequest.fire_copy(item);
1451
1452 if (const auto media = item->media()) {
1453 if (const auto data = media->document()) {
1454 if (data && data->isVideoMessage()) {
1455 const auto msgId = item->fullId();
1456 ::Media::Player::instance()->playPause({ data, msgId });
1457 }
1458 }
1459 }
1460 }
1461
animationPlayInlineRequest() const1462 rpl::producer<not_null<HistoryItem*>> Session::animationPlayInlineRequest() const {
1463 return _animationPlayInlineRequest.events();
1464 }
1465
itemRemoved() const1466 rpl::producer<not_null<const HistoryItem*>> Session::itemRemoved() const {
1467 return _itemRemoved.events();
1468 }
1469
itemRemoved(FullMsgId itemId) const1470 rpl::producer<not_null<const HistoryItem*>> Session::itemRemoved(
1471 FullMsgId itemId) const {
1472 return itemRemoved(
1473 ) | rpl::filter([=](not_null<const HistoryItem*> item) {
1474 return (itemId == item->fullId());
1475 });
1476 }
1477
notifyViewRemoved(not_null<const ViewElement * > view)1478 void Session::notifyViewRemoved(not_null<const ViewElement*> view) {
1479 _viewRemoved.fire_copy(view);
1480 }
1481
viewRemoved() const1482 rpl::producer<not_null<const ViewElement*>> Session::viewRemoved() const {
1483 return _viewRemoved.events();
1484 }
1485
notifyHistoryUnloaded(not_null<const History * > history)1486 void Session::notifyHistoryUnloaded(not_null<const History*> history) {
1487 _historyUnloaded.fire_copy(history);
1488 }
1489
historyUnloaded() const1490 rpl::producer<not_null<const History*>> Session::historyUnloaded() const {
1491 return _historyUnloaded.events();
1492 }
1493
notifyHistoryCleared(not_null<const History * > history)1494 void Session::notifyHistoryCleared(not_null<const History*> history) {
1495 _historyCleared.fire_copy(history);
1496 }
1497
historyCleared() const1498 rpl::producer<not_null<const History*>> Session::historyCleared() const {
1499 return _historyCleared.events();
1500 }
1501
notifyHistoryChangeDelayed(not_null<History * > history)1502 void Session::notifyHistoryChangeDelayed(not_null<History*> history) {
1503 history->setHasPendingResizedItems();
1504 _historiesChanged.insert(history);
1505 }
1506
historyChanged() const1507 rpl::producer<not_null<History*>> Session::historyChanged() const {
1508 return _historyChanged.events();
1509 }
1510
sendHistoryChangeNotifications()1511 void Session::sendHistoryChangeNotifications() {
1512 for (const auto &history : base::take(_historiesChanged)) {
1513 _historyChanged.fire_copy(history);
1514 }
1515 }
1516
notifyPinnedDialogsOrderUpdated()1517 void Session::notifyPinnedDialogsOrderUpdated() {
1518 _pinnedDialogsOrderUpdated.fire({});
1519 }
1520
pinnedDialogsOrderUpdated() const1521 rpl::producer<> Session::pinnedDialogsOrderUpdated() const {
1522 return _pinnedDialogsOrderUpdated.events();
1523 }
1524
registerHeavyViewPart(not_null<ViewElement * > view)1525 void Session::registerHeavyViewPart(not_null<ViewElement*> view) {
1526 _heavyViewParts.emplace(view);
1527 }
1528
unregisterHeavyViewPart(not_null<ViewElement * > view)1529 void Session::unregisterHeavyViewPart(not_null<ViewElement*> view) {
1530 _heavyViewParts.remove(view);
1531 }
1532
unloadHeavyViewParts(not_null<HistoryView::ElementDelegate * > delegate)1533 void Session::unloadHeavyViewParts(
1534 not_null<HistoryView::ElementDelegate*> delegate) {
1535 if (_heavyViewParts.empty()) {
1536 return;
1537 }
1538 const auto remove = ranges::count(
1539 _heavyViewParts,
1540 delegate,
1541 [](not_null<ViewElement*> element) { return element->delegate(); });
1542 if (remove == _heavyViewParts.size()) {
1543 for (const auto &view : base::take(_heavyViewParts)) {
1544 view->unloadHeavyPart();
1545 }
1546 } else {
1547 auto remove = std::vector<not_null<ViewElement*>>();
1548 for (const auto &view : _heavyViewParts) {
1549 if (view->delegate() == delegate) {
1550 remove.push_back(view);
1551 }
1552 }
1553 for (const auto view : remove) {
1554 view->unloadHeavyPart();
1555 }
1556 }
1557 }
1558
unloadHeavyViewParts(not_null<HistoryView::ElementDelegate * > delegate,int from,int till)1559 void Session::unloadHeavyViewParts(
1560 not_null<HistoryView::ElementDelegate*> delegate,
1561 int from,
1562 int till) {
1563 if (_heavyViewParts.empty()) {
1564 return;
1565 }
1566 auto remove = std::vector<not_null<ViewElement*>>();
1567 for (const auto &view : _heavyViewParts) {
1568 if (view->delegate() == delegate
1569 && !delegate->elementIntersectsRange(view, from, till)) {
1570 remove.push_back(view);
1571 }
1572 }
1573 for (const auto view : remove) {
1574 view->unloadHeavyPart();
1575 }
1576 }
1577
removeMegagroupParticipant(not_null<ChannelData * > channel,not_null<UserData * > user)1578 void Session::removeMegagroupParticipant(
1579 not_null<ChannelData*> channel,
1580 not_null<UserData*> user) {
1581 _megagroupParticipantRemoved.fire({ channel, user });
1582 }
1583
megagroupParticipantRemoved() const1584 auto Session::megagroupParticipantRemoved() const
1585 -> rpl::producer<MegagroupParticipant> {
1586 return _megagroupParticipantRemoved.events();
1587 }
1588
megagroupParticipantRemoved(not_null<ChannelData * > channel) const1589 rpl::producer<not_null<UserData*>> Session::megagroupParticipantRemoved(
1590 not_null<ChannelData*> channel) const {
1591 return megagroupParticipantRemoved(
1592 ) | rpl::filter([channel](auto updateChannel, auto user) {
1593 return (updateChannel == channel);
1594 }) | rpl::map([](auto updateChannel, auto user) {
1595 return user;
1596 });
1597 }
1598
addNewMegagroupParticipant(not_null<ChannelData * > channel,not_null<UserData * > user)1599 void Session::addNewMegagroupParticipant(
1600 not_null<ChannelData*> channel,
1601 not_null<UserData*> user) {
1602 _megagroupParticipantAdded.fire({ channel, user });
1603 }
1604
megagroupParticipantAdded() const1605 auto Session::megagroupParticipantAdded() const
1606 -> rpl::producer<MegagroupParticipant> {
1607 return _megagroupParticipantAdded.events();
1608 }
1609
megagroupParticipantAdded(not_null<ChannelData * > channel) const1610 rpl::producer<not_null<UserData*>> Session::megagroupParticipantAdded(
1611 not_null<ChannelData*> channel) const {
1612 return megagroupParticipantAdded(
1613 ) | rpl::filter([channel](auto updateChannel, auto user) {
1614 return (updateChannel == channel);
1615 }) | rpl::map([](auto updateChannel, auto user) {
1616 return user;
1617 });
1618 }
1619
idsToItems(const MessageIdsList & ids) const1620 HistoryItemsList Session::idsToItems(
1621 const MessageIdsList &ids) const {
1622 return ranges::views::all(
1623 ids
1624 ) | ranges::views::transform([&](const FullMsgId &fullId) {
1625 return message(fullId);
1626 }) | ranges::views::filter([](HistoryItem *item) {
1627 return item != nullptr;
1628 }) | ranges::views::transform([](HistoryItem *item) {
1629 return not_null<HistoryItem*>(item);
1630 }) | ranges::to_vector;
1631 }
1632
itemsToIds(const HistoryItemsList & items) const1633 MessageIdsList Session::itemsToIds(
1634 const HistoryItemsList &items) const {
1635 return ranges::views::all(
1636 items
1637 ) | ranges::views::transform([](not_null<HistoryItem*> item) {
1638 return item->fullId();
1639 }) | ranges::to_vector;
1640 }
1641
itemOrItsGroup(not_null<HistoryItem * > item) const1642 MessageIdsList Session::itemOrItsGroup(not_null<HistoryItem*> item) const {
1643 if (const auto group = groups().find(item)) {
1644 return itemsToIds(group->items);
1645 }
1646 return { 1, item->fullId() };
1647 }
1648
setChatPinned(const Dialogs::Key & key,FilterId filterId,bool pinned)1649 void Session::setChatPinned(
1650 const Dialogs::Key &key,
1651 FilterId filterId,
1652 bool pinned) {
1653 Expects(key.entry()->folderKnown());
1654
1655 const auto list = filterId
1656 ? chatsFilters().chatsList(filterId)
1657 : chatsList(key.entry()->folder());
1658 list->pinned()->setPinned(key, pinned);
1659 notifyPinnedDialogsOrderUpdated();
1660 }
1661
setPinnedFromDialog(const Dialogs::Key & key,bool pinned)1662 void Session::setPinnedFromDialog(const Dialogs::Key &key, bool pinned) {
1663 Expects(key.entry()->folderKnown());
1664
1665 const auto list = chatsList(key.entry()->folder())->pinned();
1666 if (pinned) {
1667 list->addPinned(key);
1668 } else {
1669 list->setPinned(key, false);
1670 }
1671 }
1672
applyPinnedChats(Data::Folder * folder,const QVector<MTPDialogPeer> & list)1673 void Session::applyPinnedChats(
1674 Data::Folder *folder,
1675 const QVector<MTPDialogPeer> &list) {
1676 for (const auto &peer : list) {
1677 peer.match([&](const MTPDdialogPeer &data) {
1678 const auto history = this->history(peerFromMTP(data.vpeer()));
1679 if (folder) {
1680 history->setFolder(folder);
1681 } else {
1682 history->clearFolder();
1683 }
1684 }, [&](const MTPDdialogPeerFolder &data) {
1685 if (folder) {
1686 LOG(("API Error: Nested folders detected."));
1687 }
1688 });
1689 }
1690 chatsList(folder)->pinned()->applyList(this, list);
1691 notifyPinnedDialogsOrderUpdated();
1692 }
1693
applyDialogs(Data::Folder * requestFolder,const QVector<MTPMessage> & messages,const QVector<MTPDialog> & dialogs,std::optional<int> count)1694 void Session::applyDialogs(
1695 Data::Folder *requestFolder,
1696 const QVector<MTPMessage> &messages,
1697 const QVector<MTPDialog> &dialogs,
1698 std::optional<int> count) {
1699 processMessages(messages, NewMessageType::Last);
1700 for (const auto &dialog : dialogs) {
1701 dialog.match([&](const auto &data) {
1702 applyDialog(requestFolder, data);
1703 });
1704 }
1705 if (requestFolder && count) {
1706 requestFolder->chatsList()->setCloudListSize(*count);
1707 }
1708 }
1709
applyDialog(Data::Folder * requestFolder,const MTPDdialog & data)1710 void Session::applyDialog(
1711 Data::Folder *requestFolder,
1712 const MTPDdialog &data) {
1713 const auto peerId = peerFromMTP(data.vpeer());
1714 if (!peerId) {
1715 return;
1716 }
1717
1718 const auto history = this->history(peerId);
1719 history->applyDialog(requestFolder, data);
1720 setPinnedFromDialog(history, data.is_pinned());
1721
1722 if (const auto from = history->peer->migrateFrom()) {
1723 if (const auto historyFrom = historyLoaded(from)) {
1724 removeChatListEntry(historyFrom);
1725 }
1726 } else if (const auto to = history->peer->migrateTo()) {
1727 if (to->amIn()) {
1728 removeChatListEntry(history);
1729 }
1730 }
1731 }
1732
applyDialog(Data::Folder * requestFolder,const MTPDdialogFolder & data)1733 void Session::applyDialog(
1734 Data::Folder *requestFolder,
1735 const MTPDdialogFolder &data) {
1736 if (requestFolder) {
1737 LOG(("API Error: requestFolder != nullptr for dialogFolder."));
1738 }
1739 const auto folder = processFolder(data.vfolder());
1740 folder->applyDialog(data);
1741 setPinnedFromDialog(folder, data.is_pinned());
1742 }
1743
pinnedChatsCount(Data::Folder * folder,FilterId filterId) const1744 int Session::pinnedChatsCount(
1745 Data::Folder *folder,
1746 FilterId filterId) const {
1747 if (!filterId) {
1748 return pinnedChatsOrder(folder, filterId).size();
1749 }
1750 const auto &list = chatsFilters().list();
1751 const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
1752 return (i != end(list)) ? i->pinned().size() : 0;
1753 }
1754
pinnedChatsLimit(Data::Folder * folder,FilterId filterId) const1755 int Session::pinnedChatsLimit(
1756 Data::Folder *folder,
1757 FilterId filterId) const {
1758 if (!filterId) {
1759 return folder
1760 ? session().serverConfig().pinnedDialogsInFolderMax.current()
1761 : session().serverConfig().pinnedDialogsCountMax.current();
1762 }
1763 const auto &list = chatsFilters().list();
1764 const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
1765 const auto pinned = (i != end(list)) ? i->pinned().size() : 0;
1766 const auto already = (i != end(list)) ? i->always().size() : 0;
1767 return Data::ChatFilter::kPinnedLimit + pinned - already;
1768 }
1769
pinnedChatsOrder(Data::Folder * folder,FilterId filterId) const1770 const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
1771 Data::Folder *folder,
1772 FilterId filterId) const {
1773 const auto list = filterId
1774 ? chatsFilters().chatsList(filterId)
1775 : chatsList(folder);
1776 return list->pinned()->order();
1777 }
1778
clearPinnedChats(Data::Folder * folder)1779 void Session::clearPinnedChats(Data::Folder *folder) {
1780 chatsList(folder)->pinned()->clear();
1781 }
1782
reorderTwoPinnedChats(FilterId filterId,const Dialogs::Key & key1,const Dialogs::Key & key2)1783 void Session::reorderTwoPinnedChats(
1784 FilterId filterId,
1785 const Dialogs::Key &key1,
1786 const Dialogs::Key &key2) {
1787 Expects(key1.entry()->folderKnown() && key2.entry()->folderKnown());
1788 Expects(filterId || (key1.entry()->folder() == key2.entry()->folder()));
1789
1790 const auto list = filterId
1791 ? chatsFilters().chatsList(filterId)
1792 : chatsList(key1.entry()->folder());
1793 list->pinned()->reorder(key1, key2);
1794 notifyPinnedDialogsOrderUpdated();
1795 }
1796
checkEntitiesAndViewsUpdate(const MTPDmessage & data)1797 bool Session::checkEntitiesAndViewsUpdate(const MTPDmessage &data) {
1798 const auto peer = peerFromMTP(data.vpeer_id());
1799 const auto existing = message(peerToChannel(peer), data.vid().v);
1800 if (!existing) {
1801 return false;
1802 }
1803 existing->applySentMessage(data);
1804 const auto result = (existing->mainView() != nullptr);
1805 if (result) {
1806 stickers().checkSavedGif(existing);
1807 }
1808 session().changes().messageUpdated(
1809 existing,
1810 Data::MessageUpdate::Flag::NewMaybeAdded);
1811 return result;
1812 }
1813
updateEditedMessage(const MTPMessage & data)1814 void Session::updateEditedMessage(const MTPMessage &data) {
1815 const auto existing = data.match([](const MTPDmessageEmpty &)
1816 -> HistoryItem* {
1817 return nullptr;
1818 }, [&](const auto &data) {
1819 const auto peer = peerFromMTP(data.vpeer_id());
1820 return message(peerToChannel(peer), data.vid().v);
1821 });
1822 if (!existing) {
1823 return;
1824 }
1825 if (existing->isLocalUpdateMedia() && data.type() == mtpc_message) {
1826 checkEntitiesAndViewsUpdate(data.c_message());
1827 }
1828 data.match([](const MTPDmessageEmpty &) {
1829 }, [&](const MTPDmessageService &data) {
1830 existing->applyEdition(data);
1831 }, [&](const auto &data) {
1832 existing->applyEdition(HistoryMessageEdition(_session, data));
1833 });
1834 }
1835
processMessages(const QVector<MTPMessage> & data,NewMessageType type)1836 void Session::processMessages(
1837 const QVector<MTPMessage> &data,
1838 NewMessageType type) {
1839 auto indices = base::flat_map<uint64, int>();
1840 for (int i = 0, l = data.size(); i != l; ++i) {
1841 const auto &message = data[i];
1842 if (message.type() == mtpc_message) {
1843 const auto &data = message.c_message();
1844 // new message, index my forwarded messages to links overview
1845 if ((type == NewMessageType::Unread)
1846 && checkEntitiesAndViewsUpdate(data)) {
1847 continue;
1848 }
1849 }
1850 const auto id = IdFromMessage(message); // Only 32 bit values here.
1851 indices.emplace((uint64(uint32(id.bare)) << 32) | uint64(i), i);
1852 }
1853 for (const auto &[position, index] : indices) {
1854 addNewMessage(
1855 data[index],
1856 MessageFlags(),
1857 type);
1858 }
1859 }
1860
processMessages(const MTPVector<MTPMessage> & data,NewMessageType type)1861 void Session::processMessages(
1862 const MTPVector<MTPMessage> &data,
1863 NewMessageType type) {
1864 processMessages(data.v, type);
1865 }
1866
processExistingMessages(ChannelData * channel,const MTPmessages_Messages & data)1867 void Session::processExistingMessages(
1868 ChannelData *channel,
1869 const MTPmessages_Messages &data) {
1870 data.match([&](const MTPDmessages_channelMessages &data) {
1871 if (channel) {
1872 channel->ptsReceived(data.vpts().v);
1873 } else {
1874 LOG(("App Error: received messages.channelMessages!"));
1875 }
1876 }, [](const auto &) {});
1877
1878 data.match([&](const MTPDmessages_messagesNotModified&) {
1879 LOG(("API Error: received messages.messagesNotModified!"));
1880 }, [&](const auto &data) {
1881 processUsers(data.vusers());
1882 processChats(data.vchats());
1883 processMessages(data.vmessages(), NewMessageType::Existing);
1884 });
1885 }
1886
messagesList(ChannelId channelId) const1887 const Session::Messages *Session::messagesList(ChannelId channelId) const {
1888 if (channelId == NoChannel) {
1889 return &_messages;
1890 }
1891 const auto i = _channelMessages.find(channelId);
1892 return (i != end(_channelMessages)) ? &i->second : nullptr;
1893 }
1894
messagesListForInsert(ChannelId channelId)1895 auto Session::messagesListForInsert(ChannelId channelId)
1896 -> not_null<Messages*> {
1897 return (channelId == NoChannel)
1898 ? &_messages
1899 : &_channelMessages[channelId];
1900 }
1901
registerMessage(not_null<HistoryItem * > item)1902 void Session::registerMessage(not_null<HistoryItem*> item) {
1903 const auto list = messagesListForInsert(item->channelId());
1904 const auto itemId = item->id;
1905 const auto i = list->find(itemId);
1906 if (i != list->end()) {
1907 LOG(("App Error: Trying to re-registerMessage()."));
1908 i->second->destroy();
1909 }
1910 list->emplace(itemId, item);
1911 }
1912
registerMessageTTL(TimeId when,not_null<HistoryItem * > item)1913 void Session::registerMessageTTL(TimeId when, not_null<HistoryItem*> item) {
1914 Expects(when > 0);
1915
1916 auto &list = _ttlMessages[when];
1917 list.emplace(item);
1918
1919 const auto nearest = _ttlMessages.begin()->first;
1920 if (nearest < when && _ttlCheckTimer.isActive()) {
1921 return;
1922 }
1923 scheduleNextTTLs();
1924 }
1925
scheduleNextTTLs()1926 void Session::scheduleNextTTLs() {
1927 if (_ttlMessages.empty()) {
1928 return;
1929 }
1930 const auto nearest = _ttlMessages.begin()->first;
1931 const auto now = base::unixtime::now();
1932
1933 // Set timer not more than for 24 hours.
1934 const auto maxTimeout = TimeId(86400);
1935 const auto timeout = std::min(std::max(now, nearest) - now, maxTimeout);
1936 _ttlCheckTimer.callOnce(timeout * crl::time(1000));
1937 }
1938
unregisterMessageTTL(TimeId when,not_null<HistoryItem * > item)1939 void Session::unregisterMessageTTL(
1940 TimeId when,
1941 not_null<HistoryItem*> item) {
1942 Expects(when > 0);
1943
1944 const auto i = _ttlMessages.find(when);
1945 if (i == end(_ttlMessages)) {
1946 return;
1947 }
1948 auto &list = i->second;
1949 list.erase(item);
1950 if (list.empty()) {
1951 _ttlMessages.erase(i);
1952 }
1953 }
1954
checkTTLs()1955 void Session::checkTTLs() {
1956 _ttlCheckTimer.cancel();
1957 const auto now = base::unixtime::now();
1958 while (!_ttlMessages.empty() && _ttlMessages.begin()->first <= now) {
1959 _ttlMessages.begin()->second.front()->destroy();
1960 }
1961 scheduleNextTTLs();
1962 }
1963
processMessagesDeleted(ChannelId channelId,const QVector<MTPint> & data)1964 void Session::processMessagesDeleted(
1965 ChannelId channelId,
1966 const QVector<MTPint> &data) {
1967 const auto list = messagesList(channelId);
1968 const auto affected = (channelId != NoChannel)
1969 ? historyLoaded(peerFromChannel(channelId))
1970 : nullptr;
1971 if (!list && !affected) {
1972 return;
1973 }
1974
1975 auto historiesToCheck = base::flat_set<not_null<History*>>();
1976 for (const auto &messageId : data) {
1977 const auto i = list ? list->find(messageId.v) : Messages::iterator();
1978 if (list && i != list->end()) {
1979 const auto history = i->second->history();
1980 i->second->destroy();
1981 if (!history->chatListMessageKnown()) {
1982 historiesToCheck.emplace(history);
1983 }
1984 } else if (affected) {
1985 affected->unknownMessageDeleted(messageId.v);
1986 }
1987 }
1988 for (const auto &history : historiesToCheck) {
1989 history->requestChatListMessage();
1990 }
1991 }
1992
removeDependencyMessage(not_null<HistoryItem * > item)1993 void Session::removeDependencyMessage(not_null<HistoryItem*> item) {
1994 const auto i = _dependentMessages.find(item);
1995 if (i == end(_dependentMessages)) {
1996 return;
1997 }
1998 const auto items = std::move(i->second);
1999 _dependentMessages.erase(i);
2000
2001 for (const auto &dependent : items) {
2002 dependent->dependencyItemRemoved(item);
2003 }
2004 }
2005
unregisterMessage(not_null<HistoryItem * > item)2006 void Session::unregisterMessage(not_null<HistoryItem*> item) {
2007 const auto peerId = item->history()->peer->id;
2008 _itemRemoved.fire_copy(item);
2009 session().changes().messageUpdated(
2010 item,
2011 Data::MessageUpdate::Flag::Destroyed);
2012 groups().unregisterMessage(item);
2013 removeDependencyMessage(item);
2014 messagesListForInsert(peerToChannel(peerId))->erase(item->id);
2015 }
2016
nextLocalMessageId()2017 MsgId Session::nextLocalMessageId() {
2018 Expects(_localMessageIdCounter < EndClientMsgId);
2019
2020 return _localMessageIdCounter++;
2021 }
2022
setSuggestToGigagroup(not_null<ChannelData * > group,bool suggest)2023 void Session::setSuggestToGigagroup(
2024 not_null<ChannelData*> group,
2025 bool suggest) {
2026 if (suggest) {
2027 _suggestToGigagroup.emplace(group);
2028 } else {
2029 _suggestToGigagroup.remove(group);
2030 }
2031 }
2032
suggestToGigagroup(not_null<ChannelData * > group) const2033 bool Session::suggestToGigagroup(not_null<ChannelData*> group) const {
2034 return _suggestToGigagroup.contains(group);
2035 }
2036
message(ChannelId channelId,MsgId itemId) const2037 HistoryItem *Session::message(ChannelId channelId, MsgId itemId) const {
2038 if (!itemId) {
2039 return nullptr;
2040 }
2041
2042 const auto data = messagesList(channelId);
2043 if (!data) {
2044 return nullptr;
2045 }
2046
2047 const auto i = data->find(itemId);
2048 return (i != data->end()) ? i->second.get() : nullptr;
2049 }
2050
message(const ChannelData * channel,MsgId itemId) const2051 HistoryItem *Session::message(
2052 const ChannelData *channel,
2053 MsgId itemId) const {
2054 return message(channel ? peerToChannel(channel->id) : 0, itemId);
2055 }
2056
message(FullMsgId itemId) const2057 HistoryItem *Session::message(FullMsgId itemId) const {
2058 return message(itemId.channel, itemId.msg);
2059 }
2060
updateDependentMessages(not_null<HistoryItem * > item)2061 void Session::updateDependentMessages(not_null<HistoryItem*> item) {
2062 const auto i = _dependentMessages.find(item);
2063 if (i != end(_dependentMessages)) {
2064 for (const auto &dependent : i->second) {
2065 dependent->updateDependencyItem();
2066 }
2067 }
2068 session().changes().messageUpdated(
2069 item,
2070 Data::MessageUpdate::Flag::Edited);
2071 }
2072
registerDependentMessage(not_null<HistoryItem * > dependent,not_null<HistoryItem * > dependency)2073 void Session::registerDependentMessage(
2074 not_null<HistoryItem*> dependent,
2075 not_null<HistoryItem*> dependency) {
2076 _dependentMessages[dependency].emplace(dependent);
2077 }
2078
unregisterDependentMessage(not_null<HistoryItem * > dependent,not_null<HistoryItem * > dependency)2079 void Session::unregisterDependentMessage(
2080 not_null<HistoryItem*> dependent,
2081 not_null<HistoryItem*> dependency) {
2082 const auto i = _dependentMessages.find(dependency);
2083 if (i != end(_dependentMessages)) {
2084 if (i->second.remove(dependent) && i->second.empty()) {
2085 _dependentMessages.erase(i);
2086 }
2087 }
2088 }
2089
registerMessageRandomId(uint64 randomId,FullMsgId itemId)2090 void Session::registerMessageRandomId(uint64 randomId, FullMsgId itemId) {
2091 _messageByRandomId.emplace(randomId, itemId);
2092 }
2093
unregisterMessageRandomId(uint64 randomId)2094 void Session::unregisterMessageRandomId(uint64 randomId) {
2095 _messageByRandomId.remove(randomId);
2096 }
2097
messageIdByRandomId(uint64 randomId) const2098 FullMsgId Session::messageIdByRandomId(uint64 randomId) const {
2099 const auto i = _messageByRandomId.find(randomId);
2100 return (i != end(_messageByRandomId)) ? i->second : FullMsgId();
2101 }
2102
registerMessageSentData(uint64 randomId,PeerId peerId,const QString & text)2103 void Session::registerMessageSentData(
2104 uint64 randomId,
2105 PeerId peerId,
2106 const QString &text) {
2107 _sentMessagesData.emplace(randomId, SentData{ peerId, text });
2108 }
2109
unregisterMessageSentData(uint64 randomId)2110 void Session::unregisterMessageSentData(uint64 randomId) {
2111 _sentMessagesData.remove(randomId);
2112 }
2113
messageSentData(uint64 randomId) const2114 Session::SentData Session::messageSentData(uint64 randomId) const {
2115 const auto i = _sentMessagesData.find(randomId);
2116 return (i != end(_sentMessagesData)) ? i->second : SentData();
2117 }
2118
defaultNotifySettings(not_null<const PeerData * > peer)2119 NotifySettings &Session::defaultNotifySettings(
2120 not_null<const PeerData*> peer) {
2121 return peer->isUser()
2122 ? _defaultUserNotifySettings
2123 : (peer->isChat() || peer->isMegagroup())
2124 ? _defaultChatNotifySettings
2125 : _defaultBroadcastNotifySettings;
2126 }
2127
defaultNotifySettings(not_null<const PeerData * > peer) const2128 const NotifySettings &Session::defaultNotifySettings(
2129 not_null<const PeerData*> peer) const {
2130 return peer->isUser()
2131 ? _defaultUserNotifySettings
2132 : (peer->isChat() || peer->isMegagroup())
2133 ? _defaultChatNotifySettings
2134 : _defaultBroadcastNotifySettings;
2135 }
2136
updateNotifySettingsLocal(not_null<PeerData * > peer)2137 void Session::updateNotifySettingsLocal(not_null<PeerData*> peer) {
2138 const auto history = historyLoaded(peer->id);
2139 auto changesIn = crl::time(0);
2140 const auto muted = notifyIsMuted(peer, &changesIn);
2141 if (history && history->changeMute(muted)) {
2142 // Notification already sent.
2143 } else {
2144 session().changes().peerUpdated(
2145 peer,
2146 PeerUpdate::Flag::Notifications);
2147 }
2148
2149 if (muted) {
2150 _mutedPeers.emplace(peer);
2151 unmuteByFinishedDelayed(changesIn);
2152 if (history) {
2153 Core::App().notifications().clearIncomingFromHistory(history);
2154 }
2155 } else {
2156 _mutedPeers.erase(peer);
2157 }
2158 }
2159
unmuteByFinishedDelayed(crl::time delay)2160 void Session::unmuteByFinishedDelayed(crl::time delay) {
2161 accumulate_min(delay, kMaxNotifyCheckDelay);
2162 if (!_unmuteByFinishedTimer.isActive()
2163 || _unmuteByFinishedTimer.remainingTime() > delay) {
2164 _unmuteByFinishedTimer.callOnce(delay);
2165 }
2166 }
2167
unmuteByFinished()2168 void Session::unmuteByFinished() {
2169 auto changesInMin = crl::time(0);
2170 for (auto i = begin(_mutedPeers); i != end(_mutedPeers);) {
2171 const auto history = historyLoaded((*i)->id);
2172 auto changesIn = crl::time(0);
2173 const auto muted = notifyIsMuted(*i, &changesIn);
2174 if (muted) {
2175 if (history) {
2176 history->changeMute(true);
2177 }
2178 if (!changesInMin || changesInMin > changesIn) {
2179 changesInMin = changesIn;
2180 }
2181 ++i;
2182 } else {
2183 if (history) {
2184 history->changeMute(false);
2185 }
2186 i = _mutedPeers.erase(i);
2187 }
2188 }
2189 if (changesInMin) {
2190 unmuteByFinishedDelayed(changesInMin);
2191 }
2192 }
2193
addNewMessage(const MTPMessage & data,MessageFlags localFlags,NewMessageType type)2194 HistoryItem *Session::addNewMessage(
2195 const MTPMessage &data,
2196 MessageFlags localFlags,
2197 NewMessageType type) {
2198 return addNewMessage(IdFromMessage(data), data, localFlags, type);
2199 }
2200
addNewMessage(MsgId id,const MTPMessage & data,MessageFlags localFlags,NewMessageType type)2201 HistoryItem *Session::addNewMessage(
2202 MsgId id,
2203 const MTPMessage &data,
2204 MessageFlags localFlags,
2205 NewMessageType type) {
2206 const auto peerId = PeerFromMessage(data);
2207 if (!peerId) {
2208 return nullptr;
2209 }
2210
2211 const auto result = history(peerId)->addNewMessage(
2212 id,
2213 data,
2214 localFlags,
2215 type);
2216 if (type == NewMessageType::Unread) {
2217 CheckForSwitchInlineButton(result);
2218 }
2219 return result;
2220 }
2221
unreadBadge() const2222 int Session::unreadBadge() const {
2223 return computeUnreadBadge(_chatsList.unreadState());
2224 }
2225
unreadBadgeMuted() const2226 bool Session::unreadBadgeMuted() const {
2227 return computeUnreadBadgeMuted(_chatsList.unreadState());
2228 }
2229
unreadBadgeIgnoreOne(const Dialogs::Key & key) const2230 int Session::unreadBadgeIgnoreOne(const Dialogs::Key &key) const {
2231 const auto remove = (key && key.entry()->inChatList())
2232 ? key.entry()->chatListUnreadState()
2233 : Dialogs::UnreadState();
2234 return computeUnreadBadge(_chatsList.unreadState() - remove);
2235 }
2236
unreadBadgeMutedIgnoreOne(const Dialogs::Key & key) const2237 bool Session::unreadBadgeMutedIgnoreOne(const Dialogs::Key &key) const {
2238 if (!Core::App().settings().includeMutedCounter()) {
2239 return false;
2240 }
2241 const auto remove = (key && key.entry()->inChatList())
2242 ? key.entry()->chatListUnreadState()
2243 : Dialogs::UnreadState();
2244 return computeUnreadBadgeMuted(_chatsList.unreadState() - remove);
2245 }
2246
unreadOnlyMutedBadge() const2247 int Session::unreadOnlyMutedBadge() const {
2248 const auto state = _chatsList.unreadState();
2249 return Core::App().settings().countUnreadMessages()
2250 ? state.messagesMuted
2251 : state.chatsMuted;
2252 }
2253
unreadBadgeChanges() const2254 rpl::producer<> Session::unreadBadgeChanges() const {
2255 return _unreadBadgeChanges.events();
2256 }
2257
notifyUnreadBadgeChanged()2258 void Session::notifyUnreadBadgeChanged() {
2259 _unreadBadgeChanges.fire({});
2260 }
2261
countUnreadRepliesLocally(not_null<HistoryItem * > root,MsgId afterId) const2262 std::optional<int> Session::countUnreadRepliesLocally(
2263 not_null<HistoryItem*> root,
2264 MsgId afterId) const {
2265 auto result = std::optional<int>();
2266 _unreadRepliesCountRequests.fire({
2267 .root = root,
2268 .afterId = afterId,
2269 .result = &result,
2270 });
2271 return result;
2272 }
2273
unreadRepliesCountRequests() const2274 auto Session::unreadRepliesCountRequests() const
2275 -> rpl::producer<UnreadRepliesCountRequest> {
2276 return _unreadRepliesCountRequests.events();
2277 }
2278
computeUnreadBadge(const Dialogs::UnreadState & state) const2279 int Session::computeUnreadBadge(const Dialogs::UnreadState &state) const {
2280 const auto all = Core::App().settings().includeMutedCounter();
2281 return std::max(state.marks - (all ? 0 : state.marksMuted), 0)
2282 + (Core::App().settings().countUnreadMessages()
2283 ? std::max(state.messages - (all ? 0 : state.messagesMuted), 0)
2284 : std::max(state.chats - (all ? 0 : state.chatsMuted), 0));
2285 }
2286
computeUnreadBadgeMuted(const Dialogs::UnreadState & state) const2287 bool Session::computeUnreadBadgeMuted(
2288 const Dialogs::UnreadState &state) const {
2289 if (!Core::App().settings().includeMutedCounter()) {
2290 return false;
2291 }
2292 return (state.marksMuted >= state.marks)
2293 && (Core::App().settings().countUnreadMessages()
2294 ? (state.messagesMuted >= state.messages)
2295 : (state.chatsMuted >= state.chats));
2296 }
2297
selfDestructIn(not_null<HistoryItem * > item,crl::time delay)2298 void Session::selfDestructIn(not_null<HistoryItem*> item, crl::time delay) {
2299 _selfDestructItems.push_back(item->fullId());
2300 if (!_selfDestructTimer.isActive()
2301 || _selfDestructTimer.remainingTime() > delay) {
2302 _selfDestructTimer.callOnce(delay);
2303 }
2304 }
2305
checkSelfDestructItems()2306 void Session::checkSelfDestructItems() {
2307 const auto now = crl::now();
2308 auto nextDestructIn = crl::time(0);
2309 for (auto i = _selfDestructItems.begin(); i != _selfDestructItems.cend();) {
2310 if (const auto item = message(*i)) {
2311 if (auto destructIn = item->getSelfDestructIn(now)) {
2312 if (nextDestructIn > 0) {
2313 accumulate_min(nextDestructIn, destructIn);
2314 } else {
2315 nextDestructIn = destructIn;
2316 }
2317 ++i;
2318 } else {
2319 i = _selfDestructItems.erase(i);
2320 }
2321 } else {
2322 i = _selfDestructItems.erase(i);
2323 }
2324 }
2325 if (nextDestructIn > 0) {
2326 _selfDestructTimer.callOnce(nextDestructIn);
2327 }
2328 }
2329
photo(PhotoId id)2330 not_null<PhotoData*> Session::photo(PhotoId id) {
2331 auto i = _photos.find(id);
2332 if (i == _photos.end()) {
2333 i = _photos.emplace(
2334 id,
2335 std::make_unique<PhotoData>(this, id)).first;
2336 }
2337 return i->second.get();
2338 }
2339
processPhoto(const MTPPhoto & data)2340 not_null<PhotoData*> Session::processPhoto(const MTPPhoto &data) {
2341 return data.match([&](const MTPDphoto &data) {
2342 return processPhoto(data);
2343 }, [&](const MTPDphotoEmpty &data) {
2344 return photo(data.vid().v);
2345 });
2346 }
2347
processPhoto(const MTPDphoto & data)2348 not_null<PhotoData*> Session::processPhoto(const MTPDphoto &data) {
2349 const auto result = photo(data.vid().v);
2350 photoApplyFields(result, data);
2351 return result;
2352 }
2353
processPhoto(const MTPPhoto & data,const PreparedPhotoThumbs & thumbs)2354 not_null<PhotoData*> Session::processPhoto(
2355 const MTPPhoto &data,
2356 const PreparedPhotoThumbs &thumbs) {
2357 Expects(!thumbs.empty());
2358
2359 const auto find = [&](const QByteArray &levels) {
2360 const auto kInvalidIndex = int(levels.size());
2361 const auto level = [&](const auto &pair) {
2362 const auto letter = pair.first;
2363 const auto index = levels.indexOf(letter);
2364 return (index >= 0) ? index : kInvalidIndex;
2365 };
2366 const auto result = ranges::max_element(
2367 thumbs,
2368 std::greater<>(),
2369 level);
2370 return (level(*result) == kInvalidIndex) ? thumbs.end() : result;
2371 };
2372 const auto image = [&](const QByteArray &levels) {
2373 const auto i = find(levels);
2374 return (i == thumbs.end())
2375 ? ImageWithLocation()
2376 : Images::FromImageInMemory(
2377 i->second.image,
2378 "JPG",
2379 i->second.bytes);
2380 };
2381 const auto small = image(SmallLevels);
2382 const auto thumbnail = image(ThumbnailLevels);
2383 const auto large = image(LargeLevels);
2384 return data.match([&](const MTPDphoto &data) {
2385 return photo(
2386 data.vid().v,
2387 data.vaccess_hash().v,
2388 data.vfile_reference().v,
2389 data.vdate().v,
2390 data.vdc_id().v,
2391 data.is_has_stickers(),
2392 QByteArray(),
2393 small,
2394 thumbnail,
2395 large,
2396 ImageWithLocation{},
2397 crl::time(0));
2398 }, [&](const MTPDphotoEmpty &data) {
2399 return photo(data.vid().v);
2400 });
2401 }
2402
photo(PhotoId id,const uint64 & access,const QByteArray & fileReference,TimeId date,int32 dc,bool hasStickers,const QByteArray & inlineThumbnailBytes,const ImageWithLocation & small,const ImageWithLocation & thumbnail,const ImageWithLocation & large,const ImageWithLocation & video,crl::time videoStartTime)2403 not_null<PhotoData*> Session::photo(
2404 PhotoId id,
2405 const uint64 &access,
2406 const QByteArray &fileReference,
2407 TimeId date,
2408 int32 dc,
2409 bool hasStickers,
2410 const QByteArray &inlineThumbnailBytes,
2411 const ImageWithLocation &small,
2412 const ImageWithLocation &thumbnail,
2413 const ImageWithLocation &large,
2414 const ImageWithLocation &video,
2415 crl::time videoStartTime) {
2416 const auto result = photo(id);
2417 photoApplyFields(
2418 result,
2419 access,
2420 fileReference,
2421 date,
2422 dc,
2423 hasStickers,
2424 inlineThumbnailBytes,
2425 small,
2426 thumbnail,
2427 large,
2428 video,
2429 videoStartTime);
2430 return result;
2431 }
2432
photoConvert(not_null<PhotoData * > original,const MTPPhoto & data)2433 void Session::photoConvert(
2434 not_null<PhotoData*> original,
2435 const MTPPhoto &data) {
2436 const auto id = data.match([](const auto &data) {
2437 return data.vid().v;
2438 });
2439 const auto idChanged = (original->id != id);
2440 if (idChanged) {
2441 auto i = _photos.find(id);
2442 if (i == _photos.end()) {
2443 const auto j = _photos.find(original->id);
2444 Assert(j != _photos.end());
2445 auto owned = std::move(j->second);
2446 _photos.erase(j);
2447 i = _photos.emplace(id, std::move(owned)).first;
2448 }
2449
2450 original->id = id;
2451 original->uploadingData = nullptr;
2452
2453 if (i->second.get() != original) {
2454 photoApplyFields(i->second.get(), data);
2455 }
2456 }
2457 photoApplyFields(original, data);
2458 }
2459
photoFromWeb(const MTPWebDocument & data,const ImageLocation & thumbnailLocation)2460 PhotoData *Session::photoFromWeb(
2461 const MTPWebDocument &data,
2462 const ImageLocation &thumbnailLocation) {
2463 const auto large = Images::FromWebDocument(data);
2464 if (!large.valid()) {
2465 return nullptr;
2466 }
2467 return photo(
2468 base::RandomValue<PhotoId>(),
2469 uint64(0),
2470 QByteArray(),
2471 base::unixtime::now(),
2472 0,
2473 false,
2474 QByteArray(),
2475 ImageWithLocation{},
2476 ImageWithLocation{ .location = thumbnailLocation },
2477 ImageWithLocation{ .location = large },
2478 ImageWithLocation{},
2479 crl::time(0));
2480 }
2481
photoApplyFields(not_null<PhotoData * > photo,const MTPPhoto & data)2482 void Session::photoApplyFields(
2483 not_null<PhotoData*> photo,
2484 const MTPPhoto &data) {
2485 if (data.type() == mtpc_photo) {
2486 photoApplyFields(photo, data.c_photo());
2487 }
2488 }
2489
photoApplyFields(not_null<PhotoData * > photo,const MTPDphoto & data)2490 void Session::photoApplyFields(
2491 not_null<PhotoData*> photo,
2492 const MTPDphoto &data) {
2493 const auto &sizes = data.vsizes().v;
2494 const auto progressive = [&] {
2495 const auto area = [&](const MTPPhotoSize &size) {
2496 return size.match([](const MTPDphotoSizeProgressive &data) {
2497 return data.vw().v * data.vh().v;
2498 }, [](const auto &) {
2499 return 0;
2500 });
2501 };
2502 const auto found = ranges::max_element(sizes, std::less<>(), area);
2503 return (found == sizes.end()
2504 || found->type() != mtpc_photoSizeProgressive)
2505 ? sizes.end()
2506 : found;
2507 }();
2508 const auto find = [&](const QByteArray &levels) {
2509 const auto kInvalidIndex = int(levels.size());
2510 const auto level = [&](const MTPPhotoSize &size) {
2511 const auto letter = size.match([](const MTPDphotoSizeEmpty &) {
2512 return char(0);
2513 }, [](const auto &size) {
2514 return size.vtype().v.isEmpty() ? char(0) : size.vtype().v[0];
2515 });
2516 const auto index = levels.indexOf(letter);
2517 return (index >= 0) ? index : kInvalidIndex;
2518 };
2519 const auto result = ranges::max_element(
2520 sizes,
2521 std::greater<>(),
2522 level);
2523 return (level(*result) == kInvalidIndex) ? sizes.end() : result;
2524 };
2525 const auto image = [&](const QByteArray &levels) {
2526 const auto i = find(levels);
2527 return (i == sizes.end())
2528 ? ImageWithLocation()
2529 : Images::FromPhotoSize(_session, data, *i);
2530 };
2531 const auto findVideoSize = [&]() -> std::optional<MTPVideoSize> {
2532 const auto sizes = data.vvideo_sizes();
2533 if (!sizes || sizes->v.isEmpty()) {
2534 return std::nullopt;
2535 }
2536 const auto area = [](const MTPVideoSize &size) {
2537 return size.match([](const MTPDvideoSize &data) {
2538 return data.vsize().v ? (data.vw().v * data.vh().v) : 0;
2539 });
2540 };
2541 const auto result = *ranges::max_element(
2542 sizes->v,
2543 std::greater<>(),
2544 area);
2545 return (area(result) > 0) ? std::make_optional(result) : std::nullopt;
2546 };
2547 const auto useProgressive = (progressive != sizes.end());
2548 const auto large = useProgressive
2549 ? Images::FromPhotoSize(_session, data, *progressive)
2550 : image(LargeLevels);
2551 if (large.location.valid()) {
2552 const auto video = findVideoSize();
2553 photoApplyFields(
2554 photo,
2555 data.vaccess_hash().v,
2556 data.vfile_reference().v,
2557 data.vdate().v,
2558 data.vdc_id().v,
2559 data.is_has_stickers(),
2560 FindPhotoInlineThumbnail(data),
2561 (useProgressive
2562 ? ImageWithLocation()
2563 : image(SmallLevels)),
2564 (useProgressive
2565 ? Images::FromProgressiveSize(_session, *progressive, 1)
2566 : image(ThumbnailLevels)),
2567 large,
2568 (video
2569 ? Images::FromVideoSize(_session, data, *video)
2570 : ImageWithLocation()),
2571 (video
2572 ? VideoStartTime(
2573 *video->match([](const auto &data) { return &data; }))
2574 : 0));
2575 }
2576 }
2577
photoApplyFields(not_null<PhotoData * > photo,const uint64 & access,const QByteArray & fileReference,TimeId date,int32 dc,bool hasStickers,const QByteArray & inlineThumbnailBytes,const ImageWithLocation & small,const ImageWithLocation & thumbnail,const ImageWithLocation & large,const ImageWithLocation & video,crl::time videoStartTime)2578 void Session::photoApplyFields(
2579 not_null<PhotoData*> photo,
2580 const uint64 &access,
2581 const QByteArray &fileReference,
2582 TimeId date,
2583 int32 dc,
2584 bool hasStickers,
2585 const QByteArray &inlineThumbnailBytes,
2586 const ImageWithLocation &small,
2587 const ImageWithLocation &thumbnail,
2588 const ImageWithLocation &large,
2589 const ImageWithLocation &video,
2590 crl::time videoStartTime) {
2591 if (!date) {
2592 return;
2593 }
2594 photo->setRemoteLocation(dc, access, fileReference);
2595 photo->date = date;
2596 photo->setHasAttachedStickers(hasStickers);
2597 photo->updateImages(
2598 inlineThumbnailBytes,
2599 small,
2600 thumbnail,
2601 large,
2602 video,
2603 videoStartTime);
2604 }
2605
document(DocumentId id)2606 not_null<DocumentData*> Session::document(DocumentId id) {
2607 auto i = _documents.find(id);
2608 if (i == _documents.cend()) {
2609 i = _documents.emplace(
2610 id,
2611 std::make_unique<DocumentData>(this, id)).first;
2612 }
2613 return i->second.get();
2614 }
2615
processDocument(const MTPDocument & data)2616 not_null<DocumentData*> Session::processDocument(const MTPDocument &data) {
2617 return data.match([&](const MTPDdocument &data) {
2618 return processDocument(data);
2619 }, [&](const MTPDdocumentEmpty &data) {
2620 return document(data.vid().v);
2621 });
2622 }
2623
processDocument(const MTPDdocument & data)2624 not_null<DocumentData*> Session::processDocument(const MTPDdocument &data) {
2625 const auto result = document(data.vid().v);
2626 documentApplyFields(result, data);
2627 return result;
2628 }
2629
processDocument(const MTPdocument & data,const ImageWithLocation & thumbnail)2630 not_null<DocumentData*> Session::processDocument(
2631 const MTPdocument &data,
2632 const ImageWithLocation &thumbnail) {
2633 return data.match([&](const MTPDdocument &data) {
2634 return document(
2635 data.vid().v,
2636 data.vaccess_hash().v,
2637 data.vfile_reference().v,
2638 data.vdate().v,
2639 data.vattributes().v,
2640 qs(data.vmime_type()),
2641 InlineImageLocation(),
2642 thumbnail,
2643 ImageWithLocation(),
2644 data.vdc_id().v,
2645 data.vsize().v);
2646 }, [&](const MTPDdocumentEmpty &data) {
2647 return document(data.vid().v);
2648 });
2649 }
2650
document(DocumentId id,const uint64 & access,const QByteArray & fileReference,TimeId date,const QVector<MTPDocumentAttribute> & attributes,const QString & mime,const InlineImageLocation & inlineThumbnail,const ImageWithLocation & thumbnail,const ImageWithLocation & videoThumbnail,int32 dc,int32 size)2651 not_null<DocumentData*> Session::document(
2652 DocumentId id,
2653 const uint64 &access,
2654 const QByteArray &fileReference,
2655 TimeId date,
2656 const QVector<MTPDocumentAttribute> &attributes,
2657 const QString &mime,
2658 const InlineImageLocation &inlineThumbnail,
2659 const ImageWithLocation &thumbnail,
2660 const ImageWithLocation &videoThumbnail,
2661 int32 dc,
2662 int32 size) {
2663 const auto result = document(id);
2664 documentApplyFields(
2665 result,
2666 access,
2667 fileReference,
2668 date,
2669 attributes,
2670 mime,
2671 inlineThumbnail,
2672 thumbnail,
2673 videoThumbnail,
2674 dc,
2675 size);
2676 return result;
2677 }
2678
documentConvert(not_null<DocumentData * > original,const MTPDocument & data)2679 void Session::documentConvert(
2680 not_null<DocumentData*> original,
2681 const MTPDocument &data) {
2682 const auto id = data.match([](const auto &data) {
2683 return data.vid().v;
2684 });
2685 const auto oldCacheKey = original->cacheKey();
2686 const auto oldGoodKey = original->goodThumbnailCacheKey();
2687 const auto idChanged = (original->id != id);
2688 if (idChanged) {
2689 auto i = _documents.find(id);
2690 if (i == _documents.end()) {
2691 const auto j = _documents.find(original->id);
2692 Assert(j != _documents.end());
2693 auto owned = std::move(j->second);
2694 _documents.erase(j);
2695 i = _documents.emplace(id, std::move(owned)).first;
2696 }
2697
2698 original->id = id;
2699 original->status = FileReady;
2700 original->uploadingData = nullptr;
2701
2702 if (i->second.get() != original) {
2703 documentApplyFields(i->second.get(), data);
2704 }
2705 }
2706 documentApplyFields(original, data);
2707 if (idChanged) {
2708 cache().moveIfEmpty(oldCacheKey, original->cacheKey());
2709 cache().moveIfEmpty(oldGoodKey, original->goodThumbnailCacheKey());
2710 if (stickers().savedGifs().indexOf(original) >= 0) {
2711 _session->local().writeSavedGifs();
2712 }
2713 }
2714 }
2715
documentFromWeb(const MTPWebDocument & data,const ImageLocation & thumbnailLocation,const ImageLocation & videoThumbnailLocation)2716 DocumentData *Session::documentFromWeb(
2717 const MTPWebDocument &data,
2718 const ImageLocation &thumbnailLocation,
2719 const ImageLocation &videoThumbnailLocation) {
2720 return data.match([&](const auto &data) {
2721 return documentFromWeb(
2722 data,
2723 thumbnailLocation,
2724 videoThumbnailLocation);
2725 });
2726 }
2727
documentFromWeb(const MTPDwebDocument & data,const ImageLocation & thumbnailLocation,const ImageLocation & videoThumbnailLocation)2728 DocumentData *Session::documentFromWeb(
2729 const MTPDwebDocument &data,
2730 const ImageLocation &thumbnailLocation,
2731 const ImageLocation &videoThumbnailLocation) {
2732 const auto result = document(
2733 base::RandomValue<DocumentId>(),
2734 uint64(0),
2735 QByteArray(),
2736 base::unixtime::now(),
2737 data.vattributes().v,
2738 data.vmime_type().v,
2739 InlineImageLocation(),
2740 ImageWithLocation{ .location = thumbnailLocation },
2741 ImageWithLocation{ .location = videoThumbnailLocation },
2742 session().mainDcId(),
2743 int32(0)); // data.vsize().v
2744 result->setWebLocation(WebFileLocation(
2745 data.vurl().v,
2746 data.vaccess_hash().v));
2747 return result;
2748 }
2749
documentFromWeb(const MTPDwebDocumentNoProxy & data,const ImageLocation & thumbnailLocation,const ImageLocation & videoThumbnailLocation)2750 DocumentData *Session::documentFromWeb(
2751 const MTPDwebDocumentNoProxy &data,
2752 const ImageLocation &thumbnailLocation,
2753 const ImageLocation &videoThumbnailLocation) {
2754 const auto result = document(
2755 base::RandomValue<DocumentId>(),
2756 uint64(0),
2757 QByteArray(),
2758 base::unixtime::now(),
2759 data.vattributes().v,
2760 data.vmime_type().v,
2761 InlineImageLocation(),
2762 ImageWithLocation{ .location = thumbnailLocation },
2763 ImageWithLocation{ .location = videoThumbnailLocation },
2764 session().mainDcId(),
2765 int32(0)); // data.vsize().v
2766 result->setContentUrl(qs(data.vurl()));
2767 return result;
2768 }
2769
documentApplyFields(not_null<DocumentData * > document,const MTPDocument & data)2770 void Session::documentApplyFields(
2771 not_null<DocumentData*> document,
2772 const MTPDocument &data) {
2773 if (data.type() == mtpc_document) {
2774 documentApplyFields(document, data.c_document());
2775 }
2776 }
2777
documentApplyFields(not_null<DocumentData * > document,const MTPDdocument & data)2778 void Session::documentApplyFields(
2779 not_null<DocumentData*> document,
2780 const MTPDdocument &data) {
2781 const auto inlineThumbnail = FindDocumentInlineThumbnail(data);
2782 const auto thumbnailSize = FindDocumentThumbnail(data);
2783 const auto videoThumbnailSize = FindDocumentVideoThumbnail(data);
2784 const auto prepared = Images::FromPhotoSize(
2785 _session,
2786 data,
2787 thumbnailSize);
2788 const auto videoThumbnail = videoThumbnailSize
2789 ? Images::FromVideoSize(_session, data, *videoThumbnailSize)
2790 : ImageWithLocation();
2791 documentApplyFields(
2792 document,
2793 data.vaccess_hash().v,
2794 data.vfile_reference().v,
2795 data.vdate().v,
2796 data.vattributes().v,
2797 qs(data.vmime_type()),
2798 inlineThumbnail,
2799 prepared,
2800 videoThumbnail,
2801 data.vdc_id().v,
2802 data.vsize().v);
2803 }
2804
documentApplyFields(not_null<DocumentData * > document,const uint64 & access,const QByteArray & fileReference,TimeId date,const QVector<MTPDocumentAttribute> & attributes,const QString & mime,const InlineImageLocation & inlineThumbnail,const ImageWithLocation & thumbnail,const ImageWithLocation & videoThumbnail,int32 dc,int32 size)2805 void Session::documentApplyFields(
2806 not_null<DocumentData*> document,
2807 const uint64 &access,
2808 const QByteArray &fileReference,
2809 TimeId date,
2810 const QVector<MTPDocumentAttribute> &attributes,
2811 const QString &mime,
2812 const InlineImageLocation &inlineThumbnail,
2813 const ImageWithLocation &thumbnail,
2814 const ImageWithLocation &videoThumbnail,
2815 int32 dc,
2816 int32 size) {
2817 if (!date) {
2818 return;
2819 }
2820 document->date = date;
2821 document->setMimeString(mime);
2822 document->updateThumbnails(
2823 inlineThumbnail,
2824 thumbnail,
2825 videoThumbnail);
2826 document->size = size;
2827 document->setattributes(attributes);
2828
2829 // Uses 'type' that is computed from attributes.
2830 document->recountIsImage();
2831 if (dc != 0 && access != 0) {
2832 document->setRemoteLocation(dc, access, fileReference);
2833 }
2834 }
2835
webpage(WebPageId id)2836 not_null<WebPageData*> Session::webpage(WebPageId id) {
2837 auto i = _webpages.find(id);
2838 if (i == _webpages.cend()) {
2839 i = _webpages.emplace(
2840 id,
2841 std::make_unique<WebPageData>(this, id)).first;
2842 }
2843 return i->second.get();
2844 }
2845
processWebpage(const MTPWebPage & data)2846 not_null<WebPageData*> Session::processWebpage(const MTPWebPage &data) {
2847 switch (data.type()) {
2848 case mtpc_webPage:
2849 return processWebpage(data.c_webPage());
2850 case mtpc_webPageEmpty: {
2851 const auto result = webpage(data.c_webPageEmpty().vid().v);
2852 if (result->pendingTill > 0) {
2853 result->pendingTill = -1; // failed
2854 notifyWebPageUpdateDelayed(result);
2855 }
2856 return result;
2857 } break;
2858 case mtpc_webPagePending:
2859 return processWebpage(data.c_webPagePending());
2860 case mtpc_webPageNotModified:
2861 LOG(("API Error: "
2862 "webPageNotModified is unexpected in Session::webpage()."));
2863 return webpage(0);
2864 }
2865 Unexpected("Type in Session::webpage().");
2866 }
2867
processWebpage(const MTPDwebPage & data)2868 not_null<WebPageData*> Session::processWebpage(const MTPDwebPage &data) {
2869 const auto result = webpage(data.vid().v);
2870 webpageApplyFields(result, data);
2871 return result;
2872 }
2873
processWebpage(const MTPDwebPagePending & data)2874 not_null<WebPageData*> Session::processWebpage(const MTPDwebPagePending &data) {
2875 constexpr auto kDefaultPendingTimeout = 60;
2876 const auto result = webpage(data.vid().v);
2877 webpageApplyFields(
2878 result,
2879 WebPageType::Article,
2880 QString(),
2881 QString(),
2882 QString(),
2883 QString(),
2884 TextWithEntities(),
2885 nullptr,
2886 nullptr,
2887 WebPageCollage(),
2888 0,
2889 QString(),
2890 data.vdate().v
2891 ? data.vdate().v
2892 : (base::unixtime::now() + kDefaultPendingTimeout));
2893 return result;
2894 }
2895
webpage(WebPageId id,const QString & siteName,const TextWithEntities & content)2896 not_null<WebPageData*> Session::webpage(
2897 WebPageId id,
2898 const QString &siteName,
2899 const TextWithEntities &content) {
2900 return webpage(
2901 id,
2902 WebPageType::Article,
2903 QString(),
2904 QString(),
2905 siteName,
2906 QString(),
2907 content,
2908 nullptr,
2909 nullptr,
2910 WebPageCollage(),
2911 0,
2912 QString(),
2913 TimeId(0));
2914 }
2915
webpage(WebPageId id,WebPageType type,const QString & url,const QString & displayUrl,const QString & siteName,const QString & title,const TextWithEntities & description,PhotoData * photo,DocumentData * document,WebPageCollage && collage,int duration,const QString & author,TimeId pendingTill)2916 not_null<WebPageData*> Session::webpage(
2917 WebPageId id,
2918 WebPageType type,
2919 const QString &url,
2920 const QString &displayUrl,
2921 const QString &siteName,
2922 const QString &title,
2923 const TextWithEntities &description,
2924 PhotoData *photo,
2925 DocumentData *document,
2926 WebPageCollage &&collage,
2927 int duration,
2928 const QString &author,
2929 TimeId pendingTill) {
2930 const auto result = webpage(id);
2931 webpageApplyFields(
2932 result,
2933 type,
2934 url,
2935 displayUrl,
2936 siteName,
2937 title,
2938 description,
2939 photo,
2940 document,
2941 std::move(collage),
2942 duration,
2943 author,
2944 pendingTill);
2945 return result;
2946 }
2947
webpageApplyFields(not_null<WebPageData * > page,const MTPDwebPage & data)2948 void Session::webpageApplyFields(
2949 not_null<WebPageData*> page,
2950 const MTPDwebPage &data) {
2951 auto description = TextWithEntities {
2952 TextUtilities::Clean(qs(data.vdescription().value_or_empty()))
2953 };
2954 const auto siteName = qs(data.vsite_name().value_or_empty());
2955 auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
2956 if (siteName == qstr("Twitter") || siteName == qstr("Instagram")) {
2957 parseFlags |= TextParseHashtags | TextParseMentions;
2958 }
2959 TextUtilities::ParseEntities(description, parseFlags);
2960 const auto pendingTill = TimeId(0);
2961 const auto photo = data.vphoto();
2962 const auto document = data.vdocument();
2963 const auto lookupInAttribute = [&](
2964 const MTPDwebPageAttributeTheme &data) -> DocumentData* {
2965 if (const auto documents = data.vdocuments()) {
2966 for (const auto &document : documents->v) {
2967 const auto processed = processDocument(document);
2968 if (processed->isTheme()) {
2969 return processed;
2970 }
2971 }
2972 }
2973 return nullptr;
2974 };
2975 const auto lookupThemeDocument = [&]() -> DocumentData* {
2976 if (const auto attributes = data.vattributes()) {
2977 for (const auto &attribute : attributes->v) {
2978 const auto result = attribute.match([&](
2979 const MTPDwebPageAttributeTheme &data) {
2980 return lookupInAttribute(data);
2981 });
2982 if (result) {
2983 return result;
2984 }
2985 }
2986 }
2987 return nullptr;
2988 };
2989 webpageApplyFields(
2990 page,
2991 ParseWebPageType(data),
2992 qs(data.vurl()),
2993 qs(data.vdisplay_url()),
2994 siteName,
2995 qs(data.vtitle().value_or_empty()),
2996 description,
2997 photo ? processPhoto(*photo).get() : nullptr,
2998 document ? processDocument(*document).get() : lookupThemeDocument(),
2999 WebPageCollage(this, data),
3000 data.vduration().value_or_empty(),
3001 qs(data.vauthor().value_or_empty()),
3002 pendingTill);
3003 }
3004
webpageApplyFields(not_null<WebPageData * > page,WebPageType type,const QString & url,const QString & displayUrl,const QString & siteName,const QString & title,const TextWithEntities & description,PhotoData * photo,DocumentData * document,WebPageCollage && collage,int duration,const QString & author,TimeId pendingTill)3005 void Session::webpageApplyFields(
3006 not_null<WebPageData*> page,
3007 WebPageType type,
3008 const QString &url,
3009 const QString &displayUrl,
3010 const QString &siteName,
3011 const QString &title,
3012 const TextWithEntities &description,
3013 PhotoData *photo,
3014 DocumentData *document,
3015 WebPageCollage &&collage,
3016 int duration,
3017 const QString &author,
3018 TimeId pendingTill) {
3019 const auto requestPending = (!page->pendingTill && pendingTill > 0);
3020 const auto changed = page->applyChanges(
3021 type,
3022 url,
3023 displayUrl,
3024 siteName,
3025 title,
3026 description,
3027 photo,
3028 document,
3029 std::move(collage),
3030 duration,
3031 author,
3032 pendingTill);
3033 if (requestPending) {
3034 _session->api().requestWebPageDelayed(page);
3035 }
3036 if (changed) {
3037 notifyWebPageUpdateDelayed(page);
3038 }
3039 }
3040
game(GameId id)3041 not_null<GameData*> Session::game(GameId id) {
3042 auto i = _games.find(id);
3043 if (i == _games.cend()) {
3044 i = _games.emplace(id, std::make_unique<GameData>(this, id)).first;
3045 }
3046 return i->second.get();
3047 }
3048
processGame(const MTPDgame & data)3049 not_null<GameData*> Session::processGame(const MTPDgame &data) {
3050 const auto result = game(data.vid().v);
3051 gameApplyFields(result, data);
3052 return result;
3053 }
3054
game(GameId id,const uint64 & accessHash,const QString & shortName,const QString & title,const QString & description,PhotoData * photo,DocumentData * document)3055 not_null<GameData*> Session::game(
3056 GameId id,
3057 const uint64 &accessHash,
3058 const QString &shortName,
3059 const QString &title,
3060 const QString &description,
3061 PhotoData *photo,
3062 DocumentData *document) {
3063 const auto result = game(id);
3064 gameApplyFields(
3065 result,
3066 accessHash,
3067 shortName,
3068 title,
3069 description,
3070 photo,
3071 document);
3072 return result;
3073 }
3074
gameConvert(not_null<GameData * > original,const MTPGame & data)3075 void Session::gameConvert(
3076 not_null<GameData*> original,
3077 const MTPGame &data) {
3078 Expects(data.type() == mtpc_game);
3079
3080 const auto id = data.c_game().vid().v;
3081 if (original->id != id) {
3082 auto i = _games.find(id);
3083 if (i == _games.end()) {
3084 const auto j = _games.find(original->id);
3085 Assert(j != _games.end());
3086 auto owned = std::move(j->second);
3087 _games.erase(j);
3088 i = _games.emplace(id, std::move(owned)).first;
3089 }
3090
3091 original->id = id;
3092 original->accessHash = 0;
3093
3094 if (i->second.get() != original) {
3095 gameApplyFields(i->second.get(), data.c_game());
3096 }
3097 }
3098 gameApplyFields(original, data.c_game());
3099 }
3100
gameApplyFields(not_null<GameData * > game,const MTPDgame & data)3101 void Session::gameApplyFields(
3102 not_null<GameData*> game,
3103 const MTPDgame &data) {
3104 const auto document = data.vdocument();
3105 gameApplyFields(
3106 game,
3107 data.vaccess_hash().v,
3108 qs(data.vshort_name()),
3109 qs(data.vtitle()),
3110 qs(data.vdescription()),
3111 processPhoto(data.vphoto()),
3112 document ? processDocument(*document).get() : nullptr);
3113 }
3114
gameApplyFields(not_null<GameData * > game,const uint64 & accessHash,const QString & shortName,const QString & title,const QString & description,PhotoData * photo,DocumentData * document)3115 void Session::gameApplyFields(
3116 not_null<GameData*> game,
3117 const uint64 &accessHash,
3118 const QString &shortName,
3119 const QString &title,
3120 const QString &description,
3121 PhotoData *photo,
3122 DocumentData *document) {
3123 if (game->accessHash) {
3124 return;
3125 }
3126 game->accessHash = accessHash;
3127 game->shortName = TextUtilities::Clean(shortName);
3128 game->title = TextUtilities::SingleLine(title);
3129 game->description = TextUtilities::Clean(description);
3130 game->photo = photo;
3131 game->document = document;
3132 notifyGameUpdateDelayed(game);
3133 }
3134
poll(PollId id)3135 not_null<PollData*> Session::poll(PollId id) {
3136 auto i = _polls.find(id);
3137 if (i == _polls.cend()) {
3138 i = _polls.emplace(id, std::make_unique<PollData>(this, id)).first;
3139 }
3140 return i->second.get();
3141 }
3142
processPoll(const MTPPoll & data)3143 not_null<PollData*> Session::processPoll(const MTPPoll &data) {
3144 return data.match([&](const MTPDpoll &data) {
3145 const auto id = data.vid().v;
3146 const auto result = poll(id);
3147 const auto changed = result->applyChanges(data);
3148 if (changed) {
3149 notifyPollUpdateDelayed(result);
3150 }
3151 if (result->closeDate > 0 && !result->closed()) {
3152 _pollsClosings.emplace(result->closeDate, result);
3153 checkPollsClosings();
3154 }
3155 return result;
3156 });
3157 }
3158
processPoll(const MTPDmessageMediaPoll & data)3159 not_null<PollData*> Session::processPoll(const MTPDmessageMediaPoll &data) {
3160 const auto result = processPoll(data.vpoll());
3161 const auto changed = result->applyResults(data.vresults());
3162 if (changed) {
3163 notifyPollUpdateDelayed(result);
3164 }
3165 return result;
3166 }
3167
checkPollsClosings()3168 void Session::checkPollsClosings() {
3169 const auto now = base::unixtime::now();
3170 auto closest = 0;
3171 for (auto i = _pollsClosings.begin(); i != _pollsClosings.end();) {
3172 if (i->first <= now) {
3173 if (i->second->closeByTimer()) {
3174 notifyPollUpdateDelayed(i->second);
3175 }
3176 i = _pollsClosings.erase(i);
3177 } else {
3178 if (!closest) {
3179 closest = i->first;
3180 }
3181 ++i;
3182 }
3183 }
3184 if (closest) {
3185 _pollsClosingTimer.callOnce((closest - now) * crl::time(1000));
3186 } else {
3187 _pollsClosingTimer.cancel();
3188 }
3189 }
3190
applyUpdate(const MTPDupdateMessagePoll & update)3191 void Session::applyUpdate(const MTPDupdateMessagePoll &update) {
3192 const auto updated = [&] {
3193 const auto poll = update.vpoll();
3194 const auto i = _polls.find(update.vpoll_id().v);
3195 return (i == end(_polls))
3196 ? nullptr
3197 : poll
3198 ? processPoll(*poll).get()
3199 : i->second.get();
3200 }();
3201 if (updated && updated->applyResults(update.vresults())) {
3202 notifyPollUpdateDelayed(updated);
3203 }
3204 }
3205
applyUpdate(const MTPDupdateChatParticipants & update)3206 void Session::applyUpdate(const MTPDupdateChatParticipants &update) {
3207 const auto chatId = update.vparticipants().match([](const auto &update) {
3208 return update.vchat_id().v;
3209 });
3210 if (const auto chat = chatLoaded(chatId)) {
3211 ApplyChatUpdate(chat, update);
3212 for (const auto &user : chat->participants) {
3213 if (user->isBot() && !user->botInfo->inited) {
3214 _session->api().requestFullPeer(user);
3215 }
3216 }
3217 }
3218 }
3219
applyUpdate(const MTPDupdateChatParticipantAdd & update)3220 void Session::applyUpdate(const MTPDupdateChatParticipantAdd &update) {
3221 if (const auto chat = chatLoaded(update.vchat_id().v)) {
3222 ApplyChatUpdate(chat, update);
3223 }
3224 }
3225
applyUpdate(const MTPDupdateChatParticipantDelete & update)3226 void Session::applyUpdate(const MTPDupdateChatParticipantDelete &update) {
3227 if (const auto chat = chatLoaded(update.vchat_id().v)) {
3228 ApplyChatUpdate(chat, update);
3229 }
3230 }
3231
applyUpdate(const MTPDupdateChatParticipantAdmin & update)3232 void Session::applyUpdate(const MTPDupdateChatParticipantAdmin &update) {
3233 if (const auto chat = chatLoaded(update.vchat_id().v)) {
3234 ApplyChatUpdate(chat, update);
3235 }
3236 }
3237
applyUpdate(const MTPDupdateChatDefaultBannedRights & update)3238 void Session::applyUpdate(const MTPDupdateChatDefaultBannedRights &update) {
3239 if (const auto peer = peerLoaded(peerFromMTP(update.vpeer()))) {
3240 if (const auto chat = peer->asChat()) {
3241 ApplyChatUpdate(chat, update);
3242 } else if (const auto channel = peer->asChannel()) {
3243 ApplyChannelUpdate(channel, update);
3244 } else {
3245 LOG(("API Error: "
3246 "User received in updateChatDefaultBannedRights."));
3247 }
3248 }
3249 }
3250
location(const LocationPoint & point)3251 not_null<Data::CloudImage*> Session::location(const LocationPoint &point) {
3252 const auto i = _locations.find(point);
3253 if (i != _locations.cend()) {
3254 return i->second.get();
3255 }
3256 const auto location = Data::ComputeLocation(point);
3257 const auto prepared = ImageWithLocation{
3258 .location = ImageLocation(
3259 { location },
3260 location.width,
3261 location.height)
3262 };
3263 return _locations.emplace(
3264 point,
3265 std::make_unique<Data::CloudImage>(
3266 _session,
3267 prepared)).first->second.get();
3268 }
3269
registerPhotoItem(not_null<const PhotoData * > photo,not_null<HistoryItem * > item)3270 void Session::registerPhotoItem(
3271 not_null<const PhotoData*> photo,
3272 not_null<HistoryItem*> item) {
3273 _photoItems[photo].insert(item);
3274 }
3275
unregisterPhotoItem(not_null<const PhotoData * > photo,not_null<HistoryItem * > item)3276 void Session::unregisterPhotoItem(
3277 not_null<const PhotoData*> photo,
3278 not_null<HistoryItem*> item) {
3279 const auto i = _photoItems.find(photo);
3280 if (i != _photoItems.end()) {
3281 auto &items = i->second;
3282 if (items.remove(item) && items.empty()) {
3283 _photoItems.erase(i);
3284 }
3285 }
3286 }
3287
registerDocumentItem(not_null<const DocumentData * > document,not_null<HistoryItem * > item)3288 void Session::registerDocumentItem(
3289 not_null<const DocumentData*> document,
3290 not_null<HistoryItem*> item) {
3291 _documentItems[document].insert(item);
3292 }
3293
unregisterDocumentItem(not_null<const DocumentData * > document,not_null<HistoryItem * > item)3294 void Session::unregisterDocumentItem(
3295 not_null<const DocumentData*> document,
3296 not_null<HistoryItem*> item) {
3297 const auto i = _documentItems.find(document);
3298 if (i != _documentItems.end()) {
3299 auto &items = i->second;
3300 if (items.remove(item) && items.empty()) {
3301 _documentItems.erase(i);
3302 }
3303 }
3304 }
3305
registerWebPageView(not_null<const WebPageData * > page,not_null<ViewElement * > view)3306 void Session::registerWebPageView(
3307 not_null<const WebPageData*> page,
3308 not_null<ViewElement*> view) {
3309 _webpageViews[page].insert(view);
3310 }
3311
unregisterWebPageView(not_null<const WebPageData * > page,not_null<ViewElement * > view)3312 void Session::unregisterWebPageView(
3313 not_null<const WebPageData*> page,
3314 not_null<ViewElement*> view) {
3315 const auto i = _webpageViews.find(page);
3316 if (i != _webpageViews.end()) {
3317 auto &items = i->second;
3318 if (items.remove(view) && items.empty()) {
3319 _webpageViews.erase(i);
3320 }
3321 }
3322 }
3323
registerWebPageItem(not_null<const WebPageData * > page,not_null<HistoryItem * > item)3324 void Session::registerWebPageItem(
3325 not_null<const WebPageData*> page,
3326 not_null<HistoryItem*> item) {
3327 _webpageItems[page].insert(item);
3328 }
3329
unregisterWebPageItem(not_null<const WebPageData * > page,not_null<HistoryItem * > item)3330 void Session::unregisterWebPageItem(
3331 not_null<const WebPageData*> page,
3332 not_null<HistoryItem*> item) {
3333 const auto i = _webpageItems.find(page);
3334 if (i != _webpageItems.end()) {
3335 auto &items = i->second;
3336 if (items.remove(item) && items.empty()) {
3337 _webpageItems.erase(i);
3338 }
3339 }
3340 }
3341
registerGameView(not_null<const GameData * > game,not_null<ViewElement * > view)3342 void Session::registerGameView(
3343 not_null<const GameData*> game,
3344 not_null<ViewElement*> view) {
3345 _gameViews[game].insert(view);
3346 }
3347
unregisterGameView(not_null<const GameData * > game,not_null<ViewElement * > view)3348 void Session::unregisterGameView(
3349 not_null<const GameData*> game,
3350 not_null<ViewElement*> view) {
3351 const auto i = _gameViews.find(game);
3352 if (i != _gameViews.end()) {
3353 auto &items = i->second;
3354 if (items.remove(view) && items.empty()) {
3355 _gameViews.erase(i);
3356 }
3357 }
3358 }
3359
registerPollView(not_null<const PollData * > poll,not_null<ViewElement * > view)3360 void Session::registerPollView(
3361 not_null<const PollData*> poll,
3362 not_null<ViewElement*> view) {
3363 _pollViews[poll].insert(view);
3364 }
3365
unregisterPollView(not_null<const PollData * > poll,not_null<ViewElement * > view)3366 void Session::unregisterPollView(
3367 not_null<const PollData*> poll,
3368 not_null<ViewElement*> view) {
3369 const auto i = _pollViews.find(poll);
3370 if (i != _pollViews.end()) {
3371 auto &items = i->second;
3372 if (items.remove(view) && items.empty()) {
3373 _pollViews.erase(i);
3374 }
3375 }
3376 }
3377
registerContactView(UserId contactId,not_null<ViewElement * > view)3378 void Session::registerContactView(
3379 UserId contactId,
3380 not_null<ViewElement*> view) {
3381 if (!contactId) {
3382 return;
3383 }
3384 _contactViews[contactId].insert(view);
3385 }
3386
unregisterContactView(UserId contactId,not_null<ViewElement * > view)3387 void Session::unregisterContactView(
3388 UserId contactId,
3389 not_null<ViewElement*> view) {
3390 if (!contactId) {
3391 return;
3392 }
3393 const auto i = _contactViews.find(contactId);
3394 if (i != _contactViews.end()) {
3395 auto &items = i->second;
3396 if (items.remove(view) && items.empty()) {
3397 _contactViews.erase(i);
3398 }
3399 }
3400 }
3401
registerContactItem(UserId contactId,not_null<HistoryItem * > item)3402 void Session::registerContactItem(
3403 UserId contactId,
3404 not_null<HistoryItem*> item) {
3405 if (!contactId) {
3406 return;
3407 }
3408 const auto contact = userLoaded(contactId);
3409 const auto canShare = contact ? contact->canShareThisContact() : false;
3410
3411 _contactItems[contactId].insert(item);
3412
3413 if (contact && canShare != contact->canShareThisContact()) {
3414 session().changes().peerUpdated(
3415 contact,
3416 PeerUpdate::Flag::CanShareContact);
3417 }
3418
3419 if (const auto i = _views.find(item); i != _views.end()) {
3420 for (const auto view : i->second) {
3421 if (const auto media = view->media()) {
3422 media->updateSharedContactUserId(contactId);
3423 }
3424 }
3425 }
3426 }
3427
unregisterContactItem(UserId contactId,not_null<HistoryItem * > item)3428 void Session::unregisterContactItem(
3429 UserId contactId,
3430 not_null<HistoryItem*> item) {
3431 if (!contactId) {
3432 return;
3433 }
3434 const auto contact = userLoaded(contactId);
3435 const auto canShare = contact ? contact->canShareThisContact() : false;
3436
3437 const auto i = _contactItems.find(contactId);
3438 if (i != _contactItems.end()) {
3439 auto &items = i->second;
3440 if (items.remove(item) && items.empty()) {
3441 _contactItems.erase(i);
3442 }
3443 }
3444
3445 if (contact && canShare != contact->canShareThisContact()) {
3446 session().changes().peerUpdated(
3447 contact,
3448 PeerUpdate::Flag::CanShareContact);
3449 }
3450 }
3451
registerCallItem(not_null<HistoryItem * > item)3452 void Session::registerCallItem(not_null<HistoryItem*> item) {
3453 _callItems.emplace(item);
3454 }
3455
unregisterCallItem(not_null<HistoryItem * > item)3456 void Session::unregisterCallItem(not_null<HistoryItem*> item) {
3457 _callItems.erase(item);
3458 }
3459
destroyAllCallItems()3460 void Session::destroyAllCallItems() {
3461 while (!_callItems.empty()) {
3462 (*_callItems.begin())->destroy();
3463 }
3464 }
3465
documentMessageRemoved(not_null<DocumentData * > document)3466 void Session::documentMessageRemoved(not_null<DocumentData*> document) {
3467 if (_documentItems.find(document) != _documentItems.end()) {
3468 return;
3469 }
3470 if (document->loading()) {
3471 document->cancel();
3472 }
3473 }
3474
checkPlayingAnimations()3475 void Session::checkPlayingAnimations() {
3476 auto check = base::flat_set<not_null<ViewElement*>>();
3477 for (const auto &view : _heavyViewParts) {
3478 if (const auto media = view->media()) {
3479 if (const auto document = media->getDocument()) {
3480 if (document->isAnimation() || document->isVideoFile()) {
3481 check.emplace(view);
3482 }
3483 } else if (const auto photo = media->getPhoto()) {
3484 if (photo->hasVideo()) {
3485 check.emplace(view);
3486 }
3487 }
3488 }
3489 }
3490 for (const auto &view : check) {
3491 view->media()->checkAnimation();
3492 }
3493 }
3494
findWebPageItem(not_null<WebPageData * > page) const3495 HistoryItem *Session::findWebPageItem(not_null<WebPageData*> page) const {
3496 const auto i = _webpageItems.find(page);
3497 if (i != _webpageItems.end()) {
3498 for (const auto &item : i->second) {
3499 if (item->isRegular()) {
3500 return item;
3501 }
3502 }
3503 }
3504 return nullptr;
3505 }
3506
findContactPhone(not_null<UserData * > contact) const3507 QString Session::findContactPhone(not_null<UserData*> contact) const {
3508 const auto result = contact->phone();
3509 return result.isEmpty()
3510 ? findContactPhone(peerToUser(contact->id))
3511 : Ui::FormatPhone(result);
3512 }
3513
findContactPhone(UserId contactId) const3514 QString Session::findContactPhone(UserId contactId) const {
3515 const auto i = _contactItems.find(contactId);
3516 if (i != _contactItems.end()) {
3517 if (const auto media = (*begin(i->second))->media()) {
3518 if (const auto contact = media->sharedContact()) {
3519 return contact->phoneNumber;
3520 }
3521 }
3522 }
3523 return QString();
3524 }
3525
hasPendingWebPageGamePollNotification() const3526 bool Session::hasPendingWebPageGamePollNotification() const {
3527 return !_webpagesUpdated.empty()
3528 || !_gamesUpdated.empty()
3529 || !_pollsUpdated.empty();
3530 }
3531
notifyWebPageUpdateDelayed(not_null<WebPageData * > page)3532 void Session::notifyWebPageUpdateDelayed(not_null<WebPageData*> page) {
3533 const auto invoke = !hasPendingWebPageGamePollNotification();
3534 _webpagesUpdated.insert(page);
3535 if (invoke) {
3536 crl::on_main(_session, [=] { sendWebPageGamePollNotifications(); });
3537 }
3538 }
3539
notifyGameUpdateDelayed(not_null<GameData * > game)3540 void Session::notifyGameUpdateDelayed(not_null<GameData*> game) {
3541 const auto invoke = !hasPendingWebPageGamePollNotification();
3542 _gamesUpdated.insert(game);
3543 if (invoke) {
3544 crl::on_main(_session, [=] { sendWebPageGamePollNotifications(); });
3545 }
3546 }
3547
notifyPollUpdateDelayed(not_null<PollData * > poll)3548 void Session::notifyPollUpdateDelayed(not_null<PollData*> poll) {
3549 const auto invoke = !hasPendingWebPageGamePollNotification();
3550 _pollsUpdated.insert(poll);
3551 if (invoke) {
3552 crl::on_main(_session, [=] { sendWebPageGamePollNotifications(); });
3553 }
3554 }
3555
sendWebPageGamePollNotifications()3556 void Session::sendWebPageGamePollNotifications() {
3557 for (const auto &page : base::take(_webpagesUpdated)) {
3558 _webpageUpdates.fire_copy(page);
3559 const auto i = _webpageViews.find(page);
3560 if (i != _webpageViews.end()) {
3561 for (const auto &view : i->second) {
3562 requestViewResize(view);
3563 }
3564 }
3565 }
3566 for (const auto &game : base::take(_gamesUpdated)) {
3567 if (const auto i = _gameViews.find(game); i != _gameViews.end()) {
3568 for (const auto &view : i->second) {
3569 requestViewResize(view);
3570 }
3571 }
3572 }
3573 for (const auto &poll : base::take(_pollsUpdated)) {
3574 if (const auto i = _pollViews.find(poll); i != _pollViews.end()) {
3575 for (const auto &view : i->second) {
3576 requestViewResize(view);
3577 }
3578 }
3579 }
3580 }
3581
webPageUpdates() const3582 rpl::producer<not_null<WebPageData*>> Session::webPageUpdates() const {
3583 return _webpageUpdates.events();
3584 }
3585
channelDifferenceTooLong(not_null<ChannelData * > channel)3586 void Session::channelDifferenceTooLong(not_null<ChannelData*> channel) {
3587 _channelDifferenceTooLong.fire_copy(channel);
3588 }
3589
channelDifferenceTooLong() const3590 rpl::producer<not_null<ChannelData*>> Session::channelDifferenceTooLong() const {
3591 return _channelDifferenceTooLong.events();
3592 }
3593
registerItemView(not_null<ViewElement * > view)3594 void Session::registerItemView(not_null<ViewElement*> view) {
3595 _views[view->data()].push_back(view);
3596 }
3597
unregisterItemView(not_null<ViewElement * > view)3598 void Session::unregisterItemView(not_null<ViewElement*> view) {
3599 Expects(!_heavyViewParts.contains(view));
3600
3601 const auto i = _views.find(view->data());
3602 if (i != end(_views)) {
3603 auto &list = i->second;
3604 list.erase(ranges::remove(list, view), end(list));
3605 if (list.empty()) {
3606 _views.erase(i);
3607 }
3608 }
3609 if (App::hoveredItem() == view) {
3610 App::hoveredItem(nullptr);
3611 }
3612 if (App::pressedItem() == view) {
3613 App::pressedItem(nullptr);
3614 }
3615 if (App::hoveredLinkItem() == view) {
3616 App::hoveredLinkItem(nullptr);
3617 }
3618 if (App::pressedLinkItem() == view) {
3619 App::pressedLinkItem(nullptr);
3620 }
3621 if (App::mousedItem() == view) {
3622 App::mousedItem(nullptr);
3623 }
3624 }
3625
folder(FolderId id)3626 not_null<Folder*> Session::folder(FolderId id) {
3627 if (const auto result = folderLoaded(id)) {
3628 return result;
3629 }
3630 const auto [it, ok] = _folders.emplace(
3631 id,
3632 std::make_unique<Folder>(this, id));
3633 return it->second.get();
3634 }
3635
folderLoaded(FolderId id) const3636 Folder *Session::folderLoaded(FolderId id) const {
3637 const auto it = _folders.find(id);
3638 return (it == end(_folders)) ? nullptr : it->second.get();
3639 }
3640
processFolder(const MTPFolder & data)3641 not_null<Folder*> Session::processFolder(const MTPFolder &data) {
3642 return data.match([&](const MTPDfolder &data) {
3643 return processFolder(data);
3644 });
3645 }
3646
processFolder(const MTPDfolder & data)3647 not_null<Folder*> Session::processFolder(const MTPDfolder &data) {
3648 return folder(data.vid().v);
3649 }
3650
chatsList(Data::Folder * folder)3651 not_null<Dialogs::MainList*> Session::chatsList(Data::Folder *folder) {
3652 return folder ? folder->chatsList().get() : &_chatsList;
3653 }
3654
chatsList(Data::Folder * folder) const3655 not_null<const Dialogs::MainList*> Session::chatsList(
3656 Data::Folder *folder) const {
3657 return folder ? folder->chatsList() : &_chatsList;
3658 }
3659
contactsList()3660 not_null<Dialogs::IndexedList*> Session::contactsList() {
3661 return &_contactsList;
3662 }
3663
contactsNoChatsList()3664 not_null<Dialogs::IndexedList*> Session::contactsNoChatsList() {
3665 return &_contactsNoChatsList;
3666 }
3667
refreshChatListEntry(Dialogs::Key key)3668 void Session::refreshChatListEntry(Dialogs::Key key) {
3669 Expects(key.entry()->folderKnown());
3670
3671 using namespace Dialogs;
3672
3673 const auto entry = key.entry();
3674 const auto history = key.history();
3675 const auto mainList = chatsList(entry->folder());
3676 auto event = ChatListEntryRefresh{ .key = key };
3677 const auto creating = event.existenceChanged = !entry->inChatList();
3678 if (event.existenceChanged) {
3679 const auto mainRow = entry->addToChatList(0, mainList);
3680 _contactsNoChatsList.del(key, mainRow);
3681 } else {
3682 event.moved = entry->adjustByPosInChatList(0, mainList);
3683 }
3684 if (event) {
3685 _chatListEntryRefreshes.fire(std::move(event));
3686 }
3687 if (!history) {
3688 return;
3689 }
3690 for (const auto &filter : _chatsFilters->list()) {
3691 const auto id = filter.id();
3692 const auto filterList = chatsFilters().chatsList(id);
3693 auto event = ChatListEntryRefresh{ .key = key, .filterId = id };
3694 if (filter.contains(history)) {
3695 event.existenceChanged = !entry->inChatList(id);
3696 if (event.existenceChanged) {
3697 entry->addToChatList(id, filterList);
3698 } else {
3699 event.moved = entry->adjustByPosInChatList(id, filterList);
3700 }
3701 } else if (entry->inChatList(id)) {
3702 entry->removeFromChatList(id, filterList);
3703 event.existenceChanged = true;
3704 }
3705 if (event) {
3706 _chatListEntryRefreshes.fire(std::move(event));
3707 }
3708 }
3709
3710 if (creating) {
3711 if (const auto from = history->peer->migrateFrom()) {
3712 if (const auto migrated = historyLoaded(from)) {
3713 removeChatListEntry(migrated);
3714 }
3715 }
3716 }
3717 }
3718
removeChatListEntry(Dialogs::Key key)3719 void Session::removeChatListEntry(Dialogs::Key key) {
3720 using namespace Dialogs;
3721
3722 const auto entry = key.entry();
3723 if (!entry->inChatList()) {
3724 return;
3725 }
3726 Assert(entry->folderKnown());
3727 for (const auto &filter : _chatsFilters->list()) {
3728 const auto id = filter.id();
3729 if (entry->inChatList(id)) {
3730 entry->removeFromChatList(id, chatsFilters().chatsList(id));
3731 _chatListEntryRefreshes.fire(ChatListEntryRefresh{
3732 .key = key,
3733 .filterId = id,
3734 .existenceChanged = true
3735 });
3736 }
3737 }
3738 const auto mainList = chatsList(entry->folder());
3739 entry->removeFromChatList(0, mainList);
3740 _chatListEntryRefreshes.fire(ChatListEntryRefresh{
3741 .key = key,
3742 .existenceChanged = true
3743 });
3744 if (_contactsList.contains(key)) {
3745 if (!_contactsNoChatsList.contains(key)) {
3746 _contactsNoChatsList.addByName(key);
3747 }
3748 }
3749 if (const auto history = key.history()) {
3750 Core::App().notifications().clearFromHistory(history);
3751 }
3752 }
3753
chatListEntryRefreshes() const3754 auto Session::chatListEntryRefreshes() const
3755 -> rpl::producer<ChatListEntryRefresh> {
3756 return _chatListEntryRefreshes.events();
3757 }
3758
3759
dialogsRowReplaced(DialogsRowReplacement replacement)3760 void Session::dialogsRowReplaced(DialogsRowReplacement replacement) {
3761 _dialogsRowReplacements.fire(std::move(replacement));
3762 }
3763
dialogsRowReplacements() const3764 auto Session::dialogsRowReplacements() const
3765 -> rpl::producer<DialogsRowReplacement> {
3766 return _dialogsRowReplacements.events();
3767 }
3768
requestNotifySettings(not_null<PeerData * > peer)3769 void Session::requestNotifySettings(not_null<PeerData*> peer) {
3770 if (peer->notifySettingsUnknown()) {
3771 _session->api().requestNotifySettings(
3772 MTP_inputNotifyPeer(peer->input));
3773 }
3774 if (defaultNotifySettings(peer).settingsUnknown()) {
3775 _session->api().requestNotifySettings(peer->isUser()
3776 ? MTP_inputNotifyUsers()
3777 : (peer->isChat() || peer->isMegagroup())
3778 ? MTP_inputNotifyChats()
3779 : MTP_inputNotifyBroadcasts());
3780 }
3781 }
3782
applyNotifySetting(const MTPNotifyPeer & notifyPeer,const MTPPeerNotifySettings & settings)3783 void Session::applyNotifySetting(
3784 const MTPNotifyPeer ¬ifyPeer,
3785 const MTPPeerNotifySettings &settings) {
3786 switch (notifyPeer.type()) {
3787 case mtpc_notifyUsers: {
3788 if (_defaultUserNotifySettings.change(settings)) {
3789 _defaultUserNotifyUpdates.fire({});
3790
3791 enumerateUsers([&](not_null<UserData*> user) {
3792 if (!user->notifySettingsUnknown()
3793 && ((!user->notifyMuteUntil()
3794 && _defaultUserNotifySettings.muteUntil())
3795 || (!user->notifySilentPosts()
3796 && _defaultUserNotifySettings.silentPosts()))) {
3797 updateNotifySettingsLocal(user);
3798 }
3799 });
3800 }
3801 } break;
3802 case mtpc_notifyChats: {
3803 if (_defaultChatNotifySettings.change(settings)) {
3804 _defaultChatNotifyUpdates.fire({});
3805
3806 enumerateGroups([&](not_null<PeerData*> peer) {
3807 if (!peer->notifySettingsUnknown()
3808 && ((!peer->notifyMuteUntil()
3809 && _defaultChatNotifySettings.muteUntil())
3810 || (!peer->notifySilentPosts()
3811 && _defaultChatNotifySettings.silentPosts()))) {
3812 updateNotifySettingsLocal(peer);
3813 }
3814 });
3815 }
3816 } break;
3817 case mtpc_notifyBroadcasts: {
3818 if (_defaultBroadcastNotifySettings.change(settings)) {
3819 _defaultBroadcastNotifyUpdates.fire({});
3820
3821 enumerateChannels([&](not_null<ChannelData*> channel) {
3822 if (!channel->notifySettingsUnknown()
3823 && ((!channel->notifyMuteUntil()
3824 && _defaultBroadcastNotifySettings.muteUntil())
3825 || (!channel->notifySilentPosts()
3826 && _defaultBroadcastNotifySettings.silentPosts()))) {
3827 updateNotifySettingsLocal(channel);
3828 }
3829 });
3830 }
3831 } break;
3832 case mtpc_notifyPeer: {
3833 const auto &data = notifyPeer.c_notifyPeer();
3834 if (const auto peer = peerLoaded(peerFromMTP(data.vpeer()))) {
3835 if (peer->notifyChange(settings)) {
3836 updateNotifySettingsLocal(peer);
3837 }
3838 }
3839 } break;
3840 }
3841 }
3842
updateNotifySettings(not_null<PeerData * > peer,std::optional<int> muteForSeconds,std::optional<bool> silentPosts)3843 void Session::updateNotifySettings(
3844 not_null<PeerData*> peer,
3845 std::optional<int> muteForSeconds,
3846 std::optional<bool> silentPosts) {
3847 if (peer->notifyChange(muteForSeconds, silentPosts)) {
3848 updateNotifySettingsLocal(peer);
3849 _session->api().updateNotifySettingsDelayed(peer);
3850 }
3851 }
3852
resetNotifySettingsToDefault(not_null<PeerData * > peer)3853 void Session::resetNotifySettingsToDefault(not_null<PeerData*> peer) {
3854 const auto empty = MTP_peerNotifySettings(
3855 MTP_flags(0),
3856 MTPBool(),
3857 MTPBool(),
3858 MTPint(),
3859 MTPstring());
3860 if (peer->notifyChange(empty)) {
3861 updateNotifySettingsLocal(peer);
3862 _session->api().updateNotifySettingsDelayed(peer);
3863 }
3864 }
3865
notifyIsMuted(not_null<const PeerData * > peer,crl::time * changesIn) const3866 bool Session::notifyIsMuted(
3867 not_null<const PeerData*> peer,
3868 crl::time *changesIn) const {
3869 const auto resultFromUntil = [&](TimeId until) {
3870 const auto now = base::unixtime::now();
3871 const auto result = (until > now) ? (until - now) : 0;
3872 if (changesIn) {
3873 *changesIn = (result > 0)
3874 ? std::min(result * crl::time(1000), kMaxNotifyCheckDelay)
3875 : kMaxNotifyCheckDelay;
3876 }
3877 return (result > 0);
3878 };
3879 if (const auto until = peer->notifyMuteUntil()) {
3880 return resultFromUntil(*until);
3881 }
3882 const auto &settings = defaultNotifySettings(peer);
3883 if (const auto until = settings.muteUntil()) {
3884 return resultFromUntil(*until);
3885 }
3886 return true;
3887 }
3888
notifySilentPosts(not_null<const PeerData * > peer) const3889 bool Session::notifySilentPosts(not_null<const PeerData*> peer) const {
3890 if (const auto silent = peer->notifySilentPosts()) {
3891 return *silent;
3892 }
3893 const auto &settings = defaultNotifySettings(peer);
3894 if (const auto silent = settings.silentPosts()) {
3895 return *silent;
3896 }
3897 return false;
3898 }
3899
notifyMuteUnknown(not_null<const PeerData * > peer) const3900 bool Session::notifyMuteUnknown(not_null<const PeerData*> peer) const {
3901 if (peer->notifySettingsUnknown()) {
3902 return true;
3903 } else if (const auto nonDefault = peer->notifyMuteUntil()) {
3904 return false;
3905 }
3906 return defaultNotifySettings(peer).settingsUnknown();
3907 }
3908
notifySilentPostsUnknown(not_null<const PeerData * > peer) const3909 bool Session::notifySilentPostsUnknown(
3910 not_null<const PeerData*> peer) const {
3911 if (peer->notifySettingsUnknown()) {
3912 return true;
3913 } else if (const auto nonDefault = peer->notifySilentPosts()) {
3914 return false;
3915 }
3916 return defaultNotifySettings(peer).settingsUnknown();
3917 }
3918
notifySettingsUnknown(not_null<const PeerData * > peer) const3919 bool Session::notifySettingsUnknown(not_null<const PeerData*> peer) const {
3920 return notifyMuteUnknown(peer) || notifySilentPostsUnknown(peer);
3921 }
3922
defaultUserNotifyUpdates() const3923 rpl::producer<> Session::defaultUserNotifyUpdates() const {
3924 return _defaultUserNotifyUpdates.events();
3925 }
3926
defaultChatNotifyUpdates() const3927 rpl::producer<> Session::defaultChatNotifyUpdates() const {
3928 return _defaultChatNotifyUpdates.events();
3929 }
3930
defaultBroadcastNotifyUpdates() const3931 rpl::producer<> Session::defaultBroadcastNotifyUpdates() const {
3932 return _defaultBroadcastNotifyUpdates.events();
3933 }
3934
defaultNotifyUpdates(not_null<const PeerData * > peer) const3935 rpl::producer<> Session::defaultNotifyUpdates(
3936 not_null<const PeerData*> peer) const {
3937 return peer->isUser()
3938 ? defaultUserNotifyUpdates()
3939 : (peer->isChat() || peer->isMegagroup())
3940 ? defaultChatNotifyUpdates()
3941 : defaultBroadcastNotifyUpdates();
3942 }
3943
serviceNotification(const TextWithEntities & message,const MTPMessageMedia & media)3944 void Session::serviceNotification(
3945 const TextWithEntities &message,
3946 const MTPMessageMedia &media) {
3947 const auto date = base::unixtime::now();
3948 if (!peerLoaded(PeerData::kServiceNotificationsId)) {
3949 processUser(MTP_user(
3950 MTP_flags(
3951 MTPDuser::Flag::f_first_name
3952 | MTPDuser::Flag::f_phone
3953 | MTPDuser::Flag::f_status
3954 | MTPDuser::Flag::f_verified),
3955 MTP_long(peerToUser(PeerData::kServiceNotificationsId).bare),
3956 MTPlong(), // access_hash
3957 MTP_string("Telegram"),
3958 MTPstring(), // last_name
3959 MTPstring(), // username
3960 MTP_string("42777"),
3961 MTP_userProfilePhotoEmpty(),
3962 MTP_userStatusRecently(),
3963 MTPint(), // bot_info_version
3964 MTPVector<MTPRestrictionReason>(),
3965 MTPstring(), // bot_inline_placeholder
3966 MTPstring())); // lang_code
3967 }
3968 const auto history = this->history(PeerData::kServiceNotificationsId);
3969 if (!history->folderKnown()) {
3970 histories().requestDialogEntry(history, [=] {
3971 insertCheckedServiceNotification(message, media, date);
3972 });
3973 } else {
3974 insertCheckedServiceNotification(message, media, date);
3975 }
3976 }
3977
insertCheckedServiceNotification(const TextWithEntities & message,const MTPMessageMedia & media,TimeId date)3978 void Session::insertCheckedServiceNotification(
3979 const TextWithEntities &message,
3980 const MTPMessageMedia &media,
3981 TimeId date) {
3982 const auto flags = MTPDmessage::Flag::f_entities
3983 | MTPDmessage::Flag::f_from_id
3984 | MTPDmessage::Flag::f_media;
3985 const auto localFlags = MessageFlag::ClientSideUnread
3986 | MessageFlag::Local;
3987 auto sending = TextWithEntities(), left = message;
3988 while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
3989 const auto id = nextLocalMessageId();
3990 addNewMessage(
3991 id,
3992 MTP_message(
3993 MTP_flags(flags),
3994 MTP_int(0), // Not used (would've been trimmed to 32 bits).
3995 peerToMTP(PeerData::kServiceNotificationsId),
3996 peerToMTP(PeerData::kServiceNotificationsId),
3997 MTPMessageFwdHeader(),
3998 MTPlong(), // via_bot_id
3999 MTPMessageReplyHeader(),
4000 MTP_int(date),
4001 MTP_string(sending.text),
4002 media,
4003 MTPReplyMarkup(),
4004 Api::EntitiesToMTP(&session(), sending.entities),
4005 MTPint(), // views
4006 MTPint(), // forwards
4007 MTPMessageReplies(),
4008 MTPint(), // edit_date
4009 MTPstring(),
4010 MTPlong(),
4011 //MTPMessageReactions(),
4012 MTPVector<MTPRestrictionReason>(),
4013 MTPint()), // ttl_period
4014 localFlags,
4015 NewMessageType::Unread);
4016 }
4017 sendHistoryChangeNotifications();
4018 }
4019
setMimeForwardIds(MessageIdsList && list)4020 void Session::setMimeForwardIds(MessageIdsList &&list) {
4021 _mimeForwardIds = std::move(list);
4022 }
4023
takeMimeForwardIds()4024 MessageIdsList Session::takeMimeForwardIds() {
4025 return std::move(_mimeForwardIds);
4026 }
4027
setTopPromoted(History * promoted,const QString & type,const QString & message)4028 void Session::setTopPromoted(
4029 History *promoted,
4030 const QString &type,
4031 const QString &message) {
4032 const auto changed = (_topPromoted != promoted);
4033 if (!changed
4034 && (!promoted || promoted->topPromotionMessage() == message)) {
4035 return;
4036 }
4037 if (changed) {
4038 if (_topPromoted) {
4039 _topPromoted->cacheTopPromotion(false, QString(), QString());
4040 }
4041 }
4042 const auto old = std::exchange(_topPromoted, promoted);
4043 if (_topPromoted) {
4044 histories().requestDialogEntry(_topPromoted);
4045 _topPromoted->cacheTopPromotion(true, type, message);
4046 _topPromoted->requestChatListMessage();
4047 session().changes().historyUpdated(
4048 _topPromoted,
4049 HistoryUpdate::Flag::TopPromoted);
4050 }
4051 if (changed && old) {
4052 session().changes().historyUpdated(
4053 old,
4054 HistoryUpdate::Flag::TopPromoted);
4055 }
4056 }
4057
updateWallpapers(const MTPaccount_WallPapers & data)4058 bool Session::updateWallpapers(const MTPaccount_WallPapers &data) {
4059 return data.match([&](const MTPDaccount_wallPapers &data) {
4060 setWallpapers(data.vwallpapers().v, data.vhash().v);
4061 return true;
4062 }, [&](const MTPDaccount_wallPapersNotModified &) {
4063 return false;
4064 });
4065 }
4066
setWallpapers(const QVector<MTPWallPaper> & data,uint64 hash)4067 void Session::setWallpapers(const QVector<MTPWallPaper> &data, uint64 hash) {
4068 _wallpapersHash = hash;
4069
4070 _wallpapers.clear();
4071 _wallpapers.reserve(data.size() + 2);
4072
4073 _wallpapers.push_back(Data::Legacy1DefaultWallPaper());
4074 _wallpapers.back().setLocalImageAsThumbnail(std::make_shared<Image>(
4075 u":/gui/art/bg_initial.jpg"_q));
4076 for (const auto &paper : data) {
4077 if (const auto parsed = Data::WallPaper::Create(&session(), paper)) {
4078 _wallpapers.push_back(*parsed);
4079 }
4080 }
4081
4082 // Put the legacy2 (flowers) wallpaper to the front of the list.
4083 const auto legacy2 = ranges::find_if(
4084 _wallpapers,
4085 Data::IsLegacy2DefaultWallPaper);
4086 if (legacy2 != end(_wallpapers)) {
4087 ranges::rotate(begin(_wallpapers), legacy2, legacy2 + 1);
4088 }
4089
4090 // Put the legacy3 (static gradient) wallpaper to the front of the list.
4091 const auto legacy3 = ranges::find_if(
4092 _wallpapers,
4093 Data::IsLegacy3DefaultWallPaper);
4094 if (legacy3 != end(_wallpapers)) {
4095 ranges::rotate(begin(_wallpapers), legacy3, legacy3 + 1);
4096 }
4097
4098 if (ranges::none_of(_wallpapers, Data::IsDefaultWallPaper)) {
4099 _wallpapers.push_back(Data::DefaultWallPaper());
4100 _wallpapers.back().setLocalImageAsThumbnail(std::make_shared<Image>(
4101 u":/gui/art/bg_thumbnail.png"_q));
4102 }
4103 }
4104
removeWallpaper(const WallPaper & paper)4105 void Session::removeWallpaper(const WallPaper &paper) {
4106 const auto i = ranges::find(_wallpapers, paper.id(), &WallPaper::id);
4107 if (i != end(_wallpapers)) {
4108 _wallpapers.erase(i);
4109 }
4110 }
4111
wallpapers() const4112 const std::vector<WallPaper> &Session::wallpapers() const {
4113 return _wallpapers;
4114 }
4115
wallpapersHash() const4116 uint64 Session::wallpapersHash() const {
4117 return _wallpapersHash;
4118 }
4119
clearLocalStorage()4120 void Session::clearLocalStorage() {
4121 _cache->close();
4122 _cache->clear();
4123 _bigFileCache->close();
4124 _bigFileCache->clear();
4125 }
4126
4127 } // namespace Data
4128