emoji_keywords.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  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_keywords.h"
  8. #include "emoji_suggestions_helper.h"
  9. #include "lang/lang_instance.h"
  10. #include "lang/lang_cloud_manager.h"
  11. #include "lang/lang_keys.h"
  12. #include "core/application.h"
  13. #include "base/platform/base_platform_info.h"
  14. #include "ui/emoji_config.h"
  15. #include "main/main_domain.h"
  16. #include "main/main_session.h"
  17. #include "apiwrap.h"
  18. #include "core/application.h"
  19. #include "core/core_settings.h"
  20. #include <QtGui/QGuiApplication>
  21. namespace ChatHelpers {
  22. namespace {
  23. constexpr auto kRefreshEach = 60 * 60 * crl::time(1000); // 1 hour.
  24. constexpr auto kKeepNotUsedLangPacksCount = 4;
  25. constexpr auto kKeepNotUsedInputLanguagesCount = 4;
  26. using namespace Ui::Emoji;
  27. using Result = EmojiKeywords::Result;
  28. struct LangPackEmoji {
  29. EmojiPtr emoji = nullptr;
  30. QString text;
  31. };
  32. struct LangPackData {
  33. int version = 0;
  34. int maxKeyLength = 0;
  35. std::map<QString, std::vector<LangPackEmoji>> emoji;
  36. };
  37. [[nodiscard]] bool MustAddPostfix(const QString &text) {
  38. if (text.size() != 1) {
  39. return false;
  40. }
  41. const auto code = text[0].unicode();
  42. return (code == 0x2122U) || (code == 0xA9U) || (code == 0xAEU);
  43. }
  44. [[nodiscard]] bool SkipExactKeyword(
  45. const QString &language,
  46. const QString &word) {
  47. if ((word.size() == 1) && !word[0].isLetter()) {
  48. return true;
  49. } else if (word == u"10"_q) {
  50. return true;
  51. } else if (language != u"en"_q) {
  52. return false;
  53. } else if ((word.size() == 1)
  54. && (word[0] != '$')
  55. && (word[0].unicode() != 8364)) { // Euro.
  56. return true;
  57. } else if ((word.size() == 2)
  58. && (word != u"us"_q)
  59. && (word != u"uk"_q)
  60. && (word != u"hi"_q)
  61. && (word != u"ok"_q)) {
  62. return true;
  63. }
  64. return false;
  65. }
  66. [[nodiscard]] EmojiPtr FindExact(const QString &text) {
  67. auto length = 0;
  68. const auto result = Find(text, &length);
  69. return (length < text.size()) ? nullptr : result;
  70. }
  71. void CreateCacheFilePath() {
  72. QDir().mkpath(internal::CacheFileFolder() + u"/keywords"_q);
  73. }
  74. [[nodiscard]] QString CacheFilePath(QString id) {
  75. static const auto BadSymbols = QRegularExpression("[^a-zA-Z0-9_\\.\\-]");
  76. id.replace(BadSymbols, QString());
  77. if (id.isEmpty()) {
  78. return QString();
  79. }
  80. return internal::CacheFileFolder() + u"/keywords/"_q + id;
  81. }
  82. [[nodiscard]] LangPackData ReadLocalCache(const QString &id) {
  83. auto file = QFile(CacheFilePath(id));
  84. if (!file.open(QIODevice::ReadOnly)) {
  85. return {};
  86. }
  87. auto result = LangPackData();
  88. auto stream = QDataStream(&file);
  89. stream.setVersion(QDataStream::Qt_5_1);
  90. auto version = qint32();
  91. auto count = qint32();
  92. stream
  93. >> version
  94. >> count;
  95. if (version < 0 || count < 0 || stream.status() != QDataStream::Ok) {
  96. return {};
  97. }
  98. for (auto i = 0; i != count; ++i) {
  99. auto key = QString();
  100. auto size = qint32();
  101. stream
  102. >> key
  103. >> size;
  104. if (size < 0 || stream.status() != QDataStream::Ok) {
  105. return {};
  106. }
  107. auto &list = result.emoji[key];
  108. for (auto j = 0; j != size; ++j) {
  109. auto text = QString();
  110. stream >> text;
  111. if (stream.status() != QDataStream::Ok) {
  112. return {};
  113. }
  114. const auto emoji = MustAddPostfix(text)
  115. ? (text + QChar(Ui::Emoji::kPostfix))
  116. : text;
  117. const auto entry = LangPackEmoji{ FindExact(emoji), text };
  118. if (!entry.emoji) {
  119. return {};
  120. }
  121. list.push_back(entry);
  122. }
  123. result.maxKeyLength = std::max(result.maxKeyLength, int(key.size()));
  124. }
  125. result.version = version;
  126. return result;
  127. }
  128. void WriteLocalCache(const QString &id, const LangPackData &data) {
  129. if (!data.version && data.emoji.empty()) {
  130. return;
  131. }
  132. CreateCacheFilePath();
  133. auto file = QFile(CacheFilePath(id));
  134. if (!file.open(QIODevice::WriteOnly)) {
  135. return;
  136. }
  137. auto stream = QDataStream(&file);
  138. stream.setVersion(QDataStream::Qt_5_1);
  139. stream
  140. << qint32(data.version)
  141. << qint32(data.emoji.size());
  142. for (const auto &[key, list] : data.emoji) {
  143. stream
  144. << key
  145. << qint32(list.size());
  146. for (const auto &emoji : list) {
  147. stream << emoji.text;
  148. }
  149. }
  150. }
  151. [[nodiscard]] QString NormalizeQuery(const QString &query) {
  152. return query.toLower();
  153. }
  154. [[nodiscard]] QString NormalizeKey(const QString &key) {
  155. return key.toLower().trimmed();
  156. }
  157. void AppendFoundEmoji(
  158. std::vector<Result> &result,
  159. const QString &label,
  160. const std::vector<LangPackEmoji> &list) {
  161. // It is important that the 'result' won't relocate while inserting.
  162. result.reserve(result.size() + list.size());
  163. const auto alreadyBegin = begin(result);
  164. const auto alreadyEnd = alreadyBegin + result.size();
  165. auto &&add = ranges::views::all(
  166. list
  167. ) | ranges::views::filter([&](const LangPackEmoji &entry) {
  168. const auto i = ranges::find(
  169. alreadyBegin,
  170. alreadyEnd,
  171. entry.emoji,
  172. &Result::emoji);
  173. return (i == alreadyEnd);
  174. }) | ranges::views::transform([&](const LangPackEmoji &entry) {
  175. return Result{ entry.emoji, label, entry.text };
  176. });
  177. result.insert(end(result), add.begin(), add.end());
  178. }
  179. void AppendLegacySuggestions(
  180. std::vector<Result> &result,
  181. const QString &query) {
  182. const auto badSuggestionChar = [](QChar ch) {
  183. return (ch < 'a' || ch > 'z')
  184. && (ch < 'A' || ch > 'Z')
  185. && (ch < '0' || ch > '9')
  186. && (ch != '_')
  187. && (ch != '-')
  188. && (ch != '+');
  189. };
  190. if (ranges::any_of(query, badSuggestionChar)) {
  191. return;
  192. }
  193. const auto suggestions = GetSuggestions(QStringToUTF16(query));
  194. // It is important that the 'result' won't relocate while inserting.
  195. result.reserve(result.size() + suggestions.size());
  196. const auto alreadyBegin = begin(result);
  197. const auto alreadyEnd = alreadyBegin + result.size();
  198. auto &&add = ranges::views::all(
  199. suggestions
  200. ) | ranges::views::transform([](const Suggestion &suggestion) {
  201. return Result{
  202. Find(QStringFromUTF16(suggestion.emoji())),
  203. QStringFromUTF16(suggestion.label()),
  204. QStringFromUTF16(suggestion.replacement())
  205. };
  206. }) | ranges::views::filter([&](const Result &entry) {
  207. const auto i = entry.emoji
  208. ? ranges::find(
  209. alreadyBegin,
  210. alreadyEnd,
  211. entry.emoji,
  212. &Result::emoji)
  213. : alreadyEnd;
  214. return (entry.emoji != nullptr)
  215. && (i == alreadyEnd);
  216. });
  217. result.insert(end(result), add.begin(), add.end());
  218. }
  219. void ApplyDifference(
  220. LangPackData &data,
  221. const QVector<MTPEmojiKeyword> &keywords,
  222. int version) {
  223. data.version = version;
  224. for (const auto &keyword : keywords) {
  225. keyword.match([&](const MTPDemojiKeyword &keyword) {
  226. const auto word = NormalizeKey(qs(keyword.vkeyword()));
  227. if (word.isEmpty()) {
  228. return;
  229. }
  230. auto &list = data.emoji[word];
  231. auto &&emoji = ranges::views::all(
  232. keyword.vemoticons().v
  233. ) | ranges::views::transform([](const MTPstring &string) {
  234. const auto text = qs(string);
  235. const auto emoji = MustAddPostfix(text)
  236. ? (text + QChar(Ui::Emoji::kPostfix))
  237. : text;
  238. return LangPackEmoji{ FindExact(emoji), text };
  239. }) | ranges::views::filter([&](const LangPackEmoji &entry) {
  240. if (!entry.emoji) {
  241. LOG(("API Warning: emoji %1 is not supported, word: %2."
  242. ).arg(
  243. entry.text,
  244. word));
  245. }
  246. return (entry.emoji != nullptr);
  247. });
  248. list.insert(end(list), emoji.begin(), emoji.end());
  249. }, [&](const MTPDemojiKeywordDeleted &keyword) {
  250. const auto word = NormalizeKey(qs(keyword.vkeyword()));
  251. if (word.isEmpty()) {
  252. return;
  253. }
  254. const auto i = data.emoji.find(word);
  255. if (i == end(data.emoji)) {
  256. return;
  257. }
  258. auto &list = i->second;
  259. for (const auto &emoji : keyword.vemoticons().v) {
  260. list.erase(
  261. ranges::remove(list, qs(emoji), &LangPackEmoji::text),
  262. end(list));
  263. }
  264. if (list.empty()) {
  265. data.emoji.erase(i);
  266. }
  267. });
  268. }
  269. if (data.emoji.empty()) {
  270. data.maxKeyLength = 0;
  271. } else {
  272. auto &&lengths = ranges::views::all(
  273. data.emoji
  274. ) | ranges::views::transform([](auto &&pair) {
  275. return pair.first.size();
  276. });
  277. data.maxKeyLength = *ranges::max_element(lengths);
  278. }
  279. }
  280. } // namespace
  281. class EmojiKeywords::LangPack final {
  282. public:
  283. using Delegate = details::EmojiKeywordsLangPackDelegate;
  284. LangPack(not_null<Delegate*> delegate, const QString &id);
  285. LangPack(const LangPack &other) = delete;
  286. LangPack &operator=(const LangPack &other) = delete;
  287. ~LangPack();
  288. [[nodiscard]] QString id() const;
  289. void refresh();
  290. void apiChanged();
  291. [[nodiscard]] std::vector<Result> query(
  292. const QString &normalized,
  293. bool exact) const;
  294. [[nodiscard]] int maxQueryLength() const;
  295. private:
  296. enum class State {
  297. ReadingCache,
  298. PendingRequest,
  299. Requested,
  300. Refreshed,
  301. };
  302. void readLocalCache();
  303. void applyDifference(const MTPEmojiKeywordsDifference &result);
  304. void applyData(LangPackData &&data);
  305. not_null<Delegate*> _delegate;
  306. QString _id;
  307. State _state = State::ReadingCache;
  308. LangPackData _data;
  309. crl::time _lastRefreshTime = 0;
  310. mtpRequestId _requestId = 0;
  311. base::binary_guard _guard;
  312. };
  313. EmojiKeywords::LangPack::LangPack(
  314. not_null<Delegate*> delegate,
  315. const QString &id)
  316. : _delegate(delegate)
  317. , _id(id) {
  318. readLocalCache();
  319. }
  320. EmojiKeywords::LangPack::~LangPack() {
  321. if (_requestId) {
  322. if (const auto api = _delegate->api()) {
  323. api->request(_requestId).cancel();
  324. }
  325. }
  326. }
  327. void EmojiKeywords::LangPack::readLocalCache() {
  328. const auto id = _id;
  329. auto callback = crl::guard(_guard.make_guard(), [=](
  330. LangPackData &&result) {
  331. applyData(std::move(result));
  332. refresh();
  333. });
  334. crl::async([id, callback = std::move(callback)]() mutable {
  335. crl::on_main([
  336. callback = std::move(callback),
  337. result = ReadLocalCache(id)
  338. ]() mutable {
  339. callback(std::move(result));
  340. });
  341. });
  342. }
  343. QString EmojiKeywords::LangPack::id() const {
  344. return _id;
  345. }
  346. void EmojiKeywords::LangPack::refresh() {
  347. if (_state != State::Refreshed) {
  348. return;
  349. } else if (_lastRefreshTime > 0
  350. && crl::now() - _lastRefreshTime < kRefreshEach) {
  351. return;
  352. }
  353. const auto api = _delegate->api();
  354. if (!api) {
  355. _state = State::PendingRequest;
  356. return;
  357. }
  358. _state = State::Requested;
  359. const auto send = [&](auto &&request) {
  360. return api->request(
  361. std::move(request)
  362. ).done([=](const MTPEmojiKeywordsDifference &result) {
  363. _requestId = 0;
  364. _lastRefreshTime = crl::now();
  365. applyDifference(result);
  366. }).fail([=] {
  367. _requestId = 0;
  368. _lastRefreshTime = crl::now();
  369. }).send();
  370. };
  371. _requestId = (_data.version > 0)
  372. ? send(MTPmessages_GetEmojiKeywordsDifference(
  373. MTP_string(_id),
  374. MTP_int(_data.version)))
  375. : send(MTPmessages_GetEmojiKeywords(
  376. MTP_string(_id)));
  377. }
  378. void EmojiKeywords::LangPack::applyDifference(
  379. const MTPEmojiKeywordsDifference &result) {
  380. result.match([&](const MTPDemojiKeywordsDifference &data) {
  381. const auto code = qs(data.vlang_code());
  382. const auto version = data.vversion().v;
  383. const auto &keywords = data.vkeywords().v;
  384. if (code != _id) {
  385. LOG(("API Error: Bad lang_code for emoji keywords %1 -> %2").arg(
  386. _id,
  387. code));
  388. _data.version = 0;
  389. _state = State::Refreshed;
  390. return;
  391. } else if (keywords.isEmpty() && _data.version >= version) {
  392. _state = State::Refreshed;
  393. return;
  394. }
  395. const auto id = _id;
  396. auto copy = _data;
  397. auto callback = crl::guard(_guard.make_guard(), [=](
  398. LangPackData &&result) {
  399. applyData(std::move(result));
  400. });
  401. crl::async([=,
  402. copy = std::move(copy),
  403. callback = std::move(callback)]() mutable {
  404. ApplyDifference(copy, keywords, version);
  405. WriteLocalCache(id, copy);
  406. crl::on_main([
  407. result = std::move(copy),
  408. callback = std::move(callback)
  409. ]() mutable {
  410. callback(std::move(result));
  411. });
  412. });
  413. });
  414. }
  415. void EmojiKeywords::LangPack::applyData(LangPackData &&data) {
  416. _data = std::move(data);
  417. _state = State::Refreshed;
  418. _delegate->langPackRefreshed();
  419. }
  420. void EmojiKeywords::LangPack::apiChanged() {
  421. if (_state == State::Requested && !_delegate->api()) {
  422. _requestId = 0;
  423. } else if (_state != State::PendingRequest) {
  424. return;
  425. }
  426. _state = State::Refreshed;
  427. refresh();
  428. }
  429. std::vector<Result> EmojiKeywords::LangPack::query(
  430. const QString &normalized,
  431. bool exact) const {
  432. if (normalized.size() > _data.maxKeyLength
  433. || _data.emoji.empty()
  434. || (exact && SkipExactKeyword(_id, normalized))) {
  435. return {};
  436. }
  437. const auto from = _data.emoji.lower_bound(normalized);
  438. auto &&chosen = ranges::make_subrange(
  439. from,
  440. end(_data.emoji)
  441. ) | ranges::views::take_while([&](const auto &pair) {
  442. const auto &key = pair.first;
  443. return exact ? (key == normalized) : key.startsWith(normalized);
  444. });
  445. auto result = std::vector<Result>();
  446. for (const auto &[key, list] : chosen) {
  447. AppendFoundEmoji(result, key, list);
  448. }
  449. return result;
  450. }
  451. int EmojiKeywords::LangPack::maxQueryLength() const {
  452. return _data.maxKeyLength;
  453. }
  454. EmojiKeywords::EmojiKeywords() {
  455. crl::on_main(&_guard, [=] {
  456. handleSessionChanges();
  457. });
  458. }
  459. EmojiKeywords::~EmojiKeywords() = default;
  460. not_null<details::EmojiKeywordsLangPackDelegate*> EmojiKeywords::delegate() {
  461. return static_cast<details::EmojiKeywordsLangPackDelegate*>(this);
  462. }
  463. ApiWrap *EmojiKeywords::api() {
  464. return _api;
  465. }
  466. void EmojiKeywords::langPackRefreshed() {
  467. _refreshed.fire({});
  468. }
  469. void EmojiKeywords::handleSessionChanges() {
  470. Core::App().domain().activeSessionValue( // #TODO multi someSessionValue
  471. ) | rpl::map([](Main::Session *session) {
  472. return session ? &session->api() : nullptr;
  473. }) | rpl::start_with_next([=](ApiWrap *api) {
  474. apiChanged(api);
  475. }, _lifetime);
  476. }
  477. void EmojiKeywords::apiChanged(ApiWrap *api) {
  478. _api = api;
  479. if (_api) {
  480. crl::on_main(&_api->session(), crl::guard(&_guard, [=] {
  481. Lang::CurrentCloudManager().firstLanguageSuggestion(
  482. ) | rpl::filter([=] {
  483. // Refresh with the suggested language if we already were asked.
  484. return !_data.empty();
  485. }) | rpl::start_with_next([=] {
  486. refresh();
  487. }, _suggestedChangeLifetime);
  488. }));
  489. } else {
  490. _langsRequestId = 0;
  491. _suggestedChangeLifetime.destroy();
  492. }
  493. for (const auto &[language, item] : _data) {
  494. item->apiChanged();
  495. }
  496. }
  497. void EmojiKeywords::refresh() {
  498. auto list = languages();
  499. if (_localList != list) {
  500. _localList = std::move(list);
  501. refreshRemoteList();
  502. } else {
  503. refreshFromRemoteList();
  504. }
  505. }
  506. std::vector<QString> EmojiKeywords::languages() {
  507. if (!_api) {
  508. return {};
  509. }
  510. refreshInputLanguages();
  511. auto result = std::vector<QString>();
  512. const auto yield = [&](const QString &language) {
  513. result.push_back(language);
  514. };
  515. const auto yieldList = [&](const QStringList &list) {
  516. result.insert(end(result), list.begin(), list.end());
  517. };
  518. yield(Lang::Id());
  519. yield(Lang::DefaultLanguageId());
  520. yield(Lang::CurrentCloudManager().suggestedLanguage());
  521. yield(Platform::SystemLanguage());
  522. yieldList(QLocale::system().uiLanguages());
  523. for (const auto &list : _inputLanguages) {
  524. yieldList(list);
  525. }
  526. ranges::sort(result);
  527. return result;
  528. }
  529. void EmojiKeywords::refreshInputLanguages() {
  530. const auto method = QGuiApplication::inputMethod();
  531. if (!method) {
  532. return;
  533. }
  534. const auto list = method->locale().uiLanguages();
  535. const auto i = ranges::find(_inputLanguages, list);
  536. if (i != end(_inputLanguages)) {
  537. std::rotate(i, i + 1, end(_inputLanguages));
  538. } else {
  539. if (_inputLanguages.size() >= kKeepNotUsedInputLanguagesCount) {
  540. _inputLanguages.pop_front();
  541. }
  542. _inputLanguages.push_back(list);
  543. }
  544. }
  545. rpl::producer<> EmojiKeywords::refreshed() const {
  546. return _refreshed.events();
  547. }
  548. std::vector<Result> EmojiKeywords::query(
  549. const QString &query,
  550. bool exact) const {
  551. const auto normalized = NormalizeQuery(query);
  552. if (normalized.isEmpty()) {
  553. return {};
  554. }
  555. auto result = std::vector<Result>();
  556. for (const auto &[language, item] : _data) {
  557. const auto list = item->query(normalized, exact);
  558. // It is important that the 'result' won't relocate while inserting.
  559. result.reserve(result.size() + list.size());
  560. const auto alreadyBegin = begin(result);
  561. const auto alreadyEnd = alreadyBegin + result.size();
  562. auto &&add = ranges::views::all(
  563. list
  564. ) | ranges::views::filter([&](Result entry) {
  565. // In each item->query() result the list has no duplicates.
  566. // So we need to check only for duplicates between queries.
  567. const auto i = ranges::find(
  568. alreadyBegin,
  569. alreadyEnd,
  570. entry.emoji,
  571. &Result::emoji);
  572. return (i == alreadyEnd);
  573. });
  574. result.insert(end(result), add.begin(), add.end());
  575. }
  576. if (!exact) {
  577. AppendLegacySuggestions(result, query);
  578. }
  579. return result;
  580. }
  581. std::vector<Result> EmojiKeywords::queryMine(
  582. const QString &query,
  583. bool exact) const {
  584. return ApplyVariants(PrioritizeRecent(this->query(query, exact)));
  585. }
  586. std::vector<Result> EmojiKeywords::PrioritizeRecent(
  587. std::vector<Result> list) {
  588. using Entry = Result;
  589. auto lastRecent = begin(list);
  590. const auto &recent = Core::App().settings().recentEmoji();
  591. for (const auto &item : recent) {
  592. const auto emoji = std::get_if<EmojiPtr>(&item.id.data);
  593. if (!emoji) {
  594. continue;
  595. }
  596. const auto original = (*emoji)->original()
  597. ? (*emoji)->original()
  598. : (*emoji);
  599. const auto it = ranges::find(list, original, [](const Entry &entry) {
  600. return entry.emoji;
  601. });
  602. if (it > lastRecent && it != end(list)) {
  603. std::rotate(lastRecent, it, it + 1);
  604. ++lastRecent;
  605. }
  606. }
  607. return list;
  608. }
  609. std::vector<Result> EmojiKeywords::ApplyVariants(std::vector<Result> list) {
  610. auto &settings = Core::App().settings();
  611. for (auto &item : list) {
  612. item.emoji = settings.lookupEmojiVariant(item.emoji);
  613. }
  614. return list;
  615. }
  616. int EmojiKeywords::maxQueryLength() const {
  617. if (_data.empty()) {
  618. return 0;
  619. }
  620. auto &&lengths = _data | ranges::views::transform([](const auto &pair) {
  621. return pair.second->maxQueryLength();
  622. });
  623. return *ranges::max_element(lengths);
  624. }
  625. void EmojiKeywords::refreshRemoteList() {
  626. if (!_api) {
  627. _localList.clear();
  628. setRemoteList({});
  629. return;
  630. }
  631. _api->request(base::take(_langsRequestId)).cancel();
  632. auto languages = QVector<MTPstring>();
  633. for (const auto &id : _localList) {
  634. languages.push_back(MTP_string(id));
  635. }
  636. _langsRequestId = _api->request(MTPmessages_GetEmojiKeywordsLanguages(
  637. MTP_vector<MTPstring>(languages)
  638. )).done([=](const MTPVector<MTPEmojiLanguage> &result) {
  639. setRemoteList(ranges::views::all(
  640. result.v
  641. ) | ranges::views::transform([](const MTPEmojiLanguage &language) {
  642. return language.match([&](const MTPDemojiLanguage &language) {
  643. return qs(language.vlang_code());
  644. });
  645. }) | ranges::to_vector);
  646. _langsRequestId = 0;
  647. }).fail([=] {
  648. _langsRequestId = 0;
  649. }).send();
  650. }
  651. void EmojiKeywords::setRemoteList(std::vector<QString> &&list) {
  652. if (_remoteList == list) {
  653. return;
  654. }
  655. _remoteList = std::move(list);
  656. for (auto i = begin(_data); i != end(_data);) {
  657. if (ranges::find(_remoteList, i->first) != end(_remoteList)) {
  658. ++i;
  659. } else {
  660. if (_notUsedData.size() >= kKeepNotUsedLangPacksCount) {
  661. _notUsedData.pop_front();
  662. }
  663. _notUsedData.push_back(std::move(i->second));
  664. i = _data.erase(i);
  665. }
  666. }
  667. refreshFromRemoteList();
  668. }
  669. void EmojiKeywords::refreshFromRemoteList() {
  670. for (const auto &id : _remoteList) {
  671. if (const auto i = _data.find(id); i != end(_data)) {
  672. i->second->refresh();
  673. continue;
  674. }
  675. const auto i = ranges::find(
  676. _notUsedData,
  677. id,
  678. [](const std::unique_ptr<LangPack> &p) { return p->id(); });
  679. if (i != end(_notUsedData)) {
  680. _data.emplace(id, std::move(*i));
  681. _notUsedData.erase(i);
  682. } else {
  683. _data.emplace(
  684. id,
  685. std::make_unique<LangPack>(delegate(), id));
  686. }
  687. }
  688. }
  689. } // namespace ChatHelpers