data_message_reactions.cpp 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378
  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 "data/data_message_reactions.h"
  8. #include "api/api_global_privacy.h"
  9. #include "chat_helpers/stickers_lottie.h"
  10. #include "core/application.h"
  11. #include "history/history.h"
  12. #include "history/history_item.h"
  13. #include "history/history_item_components.h"
  14. #include "main/main_session.h"
  15. #include "main/main_app_config.h"
  16. #include "main/session/send_as_peers.h"
  17. #include "data/components/credits.h"
  18. #include "data/data_channel.h"
  19. #include "data/data_user.h"
  20. #include "data/data_session.h"
  21. #include "data/data_histories.h"
  22. #include "data/data_changes.h"
  23. #include "data/data_document.h"
  24. #include "data/data_document_media.h"
  25. #include "data/data_file_origin.h"
  26. #include "data/data_peer_values.h"
  27. #include "data/data_saved_sublist.h"
  28. #include "data/stickers/data_custom_emoji.h"
  29. #include "storage/localimageloader.h"
  30. #include "ui/image/image_location_factory.h"
  31. #include "ui/animated_icon.h"
  32. #include "mtproto/mtproto_config.h"
  33. #include "base/timer_rpl.h"
  34. #include "base/call_delayed.h"
  35. #include "base/unixtime.h"
  36. #include "apiwrap.h"
  37. #include "styles/style_chat.h"
  38. #include "base/random.h"
  39. namespace Data {
  40. namespace {
  41. constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000);
  42. constexpr auto kPollEach = 20 * crl::time(1000);
  43. constexpr auto kSizeForDownscale = 64;
  44. constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
  45. constexpr auto kRecentReactionsLimit = 40;
  46. constexpr auto kMyTagsRequestTimeout = crl::time(1000);
  47. constexpr auto kTopRequestDelay = 60 * crl::time(1000);
  48. constexpr auto kTopReactionsLimit = 14;
  49. constexpr auto kPaidAccumulatePeriod = 5 * crl::time(1000) + 500;
  50. [[nodiscard]] QString ReactionIdToLog(const ReactionId &id) {
  51. if (const auto custom = id.custom()) {
  52. return "custom:" + QString::number(custom);
  53. }
  54. return id.emoji();
  55. }
  56. [[nodiscard]] std::vector<ReactionId> ListFromMTP(
  57. const MTPDmessages_reactions &data) {
  58. const auto &list = data.vreactions().v;
  59. auto result = std::vector<ReactionId>();
  60. result.reserve(list.size());
  61. for (const auto &reaction : list) {
  62. const auto id = ReactionFromMTP(reaction);
  63. if (id.empty()) {
  64. LOG(("API Error: reactionEmpty in messages.reactions."));
  65. } else {
  66. result.push_back(id);
  67. }
  68. }
  69. return result;
  70. }
  71. [[nodiscard]] std::vector<MyTagInfo> ListFromMTP(
  72. const MTPDmessages_savedReactionTags &data) {
  73. const auto &list = data.vtags().v;
  74. auto result = std::vector<MyTagInfo>();
  75. result.reserve(list.size());
  76. for (const auto &reaction : list) {
  77. const auto &data = reaction.data();
  78. const auto id = ReactionFromMTP(data.vreaction());
  79. if (id.empty()) {
  80. LOG(("API Error: reactionEmpty in messages.reactions."));
  81. } else {
  82. result.push_back({
  83. .id = id,
  84. .title = qs(data.vtitle().value_or_empty()),
  85. .count = data.vcount().v,
  86. });
  87. }
  88. }
  89. return result;
  90. }
  91. [[nodiscard]] Reaction CustomReaction(not_null<DocumentData*> document) {
  92. return Reaction{
  93. .id = { { document->id } },
  94. .title = "Custom reaction",
  95. .appearAnimation = document,
  96. .selectAnimation = document,
  97. .centerIcon = document,
  98. .active = true,
  99. };
  100. }
  101. [[nodiscard]] int SentReactionsLimit(not_null<HistoryItem*> item) {
  102. const auto session = &item->history()->session();
  103. const auto config = &session->appConfig();
  104. return session->premium()
  105. ? config->get<int>("reactions_user_max_premium", 3)
  106. : config->get<int>("reactions_user_max_default", 1);
  107. }
  108. [[nodiscard]] bool IsMyRecent(
  109. const MTPDmessagePeerReaction &data,
  110. const ReactionId &id,
  111. not_null<PeerData*> peer,
  112. const base::flat_map<
  113. ReactionId,
  114. std::vector<RecentReaction>> &recent,
  115. bool min) {
  116. if (peer->isSelf()) {
  117. return true;
  118. } else if (!min) {
  119. return data.is_my();
  120. }
  121. const auto j = recent.find(id);
  122. if (j == end(recent)) {
  123. return false;
  124. }
  125. const auto k = ranges::find(
  126. j->second,
  127. peer,
  128. &RecentReaction::peer);
  129. return (k != end(j->second)) && k->my;
  130. }
  131. [[nodiscard]] bool IsMyTop(
  132. const MTPDmessageReactor &data,
  133. PeerData *peer,
  134. const std::vector<MessageReactionsTopPaid> &top,
  135. bool min) {
  136. if (peer && peer->isSelf()) {
  137. return true;
  138. } else if (!min) {
  139. return data.is_my();
  140. }
  141. const auto i = ranges::find(top, peer, &MessageReactionsTopPaid::peer);
  142. return (i != end(top)) && i->my;
  143. }
  144. [[nodiscard]] std::optional<PeerId> MaybeShownPeer(
  145. uint32 privacySet,
  146. PeerId shownPeer) {
  147. return privacySet ? shownPeer : std::optional<PeerId>();
  148. }
  149. [[nodiscard]] MTPPaidReactionPrivacy PaidReactionShownPeerToTL(
  150. not_null<Main::Session*> session,
  151. std::optional<PeerId> shownPeer) {
  152. return !shownPeer
  153. ? MTPPaidReactionPrivacy()
  154. : !*shownPeer
  155. ? MTP_paidReactionPrivacyAnonymous()
  156. : (*shownPeer == session->userPeerId())
  157. ? MTP_paidReactionPrivacyDefault()
  158. : MTP_paidReactionPrivacyPeer(
  159. session->data().peer(*shownPeer)->input);
  160. }
  161. } // namespace
  162. PossibleItemReactionsRef LookupPossibleReactions(
  163. not_null<HistoryItem*> item,
  164. bool paidInFront) {
  165. if (!item->canReact()) {
  166. return {};
  167. }
  168. auto result = PossibleItemReactionsRef();
  169. auto peer = item->history()->peer;
  170. if (item->isDiscussionPost()) {
  171. if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
  172. if (forwarded->savedFromPeer) {
  173. peer = forwarded->savedFromPeer;
  174. }
  175. }
  176. }
  177. const auto session = &peer->session();
  178. if (const auto channel = peer->asChannel()) {
  179. if ((!channel->amCreator())
  180. && (channel->adminRights() & ChatAdminRight::Anonymous)
  181. && (session->sendAsPeers().resolveChosen(channel) == channel)) {
  182. return {};
  183. }
  184. }
  185. const auto reactions = &session->data().reactions();
  186. const auto &full = reactions->list(Reactions::Type::Active);
  187. const auto &top = reactions->list(Reactions::Type::Top);
  188. const auto &recent = reactions->list(Reactions::Type::Recent);
  189. const auto &myTags = reactions->list(Reactions::Type::MyTags);
  190. const auto &tags = reactions->list(Reactions::Type::Tags);
  191. const auto &all = item->reactions();
  192. const auto &allowed = PeerAllowedReactions(peer);
  193. const auto limit = UniqueReactionsLimit(peer);
  194. const auto premiumPossible = session->premiumPossible();
  195. const auto limited = (all.size() >= limit) && [&] {
  196. const auto my = item->chosenReactions();
  197. if (my.empty()) {
  198. return true;
  199. }
  200. return true; // #TODO reactions
  201. }();
  202. auto added = base::flat_set<ReactionId>();
  203. const auto add = [&](auto predicate) {
  204. auto &&all = ranges::views::concat(top, recent, full);
  205. for (const auto &reaction : all) {
  206. if (predicate(reaction)) {
  207. if (added.emplace(reaction.id).second) {
  208. result.recent.push_back(&reaction);
  209. }
  210. }
  211. }
  212. };
  213. reactions->clearTemporary();
  214. if (item->reactionsAreTags()) {
  215. auto &&all = ranges::views::concat(myTags, tags);
  216. result.recent.reserve(myTags.size() + tags.size());
  217. for (const auto &reaction : all) {
  218. if (premiumPossible
  219. || ranges::contains(tags, reaction.id, &Reaction::id)) {
  220. if (added.emplace(reaction.id).second) {
  221. result.recent.push_back(&reaction);
  222. }
  223. }
  224. }
  225. result.customAllowed = premiumPossible;
  226. result.tags = true;
  227. } else if (limited) {
  228. result.recent.reserve((allowed.paidEnabled ? 1 : 0) + all.size());
  229. add([&](const Reaction &reaction) {
  230. return ranges::contains(all, reaction.id, &MessageReaction::id);
  231. });
  232. for (const auto &reaction : all) {
  233. const auto id = reaction.id;
  234. if (added.emplace(id).second) {
  235. if (const auto temp = reactions->lookupTemporary(id)) {
  236. result.recent.push_back(temp);
  237. }
  238. }
  239. }
  240. if (allowed.paidEnabled
  241. && !added.contains(Data::ReactionId::Paid())) {
  242. result.recent.push_back(reactions->lookupPaid());
  243. }
  244. } else {
  245. result.recent.reserve((allowed.paidEnabled ? 1 : 0)
  246. + ((allowed.type == AllowedReactionsType::Some)
  247. ? allowed.some.size()
  248. : full.size()));
  249. if (allowed.paidEnabled) {
  250. result.recent.push_back(reactions->lookupPaid());
  251. }
  252. add([&](const Reaction &reaction) {
  253. const auto id = reaction.id;
  254. if (id.custom() && !premiumPossible) {
  255. return false;
  256. } else if ((allowed.type == AllowedReactionsType::Some)
  257. && !ranges::contains(allowed.some, id)) {
  258. return false;
  259. } else if (id.custom()
  260. && allowed.type == AllowedReactionsType::Default) {
  261. return false;
  262. }
  263. return true;
  264. });
  265. if (allowed.type == AllowedReactionsType::Some) {
  266. for (const auto &id : allowed.some) {
  267. if (!added.contains(id)) {
  268. if (const auto temp = reactions->lookupTemporary(id)) {
  269. result.recent.push_back(temp);
  270. }
  271. }
  272. }
  273. }
  274. result.customAllowed = (allowed.type == AllowedReactionsType::All)
  275. && premiumPossible;
  276. const auto favoriteId = reactions->favoriteId();
  277. if (favoriteId.custom()
  278. && result.customAllowed
  279. && !ranges::contains(result.recent, favoriteId, &Reaction::id)) {
  280. if (const auto temp = reactions->lookupTemporary(favoriteId)) {
  281. result.recent.insert(begin(result.recent), temp);
  282. }
  283. }
  284. }
  285. if (!item->reactionsAreTags()) {
  286. const auto toFront = [&](Data::ReactionId id) {
  287. const auto i = ranges::find(result.recent, id, &Reaction::id);
  288. if (i != end(result.recent) && i != begin(result.recent)) {
  289. std::rotate(begin(result.recent), i, i + 1);
  290. }
  291. };
  292. toFront(reactions->favoriteId());
  293. if (paidInFront) {
  294. toFront(Data::ReactionId::Paid());
  295. }
  296. }
  297. return result;
  298. }
  299. PossibleItemReactions::PossibleItemReactions(
  300. const PossibleItemReactionsRef &other)
  301. : recent(other.recent | ranges::views::transform([](const auto &value) {
  302. return *value;
  303. }) | ranges::to_vector)
  304. , stickers(other.stickers | ranges::views::transform([](const auto &value) {
  305. return *value;
  306. }) | ranges::to_vector)
  307. , customAllowed(other.customAllowed)
  308. , tags(other.tags){
  309. }
  310. Reactions::Reactions(not_null<Session*> owner)
  311. : _owner(owner)
  312. , _topRefreshTimer([=] { refreshTop(); })
  313. , _repaintTimer([=] { repaintCollected(); })
  314. , _sendPaidTimer([=] { sendPaid(); }) {
  315. refreshDefault();
  316. _myTags.emplace(nullptr);
  317. base::timer_each(
  318. kRefreshFullListEach
  319. ) | rpl::start_with_next([=] {
  320. refreshDefault();
  321. requestEffects();
  322. }, _lifetime);
  323. _owner->session().changes().messageUpdates(
  324. MessageUpdate::Flag::Destroyed
  325. ) | rpl::start_with_next([=](const MessageUpdate &update) {
  326. const auto item = update.item;
  327. _pollingItems.remove(item);
  328. _pollItems.remove(item);
  329. _repaintItems.remove(item);
  330. _sendPaidItems.remove(item);
  331. if (const auto i = _sendingPaid.find(item)
  332. ; i != end(_sendingPaid)) {
  333. _sendingPaid.erase(i);
  334. _owner->session().credits().invalidate();
  335. crl::on_main(&_owner->session(), [=] {
  336. sendPaid();
  337. });
  338. }
  339. }, _lifetime);
  340. crl::on_main(&owner->session(), [=] {
  341. // applyFavorite accesses not yet constructed parts of session.
  342. rpl::single(rpl::empty) | rpl::then(
  343. _owner->session().mtp().config().updates()
  344. ) | rpl::map([=] {
  345. const auto &config = _owner->session().mtp().configValues();
  346. return config.reactionDefaultCustom
  347. ? ReactionId{ DocumentId(config.reactionDefaultCustom) }
  348. : ReactionId{ config.reactionDefaultEmoji };
  349. }) | rpl::filter([=](const ReactionId &id) {
  350. return !_saveFaveRequestId;
  351. }) | rpl::start_with_next([=](ReactionId &&id) {
  352. applyFavorite(id);
  353. }, _lifetime);
  354. });
  355. }
  356. Reactions::~Reactions() = default;
  357. Main::Session &Reactions::session() const {
  358. return _owner->session();
  359. }
  360. void Reactions::refreshTop() {
  361. requestTop();
  362. }
  363. void Reactions::refreshRecent() {
  364. requestRecent();
  365. }
  366. void Reactions::refreshRecentDelayed() {
  367. if (_recentRequestId || _recentRequestScheduled) {
  368. return;
  369. }
  370. _recentRequestScheduled = true;
  371. base::call_delayed(kRecentRequestTimeout, &_owner->session(), [=] {
  372. if (_recentRequestScheduled) {
  373. requestRecent();
  374. }
  375. });
  376. }
  377. void Reactions::refreshDefault() {
  378. requestDefault();
  379. }
  380. void Reactions::refreshMyTags(SavedSublist *sublist) {
  381. requestMyTags(sublist);
  382. }
  383. void Reactions::refreshMyTagsDelayed() {
  384. auto &my = _myTags[nullptr];
  385. if (my.requestId || my.requestScheduled) {
  386. return;
  387. }
  388. my.requestScheduled = true;
  389. base::call_delayed(kMyTagsRequestTimeout, &_owner->session(), [=] {
  390. if (_myTags[nullptr].requestScheduled) {
  391. requestMyTags();
  392. }
  393. });
  394. }
  395. void Reactions::refreshTags() {
  396. requestTags();
  397. }
  398. void Reactions::refreshEffects() {
  399. if (_effects.empty()) {
  400. requestEffects();
  401. }
  402. }
  403. const std::vector<Reaction> &Reactions::list(Type type) const {
  404. switch (type) {
  405. case Type::Active: return _active;
  406. case Type::Recent: return _recent;
  407. case Type::Top: return _top;
  408. case Type::All: return _available;
  409. case Type::MyTags:
  410. return _myTags.find((SavedSublist*)nullptr)->second.tags;
  411. case Type::Tags: return _tags;
  412. case Type::Effects: return _effects;
  413. }
  414. Unexpected("Type in Reactions::list.");
  415. }
  416. const std::vector<MyTagInfo> &Reactions::myTagsInfo() const {
  417. return _myTags.find((SavedSublist*)nullptr)->second.info;
  418. }
  419. const QString &Reactions::myTagTitle(const ReactionId &id) const {
  420. const auto i = _myTags.find((SavedSublist*)nullptr);
  421. if (i != end(_myTags)) {
  422. const auto j = ranges::find(i->second.info, id, &MyTagInfo::id);
  423. if (j != end(i->second.info)) {
  424. return j->title;
  425. }
  426. }
  427. static const auto kEmpty = QString();
  428. return kEmpty;
  429. }
  430. ReactionId Reactions::favoriteId() const {
  431. return _favoriteId;
  432. }
  433. const Reaction *Reactions::favorite() const {
  434. return _favorite ? &*_favorite : nullptr;
  435. }
  436. void Reactions::setFavorite(const ReactionId &id) {
  437. const auto api = &_owner->session().api();
  438. if (_saveFaveRequestId) {
  439. api->request(_saveFaveRequestId).cancel();
  440. }
  441. _saveFaveRequestId = api->request(MTPmessages_SetDefaultReaction(
  442. ReactionToMTP(id)
  443. )).done([=] {
  444. _saveFaveRequestId = 0;
  445. }).fail([=] {
  446. _saveFaveRequestId = 0;
  447. }).send();
  448. applyFavorite(id);
  449. }
  450. void Reactions::incrementMyTag(const ReactionId &id, SavedSublist *sublist) {
  451. if (sublist) {
  452. incrementMyTag(id, nullptr);
  453. }
  454. auto &my = _myTags[sublist];
  455. auto i = ranges::find(my.info, id, &MyTagInfo::id);
  456. if (i == end(my.info)) {
  457. my.info.push_back({ .id = id, .count = 0 });
  458. i = end(my.info) - 1;
  459. }
  460. ++i->count;
  461. while (i != begin(my.info)) {
  462. auto j = i - 1;
  463. if (j->count >= i->count) {
  464. break;
  465. }
  466. std::swap(*i, *j);
  467. i = j;
  468. }
  469. scheduleMyTagsUpdate(sublist);
  470. }
  471. void Reactions::decrementMyTag(const ReactionId &id, SavedSublist *sublist) {
  472. if (sublist) {
  473. decrementMyTag(id, nullptr);
  474. }
  475. auto &my = _myTags[sublist];
  476. auto i = ranges::find(my.info, id, &MyTagInfo::id);
  477. if (i != end(my.info) && i->count > 0) {
  478. --i->count;
  479. while (i + 1 != end(my.info)) {
  480. auto j = i + 1;
  481. if (j->count <= i->count) {
  482. break;
  483. }
  484. std::swap(*i, *j);
  485. i = j;
  486. }
  487. }
  488. scheduleMyTagsUpdate(sublist);
  489. }
  490. void Reactions::renameTag(const ReactionId &id, const QString &name) {
  491. auto changed = false;
  492. for (auto &[sublist, my] : _myTags) {
  493. auto i = ranges::find(my.info, id, &MyTagInfo::id);
  494. if (i == end(my.info) || i->title == name) {
  495. continue;
  496. }
  497. i->title = name;
  498. changed = true;
  499. scheduleMyTagsUpdate(sublist);
  500. }
  501. if (!changed) {
  502. return;
  503. }
  504. _myTagRenamed.fire_copy(id);
  505. using Flag = MTPmessages_UpdateSavedReactionTag::Flag;
  506. _owner->session().api().request(MTPmessages_UpdateSavedReactionTag(
  507. MTP_flags(name.isEmpty() ? Flag(0) : Flag::f_title),
  508. ReactionToMTP(id),
  509. MTP_string(name)
  510. )).send();
  511. }
  512. void Reactions::scheduleMyTagsUpdate(SavedSublist *sublist) {
  513. auto &my = _myTags[sublist];
  514. my.updateScheduled = true;
  515. crl::on_main(&session(), [=] {
  516. auto &my = _myTags[sublist];
  517. if (!my.updateScheduled) {
  518. return;
  519. }
  520. my.updateScheduled = false;
  521. my.tags = resolveByInfos(my.info, _unresolvedMyTags, sublist);
  522. _myTagsUpdated.fire_copy(sublist);
  523. });
  524. }
  525. DocumentData *Reactions::chooseGenericAnimation(
  526. not_null<DocumentData*> custom) const {
  527. const auto sticker = custom->sticker();
  528. const auto i = sticker
  529. ? ranges::find(
  530. _available,
  531. ::Data::ReactionId{ { sticker->alt } },
  532. &::Data::Reaction::id)
  533. : end(_available);
  534. if (i != end(_available) && i->aroundAnimation) {
  535. const auto view = i->aroundAnimation->createMediaView();
  536. view->checkStickerLarge();
  537. if (view->loaded()) {
  538. return i->aroundAnimation;
  539. }
  540. }
  541. return randomLoadedFrom(_genericAnimations);
  542. }
  543. void Reactions::fillPaidReactionAnimations() const {
  544. const auto generate = [&](int index) {
  545. const auto session = &_owner->session();
  546. const auto name = u"star_reaction_effect%1"_q.arg(index + 1);
  547. return ChatHelpers::GenerateLocalTgsSticker(session, name);
  548. };
  549. const auto kCount = 3;
  550. for (auto i = 0; i != kCount; ++i) {
  551. const auto document = generate(i);
  552. _paidReactionAnimations.push_back(document);
  553. _paidReactionCache.emplace(
  554. document,
  555. document->createMediaView());
  556. }
  557. _paidReactionCache.front().second->checkStickerLarge();
  558. }
  559. DocumentData *Reactions::choosePaidReactionAnimation() const {
  560. if (_paidReactionAnimations.empty()) {
  561. fillPaidReactionAnimations();
  562. }
  563. return randomLoadedFrom(_paidReactionAnimations);
  564. }
  565. DocumentData *Reactions::randomLoadedFrom(
  566. std::vector<not_null<DocumentData*>> list) const {
  567. if (list.empty()) {
  568. return nullptr;
  569. }
  570. ranges::shuffle(list);
  571. const auto first = list.front();
  572. const auto view = first->createMediaView();
  573. view->checkStickerLarge();
  574. if (view->loaded()) {
  575. return first;
  576. }
  577. const auto k = ranges::find_if(list, [&](not_null<DocumentData*> value) {
  578. return value->createMediaView()->loaded();
  579. });
  580. return (k != end(list)) ? (*k) : first;
  581. }
  582. void Reactions::applyFavorite(const ReactionId &id) {
  583. if (_favoriteId != id) {
  584. _favoriteId = id;
  585. _favorite = resolveById(_favoriteId);
  586. if (!_favorite && _unresolvedFavoriteId != _favoriteId) {
  587. _unresolvedFavoriteId = _favoriteId;
  588. resolve(_favoriteId);
  589. }
  590. _favoriteUpdated.fire({});
  591. }
  592. }
  593. rpl::producer<> Reactions::topUpdates() const {
  594. return _topUpdated.events();
  595. }
  596. rpl::producer<> Reactions::recentUpdates() const {
  597. return _recentUpdated.events();
  598. }
  599. rpl::producer<> Reactions::defaultUpdates() const {
  600. return _defaultUpdated.events();
  601. }
  602. rpl::producer<> Reactions::favoriteUpdates() const {
  603. return _favoriteUpdated.events();
  604. }
  605. rpl::producer<> Reactions::myTagsUpdates() const {
  606. return _myTagsUpdated.events(
  607. ) | rpl::filter(
  608. !rpl::mappers::_1
  609. ) | rpl::to_empty;
  610. }
  611. rpl::producer<> Reactions::tagsUpdates() const {
  612. return _tagsUpdated.events();
  613. }
  614. rpl::producer<ReactionId> Reactions::myTagRenamed() const {
  615. return _myTagRenamed.events();
  616. }
  617. rpl::producer<> Reactions::effectsUpdates() const {
  618. return _effectsUpdated.events();
  619. }
  620. void Reactions::preloadReactionImageFor(const ReactionId &emoji) {
  621. if (emoji.paid() || !emoji.emoji().isEmpty()) {
  622. preloadImageFor(emoji);
  623. }
  624. }
  625. void Reactions::preloadEffectImageFor(EffectId id) {
  626. if (id != kFakeEffectId) {
  627. preloadImageFor({ DocumentId(id) });
  628. }
  629. }
  630. void Reactions::preloadImageFor(const ReactionId &id) {
  631. if (_images.contains(id)) {
  632. return;
  633. }
  634. auto &set = _images.emplace(id).first->second;
  635. set.effect = (id.custom() != 0);
  636. if (id.paid()) {
  637. loadImage(set, lookupPaid()->centerIcon, true);
  638. return;
  639. }
  640. auto &list = set.effect ? _effects : _available;
  641. const auto i = ranges::find(list, id, &Reaction::id);
  642. const auto document = (i == end(list))
  643. ? nullptr
  644. : i->centerIcon
  645. ? i->centerIcon
  646. : i->selectAnimation.get();
  647. if (document || (set.effect && i != end(list))) {
  648. if (!set.effect || i->centerIcon) {
  649. loadImage(set, document, !i->centerIcon);
  650. } else {
  651. generateImage(set, i->title);
  652. }
  653. if (set.effect) {
  654. preloadEffect(*i);
  655. }
  656. } else if (set.effect && !_waitingForEffects) {
  657. _waitingForEffects = true;
  658. refreshEffects();
  659. } else if (!set.effect && !_waitingForReactions) {
  660. _waitingForReactions = true;
  661. refreshDefault();
  662. }
  663. }
  664. void Reactions::preloadEffect(const Reaction &effect) {
  665. if (effect.aroundAnimation) {
  666. effect.aroundAnimation->createMediaView()->checkStickerLarge();
  667. } else {
  668. const auto premium = effect.selectAnimation;
  669. premium->loadVideoThumbnail(premium->stickerSetOrigin());
  670. }
  671. }
  672. void Reactions::preloadAnimationsFor(const ReactionId &id) {
  673. const auto preload = [&](DocumentData *document) {
  674. const auto view = document
  675. ? document->activeMediaView()
  676. : nullptr;
  677. if (view) {
  678. view->checkStickerLarge();
  679. }
  680. };
  681. if (id.paid()) {
  682. const auto fake = lookupPaid();
  683. preload(fake->centerIcon);
  684. preload(fake->aroundAnimation);
  685. return;
  686. }
  687. const auto custom = id.custom();
  688. const auto document = custom ? _owner->document(custom).get() : nullptr;
  689. const auto customSticker = document ? document->sticker() : nullptr;
  690. const auto findId = custom
  691. ? ReactionId{ { customSticker ? customSticker->alt : QString() } }
  692. : id;
  693. const auto i = ranges::find(_available, findId, &Reaction::id);
  694. if (i == end(_available)) {
  695. return;
  696. }
  697. if (!custom) {
  698. preload(i->centerIcon);
  699. }
  700. preload(i->aroundAnimation);
  701. }
  702. QImage Reactions::resolveReactionImageFor(const ReactionId &emoji) {
  703. Expects(!emoji.custom());
  704. return resolveImageFor(emoji);
  705. }
  706. QImage Reactions::resolveEffectImageFor(EffectId id) {
  707. return (id == kFakeEffectId)
  708. ? QImage()
  709. : resolveImageFor({ DocumentId(id) });
  710. }
  711. QImage Reactions::resolveImageFor(const ReactionId &id) {
  712. auto i = _images.find(id);
  713. if (i == end(_images)) {
  714. preloadImageFor(id);
  715. i = _images.find(id);
  716. Assert(i != end(_images));
  717. }
  718. auto &set = i->second;
  719. set.effect = (id.custom() != 0);
  720. const auto resolve = [&](QImage &image, int size) {
  721. const auto factor = style::DevicePixelRatio();
  722. const auto frameSize = set.fromSelectAnimation
  723. ? (size / 2)
  724. : size;
  725. // Must not be colored to text.
  726. image = set.icon->frame(QColor()).scaled(
  727. frameSize * factor,
  728. frameSize * factor,
  729. Qt::IgnoreAspectRatio,
  730. Qt::SmoothTransformation);
  731. if (set.fromSelectAnimation) {
  732. auto result = QImage(
  733. size * factor,
  734. size * factor,
  735. QImage::Format_ARGB32_Premultiplied);
  736. result.fill(Qt::transparent);
  737. auto p = QPainter(&result);
  738. p.drawImage(
  739. (size - frameSize) * factor / 2,
  740. (size - frameSize) * factor / 2,
  741. image);
  742. p.end();
  743. std::swap(result, image);
  744. }
  745. image.setDevicePixelRatio(factor);
  746. };
  747. if (set.image.isNull() && set.icon) {
  748. resolve(
  749. set.image,
  750. set.effect ? st::effectInfoImage : st::reactionInlineImage);
  751. crl::async([icon = std::move(set.icon)]{});
  752. }
  753. return set.image;
  754. }
  755. void Reactions::resolveReactionImages() {
  756. for (auto &[id, set] : _images) {
  757. if (set.effect || !set.image.isNull() || set.icon || set.media) {
  758. continue;
  759. }
  760. const auto i = ranges::find(_available, id, &Reaction::id);
  761. const auto document = (i == end(_available))
  762. ? nullptr
  763. : i->centerIcon
  764. ? i->centerIcon
  765. : i->selectAnimation.get();
  766. if (document) {
  767. loadImage(set, document, !i->centerIcon);
  768. } else {
  769. LOG(("API Error: Reaction '%1' not found!"
  770. ).arg(ReactionIdToLog(id)));
  771. }
  772. }
  773. }
  774. void Reactions::resolveEffectImages() {
  775. for (auto &[id, set] : _images) {
  776. if (!set.effect || !set.image.isNull() || set.icon || set.media) {
  777. continue;
  778. }
  779. const auto i = ranges::find(_effects, id, &Reaction::id);
  780. const auto document = (i == end(_effects))
  781. ? nullptr
  782. : i->centerIcon
  783. ? i->centerIcon
  784. : nullptr;
  785. if (document) {
  786. loadImage(set, document, false);
  787. } else if (i != end(_effects)) {
  788. generateImage(set, i->title);
  789. } else {
  790. LOG(("API Error: Effect '%1' not found!"
  791. ).arg(ReactionIdToLog(id)));
  792. }
  793. if (i != end(_effects)) {
  794. preloadEffect(*i);
  795. }
  796. }
  797. }
  798. void Reactions::loadImage(
  799. ImageSet &set,
  800. not_null<DocumentData*> document,
  801. bool fromSelectAnimation) {
  802. if (!set.image.isNull() || set.icon) {
  803. return;
  804. } else if (!set.media) {
  805. if (!set.effect) {
  806. set.fromSelectAnimation = fromSelectAnimation;
  807. }
  808. set.media = document->createMediaView();
  809. set.media->checkStickerLarge();
  810. }
  811. if (set.media->loaded()) {
  812. setAnimatedIcon(set);
  813. } else if (!_imagesLoadLifetime) {
  814. document->session().downloaderTaskFinished(
  815. ) | rpl::start_with_next([=] {
  816. downloadTaskFinished();
  817. }, _imagesLoadLifetime);
  818. }
  819. }
  820. void Reactions::generateImage(ImageSet &set, const QString &emoji) {
  821. Expects(set.effect);
  822. const auto e = Ui::Emoji::Find(emoji);
  823. Assert(e != nullptr);
  824. const auto large = Ui::Emoji::GetSizeLarge();
  825. const auto factor = style::DevicePixelRatio();
  826. auto image = QImage(large, large, QImage::Format_ARGB32_Premultiplied);
  827. image.setDevicePixelRatio(factor);
  828. image.fill(Qt::transparent);
  829. {
  830. QPainter p(&image);
  831. Ui::Emoji::Draw(p, e, large, 0, 0);
  832. }
  833. const auto size = st::effectInfoImage;
  834. set.image = image.scaled(size * factor, size * factor);
  835. set.image.setDevicePixelRatio(factor);
  836. }
  837. void Reactions::setAnimatedIcon(ImageSet &set) {
  838. const auto size = style::ConvertScale(kSizeForDownscale);
  839. set.icon = Ui::MakeAnimatedIcon({
  840. .generator = DocumentIconFrameGenerator(set.media),
  841. .sizeOverride = QSize(size, size),
  842. .colorized = set.media->owner()->emojiUsesTextColor(),
  843. });
  844. set.media = nullptr;
  845. }
  846. void Reactions::downloadTaskFinished() {
  847. auto hasOne = false;
  848. for (auto &[emoji, set] : _images) {
  849. if (!set.media) {
  850. continue;
  851. } else if (set.media->loaded()) {
  852. setAnimatedIcon(set);
  853. } else {
  854. hasOne = true;
  855. }
  856. }
  857. if (!hasOne) {
  858. _imagesLoadLifetime.destroy();
  859. }
  860. }
  861. void Reactions::requestTop() {
  862. if (_topRequestId) {
  863. return;
  864. }
  865. auto &api = _owner->session().api();
  866. _topRefreshTimer.cancel();
  867. _topRequestId = api.request(MTPmessages_GetTopReactions(
  868. MTP_int(kTopReactionsLimit),
  869. MTP_long(_topHash)
  870. )).done([=](const MTPmessages_Reactions &result) {
  871. _topRequestId = 0;
  872. result.match([&](const MTPDmessages_reactions &data) {
  873. updateTop(data);
  874. }, [](const MTPDmessages_reactionsNotModified&) {
  875. });
  876. }).fail([=] {
  877. _topRequestId = 0;
  878. _topHash = 0;
  879. }).send();
  880. }
  881. void Reactions::requestRecent() {
  882. if (_recentRequestId) {
  883. return;
  884. }
  885. auto &api = _owner->session().api();
  886. _recentRequestScheduled = false;
  887. _recentRequestId = api.request(MTPmessages_GetRecentReactions(
  888. MTP_int(kRecentReactionsLimit),
  889. MTP_long(_recentHash)
  890. )).done([=](const MTPmessages_Reactions &result) {
  891. _recentRequestId = 0;
  892. result.match([&](const MTPDmessages_reactions &data) {
  893. updateRecent(data);
  894. }, [](const MTPDmessages_reactionsNotModified&) {
  895. });
  896. }).fail([=] {
  897. _recentRequestId = 0;
  898. _recentHash = 0;
  899. }).send();
  900. }
  901. void Reactions::requestDefault() {
  902. if (_defaultRequestId) {
  903. return;
  904. }
  905. auto &api = _owner->session().api();
  906. _defaultRequestId = api.request(MTPmessages_GetAvailableReactions(
  907. MTP_int(_defaultHash)
  908. )).done([=](const MTPmessages_AvailableReactions &result) {
  909. _defaultRequestId = 0;
  910. result.match([&](const MTPDmessages_availableReactions &data) {
  911. updateDefault(data);
  912. }, [&](const MTPDmessages_availableReactionsNotModified &) {
  913. });
  914. }).fail([=] {
  915. _defaultRequestId = 0;
  916. _defaultHash = 0;
  917. }).send();
  918. }
  919. void Reactions::requestGeneric() {
  920. if (_genericRequestId) {
  921. return;
  922. }
  923. auto &api = _owner->session().api();
  924. _genericRequestId = api.request(MTPmessages_GetStickerSet(
  925. MTP_inputStickerSetEmojiGenericAnimations(),
  926. MTP_int(0) // hash
  927. )).done([=](const MTPmessages_StickerSet &result) {
  928. _genericRequestId = 0;
  929. result.match([&](const MTPDmessages_stickerSet &data) {
  930. updateGeneric(data);
  931. }, [](const MTPDmessages_stickerSetNotModified &) {
  932. LOG(("API Error: Unexpected messages.stickerSetNotModified."));
  933. });
  934. }).fail([=] {
  935. _genericRequestId = 0;
  936. }).send();
  937. }
  938. void Reactions::requestMyTags(SavedSublist *sublist) {
  939. auto &my = _myTags[sublist];
  940. if (my.requestId) {
  941. return;
  942. }
  943. auto &api = _owner->session().api();
  944. my.requestScheduled = false;
  945. using Flag = MTPmessages_GetSavedReactionTags::Flag;
  946. my.requestId = api.request(MTPmessages_GetSavedReactionTags(
  947. MTP_flags(sublist ? Flag::f_peer : Flag()),
  948. (sublist ? sublist->peer()->input : MTP_inputPeerEmpty()),
  949. MTP_long(my.hash)
  950. )).done([=](const MTPmessages_SavedReactionTags &result) {
  951. auto &my = _myTags[sublist];
  952. my.requestId = 0;
  953. result.match([&](const MTPDmessages_savedReactionTags &data) {
  954. updateMyTags(sublist, data);
  955. }, [](const MTPDmessages_savedReactionTagsNotModified&) {
  956. });
  957. }).fail([=] {
  958. auto &my = _myTags[sublist];
  959. my.requestId = 0;
  960. my.hash = 0;
  961. }).send();
  962. }
  963. void Reactions::requestTags() {
  964. if (_tagsRequestId) {
  965. return;
  966. }
  967. auto &api = _owner->session().api();
  968. _tagsRequestId = api.request(MTPmessages_GetDefaultTagReactions(
  969. MTP_long(_tagsHash)
  970. )).done([=](const MTPmessages_Reactions &result) {
  971. _tagsRequestId = 0;
  972. result.match([&](const MTPDmessages_reactions &data) {
  973. updateTags(data);
  974. }, [](const MTPDmessages_reactionsNotModified&) {
  975. });
  976. }).fail([=] {
  977. _tagsRequestId = 0;
  978. _tagsHash = 0;
  979. }).send();
  980. }
  981. void Reactions::requestEffects() {
  982. if (_effectsRequestId) {
  983. return;
  984. }
  985. auto &api = _owner->session().api();
  986. _effectsRequestId = api.request(MTPmessages_GetAvailableEffects(
  987. MTP_int(_effectsHash)
  988. )).done([=](const MTPmessages_AvailableEffects &result) {
  989. _effectsRequestId = 0;
  990. result.match([&](const MTPDmessages_availableEffects &data) {
  991. updateEffects(data);
  992. }, [&](const MTPDmessages_availableEffectsNotModified &) {
  993. });
  994. }).fail([=] {
  995. _effectsRequestId = 0;
  996. _effectsHash = 0;
  997. }).send();
  998. }
  999. void Reactions::updateTop(const MTPDmessages_reactions &data) {
  1000. _topHash = data.vhash().v;
  1001. _topIds = ListFromMTP(data);
  1002. _top = resolveByIds(_topIds, _unresolvedTop);
  1003. _topUpdated.fire({});
  1004. }
  1005. void Reactions::updateRecent(const MTPDmessages_reactions &data) {
  1006. _recentHash = data.vhash().v;
  1007. _recentIds = ListFromMTP(data);
  1008. _recent = resolveByIds(_recentIds, _unresolvedRecent);
  1009. recentUpdated();
  1010. }
  1011. void Reactions::updateDefault(const MTPDmessages_availableReactions &data) {
  1012. _defaultHash = data.vhash().v;
  1013. const auto &list = data.vreactions().v;
  1014. const auto oldCache = base::take(_iconsCache);
  1015. const auto toCache = [&](DocumentData *document) {
  1016. if (document) {
  1017. _iconsCache.emplace(document, document->createMediaView());
  1018. }
  1019. };
  1020. _active.clear();
  1021. _available.clear();
  1022. _active.reserve(list.size());
  1023. _available.reserve(list.size());
  1024. _iconsCache.reserve(list.size() * 4);
  1025. for (const auto &reaction : list) {
  1026. if (const auto parsed = parse(reaction)) {
  1027. _available.push_back(*parsed);
  1028. if (parsed->active) {
  1029. _active.push_back(*parsed);
  1030. toCache(parsed->appearAnimation);
  1031. toCache(parsed->selectAnimation);
  1032. toCache(parsed->centerIcon);
  1033. toCache(parsed->aroundAnimation);
  1034. }
  1035. }
  1036. }
  1037. if (_waitingForReactions) {
  1038. _waitingForReactions = false;
  1039. resolveReactionImages();
  1040. }
  1041. defaultUpdated();
  1042. }
  1043. void Reactions::updateGeneric(const MTPDmessages_stickerSet &data) {
  1044. const auto oldCache = base::take(_genericCache);
  1045. const auto toCache = [&](not_null<DocumentData*> document) {
  1046. if (document->sticker()) {
  1047. _genericAnimations.push_back(document);
  1048. _genericCache.emplace(document, document->createMediaView());
  1049. }
  1050. };
  1051. const auto &list = data.vdocuments().v;
  1052. _genericAnimations.clear();
  1053. _genericAnimations.reserve(list.size());
  1054. _genericCache.reserve(list.size());
  1055. for (const auto &sticker : data.vdocuments().v) {
  1056. toCache(_owner->processDocument(sticker));
  1057. }
  1058. if (!_genericCache.empty()) {
  1059. _genericCache.front().second->checkStickerLarge();
  1060. }
  1061. }
  1062. void Reactions::updateMyTags(
  1063. SavedSublist *sublist,
  1064. const MTPDmessages_savedReactionTags &data) {
  1065. auto &my = _myTags[sublist];
  1066. my.hash = data.vhash().v;
  1067. auto list = ListFromMTP(data);
  1068. auto renamed = base::flat_set<ReactionId>();
  1069. if (!sublist) {
  1070. for (const auto &info : list) {
  1071. const auto j = ranges::find(my.info, info.id, &MyTagInfo::id);
  1072. const auto was = (j != end(my.info)) ? j->title : QString();
  1073. if (info.title != was) {
  1074. renamed.emplace(info.id);
  1075. }
  1076. }
  1077. }
  1078. my.info = std::move(list);
  1079. my.tags = resolveByInfos(my.info, _unresolvedMyTags, sublist);
  1080. _myTagsUpdated.fire_copy(sublist);
  1081. for (const auto &id : renamed) {
  1082. _myTagRenamed.fire_copy(id);
  1083. }
  1084. }
  1085. void Reactions::updateTags(const MTPDmessages_reactions &data) {
  1086. _tagsHash = data.vhash().v;
  1087. _tagsIds = ListFromMTP(data);
  1088. _tags = resolveByIds(_tagsIds, _unresolvedTags);
  1089. _tagsUpdated.fire({});
  1090. }
  1091. void Reactions::updateEffects(const MTPDmessages_availableEffects &data) {
  1092. _effectsHash = data.vhash().v;
  1093. const auto &list = data.veffects().v;
  1094. const auto toCache = [&](DocumentData *document) {
  1095. if (document) {
  1096. _iconsCache.emplace(document, document->createMediaView());
  1097. }
  1098. };
  1099. for (const auto &document : data.vdocuments().v) {
  1100. toCache(_owner->processDocument(document));
  1101. }
  1102. _effects.clear();
  1103. _effects.reserve(list.size());
  1104. for (const auto &effect : list) {
  1105. if (const auto parsed = parse(effect)) {
  1106. _effects.push_back(*parsed);
  1107. }
  1108. }
  1109. if (_waitingForEffects) {
  1110. _waitingForEffects = false;
  1111. resolveEffectImages();
  1112. }
  1113. effectsUpdated();
  1114. }
  1115. void Reactions::recentUpdated() {
  1116. _topRefreshTimer.callOnce(kTopRequestDelay);
  1117. _recentUpdated.fire({});
  1118. }
  1119. void Reactions::defaultUpdated() {
  1120. refreshTop();
  1121. refreshRecent();
  1122. if (_genericAnimations.empty()) {
  1123. requestGeneric();
  1124. }
  1125. refreshMyTags();
  1126. refreshTags();
  1127. refreshEffects();
  1128. _defaultUpdated.fire({});
  1129. }
  1130. void Reactions::myTagsUpdated() {
  1131. if (_genericAnimations.empty()) {
  1132. requestGeneric();
  1133. }
  1134. _myTagsUpdated.fire({});
  1135. }
  1136. void Reactions::tagsUpdated() {
  1137. if (_genericAnimations.empty()) {
  1138. requestGeneric();
  1139. }
  1140. _tagsUpdated.fire({});
  1141. }
  1142. void Reactions::effectsUpdated() {
  1143. _effectsUpdated.fire({});
  1144. }
  1145. not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() {
  1146. return static_cast<CustomEmojiManager::Listener*>(this);
  1147. }
  1148. void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
  1149. if (!document->sticker()) {
  1150. return;
  1151. }
  1152. const auto id = ReactionId{ { document->id } };
  1153. const auto favorite = (_unresolvedFavoriteId == id);
  1154. const auto i = _unresolvedTop.find(id);
  1155. const auto top = (i != end(_unresolvedTop));
  1156. const auto j = _unresolvedRecent.find(id);
  1157. const auto recent = (j != end(_unresolvedRecent));
  1158. const auto k = _unresolvedMyTags.find(id);
  1159. const auto myTagSublists = (k != end(_unresolvedMyTags))
  1160. ? base::take(k->second)
  1161. : base::flat_set<SavedSublist*>();
  1162. const auto l = _unresolvedTags.find(id);
  1163. const auto tag = (l != end(_unresolvedTags));
  1164. if (favorite) {
  1165. _unresolvedFavoriteId = ReactionId();
  1166. _favorite = resolveById(_favoriteId);
  1167. }
  1168. if (top) {
  1169. _unresolvedTop.erase(i);
  1170. _top = resolveByIds(_topIds, _unresolvedTop);
  1171. }
  1172. if (recent) {
  1173. _unresolvedRecent.erase(j);
  1174. _recent = resolveByIds(_recentIds, _unresolvedRecent);
  1175. }
  1176. if (!myTagSublists.empty()) {
  1177. _unresolvedMyTags.erase(k);
  1178. for (const auto &sublist : myTagSublists) {
  1179. auto &my = _myTags[sublist];
  1180. my.tags = resolveByInfos(my.info, _unresolvedMyTags, sublist);
  1181. }
  1182. }
  1183. if (tag) {
  1184. _unresolvedTags.erase(l);
  1185. _tags = resolveByIds(_tagsIds, _unresolvedTags);
  1186. }
  1187. if (favorite) {
  1188. _favoriteUpdated.fire({});
  1189. }
  1190. if (top) {
  1191. _topUpdated.fire({});
  1192. }
  1193. if (recent) {
  1194. _recentUpdated.fire({});
  1195. }
  1196. for (const auto &sublist : myTagSublists) {
  1197. _myTagsUpdated.fire_copy(sublist);
  1198. }
  1199. if (tag) {
  1200. _tagsUpdated.fire({});
  1201. }
  1202. }
  1203. std::optional<Reaction> Reactions::resolveById(const ReactionId &id) {
  1204. if (const auto emoji = id.emoji(); !emoji.isEmpty()) {
  1205. const auto i = ranges::find(_available, id, &Reaction::id);
  1206. if (i != end(_available)) {
  1207. return *i;
  1208. }
  1209. } else if (const auto customId = id.custom()) {
  1210. const auto document = _owner->document(customId);
  1211. if (document->sticker()) {
  1212. return CustomReaction(document);
  1213. }
  1214. }
  1215. return {};
  1216. }
  1217. std::vector<Reaction> Reactions::resolveByIds(
  1218. const std::vector<ReactionId> &ids,
  1219. base::flat_set<ReactionId> &unresolved) {
  1220. auto result = std::vector<Reaction>();
  1221. result.reserve(ids.size());
  1222. for (const auto &id : ids) {
  1223. if (const auto resolved = resolveById(id)) {
  1224. result.push_back(*resolved);
  1225. } else if (unresolved.emplace(id).second) {
  1226. resolve(id);
  1227. }
  1228. }
  1229. return result;
  1230. }
  1231. std::optional<Reaction> Reactions::resolveByInfo(
  1232. const MyTagInfo &info,
  1233. SavedSublist *sublist) {
  1234. const auto withInfo = [&](Reaction reaction) {
  1235. reaction.count = info.count;
  1236. reaction.title = sublist ? myTagTitle(reaction.id) : info.title;
  1237. return reaction;
  1238. };
  1239. if (const auto emoji = info.id.emoji(); !emoji.isEmpty()) {
  1240. const auto i = ranges::find(_available, info.id, &Reaction::id);
  1241. if (i != end(_available)) {
  1242. return withInfo(*i);
  1243. }
  1244. } else if (const auto customId = info.id.custom()) {
  1245. const auto document = _owner->document(customId);
  1246. if (document->sticker()) {
  1247. return withInfo(CustomReaction(document));
  1248. }
  1249. }
  1250. return {};
  1251. }
  1252. std::vector<Reaction> Reactions::resolveByInfos(
  1253. const std::vector<MyTagInfo> &infos,
  1254. base::flat_map<
  1255. ReactionId,
  1256. base::flat_set<SavedSublist*>> &unresolved,
  1257. SavedSublist *sublist) {
  1258. auto result = std::vector<Reaction>();
  1259. result.reserve(infos.size());
  1260. for (const auto &tag : infos) {
  1261. if (auto resolved = resolveByInfo(tag, sublist)) {
  1262. result.push_back(*resolved);
  1263. } else if (const auto i = unresolved.find(tag.id)
  1264. ; i != end(unresolved)) {
  1265. i->second.emplace(sublist);
  1266. } else {
  1267. unresolved[tag.id].emplace(sublist);
  1268. resolve(tag.id);
  1269. }
  1270. }
  1271. return result;
  1272. }
  1273. void Reactions::resolve(const ReactionId &id) {
  1274. if (const auto emoji = id.emoji(); !emoji.isEmpty()) {
  1275. refreshDefault();
  1276. } else if (const auto customId = id.custom()) {
  1277. _owner->customEmojiManager().resolve(
  1278. customId,
  1279. resolveListener());
  1280. }
  1281. }
  1282. std::optional<Reaction> Reactions::parse(const MTPAvailableReaction &entry) {
  1283. const auto &data = entry.data();
  1284. const auto emoji = qs(data.vreaction());
  1285. const auto known = (Ui::Emoji::Find(emoji) != nullptr);
  1286. if (!known) {
  1287. LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji));
  1288. return std::nullopt;
  1289. }
  1290. return std::make_optional(Reaction{
  1291. .id = ReactionId{ emoji },
  1292. .title = qs(data.vtitle()),
  1293. //.staticIcon = _owner->processDocument(data.vstatic_icon()),
  1294. .appearAnimation = _owner->processDocument(
  1295. data.vappear_animation()),
  1296. .selectAnimation = _owner->processDocument(
  1297. data.vselect_animation()),
  1298. //.activateAnimation = _owner->processDocument(
  1299. // data.vactivate_animation()),
  1300. //.activateEffects = _owner->processDocument(
  1301. // data.veffect_animation()),
  1302. .centerIcon = (data.vcenter_icon()
  1303. ? _owner->processDocument(*data.vcenter_icon()).get()
  1304. : nullptr),
  1305. .aroundAnimation = (data.varound_animation()
  1306. ? _owner->processDocument(*data.varound_animation()).get()
  1307. : nullptr),
  1308. .active = !data.is_inactive(),
  1309. });
  1310. }
  1311. std::optional<Reaction> Reactions::parse(const MTPAvailableEffect &entry) {
  1312. const auto &data = entry.data();
  1313. const auto emoji = qs(data.vemoticon());
  1314. const auto known = (Ui::Emoji::Find(emoji) != nullptr);
  1315. if (!known) {
  1316. LOG(("API Error: Unknown emoji in effects: %1").arg(emoji));
  1317. return std::nullopt;
  1318. }
  1319. const auto id = DocumentId(data.vid().v);
  1320. const auto stickerId = data.veffect_sticker_id().v;
  1321. const auto document = _owner->document(stickerId);
  1322. if (!document->sticker()) {
  1323. LOG(("API Error: Bad sticker in effects: %1").arg(stickerId));
  1324. return std::nullopt;
  1325. }
  1326. const auto aroundId = data.veffect_animation_id().value_or_empty();
  1327. const auto around = aroundId
  1328. ? _owner->document(aroundId).get()
  1329. : nullptr;
  1330. if (around && !around->sticker()) {
  1331. LOG(("API Error: Bad sticker in effects around: %1").arg(aroundId));
  1332. return std::nullopt;
  1333. }
  1334. const auto iconId = data.vstatic_icon_id().value_or_empty();
  1335. const auto icon = iconId ? _owner->document(iconId).get() : nullptr;
  1336. if (icon && !icon->sticker()) {
  1337. LOG(("API Error: Bad sticker in effects icon: %1").arg(iconId));
  1338. return std::nullopt;
  1339. }
  1340. return std::make_optional(Reaction{
  1341. .id = ReactionId{ id },
  1342. .title = emoji,
  1343. .appearAnimation = document,
  1344. .selectAnimation = document,
  1345. .centerIcon = icon,
  1346. .aroundAnimation = around,
  1347. .active = true,
  1348. .effect = true,
  1349. .premium = data.is_premium_required(),
  1350. });
  1351. }
  1352. void Reactions::send(not_null<HistoryItem*> item, bool addToRecent) {
  1353. const auto id = item->fullId();
  1354. auto &api = _owner->session().api();
  1355. auto i = _sentRequests.find(id);
  1356. if (i != end(_sentRequests)) {
  1357. api.request(i->second).cancel();
  1358. } else {
  1359. i = _sentRequests.emplace(id).first;
  1360. }
  1361. const auto chosen = item->chosenReactions();
  1362. using Flag = MTPmessages_SendReaction::Flag;
  1363. const auto flags = (chosen.empty() ? Flag(0) : Flag::f_reaction)
  1364. | (addToRecent ? Flag::f_add_to_recent : Flag(0));
  1365. i->second = api.request(MTPmessages_SendReaction(
  1366. MTP_flags(flags),
  1367. item->history()->peer->input,
  1368. MTP_int(id.msg),
  1369. MTP_vector<MTPReaction>(chosen | ranges::views::filter([](
  1370. const ReactionId &id) {
  1371. return !id.paid();
  1372. }) | ranges::views::transform(
  1373. ReactionToMTP
  1374. ) | ranges::to<QVector<MTPReaction>>())
  1375. )).done([=](const MTPUpdates &result) {
  1376. _sentRequests.remove(id);
  1377. _owner->session().api().applyUpdates(result);
  1378. }).fail([=](const MTP::Error &error) {
  1379. _sentRequests.remove(id);
  1380. }).send();
  1381. }
  1382. void Reactions::poll(not_null<HistoryItem*> item, crl::time now) {
  1383. // Group them by one second.
  1384. const auto last = item->lastReactionsRefreshTime();
  1385. const auto grouped = ((last + 999) / 1000) * 1000;
  1386. if (!grouped || item->history()->peer->isUser()) {
  1387. // First reaction always edits message.
  1388. return;
  1389. } else if (const auto left = grouped + kPollEach - now; left > 0) {
  1390. if (!_repaintItems.contains(item)) {
  1391. _repaintItems.emplace(item, grouped + kPollEach);
  1392. if (!_repaintTimer.isActive()
  1393. || _repaintTimer.remainingTime() > left) {
  1394. _repaintTimer.callOnce(left);
  1395. }
  1396. }
  1397. } else if (!_pollingItems.contains(item)) {
  1398. if (_pollItems.empty() && !_pollRequestId) {
  1399. crl::on_main(&_owner->session(), [=] {
  1400. pollCollected();
  1401. });
  1402. }
  1403. _pollItems.emplace(item);
  1404. }
  1405. }
  1406. void Reactions::updateAllInHistory(not_null<PeerData*> peer, bool enabled) {
  1407. if (const auto history = _owner->historyLoaded(peer)) {
  1408. history->reactionsEnabledChanged(enabled);
  1409. }
  1410. }
  1411. void Reactions::clearTemporary() {
  1412. _temporary.clear();
  1413. }
  1414. Reaction *Reactions::lookupTemporary(const ReactionId &id) {
  1415. if (id.paid()) {
  1416. return lookupPaid();
  1417. } else if (const auto emoji = id.emoji(); !emoji.isEmpty()) {
  1418. const auto i = ranges::find(_available, id, &Reaction::id);
  1419. return (i != end(_available)) ? &*i : nullptr;
  1420. } else if (const auto customId = id.custom()) {
  1421. if (const auto i = _temporary.find(customId); i != end(_temporary)) {
  1422. return &i->second;
  1423. }
  1424. const auto document = _owner->document(customId);
  1425. if (document->sticker()) {
  1426. return &_temporary.emplace(
  1427. customId,
  1428. CustomReaction(document)).first->second;
  1429. }
  1430. _owner->customEmojiManager().resolve(
  1431. customId,
  1432. resolveListener());
  1433. return nullptr;
  1434. }
  1435. return nullptr;
  1436. }
  1437. not_null<Reaction*> Reactions::lookupPaid() {
  1438. if (!_paid) {
  1439. const auto generate = [&](const QString &name) {
  1440. const auto session = &_owner->session();
  1441. return ChatHelpers::GenerateLocalTgsSticker(session, name);
  1442. };
  1443. const auto appear = generate(u"star_reaction_appear"_q);
  1444. const auto center = generate(u"star_reaction_center"_q);
  1445. const auto select = generate(u"star_reaction_select"_q);
  1446. _paid.emplace(Reaction{
  1447. .id = ReactionId::Paid(),
  1448. .title = u"Telegram Star"_q,
  1449. .appearAnimation = appear,
  1450. .selectAnimation = select,
  1451. .centerIcon = center,
  1452. .active = true,
  1453. });
  1454. _iconsCache.emplace(appear, appear->createMediaView());
  1455. _iconsCache.emplace(center, center->createMediaView());
  1456. _iconsCache.emplace(select, select->createMediaView());
  1457. fillPaidReactionAnimations();
  1458. }
  1459. return &*_paid;
  1460. }
  1461. not_null<DocumentData*> Reactions::paidToastAnimation() {
  1462. if (!_paidToastAnimation) {
  1463. _paidToastAnimation = ChatHelpers::GenerateLocalTgsSticker(
  1464. &_owner->session(),
  1465. u"star_reaction_toast"_q);
  1466. }
  1467. return _paidToastAnimation;
  1468. }
  1469. rpl::producer<std::vector<Reaction>> Reactions::myTagsValue(
  1470. SavedSublist *sublist) {
  1471. refreshMyTags(sublist);
  1472. const auto list = [=] {
  1473. return _myTags[sublist].tags;
  1474. };
  1475. return rpl::single(
  1476. list()
  1477. ) | rpl::then(_myTagsUpdated.events(
  1478. ) | rpl::filter(
  1479. rpl::mappers::_1 == sublist
  1480. ) | rpl::map(list));
  1481. }
  1482. bool Reactions::isQuitPrevent() {
  1483. for (auto i = begin(_sendPaidItems); i != end(_sendPaidItems);) {
  1484. const auto item = i->first;
  1485. if (_sendingPaid.contains(item)) {
  1486. ++i;
  1487. } else {
  1488. i = _sendPaidItems.erase(i);
  1489. sendPaid(item);
  1490. }
  1491. }
  1492. if (_sendingPaid.empty()) {
  1493. return false;
  1494. }
  1495. LOG(("Reactions prevents quit, sending paid..."));
  1496. return true;
  1497. }
  1498. void Reactions::schedulePaid(not_null<HistoryItem*> item) {
  1499. _sendPaidItems[item] = crl::now() + kPaidAccumulatePeriod;
  1500. if (!_sendPaidTimer.isActive()) {
  1501. _sendPaidTimer.callOnce(kPaidAccumulatePeriod);
  1502. }
  1503. }
  1504. void Reactions::undoScheduledPaid(not_null<HistoryItem*> item) {
  1505. _sendPaidItems.remove(item);
  1506. item->cancelScheduledPaidReaction();
  1507. }
  1508. crl::time Reactions::sendingScheduledPaidAt(
  1509. not_null<HistoryItem*> item) const {
  1510. const auto i = _sendPaidItems.find(item);
  1511. return (i != end(_sendPaidItems)) ? i->second : crl::time();
  1512. }
  1513. crl::time Reactions::ScheduledPaidDelay() {
  1514. return kPaidAccumulatePeriod;
  1515. }
  1516. void Reactions::repaintCollected() {
  1517. const auto now = crl::now();
  1518. auto closest = crl::time();
  1519. for (auto i = begin(_repaintItems); i != end(_repaintItems);) {
  1520. if (i->second <= now) {
  1521. _owner->requestItemRepaint(i->first);
  1522. i = _repaintItems.erase(i);
  1523. } else {
  1524. if (!closest || i->second < closest) {
  1525. closest = i->second;
  1526. }
  1527. ++i;
  1528. }
  1529. }
  1530. if (closest) {
  1531. _repaintTimer.callOnce(closest - now);
  1532. }
  1533. }
  1534. void Reactions::pollCollected() {
  1535. auto toRequest = base::flat_map<not_null<PeerData*>, QVector<MTPint>>();
  1536. _pollingItems = std::move(_pollItems);
  1537. for (const auto &item : _pollingItems) {
  1538. toRequest[item->history()->peer].push_back(MTP_int(item->id));
  1539. }
  1540. auto &api = _owner->session().api();
  1541. for (const auto &[peer, ids] : toRequest) {
  1542. const auto finalize = [=] {
  1543. const auto now = crl::now();
  1544. for (const auto &item : base::take(_pollingItems)) {
  1545. const auto last = item->lastReactionsRefreshTime();
  1546. if (last && last + kPollEach <= now) {
  1547. item->updateReactions(nullptr);
  1548. }
  1549. }
  1550. _pollRequestId = 0;
  1551. if (!_pollItems.empty()) {
  1552. crl::on_main(&_owner->session(), [=] {
  1553. pollCollected();
  1554. });
  1555. }
  1556. };
  1557. _pollRequestId = api.request(MTPmessages_GetMessagesReactions(
  1558. peer->input,
  1559. MTP_vector<MTPint>(ids)
  1560. )).done([=](const MTPUpdates &result) {
  1561. _owner->session().api().applyUpdates(result);
  1562. finalize();
  1563. }).fail([=] {
  1564. finalize();
  1565. }).send();
  1566. }
  1567. }
  1568. bool Reactions::sending(not_null<HistoryItem*> item) const {
  1569. return _sentRequests.contains(item->fullId())
  1570. || _sendingPaid.contains(item);
  1571. }
  1572. bool Reactions::HasUnread(const MTPMessageReactions &data) {
  1573. return data.match([&](const MTPDmessageReactions &data) {
  1574. if (const auto &recent = data.vrecent_reactions()) {
  1575. for (const auto &one : recent->v) {
  1576. if (one.match([&](const MTPDmessagePeerReaction &data) {
  1577. return data.is_unread();
  1578. })) {
  1579. return true;
  1580. }
  1581. }
  1582. }
  1583. return false;
  1584. });
  1585. }
  1586. void Reactions::CheckUnknownForUnread(
  1587. not_null<Session*> owner,
  1588. const MTPMessage &message) {
  1589. message.match([&](const MTPDmessage &data) {
  1590. if (data.vreactions() && HasUnread(*data.vreactions())) {
  1591. const auto peerId = peerFromMTP(data.vpeer_id());
  1592. if (const auto history = owner->historyLoaded(peerId)) {
  1593. owner->histories().requestDialogEntry(history);
  1594. }
  1595. }
  1596. }, [](const auto &) {
  1597. });
  1598. }
  1599. void Reactions::sendPaid() {
  1600. if (!_sendingPaid.empty()) {
  1601. return;
  1602. }
  1603. auto next = crl::time();
  1604. const auto now = crl::now();
  1605. for (auto i = begin(_sendPaidItems); i != end(_sendPaidItems);) {
  1606. const auto item = i->first;
  1607. const auto when = i->second;
  1608. if (when > now) {
  1609. if (!next || next > when) {
  1610. next = when;
  1611. }
  1612. ++i;
  1613. } else {
  1614. i = _sendPaidItems.erase(i);
  1615. if (sendPaid(item)) {
  1616. return;
  1617. }
  1618. }
  1619. }
  1620. if (next) {
  1621. _sendPaidTimer.callOnce(next - now);
  1622. }
  1623. }
  1624. bool Reactions::sendPaid(not_null<HistoryItem*> item) {
  1625. const auto send = item->startPaidReactionSending();
  1626. if (!send.valid) {
  1627. return false;
  1628. }
  1629. sendPaidRequest(item, send);
  1630. return true;
  1631. }
  1632. void Reactions::sendPaidPrivacyRequest(
  1633. not_null<HistoryItem*> item,
  1634. PaidReactionSend send) {
  1635. Expects(!_sendingPaid.contains(item));
  1636. Expects(send.shownPeer.has_value());
  1637. Expects(!send.count);
  1638. const auto id = item->fullId();
  1639. auto &api = _owner->session().api();
  1640. const auto requestId = api.request(
  1641. MTPmessages_TogglePaidReactionPrivacy(
  1642. item->history()->peer->input,
  1643. MTP_int(id.msg),
  1644. PaidReactionShownPeerToTL(&_owner->session(), send.shownPeer))
  1645. ).done([=] {
  1646. if (const auto item = _owner->message(id)) {
  1647. if (_sendingPaid.remove(item)) {
  1648. sendPaidFinish(item, send, true);
  1649. }
  1650. }
  1651. checkQuitPreventFinished();
  1652. }).fail([=](const MTP::Error &error) {
  1653. if (const auto item = _owner->message(id)) {
  1654. if (_sendingPaid.remove(item)) {
  1655. sendPaidFinish(item, send, false);
  1656. }
  1657. }
  1658. checkQuitPreventFinished();
  1659. }).send();
  1660. _sendingPaid[item] = requestId;
  1661. }
  1662. void Reactions::sendPaidRequest(
  1663. not_null<HistoryItem*> item,
  1664. PaidReactionSend send) {
  1665. Expects(!_sendingPaid.contains(item));
  1666. if (!send.count) {
  1667. sendPaidPrivacyRequest(item, send);
  1668. return;
  1669. }
  1670. const auto id = item->fullId();
  1671. const auto randomId = base::unixtime::mtproto_msg_id();
  1672. auto &api = _owner->session().api();
  1673. using Flag = MTPmessages_SendPaidReaction::Flag;
  1674. const auto requestId = api.request(MTPmessages_SendPaidReaction(
  1675. MTP_flags(send.shownPeer ? Flag::f_private : Flag()),
  1676. item->history()->peer->input,
  1677. MTP_int(id.msg),
  1678. MTP_int(send.count),
  1679. MTP_long(randomId),
  1680. (!send.shownPeer
  1681. ? MTPPaidReactionPrivacy()
  1682. : PaidReactionShownPeerToTL(&_owner->session(), *send.shownPeer))
  1683. )).done([=](const MTPUpdates &result) {
  1684. if (const auto item = _owner->message(id)) {
  1685. if (_sendingPaid.remove(item)) {
  1686. sendPaidFinish(item, send, true);
  1687. }
  1688. }
  1689. _owner->session().api().applyUpdates(result);
  1690. checkQuitPreventFinished();
  1691. }).fail([=](const MTP::Error &error) {
  1692. if (const auto item = _owner->message(id)) {
  1693. _sendingPaid.remove(item);
  1694. if (error.type() == u"RANDOM_ID_EXPIRED"_q) {
  1695. sendPaidRequest(item, send);
  1696. } else {
  1697. sendPaidFinish(item, send, false);
  1698. }
  1699. }
  1700. checkQuitPreventFinished();
  1701. }).send();
  1702. _sendingPaid[item] = requestId;
  1703. }
  1704. void Reactions::checkQuitPreventFinished() {
  1705. if (_sendingPaid.empty()) {
  1706. if (Core::Quitting()) {
  1707. LOG(("Reactions doesn't prevent quit any more."));
  1708. }
  1709. Core::App().quitPreventFinished();
  1710. }
  1711. }
  1712. void Reactions::sendPaidFinish(
  1713. not_null<HistoryItem*> item,
  1714. PaidReactionSend send,
  1715. bool success) {
  1716. item->finishPaidReactionSending(send, success);
  1717. sendPaid();
  1718. }
  1719. MessageReactions::MessageReactions(not_null<HistoryItem*> item)
  1720. : _item(item) {
  1721. }
  1722. MessageReactions::~MessageReactions() {
  1723. cancelScheduledPaid();
  1724. if (const auto paid = _paid.get()) {
  1725. if (paid->sending > 0) {
  1726. finishPaidSending({
  1727. .count = int(paid->sending),
  1728. .valid = true,
  1729. .shownPeer = MaybeShownPeer(
  1730. paid->sendingPrivacySet,
  1731. paid->sendingShownPeer),
  1732. }, false);
  1733. }
  1734. }
  1735. }
  1736. void MessageReactions::add(const ReactionId &id, bool addToRecent) {
  1737. Expects(!id.empty());
  1738. Expects(!id.paid());
  1739. const auto history = _item->history();
  1740. const auto myLimit = SentReactionsLimit(_item);
  1741. if (ranges::contains(chosen(), id)) {
  1742. return;
  1743. }
  1744. auto my = 0;
  1745. const auto tags = _item->reactionsAreTags();
  1746. if (tags) {
  1747. const auto sublist = _item->savedSublist();
  1748. history->owner().reactions().incrementMyTag(id, sublist);
  1749. }
  1750. _list.erase(ranges::remove_if(_list, [&](MessageReaction &one) {
  1751. if (one.id.paid()) {
  1752. return false;
  1753. }
  1754. const auto removing = one.my && (my == myLimit || ++my == myLimit);
  1755. if (!removing) {
  1756. return false;
  1757. }
  1758. one.my = false;
  1759. const auto removed = !--one.count;
  1760. const auto j = _recent.find(one.id);
  1761. if (j != end(_recent)) {
  1762. if (removed) {
  1763. j->second.clear();
  1764. _recent.erase(j);
  1765. } else {
  1766. j->second.erase(
  1767. ranges::remove(j->second, true, &RecentReaction::my),
  1768. end(j->second));
  1769. if (j->second.empty()) {
  1770. _recent.erase(j);
  1771. }
  1772. }
  1773. }
  1774. if (tags) {
  1775. const auto sublist = _item->savedSublist();
  1776. history->owner().reactions().decrementMyTag(one.id, sublist);
  1777. }
  1778. return removed;
  1779. }), end(_list));
  1780. const auto peer = history->peer;
  1781. if (_item->canViewReactions() || peer->isUser()) {
  1782. auto &list = _recent[id];
  1783. const auto from = peer->session().sendAsPeers().resolveChosen(peer);
  1784. list.insert(begin(list), RecentReaction{
  1785. .peer = from,
  1786. .my = true,
  1787. });
  1788. }
  1789. const auto i = ranges::find(_list, id, &MessageReaction::id);
  1790. if (i != end(_list)) {
  1791. i->my = true;
  1792. ++i->count;
  1793. std::rotate(i, i + 1, end(_list));
  1794. } else {
  1795. _list.push_back({ .id = id, .count = 1, .my = true });
  1796. }
  1797. auto &owner = history->owner();
  1798. owner.reactions().send(_item, addToRecent);
  1799. owner.notifyItemDataChange(_item);
  1800. }
  1801. void MessageReactions::remove(const ReactionId &id) {
  1802. Expects(!id.paid());
  1803. const auto history = _item->history();
  1804. const auto self = history->session().user();
  1805. const auto i = ranges::find(_list, id, &MessageReaction::id);
  1806. const auto j = _recent.find(id);
  1807. if (i == end(_list)) {
  1808. Assert(j == end(_recent));
  1809. return;
  1810. } else if (!i->my) {
  1811. Assert(j == end(_recent)
  1812. || !ranges::contains(j->second, self, &RecentReaction::peer));
  1813. return;
  1814. }
  1815. i->my = false;
  1816. const auto tags = _item->reactionsAreTags();
  1817. const auto removed = !--i->count;
  1818. if (removed) {
  1819. _list.erase(i);
  1820. }
  1821. if (j != end(_recent)) {
  1822. if (removed) {
  1823. j->second.clear();
  1824. _recent.erase(j);
  1825. } else {
  1826. j->second.erase(
  1827. ranges::remove(j->second, true, &RecentReaction::my),
  1828. end(j->second));
  1829. if (j->second.empty()) {
  1830. _recent.erase(j);
  1831. }
  1832. }
  1833. }
  1834. if (tags) {
  1835. const auto sublist = _item->savedSublist();
  1836. history->owner().reactions().decrementMyTag(id, sublist);
  1837. }
  1838. auto &owner = history->owner();
  1839. owner.reactions().send(_item, false);
  1840. owner.notifyItemDataChange(_item);
  1841. }
  1842. bool MessageReactions::checkIfChanged(
  1843. const QVector<MTPReactionCount> &list,
  1844. const QVector<MTPMessagePeerReaction> &recent,
  1845. bool min) const {
  1846. auto &owner = _item->history()->owner();
  1847. if (owner.reactions().sending(_item)) {
  1848. // We'll apply non-stale data from the request response.
  1849. return false;
  1850. }
  1851. auto existing = base::flat_set<ReactionId>();
  1852. for (const auto &count : list) {
  1853. const auto changed = count.match([&](const MTPDreactionCount &data) {
  1854. const auto id = ReactionFromMTP(data.vreaction());
  1855. const auto nowCount = data.vcount().v;
  1856. const auto i = ranges::find(_list, id, &MessageReaction::id);
  1857. const auto wasCount = (i != end(_list)) ? i->count : 0;
  1858. if (wasCount != nowCount) {
  1859. return true;
  1860. }
  1861. existing.emplace(id);
  1862. return false;
  1863. });
  1864. if (changed) {
  1865. return true;
  1866. }
  1867. }
  1868. for (const auto &reaction : _list) {
  1869. if (!existing.contains(reaction.id)) {
  1870. return true;
  1871. }
  1872. }
  1873. auto parsed = base::flat_map<ReactionId, std::vector<RecentReaction>>();
  1874. for (const auto &reaction : recent) {
  1875. reaction.match([&](const MTPDmessagePeerReaction &data) {
  1876. const auto id = ReactionFromMTP(data.vreaction());
  1877. if (!ranges::contains(_list, id, &MessageReaction::id)) {
  1878. return;
  1879. }
  1880. const auto peerId = peerFromMTP(data.vpeer_id());
  1881. const auto peer = owner.peer(peerId);
  1882. const auto my = IsMyRecent(data, id, peer, _recent, min);
  1883. parsed[id].push_back({
  1884. .peer = peer,
  1885. .unread = data.is_unread(),
  1886. .big = data.is_big(),
  1887. .my = my,
  1888. });
  1889. });
  1890. }
  1891. return !ranges::equal(_recent, parsed, [](
  1892. const auto &a,
  1893. const auto &b) {
  1894. return ranges::equal(a.second, b.second, [](
  1895. const RecentReaction &a,
  1896. const RecentReaction &b) {
  1897. return (a.peer == b.peer) && (a.big == b.big) && (a.my == b.my);
  1898. });
  1899. });
  1900. }
  1901. bool MessageReactions::change(
  1902. const QVector<MTPReactionCount> &list,
  1903. const QVector<MTPMessagePeerReaction> &recent,
  1904. const QVector<MTPMessageReactor> &top,
  1905. bool min) {
  1906. auto &owner = _item->history()->owner();
  1907. if (owner.reactions().sending(_item)) {
  1908. // We'll apply non-stale data from the request response.
  1909. return false;
  1910. }
  1911. auto changed = false;
  1912. auto existing = base::flat_set<ReactionId>();
  1913. auto order = base::flat_map<ReactionId, int>();
  1914. for (const auto &count : list) {
  1915. count.match([&](const MTPDreactionCount &data) {
  1916. const auto id = ReactionFromMTP(data.vreaction());
  1917. const auto &chosen = data.vchosen_order();
  1918. if (!min && chosen) {
  1919. order[id] = chosen->v;
  1920. }
  1921. const auto i = ranges::find(_list, id, &MessageReaction::id);
  1922. const auto nowCount = data.vcount().v;
  1923. if (i == end(_list)) {
  1924. changed = true;
  1925. _list.push_back({
  1926. .id = id,
  1927. .count = nowCount,
  1928. .my = (!min && chosen)
  1929. });
  1930. } else {
  1931. const auto nowMy = min ? i->my : chosen.has_value();
  1932. if (i->count != nowCount || i->my != nowMy) {
  1933. i->count = nowCount;
  1934. i->my = nowMy;
  1935. changed = true;
  1936. }
  1937. }
  1938. existing.emplace(id);
  1939. });
  1940. }
  1941. if (!min && !order.empty()) {
  1942. const auto minimal = std::numeric_limits<int>::min();
  1943. const auto proj = [&](const MessageReaction &reaction) {
  1944. return reaction.my ? order[reaction.id] : minimal;
  1945. };
  1946. const auto correctOrder = [&] {
  1947. auto previousOrder = minimal;
  1948. for (const auto &reaction : _list) {
  1949. const auto nowOrder = proj(reaction);
  1950. if (nowOrder < previousOrder) {
  1951. return false;
  1952. }
  1953. previousOrder = nowOrder;
  1954. }
  1955. return true;
  1956. }();
  1957. if (!correctOrder) {
  1958. changed = true;
  1959. ranges::sort(_list, std::less(), proj);
  1960. }
  1961. }
  1962. if (_list.size() != existing.size()) {
  1963. changed = true;
  1964. for (auto i = begin(_list); i != end(_list);) {
  1965. if (!existing.contains(i->id)) {
  1966. i = _list.erase(i);
  1967. } else {
  1968. ++i;
  1969. }
  1970. }
  1971. }
  1972. auto parsed = base::flat_map<ReactionId, std::vector<RecentReaction>>();
  1973. for (const auto &reaction : recent) {
  1974. reaction.match([&](const MTPDmessagePeerReaction &data) {
  1975. const auto id = ReactionFromMTP(data.vreaction());
  1976. const auto i = ranges::find(_list, id, &MessageReaction::id);
  1977. if (i == end(_list)) {
  1978. return;
  1979. }
  1980. auto &list = parsed[id];
  1981. if (list.size() >= i->count) {
  1982. return;
  1983. }
  1984. const auto peer = owner.peer(peerFromMTP(data.vpeer_id()));
  1985. const auto my = IsMyRecent(data, id, peer, _recent, min);
  1986. list.push_back({
  1987. .peer = peer,
  1988. .unread = data.is_unread(),
  1989. .big = data.is_big(),
  1990. .my = my,
  1991. });
  1992. });
  1993. }
  1994. if (_recent != parsed) {
  1995. _recent = std::move(parsed);
  1996. changed = true;
  1997. }
  1998. auto paidTop = std::vector<TopPaid>();
  1999. const auto &paindTopNow = _paid ? _paid->top : std::vector<TopPaid>();
  2000. for (const auto &reactor : top) {
  2001. const auto &data = reactor.data();
  2002. const auto peerId = (data.is_anonymous() || !data.vpeer_id())
  2003. ? PeerId()
  2004. : peerFromMTP(*data.vpeer_id());
  2005. const auto peer = peerId ? owner.peer(peerId).get() : nullptr;
  2006. paidTop.push_back({
  2007. .peer = peer,
  2008. .count = uint32(data.vcount().v),
  2009. .top = data.is_top(),
  2010. .my = IsMyTop(data, peer, paindTopNow, min),
  2011. });
  2012. }
  2013. if (paidTop.empty()) {
  2014. if (_paid && !_paid->top.empty()) {
  2015. changed = true;
  2016. if (localPaidData()) {
  2017. _paid->top.clear();
  2018. } else {
  2019. _paid = nullptr;
  2020. }
  2021. }
  2022. } else {
  2023. if (min && _paid) {
  2024. const auto mine = [](const TopPaid &entry) {
  2025. return entry.my != 0;
  2026. };
  2027. if (!ranges::contains(paidTop, true, mine)) {
  2028. const auto nonTopMine = [](const TopPaid &entry) {
  2029. return entry.my && !entry.top;
  2030. };
  2031. const auto i = ranges::find(_paid->top, true, nonTopMine);
  2032. if (i != end(_paid->top)) {
  2033. paidTop.push_back(*i);
  2034. }
  2035. }
  2036. }
  2037. ranges::sort(paidTop, std::greater(), [](const TopPaid &entry) {
  2038. return entry.count;
  2039. });
  2040. if (!_paid) {
  2041. _paid = std::make_unique<Paid>();
  2042. }
  2043. if (_paid->top != paidTop) {
  2044. _paid->top = std::move(paidTop);
  2045. changed = true;
  2046. }
  2047. }
  2048. return changed;
  2049. }
  2050. const std::vector<MessageReaction> &MessageReactions::list() const {
  2051. return _list;
  2052. }
  2053. auto MessageReactions::recent() const
  2054. -> const base::flat_map<ReactionId, std::vector<RecentReaction>> & {
  2055. return _recent;
  2056. }
  2057. auto MessageReactions::topPaid() const -> const std::vector<TopPaid> & {
  2058. static const auto kEmpty = std::vector<TopPaid>();
  2059. return _paid ? _paid->top : kEmpty;
  2060. }
  2061. bool MessageReactions::empty() const {
  2062. return _list.empty();
  2063. }
  2064. bool MessageReactions::hasUnread() const {
  2065. for (auto &[emoji, list] : _recent) {
  2066. if (ranges::contains(list, true, &RecentReaction::unread)) {
  2067. return true;
  2068. }
  2069. }
  2070. return false;
  2071. }
  2072. void MessageReactions::markRead() {
  2073. for (auto &[emoji, list] : _recent) {
  2074. for (auto &reaction : list) {
  2075. reaction.unread = false;
  2076. }
  2077. }
  2078. }
  2079. void MessageReactions::scheduleSendPaid(
  2080. int count,
  2081. std::optional<PeerId> shownPeer) {
  2082. Expects(count >= 0);
  2083. if (!_paid) {
  2084. _paid = std::make_unique<Paid>();
  2085. }
  2086. _paid->scheduled += count;
  2087. _paid->scheduledFlag = 1;
  2088. if (shownPeer.has_value()) {
  2089. _paid->scheduledShownPeer = *shownPeer;
  2090. _paid->scheduledPrivacySet = true;
  2091. }
  2092. if (count > 0) {
  2093. _item->history()->session().credits().lock(StarsAmount(count));
  2094. }
  2095. _item->history()->owner().reactions().schedulePaid(_item);
  2096. }
  2097. int MessageReactions::scheduledPaid() const {
  2098. return _paid ? _paid->scheduled : 0;
  2099. }
  2100. void MessageReactions::cancelScheduledPaid() {
  2101. if (_paid) {
  2102. if (_paid->scheduledFlag) {
  2103. if (const auto amount = int(_paid->scheduled)) {
  2104. _item->history()->session().credits().unlock(
  2105. StarsAmount(amount));
  2106. }
  2107. _paid->scheduled = 0;
  2108. _paid->scheduledFlag = 0;
  2109. _paid->scheduledShownPeer = 0;
  2110. _paid->scheduledPrivacySet = 0;
  2111. }
  2112. if (!_paid->sendingFlag && _paid->top.empty()) {
  2113. _paid = nullptr;
  2114. }
  2115. }
  2116. }
  2117. PaidReactionSend MessageReactions::startPaidSending() {
  2118. if (!_paid || !_paid->scheduledFlag || _paid->sendingFlag) {
  2119. return {};
  2120. }
  2121. _paid->sending = _paid->scheduled;
  2122. _paid->sendingFlag = _paid->scheduledFlag;
  2123. _paid->sendingShownPeer = _paid->scheduledShownPeer;
  2124. _paid->sendingPrivacySet = _paid->scheduledPrivacySet;
  2125. _paid->scheduled = 0;
  2126. _paid->scheduledFlag = 0;
  2127. _paid->scheduledShownPeer = 0;
  2128. _paid->scheduledPrivacySet = 0;
  2129. return {
  2130. .count = int(_paid->sending),
  2131. .valid = true,
  2132. .shownPeer = MaybeShownPeer(
  2133. _paid->sendingPrivacySet,
  2134. _paid->sendingShownPeer),
  2135. };
  2136. }
  2137. void MessageReactions::finishPaidSending(
  2138. PaidReactionSend send,
  2139. bool success) {
  2140. Expects(_paid != nullptr);
  2141. Expects(send.count == _paid->sending);
  2142. Expects(send.valid == (_paid->sendingFlag == 1));
  2143. Expects(send.shownPeer == MaybeShownPeer(
  2144. _paid->sendingPrivacySet,
  2145. _paid->sendingShownPeer));
  2146. _paid->sending = 0;
  2147. _paid->sendingFlag = 0;
  2148. _paid->sendingShownPeer = 0;
  2149. _paid->sendingPrivacySet = 0;
  2150. if (!_paid->scheduledFlag && _paid->top.empty()) {
  2151. _paid = nullptr;
  2152. } else if (!send.count) {
  2153. const auto i = ranges::find_if(_paid->top, [](const TopPaid &top) {
  2154. return top.my;
  2155. });
  2156. if (i != end(_paid->top)) {
  2157. i->peer = send.shownPeer
  2158. ? _item->history()->owner().peer(*send.shownPeer).get()
  2159. : nullptr;
  2160. }
  2161. }
  2162. if (const auto amount = send.count) {
  2163. const auto credits = &_item->history()->session().credits();
  2164. if (success) {
  2165. credits->withdrawLocked(StarsAmount(amount));
  2166. } else {
  2167. credits->unlock(StarsAmount(amount));
  2168. }
  2169. }
  2170. }
  2171. bool MessageReactions::localPaidData() const {
  2172. return _paid && (_paid->scheduledFlag || _paid->sendingFlag);
  2173. }
  2174. int MessageReactions::localPaidCount() const {
  2175. return _paid ? (_paid->scheduled + _paid->sending) : 0;
  2176. }
  2177. PeerId MessageReactions::localPaidShownPeer() const {
  2178. const auto minePaidShownPeer = [&] {
  2179. for (const auto &entry : _paid->top) {
  2180. if (entry.my) {
  2181. return entry.peer ? entry.peer->id : PeerId();
  2182. }
  2183. }
  2184. const auto api = &_item->history()->session().api();
  2185. return api->globalPrivacy().paidReactionShownPeerCurrent();
  2186. };
  2187. return !_paid
  2188. ? PeerId()
  2189. : (_paid->scheduledFlag && _paid->scheduledPrivacySet)
  2190. ? _paid->scheduledShownPeer
  2191. : (_paid->sendingFlag && _paid->sendingPrivacySet)
  2192. ? _paid->sendingShownPeer
  2193. : minePaidShownPeer();
  2194. }
  2195. bool MessageReactions::clearCloudData() {
  2196. const auto result = !_list.empty();
  2197. _recent.clear();
  2198. _list.clear();
  2199. if (localPaidData()) {
  2200. _paid->top.clear();
  2201. } else {
  2202. _paid = nullptr;
  2203. }
  2204. return result;
  2205. }
  2206. std::vector<ReactionId> MessageReactions::chosen() const {
  2207. return _list
  2208. | ranges::views::filter(&MessageReaction::my)
  2209. | ranges::views::transform(&MessageReaction::id)
  2210. | ranges::to_vector;
  2211. }
  2212. } // namespace Data