api_invite_links.cpp 21 KB

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