/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_emoji_statuses.h" #include "main/main_session.h" #include "data/data_user.h" #include "data/data_session.h" #include "data/data_document.h" #include "data/stickers/data_stickers.h" #include "base/unixtime.h" #include "base/timer_rpl.h" #include "base/call_delayed.h" #include "apiwrap.h" #include "ui/controls/tabbed_search.h" namespace Data { namespace { constexpr auto kRefreshDefaultListEach = 60 * 60 * crl::time(1000); constexpr auto kRecentRequestTimeout = 10 * crl::time(1000); constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000); [[nodiscard]] std::vector ListFromMTP( const MTPDaccount_emojiStatuses &data) { const auto &list = data.vstatuses().v; auto result = std::vector(); result.reserve(list.size()); for (const auto &status : list) { const auto parsed = ParseEmojiStatus(status); if (!parsed.id) { LOG(("API Error: emojiStatusEmpty in account.emojiStatuses.")); } else { result.push_back(parsed.id); } } return result; } } // namespace EmojiStatuses::EmojiStatuses(not_null owner) : _owner(owner) , _clearingTimer([=] { processClearing(); }) { refreshDefault(); refreshColored(); base::timer_each( kRefreshDefaultListEach ) | rpl::start_with_next([=] { refreshDefault(); }, _lifetime); } EmojiStatuses::~EmojiStatuses() = default; Main::Session &EmojiStatuses::session() const { return _owner->session(); } void EmojiStatuses::refreshRecent() { requestRecent(); } void EmojiStatuses::refreshDefault() { requestDefault(); } void EmojiStatuses::refreshColored() { requestColored(); } void EmojiStatuses::refreshRecentDelayed() { if (_recentRequestId || _recentRequestScheduled) { return; } _recentRequestScheduled = true; base::call_delayed(kRecentRequestTimeout, &_owner->session(), [=] { if (_recentRequestScheduled) { requestRecent(); } }); } const std::vector &EmojiStatuses::list(Type type) const { switch (type) { case Type::Recent: return _recent; case Type::Default: return _default; case Type::Colored: return _colored; } Unexpected("Type in EmojiStatuses::list."); } rpl::producer<> EmojiStatuses::recentUpdates() const { return _recentUpdated.events(); } rpl::producer<> EmojiStatuses::defaultUpdates() const { return _defaultUpdated.events(); } void EmojiStatuses::registerAutomaticClear( not_null user, TimeId until) { if (!until) { _clearing.remove(user); if (_clearing.empty()) { _clearingTimer.cancel(); } } else if (auto &already = _clearing[user]; already != until) { already = until; const auto i = ranges::min_element(_clearing, {}, [](auto &&pair) { return pair.second; }); if (i->first == user) { const auto now = base::unixtime::now(); if (now < until) { processClearingIn(until - now); } else { processClearing(); } } } } auto EmojiStatuses::emojiGroupsValue() const -> rpl::producer { const_cast(this)->requestEmojiGroups(); return _emojiGroups.data.value(); } auto EmojiStatuses::statusGroupsValue() const -> rpl::producer { const_cast(this)->requestStatusGroups(); return _statusGroups.data.value(); } void EmojiStatuses::requestEmojiGroups() { requestGroups( &_emojiGroups, MTPmessages_GetEmojiGroups(MTP_int(_emojiGroups.hash))); } void EmojiStatuses::requestStatusGroups() { requestGroups( &_statusGroups, MTPmessages_GetEmojiStatusGroups(MTP_int(_statusGroups.hash))); } [[nodiscard]] std::vector GroupsFromTL( const MTPDmessages_emojiGroups &data) { const auto &list = data.vgroups().v; auto result = std::vector(); result.reserve(list.size()); for (const auto &group : list) { const auto &data = group.data(); auto emoticons = ranges::views::all( data.vemoticons().v ) | ranges::view::transform([](const MTPstring &emoticon) { return qs(emoticon); }) | ranges::to_vector; result.push_back({ .iconId = QString::number(data.vicon_emoji_id().v), .emoticons = std::move(emoticons), }); } return result; } template void EmojiStatuses::requestGroups( not_null type, Request &&request) { if (type->requestId) { return; } type->requestId = _owner->session().api().request( std::forward(request) ).done([=](const MTPmessages_EmojiGroups &result) { type->requestId = 0; result.match([&](const MTPDmessages_emojiGroups &data) { type->hash = data.vhash().v; type->data = GroupsFromTL(data); }, [](const MTPDmessages_emojiGroupsNotModified&) { }); }).fail([=] { type->requestId = 0; }).send(); } void EmojiStatuses::processClearing() { auto minWait = TimeId(0); const auto now = base::unixtime::now(); auto clearing = base::take(_clearing); for (auto i = begin(clearing); i != end(clearing);) { const auto until = i->second; if (now < until) { const auto wait = (until - now); if (!minWait || minWait > wait) { minWait = wait; } ++i; } else { i->first->setEmojiStatus(0, 0); i = clearing.erase(i); } } if (_clearing.empty()) { _clearing = std::move(clearing); } else { for (const auto &[user, until] : clearing) { _clearing.emplace(user, until); } } if (minWait) { processClearingIn(minWait); } else { _clearingTimer.cancel(); } } void EmojiStatuses::processClearingIn(TimeId wait) { const auto waitms = wait * crl::time(1000); _clearingTimer.callOnce(std::min(waitms, kMaxTimeout)); } void EmojiStatuses::requestRecent() { if (_recentRequestId) { return; } auto &api = _owner->session().api(); _recentRequestScheduled = false; _recentRequestId = api.request(MTPaccount_GetRecentEmojiStatuses( MTP_long(_recentHash) )).done([=](const MTPaccount_EmojiStatuses &result) { _recentRequestId = 0; result.match([&](const MTPDaccount_emojiStatuses &data) { updateRecent(data); }, [](const MTPDaccount_emojiStatusesNotModified&) { }); }).fail([=] { _recentRequestId = 0; _recentHash = 0; }).send(); } void EmojiStatuses::requestDefault() { if (_defaultRequestId) { return; } auto &api = _owner->session().api(); _defaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses( MTP_long(_recentHash) )).done([=](const MTPaccount_EmojiStatuses &result) { _defaultRequestId = 0; result.match([&](const MTPDaccount_emojiStatuses &data) { updateDefault(data); }, [&](const MTPDaccount_emojiStatusesNotModified &) { }); }).fail([=] { _defaultRequestId = 0; _defaultHash = 0; }).send(); } void EmojiStatuses::requestColored() { if (_coloredRequestId) { return; } auto &api = _owner->session().api(); _coloredRequestId = api.request(MTPmessages_GetStickerSet( MTP_inputStickerSetEmojiDefaultStatuses(), MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { _coloredRequestId = 0; result.match([&](const MTPDmessages_stickerSet &data) { updateColored(data); }, [](const MTPDmessages_stickerSetNotModified &) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); }).fail([=] { _coloredRequestId = 0; }).send(); } void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) { _recentHash = data.vhash().v; _recent = ListFromMTP(data); _recentUpdated.fire({}); } void EmojiStatuses::updateDefault(const MTPDaccount_emojiStatuses &data) { _defaultHash = data.vhash().v; _default = ListFromMTP(data); _defaultUpdated.fire({}); } void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) { const auto &list = data.vdocuments().v; _colored.clear(); _colored.reserve(list.size()); for (const auto &sticker : data.vdocuments().v) { _colored.push_back(_owner->processDocument(sticker)->id); } _coloredUpdated.fire({}); } void EmojiStatuses::set(DocumentId id, TimeId until) { auto &api = _owner->session().api(); if (_sentRequestId) { api.request(base::take(_sentRequestId)).cancel(); } _owner->session().user()->setEmojiStatus(id, until); _sentRequestId = api.request(MTPaccount_UpdateEmojiStatus( !id ? MTP_emojiStatusEmpty() : !until ? MTP_emojiStatus(MTP_long(id)) : MTP_emojiStatusUntil(MTP_long(id), MTP_int(until)) )).done([=] { _sentRequestId = 0; }).fail([=] { _sentRequestId = 0; }).send(); } bool EmojiStatuses::setting() const { return _sentRequestId != 0;; } EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status) { return status.match([](const MTPDemojiStatus &data) { return EmojiStatusData{ data.vdocument_id().v }; }, [](const MTPDemojiStatusUntil &data) { return EmojiStatusData{ data.vdocument_id().v, data.vuntil().v }; }, [](const MTPDemojiStatusEmpty &) { return EmojiStatusData(); }); } } // namespace Data