emoji_interactions.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  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 "chat_helpers/emoji_interactions.h"
  8. #include "chat_helpers/stickers_emoji_pack.h"
  9. #include "history/history_item.h"
  10. #include "history/history.h"
  11. #include "history/view/history_view_element.h"
  12. #include "history/view/media/history_view_sticker.h"
  13. #include "main/main_session.h"
  14. #include "data/data_session.h"
  15. #include "data/data_changes.h"
  16. #include "data/data_peer.h"
  17. #include "data/data_document.h"
  18. #include "data/data_document_media.h"
  19. #include "ui/emoji_config.h"
  20. #include "base/random.h"
  21. #include "apiwrap.h"
  22. #include <QtCore/QJsonDocument>
  23. #include <QtCore/QJsonArray>
  24. #include <QtCore/QJsonObject>
  25. #include <QtCore/QJsonValue>
  26. namespace ChatHelpers {
  27. namespace {
  28. constexpr auto kMinDelay = crl::time(200);
  29. constexpr auto kAccumulateDelay = crl::time(1000);
  30. constexpr auto kAccumulateSeenRequests = kAccumulateDelay;
  31. constexpr auto kAcceptSeenSinceRequest = 3 * crl::time(1000);
  32. constexpr auto kMaxDelay = 2 * crl::time(1000);
  33. constexpr auto kTimeNever = std::numeric_limits<crl::time>::max();
  34. constexpr auto kJsonVersion = 1;
  35. } // namespace
  36. auto EmojiInteractions::Combine(CheckResult a, CheckResult b) -> CheckResult {
  37. return {
  38. .nextCheckAt = std::min(a.nextCheckAt, b.nextCheckAt),
  39. .waitingForDownload = a.waitingForDownload || b.waitingForDownload,
  40. };
  41. }
  42. EmojiInteractions::EmojiInteractions(not_null<Main::Session*> session)
  43. : _session(session)
  44. , _checkTimer([=] { check(); }) {
  45. _session->changes().messageUpdates(
  46. Data::MessageUpdate::Flag::Destroyed
  47. | Data::MessageUpdate::Flag::Edited
  48. ) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
  49. if (update.flags & Data::MessageUpdate::Flag::Destroyed) {
  50. _outgoing.remove(update.item);
  51. _incoming.remove(update.item);
  52. } else if (update.flags & Data::MessageUpdate::Flag::Edited) {
  53. checkEdition(update.item, _outgoing);
  54. checkEdition(update.item, _incoming);
  55. }
  56. }, _lifetime);
  57. }
  58. EmojiInteractions::~EmojiInteractions() = default;
  59. void EmojiInteractions::checkEdition(
  60. not_null<HistoryItem*> item,
  61. base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map) {
  62. const auto &pack = _session->emojiStickersPack();
  63. const auto i = map.find(item);
  64. if (i != end(map)
  65. && (i->second.front().emoji != pack.chooseInteractionEmoji(item))) {
  66. map.erase(i);
  67. }
  68. }
  69. void EmojiInteractions::startOutgoing(
  70. not_null<const HistoryView::Element*> view) {
  71. const auto item = view->data();
  72. if (!item->isRegular() || !item->history()->peer->isUser()) {
  73. return;
  74. }
  75. const auto &pack = _session->emojiStickersPack();
  76. const auto emoticon = item->originalText().text;
  77. const auto emoji = pack.chooseInteractionEmoji(emoticon);
  78. if (!emoji) {
  79. return;
  80. }
  81. const auto &list = pack.animationsForEmoji(emoji);
  82. if (list.empty()) {
  83. return;
  84. }
  85. auto &animations = _outgoing[item];
  86. if (!animations.empty() && animations.front().emoji != emoji) {
  87. // The message was edited, forget the old emoji.
  88. animations.clear();
  89. }
  90. const auto last = !animations.empty() ? &animations.back() : nullptr;
  91. const auto listSize = int(list.size());
  92. const auto chooseDifferent = (last && listSize > 1);
  93. const auto index = chooseDifferent
  94. ? base::RandomIndex(listSize - 1)
  95. : base::RandomIndex(listSize);
  96. const auto selected = (begin(list) + index)->second;
  97. const auto document = (chooseDifferent && selected == last->document)
  98. ? (begin(list) + index + 1)->second
  99. : selected;
  100. const auto media = document->createMediaView();
  101. media->checkStickerLarge();
  102. const auto now = crl::now();
  103. animations.push_back({
  104. .emoticon = emoticon,
  105. .emoji = emoji,
  106. .document = document,
  107. .media = media,
  108. .scheduledAt = now,
  109. .index = index,
  110. });
  111. check(now);
  112. }
  113. void EmojiInteractions::startIncoming(
  114. not_null<PeerData*> peer,
  115. MsgId messageId,
  116. const QString &emoticon,
  117. EmojiInteractionsBunch &&bunch) {
  118. if (!peer->isUser() || bunch.interactions.empty()) {
  119. return;
  120. }
  121. const auto item = _session->data().message(peer->id, messageId);
  122. if (!item || !item->isRegular()) {
  123. return;
  124. }
  125. const auto &pack = _session->emojiStickersPack();
  126. const auto emoji = pack.chooseInteractionEmoji(item);
  127. if (!emoji || emoji != pack.chooseInteractionEmoji(emoticon)) {
  128. return;
  129. }
  130. const auto &list = pack.animationsForEmoji(emoji);
  131. if (list.empty()) {
  132. return;
  133. }
  134. auto &animations = _incoming[item];
  135. if (!animations.empty() && animations.front().emoji != emoji) {
  136. // The message was edited, forget the old emoji.
  137. animations.clear();
  138. }
  139. const auto now = crl::now();
  140. for (const auto &single : bunch.interactions) {
  141. const auto at = now + crl::time(base::SafeRound(single.time * 1000));
  142. if (!animations.empty() && animations.back().scheduledAt >= at) {
  143. continue;
  144. }
  145. const auto listSize = int(list.size());
  146. const auto index = (single.index - 1);
  147. if (index < listSize) {
  148. const auto document = (begin(list) + index)->second;
  149. const auto media = document->createMediaView();
  150. media->checkStickerLarge();
  151. animations.push_back({
  152. .emoticon = emoticon,
  153. .emoji = emoji,
  154. .document = document,
  155. .media = media,
  156. .scheduledAt = at,
  157. .incoming = true,
  158. .index = index,
  159. });
  160. }
  161. }
  162. if (animations.empty()) {
  163. _incoming.remove(item);
  164. } else {
  165. check(now);
  166. }
  167. }
  168. void EmojiInteractions::seenOutgoing(
  169. not_null<PeerData*> peer,
  170. const QString &emoticon) {
  171. const auto &pack = _session->emojiStickersPack();
  172. if (const auto i = _playsSent.find(peer); i != end(_playsSent)) {
  173. if (const auto emoji = pack.chooseInteractionEmoji(emoticon)) {
  174. if (const auto j = i->second.find(emoji); j != end(i->second)) {
  175. const auto last = j->second.lastDoneReceivedAt;
  176. if (!last || last + kAcceptSeenSinceRequest > crl::now()) {
  177. _seen.fire({ peer, emoticon });
  178. }
  179. }
  180. }
  181. }
  182. }
  183. auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult {
  184. return Combine(
  185. checkAnimations(now, _outgoing),
  186. checkAnimations(now, _incoming));
  187. }
  188. auto EmojiInteractions::checkAnimations(
  189. crl::time now,
  190. base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map
  191. ) -> CheckResult {
  192. auto nearest = kTimeNever;
  193. auto waitingForDownload = false;
  194. for (auto i = begin(map); i != end(map);) {
  195. auto lastStartedAt = crl::time();
  196. auto &animations = i->second;
  197. // Erase too old requests.
  198. const auto j = ranges::find_if(animations, [&](const Animation &a) {
  199. return !a.startedAt && (a.scheduledAt + kMaxDelay <= now);
  200. });
  201. if (j == begin(animations)) {
  202. i = map.erase(i);
  203. continue;
  204. } else if (j != end(animations)) {
  205. animations.erase(j, end(animations));
  206. }
  207. const auto item = i->first;
  208. for (auto &animation : animations) {
  209. if (animation.startedAt) {
  210. lastStartedAt = animation.startedAt;
  211. } else if (!animation.media->loaded()) {
  212. animation.media->checkStickerLarge();
  213. waitingForDownload = true;
  214. break;
  215. } else if (!lastStartedAt || lastStartedAt + kMinDelay <= now) {
  216. animation.startedAt = now;
  217. _playRequests.fire({
  218. animation.emoticon,
  219. item,
  220. animation.media,
  221. animation.scheduledAt,
  222. animation.incoming,
  223. });
  224. break;
  225. } else {
  226. nearest = std::min(nearest, lastStartedAt + kMinDelay);
  227. break;
  228. }
  229. }
  230. ++i;
  231. }
  232. return {
  233. .nextCheckAt = nearest,
  234. .waitingForDownload = waitingForDownload,
  235. };
  236. }
  237. void EmojiInteractions::sendAccumulatedOutgoing(
  238. crl::time now,
  239. not_null<HistoryItem*> item,
  240. std::vector<Animation> &animations) {
  241. Expects(!animations.empty());
  242. const auto firstStartedAt = animations.front().startedAt;
  243. const auto intervalEnd = firstStartedAt + kAccumulateDelay;
  244. if (intervalEnd > now) {
  245. return;
  246. }
  247. const auto from = begin(animations);
  248. const auto till = ranges::find_if(animations, [&](const auto &animation) {
  249. return !animation.startedAt || (animation.startedAt >= intervalEnd);
  250. });
  251. auto bunch = EmojiInteractionsBunch();
  252. bunch.interactions.reserve(till - from);
  253. for (const auto &animation : ranges::make_subrange(from, till)) {
  254. bunch.interactions.push_back({
  255. .index = animation.index + 1,
  256. .time = (animation.startedAt - firstStartedAt) / 1000.,
  257. });
  258. }
  259. if (bunch.interactions.empty()) {
  260. return;
  261. }
  262. const auto peer = item->history()->peer;
  263. const auto emoji = from->emoji;
  264. const auto requestId = _session->api().request(MTPmessages_SetTyping(
  265. MTP_flags(0),
  266. peer->input,
  267. MTPint(), // top_msg_id
  268. MTP_sendMessageEmojiInteraction(
  269. MTP_string(from->emoticon),
  270. MTP_int(item->id),
  271. MTP_dataJSON(MTP_bytes(ToJson(bunch))))
  272. )).done([=](const MTPBool &result, mtpRequestId requestId) {
  273. auto &sent = _playsSent[peer][emoji];
  274. if (sent.lastRequestId == requestId) {
  275. sent.lastDoneReceivedAt = crl::now();
  276. if (!_checkTimer.isActive()) {
  277. _checkTimer.callOnce(kAcceptSeenSinceRequest);
  278. }
  279. }
  280. }).send();
  281. _playsSent[peer][emoji] = PlaySent{ .lastRequestId = requestId };
  282. animations.erase(from, till);
  283. }
  284. void EmojiInteractions::clearAccumulatedIncoming(
  285. crl::time now,
  286. std::vector<Animation> &animations) {
  287. Expects(!animations.empty());
  288. const auto from = begin(animations);
  289. const auto till = ranges::find_if(animations, [&](const auto &animation) {
  290. return !animation.startedAt
  291. || (animation.startedAt + kMinDelay) > now;
  292. });
  293. animations.erase(from, till);
  294. }
  295. auto EmojiInteractions::checkAccumulated(crl::time now) -> CheckResult {
  296. auto nearest = kTimeNever;
  297. for (auto i = begin(_outgoing); i != end(_outgoing);) {
  298. auto &[item, animations] = *i;
  299. sendAccumulatedOutgoing(now, item, animations);
  300. if (animations.empty()) {
  301. i = _outgoing.erase(i);
  302. continue;
  303. } else if (const auto firstStartedAt = animations.front().startedAt) {
  304. nearest = std::min(nearest, firstStartedAt + kAccumulateDelay);
  305. Assert(nearest > now);
  306. }
  307. ++i;
  308. }
  309. for (auto i = begin(_incoming); i != end(_incoming);) {
  310. auto &animations = i->second;
  311. clearAccumulatedIncoming(now, animations);
  312. if (animations.empty()) {
  313. i = _incoming.erase(i);
  314. continue;
  315. } else {
  316. // Doesn't really matter when, just clear them finally.
  317. nearest = std::min(nearest, now + kAccumulateDelay);
  318. }
  319. ++i;
  320. }
  321. return {
  322. .nextCheckAt = nearest,
  323. };
  324. }
  325. void EmojiInteractions::check(crl::time now) {
  326. if (!now) {
  327. now = crl::now();
  328. }
  329. checkSeenRequests(now);
  330. checkSentRequests(now);
  331. const auto result1 = checkAnimations(now);
  332. const auto result2 = checkAccumulated(now);
  333. const auto result = Combine(result1, result2);
  334. if (result.nextCheckAt < kTimeNever) {
  335. Assert(result.nextCheckAt > now);
  336. _checkTimer.callOnce(result.nextCheckAt - now);
  337. } else if (!_playStarted.empty()) {
  338. _checkTimer.callOnce(kAccumulateSeenRequests);
  339. } else if (!_playsSent.empty()) {
  340. _checkTimer.callOnce(kAcceptSeenSinceRequest);
  341. }
  342. setWaitingForDownload(result.waitingForDownload);
  343. }
  344. void EmojiInteractions::checkSeenRequests(crl::time now) {
  345. for (auto i = begin(_playStarted); i != end(_playStarted);) {
  346. auto &animations = i->second;
  347. for (auto j = begin(animations); j != end(animations);) {
  348. if (j->second + kAccumulateSeenRequests <= now) {
  349. j = animations.erase(j);
  350. } else {
  351. ++j;
  352. }
  353. }
  354. if (animations.empty()) {
  355. i = _playStarted.erase(i);
  356. } else {
  357. ++i;
  358. }
  359. }
  360. }
  361. void EmojiInteractions::checkSentRequests(crl::time now) {
  362. for (auto i = begin(_playsSent); i != end(_playsSent);) {
  363. auto &animations = i->second;
  364. for (auto j = begin(animations); j != end(animations);) {
  365. const auto last = j->second.lastDoneReceivedAt;
  366. if (last && last + kAcceptSeenSinceRequest <= now) {
  367. j = animations.erase(j);
  368. } else {
  369. ++j;
  370. }
  371. }
  372. if (animations.empty()) {
  373. i = _playsSent.erase(i);
  374. } else {
  375. ++i;
  376. }
  377. }
  378. }
  379. void EmojiInteractions::setWaitingForDownload(bool waiting) {
  380. if (_waitingForDownload == waiting) {
  381. return;
  382. }
  383. _waitingForDownload = waiting;
  384. if (_waitingForDownload) {
  385. _session->downloaderTaskFinished(
  386. ) | rpl::start_with_next([=] {
  387. check();
  388. }, _downloadCheckLifetime);
  389. } else {
  390. _downloadCheckLifetime.destroy();
  391. _downloadCheckLifetime.destroy();
  392. }
  393. }
  394. void EmojiInteractions::playStarted(not_null<PeerData*> peer, QString emoji) {
  395. auto &map = _playStarted[peer];
  396. const auto i = map.find(emoji);
  397. const auto now = crl::now();
  398. if (i != end(map) && now - i->second < kAccumulateSeenRequests) {
  399. return;
  400. }
  401. _session->api().request(MTPmessages_SetTyping(
  402. MTP_flags(0),
  403. peer->input,
  404. MTPint(), // top_msg_id
  405. MTP_sendMessageEmojiInteractionSeen(MTP_string(emoji))
  406. )).send();
  407. map[emoji] = now;
  408. if (!_checkTimer.isActive()) {
  409. _checkTimer.callOnce(kAccumulateSeenRequests);
  410. }
  411. }
  412. EmojiInteractionsBunch EmojiInteractions::Parse(const QByteArray &json) {
  413. auto error = QJsonParseError{ 0, QJsonParseError::NoError };
  414. const auto document = QJsonDocument::fromJson(json, &error);
  415. if (error.error != QJsonParseError::NoError || !document.isObject()) {
  416. LOG(("API Error: Bad interactions json received."));
  417. return {};
  418. }
  419. const auto root = document.object();
  420. const auto version = root.value("v").toInt();
  421. if (version != kJsonVersion) {
  422. LOG(("API Error: Bad interactions version: %1").arg(version));
  423. return {};
  424. }
  425. const auto actions = root.value("a").toArray();
  426. if (actions.empty()) {
  427. LOG(("API Error: Empty interactions list."));
  428. return {};
  429. }
  430. auto result = EmojiInteractionsBunch();
  431. for (const auto interaction : actions) {
  432. const auto object = interaction.toObject();
  433. const auto index = object.value("i").toInt();
  434. if (index < 0 || index > 10) {
  435. LOG(("API Error: Bad interaction index: %1").arg(index));
  436. return {};
  437. }
  438. const auto time = object.value("t").toDouble();
  439. if (time < 0.
  440. || time > 1.
  441. || (!result.interactions.empty()
  442. && time <= result.interactions.back().time)) {
  443. LOG(("API Error: Bad interaction time: %1").arg(time));
  444. continue;
  445. }
  446. result.interactions.push_back({ .index = index, .time = time });
  447. }
  448. return result;
  449. }
  450. QByteArray EmojiInteractions::ToJson(const EmojiInteractionsBunch &bunch) {
  451. auto list = QJsonArray();
  452. for (const auto &single : bunch.interactions) {
  453. list.push_back(QJsonObject{
  454. { "i", single.index },
  455. { "t", single.time },
  456. });
  457. }
  458. return QJsonDocument(QJsonObject{
  459. { "v", kJsonVersion },
  460. { "a", std::move(list) },
  461. }).toJson(QJsonDocument::Compact);
  462. }
  463. } // namespace ChatHelpers