"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