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