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_peer.h"
9
10 #include "data/data_user.h"
11 #include "data/data_chat.h"
12 #include "data/data_channel.h"
13 #include "data/data_changes.h"
14 #include "data/data_photo.h"
15 #include "data/data_folder.h"
16 #include "data/data_session.h"
17 #include "data/data_file_origin.h"
18 #include "data/data_histories.h"
19 #include "data/data_cloud_themes.h"
20 #include "base/unixtime.h"
21 #include "base/crc32hash.h"
22 #include "lang/lang_keys.h"
23 #include "apiwrap.h"
24 #include "ui/boxes/confirm_box.h"
25 #include "main/main_session.h"
26 #include "main/main_session_settings.h"
27 #include "main/main_account.h"
28 #include "main/main_domain.h"
29 #include "main/main_app_config.h"
30 #include "mtproto/mtproto_config.h"
31 #include "core/application.h"
32 #include "core/click_handler_types.h"
33 #include "window/window_session_controller.h"
34 #include "window/main_window.h" // Window::LogoNoMargin.
35 #include "ui/image/image.h"
36 #include "ui/empty_userpic.h"
37 #include "ui/text/text_options.h"
38 #include "ui/toasts/common_toasts.h"
39 #include "ui/ui_utility.h"
40 #include "history/history.h"
41 #include "history/view/history_view_element.h"
42 #include "history/history_item.h"
43 #include "storage/file_download.h"
44 #include "storage/storage_facade.h"
45 #include "storage/storage_shared_media.h"
46
47 namespace {
48
49 constexpr auto kUpdateFullPeerTimeout = crl::time(5000); // Not more than once in 5 seconds.
50 constexpr auto kUserpicSize = 160;
51
52 using UpdateFlag = Data::PeerUpdate::Flag;
53
54 } // namespace
55
56 namespace Data {
57
PeerColorIndex(BareId bareId)58 int PeerColorIndex(BareId bareId) {
59 const auto index = bareId % 7;
60 const int map[] = { 0, 7, 4, 1, 6, 3, 5 };
61 return map[index];
62 }
63
PeerColorIndex(PeerId peerId)64 int PeerColorIndex(PeerId peerId) {
65 return PeerColorIndex(peerId.value & PeerId::kChatTypeMask);
66 }
67
PeerUserpicColor(PeerId peerId)68 style::color PeerUserpicColor(PeerId peerId) {
69 const style::color colors[] = {
70 st::historyPeer1UserpicBg,
71 st::historyPeer2UserpicBg,
72 st::historyPeer3UserpicBg,
73 st::historyPeer4UserpicBg,
74 st::historyPeer5UserpicBg,
75 st::historyPeer6UserpicBg,
76 st::historyPeer7UserpicBg,
77 st::historyPeer8UserpicBg,
78 };
79 return colors[PeerColorIndex(peerId)];
80 }
81
FakePeerIdForJustName(const QString & name)82 PeerId FakePeerIdForJustName(const QString &name) {
83 return peerFromUser(name.isEmpty()
84 ? 777
85 : base::crc32(name.constData(), name.size() * sizeof(QChar)));
86 }
87
UpdateBotCommands(std::vector<BotCommand> & commands,const MTPVector<MTPBotCommand> & data)88 bool UpdateBotCommands(
89 std::vector<BotCommand> &commands,
90 const MTPVector<MTPBotCommand> &data) {
91 const auto &v = data.v;
92 commands.reserve(v.size());
93 auto result = false;
94 auto index = 0;
95 for (const auto &command : v) {
96 command.match([&](const MTPDbotCommand &data) {
97 const auto command = qs(data.vcommand());
98 const auto description = qs(data.vdescription());
99 if (commands.size() <= index) {
100 commands.push_back({
101 .command = command,
102 .description = description,
103 });
104 result = true;
105 } else {
106 auto &entry = commands[index];
107 if (entry.command != command
108 || entry.description != description) {
109 entry.command = command;
110 entry.description = description;
111 result = true;
112 }
113 }
114 ++index;
115 });
116 }
117 if (index < commands.size()) {
118 result = true;
119 }
120 commands.resize(index);
121 return result;
122 }
123
UpdateBotCommands(base::flat_map<UserId,std::vector<BotCommand>> & commands,UserId botId,const MTPVector<MTPBotCommand> & data)124 bool UpdateBotCommands(
125 base::flat_map<UserId, std::vector<BotCommand>> &commands,
126 UserId botId,
127 const MTPVector<MTPBotCommand> &data) {
128 return data.v.isEmpty()
129 ? commands.remove(botId)
130 : UpdateBotCommands(commands[botId], data);
131 }
132
UpdateBotCommands(base::flat_map<UserId,std::vector<BotCommand>> & commands,const MTPVector<MTPBotInfo> & data)133 bool UpdateBotCommands(
134 base::flat_map<UserId, std::vector<BotCommand>> &commands,
135 const MTPVector<MTPBotInfo> &data) {
136 auto result = false;
137 auto filled = base::flat_set<UserId>();
138 filled.reserve(data.v.size());
139 for (const auto &item : data.v) {
140 item.match([&](const MTPDbotInfo &data) {
141 const auto id = UserId(data.vuser_id().v);
142 if (!filled.emplace(id).second) {
143 LOG(("API Error: Two BotInfo for a single bot."));
144 return;
145 } else if (UpdateBotCommands(commands, id, data.vcommands())) {
146 result = true;
147 }
148 });
149 }
150 for (auto i = begin(commands); i != end(commands);) {
151 if (filled.contains(i->first)) {
152 ++i;
153 } else {
154 i = commands.erase(i);
155 result = true;
156 }
157 }
158 return result;
159 }
160
161 } // namespace Data
162
PeerClickHandler(not_null<PeerData * > peer)163 PeerClickHandler::PeerClickHandler(not_null<PeerData*> peer)
164 : _peer(peer) {
165 }
166
onClick(ClickContext context) const167 void PeerClickHandler::onClick(ClickContext context) const {
168 if (context.button != Qt::LeftButton) {
169 return;
170 }
171 const auto my = context.other.value<ClickHandlerContext>();
172 const auto window = [&]() -> Window::SessionController* {
173 if (const auto controller = my.sessionWindow.get()) {
174 return controller;
175 }
176 const auto &windows = _peer->session().windows();
177 if (windows.empty()) {
178 _peer->session().domain().activate(&_peer->session().account());
179 if (windows.empty()) {
180 return nullptr;
181 }
182 }
183 return windows.front();
184 }();
185 if (window) {
186 window->showPeer(_peer);
187 }
188 }
189
PeerData(not_null<Data::Session * > owner,PeerId id)190 PeerData::PeerData(not_null<Data::Session*> owner, PeerId id)
191 : id(id)
192 , _owner(owner) {
193 _nameText.setText(st::msgNameStyle, QString(), Ui::NameTextOptions());
194 }
195
owner() const196 Data::Session &PeerData::owner() const {
197 return *_owner;
198 }
199
session() const200 Main::Session &PeerData::session() const {
201 return _owner->session();
202 }
203
account() const204 Main::Account &PeerData::account() const {
205 return session().account();
206 }
207
updateNameDelayed(const QString & newName,const QString & newNameOrPhone,const QString & newUsername)208 void PeerData::updateNameDelayed(
209 const QString &newName,
210 const QString &newNameOrPhone,
211 const QString &newUsername) {
212 if (name == newName && nameVersion > 1) {
213 if (isUser()) {
214 if (asUser()->nameOrPhone == newNameOrPhone
215 && asUser()->username == newUsername) {
216 return;
217 }
218 } else if (isChannel()) {
219 if (asChannel()->username == newUsername) {
220 return;
221 }
222 } else if (isChat()) {
223 return;
224 }
225 }
226 name = newName;
227 _nameText.setText(st::msgNameStyle, name, Ui::NameTextOptions());
228 _userpicEmpty = nullptr;
229
230 auto flags = UpdateFlag::None | UpdateFlag::None;
231 auto oldFirstLetters = base::flat_set<QChar>();
232 const auto nameUpdated = (nameVersion++ > 1);
233 if (nameUpdated) {
234 oldFirstLetters = nameFirstLetters();
235 flags |= UpdateFlag::Name;
236 }
237 if (isUser()) {
238 if (asUser()->username != newUsername) {
239 asUser()->username = newUsername;
240 flags |= UpdateFlag::Username;
241 }
242 asUser()->setNameOrPhone(newNameOrPhone);
243 } else if (isChannel()) {
244 if (asChannel()->username != newUsername) {
245 asChannel()->username = newUsername;
246 if (newUsername.isEmpty()) {
247 asChannel()->removeFlags(ChannelDataFlag::Username);
248 } else {
249 asChannel()->addFlags(ChannelDataFlag::Username);
250 }
251 flags |= UpdateFlag::Username;
252 }
253 }
254 fillNames();
255 if (nameUpdated) {
256 session().changes().nameUpdated(this, std::move(oldFirstLetters));
257 }
258 if (flags) {
259 session().changes().peerUpdated(this, flags);
260 }
261 }
262
ensureEmptyUserpic() const263 not_null<Ui::EmptyUserpic*> PeerData::ensureEmptyUserpic() const {
264 if (!_userpicEmpty) {
265 _userpicEmpty = std::make_unique<Ui::EmptyUserpic>(
266 Data::PeerUserpicColor(id),
267 name);
268 }
269 return _userpicEmpty.get();
270 }
271
createOpenLink()272 ClickHandlerPtr PeerData::createOpenLink() {
273 return std::make_shared<PeerClickHandler>(this);
274 }
275
setUserpic(PhotoId photoId,const ImageLocation & location)276 void PeerData::setUserpic(PhotoId photoId, const ImageLocation &location) {
277 _userpicPhotoId = photoId;
278 _userpic.set(&session(), ImageWithLocation{ .location = location });
279 }
280
setUserpicPhoto(const MTPPhoto & data)281 void PeerData::setUserpicPhoto(const MTPPhoto &data) {
282 const auto photoId = data.match([&](const MTPDphoto &data) {
283 const auto photo = owner().processPhoto(data);
284 photo->peer = this;
285 return photo->id;
286 }, [](const MTPDphotoEmpty &data) {
287 return PhotoId(0);
288 });
289 if (_userpicPhotoId != photoId) {
290 _userpicPhotoId = photoId;
291 session().changes().peerUpdated(this, UpdateFlag::Photo);
292 }
293 }
294
currentUserpic(std::shared_ptr<Data::CloudImageView> & view) const295 Image *PeerData::currentUserpic(
296 std::shared_ptr<Data::CloudImageView> &view) const {
297 if (!_userpic.isCurrentView(view)) {
298 view = _userpic.createView();
299 _userpic.load(&session(), userpicOrigin());
300 }
301 const auto image = view ? view->image() : nullptr;
302 if (image) {
303 _userpicEmpty = nullptr;
304 } else if (isNotificationsUser()) {
305 static auto result = Image(
306 Window::LogoNoMargin().scaledToWidth(
307 kUserpicSize,
308 Qt::SmoothTransformation));
309 return &result;
310 }
311 return image;
312 }
313
paintUserpic(Painter & p,std::shared_ptr<Data::CloudImageView> & view,int x,int y,int size) const314 void PeerData::paintUserpic(
315 Painter &p,
316 std::shared_ptr<Data::CloudImageView> &view,
317 int x,
318 int y,
319 int size) const {
320 if (const auto userpic = currentUserpic(view)) {
321 p.drawPixmap(x, y, userpic->pixCircled(size, size));
322 } else {
323 ensureEmptyUserpic()->paint(p, x, y, x + size + x, size);
324 }
325 }
326
loadUserpic()327 void PeerData::loadUserpic() {
328 _userpic.load(&session(), userpicOrigin());
329 }
330
hasUserpic() const331 bool PeerData::hasUserpic() const {
332 return !_userpic.empty();
333 }
334
activeUserpicView()335 std::shared_ptr<Data::CloudImageView> PeerData::activeUserpicView() {
336 return _userpic.empty() ? nullptr : _userpic.activeView();
337 }
338
createUserpicView()339 std::shared_ptr<Data::CloudImageView> PeerData::createUserpicView() {
340 if (_userpic.empty()) {
341 return nullptr;
342 }
343 auto result = _userpic.createView();
344 _userpic.load(&session(), userpicPhotoOrigin());
345 return result;
346 }
347
useEmptyUserpic(std::shared_ptr<Data::CloudImageView> & view) const348 bool PeerData::useEmptyUserpic(
349 std::shared_ptr<Data::CloudImageView> &view) const {
350 return !currentUserpic(view);
351 }
352
userpicUniqueKey(std::shared_ptr<Data::CloudImageView> & view) const353 InMemoryKey PeerData::userpicUniqueKey(
354 std::shared_ptr<Data::CloudImageView> &view) const {
355 return useEmptyUserpic(view)
356 ? ensureEmptyUserpic()->uniqueKey()
357 : inMemoryKey(_userpic.location());
358 }
359
saveUserpic(std::shared_ptr<Data::CloudImageView> & view,const QString & path,int size) const360 void PeerData::saveUserpic(
361 std::shared_ptr<Data::CloudImageView> &view,
362 const QString &path,
363 int size) const {
364 generateUserpicImage(view, size * cIntRetinaFactor()).save(path, "PNG");
365 }
366
saveUserpicRounded(std::shared_ptr<Data::CloudImageView> & view,const QString & path,int size) const367 void PeerData::saveUserpicRounded(
368 std::shared_ptr<Data::CloudImageView> &view,
369 const QString &path,
370 int size) const {
371 generateUserpicImage(
372 view,
373 size * cIntRetinaFactor(),
374 ImageRoundRadius::Small).save(path, "PNG");
375 }
376
genUserpic(std::shared_ptr<Data::CloudImageView> & view,int size) const377 QPixmap PeerData::genUserpic(
378 std::shared_ptr<Data::CloudImageView> &view,
379 int size) const {
380 if (const auto userpic = currentUserpic(view)) {
381 return userpic->pixCircled(size, size);
382 }
383 auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
384 result.setDevicePixelRatio(cRetinaFactor());
385 result.fill(Qt::transparent);
386 {
387 Painter p(&result);
388 paintUserpic(p, view, 0, 0, size);
389 }
390 return Ui::PixmapFromImage(std::move(result));
391 }
392
generateUserpicImage(std::shared_ptr<Data::CloudImageView> & view,int size) const393 QImage PeerData::generateUserpicImage(
394 std::shared_ptr<Data::CloudImageView> &view,
395 int size) const {
396 return generateUserpicImage(view, size, ImageRoundRadius::Ellipse);
397 }
398
generateUserpicImage(std::shared_ptr<Data::CloudImageView> & view,int size,ImageRoundRadius radius) const399 QImage PeerData::generateUserpicImage(
400 std::shared_ptr<Data::CloudImageView> &view,
401 int size,
402 ImageRoundRadius radius) const {
403 if (const auto userpic = currentUserpic(view)) {
404 const auto options = (radius == ImageRoundRadius::Ellipse)
405 ? (Images::Option::RoundedAll | Images::Option::Circled)
406 : (radius == ImageRoundRadius::None)
407 ? Images::Options()
408 : (Images::Option::RoundedAll | Images::Option::RoundedSmall);
409 return userpic->pixNoCache(
410 size,
411 size,
412 Images::Option::Smooth | options
413 ).toImage();
414 }
415 auto result = QImage(
416 QSize(size, size),
417 QImage::Format_ARGB32_Premultiplied);
418 result.fill(Qt::transparent);
419 {
420 Painter p(&result);
421 if (radius == ImageRoundRadius::Ellipse) {
422 ensureEmptyUserpic()->paint(p, 0, 0, size, size);
423 } else if (radius == ImageRoundRadius::None) {
424 ensureEmptyUserpic()->paintSquare(p, 0, 0, size, size);
425 } else {
426 ensureEmptyUserpic()->paintRounded(p, 0, 0, size, size);
427 }
428 }
429 return result;
430 }
431
userpicOrigin() const432 Data::FileOrigin PeerData::userpicOrigin() const {
433 return Data::FileOriginPeerPhoto(id);
434 }
435
userpicPhotoOrigin() const436 Data::FileOrigin PeerData::userpicPhotoOrigin() const {
437 return (isUser() && userpicPhotoId())
438 ? Data::FileOriginUserPhoto(peerToUser(id).bare, userpicPhotoId())
439 : Data::FileOrigin();
440 }
441
updateUserpic(PhotoId photoId,MTP::DcId dcId)442 void PeerData::updateUserpic(PhotoId photoId, MTP::DcId dcId) {
443 setUserpicChecked(
444 photoId,
445 ImageLocation(
446 { StorageFileLocation(
447 dcId,
448 isSelf() ? peerToUser(id) : UserId(),
449 MTP_inputPeerPhotoFileLocation(
450 MTP_flags(0),
451 input,
452 MTP_long(photoId))) },
453 kUserpicSize,
454 kUserpicSize));
455 }
456
clearUserpic()457 void PeerData::clearUserpic() {
458 setUserpicChecked(PhotoId(), ImageLocation());
459 }
460
setUserpicChecked(PhotoId photoId,const ImageLocation & location)461 void PeerData::setUserpicChecked(
462 PhotoId photoId,
463 const ImageLocation &location) {
464 if (_userpicPhotoId != photoId || _userpic.location() != location) {
465 setUserpic(photoId, location);
466 session().changes().peerUpdated(this, UpdateFlag::Photo);
467 }
468 }
469
unavailableReasons() const470 auto PeerData::unavailableReasons() const
471 -> const std::vector<Data::UnavailableReason> & {
472 static const auto result = std::vector<Data::UnavailableReason>();
473 return result;
474 }
475
computeUnavailableReason() const476 QString PeerData::computeUnavailableReason() const {
477 const auto &list = unavailableReasons();
478 const auto &config = session().account().appConfig();
479 const auto skip = config.get<std::vector<QString>>(
480 "ignore_restriction_reasons",
481 std::vector<QString>());
482 auto &&filtered = ranges::views::all(
483 list
484 ) | ranges::views::filter([&](const Data::UnavailableReason &reason) {
485 return !ranges::contains(skip, reason.reason);
486 });
487 const auto first = filtered.begin();
488 return (first != filtered.end()) ? first->text : QString();
489 }
490
491 // This is duplicated in CanPinMessagesValue().
canPinMessages() const492 bool PeerData::canPinMessages() const {
493 if (const auto user = asUser()) {
494 return user->flags() & UserDataFlag::CanPinMessages;
495 } else if (const auto chat = asChat()) {
496 return chat->amIn()
497 && !chat->amRestricted(ChatRestriction::PinMessages);
498 } else if (const auto channel = asChannel()) {
499 return channel->isMegagroup()
500 ? !channel->amRestricted(ChatRestriction::PinMessages)
501 : ((channel->adminRights() & ChatAdminRight::EditMessages)
502 || channel->amCreator());
503 }
504 Unexpected("Peer type in PeerData::canPinMessages.");
505 }
506
canEditMessagesIndefinitely() const507 bool PeerData::canEditMessagesIndefinitely() const {
508 if (const auto user = asUser()) {
509 return user->isSelf();
510 } else if (const auto chat = asChat()) {
511 return false;
512 } else if (const auto channel = asChannel()) {
513 return channel->isMegagroup()
514 ? channel->canPinMessages()
515 : channel->canEditMessages();
516 }
517 Unexpected("Peer type in PeerData::canEditMessagesIndefinitely.");
518 }
519
canExportChatHistory() const520 bool PeerData::canExportChatHistory() const {
521 if (isRepliesChat()) {
522 return false;
523 }
524 if (const auto channel = asChannel()) {
525 if (!channel->amIn() && channel->invitePeekExpires()) {
526 return false;
527 }
528 }
529 for (const auto &block : _owner->history(id)->blocks) {
530 for (const auto &message : block->messages) {
531 if (!message->data()->isService()) {
532 return true;
533 }
534 }
535 }
536 if (const auto from = migrateFrom()) {
537 return from->canExportChatHistory();
538 }
539 return false;
540 }
541
setAbout(const QString & newAbout)542 bool PeerData::setAbout(const QString &newAbout) {
543 if (_about == newAbout) {
544 return false;
545 }
546 _about = newAbout;
547 session().changes().peerUpdated(this, UpdateFlag::About);
548 return true;
549 }
550
checkFolder(FolderId folderId)551 void PeerData::checkFolder(FolderId folderId) {
552 const auto folder = folderId
553 ? owner().folderLoaded(folderId)
554 : nullptr;
555 if (const auto history = owner().historyLoaded(this)) {
556 if (folder && history->folder() != folder) {
557 owner().histories().requestDialogEntry(history);
558 }
559 }
560 }
561
setSettings(const MTPPeerSettings & data)562 void PeerData::setSettings(const MTPPeerSettings &data) {
563 data.match([&](const MTPDpeerSettings &data) {
564 using Flag = PeerSetting;
565 setSettings((data.is_add_contact() ? Flag::AddContact : Flag())
566 | (data.is_autoarchived() ? Flag::AutoArchived : Flag())
567 | (data.is_block_contact() ? Flag::BlockContact : Flag())
568 //| (data.is_invite_members() ? Flag::InviteMembers : Flag())
569 | (data.is_need_contacts_exception()
570 ? Flag::NeedContactsException
571 : Flag())
572 //| (data.is_report_geo() ? Flag::ReportGeo : Flag())
573 | (data.is_report_spam() ? Flag::ReportSpam : Flag())
574 | (data.is_share_contact() ? Flag::ShareContact : Flag()));
575 });
576 }
577
fillNames()578 void PeerData::fillNames() {
579 _nameWords.clear();
580 _nameFirstLetters.clear();
581 auto toIndexList = QStringList();
582 auto appendToIndex = [&](const QString &value) {
583 if (!value.isEmpty()) {
584 toIndexList.push_back(TextUtilities::RemoveAccents(value));
585 }
586 };
587
588 appendToIndex(name);
589 const auto appendTranslit = !toIndexList.isEmpty()
590 && cRussianLetters().match(toIndexList.front()).hasMatch();
591 if (appendTranslit) {
592 appendToIndex(translitRusEng(toIndexList.front()));
593 }
594 if (const auto user = asUser()) {
595 if (user->nameOrPhone != name) {
596 appendToIndex(user->nameOrPhone);
597 }
598 appendToIndex(user->username);
599 if (isSelf()) {
600 const auto english = qsl("Saved messages");
601 const auto localized = tr::lng_saved_messages(tr::now);
602 appendToIndex(english);
603 if (localized != english) {
604 appendToIndex(localized);
605 }
606 } else if (isRepliesChat()) {
607 const auto english = qsl("Replies");
608 const auto localized = tr::lng_replies_messages(tr::now);
609 appendToIndex(english);
610 if (localized != english) {
611 appendToIndex(localized);
612 }
613 }
614 } else if (const auto channel = asChannel()) {
615 appendToIndex(channel->username);
616 }
617 auto toIndex = toIndexList.join(' ');
618 toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex);
619
620 const auto namesList = TextUtilities::PrepareSearchWords(toIndex);
621 for (const auto &name : namesList) {
622 _nameWords.insert(name);
623 _nameFirstLetters.insert(name[0]);
624 }
625 }
626
627 PeerData::~PeerData() = default;
628
updateFull()629 void PeerData::updateFull() {
630 if (!_lastFullUpdate
631 || crl::now() > _lastFullUpdate + kUpdateFullPeerTimeout) {
632 updateFullForced();
633 }
634 }
635
updateFullForced()636 void PeerData::updateFullForced() {
637 session().api().requestFullPeer(this);
638 if (const auto channel = asChannel()) {
639 if (!channel->amCreator() && !channel->inviter) {
640 session().api().requestSelfParticipant(channel);
641 }
642 }
643 }
644
fullUpdated()645 void PeerData::fullUpdated() {
646 _lastFullUpdate = crl::now();
647 setLoadedStatus(LoadedStatus::Full);
648 }
649
asUser()650 UserData *PeerData::asUser() {
651 return isUser() ? static_cast<UserData*>(this) : nullptr;
652 }
653
asUser() const654 const UserData *PeerData::asUser() const {
655 return isUser() ? static_cast<const UserData*>(this) : nullptr;
656 }
657
asChat()658 ChatData *PeerData::asChat() {
659 return isChat() ? static_cast<ChatData*>(this) : nullptr;
660 }
661
asChat() const662 const ChatData *PeerData::asChat() const {
663 return isChat() ? static_cast<const ChatData*>(this) : nullptr;
664 }
665
asChannel()666 ChannelData *PeerData::asChannel() {
667 return isChannel() ? static_cast<ChannelData*>(this) : nullptr;
668 }
669
asChannel() const670 const ChannelData *PeerData::asChannel() const {
671 return isChannel()
672 ? static_cast<const ChannelData*>(this)
673 : nullptr;
674 }
675
asMegagroup()676 ChannelData *PeerData::asMegagroup() {
677 return isMegagroup() ? static_cast<ChannelData*>(this) : nullptr;
678 }
679
asMegagroup() const680 const ChannelData *PeerData::asMegagroup() const {
681 return isMegagroup()
682 ? static_cast<const ChannelData*>(this)
683 : nullptr;
684 }
685
asBroadcast()686 ChannelData *PeerData::asBroadcast() {
687 return isBroadcast() ? static_cast<ChannelData*>(this) : nullptr;
688 }
689
asBroadcast() const690 const ChannelData *PeerData::asBroadcast() const {
691 return isBroadcast()
692 ? static_cast<const ChannelData*>(this)
693 : nullptr;
694 }
695
asChatNotMigrated()696 ChatData *PeerData::asChatNotMigrated() {
697 if (const auto chat = asChat()) {
698 return chat->migrateTo() ? nullptr : chat;
699 }
700 return nullptr;
701 }
702
asChatNotMigrated() const703 const ChatData *PeerData::asChatNotMigrated() const {
704 if (const auto chat = asChat()) {
705 return chat->migrateTo() ? nullptr : chat;
706 }
707 return nullptr;
708 }
709
asChannelOrMigrated()710 ChannelData *PeerData::asChannelOrMigrated() {
711 if (const auto channel = asChannel()) {
712 return channel;
713 }
714 return migrateTo();
715 }
716
asChannelOrMigrated() const717 const ChannelData *PeerData::asChannelOrMigrated() const {
718 if (const auto channel = asChannel()) {
719 return channel;
720 }
721 return migrateTo();
722 }
723
migrateFrom() const724 ChatData *PeerData::migrateFrom() const {
725 if (const auto megagroup = asMegagroup()) {
726 return megagroup->amIn()
727 ? megagroup->getMigrateFromChat()
728 : nullptr;
729 }
730 return nullptr;
731 }
732
migrateTo() const733 ChannelData *PeerData::migrateTo() const {
734 if (const auto chat = asChat()) {
735 if (const auto result = chat->getMigrateToChannel()) {
736 return result->amIn() ? result : nullptr;
737 }
738 }
739 return nullptr;
740 }
741
migrateToOrMe()742 not_null<PeerData*> PeerData::migrateToOrMe() {
743 if (const auto channel = migrateTo()) {
744 return channel;
745 }
746 return this;
747 }
748
migrateToOrMe() const749 not_null<const PeerData*> PeerData::migrateToOrMe() const {
750 if (const auto channel = migrateTo()) {
751 return channel;
752 }
753 return this;
754 }
755
topBarNameText() const756 const Ui::Text::String &PeerData::topBarNameText() const {
757 if (const auto to = migrateTo()) {
758 return to->topBarNameText();
759 } else if (const auto user = asUser()) {
760 if (!user->phoneText.isEmpty()) {
761 return user->phoneText;
762 }
763 }
764 return _nameText;
765 }
766
nameText() const767 const Ui::Text::String &PeerData::nameText() const {
768 if (const auto to = migrateTo()) {
769 return to->nameText();
770 }
771 return _nameText;
772 }
773
shortName() const774 const QString &PeerData::shortName() const {
775 if (const auto user = asUser()) {
776 return user->firstName.isEmpty() ? user->lastName : user->firstName;
777 }
778 return name;
779 }
780
userName() const781 QString PeerData::userName() const {
782 if (const auto user = asUser()) {
783 return user->username;
784 } else if (const auto channel = asChannel()) {
785 return channel->username;
786 }
787 return QString();
788 }
789
isVerified() const790 bool PeerData::isVerified() const {
791 if (const auto user = asUser()) {
792 return user->isVerified();
793 } else if (const auto channel = asChannel()) {
794 return channel->isVerified();
795 }
796 return false;
797 }
798
isScam() const799 bool PeerData::isScam() const {
800 if (const auto user = asUser()) {
801 return user->isScam();
802 } else if (const auto channel = asChannel()) {
803 return channel->isScam();
804 }
805 return false;
806 }
807
isFake() const808 bool PeerData::isFake() const {
809 if (const auto user = asUser()) {
810 return user->isFake();
811 } else if (const auto channel = asChannel()) {
812 return channel->isFake();
813 }
814 return false;
815 }
816
isMegagroup() const817 bool PeerData::isMegagroup() const {
818 return isChannel() && asChannel()->isMegagroup();
819 }
820
isBroadcast() const821 bool PeerData::isBroadcast() const {
822 return isChannel() && asChannel()->isBroadcast();
823 }
824
isGigagroup() const825 bool PeerData::isGigagroup() const {
826 return isChannel() && asChannel()->isGigagroup();
827 }
828
isRepliesChat() const829 bool PeerData::isRepliesChat() const {
830 constexpr auto kProductionId = peerFromUser(1271266957);
831 constexpr auto kTestId = peerFromUser(708513);
832 if (id != kTestId && id != kProductionId) {
833 return false;
834 }
835 return ((session().mtp().environment() == MTP::Environment::Production)
836 ? kProductionId
837 : kTestId) == id;
838 }
839
canWrite() const840 bool PeerData::canWrite() const {
841 if (const auto user = asUser()) {
842 return user->canWrite();
843 } else if (const auto channel = asChannel()) {
844 return channel->canWrite();
845 } else if (const auto chat = asChat()) {
846 return chat->canWrite();
847 }
848 return false;
849 }
850
amRestricted(ChatRestriction right) const851 Data::RestrictionCheckResult PeerData::amRestricted(
852 ChatRestriction right) const {
853 using Result = Data::RestrictionCheckResult;
854 const auto allowByAdminRights = [](auto right, auto chat) -> bool {
855 if (right == ChatRestriction::InviteUsers) {
856 return chat->adminRights() & ChatAdminRight::InviteUsers;
857 } else if (right == ChatRestriction::ChangeInfo) {
858 return chat->adminRights() & ChatAdminRight::ChangeInfo;
859 } else if (right == ChatRestriction::PinMessages) {
860 return chat->adminRights() & ChatAdminRight::PinMessages;
861 } else {
862 return chat->hasAdminRights();
863 }
864 };
865 if (const auto channel = asChannel()) {
866 const auto defaultRestrictions = channel->defaultRestrictions()
867 | (channel->isPublic()
868 ? (ChatRestriction::PinMessages | ChatRestriction::ChangeInfo)
869 : ChatRestrictions(0));
870 return (channel->amCreator() || allowByAdminRights(right, channel))
871 ? Result::Allowed()
872 : (defaultRestrictions & right)
873 ? Result::WithEveryone()
874 : (channel->restrictions() & right)
875 ? Result::Explicit()
876 : Result::Allowed();
877 } else if (const auto chat = asChat()) {
878 return (chat->amCreator() || allowByAdminRights(right, chat))
879 ? Result::Allowed()
880 : (chat->defaultRestrictions() & right)
881 ? Result::WithEveryone()
882 : Result::Allowed();
883 }
884 return Result::Allowed();
885 }
886
amAnonymous() const887 bool PeerData::amAnonymous() const {
888 return isBroadcast()
889 || (isChannel()
890 && (asChannel()->adminRights() & ChatAdminRight::Anonymous));
891 }
892
canRevokeFullHistory() const893 bool PeerData::canRevokeFullHistory() const {
894 if (const auto user = asUser()) {
895 return !isSelf()
896 && (!user->isBot() || user->isSupport())
897 && session().serverConfig().revokePrivateInbox
898 && (session().serverConfig().revokePrivateTimeLimit == 0x7FFFFFFF);
899 } else if (const auto chat = asChat()) {
900 return chat->amCreator();
901 } else if (const auto megagroup = asMegagroup()) {
902 return megagroup->amCreator()
903 && megagroup->membersCountKnown()
904 && megagroup->canDelete();
905 }
906 return false;
907 }
908
slowmodeApplied() const909 bool PeerData::slowmodeApplied() const {
910 if (const auto channel = asChannel()) {
911 return !channel->amCreator()
912 && !channel->hasAdminRights()
913 && (channel->flags() & ChannelDataFlag::SlowmodeEnabled);
914 }
915 return false;
916 }
917
slowmodeAppliedValue() const918 rpl::producer<bool> PeerData::slowmodeAppliedValue() const {
919 using namespace rpl::mappers;
920 const auto channel = asChannel();
921 if (!channel) {
922 return rpl::single(false);
923 }
924
925 auto hasAdminRights = channel->adminRightsValue(
926 ) | rpl::map([=] {
927 return channel->hasAdminRights();
928 }) | rpl::distinct_until_changed();
929
930 auto slowmodeEnabled = channel->flagsValue(
931 ) | rpl::filter([=](const ChannelData::Flags::Change &change) {
932 return (change.diff & ChannelDataFlag::SlowmodeEnabled) != 0;
933 }) | rpl::map([=](const ChannelData::Flags::Change &change) {
934 return (change.value & ChannelDataFlag::SlowmodeEnabled) != 0;
935 }) | rpl::distinct_until_changed();
936
937 return rpl::combine(
938 std::move(hasAdminRights),
939 std::move(slowmodeEnabled),
940 !_1 && _2);
941 }
942
slowmodeSecondsLeft() const943 int PeerData::slowmodeSecondsLeft() const {
944 if (const auto channel = asChannel()) {
945 if (const auto seconds = channel->slowmodeSeconds()) {
946 if (const auto last = channel->slowmodeLastMessage()) {
947 const auto now = base::unixtime::now();
948 return std::max(seconds - (now - last), 0);
949 }
950 }
951 }
952 return 0;
953 }
954
canSendPolls() const955 bool PeerData::canSendPolls() const {
956 if (const auto user = asUser()) {
957 return user->isBot()
958 && !user->isRepliesChat()
959 && !user->isSupport();
960 } else if (const auto chat = asChat()) {
961 return chat->canSendPolls();
962 } else if (const auto channel = asChannel()) {
963 return channel->canSendPolls();
964 }
965 return false;
966 }
967
canManageGroupCall() const968 bool PeerData::canManageGroupCall() const {
969 if (const auto chat = asChat()) {
970 return chat->amCreator()
971 || (chat->adminRights() & ChatAdminRight::ManageCall);
972 } else if (const auto group = asChannel()) {
973 return group->amCreator()
974 || (group->adminRights() & ChatAdminRight::ManageCall);
975 }
976 return false;
977 }
978
groupCall() const979 Data::GroupCall *PeerData::groupCall() const {
980 if (const auto chat = asChat()) {
981 return chat->groupCall();
982 } else if (const auto group = asChannel()) {
983 return group->groupCall();
984 }
985 return nullptr;
986 }
987
groupCallDefaultJoinAs() const988 PeerId PeerData::groupCallDefaultJoinAs() const {
989 if (const auto chat = asChat()) {
990 return chat->groupCallDefaultJoinAs();
991 } else if (const auto group = asChannel()) {
992 return group->groupCallDefaultJoinAs();
993 }
994 return 0;
995 }
996
setThemeEmoji(const QString & emoticon)997 void PeerData::setThemeEmoji(const QString &emoticon) {
998 if (_themeEmoticon == emoticon) {
999 return;
1000 }
1001 if (Ui::Emoji::Find(_themeEmoticon) == Ui::Emoji::Find(emoticon)) {
1002 _themeEmoticon = emoticon;
1003 return;
1004 }
1005 _themeEmoticon = emoticon;
1006 if (!emoticon.isEmpty()
1007 && !owner().cloudThemes().themeForEmoji(emoticon)) {
1008 owner().cloudThemes().refreshChatThemes();
1009 }
1010 session().changes().peerUpdated(this, UpdateFlag::ChatThemeEmoji);
1011 }
1012
themeEmoji() const1013 const QString &PeerData::themeEmoji() const {
1014 return _themeEmoticon;
1015 }
1016
setIsBlocked(bool is)1017 void PeerData::setIsBlocked(bool is) {
1018 const auto status = is
1019 ? BlockStatus::Blocked
1020 : BlockStatus::NotBlocked;
1021 if (_blockStatus != status) {
1022 _blockStatus = status;
1023 if (const auto user = asUser()) {
1024 const auto flags = user->flags();
1025 if (is) {
1026 user->setFlags(flags | UserDataFlag::Blocked);
1027 } else {
1028 user->setFlags(flags & ~UserDataFlag::Blocked);
1029 }
1030 }
1031 session().changes().peerUpdated(this, UpdateFlag::IsBlocked);
1032 }
1033 }
1034
setLoadedStatus(LoadedStatus status)1035 void PeerData::setLoadedStatus(LoadedStatus status) {
1036 _loadedStatus = status;
1037 }
1038
messagesTTL() const1039 TimeId PeerData::messagesTTL() const {
1040 return _ttlPeriod;
1041 }
1042
setMessagesTTL(TimeId period)1043 void PeerData::setMessagesTTL(TimeId period) {
1044 if (_ttlPeriod != period) {
1045 _ttlPeriod = period;
1046 session().changes().peerUpdated(
1047 this,
1048 Data::PeerUpdate::Flag::MessagesTTL);
1049 }
1050 }
1051
1052 namespace Data {
1053
ListOfRestrictions()1054 std::vector<ChatRestrictions> ListOfRestrictions() {
1055 using Flag = ChatRestriction;
1056
1057 return {
1058 Flag::SendMessages,
1059 Flag::SendMedia,
1060 Flag::SendStickers
1061 | Flag::SendGifs
1062 | Flag::SendGames
1063 | Flag::SendInline,
1064 Flag::EmbedLinks,
1065 Flag::SendPolls,
1066 Flag::InviteUsers,
1067 Flag::PinMessages,
1068 Flag::ChangeInfo,
1069 };
1070 }
1071
RestrictionError(not_null<PeerData * > peer,ChatRestriction restriction)1072 std::optional<QString> RestrictionError(
1073 not_null<PeerData*> peer,
1074 ChatRestriction restriction) {
1075 using Flag = ChatRestriction;
1076 if (const auto restricted = peer->amRestricted(restriction)) {
1077 const auto all = restricted.isWithEveryone();
1078 const auto channel = peer->asChannel();
1079 if (!all && channel) {
1080 auto restrictedUntil = channel->restrictedUntil();
1081 if (restrictedUntil > 0 && !ChannelData::IsRestrictedForever(restrictedUntil)) {
1082 auto restrictedUntilDateTime = base::unixtime::parse(channel->restrictedUntil());
1083 auto date = restrictedUntilDateTime.toString(cDateFormat());
1084 auto time = restrictedUntilDateTime.toString(cTimeFormat());
1085
1086 switch (restriction) {
1087 case Flag::SendPolls:
1088 return tr::lng_restricted_send_polls_until(
1089 tr::now, lt_date, date, lt_time, time);
1090 case Flag::SendMessages:
1091 return tr::lng_restricted_send_message_until(
1092 tr::now, lt_date, date, lt_time, time);
1093 case Flag::SendMedia:
1094 return tr::lng_restricted_send_media_until(
1095 tr::now, lt_date, date, lt_time, time);
1096 case Flag::SendStickers:
1097 return tr::lng_restricted_send_stickers_until(
1098 tr::now, lt_date, date, lt_time, time);
1099 case Flag::SendGifs:
1100 return tr::lng_restricted_send_gifs_until(
1101 tr::now, lt_date, date, lt_time, time);
1102 case Flag::SendInline:
1103 case Flag::SendGames:
1104 return tr::lng_restricted_send_inline_until(
1105 tr::now, lt_date, date, lt_time, time);
1106 }
1107 Unexpected("Restriction in Data::RestrictionErrorKey.");
1108 }
1109 }
1110 switch (restriction) {
1111 case Flag::SendPolls:
1112 return all
1113 ? tr::lng_restricted_send_polls_all(tr::now)
1114 : tr::lng_restricted_send_polls(tr::now);
1115 case Flag::SendMessages:
1116 return all
1117 ? tr::lng_restricted_send_message_all(tr::now)
1118 : tr::lng_restricted_send_message(tr::now);
1119 case Flag::SendMedia:
1120 return all
1121 ? tr::lng_restricted_send_media_all(tr::now)
1122 : tr::lng_restricted_send_media(tr::now);
1123 case Flag::SendStickers:
1124 return all
1125 ? tr::lng_restricted_send_stickers_all(tr::now)
1126 : tr::lng_restricted_send_stickers(tr::now);
1127 case Flag::SendGifs:
1128 return all
1129 ? tr::lng_restricted_send_gifs_all(tr::now)
1130 : tr::lng_restricted_send_gifs(tr::now);
1131 case Flag::SendInline:
1132 case Flag::SendGames:
1133 return all
1134 ? tr::lng_restricted_send_inline_all(tr::now)
1135 : tr::lng_restricted_send_inline(tr::now);
1136 }
1137 Unexpected("Restriction in Data::RestrictionErrorKey.");
1138 }
1139 return std::nullopt;
1140 }
1141
SetTopPinnedMessageId(not_null<PeerData * > peer,MsgId messageId)1142 void SetTopPinnedMessageId(not_null<PeerData*> peer, MsgId messageId) {
1143 if (const auto channel = peer->asChannel()) {
1144 if (messageId <= channel->availableMinId()) {
1145 return;
1146 }
1147 }
1148 auto &session = peer->session();
1149 const auto hiddenId = session.settings().hiddenPinnedMessageId(peer->id);
1150 if (hiddenId != 0 && hiddenId != messageId) {
1151 session.settings().setHiddenPinnedMessageId(peer->id, 0);
1152 session.saveSettingsDelayed();
1153 }
1154 session.storage().add(Storage::SharedMediaAddExisting(
1155 peer->id,
1156 Storage::SharedMediaType::Pinned,
1157 messageId,
1158 { messageId, ServerMaxMsgId }));
1159 peer->owner().history(peer)->setHasPinnedMessages(true);
1160 }
1161
ResolveTopPinnedId(not_null<PeerData * > peer,PeerData * migrated)1162 FullMsgId ResolveTopPinnedId(
1163 not_null<PeerData*> peer,
1164 PeerData *migrated) {
1165 const auto slice = peer->session().storage().snapshot(
1166 Storage::SharedMediaQuery(
1167 Storage::SharedMediaKey(
1168 peer->id,
1169 Storage::SharedMediaType::Pinned,
1170 ServerMaxMsgId - 1),
1171 1,
1172 1));
1173 const auto old = migrated
1174 ? migrated->session().storage().snapshot(
1175 Storage::SharedMediaQuery(
1176 Storage::SharedMediaKey(
1177 migrated->id,
1178 Storage::SharedMediaType::Pinned,
1179 ServerMaxMsgId - 1),
1180 1,
1181 1))
1182 : Storage::SharedMediaResult{
1183 .count = 0,
1184 .skippedBefore = 0,
1185 .skippedAfter = 0,
1186 };
1187 if (!slice.messageIds.empty()) {
1188 return FullMsgId(peerToChannel(peer->id), slice.messageIds.back());
1189 } else if (!migrated || slice.count != 0 || old.messageIds.empty()) {
1190 return FullMsgId();
1191 } else {
1192 return FullMsgId(0, old.messageIds.back());
1193 }
1194 }
1195
ResolveMinPinnedId(not_null<PeerData * > peer,PeerData * migrated)1196 FullMsgId ResolveMinPinnedId(
1197 not_null<PeerData*> peer,
1198 PeerData *migrated) {
1199 const auto slice = peer->session().storage().snapshot(
1200 Storage::SharedMediaQuery(
1201 Storage::SharedMediaKey(
1202 peer->id,
1203 Storage::SharedMediaType::Pinned,
1204 1),
1205 1,
1206 1));
1207 const auto old = migrated
1208 ? migrated->session().storage().snapshot(
1209 Storage::SharedMediaQuery(
1210 Storage::SharedMediaKey(
1211 migrated->id,
1212 Storage::SharedMediaType::Pinned,
1213 1),
1214 1,
1215 1))
1216 : Storage::SharedMediaResult{
1217 .count = 0,
1218 .skippedBefore = 0,
1219 .skippedAfter = 0,
1220 };
1221 if (!old.messageIds.empty()) {
1222 return FullMsgId(0, old.messageIds.front());
1223 } else if (old.count == 0 && !slice.messageIds.empty()) {
1224 return FullMsgId(peerToChannel(peer->id), slice.messageIds.front());
1225 } else {
1226 return FullMsgId();
1227 }
1228 }
1229
ResolvePinnedCount(not_null<PeerData * > peer,PeerData * migrated)1230 std::optional<int> ResolvePinnedCount(
1231 not_null<PeerData*> peer,
1232 PeerData *migrated) {
1233 const auto slice = peer->session().storage().snapshot(
1234 Storage::SharedMediaQuery(
1235 Storage::SharedMediaKey(
1236 peer->id,
1237 Storage::SharedMediaType::Pinned,
1238 0),
1239 0,
1240 0));
1241 const auto old = migrated
1242 ? migrated->session().storage().snapshot(
1243 Storage::SharedMediaQuery(
1244 Storage::SharedMediaKey(
1245 migrated->id,
1246 Storage::SharedMediaType::Pinned,
1247 0),
1248 0,
1249 0))
1250 : Storage::SharedMediaResult{
1251 .count = 0,
1252 .skippedBefore = 0,
1253 .skippedAfter = 0,
1254 };
1255 return (slice.count.has_value() && old.count.has_value())
1256 ? std::make_optional(*slice.count + *old.count)
1257 : std::nullopt;
1258 }
1259
ChatAdminRightsFlags(const MTPChatAdminRights & rights)1260 ChatAdminRights ChatAdminRightsFlags(const MTPChatAdminRights &rights) {
1261 return rights.match([](const MTPDchatAdminRights &data) {
1262 return ChatAdminRights::from_raw(int32(data.vflags().v));
1263 });
1264 }
1265
ChatBannedRightsFlags(const MTPChatBannedRights & rights)1266 ChatRestrictions ChatBannedRightsFlags(const MTPChatBannedRights &rights) {
1267 return rights.match([](const MTPDchatBannedRights &data) {
1268 return ChatRestrictions::from_raw(int32(data.vflags().v));
1269 });
1270 }
1271
ChatBannedRightsUntilDate(const MTPChatBannedRights & rights)1272 TimeId ChatBannedRightsUntilDate(const MTPChatBannedRights &rights) {
1273 return rights.match([](const MTPDchatBannedRights &data) {
1274 return data.vuntil_date().v;
1275 });
1276 }
1277
1278 } // namespace Data
1279