"Fossies" - the Fresh Open Source Software Archive 
Member "tdesktop-2.6.1/Telegram/SourceFiles/api/api_invite_links.cpp" (24 Feb 2021, 17877 Bytes) of package /linux/misc/tdesktop-2.6.1.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "api_invite_links.cpp" see the
Fossies "Dox" file reference documentation and the last
Fossies "Diffs" side-by-side code changes report:
2.5.9_vs_2.6.0.
1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "api/api_invite_links.h"
9
10 #include "data/data_peer.h"
11 #include "data/data_user.h"
12 #include "data/data_chat.h"
13 #include "data/data_channel.h"
14 #include "data/data_session.h"
15 #include "data/data_changes.h"
16 #include "main/main_session.h"
17 #include "apiwrap.h"
18
19 namespace Api {
20 namespace {
21
22 constexpr auto kFirstPage = 10;
23 constexpr auto kPerPage = 50;
24 constexpr auto kJoinedFirstPage = 10;
25
26 void BringPermanentToFront(PeerInviteLinks &links) {
27 auto &list = links.links;
28 const auto i = ranges::find_if(list, [](const InviteLink &link) {
29 return link.permanent && !link.revoked;
30 });
31 if (i != end(list) && i != begin(list)) {
32 ranges::rotate(begin(list), i, i + 1);
33 }
34 }
35
36 void RemovePermanent(PeerInviteLinks &links) {
37 auto &list = links.links;
38 list.erase(ranges::remove_if(list, [](const InviteLink &link) {
39 return link.permanent && !link.revoked;
40 }), end(list));
41 }
42
43 } // namespace
44
45 JoinedByLinkSlice ParseJoinedByLinkSlice(
46 not_null<PeerData*> peer,
47 const MTPmessages_ChatInviteImporters &slice) {
48 auto result = JoinedByLinkSlice();
49 slice.match([&](const MTPDmessages_chatInviteImporters &data) {
50 auto &owner = peer->session().data();
51 owner.processUsers(data.vusers());
52 result.count = data.vcount().v;
53 result.users.reserve(data.vimporters().v.size());
54 for (const auto importer : data.vimporters().v) {
55 importer.match([&](const MTPDchatInviteImporter &data) {
56 result.users.push_back({
57 .user = owner.user(data.vuser_id().v),
58 .date = data.vdate().v,
59 });
60 });
61 }
62 });
63 return result;
64 }
65
66 InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
67 }
68
69 void InviteLinks::create(
70 not_null<PeerData*> peer,
71 Fn<void(Link)> done,
72 TimeId expireDate,
73 int usageLimit) {
74 performCreate(peer, std::move(done), false, expireDate, usageLimit);
75 }
76
77 void InviteLinks::performCreate(
78 not_null<PeerData*> peer,
79 Fn<void(Link)> done,
80 bool revokeLegacyPermanent,
81 TimeId expireDate,
82 int usageLimit) {
83 if (const auto i = _createCallbacks.find(peer)
84 ; i != end(_createCallbacks)) {
85 if (done) {
86 i->second.push_back(std::move(done));
87 }
88 return;
89 }
90 auto &callbacks = _createCallbacks[peer];
91 if (done) {
92 callbacks.push_back(std::move(done));
93 }
94
95 using Flag = MTPmessages_ExportChatInvite::Flag;
96 _api->request(MTPmessages_ExportChatInvite(
97 MTP_flags((revokeLegacyPermanent
98 ? Flag::f_legacy_revoke_permanent
99 : Flag(0))
100 | (expireDate ? Flag::f_expire_date : Flag(0))
101 | (usageLimit ? Flag::f_usage_limit : Flag(0))),
102 peer->input,
103 MTP_int(expireDate),
104 MTP_int(usageLimit)
105 )).done([=](const MTPExportedChatInvite &result) {
106 const auto callbacks = _createCallbacks.take(peer);
107 const auto link = prepend(peer, peer->session().user(), result);
108 if (callbacks) {
109 for (const auto &callback : *callbacks) {
110 callback(link);
111 }
112 }
113 }).fail([=](const RPCError &error) {
114 _createCallbacks.erase(peer);
115 }).send();
116 }
117
118 auto InviteLinks::lookupMyPermanent(not_null<PeerData*> peer) -> Link* {
119 auto i = _firstSlices.find(peer);
120 return (i != end(_firstSlices)) ? lookupMyPermanent(i->second) : nullptr;
121 }
122
123 auto InviteLinks::lookupMyPermanent(Links &links) -> Link* {
124 const auto first = links.links.begin();
125 return (first != end(links.links) && first->permanent && !first->revoked)
126 ? &*first
127 : nullptr;
128 }
129
130 auto InviteLinks::lookupMyPermanent(const Links &links) const -> const Link* {
131 const auto first = links.links.begin();
132 return (first != end(links.links) && first->permanent && !first->revoked)
133 ? &*first
134 : nullptr;
135 }
136
137 auto InviteLinks::prepend(
138 not_null<PeerData*> peer,
139 not_null<UserData*> admin,
140 const MTPExportedChatInvite &invite) -> Link {
141 const auto link = parse(peer, invite);
142 if (admin->isSelf()) {
143 prependMyToFirstSlice(peer, admin, link);
144 }
145 _updates.fire(Update{
146 .peer = peer,
147 .admin = admin,
148 .now = link
149 });
150 return link;
151 }
152
153 void InviteLinks::prependMyToFirstSlice(
154 not_null<PeerData*> peer,
155 not_null<UserData*> admin,
156 const Link &link) {
157 Expects(admin->isSelf());
158
159 auto i = _firstSlices.find(peer);
160 if (i == end(_firstSlices)) {
161 i = _firstSlices.emplace(peer).first;
162 }
163 auto &links = i->second;
164 const auto permanent = lookupMyPermanent(links);
165 const auto hadPermanent = (permanent != nullptr);
166 auto updateOldPermanent = Update{
167 .peer = peer,
168 .admin = admin,
169 };
170 if (link.permanent && hadPermanent) {
171 updateOldPermanent.was = permanent->link;
172 updateOldPermanent.now = *permanent;
173 updateOldPermanent.now->revoked = true;
174 links.links.erase(begin(links.links));
175 if (links.count > 0) {
176 --links.count;
177 }
178 }
179 // Must not dereference 'permanent' pointer after that.
180
181 ++links.count;
182 if (hadPermanent && !link.permanent) {
183 links.links.insert(begin(links.links) + 1, link);
184 } else {
185 links.links.insert(begin(links.links), link);
186 }
187
188 if (link.permanent) {
189 editPermanentLink(peer, link.link);
190 }
191 notify(peer);
192
193 if (updateOldPermanent.now) {
194 _updates.fire(std::move(updateOldPermanent));
195 }
196 }
197
198 void InviteLinks::edit(
199 not_null<PeerData*> peer,
200 not_null<UserData*> admin,
201 const QString &link,
202 TimeId expireDate,
203 int usageLimit,
204 Fn<void(Link)> done) {
205 performEdit(
206 peer,
207 admin,
208 link,
209 std::move(done),
210 false,
211 expireDate,
212 usageLimit);
213 }
214
215 void InviteLinks::performEdit(
216 not_null<PeerData*> peer,
217 not_null<UserData*> admin,
218 const QString &link,
219 Fn<void(Link)> done,
220 bool revoke,
221 TimeId expireDate,
222 int usageLimit) {
223 const auto key = LinkKey{ peer, link };
224 if (_deleteCallbacks.contains(key)) {
225 return;
226 } else if (const auto i = _editCallbacks.find(key)
227 ; i != end(_editCallbacks)) {
228 if (done) {
229 i->second.push_back(std::move(done));
230 }
231 return;
232 }
233
234 auto &callbacks = _editCallbacks[key];
235 if (done) {
236 callbacks.push_back(std::move(done));
237 }
238 using Flag = MTPmessages_EditExportedChatInvite::Flag;
239 _api->request(MTPmessages_EditExportedChatInvite(
240 MTP_flags((revoke ? Flag::f_revoked : Flag(0))
241 | (!revoke ? Flag::f_expire_date : Flag(0))
242 | (!revoke ? Flag::f_usage_limit : Flag(0))),
243 peer->input,
244 MTP_string(link),
245 MTP_int(expireDate),
246 MTP_int(usageLimit)
247 )).done([=](const MTPmessages_ExportedChatInvite &result) {
248 const auto callbacks = _editCallbacks.take(key);
249 const auto peer = key.peer;
250 result.match([&](const auto &data) {
251 _api->session().data().processUsers(data.vusers());
252 const auto link = parse(peer, data.vinvite());
253 auto i = _firstSlices.find(peer);
254 if (i != end(_firstSlices)) {
255 const auto j = ranges::find(
256 i->second.links,
257 key.link,
258 &Link::link);
259 if (j != end(i->second.links)) {
260 if (link.revoked && !j->revoked) {
261 i->second.links.erase(j);
262 if (i->second.count > 0) {
263 --i->second.count;
264 }
265 } else {
266 *j = link;
267 }
268 }
269 }
270 for (const auto &callback : *callbacks) {
271 callback(link);
272 }
273 _updates.fire(Update{
274 .peer = peer,
275 .admin = admin,
276 .was = key.link,
277 .now = link,
278 });
279
280 using Replaced = MTPDmessages_exportedChatInviteReplaced;
281 if constexpr (Replaced::Is<decltype(data)>()) {
282 prepend(peer, admin, data.vnew_invite());
283 }
284 });
285 }).fail([=](const RPCError &error) {
286 _editCallbacks.erase(key);
287 }).send();
288 }
289
290 void InviteLinks::revoke(
291 not_null<PeerData*> peer,
292 not_null<UserData*> admin,
293 const QString &link,
294 Fn<void(Link)> done) {
295 performEdit(peer, admin, link, std::move(done), true);
296 }
297
298 void InviteLinks::revokePermanent(
299 not_null<PeerData*> peer,
300 not_null<UserData*> admin,
301 const QString &link,
302 Fn<void()> done) {
303 const auto callback = [=](auto&&) { done(); };
304 if (!link.isEmpty()) {
305 performEdit(peer, admin, link, callback, true);
306 } else if (!admin->isSelf()) {
307 crl::on_main(&peer->session(), done);
308 } else {
309 performCreate(peer, callback, true);
310 }
311 }
312
313 void InviteLinks::destroy(
314 not_null<PeerData*> peer,
315 not_null<UserData*> admin,
316 const QString &link,
317 Fn<void()> done) {
318 const auto key = LinkKey{ peer, link };
319
320 if (const auto i = _deleteCallbacks.find(key)
321 ; i != end(_deleteCallbacks)) {
322 if (done) {
323 i->second.push_back(std::move(done));
324 }
325 return;
326 }
327
328 auto &callbacks = _deleteCallbacks[key];
329 if (done) {
330 callbacks.push_back(std::move(done));
331 }
332 _api->request(MTPmessages_DeleteExportedChatInvite(
333 peer->input,
334 MTP_string(link)
335 )).done([=](const MTPBool &result) {
336 const auto callbacks = _deleteCallbacks.take(key);
337 if (callbacks) {
338 for (const auto &callback : *callbacks) {
339 callback();
340 }
341 }
342 _updates.fire(Update{
343 .peer = peer,
344 .admin = admin,
345 .was = key.link,
346 });
347 }).fail([=](const RPCError &error) {
348 _deleteCallbacks.erase(key);
349 }).send();
350 }
351
352 void InviteLinks::destroyAllRevoked(
353 not_null<PeerData*> peer,
354 not_null<UserData*> admin,
355 Fn<void()> done) {
356 if (const auto i = _deleteRevokedCallbacks.find(peer)
357 ; i != end(_deleteRevokedCallbacks)) {
358 if (done) {
359 i->second.push_back(std::move(done));
360 }
361 return;
362 }
363 auto &callbacks = _deleteRevokedCallbacks[peer];
364 if (done) {
365 callbacks.push_back(std::move(done));
366 }
367 _api->request(MTPmessages_DeleteRevokedExportedChatInvites(
368 peer->input,
369 admin->inputUser
370 )).done([=](const MTPBool &result) {
371 if (const auto callbacks = _deleteRevokedCallbacks.take(peer)) {
372 for (const auto &callback : *callbacks) {
373 callback();
374 }
375 }
376 _allRevokedDestroyed.fire({ peer, admin });
377 }).fail([=](const RPCError &error) {
378 }).send();
379 }
380
381 void InviteLinks::requestMyLinks(not_null<PeerData*> peer) {
382 if (_firstSliceRequests.contains(peer)) {
383 return;
384 }
385 const auto requestId = _api->request(MTPmessages_GetExportedChatInvites(
386 MTP_flags(0),
387 peer->input,
388 MTP_inputUserSelf(),
389 MTPint(), // offset_date
390 MTPstring(), // offset_link
391 MTP_int(kFirstPage)
392 )).done([=](const MTPmessages_ExportedChatInvites &result) {
393 _firstSliceRequests.remove(peer);
394 auto slice = parseSlice(peer, result);
395 auto i = _firstSlices.find(peer);
396 const auto permanent = (i != end(_firstSlices))
397 ? lookupMyPermanent(i->second)
398 : nullptr;
399 if (!permanent) {
400 BringPermanentToFront(slice);
401 const auto j = _firstSlices.emplace_or_assign(
402 peer,
403 std::move(slice)).first;
404 if (const auto permanent = lookupMyPermanent(j->second)) {
405 editPermanentLink(peer, permanent->link);
406 }
407 } else {
408 RemovePermanent(slice);
409 auto &existing = i->second.links;
410 existing.erase(begin(existing) + 1, end(existing));
411 existing.insert(
412 end(existing),
413 begin(slice.links),
414 end(slice.links));
415 i->second.count = std::max(slice.count, int(existing.size()));
416 }
417 notify(peer);
418 }).fail([=](const RPCError &error) {
419 _firstSliceRequests.remove(peer);
420 }).send();
421 _firstSliceRequests.emplace(peer, requestId);
422 }
423
424 std::optional<JoinedByLinkSlice> InviteLinks::lookupJoinedFirstSlice(
425 LinkKey key) const {
426 const auto i = _firstJoined.find(key);
427 return (i != end(_firstJoined))
428 ? std::make_optional(i->second)
429 : std::nullopt;
430 }
431
432 std::optional<JoinedByLinkSlice> InviteLinks::joinedFirstSliceLoaded(
433 not_null<PeerData*> peer,
434 const QString &link) const {
435 return lookupJoinedFirstSlice({ peer, link });
436 }
437
438 rpl::producer<JoinedByLinkSlice> InviteLinks::joinedFirstSliceValue(
439 not_null<PeerData*> peer,
440 const QString &link,
441 int fullCount) {
442 const auto key = LinkKey{ peer, link };
443 auto current = lookupJoinedFirstSlice(key).value_or(JoinedByLinkSlice());
444 if (current.count == fullCount
445 && (!fullCount || !current.users.empty())) {
446 return rpl::single(current);
447 }
448 current.count = fullCount;
449 const auto remove = int(current.users.size()) - current.count;
450 if (remove > 0) {
451 current.users.erase(end(current.users) - remove, end(current.users));
452 }
453 requestJoinedFirstSlice(key);
454 using namespace rpl::mappers;
455 return rpl::single(
456 current
457 ) | rpl::then(_joinedFirstSliceLoaded.events(
458 ) | rpl::filter(
459 _1 == key
460 ) | rpl::map([=] {
461 return lookupJoinedFirstSlice(key).value_or(JoinedByLinkSlice());
462 }));
463 }
464
465 auto InviteLinks::updates(
466 not_null<PeerData*> peer,
467 not_null<UserData*> admin) const -> rpl::producer<Update> {
468 return _updates.events() | rpl::filter([=](const Update &update) {
469 return update.peer == peer && update.admin == admin;
470 });
471 }
472
473 rpl::producer<> InviteLinks::allRevokedDestroyed(
474 not_null<PeerData*> peer,
475 not_null<UserData*> admin) const {
476 return _allRevokedDestroyed.events(
477 ) | rpl::filter([=](const AllRevokedDestroyed &which) {
478 return which.peer == peer && which.admin == admin;
479 }) | rpl::to_empty;
480 }
481
482 void InviteLinks::requestJoinedFirstSlice(LinkKey key) {
483 if (_firstJoinedRequests.contains(key)) {
484 return;
485 }
486 const auto requestId = _api->request(MTPmessages_GetChatInviteImporters(
487 key.peer->input,
488 MTP_string(key.link),
489 MTP_int(0), // offset_date
490 MTP_inputUserEmpty(), // offset_user
491 MTP_int(kJoinedFirstPage)
492 )).done([=](const MTPmessages_ChatInviteImporters &result) {
493 _firstJoinedRequests.remove(key);
494 _firstJoined[key] = ParseJoinedByLinkSlice(key.peer, result);
495 _joinedFirstSliceLoaded.fire_copy(key);
496 }).fail([=](const RPCError &error) {
497 _firstJoinedRequests.remove(key);
498 }).send();
499 _firstJoinedRequests.emplace(key, requestId);
500 }
501
502 void InviteLinks::setMyPermanent(
503 not_null<PeerData*> peer,
504 const MTPExportedChatInvite &invite) {
505 auto link = parse(peer, invite);
506 if (!link.permanent) {
507 LOG(("API Error: "
508 "InviteLinks::setPermanent called with non-permanent link."));
509 return;
510 }
511 auto i = _firstSlices.find(peer);
512 if (i == end(_firstSlices)) {
513 i = _firstSlices.emplace(peer).first;
514 }
515 auto &links = i->second;
516 auto updateOldPermanent = Update{
517 .peer = peer,
518 .admin = peer->session().user(),
519 };
520 if (const auto permanent = lookupMyPermanent(links)) {
521 if (permanent->link == link.link) {
522 if (permanent->usage != link.usage) {
523 permanent->usage = link.usage;
524 _updates.fire(Update{
525 .peer = peer,
526 .admin = peer->session().user(),
527 .was = link.link,
528 .now = *permanent
529 });
530 }
531 return;
532 }
533 updateOldPermanent.was = permanent->link;
534 updateOldPermanent.now = *permanent;
535 updateOldPermanent.now->revoked = true;
536 links.links.erase(begin(links.links));
537 if (links.count > 0) {
538 --links.count;
539 }
540 }
541 links.links.insert(begin(links.links), link);
542
543 editPermanentLink(peer, link.link);
544 notify(peer);
545
546 if (updateOldPermanent.now) {
547 _updates.fire(std::move(updateOldPermanent));
548 }
549 _updates.fire(Update{
550 .peer = peer,
551 .admin = peer->session().user(),
552 .now = link
553 });
554 }
555
556 void InviteLinks::clearMyPermanent(not_null<PeerData*> peer) {
557 auto i = _firstSlices.find(peer);
558 if (i == end(_firstSlices)) {
559 return;
560 }
561 auto &links = i->second;
562 const auto permanent = lookupMyPermanent(links);
563 if (!permanent) {
564 return;
565 }
566
567 auto updateOldPermanent = Update{
568 .peer = peer,
569 .admin = peer->session().user()
570 };
571 updateOldPermanent.was = permanent->link;
572 updateOldPermanent.now = *permanent;
573 updateOldPermanent.now->revoked = true;
574 links.links.erase(begin(links.links));
575 if (links.count > 0) {
576 --links.count;
577 }
578
579 editPermanentLink(peer, QString());
580 notify(peer);
581
582 if (updateOldPermanent.now) {
583 _updates.fire(std::move(updateOldPermanent));
584 }
585 }
586
587 void InviteLinks::notify(not_null<PeerData*> peer) {
588 peer->session().changes().peerUpdated(
589 peer,
590 Data::PeerUpdate::Flag::InviteLinks);
591 }
592
593 auto InviteLinks::myLinks(not_null<PeerData*> peer) const -> const Links & {
594 static const auto kEmpty = Links();
595 const auto i = _firstSlices.find(peer);
596 return (i != end(_firstSlices)) ? i->second : kEmpty;
597 }
598
599 auto InviteLinks::parseSlice(
600 not_null<PeerData*> peer,
601 const MTPmessages_ExportedChatInvites &slice) const -> Links {
602 auto i = _firstSlices.find(peer);
603 const auto permanent = (i != end(_firstSlices))
604 ? lookupMyPermanent(i->second)
605 : nullptr;
606 auto result = Links();
607 slice.match([&](const MTPDmessages_exportedChatInvites &data) {
608 peer->session().data().processUsers(data.vusers());
609 result.count = data.vcount().v;
610 for (const auto &invite : data.vinvites().v) {
611 const auto link = parse(peer, invite);
612 if (!permanent || link.link != permanent->link) {
613 result.links.push_back(link);
614 }
615 }
616 });
617 return result;
618 }
619
620 auto InviteLinks::parse(
621 not_null<PeerData*> peer,
622 const MTPExportedChatInvite &invite) const -> Link {
623 return invite.match([&](const MTPDchatInviteExported &data) {
624 return Link{
625 .link = qs(data.vlink()),
626 .admin = peer->session().data().user(data.vadmin_id().v),
627 .date = data.vdate().v,
628 .startDate = data.vstart_date().value_or_empty(),
629 .expireDate = data.vexpire_date().value_or_empty(),
630 .usageLimit = data.vusage_limit().value_or_empty(),
631 .usage = data.vusage().value_or_empty(),
632 .permanent = data.is_permanent(),
633 .revoked = data.is_revoked(),
634 };
635 });
636 }
637
638 void InviteLinks::requestMoreLinks(
639 not_null<PeerData*> peer,
640 not_null<UserData*> admin,
641 TimeId lastDate,
642 const QString &lastLink,
643 bool revoked,
644 Fn<void(Links)> done) {
645 using Flag = MTPmessages_GetExportedChatInvites::Flag;
646 _api->request(MTPmessages_GetExportedChatInvites(
647 MTP_flags(Flag::f_offset_link
648 | (revoked ? Flag::f_revoked : Flag(0))),
649 peer->input,
650 admin->inputUser,
651 MTP_int(lastDate),
652 MTP_string(lastLink),
653 MTP_int(kPerPage)
654 )).done([=](const MTPmessages_ExportedChatInvites &result) {
655 done(parseSlice(peer, result));
656 }).fail([=](const RPCError &error) {
657 done(Links());
658 }).send();
659 }
660
661 void InviteLinks::editPermanentLink(
662 not_null<PeerData*> peer,
663 const QString &link) {
664 if (const auto chat = peer->asChat()) {
665 chat->setInviteLink(link);
666 } else if (const auto channel = peer->asChannel()) {
667 channel->setInviteLink(link);
668 } else {
669 Unexpected("Peer in InviteLinks::editMainLink.");
670 }
671 }
672
673 } // namespace Api