diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index bd1035359b992..a9f77968bcc2c 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -33,6 +33,7 @@ include(cmake/td_export.cmake) include(cmake/td_mtproto.cmake) include(cmake/td_lang.cmake) include(cmake/td_scheme.cmake) +include(cmake/td_tdb.cmake) include(cmake/td_ui.cmake) include(cmake/generate_appdata_changelog.cmake) @@ -65,6 +66,7 @@ PRIVATE tdesktop::td_mtproto tdesktop::td_lang tdesktop::td_scheme + tdesktop::td_tdb tdesktop::td_ui desktop-app::lib_webrtc desktop-app::lib_base @@ -548,6 +550,8 @@ PRIVATE data/data_reply_preview.h data/data_search_controller.cpp data/data_search_controller.h + data/data_secret_chat.cpp + data/data_secret_chat.h data/data_send_action.cpp data/data_send_action.h data/data_session.cpp @@ -1054,6 +1058,8 @@ PRIVATE media/streaming/media_streaming_loader_local.h media/streaming/media_streaming_loader_mtproto.cpp media/streaming/media_streaming_loader_mtproto.h + media/streaming/media_streaming_loader_tdb.cpp + media/streaming/media_streaming_loader_tdb.h media/streaming/media_streaming_player.cpp media/streaming/media_streaming_player.h media/streaming/media_streaming_reader.cpp @@ -1123,6 +1129,7 @@ PRIVATE overview/overview_layout.cpp overview/overview_layout.h overview/overview_layout_delegate.h + passport/passport_common.h passport/passport_encryption.cpp passport/passport_encryption.h passport/passport_form_controller.cpp @@ -1321,6 +1328,8 @@ PRIVATE storage/file_download.h storage/file_download_mtproto.cpp storage/file_download_mtproto.h + storage/file_download_tdb.cpp + storage/file_download_tdb.h storage/file_download_web.cpp storage/file_download_web.h storage/file_upload.cpp diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index 156ba726a5f37..cba08b4ff887b 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -493,11 +493,11 @@ int main(int argc, char *argv[]) cout << "Signature verified!\n"; RSA_free(pbKey); #ifdef Q_OS_WIN - QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version)); + QString outName((targetwin64 ? QString("update-win-x64-%1") : QString("update-win-x86-%1")).arg(AlphaVersion ? AlphaVersion : version)); #elif defined Q_OS_MAC - QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version)); + QString outName((targetarmac ? QString("update-mac-arm-%1") : QString("update-mac-x64-%1")).arg(AlphaVersion ? AlphaVersion : version)); #else - QString outName(QString("tlinuxupd%1").arg(AlphaVersion ? AlphaVersion : version)); + QString outName(QString("update-linux-x64-%1").arg(AlphaVersion ? AlphaVersion : version)); #endif if (AlphaVersion) { outName += "_" + AlphaSignature; diff --git a/Telegram/SourceFiles/api/api_attached_stickers.cpp b/Telegram/SourceFiles/api/api_attached_stickers.cpp index 0f18e76484a90..3c9e193215940 100644 --- a/Telegram/SourceFiles/api/api_attached_stickers.cpp +++ b/Telegram/SourceFiles/api/api_attached_stickers.cpp @@ -16,12 +16,20 @@ For license and copyright information please follow this link: #include "lang/lang_keys.h" #include "window/window_session_controller.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { +using namespace Tdb; + AttachedStickers::AttachedStickers(not_null api) +#if 0 // mtp : _api(&api->instance()) { +#endif +: _api(api) { } +#if 0 // mtp void AttachedStickers::request( not_null controller, MTPmessages_GetAttachedStickers &&mtpRequest) { @@ -70,23 +78,80 @@ void AttachedStickers::request( } }).send(); } +#endif + +void AttachedStickers::request( + not_null controller, + FileId fileId) { + const auto weak = base::make_weak(controller.get()); + _api->sender().request(_requestId).cancel(); + _requestId = _api->sender().request(TLgetAttachedStickerSets( + tl_int32(fileId) + )).done([=](const TLstickerSets &result) { + _requestId = 0; + const auto strongController = weak.get(); + if (!strongController) { + return; + } + const auto &data = result.data(); + const auto &list = data.vsets().v; + if (list.isEmpty()) { + strongController->show( + Ui::MakeInformBox(tr::lng_stickers_not_found())); + return; + } else if (list.size() > 1) { + strongController->show( + Box( + strongController->uiShow(), + data.vsets().v)); + return; + } + // Single attached sticker pack. + const auto setId = StickerSetIdentifier{ + .id = uint64(list.front().data().vid().v), + }; + strongController->show( + Box( + strongController->uiShow(), + setId, + Data::TypeFromTL(list.front().data().vsticker_type())), + Ui::LayerOption::KeepOther); + }).fail([=] { + _requestId = 0; + if (const auto strongController = weak.get()) { + strongController->show( + Ui::MakeInformBox(tr::lng_stickers_not_found())); + } + }).send(); +} void AttachedStickers::requestAttachedStickerSets( not_null controller, not_null photo) { +#if 0 // mtp request( controller, MTPmessages_GetAttachedStickers( MTP_inputStickeredMediaPhoto(photo->mtpInput()))); +#endif + const auto &location = photo->location(Data::PhotoSize::Large).file(); + if (const auto tdb = std::get_if(&location.data)) { + request(controller, tdb->fileId); + } } void AttachedStickers::requestAttachedStickerSets( not_null controller, not_null document) { +#if 0 // mtp request( controller, MTPmessages_GetAttachedStickers( MTP_inputStickeredMediaDocument(document->mtpInput()))); +#endif + if (const auto fileId = document->tdbFileId()) { + request(controller, fileId); + } } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_attached_stickers.h b/Telegram/SourceFiles/api/api_attached_stickers.h index d0dd18bb48858..a01c7610848b1 100644 --- a/Telegram/SourceFiles/api/api_attached_stickers.h +++ b/Telegram/SourceFiles/api/api_attached_stickers.h @@ -9,6 +9,8 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" +#include "tdb/tdb_request_id.h" + class ApiWrap; class DocumentData; class PhotoData; @@ -32,12 +34,21 @@ class AttachedStickers final { not_null document); private: +#if 0 // mtp void request( not_null controller, MTPmessages_GetAttachedStickers &&mtpRequest); MTP::Sender _api; mtpRequestId _requestId = 0; +#endif + + void request( + not_null controller, + FileId fileId); + + const not_null _api; + Tdb::RequestId _requestId; }; diff --git a/Telegram/SourceFiles/api/api_authorizations.cpp b/Telegram/SourceFiles/api/api_authorizations.cpp index 78e463c116fa6..5759e0027bafa 100644 --- a/Telegram/SourceFiles/api/api_authorizations.cpp +++ b/Telegram/SourceFiles/api/api_authorizations.cpp @@ -14,13 +14,18 @@ For license and copyright information please follow this link: #include "core/core_settings.h" #include "lang/lang_keys.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + constexpr auto TestApiId = 17349; constexpr auto SnapApiId = 611335; constexpr auto DesktopApiId = 2040; +#if 0 // goodToRemove Authorizations::Entry ParseEntry(const MTPDauthorization &data) { auto result = Authorizations::Entry(); @@ -79,11 +84,80 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) { return result; } +#endif + +Authorizations::Entry ParseEntry(const Tdb::TLDsession &data) { + auto result = Authorizations::Entry(); + + result.hash = data.vis_current().v ? 0 : data.vid().v; + result.incomplete = data.vis_password_pending().v; + + const auto apiId = data.vapi_id().v; + const auto isTest = (apiId == TestApiId); + const auto isDesktop = (apiId == DesktopApiId) || isTest; + + const auto appName = isDesktop + ? QString("Telegram Desktop%1").arg(isTest ? " (GitHub)" : QString()) + : data.vapplication_name().v; + const auto appVer = [&] { + const auto version = data.vapplication_version().v; + if (isDesktop) { + const auto verInt = version.toInt(); + if (version == QString::number(verInt)) { + return Core::FormatVersionDisplay(verInt); + } + } else { + if (const auto index = version.indexOf('('); index >= 0) { + return version.mid(index); + } + } + return version; + }(); + + result.name = result.hash + ? data.vdevice_model().v + : Core::App().settings().deviceModel(); + result.system = data.vsystem_version().v; + result.platform = data.vplatform().v; + result.activeTime = data.vlast_active_date().v + ? data.vlast_active_date().v + : data.vlog_in_date().v; + result.info = QString("%1%2").arg( + appName, + appVer.isEmpty() ? QString() : (' ' + appVer)); + result.ip = data.vip_address().v; + if (!result.hash) { + result.active = tr::lng_status_online(tr::now); + } else { + const auto now = QDateTime::currentDateTime(); + const auto lastTime = base::unixtime::parse(result.activeTime); + const auto nowDate = now.date(); + const auto lastDate = lastTime.date(); + if (lastDate == nowDate) { + result.active = QLocale().toString( + lastTime.time(), + QLocale::ShortFormat); + } else if (lastDate.year() == nowDate.year() + && lastDate.weekNumber() == nowDate.weekNumber()) { + result.active = langDayOfWeek(lastDate); + } else { + result.active = QLocale().toString( + lastDate, + QLocale::ShortFormat); + } + } + result.location = data.vlocation().v; + + return result; +} } // namespace Authorizations::Authorizations(not_null api) +#if 0 // goodToRemove : _api(&api->instance()) { +#endif +: _api(&api->sender()) { Core::App().settings().deviceModelChanges( ) | rpl::start_with_next([=](const QString &model) { auto changed = false; @@ -108,6 +182,7 @@ void Authorizations::reload() { return; } +#if 0 // goodToRemove _requestId = _api.request(MTPaccount_GetAuthorizations( )).done([=](const MTPaccount_Authorizations &result) { _requestId = 0; @@ -124,10 +199,30 @@ void Authorizations::reload() { }).fail([=] { _requestId = 0; }).send(); +#endif + + using namespace Tdb; + _requestId = _api.request(TLgetActiveSessions( + )).done([=](const TLDsessions &data) { + _requestId = 0; + _lastReceived = crl::now(); + _ttlDays = data.vinactive_session_ttl_days().v; + _list = ( + data.vsessions().v + ) | ranges::views::transform([](const TLsession &d) { + return ParseEntry(d.c_session()); + }) | ranges::to; + refreshCallsDisabledHereFromCloud(); + _listChanges.fire({}); + }).fail([=](const Error &error) { + _requestId = 0; + }).send(); } void Authorizations::cancelCurrentRequest() { +#if 0 // goodToRemove _api.request(base::take(_requestId)).cancel(); +#endif } void Authorizations::refreshCallsDisabledHereFromCloud() { @@ -138,6 +233,7 @@ void Authorizations::refreshCallsDisabledHereFromCloud() { } } +#if 0 // goodToRemove void Authorizations::requestTerminate( Fn &&done, Fn &&fail, @@ -167,6 +263,35 @@ void Authorizations::requestTerminate( send(MTPauth_ResetAuthorizations()); } } +#endif + +void Authorizations::requestTerminate( + Fn &&done, + Fn &&fail, + std::optional hash) { + const auto send = [&](auto request) { + _api.request( + std::move(request) + ).done([=, done = std::move(done)] { + done(); + if (hash) { + _list.erase( + ranges::remove(_list, *hash, &Entry::hash), + end(_list)); + } else { + _list.clear(); + } + _listChanges.fire({}); + }).fail( + std::move(fail) + ).send(); + }; + if (hash) { + send(Tdb::TLterminateSession(Tdb::tl_int64(*hash))); + } else { + send(Tdb::TLterminateAllOtherSessions()); + } +} Authorizations::List Authorizations::list() const { return _list; @@ -191,8 +316,12 @@ rpl::producer Authorizations::totalValue() const { void Authorizations::updateTTL(int days) { _api.request(_ttlRequestId).cancel(); +#if 0 // mtp _ttlRequestId = _api.request(MTPaccount_SetAuthorizationTTL( MTP_int(days) +#endif + _ttlRequestId = _api.request(TLsetInactiveSessionTtl( + tl_int32(days) )).done([=] { _ttlRequestId = 0; }).fail([=] { @@ -209,12 +338,17 @@ void Authorizations::toggleCallsDisabled(uint64 hash, bool disabled) { if (const auto sent = _toggleCallsDisabledRequests.take(hash)) { _api.request(*sent).cancel(); } +#if 0 // mtp using Flag = MTPaccount_ChangeAuthorizationSettings::Flag; const auto id = _api.request(MTPaccount_ChangeAuthorizationSettings( MTP_flags(Flag::f_call_requests_disabled), MTP_long(hash), MTPBool(), // encrypted_requests_disabled MTP_bool(disabled) +#endif + const auto id = _api.request(TLtoggleSessionCanAcceptCalls( + tl_int64(hash), + tl_bool(!disabled) )).done([=] { _toggleCallsDisabledRequests.remove(hash); }).fail([=] { diff --git a/Telegram/SourceFiles/api/api_authorizations.h b/Telegram/SourceFiles/api/api_authorizations.h index 5e2a41c9f9019..f6f775042b91e 100644 --- a/Telegram/SourceFiles/api/api_authorizations.h +++ b/Telegram/SourceFiles/api/api_authorizations.h @@ -7,7 +7,12 @@ For license and copyright information please follow this link: */ #pragma once -#include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLok; +class Error; +} // namespace Tdb class ApiWrap; @@ -18,7 +23,10 @@ class Authorizations final { explicit Authorizations(not_null api); struct Entry { +#if 0 // goodToRemove uint64 hash = 0; +#endif + int64 hash = 0; bool incomplete = false; bool callsDisabled = false; @@ -31,8 +39,12 @@ class Authorizations final { void reload(); void cancelCurrentRequest(); void requestTerminate( +#if 0 // goodToRemove Fn &&done, Fn &&fail, +#endif + Fn &&done, + Fn &&fail, std::optional hash = std::nullopt); [[nodiscard]] crl::time lastReceivedTime(); @@ -58,8 +70,12 @@ class Authorizations final { private: void refreshCallsDisabledHereFromCloud(); +#if 0 // goodToRemove MTP::Sender _api; mtpRequestId _requestId = 0; +#endif + Tdb::Sender _api; + Tdb::RequestId _requestId = 0; List _list; rpl::event_stream<> _listChanges; diff --git a/Telegram/SourceFiles/api/api_blocked_peers.cpp b/Telegram/SourceFiles/api/api_blocked_peers.cpp index 0a79eb733ba5d..8deec7f3a6d28 100644 --- a/Telegram/SourceFiles/api/api_blocked_peers.cpp +++ b/Telegram/SourceFiles/api/api_blocked_peers.cpp @@ -15,12 +15,18 @@ For license and copyright information please follow this link: #include "data/data_session.h" #include "main/main_session.h" +#include "tdb/tdb_account.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { constexpr auto kBlockedFirstSlice = 16; constexpr auto kBlockedPerPage = 40; +using namespace Tdb; + +#if 0 // goodToRemove BlockedPeers::Slice TLToSlice( const MTPcontacts_Blocked &blocked, Data::Session &owner) { @@ -48,12 +54,34 @@ BlockedPeers::Slice TLToSlice( return create(0, data.vblocked().v); }); } +#endif + +BlockedPeers::Slice TLToSlice(const TLDmessageSenders &data) { + auto slice = BlockedPeers::Slice(); + slice.total = data.vtotal_count().v; + slice.list.reserve(data.vsenders().v.size()); + for (const auto &sender : data.vsenders().v) { + const auto peerId = peerFromSender(sender); +#if 0 // mtp + owner.processUsers(data.vusers()); + owner.processChats(data.vchats()); +#endif + slice.list.push_back({ + .id = peerId, + .date = 0, // No info from TDLib. + }); + } + return slice; +} } // namespace BlockedPeers::BlockedPeers(not_null api) : _session(&api->session()) +#if 0 // goodToRemove , _api(&api->instance()) { +#endif +, _api(&api->sender()) { } bool BlockedPeers::Slice::Item::operator==(const Item &other) const { @@ -72,6 +100,7 @@ bool BlockedPeers::Slice::operator!=(const BlockedPeers::Slice &other) const { return !(*this == other); } +#if 0 // goodToRemove void BlockedPeers::block(not_null peer) { if (peer->isBlocked()) { _session->changes().peerUpdated( @@ -164,6 +193,7 @@ void BlockedPeers::unblock( i->second.callbacks.push_back(std::move(done)); } } +#endif bool BlockedPeers::blockAlreadySent( not_null peer, @@ -186,6 +216,7 @@ bool BlockedPeers::blockAlreadySent( return false; } +#if 0 // mtp void BlockedPeers::reload() { if (_requestId) { return; @@ -222,5 +253,103 @@ void BlockedPeers::request(int offset, Fn done) { _requestId = 0; }).send(); } +#endif + +void BlockedPeers::changeBlockState( + not_null peer, + bool blocked, + bool force, + Fn done) { + if (!force && peer->isBlocked() == blocked) { + _session->changes().peerUpdated( + peer, + Data::PeerUpdate::Flag::IsBlocked); + if (done) { + done(true); + } + return; + } else if (blockAlreadySent(peer, blocked, done)) { + return; + } + const auto requestId = _api.request(TLsetMessageSenderBlockList( + tl_messageSenderChat(peerToTdbChat(peer->id)), + blocked ? tl_blockListMain() : std::optional() + )).done([=](const TLok &ok) { + if (const auto data = _blockRequests.take(peer)) { + for (const auto &callback : data->callbacks) { + callback(true); + } + } + }).fail([=](const Error &error) { + if (const auto data = _blockRequests.take(peer)) { + for (const auto &callback : data->callbacks) { + callback(false); + } + } + }).send(); + const auto i = _blockRequests.emplace(peer, Request{ + .requestId = requestId, + .blocking = blocked, + }).first; + if (done) { + i->second.callbacks.push_back(std::move(done)); + } +} + +void BlockedPeers::block(not_null peer) { + changeBlockState(peer, true); +} + +void BlockedPeers::unblock( + not_null peer, + Fn done, + bool force) { + changeBlockState(peer, false, force, std::move(done)); +} + +rpl::producer BlockedPeers::total() { + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + + _api.request(TLgetBlockedMessageSenders( + tl_blockListMain(), + tl_int32(0), + tl_int32(1) + )).done([=](const TLmessageSenders &result) { + _total = result.data().vtotal_count().v; + consumer.put_next_copy(_total); + }).send(); + + _session->tdb().updates( + ) | rpl::start_with_next([=](const TLupdate &update) { + update.match([=](const TLDupdateChatBlockList &data) { + if (data.vblock_list() + && data.vblock_list()->type() == id_blockListMain) { + _total++; + } else if (_total > 0) { + _total--; + } + consumer.put_next_copy(_total); + }, [](const auto &) { + }); + }, lifetime); + + if (_total) { + consumer.put_next_copy(_total); + } + + return lifetime; + }; +} + +void BlockedPeers::request(int offset, Fn onDone) { + _api.request(TLgetBlockedMessageSenders( + tl_blockListMain(), + tl_int32(offset), + tl_int32(offset ? kBlockedPerPage : kBlockedFirstSlice) + )).done([=](const TLDmessageSenders &data) { + onDone(TLToSlice(data)); + }).send(); +} } // namespace Api diff --git a/Telegram/SourceFiles/api/api_blocked_peers.h b/Telegram/SourceFiles/api/api_blocked_peers.h index ca2000f67e79d..d8c16d0dc8b89 100644 --- a/Telegram/SourceFiles/api/api_blocked_peers.h +++ b/Telegram/SourceFiles/api/api_blocked_peers.h @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #pragma once #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" class ApiWrap; @@ -37,8 +38,10 @@ class BlockedPeers final { explicit BlockedPeers(not_null api); +#if 0 // goodToRemove void reload(); rpl::producer slice(); +#endif void request(int offset, Fn done); void block(not_null peer); @@ -47,6 +50,8 @@ class BlockedPeers final { Fn done = nullptr, bool force = false); + rpl::producer total(); + private: struct Request { std::vector> callbacks; @@ -59,14 +64,25 @@ class BlockedPeers final { bool blocking, Fn done = nullptr); + void changeBlockState( + not_null peer, + bool blocked, + bool force = false, + Fn done = nullptr); + const not_null _session; + Tdb::Sender _api; + base::flat_map, Request> _blockRequests; + int _total = 0; +#if 0 // goodToRemove MTP::Sender _api; base::flat_map, Request> _blockRequests; mtpRequestId _requestId = 0; std::optional _slice; rpl::event_stream _changes; +#endif }; diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 239d2d0ab89a4..581c6dfa6d9d9 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -37,9 +37,13 @@ For license and copyright information please follow this link: #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + void SendBotCallbackData( not_null controller, not_null item, @@ -68,6 +72,7 @@ void SendBotCallbackData( using ButtonType = HistoryMessageMarkupButton::Type; const auto isGame = (button->type == ButtonType::Game); +#if 0 // goodToRemove auto flags = MTPmessages_GetBotCallbackAnswer::Flags(0); QByteArray sendData; if (isGame) { @@ -90,6 +95,27 @@ void SendBotCallbackData( MTP_bytes(sendData), password ? password->result : MTP_inputCheckPasswordEmpty() )).done([=](const MTPmessages_BotCallbackAnswer &result) { +#endif + const auto withPassword = password.has_value(); + const auto weak = base::make_weak(controller.get()); + const auto show = controller->uiShow(); + auto payload = [&] { + if (isGame) { + return tl_callbackQueryPayloadGame(tl_string()); + } else { + const auto tlBytes = tl_bytes(button->data); + return withPassword + ? tl_callbackQueryPayloadDataWithPassword( + tl_string(password->password), + tlBytes) + : tl_callbackQueryPayloadData(tlBytes); + } + }(); + button->requestId = api->sender().request(TLgetCallbackQueryAnswer( + peerToTdbChat(history->peer->id), + tl_int53(item->id.bare), + std::move(payload) + )).done([=](const TLDcallbackQueryAnswer &data) { const auto guard = gsl::finally([&] { if (done) { done(); @@ -103,12 +129,17 @@ void SendBotCallbackData( button->requestId = 0; owner->requestItemRepaint(item); } +#if 0 // goodToRemove const auto &data = result.data(); const auto message = data.vmessage() ? qs(*data.vmessage()) : QString(); const auto link = data.vurl() ? qs(*data.vurl()) : QString(); const auto showAlert = data.is_alert(); +#endif + const auto message = data.vtext().v; + const auto link = data.vurl().v; + const auto showAlert = data.vshow_alert().v; if (!message.isEmpty()) { if (!show->valid()) { @@ -143,10 +174,16 @@ void SendBotCallbackData( } else if (withPassword) { show->hideLayer(); } +#if 0 // goodToRemove }).fail([=](const MTP::Error &error) { +#endif + }).fail([=](const Error &error) { const auto guard = gsl::finally([&] { if (handleError) { +#if 0 // goodToRemove handleError(error.type()); +#endif + handleError(error.message); } }); const auto item = owner->message(fullId); @@ -416,6 +453,24 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { const auto itemId = item->id; const auto id = int32(button->buttonId); const auto chosen = [=](not_null result) { + if (const auto user = result->asUser()) { + peer->session().sender().request(TLshareUserWithBot( + peerToTdbChat(peer->id), + tl_int53(itemId.bare), + tl_int32(id), + tl_int53(peerToUser(user->id).bare), + tl_bool(false) + )).send(); + } else { + peer->session().sender().request(TLshareChatWithBot( + peerToTdbChat(peer->id), + tl_int53(itemId.bare), + tl_int32(id), + peerToTdbChat(result->id), + tl_bool(false) + )).send(); + } +#if 0 // mtp peer->session().api().request(MTPmessages_SendBotRequestedPeer( peer->input, MTP_int(itemId), @@ -424,6 +479,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { )).done([=](const MTPUpdates &result) { peer->session().api().applyUpdates(result); }).send(); +#endif }; if (const auto bot = item->getMessageBot()) { ShowChoosePeerBox(controller, bot, query, chosen); diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp index 7287b56e30f00..f7903532ed0d6 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -31,9 +31,14 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_settings.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Api { namespace { +using namespace Tdb; + enum class ToggleAction { Adding, Removing, @@ -214,13 +219,35 @@ void ImportInvite( const auto peer = peers.front(); const auto api = &peer->session().api(); +#if 0 // mtp const auto callback = [=](const MTPUpdates &result) { api->applyUpdates(result); +#endif + const auto callback = [=] { if (slug.isEmpty()) { peer->owner().chatsFilters().moreChatsHide(filterId, true); } done(); }; + const auto error = [=](const Tdb::Error &error) { + fail(error.message); + }; + const auto sender = &peer->session().sender(); + auto ids = peers | ranges::views::transform([](auto peer) { + return peerToTdbChat(peer->id); + }) | ranges::to(); + if (!slug.isEmpty()) { + sender->request(TLaddChatFolderByInviteLink( + tl_string(slug), + tl_vector(std::move(ids)) + )).done(callback).fail(error).send(); + } else { + sender->request(TLprocessChatFolderNewChats( + tl_int32(filterId), + tl_vector(std::move(ids)) + )).done(callback).fail(error).send(); + } +#if 0 // mtp const auto error = [=](const MTP::Error &error) { fail(error.type()); }; @@ -238,6 +265,7 @@ void ImportInvite( MTP_vector(std::move(inputs)) )).done(callback).fail(error).send(); } +#endif } ToggleChatsController::ToggleChatsController( @@ -669,6 +697,38 @@ void SaveNewFilterPinned( not_null session, FilterId filterId) { const auto &order = session->data().pinnedChatsOrder(filterId); + session->sender().request(TLgetChatFolder( + tl_int32(filterId) + )).done([=](const TLchatFolder &result) { + const auto owner = &session->data(); + auto filter = Data::ChatFilter::FromTL(filterId, result, owner); + const auto limit = owner->pinnedChatsLimit(filterId); + auto always = filter.always(); + auto pinned = std::vector>(); + pinned.reserve(order.size()); + for (const auto &row : order) { + if (const auto history = row.history()) { + if (always.contains(history)) { + pinned.push_back(history); + } else if (always.size() < limit) { + always.insert(history); + pinned.push_back(history); + } + } + } + session->sender().request(TLeditChatFolder( + tl_int32(filterId), + Data::ChatFilter( + filterId, + filter.title(), + filter.iconEmoji(), + filter.flags(), + std::move(always), + std::move(pinned), + filter.never()).tl() + )).send(); + }).send(); +#if 0 // mtp auto &filters = session->data().chatsFilters(); const auto &filter = filters.applyUpdatedPinned(filterId, order); session->api().request(MTPmessages_UpdateDialogFilter( @@ -676,6 +736,7 @@ void SaveNewFilterPinned( MTP_int(filterId), filter.tl() )).send(); +#endif } void CheckFilterInvite( @@ -684,7 +745,10 @@ void CheckFilterInvite( const auto session = &controller->session(); const auto weak = base::make_weak(controller); session->api().checkFilterInvite(slug, [=]( +#if 0 // mtp const MTPchatlists_ChatlistInvite &result) { +#endif + const TLchatFolderInviteLinkInfo &result) { const auto strong = weak.get(); if (!strong) { return; @@ -695,6 +759,21 @@ void CheckFilterInvite( auto peers = std::vector>(); auto already = std::vector>(); auto &owner = strong->session().data(); + const auto &data = result.data(); + already = data.vadded_chat_ids().v | ranges::views::transform([&]( + const TLint53 &peer) { + return owner.peer(peerFromTdbChat(peer)); + }) | ranges::to_vector; + peers = data.vmissing_chat_ids().v | ranges::views::transform([&]( + const TLint53 &peer) { + return owner.peer(peerFromTdbChat(peer)); + }) | ranges::to_vector; + const auto &info = data.vchat_folder_info().data(); + title = info.vtitle().v; + iconEmoji = info.vicon().data().vname().v; + filterId = info.vid().v; + +#if 0 // mtp result.match([&](const auto &data) { owner.processUsers(data.vusers()); owner.processChats(data.vchats()); @@ -716,6 +795,7 @@ void CheckFilterInvite( peers = parseList(data.vmissing_peers()); already = parseList(data.valready_peers()); }); +#endif const auto notLoaded = filterId && !ranges::contains( @@ -734,7 +814,9 @@ void CheckFilterInvite( std::move(peers), std::move(already)); }, *lifetime); +#if 0 // mtp owner.chatsFilters().reload(); +#endif } else if (filterId) { ProcessFilterInvite( weak, @@ -752,8 +834,12 @@ void CheckFilterInvite( std::move(peers), std::move(already)); } +#if 0 // mtp }, [=](const MTP::Error &error) { if (error.code() != 400) { +#endif + }, [=](const Error &error) { + if (error.code != 400) { return; } ProcessFilterInvite(weak, slug, {}, {}, {}, {}, {}); diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp index dd06d2abc9166..4db76e5359753 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -29,6 +29,11 @@ For license and copyright information please follow this link: #include "styles/style_info.h" #include "styles/style_layers.h" +#include "tdb/tdb_tl_scheme.h" +#include "base/unixtime.h" + +using namespace Tdb; + namespace Api { namespace { @@ -38,6 +43,7 @@ void SubmitChatInvite( not_null session, const QString &hash, bool isGroup) { +#if 0 // mtp session->api().request(MTPmessages_ImportChatInvite( MTP_string(hash) )).done([=](const MTPUpdates &result) { @@ -76,6 +82,20 @@ void SubmitChatInvite( }); }).fail([=](const MTP::Error &error) { const auto &type = error.type(); +#endif + session->sender().request(TLjoinChatByInviteLink( + tl_string(hash) + )).done([=](const TLchat &result) { + const auto peer = session->data().processPeer(result); + const auto strong = weak.get(); + if (!strong) { + return; + } + + strong->hideLayer(); + strong->showPeerHistory(peer, Window::SectionShow::Way::Forward); + }).fail([=](const Error &error) { + const auto &type = error.message; const auto strongController = weak.get(); if (!strongController) { @@ -109,6 +129,69 @@ void CheckChatInvite( ChannelData *invitePeekChannel) { const auto session = &controller->session(); const auto weak = base::make_weak(controller); + session->api().checkChatInvite(hash, [=]( + const TLchatInviteLinkInfo &result) { + const auto strong = weak.get(); + if (!strong) { + return; + } + Core::App().hideMediaView(); + const auto &data = result.data(); + const auto id = data.vchat_id().v + ? peerFromTdbChat(data.vchat_id()) + : PeerId(); + const auto peer = id ? session->data().peer(id).get() : nullptr; + if (peer) { + if (const auto channel = peer->asChannel()) { + const auto peek = data.vaccessible_for().v; + if (peek > 0) { + const auto now = base::unixtime::now(); + channel->setInvitePeek(hash, now + peek); + } else { + channel->clearInvitePeek(); + } + } + strong->showPeerHistory( + peer, + Window::SectionShow::Way::Forward); + } else { + const auto isGroup = data.vtype().match([]( + const TLDinviteLinkChatTypeBasicGroup&) { + return true; + }, [&](const TLDinviteLinkChatTypeSupergroup&) { + return true; + }, [](const TLDinviteLinkChatTypeChannel&) { + return false; + }); + const auto box = strong->show(Box( + session, + data, + invitePeekChannel, + [=] { SubmitChatInvite(weak, session, hash, isGroup); })); + if (invitePeekChannel) { + box->boxClosing( + ) | rpl::filter([=] { + return !invitePeekChannel->amIn(); + }) | rpl::start_with_next([=] { + if (const auto strong = weak.get()) { + strong->clearSectionStack(Window::SectionShow( + Window::SectionShow::Way::ClearStack, + anim::type::normal, + anim::activation::background)); + } + }, box->lifetime()); + } + } + }, [=](const Error &error) { + if (error.code != 400) { + return; + } + Core::App().hideMediaView(); + if (const auto strong = weak.get()) { + strong->show(Ui::MakeInformBox(tr::lng_group_invite_bad_link())); + } + }); +#if 0 // mtp session->api().checkChatInvite(hash, [=](const MTPChatInvite &result) { const auto strong = weak.get(); if (!strong) { @@ -167,6 +250,7 @@ void CheckChatInvite( strong->show(Ui::MakeInformBox(tr::lng_group_invite_bad_link())); } }); +#endif } } // namespace Api @@ -176,6 +260,7 @@ struct ConfirmInviteBox::Participant { Ui::PeerUserpicView userpic; }; +#if 0 // mtp ConfirmInviteBox::ConfirmInviteBox( QWidget*, not_null session, @@ -188,6 +273,19 @@ ConfirmInviteBox::ConfirmInviteBox( invitePeekChannel, std::move(submit)) { } +#endif +ConfirmInviteBox::ConfirmInviteBox( + QWidget*, + not_null session, + const TLDchatInviteLinkInfo &data, + ChannelData *invitePeekChannel, + Fn submit) +: ConfirmInviteBox( + session, + Parse(session, data), + invitePeekChannel, + std::move(submit)) { +} ConfirmInviteBox::ConfirmInviteBox( not_null session, @@ -260,6 +358,7 @@ ConfirmInviteBox::ConfirmInviteBox( ConfirmInviteBox::~ConfirmInviteBox() = default; +#if 0 // mtp ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse( not_null session, const MTPDchatInvite &data) { @@ -289,6 +388,42 @@ ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse( .isVerified = data.is_verified(), }; } +#endif +ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse( + not_null session, + const TLDchatInviteLinkInfo &data) { + auto participants = std::vector(); + const auto &list = data.vmember_user_ids().v; + participants.reserve(list.size()); + for (const auto &userId : list) { + participants.push_back(Participant{ + session->data().user(UserId(userId.v)) + }); + } + auto result = ChatInvite{ + .title = data.vtitle().v, + .about = data.vdescription().v, + .photo = (data.vphoto() + ? session->data().processSmallPhoto(*data.vphoto()).get() + : nullptr), + .participantsCount = data.vmember_count().v, + .participants = std::move(participants), + .isPublic = data.vis_public().v, + .isRequestNeeded = data.vcreates_join_request().v, + .isFake = data.vis_fake().v, + .isScam = data.vis_scam().v, + .isVerified = data.vis_verified().v, + }; + data.vtype().match([](const TLDinviteLinkChatTypeBasicGroup&) { + }, [&](const TLDinviteLinkChatTypeSupergroup&) { + result.isChannel = true; + result.isMegagroup = true; + }, [&](const TLDinviteLinkChatTypeChannel &data) { + result.isChannel = true; + result.isBroadcast = true; + }); + return result; +} [[nodiscard]] Info::Profile::BadgeType ConfirmInviteBox::BadgeForInvite( const ChatInvite &invite) { diff --git a/Telegram/SourceFiles/api/api_chat_invite.h b/Telegram/SourceFiles/api/api_chat_invite.h index c8d99548c9d9e..74d3c64453309 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.h +++ b/Telegram/SourceFiles/api/api_chat_invite.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "ui/layers/box_content.h" +namespace Tdb { +class TLDchatInviteLinkInfo; +} // namespace Tdb + class UserData; class ChannelData; @@ -44,12 +48,20 @@ void CheckChatInvite( class ConfirmInviteBox final : public Ui::BoxContent { public: +#if 0 // mtp ConfirmInviteBox( QWidget*, not_null session, const MTPDchatInvite &data, ChannelData *invitePeekChannel, Fn submit); +#endif + ConfirmInviteBox( + QWidget*, + not_null session, + const Tdb::TLDchatInviteLinkInfo &data, + ChannelData *invitePeekChannel, + Fn submit); ~ConfirmInviteBox(); protected: @@ -75,9 +87,15 @@ class ConfirmInviteBox final : public Ui::BoxContent { bool isScam = false; bool isVerified = false; }; +#if 0 // mtp [[nodiscard]] static ChatInvite Parse( not_null session, const MTPDchatInvite &data); +#endif + [[nodiscard]] static ChatInvite Parse( + not_null session, + const Tdb::TLDchatInviteLinkInfo &data); + [[nodiscard]] Info::Profile::BadgeType BadgeForInvite( const ChatInvite &invite); diff --git a/Telegram/SourceFiles/api/api_chat_participants.cpp b/Telegram/SourceFiles/api/api_chat_participants.cpp index 0ee748f2b9972..56c3e4e38b02d 100644 --- a/Telegram/SourceFiles/api/api_chat_participants.cpp +++ b/Telegram/SourceFiles/api/api_chat_participants.cpp @@ -21,9 +21,13 @@ For license and copyright information please follow this link: #include "main/main_session.h" #include "mtproto/mtproto_config.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + using Members = ChatParticipants::Members; constexpr auto kSmallDelayMs = crl::time(5); @@ -41,11 +45,18 @@ constexpr auto kForwardMessagesOnAdd = 100; std::vector ParseList( const ChatParticipants::TLMembers &data, not_null peer) { +#if 0 // goodToRemove return ranges::views::all( data.vparticipants().v ) | ranges::views::transform([&](const MTPChannelParticipant &p) { return ChatParticipant(p, peer); }) | ranges::to_vector; +#endif + return ranges::views::all( + data.vmembers().v + ) | ranges::views::transform([&](const TLchatMember &p) { + return ChatParticipant(p, peer); + }) | ranges::to_vector; } void ApplyMegagroupAdmins(not_null channel, Members list) { @@ -213,6 +224,7 @@ void ApplyBotsList( } // namespace +#if 0 // goodToRemove ChatParticipant::ChatParticipant( const MTPChannelParticipant &p, not_null peer) { @@ -251,6 +263,35 @@ ChatParticipant::ChatParticipant( _type = Type::Left; }); } +#endif + +ChatParticipant::ChatParticipant( + const TLchatMember &p, + not_null peer) { + _peer = peerFromSender(p.data().vmember_id()); + + _by = UserId(p.data().vinviter_user_id().v); + _rights = ChatAdminRightsInfo(p.data().vstatus()); + _restrictions = ChatRestrictionsInfo(p.data().vstatus()); + + p.data().vstatus().match([&](const TLDchatMemberStatusLeft &data) { + _type = Type::Left; + }, [&](const TLDchatMemberStatusCreator &data) { + _canBeEdited = (peer->session().userPeerId() == _peer); + _type = Type::Creator; + _rank = data.vcustom_title().v; + }, [&](const TLDchatMemberStatusAdministrator &data) { + _canBeEdited = data.vcan_be_edited().v; + _type = Type::Admin; + _rank = data.vcustom_title().v; + }, [&](const TLDchatMemberStatusMember &data) { + _type = Type::Member; + }, [&](const TLDchatMemberStatusBanned &data) { + _type = Type::Banned; + }, [&](const TLDchatMemberStatusRestricted &data) { + _type = Type::Restricted; + }); +} ChatParticipant::ChatParticipant( Type type, @@ -328,7 +369,10 @@ QString ChatParticipant::rank() const { } ChatParticipants::ChatParticipants(not_null api) +#if 0 // goodToRemove : _api(&api->instance()) { +#endif +: _api(&api->sender()) { } void ChatParticipants::requestForAdd( @@ -339,12 +383,16 @@ void ChatParticipants::requestForAdd( if (_forAdd.channel == channel) { return; } + _api.request(base::take(_forAdd.requestId)).cancel(); const auto offset = 0; +#if 0 // goodToRemove const auto participantsHash = uint64(0); +#endif _forAdd.channel = channel; +#if 0 // goodToRemove _forAdd.requestId = _api.request(MTPchannels_GetParticipants( channel->inputChannel, MTP_channelParticipantsRecent(), @@ -359,6 +407,14 @@ void ChatParticipants::requestForAdd( LOG(("API Error: " "channels.channelParticipantsNotModified received!")); }); +#endif + _forAdd.requestId = _api.request(TLgetSupergroupMembers( + tl_int53(peerToChannel(channel->id).bare), + tl_supergroupMembersFilterRecent(), + tl_int32(offset), + tl_int32(channel->session().serverConfig().chatSizeMax) + )).done([=](const TLDchatMembers &data) { + base::take(_forAdd).callback(data); }).fail([=] { base::take(_forAdd); }).send(); @@ -372,6 +428,7 @@ void ChatParticipants::requestLast(not_null channel) { } const auto offset = 0; +#if 0 // goodToRemove const auto participantsHash = uint64(0); const auto requestId = _api.request(MTPchannels_GetParticipants( channel->inputChannel, @@ -389,6 +446,16 @@ void ChatParticipants::requestLast(not_null channel) { LOG(("API Error: " "channels.channelParticipantsNotModified received!")); }); +#endif + const auto requestId = _api.request(TLgetSupergroupMembers( + tl_int53(peerToChannel(channel->id).bare), + tl_supergroupMembersFilterRecent(), + tl_int32(offset), + tl_int32(channel->session().serverConfig().chatSizeMax) + )).done([=](const TLDchatMembers &data) { + _participantsRequests.remove(channel); + const auto &[availableCount, list] = Parse(channel, data); + ApplyLastList(channel, availableCount, list); }).fail([this, channel] { _participantsRequests.remove(channel); }).send(); @@ -402,6 +469,7 @@ void ChatParticipants::requestBots(not_null channel) { } const auto offset = 0; +#if 0 // goodToRemove const auto participantsHash = uint64(0); const auto requestId = _api.request(MTPchannels_GetParticipants( channel->inputChannel, @@ -418,6 +486,16 @@ void ChatParticipants::requestBots(not_null channel) { LOG(("API Error: " "channels.channelParticipantsNotModified received!")); }); +#endif + const auto requestId = _api.request(TLgetSupergroupMembers( + tl_int53(peerToChannel(channel->id).bare), + tl_supergroupMembersFilterBots(), + tl_int32(offset), + tl_int32(channel->session().serverConfig().chatSizeMax) + )).done([=](const TLDchatMembers &data) { + _botsRequests.remove(channel); + const auto &[availableCount, list] = Parse(channel, data); + ApplyBotsList(channel, availableCount, list); }).fail([=] { _botsRequests.remove(channel); }).send(); @@ -431,6 +509,7 @@ void ChatParticipants::requestAdmins(not_null channel) { } const auto offset = 0; +#if 0 // goodToRemove const auto participantsHash = uint64(0); const auto requestId = _api.request(MTPchannels_GetParticipants( channel->inputChannel, @@ -448,6 +527,15 @@ void ChatParticipants::requestAdmins(not_null channel) { LOG(("API Error: " "channels.channelParticipantsNotModified received!")); }); +#endif + const auto requestId = _api.request(TLgetSupergroupMembers( + tl_int53(peerToChannel(channel->id).bare), + tl_supergroupMembersFilterAdministrators(), + tl_int32(offset), + tl_int32(channel->session().serverConfig().chatSizeMax) + )).done([=](const TLDchatMembers &data) { + _adminsRequests.remove(channel); + ApplyMegagroupAdmins(channel, ParseList(data, channel)); }).fail([=] { channel->mgInfo->adminsLoaded = true; _adminsRequests.remove(channel); @@ -471,6 +559,7 @@ void ChatParticipants::add( Fn done) { if (const auto chat = peer->asChat()) { for (const auto &user : users) { +#if 0 // goodToRemove _api.request(MTPmessages_AddChatUser( chat->inputChat, user->inputUser, @@ -483,6 +572,21 @@ void ChatParticipants::add( ShowAddParticipantsError(type, peer, { 1, user }, show); if (done) done(false); }).afterDelay(kSmallDelayMs).send(); +#endif + _api.request(TLaddChatMember( + peerToTdbChat(peer->id), + tl_int53(peerToUser(user->id).bare), + tl_int32(kForwardMessagesOnAdd) + )).done([=] { + if (done) { + done(true); + } + }).fail([=](const Error &error) { + ShowAddParticipantsError(error.message, peer, { 1, user }); + if (done) { + done(false); + } + }).send(); } } else if (const auto channel = peer->asChannel()) { const auto hasBot = ranges::any_of(users, &UserData::isBot); @@ -490,10 +594,14 @@ void ChatParticipants::add( ShowAddParticipantsError("USER_BOT", peer, users, show); return; } +#if 0 // goodToRemove auto list = QVector(); +#endif + auto list = QVector(); list.reserve(std::min(int(users.size()), int(kMaxUsersPerInvite))); const auto send = [&] { const auto callback = base::take(done); +#if 0 // goodToRemove _api.request(MTPchannels_InviteToChannel( channel->inputChannel, MTP_vector(list) @@ -509,9 +617,27 @@ void ChatParticipants::add( ShowAddParticipantsError(error.type(), peer, users, show); if (callback) callback(false); }).afterDelay(kSmallDelayMs).send(); +#endif + _api.request(TLaddChatMembers( + peerToTdbChat(peer->id), + tl_vector(list) + )).done([=] { + requestCountDelayed(channel); + if (callback) { + callback(true); + } + }).fail([=](const Error &error) { + ShowAddParticipantsError(error.message, peer, users); + if (callback) { + callback(false); + } + }).send(); }; for (const auto &user : users) { +#if 0 // goodToRemove list.push_back(user->inputUser); +#endif + list.push_back(tl_int53(peerToUser(user->id).bare)); if (list.size() == kMaxUsersPerInvite) { send(); list.clear(); @@ -528,13 +654,18 @@ void ChatParticipants::add( ChatParticipants::Parsed ChatParticipants::Parse( not_null channel, const TLMembers &data) { +#if 0 // goodToRemove channel->owner().processUsers(data.vusers()); channel->owner().processChats(data.vchats()); +#endif auto list = ParseList(data, channel); if (channel->mgInfo) { RefreshChannelAdmins(channel, list); } +#if 0 // goodToRemove return { data.vcount().v, std::move(list) }; +#endif + return { data.vtotal_count().v, std::move(list) }; } ChatParticipants::Parsed ChatParticipants::ParseRecent( @@ -556,10 +687,11 @@ void ChatParticipants::requestSelf(not_null channel) { } const auto finalize = [=]( - UserId inviter = -1, + UserId inviter = 0, TimeId inviteDate = 0, bool inviteViaRequest = false) { channel->inviter = inviter; + channel->inviterLoaded = true; channel->inviteDate = inviteDate; channel->inviteViaRequest = inviteViaRequest; if (const auto history = channel->owner().historyLoaded(channel)) { @@ -572,6 +704,7 @@ void ChatParticipants::requestSelf(not_null channel) { } }; _selfParticipantRequests.emplace(channel); +#if 0 // goodToRemove _api.request(MTPchannels_GetParticipant( channel->inputChannel, MTP_inputPeerSelf() @@ -593,8 +726,8 @@ void ChatParticipants::requestSelf(not_null channel) { finalize(channel->session().userId(), channel->date); }, [&](const MTPDchannelParticipantAdmin &data) { const auto inviter = data.is_self() - ? data.vinviter_id().value_or(-1) - : -1; + ? data.vinviter_id().value_or(0) + : 0; finalize(inviter, data.vdate().v); }, [&](const MTPDchannelParticipantBanned &data) { LOG(("API Error: Got self banned participant.")); @@ -614,6 +747,45 @@ void ChatParticipants::requestSelf(not_null channel) { } finalize(); }).afterDelay(kSmallDelayMs).send(); +#endif + _api.request(TLgetChatMember( + peerToTdbChat(channel->id), + peerToSender(channel->session().userPeerId()) + )).done([=](const TLDchatMember &data) { + _selfParticipantRequests.erase(channel); + + data.vstatus().match([&](const TLDchatMemberStatusMember &) { + finalize( + data.vinviter_user_id().v, + data.vjoined_chat_date().v, + false); +#if 0 // tdlib todo wants to add the local 'joined' service message in tdlib. + data.is_via_invite()); +#endif + }, [&](const TLDchatMemberStatusCreator &) { + if (channel->mgInfo) { + channel->mgInfo->creator = channel->session().user(); + } + finalize(channel->session().userId(), channel->date); + }, [&](const TLDchatMemberStatusAdministrator &) { + finalize(data.vinviter_user_id().v, data.vjoined_chat_date().v); + }, [&](const TLDchatMemberStatusBanned &data) { + LOG(("API Error: Got self banned participant.")); + finalize(); + }, [&](const TLDchatMemberStatusRestricted &data) { + LOG(("API Error: Got self restricted participant.")); + finalize(); + }, [&](const TLDchatMemberStatusLeft &data) { + LOG(("API Error: Got self left participant.")); + finalize(); + }); + }).fail([=](const Error &error) { + _selfParticipantRequests.erase(channel); + if (error.message == u"CHANNEL_PRIVATE"_q) { + channel->privateErrorReceived(); + } + finalize(); + }).send(); } void ChatParticipants::kick( @@ -621,6 +793,7 @@ void ChatParticipants::kick( not_null participant) { Expects(participant->isUser()); +#if 0 // goodToRemove _api.request(MTPmessages_DeleteChatUser( MTP_flags(0), chat->inputChat, @@ -628,6 +801,14 @@ void ChatParticipants::kick( )).done([=](const MTPUpdates &result) { chat->session().api().applyUpdates(result); }).send(); +#endif + + _api.request(TLbanChatMember( + peerToTdbChat(chat->id), + peerToSender(participant->id), + tl_int32(0), + tl_bool(false) + )).send(); } void ChatParticipants::kick( @@ -635,9 +816,12 @@ void ChatParticipants::kick( not_null participant, ChatRestrictionsInfo currentRights) { const auto kick = KickRequest(channel, participant); - if (_kickRequests.contains(kick)) return; + if (_kickRequests.contains(kick)) { + return; + } const auto rights = ChannelData::KickedRestrictedRights(participant); +#if 0 // goodToRemove const auto requestId = _api.request(MTPchannels_EditBanned( channel->inputChannel, participant->input, @@ -653,6 +837,18 @@ void ChatParticipants::kick( }).fail([this, kick] { _kickRequests.remove(kick); }).send(); +#endif + const auto requestId = _api.request(TLbanChatMember( + peerToTdbChat(channel->id), + peerToSender(participant->id), + tl_int32(0), + tl_bool(false) + )).done([=] { + _kickRequests.remove(KickRequest(channel, participant)); + channel->applyEditBanned(participant, currentRights, rights); + }).fail([this, kick] { + _kickRequests.remove(kick); + }).send(); _kickRequests.emplace(kick, requestId); } @@ -665,13 +861,20 @@ void ChatParticipants::unblock( return; } +#if 0 // goodToRemove const auto requestId = _api.request(MTPchannels_EditBanned( channel->inputChannel, participant->input, MTP_chatBannedRights(MTP_flags(0), MTP_int(0)) )).done([=](const MTPUpdates &result) { channel->session().api().applyUpdates(result); +#endif + const auto requestId = _api.request(TLsetChatMemberStatus( + peerToTdbChat(channel->id), + peerToSender(participant->id), + tl_chatMemberStatusMember() + )).done([=] { _kickRequests.remove(KickRequest(channel, participant)); if (channel->kickedCount() > 0) { channel->setKickedCount(channel->kickedCount() - 1); diff --git a/Telegram/SourceFiles/api/api_chat_participants.h b/Telegram/SourceFiles/api/api_chat_participants.h index 41eda6fe681a0..8c5fb3e5733d2 100644 --- a/Telegram/SourceFiles/api/api_chat_participants.h +++ b/Telegram/SourceFiles/api/api_chat_participants.h @@ -8,9 +8,14 @@ For license and copyright information please follow this link: #pragma once #include "data/data_chat_participant_status.h" -#include "mtproto/sender.h" +#include "tdb/tdb_sender.h" #include "base/timer.h" +namespace Tdb { +class TLDchatMembers; +class TLchatMember; +} // namespace Tdb + class ApiWrap; class ChannelData; @@ -31,9 +36,14 @@ class ChatParticipant final { Banned, }; +#if 0 // goodToRemove explicit ChatParticipant( const MTPChannelParticipant &p, not_null peer); +#endif + explicit ChatParticipant( + const Tdb::TLchatMember &p, + not_null peer); ChatParticipant( Type type, PeerId peerId, @@ -81,7 +91,10 @@ class ChatParticipants final { const std::vector list; }; +#if 0 // goodToRemove using TLMembers = MTPDchannels_channelParticipants; +#endif + using TLMembers = Tdb::TLDchatMembers; using Members = const std::vector &; explicit ChatParticipants(not_null api); @@ -121,7 +134,10 @@ class ChatParticipants final { not_null participant); private: +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; using PeerRequests = base::flat_map; diff --git a/Telegram/SourceFiles/api/api_cloud_password.cpp b/Telegram/SourceFiles/api/api_cloud_password.cpp index c5ab29daaffd9..2582d2628903e 100644 --- a/Telegram/SourceFiles/api/api_cloud_password.cpp +++ b/Telegram/SourceFiles/api/api_cloud_password.cpp @@ -12,9 +12,44 @@ For license and copyright information please follow this link: #include "core/core_cloud_password.h" #include "passport/passport_encryption.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + +int TLToCodeLength(const TLDpasswordState &data) { + if (const auto email = data.vrecovery_email_address_code_info()) { + return email->match([]( + const TLDemailAddressAuthenticationCodeInfo &data) { + return data.vlength().v; + }); + } + return 0; +} + +Core::CloudPasswordState TLToCloudPasswordState( + const TLpasswordState &state) { + const auto &data = state.data(); + auto result = Core::CloudPasswordState(); + result.hasPassword = data.vhas_password().v; + result.hasRecovery = data.vhas_recovery_email_address().v; + result.notEmptyPassport = data.vhas_passport_data().v; + result.hint = data.vpassword_hint().v; + result.pendingResetDate = data.vpending_reset_date().v; + + if (const auto email = data.vrecovery_email_address_code_info()) { + result.unconfirmedPattern = email->match([]( + const TLDemailAddressAuthenticationCodeInfo &data) { + return data.vemail_address_pattern().v; + }); + } + + return result; +} + +#if 0 // mtp [[nodiscard]] Core::CloudPasswordState ProcessMtpState( const MTPaccount_password &state) { return state.match([&](const MTPDaccount_password &data) { @@ -22,11 +57,21 @@ namespace { return Core::ParseCloudPasswordState(data); }); } +#endif } // namespace CloudPassword::CloudPassword(not_null api) +#if 0 // goodToRemove : _api(&api->instance()) { +#endif +: _authorized(true) +, _api(&api->sender()) { +} + +CloudPassword::CloudPassword(Tdb::Sender &sender) +: _authorized(false) +, _api(&sender) { } void CloudPassword::apply(Core::CloudPasswordState state) { @@ -35,6 +80,7 @@ void CloudPassword::apply(Core::CloudPasswordState state) { } else { _state = std::make_unique(std::move(state)); } + _state->outdatedClient = false; _stateChanges.fire_copy(*_state); } @@ -42,6 +88,7 @@ void CloudPassword::reload() { if (_requestId) { return; } +#if 0 // goodToRemove _requestId = _api.request(MTPaccount_GetPassword( )).done([=](const MTPaccount_Password &result) { _requestId = 0; @@ -49,9 +96,41 @@ void CloudPassword::reload() { }).fail([=] { _requestId = 0; }).send(); +#endif + + _api.request(TLgetPasswordState( + )).done([=](const TLpasswordState &result) { + apply(TLToCloudPasswordState(result)); + }).fail([=](const Error &error) { + if (!_state) { + _state = std::make_unique(); + } + _state->serverError = error.message; + // See: td/telegram/PasswordManager.cpp. + _state->outdatedClient = ((error.code == 400) + && (error.message == u"Please update client to continue"_q)); + }).send(); } void CloudPassword::clearUnconfirmedPassword() { + // There is no direct replacement for MTPaccount_CancelPasswordEmail, + // but we can pass an empty info to Td::TLsetPassword + // to clear an unconfirmed password. + _requestId = _api.request(TLsetPassword( + tl_string(), + tl_string(), + tl_string(), + tl_bool(true), + tl_string() + )).done([=](const TLpasswordState &result) { + _requestId = 0; + apply(TLToCloudPasswordState(result)); + }).fail([=](const Error &error) { + _requestId = 0; + reload(); + }).send(); + +#if 0 // goodToRemove _requestId = _api.request(MTPaccount_CancelPasswordEmail( )).done([=] { _requestId = 0; @@ -60,6 +139,7 @@ void CloudPassword::clearUnconfirmedPassword() { _requestId = 0; reload(); }).send(); +#endif } rpl::producer CloudPassword::state() const { @@ -75,6 +155,7 @@ auto CloudPassword::stateCurrent() const : std::nullopt; } +#if 0 // goodToRemove auto CloudPassword::resetPassword() -> rpl::producer { return [=](auto consumer) { @@ -103,10 +184,41 @@ auto CloudPassword::resetPassword() return rpl::lifetime(); }; } +#endif + +auto CloudPassword::resetPassword() +-> rpl::producer { + return [=](auto consumer) { + _api.request(TLresetPassword( + )).done([=](const TLresetPasswordResult &result) { + result.match([&](const TLDresetPasswordResultOk &data) { + reload(); + }, [&](const TLDresetPasswordResultPending &data) { + if (!_state) { + reload(); + return; + } + const auto until = data.vpending_reset_date().v; + if (_state->pendingResetDate != until) { + _state->pendingResetDate = until; + _stateChanges.fire_copy(*_state); + } + }, [&](const TLDresetPasswordResultDeclined &data) { + consumer.put_next_copy(data.vretry_date().v); + }); + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); + + return rpl::lifetime(); + }; +} auto CloudPassword::cancelResetPassword() -> rpl::producer { return [=](auto consumer) { +#if 0 // goodToRemove _api.request(MTPaccount_DeclinePasswordReset( )).done([=] { reload(); @@ -114,6 +226,14 @@ auto CloudPassword::cancelResetPassword() }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + _api.request(TLcancelPasswordReset( + )).done([=] { + reload(); + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); return rpl::lifetime(); }; @@ -126,6 +246,32 @@ rpl::producer CloudPassword::set( bool hasRecoveryEmail, const QString &recoveryEmail) { + return [=](auto consumer) { + _api.request(TLsetPassword( + tl_string(oldPassword), + tl_string(newPassword), + tl_string(hint), + tl_bool(hasRecoveryEmail), + tl_string(recoveryEmail) + )).done([=](const TLpasswordState &result) { + apply(TLToCloudPasswordState(result)); + const auto codeLength = TLToCodeLength(result.data()); + if (codeLength) { + consumer.put_next(SetOk{ codeLength }); + } else { + consumer.put_done(); + } + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); +#if 0 // doLater handleFloodErrors. + // }).handleFloodErrors().send(); +#endif + + return rpl::lifetime(); + }; + +#if 0 // goodToRemove const auto generatePasswordCheck = [=]( const Core::CloudPasswordState &latestState) { if (oldPassword.isEmpty() || !latestState.hasPassword) { @@ -291,10 +437,35 @@ rpl::producer CloudPassword::set( }).send(); return rpl::lifetime(); }; +#endif } rpl::producer CloudPassword::check( const QString &password) { + + return [=](auto consumer) { + if (_authorized) { + _api.request(Tdb::TLgetRecoveryEmailAddress( + tl_string(password) + )).done([=] { + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); + } else { + _api.request(TLcheckAuthenticationPassword( + tl_string(password) + )).done([=](const TLok &) { + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); + } + + return rpl::lifetime(); + }; + +#if 0 // goodToRemove return [=](auto consumer) { _api.request(MTPaccount_GetPassword( )).done([=](const MTPaccount_Password &result) { @@ -326,11 +497,24 @@ rpl::producer CloudPassword::check( return rpl::lifetime(); }; +#endif } rpl::producer CloudPassword::confirmEmail( const QString &code) { return [=](auto consumer) { + _api.request(TLcheckRecoveryEmailAddressCode( + tl_string(code) + )).done([=](const TLpasswordState &result) { + apply(TLToCloudPasswordState(result)); + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); +#if 0 // doLater handleFloodErrors. + // }).handleFloodErrors().send(); +#endif +#if 0 // goodToRemove _api.request(MTPaccount_ConfirmPasswordEmail( MTP_string(code) )).done([=] { @@ -344,6 +528,7 @@ rpl::producer CloudPassword::confirmEmail( }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).handleFloodErrors().send(); +#endif return rpl::lifetime(); }; @@ -351,6 +536,14 @@ rpl::producer CloudPassword::confirmEmail( rpl::producer CloudPassword::resendEmailCode() { return [=](auto consumer) { + _api.request(TLresendRecoveryEmailAddressCode( + )).done([=](const TLpasswordState &result) { + apply(TLToCloudPasswordState(result)); + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); +#if 0 // goodToRemove _api.request(MTPaccount_ResendPasswordEmail( )).done([=] { _api.request(MTPaccount_GetPassword( @@ -363,6 +556,7 @@ rpl::producer CloudPassword::resendEmailCode() { }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).handleFloodErrors().send(); +#endif return rpl::lifetime(); }; @@ -371,6 +565,24 @@ rpl::producer CloudPassword::resendEmailCode() { rpl::producer CloudPassword::setEmail( const QString &oldPassword, const QString &recoveryEmail) { + return [=](auto consumer) { + _api.request(TLsetRecoveryEmailAddress( + tl_string(oldPassword), + tl_string(recoveryEmail) + )).done([=](const TLpasswordState &result) { + apply(TLToCloudPasswordState(result)); + const auto codeLength = TLToCodeLength(result.data()); + if (codeLength) { + consumer.put_next(SetOk{ codeLength }); + } else { + consumer.put_done(); + } + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); + return rpl::lifetime(); + }; +#if 0 // goodToRemove const auto generatePasswordCheck = [=]( const Core::CloudPasswordState &latestState) { if (oldPassword.isEmpty() || !latestState.hasPassword) { @@ -440,13 +652,41 @@ rpl::producer CloudPassword::setEmail( }).send(); return rpl::lifetime(); }; +#endif } rpl::producer CloudPassword::recoverPassword( const QString &code, const QString &newPassword, const QString &newHint) { + return [=](auto consumer) { + if (_authorized) { + _api.request(TLrecoverPassword( + tl_string(code), + tl_string(newPassword), + tl_string(newHint) + )).done([=](const TLpasswordState &result) { + apply(TLToCloudPasswordState(result)); + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); + } else { + _api.request(TLrecoverAuthenticationPassword( + tl_string(code), + tl_string(newPassword), + tl_string(newHint) + )).done([=](const TLok &) { + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); + } + + return rpl::lifetime(); + }; +#if 0 // goodToRemove const auto finish = [=](auto consumer) { _api.request(MTPaccount_GetPassword( )).done([=](const MTPaccount_Password &result) { @@ -509,10 +749,31 @@ rpl::producer CloudPassword::recoverPassword( }).send(); return rpl::lifetime(); }; +#endif } rpl::producer CloudPassword::requestPasswordRecovery() { return [=](auto consumer) { + if (!_authorized) { + _api.request(TLrequestAuthenticationPasswordRecovery( + )).done([=](const TLok &) { + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); + return rpl::lifetime(); + } + _api.request(TLrequestPasswordRecovery( + )).done([=](const TLemailAddressAuthenticationCodeInfo &result) { + result.match([&]( + const TLDemailAddressAuthenticationCodeInfo &data) { + consumer.put_next_copy(data.vemail_address_pattern().v); + }); + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); +#if 0 // goodToRemove _api.request(MTPauth_RequestPasswordRecovery( )).done([=](const MTPauth_PasswordRecovery &result) { result.match([&](const MTPDauth_passwordRecovery &data) { @@ -522,6 +783,7 @@ rpl::producer CloudPassword::requestPasswordRecovery() { }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif return rpl::lifetime(); }; } @@ -529,6 +791,27 @@ rpl::producer CloudPassword::requestPasswordRecovery() { auto CloudPassword::checkRecoveryEmailAddressCode(const QString &code) -> rpl::producer { return [=](auto consumer) { + if (_authorized) { + _api.request(TLcheckRecoveryEmailAddressCode( + tl_string(code) + )).done([=](const TLpasswordState &) { + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); +#if 0 // doLater handleFloodErrors. + // }).handleFloodErrors().send(); +#endif + } else { + _api.request(TLcheckAuthenticationPasswordRecoveryCode( + tl_string(code) + )).done([=](const TLok &) { + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); + } +#if 0 // goodToRemove _api.request(MTPauth_CheckRecoveryPassword( MTP_string(code) )).done([=] { @@ -536,6 +819,7 @@ auto CloudPassword::checkRecoveryEmailAddressCode(const QString &code) }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).handleFloodErrors().send(); +#endif return rpl::lifetime(); }; diff --git a/Telegram/SourceFiles/api/api_cloud_password.h b/Telegram/SourceFiles/api/api_cloud_password.h index 30bc37938268e..525c285cbbd95 100644 --- a/Telegram/SourceFiles/api/api_cloud_password.h +++ b/Telegram/SourceFiles/api/api_cloud_password.h @@ -8,6 +8,11 @@ For license and copyright information please follow this link: #pragma once #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLpasswordState; +} // namespace Tdb namespace Core { struct CloudPasswordState; @@ -29,6 +34,7 @@ class CloudPassword final { using ResetRetryDate = int; explicit CloudPassword(not_null api); + explicit CloudPassword(Tdb::Sender &sender); void reload(); void clearUnconfirmedPassword(); @@ -63,7 +69,12 @@ class CloudPassword final { private: void apply(Core::CloudPasswordState state); + void apply(const Tdb::TLpasswordState &state); +#if 0 // goodToRemove MTP::Sender _api; +#endif + const bool _authorized; + Tdb::Sender _api; mtpRequestId _requestId = 0; std::unique_ptr _state; rpl::event_stream _stateChanges; diff --git a/Telegram/SourceFiles/api/api_common.cpp b/Telegram/SourceFiles/api/api_common.cpp index cfb1e72207fd1..322617aa5a03e 100644 --- a/Telegram/SourceFiles/api/api_common.cpp +++ b/Telegram/SourceFiles/api/api_common.cpp @@ -30,8 +30,10 @@ SendOptions DefaultSendWhenOnlineOptions() { }; } +#if 0 // mtp MTPInputReplyTo SendAction::mtpReplyTo() const { return Data::ReplyToForMTP(history, replyTo); } +#endif } // namespace Api diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index c828411425522..4b682de0a6a9c 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -46,7 +46,9 @@ struct SendAction { bool generateLocal = true; MsgId replaceMediaOf = 0; +#if 0 // mtp [[nodiscard]] MTPInputReplyTo mtpReplyTo() const; +#endif }; struct MessageToSend { diff --git a/Telegram/SourceFiles/api/api_confirm_phone.cpp b/Telegram/SourceFiles/api/api_confirm_phone.cpp index 0dd1ae9a7f889..e6c58d4f62595 100644 --- a/Telegram/SourceFiles/api/api_confirm_phone.cpp +++ b/Telegram/SourceFiles/api/api_confirm_phone.cpp @@ -13,13 +13,18 @@ For license and copyright information please follow this link: #include "main/main_session.h" #include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_phone_box.h" -#include "ui/text/format_values.h" // Ui::FormatPhone +#include "tdb/tdb_format_phone.h" // Tdb::FormatPhone #include "window/window_session_controller.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { ConfirmPhone::ConfirmPhone(not_null api) +#if 0 // goodToRemove : _api(&api->instance()) { +#endif +: _api(&api->sender()) { } void ConfirmPhone::resolve( @@ -29,6 +34,7 @@ void ConfirmPhone::resolve( if (_sendRequestId) { return; } +#if 0 // goodToRemove _sendRequestId = _api.request(MTPaccount_SendConfirmPhoneCode( MTP_string(hash), MTP_codeSettings( @@ -158,6 +164,115 @@ void ConfirmPhone::resolve( Ui::MakeInformBox(errorText), Ui::LayerOption::CloseOther); }).handleFloodErrors().send(); +#endif + _sendRequestId = _api.request(Tdb::TLsendPhoneNumberConfirmationCode( + Tdb::tl_string(hash), + Tdb::tl_string(phone), + std::nullopt + )).done([=](const Tdb::TLDauthenticationCodeInfo &data) { + _sendRequestId = 0; + + const auto sentCodeLength = data.vtype().match([]( + const Tdb::TLDauthenticationCodeTypeTelegramMessage &data) { + LOG(("Error: should not be in-app code!")); + return 0; + }, [](const Tdb::TLDauthenticationCodeTypeSms &data) { + return data.vlength().v; + }, [](const Tdb::TLDauthenticationCodeTypeCall &data) { + return data.vlength().v; + }, [](const Tdb::TLDauthenticationCodeTypeFragment &data) { + return data.vlength().v; + }, [](const Tdb::TLDauthenticationCodeTypeMissedCall &data) { + LOG(("Error: should not be missedcall!")); + return 0; + }, [](const Tdb::TLDauthenticationCodeTypeFlashCall &data) { + LOG(("Error: should not be flashcall!")); + return 0; + }, [](const Tdb::TLDauthenticationCodeTypeFirebaseAndroid &data) { + LOG(("Error: should not be firebase android!")); + return 0; + }, [](const Tdb::TLDauthenticationCodeTypeFirebaseIos &data) { + LOG(("Error: should not be firebase ios!")); + return 0; + }); + const auto fragmentUrl = data.vtype().match([]( + const Tdb::TLDauthenticationCodeTypeFragment &data) { + return data.vurl().v; + }, [](const auto &) { return QString(); }); + const auto timeout = [&]() -> std::optional { + if (const auto next = data.vnext_type()) { + using TypeCall = Tdb::TLDauthenticationCodeTypeCall; + return next->match([&](const TypeCall &) { + return std::make_optional(data.vtimeout().v); + }, [](const auto &) { + return std::optional(std::nullopt); + }); + } + return std::nullopt; + }(); + auto box = Box( + phone, + sentCodeLength, + fragmentUrl, + timeout); + const auto boxWeak = Ui::MakeWeak(box.data()); + box->resendRequests( + ) | rpl::start_with_next([=] { + _api.request(Tdb::TLresendPhoneNumberConfirmationCode( + )).done([=] { + if (boxWeak) { + boxWeak->callDone(); + } + }).send(); + }, box->lifetime()); + box->checkRequests( + ) | rpl::start_with_next([=](const QString &code) { + if (_checkRequestId) { + return; + } + _checkRequestId = _api.request( + Tdb::TLcheckPhoneNumberConfirmationCode(Tdb::tl_string(code)) + ).done([=] { + _checkRequestId = 0; + controller->show( + Ui::MakeInformBox( + tr::lng_confirm_phone_success( + tr::now, + lt_phone, + Tdb::FormatPhone(phone))), + Ui::LayerOption::CloseOther); + }).fail([=](const Tdb::Error &error) { + _checkRequestId = 0; + if (!boxWeak) { + return; + } + + const auto errorText = Tdb::IsFloodError(error) + ? tr::lng_flood_error(tr::now) + : (error.message == (u"PHONE_CODE_EMPTY"_q) + || error.message == (u"PHONE_CODE_INVALID"_q)) + ? tr::lng_bad_code(tr::now) + : Lang::Hard::ServerError(); + boxWeak->showServerError(errorText); + }).send(); + // doLater handleFloodErrors. + }, box->lifetime()); + + controller->show(std::move(box), Ui::LayerOption::CloseOther); + }).fail([=](const Tdb::Error &error) { + _sendRequestId = 0; + _checkRequestId = 0; + + const auto errorText = Tdb::IsFloodError(error) + ? tr::lng_flood_error(tr::now) + : (error.code == 400) + ? tr::lng_confirm_phone_link_invalid(tr::now) + : Lang::Hard::ServerError(); + controller->show( + Ui::MakeInformBox(errorText), + Ui::LayerOption::CloseOther); + }).send(); + // doLater handleFloodErrors. } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_confirm_phone.h b/Telegram/SourceFiles/api/api_confirm_phone.h index 24108b17ae5a1..2626ff4be121c 100644 --- a/Telegram/SourceFiles/api/api_confirm_phone.h +++ b/Telegram/SourceFiles/api/api_confirm_phone.h @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #pragma once #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" class ApiWrap; @@ -27,7 +28,10 @@ class ConfirmPhone final { const QString &hash); private: +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; mtpRequestId _sendRequestId = 0; mtpRequestId _checkRequestId = 0; diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 005effad99478..f79eccd11f041 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -22,11 +22,17 @@ For license and copyright information please follow this link: #include "mtproto/mtproto_response.h" #include "boxes/abstract_box.h" // Ui::show(). +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" +#include "api/api_sending.h" + namespace Api { namespace { +using namespace Tdb; using namespace rpl::details; +#if 0 // mtp template constexpr auto WithId = is_callable_plain_v, mtpRequestId>; @@ -132,6 +138,7 @@ mtpRequestId EditMessage( t_bad_callback(fail); } }).send(); + return 0; } template @@ -189,16 +196,25 @@ void EditMessageWithUploadedMedia( EditMessage(item, options, done, fail, media); } +#endif } // namespace void RescheduleMessage( not_null item, SendOptions options) { + item->history()->session().sender().request(TLeditMessageSchedulingState( + peerToTdbChat(item->history()->peer->id), + tl_int53(item->id.bare), + ScheduledToTL(options.scheduled) + )).send(); +#if 0 // mtp const auto empty = [] {}; EditMessage(item, options, empty, empty); +#endif } +#if 0 // mtp void EditMessageWithUploadedDocument( HistoryItem *item, RemoteFileInfo info, @@ -224,6 +240,7 @@ void EditMessageWithUploadedPhoto( options, PrepareUploadedPhoto(item, std::move(info))); } +#endif mtpRequestId EditCaption( not_null item, @@ -231,6 +248,18 @@ mtpRequestId EditCaption( SendOptions options, Fn done, Fn fail) { + const auto session = &item->history()->session(); + return session->sender().request(TLeditMessageCaption( + peerToTdbChat(item->history()->peer->id), + tl_int53(item->id.bare), + Api::FormattedTextToTdb(caption) + )).done([=](const TLmessage &result) { + session->data().processMessage(result, NewMessageType::Existing); + done(); + }).fail([=](const Error &error) { + fail(error.message); + }).send(); +#if 0 // mtp return EditMessage( item, caption, @@ -238,6 +267,7 @@ mtpRequestId EditCaption( options, done, fail); +#endif } mtpRequestId EditTextMessage( @@ -247,11 +277,39 @@ mtpRequestId EditTextMessage( SendOptions options, Fn done, Fn fail) { + if (const auto media = item->media()) { + if (!media->webpage() && (media->photo() || media->document())) { + const auto id = std::make_shared(); + *id = EditCaption(item, caption, options, [=] { + done(*id); + }, [=](const QString &error) { + fail(error, *id); + }); + return *id; + } + } + + const auto session = &item->history()->session(); + return session->sender().request(TLeditMessageText( + peerToTdbChat(item->history()->peer->id), + tl_int53(item->id.bare), + tl_inputMessageText( + Api::FormattedTextToTdb(caption), + Data::LinkPreviewOptions(webpage), + tl_bool(true)) + )).done([=](const TLmessage &result, RequestId requestId) { + session->data().processMessage(result, NewMessageType::Existing); + done(requestId); + }).fail([=](const Error &error, RequestId requestId) { + fail(error.message, requestId); + }).send(); +#if 0 // mtp const auto callback = [=](Fn applyUpdates, mtpRequestId id) { applyUpdates(); done(id); }; return EditMessage(item, caption, webpage, options, callback, fail); +#endif } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_editing.h b/Telegram/SourceFiles/api/api_editing.h index 86f106f45e9de..d8e9fa06ac204 100644 --- a/Telegram/SourceFiles/api/api_editing.h +++ b/Telegram/SourceFiles/api/api_editing.h @@ -32,6 +32,7 @@ void RescheduleMessage( not_null item, SendOptions options); +#if 0 // mtp void EditMessageWithUploadedDocument( HistoryItem *item, RemoteFileInfo info, @@ -41,6 +42,7 @@ void EditMessageWithUploadedPhoto( HistoryItem *item, RemoteFileInfo info, SendOptions options); +#endif mtpRequestId EditCaption( not_null item, diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index 219d335f15ded..d08170d4c52e9 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -8,6 +8,8 @@ For license and copyright information please follow this link: #include "api/api_global_privacy.h" #include "apiwrap.h" +#include "base/const_string.h" +#include "tdb/tdb_option.h" #include "main/main_session.h" #include "main/main_account.h" #include "main/main_app_config.h" @@ -16,9 +18,27 @@ namespace Api { GlobalPrivacy::GlobalPrivacy(not_null api) : _session(&api->session()) +, _api(&api->sender()) { +#if 0 // goodToRemove , _api(&api->instance()) { +#endif } +bool GlobalPrivacy::apply(const Tdb::TLDupdateOption &option) { + if (option.vname().v + == u"can_archive_and_mute_new_chats_from_unknown_users"_q) { + _showArchiveAndMute = option.vvalue().match([]( + const Tdb::TLDoptionValueBoolean &data) { + return data.vvalue().v; + }, [](const auto &) { + return false; + }); + return true; + } + return false; +} + +#if 0 // goodToRemove void GlobalPrivacy::reload(Fn callback) { if (callback) { _callbacks.push_back(std::move(callback)); @@ -47,6 +67,25 @@ void GlobalPrivacy::reload(Fn callback) { false); }, _session->lifetime()); } +#endif + +void GlobalPrivacy::reload(Fn callback) { + if (callback) { + _callbacks.push_back(std::move(callback)); + } + _api.request(Tdb::TLgetArchiveChatListSettings( + )).done([=](const Tdb::TLDarchiveChatListSettings &data) { + _archiveAndMute + = data.varchive_and_mute_new_chats_from_unknown_users().v; + for (const auto &callback : base::take(_callbacks)) { + callback(); + } + }).fail([=] { + for (const auto &callback : base::take(_callbacks)) { + callback(); + } + }).send(); +} bool GlobalPrivacy::archiveAndMuteCurrent() const { return _archiveAndMute.current(); @@ -93,6 +132,7 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage( update(archiveAndMuteCurrent(), value); } +#if 0 // goodToRemove void GlobalPrivacy::update( bool archiveAndMute, UnarchiveOnNewMessage unarchiveOnNewMessage) { @@ -120,7 +160,21 @@ void GlobalPrivacy::update( _archiveAndMute = archiveAndMute; _unarchiveOnNewMessage = unarchiveOnNewMessage; } +#endif + +void GlobalPrivacy::update( + bool archiveAndMute, + UnarchiveOnNewMessage unarchiveOnNewMessage) { + using Unarchive = UnarchiveOnNewMessage; + _api.request(Tdb::TLsetArchiveChatListSettings( + Tdb::tl_archiveChatListSettings( + Tdb::tl_bool(archiveAndMute), + Tdb::tl_bool(unarchiveOnNewMessage == Unarchive::None), + Tdb::tl_bool(unarchiveOnNewMessage != Unarchive::AnyUnmuted)) + )).send(); +} +#if 0 // goodToRemove void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { data.match([&](const MTPDglobalPrivacySettings &data) { _archiveAndMute = data.is_archive_and_mute_new_noncontact_peers(); @@ -131,5 +185,6 @@ void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { : UnarchiveOnNewMessage::AnyUnmuted; }); } +#endif } // namespace Api diff --git a/Telegram/SourceFiles/api/api_global_privacy.h b/Telegram/SourceFiles/api/api_global_privacy.h index 9e4b8e12156a7..754f6788eadb9 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.h +++ b/Telegram/SourceFiles/api/api_global_privacy.h @@ -7,8 +7,13 @@ For license and copyright information please follow this link: */ #pragma once +#include "tdb/tdb_account.h" #include "mtproto/sender.h" +namespace Tdb { +class TLDupdateOption; +} // namespace Tdb + class ApiWrap; namespace Main { @@ -41,16 +46,23 @@ class GlobalPrivacy final { [[nodiscard]] rpl::producer<> suggestArchiveAndMute() const; void dismissArchiveAndMuteSuggestion(); + bool apply(const Tdb::TLDupdateOption &option); + private: +#if 0 // goodToRemove void apply(const MTPGlobalPrivacySettings &data); +#endif void update( bool archiveAndMute, UnarchiveOnNewMessage unarchiveOnNewMessage); const not_null _session; +#if 0 // goodToRemove MTP::Sender _api; mtpRequestId _requestId = 0; +#endif + Tdb::Sender _api; rpl::variable _archiveAndMute = false; rpl::variable _unarchiveOnNewMessage = UnarchiveOnNewMessage::None; diff --git a/Telegram/SourceFiles/api/api_invite_links.cpp b/Telegram/SourceFiles/api/api_invite_links.cpp index 1f02b78c13cfd..c31d9c7eb73e3 100644 --- a/Telegram/SourceFiles/api/api_invite_links.cpp +++ b/Telegram/SourceFiles/api/api_invite_links.cpp @@ -18,9 +18,13 @@ For license and copyright information please follow this link: #include "base/unixtime.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + constexpr auto kFirstPage = 10; constexpr auto kPerPage = 50; constexpr auto kJoinedFirstPage = 10; @@ -44,6 +48,40 @@ void RemovePermanent(PeerInviteLinks &links) { } // namespace +JoinedByLinkSlice ParseJoinedByLinkSlice( + not_null peer, + const TLDchatInviteLinkMembers &data) { + auto result = JoinedByLinkSlice(); + auto &owner = peer->session().data(); + result.count = data.vtotal_count().v; + result.users.reserve(data.vmembers().v.size()); + for (const auto &importer : data.vmembers().v) { + result.users.push_back({ + .user = owner.user(importer.data().vuser_id().v), + .date = importer.data().vjoined_chat_date().v, + .viaFilterLink + = importer.data().vvia_chat_folder_invite_link().v, + }); + } + return result; +} + +JoinedByLinkSlice ParseJoinedByLinkSlice( + not_null peer, + const TLDchatJoinRequests &data) { + auto result = JoinedByLinkSlice(); + auto &owner = peer->session().data(); + result.count = data.vtotal_count().v; + result.users.reserve(data.vrequests().v.size()); + for (const auto &request : data.vrequests().v) { + result.users.push_back({ + .user = owner.user(request.data().vuser_id().v), + .date = request.data().vdate().v, + }); + } + return result; +} +#if 0 // goodToRemove JoinedByLinkSlice ParseJoinedByLinkSlice( not_null peer, const MTPmessages_ChatInviteImporters &slice) { @@ -65,6 +103,7 @@ JoinedByLinkSlice ParseJoinedByLinkSlice( }); return result; } +#endif InviteLinks::InviteLinks(not_null api) : _api(api) { } @@ -106,6 +145,7 @@ void InviteLinks::performCreate( callbacks.push_back(std::move(done)); } +#if 0 // goodToRemove using Flag = MTPmessages_ExportChatInvite::Flag; _api->request(MTPmessages_ExportChatInvite( MTP_flags((revokeLegacyPermanent @@ -122,6 +162,14 @@ void InviteLinks::performCreate( MTP_int(usageLimit), MTP_string(label) )).done([=](const MTPExportedChatInvite &result) { +#endif + _api->sender().request(TLcreateChatInviteLink( + peerToTdbChat(peer->id), + tl_string(label), + tl_int32(expireDate), + tl_int32(usageLimit), + tl_bool(requestApproval) + )).done([=](const TLchatInviteLink &result) { const auto callbacks = _createCallbacks.take(peer); const auto link = prepend(peer, peer->session().user(), result); if (link && callbacks) { @@ -156,7 +204,10 @@ auto InviteLinks::lookupMyPermanent(const Links &links) const -> const Link* { auto InviteLinks::prepend( not_null peer, not_null admin, + const TLchatInviteLink &invite) -> std::optional { +#if 0 // goodToRemove const MTPExportedChatInvite &invite) -> std::optional { +#endif const auto link = parse(peer, invite); if (!link) { return link; @@ -263,6 +314,7 @@ void InviteLinks::performEdit( if (done) { callbacks.push_back(std::move(done)); } +#if 0 // goodToRemove using Flag = MTPmessages_EditExportedChatInvite::Flag; const auto flags = (revoke ? Flag::f_revoked : Flag(0)) | (!revoke ? Flag::f_title : Flag(0)) @@ -285,6 +337,12 @@ void InviteLinks::performEdit( result.match([&](const auto &data) { _api->session().data().processUsers(data.vusers()); const auto link = parse(peer, data.vinvite()); +#endif + const auto doneRequest = [=](const TLchatInviteLink &result) { + const auto callbacks = _editCallbacks.take(key); + const auto peer = key.peer; + const auto link = parse(peer, result); + { if (!link) { return; } @@ -315,6 +373,7 @@ void InviteLinks::performEdit( .now = link, }); +#if 0 // goodToRemove using Replaced = MTPDmessages_exportedChatInviteReplaced; if constexpr (Replaced::Is()) { prepend(peer, admin, data.vnew_invite()); @@ -323,6 +382,37 @@ void InviteLinks::performEdit( }).fail([=] { _editCallbacks.erase(key); }).send(); +#endif + } + }; + + if (revoke) { + _api->sender().request(TLrevokeChatInviteLink( + peerToTdbChat(peer->id), + tl_string(link) + )).done([=](const TLchatInviteLinks &result) { + doneRequest(result.data().vinvite_links().v.front()); + if (result.data().vtotal_count().v > 1) { + // A new link. + prepend(peer, admin, result.data().vinvite_links().v.back()); + } + }).fail([=] { + _editCallbacks.erase(key); + }).send(); + } else { + _api->sender().request(TLeditChatInviteLink( + peerToTdbChat(peer->id), + tl_string(link), + tl_string(label), + tl_int32(expireDate), + tl_int32(usageLimit), + tl_bool(requestApproval) + )).done([=](const TLchatInviteLink &result) { + doneRequest(result); + }).fail([=] { + _editCallbacks.erase(key); + }).send(); + } } void InviteLinks::revoke( @@ -367,9 +457,14 @@ void InviteLinks::destroy( if (done) { callbacks.push_back(std::move(done)); } +#if 0 // goodToRemove _api->request(MTPmessages_DeleteExportedChatInvite( peer->input, MTP_string(link) +#endif + _api->sender().request(TLdeleteRevokedChatInviteLink( + peerToTdbChat(peer->id), + tl_string(link) )).done([=] { const auto callbacks = _deleteCallbacks.take(key); if (callbacks) { @@ -402,9 +497,14 @@ void InviteLinks::destroyAllRevoked( if (done) { callbacks.push_back(std::move(done)); } +#if 0 // goodToRemove _api->request(MTPmessages_DeleteRevokedExportedChatInvites( peer->input, admin->inputUser +#endif + _api->sender().request(TLdeleteAllRevokedChatInviteLinks( + peerToTdbChat(peer->id), + tl_int53(peerToUser(admin->id).bare) )).done([=] { if (const auto callbacks = _deleteRevokedCallbacks.take(peer)) { for (const auto &callback : *callbacks) { @@ -419,6 +519,7 @@ void InviteLinks::requestMyLinks(not_null peer) { if (_firstSliceRequests.contains(peer)) { return; } +#if 0 // goodToRemove const auto requestId = _api->request(MTPmessages_GetExportedChatInvites( MTP_flags(0), peer->input, @@ -427,6 +528,15 @@ void InviteLinks::requestMyLinks(not_null peer) { MTPstring(), // offset_link MTP_int(kFirstPage) )).done([=](const MTPmessages_ExportedChatInvites &result) { +#endif + const auto requestId = _api->sender().request(TLgetChatInviteLinks( + peerToTdbChat(peer->id), + tl_int53(peer->session().userId().bare), + tl_bool(false), // Revoked. + tl_int32(0), // Offset date. + tl_string(), // Offset invite link. + tl_int32(kFirstPage) + )).done([=](const TLchatInviteLinks &result) { _firstSliceRequests.remove(peer); auto slice = parseSlice(peer, result); auto i = _firstSlices.find(peer); @@ -471,12 +581,19 @@ void InviteLinks::processRequest( _processRequests.emplace( std::pair{ peer, user }, ProcessRequest{ std::move(done), std::move(fail) }); +#if 0 // goodToRemove using Flag = MTPmessages_HideChatJoinRequest::Flag; _api->request(MTPmessages_HideChatJoinRequest( MTP_flags(approved ? Flag::f_approved : Flag(0)), peer->input, user->inputUser )).done([=](const MTPUpdates &result) { +#endif + _api->sender().request(TLprocessChatJoinRequest( + peerToTdbChat(peer->id), + tl_int53(peerToUser(user->id).bare), + tl_bool(approved) + )).done([=] { if (const auto chat = peer->asChat()) { if (chat->count > 0) { if (chat->participants.size() >= chat->count) { @@ -487,7 +604,9 @@ void InviteLinks::processRequest( } else if (const auto channel = peer->asChannel()) { _api->chatParticipants().requestCountDelayed(channel); } +#if 0 // goodToRemove _api->applyUpdates(result); +#endif if (link.isEmpty() && approved) { // We don't know the link that was used for this user. // Prune all the cache. @@ -602,6 +721,17 @@ void InviteLinks::requestJoinedFirstSlice(LinkKey key) { if (_firstJoinedRequests.contains(key)) { return; } + const auto requestId = _api->sender().request(TLgetChatJoinRequests( + peerToTdbChat(key.peer->id), + tl_string(key.link), + tl_string(), // Query. + tl_chatJoinRequest( // Offset. + tl_int53(0), + tl_int32(0), + tl_string()), + tl_int32(kJoinedFirstPage) + )).done([=](const TLDchatJoinRequests &result) { +#if 0 // goodToRemove const auto requestId = _api->request(MTPmessages_GetChatInviteImporters( MTP_flags(MTPmessages_GetChatInviteImporters::Flag::f_link), key.peer->input, @@ -611,6 +741,7 @@ void InviteLinks::requestJoinedFirstSlice(LinkKey key) { MTP_inputUserEmpty(), // offset_user MTP_int(kJoinedFirstPage) )).done([=](const MTPmessages_ChatInviteImporters &result) { +#endif _firstJoinedRequests.remove(key); _firstJoined[key] = ParseJoinedByLinkSlice(key.peer, result); _joinedFirstSliceLoaded.fire_copy(key); @@ -622,7 +753,10 @@ void InviteLinks::requestJoinedFirstSlice(LinkKey key) { void InviteLinks::setMyPermanent( not_null peer, + const TLchatInviteLink &invite) { +#if 0 // goodToRemove const MTPExportedChatInvite &invite) { +#endif auto link = parse(peer, invite); if (!link) { LOG(("API Error: " @@ -723,12 +857,21 @@ auto InviteLinks::myLinks(not_null peer) const -> const Links & { auto InviteLinks::parseSlice( not_null peer, - const MTPmessages_ExportedChatInvites &slice) const -> Links { + const TLchatInviteLinks &slice) const -> Links { auto i = _firstSlices.find(peer); const auto permanent = (i != end(_firstSlices)) ? lookupMyPermanent(i->second) : nullptr; auto result = Links(); + result.count = slice.data().vtotal_count().v; + for (const auto &invite : slice.data().vinvite_links().v) { + if (const auto link = parse(peer, invite)) { + if (!permanent || link->link != permanent->link) { + result.links.push_back(*link); + } + } + } +#if 0 // goodToRemove slice.match([&](const MTPDmessages_exportedChatInvites &data) { peer->session().data().processUsers(data.vusers()); result.count = data.vcount().v; @@ -740,11 +883,28 @@ auto InviteLinks::parseSlice( } } }); +#endif return result; } auto InviteLinks::parse( not_null peer, + const TLchatInviteLink &invite) const -> std::optional { + return Link{ + .link = invite.data().vinvite_link().v, + .label = invite.data().vname().v, + .admin = peer->owner().user(invite.data().vcreator_user_id().v), + .date = invite.data().vdate().v, + .startDate = invite.data().vedit_date().v, + .expireDate = invite.data().vexpiration_date().v, + .usageLimit = invite.data().vmember_limit().v, + .usage = invite.data().vmember_count().v, + .requested = invite.data().vpending_join_request_count().v, + .requestApproval = invite.data().vcreates_join_request().v, + .permanent = invite.data().vis_primary().v, + .revoked = invite.data().vis_revoked().v, + }; +#if 0 // goodToRemove const MTPExportedChatInvite &invite) const -> std::optional { return invite.match([&](const MTPDchatInviteExported &data) { return std::optional(Link{ @@ -764,6 +924,7 @@ auto InviteLinks::parse( }, [&](const MTPDchatInvitePublicJoinRequests &data) { return std::optional(); }); +#endif } void InviteLinks::requestMoreLinks( @@ -773,6 +934,15 @@ void InviteLinks::requestMoreLinks( const QString &lastLink, bool revoked, Fn done) { + _api->sender().request(TLgetChatInviteLinks( + peerToTdbChat(peer->id), + tl_int53(peerToUser(admin->id).bare), + tl_bool(revoked), + tl_int32(lastDate), + tl_string(lastLink), + tl_int32(kPerPage) + )).done([=](const TLchatInviteLinks &result) { +#if 0 // goodToRemove using Flag = MTPmessages_GetExportedChatInvites::Flag; _api->request(MTPmessages_GetExportedChatInvites( MTP_flags(Flag::f_offset_link @@ -783,6 +953,7 @@ void InviteLinks::requestMoreLinks( MTP_string(lastLink), MTP_int(kPerPage) )).done([=](const MTPmessages_ExportedChatInvites &result) { +#endif done(parseSlice(peer, result)); }).fail([=] { done(Links()); diff --git a/Telegram/SourceFiles/api/api_invite_links.h b/Telegram/SourceFiles/api/api_invite_links.h index 09b43c0868ac0..12a6a818bd7f8 100644 --- a/Telegram/SourceFiles/api/api_invite_links.h +++ b/Telegram/SourceFiles/api/api_invite_links.h @@ -7,6 +7,13 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLDchatInviteLinkMembers; +class TLDchatJoinRequests; +class TLchatInviteLink; +class TLchatInviteLinks; +} // namespace Tdb + class ApiWrap; namespace Api { @@ -51,7 +58,13 @@ struct InviteLinkUpdate { [[nodiscard]] JoinedByLinkSlice ParseJoinedByLinkSlice( not_null peer, + const Tdb::TLDchatInviteLinkMembers &data); +[[nodiscard]] JoinedByLinkSlice ParseJoinedByLinkSlice( + not_null peer, + const Tdb::TLDchatJoinRequests &data); +#if 0 // goodToRemove const MTPmessages_ChatInviteImporters &slice); +#endif class InviteLinks final { public: @@ -99,7 +112,10 @@ class InviteLinks final { void setMyPermanent( not_null peer, + const Tdb::TLchatInviteLink &invite); +#if 0 // goodToRemove const MTPExportedChatInvite &invite); +#endif void clearMyPermanent(not_null peer); void requestMyLinks(not_null peer); @@ -157,17 +173,26 @@ class InviteLinks final { [[nodiscard]] Links parseSlice( not_null peer, + const Tdb::TLchatInviteLinks &slice) const; +#if 0 // goodToRemove const MTPmessages_ExportedChatInvites &slice) const; +#endif [[nodiscard]] std::optional parse( not_null peer, + const Tdb::TLchatInviteLink &invite) const; +#if 0 // goodToRemove const MTPExportedChatInvite &invite) const; +#endif [[nodiscard]] Link *lookupMyPermanent(not_null peer); [[nodiscard]] Link *lookupMyPermanent(Links &links); [[nodiscard]] const Link *lookupMyPermanent(const Links &links) const; std::optional prepend( not_null peer, not_null admin, + const Tdb::TLchatInviteLink &invite); +#if 0 // goodToRemove const MTPExportedChatInvite &invite); +#endif void prependMyToFirstSlice( not_null peer, not_null admin, diff --git a/Telegram/SourceFiles/api/api_media.cpp b/Telegram/SourceFiles/api/api_media.cpp index ec8c1e4b26d1f..dc1a186abc546 100644 --- a/Telegram/SourceFiles/api/api_media.cpp +++ b/Telegram/SourceFiles/api/api_media.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #include "api/api_media.h" +#if 0 // mtp #include "api/api_common.h" #include "data/data_document.h" #include "data/stickers/data_stickers_set.h" @@ -129,3 +130,4 @@ bool HasAttachedStickers(MTPInputMedia media) { } } // namespace Api +#endif diff --git a/Telegram/SourceFiles/api/api_media.h b/Telegram/SourceFiles/api/api_media.h index 87912e9b7928c..49b857b77cd19 100644 --- a/Telegram/SourceFiles/api/api_media.h +++ b/Telegram/SourceFiles/api/api_media.h @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #pragma once +#if 0 // mtp class HistoryItem; namespace Api { @@ -24,3 +25,4 @@ MTPInputMedia PrepareUploadedDocument( bool HasAttachedStickers(MTPInputMedia media); } // namespace Api +#endif diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp index 09f9d6a2159ea..9f31ebb92977e 100644 --- a/Telegram/SourceFiles/api/api_messages_search.cpp +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -16,11 +16,29 @@ For license and copyright information please follow this link: #include "history/history_item.h" #include "main/main_session.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + constexpr auto kSearchPerPage = 50; +[[nodiscard]] MessageIdsList HistoryItemsFromTL( + not_null data, + const QVector &messages) { + auto result = MessageIdsList(); + for (const auto &message : messages) { + const auto item = data->processMessage( + message, + NewMessageType::Existing); + result.push_back(item->fullId()); + } + return result; +} +#if 0 // mtp [[nodiscard]] MessageIdsList HistoryItemsFromTL( not_null data, const QVector &messages) { @@ -42,6 +60,7 @@ constexpr auto kSearchPerPage = 50; } return result; } +#endif } // namespace @@ -50,19 +69,23 @@ MessagesSearch::MessagesSearch(not_null history) } MessagesSearch::~MessagesSearch() { + _history->session().sender().request(_requestId).cancel(); +#if 0 // mtp _history->owner().histories().cancelRequest( base::take(_searchInHistoryRequest)); +#endif } void MessagesSearch::searchMessages(const QString &query, PeerData *from) { _query = query; _from = from; _offsetId = {}; + _full = false; searchRequest(); } void MessagesSearch::searchMore() { - if (_searchInHistoryRequest || _requestId) { + if (_searchInHistoryRequest || _requestId || _full) { return; } searchRequest(); @@ -79,6 +102,29 @@ void MessagesSearch::searchRequest() { return; } } + if (_requestId) { + _history->session().sender().request(_requestId).cancel(); + } + _requestId = _history->session().sender().request(TLsearchChatMessages( + peerToTdbChat(_history->peer->id), + tl_string(_query), + (_from + ? peerToSender(_from->id) + : std::optional()), + tl_int53(_offsetId.bare), // from_message_id + tl_int32(0), // offset + tl_int32(kSearchPerPage), + std::nullopt, // filter + tl_int53(0) // message_thread_id + )).done([=](const TLfoundChatMessages &result, RequestId id) { + searchReceived(result, id, nextToken); + }).fail([=](const Error &error) { + _requestId = 0; + if (error.message == u"SEARCH_QUERY_EMPTY"_q) { + _messagesFounds.fire({ 0, MessageIdsList(), nextToken }); + } + }).send(); +#if 0 // mtp auto callback = [=](Fn finish) { const auto flags = _from ? MTP_flags(MTPmessages_Search::Flag::f_from_id) @@ -122,12 +168,20 @@ void MessagesSearch::searchRequest() { _history, Data::Histories::RequestType::History, std::move(callback)); +#endif } void MessagesSearch::searchReceived( const TLMessages &result, mtpRequestId requestId, const QString &nextToken) { + Expects(_requestId == requestId); + + const auto &data = result.data(); + auto items = HistoryItemsFromTL(&_history->owner(), data.vmessages().v); + const auto total = int(data.vtotal_count().v); + auto found = FoundMessages{ total, std::move(items), nextToken }; +#if 0 // mtp if (requestId != _requestId) { return; } @@ -174,13 +228,14 @@ void MessagesSearch::searchReceived( }, [](const MTPDmessages_messagesNotModified &data) { return FoundMessages{}; }); +#endif if (!_offsetId) { _cacheOfStartByToken.emplace(nextToken, result); } _requestId = 0; - _offsetId = found.messages.empty() - ? MsgId() - : found.messages.back().msg; + _offsetId = data.vnext_from_message_id().v; + _full = !_offsetId; + found.full = _full; _messagesFounds.fire(std::move(found)); } diff --git a/Telegram/SourceFiles/api/api_messages_search.h b/Telegram/SourceFiles/api/api_messages_search.h index b97ec69aae73a..db97eea39ccad 100644 --- a/Telegram/SourceFiles/api/api_messages_search.h +++ b/Telegram/SourceFiles/api/api_messages_search.h @@ -7,6 +7,10 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLfoundChatMessages; +} // namespace Tdb + class HistoryItem; class History; class PeerData; @@ -17,6 +21,7 @@ struct FoundMessages { int total = -1; MessageIdsList messages; QString nextToken; + bool full = false; }; class MessagesSearch final { @@ -30,7 +35,10 @@ class MessagesSearch final { [[nodiscard]] rpl::producer messagesFounds() const; private: +#if 0 // mtp using TLMessages = MTPmessages_Messages; +#endif + using TLMessages = Tdb::TLfoundChatMessages; void searchRequest(); void searchReceived( const TLMessages &result, @@ -48,6 +56,8 @@ class MessagesSearch final { int _searchInHistoryRequest = 0; // Not real mtpRequestId. mtpRequestId _requestId = 0; + bool _full = false; + rpl::event_stream _messagesFounds; }; diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.cpp b/Telegram/SourceFiles/api/api_messages_search_merged.cpp index a1cb69d7741e5..041144435fb97 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.cpp +++ b/Telegram/SourceFiles/api/api_messages_search_merged.cpp @@ -38,6 +38,9 @@ MessagesSearchMerged::MessagesSearchMerged(not_null history) if (data.total == int(_concatedFound.messages.size())) { _isFull = true; addFound(_migratedFirstFound); + } else if (data.full) { + _isFull = true; + addFound(_migratedFirstFound); } }; diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp index 25a7065873279..fb58b1efcd5a8 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.cpp +++ b/Telegram/SourceFiles/api/api_peer_photo.cpp @@ -23,17 +23,25 @@ For license and copyright information please follow this link: #include "data/data_user_photos.h" #include "history/history.h" #include "main/main_session.h" +#if 0 // goodToRemove #include "storage/file_upload.h" #include "storage/localimageloader.h" +#endif #include "storage/storage_user_photos.h" +#include "tdb/tdb_file_generator.h" +#include "tdb/tdb_tl_scheme.h" +#include "data/data_wall_paper.h" #include namespace Api { namespace { +using namespace Tdb; + constexpr auto kSharedMediaLimit = 100; +#if 0 // goodToRemove [[nodiscard]] SendMediaReady PreparePeerPhoto( MTP::DcId dcId, PeerId peerId, @@ -150,12 +158,45 @@ constexpr auto kSharedMediaLimit = 100; } return std::nullopt; } +#endif + +[[nodiscard]] std::optional PrepareSticker( + not_null session, + const PeerPhoto::UserPhoto &d) { + const auto &documentId = d.markupDocumentId; + const auto &colors = d.markupColors; + if (!documentId || colors.empty()) { + return std::nullopt; + } + const auto document = session->data().document(documentId); + if (const auto sticker = document->sticker()) { + if (sticker->isStatic()) { + return std::nullopt; + } else if (sticker->setType == Data::StickersType::Emoji) { + return tl_chatPhotoSticker( + tl_chatPhotoStickerTypeCustomEmoji( + tl_int64(document->id)), + Ui::ColorsToFill(colors)); + } else if (sticker->set.id) { + return tl_chatPhotoSticker( + tl_chatPhotoStickerTypeRegularOrMask( + tl_int64(sticker->set.id), + tl_int64(document->id)), + Ui::ColorsToFill(colors)); + } + } + return std::nullopt; +} } // namespace PeerPhoto::PeerPhoto(not_null api) : _session(&api->session()) +#if 0 // goodToRemove , _api(&api->instance()) { +#endif +, _api(&api->sender()) { +#if 0 // goodToRemove crl::on_main(_session, [=] { // You can't use _session->lifetime() in the constructor, // only queued, because it is not constructed yet. @@ -164,6 +205,7 @@ PeerPhoto::PeerPhoto(not_null api) ready(data.fullId, data.info.file, std::nullopt); }, _session->lifetime()); }); +#endif } void PeerPhoto::upload( @@ -181,6 +223,16 @@ void PeerPhoto::updateSelf( not_null photo, Data::FileOrigin origin, Fn done) { + _api.request(TLsetProfilePhoto( + tl_inputChatPhotoPrevious(tl_int64(photo->id)), + tl_bool(false) // is_public + )).done([=] { + if (done) { + done(); + } + }).send(); + +#if 0 // mtp const auto send = [=](auto resend) -> void { const auto usedFileReference = photo->fileReference(); _api.request(MTPphotos_UpdateProfilePhoto( @@ -208,6 +260,7 @@ void PeerPhoto::updateSelf( }).send(); }; send(send); +#endif } void PeerPhoto::upload( @@ -216,6 +269,7 @@ void PeerPhoto::upload( UploadType type, Fn done) { peer = peer->migrateToOrMe(); +#if 0 // goodToRemove const auto mtpMarkup = PrepareMtpMarkup(_session, photo); const auto fakeId = FullMsgId( @@ -241,6 +295,70 @@ void PeerPhoto::upload( base::take(photo.image)); _session->uploader().uploadMedia(fakeId, ready); } +#endif + auto generator = std::unique_ptr(); + auto inputFile = TLinputChatPhoto(); + + const auto eraseExisted = [=] { + const auto it = _uploads.find(peer); + if (it != end(_uploads)) { + if (it->second.generator) { + it->second.generator->cancel(); + } + _uploads.erase(it); + } + }; + eraseExisted(); + + if (const auto sticker = PrepareSticker(_session, photo)) { + inputFile = tl_inputChatPhotoSticker(*sticker); + } else { + auto data = QByteArray(); + auto jpegBuffer = QBuffer(&data); + photo.image.save(&jpegBuffer, "JPG", 87); + + generator = std::make_unique( + &_session->tdb(), + std::move(data), + "photo.jpg"); + inputFile = tl_inputChatPhotoStatic(generator->inputFile()); + + generator->lifetime().add(eraseExisted); + } + _uploads.emplace(peer, UploadValue{ + .generator = std::move(generator), + .type = type, + .done = std::move(done), + }); + const auto finish = [=] { + if (auto taken = _uploads.take(peer); taken && taken->done) { + taken->done(); + } + }; + + if (peer->isSelf()) { + _api.request(TLsetProfilePhoto( + std::move(inputFile), + tl_bool(type == UploadType::Fallback) + )).done(finish).send(); + } else if (const auto user = peer->asUser()) { + if (type == UploadType::Suggestion) { + _api.request(TLsuggestUserProfilePhoto( + tl_int53(peerToUser(user->id).bare), + std::move(inputFile) + )).done(finish).send(); + } else { + _api.request(TLsetUserPersonalProfilePhoto( + tl_int53(peerToUser(user->id).bare), + std::move(inputFile) + )).done(finish).send(); + } + } else { + _api.request(TLsetChatPhoto( + peerToTdbChat(peer->id), + std::move(inputFile) + )).done(finish).send(); + } } void PeerPhoto::suggest(not_null peer, UserPhoto &&photo) { @@ -249,6 +367,7 @@ void PeerPhoto::suggest(not_null peer, UserPhoto &&photo) { void PeerPhoto::clear(not_null photo) { const auto self = _session->user(); +#if 0 // mtp if (self->userpicPhotoId() == photo->id) { _api.request(MTPphotos_UpdateProfilePhoto( MTP_flags(0), @@ -292,9 +411,40 @@ void PeerPhoto::clear(not_null photo) { photo->id)); } } +#endif + + if (_session->user()->userpicPhotoId() == photo->id) { + _api.request(TLdeleteProfilePhoto( + tl_int64(photo->id) + )).send(); + } else if (photo->peer && photo->peer->userpicPhotoId() == photo->id) { + _api.request(TLsetChatPhoto( + peerToTdbChat(photo->peer->id), + null + )).send(); + } else { + const auto fallbackPhotoId = SyncUserFallbackPhotoViewer(self); + _api.request(TLdeleteProfilePhoto( + tl_int64(photo->id) + )).send(); + if (fallbackPhotoId && (*fallbackPhotoId) == photo->id) { + _session->storage().add(Storage::UserPhotosSetBack( + peerToUser(self->id), + PhotoId())); + } else { + _session->storage().remove(Storage::UserPhotosRemoveOne( + peerToUser(_session->userPeerId()), + photo->id)); + } + } } void PeerPhoto::clearPersonal(not_null user) { + _api.request(TLsetUserPersonalProfilePhoto( + tl_int53(peerToUser(user->id).bare), + std::nullopt + )).send(); +#if 0 // mtp _api.request(MTPphotos_UploadContactProfilePhoto( MTP_flags(MTPphotos_UploadContactProfilePhoto::Flag::f_save), user->inputUser, @@ -308,6 +458,7 @@ void PeerPhoto::clearPersonal(not_null user) { _session->data().processUsers(data.vusers()); }); }).send(); +#endif if (!user->userpicPhotoUnknown() && user->hasPersonalPhoto()) { _session->storage().remove(Storage::UserPhotosRemoveOne( @@ -320,6 +471,18 @@ void PeerPhoto::set(not_null peer, not_null photo) { if (peer->userpicPhotoId() == photo->id) { return; } + if (peer->isSelf()) { + _api.request(TLsetProfilePhoto( + tl_inputChatPhotoPrevious(tl_int64(photo->id)), + tl_bool(false) // is_public + )).send(); + } else { + _api.request(TLsetChatPhoto( + peerToTdbChat(peer->id), + tl_inputChatPhotoPrevious(tl_int64(photo->id)) + )).send(); + } +#if 0 // goodToRemove if (peer == _session->user()) { _api.request(MTPphotos_UpdateProfilePhoto( MTP_flags(0), @@ -347,8 +510,10 @@ void PeerPhoto::set(not_null peer, not_null photo) { )).done(applier).send(); } } +#endif } +#if 0 // mtp void PeerPhoto::ready( const FullMsgId &msgId, std::optional file, @@ -454,6 +619,7 @@ void PeerPhoto::ready( }).send(); } } +#endif void PeerPhoto::requestUserPhotos( not_null user, @@ -462,6 +628,7 @@ void PeerPhoto::requestUserPhotos( return; } +#if 0 // goodToRemove const auto requestId = _api.request(MTPphotos_GetUserPhotos( user->inputUser, MTP_int(0), @@ -475,10 +642,20 @@ void PeerPhoto::requestUserPhotos( }, [](const MTPDphotos_photosSlice &d) { return d.vcount().v; }); +#endif + const auto requestId = _api.request(TLgetUserProfilePhotos( + tl_int53(user->id.value), + tl_int32(afterId), + tl_int32(kSharedMediaLimit) + )).done([this, user](const TLchatPhotos &result) { + _userPhotosRequests.remove(user); + auto fullCount = result.data().vtotal_count().v; auto &owner = _session->data(); auto photoIds = result.match([&](const auto &data) { +#if 0 // goodToRemove owner.processUsers(data.vusers()); +#endif auto photoIds = std::vector(); photoIds.reserve(data.vphotos().v.size()); @@ -529,6 +706,7 @@ void PeerPhoto::requestEmojiList(EmojiListType type) { if (list.requestId) { return; } +#if 0 // mtp const auto send = [&](auto &&request) { return _api.request( std::move(request) @@ -552,6 +730,33 @@ void PeerPhoto::requestEmojiList(EmojiListType type) { : (type == EmojiListType::Group) ? send(MTPaccount_GetDefaultGroupPhotoEmojis()) : send(MTPaccount_GetDefaultBackgroundEmojis()); +#endif + const auto send = [&](auto &&request) { + return _api.request( + std::move(request) + ).done([=](const TLstickers &result) { + auto &list = emojiList(type); + list.requestId = 0; + + const auto &stickers = result.data().vstickers().v; + list.list = ranges::views::all( + result.data().vstickers().v + ) | ranges::views::transform([&](const TLsticker &sticker) { + return _session->data().processDocument(sticker); + }) | ranges::views::filter([](not_null sticker) { + return sticker->sticker() != nullptr; + }) | ranges::views::transform( + &DocumentData::id + ) | ranges::to_vector; + }).fail([=] { + emojiList(type).requestId = 0; + }).send(); + }; + list.requestId = (type == EmojiListType::Profile) + ? send(TLgetDefaultProfilePhotoCustomEmojiStickers()) + : (type == EmojiListType::Group) + ? send(TLgetDefaultChatPhotoCustomEmojiStickers()) + : send(TLgetDefaultBackgroundCustomEmojiStickers()); } rpl::producer PeerPhoto::emojiListValue( diff --git a/Telegram/SourceFiles/api/api_peer_photo.h b/Telegram/SourceFiles/api/api_peer_photo.h index 1d4bd1071ee5e..ffbdefb4d1fe8 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.h +++ b/Telegram/SourceFiles/api/api_peer_photo.h @@ -8,6 +8,11 @@ For license and copyright information please follow this link: #pragma once #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class FileGenerator; +} // namespace Tdb class ApiWrap; class PeerData; @@ -25,7 +30,7 @@ namespace Api { class PeerPhoto final { public: - using UserPhotoId = PhotoId; + using UserPhotoId = uint; explicit PeerPhoto(not_null api); enum class EmojiListType { @@ -79,10 +84,12 @@ class PeerPhoto final { mtpRequestId requestId = 0; }; +#if 0 // mtp void ready( const FullMsgId &msgId, std::optional file, std::optional videoSize); +#endif void upload( not_null peer, UserPhoto &&photo, @@ -92,16 +99,26 @@ class PeerPhoto final { [[nodiscard]] EmojiListData &emojiList(EmojiListType type); [[nodiscard]] const EmojiListData &emojiList(EmojiListType type) const; +#if 0 // goodToRemove const not_null _session; MTP::Sender _api; +#endif struct UploadValue { +#if 0 // mtp not_null peer; +#endif + std::shared_ptr generator; UploadType type = UploadType::Default; Fn done; }; +#if 0 // mtp base::flat_map _uploads; +#endif + const not_null _session; + Tdb::Sender _api; + base::flat_map, UploadValue> _uploads; base::flat_map, mtpRequestId> _userPhotosRequests; diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index 7699894cfb33d..953812894ffa7 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -8,7 +8,8 @@ For license and copyright information please follow this link: #include "api/api_polls.h" #include "api/api_common.h" -#include "api/api_updates.h" +#include "api/api_text_entities.h" // FormattedTextToTdb. +#include "api/api_sending.h" // MessageSendOptions. #include "apiwrap.h" #include "base/random.h" #include "data/data_changes.h" @@ -20,6 +21,8 @@ For license and copyright information please follow this link: #include "history/history_item_helpers.h" // ShouldSendSilent #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { @@ -27,11 +30,45 @@ namespace { return TimeId(msgId >> 32); } +[[nodiscard]] Tdb::TLinputMessageContent PollToTL( + not_null poll) { + auto options = ranges::views::all( + poll->answers + ) | ranges::views::transform([](const PollAnswer &answer) { + return Tdb::tl_string(answer.text); + }) | ranges::to(); + auto type = [&] { + if (poll->quiz()) { + const auto correctIt = ranges::find_if( + poll->answers, + &PollAnswer::correct); + const auto index = std::clamp( + size_t(std::distance(begin(poll->answers), correctIt)), + size_t(0), + poll->answers.size() - 1); + ; + return Tdb::tl_pollTypeQuiz( + Tdb::tl_int32(index), + Api::FormattedTextToTdb(poll->solution)); + } else { + return Tdb::tl_pollTypeRegular(Tdb::tl_bool(poll->multiChoice())); + } + }(); + return Tdb::tl_inputMessagePoll( + Tdb::tl_string(poll->question), + Tdb::tl_vector(std::move(options)), + Tdb::tl_bool(!poll->publicVotes()), + std::move(type)); +} + } // namespace Polls::Polls(not_null api) : _session(&api->session()) +#if 0 // mtp , _api(&api->instance()) { +#endif +, _api(&api->sender()) { } void Polls::create( @@ -39,8 +76,17 @@ void Polls::create( const SendAction &action, Fn done, Fn fail) { - _session->api().sendAction(action); + const auto peer = action.history->peer; + + _api.request(Tdb::TLsendMessage( + peerToTdbChat(peer->id), + MessageThreadId(peer, action), + MessageReplyTo(action), + MessageSendOptions(peer, action), + PollToTL(&data) + )).done(done).fail(fail).send(); +#if 0 // goodToRemove const auto history = action.history; const auto peer = history->peer; const auto topicRootId = action.replyTo.messageId @@ -105,6 +151,7 @@ void Polls::create( } fail(); }); +#endif } void Polls::sendVotes( @@ -134,6 +181,27 @@ void Polls::sendVotes( _session->data().requestItemRepaint(item); } + auto optionIds = ranges::views::all( + options + ) | ranges::views::transform([p = _session->data().poll(poll->id)]( + const QByteArray &d) { + return Tdb::tl_int32(p->indexByOption(d)); + }) | ranges::to(); + + const auto requestId = _api.request(Tdb::TLsetPollAnswer( + peerToTdbChat(item->history()->peer->id), + Tdb::tl_int53(item->id.bare), + Tdb::tl_vector(std::move(optionIds)) + )).done([=] { + _pollVotesRequestIds.erase(itemId); + hideSending(); + }).fail([=] { + _pollVotesRequestIds.erase(itemId); + hideSending(); + }).send(); + _pollVotesRequestIds.emplace(itemId, requestId); + +#if 0 // goodToRemove auto prepared = QVector(); prepared.reserve(options.size()); ranges::transform( @@ -153,6 +221,7 @@ void Polls::sendVotes( hideSending(); }).send(); _pollVotesRequestIds.emplace(itemId, requestId); +#endif } void Polls::close(not_null item) { @@ -165,6 +234,16 @@ void Polls::close(not_null item) { if (!poll) { return; } + const auto requestId = _api.request(Tdb::TLstopPoll( + peerToTdbChat(item->history()->peer->id), + Tdb::tl_int53(item->id.bare) + )).done([=] { + _pollCloseRequestIds.erase(itemId); + }).fail([=] { + _pollCloseRequestIds.erase(itemId); + }).send(); + _pollCloseRequestIds.emplace(itemId, requestId); +#if 0 // goodToRemove const auto requestId = _api.request(MTPmessages_EditMessage( MTP_flags(MTPmessages_EditMessage::Flag::f_media), item->history()->peer->input, @@ -181,9 +260,12 @@ void Polls::close(not_null item) { _pollCloseRequestIds.erase(itemId); }).send(); _pollCloseRequestIds.emplace(itemId, requestId); +#endif } void Polls::reloadResults(not_null item) { + // Managed by TDLib. +#if 0 // goodToRemove const auto itemId = item->fullId(); if (!item->isRegular() || _pollReloadRequestIds.contains(itemId)) { return; @@ -198,6 +280,7 @@ void Polls::reloadResults(not_null item) { _pollReloadRequestIds.erase(itemId); }).send(); _pollReloadRequestIds.emplace(itemId, requestId); +#endif } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_polls.h b/Telegram/SourceFiles/api/api_polls.h index 2ff08a1ac35b1..35dec25ac54ed 100644 --- a/Telegram/SourceFiles/api/api_polls.h +++ b/Telegram/SourceFiles/api/api_polls.h @@ -7,7 +7,10 @@ For license and copyright information please follow this link: */ #pragma once +#if 0 // mtp #include "mtproto/sender.h" +#endif +#include "tdb/tdb_sender.h" class ApiWrap; class HistoryItem; @@ -38,7 +41,10 @@ class Polls final { private: const not_null _session; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; base::flat_map _pollVotesRequestIds; base::flat_map _pollCloseRequestIds; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 58950f6f4b910..3dbc95787bb25 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -11,6 +11,7 @@ For license and copyright information please follow this link: #include "api/api_text_entities.h" #include "apiwrap.h" #include "base/random.h" +#include "data/data_channel.h" #include "data/data_document.h" #include "data/data_peer.h" #include "data/data_peer_values.h" @@ -21,9 +22,28 @@ For license and copyright information please follow this link: #include "payments/payments_form.h" #include "ui/text/format_values.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" +#include "boxes/premium_preview_box.h" + namespace Api { namespace { +using namespace Tdb; + +[[nodiscard]] GiftCode Parse(const TLDpremiumGiftCodeInfo &data) { + return { + .from = peerFromSender(data.vcreator_id()), + .to = data.vuser_id().v ? peerFromUser(data.vuser_id()) : PeerId(), + .giveawayId = data.vgiveaway_message_id().v, + .date = data.vcreation_date().v, + .used = data.vuse_date().v, + .months = data.vmonth_count().v, + .giveaway = data.vis_from_giveaway().v, + }; +} + +#if 0 // mtp [[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) { return { .from = peerFromMTP(data.vfrom_id()), @@ -35,30 +55,126 @@ namespace { .giveaway = data.is_via_giveaway(), }; } +#endif +#if 0 // mtp [[nodiscard]] Data::SubscriptionOptions GiftCodesFromTL( const QVector &tlOptions) { +#endif +[[nodiscard]] Data::SubscriptionOptions GiftCodesFromTL( + const QVector &tlOptions) { auto options = SubscriptionOptionsFromTL(tlOptions); for (auto i = 0; i < options.size(); i++) { const auto &tlOption = tlOptions[i].data(); const auto perUserText = Ui::FillAmountAndCurrency( +#if 0 // mtp tlOption.vamount().v / float64(tlOption.vusers().v), qs(tlOption.vcurrency()), +#endif + tlOption.vamount().v / float64(tlOption.vuser_count().v), + tlOption.vcurrency().v.toUtf8(), false); options[i].costPerMonth = perUserText + ' ' + QChar(0x00D7) + ' ' + + QString::number(tlOption.vuser_count().v); +#if 0 // mtp + QString::number(tlOption.vusers().v); +#endif } return options; } } // namespace +std::optional PreviewFromFeature( + const TLpremiumFeature &feature) { + const auto result = feature.match([]( + const TLDpremiumFeatureUpgradedStories &) { + return PremiumPreview::Stories; + }, [](const TLDpremiumFeatureChatBoost &) { + return PremiumPreview::kCount; + }, [](const TLDpremiumFeatureAccentColor &) { + return PremiumPreview::kCount; + }, [](const TLDpremiumFeatureIncreasedLimits &) { + return PremiumPreview::DoubleLimits; + }, [](const TLDpremiumFeatureIncreasedUploadFileSize &) { + return PremiumPreview::MoreUpload; + }, [](const TLDpremiumFeatureImprovedDownloadSpeed &) { + return PremiumPreview::FasterDownload; + }, [](const TLDpremiumFeatureVoiceRecognition &) { + return PremiumPreview::VoiceToText; + }, [](const TLDpremiumFeatureDisabledAds &) { + return PremiumPreview::NoAds; + }, [](const TLDpremiumFeatureUniqueReactions &) { + return PremiumPreview::InfiniteReactions; + }, [](const TLDpremiumFeatureUniqueStickers &) { + return PremiumPreview::Stickers; + }, [](const TLDpremiumFeatureCustomEmoji &) { + return PremiumPreview::AnimatedEmoji; + }, [](const TLDpremiumFeatureAdvancedChatManagement &) { + return PremiumPreview::AdvancedChatManagement; + }, [](const TLDpremiumFeatureProfileBadge &) { + return PremiumPreview::ProfileBadge; + }, [](const TLDpremiumFeatureEmojiStatus &) { + return PremiumPreview::EmojiStatus; + }, [](const TLDpremiumFeatureAnimatedProfilePhoto &) { + return PremiumPreview::AnimatedUserpics; + }, [](const TLDpremiumFeatureForumTopicIcon &) { + return PremiumPreview::kCount; + }, [](const TLDpremiumFeatureAppIcons &) { + return PremiumPreview::kCount; + }, [](const TLDpremiumFeatureRealTimeChatTranslation &) { + return PremiumPreview::RealTimeTranslation; + }); + return (result != PremiumPreview::kCount) + ? result + : std::optional(); +} + +TLpremiumFeature PreviewToFeature(PremiumPreview preview) { + Expects(preview != PremiumPreview::kCount); + + switch (preview) { + case PremiumPreview::Stories: + return tl_premiumFeatureUpgradedStories(); + case PremiumPreview::DoubleLimits: + return tl_premiumFeatureIncreasedLimits(); + case PremiumPreview::MoreUpload: + return tl_premiumFeatureIncreasedUploadFileSize(); + case PremiumPreview::FasterDownload: + return tl_premiumFeatureImprovedDownloadSpeed(); + case PremiumPreview::VoiceToText: + return tl_premiumFeatureVoiceRecognition(); + case PremiumPreview::NoAds: + return tl_premiumFeatureDisabledAds(); + case PremiumPreview::InfiniteReactions: + return tl_premiumFeatureUniqueReactions(); + case PremiumPreview::Stickers: + return tl_premiumFeatureUniqueStickers(); + case PremiumPreview::AnimatedEmoji: + return tl_premiumFeatureCustomEmoji(); + case PremiumPreview::AdvancedChatManagement: + return tl_premiumFeatureAdvancedChatManagement(); + case PremiumPreview::ProfileBadge: + return tl_premiumFeatureProfileBadge(); + case PremiumPreview::EmojiStatus: + return tl_premiumFeatureEmojiStatus(); + case PremiumPreview::AnimatedUserpics: + return tl_premiumFeatureAnimatedProfilePhoto(); + case PremiumPreview::RealTimeTranslation: + return tl_premiumFeatureRealTimeChatTranslation(); + } + Unexpected("PremiumPreview value in PreviewToFeature."); +} + Premium::Premium(not_null api) : _session(&api->session()) +#if 0 // mtp , _api(&api->instance()) { +#endif +, _api(&_session->sender()) { crl::on_main(_session, [=] { // You can't use _session->user() in the constructor, // only queued, because it is not constructed yet. @@ -79,7 +195,10 @@ rpl::producer Premium::statusTextValue() const { } auto Premium::videos() const +-> const base::flat_map> & { +#if 0 // mtp -> const base::flat_map> & { +#endif return _videos; } @@ -122,6 +241,55 @@ void Premium::reloadPromo() { if (_promoRequestId) { return; } + _promoRequestId = _api.request(TLgetPremiumState( + )).done([=](const TLDpremiumState &data) { + _promoRequestId = 0; + + auto list = ranges::views::all( + data.vpayment_options().v + ) | ranges::views::transform([]( + const TLpremiumStatePaymentOption &option) { + return option.data().vpayment_option(); + }) | ranges::to(); + + _subscriptionOptions = SubscriptionOptionsFromTL(list); + for (const auto &option : list) { + if (option.data().vmonth_count().v == 1) { + _monthlyAmount = option.data().vamount().v; + _monthlyCurrency = option.data().vcurrency().v; + } + } + + auto text = TextWithEntities{ + data.vstate().data().vtext().v, + EntitiesFromTdb(data.vstate().data().ventities().v), + }; + _statusText = text; + _statusTextUpdates.fire(std::move(text)); + auto videos = base::flat_map< + PremiumPreview, + not_null>(); + videos.reserve(data.vanimations().v.size()); + for (const auto &single : data.vanimations().v) { + const auto document = _session->data().processDocument( + single.data().vanimation()); + if ((!document->isVideoFile() && !document->isGifv()) + || !document->supportsStreaming()) { + document->forceIsStreamedAnimation(); + } + const auto type = PreviewFromFeature(single.data().vfeature()); + if (type) { + videos.emplace(*type, document); + } + } + if (_videos != videos) { + _videos = std::move(videos); + _videosUpdated.fire({}); + } + }).fail([=] { + _promoRequestId = 0; + }).send(); +#if 0 // mtp _promoRequestId = _api.request(MTPhelp_GetPremiumPromo( )).done([=](const MTPhelp_PremiumPromo &result) { _promoRequestId = 0; @@ -165,12 +333,31 @@ void Premium::reloadPromo() { }).fail([=] { _promoRequestId = 0; }).send(); +#endif } void Premium::reloadStickers() { if (_stickersRequestId) { return; } + _stickersRequestId = _api.request(TLgetPremiumStickerExamples( + )).done([=](const TLDstickers &result) { + _stickersRequestId = 0; + const auto &list = result.vstickers().v; + const auto owner = &_session->data(); + _stickers.clear(); + _stickers.reserve(list.size()); + for (const auto &sticker : list) { + const auto document = owner->processDocument(sticker); + if (document->isPremiumSticker()) { + _stickers.push_back(document); + } + } + _stickersUpdated.fire({}); + }).fail([=] { + _stickersRequestId = 0; + }).send(); +#if 0 // mtp _stickersRequestId = _api.request(MTPmessages_GetStickers( MTP_string("\xe2\xad\x90\xef\xb8\x8f\xe2\xad\x90\xef\xb8\x8f"), MTP_long(_stickersHash) @@ -192,12 +379,14 @@ void Premium::reloadStickers() { }).fail([=] { _stickersRequestId = 0; }).send(); +#endif } void Premium::reloadCloudSet() { if (_cloudSetRequestId) { return; } +#if 0 // mtp _cloudSetRequestId = _api.request(MTPmessages_GetStickers( MTP_string("\xf0\x9f\x93\x82\xe2\xad\x90\xef\xb8\x8f"), MTP_long(_cloudSetHash) @@ -206,16 +395,20 @@ void Premium::reloadCloudSet() { result.match([&](const MTPDmessages_stickersNotModified &) { }, [&](const MTPDmessages_stickers &data) { _cloudSetHash = data.vhash().v; - const auto owner = &_session->data(); - _cloudSet.clear(); - for (const auto &sticker : data.vstickers().v) { - const auto document = owner->processDocument(sticker); - if (document->isPremiumSticker()) { - _cloudSet.push_back(document); - } +#endif + _cloudSetRequestId = _api.request(TLgetPremiumStickers( + tl_int32(100) + )).done([=](const TLDstickers &data) { + _cloudSetRequestId = 0; + const auto owner = &_session->data(); + _cloudSet.clear(); + for (const auto &sticker : data.vstickers().v) { + const auto document = owner->processDocument(sticker); + if (document->isPremiumSticker()) { + _cloudSet.push_back(document); } - _cloudSetUpdated.fire({}); - }); + } + _cloudSetUpdated.fire({}); }).fail([=] { _cloudSetRequestId = 0; }).send(); @@ -231,6 +424,16 @@ void Premium::checkGiftCode( _api.request(_giftCodeRequestId).cancel(); } _giftCodeSlug = slug; + _giftCodeRequestId = _api.request(TLcheckPremiumGiftCode( + tl_string(slug) + )).done([=](const TLDpremiumGiftCodeInfo &data) { + _giftCodeRequestId = 0; + done(updateGiftCode(slug, Parse(data))); + }).fail([=] { + _giftCodeRequestId = 0; + done(updateGiftCode(slug, {})); + }).send(); +#if 0 // mtp _giftCodeRequestId = _api.request(MTPpayments_CheckGiftCode( MTP_string(slug) )).done([=](const MTPpayments_CheckedGiftCode &result) { @@ -245,6 +448,7 @@ void Premium::checkGiftCode( done(updateGiftCode(slug, {})); }).send(); +#endif } GiftCode Premium::updateGiftCode( @@ -268,6 +472,14 @@ rpl::producer Premium::giftCodeValue(const QString &slug) const { } void Premium::applyGiftCode(const QString &slug, Fn done) { + _api.request(TLapplyPremiumGiftCode( + tl_string(slug) + )).done([=] { + done({}); + }).fail([=](const Error &error) { + done(error.message); + }).send(); +#if 0 // mtp _api.request(MTPpayments_ApplyGiftCode( MTP_string(slug) )).done([=](const MTPUpdates &result) { @@ -276,6 +488,7 @@ void Premium::applyGiftCode(const QString &slug, Fn done) { }).fail([=](const MTP::Error &error) { done(error.type()); }).send(); +#endif } void Premium::resolveGiveawayInfo( @@ -294,6 +507,7 @@ void Premium::resolveGiveawayInfo( } _giveawayInfoPeer = peer; _giveawayInfoMessageId = messageId; +#if 0 // mtp _giveawayInfoRequestId = _api.request(MTPpayments_GetGiveawayInfo( _giveawayInfoPeer->input, MTP_int(_giveawayInfoMessageId.bare) @@ -323,6 +537,49 @@ void Premium::resolveGiveawayInfo( info.finishDate = data.vfinish_date().v; info.startDate = data.vstart_date().v; }); +#endif + _giveawayInfoRequestId = _api.request(TLgetPremiumGiveawayInfo( + peerToTdbChat(_giveawayInfoPeer->id), + tl_int53(_giveawayInfoMessageId.bare) + )).done([=](const TLpremiumGiveawayInfo &result) { + _giveawayInfoRequestId = 0; + + auto info = GiveawayInfo(); + result.match([&](const TLDpremiumGiveawayInfoOngoing &data) { + using AlreadyWasMember + = TLDpremiumGiveawayParticipantStatusAlreadyWasMember; + using Participating + = TLDpremiumGiveawayParticipantStatusParticipating; + using Administrator + = TLDpremiumGiveawayParticipantStatusAdministrator; + using DisallowedCountry + = TLDpremiumGiveawayParticipantStatusDisallowedCountry; + + data.vstatus().match([&]( + const TLDpremiumGiveawayParticipantStatusEligible &) { + }, [&](const Participating &) { + info.participating = true; + }, [&](const AlreadyWasMember &data) { + info.tooEarlyDate = data.vjoined_chat_date().v; + }, [&](const Administrator &data) { + info.adminChannelId = peerToChannel( + peerFromTdbChat(data.vchat_id())); + }, [&](const DisallowedCountry &data) { + info.disallowedCountry = data.vuser_country_code().v; + }); + info.state = data.vis_ended().v + ? GiveawayState::Preparing + : GiveawayState::Running; + info.startDate = data.vcreation_date().v; + }, [&](const TLDpremiumGiveawayInfoCompleted &data) { + info.state = data.vwas_refunded().v + ? GiveawayState::Refunded + : GiveawayState::Finished; + info.giftCode = data.vgift_code().v; + info.activatedCount = data.vactivation_count().v; + info.finishDate = data.vactual_winners_selection_date().v; + info.startDate = data.vcreation_date().v; + }); _giveawayInfoDone(std::move(info)); }).fail([=] { _giveawayInfoRequestId = 0; @@ -336,7 +593,10 @@ const Data::SubscriptionOptions &Premium::subscriptionOptions() const { PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null peer) : _peer(peer) +#if 0 // mtp , _api(&peer->session().api().instance()) { +#endif +, _api(&peer->session().sender()) { } rpl::producer PremiumGiftCodeOptions::request() { @@ -347,6 +607,7 @@ rpl::producer PremiumGiftCodeOptions::request() { return lifetime; } +#if 0 // mtp using TLOption = MTPPremiumGiftCodeOption; _api.request(MTPpayments_GetPremiumGiftCodeOptions( MTP_flags( @@ -385,6 +646,44 @@ rpl::producer PremiumGiftCodeOptions::request() { }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + using TLOption = Tdb::TLpremiumGiftCodePaymentOption; + _api.request(Tdb::TLgetPremiumGiftCodePaymentOptions( + peerToTdbChat(_peer->id) + )).done([=](const Tdb::TLDpremiumGiftCodePaymentOptions &data) { + auto tlMapOptions = base::flat_map>(); + for (const auto &tlOption : data.voptions().v) { + const auto &data = tlOption.data(); + const auto userCount = data.vuser_count().v; + tlMapOptions[userCount].push_back(tlOption); + + const auto token = Token{ userCount, data.vmonth_count().v }; + _stores[token] = Store{ + .amount = uint64(data.vamount().v), + .product = data.vstore_product_id().v.toUtf8(), + .quantity = data.vstore_product_quantity().v, + }; + if (!ranges::contains(_availablePresets, userCount)) { + _availablePresets.push_back(userCount); + } + } + for (const auto &[amount, tlOptions] : tlMapOptions) { + if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) { + _optionsForOnePerson.currency = + tlOptions.front().data().vcurrency().v.toUtf8(); + for (const auto &option : tlOptions) { + _optionsForOnePerson.months.push_back( + option.data().vmonth_count().v); + _optionsForOnePerson.totalCosts.push_back( + option.data().vamount().v); + } + } + _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); + } + consumer.put_done(); + }).fail([=](const Tdb::Error &error) { + consumer.put_error_copy(error.message); + }).send(); return lifetime; }; @@ -400,6 +699,7 @@ rpl::producer PremiumGiftCodeOptions::applyPrepaid( return lifetime; } +#if 0 // mtp _api.request(MTPpayments_LaunchPrepaidGiveaway( _peer->input, MTP_long(prepaidId), @@ -410,6 +710,17 @@ rpl::producer PremiumGiftCodeOptions::applyPrepaid( }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + using InvoiceGiveaway = Payments::InvoicePremiumGiftCodeGiveaway; + const auto &giveaway = v::get(invoice.purpose); + _api.request(Tdb::TLlaunchPrepaidPremiumGiveaway( + Tdb::tl_int64(prepaidId), + Payments::InvoiceGiftCodeGiveawayToTL(invoice) + )).done([=](const Tdb::TLok &) { + consumer.put_done(); + }).fail([=](const Tdb::Error &error) { + consumer.put_error_copy(error.message); + }).send(); return lifetime; }; @@ -445,6 +756,7 @@ Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) { if (it != end(_subscriptionOptions)) { return it->second; } else { +#if 0 // mtp auto tlOptions = QVector(); for (auto i = 0; i < _optionsForOnePerson.months.size(); i++) { tlOptions.push_back(MTP_premiumGiftCodeOption( @@ -457,6 +769,18 @@ Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) { MTP_long(_optionsForOnePerson.totalCosts[i] * amount))); } _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); +#endif + auto tlOptions = QVector(); + for (auto i = 0; i < _optionsForOnePerson.months.size(); i++) { + tlOptions.push_back(Tdb::tl_premiumGiftCodePaymentOption( + Tdb::tl_string(_optionsForOnePerson.currency), + Tdb::tl_int53(_optionsForOnePerson.totalCosts[i] * amount), + Tdb::tl_int32(amount), + Tdb::tl_int32(_optionsForOnePerson.months[i]), + Tdb::TLstring(), + Tdb::TLint32())); + } + _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions); return _subscriptionOptions[amount]; } } diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 1199b48194a40..181a4e614f03b 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -8,7 +8,17 @@ For license and copyright information please follow this link: #pragma once #include "data/data_subscription_option.h" +#if 0 // mtp #include "mtproto/sender.h" +#endif + +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLpremiumFeature; +} // namespace Tdb + +enum class PremiumPreview; class ApiWrap; @@ -65,6 +75,10 @@ struct GiveawayInfo { } }; +[[nodiscard]] std::optional PreviewFromFeature( + const Tdb::TLpremiumFeature &feature); +[[nodiscard]] Tdb::TLpremiumFeature PreviewToFeature(PremiumPreview preview); + class Premium final { public: explicit Premium(not_null api); @@ -73,7 +87,10 @@ class Premium final { [[nodiscard]] rpl::producer statusTextValue() const; [[nodiscard]] auto videos() const + -> const base::flat_map> &; +#if 0 // mtp -> const base::flat_map> &; +#endif [[nodiscard]] rpl::producer<> videosUpdated() const; [[nodiscard]] auto stickers() const @@ -109,13 +126,19 @@ class Premium final { void reloadCloudSet(); const not_null _session; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; mtpRequestId _promoRequestId = 0; std::optional _statusText; rpl::event_stream _statusTextUpdates; +#if 0 // mtp base::flat_map> _videos; +#endif + base::flat_map> _videos; rpl::event_stream<> _videosUpdated; mtpRequestId _stickersRequestId = 0; @@ -192,7 +215,10 @@ class PremiumGiftCodeOptions final { base::flat_map _stores; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; }; diff --git a/Telegram/SourceFiles/api/api_premium_option.h b/Telegram/SourceFiles/api/api_premium_option.h index 5758a8cb8f9db..f94e2078e6d78 100644 --- a/Telegram/SourceFiles/api/api_premium_option.h +++ b/Telegram/SourceFiles/api/api_premium_option.h @@ -9,6 +9,9 @@ For license and copyright information please follow this link: #include "data/data_subscription_option.h" +#include "tdb/tdb_tl_scheme.h" +#include "core/local_url_handlers.h" + namespace Api { [[nodiscard]] Data::SubscriptionOption CreateSubscriptionOption( @@ -31,11 +34,16 @@ template ranges::less(), [](const Option &o) { return o.data().vamount().v; } )->data(); +#if 0 // mtp return min.vamount().v / float64(min.vmonths().v); +#endif + return min.vamount().v / float64(min.vmonth_count().v); }(); + using TLGiftCode = Tdb::TLpremiumGiftCodePaymentOption; result.reserve(tlOptions.size()); for (const auto &tlOption : tlOptions) { const auto &option = tlOption.data(); +#if 0 // mtp auto botUrl = QString(); if constexpr (!std::is_same_v) { botUrl = qs(option.vbot_url()); @@ -43,12 +51,37 @@ template const auto months = option.vmonths().v; const auto amount = option.vamount().v; const auto currency = qs(option.vcurrency()); +#endif +#if 0 // mtp + const auto botUrl = !lnk + ? QString() + : (lnk->type() == Tdb::id_internalLinkTypeInvoice) + ? ("https://t.me/$" + + lnk->c_internalLinkTypeInvoice().vinvoice_name().v) + : (lnk->type() == Tdb::id_internalLinkTypeBotStart) + ? ("https://t.me/" + + lnk->c_internalLinkTypeBotStart().vbot_username().v + + "?start=" + + lnk->c_internalLinkTypeBotStart().vstart_parameter().v) + : QString(); +#endif + const auto botUrl = QString(); + const auto months = option.vmonth_count().v; + const auto amount = option.vamount().v; + const auto currency = option.vcurrency().v; result.push_back(CreateSubscriptionOption( months, monthlyAmount, amount, currency, botUrl)); + if constexpr (!std::is_same_v) { + if (const auto lnk = option.vpayment_link()) { + result.back().startPayment = [=, link = *lnk](QVariant c) { + Core::HandleLocalUrl(link, c); + }; + } + } } return result; } diff --git a/Telegram/SourceFiles/api/api_report.cpp b/Telegram/SourceFiles/api/api_report.cpp index 8691527371932..54fbf4fb6e16c 100644 --- a/Telegram/SourceFiles/api/api_report.cpp +++ b/Telegram/SourceFiles/api/api_report.cpp @@ -16,10 +16,30 @@ For license and copyright information please follow this link: #include "ui/boxes/report_box.h" #include "ui/layers/show.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + +[[nodiscard]] TLreportReason ReasonToTL(const Ui::ReportReason &reason) { + using Reason = Ui::ReportReason; + switch (reason) { + case Reason::Spam: return tl_reportReasonSpam(); + case Reason::Fake: return tl_reportReasonFake(); + case Reason::Violence: return tl_reportReasonViolence(); + case Reason::ChildAbuse: return tl_reportReasonChildAbuse(); + case Reason::Pornography: return tl_reportReasonPornography(); + case Reason::Copyright: return tl_reportReasonCopyright(); + case Reason::IllegalDrugs: return tl_reportReasonIllegalDrugs(); + case Reason::PersonalDetails: return tl_reportReasonPersonalDetails(); + case Reason::Other: return tl_reportReasonCustom(); + } + Unexpected("Bad reason group value."); +} +#if 0 // mtp MTPreportReason ReasonToTL(const Ui::ReportReason &reason) { using Reason = Ui::ReportReason; switch (reason) { @@ -36,6 +56,7 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) { } Unexpected("Bad reason group value."); } +#endif } // namespace @@ -52,6 +73,45 @@ void SendReport( auto done = [=] { show->showToast(tr::lng_report_thanks(tr::now)); }; + v::match(data, [&](v::null_t) { + peer->session().sender().request(TLreportChat( + peerToTdbChat(peer->id), + tl_vector(), + ReasonToTL(reason), + tl_string(comment) + )).done(std::move(done)).send(); + }, [&](const MessageIdsList &ids) { + auto list = QVector(); + list.reserve(ids.size()); + for (const auto &fullId : ids) { + list.push_back(tl_int53(fullId.msg.bare)); + } + peer->session().sender().request(TLreportChat( + peerToTdbChat(peer->id), + tl_vector(list), + ReasonToTL(reason), + tl_string(comment) + )).done(std::move(done)).send(); + }, [&](not_null photo) { + const auto tdb = std::get_if( + &photo->location(Data::PhotoSize::Large).file().data); + if (tdb) { + peer->session().sender().request(TLreportChatPhoto( + peerToTdbChat(peer->id), + tl_int32(tdb->fileId), + ReasonToTL(reason), + tl_string(comment) + )).done(std::move(done)).send(); + } + }, [&](StoryId id) { + peer->session().sender().request(TLreportStory( + peerToTdbChat(peer->id), + tl_int32(id), + ReasonToTL(reason), + tl_string(comment) + )).done(std::move(done)).send(); + }); +#if 0 // mtp v::match(data, [&](v::null_t) { peer->session().api().request(MTPaccount_ReportPeer( peer->input, @@ -85,6 +145,7 @@ void SendReport( MTP_string(comment) )).done(std::move(done)).send(); }); +#endif } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_ringtones.cpp b/Telegram/SourceFiles/api/api_ringtones.cpp index 5b471851e0a25..c41ec42edf8d2 100644 --- a/Telegram/SourceFiles/api/api_ringtones.cpp +++ b/Telegram/SourceFiles/api/api_ringtones.cpp @@ -22,9 +22,19 @@ For license and copyright information please follow this link: #include "storage/file_upload.h" #include "storage/localimageloader.h" +#include "tdb/tdb_file_generator.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + +} // namespace + +#if 0 // goodToRemove +namespace { + SendMediaReady PrepareRingtoneDocument( MTP::DcId dcId, const QString &filename, @@ -64,9 +74,12 @@ SendMediaReady PrepareRingtoneDocument( } } // namespace +#endif Ringtones::Ringtones(not_null api) : _session(&api->session()) +, _tdbApi(&api->sender()) { +#if 0 // goodToRemove , _api(&api->instance()) { crl::on_main(_session, [=] { // You can't use _session->lifetime() in the constructor, @@ -76,12 +89,14 @@ Ringtones::Ringtones(not_null api) ready(data.fullId, data.info.file); }, _session->lifetime()); }); +#endif } void Ringtones::upload( const QString &filename, const QString &filemime, const QByteArray &content) { +#if 0 // goodToRemove const auto ready = PrepareRingtoneDocument( _api.instance().mainDcId(), filename, @@ -104,8 +119,43 @@ void Ringtones::upload( } _uploads.emplace(fakeId, uploadedData); _session->uploader().uploadMedia(fakeId, ready); +#endif + const auto token = filename + QString::number(content.size()); + + auto generator = std::make_unique( + &_session->tdb(), + content, + filename); + auto inputFile = generator->inputFile(); + + const auto eraseExisted = [=] { + const auto it = _uploads.find(token); + if (it != end(_uploads)) { + it->second->cancel(); + _uploads.erase(it); + } + }; + eraseExisted(); + + generator->lifetime().add(eraseExisted); + + _uploads.emplace(token, std::move(generator)); + + _tdbApi.request(TLaddSavedNotificationSound( + std::move(inputFile) + )).done([=](const TLnotificationSound &result) { + const auto document = _session->data().processDocument(result); + _list.documents.insert(_list.documents.begin(), document->id); + const auto media = document->createMediaView(); + media->setBytes(content); + document->owner().notifySettings().cacheSound(document); + _uploadDones.fire_copy(document->id); + }).fail([=](const Error &error) { + _uploadFails.fire_copy(error.message); + }).send(); } +#if 0 // goodToRemove void Ringtones::ready(const FullMsgId &msgId, const MTPInputFile &file) { const auto maybeUploadedData = _uploads.take(msgId); if (!maybeUploadedData) { @@ -127,11 +177,13 @@ void Ringtones::ready(const FullMsgId &msgId, const MTPInputFile &file) { _uploadFails.fire_copy(error.type()); }).send(); } +#endif void Ringtones::requestList() { if (_list.requestId) { return; } +#if 0 // goodToRemove _list.requestId = _api.request( MTPaccount_GetSavedRingtones(MTP_long(_list.hash)) ).done([=](const MTPaccount_SavedRingtones &result) { @@ -148,6 +200,19 @@ void Ringtones::requestList() { _list.updates.fire({}); }, [&](const MTPDaccount_savedRingtonesNotModified &) { }); +#endif + _list.requestId = _tdbApi.request(TLgetSavedNotificationSounds( + )).done([=](const TLDnotificationSounds &data) { + _list.requestId = 0; + _list.documents.clear(); + _list.documents.reserve(data.vnotification_sounds().v.size()); + auto &owner = _session->data(); + for (const auto &s : data.vnotification_sounds().v) { + const auto document = owner.processDocument(s); + document->forceToCache(true); + _list.documents.emplace_back(document->id); + } + _list.updates.fire({}); }).fail([=] { _list.requestId = 0; }).send(); diff --git a/Telegram/SourceFiles/api/api_ringtones.h b/Telegram/SourceFiles/api/api_ringtones.h index ea97db3496a4c..2233becfe34a3 100644 --- a/Telegram/SourceFiles/api/api_ringtones.h +++ b/Telegram/SourceFiles/api/api_ringtones.h @@ -8,10 +8,15 @@ For license and copyright information please follow this link: #pragma once #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" class ApiWrap; class PeerData; +namespace Tdb { +class FileGenerator; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -43,6 +48,7 @@ class Ringtones final { [[nodiscard]] crl::time maxDuration() const; private: +#if 0 // goodToRemove struct UploadedData { QString filename; QString filemime; @@ -54,6 +60,12 @@ class Ringtones final { MTP::Sender _api; base::flat_map _uploads; +#endif + const not_null _session; + Tdb::Sender _tdbApi; + using UploadToken = QString; + base::flat_map> _uploads; + rpl::event_stream _uploadFails; rpl::event_stream _uploadDones; diff --git a/Telegram/SourceFiles/api/api_self_destruct.cpp b/Telegram/SourceFiles/api/api_self_destruct.cpp index 4bc32f836cd56..97581d7134dc9 100644 --- a/Telegram/SourceFiles/api/api_self_destruct.cpp +++ b/Telegram/SourceFiles/api/api_self_destruct.cpp @@ -9,12 +9,18 @@ For license and copyright information please follow this link: #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { SelfDestruct::SelfDestruct(not_null api) +#if 0 // goodToRemove : _api(&api->instance()) { +#endif +: _api(&api->sender()) { } +#if 0 // goodToRemove void SelfDestruct::reload() { if (!_accountTTL.requestId) { _accountTTL.requestId = _api.request(MTPaccount_GetAccountTTL( @@ -36,6 +42,30 @@ void SelfDestruct::reload() { }).send(); } } +#endif + +void SelfDestruct::reload() { + using namespace Tdb; + if (!_accountTTL.requestId) { + _accountTTL.requestId = _api.request(TLgetAccountTtl( + )).done([=](const TLDaccountTtl &data) { + _accountTTL.requestId = 0; + _accountTTL.days = data.vdays().v; + }).fail([=](const Error &error) { + _accountTTL.requestId = 0; + }).send(); + } + if (!_defaultHistoryTTL.requestId) { + _defaultHistoryTTL.requestId = _api.request( + TLgetDefaultMessageAutoDeleteTime() + ).done([=](const TLDmessageAutoDeleteTime &data) { + _defaultHistoryTTL.requestId = 0; + _defaultHistoryTTL.period = data.vtime().v; + }).fail([=](const Error &error) { + _defaultHistoryTTL.requestId = 0; + }).send(); + } +} rpl::producer SelfDestruct::daysAccountTTL() const { return _accountTTL.days.value() | rpl::filter(rpl::mappers::_1 != 0); @@ -49,6 +79,7 @@ TimeId SelfDestruct::periodDefaultHistoryTTLCurrent() const { return _defaultHistoryTTL.period.current(); } +#if 0 // goodToRemove void SelfDestruct::updateAccountTTL(int days) { _api.request(_accountTTL.requestId).cancel(); _accountTTL.requestId = _api.request(MTPaccount_SetAccountTTL( @@ -72,5 +103,31 @@ void SelfDestruct::updateDefaultHistoryTTL(TimeId period) { }).send(); _defaultHistoryTTL.period = period; } +#endif + +void SelfDestruct::updateAccountTTL(int days) { + using namespace Tdb; + _accountTTL.requestId = _api.request(TLsetAccountTtl( + tl_accountTtl(tl_int32(days)) + )).done([=](const TLok &result) { + _accountTTL.requestId = 0; + }).fail([=](const Error &result) { + _accountTTL.requestId = 0; + }).send(); + _accountTTL.days = days; +} + +void SelfDestruct::updateDefaultHistoryTTL(TimeId period) { + using namespace Tdb; + _defaultHistoryTTL.requestId = _api.request( + TLsetDefaultMessageAutoDeleteTime( + tl_messageAutoDeleteTime(tl_int32(period))) + ).done([=](const TLok &result) { + _defaultHistoryTTL.requestId = 0; + }).fail([=](const Error &result) { + _defaultHistoryTTL.requestId = 0; + }).send(); + _defaultHistoryTTL.period = period; +} } // namespace Api diff --git a/Telegram/SourceFiles/api/api_self_destruct.h b/Telegram/SourceFiles/api/api_self_destruct.h index bbde0e35cca00..2ffffb8fb1298 100644 --- a/Telegram/SourceFiles/api/api_self_destruct.h +++ b/Telegram/SourceFiles/api/api_self_destruct.h @@ -7,7 +7,7 @@ For license and copyright information please follow this link: */ #pragma once -#include "mtproto/sender.h" +#include "tdb/tdb_sender.h" class ApiWrap; @@ -26,7 +26,10 @@ class SelfDestruct final { [[nodiscard]] TimeId periodDefaultHistoryTTLCurrent() const; private: +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; struct { mtpRequestId requestId = 0; rpl::variable days = 0; diff --git a/Telegram/SourceFiles/api/api_send_progress.cpp b/Telegram/SourceFiles/api/api_send_progress.cpp index 54d3c4e00e289..7ade423f3b182 100644 --- a/Telegram/SourceFiles/api/api_send_progress.cpp +++ b/Telegram/SourceFiles/api/api_send_progress.cpp @@ -15,9 +15,14 @@ For license and copyright information please follow this link: #include "data/data_peer_values.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Api { namespace { +using namespace Tdb; + constexpr auto kCancelTypingActionTimeout = crl::time(5000); constexpr auto kSendMySpeakingInterval = 3 * crl::time(1000); constexpr auto kSendMyTypingInterval = 5 * crl::time(1000); @@ -42,7 +47,10 @@ void SendProgressManager::cancel( SendProgressType type) { const auto i = _requests.find(Key{ history, topMsgId, type }); if (i != _requests.end()) { +#if 0 // mtp _session->api().request(i->second).cancel(); +#endif + _session->sender().request(i->second).cancel(); _requests.erase(i); } } @@ -113,6 +121,7 @@ void SendProgressManager::send(const Key &key, int progress) { return; } using Type = SendProgressType; +#if 0 // mtp const auto action = [&]() -> MTPsendMessageAction { const auto p = MTP_int(progress); switch (key.type) { @@ -143,6 +152,34 @@ void SendProgressManager::send(const Key &key, int progress) { )).done([=](const MTPBool &result, mtpRequestId requestId) { done(requestId); }).send(); +#endif + const auto action = [&]() -> TLchatAction { + const auto p = tl_int32(progress); + switch (key.type) { + case Type::Typing: return tl_chatActionTyping(); + case Type::RecordVideo: return tl_chatActionRecordingVideo(); + case Type::UploadVideo: return tl_chatActionUploadingVideo(p); + case Type::RecordVoice: return tl_chatActionRecordingVoiceNote(); + case Type::UploadVoice: return tl_chatActionUploadingVoiceNote(p); + case Type::RecordRound: return tl_chatActionRecordingVideoNote(); + case Type::UploadRound: return tl_chatActionUploadingVideoNote(p); + case Type::UploadPhoto: return tl_chatActionUploadingPhoto(p); + case Type::UploadFile: return tl_chatActionUploadingDocument(p); + case Type::ChooseLocation: return tl_chatActionChoosingLocation(); + case Type::ChooseContact: return tl_chatActionChoosingContact(); + case Type::PlayGame: return tl_chatActionStartPlayingGame(); + //case Type::Speaking: ... // mtp + case Type::ChooseSticker: return tl_chatActionChoosingSticker(); + default: return tl_chatActionTyping(); + } + }(); + const auto requestId = _session->sender().request(TLsendChatAction( + peerToTdbChat(key.history->peer->id), + tl_int53(key.topMsgId.bare), + action + )).done([=](TLok, mtpRequestId requestId) { + done(requestId); + }).send(); _requests.emplace(key, requestId); if (key.type == Type::Typing) { diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index a0d6f4d95750d..0659350e7ffab 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -34,9 +34,19 @@ For license and copyright information please follow this link: #include "mainwidget.h" #include "apiwrap.h" +#include "data/data_scheduled_messages.h" +#include "tdb/tdb_file_generator.h" +#include "tdb/tdb_tl_scheme.h" +#include "inline_bots/inline_bot_result.h" +#include "data/data_forum_topic.h" + namespace Api { namespace { +using namespace Tdb; + +constexpr auto kScheduledTillOnline = Api::kScheduledUntilOnlineTimestamp; + void InnerFillMessagePostFlags( const SendOptions &options, not_null peer, @@ -59,6 +69,7 @@ void InnerFillMessagePostFlags( } } +#if 0 // mtp template void SendExistingMedia( MessageToSend &&message, @@ -179,6 +190,298 @@ void SendExistingMedia( api->finishForwarding(message.action); } +#endif + +[[nodiscard]] TLinputMessageContent MessageContentFromFile( + not_null session, + const std::shared_ptr &file, + const Storage::ReadyFileWithThumbnail &ready) { + Expects(ready.file != nullptr); + + auto caption = TextWithEntities{ + file->caption.text, + TextUtilities::ConvertTextTagsToEntities(file->caption.tags) + }; + TextUtilities::PrepareForSending(caption, 0); + TextUtilities::Trim(caption); + const auto formatted = caption.text.isEmpty() + ? std::optional() + : Api::FormattedTextToTdb(caption); + + const auto &fields = ready.file->data(); + const auto thumbnail = (ready.thumbnailGenerator + ? tl_inputThumbnail( + ready.thumbnailGenerator->inputFile(), + tl_int32(file->thumbnailDimensions.width()), + tl_int32(file->thumbnailDimensions.height())) + : std::optional()); + const auto fileById = tl_inputFileId(tl_int32(fields.vid().v)); + const auto seconds = file->duration / 1000; + const auto attached = tl_vector(file->attachedStickers + | ranges::views::transform(tl_int32) + | ranges::to()); + switch (file->filetype) { + case PreparedFileType::Photo: + return tl_inputMessagePhoto( + fileById, + thumbnail, + attached, + tl_int32(file->dimensions.width()), + tl_int32(file->dimensions.height()), + formatted, + std::nullopt, // self_destruct_type + tl_bool(file->spoiler)); + case PreparedFileType::Animation: + return tl_inputMessageAnimation( + fileById, + thumbnail, + attached, + tl_int32(seconds), + tl_int32(file->dimensions.width()), + tl_int32(file->dimensions.height()), + formatted, + tl_bool(file->spoiler)); + case PreparedFileType::Audio: + return tl_inputMessageAudio( + fileById, + thumbnail, + tl_int32(seconds), + tl_string(file->title), + tl_string(file->performer), + formatted); + case PreparedFileType::Document: + return tl_inputMessageDocument( + fileById, + thumbnail, + tl_bool(false), + formatted); + case PreparedFileType::Sticker: + return tl_inputMessageSticker( + fileById, + thumbnail, + tl_int32(file->dimensions.width()), + tl_int32(file->dimensions.height()), + tl_string()); // Emoji used for search. + case PreparedFileType::Video: + return tl_inputMessageVideo( + fileById, + thumbnail, + tl_vector(), + tl_int32(seconds), + tl_int32(file->dimensions.width()), + tl_int32(file->dimensions.height()), + tl_bool(file->supportsStreaming), + formatted, + std::nullopt, // self_destruct_type + tl_bool(file->spoiler)); + case PreparedFileType::VoiceNote: + return tl_inputMessageVoiceNote( + fileById, + tl_int32(seconds), + tl_bytes(file->waveform), + formatted); + } + Unexpected("FileLoadResult::filetype."); +} + +[[nodiscard]] TLinputMessageContent MessageContentFromExisting( + not_null document, + TextWithTags &&text) { + Expects(document->tdbFileId() != 0); + + auto caption = TextWithEntities{ + text.text, + TextUtilities::ConvertTextTagsToEntities(text.tags) + }; + TextUtilities::PrepareForSending(caption, 0); + TextUtilities::Trim(caption); + const auto formatted = caption.text.isEmpty() + ? std::optional() + : Api::FormattedTextToTdb(caption); + + const auto thumbnail = std::optional(); + const auto fileById = tl_inputFileId(tl_int32(document->tdbFileId())); + if (document->isVideoMessage()) { + return tl_inputMessageVideoNote( + fileById, + thumbnail, + tl_int32(document->duration() / 1000), + tl_int32(document->dimensions.width())); + } else if (document->isVoiceMessage()) { + return tl_inputMessageVoiceNote( + fileById, + tl_int32(document->duration() / 1000), + tl_bytes(documentWaveformEncode5bit(document->voice()->waveform)), + formatted); + } else if (document->isAnimation()) { + return tl_inputMessageAnimation( + fileById, + thumbnail, + tl_vector(), + tl_int32(document->duration() / 1000), + tl_int32(document->dimensions.width()), + tl_int32(document->dimensions.height()), + formatted, + tl_bool(false)); // has_spoiler + } else if (document->sticker()) { + return tl_inputMessageSticker( + fileById, + thumbnail, + tl_int32(document->dimensions.width()), + tl_int32(document->dimensions.height()), + tl_string()); // Emoji used for search. + } else if (document->isVideoFile()) { + return tl_inputMessageVideo( + fileById, + thumbnail, + tl_vector(), + tl_int32(document->duration() / 1000), + tl_int32(document->dimensions.width()), + tl_int32(document->dimensions.height()), + tl_bool(document->supportsStreaming()), + formatted, + std::nullopt, // self_destruct_type + tl_bool(false)); // has_spoiler + } else if (const auto song = document->song()) { + return tl_inputMessageAudio( + fileById, + thumbnail, + tl_int32(document->duration() / 1000), + tl_string(song->title), + tl_string(song->performer), + formatted); + } else { + return tl_inputMessageDocument( + fileById, + thumbnail, + tl_bool(false), + formatted); + } +} + +[[nodiscard]] TLinputMessageContent MessageContentFromExisting( + not_null photo, + TextWithTags &&text) { + Expects(v::is( + photo->location(Data::PhotoSize::Large).file().data)); + + auto caption = TextWithEntities{ + text.text, + TextUtilities::ConvertTextTagsToEntities(text.tags) + }; + TextUtilities::PrepareForSending(caption, 0); + TextUtilities::Trim(caption); + + const auto formatted = caption.text.isEmpty() + ? std::optional() + : Api::FormattedTextToTdb(caption); + + const auto thumbnail = std::optional(); + const auto fileById = tl_inputFileId(tl_int32(v::get( + photo->location(Data::PhotoSize::Large).file().data).fileId)); + return tl_inputMessagePhoto( + fileById, + thumbnail, + tl_vector(), + tl_int32(photo->width()), + tl_int32(photo->height()), + formatted, + std::nullopt, // self_destruct_type + tl_bool(false)); // spoiler +} + +void SendPreparedAlbumIfReady( + const SendAction &action, + not_null album) { + if (album->items.empty()) { + return; + } else if (album->items.size() == 1) { + if (const auto &content = album->items.front().content) { + SendPreparedMessage(action, *content); + } + return; + } + auto contents = QVector(); + contents.reserve(album->items.size()); + for (const auto &item : album->items) { + if (!item.content) { + return; + } + contents.push_back(*item.content); + } + const auto history = action.history; + const auto peer = history->peer; + const auto silentPost = ShouldSendSilent(peer, action.options); + const auto session = &peer->session(); + session->sender().request(TLsendMessageAlbum( + peerToTdbChat(peer->id), + MessageThreadId(peer, action), + MessageReplyTo(action), + tl_messageSendOptions( + tl_bool(silentPost), + tl_bool(false), // from_background + tl_bool(false), // update_order_of_installed_stickers_sets + ScheduledToTL(action.options.scheduled), + tl_int32(0), // sending_id + tl_bool(false)), // only_preview + tl_vector(std::move(contents)) + )).done([=](const TLmessages &result) { + // They should've been added by updates. + }).fail([=](const Error &error) { + const auto code = error.code; + //if (error.type() == qstr("MESSAGE_EMPTY")) { + // lastMessage->destroy(); + //} else { + // sendMessageFail(error, peer, randomId, newId); + //} + }).send(); +} + +template +void GenerateLocalMediaMessage( + const MessageToSend &message, + not_null media, + MsgId localId) { + const auto &action = message.action; + const auto history = action.history; + const auto peer = history->peer; + const auto session = &history->session(); + auto flags = NewMessageFlags(peer); + if (action.replyTo) { + flags |= MessageFlag::HasReplyInfo; + } + const auto anonymousPost = peer->amAnonymous(); + const auto silentPost = ShouldSendSilent(peer, action.options); + InnerFillMessagePostFlags(action.options, peer, flags); + const auto sendAs = action.options.sendAs; + const auto messageFromId = sendAs + ? sendAs->id + : anonymousPost + ? 0 + : session->userPeerId(); + const auto messagePostAuthor = peer->isBroadcast() + ? session->user()->name() + : QString(); + if (action.options.scheduled) { + flags |= MessageFlag::IsOrWasScheduled; + } + const auto viaBotId = UserId(); + const auto caption = TextWithEntities{ + message.textWithTags.text, + TextUtilities::ConvertTextTagsToEntities(message.textWithTags.tags) + }; + history->addNewLocalMessage( + localId, + flags, + viaBotId, + action.replyTo, + HistoryItem::NewMessageDate(action.options.scheduled), + messageFromId, + messagePostAuthor, + media, + caption, + HistoryMessageMarkupData()); +} } // namespace @@ -186,6 +489,16 @@ void SendExistingDocument( MessageToSend &&message, not_null document, std::optional localMessageId) { + if (localMessageId) { + GenerateLocalMediaMessage(message, document, *localMessageId); + } + SendPreparedMessage( + message.action, + MessageContentFromExisting( + document, + std::move(message.textWithTags)), + localMessageId); +#if 0 // mtp const auto inputMedia = [=] { return MTP_inputMediaDocument( MTP_flags(0), @@ -199,6 +512,7 @@ void SendExistingDocument( inputMedia, document->stickerOrGifOrigin(), std::move(localMessageId)); +#endif if (document->sticker()) { document->owner().stickers().incrementSticker(document); @@ -209,6 +523,14 @@ void SendExistingPhoto( MessageToSend &&message, not_null photo, std::optional localMessageId) { + if (localMessageId) { + GenerateLocalMediaMessage(message, photo, *localMessageId); + } + SendPreparedMessage( + message.action, + MessageContentFromExisting(photo, std::move(message.textWithTags)), + localMessageId); +#if 0 // mtp const auto inputMedia = [=] { return MTP_inputMediaPhoto( MTP_flags(0), @@ -221,6 +543,7 @@ void SendExistingPhoto( inputMedia, Data::FileOrigin(), std::move(localMessageId)); +#endif } bool SendDice(MessageToSend &message) { @@ -241,11 +564,15 @@ bool SendDice(MessageToSend &message) { Stickers::DicePacks::kFballString + QChar(0xFE0F), Stickers::DicePacks::kBballString, }; +#if 0 // mtp const auto list = config.get>( "emojies_send_dice", hardcoded); +#endif + const auto &pack = account.session().diceStickersPacks(); + const auto &list = pack.cloudDiceEmoticons(); const auto emoji = full.toString(); - if (!ranges::contains(list, emoji)) { + if (!ranges::contains(list.empty() ? hardcoded : list, emoji)) { return false; } const auto history = message.action.history; @@ -258,6 +585,23 @@ bool SendDice(MessageToSend &message) { message.action.generateLocal = true; api->sendAction(message.action); + const auto &action = message.action; + session->sender().request(TLsendMessage( + peerToTdbChat(peer->id), + MessageThreadId(peer, action), + MessageReplyTo(action), + MessageSendOptions(peer, action), + tl_inputMessageDice(tl_string(emoji), tl_bool(action.clearDraft)) + )).fail([=](const Error &error) { + const auto code = error.code; + //if (error.type() == qstr("MESSAGE_EMPTY")) { + // lastMessage->destroy(); + //} else { + // sendMessageFail(error, peer, randomId, newId); + //} + }).send(); + +#if 0 // mtp const auto newId = FullMsgId( peer->id, session->data().nextLocalMessageId()); @@ -328,6 +672,8 @@ bool SendDice(MessageToSend &message) { api->sendMessageFail(error, peer, randomId, newId); }); api->finishForwarding(message.action); +#endif + return true; } @@ -341,6 +687,7 @@ void FillMessagePostFlags( void SendConfirmedFile( not_null session, const std::shared_ptr &file) { +#if 0 // mtp const auto isEditing = (file->type != SendMediaType::Audio) && (file->to.replaceMediaOf != 0); const auto newId = FullMsgId( @@ -495,6 +842,190 @@ void SendConfirmedFile( ? Data::HistoryUpdate::Flag::ScheduledSent : Data::HistoryUpdate::Flag::MessageSent)); } +#endif + + const auto ready = [=](Storage::ReadyFileWithThumbnail result) { + const auto content = MessageContentFromFile(session, file, result); + + if (file->to.replaceMediaOf) { + session->sender().request(TLeditMessageMedia( + peerToTdbChat(file->to.peer), + tl_int53(file->to.replaceMediaOf.bare), + content + )).send(); + return; + } + + const auto history = session->data().history(file->to.peer); + auto action = Api::SendAction(history); + action.options = file->to.options; + action.clearDraft = false; + action.replyTo = file->to.replyTo; + action.generateLocal = true; + session->api().sendAction(action); + + if (const auto album = file->album.get()) { + const auto i = ranges::find( + album->items, + file->taskId, + &SendingAlbum::Item::taskId); + Assert(i != album->items.end()); + + i->content = std::make_unique(content); + SendPreparedAlbumIfReady(action, album); + } else { + SendPreparedMessage(action, content); + } + }; + session->uploader().start(file, ready); +} + +TLmessageSendOptions MessageSendOptions( + not_null peer, + const SendAction &action, + int32 sendingId) { + return tl_messageSendOptions( + tl_bool(ShouldSendSilent(peer, action.options)), + tl_bool(false), // from_background + tl_bool(false), // update_order_of_installed_stickers_sets + ScheduledToTL(action.options.scheduled), + tl_int32(sendingId), + tl_bool(false)); // only_preview +} + +std::optional MessageReplyTo( + not_null history, + const FullReplyTo &replyTo) { + if (const auto &storyId = replyTo.storyId) { + return tl_inputMessageReplyToStory( + peerToTdbChat(storyId.peer), + tl_int32(storyId.story)); + } else if (const auto messageId = replyTo.messageId) { + // Complex logic for external replies. + // Reply should be external if done to another thread. + const auto to = LookupReplyTo(history, messageId); + const auto replyingToTopic = replyTo.topicRootId + ? history->peer->forumTopicFor(replyTo.topicRootId) + : nullptr; + const auto replyingToTopicId = replyTo.topicRootId + ? (replyingToTopic + ? replyingToTopic->rootId() + : Data::ForumTopic::kGeneralId) + : (to ? to->topicRootId() : Data::ForumTopic::kGeneralId); + const auto replyToTopicId = to + ? to->topicRootId() + : replyingToTopicId; + const auto external = (replyTo.messageId.peer != history->peer->id) + || (replyingToTopicId != replyToTopicId); + + return tl_inputMessageReplyToMessage( + external ? peerToTdbChat(messageId.peer) : tl_int53(0), + tl_int53(messageId.msg.bare), + (replyTo.quote.empty() + ? std::optional() + : Api::FormattedTextToTdb(replyTo.quote))); + } + return std::nullopt; +} + +std::optional MessageReplyTo( + const SendAction &action) { + return MessageReplyTo(action.history, action.replyTo); +} + +TLint53 MessageThreadId( + not_null peer, + const SendAction &action) { + return tl_int53(action.replyTo.topicRootId.bare); +} + +void SendPreparedMessage( + const SendAction &action, + TLinputMessageContent content, + std::optional localMessageId) { + const auto history = action.history; + const auto peer = history->peer; + const auto topicRootId = action.replyTo.topicRootId; + const auto clearCloudDraft = (content.type() == id_inputMessageText) + && content.c_inputMessageText().vclear_draft().v; + if (clearCloudDraft) { + history->clearCloudDraft(topicRootId); + history->startSavingCloudDraft(topicRootId); + } + const auto session = &peer->session(); + const auto localId = localMessageId.value_or( + peer->owner().nextLocalMessageId()); + const auto sendingId = ClientMsgIndex(localId); + session->sender().request(TLsendMessage( + peerToTdbChat(peer->id), + MessageThreadId(peer, action), + MessageReplyTo(action), + MessageSendOptions(peer, action, sendingId), + std::move(content) + )).done([=] { + if (clearCloudDraft) { + history->finishSavingCloudDraftNow(topicRootId); + } + }).fail([=](const Error &error) { + const auto code = error.code; + //if (error.type() == qstr("MESSAGE_EMPTY")) { + // lastMessage->destroy(); + //} else { + // sendMessageFail(error, peer, randomId, newId); + //} + if (clearCloudDraft) { + history->finishSavingCloudDraftNow(topicRootId); + } + }).send(); +} + +std::optional ScheduledToTL(TimeId scheduled) { + if (!scheduled) { + return std::nullopt; + } else if (scheduled == kScheduledTillOnline) { + return tl_messageSchedulingStateSendWhenOnline(); + } else { + return tl_messageSchedulingStateSendAtDate(tl_int32(scheduled)); + } +} + +void TryGenerateLocalInlineResultMessage( + not_null bot, + not_null data, + const SendAction &action, + MsgId localId) { + const auto history = action.history; + const auto peer = history->peer; + const auto session = &history->session(); + + auto flags = NewMessageFlags(peer); + if (action.replyTo) { + flags |= MessageFlag::HasReplyInfo; + } + const auto anonymousPost = peer->amAnonymous(); + const auto silentPost = ShouldSendSilent(peer, action.options); + FillMessagePostFlags(action, peer, flags); + if (action.options.scheduled) { + flags |= MessageFlag::IsOrWasScheduled; + } + const auto sendAs = action.options.sendAs; + const auto messageFromId = sendAs + ? sendAs->id + : anonymousPost ? PeerId() + : session->userPeerId(); + const auto messagePostAuthor = peer->isBroadcast() + ? session->user()->name() + : QString(); + + data->addToHistory( + history, + flags, + localId, + messageFromId, + HistoryItem::NewMessageDate(action.options.scheduled), + (bot && !action.options.hideViaBot) ? peerToUser(bot->id) : 0, + action.replyTo, + messagePostAuthor); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_sending.h b/Telegram/SourceFiles/api/api_sending.h index e17c66f3ea00e..07022a05ab897 100644 --- a/Telegram/SourceFiles/api/api_sending.h +++ b/Telegram/SourceFiles/api/api_sending.h @@ -16,6 +16,22 @@ class PhotoData; class DocumentData; struct FileLoadResult; +namespace tl { +class int64_type; +} // namespace tl + +namespace Tdb { +class TLinputMessageContent; +class TLmessageSchedulingState; +class TLmessageSendOptions; +class TLinputMessageReplyTo; +using TLint53 = tl::int64_type; +} // namespace Tdb + +namespace InlineBots { +class Result; +} // namespace InlineBots + namespace Api { struct MessageToSend; @@ -42,4 +58,31 @@ void SendConfirmedFile( not_null session, const std::shared_ptr &file); +[[nodiscard]] Tdb::TLmessageSendOptions MessageSendOptions( + not_null peer, + const SendAction &action, + int32 sendingId = 0); +[[nodiscard]] std::optional MessageReplyTo( + not_null history, + const FullReplyTo &replyTo); +[[nodiscard]] std::optional MessageReplyTo( + const SendAction &action); +[[nodiscard]] Tdb::TLint53 MessageThreadId( + not_null peer, + const SendAction &action); + +void SendPreparedMessage( + const SendAction &action, + Tdb::TLinputMessageContent content, + std::optional localMessageId = std::nullopt); + +[[nodiscard]] std::optional ScheduledToTL( + TimeId scheduled); + +void TryGenerateLocalInlineResultMessage( + not_null bot, + not_null data, + const SendAction &action, + MsgId localId); + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_sensitive_content.cpp b/Telegram/SourceFiles/api/api_sensitive_content.cpp index e2d3e49edd256..2a4d513053d2d 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.cpp +++ b/Telegram/SourceFiles/api/api_sensitive_content.cpp @@ -8,6 +8,8 @@ For license and copyright information please follow this link: #include "api/api_sensitive_content.h" #include "apiwrap.h" +#include "base/const_string.h" +#include "tdb/tdb_option.h" #include "main/main_session.h" #include "main/main_account.h" #include "main/main_app_config.h" @@ -15,16 +17,22 @@ For license and copyright information please follow this link: namespace Api { namespace { +constexpr auto kSensitiveContentOption = + "ignore_sensitive_content_restrictions"_cs; constexpr auto kRefreshAppConfigTimeout = 3 * crl::time(1000); } // namespace SensitiveContent::SensitiveContent(not_null api) : _session(&api->session()) +#if 0 // goodToRemove , _api(&api->instance()) +#endif +, _api(&api->sender()) , _appConfigReloadTimer([=] { _session->account().appConfig().refresh(); }) { } +#if 0 // goodToRemove void SensitiveContent::reload() { if (_requestId) { return; @@ -40,6 +48,25 @@ void SensitiveContent::reload() { _requestId = 0; }).send(); } +#endif + +void SensitiveContent::reload() { + using namespace Tdb; + + const auto getOption = [&]( + const QString &name, + not_null*> variable) { + _api.request(TLgetOption( + tl_string(name) + )).done([=](const TLoptionValue &result) { + *variable = Tdb::OptionValue(result); + }).fail([](const Error &error) { + }).send(); + }; + + getOption(kSensitiveContentOption.utf8(), &_enabled); + getOption(u"can_ignore_sensitive_content_restrictions"_q, &_canChange); +} bool SensitiveContent::enabledCurrent() const { return _enabled.current(); @@ -53,6 +80,7 @@ rpl::producer SensitiveContent::canChange() const { return _canChange.value(); } +#if 0 // goodToRemove void SensitiveContent::update(bool enabled) { if (!_canChange.current()) { return; @@ -70,5 +98,21 @@ void SensitiveContent::update(bool enabled) { _appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout); } +#endif + +void SensitiveContent::update(bool enabled) { + if (!_canChange.current()) { + return; + } + using namespace Tdb; + _api.request(TLsetOption( + tl_string(kSensitiveContentOption.utf8()), + tl_optionValueBoolean(tl_bool(enabled)) + )).send(); + + _enabled = enabled; + + _appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout); +} } // namespace Api diff --git a/Telegram/SourceFiles/api/api_sensitive_content.h b/Telegram/SourceFiles/api/api_sensitive_content.h index 0a61f9da737c9..2b78b15c47724 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.h +++ b/Telegram/SourceFiles/api/api_sensitive_content.h @@ -9,6 +9,7 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" #include "base/timer.h" +#include "tdb/tdb_sender.h" class ApiWrap; @@ -31,8 +32,11 @@ class SensitiveContent final { private: const not_null _session; +#if 0 // goodToRemove MTP::Sender _api; mtpRequestId _requestId = 0; +#endif + Tdb::Sender _api; rpl::variable _enabled = false; rpl::variable _canChange = false; base::Timer _appConfigReloadTimer; diff --git a/Telegram/SourceFiles/api/api_single_message_search.cpp b/Telegram/SourceFiles/api/api_single_message_search.cpp index 0875091bc7cfd..2a1caf0d8f3d8 100644 --- a/Telegram/SourceFiles/api/api_single_message_search.cpp +++ b/Telegram/SourceFiles/api/api_single_message_search.cpp @@ -16,9 +16,15 @@ For license and copyright information please follow this link: #include "base/qthelp_url.h" #include "apiwrap.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" +#include "history/history.h" + namespace Api { namespace { +using namespace Tdb; + using Key = details::SingleMessageSearchKey; Key ExtractKey(const QString &query) { @@ -60,7 +66,10 @@ SingleMessageSearch::~SingleMessageSearch() { void SingleMessageSearch::clear() { _cache.clear(); _requestKey = Key(); + _session->sender().request(base::take(_requestId)).cancel(); +#if 0 // mtp _session->api().request(base::take(_requestId)).cancel(); +#endif } std::optional SingleMessageSearch::lookup( @@ -75,12 +84,17 @@ std::optional SingleMessageSearch::lookup( return _session->data().message(i->second); } if (!(_requestKey == key)) { + _requestQuery = query; + _session->sender().request(base::take(_requestId)).cancel(); +#if 0 // mtp _session->api().request(base::take(_requestId)).cancel(); +#endif _requestKey = key; } return performLookup(ready); } +#if 0 // mtp std::optional SingleMessageSearch::performLookupByChannel( not_null channel, Fn ready) { @@ -204,16 +218,40 @@ std::optional SingleMessageSearch::performLookupByUsername( return std::nullopt; } +#endif std::optional SingleMessageSearch::performLookup( Fn ready) { Expects(!_requestKey.empty()); + if (!ready) { + return nullptr; + } + _session->sender().request(TLgetMessageLinkInfo( + tl_string(_requestQuery) + )).done([=](const TLmessageLinkInfo &result) { + if (const auto message = result.data().vmessage()) { + const auto item = _session->data().processMessage( + *message, + NewMessageType::Existing); + _cache.emplace(_requestKey, item->fullId()); + } else { + _cache.emplace(_requestKey, FullMsgId()); + } + ready(); + }).fail([=] { + _cache.emplace(_requestKey, FullMsgId()); + ready(); + }).send(); + + return std::nullopt; +#if 0 // mtp if (!_requestKey.domainOrId[0].isDigit()) { return performLookupByUsername(_requestKey.domainOrId, ready); } const auto channelId = ChannelId(_requestKey.domainOrId.toULongLong()); return performLookupById(channelId, ready); +#endif } QString ConvertPeerSearchQuery(const QString &query) { diff --git a/Telegram/SourceFiles/api/api_single_message_search.h b/Telegram/SourceFiles/api/api_single_message_search.h index 48194d3f19e22..4c25edd71bf5b 100644 --- a/Telegram/SourceFiles/api/api_single_message_search.h +++ b/Telegram/SourceFiles/api/api_single_message_search.h @@ -54,6 +54,7 @@ class SingleMessageSearch { [[nodiscard]] std::optional performLookup( Fn ready); +#if 0 // mtp [[nodiscard]] std::optional performLookupById( ChannelId channelId, Fn ready); @@ -63,6 +64,8 @@ class SingleMessageSearch { [[nodiscard]] std::optional performLookupByChannel( not_null channel, Fn ready); +#endif + QString _requestQuery; const not_null _session; std::map _cache; diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index 1f5c093a6e62c..ad8dc30775389 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -12,14 +12,18 @@ For license and copyright information please follow this link: #include "data/data_peer.h" #include "data/data_session.h" #include "history/history.h" +#include "history/history_item.h" #include "main/main_session.h" #include "statistics/statistics_data_deserialize.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { constexpr auto kCheckRequestsTimer = 10 * crl::time(1000); +#if 0 // mtp [[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL( const MTPStatsGraph &tl) { return tl.match([&](const MTPDstatsGraph &d) { @@ -186,6 +190,163 @@ constexpr auto kCheckRequestsTimer = 10 * crl::time(1000); .topInviters = std::move(topInviters), }; } +#endif + +[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL( + const Tdb::TLstatisticalGraph &tl) { + return tl.match([&](const Tdb::TLDstatisticalGraphData &data) { + using namespace Statistic; + return Data::StatisticalGraph{ + StatisticalChartFromJSON(data.vjson_data().v.toUtf8()), + data.vzoom_token().v.toUtf8(), + }; + }, [&](const Tdb::TLDstatisticalGraphAsync &data) { + return Data::StatisticalGraph{ + .zoomToken = data.vtoken().v.toUtf8(), + }; + }, [&](const Tdb::TLDstatisticalGraphError &data) { + return Data::StatisticalGraph{ + .error = data.verror_message().v.toUtf8(), + }; + }); +} + +[[nodiscard]] Data::StatisticalValue StatisticalValueFromTL( + const Tdb::TLstatisticalValue &tl) { + return Data::StatisticalValue{ + .value = tl.data().vvalue().v, + .previousValue = tl.data().vprevious_value().v, + .growthRatePercentage = tl.data().vgrowth_rate_percentage().v, + }; +} + +[[nodiscard]] Data::ChannelStatistics ChannelStatisticsFromTL( + const Tdb::TLDchatStatisticsChannel &data) { + const auto unmuted = data.venabled_notifications_percentage().v; + using Recent = Tdb::TLchatStatisticsMessageInteractionInfo; + auto recentMessages = ranges::views::all( + data.vrecent_message_interactions().v + ) | ranges::views::transform([&](const Recent &tl) { + return Data::StatisticsMessageInteractionInfo{ + .messageId = tl.data().vmessage_id().v, + .viewsCount = tl.data().vview_count().v, + .forwardsCount = tl.data().vforward_count().v, + }; + }) | ranges::to_vector; + + return { + .startDate = data.vperiod().data().vstart_date().v, + .endDate = data.vperiod().data().vend_date().v, + + .memberCount = StatisticalValueFromTL(data.vmember_count()), + .meanViewCount = StatisticalValueFromTL(data.vmean_view_count()), + .meanShareCount = StatisticalValueFromTL(data.vmean_share_count()), + + .enabledNotificationsPercentage = unmuted, + + .memberCountGraph = StatisticalGraphFromTL( + data.vmember_count_graph()), + + .joinGraph = StatisticalGraphFromTL( + data.vjoin_graph()), + + .muteGraph = StatisticalGraphFromTL( + data.vmute_graph()), + + .viewCountByHourGraph = StatisticalGraphFromTL( + data.vview_count_by_hour_graph()), + + .viewCountBySourceGraph = StatisticalGraphFromTL( + data.vview_count_by_source_graph()), + + .joinBySourceGraph = StatisticalGraphFromTL( + data.vjoin_by_source_graph()), + + .languageGraph = StatisticalGraphFromTL( + data.vlanguage_graph()), + + .messageInteractionGraph = StatisticalGraphFromTL( + data.vmessage_interaction_graph()), + + .instantViewInteractionGraph = StatisticalGraphFromTL( + data.vinstant_view_interaction_graph()), + + .recentMessageInteractions = std::move(recentMessages), + }; +} + +[[nodiscard]] Data::SupergroupStatistics SupergroupStatisticsFromTL( + const Tdb::TLDchatStatisticsSupergroup &data) { + using Senders = Tdb::TLchatStatisticsMessageSenderInfo; + using Administrators = Tdb::TLchatStatisticsAdministratorActionsInfo; + using Inviters = Tdb::TLchatStatisticsInviterInfo; + + auto topSenders = ranges::views::all( + data.vtop_senders().v + ) | ranges::views::transform([&](const Senders &tl) { + return Data::StatisticsMessageSenderInfo{ + .userId = UserId(tl.data().vuser_id().v), + .sentMessageCount = tl.data().vsent_message_count().v, + .averageCharacterCount = tl.data().vaverage_character_count().v, + }; + }) | ranges::to_vector; + auto topAdministrators = ranges::views::all( + data.vtop_administrators().v + ) | ranges::views::transform([&](const Administrators &tl) { + return Data::StatisticsAdministratorActionsInfo{ + .userId = UserId(tl.data().vuser_id().v), + .deletedMessageCount = tl.data().vdeleted_message_count().v, + .bannedUserCount = tl.data().vbanned_user_count().v, + .restrictedUserCount = tl.data().vrestricted_user_count().v, + }; + }) | ranges::to_vector; + auto topInviters = ranges::views::all( + data.vtop_inviters().v + ) | ranges::views::transform([&](const Inviters &tl) { + return Data::StatisticsInviterInfo{ + .userId = UserId(tl.data().vuser_id().v), + .addedMemberCount = tl.data().vadded_member_count().v, + }; + }) | ranges::to_vector; + + return { + .startDate = data.vperiod().data().vstart_date().v, + .endDate = data.vperiod().data().vend_date().v, + + .memberCount = StatisticalValueFromTL(data.vmember_count()), + .messageCount = StatisticalValueFromTL(data.vmessage_count()), + .viewerCount = StatisticalValueFromTL(data.vviewer_count()), + .senderCount = StatisticalValueFromTL(data.vsender_count()), + + .memberCountGraph = StatisticalGraphFromTL( + data.vmember_count_graph()), + + .joinGraph = StatisticalGraphFromTL( + data.vjoin_graph()), + + .joinBySourceGraph = StatisticalGraphFromTL( + data.vjoin_by_source_graph()), + + .languageGraph = StatisticalGraphFromTL( + data.vlanguage_graph()), + + .messageContentGraph = StatisticalGraphFromTL( + data.vmessage_content_graph()), + + .actionGraph = StatisticalGraphFromTL( + data.vaction_graph()), + + .dayGraph = StatisticalGraphFromTL( + data.vday_graph()), + + .weekGraph = StatisticalGraphFromTL( + data.vweek_graph()), + + .topSenders = std::move(topSenders), + .topAdministrators = std::move(topAdministrators), + .topInviters = std::move(topInviters), + }; +} } // namespace @@ -195,10 +356,14 @@ Statistics::Statistics(not_null channel) StatisticsRequestSender::StatisticsRequestSender(not_null channel) : _channel(channel) +, _api(&_channel->session().sender()) { +#if 0 // mtp , _api(&_channel->session().api().instance()) , _timer([=] { checkRequests(); }) { +#endif } +#if 0 // mtp StatisticsRequestSender::~StatisticsRequestSender() { for (const auto &[dcId, ids] : _requests) { for (const auto id : ids) { @@ -247,11 +412,13 @@ auto StatisticsRequestSender::makeRequest(Request &&request) { dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0 ).overrideId(id)); } +#endif rpl::producer Statistics::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); +#if 0 // mtp if (!channel()->isMegagroup()) { makeRequest(MTPstats_GetBroadcastStats( MTP_flags(MTPstats_GetBroadcastStats::Flags(0)), @@ -275,6 +442,20 @@ rpl::producer Statistics::request() { consumer.put_error_copy(error.type()); }).send(); } +#endif + api().request(Tdb::TLgetChatStatistics( + peerToTdbChat(channel()->id), + Tdb::tl_bool(false) + )).done([=](const Tdb::TLchatStatistics &result) { + result.match([&](const Tdb::TLDchatStatisticsChannel &data) { + _channelStats = ChannelStatisticsFromTL(data); + }, [&](const Tdb::TLDchatStatisticsSupergroup &data) { + _supergroupStats = SupergroupStatisticsFromTL(data); + }); + consumer.put_done(); + }).fail([=](const Tdb::Error &error) { + consumer.put_error_copy(error.message); + }).send(); return lifetime; }; @@ -287,6 +468,7 @@ Statistics::GraphResult Statistics::requestZoom( auto lifetime = rpl::lifetime(); const auto wasEmpty = _zoomDeque.empty(); _zoomDeque.push_back([=] { +#if 0 // mtp makeRequest(MTPstats_LoadAsyncGraph( MTP_flags(x ? MTPstats_LoadAsyncGraph::Flag::f_x @@ -305,6 +487,23 @@ Statistics::GraphResult Statistics::requestZoom( }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + api().request(Tdb::TLgetStatisticalGraph( + peerToTdbChat(channel()->id), + Tdb::tl_string(token), + Tdb::tl_int53(x) + )).done([=](const Tdb::TLstatisticalGraph &result) { + consumer.put_next(StatisticalGraphFromTL(result)); + consumer.put_done(); + if (!_zoomDeque.empty()) { + _zoomDeque.pop_front(); + if (!_zoomDeque.empty()) { + _zoomDeque.front()(); + } + } + }).fail([=](const Tdb::Error &error) { + consumer.put_error_copy(error.message); + }).send(); }); if (wasEmpty) { _zoomDeque.front()(); @@ -335,6 +534,45 @@ void PublicForwards::request( if (_requestId) { return; } + constexpr auto kLimit = tl::make_int(100); + _requestId = api().request(Tdb::TLgetMessagePublicForwards( + peerToTdbChat(channel()->id), + Tdb::tl_int53(_fullId.msg.bare), + Tdb::tl_string(token.offset), + kLimit + )).done([=, channel = channel()](const Tdb::TLfoundMessages &result) { + using Messages = QVector; + _requestId = 0; + + auto nextToken = Data::PublicForwardsSlice::OffsetToken{ + result.data().vnext_offset().v.toUtf8() + }; + auto messages = Messages(); + messages.reserve(result.data().vmessages().v.size()); + for (const auto &message : result.data().vmessages().v) { + messages.push_back(channel->owner().processMessage( + message, + NewMessageType::Existing)->fullId()); + } + + auto allLoaded = false; + auto fullCount = result.data().vtotal_count().v; + + if (nextToken.offset == token.offset || nextToken.offset.isEmpty()) { + allLoaded = true; + } + + _lastTotal = std::max(_lastTotal, fullCount); + done({ + .list = std::move(messages), + .total = _lastTotal, + .allLoaded = allLoaded, + .token = std::move(nextToken), + }); + }).fail([=] { + _requestId = 0; + }).send(); +#if 0 // mtp const auto offsetPeer = channel()->owner().peer(token.fullId.peer); const auto tlOffsetPeer = offsetPeer ? offsetPeer->input @@ -418,6 +656,7 @@ void PublicForwards::request( }).fail([=] { _requestId = 0; }).send(); +#endif } MessageStatistics::MessageStatistics( @@ -453,6 +692,7 @@ void MessageStatistics::request(Fn done) { const auto requestPrivateForwards = [=]( const Data::StatisticalGraph &messageGraph) { +#if 0 // mtp api().request(MTPchannels_GetMessages( channel()->inputChannel, MTP_vector( @@ -492,8 +732,39 @@ void MessageStatistics::request(Fn done) { }).fail([=](const MTP::Error &error) { requestFirstPublicForwards(messageGraph, {}); }).send(); +#endif + api().request(Tdb::TLgetMessage( + peerToTdbChat(channel()->id), + Tdb::tl_int53(_fullId.msg.bare) + )).done([=](const Tdb::TLDmessage &data) { + auto info = Data::StatisticsMessageInteractionInfo{ + .messageId = data.vid().v, + .viewsCount = data.vinteraction_info() + ? data.vinteraction_info()->data().vview_count().v + : 0, + .forwardsCount = data.vinteraction_info() + ? data.vinteraction_info()->data().vforward_count().v + : 0, + }; + + requestFirstPublicForwards(messageGraph, std::move(info)); + }).fail([=](const Tdb::Error &error) { + requestFirstPublicForwards(messageGraph, {}); + }).send(); }; + api().request(Tdb::TLgetMessageStatistics( + peerToTdbChat(channel()->id), + Tdb::tl_int53(_fullId.msg.bare), + Tdb::tl_bool(false) + )).done([=](const Tdb::TLDmessageStatistics &data) { + requestPrivateForwards( + StatisticalGraphFromTL(data.vmessage_interaction_graph())); + }).fail([=](const Tdb::Error &error) { + requestPrivateForwards({}); + }).send(); + +#if 0 // mtp makeRequest(MTPstats_GetMessageStats( MTP_flags(MTPstats_GetMessageStats::Flags(0)), channel()->inputChannel, @@ -504,11 +775,15 @@ void MessageStatistics::request(Fn done) { }).fail([=](const MTP::Error &error) { requestPrivateForwards({}); }).send(); +#endif } Boosts::Boosts(not_null peer) : _peer(peer) +, _api(&peer->session().sender()) { +#if 0 // mtp , _api(&peer->session().api().instance()) { +#endif } rpl::producer Boosts::request() { @@ -519,6 +794,7 @@ rpl::producer Boosts::request() { return lifetime; } +#if 0 // mtp _api.request(MTPpremium_GetBoostsStatus( _peer->input )).done([=](const MTPpremium_BoostsStatus &result) { @@ -577,6 +853,48 @@ rpl::producer Boosts::request() { }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + _api.request(Tdb::TLgetChatBoostStatus( + peerToTdbChat(_peer->id) + )).done([=](const Tdb::TLDchatBoostStatus &data) { + _boostStatus.overview = Data::BoostsOverview{ + .mine = bool(data.vapplied_slot_ids().v.size()), + .level = std::max(data.vlevel().v, 0), + .boostCount = std::max( + data.vboost_count().v, + data.vcurrent_level_boost_count().v), + .currentLevelBoostCount = data.vcurrent_level_boost_count().v, + .nextLevelBoostCount = data.vnext_level_boost_count().v, + .premiumMemberCount = data.vpremium_member_count().v, + .premiumMemberPercentage = + data.vpremium_member_percentage().v, + }; + _boostStatus.link = data.vboost_url().v.toUtf8(); + + _boostStatus.prepaidGiveaway = ranges::views::all( + data.vprepaid_giveaways().v + ) | ranges::views::transform([]( + const Tdb::TLprepaidPremiumGiveaway &r) { + return Data::BoostPrepaidGiveaway{ + .months = r.data().vmonth_count().v, + .id = uint64(r.data().vid().v), + .quantity = r.data().vwinner_count().v, + .date = QDateTime::fromSecsSinceEpoch( + r.data().vpayment_date().v), + }; + }) | ranges::to_vector; + + using namespace Data; + requestBoosts({ .gifts = false }, [=](BoostsListSlice &&slice) { + _boostStatus.firstSliceBoosts = std::move(slice); + requestBoosts({ .gifts = true }, [=](BoostsListSlice &&s) { + _boostStatus.firstSliceGifts = std::move(s); + consumer.put_done(); + }); + }); + }).fail([=](const Tdb::Error &error) { + consumer.put_error_copy(error.message); + }).send(); return lifetime; }; @@ -591,6 +909,77 @@ void Boosts::requestBoosts( constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice); constexpr auto kTlLimit = tl::make_int(kLimit); const auto gifts = token.gifts; + _requestId = _api.request(Tdb::TLgetChatBoosts( + peerToTdbChat(_peer->id), + Tdb::tl_bool(gifts), + Tdb::tl_string(token.next), + token.next.isEmpty() ? kTlFirstSlice : kTlLimit + )).done([=](const Tdb::TLDfoundChatBoosts &data) { + _requestId = 0; + + auto list = std::vector(); + list.reserve(data.vboosts().v.size()); + constexpr auto kMonthsDivider = int(30 * 86400); + for (const auto &boost : data.vboosts().v) { + const auto &data = boost.data(); + + const auto userId = data.vsource().match([&](const auto &d) { + return UserId(d.vuser_id().v); + }); + const auto giftCodeLink = data.vsource().match([]( + const Tdb::TLDchatBoostSourcePremium &) { + return Data::GiftCodeLink(); + }, [&](const auto &d) { + const auto tlGiftCode = d.vgift_code().v.toUtf8(); + const auto path = !tlGiftCode.isEmpty() + ? (u"giftcode/"_q + tlGiftCode) + : QString(); + return Data::GiftCodeLink{ + _peer->session().createInternalLink(path), + _peer->session().createInternalLinkFull(path), + tlGiftCode, + }; + }); + const auto isUnclaimed = data.vsource().match([]( + const Tdb::TLDchatBoostSourceGiveaway &d) { + return d.vis_unclaimed().v; + }, [](const auto &) { + return false; + }); + const auto giveawayMessage = data.vsource().match([&]( + const Tdb::TLDchatBoostSourceGiveaway &d) { + return FullMsgId{ _peer->id, d.vgiveaway_message_id().v }; + }, [](const auto &) { + return FullMsgId(); + }); + list.push_back({ + !giftCodeLink.slug.isEmpty(), + (!!giveawayMessage), + isUnclaimed, + data.vid().v.toUtf8(), + userId, + giveawayMessage, + QDateTime::fromSecsSinceEpoch(data.vstart_date().v), + QDateTime::fromSecsSinceEpoch(data.vexpiration_date().v), + (data.vexpiration_date().v - data.vstart_date().v) + / kMonthsDivider, + std::move(giftCodeLink), + (data.vcount().v == 1 ? 0 : data.vcount().v), + }); + } + done(Data::BoostsListSlice{ + .list = std::move(list), + .multipliedTotal = data.vtotal_count().v, + .allLoaded = (data.vtotal_count().v == data.vboosts().v.size()), + .token = Data::BoostsListSlice::OffsetToken{ + .next = data.vnext_offset().v.toUtf8(), + .gifts = gifts, + }, + }); + }).fail([=] { + _requestId = 0; + }).send(); +#if 0 // mtp _requestId = _api.request(MTPpremium_GetBoostsList( gifts ? MTP_flags(MTPpremium_GetBoostsList::Flag::f_gifts) @@ -649,6 +1038,7 @@ void Boosts::requestBoosts( }).fail([=] { _requestId = 0; }).send(); +#endif } Data::BoostStatus Boosts::boostStatus() const { diff --git a/Telegram/SourceFiles/api/api_statistics.h b/Telegram/SourceFiles/api/api_statistics.h index dddf3654c238b..cd3fcf224748c 100644 --- a/Telegram/SourceFiles/api/api_statistics.h +++ b/Telegram/SourceFiles/api/api_statistics.h @@ -12,6 +12,8 @@ For license and copyright information please follow this link: #include "data/data_statistics.h" #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + class ChannelData; class PeerData; @@ -20,6 +22,7 @@ namespace Api { class StatisticsRequestSender { protected: explicit StatisticsRequestSender(not_null channel); +#if 0 // mtp ~StatisticsRequestSender(); template < @@ -31,6 +34,10 @@ class StatisticsRequestSender { [[nodiscard]] MTP::Sender &api() { return _api; } +#endif + [[nodiscard]] Tdb::Sender &api() { + return _api; + } [[nodiscard]] not_null channel() { return _channel; } @@ -39,9 +46,12 @@ class StatisticsRequestSender { void checkRequests(); const not_null _channel; + Tdb::Sender _api; +#if 0 // mtp MTP::Sender _api; base::Timer _timer; base::flat_map> _requests; +#endif }; @@ -119,7 +129,10 @@ class Boosts final { const not_null _peer; Data::BoostStatus _boostStatus; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; mtpRequestId _requestId = 0; }; diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index 6c434bc9bab33..fbb58f9f2803e 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -14,11 +14,15 @@ For license and copyright information please follow this link: #include "data/data_document.h" #include "data/data_user.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { using namespace TextUtilities; +using namespace Tdb; +#if 0 // mtp [[nodiscard]] QString CustomEmojiEntityData( const MTPDmessageEntityCustomEmoji &data) { return Data::SerializeCustomEmojiId(data.vdocument_id().v); @@ -56,9 +60,11 @@ using namespace TextUtilities; MTP_long(parsed.userId), MTP_long(parsed.accessHash)))); } +#endif } // namespace +#if 0 // mtp EntitiesInText EntitiesFromMTP( Main::Session *session, const QVector &entities) { @@ -183,5 +189,161 @@ MTPVector EntitiesToMTP( } return MTP_vector(std::move(v)); } +#endif + +EntitiesInText EntitiesFromTdb(const QVector &entities) { + auto result = EntitiesInText(); + if (!entities.isEmpty()) { + result.reserve(entities.size()); + for (const auto &entity : entities) { + entity.match([&](const TLDtextEntity &data) { + const auto offset = data.voffset().v; + const auto length = data.vlength().v; + auto additional = QString(); + const auto type = data.vtype().match([&]( + const TLDtextEntityTypeMention &data) { + return EntityType::Mention; + }, [&](const TLDtextEntityTypeHashtag &data) { + return EntityType::Hashtag; + }, [&](const TLDtextEntityTypeCashtag &data) { + return EntityType::Cashtag; + }, [&](const TLDtextEntityTypeBotCommand &data) { + return EntityType::BotCommand; + }, [&](const TLDtextEntityTypeUrl &data) { + return EntityType::Url; + }, [&](const TLDtextEntityTypeEmailAddress &data) { + return EntityType::Email; + }, [&](const TLDtextEntityTypePhoneNumber &data) { + return EntityType::Invalid; + }, [&](const TLDtextEntityTypeBankCardNumber &data) { + return EntityType::Invalid; + }, [&](const TLDtextEntityTypeBold &data) { + return EntityType::Bold; + }, [&](const TLDtextEntityTypeItalic &data) { + return EntityType::Italic; + }, [&](const TLDtextEntityTypeUnderline &data) { + return EntityType::Underline; + }, [&](const TLDtextEntityTypeStrikethrough &data) { + return EntityType::StrikeOut; + }, [&](const TLDtextEntityTypeCode &data) { + return EntityType::Code; + }, [&](const TLDtextEntityTypePre &data) { + return EntityType::Pre; + }, [&](const TLDtextEntityTypePreCode &data) { + additional = data.vlanguage().v; + return EntityType::Pre; + }, [&](const TLDtextEntityTypeTextUrl &data) { + additional = data.vurl().v; + return EntityType::CustomUrl; + }, [&](const TLDtextEntityTypeMentionName &data) { + additional = MentionNameDataFromFields({ + .userId = uint64(data.vuser_id().v), + }); + return EntityType::MentionName; + }, [&](const TLDtextEntityTypeSpoiler &data) { + return EntityType::Spoiler; + }, [&](const TLDtextEntityTypeMediaTimestamp &data) { + // later entities media timestamp links + return EntityType::Invalid; + }, [&](const TLDtextEntityTypeCustomEmoji &data) { + additional = QString::number( + uint64(data.vcustom_emoji_id().v)); + return EntityType::CustomEmoji; + }, [&](const TLDtextEntityTypeBlockQuote &data) { + return EntityType::Blockquote; + }); + if (type != EntityType::Invalid) { + result.push_back({ type, offset, length, additional }); + } + }); + } + } + return result; +} + +TextWithEntities FormattedTextFromTdb( + const TLformattedText &text) { + const auto &formatted = text.data(); + return TextWithEntities{ + formatted.vtext().v, + Api::EntitiesFromTdb(formatted.ventities().v) + }; +} + +QVector EntitiesToTdb(const EntitiesInText &entities) { + constexpr auto option = ConvertOption::SkipLocal; + + auto v = QVector(); + v.reserve(entities.size()); + for (const auto &entity : entities) { + if (entity.length() <= 0) { + continue; + } else if (option == ConvertOption::SkipLocal + && entity.type() != EntityType::Bold + //&& entity.type() != EntityType::Semibold // Not in API. + && entity.type() != EntityType::Italic + && entity.type() != EntityType::Underline + && entity.type() != EntityType::StrikeOut + && entity.type() != EntityType::Code // #TODO entities + && entity.type() != EntityType::Pre + && entity.type() != EntityType::Blockquote + && entity.type() != EntityType::Spoiler + && entity.type() != EntityType::MentionName + && entity.type() != EntityType::CustomUrl + && entity.type() != EntityType::CustomEmoji) { + continue; + } + + const auto type = [&]() -> std::optional { + switch (entity.type()) { + case EntityType::Url: return tl_textEntityTypeUrl(); + case EntityType::CustomUrl: + return tl_textEntityTypeTextUrl(tl_string(entity.data())); + case EntityType::Email: return tl_textEntityTypeEmailAddress(); + case EntityType::Hashtag: return tl_textEntityTypeHashtag(); + case EntityType::Cashtag: return tl_textEntityTypeCashtag(); + case EntityType::Mention: return tl_textEntityTypeMention(); + case EntityType::MentionName: { + const auto fields = MentionNameDataToFields(entity.data()); + return fields.userId + ? tl_textEntityTypeMentionName(tl_int53(fields.userId)) + : std::optional(); + } + case EntityType::BotCommand: + return tl_textEntityTypeBotCommand(); + case EntityType::Bold: return tl_textEntityTypeBold(); + case EntityType::Italic: return tl_textEntityTypeItalic(); + case EntityType::Underline: return tl_textEntityTypeUnderline(); + case EntityType::StrikeOut: + return tl_textEntityTypeStrikethrough(); + case EntityType::Code: return tl_textEntityTypeCode(); // #TODO entities + case EntityType::Pre: + return entity.data().isEmpty() + ? tl_textEntityTypePre() + : tl_textEntityTypePreCode(tl_string(entity.data())); + case EntityType::Spoiler: return tl_textEntityTypeSpoiler(); + case EntityType::CustomEmoji: + return tl_textEntityTypeCustomEmoji( + tl_int64(entity.data().toULongLong())); + case EntityType::Blockquote: + return tl_textEntityTypeBlockQuote(); + } + return std::nullopt; + }(); + if (type) { + v.push_back(tl_textEntity( + tl_int32(entity.offset()), + tl_int32(entity.length()), + *type)); + } + } + return v; +} + +TLformattedText FormattedTextToTdb(const TextWithEntities &text) { + return tl_formattedText( + tl_string(text.text), + tl_vector(EntitiesToTdb(text.entities))); +} } // namespace Api diff --git a/Telegram/SourceFiles/api/api_text_entities.h b/Telegram/SourceFiles/api/api_text_entities.h index 95348d6164992..2ffbab1c5ace5 100644 --- a/Telegram/SourceFiles/api/api_text_entities.h +++ b/Telegram/SourceFiles/api/api_text_entities.h @@ -9,6 +9,11 @@ For license and copyright information please follow this link: #include "ui/text/text_entity.h" +namespace Tdb { +class TLtextEntity; +class TLformattedText; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -29,4 +34,16 @@ enum class ConvertOption { const EntitiesInText &entities, ConvertOption option = ConvertOption::WithLocal); +[[nodiscard]] EntitiesInText EntitiesFromTdb( + const QVector &entities); + +[[nodiscard]] TextWithEntities FormattedTextFromTdb( + const Tdb::TLformattedText &text); + +[[nodiscard]] QVector EntitiesToTdb( + const EntitiesInText &entities); + +[[nodiscard]] Tdb::TLformattedText FormattedTextToTdb( + const TextWithEntities &text); + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_toggling_media.cpp b/Telegram/SourceFiles/api/api_toggling_media.cpp index 65d2ce317ff92..1dbadf6456bad 100644 --- a/Telegram/SourceFiles/api/api_toggling_media.cpp +++ b/Telegram/SourceFiles/api/api_toggling_media.cpp @@ -15,9 +15,19 @@ For license and copyright information please follow this link: #include "window/window_session_controller.h" #include "main/main_session.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + +} // namespace + +#if 0 // goodToRemove +namespace { + template void ToggleExistingMedia( not_null document, @@ -46,6 +56,7 @@ void ToggleExistingMedia( } } // namespace +#endif void ToggleFavedSticker( std::shared_ptr show, @@ -69,6 +80,17 @@ void ToggleFavedSticker( auto done = [=] { document->owner().stickers().setFaved(show, document, faved); }; + auto &api = document->owner().session().sender(); + if (faved) { + api.request(TLaddFavoriteSticker( + tl_inputFileId(tl_int32(document->id)) + )).done(std::move(done)).send(); + } else { + api.request(TLremoveFavoriteSticker( + tl_inputFileId(tl_int32(document->id)) + )).done(std::move(done)).send(); + } +#if 0 // goodToRemove ToggleExistingMedia( document, std::move(origin), @@ -76,6 +98,7 @@ void ToggleFavedSticker( return MTPmessages_FaveSticker(d->mtpInput(), MTP_bool(!faved)); }, std::move(done)); +#endif } void ToggleRecentSticker( @@ -90,6 +113,19 @@ void ToggleRecentSticker( document->owner().stickers().removeFromRecentSet(document); } }; + auto &api = document->owner().session().sender(); + if (saved) { + api.request(TLaddRecentSticker( + tl_bool(false), + tl_inputFileId(tl_int32(document->id)) + )).done(std::move(done)).send(); + } else { + api.request(TLremoveRecentSticker( + tl_bool(false), + tl_inputFileId(tl_int32(document->id)) + )).done(std::move(done)).send(); + } +#if 0 // goodToRemove ToggleExistingMedia( document, std::move(origin), @@ -100,6 +136,7 @@ void ToggleRecentSticker( MTP_bool(!saved)); }, std::move(done)); +#endif } void ToggleSavedGif( @@ -115,6 +152,17 @@ void ToggleSavedGif( document->owner().stickers().addSavedGif(show, document); } }; + auto &api = document->owner().session().sender(); + if (saved) { + api.request(TLaddSavedAnimation( + tl_inputFileId(tl_int32(document->id)) + )).done(std::move(done)).send(); + } else { + api.request(TLremoveSavedAnimation( + tl_inputFileId(tl_int32(document->id)) + )).done(std::move(done)).send(); + } +#if 0 // goodToRemove ToggleExistingMedia( document, std::move(origin), @@ -122,6 +170,7 @@ void ToggleSavedGif( return MTPmessages_SaveGif(d->mtpInput(), MTP_bool(!saved)); }, std::move(done)); +#endif } void ToggleSavedRingtone( @@ -129,6 +178,20 @@ void ToggleSavedRingtone( Data::FileOrigin origin, Fn &&done, bool saved) { + auto &api = document->owner().session().sender(); + if (saved) { + api.request(TLaddSavedNotificationSound( + tl_inputFileId(tl_int32(document->id))) + ).done([=](const TLnotificationSound &result) { + document->owner().processDocument(result); + done(); + }).send(); + } else { + api.request(TLremoveSavedNotificationSound( + tl_int64(document->id) + )).done(std::move(done)).send(); + } +#if 0 // goodToRemove ToggleExistingMedia( document, std::move(origin), @@ -136,6 +199,7 @@ void ToggleSavedRingtone( return MTPaccount_SaveRingtone(d->mtpInput(), MTP_bool(!saved)); }, std::move(done)); +#endif } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_transcribes.cpp b/Telegram/SourceFiles/api/api_transcribes.cpp index 6a69b53d6bf13..1daf493e10435 100644 --- a/Telegram/SourceFiles/api/api_transcribes.cpp +++ b/Telegram/SourceFiles/api/api_transcribes.cpp @@ -15,11 +15,21 @@ For license and copyright information please follow this link: #include "data/data_peer.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { +namespace { + +using namespace Tdb; + +} // namespace Transcribes::Transcribes(not_null api) : _session(&api->session()) +#if 0 // mtp , _api(&api->instance()) { +#endif +, _api(&api->sender()) { } void Transcribes::toggle(not_null item) { @@ -31,7 +41,10 @@ void Transcribes::toggle(not_null item) { _session->data().requestItemResize(item); } else if (!i->second.requestId) { i->second.shown = !i->second.shown; +#if 0 // mtp if (i->second.roundview) { +#endif + if (i->second.roundview && !i->second.pending) { _session->data().requestItemViewRefresh(item); } _session->data().requestItemResize(item); @@ -45,6 +58,7 @@ const Transcribes::Entry &Transcribes::entry( return (i != _map.end()) ? i->second : empty; } +#if 0 // mtp void Transcribes::apply(const MTPDupdateTranscribedAudio &update) { const auto id = update.vtranscription_id().v; const auto i = _ids.find(id); @@ -65,6 +79,34 @@ void Transcribes::apply(const MTPDupdateTranscribedAudio &update) { _session->data().requestItemResize(item); } } +#endif + +void Transcribes::apply( + not_null item, + const TLspeechRecognitionResult &result, + bool roundview) { + auto &entry = _map[item->fullId()]; + entry.roundview = roundview; + result.match([&](const TLDspeechRecognitionResultText &result) { + entry.requestId = 0; + entry.result = result.vtext().v; + entry.pending = false; + }, [&](const TLDspeechRecognitionResultPending &result) { + entry.result = result.vpartial_text().v; + entry.pending = true; + }, [&](const TLDspeechRecognitionResultError &result) { + entry.requestId = 0; + entry.pending = false; + entry.failed = true; + if (result.verror().data().vmessage().v == u"MSG_VOICE_TOO_LONG"_q) { + entry.toolong = true; + } + }); + if (entry.roundview && !entry.pending) { + _session->data().requestItemViewRefresh(item); + } + _session->data().requestItemResize(item); +} void Transcribes::load(not_null item) { if (!item->isHistoryEntry() || item->isLocal()) { @@ -81,6 +123,22 @@ void Transcribes::load(not_null item) { } }; const auto id = item->fullId(); + const auto requestId = _api.request(TLrecognizeSpeech( + peerToTdbChat(item->history()->peer->id), + tl_int53(item->id.bare) + )).done([=] { + auto &entry = _map[id]; + if (entry.requestId) { + entry.requestId = 0; + if (const auto item = _session->data().message(id)) { + if (!entry.pending) { + toggleRound(item, entry); + } + _session->data().requestItemResize(item); + } + } + }).fail([=](const Error &error) { +#if 0 // mtp const auto requestId = _api.request(MTPmessages_TranscribeAudio( item->history()->peer->input, MTP_int(item->id) @@ -96,11 +154,15 @@ void Transcribes::load(not_null item) { _session->data().requestItemResize(item); } }).fail([=](const MTP::Error &error) { +#endif auto &entry = _map[id]; entry.requestId = 0; entry.pending = false; entry.failed = true; +#if 0 // mtp if (error.type() == u"MSG_VOICE_TOO_LONG"_q) { +#endif + if (error.message == u"MSG_VOICE_TOO_LONG"_q) { entry.toolong = true; } if (const auto item = _session->data().message(id)) { @@ -112,7 +174,10 @@ void Transcribes::load(not_null item) { entry.requestId = requestId; entry.shown = true; entry.failed = false; +#if 0 // mtp entry.pending = false; +#endif + entry.pending = true; } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_transcribes.h b/Telegram/SourceFiles/api/api_transcribes.h index 2f8bb22349058..83205f375072c 100644 --- a/Telegram/SourceFiles/api/api_transcribes.h +++ b/Telegram/SourceFiles/api/api_transcribes.h @@ -7,7 +7,14 @@ For license and copyright information please follow this link: */ #pragma once +#include "tdb/tdb_sender.h" +#if 0 // mtp #include "mtproto/sender.h" +#endif + +namespace Tdb { +class TLspeechRecognitionResult; +} // namespace Tdb class ApiWrap; @@ -34,13 +41,22 @@ class Transcribes final { void toggle(not_null item); [[nodiscard]] const Entry &entry(not_null item) const; + void apply( + not_null item, + const Tdb::TLspeechRecognitionResult &result, + bool roundview); +#if 0 // mtp void apply(const MTPDupdateTranscribedAudio &update); +#endif private: void load(not_null item); const not_null _session; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; base::flat_map _map; base::flat_map _ids; diff --git a/Telegram/SourceFiles/api/api_unread_things.cpp b/Telegram/SourceFiles/api/api_unread_things.cpp index dac998dfdce83..a4e0520bbc3cf 100644 --- a/Telegram/SourceFiles/api/api_unread_things.cpp +++ b/Telegram/SourceFiles/api/api_unread_things.cpp @@ -17,9 +17,13 @@ For license and copyright information please follow this link: #include "history/history_unread_things.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + constexpr auto kPreloadIfLess = 5; constexpr auto kFirstRequestLimit = 10; constexpr auto kNextRequestLimit = 100; @@ -48,6 +52,7 @@ void UnreadThings::preloadEnough(Data::Thread *thread) { } } +#if 0 // mtp void UnreadThings::mediaAndMentionsRead( const base::flat_set &readIds, ChannelData *channel) { @@ -62,6 +67,7 @@ void UnreadThings::mediaAndMentionsRead( }); } } +#endif void UnreadThings::preloadEnoughMentions(not_null thread) { const auto fullCount = thread->unreadMentions().count(); @@ -82,12 +88,20 @@ void UnreadThings::preloadEnoughReactions(not_null thread) { } void UnreadThings::cancelRequests(not_null thread) { + if (const auto requestId = _mentionsRequests.take(thread)) { + _api->sender().request(*requestId).cancel(); + } + if (const auto requestId = _reactionsRequests.take(thread)) { + _api->sender().request(*requestId).cancel(); + } +#if 0 // mtp if (const auto requestId = _mentionsRequests.take(thread)) { _api->request(*requestId).cancel(); } if (const auto requestId = _reactionsRequests.take(thread)) { _api->request(*requestId).cancel(); } +#endif } void UnreadThings::requestMentions( @@ -101,6 +115,25 @@ void UnreadThings::requestMentions( MsgId(1)); const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit; const auto addOffset = loaded ? -(limit + 1) : -limit; + + const auto history = thread->owningHistory(); + const auto topic = thread->asTopic(); + const auto requestId = _api->sender().request(TLsearchChatMessages( + peerToTdbChat(thread->peer()->id), + tl_string(), + std::nullopt, + tl_int53(offsetId.bare), + tl_int32(addOffset + 2), // TDLib requires -offset < limit. + tl_int32(limit), + tl_searchMessagesFilterUnreadMention(), + tl_int53(topic ? topic->rootId().bare : 0) + )).done([=](const TLDfoundChatMessages &result) { + _mentionsRequests.remove(thread); + thread->unreadMentions().addSlice(result, loaded); + }).fail([=] { + _mentionsRequests.remove(thread); + }).send(); +#if 0 // mtp const auto maxId = 0; const auto minId = 0; const auto history = thread->owningHistory(); @@ -121,6 +154,7 @@ void UnreadThings::requestMentions( }).fail([=] { _mentionsRequests.remove(thread); }).send(); +#endif _mentionsRequests.emplace(thread, requestId); } @@ -135,6 +169,25 @@ void UnreadThings::requestReactions( : MsgId(1); const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit; const auto addOffset = loaded ? -(limit + 1) : -limit; + + const auto history = thread->owningHistory(); + const auto topic = thread->asTopic(); + const auto requestId = _api->sender().request(TLsearchChatMessages( + peerToTdbChat(history->peer->id), + tl_string(), + std::nullopt, + tl_int53(offsetId.bare), + tl_int32(addOffset + 2), // TDLib requires -offset < limit. + tl_int32(limit), + tl_searchMessagesFilterUnreadReaction(), + tl_int53(topic ? topic->rootId().bare : 0) + )).done([=](const TLDfoundChatMessages &result) { + _reactionsRequests.remove(history); + history->unreadReactions().addSlice(result, loaded); + }).fail([=] { + _reactionsRequests.remove(history); + }).send(); +#if 0 // mtp const auto maxId = 0; const auto minId = 0; const auto history = thread->owningHistory(); @@ -155,6 +208,7 @@ void UnreadThings::requestReactions( }).fail([=] { _reactionsRequests.remove(thread); }).send(); +#endif _reactionsRequests.emplace(thread, requestId); } diff --git a/Telegram/SourceFiles/api/api_unread_things.h b/Telegram/SourceFiles/api/api_unread_things.h index f3c7e1711eb10..d6697d7babfaa 100644 --- a/Telegram/SourceFiles/api/api_unread_things.h +++ b/Telegram/SourceFiles/api/api_unread_things.h @@ -26,9 +26,11 @@ class UnreadThings final { void preloadEnough(Data::Thread *thread); +#if 0 // mtp void mediaAndMentionsRead( const base::flat_set &readIds, ChannelData *channel = nullptr); +#endif void cancelRequests(not_null thread); diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 064f410c5b41a..7b9e6c4551667 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -17,6 +17,7 @@ For license and copyright information please follow this link: #include "api/api_transcribes.h" #include "main/main_session.h" #include "main/main_account.h" +#include "main/session/send_as_peers.h" #include "mtproto/mtp_instance.h" #include "mtproto/mtproto_config.h" #include "mtproto/mtproto_dc_options.h" @@ -41,6 +42,7 @@ For license and copyright information please follow this link: #include "data/data_message_reactions.h" #include "inline_bots/bot_attach_web_view.h" #include "chat_helpers/emoji_interactions.h" +#include "chat_helpers/stickers_dice_pack.h" #include "lang/lang_cloud_manager.h" #include "history/history.h" #include "history/history_item.h" @@ -58,6 +60,16 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "ui/text/format_values.h" // Ui::FormatPhone +#include "tdb/tdb_account.h" +#include "tdb/tdb_tl_scheme.h" +#include "history/history_item_reply_markup.h" +#include "data/data_document.h" +#include "data/data_forum_topic.h" +#include "window/window_lock_widgets.h" +#include "chat_helpers/stickers_emoji_pack.h" +#include "boxes/peers/add_participants_box.h" +#include "window/notifications_manager.h" + namespace Api { namespace { @@ -72,6 +84,9 @@ constexpr auto kNoUpdatesTimeout = 60 * 1000; // If nothing is received in 1 min when was a sleepmode we ping. constexpr auto kNoUpdatesAfterSleepTimeout = 60 * crl::time(1000); +using namespace Tdb; + +#if 0 // mtp enum class DataIsLoadedResult { NotLoaded = 0, FromNotLoaded = 1, @@ -219,9 +234,11 @@ DataIsLoadedResult AllDataLoadedForMessage( return DataIsLoadedResult::Ok; }); } +#endif } // namespace +#if 0 // mtp Updates::Updates(not_null session) : _session(session) , _noUpdatesTimer([=] { sendPing(); }) @@ -248,6 +265,16 @@ Updates::Updates(not_null session) )).done([=](const MTPupdates_State &result) { stateDone(result); }).send(); +#endif + +Updates::Updates(not_null session) +: _session(session) +, _onlineTimer([=] { updateOnline(); }) +, _idleFinishTimer([=] { checkIdleFinish(); }) { + session->tdb().updates( + ) | rpl::start_with_next([=](const TLupdate &update) { + applyUpdate(update); + }, _lifetime); using namespace rpl::mappers; session->changes().peerUpdates( @@ -280,6 +307,7 @@ ApiWrap &Updates::api() const { return _session->api(); } +#if 0 // mtp void Updates::checkLastUpdate(bool afterSleep) { const auto now = crl::now(); const auto skip = afterSleep @@ -728,22 +756,64 @@ void Updates::getChannelDifference( void Updates::sendPing() { _session->mtp().ping(); } +#endif + +bool Updates::isActiveChat(not_null peer) const { + return ranges::find_if(_activeChats, [&](const auto &entry) { + return (entry.second.peer == peer); + }) != end(_activeChats); +} void Updates::addActiveChat(rpl::producer chat) { const auto key = _activeChats.empty() ? 0 : _activeChats.back().first + 1; std::move( chat ) | rpl::start_with_next_done([=](PeerData *peer) { + auto &entry = _activeChats[key]; + const auto was = entry.peer; + if (was == peer) { + return; + } + const auto already = peer && isActiveChat(peer); + entry.peer = peer; + const auto still = was && isActiveChat(was); + if (was && !still) { + _session->sender().request(TLcloseChat( + peerToTdbChat(was->id) + )).send(); + } + if (peer && !already) { + _session->sender().request(TLopenChat( + peerToTdbChat(peer->id) + )).send(); + } +#if 0 // mtp _activeChats[key].peer = peer; if (const auto channel = peer ? peer->asChannel() : nullptr) { channel->ptsWaitingForShortPoll( kWaitForChannelGetDifference); } +#endif }, [=] { +#if 0 // mtp _activeChats.erase(key); +#endif + const auto i = _activeChats.find(key); + if (i == end(_activeChats)) { + return; + } + const auto peer = i->second.peer; + _activeChats.erase(i); + if (!peer || isActiveChat(peer)) { + return; + } + _session->sender().request(TLcloseChat( + peerToTdbChat(peer->id) + )).send(); }, _activeChats[key].lifetime); } +#if 0 // mtp void Updates::requestChannelRangeDifference(not_null history) { Expects(history->peer->isChannel()); @@ -881,6 +951,7 @@ void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) { int32 Updates::pts() const { return _ptsWaiter.current(); } +#endif void Updates::updateOnline(crl::time lastNonIdleTime) { updateOnline(lastNonIdleTime, false); @@ -923,10 +994,13 @@ void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) { if (isOnline != _lastWasOnline || (isOnline && _lastSetOnline + config.onlineUpdatePeriod <= ms) || (isOnline && gotOtherOffline)) { +#if 0 // mtp api().request(base::take(_onlineRequest)).cancel(); +#endif _lastWasOnline = isOnline; _lastSetOnline = ms; +#if 0 // mtp if (!Core::Quitting()) { _onlineRequest = api().request(MTPaccount_UpdateStatus( MTP_bool(!isOnline) @@ -940,6 +1014,26 @@ void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) { Core::App().quitPreventFinished(); }).send(); } +#endif + // We don't want to send requests after `logout` was called, it'll + // force creation of a new TDLib client, just to fail the request. + if (!_session->loggingOut()) { + if (!Core::Quitting()) { + _session->sender().request(TLsetOption( + tl_string("online"), + tl_optionValueBoolean(tl_bool(isOnline)) + )).send(); + } else { + _session->sender().request(TLsetOption( + tl_string("online"), + tl_optionValueBoolean(tl_bool(isOnline)) + )).done([=] { + Core::App().quitPreventFinished(); + }).fail([=] { + Core::App().quitPreventFinished(); + }).send(); + } + } const auto self = session().user(); self->onlineTill = base::unixtime::now() + (isOnline ? (config.onlineUpdatePeriod / 1000) : -1); @@ -990,6 +1084,7 @@ bool Updates::isQuitPrevent() { return true; } +#if 0 // mtp void Updates::handleSendActionUpdate( PeerId peerId, MsgId rootId, @@ -1040,6 +1135,53 @@ void Updates::handleEmojiInteraction( qs(data.vemoticon()), ChatHelpers::EmojiInteractions::Parse(json)); } +#endif + +void Updates::handleEmojiInteraction( + const TLDupdateAnimatedEmojiMessageClicked &data) { + if (session().windows().empty()) { + return; + } + const auto window = session().windows().front(); + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto peer = session().data().peerLoaded(peerId); + if (!peer) { + return; + } + window->emojiInteractions().startIncoming( + peer, + data.vmessage_id().v, + session().data().processDocument(data.vsticker())); +} + +void Updates::handleSendActionUpdate( + PeerId peerId, + MsgId rootId, + PeerId fromId, + const TLchatAction &action) { + const auto history = session().data().historyLoaded(peerId); + if (!history) { + return; + } + const auto peer = history->peer; + const auto from = (fromId == session().userPeerId()) + ? session().user().get() + : session().data().peerLoaded(fromId); + if (!from || !from->isUser() || from->isSelf()) { + return; + } + if (action.type() == id_chatActionWatchingAnimations) { + const auto &data = action.c_chatActionWatchingAnimations(); + handleEmojiInteraction(peer, data.vemoji().v); + return; + } + session().data().sendActionManager().registerFor( + history, + rootId, + from->asUser(), + action, + 0); +} void Updates::handleSpeakingInCall( not_null peer, @@ -1071,6 +1213,7 @@ void Updates::handleSpeakingInCall( } } +#if 0 // mtp void Updates::handleEmojiInteraction( not_null peer, MsgId messageId, @@ -1086,6 +1229,7 @@ void Updates::handleEmojiInteraction( emoticon, std::move(bunch)); } +#endif void Updates::handleEmojiInteraction( not_null peer, @@ -1097,6 +1241,7 @@ void Updates::handleEmojiInteraction( window->emojiInteractions().seenOutgoing(peer, emoticon); } +#if 0 // mtp void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { switch (updates.type()) { case mtpc_updateShortMessage: { @@ -2079,6 +2224,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } break; +#if 0 // goodToRemove case mtpc_updatePrivacy: { auto &d = update.c_updatePrivacy(); const auto allChatsLoaded = [&](const MTPVector &ids) { @@ -2109,6 +2255,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { d.vrules(), allLoaded()); } break; +#endif case mtpc_updatePinnedDialogs: { const auto &d = update.c_updatePinnedDialogs(); @@ -2195,6 +2342,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { auto &d = update.c_updateChannel(); if (const auto channel = session().data().channelLoaded(d.vchannel_id())) { channel->inviter = UserId(0); + channel->inviterLoaded = false; channel->inviteViaRequest = false; if (channel->amIn()) { if (channel->isMegagroup() @@ -2494,6 +2642,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { }); } break; +#if 0 // goodToRemove ////// Cloud langpacks case mtpc_updateLangPack: { const auto &data = update.c_updateLangPack(); @@ -2507,6 +2656,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { Lang::CurrentCloudManager().requestLangPackDifference(code); } } break; +#endif ////// Cloud themes case mtpc_updateTheme: { @@ -2538,5 +2688,515 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } +#endif + +void Updates::applyUpdate(const TLupdate &update) { + auto &owner = session().data(); + update.match([&](const TLDupdateAuthorizationState &data) { + const auto type = data.vauthorization_state().type(); + if (type == id_authorizationStateReady) { + session().api().requestMoreDialogsIfNeeded(); + } + }, [&](const TLDupdateNewMessage &data) { + auto oldMessageId = MsgId(); + const auto &message = data.vmessage(); + if (const auto state = message.data().vsending_state()) { + oldMessageId = state->match([]( + const TLDmessageSendingStatePending &data) { + return data.vsending_id().v + ? ClientMsgByIndex(data.vsending_id().v) + : MsgId(); + }, [](const TLDmessageSendingStateFailed &data) { + return MsgId(); + }); + } + if (oldMessageId) { + owner.processMessage(message, oldMessageId); + } else { + owner.processMessage(message, NewMessageType::Unread); + } + }, [&](const TLDupdateMessageSendAcknowledged &data) { + // later show sent checkmark while the message only was ack-ed. + }, [&](const TLDupdateMessageSendSucceeded &data) { + const auto oldMessageId = MsgId(data.vold_message_id().v); + owner.processMessage(data.vmessage(), oldMessageId); + }, [&](const TLDupdateMessageSendFailed &data) { + const auto oldMessageId = MsgId(data.vold_message_id().v); + owner.processMessage(data.vmessage(), oldMessageId); + }, [&](const TLDupdateMessageContent &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto id = data.vmessage_id().v; + data.vnew_content().match([&](const TLDmessagePoll &poll) { + owner.applyUpdate(poll.vpoll()); + }, [&](auto &&) { + if (const auto item = owner.message(peerId, id)) { + item->updateContent(data.vnew_content()); + } + }); + }, [&](const TLDupdateMessageEdited &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto id = data.vmessage_id().v; + if (const auto item = owner.message(peerId, id)) { + item->updateEditedInfo( + data.vedit_date().v, + HistoryMessageMarkupData(data.vreply_markup())); + } + }, [&](const TLDupdateMessageIsPinned &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto id = data.vmessage_id().v; + if (const auto item = owner.message(peerId, id)) { + item->setIsPinned(data.vis_pinned().v); + } + }, [&](const TLDupdateMessageInteractionInfo &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto id = data.vmessage_id().v; + if (const auto item = owner.message(peerId, id)) { + item->updateInteractionInfo(data.vinteraction_info()); + } + }, [&](const TLDupdateMessageContentOpened &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto id = data.vmessage_id().v; + if (const auto item = owner.message(peerId, id)) { + item->markMediaAndMentionRead(); + _session->data().requestItemRepaint(item); + } + }, [&](const TLDupdateMessageMentionRead &data) { + const auto history = owner.historyLoaded( + peerFromTdbChat(data.vchat_id())); + if (history) { + history->unreadMentions().markAsRead(data); + } + }, [&](const TLDupdateMessageUnreadReactions &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto id = data.vmessage_id().v; + if (const auto item = owner.message(peerId, id)) { + item->updateUnreadReactions(data.vunread_reactions().v); + item->history()->unreadReactions().setCount( + data.vunread_reaction_count().v); + } + }, [&](const TLDupdateMessageLiveLocationViewed &data) { + }, [&](const TLDupdateNewChat &data) { + owner.processPeer(data.vchat()); + }, [&](const TLDupdateChatTitle &data) { + owner.applyChatTitle(data); + }, [&](const TLDupdateChatPhoto &data) { + owner.applyChatPhoto(data); + }, [&](const TLDupdateChatAccentColor &data) { + owner.applyChatAccentColor(data); + }, [&](const TLDupdateChatBackgroundCustomEmoji &data) { + owner.applyChatBackgroundCustomEmoji(data); + }, [&](const TLDupdateChatPermissions &data) { + owner.applyChatPermissions(data); + }, [&](const TLDupdateChatLastMessage &data) { + owner.applyLastMessage(data); + }, [&](const TLDupdateChatPosition &data) { + owner.applyDialogPosition(data); + }, [&](const TLDupdateChatMessageSender &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + auto &sendAsPeers = session().sendAsPeers(); + if (const auto sender = data.vmessage_sender_id()) { + sendAsPeers.setChosen(peer, peerFromSender(*sender)); + } else { + sendAsPeers.setChosen(peer, PeerId()); + } + } + }, [&](const TLDupdateChatHasProtectedContent &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + const auto noforwards = data.vhas_protected_content().v; + if (const auto chat = peer->asChat()) { + using Flag = ChatDataFlag; + chat->setFlags((chat->flags() & ~Flag::NoForwards) + | (noforwards ? Flag::NoForwards : Flag())); + } else if (const auto channel = peer->asChannel()) { + using Flag = ChannelDataFlag; + channel->setFlags((channel->flags() & ~Flag::NoForwards) + | (noforwards ? Flag::NoForwards : Flag())); + } + } + }, [&](const TLDupdateChatIsTranslatable &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + peer->setTranslationDisabled(!data.vis_translatable().v); + } + }, [&](const TLDupdateChatIsMarkedAsUnread &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto history = owner.historyLoaded(peerId)) { + history->setUnreadMark(data.vis_marked_as_unread().v); + } + }, [&](const TLDupdateChatBlockList &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = session().data().peerLoaded(peerId)) { + peer->setIsBlocked(data.vblock_list() + && data.vblock_list()->type() == id_blockListMain); + } + }, [&](const TLDupdateChatHasScheduledMessages &data) { + // later optimization + }, [&](const TLDupdateChatVideoChat &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto callId = data.vvideo_chat().data().vgroup_call_id().v; + const auto tlAs = data.vvideo_chat().data().vdefault_participant_id(); + const auto as = tlAs ? peerFromSender(*tlAs) : PeerId(0); + if (peerIsChannel(peerId)) { + const auto channel = owner.channel(peerToChannel(peerId)); + channel->setGroupCall(callId); + channel->setGroupCallDefaultJoinAs(as); + channel->setFlags(channel->flags() + | (data.vvideo_chat().data().vhas_participants().v + ? ChannelDataFlag::CallNotEmpty + : ChannelDataFlag(0))); + } else if (peerIsChat(peerId)) { + const auto chat = owner.chat(peerToChat(peerId)); + chat->setGroupCall(callId); + chat->setGroupCallDefaultJoinAs(as); + chat->setFlags(chat->flags() + | (data.vvideo_chat().data().vhas_participants().v + ? ChatDataFlag::CallNotEmpty + : ChatDataFlag(0))); + } + }, [&](const TLDupdateChatDefaultDisableNotification &data) { + }, [&](const TLDupdateChatReadInbox &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto history = owner.historyLoaded(peerId)) { + history->applyInboxReadUpdate( + (history->folder() ? history->folder()->id() : 0), + data.vlast_read_inbox_message_id().v, + data.vunread_count().v); + } + }, [&](const TLDupdateChatReadOutbox &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto history = owner.historyLoaded(peerId)) { + history->outboxRead(data.vlast_read_outbox_message_id().v); + } + }, [&](const TLDupdateChatUnreadMentionCount &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto history = owner.historyLoaded(peerId)) { + history->unreadMentions().setCount( + data.vunread_mention_count().v); + } + }, [&](const TLDupdateChatUnreadReactionCount &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto history = owner.historyLoaded(peerId)) { + history->unreadReactions().setCount( + data.vunread_reaction_count().v); + } + }, [&](const TLDupdateChatAvailableReactions &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + if (peer->isUser()) { + return; + } + auto allowed = Data::Parse(peer, data.vavailable_reactions()); + if (const auto chat = peer->asChat()) { + chat->setAllowedReactions(std::move(allowed)); + } else if (const auto channel = peer->asChannel()) { + channel->setAllowedReactions(std::move(allowed)); + } + } + }, [&](const TLDupdateActiveEmojiReactions &data) { + owner.reactions().refreshActive(data); + }, [&](const TLDupdateDefaultReactionType &data) { + owner.reactions().refreshFavorite(data); + }, [&](const TLDupdateChatNotificationSettings &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + owner.notifySettings().apply( + peer, + data.vnotification_settings()); + } + }, [&](const TLDupdateScopeNotificationSettings &data) { + const auto type = data.vscope().match([]( + const TLDnotificationSettingsScopePrivateChats &) { + return Data::DefaultNotify::User; + }, [](const TLDnotificationSettingsScopeGroupChats &) { + return Data::DefaultNotify::Group; + }, [](const TLDnotificationSettingsScopeChannelChats &) { + return Data::DefaultNotify::Broadcast; + }); + owner.notifySettings().apply(type, data.vnotification_settings()); + }, [&](const TLDupdateChatMessageAutoDeleteTime &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + peer->setMessagesTTL(data.vmessage_auto_delete_time().v); + } + }, [&](const TLDupdateChatActionBar &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + peer->setActionBar(data.vaction_bar()); + } + }, [&](const TLDupdateChatBackground &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + peer->setWallPaper(data.vbackground() + ? Data::WallPaper::Create(_session, *data.vbackground()) + : std::nullopt); + } + }, [&](const TLDupdateChatTheme &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + peer->setThemeEmoji(data.vtheme_name().v); + } + }, [&](const TLDupdateChatReplyMarkup &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto history = owner.historyLoaded(peerId)) { + history->setLastKeyboardId(data.vreply_markup_message_id().v); + } + }, [&](const TLDupdateChatDraftMessage &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto topicRootId = MsgId(); + if (const auto draft = data.vdraft_message()) { + Data::ApplyPeerCloudDraft( + &session(), + peerId, + topicRootId, + draft->data()); + } else { + Data::ClearPeerCloudDraft( + &session(), + peerId, + topicRootId, + base::unixtime::now()); + } + if (const auto history = owner.historyLoaded(peerId)) { + for (const auto &position : data.vpositions().v) { + history->applyPosition(position.data()); + } + } + }, [&](const TLDupdateChatFolders &data) { + owner.chatsFilters().apply(data); + }, [&](const TLDupdateChatOnlineMemberCount &data) { + // later online count for all chats + }, [&](const TLDupdateNotification &data) { + }, [&](const TLDupdateNotificationGroup &data) { + Core::App().notifications().process(&session(), data); + }, [&](const TLDupdateActiveNotifications &data) { + }, [&](const TLDupdateHavePendingNotifications &data) { + }, [&](const TLDupdateDeleteMessages &data) { + if (data.vfrom_cache().v) { + return; + } + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto history = owner.historyLoaded(peerId)) { + for (const auto &id : data.vmessage_ids().v) { + if (const auto item = owner.message(peerId, id.v)) { + item->destroy(); + } + } + owner.notifyHistoryChangeDelayed(history); + } + }, [&](const TLDupdateChatAction &data) { + handleSendActionUpdate( + peerFromTdbChat(data.vchat_id()), + data.vmessage_thread_id().v, + peerFromSender(data.vsender_id()), + data.vaction()); + }, [&](const TLDupdateUserStatus &data) { + if (const auto user = owner.userLoaded(UserId(data.vuser_id()))) { + const auto oldOnlineTill = user->onlineTill; + const auto newOnlineTill = ApiWrap::OnlineTillFromStatus( + data.vstatus(), + oldOnlineTill); + if (oldOnlineTill != newOnlineTill) { + user->onlineTill = newOnlineTill; + session().changes().peerUpdated( + user, + Data::PeerUpdate::Flag::OnlineStatus); + } + } + }, [&](const TLDupdateUser &data) { + owner.processUser(data.vuser()); + }, [&](const TLDupdateBasicGroup &data) { + owner.processChat(data.vbasic_group()); + }, [&](const TLDupdateSupergroup &data) { + owner.processChannel(data.vsupergroup()); + }, [&](const TLDupdateSecretChat &data) { + owner.processSecretChat(data.vsecret_chat()); + }, [&](const TLDupdateUserFullInfo &data) { + ::Data::ApplyUserUpdate( + owner.user(UserId(data.vuser_id())), + data.vuser_full_info().data()); + }, [&](const TLDupdateBasicGroupFullInfo &data) { + ::Data::ApplyChatUpdate( + owner.chat(ChatId(data.vbasic_group_id())), + data.vbasic_group_full_info().data()); + }, [&](const TLDupdateSupergroupFullInfo &data) { + ::Data::ApplyChannelUpdate( + owner.channel(ChannelId(data.vsupergroup_id())), + data.vsupergroup_full_info().data()); + }, [&](const TLDupdateFile &data) { + }, [&](const TLDupdateFileGenerationStart &data) { + }, [&](const TLDupdateFileGenerationStop &data) { + }, [&](const TLDupdateCall &data) { + Core::App().calls().handleUpdate(&session(), data); + }, [&](const TLDupdateGroupCall &data) { + Core::App().calls().handleGroupCallUpdate(&session(), data); + }, [&](const TLDupdateGroupCallParticipant &data) { + Core::App().calls().handleGroupCallUpdate(&session(), data); + }, [&](const TLDupdateNewCallSignalingData &data) { + Core::App().calls().handleUpdate(&session(), data); + }, [&](const TLDupdateUserPrivacySettingRules &data) { + data.vsetting().match([&](const TLDuserPrivacySettingShowStatus &) { + session().api().updatePrivacyLastSeens(); + }, [](const auto &) { + }); + }, [&](const TLDupdateUnreadMessageCount &data) { + data.vchat_list().match([&](const TLDchatListMain &) { + const auto list = owner.chatsList(); + list->updateCloudUnread(data); + }, [&](const TLDchatListArchive &) { + const auto list = owner.folder(Data::Folder::kId)->chatsList(); + list->updateCloudUnread(data); + }, [&](const TLDchatListFolder &data) { + // later use tdlib counters + }); + }, [&](const TLDupdateUnreadChatCount &data) { + data.vchat_list().match([&](const TLDchatListMain &) { + owner.chatsList()->updateCloudUnread(data); + }, [&](const TLDchatListArchive &) { + owner.folder(Data::Folder::kId)->applyDialog(data); + }, [&](const TLDchatListFolder &data) { + // later use tdlib counters + }); + }, [&](const TLDupdateOption &data) { + if (session().apply(data)) { + return; + } else if (Lang::CurrentCloudManager().apply(data)) { + return; + } + }, [&](const TLDupdateStickerSet &data) { + session().data().stickers().apply(data); + }, [&](const TLDupdateInstalledStickerSets &data) { + session().data().stickers().apply(data); + }, [&](const TLDupdateTrendingStickerSets &data) { + session().data().stickers().apply(data); + }, [&](const TLDupdateRecentStickers &data) { + session().data().stickers().apply(data); + }, [&](const TLDupdateFavoriteStickers &data) { + session().data().stickers().apply(data); + }, [&](const TLDupdateSavedAnimations &data) { + session().data().stickers().apply(data); + }, [&](const TLDupdateSavedNotificationSounds &data) { + session().api().ringtones().applyUpdate(); + }, [&](const TLDupdateSelectedBackground &data) { + }, [&](const TLDupdateChatThemes &data) { + session().data().cloudThemes().applyUpdate(data); + }, [&](const TLDupdateAccentColors &data) { + session().applyAccentColors(data); + }, [&](const TLDupdateLanguagePackStrings &data) { + Lang::CurrentCloudManager().apply(data); + }, [&](const TLDupdateTermsOfService &data) { + session().lockByTerms(Window::TermsLock::FromTL( + &session(), + data.vterms_of_service().data(), + data.vterms_of_service_id().v.toUtf8())); + }, [&](const TLDupdateUsersNearby &data) { + }, [&](const TLDupdateUnconfirmedSession &data) { + // later + }, [&](const TLDupdateAttachmentMenuBots &data) { + session().attachWebView().apply(data); + }, [&](const TLDupdateWebAppMessageSent &data) { + session().data().webViewResultSent({ + .queryId = uint64(data.vweb_app_launch_id().v), + }); + }, [&](const TLDupdateDiceEmojis &data) { + session().diceStickersPacks().apply(data); + }, [&](const TLDupdateAnimatedEmojiMessageClicked &data) { + handleEmojiInteraction(data); + }, [&](const TLDupdateAnimationSearchParameters &data) { + session().emojiStickersPack().gifSectionsRefresh(data); + }, [&](const TLDupdateSuggestedActions &data) { + auto suggestionsToGigagroup = base::flat_map(); + for (const auto &action : data.vadded_actions().v) { + action.match([&]( + const TLDsuggestedActionConvertToBroadcastGroup &data) { + suggestionsToGigagroup[data.vsupergroup_id().v] = true; + }, [](const auto &) {}); + } + for (const auto &action : data.vremoved_actions().v) { + action.match([&]( + const TLDsuggestedActionConvertToBroadcastGroup &data) { + suggestionsToGigagroup[data.vsupergroup_id().v] = false; + }, [](const auto &) {}); + } + for (const auto &[id, value] : suggestionsToGigagroup) { + owner.setSuggestToGigagroup(owner.channel(id), value); + } + }, [&](const TLDupdateAutosaveSettings &data) { + }, [&](const TLDupdateChatPendingJoinRequests &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + auto count = 0; + auto recent = std::vector(); + if (const auto requests = data.vpending_join_requests()) { + const auto &data = requests->data(); + count = data.vtotal_count().v; + const auto &list = data.vuser_ids().v; + recent.reserve(list.size()); + for (const auto &id : list) { + recent.push_back(UserId(id)); + } + } + if (const auto chat = peer->asChat()) { + chat->setPendingRequestsCount(count, std::move(recent)); + } else if (const auto channel = peer->asChannel()) { + channel->setPendingRequestsCount(count, std::move(recent)); + } + } + }, [&](const TLDupdateAnimatedEmojiMessageClicked &data) { + }, [&](const TLDupdateFileDownloads &data) { + }, [&](const TLDupdateFileAddedToDownloads &data) { + }, [&](const TLDupdateFileDownload &data) { + }, [&](const TLDupdateFileRemovedFromDownloads &data) { + }, [&](const TLDupdateAddChatMembersPrivacyForbidden &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + auto users = std::vector>(); + for (const auto &id : data.vuser_ids().v) { + if (const auto user = owner.userLoaded(UserId(id.v))) { + users.push_back(user); + } + } + if (const auto window = Core::App().windowFor(peer)) { + if (const auto controller = window->sessionController()) { + if (&controller->session() == &peer->session()) { + ChatInviteForbidden( + window->uiShow(), + peer, + std::move(users)); + } + } + } + } + }, [&](const TLDupdateAutosaveSettings &data) { + }, [&](const TLDupdateForumTopicInfo &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + const auto rootId = data.vinfo().data().vmessage_thread_id().v; + if (const auto topic = peer->forumTopicFor(rootId)) { + topic->applyInfo(data.vinfo()); + } + } + }, [&](const TLDupdateStory &data) { + owner.stories().apply(data); + }, [&](const TLDupdateStoryDeleted &data) { + owner.stories().apply(data); + }, [&](const TLDupdateChatActiveStories &data) { + owner.stories().apply(data); + }, [&](const TLDupdateStoryListChatCount &data) { + owner.stories().apply(data); + }, [&](const TLDupdateStoryStealthMode &data) { + owner.stories().apply(data); + + // Updates below are handled in a different place. + }, [&](const TLDupdateConnectionState &data) { + }, [&](const TLDupdateServiceNotification &data) { + + // Updates below are not relevant. + }, [&](const TLDupdateStorySendSucceeded &data) { + }, [&](const TLDupdateStorySendFailed &data) { + }); + session().data().sendHistoryChangeNotifications(); +} } // namespace Api diff --git a/Telegram/SourceFiles/api/api_updates.h b/Telegram/SourceFiles/api/api_updates.h index f21baa9641ce1..2976450fbec52 100644 --- a/Telegram/SourceFiles/api/api_updates.h +++ b/Telegram/SourceFiles/api/api_updates.h @@ -13,6 +13,12 @@ For license and copyright information please follow this link: class ApiWrap; class History; +namespace Tdb { +class TLupdate; +class TLchatAction; +class TLDupdateAnimatedEmojiMessageClicked; +} // namespace Tdb + namespace MTP { class Error; } // namespace MTP @@ -34,6 +40,7 @@ class Updates final { [[nodiscard]] Main::Session &session() const; [[nodiscard]] ApiWrap &api() const; +#if 0 // mtp void applyUpdates( const MTPUpdates &updates, uint64 sentMessageRandomId = 0); @@ -41,6 +48,7 @@ class Updates final { void applyUpdateNoPtsCheck(const MTPUpdate &update); [[nodiscard]] int32 pts() const; +#endif void updateOnline(crl::time lastNonIdleTime = 0); [[nodiscard]] bool isIdle() const; @@ -50,6 +58,7 @@ class Updates final { crl::time lastSetOnline() const; bool isQuitPrevent(); +#if 0 // mtp bool updateAndApply(int32 pts, int32 ptsCount, const MTPUpdates &updates); bool updateAndApply(int32 pts, int32 ptsCount, const MTPUpdate &update); bool updateAndApply(int32 pts, int32 ptsCount); @@ -61,10 +70,12 @@ class Updates final { void getDifference(); void requestChannelRangeDifference(not_null history); +#endif void addActiveChat(rpl::producer chat); private: +#if 0 // mtp enum class ChannelDifferenceRequest { Unknown, PtsGapOrShortPoll, @@ -76,12 +87,16 @@ class Updates final { SkipMessageIds, SkipExceptGroupCallParticipants, }; +#endif struct ActiveChatTracker { PeerData *peer = nullptr; rpl::lifetime lifetime; }; + void applyUpdate(const Tdb::TLupdate &update); + +#if 0 // mtp void channelRangeDifferenceSend( not_null channel, MsgRange range, @@ -90,8 +105,12 @@ class Updates final { not_null channel, MsgRange range, const MTPupdates_ChannelDifference &result); +#endif + bool isActiveChat(not_null peer) const; void updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline); + +#if 0 // mtp void sendPing(); void getDifferenceByPts(); void getDifferenceAfterFail(); @@ -122,6 +141,7 @@ class Updates final { void mtpUpdateReceived(const MTPUpdates &updates); void mtpNewSessionCreated(); + void feedUpdateVector( const MTPVector &updates, SkipUpdatePolicy policy = SkipUpdatePolicy::SkipNone); @@ -146,21 +166,33 @@ class Updates final { void handleEmojiInteraction( not_null peer, const MTPDsendMessageEmojiInteraction &data); +#endif + void handleSendActionUpdate( + PeerId peerId, + MsgId rootId, + PeerId fromId, + const Tdb::TLchatAction &action); + void handleEmojiInteraction( + const Tdb::TLDupdateAnimatedEmojiMessageClicked &data); + void handleSpeakingInCall( not_null peer, PeerId participantPeerId, PeerData *participantPeerLoaded); +#if 0 // mtp void handleEmojiInteraction( not_null peer, MsgId messageId, const QString &emoticon, ChatHelpers::EmojiInteractionsBunch bunch); +#endif void handleEmojiInteraction( not_null peer, const QString &emoticon); const not_null _session; +#if 0 // mtp int32 _updatesDate = 0; int32 _updatesQts = -1; int32 _updatesSeq = 0; @@ -195,13 +227,18 @@ class Updates final { crl::time _lastUpdateTime = 0; bool _handlingChannelDifference = false; +#endif + + base::Timer _onlineTimer; base::flat_map _activeChats; base::flat_map< not_null, base::flat_map> _pendingSpeakingCallParticipants; +#if 0 // mtp mtpRequestId _onlineRequest = 0; +#endif base::Timer _idleFinishTimer; crl::time _lastSetOnline = 0; bool _lastWasOnline = false; diff --git a/Telegram/SourceFiles/api/api_user_names.cpp b/Telegram/SourceFiles/api/api_user_names.cpp index fa040f17ea0b4..849c9b18664fa 100644 --- a/Telegram/SourceFiles/api/api_user_names.cpp +++ b/Telegram/SourceFiles/api/api_user_names.cpp @@ -13,9 +13,21 @@ For license and copyright information please follow this link: #include "data/data_user.h" #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + +Data::Usernames SkipSingleNormalUsername(Data::Usernames list) { + if (list.size() == 1 && list.front().editable && list.front().active) { + return {}; + } + return list; +} + +#if 0 // mtp [[nodiscard]] Data::Username UsernameFromTL(const MTPUsername &username) { return { .username = qs(username.data().vusername()), @@ -31,12 +43,24 @@ namespace { ? std::make_optional(user->inputUser) : std::nullopt; } +#endif + +[[nodiscard]] std::optional BotUserInput( + not_null peer) { + const auto user = peer->asUser(); + return (user && user->botInfo && user->botInfo->canEditInformation) + ? std::make_optional(tl_int53(peerToUser(user->id).bare)) + : std::nullopt; +} } // namespace Usernames::Usernames(not_null api) : _session(&api->session()) +, _api(&api->sender()) { +#if 0 // mtp , _api(&api->instance()) { +#endif } rpl::producer Usernames::loadUsernames( @@ -44,6 +68,7 @@ rpl::producer Usernames::loadUsernames( return [=](auto consumer) { auto lifetime = rpl::lifetime(); +#if 0 // mtp const auto push = [consumer]( const auto &usernames, const auto &username) { @@ -102,6 +127,30 @@ rpl::producer Usernames::loadUsernames( } else if (const auto channel = peer->asChannel()) { requestChannel(channel->inputChannel); } +#endif + const auto requestUser = [&](UserId userId) { + _session->sender().request(TLgetUser( + tl_int53(userId.bare) + )).done([=](const TLDuser &result) { + consumer.put_next( + SkipSingleNormalUsername(FromTL(result.vusernames()))); + consumer.put_done(); + }).send(); + }; + const auto requestChannel = [&](ChannelId channelId) { + _session->sender().request(TLgetSupergroup( + tl_int53(channelId.bare) + )).done([=](const TLDsupergroup &result) { + consumer.put_next( + SkipSingleNormalUsername(FromTL(result.vusernames()))); + consumer.put_done(); + }).send(); + }; + if (const auto user = peer->asUser()) { + requestUser(peerToUser(user->id)); + } else if (const auto channel = peer->asChannel()) { + requestChannel(peerToChannel(channel->id)); + } return lifetime; }; } @@ -145,8 +194,12 @@ rpl::producer Usernames::toggle( const auto done = [=] { pop(Error::Unknown); }; + const auto fail = [=](const Tdb::Error &error) { + const auto type = error.message; +#if 0 // mtp const auto fail = [=](const MTP::Error &error) { const auto type = error.type(); +#endif if (type == u"USERNAMES_ACTIVE_TOO_MUCH"_q) { pop(Error::TooMuch); } else { @@ -155,22 +208,42 @@ rpl::producer Usernames::toggle( }; if (peer->isSelf()) { + _api.request(TLtoggleUsernameIsActive( + tl_string(username), + tl_bool(active) + )).done(done).fail(fail).send(); +#if 0 // mtp _api.request(MTPaccount_ToggleUsername( MTP_string(username), MTP_bool(active) )).done(done).fail(fail).send(); +#endif } else if (const auto channel = peer->asChannel()) { + _api.request(TLtoggleSupergroupUsernameIsActive( + tl_int53(peerToChannel(channel->id).bare), + tl_string(username), + tl_bool(active) + )).done(done).fail(fail).send(); +#if 0 // mtp _api.request(MTPchannels_ToggleUsername( channel->inputChannel, MTP_string(username), MTP_bool(active) )).done(done).fail(fail).send(); +#endif } else if (const auto botUserInput = BotUserInput(peer)) { + _api.request(TLtoggleBotUsernameIsActive( + *botUserInput, + tl_string(username), + tl_bool(active) + )).done(done).fail(fail).send(); +#if 0 // mtp _api.request(MTPbots_ToggleUsername( *botUserInput, MTP_string(username), MTP_bool(active) )).done(done).fail(fail).send(); +#endif } else { return rpl::never(); } @@ -190,11 +263,18 @@ rpl::producer<> Usernames::reorder( return [=](auto consumer) { auto lifetime = rpl::lifetime(); + auto tlUsernames = ranges::views::all( + usernames + ) | ranges::views::transform([](const QString &username) { + return tl_string(username); + }) | ranges::to>; +#if 0 // mtp auto tlUsernames = ranges::views::all( usernames ) | ranges::views::transform([](const QString &username) { return MTP_string(username); }) | ranges::to>; +#endif const auto finish = [=] { if (_reorderRequests.contains(peerId)) { @@ -208,32 +288,73 @@ rpl::producer<> Usernames::reorder( } if (peer->isSelf()) { + const auto requestId = _api.request(TLreorderActiveUsernames( + tl_vector(std::move(tlUsernames)) + )).done(finish).fail(finish).send(); +#if 0 // mtp const auto requestId = _api.request(MTPaccount_ReorderUsernames( MTP_vector(std::move(tlUsernames)) )).done(finish).fail(finish).send(); +#endif _reorderRequests.emplace(peerId, requestId); } else if (const auto channel = peer->asChannel()) { + const auto requestId = _api.request(TLreorderSupergroupActiveUsernames( + tl_int53(peerToChannel(channel->id).bare), + tl_vector(std::move(tlUsernames)) + )).done(finish).fail(finish).send(); +#if 0 // mtp const auto requestId = _api.request(MTPchannels_ReorderUsernames( channel->inputChannel, MTP_vector(std::move(tlUsernames)) )).done(finish).fail(finish).send(); +#endif _reorderRequests.emplace(peerId, requestId); } else if (const auto botUserInput = BotUserInput(peer)) { + const auto requestId = _api.request(TLreorderBotActiveUsernames( + *botUserInput, + tl_vector(std::move(tlUsernames)) + )).done(finish).fail(finish).send(); +#if 0 // mtp const auto requestId = _api.request(MTPbots_ReorderUsernames( *botUserInput, MTP_vector(std::move(tlUsernames)) )).done(finish).fail(finish).send(); +#endif _reorderRequests.emplace(peerId, requestId); } return lifetime; }; } +#if 0 // mtp Data::Usernames Usernames::FromTL(const MTPVector &usernames) { return ranges::views::all( usernames.v ) | ranges::views::transform(UsernameFromTL) | ranges::to_vector; } +#endif + +Data::Usernames Usernames::FromTL(const Tdb::TLusernames *usernames) { + auto result = Data::Usernames(); + if (const auto data = usernames ? &usernames->data() : nullptr) { + const auto editable = data->veditable_username().v; + for (const auto &active : data->vactive_usernames().v) { + result.push_back({ + .username = active.v, + .active = true, + .editable = (active.v == editable), + }); + } + for (const auto &inactive : data->vdisabled_usernames().v) { + result.push_back({ + .username = inactive.v, + .active = false, + .editable = (inactive.v == editable), + }); + } + } + return result; +} void Usernames::requestToCache(not_null peer) { _tinyCache = {}; diff --git a/Telegram/SourceFiles/api/api_user_names.h b/Telegram/SourceFiles/api/api_user_names.h index 54b59ac2947ed..b118483d06cf4 100644 --- a/Telegram/SourceFiles/api/api_user_names.h +++ b/Telegram/SourceFiles/api/api_user_names.h @@ -10,9 +10,15 @@ For license and copyright information please follow this link: #include "data/data_user_names.h" #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + class ApiWrap; class PeerData; +namespace Tdb { +class TLusernames; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -41,12 +47,18 @@ class Usernames final { void requestToCache(not_null peer); [[nodiscard]] Data::Usernames cacheFor(PeerId id); +#if 0 // mtp static Data::Usernames FromTL(const MTPVector &usernames); +#endif + static Data::Usernames FromTL(const Tdb::TLusernames *usernames); private: const not_null _session; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; using Key = PeerId; struct Entry final { diff --git a/Telegram/SourceFiles/api/api_user_privacy.cpp b/Telegram/SourceFiles/api/api_user_privacy.cpp index edaae7c97511a..dd6fa289ba4b4 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.cpp +++ b/Telegram/SourceFiles/api/api_user_privacy.cpp @@ -17,15 +17,91 @@ For license and copyright information please follow this link: #include "main/main_session.h" #include "settings/settings_premium.h" // Settings::ShowPremium. +#include "tdb/tdb_account.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { constexpr auto kMaxRules = 3; // Allow users, disallow users, Option. +using namespace Tdb; + +#if 0 // goodToRemove using TLInputRules = MTPVector; using TLRules = MTPVector; +#endif +using TLInputRules = TLuserPrivacySettingRules; +using TLRules = TLDuserPrivacySettingRules; TLInputRules RulesToTL(const UserPrivacy::Rule &rule) { + const auto collectInputUsers = [](const auto &peers) { + auto result = QVector(); + result.reserve(peers.size()); + for (const auto &peer : peers) { + if (const auto user = peer->asUser()) { + result.push_back( + tl_int53(peerToUser(peer->id).bare)); + } + } + return result; + }; + const auto collectInputChats = [](const auto &peers) { + auto result = QVector(); + result.reserve(peers.size()); + for (const auto &peer : peers) { + if (!peer->isUser()) { + result.push_back(peerToTdbChat(peer->id)); + } + } + return result; + }; + + auto result = QVector(); + result.reserve(kMaxRules); + if (!rule.ignoreAlways) { + const auto users = collectInputUsers(rule.always); + const auto chats = collectInputChats(rule.always); + if (!users.empty()) { + result.push_back( + tl_userPrivacySettingRuleAllowUsers( + tl_vector(users))); + } + if (!chats.empty()) { + result.push_back( + tl_userPrivacySettingRuleAllowChatMembers( + tl_vector(chats))); + } + } + if (!rule.ignoreNever) { + const auto users = collectInputUsers(rule.never); + const auto chats = collectInputChats(rule.never); + if (!users.empty()) { + result.push_back( + tl_userPrivacySettingRuleRestrictUsers( + tl_vector(users))); + } + if (!chats.empty()) { + result.push_back( + tl_userPrivacySettingRuleRestrictChatMembers( + tl_vector(chats))); + } + } + result.push_back([&] { + switch (rule.option) { + case UserPrivacy::Option::Everyone: + return tl_userPrivacySettingRuleAllowAll(); + case UserPrivacy::Option::Contacts: + return tl_userPrivacySettingRuleAllowContacts(); + case UserPrivacy::Option::Nobody: + return tl_userPrivacySettingRuleRestrictAll(); + } + Unexpected("Option value in UserPrivacy CollectResult."); + }()); + + return tl_userPrivacySettingRules( + tl_vector(std::move(result))); +#if 0 // goodToRemove const auto collectInputUsers = [](const auto &peers) { auto result = QVector(); result.reserve(peers.size()); @@ -91,12 +167,19 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) { return MTP_vector(std::move(result)); +#endif } UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) { // This is simplified version of privacy rules interpretation. // But it should be fine for all the apps // that use the same subset of features. + +#if 0 // mtp + owner.processUsers(data.vusers()); + owner.processChats(data.vchats()); +#endif + using Option = UserPrivacy::Option; auto result = UserPrivacy::Rule(); auto optionSet = false; @@ -107,6 +190,7 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) { }; auto &always = result.always; auto &never = result.never; +#if 0 // goodToRemove const auto feed = [&](const MTPPrivacyRule &rule) { rule.match([&](const MTPDprivacyValueAllowAll &) { setOption(Option::Everyone); @@ -168,13 +252,77 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) { } }); }; +#endif + const auto notNullGet = ¬_null::get; + const auto feed = [&](const TLuserPrivacySettingRule &rule) { + rule.match([&](const TLDuserPrivacySettingRuleAllowAll &) { + setOption(Option::Everyone); + }, [&](const TLDuserPrivacySettingRuleAllowContacts &) { + setOption(Option::Contacts); + }, [&](const TLDuserPrivacySettingRuleAllowUsers &data) { + const auto &users = data.vuser_ids().v; + always.reserve(always.size() + users.size()); + for (const auto &userId : users) { + const auto user = owner.user(UserId(userId.v)); + if (!ranges::contains(never, user) + && !ranges::contains(always, user)) { + always.emplace_back(user); + } + } + }, [&](const TLDuserPrivacySettingRuleAllowChatMembers &data) { + const auto &chats = data.vchat_ids().v; + always.reserve(always.size() + chats.size()); + for (const auto &chatId : chats) { + const auto peerId = peerFromTdbChat(chatId); + const auto peer = owner.peerLoaded(peerId); + if (peer + && !ranges::contains(never, peer, notNullGet) + && !ranges::contains(always, peer, notNullGet)) { + always.emplace_back(peer); + } + } + }, [&](const TLDuserPrivacySettingRuleRestrictContacts &) { + // Not supported. + }, [&](const TLDuserPrivacySettingRuleRestrictAll &) { + setOption(Option::Nobody); + }, [&](const TLDuserPrivacySettingRuleRestrictUsers &data) { + const auto &users = data.vuser_ids().v; + never.reserve(never.size() + users.size()); + for (const auto &userId : users) { + const auto user = owner.user(UserId(userId.v)); + if (!ranges::contains(always, user) + && !ranges::contains(never, user)) { + never.emplace_back(user); + } + } + }, [&](const TLDuserPrivacySettingRuleRestrictChatMembers &data) { + const auto &chats = data.vchat_ids().v; + never.reserve(never.size() + chats.size()); + for (const auto &chatId : chats) { + const auto peerId = peerFromTdbChat(chatId); + const auto peer = owner.peerLoaded(peerId); + if (peer + && !ranges::contains(always, peer, notNullGet) + && !ranges::contains(never, peer, notNullGet)) { + never.emplace_back(peer); + } + } + }); + }; +#if 0 // goodToRemove for (const auto &rule : rules.v) { feed(rule); } feed(MTP_privacyValueDisallowAll()); // Disallow by default. +#endif + for (const auto &rule : rules.vrules().v) { + feed(rule); + } + feed(tl_userPrivacySettingRuleRestrictAll()); // Disallow by default. return result; } +#if 0 // goodToRemove MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) { using Key = UserPrivacy::Key; switch (key) { @@ -218,14 +366,64 @@ std::optional TLToKey(mtpTypeId type) { } return std::nullopt; } +#endif + +TLuserPrivacySetting KeyToTL(UserPrivacy::Key key) { + using Key = UserPrivacy::Key; + switch (key) { + case Key::LastSeen: return tl_userPrivacySettingShowStatus(); + case Key::ProfilePhoto: return tl_userPrivacySettingShowProfilePhoto(); + case Key::Forwards: + return tl_userPrivacySettingShowLinkInForwardedMessages(); + case Key::PhoneNumber: return tl_userPrivacySettingShowPhoneNumber(); + case Key::Invites: return tl_userPrivacySettingAllowChatInvites(); + case Key::Calls: return tl_userPrivacySettingAllowCalls(); + case Key::CallsPeer2Peer: + return tl_userPrivacySettingAllowPeerToPeerCalls(); + case Key::AddedByPhone: + return tl_userPrivacySettingAllowFindingByPhoneNumber(); + case Key::Voices: + return tl_userPrivacySettingAllowPrivateVoiceAndVideoNoteMessages(); + case Key::About: return tl_userPrivacySettingShowBio(); + } + Unexpected("Key in Api::UserPrivacy::KetToTL."); +} + +UserPrivacy::Key TLToKey(const TLuserPrivacySetting &setting) { + return setting.match([](const TLDuserPrivacySettingShowStatus &data) { + return UserPrivacy::Key::LastSeen; + }, [](const TLDuserPrivacySettingShowProfilePhoto &data) { + return UserPrivacy::Key::ProfilePhoto; + }, [](const TLDuserPrivacySettingShowLinkInForwardedMessages &data) { + return UserPrivacy::Key::Forwards; + }, [](const TLDuserPrivacySettingShowPhoneNumber &data) { + return UserPrivacy::Key::PhoneNumber; + }, [](const TLDuserPrivacySettingAllowChatInvites &data) { + return UserPrivacy::Key::Invites; + }, [](const TLDuserPrivacySettingAllowCalls &data) { + return UserPrivacy::Key::Calls; + }, [](const TLDuserPrivacySettingAllowPeerToPeerCalls &data) { + return UserPrivacy::Key::CallsPeer2Peer; + }, [](const TLDuserPrivacySettingAllowFindingByPhoneNumber &data) { + return UserPrivacy::Key::AddedByPhone; + }, [](TLDuserPrivacySettingAllowPrivateVoiceAndVideoNoteMessages) { + return UserPrivacy::Key::Voices; + }, [](const TLDuserPrivacySettingShowBio &data) { + return UserPrivacy::Key::About; + }); +} } // namespace UserPrivacy::UserPrivacy(not_null api) : _session(&api->session()) +#if 0 // goodToRemove , _api(&api->instance()) { +#endif +, _api(&api->sender()) { } +#if 0 // goodToRemove void UserPrivacy::save( Key key, const UserPrivacy::Rule &rule) { @@ -257,7 +455,52 @@ void UserPrivacy::save( _privacySaveRequests.emplace(keyTypeId, requestId); } +#endif + +void UserPrivacy::save( + Key key, + const UserPrivacy::Rule &rule) { + _api.request(TLsetUserPrivacySettingRules( + KeyToTL(key), + RulesToTL(rule) + )).send(); +} + +rpl::producer UserPrivacy::value(UserPrivacy::Key key) { + using Result = std::optional; + auto mapUpdate = [=](const TLupdate &update) { + return update.match([&]( + const TLDupdateUserPrivacySettingRules &rules) -> Result { + if (TLToKey(rules.vsetting()) != key) { + return std::nullopt; + } + return TLToRules(rules.vrules().data(), _session->data()); + }, [](const auto &) -> Result { + return std::nullopt; + }); + }; + + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + _api.request(TLgetUserPrivacySettingRules( + KeyToTL(key) + )).done([=](const TLRules &data) { + consumer.put_next(std::move(TLToRules(data, _session->data()))); + }).send(); + + _session->tdb().updates( + ) | rpl::map( + mapUpdate + ) | rpl::start_with_next([=](Result r) { + if (r) { + consumer.put_next(base::take(*r)); + } + }, lifetime); + return lifetime; + }; +} +#if 0 // goodToRemove void UserPrivacy::apply( mtpTypeId type, const TLRules &rules, @@ -309,5 +552,6 @@ auto UserPrivacy::value(Key key) -> rpl::producer { return _privacyChanges[key].events(); } } +#endif } // namespace Api diff --git a/Telegram/SourceFiles/api/api_user_privacy.h b/Telegram/SourceFiles/api/api_user_privacy.h index be8e452d95c91..9005e59b22fe6 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.h +++ b/Telegram/SourceFiles/api/api_user_privacy.h @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #pragma once #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" class ApiWrap; @@ -50,17 +51,20 @@ class UserPrivacy final { void save( Key key, const UserPrivacy::Rule &rule); +#if 0 // goodToRemove void apply( mtpTypeId type, const MTPVector &rules, bool allLoaded); void reload(Key key); +#endif rpl::producer value(Key key); private: const not_null _session; - + Tdb::Sender _api; +#if 0 // goodToRemove void pushPrivacy(Key key, const MTPVector &rules); base::flat_map _privacySaveRequests; @@ -70,6 +74,7 @@ class UserPrivacy final { std::map> _privacyChanges; MTP::Sender _api; +#endif }; diff --git a/Telegram/SourceFiles/api/api_views.cpp b/Telegram/SourceFiles/api/api_views.cpp index 6c8ec8df7f6d4..f2fd80dc92e78 100644 --- a/Telegram/SourceFiles/api/api_views.cpp +++ b/Telegram/SourceFiles/api/api_views.cpp @@ -15,11 +15,15 @@ For license and copyright information please follow this link: #include "history/history_item.h" #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + // Send channel views each second. -constexpr auto kSendViewsTimeout = crl::time(1000); +constexpr auto kSendViewsTimeout = crl::time(100); constexpr auto kPollExtendedMediaPeriod = 30 * crl::time(1000); constexpr auto kMaxPollPerRequest = 100; @@ -27,9 +31,14 @@ constexpr auto kMaxPollPerRequest = 100; ViewsManager::ViewsManager(not_null api) : _session(&api->session()) +#if 0 // mtp , _api(&api->instance()) +#endif , _incrementTimer([=] { viewsIncrement(); }) +#if 0 // mtp , _pollTimer([=] { sendPollRequests(); }) { +#endif +{ } void ViewsManager::scheduleIncrement(not_null item) { @@ -55,6 +64,7 @@ void ViewsManager::removeIncremented(not_null peer) { _incremented.remove(peer); } +#if 0 // mtp void ViewsManager::pollExtendedMedia(not_null item) { if (!item->isRegular()) { return; @@ -73,6 +83,7 @@ void ViewsManager::pollExtendedMedia(not_null item) { _pollTimer.callOnce(kPollExtendedMediaPeriod); } } +#endif void ViewsManager::viewsIncrement() { for (auto i = _toIncrement.begin(); i != _toIncrement.cend();) { @@ -81,6 +92,26 @@ void ViewsManager::viewsIncrement() { continue; } + const auto peer = i->first; + const auto finish = [=] { + _incrementRequests.erase(peer); + if (!_toIncrement.empty() && !_incrementTimer.isActive()) { + _incrementTimer.callOnce(kSendViewsTimeout); + } + }; + auto ids = ranges::views::all( + i->second + ) | ranges::views::transform([](MsgId id) { + return tl_int53(id.bare); + }) | ranges::to(); + const auto requestId = _session->sender().request(TLviewMessages( + peerToTdbChat(peer->id), + tl_vector(std::move(ids)), + tl_messageSourceChatHistory(), + tl_bool(false) + )).done(finish).fail(finish).send(); + +#if 0 // mtp QVector ids; ids.reserve(i->second.size()); for (const auto &msgId : i->second) { @@ -97,12 +128,14 @@ void ViewsManager::viewsIncrement() { }).fail([=](const MTP::Error &error, mtpRequestId requestId) { fail(error, requestId); }).afterDelay(5).send(); +#endif _incrementRequests.emplace(i->first, requestId); i = _toIncrement.erase(i); } } +#if 0 // mtp void ViewsManager::sendPollRequests() { const auto now = crl::now(); auto toRequest = base::flat_map, QVector>(); @@ -237,5 +270,6 @@ void ViewsManager::fail(const MTP::Error &error, mtpRequestId requestId) { _incrementTimer.callOnce(kSendViewsTimeout); } } +#endif } // namespace Api diff --git a/Telegram/SourceFiles/api/api_views.h b/Telegram/SourceFiles/api/api_views.h index 759d3c6a8a66b..00058f150c38e 100644 --- a/Telegram/SourceFiles/api/api_views.h +++ b/Telegram/SourceFiles/api/api_views.h @@ -7,7 +7,9 @@ For license and copyright information please follow this link: */ #pragma once +#if 0 // mtp #include "mtproto/sender.h" +#endif #include "base/timer.h" class ApiWrap; @@ -26,7 +28,9 @@ class ViewsManager final { void scheduleIncrement(not_null item); void removeIncremented(not_null peer); +#if 0 // mtp void pollExtendedMedia(not_null item); +#endif private: struct PollExtendedMediaRequest { @@ -37,6 +41,8 @@ class ViewsManager final { }; void viewsIncrement(); + +#if 0 // mtp void sendPollRequests(); void sendPollRequests( const base::flat_map< @@ -48,9 +54,12 @@ class ViewsManager final { const MTPmessages_MessageViews &result, mtpRequestId requestId); void fail(const MTP::Error &error, mtpRequestId requestId); +#endif const not_null _session; +#if 0 // mtp MTP::Sender _api; +#endif base::flat_map, base::flat_set> _incremented; base::flat_map, base::flat_set> _toIncrement; @@ -58,10 +67,12 @@ class ViewsManager final { base::flat_map> _incrementByRequest; base::Timer _incrementTimer; +#if 0 // mtp base::flat_map< not_null, PollExtendedMediaRequest> _pollRequests; base::Timer _pollTimer; +#endif }; diff --git a/Telegram/SourceFiles/api/api_websites.cpp b/Telegram/SourceFiles/api/api_websites.cpp index 2ea6b2a094a1f..8f86e927c7167 100644 --- a/Telegram/SourceFiles/api/api_websites.cpp +++ b/Telegram/SourceFiles/api/api_websites.cpp @@ -14,9 +14,18 @@ For license and copyright information please follow this link: #include "data/data_user.h" #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + +constexpr auto TestApiId = 17349; +constexpr auto SnapApiId = 611335; +constexpr auto DesktopApiId = 2040; + +#if 0 // mtp [[nodiscard]] Websites::Entry ParseEntry( not_null owner, const MTPDwebAuthorization &data) { @@ -35,12 +44,35 @@ namespace { result.active = Authorizations::ActiveDateString(result.activeTime); return result; } +#endif +[[nodiscard]] Websites::Entry ParseEntry( + not_null owner, + const TLDconnectedWebsite &data) { + auto result = Websites::Entry{ + .hash = data.vid().v, + .bot = owner->user(data.vbot_user_id()), + .platform = data.vplatform().v, + .domain = data.vdomain_name().v, + .browser = data.vbrowser().v, + .ip = data.vip_address().v, + .location = data.vlocation().v, + }; + result.activeTime = data.vlast_active_date().v + ? data.vlast_active_date().v + : data.vlog_in_date().v; + result.active = Authorizations::ActiveDateString(result.activeTime); + return result; +} + } // namespace Websites::Websites(not_null api) : _session(&api->session()) +#if 0 // mtp , _api(&api->instance()) { +#endif +, _api(&api->sender()) { } void Websites::reload() { @@ -48,6 +80,7 @@ void Websites::reload() { return; } +#if 0 // mtp _requestId = _api.request(MTPaccount_GetWebAuthorizations( )).done([=](const MTPaccount_WebAuthorizations &result) { _requestId = 0; @@ -64,12 +97,32 @@ void Websites::reload() { }).fail([=] { _requestId = 0; }).send(); +#endif + + using namespace Tdb; + _requestId = _api.request(TLgetConnectedWebsites( + )).done([=](const TLDconnectedWebsites &data) { + _requestId = 0; + _lastReceived = crl::now(); + const auto owner = &_session->data(); + _list = ranges::views::all( + data.vwebsites().v + ) | ranges::views::transform([&](const TLconnectedWebsite &auth) { + return ParseEntry(owner, auth.data()); + }) | ranges::to; + _listChanges.fire({}); + }).fail([=](const Error &error) { + _requestId = 0; + }).send(); } void Websites::cancelCurrentRequest() { +#if 0 // mtp _api.request(base::take(_requestId)).cancel(); +#endif } +#if 0 // mtp void Websites::requestTerminate( Fn &&done, Fn &&fail, @@ -101,6 +154,39 @@ void Websites::requestTerminate( send(MTPaccount_ResetWebAuthorizations()); } } +#endif + +void Websites::requestTerminate( + Fn &&done, + Fn &&fail, + std::optional hash, + UserData *botToBlock) { + const auto send = [&](auto request) { + _api.request( + std::move(request) + ).done([=, done = std::move(done)] { + done(); + if (hash) { + _list.erase( + ranges::remove(_list, *hash, &Entry::hash), + end(_list)); + } else { + _list.clear(); + } + _listChanges.fire({}); + }).fail( + std::move(fail) + ).send(); + }; + if (hash) { + send(TLdisconnectWebsite(tl_int64(*hash))); + if (botToBlock) { + botToBlock->session().api().blockedPeers().block(botToBlock); + } + } else { + send(TLdisconnectAllWebsites()); + } +} Websites::List Websites::list() const { return _list; diff --git a/Telegram/SourceFiles/api/api_websites.h b/Telegram/SourceFiles/api/api_websites.h index 1551ae4d4ed0b..e6c83abe78b7c 100644 --- a/Telegram/SourceFiles/api/api_websites.h +++ b/Telegram/SourceFiles/api/api_websites.h @@ -9,6 +9,8 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + class ApiWrap; namespace Main { @@ -22,7 +24,10 @@ class Websites final { explicit Websites(not_null api); struct Entry { +#if 0 // mtp uint64 hash = 0; +#endif + int64 hash = 0; not_null bot; TimeId activeTime = 0; @@ -33,8 +38,12 @@ class Websites final { void reload(); void cancelCurrentRequest(); void requestTerminate( +#if 0 // mtp Fn &&done, Fn &&fail, +#endif + Fn &&done, + Fn &&fail, std::optional hash = std::nullopt, UserData *botToBlock = nullptr); @@ -48,7 +57,10 @@ class Websites final { private: not_null _session; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; mtpRequestId _requestId = 0; List _list; diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index f32358c0604b3..4c7e9cb67e65b 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.cpp @@ -30,9 +30,13 @@ For license and copyright information please follow this link: #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + constexpr auto kContextReactionsLimit = 50; using Data::ReactionId; @@ -71,7 +75,10 @@ struct CachedRead { : data(Peers{ .unknown = true }) { } rpl::variable data; +#if 0 // mtp mtpRequestId requestId = 0; +#endif + RequestId requestId = 0; }; struct CachedReacted { @@ -79,7 +86,10 @@ struct CachedReacted { : data(PeersWithReactions{ .unknown = true }) { } rpl::variable data; +#if 0 // mtp mtpRequestId requestId = 0; +#endif + RequestId requestId = 0; }; struct Context { @@ -148,13 +158,19 @@ struct State { const auto i = contexts.find(key); for (auto &[item, entry] : i->second->cachedRead) { if (const auto requestId = entry.requestId) { +#if 0 // mtp item->history()->session().api().request(requestId).cancel(); +#endif + item->history()->session().sender().request(requestId).cancel(); } } for (auto &[item, map] : i->second->cachedReacted) { for (auto &[reaction, entry] : map) { if (const auto requestId = entry.requestId) { +#if 0 // mtp item->history()->session().api().request(requestId).cancel(); +#endif + item->history()->session().sender().request(requestId).cancel(); } } } @@ -175,13 +191,19 @@ struct State { ) | rpl::start_with_next([=](const Data::MessageUpdate &update) { const auto i = context->cachedRead.find(update.item); if (i != end(context->cachedRead)) { +#if 0 // mtp session->api().request(i->second.requestId).cancel(); +#endif + session->sender().request(i->second.requestId).cancel(); context->cachedRead.erase(i); } const auto j = context->cachedReacted.find(update.item); if (j != end(context->cachedReacted)) { for (auto &[reaction, entry] : j->second) { +#if 0 // mtp session->api().request(entry.requestId).cancel(); +#endif + session->sender().request(entry.requestId).cancel(); } context->cachedReacted.erase(j); } @@ -223,6 +245,7 @@ struct State { const auto context = PreparedContextAt(weak.data(), session); auto &entry = context->cacheRead(item); if (!entry.requestId) { +#if 0 // mtp entry.requestId = session->api().request( MTPmessages_GetMessageReadParticipants( item->history()->peer->input, @@ -239,6 +262,24 @@ struct State { .date = id.data().vdate().v, }); } +#endif + entry.requestId = session->sender().request( + TLgetMessageViewers( + peerToTdbChat(item->history()->peer->id), + tl_int53(item->id.bare)) + ).done([=](const TLmessageViewers &result) { + const auto &list = result.data().vviewers().v; + auto &entry = context->cacheRead(item); + entry.requestId = 0; + auto parsed = Peers(); + parsed.list.reserve(list.size()); + for (const auto &viewer : list) { + const auto &data = viewer.data(); + parsed.list.push_back({ + .peer = peerFromUser(data.vuser_id().v), + .date = data.vview_date().v, + }); + } entry.data = std::move(parsed); }).fail([=] { auto &entry = context->cacheRead(item); @@ -277,6 +318,7 @@ struct State { const auto context = PreparedContextAt(weak.data(), session); auto &entry = context->cacheReacted(item, reaction); if (!entry.requestId) { +#if 0 // mtp using Flag = MTPmessages_GetMessageReactionsList::Flag; entry.requestId = session->api().request( MTPmessages_GetMessageReactionsList( @@ -316,6 +358,36 @@ struct State { } entry.data = std::move(parsed); }); +#endif + entry.requestId = session->sender().request( + TLgetMessageAddedReactions( + peerToTdbChat(item->history()->peer->id), + tl_int53(item->id.bare), + ReactionToMaybeTL(reaction), + tl_string(), // offset + tl_int32(kContextReactionsLimit)) + ).done([=](const TLaddedReactions &result) { + auto &entry = context->cacheReacted(item, reaction); + entry.requestId = 0; + + const auto &data = result.data(); + const auto &list = data.vreactions().v; + auto parsed = PeersWithReactions{ + .fullReactionsCount = data.vtotal_count().v, + }; + parsed.list.reserve(list.size()); + for (const auto &reaction : list) { + const auto &data = reaction.data(); + const auto &type = data.vtype(); + parsed.list.push_back(PeerWithReaction{ + .peerWithDate = WhoReadPeer{ + .peer = peerFromSender(data.vsender_id()), + .date = data.vdate().v, + }, + .reaction = Data::ReactionFromTL(type), + }); + } + entry.data = std::move(parsed); }).fail([=] { auto &entry = context->cacheReacted(item, reaction); entry.requestId = 0; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 82c8190518748..3b525372bad18 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -100,22 +100,34 @@ For license and copyright information please follow this link: #include "storage/storage_media_prepare.h" #include "storage/storage_account.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_account.h" +#include "tdb/tdb_option.h" +#include "tdb/tdb_tl_scheme.h" +#include "data/data_secret_chat.h" + namespace { // Save draft to the cloud with 1 sec extra delay. constexpr auto kSaveCloudDraftTimeout = 1000; +#if 0 // mtp constexpr auto kTopPromotionInterval = TimeId(60 * 60); constexpr auto kTopPromotionMinDelay = TimeId(10); +#endif +constexpr auto kStickersByEmojiCount = 100; constexpr auto kSmallDelayMs = 5; constexpr auto kReadFeaturedSetsTimeout = crl::time(1000); constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000); constexpr auto kStickersByEmojiInvalidateTimeout = crl::time(6 * 1000); constexpr auto kNotifySettingSaveTimeout = crl::time(1000); constexpr auto kDialogsFirstLoad = 20; -constexpr auto kDialogsPerPage = 500; +constexpr auto kDialogsPerPage = 100; +#if 0 // mtp constexpr auto kStatsSessionKillTimeout = 10 * crl::time(1000); +#endif +using namespace Tdb; using PhotoFileLocationId = Data::PhotoFileLocationId; using DocumentFileLocationId = Data::DocumentFileLocationId; using UpdatedFileReferences = Data::UpdatedFileReferences; @@ -149,18 +161,38 @@ void ShowChannelsLimitBox(not_null peer) { } // namespace +struct ApiWrap::DialogsLoadState { + TimeId offsetDate = 0; + MsgId offsetId = 0; + PeerData *offsetPeer = nullptr; + RequestId requestId = 0; + bool listReceived = false; + + RequestId pinnedRequestId = 0; + bool pinnedReceived = false; + + bool firstRequested = false; +}; + ApiWrap::ApiWrap(not_null session) +#if 0 // mtp : MTP::Sender(&session->account().mtp()) , _session(session) , _messageDataResolveDelayed([=] { resolveMessageDatas(); }) , _webPagesTimer([=] { resolveWebPages(); }) +#endif +: _session(session) , _draftsSaveTimer([=] { saveDraftsToCloud(); }) , _featuredSetsReadTimer([=] { readFeaturedSets(); }) , _dialogsLoadState(std::make_unique()) , _fileLoader(std::make_unique(kFileLoaderQueueStopTimeout)) +#if 0 // mtp , _topPromotionTimer([=] { refreshTopPromotion(); }) +#endif , _updateNotifyTimer([=] { sendNotifySettingsUpdates(); }) +#if 0 // mtp , _statsSessionKillTimer([=] { checkStatsSessions(); }) +#endif , _authorizations(std::make_unique(this)) , _attachedStickers(std::make_unique(this)) , _blockedPeers(std::make_unique(this)) @@ -184,19 +216,23 @@ ApiWrap::ApiWrap(not_null session) crl::on_main(session, [=] { // You can't use _session->lifetime() in the constructor, // only queued, because it is not constructed yet. +#if 0 // mtp _session->data().chatsFilters().changed( ) | rpl::filter([=] { return _session->data().chatsFilters().archiveNeeded(); }) | rpl::start_with_next([=] { requestMoreDialogsIfNeeded(); }, _session->lifetime()); +#endif setupSupportMode(); +#if 0 // mtp Core::App().settings().proxy().connectionTypeValue( ) | rpl::start_with_next([=] { refreshTopPromotion(); }, _session->lifetime()); +#endif }); } @@ -214,6 +250,14 @@ Api::Updates &ApiWrap::updates() const { return _session->updates(); } +Account &ApiWrap::tdb() const { + return _session->tdb(); +} + +Sender &ApiWrap::sender() const { + return _session->sender(); +} + void ApiWrap::setupSupportMode() { if (!_session->supportMode()) { return; @@ -226,6 +270,7 @@ void ApiWrap::setupSupportMode() { }, _session->lifetime()); } +#if 0 // mtp void ApiWrap::requestChangelog( const QString &sinceVersion, Fn callback) { @@ -302,10 +347,12 @@ void ApiWrap::topPromotionDone(const MTPhelp_PromoData &proxy) { data.vpsa_message().value_or_empty()); }); } +#endif void ApiWrap::requestDeepLinkInfo( const QString &path, Fn callback) { +#if 0 // mtp request(_deepLinkInfoRequestId).cancel(); _deepLinkInfoRequestId = request(MTPhelp_GetDeepLinkInfo( MTP_string(path) @@ -320,11 +367,21 @@ void ApiWrap::requestDeepLinkInfo( data.ventities().value_or_empty()) }, data.is_update_app()); } +#endif + sender().request(_deepLinkInfoRequestId).cancel(); + _deepLinkInfoRequestId = sender().request(TLgetDeepLinkInfo( + tl_string(path) + )).done([=](const TLDdeepLinkInfo &result) { + _deepLinkInfoRequestId = 0; + callback( + Api::FormattedTextFromTdb(result.vtext()), + result.vneed_update_application().v); }).fail([=] { _deepLinkInfoRequestId = 0; }).send(); } +#if 0 // mtp void ApiWrap::requestTermsUpdate() { if (_termsUpdateRequestId) { return; @@ -373,15 +430,44 @@ void ApiWrap::requestTermsUpdate() { requestTermsUpdate(); }).send(); } +#endif void ApiWrap::acceptTerms(bytes::const_span id) { + sender().request(TLacceptTermsOfService( + tl_string(QByteArray::fromRawData( + reinterpret_cast(id.data()), + id.size() + ).toStdString()) + )).send(); +#if 0 // mtp request(MTPhelp_AcceptTermsOfService( MTP_dataJSON(MTP_bytes(id)) )).done([=] { requestTermsUpdate(); }).send(); +#endif } +void ApiWrap::checkChatInvite( + const QString &hash, + FnMut done, + Fn fail) { + sender().request(base::take(_checkInviteRequestId)).cancel(); + _checkInviteRequestId = sender().request(TLcheckChatInviteLink( + tl_string(hash) + )).done(std::move(done)).fail(std::move(fail)).send(); +} + +void ApiWrap::checkFilterInvite( + const QString &slug, + FnMut done, + Fn fail) { + sender().request(base::take(_checkFilterInviteRequestId)).cancel(); + _checkFilterInviteRequestId = sender().request( + TLcheckChatFolderInviteLink(tl_string(slug)) + ).done(std::move(done)).fail(std::move(fail)).send(); +} +#if 0 // mtp void ApiWrap::checkChatInvite( const QString &hash, FnMut done, @@ -401,9 +487,11 @@ void ApiWrap::checkFilterInvite( MTPchatlists_CheckChatlistInvite(MTP_string(slug)) ).done(std::move(done)).fail(std::move(fail)).send(); } +#endif void ApiWrap::savePinnedOrder(Data::Folder *folder) { const auto &order = _session->data().pinnedChatsOrder(folder); +#if 0 // mtp const auto input = [](Dialogs::Key key) { if (const auto history = key.history()) { return MTP_inputDialogPeer(history->peer->input); @@ -423,10 +511,40 @@ void ApiWrap::savePinnedOrder(Data::Folder *folder) { MTP_int(folder ? folder->id() : 0), MTP_vector(peers) )).send(); +#endif + const auto id = [](const Dialogs::Key &key) { + if (const auto history = key.history()) { + return peerToTdbChat(history->peer->id); + } + Unexpected("Key type in pinnedDialogsOrder()."); + }; + auto chats = QVector(); + chats.reserve(order.size()); + ranges::transform( + order, + ranges::back_inserter(chats), + id); + sender().request(TLsetPinnedChats( + (folder ? tl_chatListArchive() : tl_chatListMain()), + tl_vector(chats) + )).send(); } void ApiWrap::savePinnedOrder(not_null forum) { const auto &order = _session->data().pinnedChatsOrder(forum); + const auto input = [](Dialogs::Key key) { + if (const auto topic = key.topic()) { + return tl_int53(topic->rootId().bare); + } + Unexpected("Key type in pinnedDialogsOrder()."); + }; + sender().request(TLsetPinnedForumTopics( + peerToTdbChat(forum->channel()->id), + tl_vector(order + | ranges::views::transform(input) + | ranges::to) + )).send(); +#if 0 // mtp const auto input = [](Dialogs::Key key) { if (const auto topic = key.topic()) { return MTP_int(topic->rootId().bare); @@ -446,12 +564,32 @@ void ApiWrap::savePinnedOrder(not_null forum) { )).done([=](const MTPUpdates &result) { applyUpdates(result); }).send(); +#endif } void ApiWrap::toggleHistoryArchived( not_null history, bool archived, Fn callback) { + if (const auto already = _historyArchivedRequests.take(history)) { + sender().request(already->first).cancel(); + } + const auto isPinned = history->isPinnedDialog(0); + const auto requestId = sender().request(TLaddChatToList( + peerToTdbChat(history->peer->id), + (archived ? tl_chatListArchive() : tl_chatListMain()) + )).done([=] { + if (const auto data = _historyArchivedRequests.take(history)) { + data->second(); + } + if (isPinned) { + _session->data().notifyPinnedDialogsOrderUpdated(); + } + }).fail([=] { + _historyArchivedRequests.remove(history); + }).send(); + _historyArchivedRequests.emplace(history, requestId, callback); +#if 0 // mtp if (const auto already = _historyArchivedRequests.take(history)) { request(already->first).cancel(); } @@ -480,8 +618,10 @@ void ApiWrap::toggleHistoryArchived( _historyArchivedRequests.remove(history); }).send(); _historyArchivedRequests.emplace(history, requestId, callback); +#endif } +#if 0 // mtp void ApiWrap::sendMessageFail( const MTP::Error &error, not_null peer, @@ -489,6 +629,7 @@ void ApiWrap::sendMessageFail( FullMsgId itemId) { sendMessageFail(error.type(), peer, randomId, itemId); } +#endif void ApiWrap::sendMessageFail( const QString &error, @@ -496,6 +637,8 @@ void ApiWrap::sendMessageFail( uint64 randomId, FullMsgId itemId) { const auto show = ShowForPeer(peer); + +#if 0 // mtp if (show && error == u"PEER_FLOOD"_q) { show->showBox( Ui::MakeInformBox( @@ -554,12 +697,26 @@ void ApiWrap::sendMessageFail( } } } +#endif } void ApiWrap::requestMessageData( PeerData *peer, MsgId msgId, Fn done) { + Expects(peer != nullptr); + + sender().request(TLgetMessage( + peerToTdbChat(peer->id), + tl_int53(msgId.bare) + )).done([=](const TLmessage &result) { + session().data().processMessage(result, NewMessageType::Existing); + done(); + }).fail([=] { + done(); + }).send(); + +#if 0 // mtp auto &requests = (peer && peer->isChannel()) ? _channelMessageDataRequests[peer->asChannel()][msgId] : _messageDataRequests[msgId]; @@ -569,8 +726,10 @@ void ApiWrap::requestMessageData( if (!requests.requestId) { _messageDataResolveDelayed.call(); } +#endif } +#if 0 // mtp QVector ApiWrap::collectMessageIds( const MessageDataRequests &requests) { auto result = QVector(); @@ -800,11 +959,13 @@ QString ApiWrap::exportDirectStoryLink(not_null story) { }).send(); return current; } +#endif void ApiWrap::requestContacts() { if (_session->data().contactsLoaded().current() || _contactsRequestId) { return; } +#if 0 // mtp _contactsRequestId = request(MTPcontacts_GetContacts( MTP_long(0) // hash )).done([=](const MTPcontacts_Contacts &result) { @@ -827,6 +988,20 @@ void ApiWrap::requestContacts() { }).fail([=] { _contactsRequestId = 0; }).send(); +#endif + _contactsRequestId = sender().request(TLgetContacts( + )).done([=](const TLusers &result) { + _contactsRequestId = 0; + const auto selfUserId = _session->userId(); + for (const auto &contact : result.data().vuser_ids().v) { + if (peerFromUser(contact.v) == selfUserId) { + _session->user()->setIsContact(true); + } + } + _session->data().contactsLoaded() = true; + }).fail([=](const Error &error) { + _contactsRequestId = 0; + }).send(); } void ApiWrap::requestDialogs(Data::Folder *folder) { @@ -846,6 +1021,7 @@ void ApiWrap::requestMoreDialogs(Data::Folder *folder) { return; } +#if 0 // mtp const auto firstLoad = !state->offsetDate; const auto loadCount = firstLoad ? kDialogsFirstLoad : kDialogsPerPage; const auto flags = MTPmessages_GetDialogs::Flag::f_exclude_pinned @@ -905,6 +1081,34 @@ void ApiWrap::requestMoreDialogs(Data::Folder *folder) { if (!state->pinnedReceived) { requestPinnedDialogs(folder); } +#endif + + const auto firstLoad = !state->firstRequested; + const auto loadCount = firstLoad ? kDialogsFirstLoad : kDialogsPerPage; + state->requestId = sender().request(TLloadChats( + folder ? tl_chatListArchive() : tl_chatListMain(), + tl_int32(loadCount) + )).done([=] { + const auto state = dialogsLoadState(folder); + if (!state) { + return; + } + state->pinnedReceived = true; + state->firstRequested = true; + state->requestId = 0; + requestMoreDialogsIfNeeded(); + }).fail([=] { + const auto state = dialogsLoadState(folder); + if (!state) { + return; + } + state->pinnedReceived = true; + state->firstRequested = true; + state->requestId = 0; + state->listReceived = true; + dialogsLoadFinish(folder); // may kill 'state'. + }).send(); + if (!folder) { refreshDialogsLoadBlocked(); } @@ -923,6 +1127,7 @@ void ApiWrap::refreshDialogsLoadBlocked() { } void ApiWrap::requestMoreDialogsIfNeeded() { +#if 0 // mtp const auto dialogsReady = !_dialogsLoadState || _dialogsLoadState->listReceived; if (_session->data().chatsFilters().loadNextExceptions(dialogsReady)) { @@ -938,9 +1143,17 @@ void ApiWrap::requestMoreDialogsIfNeeded() { requestMoreDialogs(folder); } } +#endif + if (_dialogsLoadState && !_dialogsLoadState->listReceived) { + if (_dialogsLoadState->requestId) { + return; + } + requestDialogs(nullptr); + } requestContacts(); } +#if 0 // mtp void ApiWrap::updateDialogsOffset( Data::Folder *folder, const QVector &dialogs, @@ -987,6 +1200,7 @@ void ApiWrap::updateDialogsOffset( } } } +#endif auto ApiWrap::dialogsLoadState(Data::Folder *folder) -> DialogsLoadState* { if (!folder) { @@ -1015,6 +1229,7 @@ void ApiWrap::dialogsLoadFinish(Data::Folder *folder) { } } +#if 0 // mtp void ApiWrap::requestPinnedDialogs(Data::Folder *folder) { const auto state = dialogsLoadState(folder); if (!state || state->pinnedReceived || state->pinnedRequestId) { @@ -1047,6 +1262,7 @@ void ApiWrap::requestPinnedDialogs(Data::Folder *folder) { finalize(); }).send(); } +#endif void ApiWrap::requestMoreBlockedByDateDialogs() { if (!_dialogsLoadState) { @@ -1072,6 +1288,7 @@ void ApiWrap::requestWallPaper( const QString &slug, Fn done, Fn fail) { +#if 0 // mtp if (_wallPaperSlug != slug) { _wallPaperSlug = slug; if (_wallPaperRequestId) { @@ -1096,6 +1313,31 @@ void ApiWrap::requestWallPaper( fail(); } }).fail([=](const MTP::Error &error) { +#endif + if (_wallPaperSlug != slug) { + _wallPaperSlug = slug; + if (_wallPaperRequestId) { + sender().request(base::take(_wallPaperRequestId)).cancel(); + } + } + _wallPaperDone = std::move(done); + _wallPaperFail = std::move(fail); + if (_wallPaperRequestId) { + return; + } + _wallPaperRequestId = sender().request(TLsearchBackground( + tl_string(slug) + )).done([=](const TLbackground &result) { + _wallPaperRequestId = 0; + _wallPaperSlug = QString(); + if (const auto paper = Data::WallPaper::Create(_session, result)) { + if (const auto done = base::take(_wallPaperDone)) { + done(*paper); + } + } else if (const auto fail = base::take(_wallPaperFail)) { + fail(); + } + }).fail([=] { _wallPaperRequestId = 0; _wallPaperSlug = QString(); if (const auto fail = base::take(_wallPaperFail)) { @@ -1105,10 +1347,65 @@ void ApiWrap::requestWallPaper( } void ApiWrap::requestFullPeer(not_null peer) { + if (const auto user = peer->secretChatUser()) { + requestFullPeer(user); + return; + } if (_fullPeerRequests.contains(peer)) { return; } + const auto requestId = [&] { + const auto failHandler = [=](const Tdb::Error &error) { + _fullPeerRequests.remove(peer); + migrateFail(peer, error.message); + }; + const auto finish = [=] { + _fullPeerRequests.remove(peer); + _session->changes().peerUpdated( + peer, + Data::PeerUpdate::Flag::FullInfo); + }; + if (const auto user = peer->asUser()) { + if (_session->supportMode()) { + _session->supportHelper().refreshInfo(user); + } + return sender().request(TLgetUserFullInfo( + tl_int53(peerToUser(user->id).bare) + )).done([=](const TLDuserFullInfo &data) { + Data::ApplyUserUpdate(user, data); + finish(); +#if 0 // mtp + if (user == _session->user() && !_session->validateSelf(fields.vid().v)) { + constexpr auto kRequestUserAgainTimeout = crl::time(10000); + base::call_delayed(kRequestUserAgainTimeout, _session, [=] { + requestFullPeer(user); + }); + return; + } +#endif + }).fail(failHandler).send(); + } else if (const auto chat = peer->asChat()) { + return sender().request(TLgetBasicGroupFullInfo( + tl_int53(peerToChat(chat->id).bare) + )).done([=](const TLDbasicGroupFullInfo &data) { + Data::ApplyChatUpdate(chat, data); + finish(); + }).fail(failHandler).send(); + } else if (const auto channel = peer->asChannel()) { + return sender().request(TLgetSupergroupFullInfo( + tl_int53(peerToChannel(channel->id).bare) + )).done([=](const TLDsupergroupFullInfo &data) { + Data::ApplyChannelUpdate(channel, data); + finish(); + migrateDone(channel, channel); + }).fail(failHandler).send(); + } + Unexpected("Peer type in requestFullPeer."); + }(); + _fullPeerRequests.emplace(peer, requestId); + +#if 0 // goodToRemove const auto requestId = [&] { const auto failHandler = [=](const MTP::Error &error) { _fullPeerRequests.remove(peer); @@ -1144,8 +1441,10 @@ void ApiWrap::requestFullPeer(not_null peer) { Unexpected("Peer type in requestFullPeer."); }(); _fullPeerRequests.emplace(peer, requestId); +#endif } +#if 0 // mtp void ApiWrap::processFullPeer( not_null peer, const MTPmessages_ChatFull &result) { @@ -1221,6 +1520,7 @@ void ApiWrap::requestPeerSettings(not_null peer) { _requestedPeerSettings.erase(peer); }).send(); } +#endif void ApiWrap::migrateChat( not_null chat, @@ -1262,6 +1562,7 @@ void ApiWrap::migrateChat( return; } +#if 0 // mtp request(MTPmessages_MigrateChat( chat->inputChat )).done([=](const MTPUpdates &result) { @@ -1281,6 +1582,20 @@ void ApiWrap::migrateChat( }).fail([=](const MTP::Error &error) { migrateFail(chat, error.type()); }).send(); +#endif + sender().request(Tdb::TLupgradeBasicGroupChatToSupergroupChat( + peerToTdbChat(chat->id) + )).done([=](const Tdb::TLchat &result) { + const auto peer = session().data().processPeer(result); + session().changes().sendNotifications(); + + if (auto handlers = _migrateCallbacks.take(chat)) { + _migrateCallbacks.emplace(peer, std::move(*handlers)); + } + requestFullPeer(peer); + }).fail([=](const Tdb::Error &error) { + migrateFail(chat, error.message); + }).send(); } void ApiWrap::migrateDone( @@ -1311,21 +1626,42 @@ void ApiWrap::migrateFail(not_null peer, const QString &error) { void ApiWrap::markContentsRead( const base::flat_set> &items) { +#if 0 // mtp auto markedIds = QVector(); auto channelMarkedIds = base::flat_map< not_null, QVector>(); markedIds.reserve(items.size()); +#endif + auto viewed = base::flat_map, QVector>(); for (const auto &item : items) { if (!item->markContentsRead(true) || !item->isRegular()) { continue; + } else if (item->isUnreadMedia()) { + sender().request(TLopenMessageContent( + peerToTdbChat(item->history()->peer->id), + tl_int53(item->id.bare) + )).send(); + } else { + viewed[item->history()].push_back(tl_int53(item->id.bare)); } +#if 0 // mtp if (const auto channel = item->history()->peer->asChannel()) { channelMarkedIds[channel].push_back(MTP_int(item->id)); } else { markedIds.push_back(MTP_int(item->id)); } +#endif + } + for (const auto &[history, ids] : viewed) { + sender().request(TLviewMessages( + peerToTdbChat(history->peer->id), + tl_vector(ids), + tl_messageSourceChatHistory(), + tl_bool(false) + )).send(); } +#if 0 // mtp if (!markedIds.isEmpty()) { request(MTPmessages_ReadMessageContents( MTP_vector(markedIds) @@ -1339,12 +1675,26 @@ void ApiWrap::markContentsRead( MTP_vector(channelIds.second) )).send(); } +#endif } void ApiWrap::markContentsRead(not_null item) { if (!item->markContentsRead(true) || !item->isRegular()) { return; + } else if (item->isUnreadMedia()) { + sender().request(TLopenMessageContent( + peerToTdbChat(item->history()->peer->id), + tl_int53(item->id.bare) + )).send(); + } else { + sender().request(TLviewMessages( + peerToTdbChat(item->history()->peer->id), + tl_vector(1, tl_int53(item->id.bare)), + tl_messageSourceChatHistory(), + tl_bool(false) + )).send(); } +#if 0 // mtp const auto ids = MTP_vector(1, MTP_int(item->id)); if (const auto channel = item->history()->peer->asChannel()) { request(MTPchannels_ReadMessageContents( @@ -1358,6 +1708,7 @@ void ApiWrap::markContentsRead(not_null item) { applyAffectedMessages(result); }).send(); } +#endif } void ApiWrap::deleteAllFromParticipant( @@ -1381,6 +1732,11 @@ void ApiWrap::deleteAllFromParticipant( void ApiWrap::deleteAllFromParticipantSend( not_null channel, not_null from) { + sender().request(TLdeleteChatMessagesBySender( + peerToTdbChat(channel->id), + peerToSender(from->id) + )).send(); +#if 0 // mtp request(MTPchannels_DeleteParticipantHistory( channel->inputChannel, from->input @@ -1392,6 +1748,7 @@ void ApiWrap::deleteAllFromParticipantSend( history->requestChatListMessage(); } }).send(); +#endif } void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) { @@ -1405,6 +1762,15 @@ void ApiWrap::requestStickerSets() { if (info.id) { continue; } + info.id = sender().request(TLgetStickerSet( + tl_int64(id) + )).done([=, setId = id](const TLstickerSet &result) { + _stickerSetRequests.remove(setId); + _session->data().stickers().feedSetFull(result); + }).fail([=, setId = id] { + _stickerSetRequests.remove(setId); + }).send(); +#if 0 // mtp info.id = request(MTPmessages_GetStickerSet( MTP_inputStickerSetID( MTP_long(id), @@ -1415,6 +1781,7 @@ void ApiWrap::requestStickerSets() { }).fail([=, setId = id] { _stickerSetRequests.remove(setId); }).afterDelay(kSmallDelayMs).send(); +#endif } } @@ -1434,17 +1801,39 @@ void ApiWrap::saveStickerSets( ? _masksReorderRequestId : _stickersReorderRequestId; }; +#if 0 // mtp for (auto requestId : base::take(setDisenableRequests)) { request(requestId).cancel(); } request(base::take(reorderRequestId())).cancel(); request(base::take(_stickersClearRecentRequestId)).cancel(); request(base::take(_stickersClearRecentAttachedRequestId)).cancel(); +#endif + for (auto requestId : base::take(setDisenableRequests)) { + sender().request(requestId).cancel(); + } + sender().request(base::take(reorderRequestId())).cancel(); + sender().request(base::take(_stickersClearRecentRequestId)).cancel(); + sender().request( + base::take(_stickersClearRecentAttachedRequestId)).cancel(); const auto stickersSaveOrder = [=] { if (localOrder.size() < 2) { return; } + auto order = QVector(); + order.reserve(localOrder.size()); + for (const auto setId : std::as_const(localOrder)) { + order.push_back(tl_int53(setId)); + } + reorderRequestId() = sender().request(TLreorderInstalledStickerSets( + ((type == Data::StickersType::Emoji) + ? tl_stickerTypeCustomEmoji() + : (type == Data::StickersType::Masks) + ? tl_stickerTypeMask() + : tl_stickerTypeRegular()), + tl_vector(std::move(order)) +#if 0 // mtp QVector mtpOrder; mtpOrder.reserve(localOrder.size()); for (const auto setId : std::as_const(localOrder)) { @@ -1460,6 +1849,7 @@ void ApiWrap::saveStickerSets( reorderRequestId() = request(MTPmessages_ReorderStickerSets( MTP_flags(flags), MTP_vector(mtpOrder) +#endif )).done([=] { reorderRequestId() = 0; }).fail([=] { @@ -1529,6 +1919,18 @@ void ApiWrap::saveStickerSets( const auto isAttached = (removedSetId == Data::Stickers::CloudRecentAttachedSetId); + const auto clearRequestId = [=]() -> mtpRequestId & { + return isAttached + ? _stickersClearRecentAttachedRequestId + : _stickersClearRecentRequestId; + }; + const auto finish = [=] { + clearRequestId() = 0; + }; + clearRequestId() = sender().request(TLclearRecentStickers( + tl_bool(isAttached) + )).done(finish).fail(finish).send(); +#if 0 // mtp const auto flags = isAttached ? MTPmessages_ClearRecentStickers::Flag::f_attached : MTPmessages_ClearRecentStickers::Flags(0); @@ -1543,6 +1945,7 @@ void ApiWrap::saveStickerSets( requestId = request(MTPmessages_ClearRecentStickers( MTP_flags(flags) )).done(finish).fail(finish).send(); +#endif continue; } @@ -1563,6 +1966,17 @@ void ApiWrap::saveStickerSets( const auto special = !!(set->flags & Flag::Special); const auto emoji = !!(set->flags & Flag::Emoji); const auto locked = (set->locked > 0); + + const auto requestId = sender().request(TLchangeStickerSet( + tl_int64(set->id), + tl_bool(false), + tl_bool(false) + )).done([=](const TLok &result, mtpRequestId requestId) { + stickerSetDisenabled(requestId); + }).fail([=](const Error &error, mtpRequestId requestId) { + stickerSetDisenabled(requestId); + }).send(); +#if 0 // mtp const auto setId = set->mtpInput(); auto requestId = request(MTPmessages_UninstallStickerSet( @@ -1572,6 +1986,7 @@ void ApiWrap::saveStickerSets( }).fail([=](const MTP::Error &error, mtpRequestId requestId) { stickerSetDisenabled(requestId); }).afterDelay(kSmallDelayMs).send(); +#endif setDisenableRequests.insert(requestId); @@ -1614,6 +2029,16 @@ void ApiWrap::saveStickerSets( const auto set = it->second.get(); const auto archived = !!(set->flags & Flag::Archived); if (archived && !localRemoved.contains(set->id)) { + const auto requestId = sender().request(TLchangeStickerSet( + tl_int64(set->id), + tl_bool(true), + tl_bool(false) + )).done([=](const TLok &result, mtpRequestId requestId) { + stickerSetDisenabled(requestId); + }).fail([=](const Error &error, mtpRequestId requestId) { + stickerSetDisenabled(requestId); + }).send(); +#if 0 // mtp const auto mtpSetId = set->mtpInput(); const auto requestId = request(MTPmessages_InstallStickerSet( @@ -1628,6 +2053,7 @@ void ApiWrap::saveStickerSets( mtpRequestId requestId) { stickerSetDisenabled(requestId); }).afterDelay(kSmallDelayMs).send(); +#endif setDisenableRequests.insert(requestId); @@ -1690,7 +2116,9 @@ void ApiWrap::saveStickerSets( if (setDisenableRequests.empty()) { stickersSaveOrder(); } else { +#if 0 // mtp requestSendDelayed(); +#endif } } @@ -1700,6 +2128,46 @@ void ApiWrap::joinChannel(not_null channel) { channel, Data::PeerUpdate::Flag::ChannelAmIn); } else if (!_channelAmInRequests.contains(channel)) { + const auto requestId = sender().request(TLjoinChat( + peerToTdbChat(channel->id) + )).done([=] { + _channelAmInRequests.remove(channel); + }).fail([=](const Error &error) { + const auto &type = error.message; + + const auto show = ShowForPeer(channel); + if (type == u"CHANNEL_PRIVATE"_q + && channel->invitePeekExpires()) { + channel->privateErrorReceived(); + } else if (type == u"CHANNELS_TOO_MUCH"_q) { + ShowChannelsLimitBox(channel); + } else { + const auto text = [&] { + if (type == u"INVITE_REQUEST_SENT"_q) { + return channel->isMegagroup() + ? tr::lng_group_request_sent(tr::now) + : tr::lng_group_request_sent_channel(tr::now); + } else if (type == u"CHANNEL_PRIVATE"_q + || type == u"CHANNEL_PUBLIC_GROUP_NA"_q + || type == u"USER_BANNED_IN_CHANNEL"_q) { + return channel->isMegagroup() + ? tr::lng_group_not_accessible(tr::now) + : tr::lng_channel_not_accessible(tr::now); + } else if (type == u"USERS_TOO_MUCH"_q) { + return tr::lng_group_full(tr::now); + } + return QString(); + }(); + if (!text.isEmpty() && show->valid()) { + show->showToast({ + .text = { text }, + .duration = kJoinErrorDuration, + }); + } + } + _channelAmInRequests.remove(channel); + }).send(); +#if 0 // mtp const auto requestId = request(MTPchannels_JoinChannel( channel->inputChannel )).done([=](const MTPUpdates &result) { @@ -1737,12 +2205,24 @@ void ApiWrap::joinChannel(not_null channel) { } _channelAmInRequests.remove(channel); }).send(); +#endif _channelAmInRequests.emplace(channel, requestId); } } void ApiWrap::leaveChannel(not_null channel) { + if (!_channelAmInRequests.contains(channel)) { + auto requestId = sender().request(TLleaveChat( + peerToTdbChat(channel->id) + )).done([=] { + _channelAmInRequests.remove(channel); + }).fail([=] { + _channelAmInRequests.remove(channel); + }).send(); + _channelAmInRequests.emplace(channel, requestId); + } +#if 0 // mtp if (!channel->amIn()) { session().changes().peerUpdated( channel, @@ -1759,8 +2239,10 @@ void ApiWrap::leaveChannel(not_null channel) { _channelAmInRequests.emplace(channel, requestId); } +#endif } +#if 0 // mtp void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) { const auto bad = peer.match([](const MTPDinputNotifyUsers &) { return false; @@ -1842,6 +2324,40 @@ void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) { }).send(); _notifySettingRequests.emplace(key, requestId); } +#endif + +void ApiWrap::requestDefaultNotifySettings() { + requestDefaultNotifySettings(Data::DefaultNotify::User); + requestDefaultNotifySettings(Data::DefaultNotify::Group); + requestDefaultNotifySettings(Data::DefaultNotify::Broadcast); +} + +void ApiWrap::requestDefaultNotifySettings(Data::DefaultNotify type) { + const auto key = [&]() -> NotifySettingsKey { + switch (type) { + case Data::DefaultNotify::User: return { peerFromUser(1) }; + case Data::DefaultNotify::Group: return { peerFromChat(1) }; + case Data::DefaultNotify::Broadcast: return { peerFromChannel(1) }; + } + Unexpected("Type in requestDefaultNotifySettings."); + }(); + if (_notifySettingRequests.find(key) != end(_notifySettingRequests)) { + return; + } + const auto requestId = sender().request(TLgetScopeNotificationSettings( + DefaultNotifyScope(type) + )).done([=](const TLscopeNotificationSettings &result) { + _session->data().notifySettings().apply(type, result); + _notifySettingRequests.erase(key); + }).fail([=] { + _session->data().notifySettings().apply( + type, + Data::PeerNotifySettings::ScopeDefault()); + _notifySettingRequests.erase(key); + }).send(); + + _notifySettingRequests.emplace(key, requestId); +} void ApiWrap::updateNotifySettingsDelayed( not_null thread) { @@ -1873,25 +2389,44 @@ void ApiWrap::updateNotifySettingsDelayed(Data::DefaultNotify type) { void ApiWrap::sendNotifySettingsUpdates() { _updateNotifyQueueLifetime.destroy(); for (const auto topic : base::take(_updateNotifyTopics)) { +#if 0 // mtp request(MTPaccount_UpdateNotifySettings( MTP_inputNotifyForumTopic( topic->channel()->input, MTP_int(topic->rootId())), topic->notify().serialize() )).afterDelay(kSmallDelayMs).send(); +#endif + sender().request(TLsetForumTopicNotificationSettings( + peerToTdbChat(topic->channel()->id), + tl_int53(topic->rootId().bare), + topic->notify().serialize() + )).send(); } for (const auto peer : base::take(_updateNotifyPeers)) { + sender().request(TLsetChatNotificationSettings( + peerToTdbChat(peer->id), + peer->notify().serialize() + )).send(); +#if 0 // mtp request(MTPaccount_UpdateNotifySettings( MTP_inputNotifyPeer(peer->input), peer->notify().serialize() )).afterDelay(kSmallDelayMs).send(); +#endif } const auto &settings = session().data().notifySettings(); for (const auto type : base::take(_updateNotifyDefaults)) { + sender().request(TLsetScopeNotificationSettings( + DefaultNotifyScope(type), + settings.defaultSettings(type).serializeDefault() + )).send(); +#if 0 // mtp request(MTPaccount_UpdateNotifySettings( Data::DefaultNotifyToMTP(type), settings.defaultSettings(type).serialize() )).afterDelay(kSmallDelayMs).send(); +#endif } session().mtp().sendAnything(); } @@ -1903,6 +2438,17 @@ void ApiWrap::saveDraftToCloudDelayed(not_null thread) { } } +void ApiWrap::saveDraftToCloudNow(not_null thread) { + _draftsSaveRequestIds.emplace(base::make_weak(thread), 0); + _draftsSaveTimer.cancel(); + saveDraftsToCloud(); +} + +void ApiWrap::manualClearCloudDraft(not_null thread) { + thread->owningHistory()->clearCloudDraft(thread->topicRootId()); + saveDraftToCloudNow(thread); +} + void ApiWrap::updatePrivacyLastSeens() { const auto now = base::unixtime::now(); _session->data().enumerateUsers([&](UserData *user) { @@ -1928,6 +2474,7 @@ void ApiWrap::updatePrivacyLastSeens() { session().data().maybeStopWatchForOffline(user); }); +#if 0 // mtp if (_contactsStatusesRequestId) { request(_contactsStatusesRequestId).cancel(); } @@ -1954,8 +2501,10 @@ void ApiWrap::updatePrivacyLastSeens() { }).fail([this] { _contactsStatusesRequestId = 0; }).send(); +#endif } +#if 0 // mtp int ApiWrap::OnlineTillFromStatus( const MTPUserStatus &status, int currentOnlineTill) { @@ -1971,12 +2520,42 @@ int ApiWrap::OnlineTillFromStatus( } Unexpected("Bad UserStatus type."); } +#endif + +TimeId ApiWrap::OnlineTillFromStatus( + const TLuserStatus &status, + TimeId currentOnlineTill) { + return status.match([&](const TLDuserStatusEmpty &) { + return 0; + }, [&](const TLDuserStatusRecently &) { + return (currentOnlineTill > -10) ? -2 : currentOnlineTill; + }, [&](const TLDuserStatusLastWeek &) { + return -3; + }, [&](const TLDuserStatusLastMonth &) { + return -4; + }, [&](const TLDuserStatusOffline &data) { + return data.vwas_online().v; + }, [&](const TLDuserStatusOnline &data) { + return data.vexpires().v; + }); +} void ApiWrap::clearHistory(not_null peer, bool revoke) { deleteHistory(peer, true, revoke); } void ApiWrap::deleteConversation(not_null peer, bool revoke) { + const auto finish = [=] { + deleteHistory(peer, false, revoke); + }; + if (const auto chat = peer->asChat()) { + sender().request(TLleaveChat( + peerToTdbChat(chat->id) + )).done(finish).fail(finish).send(); + } else { + finish(); + } +#if 0 // mtp if (const auto chat = peer->asChat()) { request(MTPmessages_DeleteChatUser( MTP_flags(0), @@ -1991,13 +2570,17 @@ void ApiWrap::deleteConversation(not_null peer, bool revoke) { } else { deleteHistory(peer, false, revoke); } +#endif } void ApiWrap::deleteHistory( not_null peer, bool justClear, bool revoke) { +#if 0 // mtp auto deleteTillId = MsgId(0); +#endif + const auto deleteTillId = MsgId(-1); const auto history = _session->data().history(peer); if (justClear) { // In case of clear history we need to know the last server message. @@ -2013,6 +2596,7 @@ void ApiWrap::deleteHistory( break; } } +#if 0 // mtp if (!history->lastMessageKnown()) { history->owner().histories().requestDialogEntry(history, [=] { Expects(history->lastMessageKnown()); @@ -2022,10 +2606,13 @@ void ApiWrap::deleteHistory( return; } deleteTillId = history->lastMessage()->id; +#endif } if (const auto channel = peer->asChannel()) { if (!justClear && !revoke) { +#if 0 // mtp channel->ptsWaitingForShortPoll(-1); +#endif leaveChannel(channel); } else { if (const auto migrated = peer->migrateFrom()) { @@ -2047,12 +2634,29 @@ void ApiWrap::deleteHistory( revoke); } if (!justClear) { +#if 0 // mtp _session->data().deleteConversationLocally(peer); +#endif } else if (history) { history->clear(History::ClearType::ClearHistory); } } +bool ApiWrap::apply(const Tdb::TLDupdateOption &update) { + if (update.vname().v == "disable_contact_registered_notifications") { + sender().request(base::take(_contactSignupSilentRequestId)).cancel(); + const auto silent = (update.vvalue().type() == id_optionValueBoolean) + && update.vvalue().c_optionValueBoolean().vvalue().v; + if (_contactSignupSilent != silent) { + _contactSignupSilent = silent; + _contactSignupSilentChanges.fire_copy(silent); + } + return true; + } + return globalPrivacy().apply(update); +} + +#if 0 // mtp void ApiWrap::applyUpdates( const MTPUpdates &updates, uint64 sentMessageRandomId) { @@ -2087,6 +2691,7 @@ void ApiWrap::applyAffectedMessages( const auto &data = result.c_messages_affectedMessages(); updates().updateAndApply(data.vpts().v, data.vpts_count().v); } +#endif void ApiWrap::saveCurrentDraftToCloud() { Core::App().materializeLocalDrafts(); @@ -2127,7 +2732,7 @@ void ApiWrap::saveDraftsToCloud() { auto cloudDraft = history->cloudDraft(topicRootId); auto localDraft = history->localDraft(topicRootId); if (cloudDraft && cloudDraft->saveRequestId) { - request(base::take(cloudDraft->saveRequestId)).cancel(); + sender().request(base::take(cloudDraft->saveRequestId)).cancel(); } if (!_session->supportMode()) { cloudDraft = history->createCloudDraft(topicRootId, localDraft); @@ -2135,6 +2740,8 @@ void ApiWrap::saveDraftsToCloud() { cloudDraft = history->createCloudDraft(topicRootId, nullptr); } + auto &textWithTags = cloudDraft->textWithTags; +#if 0 // goodToRemove auto flags = MTPmessages_SaveDraft::Flags(0); auto &textWithTags = cloudDraft->textWithTags; if (cloudDraft->webpage.removed) { @@ -2152,8 +2759,61 @@ void ApiWrap::saveDraftsToCloud() { _session, TextUtilities::ConvertTextTagsToEntities(textWithTags.tags), Api::ConvertOption::SkipLocal); +#endif + auto tlDraft = [&]() -> std::optional { + if (!cloudDraft->reply && textWithTags.text.isEmpty()) { + return std::nullopt; + } + return tl_draftMessage( + Api::MessageReplyTo(history, cloudDraft->reply), + tl_int32(0), // Date. + tl_inputMessageText( + Api::FormattedTextToTdb(TextWithEntities{ + textWithTags.text, + TextUtilities::ConvertTextTagsToEntities( + textWithTags.tags) + }), + Data::LinkPreviewOptions(cloudDraft->webpage), + tl_bool(true))); + }(); history->startSavingCloudDraft(topicRootId); + + cloudDraft->saveRequestId = sender().request(TLsetChatDraftMessage( + peerToTdbChat(history->peer->id), + tl_int53(topicRootId.bare), + std::move(tlDraft) + )).done([=](const TLok &, Tdb::RequestId requestId) { + history->finishSavingCloudDraftNow(topicRootId); + + if (const auto cloudDraft = history->cloudDraft(topicRootId)) { + if (cloudDraft->saveRequestId == requestId) { + cloudDraft->saveRequestId = 0; + history->draftSavedToCloud(topicRootId); + } + } + auto i = _draftsSaveRequestIds.find(weak); + if (i != _draftsSaveRequestIds.cend() + && i->second == requestId) { + _draftsSaveRequestIds.erase(weak); + checkQuitPreventFinished(); + } + }).fail([=](const Tdb::Error &error, Tdb::RequestId requestId) { + history->finishSavingCloudDraftNow(topicRootId); + + if (const auto cloudDraft = history->cloudDraft(topicRootId)) { + if (cloudDraft->saveRequestId == requestId) { + history->clearCloudDraft(topicRootId); + } + } + auto i = _draftsSaveRequestIds.find(weak); + if (i != _draftsSaveRequestIds.cend() + && i->second == requestId) { + _draftsSaveRequestIds.erase(weak); + checkQuitPreventFinished(); + } + }).send(); +#if 0 // goodToRemove cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( MTP_flags(flags), ReplyToForMTP(history, cloudDraft->reply), @@ -2197,6 +2857,7 @@ void ApiWrap::saveDraftsToCloud() { checkQuitPreventFinished(); } }).send(); +#endif i->second = cloudDraft->saveRequestId; ++i; @@ -2226,7 +2887,10 @@ void ApiWrap::registerModifyRequest( mtpRequestId requestId) { const auto i = _modifyRequests.find(key); if (i != end(_modifyRequests)) { + sender().request(i->second).cancel(); +#if 0 // mtp request(i->second).cancel(); +#endif i->second = requestId; } else { _modifyRequests.emplace(key, requestId); @@ -2237,6 +2901,7 @@ void ApiWrap::clearModifyRequest(const QString &key) { _modifyRequests.remove(key); } +#if 0 // mtp void ApiWrap::gotStickerSet( uint64 setId, const MTPmessages_StickerSet &result) { @@ -2565,6 +3230,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu } _session->data().sendWebPageGamePollNotifications(); } +#endif void ApiWrap::updateStickers() { const auto now = crl::now(); @@ -2592,7 +3258,15 @@ void ApiWrap::updateCustomEmoji() { } void ApiWrap::requestRecentStickersForce(bool attached) { + if (attached) { + _session->data().stickers().setLastRecentAttachedUpdate(0); + } else { + _session->data().stickers().setLastRecentUpdate(0); + } + requestRecentStickers(crl::now(), attached); +#if 0 // mtp requestRecentStickersWithHash(0, attached); +#endif } void ApiWrap::setGroupStickerSet( @@ -2601,10 +3275,16 @@ void ApiWrap::setGroupStickerSet( Expects(megagroup->mgInfo != nullptr); megagroup->mgInfo->stickerSet = set; + sender().request(TLsetSupergroupStickerSet( + tl_int53(peerToChannel(megagroup->id).bare), + tl_int64(set.id) + )).send(); +#if 0 // mtp request(MTPchannels_SetStickers( megagroup->inputChannel, Data::InputStickerSet(set) )).send(); +#endif _session->data().stickers().notifyUpdated(Data::StickersType::Stickers); } @@ -2621,6 +3301,26 @@ std::vector> *ApiWrap::stickersByEmoji( && (received + kStickersByEmojiInvalidateTimeout) <= now; }(); if (sendRequest) { + sender().request(TLsearchStickers( + tl_stickerTypeRegular(), + tl_string(key), + tl_int32(kStickersByEmojiCount) + )).done([=](const TLDstickers &data) { + auto &entry = _stickersByEmoji[key]; + entry.list.clear(); + entry.list.reserve(data.vstickers().v.size()); + for (const auto &sticker : data.vstickers().v) { + const auto document = _session->data().processDocument( + sticker); + if (document->sticker()) { + entry.list.push_back(document); + } + } + entry.received = crl::now(); + _session->data().stickers().notifyUpdated( + Data::StickersType::Stickers); + }).send(); +#if 0 // mtp const auto hash = (it != _stickersByEmoji.end()) ? it->second.hash : uint64(0); @@ -2648,6 +3348,7 @@ std::vector> *ApiWrap::stickersByEmoji( _session->data().stickers().notifyUpdated( Data::StickersType::Stickers); }).send(); +#endif } if (it == _stickersByEmoji.end()) { _stickersByEmoji.emplace(key, StickersByEmoji()); @@ -2662,6 +3363,7 @@ void ApiWrap::requestStickers(TimeId now) { || _stickersUpdateRequest) { return; } +#if 0 // mtp const auto done = [=](const MTPmessages_AllStickers &result) { _session->data().stickers().setLastUpdate(crl::now()); _stickersUpdateRequest = 0; @@ -2679,6 +3381,14 @@ void ApiWrap::requestStickers(TimeId now) { LOG(("App Fail: Failed to get stickers!")); done(MTP_messages_allStickersNotModified()); }).send(); +#endif + _stickersUpdateRequest = sender().request(TLgetInstalledStickerSets( + tl_stickerTypeRegular() + )).done([=](const TLstickerSets &result) { + _session->data().stickers().setLastUpdate(crl::now()); + _stickersUpdateRequest = 0; + _session->data().stickers().setsReceived(result.data().vsets().v); + }).send(); } void ApiWrap::requestMasks(TimeId now) { @@ -2686,6 +3396,7 @@ void ApiWrap::requestMasks(TimeId now) { || _masksUpdateRequest) { return; } +#if 0 // mtp const auto done = [=](const MTPmessages_AllStickers &result) { _session->data().stickers().setLastMasksUpdate(crl::now()); _masksUpdateRequest = 0; @@ -2703,6 +3414,14 @@ void ApiWrap::requestMasks(TimeId now) { LOG(("App Fail: Failed to get masks!")); done(MTP_messages_allStickersNotModified()); }).send(); +#endif + _masksUpdateRequest = sender().request(TLgetInstalledStickerSets( + tl_stickerTypeMask() + )).done([=](const TLstickerSets &result) { + _session->data().stickers().setLastMasksUpdate(crl::now()); + _masksUpdateRequest = 0; + _session->data().stickers().masksReceived(result.data().vsets().v); + }).send(); } void ApiWrap::requestCustomEmoji(TimeId now) { @@ -2710,6 +3429,7 @@ void ApiWrap::requestCustomEmoji(TimeId now) { || _customEmojiUpdateRequest) { return; } +#if 0 // mtp const auto done = [=](const MTPmessages_AllStickers &result) { _session->data().stickers().setLastEmojiUpdate(crl::now()); _customEmojiUpdateRequest = 0; @@ -2727,6 +3447,15 @@ void ApiWrap::requestCustomEmoji(TimeId now) { LOG(("App Fail: Failed to get custom emoji!")); done(MTP_messages_allStickersNotModified()); }).send(); +#endif + _customEmojiUpdateRequest = sender().request(TLgetInstalledStickerSets( + tl_stickerTypeCustomEmoji() + )).done([=](const TLstickerSets &result) { + _session->data().stickers().setLastEmojiUpdate(crl::now()); + _customEmojiUpdateRequest = 0; + _session->data().stickers().emojiReceived(result.data().vsets().v); + }).send(); + } void ApiWrap::requestRecentStickers(TimeId now, bool attached) { @@ -2736,10 +3465,47 @@ void ApiWrap::requestRecentStickers(TimeId now, bool attached) { if (!needed) { return; } + const auto requestId = [=]() -> mtpRequestId & { + return attached + ? _recentAttachedStickersUpdateRequest + : _recentStickersUpdateRequest; + }; + if (requestId()) { + return; + } + const auto finish = [=] { + auto &stickers = _session->data().stickers(); + if (attached) { + stickers.setLastRecentAttachedUpdate(crl::now()); + } else { + stickers.setLastRecentUpdate(crl::now()); + } + requestId() = 0; + }; + const auto flags = attached + ? MTPmessages_getRecentStickers::Flag::f_attached + : MTPmessages_getRecentStickers::Flags(0); + requestId() = sender().request(TLgetRecentStickers( + tl_bool(attached) + )).done([=](const TLDstickers &result) { + finish(); + _session->data().stickers().specialSetReceived( + (attached + ? Data::Stickers::CloudRecentAttachedSetId + : Data::Stickers::CloudRecentSetId), + tr::lng_recent_stickers(tr::now), + result.vstickers().v); + }).fail([=] { + finish(); + LOG(("App Fail: Failed to get recent stickers!")); + }).send(); +#if 0 // mtp requestRecentStickersWithHash( Api::CountRecentStickersHash(_session, attached), attached); +#endif } +#if 0 // mtp void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) { const auto requestId = [=]() -> mtpRequestId & { return attached @@ -2789,12 +3555,30 @@ void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) { LOG(("App Fail: Failed to get recent stickers!")); }).send(); } +#endif void ApiWrap::requestFavedStickers(TimeId now) { if (!_session->data().stickers().favedUpdateNeeded(now) || _favedStickersUpdateRequest) { return; } + + const auto finish = [=] { + _session->data().stickers().setLastFavedUpdate(crl::now()); + _favedStickersUpdateRequest = 0; + }; + _favedStickersUpdateRequest = sender().request(TLgetFavoriteStickers( + )).done([=](const TLDstickers &result) { + finish(); + _session->data().stickers().specialSetReceived( + Data::Stickers::FavedSetId, + Lang::Hard::FavedSetTitle(), + result.vstickers().v); + }).fail([=] { + finish(); + LOG(("App Fail: Failed to get faved stickers!")); + }).send(); +#if 0 // mtp _favedStickersUpdateRequest = request(MTPmessages_GetFavedStickers( MTP_long(Api::CountFavedStickersHash(_session)) )).done([=](const MTPmessages_FavedStickers &result) { @@ -2820,6 +3604,7 @@ void ApiWrap::requestFavedStickers(TimeId now) { LOG(("App Fail: Failed to get faved stickers!")); }).send(); +#endif } void ApiWrap::requestFeaturedStickers(TimeId now) { @@ -2827,6 +3612,23 @@ void ApiWrap::requestFeaturedStickers(TimeId now) { || _featuredStickersUpdateRequest) { return; } + const auto finish = [=] { + _session->data().stickers().setLastFeaturedUpdate(crl::now()); + _featuredStickersUpdateRequest = 0; + }; + _featuredStickersUpdateRequest = sender().request( + TLgetTrendingStickerSets( + tl_stickerTypeRegular(), + tl_int32(0), + tl_int32(100)) + ).done([=](const TLtrendingStickerSets &result) { + finish(); + _session->data().stickers().featuredSetsReceived(result); + }).fail([=] { + finish(); + LOG(("App Fail: Failed to get featured stickers!")); + }).send(); +#if 0 // mtp _featuredStickersUpdateRequest = request(MTPmessages_GetFeaturedStickers( MTP_long(Api::CountFeaturedStickersHash(_session)) )).done([=](const MTPmessages_FeaturedStickers &result) { @@ -2837,6 +3639,7 @@ void ApiWrap::requestFeaturedStickers(TimeId now) { _session->data().stickers().setLastFeaturedUpdate(crl::now()); LOG(("App Fail: Failed to get featured stickers!")); }).send(); +#endif } void ApiWrap::requestFeaturedEmoji(TimeId now) { @@ -2844,6 +3647,23 @@ void ApiWrap::requestFeaturedEmoji(TimeId now) { || _featuredEmojiUpdateRequest) { return; } + const auto finish = [=] { + _session->data().stickers().setLastFeaturedEmojiUpdate(crl::now()); + _featuredStickersUpdateRequest = 0; + }; + _featuredStickersUpdateRequest = sender().request( + TLgetTrendingStickerSets( + tl_stickerTypeCustomEmoji(), + tl_int32(0), + tl_int32(100)) + ).done([=](const TLtrendingStickerSets &result) { + finish(); + _session->data().stickers().featuredEmojiSetsReceived(result); + }).fail([=] { + finish(); + LOG(("App Fail: Failed to get featured emoji!")); + }).send(); +#if 0 // mtp _featuredEmojiUpdateRequest = request( MTPmessages_GetFeaturedEmojiStickers( MTP_long(Api::CountFeaturedStickersHash(_session))) @@ -2855,6 +3675,7 @@ void ApiWrap::requestFeaturedEmoji(TimeId now) { _session->data().stickers().setLastFeaturedEmojiUpdate(crl::now()); LOG(("App Fail: Failed to get featured emoji!")); }).send(); +#endif } void ApiWrap::requestSavedGifs(TimeId now) { @@ -2862,6 +3683,19 @@ void ApiWrap::requestSavedGifs(TimeId now) { || _savedGifsUpdateRequest) { return; } + _savedGifsUpdateRequest = sender().request(TLgetSavedAnimations( + )).done([=](const TLDanimations &data) { + _session->data().stickers().setLastSavedGifsUpdate(crl::now()); + _savedGifsUpdateRequest = 0; + + _session->data().stickers().gifsReceived(data.vanimations().v, 0); + }).fail([=] { + _session->data().stickers().setLastSavedGifsUpdate(crl::now()); + _savedGifsUpdateRequest = 0; + + LOG(("App Fail: Failed to get saved gifs!")); + }).send(); +#if 0 // goodToRemove _savedGifsUpdateRequest = request(MTPmessages_GetSavedGifs( MTP_long(Api::CountSavedGifsHash(_session)) )).done([=](const MTPmessages_SavedGifs &result) { @@ -2884,6 +3718,7 @@ void ApiWrap::requestSavedGifs(TimeId now) { LOG(("App Fail: Failed to get saved gifs!")); }).send(); +#endif } void ApiWrap::readFeaturedSetDelayed(uint64 setId) { @@ -2896,13 +3731,19 @@ void ApiWrap::readFeaturedSetDelayed(uint64 setId) { void ApiWrap::readFeaturedSets() { const auto &sets = _session->data().stickers().sets(); auto count = _session->data().stickers().featuredSetsUnreadCount(); +#if 0 // mtp QVector wrappedIds; +#endif + auto wrappedIds = QVector(); wrappedIds.reserve(_featuredSetsRead.size()); for (const auto setId : _featuredSetsRead) { const auto it = sets.find(setId); if (it != sets.cend()) { it->second->flags &= ~Data::StickersSetFlag::Unread; +#if 0 // mtp wrappedIds.append(MTP_long(setId)); +#endif + wrappedIds.append(tl_int64(setId)); if (count) { --count; } @@ -2911,6 +3752,14 @@ void ApiWrap::readFeaturedSets() { _featuredSetsRead.clear(); if (!wrappedIds.empty()) { + sender().request(TLviewTrendingStickerSets( + tl_vector(wrappedIds) + )).done([=] { + local().writeFeaturedStickers(); + _session->data().stickers().notifyUpdated( + Data::StickersType::Stickers); + }).send(); +#if 0 // mtp auto requestData = MTPmessages_ReadFeaturedStickers( MTP_vector(wrappedIds)); request(std::move(requestData)).done([=] { @@ -2918,6 +3767,7 @@ void ApiWrap::readFeaturedSets() { _session->data().stickers().notifyUpdated( Data::StickersType::Stickers); }).send(); +#endif _session->data().stickers().setFeaturedSetsUnreadCount(count); } @@ -2940,6 +3790,57 @@ void ApiWrap::requestMessageAfterDate( MsgId topicRootId, const QDate &date, Callback &&callback) { + const auto offsetDate = static_cast( + date.startOfDay().toSecsSinceEpoch()) - 1; + sender().request(TLgetChatMessageByDate( + peerToTdbChat(peer->id), + tl_int32(offsetDate) + )).done([=](const TLmessage &message) { + const auto item = _session->data().processMessage( + message, + NewMessageType::Existing); + const auto send = [&](auto &&request) { + sender().request( + std::move(request) + ).done([=](const TLmessages &result) { + const auto list = result.data().vmessages().v; + if (list.empty() || !list.front()) { + callback(ShowAtUnreadMsgId); + } else { + const auto item = session().data().processMessage( + *list.front(), + NewMessageType::Existing); + if (item->date() >= offsetDate) { + callback(item->id); + } else { + callback(ShowAtUnreadMsgId); + } + } + }).fail([=] { + callback(ShowAtUnreadMsgId); + }).send(); + }; + if (topicRootId) { + send(TLgetMessageThreadHistory( + peerToTdbChat(peer->id), + tl_int53(topicRootId.bare), + tl_int53(item->id.bare), + tl_int32(-2), + tl_int32(2) + )); + } else { + send(TLgetChatHistory( + peerToTdbChat(peer->id), + tl_int53(item->id.bare), + tl_int32(-2), + tl_int32(2), + tl_bool(false) + )); + } + }).fail([=] { + callback(ShowAtUnreadMsgId); + }).send(); +#if 0 // mtp // API returns a message with date <= offset_date. // So we request a message with offset_date = desired_date - 1 and add_offset = -1. // This should give us the first message with date >= desired_date. @@ -3016,6 +3917,7 @@ void ApiWrap::requestMessageAfterDate( MTP_int(minId), MTP_long(historyHash))); } +#endif } void ApiWrap::resolveJumpToHistoryDate( @@ -3065,7 +3967,7 @@ void ApiWrap::requestSharedMedia( return; } - const auto prepared = Api::PrepareSearchRequest( + auto prepared = Api::PrepareSearchRequest( peer, topicRootId, type, @@ -3076,12 +3978,18 @@ void ApiWrap::requestSharedMedia( return; } +#if 0 // mtp const auto history = _session->data().history(peer); auto &histories = history->owner().histories(); const auto requestType = Data::Histories::RequestType::History; histories.sendRequest(history, requestType, [=](Fn finish) { return request( std::move(*prepared) +#endif + { + const auto finish = [] {}; + sender().request( + std::move(*prepared) ).done([=](const Api::SearchRequestResult &result) { _sharedMediaRequests.remove(key); auto parsed = Api::ParseSearchResult( @@ -3094,9 +4002,22 @@ void ApiWrap::requestSharedMedia( finish(); }).fail([=] { _sharedMediaRequests.remove(key); + // In case of left legacy groups MTProto API allows requesting + // messages / search / etc, while TDLib disallows. + if (const auto chat = peer->asChat()) { + if (chat->isForbidden()) { + using namespace Api; + sharedMediaDone(peer, topicRootId, type, SearchResult{ + .noSkipRange = { 0, ServerMaxMsgId } + }); + } + } finish(); }).send(); + } +#if 0 // mtp }); +#endif _sharedMediaRequests.emplace(key); } @@ -3109,6 +4030,7 @@ void ApiWrap::sharedMediaDone( if (topicRootId && !topic) { return; } + const auto got = !parsed.messageIds.empty(); _session->storage().add(Storage::SharedMediaAddSlice( peer->id, topicRootId, @@ -3117,12 +4039,23 @@ void ApiWrap::sharedMediaDone( parsed.noSkipRange, parsed.fullCount )); + if (type == SharedMediaType::Pinned + && parsed.noSkipRange.till == ServerMaxMsgId + && got) { + peer->owner().history(peer)->setHasPinnedMessages(true); + if (topic) { + topic->setHasPinnedMessages(true); + } + + } +#if 0 // mtp if (type == SharedMediaType::Pinned && !parsed.messageIds.empty()) { peer->owner().history(peer)->setHasPinnedMessages(true); if (topic) { topic->setHasPinnedMessages(true); } } +#endif } void ApiWrap::sendAction(const SendAction &action) { @@ -3231,14 +4164,38 @@ void ApiWrap::forwardMessages( } auto forwardFrom = draft.items.front()->history()->peer; +#if 0 // mtp auto ids = QVector(); auto randomIds = QVector(); auto localIds = std::shared_ptr>(); +#endif + auto ids = QVector(); const auto sendAccumulated = [&] { if (shared) { ++shared->requestsLeft; } + return sender().request(TLforwardMessages( + peerToTdbChat(peer->id), + tl_int53(action.replyTo.topicRootId.bare), + peerToTdbChat(forwardFrom->id), + tl_vector(ids), + tl_messageSendOptions( + tl_bool(silentPost), + tl_bool(false), // from_background + tl_bool(false), // update_order_of_installed_stickers_sets + Api::ScheduledToTL(action.options.scheduled), + tl_int32(0), // sending_id + tl_bool(false)), // only_preview + tl_bool(draft.options != Data::ForwardOptions::PreserveInfo), + tl_bool( + draft.options == Data::ForwardOptions::NoNamesAndCaptions) + )).done([=](const TLmessages &result) { + // They should've been added by updates. + }).fail([=](const Error &error) { + sendMessageFail(error.message, peer); + }).send(); +#if 0 // mtp const auto requestType = Data::Histories::RequestType::Send; const auto idsCopy = localIds; histories.sendRequest(history, requestType, [=](Fn finish) { @@ -3271,13 +4228,17 @@ void ApiWrap::forwardMessages( ).send(); return history->sendRequestId; }); +#endif ids.resize(0); +#if 0 // mtp randomIds.resize(0); localIds = nullptr; +#endif }; ids.reserve(count); +#if 0 // mtp randomIds.reserve(count); for (const auto item : draft.items) { const auto randomId = base::RandomValue(); @@ -3308,13 +4269,18 @@ void ApiWrap::forwardMessages( } localIds->emplace(randomId, newId); } +#endif + for (const auto item : draft.items) { const auto newFrom = item->history()->peer; if (forwardFrom != newFrom) { sendAccumulated(); forwardFrom = newFrom; } +#if 0 // mtp ids.push_back(MTP_int(item->id)); randomIds.push_back(MTP_long(randomId)); +#endif + ids.push_back(tl_int53(item->id.bare)); } sendAccumulated(); _session->data().sendHistoryChangeNotifications(); @@ -3366,6 +4332,7 @@ void ApiWrap::sendSharedContact( Fn done) { sendAction(action); +#if 0 // mtp const auto history = action.history; const auto peer = history->peer; @@ -3421,6 +4388,15 @@ void ApiWrap::sendSharedContact( (action.options.scheduled ? Data::HistoryUpdate::Flag::ScheduledSent : Data::HistoryUpdate::Flag::MessageSent)); +#endif + + Api::SendPreparedMessage(action, tl_inputMessageContact( + tl_contact( + tl_string(phone), + tl_string(firstName), + tl_string(lastName), + tl_string(), // vcard + tl_int53(userId.bare)))); } void ApiWrap::sendVoiceMessage( @@ -3477,9 +4453,11 @@ void ApiWrap::sendFiles( } const auto to = fileLoadTaskOptions(action); +#if 0 // mtp if (album) { album->options = to.options; } +#endif auto tasks = std::vector>(); tasks.reserve(list.files.size()); for (auto &file : list.files) { @@ -3502,7 +4480,9 @@ void ApiWrap::sendFiles( caption = TextWithTags(); } if (album) { +#if 0 // mtp _sendingAlbums.emplace(album->groupId, album); +#endif album->items.reserve(tasks.size()); for (const auto &task : tasks) { album->items.emplace_back(task->id()); @@ -3530,6 +4510,7 @@ void ApiWrap::sendFile( spoiler)); } +#if 0 // mtp void ApiWrap::sendUploadedPhoto( FullMsgId localId, Api::RemoteFileInfo info, @@ -3571,6 +4552,7 @@ void ApiWrap::cancelLocalItem(not_null item) { sendAlbumWithCancelled(item, groupId); } } +#endif void ApiWrap::sendMessage(MessageToSend &&message) { const auto history = message.action.history; @@ -3619,13 +4601,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) { TextUtilities::Trim(left); const auto isLast = left.empty(); +#if 0 // mtp auto newId = FullMsgId( peer->id, _session->data().nextLocalMessageId()); auto randomId = base::RandomValue(); - +#endif TextUtilities::Trim(sending); - +#if 0 // mtp _session->data().registerMessageRandomId(randomId, newId); _session->data().registerMessageSentData( randomId, @@ -3641,11 +4624,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) { sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to; mediaFlags |= MTPmessages_SendMedia::Flag::f_reply_to; } +#endif const auto ignoreWebPage = message.webPage.removed || (exactWebPage && !isLast); const auto manualWebPage = exactWebPage && !ignoreWebPage && (message.webPage.manual || (isLast && !isFirst)); +#if 0 // mtp MTPMessageMedia media = MTP_messageMediaEmpty(); if (ignoreWebPage) { sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage; @@ -3784,6 +4769,18 @@ void ApiWrap::sendMessage(MessageToSend &&message) { (sendAs ? sendAs->input : MTP_inputPeerEmpty()) ), done, fail); } +#endif + const auto sendWebPage = exactWebPage + && !ignoreWebPage + && (manualWebPage || sending.empty()); + Api::SendPreparedMessage(action, tl_inputMessageText( + Api::FormattedTextToTdb(sending), + (ignoreWebPage + ? Data::LinkPreviewOptions({ .removed = true }) + : sendWebPage + ? Data::LinkPreviewOptions(message.webPage) + : std::nullopt), + tl_bool(action.clearDraft))); isFirst = false; } @@ -3815,6 +4812,19 @@ void ApiWrap::sendBotStart( sendMessage(std::move(message)); return; } + if (!chat) { + info->startToken = QString(); + } + sender().request(TLsendBotStartMessage( + tl_int53(peerToUser(bot->id).bare), + peerToTdbChat(chat ? chat->id : bot->id), + tl_string(token) + )).fail([=](const Error &error) { + if (chat) { + ShowAddParticipantsError(error.message, chat, { 1, bot }); + } + }).send(); +#if 0 // mtp const auto randomId = base::RandomValue(); if (!chat) { info->startToken = QString(); @@ -3832,6 +4842,7 @@ void ApiWrap::sendBotStart( ShowAddParticipantsError(type, chat, { 1, bot }); } }).send(); +#endif } void ApiWrap::sendInlineResult( @@ -3841,8 +4852,36 @@ void ApiWrap::sendInlineResult( std::optional localMessageId) { sendAction(action); + if (localMessageId) { + Api::TryGenerateLocalInlineResultMessage( + bot, + data, + action, + *localMessageId); + } const auto history = action.history; const auto peer = history->peer; + const auto localId = localMessageId.value_or( + bot->owner().nextLocalMessageId()); + const auto sendingId = ClientMsgIndex(localId); + sender().request(TLsendInlineQueryResultMessage( + peerToTdbChat(peer->id), + MessageThreadId(peer, action), + MessageReplyTo(action), + MessageSendOptions(peer, action, sendingId), + tl_int64(data->getQueryId()), + tl_string(data->getId()), + tl_bool(action.options.hideViaBot) + )).fail([=](const Error &error) { + const auto code = error.code; + //if (error.type() == qstr("MESSAGE_EMPTY")) { + // lastMessage->destroy(); + //} else { + // sendMessageFail(error, peer, randomId, newId); + //} + }).send(); + manualClearCloudDraft(history); +#if 0 // mtp const auto newId = FullMsgId( peer->id, localMessageId @@ -3926,8 +4965,10 @@ void ApiWrap::sendInlineResult( UnixtimeFromMsgId(response.outerMsgId)); }); finishForwarding(action); +#endif } +#if 0 // mtp void ApiWrap::uploadAlbumMedia( not_null item, const MessageGroupId &groupId, @@ -4173,6 +5214,7 @@ void ApiWrap::sendAlbumIfReady(not_null album) { } }); } +#endif FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const { const auto peer = action.history->peer; @@ -4187,12 +5229,21 @@ void ApiWrap::reloadContactSignupSilent() { if (_contactSignupSilentRequestId) { return; } +#if 0 // mtp const auto requestId = request(MTPaccount_GetContactSignUpNotification( )).done([=](const MTPBool &result) { _contactSignupSilentRequestId = 0; const auto silent = mtpIsTrue(result); - _contactSignupSilent = silent; - _contactSignupSilentChanges.fire_copy(silent); +#endif + const auto requestId = sender().request(TLgetOption( + tl_string("disable_contact_registered_notifications") + )).done([=](const TLoptionValue &result) { + const auto silent = (result.type() == id_optionValueBoolean) + && result.c_optionValueBoolean().vvalue().v; + if (_contactSignupSilent != silent) { + _contactSignupSilent = silent; + _contactSignupSilentChanges.fire_copy(silent); + } }).fail([=] { _contactSignupSilentRequestId = 0; }).send(); @@ -4211,10 +5262,17 @@ std::optional ApiWrap::contactSignupSilentCurrent() const { } void ApiWrap::saveContactSignupSilent(bool silent) { +#if 0 // mtp request(base::take(_contactSignupSilentRequestId)).cancel(); const auto requestId = request(MTPaccount_SetContactSignUpNotification( MTP_bool(silent) +#endif + sender().request(base::take(_contactSignupSilentRequestId)).cancel(); + + const auto requestId = sender().request(TLsetOption( + tl_string("disable_contact_registered_notifications"), + tl_optionValueBoolean(tl_bool(silent)) )).done([=] { _contactSignupSilentRequestId = 0; _contactSignupSilent = silent; @@ -4247,6 +5305,7 @@ void ApiWrap::requestBotCommonGroups( } }; const auto limit = 100; +#if 0 // mtp request(MTPmessages_GetCommonChats( bot->inputUser, MTP_long(0), // max_id @@ -4263,6 +5322,21 @@ void ApiWrap::requestBotCommonGroups( list.push_back(peer); } } +#endif + sender().request(TLgetGroupsInCommon( + tl_int53(peerToUser(bot->id).bare), + tl_int53(0), // offset_chat_id + tl_int32(limit) + )).done([=](const TLDchats &data) { + auto &owner = session().data(); + auto list = std::vector>(); + list.reserve(data.vchat_ids().v.size()); + for (const auto &chatId : data.vchat_ids().v) { + const auto peerId = peerFromTdbChat(chatId); + if (const auto peer = owner.peerLoaded(peerId)) { + list.push_back(peer); + } + } finish(std::move(list)); }).fail([=] { finish({}); @@ -4272,12 +5346,25 @@ void ApiWrap::requestBotCommonGroups( void ApiWrap::saveSelfBio(const QString &text) { if (_bio.requestId) { if (text != _bio.requestedText) { + sender().request(_bio.requestId).cancel(); +#if 0 // mtp request(_bio.requestId).cancel(); +#endif } else { return; } } _bio.requestedText = text; + _bio.requestId = sender().request(Tdb::TLsetBio( + Tdb::tl_string(text) + )).done([=] { + _bio.requestId = 0; + + _session->user()->setAbout(_bio.requestedText); + }).fail([=](const Tdb::Error &error) { + _bio.requestId = 0; + }).send(); +#if 0 // goodToRemove _bio.requestId = request(MTPaccount_UpdateProfile( MTP_flags(MTPaccount_UpdateProfile::Flag::f_about), MTPstring(), @@ -4291,8 +5378,10 @@ void ApiWrap::saveSelfBio(const QString &text) { }).fail([=] { _bio.requestId = 0; }).send(); +#endif } +#if 0 // mtp void ApiWrap::registerStatsRequest(MTP::DcId dcId, mtpRequestId id) { _statsRequests[dcId].emplace(id); } @@ -4318,6 +5407,7 @@ void ApiWrap::checkStatsSessions() { } } } +#endif Api::Authorizations &ApiWrap::authorizations() { return *_authorizations; diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index ecb0f968aaefc..8643ebb2fe4be 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -14,6 +14,7 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" #include "data/stickers/data_stickers_set.h" #include "data/data_messages.h" +#include "tdb/tdb_sender.h" class TaskQueue; struct MessageGroupId; @@ -22,6 +23,18 @@ enum class SendMediaType; struct FileLoadTo; struct ChatRestrictionsInfo; +namespace Tdb { +class Account; +class Sender; +class Error; +class TLstickerSet; +class TLscopeNotificationSettings; +class TLchatInviteLinkInfo; +class TLDupdateOption; +class TLuserStatus; +class TLchatFolderInviteLinkInfo; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -127,7 +140,10 @@ QString RequestKey(Types &&...values) { } // namespace Api +#if 0 // mtp class ApiWrap final : public MTP::Sender { +#endif +class ApiWrap final { public: using SendAction = Api::SendAction; using MessageToSend = Api::MessageToSend; @@ -139,12 +155,18 @@ class ApiWrap final : public MTP::Sender { [[nodiscard]] Storage::Account &local() const; [[nodiscard]] Api::Updates &updates() const; + [[nodiscard]] Tdb::Account &tdb() const; + [[nodiscard]] Tdb::Sender &sender() const; + + bool apply(const Tdb::TLDupdateOption &update); +#if 0 // mtp void applyUpdates( const MTPUpdates &updates, uint64 sentMessageRandomId = 0); int applyAffectedHistory( PeerData *peer, // May be nullptr, like for deletePhoneCallHistory. const MTPmessages_AffectedHistory &result); +#endif void registerModifyRequest(const QString &key, mtpRequestId requestId); void clearModifyRequest(const QString &key); @@ -159,14 +181,18 @@ class ApiWrap final : public MTP::Sender { Fn callback); void requestMessageData(PeerData *peer, MsgId msgId, Fn done); +#if 0 // mtp QString exportDirectMessageLink( not_null item, bool inRepliesContext); QString exportDirectStoryLink(not_null item); +#endif void requestContacts(); void requestDialogs(Data::Folder *folder = nullptr); +#if 0 // mtp void requestPinnedDialogs(Data::Folder *folder = nullptr); +#endif void requestMoreBlockedByDateDialogs(); void requestMoreDialogsIfNeeded(); rpl::producer dialogsLoadMayBlockByDate() const; @@ -180,6 +206,7 @@ class ApiWrap final : public MTP::Sender { void requestFullPeer(not_null peer); void requestPeerSettings(not_null peer); +#if 0 // mtp using UpdatedFileReferences = Data::UpdatedFileReferences; using FileReferencesHandler = FnMut; void refreshFileReference( @@ -195,12 +222,24 @@ class ApiWrap final : public MTP::Sender { const QString &sinceVersion, Fn callback); void refreshTopPromotion(); +#endif void requestDeepLinkInfo( const QString &path, Fn callback); +#if 0 // mtp void requestTermsUpdate(); +#endif void acceptTerms(bytes::const_span termsId); + void checkChatInvite( + const QString &hash, + FnMut done, + Fn fail); + void checkFilterInvite( + const QString &slug, + FnMut done, + Fn fail); +#if 0 // mtp void checkChatInvite( const QString &hash, FnMut done, @@ -213,6 +252,7 @@ class ApiWrap final : public MTP::Sender { void processFullPeer( not_null peer, const MTPmessages_ChatFull &result); +#endif void migrateChat( not_null chat, @@ -227,9 +267,11 @@ class ApiWrap final : public MTP::Sender { not_null channel, not_null from); +#if 0 // mtp void requestWebPageDelayed(not_null page); void clearWebPageRequest(not_null page); void clearWebPageRequests(); +#endif void scheduleStickerSetRequest(uint64 setId, uint64 access); void requestStickerSets(); @@ -251,15 +293,26 @@ class ApiWrap final : public MTP::Sender { void joinChannel(not_null channel); void leaveChannel(not_null channel); +#if 0 // mtp void requestNotifySettings(const MTPInputNotifyPeer &peer); +#endif + void requestDefaultNotifySettings(); + void requestDefaultNotifySettings(Data::DefaultNotify type); void updateNotifySettingsDelayed(not_null thread); void updateNotifySettingsDelayed(not_null peer); void updateNotifySettingsDelayed(Data::DefaultNotify type); void saveDraftToCloudDelayed(not_null thread); +#if 0 // mtp static int OnlineTillFromStatus( const MTPUserStatus &status, int currentOnlineTill); +#endif + [[nodiscard]] static TimeId OnlineTillFromStatus( + const Tdb::TLuserStatus &status, + TimeId currentOnlineTill); + void saveDraftToCloudNow(not_null thread); + void manualClearCloudDraft(not_null thread); void clearHistory(not_null peer, bool revoke); void deleteConversation(not_null peer, bool revoke); @@ -300,9 +353,12 @@ class ApiWrap final : public MTP::Sender { not_null user, const SendAction &action, Fn done = nullptr); + +#if 0 // mtp void applyAffectedMessages( not_null peer, const MTPmessages_AffectedMessages &result); +#endif void sendVoiceMessage( QByteArray result, @@ -326,6 +382,7 @@ class ApiWrap final : public MTP::Sender { TextWithTags &&caption, const SendAction &action); +#if 0 // mtp void sendUploadedPhoto( FullMsgId localId, Api::RemoteFileInfo info, @@ -336,6 +393,7 @@ class ApiWrap final : public MTP::Sender { Api::SendOptions options); void cancelLocalItem(not_null item); +#endif void sendMessage(MessageToSend &&message); void sendBotStart( @@ -347,11 +405,13 @@ class ApiWrap final : public MTP::Sender { not_null data, const SendAction &action, std::optional localMessageId); +#if 0 // mtp void sendMessageFail( const MTP::Error &error, not_null peer, uint64 randomId = 0, FullMsgId itemId = FullMsgId()); +#endif void sendMessageFail( const QString &error, not_null peer, @@ -369,8 +429,10 @@ class ApiWrap final : public MTP::Sender { void saveSelfBio(const QString &text); +#if 0 // mtp void registerStatsRequest(MTP::DcId dcId, mtpRequestId id); void unregisterStatsRequest(MTP::DcId dcId, mtpRequestId id); +#endif [[nodiscard]] Api::Authorizations &authorizations(); [[nodiscard]] Api::AttachedStickers &attachedStickers(); @@ -413,23 +475,16 @@ class ApiWrap final : public MTP::Sender { crl::time received = 0; }; - struct DialogsLoadState { - TimeId offsetDate = 0; - MsgId offsetId = 0; - PeerData *offsetPeer = nullptr; - mtpRequestId requestId = 0; - bool listReceived = false; - - mtpRequestId pinnedRequestId = 0; - bool pinnedReceived = false; - }; + struct DialogsLoadState; void setupSupportMode(); void refreshDialogsLoadBlocked(); +#if 0 // mtp void updateDialogsOffset( Data::Folder *folder, const QVector &dialogs, const QVector &messages); +#endif void requestMoreDialogs(Data::Folder *folder); DialogsLoadState *dialogsLoadState(Data::Folder *folder); void dialogsLoadFinish(Data::Folder *folder); @@ -438,6 +493,7 @@ class ApiWrap final : public MTP::Sender { void saveDraftsToCloud(); +#if 0 // mtp void resolveMessageDatas(); void finalizeMessageDataRequest( ChannelData *channel, @@ -461,12 +517,15 @@ class ApiWrap final : public MTP::Sender { const MTPmessages_Messages &result, mtpRequestId req); void gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result); +#endif void requestStickers(TimeId now); void requestMasks(TimeId now); void requestCustomEmoji(TimeId now); void requestRecentStickers(TimeId now, bool attached = false); +#if 0 // mtp void requestRecentStickersWithHash(uint64 hash, bool attached = false); +#endif void requestFavedStickers(TimeId now); void requestFeaturedStickers(TimeId now); void requestFeaturedEmoji(TimeId now); @@ -503,12 +562,16 @@ class ApiWrap final : public MTP::Sender { not_null peer, bool justClear, bool revoke); + +#if 0 // mtp void applyAffectedMessages(const MTPmessages_AffectedMessages &result); +#endif void deleteAllFromParticipantSend( not_null channel, not_null from); +#if 0 // mtp void uploadAlbumMedia( not_null item, const MessageGroupId &groupId, @@ -532,46 +595,60 @@ class ApiWrap final : public MTP::Sender { Api::SendOptions options, uint64 randomId, Fn done = nullptr); +#endif + FileLoadTo fileLoadTaskOptions(const SendAction &action) const; +#if 0 // mtp void getTopPromotionDelayed(TimeId now, TimeId next); void topPromotionDone(const MTPhelp_PromoData &proxy); +#endif void sendNotifySettingsUpdates(); +#if 0 // mtp template void requestFileReference( Data::FileOrigin origin, FileReferencesHandler &&handler, Request &&data); +#endif void migrateDone( not_null peer, not_null channel); void migrateFail(not_null peer, const QString &error); +#if 0 // mtp void checkStatsSessions(); +#endif const not_null _session; base::flat_map _modifyRequests; +#if 0 // mtp MessageDataRequests _messageDataRequests; base::flat_map< not_null, MessageDataRequests> _channelMessageDataRequests; SingleQueuedInvokation _messageDataResolveDelayed; +#endif using PeerRequests = base::flat_map; PeerRequests _fullPeerRequests; +#if 0 // mtp base::flat_set> _requestedPeerSettings; +#endif base::flat_map< not_null, std::pair>> _historyArchivedRequests; +#if 0 // mtp base::flat_map, mtpRequestId> _webPagesPending; base::Timer _webPagesTimer; +#endif struct StickerSetRequest { uint64 accessHash = 0; @@ -623,7 +700,9 @@ class ApiWrap final : public MTP::Sender { base::flat_map _stickersByEmoji; mtpRequestId _contactsRequestId = 0; +#if 0 // mtp mtpRequestId _contactsStatusesRequestId = 0; +#endif struct SharedMediaRequest { not_null peer; @@ -650,12 +729,15 @@ class ApiWrap final : public MTP::Sender { rpl::event_stream _sendActions; std::unique_ptr _fileLoader; + +#if 0 // mtp base::flat_map> _sendingAlbums; mtpRequestId _topPromotionRequestId = 0; std::pair _topPromotionKey; TimeId _topPromotionNextRequestTime = TimeId(0); base::Timer _topPromotionTimer; +#endif base::flat_set> _updateNotifyTopics; base::flat_set> _updateNotifyPeers; @@ -663,14 +745,18 @@ class ApiWrap final : public MTP::Sender { base::Timer _updateNotifyTimer; rpl::lifetime _updateNotifyQueueLifetime; +#if 0 // mtp std::map< Data::FileOrigin, std::vector> _fileReferenceHandlers; +#endif mtpRequestId _deepLinkInfoRequestId = 0; +#if 0 // mtp crl::time _termsUpdateSendAt = 0; mtpRequestId _termsUpdateRequestId = 0; +#endif mtpRequestId _checkInviteRequestId = 0; mtpRequestId _checkFilterInviteRequestId = 0; @@ -688,8 +774,10 @@ class ApiWrap final : public MTP::Sender { QString requestedText; } _bio; +#if 0 // mtp base::flat_map> _statsRequests; base::Timer _statsSessionKillTimer; +#endif const std::unique_ptr _authorizations; const std::unique_ptr _attachedStickers; diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 8fd1f5c2b00e9..886839ba2d54b 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -48,6 +48,7 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "api/api_invite_links.h" #include "api/api_peer_photo.h" +#include "tdb/tdb_format_phone.h" #include "main/main_session.h" #include "styles/style_info.h" #include "styles/style_layers.h" @@ -56,11 +57,15 @@ For license and copyright information please follow this link: #include "styles/style_dialogs.h" #include "styles/style_widgets.h" +#include "tdb/tdb_tl_scheme.h" + #include #include namespace { +using namespace Tdb; + bool IsValidPhone(QString phone) { phone = phone.replace(QRegularExpression(u"[^\\d]"_q), QString()); return (phone.length() >= 8) @@ -72,6 +77,7 @@ bool IsValidPhone(QString phone) { || phone == u"4242"_q)); } +#if 0 // mtp void ChatCreateDone( not_null navigation, QImage image, @@ -127,14 +133,20 @@ void ChatCreateDone( "(ContactsBox::creationDone)")); } } +#endif void MustBePublicDestroy(not_null channel) { const auto session = &channel->session(); + session->sender().request(TLdeleteChat( + peerToTdbChat(channel->id) + )).send(); +#if 0 // mtp session->api().request(MTPchannels_DeleteChannel( channel->inputChannel )).done([=](const MTPUpdates &result) { session->api().applyUpdates(result); }).send(); +#endif } void MustBePublicFailed( @@ -280,7 +292,10 @@ AddContactBox::AddContactBox( tr::lng_contact_phone(), Countries::ExtractPhoneCode(session->user()->phone()), phone, + [](const QString &s) { return PhonePatternGroups(s); }) +#if 0 // goodToRemove [](const QString &s) { return Countries::Groups(s); }) +#endif , _invertOrder(langFirstNameGoesSecond()) { if (!phone.isEmpty()) { _phone->setDisabled(true); @@ -431,6 +446,25 @@ void AddContactBox::save() { const auto session = _session; _sentName = firstName; _contactId = base::RandomValue(); + + _addRequest = _session->sender().request(Tdb::TLimportContacts( + Tdb::tl_vector( + 1, + Tdb::tl_contact( + Tdb::tl_string(phone), + Tdb::tl_string(firstName), + Tdb::tl_string(lastName), + Tdb::TLstring(), // VCard. + Tdb::tl_int53(_contactId))) + )).done(crl::guard(weak, [=](const Tdb::TLDimportedContacts &data) { + if (!weak) { + return; + } + const auto &list = data.vuser_ids().v; + const auto user = list.isEmpty() + ? nullptr + : session->data().userLoaded(UserId(list.front())); +#if 0 // goodToRemove _addRequest = _session->api().request(MTPcontacts_ImportContacts( MTP_vector( 1, @@ -457,6 +491,7 @@ void AddContactBox::save() { const auto user = list.isEmpty() ? nullptr : extractUser(list.front()); +#endif if (user) { if (user->isContact() || user->session().supportMode()) { if (const auto window = user->session().tryResolveWindow()) { @@ -506,7 +541,10 @@ GroupInfoBox::GroupInfoBox( const QString &title, Fn)> channelDone) : _navigation(navigation) +#if 0 // goodToRemove , _api(&_navigation->session().mtp()) +#endif +, _api(&_navigation->session().sender()) , _type(type) , _initialTitle(title) , _done(WrapPeerDoneFromChannelDone(std::move(channelDone))) { @@ -519,7 +557,10 @@ GroupInfoBox::GroupInfoBox( RequestPeerQuery query, Fn)> done) : _navigation(navigation) +#if 0 // mtp , _api(&_navigation->session().mtp()) +#endif +, _api(&_navigation->session().sender()) , _type((query.type == RequestPeerQuery::Type::Broadcast) ? Type::Channel : (query.groupIsForum == RequestPeerQuery::Restriction::Yes) @@ -694,16 +735,23 @@ void GroupInfoBox::createGroup( if (_creationRequestId) { return; } +#if 0 // goodToRemove using TLUsers = MTPInputUser; +#endif + using TLUsers = TLint53; auto inputs = QVector(); inputs.reserve(users.size()); for (auto peer : users) { auto user = peer->asUser(); Assert(user != nullptr); if (!user->isSelf()) { +#if 0 // goodToRemove inputs.push_back(user->inputUser); +#endif + inputs.push_back(tl_int53(peerToUser(user->id).bare)); } } +#if 0 // goodToRemove _creationRequestId = _api.request(MTPmessages_CreateChat( MTP_flags(_ttlPeriod ? MTPmessages_CreateChat::Flag::f_ttl_period @@ -712,15 +760,39 @@ void GroupInfoBox::createGroup( MTP_string(title), MTP_int(_ttlPeriod) )).done([=](const MTPUpdates &result) { +#endif + _creationRequestId = _api.request(TLcreateNewBasicGroupChat( + tl_vector(std::move(inputs)), + tl_string(title), + tl_int32(_ttlPeriod) + )).done([=](const TLchat &result) { auto image = _photo->takeResultImage(); const auto period = _ttlPeriod; const auto navigation = _navigation; const auto done = _done; getDelegate()->hideLayer(); // Destroys 'this'. +#if 0 // goodToRemove ChatCreateDone(navigation, std::move(image), period, result, done); }).fail([=](const MTP::Error &error) { const auto &type = error.type(); +#endif + const auto peer = navigation->session().data().processPeer(result); + if (!image.isNull()) { + navigation->session().api().peerPhoto().upload( + peer, + { std::move(image) }); + } + if (period) { + peer->setMessagesTTL(period); + } + if (done) { + done(peer); + } else { + navigation->showPeerHistory(peer); + } + }).fail([=](const Error &error) { + const auto &type = error.message; _creationRequestId = 0; const auto controller = _navigation->parentController(); if (type == u"NO_CHAT_TITLE"_q) { @@ -793,6 +865,7 @@ void GroupInfoBox::createChannel( const QString &description) { Expects(!_creationRequestId); +#if 0 // goodToRemove using Flag = MTPchannels_CreateChannel::Flag; const auto flags = Flag() | ((_type == Type::Megagroup || _type == Type::Forum) @@ -855,6 +928,30 @@ void GroupInfoBox::createChannel( } }).fail([this](const MTP::Error &error) { const auto &type = error.type(); +#endif + const auto tlZero = Tdb::tl_double(0); + _creationRequestId = _api.request(Tdb::TLcreateNewSupergroupChat( + Tdb::tl_string(title), + Tdb::tl_bool(_type == Type::Forum), + Tdb::tl_bool(_type == Type::Channel), + Tdb::tl_string(description), + Tdb::tl_chatLocation( + Tdb::tl_location(tlZero, tlZero, tlZero), + Tdb::tl_string()), + Tdb::tl_int32((_type == Type::Megagroup) ? _ttlPeriod : 0), + Tdb::tl_bool(false) // For import. + )).done([=](const Tdb::TLchat &result) { + auto &session = _navigation->session(); + const auto peer = session.data().processPeer(result); + auto image = _photo->takeResultImage(); + if (!image.isNull()) { + session.api().peerPhoto().upload(peer, { std::move(image) }); + } + session.api().requestFullPeer(peer); + _createdChannel = peer->asChannel(); + checkInviteLink(); + }).fail([this](const Error &error) { + const auto &type = error.message; _creationRequestId = 0; const auto controller = _navigation->parentController(); if (type == u"NO_CHAT_TITLE"_q) { @@ -936,7 +1033,10 @@ SetupChannelBox::SetupChannelBox( Fn)> done) : _navigation(navigation) , _channel(channel) +#if 0 // goodToRemove , _api(&_channel->session().mtp()) +#endif +, _api(&_channel->session().sender()) , _mustBePublic(mustBePublic) , _done(std::move(done)) , _privacyGroup( @@ -999,6 +1099,14 @@ void SetupChannelBox::prepare() { setMouseTracking(true); + _checkRequestId = _api.request(TLcheckChatUsername( + peerToTdbChat(_channel->id), + tl_string("preston") + )).done([=](const TLcheckChatUsernameResult &result) { + _checkRequestId = 0; + firstCheckFail(parseError(result)); + }).send(); +#if 0 // goodToRemove _checkRequestId = _api.request(MTPchannels_CheckUsername( _channel->inputChannel, MTP_string("preston") @@ -1006,6 +1114,7 @@ void SetupChannelBox::prepare() { _checkRequestId = 0; firstCheckFail(parseError(error.type())); }).send(); +#endif addButton(tr::lng_settings_save(), [=] { save(); }); @@ -1246,6 +1355,26 @@ void SetupChannelBox::updateSelected(const QPoint &cursorGlobalPosition) { void SetupChannelBox::save() { const auto saveUsername = [&](const QString &link) { _sentUsername = link; + _saveRequestId = _api.request(TLsetSupergroupUsername( + tl_int53(peerToChannel(_channel->id).bare), + tl_string(_sentUsername) + )).done([=] { + _saveRequestId = 0; + updateFail(UsernameResult::Ok); + }).fail([=](const Error &error) { + const auto &type = error.message; + _saveRequestId = 0; + updateFail([&] { + if (type == u"USERNAME_INVALID"_q) { + return UsernameResult::Invalid; + } else if (type == u"USERNAME_OCCUPIED"_q + || type == u"USERNAMES_UNAVAILABLE"_q) { + return UsernameResult::Occupied; + } + return UsernameResult::Invalid; + }()); + }).send(); +#if 0 // goodToRemove _saveRequestId = _api.request(MTPchannels_UpdateUsername( _channel->inputChannel, MTP_string(_sentUsername) @@ -1263,6 +1392,7 @@ void SetupChannelBox::save() { _saveRequestId = 0; updateFail(parseError(error.type())); }).send(); +#endif }; if (_saveRequestId) { return; @@ -1330,6 +1460,25 @@ void SetupChannelBox::check() { const auto link = _link->text().trimmed(); if (link.size() >= Ui::EditPeer::kMinUsernameLength) { _checkUsername = link; + _checkRequestId = _api.request(TLcheckChatUsername( + peerToTdbChat(_channel->id), + tl_string(link) + )).done([=](const TLcheckChatUsernameResult &result) { + _checkRequestId = 0; + const auto parsed = parseError(result); + if ((parsed == UsernameResult::Ok) + || _checkUsername == _channel->username()) { + _errorText = QString(); + _goodText = tr::lng_create_channel_link_available(tr::now); + update(); + } else { + checkFail(parsed); + } + }).fail([=] { + _checkRequestId = 0; + checkFail(UsernameResult::Unknown); + }).send(); +#if 0 // goodToRemove _checkRequestId = _api.request(MTPchannels_CheckUsername( _channel->inputChannel, MTP_string(link) @@ -1347,6 +1496,7 @@ void SetupChannelBox::check() { _checkRequestId = 0; checkFail(parseError(error.type())); }).send(); +#endif } } @@ -1378,6 +1528,21 @@ void SetupChannelBox::privacyChanged(Privacy value) { } SetupChannelBox::UsernameResult SetupChannelBox::parseError( + const TLcheckChatUsernameResult &result) { + return result.match([](const TLDcheckChatUsernameResultOk &) { + return UsernameResult::Ok; + }, [](const TLDcheckChatUsernameResultUsernameInvalid &) { + return UsernameResult::Invalid; + }, [](const TLDcheckChatUsernameResultUsernameOccupied &) { + return UsernameResult::Occupied; + }, [](const TLDcheckChatUsernameResultPublicChatsTooMany &) { + return UsernameResult::ChatsTooMuch; + }, [](const TLDcheckChatUsernameResultPublicGroupsUnavailable &) { + return UsernameResult::NA; + }, [](const TLDcheckChatUsernameResultUsernamePurchasable &) { + return UsernameResult::Occupied; + }); +#if 0 // goodToRemove const QString &error) { if (error == u"USERNAME_NOT_MODIFIED"_q) { return UsernameResult::Ok; @@ -1396,6 +1561,7 @@ SetupChannelBox::UsernameResult SetupChannelBox::parseError( } else { return UsernameResult::Unknown; } +#endif } void SetupChannelBox::updateFail(UsernameResult result) { @@ -1497,7 +1663,10 @@ void SetupChannelBox::firstCheckFail(UsernameResult result) { EditNameBox::EditNameBox(QWidget*, not_null user) : _user(user) +#if 0 // goodToRemove , _api(&_user->session().mtp()) +#endif +, _api(&_user->session().sender()) , _first( this, st::defaultInputField, @@ -1609,6 +1778,16 @@ void EditNameBox::save() { last = QString(); } _sentName = first; + _requestId = _api.request(TLsetName( + tl_string(first), + tl_string(last) + )).done([=] { + closeBox(); + }).fail([=](const Error &error) { + _requestId = 0; + saveSelfFail(error.message); + }).send(); +#if 0 // goodToRemove auto flags = MTPaccount_UpdateProfile::Flag::f_first_name | MTPaccount_UpdateProfile::Flag::f_last_name; _requestId = _api.request(MTPaccount_UpdateProfile( @@ -1623,6 +1802,7 @@ void EditNameBox::save() { _requestId = 0; saveSelfFail(error.type()); }).send(); +#endif } void EditNameBox::saveSelfFail(const QString &error) { diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index ef70f7c3b5ea7..46c3ebe3088ad 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -10,6 +10,11 @@ For license and copyright information please follow this link: #include "ui/layers/box_content.h" #include "base/timer.h" #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLcheckChatUsernameResult; +} // namespace Tdb class PeerListBox; struct RequestPeerQuery; @@ -133,7 +138,10 @@ class GroupInfoBox : public Ui::BoxContent { void updateMaxHeight(); const not_null _navigation; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; Type _type = Type::Group; QString _initialTitle; @@ -187,7 +195,11 @@ class SetupChannelBox final : public Ui::BoxContent { NA, Unknown, }; +#if 0 // goodToRemove [[nodiscard]] UsernameResult parseError(const QString &error); +#endif + [[nodiscard]] UsernameResult parseError( + const Tdb::TLcheckChatUsernameResult &result); void privacyChanged(Privacy value); void updateSelected(const QPoint &cursorGlobalPosition); @@ -207,7 +219,10 @@ class SetupChannelBox final : public Ui::BoxContent { const not_null _navigation; const not_null _channel; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; bool _creatingInviteLink = false; bool _mustBePublic = false; @@ -249,7 +264,10 @@ class EditNameBox : public Ui::BoxContent { void saveSelfFail(const QString &error); const not_null _user; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; object_ptr _first; object_ptr _last; diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index 834d23f0a8b4f..66e8022b6e8c3 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -35,8 +35,13 @@ For license and copyright information please follow this link: #include "styles/style_chat_helpers.h" #include "styles/style_info.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + constexpr auto kBackgroundsInRow = 3; QImage TakeMiddleSample(QImage original, QSize size) { @@ -132,7 +137,10 @@ class BackgroundBox::Inner final : public Ui::RpWidget { const not_null _session; PeerData * const _forPeer = nullptr; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; std::vector _papers; uint64 _currentId = 0; @@ -298,6 +306,7 @@ void BackgroundBox::chosen(const Data::WallPaper &paper) { } void BackgroundBox::resetForPeer() { +#if 0 // mtp const auto api = &_controller->session().api(); api->request(MTPmessages_SetChatWallPaper( MTP_flags(0), @@ -308,6 +317,14 @@ void BackgroundBox::resetForPeer() { )).done([=](const MTPUpdates &result) { api->applyUpdates(result); }).send(); +#endif + const auto sender = &_controller->session().sender(); + sender->request(TLsetChatBackground( + peerToTdbChat(_forPeer->id), + std::nullopt, + std::nullopt, + tl_int32(0) + )).send(); const auto weak = Ui::MakeWeak(this); _forPeer->setWallPaper(std::nullopt); @@ -324,11 +341,16 @@ void BackgroundBox::removePaper(const Data::WallPaper &paper) { weak->_inner->removePaper(paper); } session->data().removeWallpaper(paper); + session->sender().request(TLremoveBackground( + tl_int64(paper.id()) + )).send(); +#if 0 // mtp session->api().request(MTPaccount_SaveWallPaper( paper.mtpInput(session), MTP_bool(true), paper.mtpSettings() )).send(); +#endif }; _controller->show(Ui::MakeConfirmBox({ .text = tr::lng_background_sure_delete(), @@ -344,7 +366,10 @@ BackgroundBox::Inner::Inner( : RpWidget(parent) , _session(session) , _forPeer(forPeer) +#if 0 // mtp , _api(&_session->mtp()) +#endif +, _api(&_session->sender()) , _check(std::make_unique(st::overviewCheck, [=] { update(); })) { _check->setChecked(true, anim::type::instant); resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); @@ -379,6 +404,13 @@ BackgroundBox::Inner::Inner( } void BackgroundBox::Inner::requestPapers() { + _api.request(TLgetBackgrounds( + )).done([=](const TLbackgrounds &result) { + if (_session->data().updateWallpapers(result)) { + updatePapers(); + } + }).send(); +#if 0 // mtp _api.request(MTPaccount_GetWallPapers( MTP_long(_session->data().wallpapersHash()) )).done([=](const MTPaccount_WallPapers &result) { @@ -386,6 +418,7 @@ void BackgroundBox::Inner::requestPapers() { updatePapers(); } }).send(); +#endif } auto BackgroundBox::Inner::resolveResetCustomPaper() const diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index e0f99a7a1a3e8..2a353871cd142 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -47,8 +47,14 @@ For license and copyright information please follow this link: #include #include +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_file_generator.h" + namespace { +using namespace Tdb; + constexpr auto kMaxWallPaperSlugLength = 255; constexpr auto kDefaultDimming = 50; @@ -106,7 +112,9 @@ constexpr auto kDefaultDimming = 50; out ? history->session().userId() : peerToUser(history->peer->id), QString(), TextWithEntities{ text }, +#if 0 // mtp MTP_messageMediaEmpty(), +#endif HistoryMessageMarkupData(), groupedId); return AdminLog::OwnedItem(delegate, item); @@ -527,6 +535,35 @@ void BackgroundPreviewBox::uploadForPeer() { } const auto session = &_controller->session(); + if (_uploadLifetime) { + return; + } + const auto prepared = Window::Theme::PrepareWallPaper( + {}, + _paper.localThumbnail()->original()); + const auto generator = _uploadLifetime.make_state( + &session->tdb(), + prepared.data, + prepared.filename); + const auto sender = &_controller->session().sender(); + sender->request(TLsetChatBackground( + peerToTdbChat(_forPeer->id), + tl_inputBackgroundLocal(generator->inputFile()), + _paper.tlType(), + tl_int32((_paper.isPattern() || !_paper.document()) + ? 0 + : _paper.patternIntensity()) + )).done([=] { + _uploadProgress = 1.; + _uploadLifetime.destroy(); + update(radialRect()); + }).fail([=](const Error &error) { + _uploadProgress = 0.; + _uploadLifetime.destroy(); + update(radialRect()); + }).send(); + +#if 0 // mtp const auto ready = Window::Theme::PrepareWallPaper( session->mainDcId(), _paper.localThumbnail()->original()); @@ -582,6 +619,7 @@ void BackgroundPreviewBox::uploadForPeer() { } }).send(); }, _uploadLifetime); +#endif _uploadProgress = 0.; _radial.start(_uploadProgress); @@ -596,6 +634,8 @@ void BackgroundPreviewBox::setExistingForPeer(const Data::WallPaper &paper) { return; } } + +#if 0 // mtp const auto api = &_controller->session().api(); using Flag = MTPmessages_SetChatWallPaper::Flag; api->request(MTPmessages_SetChatWallPaper( @@ -609,6 +649,18 @@ void BackgroundPreviewBox::setExistingForPeer(const Data::WallPaper &paper) { )).done([=](const MTPUpdates &result) { api->applyUpdates(result); }).send(); +#endif + const auto sender = &_controller->session().sender(); + sender->request(TLsetChatBackground( + peerToTdbChat(_forPeer->id), + (_fromMessageId + ? tl_inputBackgroundPrevious(tl_int53(_fromMessageId.msg.bare)) + : tl_inputBackgroundRemote(tl_int64(paper.id()))), + paper.tlType(), + tl_int32((paper.isPattern() || !paper.document()) + ? 0 + : paper.patternIntensity()) + )).send(); _forPeer->setWallPaper(paper); _controller->finishChatThemeEdit(_forPeer); @@ -629,18 +681,33 @@ void BackgroundPreviewBox::applyForEveryone() { && Data::IsCloudWallPaper(_paper); _controller->content()->setChatBackground(_paper, std::move(_full)); if (install) { + _controller->session().sender().request(TLsetBackground( + tl_inputBackgroundRemote(tl_int64(_paper.id())), + _paper.tlType(), + tl_bool(Window::Theme::IsNightMode()) + )).send(); +#if 0 // mtp _controller->session().api().request(MTPaccount_InstallWallPaper( _paper.mtpInput(&_controller->session()), _paper.mtpSettings() )).send(); +#endif } closeBox(); } void BackgroundPreviewBox::share() { + const auto weak = Ui::MakeWeak(this); + const auto session = &_controller->session(); + _paper.requestShareUrl(session, crl::guard(this, [=](QString url) { + QGuiApplication::clipboard()->setText(url); + showToast(tr::lng_background_link_copied(tr::now)); + })); +#if 0 // mtp QGuiApplication::clipboard()->setText( _paper.shareUrl(&_controller->session())); showToast(tr::lng_background_link_copied(tr::now)); +#endif } void BackgroundPreviewBox::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index bc61e9fe2271b..fc1b300f75d9e 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -22,18 +22,25 @@ For license and copyright information please follow this link: #include "window/window_session_controller.h" #include "styles/style_media_player.h" // mediaPlayerMenuCheck +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace { +using namespace Tdb; + Data::ChatFilter ChangedFilter( const Data::ChatFilter &filter, not_null history, bool add) { auto always = base::duplicate(filter.always()); + auto pinned = base::duplicate(filter.pinned()); auto never = base::duplicate(filter.never()); if (add) { never.remove(history); } else { always.remove(history); + pinned.erase(ranges::remove(pinned, history), end(pinned)); } const auto result = Data::ChatFilter( filter.id(), @@ -41,10 +48,16 @@ Data::ChatFilter ChangedFilter( filter.iconEmoji(), filter.flags(), std::move(always), +#if 0 // mtp filter.pinned(), +#endif + std::move(pinned), std::move(never)); +#if 0 // mtp const auto in = result.contains(history); if (in == add) { +#endif + if (add == result.computeContains(history)) { return result; } always = base::duplicate(result.always()); @@ -73,6 +86,58 @@ void ChangeFilterById( const auto list = history->owner().chatsFilters().list(); const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); if (i != end(list)) { + const auto hadMyLinks = i->hasMyLinks(); + const auto sender = &history->session().sender(); + sender->request(TLgetChatFolder( + tl_int32(filterId) + )).done([=](const TLchatFolder &result) { + const auto owner = &history->owner(); + auto parsed = Data::ChatFilter::FromTL( + filterId, + result, + owner, + hadMyLinks); + const auto chat = history->peer->name(); + const auto guard = base::make_weak(&history->session()); + const auto account = &history->session().account(); + const auto name = parsed.title(); + const auto show = [=](TextWithEntities text) { + if (!guard) { + return; + } + if (const auto controller = Core::App().windowFor(account)) { + controller->showToast(std::move(text)); + } + }; + sender->request(TLeditChatFolder( + tl_int32(filterId), + ChangedFilter(std::move(parsed), history, add).tl() + )).done([=] { + // Since only the primary window has dialogs list, + // We can safely show toast there. + show((add + ? tr::lng_filters_toast_add + : tr::lng_filters_toast_remove)( + tr::now, + lt_chat, + Ui::Text::Bold(chat), + lt_folder, + Ui::Text::Bold(name), + Ui::Text::WithEntities)); + }).fail([=](const Error &error) { +#if 0 // tdb errors + const auto &text = error.message; + if (text == u"The maximum number of excluded chats exceeded"_q) { + } else if (text == u"The maximum number of included chats exceeded"_q) { + + } else if (text == u"Folder must contain at least 1 chat"_q) { + } else if (text == u"Folder must be different from the main chat list"_q) { + } +#endif + show({ error.message }); + }).send(); + }).send(); +#if 0 // mtp const auto was = *i; const auto filter = ChangedFilter(was, history, add); history->owner().chatsFilters().set(filter); @@ -97,6 +162,7 @@ void ChangeFilterById( // Revert filter on fail. history->owner().chatsFilters().set(was); }).send(); +#endif } } @@ -107,17 +173,22 @@ ChooseFilterValidator::ChooseFilterValidator(not_null history) } bool ChooseFilterValidator::canAdd() const { + return true; // We'll show the error when we try and fail. +#if 0 // mtp for (const auto &filter : _history->owner().chatsFilters().list()) { if (filter.id() && !filter.contains(_history)) { return true; } } return false; +#endif } bool ChooseFilterValidator::canRemove(FilterId filterId) const { Expects(filterId != 0); + return true; // We'll show the error when we try and fail. +#if 0 // mtp const auto list = _history->owner().chatsFilters().list(); const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); if (i != end(list)) { @@ -126,6 +197,7 @@ bool ChooseFilterValidator::canRemove(FilterId filterId) const { && ((filter.always().size() > 1) || filter.flags()); } return false; +#endif } ChooseFilterValidator::LimitData ChooseFilterValidator::limitReached( @@ -133,6 +205,8 @@ ChooseFilterValidator::LimitData ChooseFilterValidator::limitReached( bool always) const { Expects(filterId != 0); + return {}; // We'll show the error when we try and fail. +#if 0 // mtp const auto list = _history->owner().chatsFilters().list(); const auto i = ranges::find(list, filterId, &Data::ChatFilter::id); const auto limit = _history->owner().pinnedChatsLimit(filterId); @@ -143,6 +217,7 @@ ChooseFilterValidator::LimitData ChooseFilterValidator::limitReached( && (chatsList.size() >= limit), .count = int(chatsList.size()), }; +#endif } void ChooseFilterValidator::add(FilterId filterId) const { diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 144582a533fce..2a7b6c2cc9ffc 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -39,11 +39,16 @@ For license and copyright information please follow this link: #include "styles/style_info.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + #include #include namespace { +using namespace Tdb; + constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000); using ProxyData = MTP::ProxyData; @@ -1070,6 +1075,7 @@ void ProxyBox::addLabel( ProxiesBoxController::ProxiesBoxController(not_null account) : _account(account) , _settings(Core::App().settings().proxy()) +, _sender(&account->sender()) , _saveTimer([] { Local::writeSettings(); }) { _list = ranges::views::all( _settings.list() @@ -1086,9 +1092,45 @@ ProxiesBoxController::ProxiesBoxController(not_null account) } }, _lifetime); +#if 0 // mtp for (auto &item : _list) { refreshChecker(item); } +#endif + resolveTdb(); +} + +void ProxiesBoxController::resolveTdb() { + _listRequestId = _sender.request(TLgetProxies( + )).done([=](const TLproxies &result) { + auto enabled = ProxyData(); + for (const auto &proxy : result.data().vproxies().v) { + const auto value = FromTL(proxy); + const auto i = ranges::find(_list, value, &Item::data); + if (i != end(_list)) { + i->tdbId = proxy.data().vid().v; + refreshChecker(*i); + } + if (proxy.data().vis_enabled().v) { + enabled = value; + } + } + const auto selected = _settings.selected(); + for (auto &item : _list) { + const auto data = item.data; + const auto active = (data == selected) && _settings.isEnabled(); + if (!item.tdbId) { + addToTdb(item); + } else if (active && data != enabled) { + _sender.request(TLenableProxy( + tl_int32(item.tdbId) + )).send(); + } + } + if (!_settings.isEnabled() && enabled != ProxyData()) { + _sender.request(TLdisableProxy()).send(); + } + }).send(); } void ProxiesBoxController::ShowApplyConfirmation( @@ -1164,7 +1206,56 @@ auto ProxiesBoxController::proxySettingsValue() const ) | rpl::distinct_until_changed(); } +void ProxiesBoxController::addToTdb(Item &item) { + const auto &data = item.data; + const auto active = _settings.isEnabled() + && (data == _settings.selected()); + _sender.request(item.addRequestId).cancel(); + item.addRequestId = _sender.request(TLaddProxy( + tl_string(data.host), + tl_int32(data.port), + tl_bool(active), + TypeToTL(data) + )).done([=](const TLproxy &result) { + const auto i = ranges::find(_list, data, &Item::data); + if (i != end(_list)) { + i->addRequestId = 0; + i->tdbId = result.data().vid().v; + refreshChecker(*i); + } + }).send(); +} + void ProxiesBoxController::refreshChecker(Item &item) { + Expects(item.tdbId != 0); + Expects(!item.addRequestId); + + const auto id = item.tdbId; + item.state = ItemState::Checking; + _sender.request(item.checkRequestId).cancel(); + item.checkRequestId = _sender.request(TLpingProxy( + tl_int32(item.tdbId) + )).done([=](const TLDseconds &result) { + const auto i = ranges::find(_list, id, &Item::tdbId); + if (i != end(_list)) { + i->checkRequestId = 0; + if (i->state == ItemState::Checking) { + i->state = ItemState::Available; + i->ping = int(base::SafeRound(result.vseconds().v * 1000)); + updateView(*i); + } + } + }).fail([=] { + const auto i = ranges::find(_list, id, &Item::tdbId); + if (i != end(_list)) { + i->checkRequestId = 0; + if (i->state == ItemState::Checking) { + i->state = ItemState::Unavailable; + updateView(*i); + } + } + }).send(); +#if 0 // mtp using Variants = MTP::DcOptions::Variants; const auto type = (item.data.type == Type::Http) ? Variants::Http @@ -1223,8 +1314,10 @@ void ProxiesBoxController::refreshChecker(Item &item) { item.state = ItemState::Unavailable; } } +#endif } +#if 0 // mtp void ProxiesBoxController::setupChecker(int id, const Checker &checker) { using Connection = MTP::details::AbstractConnection; const auto pointer = checker.get(); @@ -1256,6 +1349,7 @@ void ProxiesBoxController::setupChecker(int id, const Checker &checker) { pointer->connect(pointer, &Connection::disconnected, failed); pointer->connect(pointer, &Connection::error, failed); } +#endif object_ptr ProxiesBoxController::CreateOwningBox( not_null account) { @@ -1345,6 +1439,10 @@ void ProxiesBoxController::setDeleted(int id, bool deleted) { _lastSelectedProxyUsed = false; } } + + if (item->tdbId) { + _sender.request(TLremoveProxy(tl_int32(item->tdbId))).send(); + } } else { auto &proxies = _settings.list(); if (ranges::find(proxies, item->data) == end(proxies)) { @@ -1419,7 +1517,18 @@ void ProxiesBoxController::replaceItemValue( Assert(i != end(proxies)); *i = proxy; which->data = proxy; - refreshChecker(*which); + if (which->tdbId) { + _sender.request(TLeditProxy( + tl_int32(which->tdbId), + tl_string(proxy.host), + tl_int32(proxy.port), + tl_bool(true), + TypeToTL(proxy) + )).send(); + refreshChecker(*which); + } else if (which->addRequestId) { + addToTdb(*which); + } applyItem(which->id); saveDelayed(); @@ -1449,8 +1558,8 @@ void ProxiesBoxController::addNewItem(const ProxyData &proxy) { proxies.push_back(proxy); _list.push_back({ ++_idCounter, proxy }); - refreshChecker(_list.back()); applyItem(_list.back().id); + addToTdb(_list.back()); } bool ProxiesBoxController::setProxySettings(ProxyData::Settings value) { @@ -1513,10 +1622,16 @@ void ProxiesBoxController::updateView(const Item &item) { Unexpected("Proxy type in ProxiesBoxController::updateView."); }(); const auto state = [&] { + using State = Main::Account::ConnectionState; if (!selected || !_settings.isEnabled()) { return item.state; +#if 0 // mtp } else if (_account->mtp().dcstate() == MTP::ConnectedState) { return ItemState::Online; +#endif + } else if (_account->connectionState() == State::Ready + || _account->connectionState() == State::Updating) { + return ItemState::Online; } return ItemState::Connecting; }(); @@ -1561,3 +1676,39 @@ ProxiesBoxController::~ProxiesBoxController() { [] { Local::writeSettings(); }); } } + +MTP::ProxyData FromTL(const Tdb::TLproxy &value) { + const auto &fields = value.data(); + auto result = ProxyData{ + .host = fields.vserver().v, + .port = uint32(fields.vport().v), + }; + fields.vtype().match([&](const TLDproxyTypeHttp &data) { + result.type = ProxyData::Type::Http; + result.user = data.vusername().v; + result.password = data.vpassword().v; + }, [&](const TLDproxyTypeSocks5 &data) { + result.type = ProxyData::Type::Socks5; + result.user = data.vusername().v; + result.password = data.vpassword().v; + }, [&](const TLDproxyTypeMtproto &data) { + result.type = ProxyData::Type::Mtproto; + result.password = data.vsecret().v; + }); + return result; +} + +TLproxyType TypeToTL(const MTP::ProxyData &value) { + Expects(value.type != MTP::ProxyData::Type::None); + + return (value.type == MTP::ProxyData::Type::Http) + ? tl_proxyTypeHttp( + tl_string(value.user), + tl_string(value.password), + tl_bool(false)) // http_only + : (value.type == MTP::ProxyData::Type::Socks5) + ? tl_proxyTypeSocks5( + tl_string(value.user), + tl_string(value.password)) + : tl_proxyTypeMtproto(tl_string(value.password)); +} diff --git a/Telegram/SourceFiles/boxes/connection_box.h b/Telegram/SourceFiles/boxes/connection_box.h index 8c78ebf34e109..62d00e7a8b7d3 100644 --- a/Telegram/SourceFiles/boxes/connection_box.h +++ b/Telegram/SourceFiles/boxes/connection_box.h @@ -13,6 +13,13 @@ For license and copyright information please follow this link: #include "mtproto/connection_abstract.h" #include "mtproto/mtproto_proxy_data.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLproxy; +class TLproxyType; +} // namespace Tdb + namespace Ui { class Show; class BoxContent; @@ -87,8 +94,13 @@ class ProxiesBoxController { int id = 0; ProxyData data; bool deleted = false; +#if 0 // mtp Checker checker; Checker checkerv6; +#endif + int tdbId = 0; + mtpRequestId addRequestId = 0; + mtpRequestId checkRequestId = 0; ItemState state = ItemState::Checking; int ping = 0; @@ -103,6 +115,8 @@ class ProxiesBoxController { void refreshChecker(Item &item); void setupChecker(int id, const Checker &checker); + void addToTdb(Item &item); + void resolveTdb(); void replaceItemWith( std::vector::iterator which, std::vector::iterator with); @@ -113,6 +127,7 @@ class ProxiesBoxController { const not_null _account; Core::SettingsProxy &_settings; + Tdb::Sender _sender; int _idCounter = 0; std::vector _list; rpl::event_stream _views; @@ -125,4 +140,9 @@ class ProxiesBoxController { rpl::lifetime _lifetime; + mtpRequestId _listRequestId = 0; + }; + +[[nodiscard]] MTP::ProxyData FromTL(const Tdb::TLproxy &value); +[[nodiscard]] Tdb::TLproxyType TypeToTL(const MTP::ProxyData &value); diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.cpp b/Telegram/SourceFiles/boxes/delete_messages_box.cpp index 99fe6b8835a65..1dc828316fc6a 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/delete_messages_box.cpp @@ -32,6 +32,12 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_boxes.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" +#include "data/data_secret_chat.h" + +using namespace Tdb; + DeleteMessagesBox::DeleteMessagesBox( QWidget*, not_null item, @@ -140,6 +146,10 @@ void DeleteMessagesBox::prepare() { tr::now, lt_contact, peer->name()) + : peer->isSecretChat() // secret langs + ? u"Are you sure you want to delete your secret chat with "_q + + peer->asSecretChat()->user()->name() + + u"?"_q : peer->isChat() ? tr::lng_sure_delete_and_exit( tr::now, @@ -150,6 +160,7 @@ void DeleteMessagesBox::prepare() { : tr::lng_sure_leave_channel(tr::now); details = Ui::Text::RichLangValue(details.text); if (!peer->isUser()) { + if (!peer->isSecretChat()) *deleteText = tr::lng_box_leave(); } deleteStyle = &st::attentionBoxButton; @@ -267,6 +278,13 @@ void DeleteMessagesBox::prepare() { count) }); } + } else if (peer->isSecretChat()) { + appendDetails({ + tr::lng_delete_for_everyone_hint( + tr::now, + lt_count, + count) + }); } else if (peer->isChat()) { appendDetails({ tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count) @@ -363,6 +381,9 @@ PeerData *DeleteMessagesBox::checkFromSinglePeer() const { auto DeleteMessagesBox::revokeText(not_null peer) const -> std::optional { + if (peer->isSecretChat()) { + return {}; + } auto result = RevokeConfig(); if (peer == _wipeHistoryPeer) { if (!peer->canRevokeFullHistory()) { @@ -558,12 +579,19 @@ void DeleteMessagesBox::deleteAndClear() { ChatRestrictionsInfo()); } if (_reportSpam->checked()) { + _moderateInChannel->session().sender().request( + TLreportSupergroupSpam( + tl_int53(peerToChannel(_moderateInChannel->id).bare), + tl_vector(1, tl_int53(_ids[0].msg.bare))) + ).send(); +#if 0 // mtp _moderateInChannel->session().api().request( MTPchannels_ReportSpam( _moderateInChannel->inputChannel, _moderateFrom->input, MTP_vector(1, MTP_int(_ids[0].msg))) ).send(); +#endif } if (_deleteAll && _deleteAll->checked()) { _moderateInChannel->session().api().deleteAllFromParticipant( diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index defc4bd645d47..ee6e02e3f8c3c 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -949,7 +949,10 @@ void EditCaptionBox::save() { lifetime().add([=] { if (_saveRequestId) { auto &session = _controller->session(); +#if 0 // mtp session.api().request(base::take(_saveRequestId)).cancel(); +#endif + session.sender().request(base::take(_saveRequestId)).cancel(); } }); diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 37b946d637ae3..88bb49068d207 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -45,8 +45,12 @@ For license and copyright information please follow this link: #include "styles/style_chat.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; using namespace Settings; constexpr auto kMaxFilterTitleLength = 12; @@ -872,6 +876,11 @@ void EditExistingFilter( const auto doneCallback = [=](const Data::ChatFilter &result) { Expects(id == result.id()); + session->sender().request(TLeditChatFolder( + tl_int32(id), + result.tl() + )).send(); +#if 0 // mtp const auto tl = result.tl(); session->data().chatsFilters().apply(MTP_updateDialogFilter( MTP_flags(MTPDupdateDialogFilter::Flag::f_filter), @@ -882,6 +891,7 @@ void EditExistingFilter( MTP_int(id), tl )).send(); +#endif }; const auto saveAnd = [=]( const Data::ChatFilter &data, diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp index d60cc030d1419..d432b16229180 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp @@ -38,8 +38,13 @@ For license and copyright information please follow this link: #include "styles/style_menu_icons.h" #include "styles/style_settings.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + constexpr auto kMaxLinkTitleLength = 32; using InviteLinkData = Data::ChatFilterLink; @@ -999,6 +1004,24 @@ void ExportFilterLink( const auto front = peers.front(); const auto session = &front->session(); + const auto ids = peers | ranges::views::transform( + [](not_null peer) { return peerToTdbChat(peer->id); } + ) | ranges::to(); + session->sender().request(TLcreateChatFolderInviteLink( + tl_int32(id), + tl_string(), + tl_vector(ids) + )).done([=](const TLchatFolderInviteLink &result) { + const auto link = session->data().chatsFilters().add(id, result); + if (!link.url.isEmpty()) { + done(link); + } else { + fail(u"CREATE_FAILED"_q); + } + }).fail([=](const Error &error) { + fail(error.message); + }).send(); +#if 0 // mtp auto mtpPeers = peers | ranges::views::transform( [](not_null peer) { return MTPInputPeer(peer->input); } ) | ranges::to>(); @@ -1019,6 +1042,7 @@ void ExportFilterLink( }).fail([=](const MTP::Error &error) { fail(error.type()); }).send(); +#endif } void EditLinkChats( @@ -1032,6 +1056,21 @@ void EditLinkChats( const auto id = link.id; const auto front = peers.front(); const auto session = &front->session(); + const auto ids = peers | ranges::views::transform( + [](not_null peer) { return peerToTdbChat(peer->id); } + ) | ranges::to(); + session->sender().request(TLeditChatFolderInviteLink( + tl_int32(id), + tl_string(link.url), + tl_string(link.title), + tl_vector(ids) + )).done([=](const TLchatFolderInviteLink &result) { + const auto link = session->data().chatsFilters().add(id, result); + done(QString()); + }).fail([=](const Error &error) { + done(error.message); + }).send(); +#if 0 // mtp auto mtpPeers = peers | ranges::views::transform( [](not_null peer) { return MTPInputPeer(peer->input); } ) | ranges::to>(); @@ -1047,6 +1086,7 @@ void EditLinkChats( }).fail([=](const MTP::Error &error) { done(error.type()); }).send(); +#endif } object_ptr ShowLinkBox( diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 1244964d2e8c3..b178543250261 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -47,21 +47,32 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_premium.h" +#include "tdb/tdb_tl_scheme.h" +#include "core/click_handler_types.h" + #include namespace { +using namespace Tdb; + constexpr auto kDiscountDivider = 5.; using GiftOption = Data::SubscriptionOption; using GiftOptions = Data::SubscriptionOptions; +#if 0 // mtp GiftOptions GiftOptionFromTL(const MTPDuserFull &data) { auto result = GiftOptions(); const auto gifts = data.vpremium_gifts(); if (!gifts) { return result; } +#endif + +GiftOptions GiftOptionFromTL(const TLDuserFullInfo &data) { + auto result = GiftOptions(); + const auto gifts = &data.vpremium_gift_options(); result = Api::SubscriptionOptionsFromTL(gifts->v); for (auto &option : result) { option.costPerMonth = tr::lng_premium_gift_per( @@ -208,6 +219,14 @@ void GiftBox( // Button. const auto &stButton = st::premiumGiftBox; box->setStyle(stButton); + const auto weak = base::make_weak(controller); + const auto startSubscription = [=] { + const auto value = group->value(); + if (value >= 0 && value < options.size()) { + options[value].startPayment(QVariant::fromValue( + ClickHandlerContext{ .sessionWindow = weak })); + } + }; auto raw = Settings::CreateSubscribeButton({ controller, box, @@ -220,6 +239,8 @@ void GiftBox( ? options[value].botUrl : QString(); }, + controller->uiShow(), + startSubscription, }); auto button = object_ptr::fromRaw(raw); button->resizeToWidth(boxWidth @@ -432,7 +453,10 @@ void AddTable( GiftPremiumValidator::GiftPremiumValidator( not_null controller) : _controller(controller) +#if 0 // mtp , _api(&_controller->session().mtp()) { +#endif +, _api(&_controller->session().sender()) { } void GiftPremiumValidator::cancel() { @@ -443,19 +467,26 @@ void GiftPremiumValidator::showBox(not_null user) { if (_requestId) { return; } +#if 0 // mtp _requestId = _api.request(MTPusers_GetFullUser( user->inputUser )).done([=](const MTPusers_UserFull &result) { +#endif + _requestId = _api.request(TLgetUserFullInfo( + tl_int53(peerToUser(user->id).bare) + )).done([=](const TLDuserFullInfo &fullUser) { if (!_requestId) { // Canceled. return; } _requestId = 0; +#if 0 // mtp // _controller->api().processFullPeer(peer, result); _controller->session().data().processUsers(result.data().vusers()); _controller->session().data().processChats(result.data().vchats()); const auto &fullUser = result.data().vfull_user().data(); +#endif auto options = GiftOptionFromTL(fullUser); if (!options.empty()) { _controller->show( @@ -562,8 +593,12 @@ void GiftCodeBox( const auto content = controller->parentController()->content(); return content->shareUrl( thread, +#if 0 // mtp MakeGiftCodeLink(&controller->session(), slug).link, QString()); +#endif + { MakeGiftCodeLink(&controller->session(), slug).link }, + true); }; Window::ShowChooseRecipientBox(controller, chosen); return false; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index 1891ce5ff43fa..d8512a80c0ab6 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -9,6 +9,8 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + class UserData; namespace Api { @@ -37,7 +39,10 @@ class GiftPremiumValidator final { private: const not_null _controller; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; mtpRequestId _requestId = 0; diff --git a/Telegram/SourceFiles/boxes/local_storage_box.cpp b/Telegram/SourceFiles/boxes/local_storage_box.cpp index fcdbd8d6d2868..7bcaf84d024f5 100644 --- a/Telegram/SourceFiles/boxes/local_storage_box.cpp +++ b/Telegram/SourceFiles/boxes/local_storage_box.cpp @@ -26,8 +26,13 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_boxes.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + constexpr auto kMegabyte = int64(1024 * 1024); constexpr auto kTotalSizeLimitsCount = 18; constexpr auto kMediaSizeLimitsCount = 18; @@ -356,6 +361,34 @@ void LocalStorageBox::clearByTag(uint16 tag) { _db->clear(); _dbBig->clear(); Ui::Emoji::ClearIrrelevantCache(); + + _session->sender().request(TLoptimizeStorage( + tl_int53(0), + tl_int32(0), + tl_int32(0), + tl_int32(0), + tl_vector({ + tl_fileTypeAnimation(), + tl_fileTypeAudio(), + tl_fileTypeDocument(), + tl_fileTypePhoto(), + tl_fileTypeProfilePhoto(), + tl_fileTypeSecret(), + tl_fileTypeSecretThumbnail(), + tl_fileTypeSecure(), + tl_fileTypeSticker(), + tl_fileTypeThumbnail(), + tl_fileTypeUnknown(), + tl_fileTypeVideo(), + tl_fileTypeVideoNote(), + tl_fileTypeVoiceNote(), + tl_fileTypeWallpaper(), + }), + tl_vector(), + tl_vector(), + tl_bool(false), + tl_int32(0) + )).send(); } } diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp index e320b434ff0b1..860a398d1f759 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.cpp +++ b/Telegram/SourceFiles/boxes/passcode_box.cpp @@ -13,7 +13,6 @@ For license and copyright information please follow this link: #include "base/unixtime.h" #include "mainwindow.h" #include "apiwrap.h" -#include "api/api_cloud_password.h" #include "main/main_session.h" #include "main/main_domain.h" #include "core/application.h" @@ -34,6 +33,8 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "base/qt/qt_common_adapters.h" +#include "tdb/tdb_tl_scheme.h" + namespace { enum class PasswordErrorType { @@ -155,9 +156,11 @@ PasscodeBox::CloudFields PasscodeBox::CloudFields::From( const Core::CloudPasswordState ¤t) { auto result = CloudFields(); result.hasPassword = current.hasPassword; +#if 0 // goodToRemove result.mtp.curRequest = current.mtp.request; result.mtp.newAlgo = current.mtp.newPassword; result.mtp.newSecureSecretAlgo = current.mtp.newSecureSecret; +#endif result.hasRecovery = current.hasRecovery; result.notEmptyPassport = current.notEmptyPassport; result.hint = current.hint; @@ -170,7 +173,11 @@ PasscodeBox::PasscodeBox( not_null session, bool turningOff) : _session(session) +#if 0 // goodToRemove , _api(&_session->mtp()) +#endif +, _api(_session->sender()) +, _unauthCloudPassword(std::nullopt) , _turningOff(turningOff) , _about(st::boxWidth - st::boxPadding.left() * 1.5) , _oldPasscode(this, st::defaultInputField, tr::lng_passcode_enter_old()) @@ -188,11 +195,18 @@ PasscodeBox::PasscodeBox( PasscodeBox::PasscodeBox( QWidget*, +#if 0 // goodToRemove not_null mtp, +#endif + Tdb::Sender &sender, Main::Session *session, const CloudFields &fields) : _session(session) +#if 0 // goodToRemove , _api(mtp) +#endif +, _api(sender) +, _unauthCloudPassword(sender) , _turningOff(fields.turningOff) , _cloudPwd(true) , _cloudFields(fields) @@ -228,7 +242,16 @@ PasscodeBox::PasscodeBox( QWidget*, not_null session, const CloudFields &fields) -: PasscodeBox(nullptr, &session->mtp(), session, fields) { +: PasscodeBox(nullptr, session->sender(), session, fields) { +} + +Api::CloudPassword &PasscodeBox::cloudPassword() { + if (_session) { + return _session->api().cloudPassword(); + } else if (_unauthCloudPassword) { + return (*_unauthCloudPassword); + } + Unexpected("Can't find TDLib sender in PasscodeBox."); } rpl::producer PasscodeBox::newPasswordSet() const { @@ -438,15 +461,20 @@ void PasscodeBox::setInnerFocus() { } } +#if 0 // goodToRemove void PasscodeBox::recoverPasswordDone( const QByteArray &newPasswordBytes, const MTPauth_Authorization &result) { +#endif +void PasscodeBox::recoverPasswordDone(const QByteArray &newPasswordBytes) { if (_replacedBy) { _replacedBy->closeBox(); } _setRequest = 0; const auto weak = Ui::MakeWeak(this); +#if 0 // mtp _newAuthorization.fire_copy(result); +#endif if (weak) { _newPasswordSet.fire_copy(newPasswordBytes); if (weak) { @@ -487,8 +515,30 @@ void PasscodeBox::closeReplacedBy() { } } +void PasscodeBox::setPasswordFail( + const QByteArray &newPasswordBytes, + const QString &email, + const QString &errorMessage) { + const auto prefix = u"EMAIL_UNCONFIRMED_"_q; + if (errorMessage.startsWith(prefix)) { + const auto codeLength = base::StringViewMid( + errorMessage, + prefix.size()).toInt(); + + closeReplacedBy(); + _setRequest = 0; + + validateEmail(email, codeLength, newPasswordBytes); + } else { + setPasswordFail(errorMessage); + } +} + void PasscodeBox::setPasswordFail(const QString &type) { +#if 0 // goodToRemove if (MTP::IsFloodError(type)) { +#endif + if (Tdb::IsFloodError(type)) { closeReplacedBy(); _setRequest = 0; @@ -513,8 +563,10 @@ void PasscodeBox::setPasswordFail(const QString &type) { } else { badOldPasscode(); } +#if 0 // goodToRemove - unneeded due TDLib } else if (type == u"SRP_ID_INVALID"_q) { handleSrpIdInvalid(); +#endif //} else if (type == u"NEW_PASSWORD_BAD"_q) { //} else if (type == u"NEW_SALT_INVALID"_q) { } else if (type == u"EMAIL_INVALID"_q) { @@ -525,6 +577,7 @@ void PasscodeBox::setPasswordFail(const QString &type) { } } +#if 0 // goodToRemove void PasscodeBox::setPasswordFail( const QByteArray &newPasswordBytes, const QString &email, @@ -541,6 +594,7 @@ void PasscodeBox::setPasswordFail( setPasswordFail(error.type()); } } +#endif void PasscodeBox::validateEmail( const QString &email, @@ -550,6 +604,32 @@ void PasscodeBox::validateEmail( const auto resent = std::make_shared>(); const auto set = std::make_shared(false); const auto submit = crl::guard(this, [=](QString code) { + cloudPassword().confirmEmail( + code + ) | rpl::start_with_error_done([=](const QString &error) { + _setRequest = 0; + if (Tdb::IsFloodError(error)) { + errors->fire(tr::lng_flood_error(tr::now)); + } else if (error == u"CODE_INVALID"_q) { + errors->fire(tr::lng_signin_wrong_code(tr::now)); + } else if (error == u"EMAIL_HASH_EXPIRED"_q) { + const auto weak = Ui::MakeWeak(this); + _clearUnconfirmedPassword.fire({}); + if (weak) { + auto box = Ui::MakeInformBox( + Lang::Hard::EmailConfirmationExpired()); + weak->getDelegate()->show( + std::move(box), + Ui::LayerOption::CloseOther); + } + } else { + errors->fire(Lang::Hard::ServerError()); + } + }, [=] { + *set = true; + setPasswordDone(newPasswordBytes); + }, lifetime()); +#if 0 // goodToRemove if (_setRequest) { return; } @@ -577,8 +657,20 @@ void PasscodeBox::validateEmail( errors->fire(Lang::Hard::ServerError()); } }).handleFloodErrors().send(); +#endif }); const auto resend = crl::guard(this, [=] { + cloudPassword().resendEmailCode( + ) | rpl::start_with_error_done([=](const QString &error) { + if (Tdb::IsFloodError(error)) { + errors->fire(tr::lng_flood_error(tr::now)); + } else { + errors->fire(Lang::Hard::ServerError()); + } + }, [=] { + resent->fire(tr::lng_cloud_password_resent(tr::now)); + }, lifetime()); +#if 0 // goodToRemove if (_setRequest) { return; } @@ -590,6 +682,7 @@ void PasscodeBox::validateEmail( _setRequest = 0; errors->fire(Lang::Hard::ServerError()); }).send(); +#endif }); const auto box = _replacedBy = getDelegate()->show( Passport::VerifyEmailBox( @@ -615,6 +708,7 @@ void PasscodeBox::validateEmail( }, box->lifetime()); } +#if 0 // goodToRemove - unneeded due TDLib void PasscodeBox::handleSrpIdInvalid() { const auto now = crl::now(); if (_lastSrpIdInvalidTime > 0 @@ -627,6 +721,7 @@ void PasscodeBox::handleSrpIdInvalid() { requestPasswordData(); } } +#endif void PasscodeBox::save(bool force) { if (_setRequest) return; @@ -737,7 +832,34 @@ void PasscodeBox::submitOnlyCheckCloudPassword(const QString &oldPassword) { } } +void PasscodeBox::sendClearCloudPassword(const QString &oldPassword) { + cloudPassword().set( + oldPassword, + QString(), + QString(), + false, + QString() + ) | rpl::start_with_error_done([=](const QString &error) { + setPasswordFail({}, QString(), error); + }, [=] { + setPasswordDone({}); + }, lifetime()); +} + void PasscodeBox::sendOnlyCheckCloudPassword(const QString &oldPassword) { + cloudPassword().check( + oldPassword + ) | rpl::start_with_error_done([=](const QString &error) { + setPasswordFail({}, QString(), error); + }, [=] { + if (const auto onstack = _cloudFields.customCheckCallback) { + onstack({ .password = oldPassword.toUtf8() }); + } else { + Assert(_cloudFields.turningOff); + sendClearCloudPassword(oldPassword); + } + }, lifetime()); +#if 0 // goodToRemove checkPassword(oldPassword, [=](const Core::CloudPasswordResult &check) { if (const auto onstack = _cloudFields.customCheckCallback) { onstack(check); @@ -746,8 +868,10 @@ void PasscodeBox::sendOnlyCheckCloudPassword(const QString &oldPassword) { sendClearCloudPassword(check); } }); +#endif } +#if 0 // goodToRemove - unneeded due TDLib void PasscodeBox::checkPassword( const QString &oldPassword, CheckPasswordCallback callback) { @@ -780,7 +904,9 @@ void PasscodeBox::passwordChecked() { _cloudFields.mtp.curRequest.id = 0; _checkPasswordCallback(check); } +#endif +#if 0 // goodToRemove - unneeded due TDLib void PasscodeBox::requestPasswordData() { if (!_checkPasswordCallback) { return serverError(); @@ -797,18 +923,24 @@ void PasscodeBox::requestPasswordData() { }); }).send(); } +#endif void PasscodeBox::serverError() { getDelegate()->show(Ui::MakeInformBox(Lang::Hard::ServerError())); closeBox(); } +#if 0 // mtp bool PasscodeBox::handleCustomCheckError(const MTP::Error &error) { return handleCustomCheckError(error.type()); } +#endif bool PasscodeBox::handleCustomCheckError(const QString &type) { +#if 0 // goodToRemove if (MTP::IsFloodError(type) +#endif + if (Tdb::IsFloodError(type) || type == u"PASSWORD_HASH_INVALID"_q || type == u"SRP_PASSWORD_CHANGED"_q || type == u"SRP_ID_INVALID"_q) { @@ -818,6 +950,7 @@ bool PasscodeBox::handleCustomCheckError(const QString &type) { return false; } +#if 0 // goodToRemove void PasscodeBox::sendClearCloudPassword( const Core::CloudPasswordResult &check) { const auto hint = QString(); @@ -899,7 +1032,61 @@ void PasscodeBox::changeCloudPassword( changeCloudPassword(oldPassword, check, newPassword); }); } +#endif + +void PasscodeBox::setNewCloudPassword(const QString &newPassword) { + const auto newPasswordBytes = newPassword.toUtf8(); + const auto hint = _passwordHint->getLastText(); + const auto email = _recoverEmail->getLastText().trimmed(); + _checkPasswordCallback = nullptr; + + if (_cloudFields.fromRecoveryCode.isEmpty()) { + cloudPassword().set( + QString(), + newPassword, + hint, + !email.isEmpty(), + email + ) | rpl::start_with_error_done([=](const QString &error) { + setPasswordFail(newPasswordBytes, email, error); + }, [=] { + setPasswordDone(newPasswordBytes); + }, lifetime()); + } else { + cloudPassword().recoverPassword( + _cloudFields.fromRecoveryCode, + newPassword, + hint + ) | rpl::start_with_error_done([=](const QString &error) { + if (Tdb::IsFloodError(error)) { + _newError = tr::lng_flood_error(tr::now); + update(); + } + setPasswordFail(newPasswordBytes, email, error); + }, [=] { + recoverPasswordDone(newPasswordBytes); + }, lifetime()); + } +} +void PasscodeBox::changeCloudPassword( + const QString &oldPassword, + const QString &newPassword) { + const auto newPasswordBytes = newPassword.toUtf8(); + cloudPassword().set( + oldPassword, + newPassword, + _passwordHint->getLastText(), + false, + QString() + ) | rpl::start_with_error_done([=](const QString &error) { + setPasswordFail(newPasswordBytes, QString(), error); + }, [=] { + setPasswordDone(newPasswordBytes); + }, lifetime()); +} + +#if 0 // goodToRemove void PasscodeBox::changeCloudPassword( const QString &oldPassword, const Core::CloudPasswordResult &check, @@ -946,7 +1133,9 @@ void PasscodeBox::changeCloudPassword( setPasswordFail(error.type()); }).handleFloodErrors().send(); } +#endif +#if 0 // goodToRemove - unneeded due TDLib? void PasscodeBox::suggestSecretReset(const QString &newPassword) { auto resetSecretAndSave = [=](Fn &&close) { checkPasswordHash([=, close = std::move(close)]( @@ -992,7 +1181,9 @@ void PasscodeBox::resetSecret( } }).send(); } +#endif +#if 0 // goodToRemove void PasscodeBox::sendChangeCloudPassword( const Core::CloudPasswordResult &check, const QString &newPassword, @@ -1038,6 +1229,7 @@ void PasscodeBox::sendChangeCloudPassword( setPasswordFail(newPasswordBytes, QString(), error); }).handleFloodErrors().send(); } +#endif void PasscodeBox::badOldPasscode() { _oldPasscode->selectAll(); @@ -1083,12 +1275,32 @@ void PasscodeBox::recoverByEmail() { })); } else if (_pattern.isEmpty()) { _pattern = "-"; + + const auto recoverStarted = [=](const QString &email) { + _pattern = email; + recover(); + }; + + const auto recoverStartFail = [=] { + _pattern = QString(); + closeBox(); + }; + + cloudPassword().requestPasswordRecovery( + ) | rpl::start_with_next_error([=](const QString &pattern) { + recoverStarted(pattern); + }, [=](const QString &error) { + recoverStartFail(); + }, lifetime()); + +#if 0 // goodToRemove _api.request(MTPauth_RequestPasswordRecovery( )).done([=](const MTPauth_PasswordRecovery &result) { recoverStarted(result); }).fail([=](const MTP::Error &error) { recoverStartFail(error); }).send(); +#endif } else { recover(); } @@ -1105,7 +1317,10 @@ void PasscodeBox::recover() { const auto weak = Ui::MakeWeak(this); const auto box = getDelegate()->show(Box( + _api, +#if 0 // goodToRemove &_api.instance(), +#endif _session, _pattern, _cloudFields, @@ -1122,6 +1337,7 @@ void PasscodeBox::recover() { _replacedBy = box; } +#if 0 // goodToRemove void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) { _pattern = qs(result.c_auth_passwordRecovery().vemail_pattern()); recover(); @@ -1131,16 +1347,23 @@ void PasscodeBox::recoverStartFail(const MTP::Error &error) { _pattern = QString(); closeBox(); } +#endif RecoverBox::RecoverBox( QWidget*, + Tdb::Sender &sender, +#if 0 // goodToRemove not_null mtp, +#endif Main::Session *session, const QString &pattern, const PasscodeBox::CloudFields &fields, Fn closeParent) : _session(session) +#if 0 // goodToRemove , _api(mtp) +#endif +, _api(sender) , _pattern(st::normalFont->elided(tr::lng_signin_recover_hint(tr::now, lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5)) , _cloudFields(fields) , _recoverCode(this, st::defaultInputField, tr::lng_signin_code()) @@ -1239,6 +1462,7 @@ void RecoverBox::submit() { } const auto send = crl::guard(this, [=] { +#if 0 // goodToRemove if (_cloudFields.turningOff) { // From "Disable cloud password". _submitRequest = _api.request(MTPauth_RecoverPassword( @@ -1260,6 +1484,36 @@ void RecoverBox::submit() { checkSubmitFail(error); }).handleFloodErrors().send(); } +#endif + _submitRequest = -1; + + const auto unauthCloudPassword = + lifetime().make_state(_api); + const auto cloudPassword = _session + ? &_session->api().cloudPassword() + : unauthCloudPassword; + + if (_cloudFields.turningOff) { + // From "Disable cloud password". + cloudPassword->recoverPassword( + code, + QString(), + QString() + ) | rpl::start_with_error_done([=](const QString &error) { + codeSubmitFail(error); + }, [=] { + proceedToClear(); + }, lifetime()); + } else { + // From "Change cloud password". + cloudPassword->checkRecoveryEmailAddressCode( + code + ) | rpl::start_with_error_done([=](const QString &error) { + codeSubmitFail(error); + }, [=] { + proceedToChange(code); + }, lifetime()); + } }); if (_cloudFields.notEmptyPassport) { getDelegate()->show(Ui::MakeConfirmBox({ @@ -1301,7 +1555,9 @@ void RecoverBox::proceedToChange(const QString &code) { fields.hasRecovery = false; // we could've been turning off, no need to force new password then // like if (_cloudFields.turningOff) { just RecoverPassword else Check } +#if 0 // goodToRemove fields.mtp.curRequest = {}; +#endif fields.hasPassword = false; fields.customCheckCallback = nullptr; auto box = Box(_session, fields); @@ -1325,6 +1581,39 @@ void RecoverBox::proceedToChange(const QString &code) { getDelegate()->show(std::move(box)); } +void RecoverBox::codeSubmitFail(const QString &error) { + if (Tdb::IsFloodError(error)) { + _submitRequest = 0; + setError(tr::lng_flood_error(tr::now)); + _recoverCode->showError(); + return; + } + _submitRequest = 0; + + if (error == u"PASSWORD_EMPTY"_q) { + _newPasswordSet.fire(QByteArray()); + getDelegate()->show( + Ui::MakeInformBox(tr::lng_cloud_password_removed(tr::now)), + Ui::LayerOption::CloseOther); + } else if (error == u"PASSWORD_RECOVERY_NA"_q) { + closeBox(); + } else if (error == u"PASSWORD_RECOVERY_EXPIRED"_q) { + _recoveryExpired.fire({}); + closeBox(); + } else if (error == u"CODE_INVALID"_q) { + setError(tr::lng_signin_wrong_code(tr::now)); + _recoverCode->selectAll(); + _recoverCode->setFocus(); + _recoverCode->showError(); + } else { + setError(Logs::DebugEnabled() // internal server error + ? error + : Lang::Hard::ServerError()); + _recoverCode->setFocus(); + } +} + +#if 0 // goodToRemove void RecoverBox::checkSubmitFail(const MTP::Error &error) { if (MTP::IsFloodError(error)) { _submitRequest = 0; @@ -1357,6 +1646,7 @@ void RecoverBox::checkSubmitFail(const MTP::Error &error) { _recoverCode->setFocus(); } } +#endif RecoveryEmailValidation ConfirmRecoveryEmail( not_null session, @@ -1369,6 +1659,36 @@ RecoveryEmailValidation ConfirmRecoveryEmail( const auto cancels = std::make_shared>(); const auto submit = [=](QString code) { + session->api().cloudPassword().confirmEmail( + code + ) | rpl::start_with_error_done([=](const QString &error) { + if (Tdb::IsFloodError(error)) { + errors->fire(tr::lng_flood_error(tr::now)); + } else if (error == qstr("CODE_INVALID")) { + errors->fire(tr::lng_signin_wrong_code(tr::now)); + } else if (error == qstr("EMAIL_HASH_EXPIRED")) { + cancels->fire({}); + if (*weak) { + auto box = Ui::MakeInformBox( + Lang::Hard::EmailConfirmationExpired()); + (*weak)->getDelegate()->show( + std::move(box), + Ui::LayerOption::CloseOther); + } + } else { + errors->fire(Lang::Hard::ServerError()); + } + }, [=] { + reloads->fire({}); + if (*weak) { + (*weak)->getDelegate()->show( + Ui::MakeInformBox( + tr::lng_cloud_password_was_set(tr::now)), + Ui::LayerOption::CloseOther); + } + }, (*weak)->lifetime()); + +#if 0 // goodToRemove if (*requestId) { return; } @@ -1401,8 +1721,10 @@ RecoveryEmailValidation ConfirmRecoveryEmail( errors->fire(Lang::Hard::ServerError()); } }).handleFloodErrors().send(); +#endif }; const auto resend = [=] { +#if 0 // goodToRemove if (*requestId) { return; } @@ -1414,6 +1736,18 @@ RecoveryEmailValidation ConfirmRecoveryEmail( *requestId = 0; errors->fire(Lang::Hard::ServerError()); }).send(); +#endif + + session->api().cloudPassword().resendEmailCode( + ) | rpl::start_with_error_done([=](const QString &error) { + if (Tdb::IsFloodError(error)) { + errors->fire(tr::lng_flood_error(tr::now)); + } else { + errors->fire(Lang::Hard::ServerError()); + } + }, [=] { + resent->fire(tr::lng_cloud_password_resent(tr::now)); + }, (*weak)->lifetime()); }; auto box = Passport::VerifyEmailBox( diff --git a/Telegram/SourceFiles/boxes/passcode_box.h b/Telegram/SourceFiles/boxes/passcode_box.h index 684da859ae8cf..052ae9c275ef8 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.h +++ b/Telegram/SourceFiles/boxes/passcode_box.h @@ -11,6 +11,9 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" #include "core/core_cloud_password.h" +#include "tdb/tdb_sender.h" +#include "api/api_cloud_password.h" + namespace MTP { class Instance; } // namespace MTP @@ -36,12 +39,14 @@ class PasscodeBox : public Ui::BoxContent { struct CloudFields { static CloudFields From(const Core::CloudPasswordState ¤t); +#if 0 // goodToRemove struct Mtp { Core::CloudPasswordCheckRequest curRequest; Core::CloudPasswordAlgo newAlgo; Core::SecureSecretAlgo newSecureSecretAlgo; }; Mtp mtp; +#endif bool hasPassword = false; bool hasRecovery = false; QString fromRecoveryCode; @@ -58,7 +63,10 @@ class PasscodeBox : public Ui::BoxContent { }; PasscodeBox( QWidget*, +#if 0 // goodToRemove not_null mtp, +#endif + Tdb::Sender &sender, Main::Session *session, const CloudFields &fields); PasscodeBox( @@ -72,7 +80,9 @@ class PasscodeBox : public Ui::BoxContent { rpl::producer newAuthorization() const; +#if 0 // mtp bool handleCustomCheckError(const MTP::Error &error); +#endif bool handleCustomCheckError(const QString &type); protected: @@ -98,34 +108,48 @@ class PasscodeBox : public Ui::BoxContent { bool onlyCheckCurrent() const; void setPasswordDone(const QByteArray &newPasswordBytes); +#if 0 // goodToRemove void recoverPasswordDone( const QByteArray &newPasswordBytes, const MTPauth_Authorization &result); +#endif + void recoverPasswordDone(const QByteArray &newPasswordBytes); + void setPasswordFail( + const QByteArray &newPasswordBytes, + const QString &email, + const QString &errorMessage); void setPasswordFail(const QString &type); +#if 0 // goodToRemove void setPasswordFail( const QByteArray &newPasswordBytes, const QString &email, const MTP::Error &error); +#endif void validateEmail( const QString &email, int codeLength, const QByteArray &newPasswordBytes); +#if 0 // goodToRemove void recoverStarted(const MTPauth_PasswordRecovery &result); void recoverStartFail(const MTP::Error &error); +#endif void recover(); void submitOnlyCheckCloudPassword(const QString &oldPassword); void setNewCloudPassword(const QString &newPassword); +#if 0 // goodToRemove void checkPassword( const QString &oldPassword, CheckPasswordCallback callback); void checkPasswordHash(CheckPasswordCallback callback); +#endif void changeCloudPassword( const QString &oldPassword, const QString &newPassword); +#if 0 // goodToRemove void changeCloudPassword( const QString &oldPassword, const Core::CloudPasswordResult &check, @@ -135,22 +159,34 @@ class PasscodeBox : public Ui::BoxContent { const Core::CloudPasswordResult &check, const QString &newPassword, const QByteArray &secureSecret); +#endif +#if 0 // goodToRemove - unneeded due TDLib? void suggestSecretReset(const QString &newPassword); void resetSecret( const Core::CloudPasswordResult &check, const QString &newPassword, Fn callback); +#endif void sendOnlyCheckCloudPassword(const QString &oldPassword); + void sendClearCloudPassword(const QString &oldPassword); +#if 0 // goodToRemove void sendClearCloudPassword(const Core::CloudPasswordResult &check); void handleSrpIdInvalid(); void requestPasswordData(); +#endif void passwordChecked(); void serverError(); Main::Session *_session = nullptr; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender &_api; + std::optional _unauthCloudPassword = std::nullopt; + + Api::CloudPassword &cloudPassword(); QString _pattern; @@ -190,7 +226,10 @@ class RecoverBox final : public Ui::BoxContent { public: RecoverBox( QWidget*, + Tdb::Sender &sender, +#if 0 // goodToRemove not_null mtp, +#endif Main::Session *session, const QString &pattern, const PasscodeBox::CloudFields &fields, @@ -214,12 +253,19 @@ class RecoverBox final : public Ui::BoxContent { void codeChanged(); void proceedToClear(); void proceedToChange(const QString &code); +#if 0 // goodToRemove void checkSubmitFail(const MTP::Error &error); +#endif + void codeSubmitFail(const QString &error); void setError(const QString &error); Main::Session *_session = nullptr; +#if 0 // goodToRemove MTP::Sender _api; mtpRequestId _submitRequest = 0; +#endif + Tdb::Sender &_api; + Tdb::RequestId _submitRequest = 0; QString _pattern; diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index bad9dbd89000b..33486d2394378 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -49,8 +49,12 @@ For license and copyright information please follow this link: #include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_list.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + constexpr auto kSortByOnlineThrottle = 3 * crl::time(1000); constexpr auto kSearchPerPage = 50; @@ -178,7 +182,10 @@ void PeerListRowWithLink::rightActionPaint( PeerListGlobalSearchController::PeerListGlobalSearchController( not_null session) : _session(session) +#if 0 // goodToRemove , _api(&session->mtp()) { +#endif +, _api(&session->sender()) { _timer.setCallback([this] { searchOnServer(); }); } @@ -205,6 +212,7 @@ bool PeerListGlobalSearchController::searchInCache() { } void PeerListGlobalSearchController::searchOnServer() { +#if 0 // goodToRemove _requestId = _api.request(MTPcontacts_Search( MTP_string(_query), MTP_int(SearchPeopleLimit) @@ -216,19 +224,38 @@ void PeerListGlobalSearchController::searchOnServer() { delegate()->peerListSearchRefreshRows(); } }).send(); +#endif + _requestId = _api.request(TLsearchChatsOnServer( + tl_string(_query), + tl_int32(SearchPeopleLimit) + )).done([=](const TLchats &result, RequestId requestId) { + searchDone(result, requestId); + }).fail([=](const Error &error, RequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + delegate()->peerListSearchRefreshRows(); + } + }).send(); _queries.emplace(_requestId, _query); } void PeerListGlobalSearchController::searchDone( +#if 0 // goodToRemove const MTPcontacts_Found &result, +#endif + const TLchats &result, mtpRequestId requestId) { +#if 0 // goodToRemove Expects(result.type() == mtpc_contacts_found); auto &contacts = result.c_contacts_found(); +#endif auto query = _query; if (requestId) { +#if 0 // goodToRemove _session->data().processUsers(contacts.vusers()); _session->data().processChats(contacts.vchats()); +#endif auto it = _queries.find(requestId); if (it != _queries.cend()) { query = it->second; @@ -236,6 +263,7 @@ void PeerListGlobalSearchController::searchDone( _queries.erase(it); } } +#if 0 // goodToRemove const auto feedList = [&](const MTPVector &list) { for (const auto &mtpPeer : list.v) { const auto peer = _session->data().peerLoaded( @@ -245,10 +273,21 @@ void PeerListGlobalSearchController::searchDone( } } }; +#endif if (_requestId == requestId) { _requestId = 0; + + for (const auto &peerId : result.data().vchat_ids().v) { + const auto peer = _session->data().peerLoaded( + peerFromTdbChat(peerId)); + if (peer) { + delegate()->peerListSearchAddRow(peer); + } + } +#if 0 // goodToRemove feedList(contacts.vmy_results()); feedList(contacts.vresults()); +#endif delegate()->peerListSearchRefreshRows(); } } @@ -713,7 +752,10 @@ auto ChooseRecipientBoxController::createRow( ChooseTopicSearchController::ChooseTopicSearchController( not_null forum) : _forum(forum) +, _api(&forum->session().sender()) +#if 0 // mtp , _api(&forum->session().mtp()) +#endif , _timer([=] { searchOnServer(); }) { } @@ -734,6 +776,24 @@ void ChooseTopicSearchController::searchQuery(const QString &query) { } void ChooseTopicSearchController::searchOnServer() { + _requestId = _api.request(TLgetForumTopics( + peerToTdbChat(_forum->channel()->id), + tl_string(_query), + tl_int32(_offsetDate), + tl_int53(_offsetId.bare), + tl_int53(_offsetTopicId.bare), + tl_int32(kSearchPerPage) + )).done([=](const TLDforumTopics &result) { + _requestId = 0; + const auto savedTopicId = _offsetTopicId; + _forum->applyReceivedTopics(result.vtopics().v, [&]( + not_null topic) { + delegate()->peerListSearchAddRow(topic->rootId().bare); + }); + _offsetDate = result.vnext_offset_date().v; + _offsetId = result.vnext_offset_message_id().v; + _offsetTopicId = result.vnext_offset_message_thread_id().v; +#if 0 // mtp _requestId = _api.request(MTPchannels_GetForumTopics( MTP_flags(MTPchannels_GetForumTopics::Flag::f_q), _forum->channel()->inputChannel, @@ -760,6 +820,7 @@ void ChooseTopicSearchController::searchOnServer() { } delegate()->peerListSearchAddRow(topic->rootId().bare); }); +#endif if (_offsetTopicId == savedTopicId) { _allLoaded = true; } diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index d720a3423eb37..5b25471e79e7a 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -13,6 +13,12 @@ For license and copyright information please follow this link: #include "base/timer.h" #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLchats; +} // namespace Tdb + class History; namespace Data { @@ -77,14 +83,23 @@ class PeerListGlobalSearchController : public PeerListSearchController { private: bool searchInCache(); void searchOnServer(); +#if 0 // goodToRemove void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId); +#endif + void searchDone(const Tdb::TLchats &result, mtpRequestId requestId); const not_null _session; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; base::Timer _timer; QString _query; mtpRequestId _requestId = 0; +#if 0 // goodToRemove std::map _cache; +#endif + std::map _cache; std::map _queries; }; @@ -245,7 +260,10 @@ class ChooseTopicSearchController : public PeerListSearchController { void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId); const not_null _forum; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; base::Timer _timer; QString _query; mtpRequestId _requestId = 0; diff --git a/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp b/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp index 1bffcb2eed959..f7963725e8795 100644 --- a/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp @@ -28,8 +28,12 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "styles/style_boxes.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + class Controller final : public PeerListController , public base::has_weak_ptr { @@ -156,6 +160,21 @@ void AddBotToGroupBoxController::requestExistingRights( return; } _existingRightsChannel = channel; + _bot->session().sender().request(_existingRightsRequestId).cancel(); + _bot->session().sender().request(TLgetChatMember( + peerToTdbChat(_existingRightsChannel->id), + peerToSender(_bot->id) + )).done([=](const TLchatMember &data) { + const auto participant = Api::ChatParticipant(data, channel); + _existingRights = participant.rights().flags; + _existingRank = participant.rank(); + addBotToGroup(_existingRightsChannel); + }).fail([=] { + _existingRights = ChatAdminRights(); + _existingRank = QString(); + addBotToGroup(_existingRightsChannel); + }).send(); +#if 0 // mtp _bot->session().api().request(_existingRightsRequestId).cancel(); _existingRightsRequestId = _bot->session().api().request( MTPchannels_GetParticipant( @@ -176,6 +195,7 @@ void AddBotToGroupBoxController::requestExistingRights( _existingRank = QString(); addBotToGroup(_existingRightsChannel); }).send(); +#endif } void AddBotToGroupBoxController::addBotToGroup(not_null chat) { @@ -190,7 +210,10 @@ void AddBotToGroupBoxController::addBotToGroup(not_null chat) { _existingRights = {}; _existingRank = QString(); _existingRightsChannel = nullptr; +#if 0 // mtp _bot->session().api().request(_existingRightsRequestId).cancel(); +#endif + _bot->session().sender().request(_existingRightsRequestId).cancel(); } const auto requestedAddAdmin = (_scope == Scope::GroupAdmin) || (_scope == Scope::ChannelAdmin); diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index b27ce349a7871..1e8d090dd12a0 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -36,6 +36,8 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "styles/style_layers.h" +#include "tdb/tdb_tl_scheme.h" + namespace { constexpr auto kParticipantsFirstPageCount = 16; @@ -634,7 +636,10 @@ AddSpecialBoxController::AddSpecialBoxController( peer, &_additional)) , _peer(peer) +#if 0 // goodToRemove , _api(&_peer->session().mtp()) +#endif +, _api(&_peer->session().sender()) , _role(role) , _additional(peer, Role::Members) , _adminDoneCallback(std::move(adminDoneCallback)) @@ -780,6 +785,7 @@ void AddSpecialBoxController::loadMoreRows() { const auto participantsHash = uint64(0); const auto channel = _peer->asChannel(); +#if 0 // goodToRemove _loadRequestId = _api.request(MTPchannels_GetParticipants( channel->inputChannel, MTP_channelParticipantsRecent(), @@ -816,6 +822,38 @@ void AddSpecialBoxController::loadMoreRows() { }).fail([this] { _loadRequestId = 0; }).send(); +#endif + _loadRequestId = _api.request(Tdb::TLgetSupergroupMembers( + Tdb::tl_int53(peerToChannel(channel->id).bare), + Tdb::tl_supergroupMembersFilterRecent(), + Tdb::tl_int32(_offset), + Tdb::tl_int32(perPage) + )).done([=](const Tdb::TLDchatMembers &data) { + _loadRequestId = 0; + auto &session = channel->session(); + const auto &[availableCount, list] = Api::ChatParticipants::Parse( + channel, + data); + for (const auto &data : list) { + if (const auto participant = _additional.applyParticipant(data)) { + appendRow(participant); + } + } + if (const auto size = list.size()) { + _offset += size; + } else { + // To be sure - wait for a whole empty result list. + _allLoaded = true; + } + if (delegate()->peerListFullRowsCount() > 0) { + setDescriptionText(QString()); + } else if (_allLoaded) { + setDescriptionText(tr::lng_blocked_list_not_found(tr::now)); + } + delegate()->peerListRefreshRows(); + }).fail([this] { + _loadRequestId = 0; + }).send(); } void AddSpecialBoxController::rowClicked(not_null row) { @@ -843,6 +881,7 @@ bool AddSpecialBoxController::checkInfoLoaded( // We don't know what this user status is in the group. const auto channel = _peer->asChannel(); +#if 0 // goodToRemove _api.request(MTPchannels_GetParticipant( channel->inputChannel, participant->input @@ -853,6 +892,13 @@ bool AddSpecialBoxController::checkInfoLoaded( Api::ChatParticipant(data.vparticipant(), channel)); }); callback(); +#endif + _api.request(Tdb::TLgetChatMember( + peerToTdbChat(channel->id), + peerToSender(participant->id) + )).done([=](const Tdb::TLchatMember &result) { + _additional.applyParticipant(Api::ChatParticipant(result, channel)); + callback(); }).fail([=] { _additional.setExternal(participant); callback(); @@ -1142,12 +1188,20 @@ std::unique_ptr AddSpecialBoxController::createRow( return std::make_unique(participant); } +struct AddSpecialBoxSearchController::CacheEntry { + Tdb::TLchatMembers result; + int requestedCount = 0; +}; + AddSpecialBoxSearchController::AddSpecialBoxSearchController( not_null peer, not_null additional) : _peer(peer) , _additional(additional) +#if 0 // goodToRemove , _api(&_peer->session().mtp()) +#endif +, _api(&_peer->session().sender()) , _timer([=] { searchOnServer(); }) { subscribeToMigration(); } @@ -1243,6 +1297,7 @@ void AddSpecialBoxSearchController::requestParticipants() { const auto participantsHash = uint64(0); const auto channel = _peer->asChannel(); +#if 0 // goodToRemove _requestId = _api.request(MTPchannels_GetParticipants( channel->inputChannel, MTP_channelParticipantsSearch(MTP_string(_query)), @@ -1254,6 +1309,15 @@ void AddSpecialBoxSearchController::requestParticipants() { mtpRequestId requestId) { searchParticipantsDone(requestId, result, perPage); }).fail([=](const MTP::Error &error, mtpRequestId requestId) { +#endif + _requestId = _api.request(Tdb::TLgetSupergroupMembers( + Tdb::tl_int53(peerToChannel(channel->id).bare), + Tdb::tl_supergroupMembersFilterSearch(Tdb::tl_string(_query)), + Tdb::tl_int32(_offset), + Tdb::tl_int32(perPage) + )).done([=](const Tdb::TLchatMembers &result, Tdb::RequestId requestId) { + searchParticipantsDone(requestId, result, perPage); + }).fail([=](const Tdb::Error &error, Tdb::RequestId requestId) { if (_requestId == requestId) { _requestId = 0; _participantsLoaded = true; @@ -1270,7 +1334,10 @@ void AddSpecialBoxSearchController::requestParticipants() { void AddSpecialBoxSearchController::searchParticipantsDone( mtpRequestId requestId, +#if 0 // goodToRemove const MTPchannels_ChannelParticipants &result, +#endif + const Tdb::TLchatMembers &result, int requestedCount) { Expects(_peer->isChannel()); @@ -1289,6 +1356,9 @@ void AddSpecialBoxSearchController::searchParticipantsDone( _participantsQueries.erase(it); } }; + Api::ChatParticipants::Parse(channel, result.data()); + addToCache(); +#if 0 // goodToRemove result.match([&](const MTPDchannels_channelParticipants &data) { Api::ChatParticipants::Parse(channel, data); addToCache(); @@ -1296,12 +1366,14 @@ void AddSpecialBoxSearchController::searchParticipantsDone( LOG(("API Error: " "channels.channelParticipantsNotModified received!")); }); +#endif } if (_requestId != requestId) { return; } _requestId = 0; +#if 0 // goodToRemove result.match([&](const MTPDchannels_channelParticipants &data) { const auto &list = data.vparticipants().v; if (list.size() < requestedCount) { @@ -1324,6 +1396,25 @@ void AddSpecialBoxSearchController::searchParticipantsDone( }, [&](const MTPDchannels_channelParticipantsNotModified &) { _participantsLoaded = true; }); +#endif + const auto &list = result.data().vmembers().v; + if (list.size() < requestedCount) { + // We want cache to have full information about a query with + // small results count (that we don't need the second request). + // So we don't wait for empty list unlike the non-search case. + _participantsLoaded = true; + if (list.empty() && _offset == 0) { + // No results, request global search immediately. + loadMoreRows(); + } + } + for (const auto &data : list) { + if (const auto user = _additional->applyParticipant( + Api::ChatParticipant(data, channel))) { + delegate()->peerListSearchAddRow(user); + } + } + _offset += list.size(); delegate()->peerListSearchRefreshRows(); } @@ -1334,6 +1425,7 @@ void AddSpecialBoxSearchController::requestGlobal() { return; } +#if 0 // goodToRemove auto perPage = SearchPeopleLimit; _requestId = _api.request(MTPcontacts_Search( MTP_string(_query), @@ -1341,6 +1433,15 @@ void AddSpecialBoxSearchController::requestGlobal() { )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) { searchGlobalDone(requestId, result); }).fail([=](const MTP::Error &error, mtpRequestId requestId) { +#endif + _globalQueries.emplace(_requestId, _query); + auto perPage = SearchPeopleLimit; + _requestId = _api.request(Tdb::TLsearchChatsOnServer( + Tdb::tl_string(_query), + Tdb::tl_int32(perPage) + )).done([=](const Tdb::TLchats &result, Tdb::RequestId requestId) { + searchGlobalDone(requestId, result); + }).fail([=](const Tdb::Error &error, Tdb::RequestId requestId) { if (_requestId == requestId) { _requestId = 0; _globalLoaded = true; @@ -1350,16 +1451,20 @@ void AddSpecialBoxSearchController::requestGlobal() { _globalQueries.emplace(_requestId, _query); } + void AddSpecialBoxSearchController::searchGlobalDone( mtpRequestId requestId, + const Tdb::TLchats &result) { +#if 0 // goodToRemove const MTPcontacts_Found &result) { - Expects(result.type() == mtpc_contacts_found); - auto &found = result.c_contacts_found(); +#endif auto query = _query; if (requestId) { +#if 0 // goodToRemove _peer->owner().processUsers(found.vusers()); _peer->owner().processChats(found.vchats()); +#endif auto it = _globalQueries.find(requestId); if (it != _globalQueries.cend()) { query = it->second; @@ -1368,9 +1473,14 @@ void AddSpecialBoxSearchController::searchGlobalDone( } } +#if 0 // goodToRemove const auto feedList = [&](const MTPVector &list) { for (const auto &mtpPeer : list.v) { const auto peerId = peerFromMTP(mtpPeer); +#endif + const auto feedList = [&] { + for (const auto &chatId : result.data().vchat_ids().v) { + const auto peerId = peerFromTdbChat(chatId); if (const auto peer = _peer->owner().peerLoaded(peerId)) { if (const auto user = peer->asUser()) { _additional->checkForLoaded(user); @@ -1382,8 +1492,11 @@ void AddSpecialBoxSearchController::searchGlobalDone( if (_requestId == requestId) { _requestId = 0; _globalLoaded = true; +#if 0 // goodToRemove feedList(found.vmy_results()); feedList(found.vresults()); +#endif + feedList(); delegate()->peerListSearchRefreshRows(); } } diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.h b/Telegram/SourceFiles/boxes/peers/add_participants_box.h index c86c9968776f6..1e9f697304883 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.h +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.h @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #pragma once +#include "tdb/tdb_sender.h" #include "boxes/peer_list_controllers.h" #include "boxes/peers/edit_participants_box.h" @@ -141,7 +142,10 @@ class AddSpecialBoxController QPointer showBox(object_ptr box) const; not_null _peer; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; Role _role = Role::Admins; int _offset = 0; mtpRequestId _loadRequestId = 0; @@ -172,10 +176,13 @@ class AddSpecialBoxSearchController : public PeerListSearchController { bool loadMoreRows() override; private: +#if 0 // goodToRemove struct CacheEntry { MTPchannels_ChannelParticipants result; int requestedCount = 0; }; +#endif + struct CacheEntry; struct Query { QString text; int offset = 0; @@ -185,12 +192,18 @@ class AddSpecialBoxSearchController : public PeerListSearchController { bool searchParticipantsInCache(); void searchParticipantsDone( mtpRequestId requestId, +#if 0 // goodToRemove const MTPchannels_ChannelParticipants &result, +#endif + const Tdb::TLchatMembers &result, int requestedCount); bool searchGlobalInCache(); void searchGlobalDone( mtpRequestId requestId, + const Tdb::TLchats &result); +#if 0 // goodToRemove const MTPcontacts_Found &result); +#endif void requestParticipants(); void addChatMembers(not_null chat); void addChatsContacts(); @@ -200,7 +213,10 @@ class AddSpecialBoxSearchController : public PeerListSearchController { not_null _peer; not_null _additional; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; base::Timer _timer; QString _query; @@ -212,7 +228,10 @@ class AddSpecialBoxSearchController : public PeerListSearchController { bool _globalLoaded = false; std::map _participantsCache; std::map _participantsQueries; +#if 0 // goodToRemove std::map _globalCache; +#endif + std::map _globalCache; std::map _globalQueries; }; diff --git a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp index 19af19ac68493..5658b3a497007 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp @@ -27,8 +27,13 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "styles/style_info.h" +#include "tdb/tdb_format_phone.h" // Tdb::FormatPhone +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + QString UserPhone(not_null user) { const auto phone = user->phone(); return phone.isEmpty() @@ -45,6 +50,7 @@ void SendRequest( const QString &phone, Fn done) { const auto wasContact = user->isContact(); +#if 0 // goodToRemove using Flag = MTPcontacts_AddContact::Flag; user->session().api().request(MTPcontacts_AddContact( MTP_flags(sharePhone @@ -55,12 +61,24 @@ void SendRequest( MTP_string(last), MTP_string(phone) )).done([=](const MTPUpdates &result) { +#endif + user->session().sender().request(TLaddContact( + tl_contact( + tl_string(phone), + tl_string(first), + tl_string(last), + tl_string(), // VCard. + tl_int53(peerToUser(user->id).bare)), + tl_bool(sharePhone) + )).done([=] { user->setName( first, last, user->nameOrPhone, user->username()); +#if 0 // goodToRemove user->session().api().applyUpdates(result); +#endif if (const auto settings = user->settings()) { const auto flags = PeerSetting::AddContact | PeerSetting::BlockContact @@ -147,7 +165,7 @@ void Controller::setupCover() { Info::Profile::Cover::Role::EditContact, (_phone.isEmpty() ? tr::lng_contact_mobile_hidden() - : rpl::single(Ui::FormatPhone(_phone)))), + : rpl::single(Tdb::FormatPhone(_phone)))), style::margins()); _updatedPersonalPhoto = [=] { return cover->updatedPersonalPhoto(); }; } diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index 2a48825886ecd..b5b646875b892 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -38,8 +38,13 @@ For license and copyright information please follow this link: #include "styles/style_dialogs.h" #include "styles/style_chat_helpers.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace { +using namespace Tdb; + constexpr auto kDefaultIconId = DocumentId(0x7FFF'FFFF'FFFF'FFFFULL); struct DefaultIcon { @@ -534,6 +539,22 @@ void EditForumTopicBox( topic->applyIconId(state->iconId.current()); box->closeBox(); } else { + const auto sender = &forum->session().sender(); + const auto weak = Ui::MakeWeak(box.get()); + state->requestId = sender->request(TLeditForumTopic( + peerToTdbChat(topic->channel()->id), + tl_int53(topic->rootId().bare), + tl_string(title->getLastText().trimmed()), + tl_bool(!topic->isGeneral()), + tl_int64(state->iconId.current()) + )).done([=] { + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }).fail([=] { + state->requestId = -1; + }).send(); +#if 0 // mtp using Flag = MTPchannels_EditForumTopic::Flag; const auto api = &forum->session().api(); const auto weak = Ui::MakeWeak(box.get()); @@ -560,6 +581,7 @@ void EditForumTopicBox( } } }).send(); +#endif } }; diff --git a/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp b/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp index 8cf55a1e372e9..d54614512bd1f 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp @@ -23,8 +23,13 @@ For license and copyright information please follow this link: #include "styles/style_info.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace { +using namespace Tdb; + [[nodiscard]] int EnableHideMembersMin(not_null channel) { return channel->session().account().appConfig().get( u"hidden_members_group_size_min"_q, @@ -64,6 +69,7 @@ namespace { button->toggledValue( ) | rpl::start_with_next([=](bool toggled) { +#if 0 // mtp megagroup->session().api().request( MTPchannels_ToggleParticipantsHidden( megagroup->inputChannel, @@ -72,6 +78,12 @@ namespace { ).done([=](const MTPUpdates &result) { megagroup->session().api().applyUpdates(result); }).send(); +#endif + megagroup->session().sender().request( + TLtoggleSupergroupHasHiddenMembers( + peerToTdbChat(megagroup->id), + tl_bool(toggled)) + ).send(); }, button->lifetime()); return result; diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index d867c0607a375..d1ddc80f0ab02 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -43,6 +43,8 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "styles/style_info.h" +#include "tdb/tdb_tl_scheme.h" + namespace { constexpr auto kMaxRestrictDelayDays = 366; @@ -527,6 +529,7 @@ void EditAdminBox::transferOwnership() { return; } +#if 0 // goodToRemove const auto channel = peer()->isChannel() ? peer()->asChannel()->inputChannel : MTP_inputChannelEmpty(); @@ -539,6 +542,25 @@ void EditAdminBox::transferOwnership() { )).fail([=](const MTP::Error &error) { _checkTransferRequestId = 0; if (!handleTransferPasswordError(error.type())) { +#endif + peer()->session().api().cloudPassword().reload(); + using namespace Tdb; + _checkTransferRequestId = peer()->session().sender().request( + TLcanTransferOwnership() + ).done([=](const TLcanTransferOwnershipResult &result) { + _checkTransferRequestId = 0; + // later Can be simplified. + const auto error = result.match([]( + const TLDcanTransferOwnershipResultOk &) { + return QString(); + }, [](const TLDcanTransferOwnershipResultPasswordNeeded &) { + return u"PASSWORD_MISSING"_q; + }, [](const TLDcanTransferOwnershipResultPasswordTooFresh &) { + return u"PASSWORD_TOO_FRESH_"_q; + }, [](const TLDcanTransferOwnershipResultSessionTooFresh &) { + return u"SESSION_TOO_FRESH_"_q; + }); + if (!handleTransferPasswordError(error)) { const auto callback = crl::guard(this, [=](Fn &&close) { transferOwnershipChecked(); close(); @@ -616,6 +638,13 @@ void EditAdminBox::sendTransferRequestFrom( } const auto weak = Ui::MakeWeak(this); const auto user = this->user(); + _transferRequestId = channel->session().sender().request( + Tdb::TLtransferChatOwnership( + peerToTdbChat(channel->id), + Tdb::tl_int53(peerToUser(user->id).bare), + Tdb::tl_string(result.password)) + ).done([=] { +#if 0 // goodToRemove const auto api = &channel->session().api(); _transferRequestId = api->request(MTPchannels_EditCreator( channel->inputChannel, @@ -623,6 +652,7 @@ void EditAdminBox::sendTransferRequestFrom( result.result )).done([=](const MTPUpdates &result) { api->applyUpdates(result); +#endif if (!box && !weak) { return; } @@ -635,6 +665,16 @@ void EditAdminBox::sendTransferRequestFrom( lt_user, user->shortName())); show->hideLayer(); + }).fail(crl::guard(this, [=](const Tdb::Error &error) { + if (weak) { + _transferRequestId = 0; + } + if (box && box->handleCustomCheckError(error.message)) { + return; + } + + const auto &type = error.message; +#if 0 // goodToRemove }).fail(crl::guard(this, [=](const MTP::Error &error) { if (weak) { _transferRequestId = 0; @@ -644,6 +684,7 @@ void EditAdminBox::sendTransferRequestFrom( } const auto &type = error.type(); +#endif const auto problem = [&] { if (type == u"CHANNELS_ADMIN_PUBLIC_TOO_MUCH"_q) { return tr::lng_channels_too_much_public_other(tr::now); @@ -673,7 +714,10 @@ void EditAdminBox::sendTransferRequestFrom( if (weak && !recoverable) { closeBox(); } +#if 0 // goodToRemove })).handleFloodErrors().send(); +#endif + })).send(); } void EditAdminBox::refreshAboutAddAdminsText(bool canAddAdmins) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index ff5926608167f..e534e641247e3 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #include "boxes/peers/edit_participants_box.h" +#include "data/data_channel_admins.h" #include "api/api_chat_participants.h" #include "boxes/peers/edit_participant_box.h" #include "boxes/peers/add_participants_box.h" @@ -36,8 +37,12 @@ For license and copyright information please follow this link: #include "history/history.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + // How many messages from chat history server should forward to user, // that was added to this chat. constexpr auto kForwardMessagesOnAdd = 100; @@ -47,11 +52,33 @@ constexpr auto kParticipantsPerPage = 200; constexpr auto kSortByOnlineDelay = crl::time(1000); void RemoveAdmin( +#if 0 // goodToRemove not_null channel, +#endif + not_null peer, not_null user, ChatAdminRightsInfo oldRights, Fn onDone, Fn onFail) { + peer->session().sender().request(TLsetChatMemberStatus( + peerToTdbChat(peer->id), + peerToSender(user->id), + tl_chatMemberStatusMember() + )).done([=] { + if (const auto channel = peer->asChannel()) { + channel->applyEditAdmin(user, oldRights, {}, {}); + } else if (const auto chat = peer->asChat()) { + chat->applyEditAdmin(user, false); + } + if (onDone) { + onDone(); + } + }).fail([=] { + if (onFail) { + onFail(); + } + }).send(); +#if 0 // goodToRemove const auto newRights = MTP_chatAdminRights(MTP_flags(0)); channel->session().api().request(MTPchannels_EditAdmin( channel->inputChannel, @@ -69,6 +96,7 @@ void RemoveAdmin( onFail(); } }).send(); +#endif } void AddChatParticipant( @@ -76,6 +104,21 @@ void AddChatParticipant( not_null user, Fn onDone, Fn onFail) { + chat->session().sender().request(TLaddChatMember( + peerToTdbChat(chat->id), + tl_int53(peerToUser(user->id).bare), + tl_int32(kForwardMessagesOnAdd) + )).done([=] { + if (onDone) { + onDone(); + } + }).fail([=](const Error &error) { + ShowAddParticipantsError(error.message, chat, { 1, user }); + if (onFail) { + onFail(); + } + }).send(); +#if 0 // goodToRemove chat->session().api().request(MTPmessages_AddChatUser( chat->inputChat, user->inputUser, @@ -91,8 +134,10 @@ void AddChatParticipant( onFail(); } }).send(); +#endif } +#if 0 // goodToRemove void SaveChatAdmin( not_null chat, not_null user, @@ -198,6 +243,7 @@ void SaveChatParticipantKick( } }).send(); } +#endif } // namespace @@ -216,6 +262,65 @@ Fn peer) -> void { + Expects(peer->isChat() || peer->isChannel()); + const auto isChannel = peer->isChat(); + peer->session().sender().request(TLsetChatMemberStatus( + peerToTdbChat(peer->id), + peerToSender(user->id), + ChatAdminRightsInfo::ToTL(newRights, rank) + )).done([=] { + if (isChannel) { + (peer->asChannel())->applyEditAdmin( + user, + oldRights, + newRights, + rank); + } else if (const auto chat = peer->asChat()) { + chat->applyEditAdmin(user, isAdmin); + } + done(); + }).fail([=](const Error &error) { + const auto message = error.message; + if (isChannel) { + ShowAddParticipantsError( + message, + peer->asChannel(), + { 1, user }); + } else if (peer->isChat() + && isAdmin + && (message == u"USER_NOT_PARTICIPANT"_q)) { + AddChatParticipant(peer->asChat(), user, [=] { + repeatRequest(repeatRequest, peer); + }, onFail); + } else if (onFail) { + onFail(); + } + }).send(); + }; + + if (const auto chat = peer->asChatNotMigrated()) { + if (newRights.flags == chat->defaultAdminRights(user).flags + && rank.isEmpty()) { + request(request, chat); + } else if (!newRights.flags) { + request(request, chat); + } else { + peer->session().api().migrateChat(chat, [=]( + not_null channel) { + request(request, channel); + }); + } + } else if (const auto channel = peer->asChannelOrMigrated()) { + request(request, channel); + } else { + Unexpected("Peer in SaveAdminCallback."); + } +#if 0 // goodToRemove const auto saveForChannel = [=](not_null channel) { SaveChannelAdmin( channel, @@ -243,6 +348,7 @@ Fn peer) { + Expects(peer->isChat() || peer->isChannel()); + const auto isChannel = peer->isChat(); + peer->session().sender().request(TLsetChatMemberStatus( + peerToTdbChat(peer->id), + peerToSender(participant->id), + ChatRestrictionsInfo::ToTL(newRights) + )).done([=] { + if (isChannel) { + (peer->asChannel())->applyEditBanned( + participant, + oldRights, + newRights); + } + done(); + }).fail([=] { + if (onFail) { + onFail(); + } + }).send(); + }; + if (const auto chat = peer->asChatNotMigrated()) { + if (participant->isUser() + && (newRights.flags & ChatRestriction::ViewMessages)) { + request(chat); + } else if (!newRights.flags) { + done(); + } else { + peer->session().api().migrateChat(chat, [=]( + const not_null channel) { + request(channel); + }); + } + } else if (const auto channel = peer->asChannelOrMigrated()) { + request(channel); + } else { + Unexpected("Peer in SaveAdminCallback."); + } +#if 0 // goodToRemove const auto saveForChannel = [=](not_null channel) { SaveChannelRestriction( channel, @@ -284,6 +430,7 @@ Fnsession().mtp()) +#endif +, _api(&_peer->session().sender()) , _role(role) , _additional(peer, _role) { subscribeToMigration(); @@ -1446,6 +1596,7 @@ void ParticipantsBoxController::loadMoreRows() { return; } +#if 0 // goodToRemove const auto filter = [&] { if (_role == Role::Members || _role == Role::Profile) { return MTP_channelParticipantsRecent(); @@ -1456,11 +1607,24 @@ void ParticipantsBoxController::loadMoreRows() { } return MTP_channelParticipantsKicked(MTP_string()); }(); +#endif + using namespace Tdb; + const auto filter = [&] { + if (_role == Role::Members || _role == Role::Profile) { + return tl_supergroupMembersFilterRecent(); + } else if (_role == Role::Admins) { + return tl_supergroupMembersFilterAdministrators(); + } else if (_role == Role::Restricted) { + return tl_supergroupMembersFilterRestricted(tl_string()); + } + return tl_supergroupMembersFilterBanned(tl_string()); + }(); // First query is small and fast, next loads a lot of rows. const auto perPage = (_offset > 0) ? kParticipantsPerPage : kParticipantsFirstPageCount; +#if 0 // goodToRemove const auto participantsHash = uint64(0); _loadRequestId = _api.request(MTPchannels_GetParticipants( @@ -1508,6 +1672,34 @@ void ParticipantsBoxController::loadMoreRows() { if (!firstLoad && !added) { _allLoaded = true; } +#endif + _loadRequestId = _api.request(TLgetSupergroupMembers( + tl_int53(peerToChannel(channel->id).bare), + filter, + tl_int32(_offset), + tl_int32(perPage) + )).done([=](const TLchatMembers &result) { + const auto firstLoad = !_offset; + _loadRequestId = 0; + + const auto wasRecentRequest = firstLoad + && (_role == Role::Members || _role == Role::Profile); + + const auto &[availableCount, list] = wasRecentRequest + ? Api::ChatParticipants::ParseRecent(channel, result.data()) + : Api::ChatParticipants::Parse(channel, result.data()); + for (const auto &data : list) { + if (const auto participant = _additional.applyParticipant(data)) { + appendRow(participant); + } + } + if (const auto size = list.size()) { + _offset += size; + } else { + // To be sure - wait for a whole empty result list. + _allLoaded = true; + } + if (_allLoaded || (firstLoad && delegate()->peerListFullRowsCount() > 0)) { refreshDescription(); @@ -1905,7 +2097,10 @@ void ParticipantsBoxController::removeAdminSure(not_null user) { _editBox = nullptr; if (const auto chat = _peer->asChat()) { +#if 0 // goodToRemove SaveChatAdmin(chat, user, false, crl::guard(this, [=] { +#endif + RemoveAdmin(chat, user, {}, crl::guard(this, [=] { editAdminDone( user, ChatAdminRightsInfo(), @@ -2143,6 +2338,7 @@ void ParticipantsBoxController::subscribeToCreatorChange( return; } const auto weak = base::make_weak(this); +#if 0 // goodToRemove const auto api = &channel->session().api(); api->request(MTPchannels_GetParticipants( channel->inputChannel, @@ -2151,6 +2347,13 @@ void ParticipantsBoxController::subscribeToCreatorChange( MTP_int(channel->session().serverConfig().chatSizeMax), MTP_long(0) // hash )).done([=](const MTPchannels_ChannelParticipants &result) { +#endif + channel->session().sender().request(TLgetSupergroupMembers( + tl_int53(peerToChannel(channel->id).bare), + tl_supergroupMembersFilterRecent(), + tl_int32(0), // offset + tl_int32(channel->session().serverConfig().chatSizeMax) + )).done([=](const TLchatMembers &result) { if (channel->amCreator()) { channel->mgInfo->creator = channel->session().user().get(); } @@ -2158,10 +2361,13 @@ void ParticipantsBoxController::subscribeToCreatorChange( channel->mgInfo->lastRestricted.clear(); channel->mgInfo->lastParticipants.clear(); + Api::ChatParticipants::ParseRecent(channel, result.data()); +#if 0 // goodToRemove result.match([&](const MTPDchannels_channelParticipants &data) { Api::ChatParticipants::ParseRecent(channel, data); }, [](const MTPDchannels_channelParticipantsNotModified &) { }); +#endif if (weak) { fullListRefresh(); @@ -2186,6 +2392,11 @@ void ParticipantsBoxController::refreshRows() { delegate()->peerListRefreshRows(); } +struct ParticipantsBoxSearchController::CacheEntry { + TLchatMembers result; + int requestedCount = 0; +}; + ParticipantsBoxSearchController::ParticipantsBoxSearchController( not_null channel, Role role, @@ -2193,7 +2404,10 @@ ParticipantsBoxSearchController::ParticipantsBoxSearchController( : _channel(channel) , _role(role) , _additional(additional) +#if 0 // goodToRemove , _api(&_channel->session().mtp()) { +#endif +, _api(&_channel->session().sender()) { _timer.setCallback([=] { searchOnServer(); }); } @@ -2269,6 +2483,7 @@ bool ParticipantsBoxSearchController::loadMoreRows() { if (_allLoaded || isLoading()) { return true; } +#if 0 // goodToRemove auto filter = [&] { switch (_role) { case Role::Admins: // Search for members, appoint as admin on found. @@ -2282,11 +2497,27 @@ bool ParticipantsBoxSearchController::loadMoreRows() { } Unexpected("Role in ParticipantsBoxSearchController."); }(); +#endif + using namespace Tdb; + const auto filter = [&] { + switch (_role) { + case Role::Admins: // Search for members, appoint as admin on found. + case Role::Profile: + case Role::Members: + return tl_supergroupMembersFilterSearch(tl_string(_query)); + case Role::Restricted: + return tl_supergroupMembersFilterRestricted(tl_string(_query)); + case Role::Kicked: + return tl_supergroupMembersFilterBanned(tl_string(_query)); + } + Unexpected("Role in ParticipantsBoxSearchController."); + }(); // For search we request a lot of rows from the first query. // (because we've waited for search request by timer already, // so we don't expect it to be fast, but we want to fill cache). const auto perPage = kParticipantsPerPage; +#if 0 // goodToRemove const auto participantsHash = uint64(0); _requestId = _api.request(MTPchannels_GetParticipants( @@ -2300,6 +2531,15 @@ bool ParticipantsBoxSearchController::loadMoreRows() { mtpRequestId requestId) { searchDone(requestId, result, perPage); }).fail([=](const MTP::Error &error, mtpRequestId requestId) { +#endif + _requestId = _api.request(TLgetSupergroupMembers( + tl_int53(peerToChannel(_channel->id).bare), + filter, + tl_int32(_offset), + tl_int32(perPage) + )).done([=](const TLchatMembers &result, RequestId requestId) { + searchDone(requestId, result, perPage); + }).fail([=](const Error &error, RequestId requestId) { if (_requestId == requestId) { _requestId = 0; _allLoaded = true; @@ -2316,7 +2556,10 @@ bool ParticipantsBoxSearchController::loadMoreRows() { void ParticipantsBoxSearchController::searchDone( mtpRequestId requestId, +#if 0 // goodToRemove const MTPchannels_ChannelParticipants &result, +#endif + const TLchatMembers &result, int requestedCount) { auto query = _query; if (requestId) { @@ -2332,6 +2575,9 @@ void ParticipantsBoxSearchController::searchDone( _queries.erase(it); } }; + Api::ChatParticipants::Parse(_channel, result.data()); + addToCache(); +#if 0 // goodToRemove result.match([&](const MTPDchannels_channelParticipants &data) { Api::ChatParticipants::Parse(_channel, data); addToCache(); @@ -2339,12 +2585,14 @@ void ParticipantsBoxSearchController::searchDone( LOG(("API Error: " "channels.channelParticipantsNotModified received!")); }); +#endif } if (_requestId != requestId) { return; } _requestId = 0; +#if 0 // goodToRemove result.match([&](const MTPDchannels_channelParticipants &data) { const auto &list = data.vparticipants().v; if (list.size() < requestedCount) { @@ -2368,6 +2616,26 @@ void ParticipantsBoxSearchController::searchDone( }, [&](const MTPDchannels_channelParticipantsNotModified &) { _allLoaded = true; }); +#endif + const auto &list = result.data().vmembers().v; + if (list.size() < requestedCount) { + // We want cache to have full information about a query with + // small results count (that we don't need the second request). + // So we don't wait for empty list unlike the non-search case. + _allLoaded = true; + } + const auto overrideRole = (_role == Role::Admins) + ? Role::Members + : _role; + for (const auto &data : list) { + const auto user = _additional->applyParticipant( + Api::ChatParticipant(data, _channel), + overrideRole); + if (user) { + delegate()->peerListSearchAddRow(user); + } + } + _offset += list.size(); delegate()->peerListSearchRefreshRows(); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.h b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h index d43d99e274f47..d1a53c97fb64b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h @@ -13,6 +13,12 @@ For license and copyright information please follow this link: #include "base/weak_ptr.h" #include "info/profile/info_profile_members_controllers.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLchatMembers; +} // namespace Tdb + class PeerListStories; struct ChatAdminRightsInfo; struct ChatRestrictionsInfo; @@ -280,7 +286,10 @@ class ParticipantsBoxController Window::SessionNavigation *_navigation = nullptr; not_null _peer; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; Role _role = Role::Admins; int _offset = 0; mtpRequestId _loadRequestId = 0; @@ -321,10 +330,13 @@ class ParticipantsBoxSearchController : public PeerListSearchController { bool allLoaded = false; bool wasLoading = false; }; +#if 0 // goodToRemove struct CacheEntry { MTPchannels_ChannelParticipants result; int requestedCount = 0; }; +#endif + struct CacheEntry; struct Query { QString text; int offset = 0; @@ -334,13 +346,19 @@ class ParticipantsBoxSearchController : public PeerListSearchController { bool searchInCache(); void searchDone( mtpRequestId requestId, +#if 0 // goodToRemove const MTPchannels_ChannelParticipants &result, +#endif + const Tdb::TLchatMembers &result, int requestedCount); not_null _channel; Role _role = Role::Restricted; not_null _additional; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; base::Timer _timer; QString _query; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 1ebe391197e30..8305d38e47db5 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -47,8 +47,14 @@ For license and copyright information please follow this link: #include "styles/style_settings.h" #include "styles/style_widgets.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_options.h" + namespace { +using namespace Tdb; + using namespace Settings; constexpr auto kFakeChannelId = ChannelId(0xFFFFFFF000ULL); @@ -299,7 +305,9 @@ PreviewWrap::PreviewWrap( ? tr::lng_settings_color_reply(tr::now) : tr::lng_settings_color_reply_channel(tr::now), }, +#if 0 // mtp MTP_messageMediaEmpty(), +#endif HistoryMessageMarkupData(), uint64(0))) , _replyItem(_history->addNewLocalMessage( @@ -317,6 +325,7 @@ PreviewWrap::PreviewWrap( ? tr::lng_settings_color_text(tr::now) : tr::lng_settings_color_text_channel(tr::now), }, +#if 0 // mtp MTP_messageMediaWebPage( MTP_flags(0), MTP_webPagePending( @@ -324,12 +333,21 @@ PreviewWrap::PreviewWrap( MTP_long(_webpage->id), MTPstring(), MTP_int(0))), +#endif HistoryMessageMarkupData(), uint64(0))) +#if 0 // mtp , _element(_replyItem->createView(_delegate.get())) +#endif , _position(0, st::msgMargin.bottom()) { _style->apply(_theme.get()); + _replyItem->setMediaExplicit(std::make_unique( + _replyItem.get(), + _webpage.get(), + MediaWebPageFlag::Manual)); + _element = _replyItem->createView(_delegate.get()); + _fake->setName(peer->name(), QString()); std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) { _fake->changeColorIndex(index); @@ -450,6 +468,25 @@ void Set( ? tr::lng_settings_color_changed(tr::now) : tr::lng_settings_color_changed_channel(tr::now)); }; + const auto fail = [=](const Error &error) { + setLocal(wasIndex, wasEmojiId); + show->showToast(error.message); + }; + const auto send = [&](auto &&request) { + peer->session().sender().request( + std::move(request) + ).done(done).fail(fail).send(); + }; + if (peer->isSelf()) { + send(TLsetAccentColor( + tl_int32(colorIndex), + tl_int64(backgroundEmojiId))); + } else if (const auto channel = peer->asChannel()) { + send(TLsetChatAccentColor( + peerToTdbChat(channel->id), + tl_int32(colorIndex), + tl_int64(backgroundEmojiId))); +#if 0 // mtp const auto fail = [=](const MTP::Error &error) { setLocal(wasIndex, wasEmojiId); show->showToast(error.type()); @@ -472,6 +509,7 @@ void Set( channel->inputChannel, MTP_int(colorIndex), MTP_long(backgroundEmojiId))); +#endif } else { Unexpected("Invalid peer type in Set(colorIndex)."); } @@ -504,6 +542,10 @@ void Apply( Set(show, peer, colorIndex, backgroundEmojiId); close(); } else { + session->sender().request(TLgetChatBoostStatus( + peerToTdbChat(peer->id) + )).done([=](const TLchatBoostStatus &result) { +#if 0 // mtp session->api().request(MTPpremium_GetBoostsStatus( peer->input )).done([=](const MTPpremium_BoostsStatus &result) { @@ -512,6 +554,11 @@ void Apply( "channel_color_level_min", 5); if (data.vlevel().v >= required) { +#endif + const auto options = &session->account().options(); + const auto required + = options->channelCustomAccentColorBoostLevelMin(); + if (5 >= required) { Set(show, peer, colorIndex, backgroundEmojiId); close(); return; @@ -525,13 +572,20 @@ void Apply( auto counters = ParseBoostCounters(result); counters.mine = 0; // Don't show current level as just-reached. show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{ +#if 0 // mtp .link = qs(data.vboost_url()), +#endif + .link = result.data().vboost_url().v, .boost = counters, .requiredLevel = required, }, openStatistics, nullptr)); cancel(); +#if 0 // mtp }).fail([=](const MTP::Error &error) { show->showToast(error.type()); +#endif + }).fail([=](const Error &error) { + show->showToast(error.message); cancel(); }).send(); } @@ -787,6 +841,7 @@ void EditPeerColorBox( state->emojiId.value() ), {}); +#if 0 // mtp const auto appConfig = &peer->session().account().appConfig(); auto indices = rpl::single( rpl::empty @@ -800,6 +855,8 @@ void EditPeerColorBox( return uint8(i); }) | ranges::to_vector; }); +#endif + auto indices = peer->session().availableColorIndicesValue(); const auto margin = st::settingsColorRadioMargin; const auto skip = st::settingsColorRadioSkip; box->addRow( diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 8b70318263049..2cfc0213715bd 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -61,8 +61,12 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "styles/style_info.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + constexpr auto kBotManagerUsername = "BotFather"_cs; [[nodiscard]] auto ToPositiveNumberString() { @@ -135,7 +139,25 @@ void SaveDefaultRestrictions( Fn done) { const auto api = &peer->session().api(); const auto key = Api::RequestKey("default_restrictions", peer->id); - + const auto requestId = api->sender().request(TLsetChatPermissions( + peerToTdbChat(peer->id), + ChatRestrictionsInfo::ToTLPermissions(rights) + )).done([=] { + api->clearModifyRequest(key); + { // CHAT_NOT_MODIFIED is processed as TLok. + if (const auto chat = peer->asChat()) { + chat->setDefaultRestrictions(rights); + } else if (const auto channel = peer->asChannel()) { + channel->setDefaultRestrictions(rights); + } else { + Unexpected("Peer in ApiWrap::saveDefaultRestrictions."); + } + } + done(); + }).fail([=] { + api->clearModifyRequest(key); + }).send(); +#if 0 // goodToRemove const auto requestId = api->request( MTPmessages_EditChatDefaultBannedRights( peer->input, @@ -161,6 +183,7 @@ void SaveDefaultRestrictions( } done(); }).send(); +#endif api->registerModifyRequest(key, requestId); } @@ -171,7 +194,18 @@ void SaveSlowmodeSeconds( Fn done) { const auto api = &channel->session().api(); const auto key = Api::RequestKey("slowmode_seconds", channel->id); - + const auto requestId = api->sender().request(TLsetChatSlowModeDelay( + peerToTdbChat(channel->id), + tl_int32(seconds) + )).done([=] { + api->clearModifyRequest(key); + // CHAT_NOT_MODIFIED is processed as TLok. + channel->setSlowmodeSeconds(seconds); + done(); + }).fail([=] { + api->clearModifyRequest(key); + }).send(); +#if 0 // goodToRemove const auto requestId = api->request(MTPchannels_ToggleSlowMode( channel->inputChannel, MTP_int(seconds) @@ -188,6 +222,7 @@ void SaveSlowmodeSeconds( channel->setSlowmodeSeconds(seconds); done(); }).send(); +#endif api->registerModifyRequest(key, requestId); } @@ -357,7 +392,10 @@ class Controller : public base::has_weak_ptr { const not_null _navigation; const not_null _box; not_null _peer; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Sender _api; const bool _isGroup = false; const bool _isBot = false; @@ -382,7 +420,10 @@ Controller::Controller( : _navigation(navigation) , _box(box) , _peer(peer) +#if 0 // goodToRemove , _api(&_peer->session().mtp()) +#endif +, _api(&_peer->session().sender()) , _isGroup(_peer->isChat() || _peer->isMegagroup()) , _isBot(_peer->isUser() && _peer->asUser()->botInfo) { _box->setTitle(_isBot @@ -709,6 +750,20 @@ void Controller::showEditLinkedChatBox() { } return; } + _linkedChatsRequestId = _api.request( + TLgetSuitableDiscussionChats() + ).done([=](const TLDchats &data) { + _linkedChatsRequestId = 0; + + auto chats = std::vector>(); + chats.reserve(data.vchat_ids().v.size()); + for (const auto &chatId : data.vchat_ids().v) { + const auto peerId = peerFromTdbChat(chatId); + if (const auto peer = _peer->owner().peerLoaded(peerId)) { + chats.push_back(peer); + } + } +#if 0 // goodToRemove _linkedChatsRequestId = _api.request( MTPchannels_GetGroupsForDiscussion() ).done([=](const MTPmessages_Chats &result) { @@ -721,6 +776,7 @@ void Controller::showEditLinkedChatBox() { for (const auto &item : list) { chats.emplace_back(_peer->owner().processChat(item)); } +#endif *box = _navigation->parentController()->show(EditLinkedChatBox( _navigation, channel, @@ -1592,8 +1648,12 @@ void Controller::saveUsernamesOrder() { return continueSave(); } if (_savingData.usernamesOrder->empty()) { + _api.request(TLdisableAllSupergroupUsernames( + tl_int53(peerToChannel(channel->id).bare) +#if 0 // mtp _api.request(MTPchannels_DeactivateAllUsernames( channel->inputChannel +#endif )).done([=] { channel->setUsernames(channel->editableUsername().isEmpty() ? Data::Usernames() @@ -1646,6 +1706,7 @@ void Controller::saveUsername() { } const auto newUsername = (*_savingData.username); +#if 0 // goodToRemove _api.request(MTPchannels_UpdateUsername( channel->inputChannel, MTP_string(newUsername) @@ -1663,7 +1724,18 @@ void Controller::saveUsername() { continueSave(); return; } - +#endif + _api.request(Tdb::TLsetSupergroupUsername( + Tdb::tl_int53(peerToChannel(channel->id).bare), + Tdb::tl_string(newUsername) + )).done([=] { + // USERNAME_NOT_MODIFIED is processed as TLok. + channel->setName( + TextUtilities::SingleLine(channel->name()), + newUsername); + continueSave(); + }).fail([=](const Error &error) { + const auto &type = error.message; // Very rare case. showEditPeerTypeBox([&] { if (type == u"USERNAME_INVALID"_q) { @@ -1698,12 +1770,20 @@ void Controller::saveLinkedChat() { return; } +#if 0 // goodToRemove const auto input = *_savingData.linkedChat ? (*_savingData.linkedChat)->inputChannel : MTP_inputChannelEmpty(); _api.request(MTPchannels_SetDiscussionGroup( (channel->isBroadcast() ? channel->inputChannel : input), (channel->isBroadcast() ? input : channel->inputChannel) +#endif + const auto input = *_savingData.linkedChat + ? peerToTdbChat((*_savingData.linkedChat)->id) + : tl_int53(0); + _api.request(TLsetChatDiscussionGroup( + (channel->isBroadcast() ? peerToTdbChat(channel->id) : input), + (channel->isBroadcast() ? input : peerToTdbChat(channel->id)) )).done([=] { channel->setLinkedChat(*_savingData.linkedChat); continueSave(); @@ -1716,7 +1796,7 @@ void Controller::saveTitle() { if (!_savingData.title || *_savingData.title == _peer->name()) { return continueSave(); } - +#if 0 // goodToRemove const auto onDone = [=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); continueSave(); @@ -1784,6 +1864,29 @@ void Controller::saveTitle() { } else { continueSave(); } +#endif + if (!_peer->isChat() && !_peer->isChannel()) { + return continueSave(); + } + _api.request(TLsetChatTitle( + peerToTdbChat(_peer->id), + tl_string(*_savingData.title) + )).done([=] { + // CHAT_NOT_MODIFIED is processed as TLok. + if (const auto channel = _peer->asChannel()) { + channel->setName(*_savingData.title, channel->username()); + } else if (const auto chat = _peer->asChat()) { + chat->setName(*_savingData.title); + } + continueSave(); + }).fail([=](const Error &error) { + const auto &type = error.message; + _controls.title->showError(); + if (error.message == u"NO_CHAT_TITLE"_q) { + _box->scrollToWidget(_controls.title); + } + cancelSave(); + }).send(); } void Controller::saveDescription() { @@ -1791,6 +1894,7 @@ void Controller::saveDescription() { || *_savingData.description == _peer->about()) { return continueSave(); } +#if 0 // goodToRemove const auto successCallback = [=] { _peer->setAbout(*_savingData.description); continueSave(); @@ -1839,6 +1943,18 @@ void Controller::saveDescription() { _controls.description->showError(); cancelSave(); }).send(); +#endif + _api.request(TLsetChatDescription( + peerToTdbChat(_peer->id), + tl_string(*_savingData.description) + )).done([=] { + // CHAT_ABOUT_NOT_MODIFIED is processed as TLok. + _peer->setAbout(*_savingData.description); + continueSave(); + }).fail([=] { + _controls.description->showError(); + cancelSave(); + }).send(); } void Controller::saveHistoryVisibility() { @@ -1869,6 +1985,16 @@ void Controller::saveHistoryVisibility() { void Controller::toggleBotManager(const QString &command) { const auto controller = _navigation->parentController(); + _api.request(TLsearchPublicChat( + tl_string(kBotManagerUsername.utf16()) + )).done([=](const TLchat &result) { + const auto botPeer = _peer->owner().processPeer(result); + if (const auto bot = botPeer ? botPeer->asUser() : nullptr) { + _peer->session().api().sendBotStart(bot, bot, command); + controller->showPeerHistory(bot); + } + }).send(); +#if 0 // mtp _api.request(MTPcontacts_ResolveUsername( MTP_string(kBotManagerUsername.utf16()) )).done([=](const MTPcontacts_ResolvedPeer &result) { @@ -1881,6 +2007,7 @@ void Controller::toggleBotManager(const QString &command) { controller->showPeerHistory(bot); } }).send(); +#endif } void Controller::togglePreHistoryHidden( @@ -1888,6 +2015,7 @@ void Controller::togglePreHistoryHidden( bool hidden, Fn done, Fn fail) { + // CHAT_NOT_MODIFIED is processed as TLok. const auto apply = [=] { // Update in the result doesn't contain the // channelFull:flags field which holds this value. @@ -1898,6 +2026,11 @@ void Controller::togglePreHistoryHidden( done(); }; + _api.request(TLtoggleSupergroupIsAllHistoryAvailable( + tl_int53(peerToChannel(channel->id).bare), + tl_bool(hidden) + )).done(apply).fail(fail).send(); +#if 0 // goodToRemove _api.request(MTPchannels_TogglePreHistoryHidden( channel->inputChannel, MTP_bool(hidden) @@ -1911,6 +2044,7 @@ void Controller::togglePreHistoryHidden( fail(); } }).send(); +#endif } void Controller::saveForum() { @@ -1931,6 +2065,7 @@ void Controller::saveForum() { crl::guard(this, saveForChannel)); return; } +#if 0 // mtp _api.request(MTPchannels_ToggleForum( channel->inputChannel, MTP_bool(*_savingData.forum) @@ -1944,6 +2079,15 @@ void Controller::saveForum() { cancelSave(); } }).send(); +#endif + _api.request(TLtoggleSupergroupIsForum( + tl_int53(peerToChannel(channel->id).bare), + tl_bool(*_savingData.forum) + )).done([=] { + continueSave(); + }).fail([=] { + cancelSave(); + }).send(); } void Controller::saveSignatures() { @@ -1953,6 +2097,7 @@ void Controller::saveSignatures() { || *_savingData.signatures == channel->addsSignature()) { return continueSave(); } +#if 0 // goodToRemove _api.request(MTPchannels_ToggleSignatures( channel->inputChannel, MTP_bool(*_savingData.signatures) @@ -1966,6 +2111,16 @@ void Controller::saveSignatures() { cancelSave(); } }).send(); +#endif + _api.request(TLtoggleSupergroupSignMessages( + tl_int53(peerToChannel(channel->id).bare), + tl_bool(*_savingData.signatures) + )).done([=] { + // CHAT_NOT_MODIFIED is processed as TLok. + continueSave(); + }).fail([=] { + cancelSave(); + }).send(); } void Controller::saveForwards() { @@ -1973,6 +2128,7 @@ void Controller::saveForwards() { || *_savingData.noForwards != _peer->allowsForwarding()) { return continueSave(); } +#if 0 // mtp _api.request(MTPmessages_ToggleNoForwards( _peer->input, MTP_bool(*_savingData.noForwards) @@ -1986,6 +2142,16 @@ void Controller::saveForwards() { cancelSave(); } }).send(); +#endif + _api.request(TLtoggleChatHasProtectedContent( + peerToTdbChat(_peer->id), + tl_bool(*_savingData.noForwards) + )).done([=] { + // CHAT_NOT_MODIFIED is processed as TLok. + continueSave(); + }).fail([=] { + cancelSave(); + }).send(); } void Controller::saveJoinToWrite() { @@ -1995,6 +2161,7 @@ void Controller::saveJoinToWrite() { || *_savingData.joinToWrite == joinToWrite) { return continueSave(); } +#if 0 // mtp _api.request(MTPchannels_ToggleJoinToSend( _peer->asChannel()->inputChannel, MTP_bool(*_savingData.joinToWrite) @@ -2008,6 +2175,19 @@ void Controller::saveJoinToWrite() { cancelSave(); } }).send(); +#endif + if (!_peer->isChannel()) { + return continueSave(); + } + _api.request(TLtoggleSupergroupJoinToSendMessages( + tl_int53(peerToChannel(_peer->id).bare), + tl_bool(*_savingData.joinToWrite) + )).done([=] { + // CHAT_NOT_MODIFIED is processed as TLok. + continueSave(); + }).fail([=] { + cancelSave(); + }).send(); } void Controller::saveRequestToJoin() { @@ -2017,6 +2197,7 @@ void Controller::saveRequestToJoin() { || *_savingData.requestToJoin == requestToJoin) { return continueSave(); } +#if 0 // mtp _api.request(MTPchannels_ToggleJoinRequest( _peer->asChannel()->inputChannel, MTP_bool(*_savingData.requestToJoin) @@ -2030,6 +2211,19 @@ void Controller::saveRequestToJoin() { cancelSave(); } }).send(); +#endif + if (!_peer->isChannel()) { + return continueSave(); + } + _api.request(TLtoggleSupergroupJoinByRequest( + tl_int53(peerToChannel(_peer->id).bare), + tl_bool(*_savingData.requestToJoin) + )).done([=] { + // CHAT_NOT_MODIFIED is processed as TLok. + continueSave(); + }).fail([=] { + cancelSave(); + }).send(); } void Controller::savePhoto() { @@ -2076,6 +2270,7 @@ void Controller::deleteChannel() { if (chat) { session->api().deleteConversation(chat, false); } +#if 0 // goodToRemove session->api().request(MTPchannels_DeleteChannel( channel->inputChannel )).done([=](const MTPUpdates &result) { @@ -2085,6 +2280,14 @@ void Controller::deleteChannel() { // Ui::show(Box(tr::lng_cant_delete_channel(tr::now))); // } }).send(); +#endif + session->sender().request(TLdeleteChat( + peerToTdbChat(channel->id) + )).send(); + //}).fail([=](const MTP::Error &error) { + // if (error.type() == qstr("CHANNEL_TOO_LARGE")) { + // Ui::show(Box(tr::lng_cant_delete_channel(tr::now))); + // } } } // namespace diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index 03bf6108da7b7..220a95aa01da1 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -48,11 +48,16 @@ For license and copyright information please follow this link: #include "styles/style_info.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + #include #include namespace { +using namespace Tdb; + constexpr auto kFirstPage = 20; constexpr auto kPerPage = 100; constexpr auto kShareQrSize = 768; @@ -186,7 +191,10 @@ class Controller final Ui::RpWidget *_headerWidget = nullptr; rpl::variable _addedHeight; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Sender _api; rpl::lifetime _lifetime; }; @@ -321,7 +329,10 @@ Controller::Controller( : _peer(peer) , _role(role) , _data(LinkData{ .admin = admin }) +#if 0 // goodToRemove , _api(&session().api().instance()) { +#endif +, _api(&session().sender()) { _data = std::move(data); const auto current = _data.current(); _link = current.link; @@ -700,6 +711,7 @@ void Controller::loadMoreRows() { if (_requestId || _allLoaded) { return; } +#if 0 // goodToRemove using Flag = MTPmessages_GetChatInviteImporters::Flag; _requestId = _api.request(MTPmessages_GetChatInviteImporters( MTP_flags(Flag::f_link @@ -711,14 +723,54 @@ void Controller::loadMoreRows() { _lastUser ? _lastUser->user->inputUser : MTP_inputUserEmpty(), MTP_int(_lastUser ? kPerPage : kFirstPage) )).done([=](const MTPmessages_ChatInviteImporters &result) { +#endif + const auto done = [=](const auto &result) { _requestId = 0; auto slice = Api::ParseJoinedByLinkSlice(_peer, result); _allLoaded = slice.users.empty(); appendSlice(slice); + }; + const auto fail = [=] { + _requestId = 0; + _allLoaded = true; + }; +#if 0 // goodToRemove }).fail([=] { _requestId = 0; _allLoaded = true; }).send(); +#endif + if (_role == Role::Requested) { + _requestId = _api.request(TLgetChatJoinRequests( + peerToTdbChat(_peer->id), + tl_string(_link), + tl_string(), // Query. + tl_chatJoinRequest( // Offset. + tl_int53(_lastUser + ? peerToUser(_lastUser->user->id).bare + : 0), + tl_int32(_lastUser ? _lastUser->date : 0), + tl_string()), + tl_int32(_lastUser ? kPerPage : kFirstPage) + )).done([=](const TLDchatJoinRequests &data) { + done(data); + }).fail(fail).send(); + } else { + _requestId = _api.request(TLgetChatInviteLinkMembers( + peerToTdbChat(_peer->id), + tl_string(_link), + tl_chatInviteLinkMember( // Offset. + tl_int53(_lastUser + ? peerToUser(_lastUser->user->id).bare + : 0), + tl_int32(_lastUser ? _lastUser->date : 0), + tl_bool(_lastUser ? _lastUser->viaFilterLink : false), + tl_int53(0)), + tl_int32(_lastUser ? kPerPage : kFirstPage) + )).done([=](const TLDchatInviteLinkMembers &data) { + done(data); + }).fail(fail).send(); + } } void Controller::appendSlice(const Api::JoinedByLinkSlice &slice) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp index 46905d67be706..ec8294cb87ef0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp @@ -31,6 +31,8 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" // st::boxDividerLabel #include "styles/style_menu_icons.h" +#include "tdb/tdb_tl_scheme.h" + namespace { enum class Color { @@ -759,7 +761,10 @@ AdminsController::AdminsController( } AdminsController::~AdminsController() { +#if 0 // mtp session().api().request(base::take(_requestId)).cancel(); +#endif + session().sender().request(base::take(_requestId)).cancel(); } void AdminsController::prepare() { @@ -775,6 +780,21 @@ void AdminsController::prepare() { if (!_admin->isSelf()) { return; } + _requestId = session().sender().request(Tdb::TLgetChatInviteLinkCounts( + peerToTdbChat(_peer->id) + )).done([=](const Tdb::TLDchatInviteLinkCounts &data) { + for (const auto &inviteLinkCounts : data.vinvite_link_counts().v) { + auto &owner = _peer->owner(); + const auto &data = inviteLinkCounts.data(); + if (const auto user = owner.userLoaded(data.vuser_id().v)) { + if (!user->isSelf()) { + appendRow(user, data.vinvite_link_count().v); + } + } + } + delegate()->peerListRefreshRows(); + }).send(); +#if 0 // goodToRemove _requestId = session().api().request(MTPmessages_GetAdminsWithInvites( _peer->input )).done([=](const MTPmessages_ChatAdminsWithInvites &result) { @@ -794,6 +814,7 @@ void AdminsController::prepare() { delegate()->peerListRefreshRows(); }); }).send(); +#endif } void AdminsController::loadMoreRows() { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index f5024859cda5f..7217593215af4 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -39,6 +39,8 @@ For license and copyright information please follow this link: #include "styles/style_window.h" #include "styles/style_settings.h" +#include "tdb/tdb_tl_scheme.h" + namespace { constexpr auto kSlowmodeValues = 7; @@ -1017,10 +1019,17 @@ Fn AboutGigagroupCallback( return; } *converting = true; +#if 0 // goodToRemove channel->session().api().request(MTPchannels_ConvertToGigagroup( channel->inputChannel )).done([=](const MTPUpdates &result) { channel->session().api().applyUpdates(result); +#endif + channel->session().sender().request( + Tdb::TLtoggleSupergroupIsBroadcastGroup( + Tdb::tl_int53(peerToChannel(channel->id).bare) + ) + ).done([=] { if (const auto strong = weak.get()) { strong->window().hideSettingsAndLayer(); strong->showToast(tr::lng_gigagroup_done(tr::now)); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp index 91773b9fd396e..e288fd9c138b4 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp @@ -25,6 +25,15 @@ For license and copyright information please follow this link: #include "styles/style_settings.h" #include "styles/style_info.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + +namespace { + +using namespace Tdb; + +} // namespace + void EditAllowedReactionsBox( not_null box, not_null navigation, @@ -258,6 +267,20 @@ void EditAllowedReactionsBox( void SaveAllowedReactions( not_null peer, const Data::AllowedReactions &allowed) { + auto ids = allowed.some | ranges::views::transform( + Data::ReactionToTL + ) | ranges::to; + + using Type = Data::AllowedReactionsType; + const auto updated = (allowed.type != Type::Some) + ? tl_chatAvailableReactionsAll() + : tl_chatAvailableReactionsSome(MTP_vector(ids)); + peer->session().sender().request(TLsetChatAvailableReactions( + peerToTdbChat(peer->id), + updated + )).send(); + +#if 0 // mtp auto ids = allowed.some | ranges::views::transform( Data::ReactionToMTP ) | ranges::to>; @@ -288,4 +311,5 @@ void SaveAllowedReactions( peer->owner().reactions().refreshDefault(); } }).send(); +#endif } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp index 406c8a978a652..46a114cdad1bc 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp @@ -30,6 +30,8 @@ For license and copyright information please follow this link: #include "api/api_invite_links.h" #include "styles/style_boxes.h" +#include "tdb/tdb_tl_scheme.h" + namespace { constexpr auto kFirstPageCount = 16; @@ -252,7 +254,10 @@ RequestsBoxController::RequestsBoxController( , _navigation(navigation) , _helper(std::make_unique(!peer->isBroadcast())) , _peer(peer) +#if 0 // goodToRemove , _api(&_peer->session().mtp()) { +#endif +, _api(&_peer->session().sender()) { setStyleOverrides(&st::requestsBoxList); subscribeToMigration(); } @@ -308,6 +313,7 @@ void RequestsBoxController::loadMoreRows() { // First query is small and fast, next loads a lot of rows. const auto limit = _offsetDate ? kPerPage : kFirstPageCount; +#if 0 // goodToRemove using Flag = MTPmessages_GetChatInviteImporters::Flag; _loadRequestId = _api.request(MTPmessages_GetChatInviteImporters( MTP_flags(Flag::f_requested), @@ -335,6 +341,28 @@ void RequestsBoxController::loadMoreRows() { // To be sure - wait for a whole empty result list. _allLoaded = importers.isEmpty(); }); +#endif + _loadRequestId = _api.request(Tdb::TLgetChatJoinRequests( + peerToTdbChat(_peer->id), + Tdb::tl_string(), // Invite link. + Tdb::tl_string(), // Query. + Tdb::tl_chatJoinRequest( // Offset. + Tdb::tl_int53(_offsetUser ? peerToUser(_offsetUser->id).bare : 0), + Tdb::tl_int32(_offsetDate), + Tdb::tl_string()), + Tdb::tl_int32(limit) + )).done([=](const Tdb::TLDchatJoinRequests &data) { + const auto firstLoad = !_offsetDate; + _loadRequestId = 0; + + auto &owner = _peer->owner(); + for (const auto &importer : data.vrequests().v) { + _offsetDate = importer.data().vdate().v; + _offsetUser = owner.user(importer.data().vuser_id().v); + appendRow(_offsetUser, _offsetDate); + } + // To be sure - wait for a whole empty result list. + _allLoaded = data.vrequests().v.empty(); if (_allLoaded || (firstLoad && delegate()->peerListFullRowsCount() > 0)) { @@ -527,7 +555,10 @@ void RequestsBoxController::migrate( RequestsBoxSearchController::RequestsBoxSearchController( not_null peer) : _peer(peer) +#if 0 // goodToRemove , _api(&_peer->session().mtp()) { +#endif +, _api(&_peer->session().sender()) { _timer.setCallback([=] { searchOnServer(); }); } @@ -597,6 +628,29 @@ bool RequestsBoxSearchController::loadMoreRows() { // (because we've waited for search request by timer already, // so we don't expect it to be fast, but we want to fill cache). const auto limit = kPerPage; + _requestId = _api.request(Tdb::TLgetChatJoinRequests( + peerToTdbChat(_peer->id), + Tdb::tl_string(), // Invite link. + Tdb::tl_string(_query), + Tdb::tl_chatJoinRequest( // Offset. + Tdb::tl_int53(_offsetUser ? peerToUser(_offsetUser->id).bare : 0), + Tdb::tl_int32(_offsetDate), + Tdb::tl_string()), + Tdb::tl_int32(limit) + )).done([=]( + const Tdb::TLDchatJoinRequests &data, + Tdb::RequestId requestId) { + auto items = std::vector(); + auto &owner = _peer->owner(); + items.reserve(data.vrequests().v.size()); + for (const auto &importer : data.vrequests().v) { + items.push_back({ + owner.user(importer.data().vuser_id().v), + importer.data().vdate().v, + }); + } +#if 0 // goodToRemove + }).send(); using Flag = MTPmessages_GetChatInviteImporters::Flag; _requestId = _api.request(MTPmessages_GetChatInviteImporters( MTP_flags(Flag::f_requested | Flag::f_q), @@ -624,6 +678,7 @@ bool RequestsBoxSearchController::loadMoreRows() { }); } }); +#endif searchDone(requestId, items, limit); auto it = _queries.find(requestId); @@ -636,7 +691,10 @@ bool RequestsBoxSearchController::loadMoreRows() { } _queries.erase(it); } +#if 0 // goodToRemove }).fail([=](const MTP::Error &error, mtpRequestId requestId) { +#endif + }).fail([=](const Tdb::Error &error, Tdb::RequestId requestId) { if (_requestId == requestId) { _requestId = 0; _allLoaded = true; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h index 005b83c8475b0..cdf7522d9163f 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h @@ -11,6 +11,8 @@ For license and copyright information please follow this link: #include "base/weak_ptr.h" #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + namespace Window { class SessionNavigation; } // namespace Window @@ -61,7 +63,10 @@ class RequestsBoxController final const not_null _navigation; const std::unique_ptr _helper; not_null _peer; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; TimeId _offsetDate = 0; UserData *_offsetUser = nullptr; @@ -105,7 +110,10 @@ class RequestsBoxSearchController final : public PeerListSearchController { int requestedCount); not_null _peer; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; base::Timer _timer; QString _query; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp index 5e5dee319adf0..f3fe68e842303 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp @@ -46,8 +46,12 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "styles/style_info.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + class Controller : public base::has_weak_ptr { public: Controller( @@ -145,7 +149,10 @@ class Controller : public base::has_weak_ptr { not_null _peer; bool _linkOnly = false; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Sender _api; std::optional _dataSavedValue; bool _useLocationPhrases = false; @@ -177,7 +184,10 @@ Controller::Controller( , _show(show) , _peer(peer) , _linkOnly(!dataSavedValue.has_value()) +#if 0 // goodToRemove , _api(&_peer->session().mtp()) +#endif +, _api(&_peer->session().sender()) , _dataSavedValue(dataSavedValue) , _useLocationPhrases(useLocationPhrases) , _isGroup(_peer->isChat() || _peer->isMegagroup()) @@ -551,6 +561,51 @@ void Controller::checkUsernameAvailability() { } const auto channel = _peer->migrateToOrMe()->asChannel(); const auto username = channel ? channel->editableUsername() : QString(); + _checkUsernameRequestId = _api.request(Tdb::TLcheckChatUsername( + peerToTdbChat(channel->id), + tl_string(checking) + )).done([=](const TLcheckChatUsernameResult &result) { + _checkUsernameRequestId = 0; + using namespace Tdb; + result.match([&](const TLDcheckChatUsernameResultOk &) { + if (initial) { + return; + } + showUsernameGood(); + }, [&](const TLDcheckChatUsernameResultUsernameInvalid &) { + showUsernameError(tr::lng_create_channel_link_invalid()); + }, [&](const TLDcheckChatUsernameResultUsernameOccupied &) { + if (checking != username) { + showUsernameError(tr::lng_create_channel_link_occupied()); + } + }, [&](const TLDcheckChatUsernameResultPublicChatsTooMany &) { + _usernameState = UsernameState::TooMany; + if (_controls.privacy->value() == Privacy::HasUsername) { + askUsernameRevoke(); + } + }, [&](const TLDcheckChatUsernameResultPublicGroupsUnavailable &) { + _usernameState = UsernameState::NotAvailable; + _controls.privacy->setValue(Privacy::NoUsername); + }, [&](const TLDcheckChatUsernameResultUsernamePurchasable &) { + _goodUsername = false; + _usernameCheckInfo.fire({ + .type = UsernameCheckInfo::Type::PurchaseAvailable, + }); + }); + }).fail([=](const Error &error) { + _checkUsernameRequestId = 0; + _usernameState = UsernameState::Normal; + const auto &type = error.message; + if (initial) { + if (_controls.privacy->value() == Privacy::HasUsername) { + showUsernameEmpty(); + setFocusUsername(); + } + } else if (type == u"USERNAME_OCCUPIED"_q && (checking != username)) { + showUsernameError(tr::lng_create_channel_link_occupied()); + } + }).send(); +#if 0 // goodToRemove _checkUsernameRequestId = _api.request(MTPchannels_CheckUsername( channel ? channel->inputChannel : MTP_inputChannelEmpty(), MTP_string(checking) @@ -592,6 +647,7 @@ void Controller::checkUsernameAvailability() { showUsernameError(tr::lng_create_channel_link_occupied()); } }).send(); +#endif } void Controller::askUsernameRevoke() { diff --git a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp index d71645316e42f..1515529813771 100644 --- a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp @@ -24,6 +24,7 @@ For license and copyright information please follow this link: #include "window/window_session_controller.h" #include "info/profile/info_profile_values.h" #include "ui/text/format_values.h" +#include "tdb/tdb_format_phone.h" // Tdb::FormatPhone #include "base/unixtime.h" #include "lang/lang_keys.h" #include "styles/style_info.h" @@ -209,7 +210,7 @@ void ProcessFullPhoto( const auto username = peer->userName(); return PeerShortInfoFields{ .name = peer->name(), - .phone = user ? Ui::FormatPhone(user->phone()) : QString(), + .phone = user ? Tdb::FormatPhone(user->phone()) : QString(), .link = ((user || username.isEmpty()) ? QString() : peer->session().createInternalLinkFull(username)), diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index 0129b85960c9a..894dfbf10b69f 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -33,8 +33,12 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "styles/style_premium.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + constexpr auto kWaitingOpacity = 0.5; class Row final : public PeerListRow { @@ -376,18 +380,30 @@ object_ptr ReassignBoostSingleBox( } // namespace +#if 0 // mtp ForChannelBoostSlots ParseForChannelBoostSlots( not_null channel, const QVector &boosts) { +#endif +ForChannelBoostSlots ParseForChannelBoostSlots( + not_null channel, + const TLchatBoostSlots &slots) { + const auto &boosts = slots.data().vslots().v; auto result = ForChannelBoostSlots(); const auto now = base::unixtime::now(); for (const auto &my : boosts) { const auto &data = my.data(); +#if 0 // mtp const auto id = data.vslot().v; const auto cooldown = data.vcooldown_until_date().value_or(0); const auto peerId = data.vpeer() ? peerFromMTP(*data.vpeer()) : PeerId(); +#endif + const auto id = data.vslot_id().v; + const auto cooldown = data.vcooldown_until_date().v; + const auto peerId = peerFromTdbChat( + data.vcurrently_boosted_chat_id()); if (!peerId && cooldown <= now) { result.free.push_back(id); } else if (peerId == channel->id) { @@ -395,7 +411,10 @@ ForChannelBoostSlots ParseForChannelBoostSlots( } else { result.other.push_back({ .id = id, +#if 0 // mtp .expires = data.vexpires().v, +#endif + .expires = data.vexpiration_date().v, .peerId = peerId, .cooldown = cooldown, }); @@ -404,6 +423,7 @@ ForChannelBoostSlots ParseForChannelBoostSlots( return result; } +#if 0 // mtp Ui::BoostCounters ParseBoostCounters( const MTPpremium_BoostsStatus &status) { const auto &data = status.data(); @@ -416,6 +436,18 @@ Ui::BoostCounters ParseBoostCounters( .mine = slots ? int(slots->v.size()) : 0, }; } +#endif + +Ui::BoostCounters ParseBoostCounters(const TLchatBoostStatus &status) { + const auto &data = status.data(); + return { + .level = data.vlevel().v, + .boosts = data.vboost_count().v, + .thisLevelBoosts = data.vcurrent_level_boost_count().v, + .nextLevelBoosts = data.vnext_level_boost_count().v, + .mine = int(data.vapplied_slot_ids().v.size()), + }; +} int BoostsForGift(not_null session) { const auto key = u"boosts_per_sent_gift"_q; diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h index f15cf0b148200..11c7b63c13486 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h @@ -9,6 +9,11 @@ For license and copyright information please follow this link: #include "base/object_ptr.h" +namespace Tdb { +class TLchatBoostStatus; +class TLchatBoostSlots; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -32,12 +37,21 @@ struct ForChannelBoostSlots { std::vector other; }; +#if 0 // mtp [[nodiscard]] ForChannelBoostSlots ParseForChannelBoostSlots( not_null channel, const QVector &boosts); [[nodiscard]] Ui::BoostCounters ParseBoostCounters( const MTPpremium_BoostsStatus &status); +#endif + +[[nodiscard]] ForChannelBoostSlots ParseForChannelBoostSlots( + not_null channel, + const Tdb::TLchatBoostSlots &slots); + +[[nodiscard]] Ui::BoostCounters ParseBoostCounters( + const Tdb::TLchatBoostStatus &status); [[nodiscard]] int BoostsForGift(not_null session); diff --git a/Telegram/SourceFiles/boxes/pin_messages_box.cpp b/Telegram/SourceFiles/boxes/pin_messages_box.cpp index 163e9dedc6f5f..9c2486066e11a 100644 --- a/Telegram/SourceFiles/boxes/pin_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/pin_messages_box.cpp @@ -19,8 +19,13 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_boxes.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + [[nodiscard]] bool IsOldForPin( MsgId id, not_null peer, @@ -55,8 +60,10 @@ void PinMessageBox( const auto topicRootId = item->topic() ? item->topicRootId() : MsgId(); const auto pinningOld = IsOldForPin(msgId, peer, topicRootId); const auto state = box->lifetime().make_state(); +#if 0 // mtp const auto api = box->lifetime().make_state( &peer->session().mtp()); +#endif auto checkbox = [&]() -> object_ptr { if (peer->isUser() && !peer->isSelf()) { @@ -89,6 +96,14 @@ void PinMessageBox( return; } + const auto finished = crl::guard(box, [=] { box->closeBox(); }); + state->requestId = peer->session().sender().request(TLpinChatMessage( + peerToTdbChat(peer->id), + tl_int53(msgId.bare), + tl_bool(state->notify && !state->notify->checked()), + tl_bool(state->pinForPeer && !state->pinForPeer->checked()) + )).done(finished).fail(finished).send(); +#if 0 // mtp auto flags = MTPmessages_UpdatePinnedMessage::Flags(0); if (state->notify && !state->notify->checked()) { flags |= MTPmessages_UpdatePinnedMessage::Flag::f_silent; @@ -106,6 +121,7 @@ void PinMessageBox( }).fail([=] { box->closeBox(); }).send(); +#endif }; Ui::ConfirmBox(box, { diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.cpp b/Telegram/SourceFiles/boxes/premium_limits_box.cpp index 64452a906e599..eaf79d4ff7568 100644 --- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp @@ -37,8 +37,15 @@ For license and copyright information please follow this link: #include "styles/style_info.h" #include "styles/style_settings.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" +#include "history/history.h" +#include "history/history_item.h" + namespace { +using namespace Tdb; + struct InfographicDescriptor { float64 defaultLimit = 0; float64 current = 0; @@ -197,7 +204,10 @@ InactiveController::InactiveController(not_null session) InactiveController::~InactiveController() { if (_requestId) { + _session->sender().request(_requestId).cancel(); +#if 0 // mtp _session->api().request(_requestId).cancel(); +#endif } } @@ -206,6 +216,20 @@ Main::Session &InactiveController::session() const { } void InactiveController::prepare() { + _requestId = _session->sender().request(TLgetInactiveSupergroupChats( + )).done([=](const TLchats &result) { + _requestId = 0; + for (const auto chatId : result.data().vchat_ids().v) { + const auto peerId = peerFromTdbChat(chatId); + const auto history = _session->data().historyLoaded(peerId); + const auto item = history ? history->lastMessage() : nullptr; + appendRow( + history ? history->peer : _session->data().peer(peerId), + item ? item->date() : TimeId(0)); + } + delegate()->peerListRefreshRows(); + }).send(); +#if 0 // mtp _requestId = _session->api().request(MTPchannels_GetInactiveChannels( )).done([=](const MTPmessages_InactiveChats &result) { _requestId = 0; @@ -221,6 +245,7 @@ void InactiveController::prepare() { delegate()->peerListRefreshRows(); }); }).send(); +#endif } void InactiveController::rowClicked(not_null row) { @@ -287,7 +312,10 @@ PublicsController::PublicsController( PublicsController::~PublicsController() { if (_requestId) { + _navigation->session().sender().request(_requestId).cancel(); +#if 0 // mtp _navigation->session().api().request(_requestId).cancel(); +#endif } } @@ -296,6 +324,22 @@ Main::Session &PublicsController::session() const { } void PublicsController::prepare() { + _requestId = _navigation->session().sender().request( + TLgetCreatedPublicChats(tl_publicChatTypeHasUsername()) + ).done([=](const TLchats &result) { + _requestId = 0; + + auto &owner = _navigation->session().data(); + for (const auto &chatId : result.data().vchat_ids().v) { + const auto peer = owner.peer(peerFromTdbChat(chatId)); + if (!peer->isChannel() || peer->userName().isEmpty()) { + continue; + } + appendRow(peer); + } + delegate()->peerListRefreshRows(); + }).send(); +#if 0 // mtp _requestId = _navigation->session().api().request( MTPchannels_GetAdminedPublicChannels(MTP_flags(0)) ).done([=](const MTPmessages_Chats &result) { @@ -315,6 +359,7 @@ void PublicsController::prepare() { delegate()->peerListRefreshRows(); } }).send(); +#endif } void PublicsController::rowClicked(not_null row) { @@ -342,6 +387,18 @@ void PublicsController::rowRightActionClicked(not_null row) { return; } *once = true; + peer->session().sender().request(TLsetSupergroupUsername( + tl_int53(peerToChannel(peer->id).bare), + tl_string() + )).done([=] { + peer->session().sender().request(TLdisableAllSupergroupUsernames( + tl_int53(peerToChannel(peer->id).bare) + )).done([=] { + closeBox(); + close(); + }).send(); + }).send(); +#if 0 // mtp peer->session().api().request(MTPchannels_UpdateUsername( peer->asChannel()->inputChannel, MTP_string() @@ -353,6 +410,7 @@ void PublicsController::rowRightActionClicked(not_null row) { close(); }).send(); }).send(); +#endif }); _navigation->parentController()->show( Ui::MakeConfirmBox({ diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 7a49f5cc09c0c..b23e4af77907e 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -46,6 +46,8 @@ For license and copyright information please follow this link: #include "styles/style_premium.h" #include "styles/style_settings.h" +#include "tdb/tdb_tl_scheme.h" + #include namespace { @@ -475,7 +477,10 @@ struct VideoPreviewDocument { return ""; }(); const auto &videos = session->api().premium().videos(); +#if 0 // mtp const auto i = videos.find(name); +#endif + const auto i = videos.find(section); return (i != end(videos)) ? i->second.get() : nullptr; } @@ -1044,12 +1049,26 @@ void PreviewBox( ? tr::lng_premium_unlock_status() : tr::lng_premium_more_about(); }) | rpl::flatten_latest(); + const auto startSubscription = [=] { + // later do async - it is still slow if done after launch + const auto features = Settings::GetPremiumFeaturesSync( + &show->session(), + computeRef()); + const auto window = show->resolveWindow( + ChatHelpers::WindowUsage::PremiumPromo); + if (window) { + Settings::CreateStartSubscription( + window, + features ? &*features : nullptr)(); + } + }; auto button = descriptor.fromSettings ? object_ptr::fromRaw( Settings::CreateSubscribeButton({ .parent = box, .computeRef = computeRef, .show = show, + .startSubscription = startSubscription, })) : CreateUnlockButton(box, std::move(unlock)); button->resizeToWidth(width); @@ -1151,10 +1170,24 @@ void DecorateListPromoBox( box->closeBox(); }); } else { + const auto startSubscription = [=] { + // later do async - it is still slow if done after launch + const auto features = Settings::GetPremiumFeaturesSync( + &show->session(), + u"double_limits"_q); + const auto window = show->resolveWindow( + ChatHelpers::WindowUsage::PremiumPromo); + if (window) { + Settings::CreateStartSubscription( + window, + features ? &*features : nullptr)(); + } + }; const auto button = Settings::CreateSubscribeButton({ .parent = box, .computeRef = [] { return u"double_limits"_q; }, .show = show, + .startSubscription = startSubscription, }); box->setShowFinishedCallback([=] { diff --git a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp index 52ab5dc77f477..4acbd1a99cf76 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -41,12 +41,41 @@ For license and copyright information please follow this link: #include "styles/style_media_player.h" // mediaPlayerMenuCheck #include "styles/style_settings.h" +#include "tdb/tdb_tl_scheme.h" + namespace { PeerId GenerateUser(not_null history, const QString &name) { Expects(history->peer->isUser()); const auto peerId = Data::FakePeerIdForJustName(name); + history->owner().processUser(Tdb::tl_user( + peerToTdbChat(peerId), + Tdb::tl_string(tr::lng_settings_chat_message_reply_from(tr::now)), + Tdb::TLstring(), // last_name + std::nullopt, // usernames + Tdb::TLstring(), // phone_number + Tdb::tl_userStatusEmpty(), // status + Tdb::null, // profile_photo + Tdb::tl_int32(Data::DecideColorIndex(peerId)), + Tdb::tl_int64(0), // background_custom_emoji_id + Tdb::null, // emoji_status + Tdb::tl_bool(false), // is_contact + Tdb::tl_bool(false), // is_mutual_contact + Tdb::tl_bool(false), // is_close_friend + Tdb::tl_bool(false), // is_verified + Tdb::tl_bool(false), // is_premium + Tdb::tl_bool(false), // is_support + Tdb::TLstring(), // restriction_reason + Tdb::tl_bool(false), // is_scam + Tdb::tl_bool(false), // is_fake + Tdb::tl_bool(false), // has_active_stories + Tdb::tl_bool(false), // has_unread_active_stories + Tdb::tl_bool(true), // have_access + Tdb::tl_userTypeRegular(), // type + Tdb::TLstring(), // language_code + Tdb::tl_bool(false))); // added_to_attachment_menu +#if 0 // goodToRemove history->owner().processUser(MTP_user( MTP_flags(MTPDuser::Flag::f_first_name | MTPDuser::Flag::f_min), peerToBareMTPInt(peerId), @@ -66,6 +95,7 @@ PeerId GenerateUser(not_null history, const QString &name) { MTPint(), // stories_max_id MTP_int(0), // color MTPlong())); // background_emoji_id +#endif return peerId; } @@ -88,7 +118,9 @@ AdminLog::OwnedItem GenerateItem( from, QString(), // postAuthor TextWithEntities{ .text = text }, +#if 0 // mtp MTP_messageMediaEmpty(), +#endif HistoryMessageMarkupData(), uint64(0)); // groupedId diff --git a/Telegram/SourceFiles/boxes/sessions_box.cpp b/Telegram/SourceFiles/boxes/sessions_box.cpp index ec428042af606..24019bc1d7fb3 100644 --- a/Telegram/SourceFiles/boxes/sessions_box.cpp +++ b/Telegram/SourceFiles/boxes/sessions_box.cpp @@ -17,6 +17,7 @@ For license and copyright information please follow this link: #include "boxes/peer_lists_box.h" #include "ui/boxes/confirm_box.h" #include "lang/lang_keys.h" +#include "tdb/tdb_sender.h" #include "main/main_session.h" #include "ui/widgets/buttons.h" #include "ui/widgets/fields/input_field.h" @@ -847,6 +848,7 @@ void SessionsContent::terminate(Fn terminateRequest, QString message) { _controller->show(std::move(box)); } +#if 0 // goodToRemove void SessionsContent::terminateOne(uint64 hash) { const auto weak = Ui::MakeWeak(this); auto callback = [=] { @@ -875,6 +877,28 @@ void SessionsContent::terminateOne(uint64 hash) { }; terminate(std::move(callback), tr::lng_settings_reset_one_sure(tr::now)); } +#endif + +void SessionsContent::terminateOne(uint64 hash) { + const auto weak = Ui::MakeWeak(this); + auto callback = [=] { + auto done = crl::guard(weak, [=] { + const auto removeByHash = [&](std::vector &list) { + list.erase( + ranges::remove( + list, + hash, + [](const EntryData &entry) { return entry.hash; }), + end(list)); + }; + removeByHash(_data.incomplete); + removeByHash(_data.list); + _inner->showData(_data); + }); + _authorizations->requestTerminate(std::move(done), [] {}, hash); + }; + terminate(std::move(callback), tr::lng_settings_reset_one_sure(tr::now)); +} void SessionsContent::terminateAll() { const auto weak = Ui::MakeWeak(this); @@ -884,8 +908,12 @@ void SessionsContent::terminateAll() { _authorizations->reload(); }); _authorizations->requestTerminate( +#if 0 // goodToRemove [=](const MTPBool &result) { reset(); }, [=](const MTP::Error &result) { reset(); }); +#endif + reset, + reset); _loading = true; }; terminate(std::move(callback), tr::lng_settings_reset_sure(tr::now)); diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index ad992b525f8fb..ac8e3e712f570 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -14,6 +14,7 @@ For license and copyright information please follow this link: #include "storage/storage_account.h" #include "ui/boxes/confirm_box.h" #include "apiwrap.h" +#include "api/api_sending.h" // ScheduledToTL #include "ui/chat/forward_options_box.h" #include "ui/toast/toast.h" #include "ui/widgets/checkbox.h" @@ -54,9 +55,17 @@ For license and copyright information please follow this link: #include "styles/style_chat.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_tl_scheme.h" + #include #include +namespace { + +using namespace Tdb; + +} // namespace + class ShareBox::Inner final : public Ui::RpWidget { public: Inner( @@ -73,8 +82,11 @@ class ShareBox::Inner final : public Ui::RpWidget { void peopleReceived( const QString &query, + const Tdb::TLchats &result); +#if 0 // goodToRemove const QVector &my, const QVector &people); +#endif void activateSkipRow(int direction); void activateSkipColumn(int direction); @@ -179,7 +191,10 @@ class ShareBox::Inner final : public Ui::RpWidget { ShareBox::ShareBox(QWidget*, Descriptor &&descriptor) : _descriptor(std::move(descriptor)) +#if 0 // goodToRemove , _api(&_descriptor.session->mtp()) +#endif +, _api(&_descriptor.session->sender()) , _select( this, (_descriptor.stMultiSelect @@ -343,6 +358,25 @@ bool ShareBox::searchByUsername(bool searchCache) { } return true; } + + const auto peopleDone = [=]( + const Tdb::TLchats &result, + Tdb::RequestId requestId) { + auto query = _peopleQuery; + + auto i = _peopleQueries.find(requestId); + if (i != _peopleQueries.cend()) { + query = i.value(); + _peopleCache[query] = result; + _peopleQueries.erase(i); + } + + if (_peopleRequest == requestId) { + _inner->peopleReceived(query, result); + _peopleRequest = 0; + } + }; + if (!query.isEmpty()) { if (searchCache) { auto i = _peopleCache.constFind(query); @@ -355,6 +389,17 @@ bool ShareBox::searchByUsername(bool searchCache) { } else if (_peopleQuery != query) { _peopleQuery = query; _peopleFull = false; + _peopleRequest = _api.request(Tdb::TLsearchPublicChats( + Tdb::tl_string(_peopleQuery) + )).done([=](const Tdb::TLchats &chats, Tdb::RequestId requestId) { + peopleDone(chats, requestId); + }).fail([=](const Tdb::Error &error, mtpRequestId requestId) { + if (_peopleRequest == requestId) { + _peopleRequest = 0; + _peopleFull = true; + } + }).send(); +#if 0 // goodToRemove _peopleRequest = _api.request(MTPcontacts_Search( MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit) @@ -363,6 +408,7 @@ bool ShareBox::searchByUsername(bool searchCache) { }).fail([=](const MTP::Error &error, mtpRequestId requestId) { peopleFail(error, requestId); }).send(); +#endif _peopleQueries.insert(_peopleRequest, _peopleQuery); } } @@ -375,6 +421,7 @@ void ShareBox::needSearchByUsername() { } } +#if 0 // goodToRemove void ShareBox::peopleDone( const MTPcontacts_Found &result, mtpRequestId requestId) { @@ -412,6 +459,7 @@ void ShareBox::peopleFail(const MTP::Error &error, mtpRequestId requestId) { _peopleFull = true; } } +#endif void ShareBox::setInnerFocus() { if (_comment->isHidden()) { @@ -1213,13 +1261,17 @@ rpl::producer<> ShareBox::Inner::searchRequests() const { void ShareBox::Inner::peopleReceived( const QString &query, + const Tdb::TLchats &result) { +#if 0 // goodToRemove const QVector &my, const QVector &people) { +#endif _lastQuery = query.toLower().trimmed(); if (_lastQuery.at(0) == '@') { _lastQuery = _lastQuery.mid(1); } int32 already = _byUsernameFiltered.size(); +#if 0 // goodToRemove _byUsernameFiltered.reserve(already + my.size() + people.size()); d_byUsernameFiltered.reserve(already + my.size() + people.size()); const auto feedList = [&](const QVector &list) { @@ -1228,6 +1280,15 @@ void ShareBox::Inner::peopleReceived( peerFromMTP(data))) { const auto history = _descriptor.session->data().history( peer); +#endif + _byUsernameFiltered.reserve(already + result.data().vchat_ids().v.size()); + d_byUsernameFiltered.reserve(_byUsernameFiltered.size()); + const auto feedList = [&] { + for (const auto &chatId : result.data().vchat_ids().v) { + const auto peerId = peerFromTdbChat(chatId); + auto &owner = _descriptor.session->data(); + if (const auto peer = owner.peerLoaded(peerId)) { + const auto history = owner.historyLoaded(peer); if (!history->asForum() && !_descriptor.filterCallback(history)) { continue; @@ -1245,8 +1306,11 @@ void ShareBox::Inner::peopleReceived( } } }; +#if 0 // goodToRemove feedList(my); feedList(people); +#endif + feedList(); _searching = false; refresh(); @@ -1375,6 +1439,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( return; } +#if 0 // goodToRemove using Flag = MTPmessages_ForwardMessages::Flag; const auto commonSendFlags = Flag(0) | Flag::f_with_my_score @@ -1397,6 +1462,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( } return result; }; +#endif auto &api = history->owner().session().api(); auto &histories = history->owner().histories(); const auto requestType = Data::Histories::RequestType::Send; @@ -1415,6 +1481,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( : topicRootId; const auto peer = thread->peer(); const auto threadHistory = thread->owningHistory(); +#if 0 // mtp histories.sendRequest(threadHistory, requestType, [=]( Fn finish) { auto &api = threadHistory->session().api(); @@ -1456,6 +1523,81 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( return threadHistory->sendRequestId; }); state->requests.insert(threadHistory->sendRequestId); +#endif + const auto requestDone = [=] { + if (state->requests.empty()) { + if (show->valid()) { + Ui::Toast::Show( + show->toastParent(), + tr::lng_share_done(tr::now)); + show->hideLayer(); + } + } + }; + auto &api = threadHistory->session().sender(); + auto tlOptions = tl_messageSendOptions( + tl_bool(ShouldSendSilent(peer, options)), + tl_bool(true), // from_background. + tl_bool(false), // update_order_of_installed_stickers_sets + Api::ScheduledToTL(options.scheduled), + tl_int32(0), // sending_id + tl_bool(false)); // only_preview + + // If we have only one message to send + // Then there is a chance we should send a game score. + const auto dropNames = (forwardOptions != Data::ForwardOptions::PreserveInfo); + const auto dropCaptions = (forwardOptions == Data::ForwardOptions::NoNamesAndCaptions); + if (msgIds.size() == 1) { + auto tlCopyOptions = [&]( + ) -> std::optional { + if (!dropNames && !dropCaptions) { + return std::nullopt; + } + return Tdb::tl_messageCopyOptions( + Tdb::tl_bool(dropNames), + Tdb::tl_bool(dropCaptions), + Tdb::tl_formattedText( + Tdb::tl_string(), + Tdb::tl_vector())); + }(); + state->requests.insert(api.request( + Tdb::TLsendMessage( + peerToTdbChat(peer->id), + Tdb::tl_int53(topicRootId.bare), + std::nullopt, // replyTo + std::move(tlOptions), + Tdb::tl_inputMessageForwarded( + peerToTdbChat(msgIds.front().peer), + tl_int53(msgIds.front().msg.bare), + Tdb::tl_bool(true), // In game share. + std::move(tlCopyOptions)) + )).done([=]( + const Tdb::TLmessage &message, + Tdb::RequestId reqId) { + state->requests.remove(reqId); + requestDone(); + }).send()); + } else { + state->requests.insert(api.request( + Tdb::TLforwardMessages( + peerToTdbChat(peer->id), + tl_int53(topicRootId.bare), + peerToTdbChat(history->peer->id), + Tdb::tl_vector(ranges::views::all( + msgIds + ) | ranges::views::transform([](FullMsgId id) { + return tl_int53(id.msg.bare); + }) | ranges::to), + std::move(tlOptions), + Tdb::tl_bool(dropNames), + Tdb::tl_bool(dropCaptions) + )).done([=]( + const Tdb::TLmessages &messages, + Tdb::RequestId reqId) { + state->requests.remove(reqId); + requestDone(); + }).send()); + } } }; } @@ -1620,6 +1762,10 @@ void ShareGameScoreByHash( api.requestMessageData(peer, msgId, std::move(done)); }); + // In Tdb we use just peerId in ApiWrap::requestMessageData. + resolveMessageAndShareScore( + controller->session().data().peer(peerId)); +#if 0 // mtp const auto peer = peerIsChannel(peerId) ? controller->session().data().peerLoaded(peerId) : nullptr; @@ -1627,6 +1773,7 @@ void ShareGameScoreByHash( resolveMessageAndShareScore(peer); } else { const auto owner = &controller->session().data(); +#if 0 // goodToRemove controller->session().api().request(MTPchannels_GetChannels( MTP_vector( 1, @@ -1637,10 +1784,16 @@ void ShareGameScoreByHash( result.match([&](const auto &data) { owner->processChats(data.vchats()); }); +#endif + controller->session().sender().request(Tdb::TLgetSupergroup( + Tdb::tl_int53(peerToChannel(peerId).bare) + )).done([=](const Tdb::TLsupergroup &result) { + owner->processChannel(result); if (const auto peer = owner->peerLoaded(peerId)) { resolveMessageAndShareScore(peer); } }).send(); } +#endif } } diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index af6bde3a1aa4b..1387340e043f8 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -14,9 +14,14 @@ For license and copyright information please follow this link: #include "ui/effects/animations.h" #include "ui/effects/round_checkbox.h" #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" class History; +namespace Tdb { +class TLchats; +} // namespace Tdb + namespace style { struct MultiSelect; struct InputField; @@ -137,15 +142,20 @@ class ShareBox final : public Ui::BoxContent { void addPeerToMultiSelect(not_null thread); void innerSelectedChanged(not_null thread, bool checked); +#if 0 // goodToRemove void peopleDone( const MTPcontacts_Found &result, mtpRequestId requestId); void peopleFail(const MTP::Error &error, mtpRequestId requestId); +#endif void showMenu(not_null parent); Descriptor _descriptor; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; object_ptr _select; object_ptr> _comment; @@ -165,7 +175,10 @@ class ShareBox final : public Ui::BoxContent { bool _peopleFull = false; mtpRequestId _peopleRequest = 0; +#if 0 // goodToRemove using PeopleCache = QMap; +#endif + using PeopleCache = QMap; PeopleCache _peopleCache; using PeopleQueries = QMap; diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 82e4e3deba3c9..dd720f5117988 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -57,12 +57,18 @@ For license and copyright information please follow this link: #include "styles/style_menu_icons.h" #include "styles/style_premium.h" +#include "tdb/tdb_tl_scheme.h" + #include #include #include +#include "tdb/tdb_sender.h" + namespace { +using namespace Tdb; + constexpr auto kStickersPerRow = 5; constexpr auto kEmojiPerRow = 8; constexpr auto kMinRepaintDelay = crl::time(33); @@ -311,8 +317,12 @@ class StickerSetBox::Inner final : public Ui::RpWidget { void startOverAnimation(int index, float64 from, float64 to); int stickerFromGlobalPos(const QPoint &p) const; +#if 0 // mtp void gotSet(const MTPmessages_StickerSet &set); void installDone(const MTPmessages_StickerSetInstallResult &result); +#endif + void gotSet(const TLstickerSet &set); + void installDone(); void chosen( int index, @@ -330,7 +340,10 @@ class StickerSetBox::Inner final : public Ui::RpWidget { const std::shared_ptr _show; const not_null _session; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; std::vector _elements; std::unique_ptr _lottiePlayer; @@ -655,7 +668,10 @@ StickerSetBox::Inner::Inner( : RpWidget(parent) , _show(std::move(show)) , _session(&_show->session()) +#if 0 // mtp , _api(&_session->mtp()) +#endif +, _api(&_session->sender()) , _setId(set.id) , _setAccessHash(set.accessHash) , _setShortName(set.shortName) @@ -672,6 +688,7 @@ StickerSetBox::Inner::Inner( , _previewTimer([=] { showPreview(); }) { setAttribute(Qt::WA_OpaquePaintEvent); +#if 0 // mtp _api.request(MTPmessages_GetStickerSet( Data::InputStickerSet(_input), MTP_int(0) // hash @@ -681,6 +698,26 @@ StickerSetBox::Inner::Inner( _loaded = true; _errors.fire(Error::NotFound); }).send(); +#endif + if (_input.id) { + _api.request(TLgetStickerSet( + tl_int64(_input.id) + )).done([=](const TLstickerSet &result) { + gotSet(result); + }).fail([=] { + _loaded = true; + _errors.fire(Error::NotFound); + }).send(); + } else { + _api.request(TLsearchStickerSet( + tl_string(_input.shortName) + )).done([=](const TLstickerSet &result) { + gotSet(result); + }).fail([=] { + _loaded = true; + _errors.fire(Error::NotFound); + }).send(); + } _session->api().updateStickers(); @@ -692,7 +729,10 @@ StickerSetBox::Inner::Inner( setMouseTracking(true); } +#if 0 // mtp void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { +#endif +void StickerSetBox::Inner::gotSet(const TLstickerSet &set) { _pack.clear(); _emoji.clear(); _elements.clear(); @@ -700,14 +740,21 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { setCursor(style::cur_default); const auto owner = &_session->data(); const auto premiumPossible = _session->premiumPossible(); +#if 0 // mtp set.match([&](const MTPDmessages_stickerSet &data) { const auto &v = data.vdocuments().v; +#endif + set.match([&](const TLDstickerSet &data) { + _input.id = set.data().vid().v; + + const auto &v = data.vstickers().v; _pack.reserve(v.size()); _elements.reserve(v.size()); for (const auto &item : v) { const auto document = owner->processDocument(item); const auto sticker = document->sticker(); if (!sticker) { + _pack.push_back(nullptr); continue; } _pack.push_back(document); @@ -718,6 +765,33 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { }); } } + + const auto &emojis = data.vemojis().v; + Assert(emojis.size() == _pack.size()); + for (auto i = 0, l = int(emojis.size()); i != l; ++i) { + if (const auto document = _pack[i]) { + for (const auto &string : emojis[i].data().vemojis().v) { + if (const auto emoji = Ui::Emoji::Find(string.v)) { + _emoji[emoji].push_back(document); + } + } + } + } + _pack.erase(ranges::remove(_pack, nullptr), _pack.end()); + _setTitle = _show->session().data().stickers().getSetTitle( + data); + _setShortName = data.vname().v; + _setId = data.vid().v; + _setCount = _pack.size(); + _setFlags = Data::ParseStickersSetFlags(data); + _setInstallDate = data.vis_installed().v ? base::unixtime::now() : 0; + _setThumbnail = [&] { + if (const auto thumbnail = data.vthumbnail()) { + return Images::FromThumbnail(*thumbnail); + } + return ImageWithLocation(); + }(); +#if 0 // mtp for (const auto &pack : data.vpacks().v) { pack.match([&](const MTPDstickerPack &pack) { if (const auto emoji = Ui::Emoji::Find(qs(pack.vemoticon()))) { @@ -761,6 +835,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { } return ImageWithLocation(); }(); +#endif const auto &sets = _session->data().stickers().sets(); const auto it = sets.find(_setId); if (it != sets.cend()) { @@ -777,9 +852,11 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { set->emoji = _emoji; set->setThumbnail(_setThumbnail); } +#if 0 // mtp }); }, [&](const MTPDmessages_stickerSetNotModified &data) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); +#endif }); if (_pack.isEmpty()) { @@ -815,8 +892,11 @@ rpl::producer StickerSetBox::Inner::errors() const { return _errors.events(); } +#if 0 // mtp void StickerSetBox::Inner::installDone( const MTPmessages_StickerSetInstallResult &result) { +#endif +void StickerSetBox::Inner::installDone() { auto &stickers = _session->data().stickers(); auto &sets = stickers.setsRef(); const auto type = setType(); @@ -886,10 +966,13 @@ void StickerSetBox::Inner::installDone( } } +#if 0 // mtp if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { stickers.applyArchivedResult( result.c_messages_stickerSetInstallResultArchive()); } else { +#endif + { auto &storage = _session->local(); if (wasArchived && type != Data::StickersType::Emoji) { if (type == Data::StickersType::Masks) { @@ -1426,6 +1509,7 @@ void StickerSetBox::Inner::install() { if (_installRequest) { return; } +#if 0 // mtp _installRequest = _api.request(MTPmessages_InstallStickerSet( Data::InputStickerSet(_input), MTP_bool(false) @@ -1434,9 +1518,20 @@ void StickerSetBox::Inner::install() { }).fail([=] { _errors.fire(Error::NotFound); }).send(); +#endif + _installRequest = _api.request(TLchangeStickerSet( + tl_int64(_input.id), + tl_bool(true), + tl_bool(false) + )).done([=] { + installDone(); + }).fail([=] { + _errors.fire(Error::NotFound); + }).send(); } void StickerSetBox::Inner::archiveStickers() { +#if 0 // mtp _api.request(MTPmessages_InstallStickerSet( Data::InputStickerSet(_input), MTP_boolTrue() @@ -1447,6 +1542,16 @@ void StickerSetBox::Inner::archiveStickers() { }).fail([=] { _show->showToast(Lang::Hard::ServerError()); }).send(); +#endif + _api.request(TLchangeStickerSet( + tl_int64(_input.id), + tl_bool(true), + tl_bool(true) + )).done([=] { + _setArchived.fire_copy(_setId); + }).fail([=] { + _show->showToast(Lang::Hard::ServerError()); + }).send(); } void StickerSetBox::Inner::updateItems() { diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 3f35e95913d43..1f34152dd8b97 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -43,8 +43,13 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace { +using namespace Tdb; + using Data::StickersSet; using Data::StickersSetsOrder; using Data::StickersSetThumbnailView; @@ -273,7 +278,10 @@ class StickersBox::Inner : public Ui::RpWidget { const style::PeerListItem &_st; const std::shared_ptr _show; const not_null _session; +#if 0 // mtp MTP::Sender _api; +#endif + Sender _api; const Section _section; const bool _isInstalledTab; @@ -331,7 +339,10 @@ class StickersBox::Inner : public Ui::RpWidget { object_ptr _megagroupDivider = { nullptr }; object_ptr _megagroupSubTitle = { nullptr }; base::Timer _megagroupSetAddressChangedTimer; +#if 0 // mtp mtpRequestId _megagroupSetRequestId = 0; +#endif + RequestId _megagroupSetRequestId = 0; }; @@ -411,7 +422,10 @@ StickersBox::StickersBox( : _st(st::stickersRowItem) , _show(std::move(show)) , _session(&_show->session()) +#if 0 // mtp , _api(&_session->mtp()) +#endif +, _api(&_session->sender()) , _tabs(this, st::stickersTabs) , _unreadBadge( this, @@ -433,7 +447,10 @@ StickersBox::StickersBox( : _st(st::stickersRowItem) , _show(std::move(show)) , _session(&_show->session()) +#if 0 // mtp , _api(&_session->mtp()) +#endif +, _api(&_session->sender()) , _section(Section::Installed) , _isMasks(false) , _isEmoji(false) @@ -445,6 +462,7 @@ StickersBox::StickersBox( }, lifetime()); } +#if 0 // mtp StickersBox::StickersBox( QWidget*, std::shared_ptr show, @@ -460,6 +478,21 @@ StickersBox::StickersBox( , _attachedType(Data::StickersType::Stickers) , _attachedSets(attachedSets) { } +#endif +StickersBox::StickersBox( + QWidget*, + std::shared_ptr show, + const QVector &attachedSets) +: _st(st::stickersRowItem) +, _show(std::move(show)) +, _session(&_show->session()) +, _api(&_show->session().sender()) +, _section(Section::Attached) +, _isMasks(false) +, _isEmoji(false) +, _attached(0, this, _show, Section::Attached) +, _attachedSets(attachedSets) { +} StickersBox::StickersBox( QWidget*, @@ -468,7 +501,10 @@ StickersBox::StickersBox( : _st(st::stickersRowItem) , _show(std::move(show)) , _session(&_show->session()) +#if 0 // mtp , _api(&_session->mtp()) +#endif +, _api(&_session->sender()) , _section(Section::Attached) , _isMasks(false) , _isEmoji(true) @@ -515,15 +551,21 @@ void StickersBox::showAttachedStickers() { } void StickersBox::getArchivedDone( +#if 0 // mtp const MTPmessages_ArchivedStickers &result, +#endif + const TLstickerSets &result, uint64 offsetId) { _archivedRequestId = 0; _archivedLoaded = true; +#if 0 // mtp if (result.type() != mtpc_messages_archivedStickers) { return; } auto &stickers = result.c_messages_archivedStickers(); +#endif + const auto &stickers = result.data(); auto &archived = archivedSetsOrderRef(); if (offsetId) { auto index = archived.indexOf(offsetId); @@ -786,6 +828,14 @@ void StickersBox::loadMoreArchived() { } } } + _archivedRequestId = _api.request(TLgetArchivedStickerSets( + (_isMasks ? tl_stickerTypeMask() : tl_stickerTypeRegular()), + tl_int64(lastId), + tl_int32(kArchivedLimitPerPage) + )).done([=](const TLstickerSets &result) { + getArchivedDone(result, lastId); + }).send(); +#if 0 // mtp const auto flags = _isMasks ? MTPmessages_GetArchivedStickers::Flag::f_masks : MTPmessages_GetArchivedStickers::Flags(0); @@ -796,6 +846,7 @@ void StickersBox::loadMoreArchived() { )).done([=](const MTPmessages_ArchivedStickers &result) { getArchivedDone(result, lastId); }).send(); +#endif } void StickersBox::paintEvent(QPaintEvent *e) { @@ -934,6 +985,20 @@ void StickersBox::installSet(uint64 setId) { } if (!(set->flags & SetFlag::Installed) || (set->flags & SetFlag::Archived)) { + _api.request(TLchangeStickerSet( + tl_int64(set->id), + tl_bool(true), // installed + tl_bool(false) // archived + )).fail([=] { + const auto &sets = session().data().stickers().sets(); + const auto it = sets.find(setId); + if (it == sets.cend()) { + rebuildList(); + } else { + session().data().stickers().undoInstallLocally(setId); + } + }).send(); +#if 0 // mtp _api.request(MTPmessages_InstallStickerSet( set->mtpInput(), MTP_boolFalse() @@ -942,11 +1007,13 @@ void StickersBox::installSet(uint64 setId) { }).fail([=](const MTP::Error &error) { installFail(error, setId); }).send(); +#endif session().data().stickers().installLocally(setId); } } +#if 0 // mtp void StickersBox::installDone( const MTPmessages_StickerSetInstallResult &result) const { if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { @@ -964,12 +1031,14 @@ void StickersBox::installFail(const MTP::Error &error, uint64 setId) { session().data().stickers().undoInstallLocally(setId); } } +#endif void StickersBox::preloadArchivedSets() { if (!_tabs) { return; } if (!_archivedRequestId) { +#if 0 // mtp const auto flags = _isMasks ? MTPmessages_GetArchivedStickers::Flag::f_masks : MTPmessages_GetArchivedStickers::Flags(0); @@ -980,6 +1049,14 @@ void StickersBox::preloadArchivedSets() { )).done([=](const MTPmessages_ArchivedStickers &result) { getArchivedDone(result, 0); }).send(); +#endif + _archivedRequestId = _api.request(TLgetArchivedStickerSets( + (_isMasks ? tl_stickerTypeMask() : tl_stickerTypeRegular()), + tl_int64(0), + tl_int32(kArchivedLimitFirstRequest) + )).done([=](const TLstickerSets &result) { + getArchivedDone(result, 0); + }).send(); } } @@ -1186,7 +1263,10 @@ StickersBox::Inner::Inner( , _st(st::stickersRowItem) , _show(std::move(show)) , _session(&_show->session()) +#if 0 // mtp , _api(&_session->mtp()) +#endif +, _api(&_session->sender()) , _section(section) , _isInstalledTab(_section == Section::Installed || _section == Section::Masks) @@ -1225,7 +1305,10 @@ StickersBox::Inner::Inner( , _st(st::stickersRowItem) , _show(std::move(show)) , _session(&_show->session()) +#if 0 // mtp , _api(&_session->mtp()) +#endif +, _api(&_session->sender()) , _section(StickersBox::Section::Installed) , _isInstalledTab(_section == Section::Installed || _section == Section::Masks) @@ -2131,6 +2214,17 @@ void StickersBox::Inner::handleMegagroupSetAddressChange() { } } } else if (!_megagroupSetRequestId) { + _megagroupSetRequestId = _api.request(TLsearchStickerSet( + tl_string(text) + )).done([=](const TLstickerSet &result) { + _megagroupSetRequestId = 0; + const auto set = session().data().stickers().feedSetFull(result); + setMegagroupSelectedSet(set->identifier()); + }).fail([=] { + _megagroupSetRequestId = 0; + setMegagroupSelectedSet({}); + }).send(); +#if 0 // mtp _megagroupSetRequestId = _api.request(MTPmessages_GetStickerSet( MTP_inputStickerSetShortName(MTP_string(text)), MTP_int(0) // hash @@ -2146,6 +2240,7 @@ void StickersBox::Inner::handleMegagroupSetAddressChange() { _megagroupSetRequestId = 0; setMegagroupSelectedSet({}); }).send(); +#endif } else { _megagroupSetAddressChangedTimer.callOnce(kHandleMegagroupSetAddressChangeTimeout); } diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h index 255ed39369c54..2432e9290e71a 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -13,6 +13,13 @@ For license and copyright information please follow this link: #include "data/stickers/data_stickers_set.h" #include "ui/effects/animations.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLstickerSets; +class TLstickerSetInfo; +} // namespace Tdb + namespace style { struct RippleAnimation; struct PeerListItem; @@ -67,10 +74,16 @@ class StickersBox final : public Ui::BoxContent { QWidget*, std::shared_ptr show, not_null megagroup); +#if 0 // mtp StickersBox( QWidget*, std::shared_ptr show, const QVector &attachedSets); +#endif + StickersBox( + QWidget*, + std::shared_ptr show, + const QVector &attachedSets); StickersBox( QWidget*, std::shared_ptr show, @@ -126,14 +139,19 @@ class StickersBox final : public Ui::BoxContent { QPixmap grabContentCache(); +#if 0 // mtp void installDone(const MTPmessages_StickerSetInstallResult &result) const; void installFail(const MTP::Error &error, uint64 setId); +#endif void preloadArchivedSets(); void requestArchivedSets(); void loadMoreArchived(); void getArchivedDone( +#if 0 // mtp const MTPmessages_ArchivedStickers &result, +#endif + const Tdb::TLstickerSets &result, uint64 offsetId); void showAttachedStickers(); @@ -145,7 +163,10 @@ class StickersBox final : public Ui::BoxContent { const style::PeerListItem &_st; const std::shared_ptr _show; const not_null _session; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; object_ptr _tabs = { nullptr }; QList
_tabIndices; @@ -166,7 +187,10 @@ class StickersBox final : public Ui::BoxContent { Tab *_tab = nullptr; const Data::StickersType _attachedType = {}; +#if 0 // mtp const QVector _attachedSets; +#endif + const QVector _attachedSets; const std::vector _emojiSets; ChannelData *_megagroupSet = nullptr; @@ -174,7 +198,10 @@ class StickersBox final : public Ui::BoxContent { std::unique_ptr _slideAnimation; object_ptr _titleShadow = { nullptr }; +#if 0 // mtp mtpRequestId _archivedRequestId = 0; +#endif + Tdb::RequestId _archivedRequestId = 0; bool _archivedLoaded = false; bool _allArchivedLoaded = false; bool _someArchivedLoaded = false; diff --git a/Telegram/SourceFiles/boxes/translate_box.cpp b/Telegram/SourceFiles/boxes/translate_box.cpp index e0a760350d413..4d9d8965326db 100644 --- a/Telegram/SourceFiles/boxes/translate_box.cpp +++ b/Telegram/SourceFiles/boxes/translate_box.cpp @@ -38,9 +38,14 @@ For license and copyright information please follow this link: #include +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace Ui { namespace { +using namespace Tdb; + constexpr auto kSkipAtLeastOneDuration = 3 * crl::time(1000); class ShowButton final : public RpWidget { @@ -103,10 +108,16 @@ void TranslateBox( const auto container = box->verticalLayout(); struct State { +#if 0 // mtp State(not_null session) : api(&session->mtp()) { } MTP::Sender api; +#endif + State(not_null session) : api(&session->sender()) { + } + + Tdb::Sender api; rpl::variable to; }; const auto state = box->lifetime().make_state(&peer->session()); @@ -232,6 +243,28 @@ void TranslateBox( const auto send = [=](LanguageId to) { loading->show(anim::type::instant); translated->hide(anim::type::instant); + const auto done = [=](const TLformattedText &result) { + auto text = Api::FormattedTextFromTdb(result); + showText(text.empty() + ? Ui::Text::Italic(tr::lng_translate_box_error(tr::now)) + : std::move(text)); + }; + const auto fail = [=] { + done(tl_formattedText(tl_string(), tl_vector())); + }; + if (msgId) { + state->api.request(TLtranslateMessageText( + peerToTdbChat(peer->id), + tl_int53(msgId.bare), + tl_string(to.twoLetterCode()) + )).done(done).fail(fail).send(); + } else { + state->api.request(TLtranslateText( + Api::FormattedTextToTdb(text), + tl_string(to.twoLetterCode()) + )).done(done).fail(fail).send(); + } +#if 0 // mtp state->api.request(MTPmessages_TranslateText( MTP_flags(flags), msgId ? peer->input : MTP_inputPeerEmpty(), @@ -265,6 +298,7 @@ void TranslateBox( showText( Ui::Text::Italic(tr::lng_translate_box_error(tr::now))); }).send(); +#endif }; state->to.value() | rpl::start_with_next(send, box->lifetime()); diff --git a/Telegram/SourceFiles/boxes/url_auth_box.cpp b/Telegram/SourceFiles/boxes/url_auth_box.cpp index ef72584cf5f16..a97d11c43e556 100644 --- a/Telegram/SourceFiles/boxes/url_auth_box.cpp +++ b/Telegram/SourceFiles/boxes/url_auth_box.cpp @@ -24,6 +24,15 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_boxes.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + +namespace { + +using namespace Tdb; + +} // namespace + void UrlAuthBox::Activate( not_null message, int row, @@ -38,6 +47,45 @@ void UrlAuthBox::Activate( return; } const auto session = &message->history()->session(); + const auto url = QString::fromUtf8(button->data); + button->requestId = session->sender().request(TLgetLoginUrlInfo( + peerToTdbChat(message->history()->peer->id), + tl_int53(message->id.bare), + tl_int53(button->buttonId) + )).done([=](const TLloginUrlInfo &result) { + const auto button = HistoryMessageMarkupButton::Get( + &session->data(), + itemId, + row, + column); + if (!button) { + return; + } + + button->requestId = 0; + result.match([&](const TLDloginUrlInfoOpen &data) { + if (data.vskip_confirmation().v) { + UrlClickHandler::Open(data.vurl().v); + } else { + HiddenUrlClickHandler::Open(data.vurl().v); + } + }, [&](const TLDloginUrlInfoRequestConfirmation &data) { + if (const auto item = session->data().message(itemId)) { + Request(data, item, row, column); + } + }); + }).fail([=] { + const auto button = HistoryMessageMarkupButton::Get( + &session->data(), + itemId, + row, + column); + if (!button) return; + + button->requestId = 0; + HiddenUrlClickHandler::Open(url); + }).send(); +#if 0 // mtp const auto inputPeer = message->history()->peer->input; const auto buttonId = button->buttonId; const auto url = QString::fromUtf8(button->data); @@ -80,18 +128,23 @@ void UrlAuthBox::Activate( button->requestId = 0; HiddenUrlClickHandler::Open(url); }).send(); +#endif } void UrlAuthBox::Activate( not_null session, const QString &url, QVariant context) { + const auto my = context.value(); + const auto confirm = my.mayShowConfirmation; context = QVariant::fromValue([&] { auto result = context.value(); result.skipBotAutoLogin = true; + result.mayShowConfirmation = false; return result; }()); +#if 0 // mtp using Flag = MTPmessages_RequestUrlAuth::Flag; session->api().request(MTPmessages_RequestUrlAuth( MTP_flags(Flag::f_url), @@ -107,13 +160,33 @@ void UrlAuthBox::Activate( }, [&](const MTPDurlAuthResultRequest &data) { Request(data, session, url, context); }); +#endif + session->sender().request(TLgetExternalLinkInfo( + tl_string(url) + )).done([=](const TLloginUrlInfo &result) { + result.match([&](const TLDloginUrlInfoOpen &data) { + if (data.vskip_confirmation().v || !confirm) { + UrlClickHandler::Open(data.vurl().v, context); + } else { + HiddenUrlClickHandler::Confirm(data.vurl().v, context); + } + }, [&](const TLDloginUrlInfoRequestConfirmation &data) { + UrlAuthBox::Request(data, session, data.vurl().v, context); + }); }).fail([=] { - HiddenUrlClickHandler::Open(url, context); + if (!confirm) { + UrlClickHandler::Open(url, context); + } else { + HiddenUrlClickHandler::Confirm(url, context); + } }).send(); } void UrlAuthBox::Request( +#if 0 // mtp const MTPDurlAuthResultRequest &request, +#endif + const Tdb::TLDloginUrlInfoRequestConfirmation &request, not_null message, int row, int column) { @@ -127,13 +200,20 @@ void UrlAuthBox::Request( return; } const auto session = &message->history()->session(); +#if 0 // mtp const auto inputPeer = message->history()->peer->input; +#endif const auto buttonId = button->buttonId; const auto url = QString::fromUtf8(button->data); +#if 0 // mtp const auto bot = request.is_request_write_access() ? session->data().processUser(request.vbot()).get() : nullptr; +#endif + const auto bot = request.vrequest_write_access().v + ? session->data().user(UserId(request.vbot_user_id().v)).get() + : nullptr; const auto box = std::make_shared>(); const auto finishWithUrl = [=](const QString &url) { if (*box) { @@ -146,6 +226,7 @@ void UrlAuthBox::Request( finishWithUrl(url); } else if (const auto msg = session->data().message(itemId)) { const auto allowWrite = (result == Result::AuthAndAllowWrite); +#if 0 // mtp using Flag = MTPmessages_AcceptUrlAuth::Flag; const auto flags = (allowWrite ? Flag::f_write_allowed : Flag(0)) | (Flag::f_peer | Flag::f_msg_id | Flag::f_button_id); @@ -166,6 +247,14 @@ void UrlAuthBox::Request( "got urlAuthResultRequest after acceptUrlAuth.")); return url; }); +#endif + session->sender().request(TLgetLoginUrl( + peerToTdbChat(msg->history()->peer->id), + tl_int53(msg->id.bare), + tl_int53(buttonId), + tl_bool(allowWrite) + )).done([=](const TLhttpUrl &result) { + const auto to = result.data().vurl().v; finishWithUrl(to); }).fail([=] { finishWithUrl(url); @@ -173,18 +262,29 @@ void UrlAuthBox::Request( } }; *box = Ui::show( +#if 0 // mtp Box(session, url, qs(request.vdomain()), bot, callback), +#endif + Box(session, url, request.vdomain().v, bot, callback), Ui::LayerOption::KeepOther); } void UrlAuthBox::Request( +#if 0 // mtp const MTPDurlAuthResultRequest &request, +#endif + const Tdb::TLDloginUrlInfoRequestConfirmation &request, not_null session, const QString &url, QVariant context) { +#if 0 // mtp const auto bot = request.is_request_write_access() ? session->data().processUser(request.vbot()).get() : nullptr; +#endif + const auto bot = request.vrequest_write_access().v + ? session->data().user(UserId(request.vbot_user_id().v)).get() + : nullptr; const auto box = std::make_shared>(); const auto finishWithUrl = [=](const QString &url) { if (*box) { @@ -197,6 +297,7 @@ void UrlAuthBox::Request( finishWithUrl(url); } else { const auto allowWrite = (result == Result::AuthAndAllowWrite); +#if 0 // mtp using Flag = MTPmessages_AcceptUrlAuth::Flag; const auto flags = (allowWrite ? Flag::f_write_allowed : Flag(0)) | Flag::f_url; @@ -217,6 +318,12 @@ void UrlAuthBox::Request( "got urlAuthResultRequest after acceptUrlAuth.")); return url; }); +#endif + session->sender().request(TLgetExternalLink( + tl_string(url), + tl_bool(allowWrite) + )).done([=](const TLhttpUrl &result) { + const auto to = result.data().vurl().v; finishWithUrl(to); }).fail([=] { finishWithUrl(url); @@ -224,7 +331,10 @@ void UrlAuthBox::Request( } }; *box = Ui::show( +#if 0 // mtp Box(session, url, qs(request.vdomain()), bot, callback), +#endif + Box(session, url, request.vdomain().v, bot, callback), Ui::LayerOption::KeepOther); } diff --git a/Telegram/SourceFiles/boxes/url_auth_box.h b/Telegram/SourceFiles/boxes/url_auth_box.h index f48af2082696c..1734a235b37ba 100644 --- a/Telegram/SourceFiles/boxes/url_auth_box.h +++ b/Telegram/SourceFiles/boxes/url_auth_box.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "ui/layers/box_content.h" +namespace Tdb { +class TLDloginUrlInfoRequestConfirmation; +} // namespace Tdb + class HistoryItem; struct HistoryMessageMarkupButton; @@ -31,6 +35,7 @@ class UrlAuthBox : public Ui::BoxContent { void prepare() override; private: +#if 0 // mtp static void Request( const MTPDurlAuthResultRequest &request, not_null message, @@ -41,6 +46,17 @@ class UrlAuthBox : public Ui::BoxContent { not_null session, const QString &url, QVariant context); +#endif + static void Request( + const Tdb::TLDloginUrlInfoRequestConfirmation &request, + not_null message, + int row, + int column); + static void Request( + const Tdb::TLDloginUrlInfoRequestConfirmation &request, + not_null session, + const QString &url, + QVariant context); enum class Result { None, diff --git a/Telegram/SourceFiles/boxes/username_box.cpp b/Telegram/SourceFiles/boxes/username_box.cpp index 52c08b684181f..62e6fa8113b71 100644 --- a/Telegram/SourceFiles/boxes/username_box.cpp +++ b/Telegram/SourceFiles/boxes/username_box.cpp @@ -31,8 +31,16 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_boxes.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + +#include +#include + namespace { +using namespace Tdb; + [[nodiscard]] TextWithEntities PurchaseAvailableText() { constexpr auto kUsernameAuction = "auction"; return tr::lng_username_purchase_available( @@ -75,7 +83,10 @@ class UsernameEditor final : public Ui::RpWidget { const not_null _peer; const not_null _session; const style::margins &_padding; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; object_ptr _username; @@ -96,7 +107,10 @@ UsernameEditor::UsernameEditor( : _peer(peer) , _session(&peer->session()) , _padding(st::usernamePadding) +, _api(&_session->sender()) +#if 0 // goodToRemove , _api(&_session->mtp()) +#endif , _username( this, st::defaultInputField, @@ -148,6 +162,16 @@ rpl::producer<> UsernameEditor::save() { } _sentUsername = getName(); + _saveRequestId = _api.request(TLsetUsername( + tl_string(_sentUsername) + )).done([=] { + _saveRequestId = 0; + _saved.fire_done(); + }).fail([=](const Error &error) { + _saveRequestId = 0; + updateFail(error.message); + }).send(); +#if 0 // goodToRemove _saveRequestId = _api.request(MTPaccount_UpdateUsername( MTP_string(_sentUsername) )).done([=](const MTPUser &result) { @@ -158,6 +182,7 @@ rpl::producer<> UsernameEditor::save() { _saveRequestId = 0; updateFail(error.type()); }).send(); +#endif return _saved.events(); } @@ -183,6 +208,33 @@ void UsernameEditor::check() { return; } _checkUsername = name; + _checkRequestId = _api.request(TLcheckChatUsername( + peerToTdbChat(_session->userPeerId()), + tl_string(name) + )).done([=](const TLcheckChatUsernameResult &result) { + using namespace Tdb; + _checkRequestId = 0; + result.match([&](const TLDcheckChatUsernameResultOk &data) { + _errorText = QString(); + _goodText = tr::lng_username_available(tr::now); + checkInfoChange(); + }, [&](const TLDcheckChatUsernameResultUsernameInvalid &data) { + _errorText = tr::lng_username_invalid(tr::now); + checkInfoChange(); + }, [&](const TLDcheckChatUsernameResultUsernameOccupied &data) { + _errorText = tr::lng_username_occupied(tr::now); + checkInfoChange(); + }, [](const TLDcheckChatUsernameResultPublicChatsTooMany &data) { + }, [](const TLDcheckChatUsernameResultPublicGroupsUnavailable &data) { + }, [&](const TLDcheckChatUsernameResultUsernamePurchasable &) { + checkInfoPurchaseAvailable(); + }); + }).fail([=] { + _checkRequestId = 0; + _goodText = QString(); + _username->setFocus(); + }).send(); +#if 0 // goodToRemove _checkRequestId = _api.request(MTPaccount_CheckUsername( MTP_string(name) )).done([=](const MTPBool &result) { @@ -201,6 +253,7 @@ void UsernameEditor::check() { _checkRequestId = 0; checkFail(error.type()); }).send(); +#endif } void UsernameEditor::changed() { diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index 66a4e01f5b4a2..0e052b4c22b07 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -37,6 +37,8 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_tl_scheme.h" + namespace Calls { namespace { @@ -472,7 +474,10 @@ void BoxController::Row::rightActionStopLastRipple() { BoxController::BoxController(not_null window) : _window(window) +, _tdbApi(&_window->session().sender()) { +#if 0 // goodToRemove , _api(&_window->session().mtp()) { +#endif } Main::Session &BoxController::session() const { @@ -514,7 +519,17 @@ void BoxController::loadMoreRows() { if (_loadRequestId || _allLoaded) { return; } - + _loadRequestId = _tdbApi.request(Tdb::TLsearchCallMessages( + Tdb::tl_string(_offset), + Tdb::tl_int32(_offset.isEmpty() ? kFirstPageCount : kPerPageCount), + Tdb::tl_bool(false) + )).done([=](const Tdb::TLDfoundMessages &messages) { + _loadRequestId = 0; + receivedCalls(messages); + }).fail([=](const Tdb::Error &error) { + _loadRequestId = 0; + }).send(); +#if 0 // goodToRemove _loadRequestId = _api.request(MTPmessages_Search( MTP_flags(0), MTP_inputPeerEmpty(), @@ -554,6 +569,7 @@ void BoxController::loadMoreRows() { }).fail([this] { _loadRequestId = 0; }).send(); +#endif } base::unique_qptr BoxController::rowContextMenu( @@ -596,6 +612,32 @@ void BoxController::rowRightActionClicked(not_null row) { Core::App().calls().startOutgoingCall(user, false); } +void BoxController::receivedCalls(const Tdb::TLDfoundMessages &messages) { + if (messages.vmessages().v.empty()) { + _allLoaded = true; + } + for (const auto &message : messages.vmessages().v) { + const auto peerId = peerFromTdbChat(message.data().vchat_id()); + if (const auto peer = session().data().peerLoaded(peerId)) { + const auto item = session().data().processMessage( + message, + NewMessageType::Existing); + insertRow(item, InsertWay::Append); + } else { + LOG(("API Error: a search results with not loaded peer %1" + ).arg(peerId.value)); + } + } + _offset = messages.vnext_offset().v; + if (_offset.isEmpty()) { + _allLoaded = true; + } + + refreshAbout(); + delegate()->peerListRefreshRows(); +} + +#if 0 // goodToRemove void BoxController::receivedCalls(const QVector &result) { if (result.empty()) { _allLoaded = true; @@ -620,6 +662,7 @@ void BoxController::receivedCalls(const QVector &result) { refreshAbout(); delegate()->peerListRefreshRows(); } +#endif bool BoxController::insertRow( not_null item, @@ -712,6 +755,7 @@ void ClearCallsBox( st::boxPadding.bottom())); const auto api = &window->session().api(); +#if 0 // goodToRemove const auto sendRequest = [=](bool revoke, auto self) -> void { using Flag = MTPmessages_DeletePhoneCallHistory::Flag; api->request(MTPmessages_DeletePhoneCallHistory( @@ -742,9 +786,19 @@ void ClearCallsBox( }); }).send(); }; - +#endif box->addButton(tr::lng_call_box_clear_button(), [=] { +#if 0 // goodToRemove sendRequest(revokeCheckbox->checked(), sendRequest); +#endif + api->sender().request(Tdb::TLdeleteAllCallMessages( + Tdb::tl_bool(revokeCheckbox->checked()) + )).done([=] { + api->session().data().destroyAllCallItems(); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }).send(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } diff --git a/Telegram/SourceFiles/calls/calls_box_controller.h b/Telegram/SourceFiles/calls/calls_box_controller.h index 860839cdf24eb..b2eaf0aac7194 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.h +++ b/Telegram/SourceFiles/calls/calls_box_controller.h @@ -11,6 +11,12 @@ For license and copyright information please follow this link: #include "ui/layers/generic_box.h" #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLDfoundMessages; +} // namespace Tdb + namespace Window { class SessionController; } // namespace Window @@ -53,7 +59,10 @@ class BoxController : public PeerListController { not_null row) override; private: + void receivedCalls(const Tdb::TLDfoundMessages &messages); +#if 0 // goodToRemove void receivedCalls(const QVector &result); +#endif void refreshAbout(); class GroupCallRow; @@ -69,9 +78,13 @@ class BoxController : public PeerListController { not_null item) const; const not_null _window; + Tdb::Sender _tdbApi; +#if 0 // goodToRemove MTP::Sender _api; MsgId _offsetId = 0; +#endif + QString _offset; int _loadRequestId = 0; // Not a real mtpRequestId. bool _allLoaded = false; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 471468ee34a37..b2b873f7c637a 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -31,6 +31,8 @@ For license and copyright information please follow this link: #include "data/data_user.h" #include "data/data_session.h" +#include "tdb/tdb_tl_scheme.h" + #include #include #include @@ -59,6 +61,7 @@ const auto RegV2Ref = tgcalls::Register(); const auto RegisterV240 = tgcalls::Register(); const auto RegisterLegacy = tgcalls::Register(); +#if 0 // goodToRemove [[nodiscard]] base::flat_set CollectEndpointIds( const QVector &list) { auto result = base::flat_set(); @@ -169,7 +172,77 @@ void AppendServer( } }); } +#endif + +void AppendEndpoint( + std::vector &list, + const Tdb::TLDcallServer &server) { + using namespace Tdb; + server.vtype().match([&](const TLDcallServerTypeTelegramReflector &data) { + if (data.vpeer_tag().v.length() != 16) { + return; + } + tgcalls::Endpoint endpoint = { + .endpointId = server.vid().v, + .host = tgcalls::EndpointHost{ + .ipv4 = server.vip_address().v.toStdString(), + .ipv6 = server.vipv6_address().v.toStdString() }, + .port = (uint16_t)server.vport().v, + .type = tgcalls::EndpointType::UdpRelay, + }; + const auto tag = data.vpeer_tag().v; + if (tag.size() >= 16) { + memcpy(endpoint.peerTag, tag.data(), 16); + } + list.push_back(std::move(endpoint)); + }, [](const TLDcallServerTypeWebrtc &data) { + }); +} +void AppendServer( + std::vector &list, + const Tdb::TLDcallServer &server) { + using namespace Tdb; + server.vtype().match([](const TLDcallServerTypeTelegramReflector &) { + }, [&](const TLDcallServerTypeWebrtc &data) { + const auto host = server.vip_address().v; + const auto hostv6 = server.vipv6_address().v; + const auto port = uint16_t(server.vport().v); + if (data.vsupports_stun().v) { + const auto pushStun = [&](const QString &host) { + if (host.isEmpty()) { + return; + } + list.push_back(tgcalls::RtcServer{ + .host = host.toStdString(), + .port = port, + .isTurn = false + }); + }; + pushStun(host); + pushStun(hostv6); + } + const auto username = data.vusername().v; + const auto password = data.vpassword().v; + if (data.vsupports_turn().v + && !username.isEmpty() + && !password.isEmpty()) { + const auto pushTurn = [&](const QString &host) { + list.push_back(tgcalls::RtcServer{ + .host = host.toStdString(), + .port = port, + .login = username.toStdString(), + .password = password.toStdString(), + .isTurn = true, + }); + }; + pushTurn(host); + pushTurn(hostv6); + } + }); +} + +#if 0 // goodToRemove constexpr auto kFingerprintDataSize = 256; uint64 ComputeFingerprint(bytes::const_span authKey) { Expects(authKey.size() == kFingerprintDataSize); @@ -197,6 +270,19 @@ uint64 ComputeFingerprint(bytes::const_span authKey) { [[nodiscard]] QVector CollectVersionsForApi() { return WrapVersions(tgcalls::Meta::Versions() | ranges::actions::reverse); } +#endif +[[nodiscard]] QVector WrapVersions( + const std::vector &data) { + return ranges::views::all( + data + ) | ranges::views::transform([=](const std::string &string) { + return Tdb::tl_string(string); + }) | ranges::to>; +} + +[[nodiscard]] QVector CollectVersionsForApi() { + return WrapVersions(tgcalls::Meta::Versions() | ranges::actions::reverse); +} [[nodiscard]] Webrtc::VideoState StartVideoState(bool enabled) { using State = Webrtc::VideoState; @@ -208,11 +294,16 @@ uint64 ComputeFingerprint(bytes::const_span authKey) { Call::Call( not_null delegate, not_null user, + CallId id, Type type, bool video) : _delegate(delegate) , _user(user) +, _id(id) +#if 0 // goodToRemove , _api(&_user->session().mtp()) +#endif +, _api(&_user->session().sender()) , _type(type) , _discardByTimeoutTimer([=] { hangup(); }) , _videoIncoming( @@ -226,11 +317,12 @@ Call::Call( } else { const auto &config = _user->session().serverConfig(); _discardByTimeoutTimer.callOnce(config.callRingTimeoutMs); + setState(State::WaitingIncoming); startWaitingTrack(); } setupOutgoingVideo(); } - +#if 0 // goodToRemove void Call::generateModExpFirst(bytes::const_span randomSeed) { auto first = MTP::CreateModExp(_dhConfig.g, _dhConfig.p, randomSeed); if (first.modexp.empty()) { @@ -247,6 +339,7 @@ void Call::generateModExpFirst(bytes::const_span randomSeed) { _gaHash = openssl::Sha256(_ga); } } +#endif bool Call::isIncomingWaiting() const { if (type() != Call::Type::Incoming) { @@ -256,6 +349,7 @@ bool Call::isIncomingWaiting() const { || (state() == State::WaitingIncoming); } +#if 0 // goodToRemove void Call::start(bytes::const_span random) { // Save config here, because it is possible that it changes between // different usages inside the same call. @@ -345,6 +439,7 @@ void Call::startIncoming() { handleRequestError(error.type()); }).send(); } +#endif void Call::applyUserConfirmation() { if (_state.current() == State::WaitingUserConfirmation) { @@ -359,6 +454,7 @@ void Call::answer() { }), video); } +#if 0 // goodToRemove void Call::actuallyAnswer() { Expects(_type == Type::Incoming); @@ -402,6 +498,30 @@ void Call::actuallyAnswer() { handleRequestError(error.type()); }).send(); } +#endif + +void Call::actuallyAnswer() { + Expects(_type == Type::Incoming); + + const auto state = _state.current(); + if (state != State::Starting && state != State::WaitingIncoming) { + if (state != State::ExchangingKeys) { +#if 0 // goodToRemove + if (state != State::ExchangingKeys + || !_answerAfterDhConfigReceived) { +#endif + return; + } + } + + setState(State::ExchangingKeys); + _api.request(Tdb::TLacceptCall( + Tdb::tl_int32(_id), + Protocol() + )).fail([=](const Tdb::Error &error) { + handleRequestError(error.message); + }).send(); +} void Call::setMuted(bool mute) { _muted = mute; @@ -474,6 +594,7 @@ void Call::hangup() { if (state == State::Busy) { _delegate->callFinished(this); } else { +#if 0 // goodToRemove const auto missed = (state == State::Ringing || (state == State::Waiting && _type == Type::Outgoing)); const auto declined = isIncomingWaiting(); @@ -483,6 +604,8 @@ void Call::hangup() { ? MTP_phoneCallDiscardReasonBusy() : MTP_phoneCallDiscardReasonHangup(); finish(FinishType::Ended, reason); +#endif + finish(FinishType::Ended, false); } } @@ -493,7 +616,9 @@ void Call::redial() { Assert(_instance == nullptr); _type = Type::Outgoing; setState(State::Requesting); +#if 0 // goodToRemove _answerAfterDhConfigReceived = false; +#endif startWaitingTrack(); _delegate->callRedial(this); } @@ -516,6 +641,13 @@ void Call::startWaitingTrack() { } void Call::sendSignalingData(const QByteArray &data) { + _api.request(Tdb::TLsendCallSignalingData( + Tdb::tl_int32(_id), + Tdb::tl_bytes(data) + )).fail([=](const Tdb::Error &error) { + handleRequestError(error.message); + }).send(); +#if 0 // goodToRemove _api.request(MTPphone_SendSignalingData( MTP_inputPhoneCall( MTP_long(_id), @@ -528,6 +660,7 @@ void Call::sendSignalingData(const QByteArray &data) { }).fail([=](const MTP::Error &error) { handleRequestError(error.type()); }).send(); +#endif } float64 Call::getWaitingSoundPeakValue() const { @@ -538,6 +671,14 @@ float64 Call::getWaitingSoundPeakValue() const { return 0.; } +bool Call::hasFingerprint() const { + return !_fingerprint.empty(); +} + +std::vector Call::fingerprint() const { + return _fingerprint; +} +#if 0 // goodToRemove bool Call::isKeyShaForFingerprintReady() const { return (_keyFingerprint != 0); } @@ -704,6 +845,101 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { Unexpected("phoneCall type inside an existing call handleUpdate()"); } +#endif + +bool Call::handleUpdate(const Tdb::TLDcall &call) { + const auto id = call.vid().v; + if (id != _id) { + return false; + } + return call.vstate().match([&](const Tdb::TLDcallStatePending &data) { + if (data.vis_created().v) { // Is not a local pending state. + if (_finishAfterRequestingCall != FinishType::None) { + if (_finishAfterRequestingCall == FinishType::Failed) { + finish(_finishAfterRequestingCall); + } else { + hangup(); + } + return false; + } + } + + if (data.vis_created().v && (_type == Type::Outgoing)) { + setState(data.vis_received().v ? State::Ringing : State::Waiting); + return true; + } + return false; + }, [&](const Tdb::TLDcallStateExchangingKeys &data) { + setState(State::ExchangingKeys); + return true; + }, [&](const Tdb::TLDcallStateReady &data) { + // Both Outcoming and Incoming. + if (_state.current() == State::ExchangingKeys && !_instance) { + createAndStartController(data); + } + return true; + }, [&](const Tdb::TLDcallStateHangingUp &data) { + setState(State::HangingUp); + return true; + }, [&](const Tdb::TLDcallStateDiscarded &data) { + if (data.vneed_debug_information().v) { + const auto debugLog = _instance + ? _instance->getDebugInfo() + : std::string(); + if (!debugLog.empty()) { + user()->session().api().sender().request( + Tdb::TLsendCallDebugInformation( + Tdb::tl_int32(_id), + Tdb::tl_string(debugLog) + )).send(); + } + } + if (data.vneed_rating().v) { + const auto session = &_user->session(); + const auto callId = _id; + const auto box = Ui::show(Box( + Core::App().settings().sendSubmitWay())); + const auto sender = box->lifetime().make_state( + &session->sender()); + box->sends( + ) | rpl::take( + 1 // Instead of keeping requestId. + ) | rpl::start_with_next([=](const Ui::RateCallBox::Result &r) { + sender->request(Tdb::TLsendCallRating( + Tdb::tl_int32(callId), + Tdb::tl_int32(r.rating), + Tdb::tl_string(r.comment), + Tdb::tl_vector() + )).done([=] { + if (box) { + box->closeBox(); + } + }).fail([=] { + if (box) { + box->closeBox(); + } + }).send(); + }, box->lifetime()); + } + data.vreason().match([=]( + const Tdb::TLDcallDiscardReasonDeclined &) { + setState((_type == Type::Outgoing) ? State::Busy : State::Ended); + }, [=](const auto &) { + if (_type == Type::Outgoing + || _state.current() == State::HangingUp) { + setState(State::Ended); + } else { + setState(State::EndedByOtherDevice); + } + }); + return true; + }, [&](const Tdb::TLDcallStateError &data) { + const auto error = data.verror().data().vmessage().v; + LOG(("Call Error: %1.").arg(error)); + handleRequestError(error); + return true; + }); +} void Call::updateRemoteMediaState( tgcalls::AudioState audio, @@ -729,9 +965,14 @@ void Call::updateRemoteMediaState( }()); } +#if 0 // mtp bool Call::handleSignalingData( const MTPDupdatePhoneCallSignalingData &data) { if (data.vphone_call_id().v != _id || !_instance) { +#endif +bool Call::handleSignalingData( + const Tdb::TLDupdateNewCallSignalingData &data) { + if (data.vcall_id().v != _id || !_instance) { return false; } auto prepared = ranges::views::all( @@ -742,7 +983,7 @@ bool Call::handleSignalingData( _instance->receiveSignalingData(std::move(prepared)); return true; } - +#if 0 // goodToRemove void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { Expects(_type == Type::Outgoing); @@ -823,19 +1064,30 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) { } void Call::createAndStartController(const MTPDphoneCall &call) { +#endif +void Call::createAndStartController(const Tdb::TLDcallStateReady &data) { _discardByTimeoutTimer.cancel(); +#if 0 // goodToRemove if (!checkCallFields(call) || _authKey.size() != kAuthKeySize) { return; } const auto &protocol = call.vprotocol().c_phoneCallProtocol(); +#endif + const auto &protocol = data.vprotocol().data(); const auto &serverConfig = _user->session().serverConfig(); auto encryptionKeyValue = std::make_shared>(); +#if 0 // goodToRemove memcpy(encryptionKeyValue->data(), _authKey.data(), kAuthKeySize); +#endif + memcpy( + encryptionKeyValue->data(), + data.vencryption_key().v.data(),kAuthKeySize); +#if 0 // goodToRemove const auto version = call.vprotocol().match([&]( const MTPDphoneCallProtocol &data) { return data.vlibrary_versions().v; @@ -844,6 +1096,12 @@ void Call::createAndStartController(const MTPDphoneCall &call) { LOG(("Call Info: Creating instance with version '%1', allowP2P: %2").arg( QString::fromUtf8(version), Logs::b(call.is_p2p_allowed()))); +#endif + const auto version = protocol.vlibrary_versions().v.front().v; + + LOG(("Call Info: Creating instance with version '%1', allowP2P: %2").arg( + version, + Logs::b(data.vallow_p2p().v))); const auto versionString = version.toStdString(); const auto &settings = Core::App().settings(); @@ -855,7 +1113,10 @@ void Call::createAndStartController(const MTPDphoneCall &call) { serverConfig.callConnectTimeoutMs / 1000., .receiveTimeout = serverConfig.callPacketTimeoutMs / 1000., .dataSaving = tgcalls::DataSaving::Never, +#if 0 // goodToRemove .enableP2P = call.is_p2p_allowed(), +#endif + .enableP2P = data.vallow_p2p().v, .enableAEC = false, .enableNS = true, .enableAGC = true, @@ -915,6 +1176,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) { QDir().mkpath(callLogFolder); } +#if 0 // goodToRemove const auto ids = CollectEndpointIds(call.vconnections().v); for (const auto &connection : call.vconnections().v) { AppendEndpoint(descriptor.endpoints, connection); @@ -922,6 +1184,14 @@ void Call::createAndStartController(const MTPDphoneCall &call) { for (const auto &connection : call.vconnections().v) { AppendServer(descriptor.rtcServers, connection, ids); } +#endif + + for (const auto &server : data.vservers().v) { + AppendEndpoint(descriptor.endpoints, server.data()); + } + for (const auto &server : data.vservers().v) { + AppendServer(descriptor.rtcServers, server.data()); + } { const auto &settingsProxy = Core::App().settings().proxy(); @@ -940,8 +1210,11 @@ void Call::createAndStartController(const MTPDphoneCall &call) { } _instance = tgcalls::Meta::Create(versionString, std::move(descriptor)); if (!_instance) { +#if 0 // goodToRemove LOG(("Call Error: Wrong library version: %1." ).arg(QString::fromUtf8(version))); + ).arg(version)); +#endif finish(FinishType::Failed); return; } @@ -953,6 +1226,14 @@ void Call::createAndStartController(const MTPDphoneCall &call) { raw->setIncomingVideoOutput(_videoIncoming->sink()); raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled()); + + { + _fingerprint = ranges::views::all( + data.vemojis().v + ) | ranges::views::transform([](const Tdb::TLstring &string) { + return Ui::Emoji::Find(string.v); + }) | ranges::to_vector; + } } void Call::handleControllerStateChange(tgcalls::State state) { @@ -992,7 +1273,7 @@ void Call::handleControllerBarCountChange(int count) { void Call::setSignalBarCount(int count) { _signalBarCount = count; } - +#if 0 // goodToRemove template bool Call::checkCallCommonFields(const T &call) { const auto checkFailed = [this] { @@ -1039,6 +1320,7 @@ bool Call::checkCallFields(const MTPDphoneCall &call) { bool Call::checkCallFields(const MTPDphoneCallAccepted &call) { return checkCallCommonFields(call); } +#endif void Call::setState(State state) { const auto was = _state.current(); @@ -1206,7 +1488,10 @@ void Call::toggleScreenSharing(std::optional uniqueId) { _videoOutgoing->setState(Webrtc::VideoState::Active); } +#if 0 // goodToRemove void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { +#endif +void Call::finish(FinishType type, bool isDisconnected) { Expects(type != FinishType::None); setSignalBarCount(kSignalBarFinished); @@ -1247,15 +1532,29 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { }); using Video = Webrtc::VideoState; +#if 0 // goodToRemove const auto flags = ((_videoIncoming->state() != Video::Inactive) || (_videoOutgoing->state() != Video::Inactive)) ? MTPphone_DiscardCall::Flag::f_video : MTPphone_DiscardCall::Flag(0); +#endif + const auto isVideo = ((_videoIncoming->state() != Video::Inactive) + || (_videoOutgoing->state() != Video::Inactive)); // We want to discard request still being sent and processed even if // the call is already destroyed. const auto session = &_user->session(); const auto weak = base::make_weak(this); + session->api().sender().request(Tdb::TLdiscardCall( + Tdb::tl_int32(_id), + Tdb::tl_bool(isDisconnected), + Tdb::tl_int32(duration), + Tdb::tl_bool(isVideo), + Tdb::tl_int64(connectionId) + )).fail(crl::guard(weak, [this, finalState] { + setState(finalState); + })).send(); +#if 0 // goodToRemove session->api().request(MTPphone_DiscardCall( // We send 'discard' here. MTP_flags(flags), MTP_inputPhoneCall( @@ -1272,6 +1571,7 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { }).fail(crl::guard(weak, [this, finalState] { setState(finalState); })).send(); +#endif } void Call::setStateQueued(State state) { @@ -1328,12 +1628,23 @@ void Call::destroyController() { setSignalBarCount(kSignalBarFinished); } +Tdb::TLcallProtocol Call::Protocol() { + return Tdb::tl_callProtocol( + Tdb::tl_bool(true), + Tdb::tl_bool(true), + Tdb::tl_int32(kMinLayer), + Tdb::tl_int32(tgcalls::Meta::MaxLayer()), + Tdb::tl_vector(CollectVersionsForApi())); +} + Call::~Call() { destroyController(); } +#if 0 // goodToRemove void UpdateConfig(const std::string &data) { tgcalls::SetLegacyGlobalServerConfig(data); } +#endif } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 1a72983e1b02d..40a765c0c161f 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -11,7 +11,16 @@ For license and copyright information please follow this link: #include "base/timer.h" #include "base/bytes.h" #include "mtproto/sender.h" +#include "tdb/tdb_sender.h" #include "mtproto/mtproto_auth_key.h" +#include "ui/emoji_config.h" + +namespace Tdb { +class TLDcall; +class TLcallProtocol; +class TLDcallStateReady; +class TLDupdateNewCallSignalingData; +} // namespace Tdb namespace Media { namespace Audio { @@ -33,12 +42,13 @@ class VideoTrack; } // namespace Webrtc namespace Calls { - +#if 0 // goodToRemove struct DhConfig { int32 version = 0; int32 g = 0; bytes::vector p; }; +#endif enum class ErrorType { NoCamera, @@ -62,7 +72,9 @@ class Call : public base::has_weak_ptr { public: class Delegate { public: +#if 0 // goodToRemove virtual DhConfig getDhConfig() const = 0; +#endif virtual void callFinished(not_null call) = 0; virtual void callFailed(not_null call) = 0; virtual void callRedial(not_null call) = 0; @@ -92,6 +104,7 @@ class Call : public base::has_weak_ptr { Call( not_null delegate, not_null user, + CallId id, Type type, bool video); @@ -106,9 +119,16 @@ class Call : public base::has_weak_ptr { } [[nodiscard]] bool isIncomingWaiting() const; +#if 0 // goodToRemove void start(bytes::const_span random); + bool handleUpdate(const MTPPhoneCall &call); +#endif + bool handleUpdate(const Tdb::TLDcall &data); + bool handleSignalingData(const Tdb::TLDupdateNewCallSignalingData &data); +#if 0 // goodToRemove bool handleSignalingData(const MTPDupdatePhoneCallSignalingData &data); +#endif enum State { Starting, @@ -185,8 +205,12 @@ class Call : public base::has_weak_ptr { void hangup(); void redial(); +#if 0 // goodToRemove bool isKeyShaForFingerprintReady() const; bytes::vector getKeyShaForFingerprint() const; +#endif + bool hasFingerprint() const; + std::vector fingerprint() const; QString getDebugLog() const; @@ -211,6 +235,8 @@ class Call : public base::has_weak_ptr { return _lifetime; } + static Tdb::TLcallProtocol Protocol(); + ~Call(); private: @@ -222,28 +248,38 @@ class Call : public base::has_weak_ptr { void handleRequestError(const QString &error); void handleControllerError(const QString &error); + void finish(FinishType type, bool isDisconnected = true); +#if 0 // goodToRemove void finish( FinishType type, const MTPPhoneCallDiscardReason &reason = MTP_phoneCallDiscardReasonDisconnect()); void startOutgoing(); void startIncoming(); +#endif void startWaitingTrack(); void sendSignalingData(const QByteArray &data); +#if 0 // goodToRemove void generateModExpFirst(bytes::const_span randomSeed); +#endif void handleControllerStateChange(tgcalls::State state); void handleControllerBarCountChange(int count); + void createAndStartController(const Tdb::TLDcallStateReady &data); +#if 0 // goodToRemove void createAndStartController(const MTPDphoneCall &call); template bool checkCallCommonFields(const T &call); bool checkCallFields(const MTPDphoneCall &call); bool checkCallFields(const MTPDphoneCallAccepted &call); +#endif void actuallyAnswer(); +#if 0 // goodToRemove void confirmAcceptedCall(const MTPDphoneCallAccepted &call); void startConfirmedCall(const MTPDphoneCall &call); +#endif void setState(State state); void setStateQueued(State state); void setFailedQueued(const QString &error); @@ -257,7 +293,12 @@ class Call : public base::has_weak_ptr { const not_null _delegate; const not_null _user; + const CallId _id = 0; + +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; Type _type = Type::Outgoing; rpl::variable _state = State::Starting; rpl::variable _remoteAudioState = @@ -265,7 +306,9 @@ class Call : public base::has_weak_ptr { rpl::variable _remoteVideoState; rpl::event_stream _errors; FinishType _finishAfterRequestingCall = FinishType::None; +#if 0 // goodToRemove bool _answerAfterDhConfigReceived = false; +#endif rpl::variable _signalBarCount = kSignalBarStarting; crl::time _startTime = 0; base::DelayedCallTimer _finishByTimeoutTimer; @@ -273,6 +316,7 @@ class Call : public base::has_weak_ptr { rpl::variable _muted = false; +#if 0 // goodToRemove DhConfig _dhConfig; bytes::vector _ga; bytes::vector _gb; @@ -283,6 +327,9 @@ class Call : public base::has_weak_ptr { CallId _id = 0; uint64 _accessHash = 0; uint64 _keyFingerprint = 0; +#endif + + std::vector _fingerprint; std::unique_ptr _instance; std::shared_ptr _videoCapture; @@ -296,7 +343,8 @@ class Call : public base::has_weak_ptr { rpl::lifetime _lifetime; }; - +#if 0 // goodToRemove void UpdateConfig(const std::string &data); +#endif } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp b/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp index a39ae87edce56..ec977d12a5647 100644 --- a/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp +++ b/Telegram/SourceFiles/calls/calls_emoji_fingerprint.cpp @@ -21,7 +21,7 @@ namespace Calls { namespace { constexpr auto kTooltipShowTimeoutMs = 1000; - +#if 0 // goodToRemove const ushort Data[] = { 0xd83d, 0xde09, 0xd83d, 0xde0d, 0xd83d, 0xde1b, 0xd83d, 0xde2d, 0xd83d, 0xde31, 0xd83d, 0xde21, 0xd83d, 0xde0e, 0xd83d, 0xde34, 0xd83d, 0xde35, 0xd83d, 0xde08, 0xd83d, 0xde2c, 0xd83d, 0xde07, @@ -153,6 +153,9 @@ std::vector ComputeEmojiFingerprint(not_null call) { } return result; } +#endif + +} // namespace object_ptr CreateFingerprintAndSignalBars( not_null parent, @@ -208,7 +211,10 @@ object_ptr CreateFingerprintAndSignalBars( bars->setAttribute(Qt::WA_TransparentForMouseEvents); // Geometry. +#if 0 // goodToRemove const auto print = ComputeEmojiFingerprint(call); +#endif + const auto print = call->fingerprint(); auto realSize = Ui::Emoji::GetSizeNormal(); auto size = realSize / cIntRetinaFactor(); auto count = print.size(); diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index d261409166446..dbe9a20c70722 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -36,6 +36,8 @@ For license and copyright information please follow this link: #include "mtproto/mtproto_config.h" #include "boxes/abstract_box.h" // Ui::show(). +#include "tdb/tdb_tl_scheme.h" + #include #include @@ -54,8 +56,9 @@ class Instance::Delegate final , public GroupCall::Delegate { public: explicit Delegate(not_null instance); - +#if 0 // goodToRemove DhConfig getDhConfig() const override; +#endif void callFinished(not_null call) override; void callFailed(not_null call) override; @@ -85,10 +88,11 @@ class Instance::Delegate final Instance::Delegate::Delegate(not_null instance) : _instance(instance) { } - +#if 0 // goodToRemove DhConfig Instance::Delegate::getDhConfig() const { return *_instance->_cachedDhConfig; } +#endif void Instance::Delegate::callFinished(not_null call) { crl::on_main(call, [=] { @@ -104,7 +108,11 @@ void Instance::Delegate::callFailed(not_null call) { void Instance::Delegate::callRedial(not_null call) { if (_instance->_currentCall.get() == call) { + const auto video = call->isSharingVideo(); + _instance->requestToCreateCall(call->user(), video); +#if 0 // goodToRemove _instance->refreshDhConfig(); +#endif } } @@ -172,7 +180,9 @@ FnMut Instance::Delegate::groupCallAddAsyncWaiter() { Instance::Instance() : _delegate(std::make_unique(this)) +#if 0 // goodToRemove , _cachedDhConfig(std::make_unique()) +#endif , _chooseJoinAs(std::make_unique()) , _startWithRtmp(std::make_unique()) { } @@ -200,10 +210,25 @@ void Instance::startOutgoingCall(not_null user, bool video) { return; } requestPermissionsOrFail(crl::guard(this, [=] { +#if 0 // goodToRemove createCall(user, Call::Type::Outgoing, video); +#endif + requestToCreateCall(user, video); }), video); } +void Instance::requestToCreateCall(not_null user, bool video) { + user->session().sender().request(Tdb::TLcreateCall( + peerToTdbChat(user->id), + Call::Protocol(), + Tdb::tl_bool(video) + )).fail([=](const Tdb::Error &error) { + if (_currentCall) { + _currentCall->hangup(); + } + }).send(); +} + void Instance::startOrJoinGroupCall( std::shared_ptr show, not_null peer, @@ -223,9 +248,12 @@ void Instance::startOrJoinGroupCall( if (call) { info.rtmp = call->rtmp(); } +#if 0 // goodToRemove createGroupCall( std::move(info), call ? call->input() : MTP_inputGroupCall({}, {})); +#endif + createGroupCall(std::move(info), call ? call->id() : 0); }); }); } @@ -286,7 +314,10 @@ void Instance::showStartWithRtmp( _startWithRtmp->start(peer, show, [=](Group::JoinInfo info) { confirmLeaveCurrent(show, peer, {}, [=](auto) { _startWithRtmp->close(); +#if 0 // goodToRemove createGroupCall(std::move(info), MTP_inputGroupCall({}, {})); +#endif + createGroupCall(std::move(info), 0); }); }); } @@ -311,6 +342,15 @@ void Instance::playSoundOnce(const QString &key) { void Instance::destroyCall(not_null call) { if (_currentCall.get() == call) { _currentCallPanel->closeBeforeDestroy(); + + call->user()->session().api().sender().request(Tdb::TLdiscardCall( + Tdb::tl_int32(call->id()), + Tdb::tl_bool(true), + Tdb::tl_int32(0), + Tdb::tl_bool(call->isSharingVideo()), + Tdb::tl_int64(0) // doLater busy. + )).send(); + _currentCallPanel = nullptr; auto taken = base::take(_currentCall); @@ -327,6 +367,7 @@ void Instance::destroyCall(not_null call) { void Instance::createCall( not_null user, Call::Type type, + int id, bool isVideo) { struct Performer final { explicit Performer(Fn callback) @@ -339,7 +380,15 @@ void Instance::createCall( bool isConfirmed, const Performer &repeater) { const auto delegate = _delegate.get(); +#if 0 // goodToRemove auto call = std::make_unique(delegate, user, type, video); +#endif + auto call = std::make_unique( + delegate, + user, + id, + type, + video); if (isConfirmed) { call->applyUserConfirmation(); } @@ -364,8 +413,10 @@ void Instance::createCall( repeater.callback(video, true, repeater); }, raw->lifetime()); } else { +#if 0 // goodToRemove refreshServerConfig(&user->session()); refreshDhConfig(); +#endif } _currentCallChanges.fire_copy(raw); }); @@ -390,13 +441,19 @@ void Instance::destroyGroupCall(not_null call) { void Instance::createGroupCall( Group::JoinInfo info, +#if 0 // goodToRemove const MTPInputGroupCall &inputCall) { +#endif + CallId id) { destroyCurrentCall(); auto call = std::make_unique( _delegate.get(), std::move(info), +#if 0 // goodToRemove inputCall); +#endif + id); const auto raw = call.get(); info.peer->session().account().sessionChanges( @@ -408,7 +465,7 @@ void Instance::createGroupCall( _currentGroupCall = std::move(call); _currentGroupCallChanges.fire_copy(raw); } - +#if 0 // goodToRemove void Instance::refreshDhConfig() { Expects(_currentCall != nullptr); @@ -509,6 +566,24 @@ void Instance::handleUpdate( Unexpected("Update type in Calls::Instance::handleUpdate."); }); } +#endif + +void Instance::handleUpdate( + not_null session, + const Tdb::TLDupdateCall &update) { + handleCallUpdate(session, update.vcall().data()); +} + +void Instance::handleUpdate( + not_null session, + const Tdb::TLDupdateNewCallSignalingData &update) { + if (!_currentCall + || (&_currentCall->user()->session() != session) + || !_currentCall->handleSignalingData(update)) { + DEBUG_LOG(("API Warning: unexpected call signaling data %1" + ).arg(update.vcall_id().v)); + } +} void Instance::showInfoPanel(not_null call) { if (_currentCall.get() == call) { @@ -573,6 +648,78 @@ bool Instance::isQuitPrevent() { return true; } +void Instance::handleCallUpdate( + not_null session, + const Tdb::TLDcall &call) { + const auto isVideo = call.vis_video().v; + const auto id = call.vid().v; + const auto loadedUser = [&] { + return session->data().userLoaded( + peerToUser(peerFromTdbChat(call.vuser_id()))); + }; + const auto processOutgoing = [&] { + if (const auto user = loadedUser()) { + createCall(user, Call::Type::Outgoing, id, isVideo); + } else { + Unexpected("Can't find loaded user we're calling."); + } + }; + const auto processIncoming = [&] { + const auto user = loadedUser(); + if (!user) { + LOG(("API Error: User not loaded for phoneCallRequested.")); + } else if (user->isSelf()) { + LOG(("API Error: Self found in phoneCallRequested.")); + } + if (inCall() || inGroupCall() || !user || user->isSelf()) { + if (_currentCall && _currentCall->id() == id) { + return; + } + session->api().sender().request(Tdb::TLdiscardCall( + call.vid(), + Tdb::tl_bool(false), + Tdb::tl_int32(0), + Tdb::tl_bool(isVideo), + Tdb::tl_int64(0) // doLater busy. + )).send(); + } else { + createCall(user, Call::Type::Incoming, id, isVideo); + } + }; + call.vstate().match([&](const Tdb::TLDcallStatePending &stateData) { + const auto received = stateData.vis_received().v; + const auto localPending = !stateData.vis_created().v; + if (localPending) { + if (!received) { + processOutgoing(); + } else { + Unexpected("TDLib error: " + "local call is received from participant."); + } + } else { + if (received) { + if (inCall() + && _currentCall->type() == Call::Type::Outgoing + && _currentCall->user()->id == session->userPeerId() + && (peerFromUser(call.vuser_id()) + == _currentCall->user()->session().userPeerId())) { + // Ignore call from the same running app, other account. + return; + } + processIncoming(); + } + _currentCall->handleUpdate(call); + } + }, [&](const auto &stateData) { + if (!_currentCall + || (&_currentCall->user()->session() != session) + || !_currentCall->handleUpdate(call)) { +// DEBUG_LOG(("API Warning: unexpected phone call update %1" +// ).arg(call.vtype())); + } + }); +} +#if 0 // goodToRemove void Instance::handleCallUpdate( not_null session, const MTPPhoneCall &call) { @@ -623,7 +770,31 @@ void Instance::handleCallUpdate( DEBUG_LOG(("API Warning: unexpected phone call update %1").arg(call.type())); } } +#endif +void Instance::handleGroupCallUpdate( + not_null session, + const Tdb::TLDupdateGroupCall &update) { + const auto callId = update.vgroup_call().data().vid().v; + if (const auto existing = session->data().groupCall(callId)) { + existing->applyUpdate(update); + } +} + +void Instance::handleGroupCallUpdate( + not_null session, + const Tdb::TLDupdateGroupCallParticipant &update) { + const auto callId = update.vgroup_call_id().v; + if (const auto existing = session->data().groupCall(callId)) { + existing->applyUpdate(update); + } + if (_currentGroupCall + && (&_currentGroupCall->peer()->session() == session)) { + _currentGroupCall->handleUpdate(update); + } +} + +#if 0 // goodToRemove void Instance::handleGroupCallUpdate( not_null session, const MTPUpdate &update) { @@ -666,7 +837,6 @@ void Instance::applyGroupCallUpdateChecked( _currentGroupCall->handleUpdate(update); } } - void Instance::handleSignalingData( not_null session, const MTPDupdatePhoneCallSignalingData &data) { @@ -677,6 +847,7 @@ void Instance::handleSignalingData( ).arg(data.vphone_call_id().v)); } } +#endif bool Instance::inCall() const { if (!_currentCall) { diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index e15d800be7c56..cdd065195b6ec 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -9,6 +9,14 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" +namespace Tdb { +class TLDupdateCall; +class TLDupdateNewCallSignalingData; +class TLDupdateGroupCall; +class TLDupdateGroupCallParticipant; +class TLDcall; +} // namespace Tdb + namespace crl { class semaphore; } // namespace crl @@ -72,14 +80,31 @@ class Instance final : public base::has_weak_ptr { void showStartWithRtmp( std::shared_ptr show, not_null peer); + +#if 0 // goodToRemove void handleUpdate( not_null session, const MTPUpdate &update); +#endif + void handleUpdate( + not_null session, + const Tdb::TLDupdateCall &update); + void handleUpdate( + not_null session, + const Tdb::TLDupdateNewCallSignalingData &update); + void handleGroupCallUpdate( + not_null session, + const Tdb::TLDupdateGroupCall &update); + void handleGroupCallUpdate( + not_null session, + const Tdb::TLDupdateGroupCallParticipant &update); +#if 0 // goodToRemove // Called by Data::GroupCall when it is appropriate by the 'version'. void applyGroupCallUpdateChecked( not_null session, const MTPUpdate &update); +#endif void showInfoPanel(not_null call); void showInfoPanel(not_null call); @@ -117,12 +142,23 @@ class Instance final : public base::has_weak_ptr { not_null ensureSoundLoaded(const QString &key); void playSoundOnce(const QString &key); +#if 0 // goodToRemove void createCall(not_null user, CallType type, bool isVideo); +#endif + void createCall( + not_null user, + CallType type, + int id, + bool video); void destroyCall(not_null call); + void requestToCreateCall(not_null user, bool video); +#if 0 // goodToRemove void createGroupCall( Group::JoinInfo info, const MTPInputGroupCall &inputCall); +#endif + void createGroupCall(Group::JoinInfo info, CallId id); void destroyGroupCall(not_null call); void confirmLeaveCurrent( std::shared_ptr show, @@ -134,26 +170,40 @@ class Instance final : public base::has_weak_ptr { Platform::PermissionType type, Fn onSuccess); +#if 0 // goodToRemove void refreshDhConfig(); void refreshServerConfig(not_null session); bytes::const_span updateDhConfig(const MTPmessages_DhConfig &data); +#endif void destroyCurrentCall(); + +#if 0 // goodToRemove void handleCallUpdate( not_null session, const MTPPhoneCall &call); +#endif + void handleCallUpdate( + not_null session, + const Tdb::TLDcall &call); +#if 0 // goodToRemove void handleSignalingData( not_null session, const MTPDupdatePhoneCallSignalingData &data); void handleGroupCallUpdate( not_null session, const MTPUpdate &update); +#endif const std::unique_ptr _delegate; +#if 0 // goodToRemove const std::unique_ptr _cachedDhConfig; +#endif +#if 0 // goodToRemove crl::time _lastServerConfigUpdateTime = 0; base::weak_ptr _serverConfigRequestSession; +#endif std::weak_ptr _videoCapture; std::unique_ptr _currentCall; diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 8575964755a7c..2ebb1be8a1f52 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -908,7 +908,10 @@ void Panel::stateChanged(State state) { _answerHangupRedialState = answerHangupRedialState; refreshAnswerHangupRedialLabel(); } - if (!_call->isKeyShaForFingerprintReady()) { +#if 0 // goodToRemove + if (!_call->sKeyShaForFingerprintReady()) { +#endif + if (!_call->hasFingerprint()) { _fingerprint.destroy(); } else if (!_fingerprint) { _fingerprint = CreateFingerprintAndSignalBars(widget(), _call); diff --git a/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp b/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp index a3f57de1014e7..7f1779f85843d 100644 --- a/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp +++ b/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp @@ -29,9 +29,13 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_calls.h" +#include "tdb/tdb_tl_scheme.h" + namespace Calls::Group { namespace { +using namespace Tdb; + constexpr auto kLabelRefreshInterval = 10 * crl::time(1000); using Context = ChooseJoinAsProcess::Context; @@ -301,7 +305,10 @@ void ChooseJoinAsBox( ChooseJoinAsProcess::~ChooseJoinAsProcess() { if (_request) { +#if 0 // mtp _request->peer->session().api().request(_request->id).cancel(); +#endif + _request->peer->session().sender().request(_request->id).cancel(); } } @@ -324,7 +331,10 @@ void ChooseJoinAsProcess::start( _request->changingJoinAsFrom = changingJoinAsFrom; return; } +#if 0 // mtp session->api().request(_request->id).cancel(); +#endif + session->sender().request(_request->id).cancel(); _request = nullptr; } @@ -360,6 +370,7 @@ void ChooseJoinAsProcess::start( void ChooseJoinAsProcess::requestList() { const auto session = &_request->peer->session(); +#if 0 // goodToRemove _request->id = session->api().request(MTPphone_GetGroupCallJoinAs( _request->peer->input )).done([=](const MTPphone_JoinAsPeers &result) { @@ -380,6 +391,27 @@ void ChooseJoinAsProcess::requestList() { return list; }); processList(std::move(list)); +#endif + _request->id = session->sender().request( + TLgetVideoChatAvailableParticipants( + peerToTdbChat(_request->peer->id) + )).done([=](const TLDmessageSenders &data) { + auto list = std::vector>(); + list.reserve(data.vtotal_count().v); + for (const auto &sender : data.vsenders().v) { + sender.match([&](const TLDmessageSenderUser &data) { + const auto userId = data.vuser_id(); + if (const auto user = session->data().userLoaded(userId)) { + list.push_back(user); + } + }, [&](const TLDmessageSenderChat &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = session->data().peerLoaded(peerId)) { + list.push_back(peer); + } + }); + } + processList(std::move(list)); }).fail([=] { finish({ .peer = _request->peer, diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 49dd3db91534a..ec6138362c021 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -32,6 +32,9 @@ For license and copyright information please follow this link: #include "webrtc/webrtc_media_devices.h" #include "webrtc/webrtc_create_adm.h" +#include "tdb/tdb_option.h" +#include "tdb/tdb_tl_scheme.h" + #include #include #include @@ -42,9 +45,13 @@ For license and copyright information please follow this link: namespace Calls { namespace { +using namespace Tdb; + constexpr auto kMaxInvitePerSlice = 10; constexpr auto kCheckLastSpokeInterval = crl::time(1000); +#if 0 // goodToRemove constexpr auto kCheckJoinedTimeout = 4 * crl::time(1000); +#endif constexpr auto kUpdateSendActionEach = crl::time(500); constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000); constexpr auto kFixManualLargeVideoDuration = 5 * crl::time(1000); @@ -70,6 +77,7 @@ constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums. : nullptr; } +#if 0 // goodToRemove [[nodiscard]] double TimestampFromMsgId(mtpMsgId msgId) { return msgId / double(1ULL << 32); } @@ -87,6 +95,7 @@ constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums. &Data::GroupCallParticipant::raisedHandRating); return (i == end(list)) ? 1 : (i->raisedHandRating + 1); } +#endif struct JoinVideoEndpoint { std::string id; @@ -283,11 +292,16 @@ bool VideoEndpoint::rtmp() const noexcept { struct VideoParams { std::string endpointId; std::vector ssrcGroups; +#if 0 // mtp uint32 additionalSsrc = 0; +#endif bool paused = false; [[nodiscard]] bool empty() const { +#if 0 // mtp return !additionalSsrc && (endpointId.empty() || ssrcGroups.empty()); +#endif + return endpointId.empty() || ssrcGroups.empty(); } [[nodiscard]] explicit operator bool() const { return !empty(); @@ -299,6 +313,83 @@ struct ParticipantVideoParams { VideoParams screen; }; +[[nodiscard]] bool VideoParamsAreEqual( + const VideoParams &was, + const tl::conditional &now) { + if (!now) { + return !was; + } + const auto &data = now->data(); + if (data.vis_paused().v != was.paused) { + return false; + } + if (gsl::make_span(data.vendpoint_id().v.toUtf8()) + != gsl::make_span(was.endpointId)) { + return false; + } + const auto &list = data.vsource_groups().v; + if (list.size() != was.ssrcGroups.size()) { + return false; + } + auto index = 0; + for (const auto &group : list) { + const auto equal = group.match([&]( + const TLDgroupCallVideoSourceGroup &data) { + const auto &group = was.ssrcGroups[index++]; + if (gsl::make_span(data.vsemantics().v.toUtf8()) + != gsl::make_span(group.semantics)) { + return false; + } + const auto &list = data.vsource_ids().v; + if (list.size() != group.ssrcs.size()) { + return false; + } + auto i = 0; + for (const auto &ssrc : list) { + if (ssrc.v != group.ssrcs[i++]) { + return false; + } + } + return true; + }); + if (!equal) { + return false; + } + } + return true; +} + +[[nodiscard]] VideoParams ParseVideoParams( + const tl::conditional ¶ms) { + if (!params) { + return VideoParams(); + } + auto result = VideoParams(); + const auto &data = params->data(); + result.paused = data.vis_paused().v; + result.endpointId = data.vendpoint_id().v.toStdString(); +#if 0 // mtp + result.additionalSsrc = data.vaudio_source().value_or_empty(); +#endif + const auto &list = data.vsource_groups().v; + result.ssrcGroups.reserve(list.size()); + for (const auto &group : list) { + const auto &data = group.data(); + const auto &list = data.vsource_ids().v; + auto ssrcs = std::vector(); + ssrcs.reserve(list.size()); + for (const auto &ssrc : list) { + ssrcs.push_back(ssrc.v); + } + result.ssrcGroups.push_back({ + .semantics = data.vsemantics().v.toStdString(), + .ssrcs = std::move(ssrcs), + }); + } + return result; +} + +#if 0 // goodToRemove [[nodiscard]] bool VideoParamsAreEqual( const VideoParams &was, const tl::conditional &now) { @@ -377,6 +468,7 @@ struct ParticipantVideoParams { }); return result; } +#endif const std::string &GetCameraEndpoint( const std::shared_ptr ¶ms) { @@ -396,14 +488,20 @@ bool IsScreenPaused(const std::shared_ptr ¶ms) { return params && params->screen.paused; } +#if 0 // mtp uint32 GetAdditionalAudioSsrc( const std::shared_ptr ¶ms) { return params ? params->screen.additionalSsrc : 0; } +#endif std::shared_ptr ParseVideoParams( +#if 0 // goodToRemove const tl::conditional &camera, const tl::conditional &screen, +#endif + const tl::conditional &camera, + const tl::conditional &screen, const std::shared_ptr &existing) { using namespace tgcalls; @@ -575,21 +673,32 @@ rpl::producer GroupCall::TrackSizeValue( GroupCall::GroupCall( not_null delegate, Group::JoinInfo info, +#if 0 // goodToRemove const MTPInputGroupCall &inputCall) +#endif + CallId id) : _delegate(delegate) , _peer(info.peer) , _history(_peer->owner().history(_peer)) +#if 0 // goodToRemove , _api(&_peer->session().mtp()) +#endif +, _tdbApi(&_peer->session().sender()) , _joinAs(info.joinAs) , _possibleJoinAs(std::move(info.possibleJoinAs)) , _joinHash(info.joinHash) , _rtmpUrl(info.rtmpInfo.url) , _rtmpKey(info.rtmpInfo.key) , _canManage(Data::CanManageGroupCallValue(_peer)) +#if 0 // goodToRemove , _id(inputCall.c_inputGroupCall().vid().v) +#endif +, _id(id) , _scheduleDate(info.scheduleDate) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) +#if 0 // goodToRemove , _checkJoinedTimer([=] { checkJoined(); }) +#endif , _pushToTalkCancelTimer([=] { pushToTalkCancel(); }) , _connectingSoundTimer([=] { playConnectingSoundOnce(); }) , _listenersHidden(info.rtmp) @@ -649,7 +758,10 @@ GroupCall::GroupCall( setupOutgoingVideo(); if (_id) { +#if 0 // goodToRemove join(inputCall); +#endif + join(id); } else { start(info.scheduleDate, info.rtmp); } @@ -765,7 +877,10 @@ void GroupCall::setScheduledDate(TimeId date) { const auto was = _scheduleDate; _scheduleDate = date; if (was && !date) { +#if 0 // goodToRemove join(inputCall()); +#endif + join(_id); } } @@ -803,7 +918,13 @@ void GroupCall::subscribeToReal(not_null real) { const auto peer = data.was ? data.was->peer : data.now->peer; if (peer == joinAs()) { + // Since TDLib has no video_joined info + // we set videoIsWorking always true + // just like the Android client does. +#if 0 // goodToRemove const auto working = data.now && data.now->videoJoined; +#endif + const auto working = true; if (videoIsWorking() != working) { fillActiveVideoEndpoints(); } @@ -972,9 +1093,11 @@ void GroupCall::setState(State state) { _delegate->groupCallFailed(this); break; case State::Connecting: +#if 0 // goodToRemove if (!_checkJoinedTimer.isActive()) { _checkJoinedTimer.callOnce(kCheckJoinedTimeout); } +#endif break; } } @@ -1056,6 +1179,7 @@ rpl::producer> GroupCall::real() const { } void GroupCall::start(TimeId scheduleDate, bool rtmp) { +#if 0 // goodToRemove using Flag = MTPphone_CreateGroupCall::Flag; _createRequestId = _api.request(MTPphone_CreateGroupCall( MTP_flags((scheduleDate ? Flag::f_schedule_date : Flag(0)) @@ -1074,13 +1198,41 @@ void GroupCall::start(TimeId scheduleDate, bool rtmp) { ).arg(error.type())); hangup(); }).send(); +#endif + _createRequestId = _tdbApi.request(TLcreateVideoChat( + peerToTdbChat(_peer->id), + tl_string(), // title + tl_int32(scheduleDate), + tl_bool(rtmp) + )).done([=](const TLDgroupCallId &data) { + if (!_instance && !_id) { + const auto id = data.vid().v; + setScheduledDate(scheduleDate); + if (const auto chat = _peer->asChat()) { + chat->setGroupCall(id, scheduleDate, rtmp); + } else if (const auto group = _peer->asChannel()) { + group->setGroupCall(id, scheduleDate, rtmp); + } else { + Unexpected("Peer type in GroupCall::join."); + } + join(id); + } + return; + }).fail([=](const Tdb::Error &error) { + LOG(("Call Error: Could not create, error: %1").arg(error.message)); + hangup(); + }).send(); } +#if 0 // goodToRemove void GroupCall::join(const MTPInputGroupCall &inputCall) { inputCall.match([&](const MTPDinputGroupCall &data) { _id = data.vid().v; _accessHash = data.vaccess_hash().v; }); +#endif +void GroupCall::join(CallId id) { + _id = id; setState(_scheduleDate ? State::Waiting : State::Joining); if (_scheduleDate) { @@ -1098,7 +1250,10 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { if (!update.now) { _instance->removeSsrcs({ update.was->ssrc, + update.was->screencastSsrc, +#if 0 // mtp GetAdditionalAudioSsrc(update.was->videoParams), +#endif }); } else if (!_rtmp) { updateInstanceVolume(update.was, *update.now); @@ -1304,10 +1459,16 @@ void GroupCall::setJoinAs(not_null as) { void GroupCall::saveDefaultJoinAs(not_null as) { setJoinAs(as); +#if 0 // goodToRemove _api.request(MTPphone_SaveDefaultGroupCallJoinAs( _peer->input, joinAs()->input )).send(); +#endif + _tdbApi.request(TLsetVideoChatDefaultParticipant( + peerToTdbChat(_peer->id), + peerToSender(joinAs()->id) + )).send(); } void GroupCall::rejoin(not_null as) { @@ -1331,10 +1492,16 @@ void GroupCall::rejoin(not_null as) { if (!tryCreateController()) { setInstanceMode(InstanceMode::None); } +#if 0 // goodToRemove applyMeInCallLocally(); +#endif LOG(("Call Info: Requesting join payload.")); setJoinAs(as); + unixtime([=](int64 time) { + _serverTimeMs = time; + _serverTimeMsGotAt = crl::now(); + }); const auto weak = base::make_weak(&_instanceGuard); _instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) { @@ -1351,6 +1518,7 @@ void GroupCall::rejoin(not_null as) { const auto json = QByteArray::fromStdString(payload.json); const auto wasMuteState = muted(); const auto wasVideoStopped = !isSharingCamera(); +#if 0 // goodToRemove using Flag = MTPphone_JoinGroupCall::Flag; const auto flags = (wasMuteState != MuteState::Active ? Flag::f_muted @@ -1372,6 +1540,20 @@ void GroupCall::rejoin(not_null as) { const MTP::Response &response) { _serverTimeMs = TimestampInMsFromMsgId(response.outerMsgId); _serverTimeMsGotAt = crl::now(); +#endif + _tdbApi.request(TLjoinGroupCall( + tl_int32(_id), + peerToSender(joinAs()->id), + tl_int32(ssrc), + tl_string(json), + tl_bool(wasMuteState != MuteState::Active), + tl_bool(!wasVideoStopped), + tl_string(_joinHash) + )).done([=](const TLDtext &data) { + unixtime([=](int64 time) { + _serverTimeMs = time; + _serverTimeMsGotAt = crl::now(); + }); _joinState.finish(ssrc); _mySsrcs.emplace(ssrc); @@ -1380,9 +1562,32 @@ void GroupCall::rejoin(not_null as) { == InstanceState::Disconnected) ? State::Connecting : State::Joined); +#if 0 // goodToRemove applyMeInCallLocally(); +#endif maybeSendMutedUpdate(wasMuteState); + +#if 0 // goodToRemove _peer->session().api().applyUpdates(updates); +#endif + if (_instance) { + const auto json = data.vtext().v; + const auto response = ParseJoinResponse(json.toUtf8()); + const auto endpoint = std::get_if( + &response); + if (v::is(response)) { + setInstanceMode(InstanceMode::Stream); + } else { + setInstanceMode(InstanceMode::Rtc); + setCameraEndpoint(endpoint + ? endpoint->id + : std::string()); + _instance->setJoinResponsePayload(json.toStdString()); + } + updateRequestedVideoChannels(); + checkMediaChannelDescriptions(); + } + applyQueuedSelfUpdates(); checkFirstTimeJoined(); _screenJoinState.nextActionPending = true; @@ -1401,10 +1606,16 @@ void GroupCall::rejoin(not_null as) { real->reloadIfStale(); } } + }).fail([=](const Tdb::Error &error) { +#if 0 // goodToRemove }).fail([=](const MTP::Error &error) { +#endif _joinState.finish(); +#if 0 // goodToRemove const auto type = error.type(); +#endif + const auto type = error.message; LOG(("Call Error: Could not join, error: %1").arg(type)); if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { @@ -1477,25 +1688,57 @@ void GroupCall::rejoinPresentation() { LOG(("Call Info: Join screen payload received, ssrc: %1." ).arg(ssrc)); + const auto json = QByteArray::fromStdString(payload.json); +#if 0 // goodToRemove _api.request( MTPphone_JoinGroupCallPresentation( inputCall(), MTP_dataJSON(MTP_bytes(json))) ).done([=](const MTPUpdates &updates) { +#endif + _tdbApi.request(TLstartGroupCallScreenSharing( + tl_int32(_id), + tl_int32(ssrc), + tl_string(json) + )).done([=](const TLDtext &data) { _screenJoinState.finish(ssrc); _mySsrcs.emplace(ssrc); +#if 0 // goodToRemove _peer->session().api().applyUpdates(updates); +#endif + if (_screenInstance) { + setScreenInstanceMode(InstanceMode::Rtc); + const auto json = data.vtext().v; + const auto response = ParseJoinResponse(json.toUtf8()); + const auto endpoint = std::get_if( + &response); + if (endpoint) { + setScreenEndpoint(endpoint->id); + } else { + LOG(("Call Error: " + "Bad response for 'presentation' flag.")); + } + _screenInstance->setJoinResponsePayload( + json.toStdString()); + } + checkNextJoinAction(); if (isScreenPaused()) { sendSelfUpdate(SendUpdateType::ScreenPaused); } sendPendingSelfUpdates(); + }).fail([=](const Tdb::Error &error) { +#if 0 // goodToRemove }).fail([=](const MTP::Error &error) { +#endif _screenJoinState.finish(); +#if 0 // goodToRemove const auto type = error.type(); +#endif + const auto type = error.message; if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { _screenJoinState.nextActionPending = true; checkNextJoinAction(); @@ -1530,18 +1773,32 @@ void GroupCall::leavePresentation() { _screenJoinState.nextActionPending = true; return; } +#if 0 // goodToRemove _api.request( MTPphone_LeaveGroupCallPresentation(inputCall()) ).done([=](const MTPUpdates &updates) { +#endif + _tdbApi.request(TLendGroupCallScreenSharing( + tl_int32(_id) + )).done([=] { _screenJoinState.finish(); +#if 0 // goodToRemove _peer->session().api().applyUpdates(updates); +#endif + setScreenEndpoint(std::string()); checkNextJoinAction(); +#if 0 // goodToRemove }).fail([=](const MTP::Error &error) { +#endif + }).fail([=](const Tdb::Error &error) { _screenJoinState.finish(); +#if 0 // goodToRemove const auto type = error.type(); +#endif + const auto type = error.message; LOG(("Call Error: " "Could not screen leave, error: %1").arg(type)); setScreenEndpoint(std::string()); @@ -1549,6 +1806,7 @@ void GroupCall::leavePresentation() { }).send(); } +#if 0 // goodToRemove void GroupCall::applyMeInCallLocally() { const auto real = lookupReal(); if (!real) { @@ -1645,6 +1903,7 @@ void GroupCall::applyParticipantLocally( MTPGroupCallParticipantVideo())), MTP_int(0)).c_updateGroupCallParticipants()); } +#endif void GroupCall::hangup() { finish(FinishType::Ended); @@ -1652,17 +1911,27 @@ void GroupCall::hangup() { void GroupCall::discard() { if (!_id) { +#if 0 // goodToRemove _api.request(_createRequestId).cancel(); +#endif + _tdbApi.request(_createRequestId).cancel(); hangup(); return; } +#if 0 // goodToRemove _api.request(MTPphone_DiscardGroupCall( inputCall() )).done([=](const MTPUpdates &result) { +#endif + _tdbApi.request(TLendGroupCall( + tl_int32(_id) + )).done([=] { // Here 'this' could be destroyed by updates, so we set Ended after // updates being handled, but in a guarded way. crl::on_main(this, [=] { hangup(); }); +#if 0 // goodToRemove _peer->session().api().applyUpdates(result); +#endif }).fail([=] { hangup(); }).send(); @@ -1723,14 +1992,21 @@ void GroupCall::leave() { // the call is already destroyed. const auto session = &_peer->session(); const auto weak = base::make_weak(this); +#if 0 // goodToRemove session->api().request(MTPphone_LeaveGroupCall( inputCall(), MTP_int(base::take(_joinState.ssrc)) )).done([=](const MTPUpdates &result) { +#endif + session->api().sender().request(TLleaveGroupCall( + tl_int32(_id) + )).done([=] { // Here 'this' could be destroyed by updates, so we set Ended after // updates being handled, but in a guarded way. crl::on_main(weak, [=] { setState(finalState); }); +#if 0 // goodToRemove session->api().applyUpdates(result); +#endif }).fail(crl::guard(weak, [=] { setState(finalState); })).send(); @@ -1740,23 +2016,34 @@ void GroupCall::startScheduledNow() { if (!lookupReal()) { return; } +#if 0 // goodToRemove _api.request(MTPphone_StartScheduledGroupCall( inputCall() )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); }).send(); +#endif + _tdbApi.request(TLstartScheduledGroupCall( + tl_int32(_id) + )).send(); } void GroupCall::toggleScheduleStartSubscribed(bool subscribed) { if (!lookupReal()) { return; } +#if 0 // goodToRemove _api.request(MTPphone_ToggleGroupCallStartSubscription( inputCall(), MTP_bool(subscribed) )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); }).send(); +#endif + _tdbApi.request(TLtoggleGroupCallEnabledStartNotification( + tl_int32(_id), + tl_bool(subscribed) + )).send(); } void GroupCall::setNoiseSuppression(bool enabled) { @@ -1786,9 +2073,11 @@ void GroupCall::setMuted(MuteState mute) { const auto nowMuted = (now == MuteState::Muted) || (now == MuteState::PushToTalk); const auto nowRaiseHand = (now == MuteState::RaisedHand); +#if 0 // goodToRemove if (wasMuted != nowMuted || wasRaiseHand != nowRaiseHand) { applyMeInCallLocally(); } +#endif if (mutedByAdmin()) { toggleVideo(false); toggleScreenSharing(std::nullopt); @@ -1821,6 +2110,7 @@ void GroupCall::setMutedAndUpdate(MuteState mute) { } } +#if 0 // goodToRemove void GroupCall::handlePossibleCreateOrJoinResponse( const MTPDupdateGroupCall &data) { data.vcall().match([&](const MTPDgroupCall &data) { @@ -1916,6 +2206,7 @@ void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { hangup(); } } +#endif void GroupCall::checkMediaChannelDescriptions( Fn resolved) { @@ -1936,6 +2227,44 @@ void GroupCall::checkMediaChannelDescriptions( } } +void GroupCall::handleUpdate(const TLDupdateGroupCall &update) { + const auto &call = update.vgroup_call().data(); + if ((call.vid().v == _id) && (call.vduration().v > 0)) { + LOG(("Call Info: Hangup after groupCallDiscarded.")); + _joinState.finish(); + hangup(); + } +} + +void GroupCall::handleUpdate( + const TLDupdateGroupCallParticipant &update) { + if (update.vgroup_call_id().v != _id) { + return; + } + const auto &data = update.vparticipant().data(); + if (data.vis_current_user().v) { + const auto joined = (_state.current() == State::Joined) + || (_state.current() == State::Connecting); + if (joined) { + applySelfUpdate(data); + } else { + _queuedSelfUpdates.push_back(update.vparticipant()); + } + } else { + const auto participantPeer = _peer->owner().peer( + peerFromSender(data.vparticipant_id())); + if (!LookupParticipant(_peer, _id, participantPeer)) { + return; + } + _otherParticipantStateValue.fire(Group::ParticipantState{ + .peer = participantPeer, + .volume = data.vvolume_level().v, + .mutedByMe = data.vis_muted_for_current_user().v, + }); + } +} + +#if 0 // goodToRemove void GroupCall::handleUpdate(const MTPUpdate &update) { update.match([&](const MTPDupdateGroupCall &data) { handleUpdate(data); @@ -1978,6 +2307,7 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) { }); } } +#endif void GroupCall::applyQueuedSelfUpdates() { const auto weak = base::make_weak(this); @@ -1987,12 +2317,62 @@ void GroupCall::applyQueuedSelfUpdates() { || _state.current() == State::Connecting)) { const auto update = _queuedSelfUpdates.front(); _queuedSelfUpdates.erase(_queuedSelfUpdates.begin()); + applySelfUpdate(update.data()); +#if 0 // goodToRemove update.match([&](const MTPDgroupCallParticipant &data) { applySelfUpdate(data); }); +#endif } } +void GroupCall::applySelfUpdate(const TLDgroupCallParticipant &data) { + const auto ssrc = uint32(data.vaudio_source_id().v); + if (data.vorder().v.isEmpty()) { + if (ssrc == _joinState.ssrc) { + // I was removed from the call, rejoin. + LOG(("Call Info: " + "Rejoin after got empty order with my ssrc.")); + setState(State::Joining); + rejoin(); + } + return; + } else if (ssrc != _joinState.ssrc) { + if (!_mySsrcs.contains(ssrc)) { + // I joined from another device, hangup. + LOG(("Call Info: " + "Hangup after '!left' with ssrc %1, my %2." + ).arg(ssrc + ).arg(_joinState.ssrc)); + _joinState.finish(); + hangup(); + } else { + LOG(("Call Info: " + "Some old 'self' with '!left' and ssrc %1, my %2." + ).arg(ssrc + ).arg(_joinState.ssrc)); + } + return; + } + if (data.vis_muted_for_all_users().v && !data.vcan_unmute_self().v) { + setMuted(data.vis_hand_raised().v + ? MuteState::RaisedHand + : MuteState::ForceMuted); + } else if (_instanceMode == InstanceMode::Stream) { + LOG(("Call Info: Rejoin after unforcemute in stream mode.")); + setState(State::Joining); + rejoin(); + } else if (mutedByAdmin()) { + setMuted(MuteState::Muted); + if (!_instanceTransitioning) { + notifyAboutAllowedToSpeak(); + } + } else if (data.vis_muted_for_all_users().v + && (muted() != MuteState::Muted)) { + setMuted(MuteState::Muted); + } +} +#if 0 // goodToRemove void GroupCall::applySelfUpdate(const MTPDgroupCallParticipant &data) { if (data.is_left()) { if (data.vsource().v == _joinState.ssrc) { @@ -2056,6 +2436,7 @@ void GroupCall::applyOtherParticipantUpdate( .mutedByMe = data.is_muted_by_you(), }); } +#endif void GroupCall::setupMediaDevices() { _mediaDevices->audioInputId( @@ -2110,8 +2491,11 @@ bool GroupCall::emitShareCameraError() { emitShareCameraError(error); return true; }; +#if 0 // goodToRemove if (const auto real = lookupReal() ; real && activeVideoSendersCount() >= real->unmutedVideoLimit()) { +#endif + if (const auto real = lookupReal(); real && !real->canEnableVideo()) { return emitError(Error::DisabledNoCamera); } else if (!videoIsWorking()) { return emitError(Error::DisabledNoCamera); @@ -2137,8 +2521,11 @@ bool GroupCall::emitShareScreenError() { emitShareScreenError(error); return true; }; +#if 0 // goodToRemove if (const auto real = lookupReal() ; real && activeVideoSendersCount() >= real->unmutedVideoLimit()) { +#endif + if (const auto real = lookupReal(); real && !real->canEnableVideo()) { return emitError(Error::DisabledNoScreen); } else if (!videoIsWorking()) { return emitError(Error::DisabledNoScreen); @@ -2209,7 +2596,9 @@ void GroupCall::setupOutgoingVideo() { _cameraEndpoint }, nowActive, nowPaused); sendSelfUpdate(SendUpdateType::CameraStopped); +#if 0 // goodToRemove applyMeInCallLocally(); +#endif }, _lifetime); _screenState.value( @@ -2287,6 +2676,7 @@ void GroupCall::changeTitle(const QString &title) { return; } +#if 0 // goodToRemove _api.request(MTPphone_EditGroupCallTitle( inputCall(), MTP_string(title) @@ -2294,6 +2684,13 @@ void GroupCall::changeTitle(const QString &title) { _peer->session().api().applyUpdates(result); _titleChanged.fire({}); }).send(); +#endif + _tdbApi.request(TLsetGroupCallTitle( + tl_int32(_id), + tl_string(title) + )).done([=] { + _titleChanged.fire({}); + }).send(); } void GroupCall::toggleRecording( @@ -2314,6 +2711,7 @@ void GroupCall::toggleRecording( if (!enabled) { _recordingStoppedByMe = true; } +#if 0 // goodToRemove using Flag = MTPphone_ToggleGroupCallRecord::Flag; _api.request(MTPphone_ToggleGroupCallRecord( MTP_flags((enabled ? Flag::f_start : Flag(0)) @@ -2328,6 +2726,24 @@ void GroupCall::toggleRecording( }).fail([=] { _recordingStoppedByMe = false; }).send(); +#endif + + const auto finish = [=] { + _recordingStoppedByMe = false; + }; + + if (enabled) { + _tdbApi.request(TLstartGroupCallRecording( + tl_int32(_id), + tl_string(title), + tl_bool(video), + tl_bool(videoPortrait) + )).done(finish).fail(finish).send(); + } else { + _tdbApi.request(TLendGroupCallRecording( + tl_int32(_id) + )).done(finish).fail(finish).send(); + } } bool GroupCall::tryCreateController() { @@ -2485,6 +2901,14 @@ bool GroupCall::tryCreateScreencast() { return true; } +RequestId GroupCall::unixtime(Fn &&callback) { + return _tdbApi.request(TLgetOption( + tl_string("unix_time") + )).done([c = std::move(callback)](const TLoptionValue &value) { + c(OptionValue(value) * 1000); + }).send(); +} + void GroupCall::broadcastPartStart(std::shared_ptr task) { const auto raw = task.get(); const auto time = raw->time(); @@ -2497,6 +2921,55 @@ void GroupCall::broadcastPartStart(std::shared_ptr task) { }; using Status = tgcalls::BroadcastPart::Status; using Quality = tgcalls::VideoChannelDescription::Quality; + const auto requestId = _tdbApi.request(TLgetGroupCallStreamSegment( + tl_int32(_id), + tl_int53(time), + tl_int32(scale), + tl_int32(videoChannel), + ((videoQuality == Quality::Full) + ? tl_groupCallVideoQualityFull() + : (videoQuality == Quality::Medium) + ? tl_groupCallVideoQualityMedium() + : tl_groupCallVideoQualityThumbnail()) + )).done([=](const TLDfilePart &part) { + auto data = part.vdata().v; + _broadcastParts[raw].requestId = unixtime([=](int64 time) { + const auto size = data.size(); + auto bytes = std::vector(size); + memcpy(bytes.data(), data.constData(), size); + finish({ + .timestampMilliseconds = time, + .responseTimestamp = float64(time), + .status = Status::Success, + .data = std::move(bytes), + }); + }); + }).fail([=](const Tdb::Error &error) { + if (error.message == u"GROUPCALL_JOIN_MISSING"_q + || error.message == u"GROUPCALL_FORBIDDEN"_q) { + for (const auto &[task, part] : _broadcastParts) { + _tdbApi.request(part.requestId).cancel(); + } + setState(State::Joining); + rejoin(); + return; + } + const auto status = (IsFloodError(error) + || error.message == u"TIME_TOO_BIG"_q) + ? Status::NotReady + : Status::ResyncNeeded; + _broadcastParts[raw].requestId = unixtime([=](int64 time) { + finish({ + .timestampMilliseconds = time, + .responseTimestamp = float64(time), + .status = status, + }); + }); + }).send(); + _broadcastParts.emplace( + raw, + LoadingPart{ std::move(task), requestId }); +#if 0 // goodToRemove using Flag = MTPDinputGroupCallStream::Flag; const auto requestId = _api.request(MTPupload_GetFile( MTP_flags(0), @@ -2559,12 +3032,16 @@ void GroupCall::broadcastPartStart(std::shared_ptr task) { MTP::groupCallStreamDcId(_broadcastDcId) ).send(); _broadcastParts.emplace(raw, LoadingPart{ std::move(task), requestId }); +#endif } void GroupCall::broadcastPartCancel(not_null task) { const auto i = _broadcastParts.find(task); if (i != end(_broadcastParts)) { +#if 0 // goodToRemove _api.request(i->second.requestId).cancel(); +#endif + _tdbApi.request(i->second.requestId).cancel(); _broadcastParts.erase(i); } } @@ -2641,6 +3118,32 @@ void GroupCall::requestCurrentTimeStart( task->done(value); } }; + _requestCurrentTimeRequestId = _tdbApi.request( + TLgetGroupCallStreams(tl_int32(_id)) + ).done([=](const TLgroupCallStreams &result) { + const auto &data = result.data(); + const auto &list = data.vstreams().v; + const auto empty = list.isEmpty(); + if (!empty) { + const auto &first = list.front().data(); + finish(first.vtime_offset().v); + } else { + finish(0); + } + _emptyRtmp = empty; + }).fail([=](const Tdb::Error &error) { + finish(0); + + if (error.message == u"GROUPCALL_JOIN_MISSING"_q + || error.message == u"GROUPCALL_FORBIDDEN"_q) { + for (const auto &[task, part] : _broadcastParts) { + _tdbApi.request(part.requestId).cancel(); + } + setState(State::Joining); + rejoin(); + } + }).send(); +#if 0 // mtp _requestCurrentTimeRequestId = _api.request( MTPphone_GetGroupCallStreamChannels(inputCall()) ).done([=](const MTPphone_GroupCallStreamChannels &result) { @@ -2671,6 +3174,7 @@ void GroupCall::requestCurrentTimeStart( }).handleAllErrors().toDC( MTP::groupCallStreamDcId(_broadcastDcId) ).send(); +#endif } void GroupCall::requestCurrentTimeCancel( @@ -2821,7 +3325,10 @@ void GroupCall::fillActiveVideoEndpoints() { } const auto me = real->participantByPeer(joinAs()); + if (true) { +#if 0 // goodToRemove if (me && me->videoJoined) { +#endif _videoIsWorking = true; } else { _videoIsWorking = false; @@ -2915,13 +3422,19 @@ void GroupCall::updateInstanceVolume( const auto volumeChanged = was ? (was->volume != now.volume || was->mutedByMe != now.mutedByMe) : nonDefault; +#if 0 // mtp const auto additionalSsrc = GetAdditionalAudioSsrc(now.videoParams); +#endif + const auto additionalSsrc = now.screencastSsrc; const auto set = now.ssrc && (volumeChanged || (was && was->ssrc != now.ssrc)); const auto additionalSet = additionalSsrc && (volumeChanged + || (was && was->screencastSsrc != additionalSsrc)); +#if 0 // mtp || (was && (GetAdditionalAudioSsrc(was->videoParams) != additionalSsrc))); +#endif const auto localVolume = now.mutedByMe ? 0. : (now.volume / float64(Group::kDefaultVolume)); @@ -2962,6 +3475,7 @@ void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) { if (level <= kSpeakLevelThreshold) { continue; } +#if 0 // mtp if (me && voice && (!_lastSendProgressUpdate @@ -2971,6 +3485,7 @@ void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) { _history, Api::SendProgressType::Speaking); } +#endif check = true; const auto i = _lastSpoke.find(ssrc); @@ -3039,6 +3554,7 @@ void GroupCall::checkLastSpoke() { } } +#if 0 // goodToRemove void GroupCall::checkJoined() { if (state() != State::Connecting || !_id || !_joinState.ssrc) { return; @@ -3075,6 +3591,7 @@ void GroupCall::checkJoined() { rejoin(); }).send(); } +#endif void GroupCall::setInstanceConnected( tgcalls::GroupNetworkState networkState) { @@ -3214,11 +3731,62 @@ void GroupCall::sendPendingSelfUpdates() { } void GroupCall::sendSelfUpdate(SendUpdateType type) { +#if 0 // goodToRemove if ((state() != State::Connecting && state() != State::Joined) || _selfUpdateRequestId) { _pendingSelfUpdates |= type; return; } +#endif + const auto failed = [=](const Tdb::Error &error) { + if (error.message == u"GROUPCALL_FORBIDDEN"_q) { + LOG(("Call Info: Rejoin after error '%1' in editGroupCallMember." + ).arg(error.message)); + rejoin(); + } + }; + const auto tlId = tl_int32(_id); + const auto tlSelf = peerToSender(joinAs()->id); + switch (type) { + case SendUpdateType::RaiseHand: { + _tdbApi.request( + TLtoggleGroupCallParticipantIsHandRaised( + tlId, + tlSelf, + tl_bool(muted() == MuteState::RaisedHand)) + ).fail(failed).send(); + } break; + case SendUpdateType::CameraStopped: { + _tdbApi.request( + TLtoggleGroupCallIsMyVideoEnabled( + tlId, + tl_bool(isSharingCamera())) + ).fail(failed).send(); + } break; + case SendUpdateType::CameraPaused: { + _tdbApi.request( + TLtoggleGroupCallIsMyVideoPaused( + tlId, + tl_bool(isCameraPaused())) + ).fail(failed).send(); + } break; + case SendUpdateType::ScreenPaused: { + _tdbApi.request( + TLtoggleGroupCallScreenSharingIsPaused( + tlId, + tl_bool(isScreenPaused())) + ).fail(failed).send(); + } break; + case SendUpdateType::Mute: { + _tdbApi.request( + TLtoggleGroupCallParticipantIsMuted( + tlId, + tlSelf, + tl_bool(muted() != MuteState::Active)) + ).fail(failed).send(); + } break; + }; +#if 0 // goodToRemove using Flag = MTPphone_EditGroupCallParticipant::Flag; _selfUpdateRequestId = _api.request(MTPphone_EditGroupCallParticipant( MTP_flags((type == SendUpdateType::RaiseHand) @@ -3250,6 +3818,7 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { rejoin(); } }).send(); +#endif } void GroupCall::pinVideoEndpoint(VideoEndpoint endpoint) { @@ -3303,9 +3872,22 @@ void GroupCall::toggleMute(const Group::MuteRequest &data) { _rtmpVolume = data.mute ? 0 : Group::kDefaultVolume; updateInstanceVolumes(); } else if (data.locallyOnly) { +#if 0 // goodToRemove applyParticipantLocally(data.peer, data.mute, std::nullopt); +#endif + _peer->groupCall()->applyLocalVolume( + data.peer, + data.mute, + std::nullopt); } else { +#if 0 // goodToRemove editParticipant(data.peer, data.mute, std::nullopt); +#endif + _tdbApi.request(TLtoggleGroupCallParticipantIsMuted( + tl_int32(_id), + peerToSender(data.peer->id), + tl_bool(data.mute) + )).send(); } } @@ -3314,12 +3896,23 @@ void GroupCall::changeVolume(const Group::VolumeRequest &data) { _rtmpVolume = data.volume; updateInstanceVolumes(); } else if (data.locallyOnly) { +#if 0 // goodToRemove applyParticipantLocally(data.peer, false, data.volume); +#endif + _peer->groupCall()->applyLocalVolume(data.peer, false, data.volume); } else { +#if 0 // goodToRemove editParticipant(data.peer, false, data.volume); +#endif + _tdbApi.request(TLsetGroupCallParticipantVolumeLevel( + tl_int32(_id), + peerToSender(data.peer->id), + tl_int32(data.volume) + )).send(); } } +#if 0 // goodToRemove void GroupCall::editParticipant( not_null participantPeer, bool mute, @@ -3353,6 +3946,7 @@ void GroupCall::editParticipant( } }).send(); } +#endif std::variant> GroupCall::inviteUsers( const std::vector> &users) { @@ -3363,17 +3957,26 @@ std::variant> GroupCall::inviteUsers( const auto owner = &_peer->owner(); auto count = 0; + auto slice = QVector(); +#if 0 // goodToRemove auto slice = QVector(); +#endif auto result = std::variant>(0); slice.reserve(kMaxInvitePerSlice); const auto sendSlice = [&] { count += slice.size(); +#if 0 // goodToRemove _api.request(MTPphone_InviteToGroupCall( inputCall(), MTP_vector(slice) )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); }).send(); +#endif + _tdbApi.request(TLinviteGroupCallParticipants( + tl_int32(_id), + tl_vector(slice) + )).send(); slice.clear(); }; for (const auto &user : users) { @@ -3381,7 +3984,10 @@ std::variant> GroupCall::inviteUsers( result = user; } owner->registerInvitedToCallUser(_id, _peer, user); +#if 0 // goodToRemove slice.push_back(user->inputUser); +#endif + slice.push_back(peerToTdbChat(user->id)); if (slice.size() == kMaxInvitePerSlice) { sendSlice(); } @@ -3392,6 +3998,7 @@ std::variant> GroupCall::inviteUsers( if (!slice.empty()) { sendSlice(); } + return result; } @@ -3467,6 +4074,7 @@ auto GroupCall::otherParticipantStateValue() const return _otherParticipantStateValue.events(); } +#if 0 // goodToRemove MTPInputGroupCall GroupCall::inputCall() const { Expects(_id != 0); @@ -3474,6 +4082,7 @@ MTPInputGroupCall GroupCall::inputCall() const { MTP_long(_id), MTP_long(_accessHash)); } +#endif void GroupCall::destroyController() { if (_instance) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 8b8eb34ca5846..d21120eab5062 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -12,6 +12,15 @@ For license and copyright information please follow this link: #include "base/bytes.h" #include "mtproto/sender.h" #include "mtproto/mtproto_auth_key.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLgroupCallParticipant; +class TLDgroupCallParticipant; +class TLgroupCallParticipantVideoInfo; +class TLDupdateGroupCall; +class TLDupdateGroupCallParticipant; +} // namespace Tdb class History; @@ -159,8 +168,12 @@ struct VideoQualityRequest { struct ParticipantVideoParams; [[nodiscard]] std::shared_ptr ParseVideoParams( +#if 0 // goodToRemove const tl::conditional &camera, const tl::conditional &screen, +#endif + const tl::conditional &camera, + const tl::conditional &screen, const std::shared_ptr &existing); [[nodiscard]] const std::string &GetCameraEndpoint( @@ -171,8 +184,10 @@ struct ParticipantVideoParams; const std::shared_ptr ¶ms); [[nodiscard]] bool IsScreenPaused( const std::shared_ptr ¶ms); +#if 0 // mtp [[nodiscard]] uint32 GetAdditionalAudioSsrc( const std::shared_ptr ¶ms); +#endif class GroupCall final : public base::has_weak_ptr { public: @@ -212,7 +227,10 @@ class GroupCall final : public base::has_weak_ptr { GroupCall( not_null delegate, Group::JoinInfo info, +#if 0 // goodToRemove const MTPInputGroupCall &inputCall); +#endif + CallId id); ~GroupCall(); [[nodiscard]] CallId id() const { @@ -250,11 +268,17 @@ class GroupCall final : public base::has_weak_ptr { void discard(); void rejoinAs(Group::JoinInfo info); void rejoinWithHash(const QString &hash); + void join(CallId id); +#if 0 // goodToRemove void join(const MTPInputGroupCall &inputCall); void handleUpdate(const MTPUpdate &update); void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data); void handlePossibleCreateOrJoinResponse( const MTPDupdateGroupCallConnection &data); +#endif + void handleUpdate(const Tdb::TLDupdateGroupCall &update); + void handleUpdate(const Tdb::TLDupdateGroupCallParticipant &update); + void changeTitle(const QString &title); void toggleRecording( bool enabled, @@ -426,7 +450,10 @@ class GroupCall final : public base::has_weak_ptr { struct LoadingPart { std::shared_ptr task; +#if 0 // mtp mtpRequestId requestId = 0; +#endif + Tdb::RequestId requestId = 0; }; enum class FinishType { @@ -483,10 +510,12 @@ class GroupCall final : public base::has_weak_ptr { Fn resolved = nullptr); void checkMediaChannelDescriptions(Fn resolved = nullptr); +#if 0 // goodToRemove void handlePossibleCreateOrJoinResponse(const MTPDgroupCall &data); void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); +#endif bool tryCreateController(); void destroyController(); bool tryCreateScreencast(); @@ -504,7 +533,9 @@ class GroupCall final : public base::has_weak_ptr { void updateInstanceVolume( const std::optional &was, const Data::GroupCallParticipant &now); +#if 0 // goodToRemove void applyMeInCallLocally(); +#endif void rejoin(); void leave(); void rejoin(not_null as); @@ -525,7 +556,9 @@ class GroupCall final : public base::has_weak_ptr { void pushToTalkCancel(); void checkGlobalShortcutAvailability(); +#if 0 // goodToRemove void checkJoined(); +#endif void checkFirstTimeJoined(); void notifyAboutAllowedToSpeak(); @@ -537,6 +570,7 @@ class GroupCall final : public base::has_weak_ptr { void updateRequestedVideoChannelsDelayed(); void fillActiveVideoEndpoints(); +#if 0 // goodToRemove void editParticipant( not_null participantPeer, bool mute, @@ -545,10 +579,14 @@ class GroupCall final : public base::has_weak_ptr { not_null participantPeer, bool mute, std::optional volume); +#endif void applyQueuedSelfUpdates(); void sendPendingSelfUpdates(); +#if 0 // goodToRemove void applySelfUpdate(const MTPDgroupCallParticipant &data); void applyOtherParticipantUpdate(const MTPDgroupCallParticipant &data); +#endif + void applySelfUpdate(const Tdb::TLDgroupCallParticipant &data); void setupMediaDevices(); void setupOutgoingVideo(); @@ -557,6 +595,8 @@ class GroupCall final : public base::has_weak_ptr { void addVideoOutput(const std::string &endpoint, SinkPointer sink); void setVideoEndpointLarge(VideoEndpoint endpoint); + Tdb::RequestId unixtime(Fn &&callback); + void markEndpointActive( VideoEndpoint endpoint, bool active, @@ -566,13 +606,18 @@ class GroupCall final : public base::has_weak_ptr { [[nodiscard]] int activeVideoSendersCount() const; +#if 0 // goodToRemove [[nodiscard]] MTPInputGroupCall inputCall() const; +#endif const not_null _delegate; not_null _peer; // Can change in legacy group migration. rpl::event_stream _peerStream; not_null _history; // Can change in legacy group migration. +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _tdbApi; rpl::event_stream> _realChanges; rpl::variable _state = State::Creating; base::flat_set _unresolvedSsrcs; @@ -580,7 +625,9 @@ class GroupCall final : public base::has_weak_ptr { bool _recordingStoppedByMe = false; bool _requestedVideoChannelsUpdateScheduled = false; +#if 0 // goodToRemove MTP::DcId _broadcastDcId = 0; +#endif base::flat_map, LoadingPart> _broadcastParts; base::flat_set< std::shared_ptr, @@ -606,13 +653,20 @@ class GroupCall final : public base::has_weak_ptr { rpl::variable _videoIsWorking = false; rpl::variable _emptyRtmp = false; bool _initialMuteStateSent = false; +#if 0 // goodToRemove bool _acceptFields = false; +#endif rpl::event_stream _otherParticipantStateValue; +#if 0 // goodToRemove std::vector _queuedSelfUpdates; +#endif + std::vector _queuedSelfUpdates; CallId _id = 0; +#if 0 // goodToRemove CallId _accessHash = 0; +#endif JoinState _joinState; JoinState _screenJoinState; std::string _cameraEndpoint; @@ -663,7 +717,9 @@ class GroupCall final : public base::has_weak_ptr { rpl::event_stream<> _allowedToSpeakNotifications; rpl::event_stream<> _titleChanged; base::Timer _lastSpokeCheckTimer; +#if 0 // goodToRemove base::Timer _checkJoinedTimer; +#endif crl::time _lastSendProgressUpdate = 0; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 73ac7d1b967d1..01be55c8bc60d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -147,9 +147,11 @@ class Members::Controller final void checkRowPosition(not_null row); [[nodiscard]] bool needToReorder(not_null row) const; [[nodiscard]] bool allRowsAboveAreSpeaking(not_null row) const; +#if 0 // goodToRemove [[nodiscard]] bool allRowsAboveMoreImportantThanHand( not_null row, uint64 raiseHandRating) const; +#endif [[nodiscard]] const Data::GroupCallParticipant *findParticipant( const std::string &endpoint) const; [[nodiscard]] const std::string &computeScreenEndpoint( @@ -515,7 +517,10 @@ void Members::Controller::updateRow( } updateRow(row, was, &now); if ((now.speaking && (!was || !was->speaking)) +#if 0 // goodToRemove || (now.raisedHandRating != (was ? was->raisedHandRating : 0)) +#endif + || (now.isHandRaised != (was ? was->isHandRaised : false)) || (!now.canSelfUnmute && was && was->canSelfUnmute)) { checkPosition = row; } @@ -524,7 +529,10 @@ void Members::Controller::updateRow( delegate()->peerListPrependRow(std::move(row)); } else { reorderIfInvitedBefore = delegate()->peerListFullRowsCount(); +#if 0 // goodToRemove if (now.raisedHandRating != 0) { +#endif + if (now.isHandRaised) { checkPosition = row.get(); } else { addedToBottom = row.get(); @@ -551,6 +559,7 @@ void Members::Controller::updateRow( if (checkPosition) { checkRowPosition(checkPosition); } else if (addedToBottom) { +#if 0 // goodToRemove const auto real = _call->lookupReal(); if (real && real->joinedToTop()) { const auto proj = [&](const PeerListRow &other) { @@ -567,6 +576,7 @@ void Members::Controller::updateRow( return proj(a) > proj(b); }); } +#endif } } @@ -584,6 +594,7 @@ bool Members::Controller::allRowsAboveAreSpeaking(not_null row) const { return false; } +#if 0 // goodToRemove bool Members::Controller::allRowsAboveMoreImportantThanHand( not_null row, uint64 raiseHandRating) const { @@ -606,6 +617,7 @@ bool Members::Controller::allRowsAboveMoreImportantThanHand( } return false; } +#endif bool Members::Controller::needToReorder(not_null row) const { // All reorder cases: @@ -620,6 +632,7 @@ bool Members::Controller::needToReorder(not_null row) const { return false; } +#if 0 // goodToRemove const auto rating = row->raisedHandRating(); if (!rating && row->state() != Row::State::Muted) { return false; @@ -627,6 +640,7 @@ bool Members::Controller::needToReorder(not_null row) const { if (rating > 0 && !allRowsAboveMoreImportantThanHand(row, rating)) { return true; } +#endif const auto index = row->absoluteIndex(); if (index + 1 == delegate()->peerListFullRowsCount()) { // Last one, can't bring lower. @@ -637,10 +651,15 @@ bool Members::Controller::needToReorder(not_null row) const { if ((state != Row::State::Muted) && (state != Row::State::RaisedHand)) { return true; } +#if 0 // goodToRemove if (!rating && static_cast(next.get())->raisedHandRating()) { return true; } +#endif + return true; +#if 0 // goodToRemove - prefer to reorder. return false; +#endif } void Members::Controller::checkRowPosition(not_null row) { @@ -655,6 +674,7 @@ void Members::Controller::checkRowPosition(not_null row) { // Someone started speaking and has a non-speaking row above him. // Or someone raised hand and has force muted above him. // Or someone was forced muted and had can_unmute_self below him. Sort. +#if 0 // goodToRemove static constexpr auto kTop = std::numeric_limits::max(); const auto projForAdmin = [&](const PeerListRow &other) { const auto &real = static_cast(other); @@ -684,9 +704,18 @@ void Members::Controller::checkRowPosition(not_null row) { return proj(a) > proj(b); }; }; +#endif + const auto comparator = [&](const PeerListRow &a, const PeerListRow &b) { + const auto &realA = static_cast(a); + const auto &realB = static_cast(b); + return realA.order() > realB.order(); + }; + delegate()->peerListSortRows(comparator); +#if 0 // goodToRemove delegate()->peerListSortRows(_peer->canManageGroupCall() ? makeComparator(projForAdmin) : makeComparator(projForOther)); +#endif } void Members::Controller::updateRow( @@ -696,7 +725,10 @@ void Members::Controller::updateRow( const auto wasSounding = row->sounding(); const auto wasSsrc = was ? was->ssrc : 0; const auto wasAdditionalSsrc = was + ? was->screencastSsrc +#if 0 // mtp ? GetAdditionalAudioSsrc(was->videoParams) +#endif : 0; row->setSkipLevelUpdate(_skipRowLevelUpdate); row->updateState(participant); @@ -727,7 +759,10 @@ void Members::Controller::updateRowInSoundingMap( const auto nowSounding = row->sounding(); const auto nowSsrc = participant ? participant->ssrc : 0; const auto nowAdditionalSsrc = participant + ? participant->screencastSsrc +#if 0// mtp ? GetAdditionalAudioSsrc(participant->videoParams) +#endif : 0; updateRowInSoundingMap(row, wasSounding, wasSsrc, nowSounding, nowSsrc); updateRowInSoundingMap( @@ -1331,7 +1366,10 @@ base::unique_qptr Members::Controller::createRowContextMenu( } else if (participant && (!isMe(participantPeer) || _peer->canManageGroupCall()) && (participant->ssrc != 0 + || participant->screencastSsrc != 0)) { +#if 0 // mtp || GetAdditionalAudioSsrc(participant->videoParams) != 0)) { +#endif addMuteActionsToContextMenu( result, participantPeer, @@ -1519,7 +1557,10 @@ void Members::Controller::addMuteActionsToContextMenu( ) | rpl::start_with_next([=](bool mutedFromVolume) { const auto state = _call->canManage() ? (mutedFromVolume +#if 0 // goodToRemove ? (row->raisedHandRating() +#endif + ? (row->isHandRaised() ? Row::State::RaisedHand : Row::State::Muted) : Row::State::Inactive) diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp index 96a31fcb98937..c9b8414d707a8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp @@ -148,33 +148,56 @@ void MembersRow::updateState( setSounding(false); setSpeaking(false); _mutedByMe = false; +#if 0 // goodToRemove _raisedHandRating = 0; +#endif + _isHandRaised = false; } else if (!participant->muted || (participant->sounding && participant->ssrc != 0) || (participant->additionalSounding + && participant->screencastSsrc != 0)) { +#if 0 // mtp && GetAdditionalAudioSsrc(participant->videoParams) != 0)) { +#endif setState(State::Active); setSounding((participant->sounding && participant->ssrc != 0) || (participant->additionalSounding + && participant->screencastSsrc != 0)); +#if 0 // mtp && GetAdditionalAudioSsrc(participant->videoParams) != 0)); +#endif setSpeaking((participant->speaking && participant->ssrc != 0) || (participant->additionalSpeaking + && participant->screencastSsrc != 0)); +#if 0 // mtp && GetAdditionalAudioSsrc(participant->videoParams) != 0)); +#endif _mutedByMe = participant->mutedByMe; +#if 0 // goodToRemove _raisedHandRating = 0; +#endif + _isHandRaised = false; } else if (participant->canSelfUnmute) { setState(State::Inactive); setSounding(false); setSpeaking(false); _mutedByMe = participant->mutedByMe; +#if 0 // goodToRemove _raisedHandRating = 0; +#endif + _isHandRaised = false; } else { setSounding(false); setSpeaking(false); _mutedByMe = participant->mutedByMe; +#if 0 // goodToRemove _raisedHandRating = participant->raisedHandRating; setState(_raisedHandRating ? State::RaisedHand : State::Muted); +#endif + _isHandRaised = participant->isHandRaised; + setState(_isHandRaised ? State::RaisedHand : State::Muted); } + _order = participant ? participant->rowOrder() : QString(); refreshStatus(); } @@ -310,9 +333,11 @@ void MembersRow::updateLevel(float level) { const auto spoke = (level >= GroupCall::kSpeakLevelThreshold) ? crl::now() : crl::time(); +#if 0 // goodToRemove if (spoke && _speaking) { _speakingLastTime = spoke; } +#endif if (_skipLevelUpdate) { return; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.h b/Telegram/SourceFiles/calls/group/calls_group_members_row.h index 8dd02f1def4aa..62949def10553 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.h @@ -95,15 +95,25 @@ class MembersRow final : public PeerListRow { [[nodiscard]] bool mutedByMe() const { return _mutedByMe; } +#if 0 // goodToRemove [[nodiscard]] crl::time speakingLastTime() const { return _speakingLastTime; } +#endif [[nodiscard]] int volume() const { return _volume; } +#if 0 // goodToRemove [[nodiscard]] uint64 raisedHandRating() const { return _raisedHandRating; } +#endif + [[nodiscard]] bool isHandRaised() const { + return _isHandRaised; + } + [[nodiscard]] QString order() const { + return _order; + } void refreshName(const style::PeerListItem &st) override; @@ -209,8 +219,10 @@ class MembersRow final : public PeerListRow { Ui::Animations::Simple _mutedAnimation; // For gray/red icon. Ui::Animations::Simple _activeAnimation; // For icon cross animation. QString _aboutText; +#if 0 // goodToRemove crl::time _speakingLastTime = 0; uint64 _raisedHandRating = 0; +#endif int _volume = Group::kDefaultVolume; bool _sounding : 1 = false; bool _speaking : 1 = false; @@ -218,6 +230,9 @@ class MembersRow final : public PeerListRow { bool _skipLevelUpdate : 1 = false; bool _mutedByMe : 1 = false; + bool _isHandRaised = false; + QString _order; + }; } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp index 51a9068a7396e..1381c8069856e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp @@ -505,7 +505,10 @@ void FillMenu( const auto addEditTitle = call->canManage(); const auto addEditRecording = call->canManage() && !real->scheduleDate(); const auto addScreenCast = !wide + && real->canEnableVideo() +#if 0 // goodToRemove && call->videoIsWorking() +#endif && !real->scheduleDate(); if (addEditJoinAs) { menu->addAction(MakeJoinAsAction( diff --git a/Telegram/SourceFiles/calls/group/calls_group_rtmp.cpp b/Telegram/SourceFiles/calls/group/calls_group_rtmp.cpp index fded076144a48..c757e2201befd 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_rtmp.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_rtmp.cpp @@ -27,6 +27,8 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_tl_scheme.h" + #include #include @@ -111,7 +113,10 @@ void StartRtmpProcess::start( _request->done = std::move(done); return; } +#if 0 // mtp session->api().request(_request->id).cancel(); +#endif + session->sender().request(_request->id).cancel(); _request = nullptr; } _request = std::make_unique( @@ -130,7 +135,10 @@ void StartRtmpProcess::start( void StartRtmpProcess::close() { if (_request) { +#if 0 // mtp _request->peer->session().api().request(_request->id).cancel(); +#endif + _request->peer->session().sender().request(_request->id).cancel(); if (const auto strong = _request->box.data()) { strong->closeBox(); } @@ -140,6 +148,25 @@ void StartRtmpProcess::close() { void StartRtmpProcess::requestUrl(bool revoke) { const auto session = &_request->peer->session(); + const auto done = [=](const Tdb::TLDrtmpUrl &data) { + processUrl(RtmpInfo{ + .url = data.vurl().v, + .key = data.vstream_key().v, + }); + }; + const auto fail = [=] { + Ui::Toast::Show( + _request->show->toastParent(), + Lang::Hard::ServerError()); + }; + _request->id = (!revoke) + ? session->sender().request( + Tdb::TLgetVideoChatRtmpUrl(peerToTdbChat(_request->peer->id)) + ).done(done).fail(fail).send() + : session->sender().request( + Tdb::TLreplaceVideoChatRtmpUrl(peerToTdbChat(_request->peer->id)) + ).done(done).fail(fail).send(); +#if 0 // goodToRemove _request->id = session->api().request(MTPphone_GetGroupCallStreamRtmpUrl( _request->peer->input, MTP_bool(revoke) @@ -152,6 +179,7 @@ void StartRtmpProcess::requestUrl(bool revoke) { }).fail([=] { _request->show->showToast(Lang::Hard::ServerError()); }).send(); +#endif } void StartRtmpProcess::processUrl(RtmpInfo data) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 8e0df2f3ea571..9b54f6629023d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -50,11 +50,15 @@ For license and copyright information please follow this link: #include "styles/style_calls.h" #include "styles/style_settings.h" +#include "tdb/tdb_tl_scheme.h" + #include namespace Calls::Group { namespace { +using namespace Tdb; + constexpr auto kDelaysCount = 201; constexpr auto kMicrophoneTooltipAfterLoudCount = 3; constexpr auto kDropLoudAfterQuietCount = 5; @@ -78,11 +82,18 @@ void SaveCallJoinMuted( return; } call->setJoinMutedLocally(joinMuted); + peer->session().sender().request( + TLtoggleGroupCallMuteNewParticipants( + tl_int32(callId), + tl_bool(joinMuted) + )).send(); +#if 0 // goodToRemove peer->session().api().request(MTPphone_ToggleGroupCallSettings( MTP_flags(MTPphone_ToggleGroupCallSettings::Flag::f_join_muted), call->input(), MTP_bool(joinMuted) )).send(); +#endif } [[nodiscard]] crl::time DelayByIndex(int index) { @@ -666,6 +677,15 @@ void SettingsBox( const auto state = top->lifetime().make_state(); const auto revokeSure = [=] { const auto session = &peer->session(); + + state->requestId = session->sender().request( + Tdb::TLreplaceVideoChatRtmpUrl(peerToTdbChat(peer->id) + )).done([=](const Tdb::TLrtmpUrl &result) { + auto data = RtmpInfo{ + .url = result.data().vurl().v, + .key = result.data().vstream_key().v, + }; +#if 0 // goodToRemove state->requestId = session->api().request( MTPphone_GetGroupCallStreamRtmpUrl( peer->input, @@ -678,6 +698,7 @@ void SettingsBox( .key = qs(data.vkey()), }; }); +#endif if (const auto call = weakCall.get()) { call->setRtmpInfo(data); } @@ -794,8 +815,11 @@ std::pair, rpl::lifetime> ShareInviteLinkAction( State(not_null session) : session(session) { } ~State() { +#if 0 // mtp session->api().request(linkListenerRequestId).cancel(); session->api().request(linkSpeakerRequestId).cancel(); +#endif + session->sender().request(linkListenerRequestId).cancel(); } not_null session; @@ -829,6 +853,17 @@ std::pair, rpl::lifetime> ShareInviteLinkAction( } state->generatingLink = true; + state->linkListenerRequestId = peer->session().sender().request( + TLgetGroupCallInviteLink( + tl_int32(real->id()), + tl_bool(false) + ) + ).done([=](const TLDhttpUrl &data) { + state->linkListenerRequestId = 0; + state->linkListener = data.vurl().v; + shareReady(); + }).send(); +#if 0 // goodToRemove state->linkListenerRequestId = peer->session().api().request( MTPphone_ExportGroupCallInvite( MTP_flags(0), @@ -842,12 +877,14 @@ std::pair, rpl::lifetime> ShareInviteLinkAction( shareReady(); }); }).send(); +#endif if (real->rtmp()) { state->linkSpeaker = QString(); state->linkSpeakerRequestId = 0; shareReady(); } else if (!state->linkSpeaker.has_value()) { +#if 0 // goodToRemove using Flag = MTPphone_ExportGroupCallInvite::Flag; state->linkSpeakerRequestId = peer->session().api().request( MTPphone_ExportGroupCallInvite( @@ -865,6 +902,21 @@ std::pair, rpl::lifetime> ShareInviteLinkAction( state->linkSpeaker = QString(); shareReady(); }).send(); +#endif + state->linkListenerRequestId = peer->session().sender().request( + TLgetGroupCallInviteLink( + tl_int32(real->id()), + tl_bool(true) + ) + ).done([=](const TLDhttpUrl &data) { + state->linkSpeakerRequestId = 0; + state->linkSpeaker = data.vurl().v; + shareReady(); + }).fail([=] { + state->linkSpeakerRequestId = 0; + state->linkSpeaker = QString(); + shareReady(); + }).send(); } }; return { std::move(callback), std::move(lifetime) }; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp b/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp index 9f7fe7bb2803c..7522f8797e695 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp @@ -22,6 +22,9 @@ For license and copyright information please follow this link: #include "base/random.h" #include "apiwrap.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + #include #include #include @@ -30,6 +33,8 @@ For license and copyright information please follow this link: namespace ChatHelpers { namespace { +using namespace Tdb; + constexpr auto kMinDelay = crl::time(200); constexpr auto kAccumulateDelay = crl::time(1000); constexpr auto kAccumulateSeenRequests = kAccumulateDelay; @@ -52,20 +57,25 @@ EmojiInteractions::EmojiInteractions(not_null session) , _checkTimer([=] { check(); }) { _session->changes().messageUpdates( Data::MessageUpdate::Flag::Destroyed +#if 0 // mtp | Data::MessageUpdate::Flag::Edited +#endif ) | rpl::start_with_next([=](const Data::MessageUpdate &update) { if (update.flags & Data::MessageUpdate::Flag::Destroyed) { _outgoing.remove(update.item); _incoming.remove(update.item); +#if 0 // mtp } else if (update.flags & Data::MessageUpdate::Flag::Edited) { checkEdition(update.item, _outgoing); checkEdition(update.item, _incoming); +#endif } }, _lifetime); } EmojiInteractions::~EmojiInteractions() = default; +#if 0 // mtp void EmojiInteractions::checkEdition( not_null item, base::flat_map, std::vector> &map) { @@ -76,6 +86,7 @@ void EmojiInteractions::checkEdition( map.erase(i); } } +#endif void EmojiInteractions::startOutgoing( not_null view) { @@ -83,6 +94,43 @@ void EmojiInteractions::startOutgoing( if (!item->isRegular() || !item->history()->peer->isUser()) { return; } + const auto emoticon = item->originalText().text; + const auto emoji = Ui::Emoji::Find(emoticon); + if (!emoji) { + return; + } + const auto itemId = item->fullId(); + const auto id = _session->sender().request(TLclickAnimatedEmojiMessage( + peerToTdbChat(item->history()->peer->id), + tl_int53(item->id.bare) + )).done([=](const TLsticker &result, RequestId requestId) { + _outgoingRequests.remove(requestId); + + const auto item = _session->data().message(itemId); + if (!item) { + return; + } + const auto document = _session->data().processDocument(result); + const auto media = document->createMediaView(); + media->checkStickerLarge(); + const auto now = crl::now(); + auto &animations = _outgoing[item]; + if (item->originalText().text != emoticon) { + // The message was edited, forget the old emoji. + animations.clear(); + } + animations.push_back({ + .emoticon = emoticon, + .emoji = emoji, + .document = document, + .media = media, + .scheduledAt = now, + }); + check(now); + }).fail([=](const Error &error, RequestId requestId) { + _outgoingRequests.remove(requestId); + }).send(); +#if 0 // mtp const auto &pack = _session->emojiStickersPack(); const auto emoticon = item->originalText().text; const auto emoji = pack.chooseInteractionEmoji(emoticon); @@ -120,8 +168,10 @@ void EmojiInteractions::startOutgoing( .index = index, }); check(now); +#endif } +#if 0 // mtp void EmojiInteractions::startIncoming( not_null peer, MsgId messageId, @@ -177,10 +227,49 @@ void EmojiInteractions::startIncoming( check(now); } } +#endif +void EmojiInteractions::startIncoming( + not_null peer, + MsgId messageId, + not_null document) { + if (!peer->isUser() || !document->sticker()) { + return; + } + const auto item = _session->data().message(peer->id, messageId); + if (!item || !item->isRegular()) { + return; + } + const auto emoticon = document->sticker()->alt; + const auto emoji = Ui::Emoji::Find(emoticon); + auto &animations = _incoming[item]; + if (!animations.empty() && animations.front().emoji != emoji) { + // The message was edited, forget the old emoji. + animations.clear(); + } + const auto now = crl::now(); + if (animations.empty() || animations.back().scheduledAt < now) { + const auto media = document->createMediaView(); + media->checkStickerLarge(); + animations.push_back({ + .emoticon = emoticon, + .emoji = emoji, + .document = document, + .media = media, + .scheduledAt = now, + .incoming = true, + }); + } + if (animations.empty()) { + _incoming.remove(item); + } else { + check(now); + } +} void EmojiInteractions::seenOutgoing( not_null peer, const QString &emoticon) { +#if 0 // mtp const auto &pack = _session->emojiStickersPack(); if (const auto i = _playsSent.find(peer); i != end(_playsSent)) { if (const auto emoji = pack.chooseInteractionEmoji(emoticon)) { @@ -192,6 +281,8 @@ void EmojiInteractions::seenOutgoing( } } } +#endif + _seen.fire({ peer, emoticon }); } auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult { @@ -266,6 +357,7 @@ void EmojiInteractions::sendAccumulatedOutgoing( const auto till = ranges::find_if(animations, [&](const auto &animation) { return !animation.startedAt || (animation.startedAt >= intervalEnd); }); +#if 0 // mtp auto bunch = EmojiInteractionsBunch(); bunch.interactions.reserve(till - from); for (const auto &animation : ranges::make_subrange(from, till)) { @@ -297,6 +389,7 @@ void EmojiInteractions::sendAccumulatedOutgoing( } }).send(); _playsSent[peer][emoji] = PlaySent{ .lastRequestId = requestId }; +#endif animations.erase(from, till); } @@ -349,7 +442,9 @@ void EmojiInteractions::check(crl::time now) { now = crl::now(); } checkSeenRequests(now); +#if 0 // mtp checkSentRequests(now); +#endif const auto result1 = checkAnimations(now); const auto result2 = checkAccumulated(now); const auto result = Combine(result1, result2); @@ -358,8 +453,10 @@ void EmojiInteractions::check(crl::time now) { _checkTimer.callOnce(result.nextCheckAt - now); } else if (!_playStarted.empty()) { _checkTimer.callOnce(kAccumulateSeenRequests); +#if 0 // mtp } else if (!_playsSent.empty()) { _checkTimer.callOnce(kAcceptSeenSinceRequest); +#endif } setWaitingForDownload(result.waitingForDownload); } @@ -382,6 +479,7 @@ void EmojiInteractions::checkSeenRequests(crl::time now) { } } +#if 0 // mtp void EmojiInteractions::checkSentRequests(crl::time now) { for (auto i = begin(_playsSent); i != end(_playsSent);) { auto &animations = i->second; @@ -400,6 +498,7 @@ void EmojiInteractions::checkSentRequests(crl::time now) { } } } +#endif void EmojiInteractions::setWaitingForDownload(bool waiting) { if (_waitingForDownload == waiting) { @@ -424,12 +523,19 @@ void EmojiInteractions::playStarted(not_null peer, QString emoji) { if (i != end(map) && now - i->second < kAccumulateSeenRequests) { return; } + _session->sender().request(TLsendChatAction( + peerToTdbChat(peer->id), + tl_int53(0), // message_thread_id + tl_chatActionWatchingAnimations(tl_string(emoji)) + )).send(); +#if 0 // mtp _session->api().request(MTPmessages_SetTyping( MTP_flags(0), peer->input, MTPint(), // top_msg_id MTP_sendMessageEmojiInteractionSeen(MTP_string(emoji)) )).send(); +#endif map[emoji] = now; if (!_checkTimer.isActive()) { _checkTimer.callOnce(kAccumulateSeenRequests); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_interactions.h b/Telegram/SourceFiles/chat_helpers/emoji_interactions.h index d4c52474d7f02..a23759dbf837b 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_interactions.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_interactions.h @@ -56,11 +56,17 @@ class EmojiInteractions final { using PlayRequest = EmojiInteractionPlayRequest; void startOutgoing(not_null view); +#if 0 // mtp void startIncoming( not_null peer, MsgId messageId, const QString &emoticon, EmojiInteractionsBunch &&bunch); +#endif + void startIncoming( + not_null peer, + MsgId messageId, + not_null document); void seenOutgoing(not_null peer, const QString &emoticon); [[nodiscard]] rpl::producer seen() const { @@ -87,10 +93,12 @@ class EmojiInteractions final { bool incoming = false; int index = 0; }; +#if 0 // mtp struct PlaySent { mtpRequestId lastRequestId = 0; crl::time lastDoneReceivedAt = 0; }; +#endif struct CheckResult { crl::time nextCheckAt = 0; bool waitingForDownload = false; @@ -113,10 +121,12 @@ class EmojiInteractions final { void setWaitingForDownload(bool waiting); void checkSeenRequests(crl::time now); +#if 0 // mtp void checkSentRequests(crl::time now); void checkEdition( not_null item, base::flat_map, std::vector> &map); +#endif const not_null _session; @@ -127,9 +137,11 @@ class EmojiInteractions final { base::flat_map< not_null, base::flat_map> _playStarted; +#if 0 // mtp base::flat_map< not_null, base::flat_map, PlaySent>> _playsSent; +#endif rpl::event_stream _seen; bool _waitingForDownload = false; @@ -137,6 +149,8 @@ class EmojiInteractions final { rpl::lifetime _lifetime; + base::flat_set _outgoingRequests; + }; } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp b/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp index 500de20b6ccc9..7a4d9d402f436 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp @@ -22,9 +22,15 @@ For license and copyright information please follow this link: #include +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace ChatHelpers { namespace { +using namespace Tdb; +constexpr auto kMaxQueryLength = 64; + constexpr auto kRefreshEach = 60 * 60 * crl::time(1000); // 1 hour. constexpr auto kKeepNotUsedLangPacksCount = 4; constexpr auto kKeepNotUsedInputLanguagesCount = 4; @@ -33,6 +39,7 @@ using namespace Ui::Emoji; using Result = EmojiKeywords::Result; +#if 0 // mtp struct LangPackEmoji { EmojiPtr emoji = nullptr; QString text; @@ -164,6 +171,7 @@ void WriteLocalCache(const QString &id, const LangPackData &data) { } } } +#endif [[nodiscard]] QString NormalizeQuery(const QString &query) { return query.toLower(); @@ -173,6 +181,7 @@ void WriteLocalCache(const QString &id, const LangPackData &data) { return key.toLower().trimmed(); } +#if 0 // mtp void AppendFoundEmoji( std::vector &result, const QString &label, @@ -196,6 +205,7 @@ void AppendFoundEmoji( }); result.insert(end(result), add.begin(), add.end()); } +#endif void AppendLegacySuggestions( std::vector &result, @@ -241,6 +251,7 @@ void AppendLegacySuggestions( result.insert(end(result), add.begin(), add.end()); } +#if 0 // mtp void ApplyDifference( LangPackData &data, const QVector &keywords, @@ -302,9 +313,11 @@ void ApplyDifference( data.maxKeyLength = *ranges::max_element(lengths); } } +#endif } // namespace +#if 0 // mtp class EmojiKeywords::LangPack final { public: using Delegate = details::EmojiKeywordsLangPackDelegate; @@ -498,6 +511,7 @@ std::vector EmojiKeywords::LangPack::query( int EmojiKeywords::LangPack::maxQueryLength() const { return _data.maxKeyLength; } +#endif EmojiKeywords::EmojiKeywords() { crl::on_main(&_guard, [=] { @@ -530,6 +544,7 @@ void EmojiKeywords::handleSessionChanges() { void EmojiKeywords::apiChanged(ApiWrap *api) { _api = api; +#if 0 // mtp if (_api) { crl::on_main(&_api->session(), crl::guard(&_guard, [=] { Lang::CurrentCloudManager().firstLanguageSuggestion( @@ -547,9 +562,11 @@ void EmojiKeywords::apiChanged(ApiWrap *api) { for (const auto &[language, item] : _data) { item->apiChanged(); } +#endif } void EmojiKeywords::refresh() { +#if 0 // mtp auto list = languages(); if (_localList != list) { _localList = std::move(list); @@ -557,6 +574,7 @@ void EmojiKeywords::refresh() { } else { refreshFromRemoteList(); } +#endif } std::vector EmojiKeywords::languages() { @@ -601,6 +619,7 @@ void EmojiKeywords::refreshInputLanguages() { } } +#if 0 // mtp rpl::producer<> EmojiKeywords::refreshed() const { return _refreshed.events(); } @@ -646,6 +665,72 @@ std::vector EmojiKeywords::queryMine( bool exact) const { return ApplyVariants(PrioritizeRecent(this->query(query, exact))); } +#endif + +mtpRequestId EmojiKeywords::requestMine( + const QString &query, + Fn)> callback, + mtpRequestId previousId, + bool exact) { + const auto normalized = NormalizeQuery(query); + if (normalized.isEmpty()) { + return {}; + } + if (previousId) { + const auto i = _requests.find(previousId); + if (i != end(_requests)) { + if (i->second.normalizedQuery == normalized) { + i->second.callback = std::move(callback); + return previousId; + } + if (_api) { + _api->sender().request(previousId).cancel(); + } + _requests.erase(i); + } + } + if (!_api || normalized.isEmpty()) { + crl::on_main([=] { callback({}); }); + return 0; + } + const auto list = languages(); + auto langs = list | ranges::views::transform([](const QString &id) { + return tl_string(id); + }) | ranges::to(); + const auto requestId = _api->sender().preallocateId(); + _requests.emplace(requestId, Request{ + .query = query, + .normalizedQuery = normalized, + .callback = std::move(callback), + }); + _api->sender().request(TLsearchEmojis( + tl_string(normalized), + tl_bool(exact), + tl_vector(std::move(langs)) + ), requestId).done([=](const TLemojis &result) { + const auto request = _requests.take(requestId); + if (!request) { + return; + } + const auto &list = result.data().vemojis().v; + auto cloud = std::vector(); + cloud.reserve(list.size()); + for (const auto &emoji : list) { + if (const auto found = Find(emoji.v)) { + cloud.push_back(Result{ found }); + } + } + if (!exact) { + AppendLegacySuggestions(cloud, request->query); + } + request->callback(ApplyVariants(PrioritizeRecent(std::move(cloud)))); + }).fail([=] { + if (const auto request = _requests.take(requestId)) { + request->callback({}); + } + }).send(); + return requestId; +} std::vector EmojiKeywords::PrioritizeRecent( std::vector list) { @@ -680,6 +765,7 @@ std::vector EmojiKeywords::ApplyVariants(std::vector list) { } int EmojiKeywords::maxQueryLength() const { +#if 0 // mtp if (_data.empty()) { return 0; } @@ -687,8 +773,11 @@ int EmojiKeywords::maxQueryLength() const { return pair.second->maxQueryLength(); }); return *ranges::max_element(lengths); +#endif + return kMaxQueryLength; } +#if 0 // mtp void EmojiKeywords::refreshRemoteList() { if (!_api) { _localList.clear(); @@ -755,5 +844,6 @@ void EmojiKeywords::refreshFromRemoteList() { } } } +#endif } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/emoji_keywords.h b/Telegram/SourceFiles/chat_helpers/emoji_keywords.h index 3df6c497db503..7bb4118851b22 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_keywords.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_keywords.h @@ -33,19 +33,28 @@ class EmojiKeywords final : private details::EmojiKeywordsLangPackDelegate { void refresh(); +#if 0 // mtp [[nodiscard]] rpl::producer<> refreshed() const; +#endif struct Result { EmojiPtr emoji = nullptr; QString label; QString replacement; }; +#if 0 // mtp [[nodiscard]] std::vector query( const QString &query, bool exact = false) const; [[nodiscard]] std::vector queryMine( const QString &query, bool exact = false) const; +#endif + mtpRequestId requestMine( + const QString &query, + Fn)> callback, + mtpRequestId previousId = 0, + bool exact = false); [[nodiscard]] int maxQueryLength() const; private: @@ -64,16 +73,26 @@ class EmojiKeywords final : private details::EmojiKeywordsLangPackDelegate { void apiChanged(ApiWrap *api); void refreshInputLanguages(); [[nodiscard]] std::vector languages(); +#if 0 // mtp void refreshRemoteList(); void setRemoteList(std::vector &&list); void refreshFromRemoteList(); +#endif ApiWrap *_api = nullptr; std::vector _localList; +#if 0 // mtp std::vector _remoteList; mtpRequestId _langsRequestId = 0; base::flat_map> _data; std::deque> _notUsedData; +#endif + struct Request { + QString query; + QString normalizedQuery; + Fn)> callback; + }; + base::flat_map _requests; std::deque _inputLanguages; rpl::event_stream<> _refreshed; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 2a51048b56c53..e964a13261f6e 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -551,8 +551,10 @@ void EmojiListWidget::applyNextSearchQuery() { return; } _searchQuery = _nextSearchQuery; +#if 0 // mtp std::swap(_searchEmoji, _searchEmojiPrevious); _searchEmoji.clear(); +#endif const auto finish = [&](bool searching = true) { if (!_searchMode && !searching) { return; @@ -561,6 +563,7 @@ void EmojiListWidget::applyNextSearchQuery() { clearSelection(); if (modeChanged) { _searchMode = searching; + invalidate_weak_ptrs(&_searchGuard); } if (!searching) { _searchResults.clear(); @@ -574,11 +577,20 @@ void EmojiListWidget::applyNextSearchQuery() { updateSelected(); }; if (_searchQuery.empty()) { + std::swap(_searchEmoji, _searchEmojiPrevious); + _searchEmoji.clear(); finish(false); return; } + const auto callback = crl::guard(&_searchGuard, [=]( + const std::vector &plain) { + + std::swap(_searchEmoji, _searchEmojiPrevious); const auto guard = gsl::finally([&] { finish(); }); +#if 0 // mtp auto plain = collectPlainSearchResults(); +#endif + _searchEmoji = { begin(plain), end(plain) }; if (_searchEmoji == _searchEmojiPrevious) { return; } @@ -594,11 +606,19 @@ void EmojiListWidget::applyNextSearchQuery() { }); } } + + }); + _searchRequestId = SearchEmoji( + _searchRequestId, + _searchQuery, + callback); } +#if 0 // mtp std::vector EmojiListWidget::collectPlainSearchResults() { return SearchEmoji(_searchQuery, _searchEmoji); } +#endif void EmojiListWidget::appendPremiumSearchResults() { const auto test = session().isTestMode(); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 5735d7a55d381..c166b3a3e5a0f 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -261,7 +261,9 @@ class EmojiListWidget final void unloadCustomIn(const SectionInfo &info); void setupSearch(); +#if 0 // mtp [[nodiscard]] std::vector collectPlainSearchResults(); +#endif void appendPremiumSearchResults(); void ensureLoaded(int section); void updateSelected(); @@ -404,6 +406,8 @@ class EmojiListWidget final base::flat_set _searchEmojiPrevious; base::flat_set _searchCustomIds; std::vector _searchResults; + mtpRequestId _searchRequestId = 0; + base::has_weak_ptr _searchGuard; bool _searchMode = false; int _rowsTop = 0; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index f6c54ec6d01bf..aba82bb02a2a6 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -71,12 +71,16 @@ class SuggestionsWidget final : public Ui::RpWidget { Ui::Text::CustomEmoji *custom = nullptr; DocumentData *document = nullptr; not_null emoji; +#if 0 // mtp QString replacement; +#endif }; struct Custom { not_null document; not_null emoji; +#if 0 // mtp QString replacement; +#endif }; bool eventHook(QEvent *e) override; @@ -126,6 +130,8 @@ class SuggestionsWidget final : public Ui::RpWidget { const style::EmojiSuggestions &_st; const not_null _session; SuggestionsQuery _query; + mtpRequestId _queryRequestId = 0; + base::has_weak_ptr _queryGuard; std::vector _rows; bool _suggestCustomEmoji = false; Fn)> _allowCustomWithoutPremium; @@ -192,6 +198,7 @@ void SuggestionsWidget::showWithQuery(SuggestionsQuery query, bool force) { return; } _query = query; +#if 0 // mtp auto rows = [&] { if (const auto emoji = std::get_if(&query)) { return appendCustom( @@ -200,6 +207,11 @@ void SuggestionsWidget::showWithQuery(SuggestionsQuery query, bool force) { } return appendCustom(getRowsByQuery(v::get(query))); }(); +#endif + const auto showRows = [=](std::vector rows) { + invalidate_weak_ptrs(&_queryGuard); + _queryRequestId = 0; + if (rows.empty()) { _toggleAnimated.fire(false); } @@ -214,6 +226,46 @@ void SuggestionsWidget::showWithQuery(SuggestionsQuery query, bool force) { _toggleAnimated.fire(true); } }); + + }; + if (const auto emoji = std::get_if(&query)) { + showRows(appendCustom( + {}, + lookupCustom({ Row(*emoji, (*emoji)->text()) }))); + return; + } + const auto text = v::get(query).trimmed(); + if (text.isEmpty()) { + showRows({}); + return; + } + const auto middle = (text[0] == ':'); + const auto real = middle ? text.mid(1) : text; + const auto simple = [&] { + if (!middle || text.size() > 2) { + return false; + } + // Suggest :D and :-P only as exact matches. + return ranges::none_of(text, [](QChar ch) { return ch.isLower(); }); + }(); + const auto exact = !middle || simple; + using Entry = ChatHelpers::EmojiKeywords::Result; + const auto callback = crl::guard(&_queryGuard, [=]( + const std::vector &list) { + showRows(appendCustom(ranges::views::all( + list + ) | ranges::views::transform([](const Entry &result) { + return Row(result.emoji, result.replacement); + }) | ranges::to_vector)); + if (middle) { + selectFirstResult(); + } + }); + _queryRequestId = Core::App().emojiKeywords().requestMine( + real, + callback, + _queryRequestId, + exact); } void SuggestionsWidget::selectFirstResult() { @@ -263,7 +315,9 @@ auto SuggestionsWidget::lookupCustom(const std::vector &rows) const custom.emplace(int(j - begin(rows)), Custom{ .document = document, .emoji = emoji, +#if 0 // mtp .replacement = j->replacement, +#endif }); } } @@ -279,7 +333,10 @@ auto SuggestionsWidget::appendCustom( -> std::vector { rows.reserve(rows.size() + custom.size()); for (const auto &[position, one] : custom) { +#if 0 // mtp rows.push_back(Row(one.emoji, one.replacement)); +#endif + rows.push_back(Row(one.emoji, QString())); rows.back().document = one.document; rows.back().custom = resolveCustomEmoji(one.document); } @@ -313,10 +370,14 @@ void SuggestionsWidget::customEmojiRepaint() { SuggestionsWidget::Row::Row( not_null emoji, const QString &replacement) +#if 0 // mtp : emoji(emoji) , replacement(replacement) { +#endif +: emoji(emoji) { } +#if 0 // mtp auto SuggestionsWidget::getRowsByQuery(const QString &text) const -> std::vector { if (text.isEmpty()) { @@ -340,6 +401,7 @@ auto SuggestionsWidget::getRowsByQuery(const QString &text) const return Row(result.emoji, result.replacement); }) | ranges::to_vector; } +#endif void SuggestionsWidget::resizeToRows() { const auto count = int(_rows.size()); @@ -768,6 +830,7 @@ SuggestionsController::SuggestionsController( ) | rpl::start_with_next([=](const SuggestionsWidget::Chosen &chosen) { replaceCurrent(chosen.emoji, chosen.customData); }, _lifetime); +#if 0 // mtp Core::App().emojiKeywords().refreshed( ) | rpl::start_with_next([=] { _keywordsRefreshed = true; @@ -775,6 +838,7 @@ SuggestionsController::SuggestionsController( showWithQuery(_lastShownQuery); } }, _lifetime); +#endif updateForceHidden(); @@ -836,10 +900,12 @@ void SuggestionsController::setReplaceCallback( } void SuggestionsController::handleTextChange() { +#if 0 // mtp if (Core::App().settings().suggestEmoji() && _field->textCursor().position() > 0) { Core::App().emojiKeywords().refresh(); } +#endif _ignoreCursorPositionChange = true; InvokeQueued(_container, [=] { _ignoreCursorPositionChange = false; }); diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index 13cfda86db664..17715c96acd32 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -42,11 +42,16 @@ For license and copyright information please follow this link: #include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_option.h" +#include "tdb/tdb_tl_scheme.h" + #include namespace ChatHelpers { namespace { +using namespace Tdb; + constexpr auto kSearchRequestDelay = 400; constexpr auto kSearchBotUsername = "gif"_cs; constexpr auto kMinRepaintDelay = crl::time(33); @@ -106,7 +111,10 @@ GifsListWidget::GifsListWidget( descriptor.show, descriptor.paused) , _show(std::move(descriptor.show)) +#if 0 // mtp , _api(&session().mtp()) +#endif +, _api(&session().sender()) , _section(Section::Gifs) , _updateInlineItems([=] { updateInlineItems(); }) , _mosaic(st::emojiPanWidth - st::inlineResultsLeft) @@ -286,18 +294,29 @@ void GifsListWidget::cancelGifsSearch() { refreshInlineRows(nullptr, true); } +#if 0 // mtp void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) { +#endif +void GifsListWidget::inlineResultsDone( + const Tdb::TLinlineQueryResults &result) { _search->setLoading(false); _inlineRequestId = 0; auto it = _inlineCache.find(_inlineQuery); auto adding = (it != _inlineCache.cend()); +#if 0 // goodToRemove if (result.type() == mtpc_messages_botResults) { auto &d = result.c_messages_botResults(); session().data().processUsers(d.vusers()); +#endif + if (!result.data().vresults().v.empty()) { + const auto &d = result.data(); auto &v = d.vresults().v; +#if 0 // goodToRemove auto queryId = d.vquery_id().v; +#endif + const auto queryId = d.vinline_query_id().v; if (it == _inlineCache.cend()) { it = _inlineCache.emplace( @@ -305,7 +324,10 @@ void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) { std::make_unique()).first; } const auto entry = it->second.get(); +#if 0 // goodToRemove entry->nextOffset = qs(d.vnext_offset().value_or_empty()); +#endif + entry->nextOffset = d.vnext_offset().v; if (const auto count = v.size()) { entry->results.reserve(entry->results.size() + count); } @@ -855,7 +877,20 @@ void GifsListWidget::searchForGifs(const QString &query) { } if (!_searchBot && !_searchBotRequestId) { - auto username = kSearchBotUsername.utf16(); + _searchBotRequestId = _api.request(TLgetOption( + tl_string("animation_search_bot_username") + )).done([=](const TLoptionValue &result) { + _searchBotRequestId = _api.request(TLsearchPublicChat( + tl_string(OptionValue(result)) + )).done([=](const TLchat &result) { + const auto peer = session().data().processPeer(result); + if (const auto user = peer ? peer->asUser() : nullptr) { + _searchBot = user; + } + }).send(); + }).send(); + +#if 0 // goodToRemove _searchBotRequestId = _api.request(MTPcontacts_ResolveUsername( MTP_string(username) )).done([=](const MTPcontacts_ResolvedPeer &result) { @@ -870,6 +905,7 @@ void GifsListWidget::searchForGifs(const QString &query) { _searchBot = user; } }).send(); +#endif } } @@ -906,6 +942,7 @@ void GifsListWidget::sendInlineRequest() { } _search->setLoading(true); +#if 0 // goodToRemove _inlineRequestId = _api.request(MTPmessages_GetInlineBotResults( MTP_flags(0), _searchBot->inputUser, @@ -920,6 +957,23 @@ void GifsListWidget::sendInlineRequest() { _search->setLoading(false); _inlineRequestId = 0; }).handleAllErrors().send(); +#endif + _inlineRequestId = _api.request(TLgetInlineQueryResults( + peerToTdbChat(_searchBot->id), + peerToTdbChat(_inlineQueryPeer->id), + std::nullopt, // Location. + tl_string(_inlineQuery), + tl_string(nextOffset) + )).done([this](const TLinlineQueryResults &result) { + inlineResultsDone(result); + }).fail([this] { + // show error? + _search->setLoading(false); + _inlineRequestId = 0; + }).send(); +#if 0 // doLater handleAllErrors. + }).handleAllErrors().send(); +#endif } void GifsListWidget::refreshRecent() { diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h index 84e20c86465fb..564ce1f105dc6 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h @@ -14,6 +14,12 @@ For license and copyright information please follow this link: #include +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLinlineQueryResults; +} // namespace Tdb + namespace style { struct ComposeIcons; } // namespace style @@ -149,7 +155,10 @@ class GifsListWidget final int32 showInlineRows(bool newResults); bool refreshInlineRows(int32 *added = 0); +#if 0 // goodToRemove void inlineResultsDone(const MTPmessages_BotResults &result); +#endif + void inlineResultsDone(const Tdb::TLinlineQueryResults &result); void updateSelected(); void paintInlineItems(Painter &p, QRect clip); @@ -177,7 +186,10 @@ class GifsListWidget final const std::shared_ptr _show; std::unique_ptr _search; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; Section _section = Section::Gifs; crl::time _lastScrolledAt = 0; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 475e87cb388ac..58fed46f9b626 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -87,7 +87,10 @@ QString FieldTagMimeProcessor::operator()(QStringView mimeTag) { const auto tag = *i; if (TextUtilities::IsMentionLink(tag) && TextUtilities::MentionNameDataToFields(tag).selfId != id) { +#if 0 // mtp i = all.erase(i); +#endif + ++i; continue; } else if (Ui::InputField::IsCustomEmojiLink(tag)) { const auto data = Ui::InputField::CustomEmojiEntityData(tag); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index dcebbfdc42e62..96ac0181371dc 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -16,11 +16,16 @@ For license and copyright information please follow this link: #include "base/unixtime.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" +#include "history/view/media/history_view_slot_machine.h" + #include #include namespace Stickers { +using namespace Tdb; + const QString DicePacks::kDiceString = QString::fromUtf8("\xF0\x9F\x8E\xB2"); const QString DicePacks::kDartString = QString::fromUtf8("\xF0\x9F\x8E\xAF"); const QString DicePacks::kSlotString = QString::fromUtf8("\xF0\x9F\x8E\xB0"); @@ -43,7 +48,32 @@ DocumentData *DicePack::lookup(int value) { return (i != end(_map)) ? i->second.get() : nullptr; } +void DicePack::apply(const Tdb::TLDmessageDice &data) { + const auto emplace = [&](int index, const TLsticker &sticker) { + const auto document = _session->data().processDocument(sticker); + if (document->sticker()) { + _map.emplace(index, document); + } + }; + if (const auto &state = data.vinitial_state()) { + state->match([&](const TLDdiceStickersRegular &data) { + emplace(0, data.vsticker()); + }, [&](const TLDdiceStickersSlotMachine &data) { + HistoryView::EnumerateSlotMachineParts(0, data, emplace); + }); + } + if (const auto &state = data.vfinal_state()) { + const auto value = data.vvalue().v; + state->match([&](const TLDdiceStickersRegular &data) { + emplace(value, data.vsticker()); + }, [&](const TLDdiceStickersSlotMachine &data) { + HistoryView::EnumerateSlotMachineParts(value, data, emplace); + }); + } +} + void DicePack::load() { +#if 0 // mtp if (_requestId) { return; } @@ -59,8 +89,10 @@ void DicePack::load() { }).fail([=] { _requestId = 0; }).send(); +#endif } +#if 0 // mtp void DicePack::applySet(const MTPDmessages_stickerSet &data) { const auto isSlotMachine = DicePacks::IsSlot(_emoji); auto index = 0; @@ -97,6 +129,7 @@ void DicePack::applySet(const MTPDmessages_stickerSet &data) { }); } } +#endif void DicePack::tryGenerateLocalZero() { if (!_map.empty()) { @@ -134,9 +167,12 @@ void DicePack::generateLocal(int index, const QString &name) { task.process({ .generateGoodThumbnail = false }); const auto result = task.peekResult(); Assert(result != nullptr); +#if 0 // mtp const auto document = _session->data().processDocument( result->document, Images::FromImageInMemory(result->thumb, "WEBP", result->thumbbytes)); +#endif + const auto document = _session->data().processDocument(result->document); document->setLocation(Core::FileLocation(path)); _map.emplace(index, document); @@ -162,4 +198,30 @@ DocumentData *DicePacks::lookup(const QString &emoji, int value) { ).first->second->lookup(value); } +void DicePacks::apply(const TLDupdateDiceEmojis &update) { + const auto &list = update.vemojis().v; + _cloudDiceEmoticons = list + | ranges::views::transform(&TLstring::v) + | ranges::to_vector; +} + +void DicePacks::apply(const TLDmessageDice &data) { + const auto emoji = data.vemoji().v; + const auto key = emoji.endsWith(QChar(0xFE0F)) + ? emoji.mid(0, emoji.size() - 1) + : emoji; + const auto i = _packs.find(key); + if (i != end(_packs)) { + return i->second->apply(data); + } + return _packs.emplace( + key, + std::make_unique(_session, key) + ).first->second->apply(data); +} + +const std::vector &DicePacks::cloudDiceEmoticons() const { + return _cloudDiceEmoticons; +} + } // namespace Stickers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h index 53564363a9425..7f2220a8ee541 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.h @@ -7,6 +7,11 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLDupdateDiceEmojis; +class TLDmessageDice; +} // namespace Tdb + class DocumentData; namespace Main { @@ -22,9 +27,13 @@ class DicePack final { [[nodiscard]] DocumentData *lookup(int value); + void apply(const Tdb::TLDmessageDice &data); + private: void load(); +#if 0 // mtp void applySet(const MTPDmessages_stickerSet &data); +#endif void tryGenerateLocalZero(); void generateLocal(int index, const QString &name); @@ -51,11 +60,18 @@ class DicePacks final { [[nodiscard]] DocumentData *lookup(const QString &emoji, int value); + void apply(const Tdb::TLDupdateDiceEmojis &update); + [[nodiscard]] const std::vector &cloudDiceEmoticons() const; + + void apply(const Tdb::TLDmessageDice &data); + private: const not_null _session; base::flat_map> _packs; + std::vector _cloudDiceEmoticons; + }; } // namespace Stickers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp index 35af9b1d59d45..c81836db9d19c 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp @@ -31,9 +31,14 @@ For license and copyright information please follow this link: #include +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Stickers { namespace { +using namespace Tdb; + constexpr auto kRefreshTimeout = 7200 * crl::time(1000); constexpr auto kEmojiCachesCount = 4; constexpr auto kPremiumCachesCount = 8; @@ -100,7 +105,9 @@ QSize LargeEmojiImage::Size() { EmojiPack::EmojiPack(not_null session) : _session(session) { +#if 0 // mtp refresh(); +#endif session->data().viewRemoved( ) | rpl::filter([](not_null view) { @@ -151,6 +158,7 @@ void EmojiPack::remove(not_null view) { } } +#if 0 // mtp auto EmojiPack::stickerForEmoji(EmojiPtr emoji) -> Sticker { Expects(emoji != nullptr); @@ -179,6 +187,7 @@ auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker { } return {}; } +#endif std::shared_ptr EmojiPack::image(EmojiPtr emoji) { const auto i = _images.emplace( @@ -215,6 +224,7 @@ std::shared_ptr EmojiPack::image(EmojiPtr emoji) { return result; } +#if 0 // mtp EmojiPtr EmojiPack::chooseInteractionEmoji( not_null item) const { return chooseInteractionEmoji(item->originalText().text); @@ -266,6 +276,7 @@ bool EmojiPack::hasAnimationsFor(not_null item) const { bool EmojiPack::hasAnimationsFor(const QString &emoticon) const { return !animationsForEmoji(chooseInteractionEmoji(emoticon)).empty(); } +#endif std::unique_ptr EmojiPack::effectPlayer( not_null document, @@ -318,6 +329,7 @@ std::unique_ptr EmojiPack::effectPlayer( return std::make_unique(std::move(shared), request); } +#if 0 // mtp void EmojiPack::refresh() { if (_requestId) { return; @@ -436,6 +448,7 @@ auto EmojiPack::collectAnimationsIndices( } return result; } +#endif void EmojiPack::refreshAll() { auto items = base::flat_set>(); @@ -487,6 +500,7 @@ void EmojiPack::refreshItems( } } +#if 0 // mtp void EmojiPack::applyPack( const MTPDstickerPack &data, const base::flat_map> &map) { @@ -525,5 +539,93 @@ void EmojiPack::refreshDelayed() { refresh(); }); } +#endif + +void EmojiPack::gifSectionsRefresh( + const Tdb::TLDupdateAnimationSearchParameters &data) { + const auto &list = data.vemojis().v; + auto &sender = _session->sender(); + auto old = base::take(_gifSectionsEmojiList); + _gifSectionsEmojiList.reserve(list.size()); + for (const auto &text : list) { + if (const auto emoji = Ui::Emoji::Find(text.v)) { + const auto i = ranges::find(old, emoji, &GifSection::emoji); + if (i != end(old) && i->document) { + _gifSectionsEmojiList.push_back(*i); + continue; + } + _gifSectionsEmojiList.push_back({ emoji, nullptr }); + if (_gifSectionsResolves.contains(emoji)) { + continue; + } + const auto finish = [=] { + _gifSectionsResolves.remove(emoji); + if (_gifSectionsResolves.empty()) { + gifSectionsPush(); + } + }; + const auto requestId = sender.request(TLgetAnimatedEmoji( + text + )).done([=](const TLDanimatedEmoji &data) { + auto &owner = _session->data(); + if (const auto entry = data.vsticker()) { + const auto document = owner.processDocument(*entry); + if (document->sticker()) { + const auto i = ranges::find( + _gifSectionsEmojiList, + emoji, + &GifSection::emoji); + if (i != end(_gifSectionsEmojiList)) { + i->document = document; + } + } + } + finish(); + }).fail([=](const Error &error) { + finish(); + }).send(); + _gifSectionsResolves[emoji] = requestId; + } + } + for (auto i = begin(_gifSectionsResolves) + ; i != end(_gifSectionsResolves) + ;) { + const auto requested = ranges::contains( + _gifSectionsEmojiList, + i->first, + &GifSection::emoji); + if (!requested) { + sender.request(i->second).cancel(); + i = _gifSectionsResolves.erase(i); + } else { + ++i; + } + } + if (_gifSectionsResolves.empty()) { + gifSectionsPush(); + } +} + +void EmojiPack::gifSectionsPush() { + Expects(_gifSectionsResolves.empty()); + + _gifSectionsEmojiList.erase(ranges::remove( + _gifSectionsEmojiList, + nullptr, + &GifSection::document + ), end(_gifSectionsEmojiList)); + _gifSections = _gifSectionsEmojiList; +} + +rpl::producer> EmojiPack::gifSectionsValue() const { + return _gifSections.value(); +} + +const Lottie::ColorReplacements *ReplacementsByFitzpatrickType(int type) { + if (type < 2 || type > 6) { + return nullptr; + } + return ColorReplacements(type - 1); +} } // namespace Stickers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h index 0226b04ef6246..93d0f27247e1e 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h @@ -13,6 +13,10 @@ For license and copyright information please follow this link: #include +namespace Tdb { +class TLDupdateAnimationSearchParameters; +} // namespace Tdb + class HistoryItem; class DocumentData; @@ -72,10 +76,13 @@ class EmojiPack final { bool add(not_null view); void remove(not_null view); +#if 0 // mtp [[nodiscard]] Sticker stickerForEmoji(EmojiPtr emoji); [[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji); +#endif [[nodiscard]] std::shared_ptr image(EmojiPtr emoji); +#if 0 // mtp [[nodiscard]] EmojiPtr chooseInteractionEmoji( not_null item) const; [[nodiscard]] EmojiPtr chooseInteractionEmoji( @@ -90,6 +97,7 @@ class EmojiPack final { [[nodiscard]] rpl::producer<> refreshed() const { return _refreshed.events(); } +#endif [[nodiscard]] std::unique_ptr effectPlayer( not_null document, @@ -97,11 +105,25 @@ class EmojiPack final { QString filepath, bool premium); + struct GifSection { + EmojiPtr emoji = nullptr; + DocumentData *document = nullptr; + + friend inline constexpr auto operator<=>( + GifSection, + GifSection) = default; + }; + void gifSectionsRefresh( + const Tdb::TLDupdateAnimationSearchParameters &data); + [[nodiscard]] auto gifSectionsValue() const + -> rpl::producer>; + private: class ImageLoader; void refresh(); void refreshDelayed(); +#if 0 // mtp void refreshAnimations(); void applySet(const MTPDmessages_stickerSet &data); void applyPack( @@ -113,13 +135,17 @@ class EmojiPack final { [[nodiscard]] auto collectAnimationsIndices( const QVector &packs) const -> base::flat_map>; +#endif + void gifSectionsPush(); void refreshAll(); void refreshItems(EmojiPtr emoji); void refreshItems(const base::flat_set> &list); void refreshItems(const base::flat_set> &items); const not_null _session; +#if 0 // mtp base::flat_map> _map; +#endif base::flat_map< IsolatedEmoji, base::flat_set>> _items; @@ -140,8 +166,15 @@ class EmojiPack final { rpl::event_stream<> _refreshed; + rpl::variable> _gifSections; + std::vector _gifSectionsEmojiList; + base::flat_map _gifSectionsResolves; + rpl::lifetime _lifetime; }; +[[nodiscard]] const Lottie::ColorReplacements *ReplacementsByFitzpatrickType( + int type); + } // namespace Stickers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp index e20c361d66cc4..62d3f6b79853b 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #include "chat_helpers/stickers_gift_box_pack.h" +#if 0 // mtp + #include "apiwrap.h" #include "data/data_document.h" #include "data/data_session.h" @@ -97,3 +99,5 @@ void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data) { } } // namespace Stickers + +#endif diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h index e722c19796374..9a3bc451fea76 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #pragma once +#if 0 // mtp + class DocumentData; namespace Main { @@ -37,3 +39,5 @@ class GiftBoxPack final { }; } // namespace Stickers + +#endif diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 66784f7748b3a..d2a7efeb5ac63 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -32,11 +32,15 @@ For license and copyright information please follow this link: #include "ui/rect_part.h" #include "styles/style_chat_helpers.h" +#include "tdb/tdb_tl_scheme.h" + #include namespace ChatHelpers { namespace { +using namespace Tdb; + constexpr auto kEmojiSectionSetIdBase = uint64(0x77FF'FFFF'FFFF'FFF0ULL); constexpr auto kEmojiSearchLimit = 32; @@ -113,6 +117,13 @@ std::optional SetIdEmojiSection(uint64 id) { rpl::producer> GifSectionsValue( not_null session) { + return session->emojiStickersPack().gifSectionsValue( + ) | rpl::map([](std::vector &&list) { + return list | ranges::views::transform([](auto &§ion) { + return GifSection{ section.document, section.emoji }; + }) | ranges::to_vector; + }); +#if 0 // mtp const auto config = &session->account().appConfig(); return rpl::single( rpl::empty_value() @@ -146,8 +157,10 @@ rpl::producer> GifSectionsValue( }) | ranges::to_vector; }) | rpl::distinct_until_changed(); }) | rpl::flatten_latest(); +#endif } +#if 0 // mtp [[nodiscard]] std::vector SearchEmoji( const std::vector &query, base::flat_set &outResultSet) { @@ -185,6 +198,54 @@ rpl::producer> GifSectionsValue( } return result; } +#endif + +mtpRequestId SearchEmoji( + mtpRequestId previousId, + const std::vector &query, + Fn)> callback) { + auto result = std::vector(); + const auto callOnMain = [&] { + crl::on_main([=, result = std::move(result)]() mutable { + callback(std::move(result)); + }); + }; + const auto pushPlain = [&](EmojiPtr emoji) { + if (ranges::contains(result, emoji)) { + return; + } + if (const auto original = emoji->original(); original != emoji) { + if (ranges::contains(result, original)) { + return; + } + } + result.push_back(emoji); + }; + auto refreshed = false; + auto &keywords = Core::App().emojiKeywords(); + for (const auto &entry : query) { + if (const auto emoji = Ui::Emoji::Find(entry)) { + pushPlain(emoji); + if (result.size() >= kEmojiSearchLimit) { + callOnMain(); + return 0; + } + } else if (!entry.isEmpty()) { + using Result = EmojiKeywords::Result; + const auto done = [=](std::vector list) { + if (list.size() > kEmojiSearchLimit) { + list.resize(kEmojiSearchLimit); + } + callback(list | ranges::views::transform( + &Result::emoji + ) | ranges::to_vector); + }; + return keywords.requestMine(entry, done, previousId); + } + } + callOnMain(); + return 0; +} StickerIcon::StickerIcon(uint64 setId) : setId(setId) { } @@ -1478,7 +1539,10 @@ void StickersListFooter::paintSetIconToCache( LocalStickersManager::LocalStickersManager(not_null session) : _session(session) +#if 0 // mtp , _api(&session->mtp()) { +#endif +, _api(&session->sender()) { } void LocalStickersManager::install(uint64 setId) { @@ -1488,6 +1552,13 @@ void LocalStickersManager::install(uint64 setId) { return; } const auto set = it->second.get(); + const auto input = std::nullopt; + if (!(set->flags & Data::StickersSetFlag::NotLoaded) + && !set->stickers.empty()) { + sendInstallRequest(setId, input); + return; + } +#if 0 // mtp const auto input = set->mtpInput(); if (!(set->flags & Data::StickersSetFlag::NotLoaded) && !set->stickers.empty()) { @@ -1499,10 +1570,16 @@ void LocalStickersManager::install(uint64 setId) { MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { result.match([&](const MTPDmessages_stickerSet &data) { +#endif + _api.request(TLgetStickerSet( + tl_int64(setId) + )).done([=](const TLstickerSet &data) { _session->data().stickers().feedSetFull(data); +#if 0 // mtp }, [](const MTPDmessages_stickerSetNotModified &) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); +#endif sendInstallRequest(setId, input); }).send(); } @@ -1511,6 +1588,7 @@ bool LocalStickersManager::isInstalledLocally(uint64 setId) const { return _installedLocallySets.contains(setId); } +#if 0 // mtp void LocalStickersManager::sendInstallRequest( uint64 setId, const MTPInputStickerSet &input) { @@ -1523,6 +1601,13 @@ void LocalStickersManager::sendInstallRequest( result.c_messages_stickerSetInstallResultArchive()); } }).fail([=] { +#endif +void LocalStickersManager::sendInstallRequest(uint64 setId, std::nullopt_t) { + _api.request(TLchangeStickerSet( + tl_int64(setId), + tl_bool(true), + tl_bool(false) + )).fail([=] { notInstalledLocally(setId); _session->data().stickers().undoInstallLocally(setId); }).send(); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h index 5dbdd30f9def9..f21cf08e7f3fa 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h @@ -15,6 +15,8 @@ For license and copyright information please follow this link: #include "ui/round_rect.h" #include "ui/userpic_view.h" +#include "tdb/tdb_sender.h" + namespace Ui { class InputField; class CrossButton; @@ -64,9 +66,16 @@ struct GifSection { [[nodiscard]] rpl::producer> GifSectionsValue( not_null session); +#if 0 // mtp [[nodiscard]] std::vector SearchEmoji( const std::vector &query, base::flat_set &outResultSet); +#endif + +mtpRequestId SearchEmoji( + mtpRequestId previousId, + const std::vector &query, + Fn)> callback); struct StickerIcon { explicit StickerIcon(uint64 setId); @@ -323,14 +332,20 @@ class LocalStickersManager final { bool clearInstalledLocally(); private: +#if 0 // mtp void sendInstallRequest( uint64 setId, const MTPInputStickerSet &input); +#endif + void sendInstallRequest(uint64 setId, std::nullopt_t); void installedLocally(uint64 setId); void notInstalledLocally(uint64 setId); const not_null _session; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; base::flat_set _installedLocallySets; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 3675c048319ce..9584351ff09ec 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -53,11 +53,16 @@ For license and copyright information please follow this link: #include "styles/style_window.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_tl_scheme.h" +#include "chat_helpers/emoji_keywords.h" + #include namespace ChatHelpers { namespace { +using namespace Tdb; + constexpr auto kSearchRequestDelay = 400; constexpr auto kRecentDisplayLimit = 20; constexpr auto kPreloadOfficialPages = 4; @@ -187,7 +192,10 @@ StickersListWidget::StickersListWidget( , _show(std::move(descriptor.show)) , _features(descriptor.features) , _overBg(st::roundRadiusLarge, st().overBg) +#if 0 // mtp , _api(&session().mtp()) +#endif +, _api(&session().sender()) , _localSetsManager(std::make_unique(&session())) , _section(Section::Stickers) , _isMasks(_mode == Mode::Masks) @@ -372,6 +380,7 @@ void StickersListWidget::preloadMoreOfficial() { if (_officialRequestId) { return; } +#if 0 // mtp _officialRequestId = _api.request(MTPmessages_GetOldFeaturedStickers( MTP_int(_officialOffset), MTP_int(kOfficialLoadLimit), @@ -381,6 +390,15 @@ void StickersListWidget::preloadMoreOfficial() { result.match([&](const MTPDmessages_featuredStickersNotModified &d) { LOG(("Api Error: messages.featuredStickersNotModified.")); }, [&](const MTPDmessages_featuredStickers &data) { +#endif + _officialRequestId = _api.request(TLgetTrendingStickerSets( + tl_stickerTypeRegular(), + tl_int32(_officialOffset), + tl_int32(kOfficialLoadLimit) + )).done([=](const TLtrendingStickerSets &result) { + _officialRequestId = 0; + const auto &data = result.data(); + const auto &list = data.vsets().v; _officialOffset += list.size(); for (int i = 0, l = list.size(); i != l; ++i) { @@ -396,7 +414,11 @@ void StickersListWidget::preloadMoreOfficial() { externalLayout, AppendSkip::Installed); } + +#if 0 // mtp }); +#endif + resizeToWidth(width()); repaintItems(); }).send(); @@ -547,6 +569,16 @@ void StickersListWidget::sendSearchRequest() { } _search->setLoading(true); + _searchRequestId = _api.request(TLsearchStickerSets( + tl_string(_searchQuery) + )).done([=](const TLstickerSets &result) { + searchResultsDone(result); + }).fail([=] { + // show error? + _search->setLoading(false); + _searchRequestId = 0; + }).send(); +#if 0 // mtp const auto hash = uint64(0); _searchRequestId = _api.request(MTPmessages_SearchStickerSets( MTP_flags(0), @@ -559,6 +591,7 @@ void StickersListWidget::sendSearchRequest() { _search->setLoading(false); _searchRequestId = 0; }).handleAllErrors().send(); +#endif } void StickersListWidget::searchForSets( @@ -596,6 +629,8 @@ void StickersListWidget::cancelSetsSearch() { _api.request(requestId).cancel(); } _searchRequestTimer.cancel(); + _searchEmojiRequestId = 0; + invalidate_weak_ptrs(&_searchEmojiGuard); _searchQuery = _searchNextQuery = QString(); _filteredStickers.clear(); _searchCache.clear(); @@ -811,10 +846,14 @@ auto StickersListWidget::shownSets() -> std::vector & { } void StickersListWidget::searchResultsDone( +#if 0 // mtp const MTPmessages_FoundStickerSets &result) { +#endif + const TLstickerSets &result) { _search->setLoading(false); _searchRequestId = 0; +#if 0 // mtp if (result.type() == mtpc_messages_foundStickerSetsNotModified) { LOG(("API Error: " "messages.foundStickerSetsNotModified not expected.")); @@ -822,6 +861,7 @@ void StickersListWidget::searchResultsDone( } Assert(result.type() == mtpc_messages_foundStickerSets); +#endif auto it = _searchCache.find(_searchQuery); if (it == _searchCache.cend()) { @@ -829,7 +869,10 @@ void StickersListWidget::searchResultsDone( _searchQuery, std::vector()).first; } +#if 0 // mtp auto &d = result.c_messages_foundStickerSets(); +#endif + const auto &d = result.data(); for (const auto &data : d.vsets().v) { const auto set = session().data().stickers().feedSet(data); if (set->stickers.empty() && set->covers.empty()) { @@ -2298,11 +2341,16 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) { return; } _megagroupSetIdRequested = set.id; +#if 0 // mtp _api.request(MTPmessages_GetStickerSet( Data::InputStickerSet(set), MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { result.match([&](const MTPDmessages_stickerSet &data) { +#endif + _api.request(TLgetStickerSet( + tl_int64(set.id) + )).done([=](const TLstickerSet &data) { if (const auto set = session().data().stickers().feedSetFull(data)) { refreshStickers(); if (set->id == _megagroupSetIdRequested) { @@ -2311,9 +2359,11 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) { LOG(("API Error: Got different set.")); } } +#if 0 // mtp }, [](const MTPDmessages_stickerSetNotModified &) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); +#endif }).send(); } @@ -2610,7 +2660,21 @@ void StickersListWidget::setupSearch() { QString b) { return a.isEmpty() ? b : (a + ' ' + b); }); + if (text.trimmed().isEmpty()) { + cancelSetsSearch(); + return; + } + const auto callback = crl::guard(&_searchEmojiGuard, [=]( + const std::vector &result) { + searchForSets(text, result); + }); + _searchEmojiRequestId = SearchEmoji( + _searchEmojiRequestId, + query, + callback); +#if 0 // mtp searchForSets(std::move(text), SearchEmoji(query, set)); +#endif }, session, false, (_mode == Mode::UserpicBuilder)); } @@ -2709,6 +2773,7 @@ object_ptr MakeConfirmRemoveSetBox( const auto it = sets.find(setId); if (it != sets.cend()) { const auto set = it->second.get(); +#if 0 // mtp if (set->id && set->accessHash) { session->api().request(MTPmessages_UninstallStickerSet( MTP_inputStickerSetID( @@ -2721,6 +2786,12 @@ object_ptr MakeConfirmRemoveSetBox( MTP_string(set->shortName))) ).send(); } +#endif + session->sender().request(TLchangeStickerSet( + tl_int64(setId), + tl_bool(false), + tl_bool(false) + )).send(); auto writeRecent = false; auto &recent = session->data().stickers().getRecentPack(); for (auto i = recent.begin(); i != recent.cend();) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index 2e1ef9c1cdf53..71dabf5afeb8f 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -14,6 +14,12 @@ For license and copyright information please follow this link: #include "base/variant.h" #include "base/timer.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLstickerSets; +} // namespace Tdb + class StickerPremiumMark; namespace Main { @@ -339,7 +345,10 @@ class StickersListWidget final : public TabbedSelector::Inner { void cancelSetsSearch(); void showSearchResults(); +#if 0 // mtp void searchResultsDone(const MTPmessages_FoundStickerSets &result); +#endif + void searchResultsDone(const Tdb::TLstickerSets &result); void refreshSearchRows(); void refreshSearchRows(const std::vector *cloudSets); void fillFilteredStickersRow(); @@ -359,7 +368,10 @@ class StickersListWidget final : public TabbedSelector::Inner { const ComposeFeatures _features; Ui::RoundRect _overBg; std::unique_ptr _search; +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; std::unique_ptr _localSetsManager; ChannelData *_megagroupSet = nullptr; uint64 _megagroupSetIdRequested = 0; @@ -423,6 +435,8 @@ class StickersListWidget final : public TabbedSelector::Inner { base::Timer _searchRequestTimer; QString _searchQuery, _searchNextQuery; mtpRequestId _searchRequestId = 0; + mtpRequestId _searchEmojiRequestId = 0; + base::has_weak_ptr _searchEmojiGuard; rpl::event_stream _chosen; rpl::event_stream<> _scrollUpdated; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 4860481e706d6..d23f2745662c0 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -316,7 +316,10 @@ std::unique_ptr MakeSearch( Ui::SearchWithGroups::IconSizeOverride()) }); +#if 0 // mtp result->queryValue( +#endif + result->debouncedQueryValue( ) | rpl::skip(1) | rpl::start_with_next( std::move(callback), parent->lifetime()); diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 5dc8b7ce767c6..b2a54be3144bc 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -10,6 +10,8 @@ For license and copyright information please follow this link: #include "core/version.h" #include "settings.h" +inline constexpr bool kTdxForcePath = true; + enum { MaxSelectedItems = 100, diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index a25bf54c7a932..e1398183b8063 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -98,6 +98,9 @@ For license and copyright information please follow this link: #include "ui/boxes/confirm_box.h" #include "styles/style_window.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + #include #include #include @@ -107,6 +110,8 @@ For license and copyright information please follow this link: namespace Core { namespace { +using namespace Tdb; + constexpr auto kQuitPreventTimeoutMs = crl::time(1500); constexpr auto kAutoLockTimeoutLateMs = crl::time(3000); constexpr auto kClearEmojiImageSourceTimeout = 10 * crl::time(1000); @@ -294,6 +299,7 @@ void Application::run() { PowerSaving::SetForceAll(saving && !ignore); }, _lifetime); +#if 0 // mtp style::ShortAnimationPlaying( ) | rpl::start_with_next([=](bool playing) { if (playing) { @@ -302,6 +308,7 @@ void Application::run() { MTP::details::unpause(); } }, _lifetime); +#endif DEBUG_LOG(("Application Info: inited...")); @@ -876,6 +883,7 @@ void Application::logoutWithChecks(Main::Account *account) { void Application::forceLogOut( not_null account, const TextWithEntities &explanation) { + // #TODO secret allow cancel force logout const auto box = Ui::show(Ui::MakeConfirmBox({ .text = explanation, .confirmText = tr::lng_passcode_logout(tr::now), @@ -898,9 +906,11 @@ void Application::checkLocalTime() { base::ConcurrentTimerEnvironment::Adjust(); base::unixtime::http_invalidate(); } +#if 0 // mtp if (const auto session = maybePrimarySession()) { session->updates().checkLastUpdate(adjusted); } +#endif } void Application::handleAppActivated() { @@ -1069,15 +1079,34 @@ void Application::checkStartUrl() { && !_lastActivePrimaryWindow->locked()) { const auto url = cStartUrl(); cSetStartUrl(QString()); +#if 0 // mtp if (!openLocalUrl(url, {})) { cSetStartUrl(url); } +#endif + _lastActivePrimaryWindow->account().sender().request( + TLgetInternalLinkType(tl_string(url)) + ).done([=](const TLinternalLinkType &result) { + if (!HandleLocalUrl( + result, + QVariant::fromValue(OpenFromExternalContext{}))) { + if (!_lastActivePrimaryWindow + || !_lastActivePrimaryWindow->sessionController()) { + // Maybe we're still waiting for the session. + cSetStartUrl(url); + } + } + }).fail([=] { + cSetStartUrl(url); + }).send(); } } +#if 0 // mtp bool Application::openLocalUrl(const QString &url, QVariant context) { return openCustomUrl("tg://", LocalUrlHandlers(), url, context); } +#endif bool Application::openInternalUrl(const QString &url, QVariant context) { return openCustomUrl("internal:", InternalUrlHandlers(), url, context); diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h index f9059da8d8e5b..e8374025fe24b 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -260,7 +260,9 @@ class Application final : public QObject { void checkStartUrl(); void checkSendPaths(); void checkFileOpen(); +#if 0 // mtp bool openLocalUrl(const QString &url, QVariant context); +#endif bool openInternalUrl(const QString &url, QVariant context); [[nodiscard]] QString changelogLink() const; diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index 2d0d52fe06a13..b79f4412d79cb 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -17,9 +17,14 @@ For license and copyright information please follow this link: #include "mainwindow.h" #include "apiwrap.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace Core { namespace { +using namespace Tdb; + std::map BetaLogs() { return { { @@ -69,6 +74,7 @@ std::unique_ptr Changelogs::Create( void Changelogs::requestCloudLogs() { _chatsSubscription.destroy(); +#if 0 // mtp const auto callback = [this](const MTPUpdates &result) { _session->api().applyUpdates(result); @@ -97,6 +103,13 @@ void Changelogs::requestCloudLogs() { _session->api().requestChangelog( FormatVersionPrecise(_oldVersion), crl::guard(this, callback)); +#endif + _session->sender().request(TLaddApplicationChangelog( + tl_string(FormatVersionPrecise(_oldVersion)) + )).done([=] { + // tdlib todo if not added some logs add local + addLocalLogs(); + }).send(); } void Changelogs::addLocalLogs() { diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index f796619713590..aa0463713e6be 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -98,14 +98,32 @@ QString HiddenUrlClickHandler::dragText() const { } void HiddenUrlClickHandler::Open(QString url, QVariant context) { +#if 0 // mtp url = Core::TryConvertUrlToLocal(url); if (Core::InternalPassportLink(url)) { return; } +#endif + UrlClickHandler::Open(url, QVariant::fromValue([&] { + auto result = context.value(); + result.mayShowConfirmation = !base::IsCtrlPressed(); + return result; + }())); +} +void HiddenUrlClickHandler::Confirm( + QString url, + QVariant context, + bool force) { + context = QVariant::fromValue([&] { + auto result = context.value(); + result.mayShowConfirmation = false; + return result; + }()); const auto open = [=] { UrlClickHandler::Open(url, context); }; +#if 0 // mtp if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || url.startsWith(u"internal:"_q, Qt::CaseInsensitive)) { UrlClickHandler::Open(url, QVariant::fromValue([&] { @@ -116,6 +134,10 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) { } else { const auto parsedUrl = QUrl::fromUserInput(url); if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) { +#endif + { + const auto parsedUrl = QUrl::fromUserInput(url); + if (force || UrlRequiresConfirmation(parsedUrl)) { Core::App().hideMediaView(); const auto displayed = parsedUrl.isValid() ? parsedUrl.toDisplayString() diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index 15a7dc1f95e26..a996b2b58a8ce 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -69,6 +69,8 @@ class HiddenUrlClickHandler : public UrlClickHandler { TextEntity getTextEntity() const override; + static void Confirm(QString url, QVariant context, bool force = true); + }; class UserData; diff --git a/Telegram/SourceFiles/core/core_cloud_password.cpp b/Telegram/SourceFiles/core/core_cloud_password.cpp index 99b5d589ef57d..5c5ddd7a99964 100644 --- a/Telegram/SourceFiles/core/core_cloud_password.cpp +++ b/Telegram/SourceFiles/core/core_cloud_password.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #include "core/core_cloud_password.h" +#if 0 // mtp #include "base/openssl_help.h" #include "mtproto/mtproto_dh_utils.h" @@ -303,16 +304,22 @@ bytes::vector ComputeSecureSecretHash( CloudPasswordState ParseCloudPasswordState( const MTPDaccount_password &data) { auto result = CloudPasswordState(); +#if 0 // goodToRemove result.mtp.request = ParseCloudPasswordCheckRequest(data); +#endif result.hasPassword = (!!result.mtp.request); +#if 0 // goodToRemove result.mtp.unknownAlgorithm = data.vcurrent_algo() && !result.hasPassword; +#endif result.hasRecovery = data.is_has_recovery(); result.notEmptyPassport = data.is_has_secure_values(); result.hint = qs(data.vhint().value_or_empty()); +#if 0 // goodToRemove result.mtp.newPassword = ValidateNewCloudPasswordAlgo( ParseCloudPasswordAlgo(data.vnew_algo())); result.mtp.newSecureSecret = ValidateNewSecureSecretAlgo( ParseSecureSecretAlgo(data.vnew_secure_algo())); +#endif result.unconfirmedPattern = qs(data.vemail_unconfirmed_pattern().value_or_empty()); result.pendingResetDate = data.vpending_reset_date().value_or_empty(); @@ -354,3 +361,4 @@ CloudPasswordState ParseCloudPasswordState( } } // namespace Core +#endif diff --git a/Telegram/SourceFiles/core/core_cloud_password.h b/Telegram/SourceFiles/core/core_cloud_password.h index 2c4d06bc860f7..0c66e23895f0c 100644 --- a/Telegram/SourceFiles/core/core_cloud_password.h +++ b/Telegram/SourceFiles/core/core_cloud_password.h @@ -11,6 +11,7 @@ For license and copyright information please follow this link: namespace Core { +#if 0 // mtp constexpr auto kHandleSrpIdInvalidTimeout = 60 * crl::time(1000); struct CloudPasswordAlgoModPow { @@ -61,13 +62,20 @@ inline bool operator!=( CloudPasswordCheckRequest ParseCloudPasswordCheckRequest( const MTPDaccount_password &data); +#endif struct CloudPasswordResult { +#if 0 // mtp MTPInputCheckPasswordSRP result; +#endif + QByteArray password; +#if 0 // mtp explicit operator bool() const; +#endif }; +#if 0 // mtp struct CloudPasswordDigest { bytes::vector modpow; }; @@ -120,8 +128,10 @@ MTPSecurePasswordKdfAlgo PrepareSecureSecretAlgo( bytes::vector ComputeSecureSecretHash( const SecureSecretAlgo &algo, bytes::const_span password); +#endif struct CloudPasswordState { +#if 0 // goodToRemove struct Mtp { CloudPasswordCheckRequest request; bool unknownAlgorithm = false; @@ -129,6 +139,7 @@ struct CloudPasswordState { SecureSecretAlgo newSecureSecret; }; Mtp mtp; +#endif bool hasPassword = false; bool hasRecovery = false; bool notEmptyPassport = false; @@ -136,9 +147,13 @@ struct CloudPasswordState { QString hint; QString unconfirmedPattern; TimeId pendingResetDate = 0; + + QString serverError; }; +#if 0 // mtp CloudPasswordState ParseCloudPasswordState( const MTPDaccount_password &data); +#endif } // namespace Core diff --git a/Telegram/SourceFiles/core/file_utilities.cpp b/Telegram/SourceFiles/core/file_utilities.cpp index 7103e1bd6771e..5cc32dc121dff 100644 --- a/Telegram/SourceFiles/core/file_utilities.cpp +++ b/Telegram/SourceFiles/core/file_utilities.cpp @@ -20,6 +20,8 @@ For license and copyright information please follow this link: #include "main/main_session.h" #include "mainwindow.h" +#include "core/launcher.h" + #include #include #include @@ -168,6 +170,9 @@ void ShowInFolder(const QString &filepath) { } QString DefaultDownloadPathFolder(not_null session) { + if (Core::IsTdxPathUsed()) { + return session->supportMode() ? u"Tdx Support"_q : u"Tdx"_q; + } return session->supportMode() ? u"Tsupport Desktop"_q : AppName.utf16(); } diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index b171b070e10fb..d45fdb754a24d 100644 --- a/Telegram/SourceFiles/core/launcher.cpp +++ b/Telegram/SourceFiles/core/launcher.cpp @@ -25,6 +25,9 @@ For license and copyright information please follow this link: namespace Core { namespace { +constexpr bool kTdxForcePortable = kTdxForcePath && !Platform::IsMac(); +bool TdxPathUsed/* = false*/; + uint64 InstallationTag = 0; base::options::toggle OptionFreeType({ @@ -231,11 +234,27 @@ bool CheckPortableVersionFolder() { const auto portable = cExeDir() + u"TelegramForcePortable"_q; QFile key(portable + u"/tdata/alpha"_q); + + auto tdx = QFile(portable + u"/tdata/tdx"_q); + if (cAlphaVersion()) { Assert(*AlphaPrivateKey != 0); cForceWorkingDir(portable); QDir().mkpath(cWorkingDir() + u"tdata"_q); + + if constexpr (kTdxForcePortable) { + if (!tdx.open(QIODevice::WriteOnly)) { + LOG(("FATAL: Could not open '%1' for writing tdx tag!" + ).arg(tdx.fileName())); + return false; + } + key.write("1", 1); + TdxPathUsed = true; + } else if (tdx.exists()) { + TdxPathUsed = true; + } + cSetAlphaPrivateKey(QByteArray(AlphaPrivateKey)); if (!key.open(QIODevice::WriteOnly)) { LOG(("FATAL: Could not open '%1' for writing private key!" @@ -247,6 +266,22 @@ bool CheckPortableVersionFolder() { dataStream << quint64(cRealAlphaVersion()) << cAlphaPrivateKey(); return true; } + + if constexpr (kTdxForcePortable) { + cForceWorkingDir(portable + '/'); + QDir().mkpath(cWorkingDir() + u"tdata"_q); + if (!tdx.open(QIODevice::WriteOnly)) { + LOG(("FATAL: Could not open '%1' for writing tdx tag!" + ).arg(tdx.fileName())); + return false; + } + key.write("1", 1); + TdxPathUsed = true; + return true; + } else if (tdx.exists()) { + TdxPathUsed = true; + } + if (!QDir(portable).exists()) { return true; } @@ -578,4 +613,12 @@ int Launcher::executeApplication() { return sandbox.start(); } +bool IsTdxPathUsed() { + return TdxPathUsed; +} + +void SetTdxPathUsed() { + TdxPathUsed = true; +} + } // namespace Core diff --git a/Telegram/SourceFiles/core/launcher.h b/Telegram/SourceFiles/core/launcher.h index ea5b1c97f3566..cb83b2964ca29 100644 --- a/Telegram/SourceFiles/core/launcher.h +++ b/Telegram/SourceFiles/core/launcher.h @@ -90,4 +90,7 @@ class Launcher { }; +[[nodiscard]] bool IsTdxPathUsed(); +void SetTdxPathUsed(); + } // namespace Core diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index f21b163968818..1f21bdb1c3b17 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -60,13 +60,21 @@ For license and copyright information please follow this link: #include "base/qt/qt_common_adapters.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" +#include "core/file_utilities.h" +#include "data/data_user.h" +#include "main/main_domain.h" + #include namespace Core { namespace { +using namespace Tdb; + using Match = qthelp::RegularExpressionMatch; +#if 0 // mtp bool JoinGroupByHash( Window::SessionController *controller, const Match &match, @@ -125,12 +133,14 @@ bool ShowTheme( controller->window().activate(); return true; } +#endif void ShowLanguagesBox(Window::SessionController *controller) { static auto Guard = base::binary_guard(); Guard = LanguageBox::Show(controller); } +#if 0 // mtp bool SetLanguage( Window::SessionController *controller, const Match &match, @@ -575,6 +585,7 @@ bool HandleUnknown( controller->session().api().requestDeepLinkInfo(request, callback); return true; } +#endif bool OpenMediaTimestamp( Window::SessionController *controller, @@ -659,6 +670,7 @@ void ExportTestChatTheme( not_null theme) { const auto session = &controller->session(); const auto show = controller->uiShow(); +#if 0 // tdlib todo const auto inputSettings = [&](Data::CloudThemeType type) -> std::optional { const auto i = theme->settings.find(type); @@ -765,6 +777,7 @@ void ExportTestChatTheme( }).fail([=](const MTP::Error &error) { show->showToast(u"Error: "_q + error.type()); }).send(); +#endif } bool ResolveTestChatTheme( @@ -801,6 +814,7 @@ bool ResolveTestChatTheme( return true; } +#if 0 // mtp bool ResolveInvoice( Window::SessionController *controller, const Match &match, @@ -840,6 +854,7 @@ bool ResolvePremiumOffer( controller->window().activate(); return true; } +#endif bool ResolveLoginCode( Window::SessionController *controller, @@ -891,6 +906,7 @@ bool ResolveBoost( } // namespace +#if 0 // mtp const std::vector &LocalUrlHandlers() { static auto Result = std::vector{ { @@ -980,6 +996,7 @@ const std::vector &LocalUrlHandlers() { }; return Result; } +#endif const std::vector &InternalUrlHandlers() { static auto Result = std::vector{ @@ -1165,4 +1182,469 @@ bool StartUrlRequiresActivate(const QString &url) { : !InternalPassportLink(url); } +bool HandleLocalUrl( + const TLinternalLinkType &link, + const QVariant &context) { + using Navigation = Window::SessionNavigation; + const auto my = context.value(); + const auto attach = context.value(); + const auto external = context.canConvert(); + const auto controller = my.sessionWindow.get() + ? my.sessionWindow.get() + : attach.controller + ? attach.controller + : Core::App().activePrimaryWindow() + ? Core::App().activePrimaryWindow()->sessionController() + : nullptr; + const auto session = controller ? &controller->session() : nullptr; + const auto sender = session ? &session->sender() : nullptr; + const auto settingsSection = [&](::Settings::Type type) { + if (!controller) { + return false; + } + controller->showSettings(type); + controller->window().activate(); + return true; + }; + return link.match([&](const TLDinternalLinkTypeActiveSessions &) { + if (session) { + session->api().authorizations().reload(); + } + return settingsSection(::Settings::Sessions::Id()); + }, [&](const TLDinternalLinkTypeAttachmentMenuBot &data) { + if (!controller) { + return false; + } + const auto botUsername = data.vbot_username().v; + const auto openWebAppUrl = data.vurl().v; + return data.vtarget_chat().match([&](const TLDtargetChatCurrent &) { + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = botUsername, + .attachBotToggleCommand = openWebAppUrl, + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDtargetChatChosen &data) { + using Type = InlineBots::PeerType; + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = botUsername, + .attachBotToggleCommand = openWebAppUrl, + .attachBotChooseTypes = (Type() + | (data.vallow_bot_chats().v ? Type::Bot : Type()) + | (data.vallow_user_chats().v ? Type::User : Type()) + | (data.vallow_group_chats().v ? Type::Group : Type()) + | (data.vallow_channel_chats().v ? Type::Broadcast : Type())), + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDtargetChatInternalLink &data) { + return HandleLocalUrl(data.vlink(), QVariant::fromValue( + StartAttachContext{ + .controller = controller, + .botUsername = botUsername, + .openWebAppUrl = openWebAppUrl, + })); + }); + }, [&](const TLDinternalLinkTypeSideMenuBot &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = data.vbot_username().v, + .attachBotToggleCommand = data.vurl().v, + .attachBotMenuOpen = true, + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeAuthenticationCode &data) { + const auto account = controller + ? &controller->session().account() + : Core::App().domain().started() + ? &Core::App().domain().active() + : nullptr; + if (!account) { + return false; + } + account->handleLoginCode(data.vcode().v); + if (controller) { + controller->window().activate(); + } else if (const auto window = Core::App().activeWindow()) { + window->activate(); + } + return true; + }, [&](const TLDinternalLinkTypeBackground &data) { + if (!controller) { + return false; + } + sender->request(TLsearchBackground( + data.vbackground_name() + )).done(crl::guard(controller, [=](const TLbackground &result) { + const auto paper = Data::WallPaper::Create( + &controller->session(), + result); + if (paper) { + controller->show( + Box(controller, *paper)); + } else { + controller->show( + Ui::MakeInformBox(tr::lng_background_bad_link())); + } + controller->window().activate(); + })).send(); + return true; + }, [&](const TLDinternalLinkTypeBotStart &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = data.vbot_username().v, + .resolveType = Window::ResolveType::BotStart, + .startToken = data.vstart_parameter().v, + .startAutoSubmit = data.vautostart().v || my.botStartAutoSubmit, + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeBotStartInGroup &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = data.vbot_username().v, + .resolveType = Window::ResolveType::AddToGroup, + .startToken = data.vstart_parameter().v, + .startAdminRights = (data.vadministrator_rights() + ? AdminRightsFromChatAdministratorRights( + *data.vadministrator_rights()) + : ChatAdminRights()), + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeBotAddToChannel &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = data.vbot_username().v, + .resolveType = Window::ResolveType::AddToChannel, + .startAdminRights = AdminRightsFromChatAdministratorRights( + data.vadministrator_rights()), + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeChangePhoneNumber &) { + if (!controller) { + return false; + } + controller->show(Ui::MakeInformBox(tr::lng_change_phone_error())); + return true; + }, [&](const TLDinternalLinkTypeChatBoost &data) { + if (!controller) { + return false; + } + controller->resolveBoostLink(data.vurl().v); + return true; + }, [&](const TLDinternalLinkTypeChatInvite &data) { + if (!controller) { + return false; + } + Api::CheckChatInvite(controller, data.vinvite_link().v); + return true; + }, [&](const TLDinternalLinkTypeChatFolderInvite &data) { + if (!controller) { + return false; + } + Api::CheckFilterInvite(controller, data.vinvite_link().v); + return true; + }, [&](const TLDinternalLinkTypeDefaultMessageAutoDeleteTimerSettings &) { + return settingsSection(::Settings::GlobalTTLId()); + }, [&](const TLDinternalLinkTypeEditProfileSettings &) { + return settingsSection(::Settings::Information::Id()); + }, [&](const TLDinternalLinkTypeChatFolderSettings &) { + return settingsSection(::Settings::Folders::Id()); + }, [&](const TLDinternalLinkTypeGame &data) { + // tdlib tg://share_game_score links + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = data.vbot_username().v, + .resolveType = Window::ResolveType::ShareGame, + .startToken = data.vgame_short_name().v, + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeInstantView &data) { + File::OpenUrl(data.vfallback_url().v); + return true; + }, [&](const TLDinternalLinkTypeInvoice &data) { + if (!controller) { + return false; + } + const auto window = &controller->window(); + Payments::CheckoutProcess::Start( + &controller->session(), + data.vinvoice_name().v, + crl::guard(window, [=](auto) { window->activate(); })); + return true; + }, [&](const TLDinternalLinkTypeLanguagePack &data) { + Lang::CurrentCloudManager().switchWithWarning( + data.vlanguage_pack_id().v); + if (controller) { + controller->window().activate(); + } + return true; + }, [&](const TLDinternalLinkTypeLanguageSettings &data) { + ShowLanguagesBox(controller); + return true; + }, [&](const TLDinternalLinkTypeMessage &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .clickFromMessageId = my.itemId, + .messageLink = data.vurl().v, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeMessageDraft &data) { + if (!controller) { + return false; + } + const auto text = Api::FormattedTextFromTdb(data.vtext()); + const auto link = data.vcontains_link().v; + const auto chosen = [=](not_null thread) { + const auto content = controller->content(); + return content->shareUrl(thread, text, link); + }; + Window::ShowChooseRecipientBox(controller, chosen); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypePassportDataRequest &data) { + if (!external) { + return true; + } else if (!controller) { + return false; + } + controller->showPassportForm(Passport::FormRequest( + UserId(data.vbot_user_id().v), + data.vscope().v, + data.vcallback_url().v, + data.vpublic_key().v, + data.vnonce().v)); + return true; + }, [&](const TLDinternalLinkTypePhoneNumberConfirmation &data) { + if (!controller) { + return false; + } + session->api().confirmPhone().resolve( + controller, + data.vphone_number().v, + data.vhash().v); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypePremiumFeatures &data) { + if (!controller) { + return false; + } + const auto refAddition = data.vreferrer().v; + const auto ref = u"deeplink"_q + + (refAddition.isEmpty() ? QString() : '_' + refAddition); + ::Settings::ShowPremium(controller, ref); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypePremiumGiftCode &data) { + if (!controller) { + return false; + } + const auto slug = data.vcode().v; + const auto done = [=](Api::GiftCode code) { + if (!code) { + controller->showToast(tr::lng_gift_link_expired(tr::now)); + } else { + controller->uiShow()->showBox( + Box(GiftCodeBox, controller, slug)); + } + }; + controller->session().api().premium().checkGiftCode( + slug, + crl::guard(controller, done)); + return true; + }, [&](const TLDinternalLinkTypePrivacyAndSecuritySettings &) { + return settingsSection(::Settings::PrivacySecurity::Id()); + }, [&](const TLDinternalLinkTypeProxy &data) { + auto fields = QMap{ + { u"server"_q, data.vserver().v }, + { u"port"_q, QString::number(data.vport().v) }, + }; + const auto type = data.vtype().match([&]( + const TLDproxyTypeHttp &data) { + fields.insert(u"user"_q, data.vusername().v); + fields.insert(u"pass"_q, data.vpassword().v); + return MTP::ProxyData::Type::Http; + }, [&](const TLDproxyTypeSocks5 &data) { + fields.insert(u"user"_q, data.vusername().v); + fields.insert(u"pass"_q, data.vpassword().v); + return MTP::ProxyData::Type::Socks5; + }, [&](const TLDproxyTypeMtproto &data) { + fields.insert(u"secret"_q, data.vsecret().v); + return MTP::ProxyData::Type::Mtproto; + }); + ProxiesBoxController::ShowApplyConfirmation(type, fields); + if (controller) { + controller->window().activate(); + } + return true; + }, [&](const TLDinternalLinkTypePublicChat &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = data.vchat_username().v, + .attachBotUsername = (attach ? attach.botUsername : QString()), + .attachBotToggleCommand = (attach + ? attach.openWebAppUrl + : std::optional()), + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeQrCodeAuthentication &data) { + return false; + }, [&](const TLDinternalLinkTypeRestorePurchases &data) { + return false; + }, [&](const TLDinternalLinkTypeSettings &data) { + return settingsSection(::Settings::Main::Id()); + }, [&](const TLDinternalLinkTypeStickerSet &data) { + if (!controller) { + return false; + } + Core::App().hideMediaView(); + controller->show(Box( + controller->uiShow(), + StickerSetIdentifier{ .shortName = data.vsticker_set_name().v }, + (data.vexpect_custom_emoji().v + ? Data::StickersType::Emoji + : Data::StickersType::Stickers))); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeTheme &data) { + if (!controller) { + return false; + } + Core::App().hideMediaView(); + controller->session().data().cloudThemes().resolve( // tdlib themes + &controller->window(), + data.vtheme_name().v, + my.itemId); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeThemeSettings &data) { + return settingsSection(::Settings::Chat::Id()); + }, [&](const TLDinternalLinkTypeUnknownDeepLink &data) { + if (!controller) { + return false; + } + const auto callback = crl::guard(controller, [=]( + TextWithEntities message, + bool updateRequired) { + if (updateRequired) { + const auto callback = [=](Fn &&close) { + Core::UpdateApplication(); + close(); + }; + controller->show(Ui::MakeConfirmBox({ + .text = message, + .confirmed = callback, + .confirmText = tr::lng_menu_update(), + })); + } else { + controller->show(Ui::MakeInformBox(message)); + } + }); + session->api().requestDeepLinkInfo(data.vlink().v, callback); + return true; + }, [&](const TLDinternalLinkTypeUnsupportedProxy &data) { + if (const auto window = Core::App().activePrimaryWindow()) { + window->uiShow()->show( + Ui::MakeInformBox(tr::lng_proxy_unsupported(tr::now))); + } + return true; + }, [&](const TLDinternalLinkTypeUserPhoneNumber &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .phone = data.vphone_number().v, + .attachBotUsername = (attach ? attach.botUsername : QString()), + .attachBotToggleCommand = (attach + ? attach.openWebAppUrl + : std::optional()), + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeUserToken &data) { + if (!controller) { + return false; + } + const auto token = data.vtoken().v; + sender->request(TLsearchUserByToken( + tl_string(token) + )).done(crl::guard(controller, [=](const TLuser &result) { + controller->showPeerHistory( + controller->session().data().processUser(result)); + })).fail(crl::guard(controller, [=] { + controller->show( + Ui::MakeInformBox( + tr::lng_username_not_found(tr::now, lt_user, token)), + Ui::LayerOption::CloseOther); + })).send(); + return true; + }, [&](const TLDinternalLinkTypeVideoChat &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = data.vchat_username().v, + .voicechatHash = data.vinvite_hash().v, + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeWebApp &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = data.vbot_username().v, + .resolveType = Window::ResolveType::BotApp, + .startToken = data.vstart_parameter().v, + .botAppName = data.vweb_app_short_name().v, + .botAppForceConfirmation = my.mayShowConfirmation, + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeStory &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = data.vstory_sender_username().v, + .storyId = data.vstory_id().v, + .resolveType = Window::ResolveType::Default, + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }); +} + } // namespace Core diff --git a/Telegram/SourceFiles/core/local_url_handlers.h b/Telegram/SourceFiles/core/local_url_handlers.h index 9a5cc6a55e1ed..9f05b5710353f 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.h +++ b/Telegram/SourceFiles/core/local_url_handlers.h @@ -7,6 +7,10 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLinternalLinkType; +} // namespace Tdb + namespace qthelp { class RegularExpressionMatch; } // namespace qthelp @@ -25,7 +29,9 @@ struct LocalUrlHandler { const QVariant &context)> handler; }; +#if 0 // mtp [[nodiscard]] const std::vector &LocalUrlHandlers(); +#endif [[nodiscard]] const std::vector &InternalUrlHandlers(); [[nodiscard]] QString TryConvertUrlToLocal(QString url); @@ -34,4 +40,22 @@ struct LocalUrlHandler { [[nodiscard]] bool StartUrlRequiresActivate(const QString &url); +struct OpenFromExternalContext { +}; +struct StartAttachContext { + Window::SessionController *controller = nullptr; + QString botUsername; + QString openWebAppUrl; + + explicit operator bool() const { + return !botUsername.isEmpty(); + } +}; +bool HandleLocalUrl( + const Tdb::TLinternalLinkType &link, + const QVariant &context); + } // namespace Core + +Q_DECLARE_METATYPE(Core::OpenFromExternalContext); +Q_DECLARE_METATYPE(Core::StartAttachContext); diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index ba2593e7b5b6c..71cee0e44560d 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -30,9 +30,18 @@ For license and copyright information please follow this link: #include "window/window_session_controller.h" #include "mainwindow.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" +#include "core/local_url_handlers.h" +#include "main/main_domain.h" +#include "window/window_session_controller.h" + namespace Core { namespace { +using namespace Tdb; + +#if 0 // mtp const auto kGoodPrefix = u"https://"_q; const auto kBadPrefix = u"http://"_q; @@ -91,6 +100,7 @@ const auto kBadPrefix = u"http://"_q; UrlAuthBox::Activate(&account.session(), good, context); return true; } +#endif [[nodiscard]] QString OpenGLCheckFilePath() { return cWorkingDir() + "tdata/opengl_crash_check"; @@ -218,6 +228,50 @@ std::shared_ptr UiIntegration::createLinkHandler( bool UiIntegration::handleUrlClick( const QString &url, const QVariant &context) { + const auto isEmail = UrlClickHandler::IsEmail(url); + const auto isInternal = !isEmail + && url.startsWith(u"internal:"_q, Qt::CaseInsensitive); + const auto window = context.value().sessionWindow; + const auto session = (isEmail || isInternal) + ? nullptr + : window.get() + ? &window->session() + : Core::App().domain().started() + ? (Core::App().domain().active().sessionExists() + ? &Core::App().domain().active().session() + : nullptr) + : nullptr; + const auto sender = (isEmail || isInternal) + ? nullptr + : session + ? &session->sender() + : Core::App().domain().started() + ? &Core::App().domain().active().sender() + : nullptr; + const auto my = context.value(); + const auto skip = my.skipBotAutoLogin; + const auto confirm = my.mayShowConfirmation; + if (sender) { + const auto fallback = [=, weak = base::make_weak(session)] { + if (const auto strong = skip ? nullptr : weak.get()) { + UrlAuthBox::Activate(strong, url, context); + } else if (confirm) { + HiddenUrlClickHandler::Confirm(url, context, true); + } else { + File::OpenUrl(url); + } + }; + sender->request(TLgetInternalLinkType( + tl_string(url) + )).done([=](const TLinternalLinkType &result) { + if (!Core::HandleLocalUrl(result, context)) { + fallback(); + } + }).fail(fallback).send(); + return true; + } + +#if 0 // mtp const auto local = Core::TryConvertUrlToLocal(url); if (Core::InternalPassportLink(local)) { return true; @@ -240,6 +294,16 @@ bool UiIntegration::handleUrlClick( if (skip || !BotAutoLogin(url, domain, context)) { File::OpenUrl(UrlWithAutoLoginToken(url, std::move(parsed), domain)); } +#endif + if (isEmail) { + File::OpenEmailLink(url); + } else if (isInternal) { + Core::App().openInternalUrl(url, context); + } else if (confirm) { + HiddenUrlClickHandler::Confirm(url, context, false); + } else { + File::OpenUrl(url); + } return true; } diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index 43cbbdaeb1bf7..682ea4bfb5367 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -29,6 +29,9 @@ For license and copyright information please follow this link: #include "settings/settings_intro.h" #include "ui/layers/box_content.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_account.h" + #include #include @@ -203,11 +206,17 @@ class MtpChecker : public Checker { using FileLocation = MTP::DedicatedLoader::Location; using Checker::fail; + [[nodiscard]] Fn failHandler(); +#if 0 // mtp Fn failHandler(); void gotMessage(const MTPmessages_Messages &result); std::optional parseMessage( const MTPmessages_Messages &result) const; +#endif + void gotMessage(const Tdb::TLmessages &result); + [[nodiscard]] std::optional parseMessage( + const Tdb::TLmessages &result) const; std::optional parseText(const QByteArray &text) const; FileLocation validateLatestLocation( uint64 availableVersion, @@ -215,6 +224,8 @@ class MtpChecker : public Checker { MTP::WeakInstance _mtp; + rpl::lifetime _updatesLifetime; + }; std::shared_ptr GetUpdaterInstance() { @@ -242,13 +253,7 @@ QString FindUpdateFile() { const auto list = updates.entryInfoList(QDir::Files); for (const auto &info : list) { if (QRegularExpression( - "^(" - "tupdate|" - "tx64upd|" - "tmacupd|" - "tarmacupd|" - "tlinuxupd|" - ")\\d+(_[a-z\\d]+)?$", + "^update-(win|mac|linux)-(x86|x64|arm)-\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption ).match(info.fileName()).hasMatch()) { return info.absoluteFilePath(); @@ -257,6 +262,12 @@ QString FindUpdateFile() { return QString(); } +bool ChannelSyncedUpdate(const Tdb::TLupdate &update) { + return (update.type() == Tdb::id_updateHavePendingNotifications) + && !update.c_updateHavePendingNotifications( + ).vhave_unreceived_notifications().v; +} + QString ExtractFilename(const QString &url) { const auto expression = QRegularExpression(u"/([^/\\?]+)(\\?|$)"_q); if (const auto match = expression.match(url); match.hasMatch()) { @@ -932,10 +943,39 @@ void MtpChecker::start() { crl::on_main(this, [=] { fail(); }); return; } + + using namespace Tdb; + const auto updaterVersion = Platform::AutoUpdateVersion(); const auto feed = "tdhbcfeed" + (updaterVersion > 1 ? QString::number(updaterVersion) : QString()); + _updatesLifetime.destroy(); MTP::ResolveChannel(&_mtp, feed, [=]( + ChannelId id) { + const auto peerId = peerFromChannel(id); + _mtp.send(TLopenChat(peerToTdbChat(peerId)), [=](const TLok &) { + _updatesLifetime = _mtp.session()->tdb().updates( + ) | rpl::filter([=](const TLupdate &update) { + return ChannelSyncedUpdate(update); + }) | rpl::start_with_next([=] { + _updatesLifetime.destroy(); + + _mtp.send(TLgetChatHistory( + peerToTdbChat(peerId), + tl_int53(0), // from_message_id + tl_int32(0), // offset + tl_int32(1), // limit + tl_bool(false) // only_local + ), [=](const TLmessages &result) { + gotMessage(result); + }, failHandler()); + + _mtp.send(TLcloseChat(peerToTdbChat(peerId)), [=]( + const TLok &) { + }, [=](const Tdb::Error &) {}); + }); + }, failHandler()); +#if 0 // mtp const MTPInputChannel &channel) { _mtp.send( MTPmessages_GetHistory( @@ -951,9 +991,11 @@ void MtpChecker::start() { MTP_long(0)), // hash [=](const MTPmessages_Messages &result) { gotMessage(result); }, failHandler()); +#endif }, [=] { fail(); }); } +#if 0 // mtp void MtpChecker::gotMessage(const MTPmessages_Messages &result) { const auto location = parseMessage(result); if (!location) { @@ -982,6 +1024,43 @@ auto MtpChecker::parseMessage(const MTPmessages_Messages &result) const } return parseText(message->c_message().vmessage().v); } +#endif + +void MtpChecker::gotMessage(const Tdb::TLmessages &result) { + const auto location = parseMessage(result); + if (!location) { + fail(); + return; + } else if (location->username.isEmpty()) { + done(nullptr); + return; + } + const auto ready = [=](std::unique_ptr loader) { + if (loader) { + done(std::move(loader)); + } else { + fail(); + } + }; + const_cast(*location).lowPriority = true; + MTP::StartDedicatedLoader(&_mtp, *location, UpdatesFolder(), ready); +} + +auto MtpChecker::parseMessage(const Tdb::TLmessages &result) const +-> std::optional { + const auto &list = result.data().vmessages().v; + if (list.empty() || !list.front()) { + LOG(("Update Error: MTP feed message not found.")); + return std::nullopt; + } + const auto &content = list.front()->data().vcontent(); + if (content.type() != Tdb::id_messageText) { + LOG(("Update Error: MTP feed message has no text.")); + return std::nullopt; + } + return parseText( + content.c_messageText().vtext().data().vtext().v.toUtf8()); +} auto MtpChecker::parseText(const QByteArray &text) const -> std::optional { @@ -1040,10 +1119,16 @@ auto MtpChecker::validateLatestLocation( return (availableVersion <= myVersion) ? FileLocation() : location; } +#if 0 // mtp Fn MtpChecker::failHandler() { return [=](const MTP::Error &error) { LOG(("Update Error: MTP check failed with '%1'" ).arg(QString::number(error.code()) + ':' + error.type())); +#endif +Fn MtpChecker::failHandler() { + return [=](const Tdb::Error &error) { + LOG(("Update Error: MTP check failed with '%1'" + ).arg(QString::number(error.code) + ':' + error.message)); fail(); }; } diff --git a/Telegram/SourceFiles/countries/countries_manager.cpp b/Telegram/SourceFiles/countries/countries_manager.cpp index 5b8c30c99b88c..62d47e1d622e2 100644 --- a/Telegram/SourceFiles/countries/countries_manager.cpp +++ b/Telegram/SourceFiles/countries/countries_manager.cpp @@ -14,6 +14,8 @@ For license and copyright information please follow this link: #include "main/main_domain.h" #include "mtproto/mtp_instance.h" +#include "tdb/tdb_tl_scheme.h" + #include namespace Countries { @@ -149,6 +151,7 @@ Manager::Manager(not_null domain) : _path(cWorkingDir() + "tdata/countries") { read(); +#if 0 // goodToRemove const auto mtpLifetime = _lifetime.make_state(); domain->activeValue( ) | rpl::filter([=](Main::Account *account) { @@ -162,6 +165,17 @@ Manager::Manager(not_null domain) }, [=] { _api.reset(); }, _lifetime); +#endif + Core::App().domain().activeValue( + ) | rpl::filter([=](Main::Account *account) { + return (account != nullptr); + }) | rpl::start_with_next_done([=](Main::Account *account) { + _api.emplace(account->sender()); + + request(); + }, [=] { + _api.reset(); + }, _lifetime); } void Manager::read() { @@ -192,6 +206,7 @@ void Manager::write() const { void Manager::request() { Expects(_api.has_value()); +#if 0 // goodToRemove const auto convertMTP = [](const auto &vector, bool force = false) { if (!vector) { return std::vector(force ? 1 : 0); @@ -246,6 +261,42 @@ void Manager::request() { ).arg(error.type())); _lifetime.destroy(); }).send(); +#endif + _api->request(Tdb::TLgetCountries( + )).done([=](const Tdb::TLDcountries &data) { + auto infos = std::vector(); + + for (const auto &country : data.vcountries().v) { + + const auto &countryData = country.data(); + if (countryData.vis_hidden().v) { + continue; + } + + auto info = Info(ProcessAlternativeName({ + .name = countryData.venglish_name().v, + .iso2 = countryData.vcountry_code().v, + .isHidden = countryData.vis_hidden().v, + })); + for (const auto &code : countryData.vcalling_codes().v) { + info.codes.push_back(CallingCodeInfo{ + .callingCode = code.v, + .prefixes = {}, // Is not provided by TDLib. + .patterns = {}, // Is not provided by TDLib. + }); + } + + infos.push_back(std::move(info)); + } + + Instance().setList(std::move(infos)); + write(); + _lifetime.destroy(); + }).fail([=](const Tdb::Error &error) { + LOG(("API Error: getting countries failed with error %1" + ).arg(error.message)); + _lifetime.destroy(); + }).send(); } rpl::lifetime &Manager::lifetime() { diff --git a/Telegram/SourceFiles/countries/countries_manager.h b/Telegram/SourceFiles/countries/countries_manager.h index 1ce91307852d3..76c92dab8f959 100644 --- a/Telegram/SourceFiles/countries/countries_manager.h +++ b/Telegram/SourceFiles/countries/countries_manager.h @@ -7,7 +7,7 @@ For license and copyright information please follow this link: */ #pragma once -#include "mtproto/sender.h" +#include "tdb/tdb_sender.h" namespace Main { class Domain; @@ -28,7 +28,10 @@ class Manager final { private: void request(); +#if 0 // goodToRemove std::optional _api; +#endif + std::optional _api; const QString _path; int _hash = 0; diff --git a/Telegram/SourceFiles/data/data_bot_app.cpp b/Telegram/SourceFiles/data/data_bot_app.cpp index ff9705cb64080..b39096401039b 100644 --- a/Telegram/SourceFiles/data/data_bot_app.cpp +++ b/Telegram/SourceFiles/data/data_bot_app.cpp @@ -7,7 +7,22 @@ For license and copyright information please follow this link: */ #include "data/data_bot_app.h" +#include "tdb/tdb_tl_scheme.h" +#include "data/data_session.h" + BotAppData::BotAppData(not_null owner, const BotAppId &id) : owner(owner) , id(id) { } + +void BotAppData::apply(PeerId bot, const Tdb::TLwebApp &app) { + const auto &data = app.data(); + botId = bot; + shortName = data.vshort_name().v; + title = data.vtitle().v; + description = data.vdescription().v; + photo = owner->processPhoto(data.vphoto()); + if (const auto animation = data.vanimation()) { + document = owner->processDocument(*animation); + } +} diff --git a/Telegram/SourceFiles/data/data_bot_app.h b/Telegram/SourceFiles/data/data_bot_app.h index 7de4aefe86e2e..69f8b8ce8aa9e 100644 --- a/Telegram/SourceFiles/data/data_bot_app.h +++ b/Telegram/SourceFiles/data/data_bot_app.h @@ -10,9 +10,15 @@ For license and copyright information please follow this link: #include "data/data_photo.h" #include "data/data_document.h" +namespace Tdb { +class TLwebApp; +} // namespace Tdb + struct BotAppData { BotAppData(not_null owner, const BotAppId &id); + void apply(PeerId bot, const Tdb::TLwebApp &app); + const not_null owner; BotAppId id = 0; PeerId botId = 0; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 5daff7e176115..b9ce0ee4848f9 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -32,11 +32,13 @@ For license and copyright information please follow this link: #include "main/main_session.h" #include "api/api_chat_invite.h" #include "api/api_invite_links.h" +#include "tdb/tdb_tl_scheme.h" #include "apiwrap.h" #include "window/notifications_manager.h" namespace { +using namespace Tdb; using UpdateFlag = Data::PeerUpdate::Flag; } // namespace @@ -88,11 +90,15 @@ std::unique_ptr MegagroupInfo::takeForumData() { ChannelData::ChannelData(not_null owner, PeerId id) : PeerData(owner, id) +{ +#if 0 // mtp , inputChannel( MTP_inputChannel(MTP_long(peerToChannel(id).bare), MTP_long(0))) , _ptsWaiter(&owner->session().updates()) { +#endif } +#if 0 // mtp void ChannelData::setPhoto(const MTPChatPhoto &photo) { photo.match([&](const MTPDchatPhoto & data) { updateUserpic( @@ -103,6 +109,11 @@ void ChannelData::setPhoto(const MTPChatPhoto &photo) { clearUserpic(); }); } +#endif + +void ChannelData::setPhoto(const TLchatPhotoInfo &photo) { + updateUserpic(photo); +} void ChannelData::setName( const QString &newName, @@ -143,6 +154,7 @@ const std::vector &ChannelData::usernames() const { return _username.usernames(); } +#if 0 // mtp void ChannelData::setAccessHash(uint64 accessHash) { access = accessHash; input = MTP_inputPeerChannel( @@ -152,6 +164,7 @@ void ChannelData::setAccessHash(uint64 accessHash) { MTP_long(peerToChannel(id).bare), MTP_long(accessHash)); } +#endif void ChannelData::setFlags(ChannelDataFlags which) { const auto diff = flags() ^ which; @@ -213,12 +226,16 @@ bool ChannelData::canHaveInviteLink() const { || (adminRights() & AdminRight::InviteByLinkOrAdd); } +#if 0 // mtp void ChannelData::setLocation(const MTPChannelLocation &data) { +#endif +void ChannelData::setLocation(const TLchatLocation *location) { if (!mgInfo) { return; } const auto was = mgInfo->getLocation(); const auto wasValue = was ? *was : ChannelLocation(); +#if 0 // mtp data.match([&](const MTPDchannelLocation &data) { data.vgeo_point().match([&](const MTPDgeoPoint &point) { mgInfo->setLocation({ @@ -231,6 +248,16 @@ void ChannelData::setLocation(const MTPChannelLocation &data) { }, [&](const MTPDchannelLocationEmpty &) { mgInfo->setLocation(ChannelLocation()); }); +#endif + if (location) { + const auto &data = location->data(); + mgInfo->setLocation({ + data.vaddress().v, + Data::LocationPoint(data.vlocation()) + }); + } else { + mgInfo->setLocation(ChannelLocation()); + } const auto now = mgInfo->getLocation(); const auto nowValue = now ? *now : ChannelLocation(); if (was != now || (was && wasValue != nowValue)) { @@ -297,6 +324,7 @@ void ChannelData::setKickedCount(int newKickedCount) { } } +#if 0 // mtp void ChannelData::setPendingRequestsCount( int count, const QVector &recentRequesters) { @@ -306,6 +334,7 @@ void ChannelData::setPendingRequestsCount( return UserId(value); }) | ranges::to_vector); } +#endif void ChannelData::setPendingRequestsCount( int count, @@ -476,6 +505,35 @@ void ChannelData::applyEditBanned( } void ChannelData::markForbidden() { + auto wasInChannel = amIn(); + auto wasViewAdmins = canViewAdmins(); + auto wasViewMembers = canViewMembers(); + auto wasAddMembers = canAddMembers(); + + setFlags(this->flags() | ChannelDataFlag::Forbidden); + if (hasAdminRights()) { + setAdminRights(ChatAdminRights()); + } + if (hasRestrictions()) { + setRestrictions(ChatRestrictionsInfo()); + } + clearUserpic(); + date = 0; + setMembersCount(0); + + auto flags = UpdateFlag(0) | UpdateFlag(0); + if (wasInChannel != amIn()) { + flags |= UpdateFlag::ChannelAmIn; + } + if (wasViewAdmins != canViewAdmins() + || wasViewMembers != canViewMembers() + || wasAddMembers != canAddMembers()) { + flags |= UpdateFlag::Rights; + } + if (flags) { + session().changes().peerUpdated(this, flags); + } +#if 0 // mtp owner().processChat(MTP_channelForbidden( MTP_flags(isMegagroup() ? MTPDchannelForbidden::Flag::f_megagroup @@ -484,6 +542,7 @@ void ChannelData::markForbidden() { MTP_long(access), MTP_string(name()), MTPint())); +#endif } bool ChannelData::isGroupAdmin(not_null user) const { @@ -835,6 +894,7 @@ void ChannelData::migrateCall(std::unique_ptr call) { addFlags(Flag::CallActive); } +#if 0 // goodToRemove void ChannelData::setGroupCall( const MTPInputGroupCall &call, TimeId scheduleDate, @@ -863,6 +923,33 @@ void ChannelData::setGroupCall( addFlags(Flag::CallActive); }); } +#endif + +void ChannelData::setGroupCall( + CallId callId, + TimeId scheduleDate, + bool rtmp) { + if (_call && _call->id() == callId) { + return; + } else if (!_call && !callId) { + return; + } else if (!callId) { + clearGroupCall(); + return; + } + const auto hasCall = (_call != nullptr); + if (hasCall) { + owner().unregisterGroupCall(_call.get()); + } + _call = std::make_unique( + this, + callId, + scheduleDate, + rtmp); + owner().registerGroupCall(_call.get()); + session().changes().peerUpdated(this, UpdateFlag::GroupCall); + addFlags(Flag::CallActive); +} void ChannelData::clearGroupCall() { if (!_call) { @@ -892,7 +979,9 @@ void ChannelData::setAllowedReactions(Data::AllowedReactions value) { _allowedReactions = std::move(value); const auto now = enabled(_allowedReactions); if (was != now) { +#if 0 // mtp owner().reactions().updateAllInHistory(this, now); +#endif } session().changes().peerUpdated(this, UpdateFlag::Reactions); } @@ -934,11 +1023,13 @@ void ChannelData::setStoriesState(StoriesState state) { } } +#if 0 // mtp void ChannelData::processTopics(const MTPVector &topics) { if (const auto forum = this->forum()) { forum->applyReceivedTopics(topics); } } +#endif namespace Data { @@ -951,6 +1042,7 @@ void ApplyMigration( channel->setMigrateFromChat(chat); } +#if 0 // mtp void ApplyChannelUpdate( not_null channel, const MTPDupdateChatDefaultBannedRights &update) { @@ -1133,5 +1225,116 @@ void ApplyChannelUpdate( // For clearUpTill() call. channel->owner().sendHistoryChangeNotifications(); } +#endif + +void ApplyChannelUpdate( + not_null channel, + const TLDsupergroupFullInfo &update) { + const auto session = &channel->session(); + + auto canViewAdmins = channel->canViewAdmins(); + auto canViewMembers = channel->canViewMembers(); + auto canEditStickers = channel->canEditStickers(); + + //TTL of messages goes from updates. + + using Flag = ChannelDataFlag; + const auto mask = Flag::CanSetUsername + | Flag::CanViewParticipants + | Flag::CanSetStickers + | Flag::PreHistoryHidden + | Flag::AntiSpam + | Flag::Location + | Flag::CanHideMembers + | Flag::CanEnableAntiSpam + | Flag::CanGetStatistics; + channel->setFlags((channel->flags() & ~mask) + | Flag::CanSetUsername // Creators can always set usernames. + | (update.vcan_get_members().v ? Flag::CanViewParticipants : Flag()) + | (update.vcan_set_sticker_set().v ? Flag::CanSetStickers : Flag()) + | (update.vis_all_history_available().v + ? Flag::PreHistoryHidden + : Flag()) + | (update.vhas_aggressive_anti_spam_enabled().v + ? Flag::AntiSpam + : Flag()) + | (update.vlocation() ? Flag::Location : Flag()) + | (update.vhas_hidden_members().v + ? Flag::ParticipantsHidden + : Flag()) + | (update.vcan_hide_members().v ? Flag::CanHideMembers : Flag()) + | (update.vcan_toggle_aggressive_anti_spam().v + ? Flag::CanEnableAntiSpam + : Flag()) + | (update.vcan_get_statistics().v ? Flag::CanGetStatistics : Flag())); + + if (const auto photo = update.vphoto()) { + channel->setPhotoFull(*photo); + } + if (const auto from = update.vupgraded_from_basic_group_id().v) { + channel->addFlags(ChannelDataFlag::Megagroup); + const auto chat = channel->owner().chat(ChatId(from)); + Data::ApplyMigration(chat, channel); + } + channel->setAbout(update.vdescription().v); + channel->setMembersCount(update.vmember_count().v); + channel->setAdminsCount(update.vadministrator_count().v); + channel->setRestrictedCount(update.vrestricted_count().v); + channel->setKickedCount(update.vbanned_count().v); + + channel->setSlowmodeSeconds(update.vslow_mode_delay().v); + if (const auto in = update.vslow_mode_delay_expires_in().v; in > 0.) { + // later better slowmode management + channel->growSlowmodeLastMessage( + base::unixtime::now() + int(base::SafeRound(in * 1000)) - channel->slowmodeSeconds()); + } + + if (const auto invite = update.vinvite_link()) { + channel->session().api().inviteLinks().setMyPermanent( + channel, + *invite); + } else { + channel->session().api().inviteLinks().clearMyPermanent(channel); + } + channel->setLocation(update.vlocation()); + + const auto linkedChatId = peerToChannel( + peerFromTdbChat(update.vlinked_chat_id())); + channel->setLinkedChat(linkedChatId + ? channel->owner().channelLoaded(linkedChatId) + : nullptr); + if (channel->isMegagroup()) { + auto commands = ranges::views::all( + update.vbot_commands().v + ) | ranges::views::transform( + Data::BotCommandsFromTL + ) | ranges::to_vector; + if (channel->mgInfo->setBotCommands(std::move(commands))) { + channel->owner().botCommandsChanged(channel); + } + + const auto newSetId = uint64(update.vsticker_set_id().v); + const auto oldSetId = channel->mgInfo->stickerSet.id; + const auto stickersChanged = (canEditStickers != channel->canEditStickers()) + || (oldSetId != newSetId); + if (oldSetId != newSetId) { + channel->mgInfo->stickerSet = StickerSetIdentifier{ + .id = newSetId, + }; + } + if (stickersChanged) { + session->changes().peerUpdated(channel, UpdateFlag::StickersSet); + } + } + channel->fullUpdated(); + + if (canViewAdmins != channel->canViewAdmins() + || canViewMembers != channel->canViewMembers()) { + session->changes().peerUpdated(channel, UpdateFlag::Rights); + } + + // For clearUpTill() call. + channel->owner().sendHistoryChangeNotifications(); +} } // namespace Data diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index bdf0f42054386..fd17b88d8d72a 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -14,6 +14,11 @@ For license and copyright information please follow this link: #include "data/data_peer_bot_commands.h" #include "data/data_user_names.h" +namespace Tdb { +class TLDsupergroupFullInfo; +class TLchatLocation; +} // namespace Tdb + struct ChannelLocation { QString address; Data::LocationPoint point; @@ -63,6 +68,9 @@ enum class ChannelDataFlag { HasActiveStories = (1 << 27), HasUnreadStories = (1 << 28), CanGetStatistics = (1 << 29), + + CanHideMembers = (1 << 30), + CanEnableAntiSpam = (1 << 31), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; @@ -156,8 +164,13 @@ class ChannelData final : public PeerData { void setName(const QString &name, const QString &username); void setUsername(const QString &username); void setUsernames(const Data::Usernames &newUsernames); + +#if 0 // mtp void setPhoto(const MTPChatPhoto &photo); void setAccessHash(uint64 accessHash); +#endif + + void setPhoto(const Tdb::TLchatPhotoInfo &photo); void setFlags(ChannelDataFlags which); void addFlags(ChannelDataFlags which); @@ -202,9 +215,11 @@ class ChannelData final : public PeerData { [[nodiscard]] const std::vector &recentRequesters() const { return _recentRequesters; } +#if 0 // mtp void setPendingRequestsCount( int count, const QVector &recentRequesters); +#endif void setPendingRequestsCount( int count, std::vector recentRequesters); @@ -359,13 +374,17 @@ class ChannelData final : public PeerData { } [[nodiscard]] bool canHaveInviteLink() const; +#if 0 // mtp void setLocation(const MTPChannelLocation &data); +#endif + void setLocation(const Tdb::TLchatLocation *location); [[nodiscard]] const ChannelLocation *getLocation() const; void setLinkedChat(ChannelData *linked); [[nodiscard]] ChannelData *linkedChat() const; [[nodiscard]] bool linkedChatKnown() const; +#if 0 // mtp void ptsInit(int32 pts) { _ptsWaiter.init(pts); } @@ -409,6 +428,7 @@ class ChannelData final : public PeerData { [[nodiscard]] bool ptsWaitingForShortPoll() const { return _ptsWaiter.waitingForShortPoll(); } +#endif void setUnavailableReasons( std::vector &&reason); @@ -436,10 +456,16 @@ class ChannelData final : public PeerData { return _call.get(); } void migrateCall(std::unique_ptr call); +#if 0 // goodToRemove void setGroupCall( const MTPInputGroupCall &call, TimeId scheduleDate = 0, bool rtmp = false); +#endif + void setGroupCall( + CallId callId, + TimeId scheduleDate = 0, + bool rtmp = false); void clearGroupCall(); void setGroupCallDefaultJoinAs(PeerId peerId); [[nodiscard]] PeerId groupCallDefaultJoinAs() const; @@ -455,20 +481,24 @@ class ChannelData final : public PeerData { return mgInfo ? mgInfo->forum() : nullptr; } +#if 0 // mtp void processTopics(const MTPVector &topics); +#endif // Still public data members. uint64 access = 0; +#if 0 // mtp MTPinputChannel inputChannel = MTP_inputChannelEmpty(); +#endif int32 date = 0; std::unique_ptr mgInfo; - // > 0 - user who invited me to channel, < 0 - not in channel. UserId inviter = 0; TimeId inviteDate = 0; bool inviteViaRequest = false; + bool inviterLoaded = false; private: struct InvitePeek { @@ -482,7 +512,9 @@ class ChannelData final : public PeerData { Flags _flags = ChannelDataFlags(Flag::Forbidden); +#if 0 // mtp PtsWaiter _ptsWaiter; +#endif Data::UsernamesInfo _username; @@ -520,6 +552,7 @@ void ApplyMigration( not_null chat, not_null channel); +#if 0 // mtp void ApplyChannelUpdate( not_null channel, const MTPDupdateChatDefaultBannedRights &update); @@ -527,5 +560,10 @@ void ApplyChannelUpdate( void ApplyChannelUpdate( not_null channel, const MTPDchannelFull &update); +#endif + +void ApplyChannelUpdate( + not_null channel, + const Tdb::TLDsupergroupFullInfo &update); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 1e19a00d1b407..7278bb5903ca2 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -16,18 +16,23 @@ For license and copyright information please follow this link: #include "data/notify/data_notify_settings.h" #include "history/history.h" #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" #include "apiwrap.h" #include "api/api_invite_links.h" namespace { +using namespace Tdb; using UpdateFlag = Data::PeerUpdate::Flag; } // namespace ChatData::ChatData(not_null owner, PeerId id) : PeerData(owner, id) +{ +#if 0 // mtp , inputChat(MTP_long(peerToChat(id).bare)) { +#endif _flags.changes( ) | rpl::start_with_next([=](const Flags::Change &change) { if (change.diff & Flag::CallNotEmpty) { @@ -38,6 +43,7 @@ ChatData::ChatData(not_null owner, PeerId id) }, _lifetime); } +#if 0 // mtp void ChatData::setPhoto(const MTPChatPhoto &photo) { photo.match([&](const MTPDchatPhoto &data) { updateUserpic( @@ -48,6 +54,11 @@ void ChatData::setPhoto(const MTPChatPhoto &photo) { clearUserpic(); }); } +#endif + +void ChatData::setPhoto(const TLchatPhotoInfo &photo) { + updateUserpic(photo); +} ChatAdminRightsInfo ChatData::defaultAdminRights(not_null user) { const auto isCreator = (creator == peerToUser(user->id)) @@ -196,6 +207,7 @@ void ChatData::setMigrateToChannel(ChannelData *channel) { } } +#if 0 // goodToRemove void ChatData::setGroupCall( const MTPInputGroupCall &call, TimeId scheduleDate, @@ -227,6 +239,33 @@ void ChatData::setGroupCall( addFlags(Flag::CallActive); }); } +#endif + +void ChatData::setGroupCall(CallId callId, TimeId scheduleDate, bool rtmp) { + if (migrateTo()) { + return; + } + if (_call && _call->id() == callId) { + return; + } else if (!_call && !callId) { + return; + } else if (!callId) { + clearGroupCall(); + return; + } + const auto hasCall = (_call != nullptr); + if (hasCall) { + owner().unregisterGroupCall(_call.get()); + } + _call = std::make_unique( + this, + callId, + scheduleDate, + rtmp); + owner().registerGroupCall(_call.get()); + session().changes().peerUpdated(this, UpdateFlag::GroupCall); + addFlags(ChatDataFlag::CallActive); +} void ChatData::clearGroupCall() { if (!_call) { @@ -255,6 +294,7 @@ void ChatData::setBotCommands(const std::vector &list) { } } +#if 0 // mtp void ChatData::setPendingRequestsCount( int count, const QVector &recentRequesters) { @@ -264,6 +304,7 @@ void ChatData::setPendingRequestsCount( return UserId(value); }) | ranges::to_vector); } +#endif void ChatData::setPendingRequestsCount( int count, @@ -286,7 +327,9 @@ void ChatData::setAllowedReactions(Data::AllowedReactions value) { _allowedReactions = std::move(value); const auto now = enabled(_allowedReactions); if (was != now) { +#if 0 // mtp owner().reactions().updateAllInHistory(this, now); +#endif } session().changes().peerUpdated(this, UpdateFlag::Reactions); } @@ -298,6 +341,7 @@ const Data::AllowedReactions &ChatData::allowedReactions() const { namespace Data { +#if 0 // mtp void ApplyChatUpdate( not_null chat, const MTPDupdateChatParticipants &update) { @@ -564,5 +608,102 @@ void ApplyChatUpdate( UpdateFlag::Members | UpdateFlag::Admins); }); } +#endif + +void ApplyChatUpdate( + not_null chat, + const TLDbasicGroupFullInfo &update) { + ApplyChatUpdate(chat, update.vmembers().v); + + chat->creator = UserId(update.vcreator_user_id()); + { + auto &&commands = ranges::views::all( + update.vbot_commands().v + ) | ranges::views::transform(Data::BotCommandsFromTL); + chat->setBotCommands(std::move(commands) | ranges::to_vector); + } + + using Flag = ChatDataFlag; + const auto mask = Flag::CanHideMembers + | Flag::CanSetUsername + | Flag::CanEnableAntiSpam; + chat->setFlags((chat->flags() & ~mask) + | (update.vcan_hide_members().v ? Flag::CanHideMembers : Flag()) + | Flag::CanSetUsername // Creators can always set usernames. + | (update.vcan_toggle_aggressive_anti_spam().v + ? Flag::CanEnableAntiSpam + : Flag())); + + if (const auto photo = update.vphoto()) { + chat->setPhotoFull(*photo); + } + if (const auto invite = update.vinvite_link()) { + chat->session().api().inviteLinks().setMyPermanent(chat, *invite); + } else { + chat->session().api().inviteLinks().clearMyPermanent(chat); + } + chat->fullUpdated(); + chat->setAbout(update.vdescription().v); +} + +void ApplyChatUpdate( + not_null chat, + const QVector &update) { + const auto session = &chat->session(); + if (update.isEmpty()) { + chat->count = -1; + chat->invalidateParticipants(); + return; + } + chat->count = update.size(); + chat->participants.clear(); + chat->invitedByMe.clear(); + chat->admins.clear(); + chat->setAdminRights(ChatAdminRights()); + const auto selfUserId = session->userId(); + for (const auto &participant : update) { + const auto &data = participant.data(); + const auto userId = peerFromSender(data.vmember_id()); + const auto userPeer = chat->owner().peerLoaded(userId); + const auto user = userPeer ? userPeer->asUser() : nullptr; + if (!user) { + --chat->count; + continue; + } + chat->participants.emplace(user); + const auto inviterId = UserId(data.vinviter_user_id().v); + if (inviterId == selfUserId) { + chat->invitedByMe.insert(user); + } + data.vstatus().match([&](const TLDchatMemberStatusCreator &data) { + chat->creator = peerToUser(userId); + }, [&](const TLDchatMemberStatusAdministrator &data) { + chat->admins.emplace(user); + if (user->isSelf()) { + chat->setAdminRights( + chat->defaultAdminRights(user).flags); + } + }, [](const auto &) { + }); + } + if (chat->participants.empty()) { + return; + } + if (const auto history = chat->owner().historyLoaded(chat)) { + if (history->lastKeyboardFrom) { + const auto i = ranges::find( + chat->participants, + history->lastKeyboardFrom, + &UserData::id); + if (i == end(chat->participants)) { + history->clearLastKeyboard(); + } + } + } + chat->refreshBotStatus(); + session->changes().peerUpdated( + chat, + UpdateFlag::Members | UpdateFlag::Admins); +} } // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat.h b/Telegram/SourceFiles/data/data_chat.h index 93202608a3156..fa3f3f40a7230 100644 --- a/Telegram/SourceFiles/data/data_chat.h +++ b/Telegram/SourceFiles/data/data_chat.h @@ -13,6 +13,11 @@ For license and copyright information please follow this link: enum class ChatAdminRight; +namespace Tdb { +class TLDbasicGroupFullInfo; +class TLchatMember; +} // namespace Tdb + enum class ChatDataFlag { Left = (1 << 0), //Kicked = (1 << 1), @@ -23,6 +28,9 @@ enum class ChatDataFlag { CallNotEmpty = (1 << 6), CanSetUsername = (1 << 7), NoForwards = (1 << 8), + + CanHideMembers = (1 << 9), + CanEnableAntiSpam = (1 << 10), }; inline constexpr bool is_flag_type(ChatDataFlag) { return true; }; using ChatDataFlags = base::flags; @@ -35,7 +43,11 @@ class ChatData final : public PeerData { ChatData(not_null owner, PeerId id); void setName(const QString &newName); +#if 0 // mtp void setPhoto(const MTPChatPhoto &photo); +#endif + + void setPhoto(const Tdb::TLchatPhotoInfo &photo); void invalidateParticipants(); [[nodiscard]] bool noParticipantInfo() const { @@ -139,10 +151,16 @@ class ChatData final : public PeerData { [[nodiscard]] Data::GroupCall *groupCall() const { return _call.get(); } +#if 0 // goodToRemove void setGroupCall( const MTPInputGroupCall &call, TimeId scheduleDate = 0, bool rtmp = false); +#endif + void setGroupCall( + CallId callId, + TimeId scheduleDate = 0, + bool rtmp = false); void clearGroupCall(); void setGroupCallDefaultJoinAs(PeerId peerId); [[nodiscard]] PeerId groupCallDefaultJoinAs() const; @@ -158,9 +176,11 @@ class ChatData final : public PeerData { [[nodiscard]] const std::vector &recentRequesters() const { return _recentRequesters; } +#if 0 // mtp void setPendingRequestsCount( int count, const QVector &recentRequesters); +#endif void setPendingRequestsCount( int count, std::vector recentRequesters); @@ -168,11 +188,17 @@ class ChatData final : public PeerData { void setAllowedReactions(Data::AllowedReactions value); [[nodiscard]] const Data::AllowedReactions &allowedReactions() const; +#if 0 // mtp // Still public data members. const MTPlong inputChat; +#endif int count = 0; + +#if 1 // mtp? TimeId date = 0; +#endif + UserId creator = 0; base::flat_set> participants; @@ -205,6 +231,7 @@ class ChatData final : public PeerData { namespace Data { +#if 0 // mtp void ApplyChatUpdate( not_null chat, const MTPDupdateChatParticipants &update); @@ -226,5 +253,13 @@ void ApplyChatUpdate( void ApplyChatUpdate( not_null chat, const MTPChatParticipants &update); +#endif + +void ApplyChatUpdate( + not_null chat, + const Tdb::TLDbasicGroupFullInfo &update); +void ApplyChatUpdate( + not_null chat, + const QVector &update); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 7e824cc33ad42..2f93d78bffe16 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -25,9 +25,15 @@ For license and copyright information please follow this link: #include "main/main_app_config.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Data { namespace { +using namespace Tdb; + +constexpr auto kSuggestedStartId = 77; constexpr auto kRefreshSuggestedTimeout = 7200 * crl::time(1000); constexpr auto kLoadExceptionsAfter = 100; constexpr auto kLoadExceptionsPerRequest = 100; @@ -57,6 +63,7 @@ ChatFilter::ChatFilter( , _flags(flags) { } +#if 0 // mtp ChatFilter ChatFilter::FromTL( const MTPDialogFilter &data, not_null owner) { @@ -148,6 +155,7 @@ ChatFilter ChatFilter::FromTL( {}); }); } +#endif ChatFilter ChatFilter::withId(FilterId id) const { auto result = *this; @@ -176,6 +184,7 @@ ChatFilter ChatFilter::withChatlist(bool chatlist, bool hasMyLinks) const { return result; } +#if 0 // mtp MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { auto always = _always; auto pinned = QVector(); @@ -226,6 +235,108 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { MTP_vector(include), MTP_vector(never)); } +#endif + +ChatFilter ChatFilter::FromTL( + FilterId id, + const TLchatFolder &filter, + not_null owner, + bool hasMyLinks) { + const auto &data = filter.data(); + const auto flags = Flag(0) + | (data.vinclude_contacts().v ? Flag::Contacts : Flag(0)) + | (data.vinclude_non_contacts().v ? Flag::NonContacts : Flag(0)) + | (data.vinclude_groups().v ? Flag::Groups : Flag(0)) + | (data.vinclude_channels().v ? Flag::Channels : Flag(0)) + | (data.vinclude_bots().v ? Flag::Bots : Flag(0)) + | (data.vexclude_muted().v ? Flag::NoMuted : Flag(0)) + | (data.vexclude_read().v ? Flag::NoRead : Flag(0)) + | (data.vexclude_archived().v ? Flag::NoArchived : Flag(0)) + | (data.vis_shareable().v + ? (Flag::Chatlist | (hasMyLinks ? Flag::HasMyLinks : Flag(0))) + : Flag(0)); + auto &&to_histories = ranges::views::transform([&](const TLint53 &id) { + const auto peer = peerFromTdbChat(id); + return peer ? owner->history(peer).get() : nullptr; + }) | ranges::views::filter([](History *history) { + return history != nullptr; + }) | ranges::views::transform([](History *history) { + return not_null(history); + }); + auto &&always = ranges::views::concat( + data.vincluded_chat_ids().v + ) | to_histories; + auto pinned = ranges::views::all( + data.vpinned_chat_ids().v + ) | to_histories | ranges::to_vector; + auto &&never = ranges::views::all( + data.vexcluded_chat_ids().v + ) | to_histories; + auto &&all = ranges::views::concat(always, pinned); + auto list = base::flat_set>{ + all.begin(), + all.end() + }; + + return ChatFilter( + id, + data.vtitle().v, + data.vicon() ? data.vicon()->data().vname().v : QString(), + flags, + std::move(list), + std::move(pinned), + { never.begin(), never.end() }); +} + +ChatFilter ChatFilter::FromTL(const Tdb::TLchatFolderInfo &filter) { + const auto &data = filter.data(); + return ChatFilter( + data.vid().v, + data.vtitle().v, + data.vicon().data().vname().v, + ((data.vhas_my_invite_links().v ? Flag::HasMyLinks : Flag()) + | (data.vis_shareable().v ? Flag::Chatlist : Flag())), + {}, + {}, + {}); +} + +TLchatFolder ChatFilter::tl() const { + auto always = _always; + auto pinned = QVector(); + pinned.reserve(_pinned.size()); + for (const auto &history : _pinned) { + pinned.push_back(peerToTdbChat(history->peer->id)); + always.remove(history); + } + auto include = QVector(); + include.reserve(always.size()); + for (const auto &history : always) { + include.push_back(peerToTdbChat(history->peer->id)); + } + auto never = QVector(); + never.reserve(_never.size()); + for (const auto &history : _never) { + never.push_back(peerToTdbChat(history->peer->id)); + } + return tl_chatFolder( + tl_string(_title), + (_iconEmoji.isEmpty() + ? std::optional() + : tl_chatFolderIcon(tl_string(_iconEmoji))), + tl_bool(_flags & Flag::Chatlist), + tl_vector(pinned), + tl_vector(include), + tl_vector(never), + tl_bool(_flags & Flag::NoMuted), + tl_bool(_flags & Flag::NoRead), + tl_bool(_flags & Flag::NoArchived), + tl_bool(_flags & Flag::Contacts), + tl_bool(_flags & Flag::NonContacts), + tl_bool(_flags & Flag::Bots), + tl_bool(_flags & Flag::Groups), + tl_bool(_flags & Flag::Channels)); +} FilterId ChatFilter::id() const { return _id; @@ -264,6 +375,21 @@ const base::flat_set> &ChatFilter::never() const { } bool ChatFilter::contains(not_null history) const { + return history->inChatList(_id); +} + +bool ChatFilter::loaded() const { + return !_id || !_always.empty() || !_never.empty() || (_flags != 0); +} + +void ChatFilter::unload() { + _always = {}; + _pinned = {}; + _never = {}; + _flags = 0; +} + +bool ChatFilter::computeContains(not_null history) const { const auto flag = [&] { const auto peer = history->peer; if (const auto user = peer->asUser()) { @@ -310,7 +436,9 @@ ChatFilters::ChatFilters(not_null owner) : _owner(owner) , _moreChatsTimer([=] { checkLoadMoreChatsLists(); }) { _list.emplace_back(); +#if 0 // mtp crl::on_main(&owner->session(), [=] { load(); }); +#endif } ChatFilters::~ChatFilters() = default; @@ -336,6 +464,7 @@ void ChatFilters::clear() { _list.clear(); } +#if 0 // mtp void ChatFilters::setPreloaded(const QVector &result) { _loadRequestId = -1; received(result); @@ -432,6 +561,10 @@ void ChatFilters::apply(const MTPUpdate &update) { ChatFilterLink ChatFilters::add( FilterId id, const MTPExportedChatlistInvite &update) { +#endif +ChatFilterLink ChatFilters::add( + FilterId id, + const TLchatFolderInviteLink &update) { const auto i = ranges::find(_list, id, &ChatFilter::id); if (i == end(_list) || !i->chatlist()) { LOG(("Api Error: " @@ -441,12 +574,20 @@ ChatFilterLink ChatFilters::add( } auto &links = _chatlistLinks[id]; const auto &data = update.data(); +#if 0 // mtp const auto url = qs(data.vurl()); const auto title = qs(data.vtitle()); auto chats = data.vpeers().v | ranges::views::transform([&]( const MTPPeer &peer) { return _owner->history(peerFromMTP(peer)); }) | ranges::to_vector; +#endif + const auto url = data.vinvite_link().v; + const auto title = data.vname().v; + auto chats = data.vchat_ids().v | ranges::views::transform([&]( + const TLint53 &peer) { + return _owner->history(peerFromTdbChat(peer)); + }) | ranges::to_vector; const auto j = ranges::find(links, url, &ChatFilterLink::url); if (j != end(links)) { if (j->title != title || j->chats != chats) { @@ -476,6 +617,12 @@ void ChatFilters::edit( i->title = title; _chatlistLinksUpdated.fire_copy(id); + _owner->session().sender().request(TLeditChatFolderInviteLink( + + )).done([=] { + }).fail([=] { + }).send(); +#if 0 // mtp _owner->session().api().request(MTPchatlists_EditExportedInvite( MTP_flags(MTPchatlists_EditExportedInvite::Flag::f_title), MTP_inputChatlistDialogFilter(MTP_int(id)), @@ -489,6 +636,7 @@ void ChatFilters::edit( }).fail([=](const MTP::Error &error) { //done({ .id = id }); }).send(); +#endif } } @@ -499,12 +647,19 @@ void ChatFilters::destroy(FilterId id, const QString &url) { links.erase(i); _chatlistLinksUpdated.fire_copy(id); +#if 0 // mtp const auto api = &_owner->session().api(); api->request(_linksRequestId).cancel(); _linksRequestId = api->request(MTPchatlists_DeleteExportedInvite( MTP_inputChatlistDialogFilter(MTP_int(id)), MTP_string(url) )).send(); +#endif + const auto sender = &_owner->session().sender(); + _linksRequestId = sender->request(TLdeleteChatFolderInviteLink( + tl_int32(id), + tl_string(url) + )).send(); } } @@ -521,6 +676,18 @@ rpl::producer> ChatFilters::chatlistLinks( } void ChatFilters::reloadChatlistLinks(FilterId id) { + const auto sender = &_owner->session().sender(); + _linksRequestId = sender->request(TLgetChatFolderInviteLinks( + tl_int32(id) + )).done([=](const TLchatFolderInviteLinks &result) { + const auto &data = result.data(); + _chatlistLinks[id].clear(); + for (const auto &link : data.vinvite_links().v) { + add(id, link); + } + _chatlistLinksUpdated.fire_copy(id); + }).send(); +#if 0 // mtp const auto api = &_owner->session().api(); api->request(_linksRequestId).cancel(); _linksRequestId = api->request(MTPchatlists_GetExportedInvites( @@ -535,8 +702,26 @@ void ChatFilters::reloadChatlistLinks(FilterId id) { } _chatlistLinksUpdated.fire_copy(id); }).send(); +#endif } +void ChatFilters::apply(const TLDupdateChatFolders &update) { + _list.clear(); + const auto maybePushMain = [&] { + if (_list.size() == update.vmain_chat_list_position().v) { + _list.push_back(ChatFilter()); + } + }; + for (const auto &filter : update.vchat_folders().v) { + maybePushMain(); + _list.push_back(ChatFilter::FromTL(filter)); + } + maybePushMain(); + _loaded = true; + _listChanged.fire({}); +} + +#if 0 // mtp void ChatFilters::set(ChatFilter filter) { if (!filter.id()) { return; @@ -567,6 +752,7 @@ void ChatFilters::remove(FilterId id) { applyRemove(i - begin(_list)); _listChanged.fire({}); } +#endif void ChatFilters::moveAllToFront() { const auto i = ranges::find(_list, FilterId(), &ChatFilter::id); @@ -578,6 +764,7 @@ void ChatFilters::moveAllToFront() { _list.insert(begin(_list), ChatFilter()); } +#if 0 // mtp void ChatFilters::applyRemove(int position) { Expects(position >= 0 && position < _list.size()); @@ -717,10 +904,32 @@ const ChatFilter &ChatFilters::applyUpdatedPinned( i->never())); return *i; } +#endif void ChatFilters::saveOrder( const std::vector &order, mtpRequestId after) { + if (order.size() < 2) { + return; + } + + const auto sender = &_owner->session().sender(); + sender->request(_saveOrderRequestId).cancel(); + + auto ids = QVector(); + auto mainPosition = 0; + for (const auto id : order) { + if (id) { + ids.push_back(tl_int32(id)); + } else { + mainPosition = ids.size(); + } + } + sender->request(TLreorderChatFolders( + tl_vector(std::move(ids)), + tl_int32(mainPosition) + )).send(); +#if 0 // mtp if (after) { _saveOrderAfterId = after; } @@ -738,8 +947,10 @@ void ChatFilters::saveOrder( _saveOrderRequestId = api->request(MTPmessages_UpdateDialogFiltersOrder( wrapped )).afterRequest(_saveOrderAfterId).send(); +#endif } +#if 0 // mtp bool ChatFilters::archiveNeeded() const { for (const auto &filter : _list) { if (!(filter.flags() & ChatFilter::Flag::NoArchived)) { @@ -748,6 +959,7 @@ bool ChatFilters::archiveNeeded() const { } return false; } +#endif const std::vector &ChatFilters::list() const { return _list; @@ -787,6 +999,7 @@ rpl::producer ChatFilters::isChatlistChanged() const { return _isChatlistChanged.events(); } +#if 0 // mtp bool ChatFilters::loadNextExceptions(bool chatsListLoaded) { if (_exceptionsLoadRequestId) { return true; @@ -836,11 +1049,20 @@ bool ChatFilters::loadNextExceptions(bool chatsListLoaded) { }).send(); return true; } +#endif + +void ChatFilters::unloadDetails() { + for (auto &filter : _list) { + filter.unload(); + } +} void ChatFilters::refreshHistory(not_null history) { +#if 0 // mtp if (history->inChatList() && !list().empty()) { _owner->refreshChatListEntry(history); } +#endif } void ChatFilters::requestSuggested() { @@ -851,6 +1073,23 @@ void ChatFilters::requestSuggested() { && crl::now() - _suggestedLastReceived < kRefreshSuggestedTimeout) { return; } + const auto sender = &_owner->session().sender(); + _suggestedRequestId = sender->request(TLgetRecommendedChatFolders( + )).done([=](const TLDrecommendedChatFolders &result) { + _suggestedRequestId = 0; + _suggestedLastReceived = crl::now(); + + auto id = kSuggestedStartId; + _suggested = ranges::views::all( + result.vchat_folders().v + ) | ranges::views::transform([&](const TLrecommendedChatFolder &f) { + const auto &data = f.data(); + return SuggestedFilter{ + Data::ChatFilter::FromTL(++id, data.vfolder(), _owner), + data.vdescription().v, + }; + }) | ranges::to_vector; +#if 0 // mtp const auto api = &_owner->session().api(); _suggestedRequestId = api->request(MTPmessages_GetSuggestedDialogFilters( )).done([=](const MTPVector &data) { @@ -867,6 +1106,7 @@ void ChatFilters::requestSuggested() { }; }); }) | ranges::to_vector; +#endif _suggestedUpdated.fire({}); }).fail([=] { @@ -930,16 +1170,25 @@ const std::vector> &ChatFilters::moreChats( void ChatFilters::moreChatsHide(FilterId id, bool localOnly) { if (!localOnly) { +#if 0 // mtp const auto api = &_owner->session().api(); api->request(MTPchatlists_HideChatlistUpdates( MTP_inputChatlistDialogFilter(MTP_int(id)) )).send(); +#endif + _owner->session().sender().request(TLprocessChatFolderNewChats( + tl_int32(id), + tl_vector() + )).send(); } const auto i = _moreChatsData.find(id); if (i != end(_moreChatsData)) { if (const auto requestId = base::take(i->second.requestId)) { +#if 0 // mtp _owner->session().api().request(requestId).cancel(); +#endif + _owner->session().sender().request(requestId).cancel(); } i->second.missing = {}; i->second.lastUpdate = crl::now(); @@ -968,6 +1217,7 @@ void ChatFilters::loadMoreChatsList(FilterId id) { } return; } +#if 0 // mtp auto &api = _owner->session().api(); entry.requestId = api.request(MTPchatlists_GetChatlistUpdates( MTP_inputChatlistDialogFilter(MTP_int(id)) @@ -980,6 +1230,14 @@ void ChatFilters::loadMoreChatsList(FilterId id) { ) | ranges::views::transform([&](const MTPPeer &peer) { return _owner->peer(peerFromMTP(peer)); }) | ranges::to_vector; +#endif + entry.requestId = _owner->session().sender().request( + TLgetChatFolderNewChats(tl_int32(id)) + ).done([=](const TLchats &result) { + const auto &ids = result.data().vchat_ids().v; + auto list = ids | ranges::views::transform([&](const TLint53 &id) { + return _owner->peer(peerFromTdbChat(id)); + }) | ranges::to_vector; auto &entry = _moreChatsData[id]; entry.requestId = 0; diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 987d55ebe8cae..622878c6516cd 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -10,6 +10,13 @@ For license and copyright information please follow this link: #include "base/flags.h" #include "base/timer.h" +namespace Tdb { +class TLchatFolder; +class TLchatFolderInfo; +class TLDupdateChatFolders; +class TLchatFolderInviteLink; +} // namespace Tdb + class History; namespace Dialogs { @@ -59,10 +66,23 @@ class ChatFilter final { bool chatlist, bool hasMyLinks) const; +#if 0 // mtp [[nodiscard]] static ChatFilter FromTL( const MTPDialogFilter &data, not_null owner); [[nodiscard]] MTPDialogFilter tl(FilterId replaceId = 0) const; +#endif + [[nodiscard]] static ChatFilter FromTL( + FilterId id, + const Tdb::TLchatFolder &filter, + not_null owner, + bool hasMyLinks = false); + [[nodiscard]] static ChatFilter FromTL( + const Tdb::TLchatFolderInfo &filter); + [[nodiscard]] Tdb::TLchatFolder tl() const; + [[nodiscard]] bool computeContains(not_null history) const; + [[nodiscard]] bool loaded() const; + void unload(); [[nodiscard]] FilterId id() const; [[nodiscard]] QString title() const; @@ -120,6 +140,8 @@ class ChatFilters final { explicit ChatFilters(not_null owner); ~ChatFilters(); + void apply(const Tdb::TLDupdateChatFolders &update); +#if 0 // mtp void setPreloaded(const QVector &result); void load(); @@ -127,6 +149,7 @@ class ChatFilters final { void apply(const MTPUpdate &update); void set(ChatFilter filter); void remove(FilterId id); +#endif void moveAllToFront(); [[nodiscard]] const std::vector &list() const; [[nodiscard]] rpl::producer<> changed() const; @@ -137,21 +160,28 @@ class ChatFilters final { [[nodiscard]] FilterId defaultId() const; [[nodiscard]] FilterId lookupId(int index) const; +#if 0 // mtp bool loadNextExceptions(bool chatsListLoaded); +#endif + void unloadDetails(); void refreshHistory(not_null history); [[nodiscard]] not_null chatsList(FilterId filterId); void clear(); +#if 0 // mtp const ChatFilter &applyUpdatedPinned( FilterId id, const std::vector &dialogs); +#endif void saveOrder( const std::vector &order, mtpRequestId after = 0); +#if 0 // mtp [[nodiscard]] bool archiveNeeded() const; +#endif void requestSuggested(); [[nodiscard]] bool suggestedLoaded() const; @@ -159,9 +189,14 @@ class ChatFilters final { -> const std::vector &; [[nodiscard]] rpl::producer<> suggestedUpdated() const; +#if 0 // mtp ChatFilterLink add( FilterId id, const MTPExportedChatlistInvite &update); +#endif + ChatFilterLink add( + FilterId id, + const Tdb::TLchatFolderInviteLink &update); void edit( FilterId id, const QString &url, @@ -185,12 +220,14 @@ class ChatFilters final { std::weak_ptr watching; }; +#if 0 // mtp void load(bool force); void received(const QVector &list); bool applyOrder(const QVector &order); bool applyChange(ChatFilter &filter, ChatFilter &&updated); void applyInsert(ChatFilter filter, int position); void applyRemove(int position); +#endif void checkLoadMoreChatsLists(); void loadMoreChatsList(FilterId id); diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index f875ccdb3abe2..e4a65fdea37ea 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -17,6 +17,10 @@ For license and copyright information please follow this link: #include "lang/lang_keys.h" #include "ui/chat/attach/attach_prepare.h" +#include "tdb/tdb_tl_scheme.h" +#include "data/data_secret_chat.h" + +#if 0 // goodToRemove namespace { [[nodiscard]] ChatAdminRights ChatAdminRightsFlags( @@ -50,6 +54,151 @@ ChatRestrictionsInfo::ChatRestrictionsInfo(const MTPChatBannedRights &rights) : flags(ChatBannedRightsFlags(rights)) , until(ChatBannedRightsUntilDate(rights)) { } +#endif + +ChatRestrictions RestrictionsFromPermissions( + const Tdb::TLchatPermissions &permissions) { + using Flag = ChatRestriction; + const auto &data = permissions.data(); + const auto bit = [&](const Tdb::TLbool &check, Flag value) { + return check.v ? Flag(0) : value; + }; + return Flag(0) + | bit(data.vcan_add_web_page_previews(), Flag::EmbedLinks) + | bit(data.vcan_change_info(), Flag::ChangeInfo) + | bit(data.vcan_invite_users(), Flag::AddParticipants) + | bit(data.vcan_pin_messages(), Flag::PinMessages) + | bit(data.vcan_send_audios(), Flag::SendMusic) + | bit(data.vcan_send_documents(), Flag::SendFiles) + | bit(data.vcan_send_photos(), Flag::SendPhotos) + | bit(data.vcan_send_videos(), Flag::SendVideos) + | bit(data.vcan_send_video_notes(), Flag::SendVideoMessages) + | bit(data.vcan_send_voice_notes(), Flag::SendVoiceMessages) + | bit(data.vcan_send_basic_messages(), Flag::SendOther) + | bit(data.vcan_send_polls(), Flag::SendPolls) + | bit(data.vcan_send_other_messages(), Flag::SendStickers) + | bit(data.vcan_send_other_messages(), Flag::SendGames) + | bit(data.vcan_send_other_messages(), Flag::SendGifs) + | bit(data.vcan_send_other_messages(), Flag::SendInline); +} + +[[nodiscard]] ChatAdminRights AdminRightsFromChatAdministratorRights( + const Tdb::TLchatAdministratorRights &rights) { + using Flag = ChatAdminRight; + const auto &data = rights.data(); + const auto bit = [&](const Tdb::TLbool &check, Flag value) { + return check.v ? value : Flag(0); + }; + return Flag(0) + | bit(data.vcan_manage_chat(), Flag::Other) + | bit(data.vcan_change_info(), Flag::ChangeInfo) + | bit(data.vcan_post_messages(), Flag::PostMessages) + | bit(data.vcan_edit_messages(), Flag::EditMessages) + | bit(data.vcan_delete_messages(), Flag::DeleteMessages) + | bit(data.vcan_post_stories(), Flag::PostStories) + | bit(data.vcan_edit_stories(), Flag::EditStories) + | bit(data.vcan_delete_stories(), Flag::DeleteStories) + | bit(data.vcan_invite_users(), Flag::InviteByLinkOrAdd) + | bit(data.vcan_manage_video_chats(), Flag::ManageCall) + | bit(data.vcan_pin_messages(), Flag::PinMessages) + | bit(data.vcan_promote_members(), Flag::AddAdmins) + | bit(data.vcan_restrict_members(), Flag::BanUsers) + | bit(data.vis_anonymous(), Flag::Anonymous); +} + +ChatAdminRightsInfo::ChatAdminRightsInfo( + const Tdb::TLchatMemberStatus &status) { + const auto empty = ChatAdminRights(0); + status.match([&](const Tdb::TLDchatMemberStatusAdministrator &data) { + flags = AdminRightsFromChatAdministratorRights(data.vrights()); + }, [&](const Tdb::TLDchatMemberStatusCreator &data) { + _creator = true; + flags = ChatAdminRight::ChangeInfo + | ChatAdminRight::PostMessages + | ChatAdminRight::EditMessages + | ChatAdminRight::DeleteMessages + | ChatAdminRight::BanUsers + | ChatAdminRight::InviteByLinkOrAdd + | ChatAdminRight::PinMessages + | ChatAdminRight::AddAdmins + | (data.vis_anonymous().v ? ChatAdminRight::Anonymous : empty) + | ChatAdminRight::ManageCall + | ChatAdminRight::Other; + }, [](const auto &) { + }); +} + +Tdb::TLchatMemberStatus ChatAdminRightsInfo::ToTL( + const ChatAdminRightsInfo &rights, + const QString &rank) { + return Tdb::tl_chatMemberStatusAdministrator( + Tdb::tl_string(rank), + Tdb::tl_bool(true), + Tdb::tl_chatAdministratorRights( + Tdb::tl_bool(rights.flags & ChatAdminRight::Other), + Tdb::tl_bool(rights.flags & ChatAdminRight::ChangeInfo), + Tdb::tl_bool(rights.flags & ChatAdminRight::PostMessages), + Tdb::tl_bool(rights.flags & ChatAdminRight::EditMessages), + Tdb::tl_bool(rights.flags & ChatAdminRight::DeleteMessages), + Tdb::tl_bool(rights.flags & ChatAdminRight::InviteByLinkOrAdd), + Tdb::tl_bool(rights.flags & ChatAdminRight::BanUsers), + Tdb::tl_bool(rights.flags & ChatAdminRight::PinMessages), + Tdb::tl_bool(rights.flags & ChatAdminRight::ManageTopics), + Tdb::tl_bool(rights.flags & ChatAdminRight::AddAdmins), + Tdb::tl_bool(rights.flags & ChatAdminRight::ManageCall), + Tdb::tl_bool(rights.flags & ChatAdminRight::PostStories), + Tdb::tl_bool(rights.flags & ChatAdminRight::EditStories), + Tdb::tl_bool(rights.flags & ChatAdminRight::DeleteStories), + Tdb::tl_bool(rights.flags & ChatAdminRight::Anonymous))); +} + +ChatRestrictionsInfo::ChatRestrictionsInfo( + const Tdb::TLchatMemberStatus &status) { + status.match([&](const Tdb::TLDchatMemberStatusRestricted &status) { + until = status.vrestricted_until_date().v; + flags = RestrictionsFromPermissions(status.vpermissions()); + }, [&](const Tdb::TLDchatMemberStatusBanned &data) { +#if 0 // doLater Check if it's an user. +#endif + until = data.vbanned_until_date().v; + flags |= ChatRestriction::ViewMessages; + }, [](const auto &) { + }); +} + +Tdb::TLchatPermissions ChatRestrictionsInfo::ToTLPermissions( + const ChatRestrictions &flags) { + return Tdb::tl_chatPermissions( + Tdb::tl_bool(!(flags & ChatRestriction::SendOther)), + Tdb::tl_bool(!(flags & ChatRestriction::SendMusic)), + Tdb::tl_bool(!(flags & ChatRestriction::SendFiles)), + Tdb::tl_bool(!(flags & ChatRestriction::SendPhotos)), + Tdb::tl_bool(!(flags & ChatRestriction::SendVideos)), + Tdb::tl_bool(!(flags & ChatRestriction::SendVideoMessages)), + Tdb::tl_bool(!(flags & ChatRestriction::SendVoiceMessages)), + Tdb::tl_bool(!(flags & ChatRestriction::SendPolls)), + Tdb::tl_bool((!(flags & ChatRestriction::SendStickers)) + || (!(flags & ChatRestriction::SendGifs)) + || (!(flags & ChatRestriction::SendInline)) + || (!(flags & ChatRestriction::SendGames))), + Tdb::tl_bool(!(flags & ChatRestriction::EmbedLinks)), + Tdb::tl_bool(!(flags & ChatRestriction::ChangeInfo)), + Tdb::tl_bool(!(flags & ChatRestriction::AddParticipants)), + Tdb::tl_bool(!(flags & ChatRestriction::PinMessages)), + Tdb::tl_bool(!(flags & ChatRestriction::CreateTopics))); +} + +Tdb::TLchatMemberStatus ChatRestrictionsInfo::ToTL( + const ChatRestrictionsInfo &rights) { + const auto kicked = rights.flags & ChatRestriction::ViewMessages; + return kicked + ? Tdb::tl_chatMemberStatusBanned(Tdb::tl_int32(rights.until)) + : Tdb::tl_chatMemberStatusRestricted( + Tdb::tl_bool(true), + Tdb::tl_int32(rights.until), + ToTLPermissions(rights.flags)); + +} namespace Data { @@ -157,6 +306,9 @@ bool CanSendAnyOf( } } return false; + } else if (const auto secretChat = peer->asSecretChat()) { + return (secretChat->state() == SecretChatState::Ready) + && CanSendAnyOf(secretChat->user(), rights, forbidInForums); } Unexpected("Peer type in CanSendAnyOf."); } diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.h b/Telegram/SourceFiles/data/data_chat_participant_status.h index 8dd7cd682ffa9..e1ee40c8d98d3 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.h +++ b/Telegram/SourceFiles/data/data_chat_participant_status.h @@ -12,6 +12,12 @@ struct PreparedList; struct PreparedFile; } // namespace Ui +namespace Tdb { +class TLchatMemberStatus; +class TLchatPermissions; +class TLchatAdministratorRights; +} // namespace Tdb + enum class ChatAdminRight { ChangeInfo = (1 << 0), PostMessages = (1 << 1), @@ -58,13 +64,27 @@ enum class ChatRestriction { inline constexpr bool is_flag_type(ChatRestriction) { return true; } using ChatRestrictions = base::flags; +[[nodiscard]] ChatRestrictions RestrictionsFromPermissions( + const Tdb::TLchatPermissions &permissions); +[[nodiscard]] ChatAdminRights AdminRightsFromChatAdministratorRights( + const Tdb::TLchatAdministratorRights &rights); + struct ChatAdminRightsInfo { ChatAdminRightsInfo() = default; explicit ChatAdminRightsInfo(ChatAdminRights flags) : flags(flags) { } +#if 0 // goodToRemove explicit ChatAdminRightsInfo(const MTPChatAdminRights &rights); +#endif + explicit ChatAdminRightsInfo(const Tdb::TLchatMemberStatus &status); + + static Tdb::TLchatMemberStatus ToTL( + const ChatAdminRightsInfo &rights, + const QString &rank); ChatAdminRights flags; +private: + bool _creator = false; }; struct ChatRestrictionsInfo { @@ -73,7 +93,14 @@ struct ChatRestrictionsInfo { : flags(flags) , until(until) { } +#if 0 // goodToRemove explicit ChatRestrictionsInfo(const MTPChatBannedRights &rights); +#endif + explicit ChatRestrictionsInfo(const Tdb::TLchatMemberStatus &status); + + static Tdb::TLchatPermissions ToTLPermissions( + const ChatRestrictions &flags); + static Tdb::TLchatMemberStatus ToTL(const ChatRestrictionsInfo &rights); ChatRestrictions flags; TimeId until = 0; diff --git a/Telegram/SourceFiles/data/data_cloud_themes.cpp b/Telegram/SourceFiles/data/data_cloud_themes.cpp index 5ac1c2052cd52..051b9c9d3988d 100644 --- a/Telegram/SourceFiles/data/data_cloud_themes.cpp +++ b/Telegram/SourceFiles/data/data_cloud_themes.cpp @@ -21,16 +21,29 @@ For license and copyright information please follow this link: #include "lang/lang_keys.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kFirstReloadTimeout = 10 * crl::time(1000); constexpr auto kReloadTimeout = 3600 * crl::time(1000); bool IsTestingColors/* = false*/; +[[nodiscard]] uint64 ChatThemeId(const QString &emoticon) { + auto result = uint64(1); + for (const auto ch : emoticon) { + result = (result * 31) + ch.unicode(); + } + return result; +} + } // namespace +#if 0 // mtp CloudTheme CloudTheme::Parse( not_null session, const MTPDtheme &data, @@ -119,6 +132,47 @@ CloudTheme CloudTheme::Parse( return CloudTheme::Parse(session, data, parseSettings); }); } +#endif +CloudTheme CloudTheme::Parse( + not_null session, + const TLchatTheme &theme, + bool) { + const auto &data = theme.data(); + const auto emoticon = data.vname().v; + const auto parse = [&](Type type) { + const auto &settings = (type == CloudThemeType::Dark) + ? data.vdark_settings() + : data.vlight_settings(); + const auto &fields = settings.data(); + const auto hasOutAccent = fields.voutgoing_message_accent_color().v + && (fields.voutgoing_message_accent_color().v + != fields.vaccent_color().v);; + return Settings{ + .paper = (fields.vbackground() + ? WallPaper::Create(session, *fields.vbackground()) + : std::nullopt), + .accentColor = Ui::ColorFromSerialized(fields.vaccent_color()), + .outgoingAccentColor = (hasOutAccent + ? Ui::ColorFromSerialized( + fields.voutgoing_message_accent_color()) + : std::optional()), + .outgoingMessagesColors = Ui::ColorsFromFill( + fields.voutgoing_message_fill(), + true), + }; + }; + auto settings = base::flat_map(); + settings.emplace(Type::Dark, parse(Type::Dark)); + settings.emplace(Type::Light, parse(Type::Light)); + return { + .id = ChatThemeId(emoticon), + .emoticon = emoticon, + .settings = { + { Type::Dark, parse(Type::Dark) }, + { Type::Light, parse(Type::Light) }, + }, + }; +} QString CloudThemes::Format() { static const auto kResult = QString::fromLatin1("tdesktop"); @@ -169,6 +223,7 @@ void CloudThemes::install() { return; } themeId = cloudId; +#if 0 // tdlib todo using Flag = MTPaccount_InstallTheme::Flag; const auto flags = (IsNightMode() ? Flag::f_dark : Flag(0)) | Flag::f_format @@ -179,6 +234,7 @@ void CloudThemes::install() { MTP_string(Format()), MTPBaseTheme() )).send(); +#endif } void CloudThemes::reloadCurrent() { @@ -186,6 +242,7 @@ void CloudThemes::reloadCurrent() { return; } const auto &fields = Window::Theme::Background()->themeObject().cloud; +#if 0 // tdlib todo _session->api().request(MTPaccount_GetTheme( MTP_string(Format()), MTP_inputTheme(MTP_long(fields.id), MTP_long(fields.accessHash)) @@ -194,8 +251,10 @@ void CloudThemes::reloadCurrent() { }).fail([=] { _reloadCurrentTimer.callOnce(kReloadTimeout); }).send(); +#endif } +#if 0 // mtp void CloudThemes::applyUpdate(const MTPTheme &theme) { theme.match([&](const MTPDtheme &data) { const auto cloud = CloudTheme::Parse(_session, data); @@ -209,11 +268,18 @@ void CloudThemes::applyUpdate(const MTPTheme &theme) { }); scheduleReload(); } +#endif + +void CloudThemes::applyUpdate(const TLDupdateChatThemes &data) { + parseChatThemes(data.vchat_themes().v); + _chatThemesUpdates.fire({}); +} void CloudThemes::resolve( not_null controller, const QString &slug, const FullMsgId &clickFromMessageId) { +#if 0 // tdlib todo _session->api().request(_resolveRequestId).cancel(); _resolveRequestId = _session->api().request(MTPaccount_GetTheme( MTP_string(Format()), @@ -225,8 +291,10 @@ void CloudThemes::resolve( controller->show(Ui::MakeInformBox(tr::lng_theme_no_desktop())); } }).send(); +#endif } +#if 0 // mtp void CloudThemes::showPreview( not_null controller, const MTPTheme &data) { @@ -234,6 +302,7 @@ void CloudThemes::showPreview( showPreview(controller, CloudTheme::Parse(_session, data)); }); } +#endif void CloudThemes::showPreview( not_null controller, @@ -330,6 +399,7 @@ void CloudThemes::refresh() { if (_refreshRequestId) { return; } +#if 0 // tdlib todo _refreshRequestId = _session->api().request(MTPaccount_GetThemes( MTP_string(Format()), MTP_long(_hash) @@ -344,8 +414,10 @@ void CloudThemes::refresh() { }).fail([=] { _refreshRequestId = 0; }).send(); +#endif } +#if 0 // mtp void CloudThemes::parseThemes(const QVector &list) { _list.clear(); _list.reserve(list.size()); @@ -354,11 +426,13 @@ void CloudThemes::parseThemes(const QVector &list) { } checkCurrentTheme(); } +#endif void CloudThemes::refreshChatThemes() { if (_chatThemesRequestId) { return; } +#if 0 // mtp _chatThemesRequestId = _session->api().request(MTPaccount_GetChatThemes( MTP_long(_chatThemesHash) )).done([=](const MTPaccount_Themes &result) { @@ -372,6 +446,7 @@ void CloudThemes::refreshChatThemes() { }).fail([=] { _chatThemesRequestId = 0; }).send(); +#endif } const std::vector &CloudThemes::chatThemes() const { @@ -566,7 +641,10 @@ std::optional CloudThemes::updateThemeFromLink( return *i; } +#if 0 // mtp void CloudThemes::parseChatThemes(const QVector &list) { +#endif +void CloudThemes::parseChatThemes(const QVector &list) { _chatThemes.clear(); _chatThemes.reserve(list.size()); for (const auto &theme : list) { @@ -609,12 +687,14 @@ void CloudThemes::remove(uint64 cloudThemeId) { if (i == end(_list)) { return; } +#if 0 // tdlib todo _session->api().request(MTPaccount_SaveTheme( MTP_inputTheme( MTP_long(i->id), MTP_long(i->accessHash)), MTP_bool(true) )).send(); +#endif _list.erase(i); _updates.fire({}); } diff --git a/Telegram/SourceFiles/data/data_cloud_themes.h b/Telegram/SourceFiles/data/data_cloud_themes.h index 1ac4ceff1940d..d5ccbec0629d4 100644 --- a/Telegram/SourceFiles/data/data_cloud_themes.h +++ b/Telegram/SourceFiles/data/data_cloud_themes.h @@ -10,6 +10,11 @@ For license and copyright information please follow this link: #include "base/timer.h" #include "data/data_wall_paper.h" +namespace Tdb { +class TLDupdateChatThemes; +class TLchatTheme; +} // namespace Tdb + class DocumentData; namespace Main { @@ -48,6 +53,7 @@ struct CloudTheme { }; base::flat_map settings; +#if 0 // mtp static CloudTheme Parse( not_null session, const MTPDtheme &data, @@ -56,6 +62,11 @@ struct CloudTheme { not_null session, const MTPTheme &data, bool parseSettings = false); +#endif + static CloudTheme Parse( + not_null session, + const Tdb::TLchatTheme &theme, + bool); }; class CloudThemes final { @@ -85,15 +96,20 @@ class CloudThemes final { const QString &emoticon, const QMap ¶ms); +#if 0 // mtp void applyUpdate(const MTPTheme &theme); +#endif + void applyUpdate(const Tdb::TLDupdateChatThemes &data); void resolve( not_null controller, const QString &slug, const FullMsgId &clickFromMessageId); +#if 0 // mtp void showPreview( not_null controller, const MTPTheme &data); +#endif void showPreview( not_null controller, const CloudTheme &cloud); @@ -126,7 +142,10 @@ class CloudThemes final { Fn)> callback); void invokeForLoaded(LoadingDocument &value); +#if 0 // mtp void parseChatThemes(const QVector &list); +#endif + void parseChatThemes(const QVector &list); const not_null _session; uint64 _hash = 0; diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 5852a9606a8a8..3beb2db12e755 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -47,12 +47,21 @@ For license and copyright information please follow this link: #include "lottie/lottie_animation.h" #include "boxes/abstract_box.h" // Ui::hideLayer(). +#include "tdb/tdb_tl_scheme.h" +#include "ui/image/image_location_factory.h" +#include "storage/file_upload.h" +#include "storage/file_download_tdb.h" +#include "media/streaming/media_streaming_loader_tdb.h" +#include + #include #include #include namespace { +using namespace Tdb; + constexpr auto kDefaultCoverThumbnailSize = 100; constexpr auto kMaxAllowedPreloadPrefix = 6 * 1024 * 1024; @@ -60,7 +69,9 @@ const auto kLottieStickerDimensions = QSize( kStickerSideSize, kStickerSideSize); -QString JoinStringList(const QStringList &list, const QString &separator) { +[[nodiscard]] QString JoinStringList( + const QStringList &list, + const QString &separator) { const auto count = list.size(); if (!count) { return QString(); @@ -82,6 +93,7 @@ QString JoinStringList(const QStringList &list, const QString &separator) { void UpdateStickerSetIdentifier( StickerSetIdentifier &now, const MTPInputStickerSet &from) { +#if 0 // mtp now = from.match([&](const MTPDinputStickerSetID &data) { return StickerSetIdentifier{ .id = data.vid().v, @@ -90,6 +102,46 @@ void UpdateStickerSetIdentifier( }, [](const auto &) { return StickerSetIdentifier(); }); +#endif +} + +[[nodiscard]] QPainterPath PathFromOutline( + const QVector &outline) { + auto result = QPainterPath(); + const auto point = [](const Tdb::TLpoint &data) { + return QPointF(data.data().vx().v, data.data().vy().v); + }; + for (const auto &path : outline) { + const auto &commands = path.data().vcommands().v; + if (commands.isEmpty()) { + continue; + } + const auto start = commands.back().match([&](const auto &data) { + return data.vend_point(); + }); + result.moveTo(point(start)); + for (const auto &command : commands) { + command.match([&](const Tdb::TLDvectorPathCommandLine &data) { + result.lineTo(point(data.vend_point())); + }, [&](const Tdb::TLDvectorPathCommandCubicBezierCurve &data) { + result.cubicTo( + point(data.vstart_control_point()), + point(data.vend_control_point()), + point(data.vend_point())); + }); + } + } + return result; +} + +[[nodiscard]] Fn PathFromOutlineGenerator( + const QVector &outline) { + if (outline.isEmpty()) { + return nullptr; + } + return [outline] { + return PathFromOutline(outline); + }; } } // namespace @@ -317,6 +369,333 @@ DocumentData::~DocumentData() { destroyLoader(); } +DocumentId DocumentData::IdFromTdb(const TLdocument &data) { + return data.data().vdocument().data().vid().v; +} + +void DocumentData::updateThumbnails( + const TLminithumbnail *inlineThumbnail, + const TLthumbnail *thumbnail, + const Tdb::TLfile *premiumStickerAnimation) { + const auto inlineThumbnailBytes = inlineThumbnail + ? inlineThumbnail->data().vdata().v + : QByteArray(); + auto thumbnailLocation = ImageWithLocation(); + auto videoThumbnailLocation = ImageWithLocation(); + if (thumbnail) { + const auto &fields = thumbnail->data(); + fields.vformat().match([&](const auto &data) { + using T = decltype(data); + if constexpr (TLDthumbnailFormatJpeg::Is() + || TLDthumbnailFormatPng::Is() + || TLDthumbnailFormatWebp::Is()) { + thumbnailLocation = Images::FromThumbnail(*thumbnail); + } else if constexpr (TLDthumbnailFormatGif::Is() + || TLDthumbnailFormatMpeg4::Is()) { + videoThumbnailLocation = Images::FromThumbnail(*thumbnail); + } + }); + } + const auto isPremiumSticker = (premiumStickerAnimation != nullptr); + if (isPremiumSticker) { + videoThumbnailLocation = Images::FromTdbFile( + *premiumStickerAnimation, + kStickerSideSize, + kStickerSideSize); + } + updateThumbnails(InlineImageLocation{ + .bytes = inlineThumbnailBytes, + }, thumbnailLocation, videoThumbnailLocation, isPremiumSticker); +} + +void DocumentData::setFromTdb(const TLdocument &data) { + const auto &fields = data.data(); + setFileName(fields.vfile_name().v); + setMimeString(fields.vmime_type().v); + updateThumbnails(fields.vminithumbnail(), fields.vthumbnail(), nullptr); + setTdbLocation(fields.vdocument()); + recountIsImage(); + dimensions = QSize(); + type = FileDocument; + _additional = nullptr; + _flags = (_flags | kStreamingSupportedUnknown) + & ~Flag::HasAttachedStickers; +} + +DocumentId DocumentData::IdFromTdb(const TLvideo &data) { + return data.data().vvideo().data().vid().v; +} + +void DocumentData::setFromTdb(const TLvideo &data) { + const auto &fields = data.data(); + setFileName(fields.vfile_name().v); + setMimeString(fields.vmime_type().v); + updateThumbnails(fields.vminithumbnail(), fields.vthumbnail(), nullptr); + setTdbLocation(fields.vvideo()); + recountIsImage(); + setMaybeSupportsStreaming(fields.vsupports_streaming().v); + dimensions = QSize(fields.vwidth().v, fields.vheight().v); + type = VideoDocument; + _additional = nullptr; + _duration = fields.vduration().v * crl::time(1000); + if (fields.vhas_stickers().v) { + _flags |= Flag::HasAttachedStickers; + } else { + _flags &= ~Flag::HasAttachedStickers; + } +} + +DocumentId DocumentData::IdFromTdb(const TLaudio &data) { + return data.data().vaudio().data().vid().v; +} + +void DocumentData::setFromTdb(const TLaudio &data) { + const auto &fields = data.data(); + setFileName(fields.vfile_name().v); + setMimeString(fields.vmime_type().v); + updateThumbnails( + fields.valbum_cover_minithumbnail(), + fields.valbum_cover_thumbnail(), + nullptr); + setTdbLocation(fields.vaudio()); + recountIsImage(); + setMaybeSupportsStreaming(true); + dimensions = QSize(); + type = SongDocument; + _additional = std::make_unique(); + _duration = fields.vduration().v * crl::time(1000); + _flags &= ~Flag::HasAttachedStickers; + const auto songData = song(); + songData->title = fields.vtitle().v; + songData->performer = fields.vperformer().v; + if (!hasThumbnail() + && !songData->title.isEmpty() + && !songData->performer.isEmpty() + && !fields.vexternal_album_covers().v.empty()) { + _flags |= Flag::PossibleCoverThumbnail; + updateThumbnails( + nullptr, + &fields.vexternal_album_covers().v.front(), + nullptr); + loadThumbnail({}); + } +} + +DocumentId DocumentData::IdFromTdb(const TLnotificationSound &data) { + return data.data().vid().v; +} + +void DocumentData::setFromTdb(const TLnotificationSound &data) { + setFromTdb(Tdb::tl_audio( + data.data().vduration(), + data.data().vtitle(), + Tdb::tl_string(), // performer + tl_string(data.data().vtitle().v + ".mp3"), // file_name + Tdb::tl_string("audio/mp3"), + std::nullopt, // album_cover_minithumbnail + std::nullopt, // album_cover_thumbnail + tl_vector(), // external_album_covers + data.data().vsound())); + date = data.data().vdate().v; +} + +DocumentId DocumentData::IdFromTdb(const TLanimation &data) { + return data.data().vanimation().data().vid().v; +} + +void DocumentData::setFromTdb(const TLanimation &data) { + const auto &fields = data.data(); + setFileName(fields.vfile_name().v); + setMimeString(fields.vmime_type().v); + updateThumbnails(fields.vminithumbnail(), fields.vthumbnail(), nullptr); + setTdbLocation(fields.vanimation()); + setMaybeSupportsStreaming(true); + dimensions = QSize(fields.vwidth().v, fields.vheight().v); + type = AnimatedDocument; + _additional = nullptr; + _duration = fields.vduration().v * crl::time(1000); + if (fields.vhas_stickers().v) { + _flags |= Flag::HasAttachedStickers; + } else { + _flags &= ~Flag::HasAttachedStickers; + } + recountIsImage(); +} + +DocumentId DocumentData::IdFromTdb(const TLsticker &data) { + const auto &fields = data.data(); + return fields.vfull_type().match([]( + const TLDstickerFullTypeCustomEmoji &data) -> DocumentId { + return data.vcustom_emoji_id().v; + }, [&](const auto &) -> DocumentId { + return fields.vid().v + ? fields.vid().v + : fields.vsticker().data().vid().v; + }); +} + +void DocumentData::setFromTdb(const TLsticker &data) { + const auto &fields = data.data(); + const auto stickerType = fields.vformat().match([&]( + const TLDstickerFormatTgs &) { + return StickerType::Tgs; + }, [&](const TLDstickerFormatWebp &) { + return StickerType::Webp; + }, [&](const TLDstickerFormatWebm &) { + return StickerType::Webm; + }); + setFileName(QString());// animated ? u"sticker.tgs"_q : u"sticker.webp"_q + setMimeString((stickerType == StickerType::Tgs) + ? u"application/x-tgsticker"_q + : (stickerType == StickerType::Webm) + ? u"video/webm"_q + : u"image/webp"_q); + + const auto premiumAnimation = fields.vfull_type().match([&]( + const TLDstickerFullTypeRegular &data) -> const TLfile* { + return data.vpremium_animation(); + }, [](const auto &) -> const TLfile* { + return nullptr; + }); + updateThumbnails(nullptr, fields.vthumbnail(), premiumAnimation); + + setTdbLocation(fields.vsticker()); + dimensions = (stickerType == StickerType::Tgs) + ? kLottieStickerDimensions + : QSize(fields.vwidth().v, fields.vheight().v); + type = StickerDocument; + _additional = std::make_unique(); + _duration = -1; + _flags = (_flags | kStreamingSupportedUnknown) + & ~Flag::HasAttachedStickers; + const auto stickerData = sticker(); + stickerData->type = stickerType; + stickerData->setType = fields.vfull_type().match([&]( + const TLDstickerFullTypeRegular &data) { + return Data::StickersType::Stickers; + }, [&](const TLDstickerFullTypeMask &data) { + return Data::StickersType::Masks; + }, [&](const TLDstickerFullTypeCustomEmoji &data) { + if (data.vneeds_repainting().v) { + _flags |= Flag::UseTextColor; + } + return Data::StickersType::Emoji; + }); + + stickerData->alt = fields.vemoji().v; + stickerData->outlineGenerator = PathFromOutlineGenerator( + fields.voutline().v); + if (!sticker()->set.id) { + sticker()->set = StickerSetIdentifier{ + .id = uint64(fields.vset_id().v), + }; + } + if ((size > Storage::kMaxStickerBytesSize) + || (!sticker()->isLottie() + && !GoodStickerDimensions( + dimensions.width(), + dimensions.height()))) { + type = FileDocument; + _additional = nullptr; + } + recountIsImage(); +} + +DocumentId DocumentData::IdFromTdb(const TLvoiceNote &data) { + return data.data().vvoice().data().vid().v; +} + +void DocumentData::setFromTdb(const TLvoiceNote &data) { + const auto &fields = data.data(); + setFileName(QString()); + setMimeString(fields.vmime_type().v); + updateThumbnails(nullptr, nullptr, nullptr); + setTdbLocation(fields.vvoice()); + setMaybeSupportsStreaming(true); + dimensions = QSize(); + type = VoiceDocument; + _additional = std::make_unique(); + _duration = fields.vduration().v * crl::time(1000); + _flags &= ~Flag::HasAttachedStickers; + const auto voiceData = voice(); + voiceData->waveform = documentWaveformDecode(fields.vwaveform().v); + voiceData->wavemax = voiceData->waveform.empty() + ? uchar(0) + : *ranges::max_element(voiceData->waveform); + recountIsImage(); +} + +DocumentId DocumentData::IdFromTdb(const TLvideoNote &data) { + return data.data().vvideo().data().vid().v; +} + +void DocumentData::setFromTdb(const TLvideoNote &data) { + const auto &fields = data.data(); + setFileName(QString()); + setMimeString(u"video/mp4"_q); + updateThumbnails(fields.vminithumbnail(), fields.vthumbnail(), nullptr); + setTdbLocation(fields.vvideo()); + setMaybeSupportsStreaming(true); + dimensions = QSize(fields.vlength().v, fields.vlength().v); + type = RoundVideoDocument; + _additional = std::make_unique(); + _duration = fields.vduration().v * crl::time(1000); + _flags &= ~Flag::HasAttachedStickers; + recountIsImage(); +} + +DocumentId DocumentData::IdFromTdb(const TLstoryVideo &data) { + return data.data().vvideo().data().vid().v; +} + +void DocumentData::setFromTdb(const TLstoryVideo &data) { + const auto &fields = data.data(); + setFileName(QString()); + setMimeString(u"video/mp4"_q); // We don't know for sure. + updateThumbnails(fields.vminithumbnail(), fields.vthumbnail(), nullptr); + setTdbLocation(fields.vvideo()); + setMaybeSupportsStreaming(true); + dimensions = QSize(fields.vwidth().v, fields.vheight().v); + type = VideoDocument; + if (fields.vis_animation().v) { + _flags |= Flag::SilentVideo; + } else { + _flags &= ~Flag::SilentVideo; + } + const auto prefix = fields.vpreload_prefix_size().v; + _videoPreloadPrefix = (prefix > 0 && prefix < kMaxAllowedPreloadPrefix) + ? prefix + : 0; + _additional = nullptr; + _duration = crl::time(base::SafeRound(fields.vduration().v * 1000)); + if (fields.vhas_stickers().v) { + _flags |= Flag::HasAttachedStickers; + } else { + _flags &= ~Flag::HasAttachedStickers; + } + recountIsImage(); +} + +void DocumentData::setSimpleFromTdb( + const TLfile &data, + SimpleDocumentType type) { + setTdbLocation(data); + switch (type) { + case SimpleDocumentType::BotAttachSvgIcon: + forceToCache(true); + break; + case SimpleDocumentType::TgsSticker: + forceToCache(true); + this->type = StickerDocument; + _additional = std::make_unique(); + sticker()->setType = Data::StickersType::Stickers; + sticker()->type = StickerType::Tgs; + break; + default: + Unexpected("SimpleDocumentType in DocumentData::setSimpleFromTdb."); + } +} + Data::Session &DocumentData::owner() const { return *_owner; } @@ -325,6 +704,7 @@ Main::Session &DocumentData::session() const { return _owner->session(); } +#if 0 // mtp void DocumentData::setattributes( const QVector &attributes) { _duration = -1; @@ -460,6 +840,7 @@ void DocumentData::setattributes( setMaybeSupportsStreaming(true); } } +#endif void DocumentData::validateLottieSticker() { if (type == FileDocument @@ -541,6 +922,152 @@ void DocumentData::updateThumbnails( [&](Data::FileOrigin origin) { loadVideoThumbnail(origin); }); } +void DocumentData::setTdbLocation(const TLfile &file) { + const auto was = _tdbFile.fileId; + _tdbFile = TdbFileLocation(file); + _owner->documentFileIdUpdated(this, was, _tdbFile.fileId); + applyTdbFile(file); +} + +void DocumentData::applyTdbFile(const TLfile &file) { + size = file.data().vsize().v; + const auto &remote = file.data().vremote().data(); + if (!remote.vis_uploading_completed().v) { + if (!uploadingData) { + uploadingData = std::make_unique(size); + } + uploadingData->offset = remote.vuploaded_size().v; + _owner->requestDocumentViewRepaint(this); + } else if (uploadingData) { + uploadingData = nullptr; + _owner->requestDocumentViewRepaint(this); + } + const auto &local = file.data().vlocal().data(); + if (local.vis_downloading_completed().v && !local.vpath().v.isEmpty()) { + _location = Core::FileLocation(local.vpath().v); + } +} + +void DocumentData::setFromLocal(const Data::DocumentLocalData &data) { + Expects(id == data.id); + + _flags &= ~(Flag::ImageType + | Flag::HasAttachedStickers + | Flag::UseTextColor + | kStreamingSupportedMask); + _flags |= kStreamingSupportedUnknown; + + type = FileDocument; + + date = data.added; + setFileName(data.name); + setMimeString(data.mime); + size = data.size; + + if (!data.thumb.isNull()) { + const auto isPremiumSticker = false; + updateThumbnails( + {}, + ImageWithLocation{ + .location = ImageLocation( + {.data = InMemoryLocation{.bytes = data.thumbBytes } }, + data.thumb.width(), + data.thumb.height()), + .bytes = data.thumbBytes, + .preloaded = data.thumb, + .bytesCount = int(data.thumbBytes.size()), + }, + {}, + isPremiumSticker); + } + + validateLottieSticker(); + + if (type == FileDocument) { + if (data.voiceDuration) { + type = VoiceDocument; + _additional = std::make_unique(); + } else if (data.audioDuration + || !data.audioTitle.isEmpty() + || !data.audioPerformer.isEmpty()) { + type = SongDocument; + _additional = std::make_unique(); + } + } + if (const auto voiceData = voice() ? voice() : round()) { + _duration = data.voiceDuration; + voiceData->waveform = documentWaveformDecode(data.voiceWaveform); + voiceData->wavemax = voiceData->waveform.empty() + ? uchar(0) + : *ranges::max_element(voiceData->waveform); + } else if (const auto songData = song()) { + _duration = data.audioDuration; + songData->title = data.audioTitle; + songData->performer = data.audioPerformer; + refreshPossibleCoverThumbnail(); + } + + if (!data.imageDimensions.isEmpty()) { + dimensions = data.imageDimensions; + } + if (data.imageIsSticker) { + const auto was = type; + if (type == FileDocument || type == VideoDocument) { + type = StickerDocument; + _additional = std::make_unique(); + } + if (const auto info = sticker()) { + info->setType = Data::StickersType::Stickers; + } + } + if (data.videoDuration > 0 || !data.videoDimensions.isEmpty()) { + if (type == FileDocument) { + type = VideoDocument; + } else if (const auto info = sticker()) { + info->type = StickerType::Webm; + } + _duration = data.videoDuration; + setMaybeSupportsStreaming(data.videoSupportsStreaming); + dimensions = data.videoDimensions; + } + if (data.imageAnimated || data.videoIsGifv) { + if (type == FileDocument + || type == VideoDocument + || (sticker() && sticker()->type != StickerType::Webm)) { + type = AnimatedDocument; + _additional = nullptr; + } + } + + if (type == StickerDocument + && ((size > Storage::kMaxStickerBytesSize) + || (!sticker()->isLottie() + && !GoodStickerDimensions( + dimensions.width(), + dimensions.height())))) { + type = FileDocument; + _additional = nullptr; + } else if (type == FileDocument + && hasMimeType(u"video/webm"_q) + && (size < Storage::kMaxStickerBytesSize) + && GoodStickerDimensions(dimensions.width(), dimensions.height())) { + type = StickerDocument; + _additional = std::make_unique(); + sticker()->type = StickerType::Webm; + } + if (isAudioFile() || isAnimation() || isVoiceMessage()) { + setMaybeSupportsStreaming(true); + } +} + +FileId DocumentData::tdbFileId() const { + return _tdbFile.fileId; +} + +uint64 DocumentData::persistentId() const { + return _tdbFile.fileId ? _tdbFile.hash : id; +} + bool DocumentData::isWallPaper() const { return (type == WallPaperDocument); } @@ -735,6 +1262,11 @@ PhotoData *DocumentData::goodThumbnailPhoto() const { } Storage::Cache::Key DocumentData::bigFileBaseCacheKey() const { + if (_tdbFile.fileId) { + return TdbFileLocation::BigFileBaseCacheKey( + _tdbFile.hash, + kTdbLocationTypeDocument); + } return hasRemoteLocation() ? StorageFileLocation( _dc, @@ -968,6 +1500,7 @@ void DocumentData::save( fromCloud, autoLoading, cacheTag()); +#if 0 // mtp } else if (hasWebLocation()) { _loader = std::make_unique( &session(), @@ -977,6 +1510,7 @@ void DocumentData::save( fromCloud, autoLoading, cacheTag()); +#endif } else if (!_access && !_url.isEmpty()) { _loader = std::make_unique( &session(), @@ -985,6 +1519,19 @@ void DocumentData::save( fromCloud, autoLoading, cacheTag()); + } else if (_tdbFile.fileId) { + _loader = std::make_unique( + &session(), + _tdbFile.fileId, + locationType(), + toFile, + size, + size, + (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), + fromCloud, + autoLoading, + cacheTag()); +#if 0 // mtp } else { _loader = std::make_unique( &session(), @@ -1005,6 +1552,7 @@ void DocumentData::save( fromCloud, autoLoading, cacheTag()); +#endif } handleLoaderUpdates(); } @@ -1309,6 +1857,9 @@ const RoundData *DocumentData::round() const { } bool DocumentData::hasRemoteLocation() const { + if (_tdbFile.fileId) { + return true; + } return (_dc != 0 && _access != 0); } @@ -1349,6 +1900,7 @@ int DocumentData::videoPreloadPrefix() const { return _videoPreloadPrefix; } +#if 0 // mtp StorageFileLocation DocumentData::videoPreloadLocation() const { return hasRemoteLocation() ? StorageFileLocation( @@ -1361,6 +1913,7 @@ StorageFileLocation DocumentData::videoPreloadLocation() const { MTP_string())) : StorageFileLocation(); } +#endif auto DocumentData::createStreamingLoader( Data::FileOrigin origin, @@ -1380,6 +1933,17 @@ auto DocumentData::createStreamingLoader( return result; } } + if (_tdbFile.fileId) { + return std::make_unique( + &session().tdb(), + _tdbFile.fileId, + TdbFileLocation::BigFileBaseCacheKey( + _tdbFile.hash, + kTdbLocationTypeDocument), + size); + } + return nullptr; +#if 0 // mtp return hasRemoteLocation() ? std::make_unique( &session().downloader(), @@ -1394,6 +1958,7 @@ auto DocumentData::createStreamingLoader( size, origin) : nullptr; +#endif } bool DocumentData::hasWebLocation() const { @@ -1408,6 +1973,7 @@ bool DocumentData::isNull() const { && _location.isEmpty(); } +#if 0 // mtp MTPInputDocument DocumentData::mtpInput() const { if (_access) { return MTP_inputDocument( @@ -1427,6 +1993,7 @@ void DocumentData::refreshFileReference(const QByteArray &value) { _thumbnail.location.refreshFileReference(value); _videoThumbnail.location.refreshFileReference(value); } +#endif QString DocumentData::filename() const { return _filename; @@ -1598,6 +2165,7 @@ void DocumentData::recountIsImage() { } } +#if 0 // mtp void DocumentData::setRemoteLocation( int32 dc, uint64 access, @@ -1622,6 +2190,7 @@ void DocumentData::setRemoteLocation( } } } +#endif void DocumentData::setStoryMedia(bool value) { if (value) { @@ -1644,6 +2213,7 @@ void DocumentData::setWebLocation(const WebFileLocation &location) { _urlLocation = location; } +#if 0 // mtp void DocumentData::collectLocalData(not_null local) { if (local == this) { return; @@ -1660,3 +2230,4 @@ void DocumentData::collectLocalData(not_null local) { session().local().writeFileLocation(mediaKey(), _location); } } +#endif diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 59891c005fba3..bab100bc736b7 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -14,6 +14,22 @@ For license and copyright information please follow this link: #include "core/file_location.h" #include "ui/image/image.h" +namespace Tdb { +class TLfile; +class TLdocument; +class TLvideo; +class TLaudio; +class TLanimation; +class TLsticker; +class TLvoiceNote; +class TLvideoNote; +class TLthumbnail; +class TLminithumbnail; +class TLclosedVectorPath; +class TLnotificationSound; +class TLstoryVideo; +} // namespace Tdb + enum class ChatRestriction; class mtpFileLoader; @@ -34,10 +50,41 @@ class Loader; } // namespace Media namespace Data { + class Session; class DocumentMedia; class ReplyPreview; enum class StickersType : uchar; + +struct DocumentLocalData { + DocumentId id = 0; + + TimeId added = 0; + QString name; + QString mime; + int64 size = 0; + + QImage thumb; + QString thumbName; + QByteArray thumbBytes; + + QSize imageDimensions; + bool imageIsSticker = false; + bool imageAnimated = false; + + QString audioTitle; + QString audioPerformer; + crl::time audioDuration = 0; + + QSize videoDimensions; + crl::time videoDuration = 0; + bool videoSupportsStreaming = false; + bool videoIsGifv = false; + + crl::time voiceDuration = 0; + QByteArray voiceWaveform; +}; + } // namespace Data namespace Main { @@ -76,6 +123,8 @@ struct StickerData : public DocumentAdditionalData { StickerSetIdentifier set; StickerType type = StickerType::Webp; Data::StickersType setType = Data::StickersType(); + + Fn outlineGenerator; }; struct SongData : public DocumentAdditionalData { @@ -91,6 +140,11 @@ struct VoiceData : public DocumentAdditionalData { using RoundData = VoiceData; +enum class SimpleDocumentType { + BotAttachSvgIcon, + TgsSticker, +}; + namespace Serialize { class Document; } // namespace Serialize; @@ -100,11 +154,40 @@ class DocumentData final { DocumentData(not_null owner, DocumentId id); ~DocumentData(); + [[nodiscard]] static DocumentId IdFromTdb(const Tdb::TLdocument &data); + void setFromTdb(const Tdb::TLdocument &data); + [[nodiscard]] static DocumentId IdFromTdb(const Tdb::TLvideo &data); + void setFromTdb(const Tdb::TLvideo &data); + [[nodiscard]] static DocumentId IdFromTdb(const Tdb::TLaudio &data); + void setFromTdb(const Tdb::TLaudio &data); + [[nodiscard]] static DocumentId IdFromTdb(const Tdb::TLanimation &data); + void setFromTdb(const Tdb::TLanimation &data); + [[nodiscard]] static DocumentId IdFromTdb(const Tdb::TLsticker &data); + void setFromTdb(const Tdb::TLsticker &data); + [[nodiscard]] static DocumentId IdFromTdb(const Tdb::TLvoiceNote &data); + void setFromTdb(const Tdb::TLvoiceNote &data); + [[nodiscard]] static DocumentId IdFromTdb(const Tdb::TLvideoNote &data); + void setFromTdb(const Tdb::TLvideoNote &data); + [[nodiscard]] static DocumentId IdFromTdb( + const Tdb::TLnotificationSound &data); + void setFromTdb(const Tdb::TLnotificationSound &data); + [[nodiscard]] static DocumentId IdFromTdb( + const Tdb::TLstoryVideo &data); + void setFromTdb(const Tdb::TLstoryVideo &data); + void setSimpleFromTdb(const Tdb::TLfile &data, SimpleDocumentType type); + void applyTdbFile(const Tdb::TLfile &file); + void setFromLocal(const Data::DocumentLocalData &data); + + [[nodiscard]] FileId tdbFileId() const; + [[nodiscard]] uint64 persistentId() const; + [[nodiscard]] Data::Session &owner() const; [[nodiscard]] Main::Session &session() const; +#if 0 // mtp void setattributes( const QVector &attributes); +#endif void automaticLoadSettingsChanged(); @@ -236,15 +319,18 @@ class DocumentData final { void setStoryMedia(bool value); [[nodiscard]] bool storyMedia() const; +#if 0 // mtp void setRemoteLocation( int32 dc, uint64 access, const QByteArray &fileReference); +#endif void setContentUrl(const QString &url); void setWebLocation(const WebFileLocation &location); [[nodiscard]] bool hasRemoteLocation() const; [[nodiscard]] bool hasWebLocation() const; [[nodiscard]] bool isNull() const; +#if 0 // mtp [[nodiscard]] MTPInputDocument mtpInput() const; [[nodiscard]] QByteArray fileReference() const; void refreshFileReference(const QByteArray &value); @@ -254,6 +340,7 @@ class DocumentData final { // and it has downloaded data, we can collect that data from it // to (this) received from the server "same" document. void collectLocalData(not_null local); +#endif [[nodiscard]] QString filename() const; [[nodiscard]] QString mimeString() const; @@ -276,7 +363,9 @@ class DocumentData final { void setInappPlaybackFailed(); [[nodiscard]] bool inappPlaybackFailed() const; [[nodiscard]] int videoPreloadPrefix() const; +#if 0 // mtp [[nodiscard]] StorageFileLocation videoPreloadLocation() const; +#endif DocumentId id = 0; int64 size = 0; @@ -330,7 +419,9 @@ class DocumentData final { | Flag::StreamingMaybeNo; static constexpr Flags kStreamingSupportedNo = Flags(); +#if 0 // mtp friend class Serialize::Document; +#endif [[nodiscard]] LocationType locationType() const; void validateLottieSticker(); @@ -346,6 +437,12 @@ class DocumentData final { void refreshPossibleCoverThumbnail(); + void updateThumbnails( + const Tdb::TLminithumbnail *inlineThumbnail, + const Tdb::TLthumbnail *thumbnail, + const Tdb::TLfile *premiumStickerAnimation); + void setTdbLocation(const Tdb::TLfile &file); + const not_null _owner; int _videoPreloadPrefix = 0; @@ -358,6 +455,9 @@ class DocumentData final { QString _mimeString; WebFileLocation _urlLocation; + // TDLib location type. + TdbFileLocation _tdbFile; + QByteArray _inlineThumbnailBytes; Data::CloudFile _thumbnail; Data::CloudFile _videoThumbnail; @@ -391,3 +491,26 @@ QString DocumentFileNameForSave( bool forceSavingAs = false, const QString &already = QString(), const QDir &dir = QDir()); + +namespace Data { + +template +struct IsTdbDocumentHelper { + template < + typename U, + typename = decltype(DocumentData::IdFromTdb(std::declval()))> + static char test(const U &); + static long test(...); + + static constexpr bool value() { + static_assert(sizeof(char) != sizeof(long)); + return sizeof(test(std::declval())) == sizeof(char); + } +}; + +template +DocumentId DocumentIdFromTdb(const T &data) { + return DocumentData::IdFromTdb(data); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_document_media.cpp b/Telegram/SourceFiles/data/data_document_media.cpp index 37ebbf8436fea..35c2c36580916 100644 --- a/Telegram/SourceFiles/data/data_document_media.cpp +++ b/Telegram/SourceFiles/data/data_document_media.cpp @@ -27,6 +27,7 @@ For license and copyright information please follow this link: #include "storage/file_download.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/image/image.h" +#include "tdb/tdb_tl_scheme.h" #include #include @@ -194,7 +195,7 @@ Image *DocumentMedia::thumbnailInline() const { if (!_inlineThumbnail && !_owner->inlineThumbnailIsPath()) { const auto bytes = _owner->inlineThumbnailBytes(); if (!bytes.isEmpty()) { - auto image = Images::FromInlineBytes(bytes); + auto image = Images::Read({ .content = bytes }).image; if (image.isNull()) { _owner->clearInlineThumbnailBytes(); } else { @@ -206,12 +207,21 @@ Image *DocumentMedia::thumbnailInline() const { } const QPainterPath &DocumentMedia::thumbnailPath() const { - if (_pathThumbnail.isEmpty() && _owner->inlineThumbnailIsPath()) { - const auto bytes = _owner->inlineThumbnailBytes(); - if (!bytes.isEmpty()) { - _pathThumbnail = Images::PathFromInlineBytes(bytes); - if (_pathThumbnail.isEmpty()) { - _owner->clearInlineThumbnailBytes(); + if (_pathThumbnail.isEmpty()) { + if (_owner->inlineThumbnailIsPath()) { + const auto bytes = _owner->inlineThumbnailBytes(); + if (!bytes.isEmpty()) { + _pathThumbnail = Images::PathFromInlineBytes(bytes); + if (_pathThumbnail.isEmpty()) { + _owner->clearInlineThumbnailBytes(); + } + } + } else if (const auto sticker = _owner->sticker()) { + if (const auto &generator = sticker->outlineGenerator) { + _pathThumbnail = generator(); + if (_pathThumbnail.isEmpty()) { + sticker->outlineGenerator = nullptr; + } } } } @@ -320,6 +330,7 @@ void DocumentMedia::automaticLoad( true); } +#if 0 // mtp void DocumentMedia::collectLocalData(not_null local) { if (const auto image = local->_goodThumbnail.get()) { _goodThumbnail = std::make_unique(image->original()); @@ -337,6 +348,7 @@ void DocumentMedia::collectLocalData(not_null local) { _videoThumbnailBytes = local->_videoThumbnailBytes; _flags = local->_flags; } +#endif void DocumentMedia::setBytes(const QByteArray &bytes) { if (!bytes.isEmpty()) { diff --git a/Telegram/SourceFiles/data/data_document_media.h b/Telegram/SourceFiles/data/data_document_media.h index 9dfb3588a4f1f..a6951a50eb3d3 100644 --- a/Telegram/SourceFiles/data/data_document_media.h +++ b/Telegram/SourceFiles/data/data_document_media.h @@ -82,7 +82,9 @@ class DocumentMedia final { void automaticLoad(Data::FileOrigin origin, const HistoryItem *item); +#if 0 // mtp void collectLocalData(not_null local); +#endif // For DocumentData. static void CheckGoodThumbnail(not_null document); diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp index 1d7ea12af01b2..4e1f9166d9339 100644 --- a/Telegram/SourceFiles/data/data_download_manager.cpp +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -36,9 +36,14 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "styles/style_layers.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kClearLoadingTimeout = 5 * crl::time(1000); constexpr auto kMaxFileSize = 4000 * int64(1024 * 1024); constexpr auto kMaxResolvePerAttempt = 100; @@ -56,6 +61,11 @@ constexpr auto ByDocument = [](const auto &entry) { return entry.object.document; }; +template +[[nodiscard]] uint64 IdFrom(Type *item) { + return item->persistentId(); +} + [[nodiscard]] uint64 PeerAccessHash(not_null peer) { if (const auto user = peer->asUser()) { return user->accessHash(); @@ -307,8 +317,12 @@ void DownloadManager::addLoaded( auto &data = sessionData(item); const auto id = object.document +#if 0 // mtp ? DownloadId{ object.document->id, DownloadType::Document } : DownloadId{ object.photo->id, DownloadType::Photo }; +#endif + ? DownloadId{ IdFrom(object.document), DownloadType::Document } + : DownloadId{ IdFrom(object.photo), DownloadType::Photo }; data.downloaded.push_back({ .download = id, .started = started, @@ -633,12 +647,17 @@ void DownloadManager::resolve( || data.resolveSentTotal >= kMaxResolvePerAttempt) { return; } +#if 0 // mtp struct Prepared { uint64 peerAccessHash = 0; QVector ids; }; +#endif auto &owner = session->data(); +#if 0 // mtp auto prepared = base::flat_map(); +#endif + auto prepared = base::flat_set(); auto last = begin(data.downloaded); auto from = last + (data.resolveNeeded - data.resolveSentTotal); for (auto i = from; i != last;) { @@ -649,6 +668,7 @@ void DownloadManager::resolve( // Mark as deleted. id.path = QString(); } else if (!owner.message(id.itemId) && IsServerMsgId(msgId)) { +#if 0 // mtp const auto groupByPeer = peerIsChannel(id.itemId.peer) ? id.itemId.peer : session->userPeerId(); @@ -657,6 +677,8 @@ void DownloadManager::resolve( perPeer.peerAccessHash = id.peerAccessHash; } perPeer.ids.push_back(MTP_inputMessageID(MTP_int(msgId.bare))); +#endif + prepared.emplace(id.itemId); } if (++data.resolveSentTotal >= kMaxResolvePerAttempt) { break; @@ -672,6 +694,16 @@ void DownloadManager::resolve( --sessionData(session).resolveSentRequests; check(); }; + for (const auto &itemId : prepared) { + session->sender().request(TLgetMessage( + peerToTdbChat(itemId.peer), + tl_int53(itemId.msg.bare) + )).done([=](const TLmessage &result) { + session->data().processMessage(result, NewMessageType::Existing); + requestFinished(); + }).fail(requestFinished).send(); + } +#if 0 // mtp for (auto &[peer, perPeer] : prepared) { if (const auto channelId = peerToChannel(peer)) { session->api().request(MTPchannels_GetMessages( @@ -694,6 +726,7 @@ void DownloadManager::resolve( }).fail(requestFinished).send(); } } +#endif data.resolveSentRequests += prepared.size(); check(); } @@ -713,10 +746,16 @@ void DownloadManager::resolveRequestsFinished( const auto document = media ? media->document() : nullptr; const auto photo = media ? media->photo() : nullptr; if (i->download.type == DownloadType::Document +#if 0 // mtp && (!document || document->id != i->download.objectId)) { +#endif + && (!document || IdFrom(document) != i->download.objectId)) { generateEntry(session, *i); } else if (i->download.type == DownloadType::Photo +#if 0 // mtp && (!photo || photo->id != i->download.objectId)) { +#endif + && (!photo || IdFrom(photo) != i->download.objectId)) { generateEntry(session, *i); } else { i->object = std::make_unique(DownloadObject{ @@ -752,6 +791,15 @@ void DownloadManager::generateEntry( Expects(!id.object); const auto info = QFileInfo(id.path); + const auto data = Data::DocumentLocalData{ + .id = base::RandomValue(), + .added = TimeId(id.started / 1000), + .name = info.fileName(), + .mime = Core::MimeTypeForFile(info).name(), + .size = id.size, + }; + const auto document = session->data().processDocument(data); +#if 0 // mtp const auto document = session->data().document( base::RandomValue(), 0, // accessHash @@ -768,6 +816,7 @@ void DownloadManager::generateEntry( false, // isPremiumSticker 0, // dc id.size); +#endif document->setLocation(Core::FileLocation(info)); _generatedDocuments.emplace(document); diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 6f386e582cc86..7dd03cf3be6d2 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -19,7 +19,41 @@ For license and copyright information please follow this link: #include "mainwidget.h" #include "storage/localstorage.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { +namespace { + +using namespace Tdb; + +[[nodiscard]] FullReplyTo ReplyToFromTL( + not_null history, + const TLinputMessageReplyTo *reply) { + if (!reply) { + return {}; + } + return reply->match([&](const TLDinputMessageReplyToMessage &data) { + auto result = FullReplyTo{ + .messageId = { history->peer->id, data.vmessage_id().v }, + }; + if (const auto peerId = peerFromTdbChat(data.vchat_id())) { + result.messageId.peer = peerId; + } + if (const auto quote = data.vquote()) { + result.quote = Api::FormattedTextFromTdb(*quote); + } + return result; + }, [&](const TLDinputMessageReplyToStory &data) { + if (const auto id = peerFromTdbChat(data.vstory_sender_chat_id())) { + return FullReplyTo{ + .storyId = { id, data.vstory_id().v }, + }; + } + return FullReplyTo(); + }); +} + +} // namespace WebPageDraft WebPageDraft::FromItem(not_null item) { const auto previewMedia = item->media(); @@ -65,6 +99,7 @@ Draft::Draft( , webpage(webpage) { } +#if 0 // goodToRemove void ApplyPeerCloudDraft( not_null session, PeerId peerId, @@ -113,6 +148,48 @@ void ApplyPeerCloudDraft( history->setCloudDraft(std::move(cloudDraft)); history->applyCloudDraft(topicRootId); } +#endif + +void ApplyPeerCloudDraft( + not_null session, + PeerId peerId, + MsgId topicRootId, + const Tdb::TLDdraftMessage &draft) { + draft.vinput_message_text().match([&](const Tdb::TLDinputMessageText &d) { + const auto history = session->data().history(peerId); + const auto date = draft.vdate().v; + if (history->skipCloudDraftUpdate(topicRootId, date)) { + return; + } + const auto text = Api::FormattedTextFromTdb(d.vtext()); + const auto textWithTags = TextWithTags{ + text.text, + TextUtilities::ConvertEntitiesToTextTags(text.entities) + }; + auto replyTo = ReplyToFromTL(history, draft.vreply_to()); + replyTo.topicRootId = topicRootId; + auto webpage = WebPageDraft(); + if (const auto options = d.vlink_preview_options()) { + const auto &fields = options->data(); + webpage.removed = fields.vis_disabled().v; + webpage.invert = fields.vshow_above_text().v; + webpage.forceLargeMedia = fields.vforce_large_media().v; + webpage.forceSmallMedia = fields.vforce_small_media().v; + webpage.url = fields.vurl().v; + }; + auto cloudDraft = std::make_unique( + textWithTags, + replyTo, + MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax), + std::move(webpage)); + cloudDraft->date = date; + + history->setCloudDraft(std::move(cloudDraft)); + history->applyCloudDraft(topicRootId); + }, [](const auto &) { + Unexpected("Unsupported draft content type."); + }); +} void ClearPeerCloudDraft( not_null session, @@ -128,4 +205,16 @@ void ClearPeerCloudDraft( history->applyCloudDraft(topicRootId); } +std::optional LinkPreviewOptions( + const WebPageDraft &webpage) { + return (webpage.removed || !webpage.url.isEmpty()) + ? tl_linkPreviewOptions( + tl_bool(webpage.removed), + tl_string(webpage.url), + tl_bool(webpage.forceSmallMedia), + tl_bool(webpage.forceLargeMedia), + tl_bool(webpage.invert)) + : std::optional(); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_drafts.h b/Telegram/SourceFiles/data/data_drafts.h index 3f5e22a2310ce..3e32ebd61d15d 100644 --- a/Telegram/SourceFiles/data/data_drafts.h +++ b/Telegram/SourceFiles/data/data_drafts.h @@ -17,13 +17,21 @@ namespace Main { class Session; } // namespace Main +namespace Tdb { +class TLDdraftMessage; +class TLlinkPreviewOptions; +} // namespace Tdb + namespace Data { void ApplyPeerCloudDraft( not_null session, PeerId peerId, MsgId topicRootId, + const Tdb::TLDdraftMessage &draft); +#if 0 // goodToRemove const MTPDdraftMessage &draft); +#endif void ClearPeerCloudDraft( not_null session, PeerId peerId, @@ -45,6 +53,9 @@ struct WebPageDraft { = default; }; +[[nodiscard]] std::optional LinkPreviewOptions( + const WebPageDraft &webpage); + struct Draft { Draft() = default; Draft( diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.cpp b/Telegram/SourceFiles/data/data_emoji_statuses.cpp index a394edc0cc53b..50a05a72d2ff0 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.cpp +++ b/Telegram/SourceFiles/data/data_emoji_statuses.cpp @@ -18,13 +18,19 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "ui/controls/tabbed_search.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kRefreshDefaultListEach = 60 * 60 * crl::time(1000); constexpr auto kRecentRequestTimeout = 10 * crl::time(1000); constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000); +#if 0 // mtp [[nodiscard]] std::vector ListFromMTP( const MTPDaccount_emojiStatuses &data) { const auto &list = data.vstatuses().v; @@ -40,12 +46,27 @@ constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000); } return result; } +#endif + +[[nodiscard]] std::vector ListFromTL( + const TLDemojiStatuses &data) { + const auto &list = data.vcustom_emoji_ids().v; + auto result = std::vector(); + result.reserve(list.size()); + for (const auto &status : list) { + result.push_back(status.v); + } + return result; +} } // namespace EmojiStatuses::EmojiStatuses(not_null owner) : _owner(owner) +#if 0 // mtp , _clearingTimer([=] { processClearing(); }) { +#endif +{ refreshDefault(); refreshColored(); @@ -103,6 +124,7 @@ rpl::producer<> EmojiStatuses::defaultUpdates() const { return _defaultUpdated.events(); } +#if 0 // mtp void EmojiStatuses::registerAutomaticClear( not_null user, TimeId until) { @@ -126,6 +148,7 @@ void EmojiStatuses::registerAutomaticClear( } } } +#endif auto EmojiStatuses::emojiGroupsValue() const -> rpl::producer { const_cast(this)->requestEmojiGroups(); @@ -146,37 +169,60 @@ auto EmojiStatuses::profilePhotoGroupsValue() const void EmojiStatuses::requestEmojiGroups() { requestGroups( &_emojiGroups, + tl_emojiCategoryTypeDefault()); +#if 0 // mtp MTPmessages_GetEmojiGroups(MTP_int(_emojiGroups.hash))); +#endif } void EmojiStatuses::requestStatusGroups() { requestGroups( &_statusGroups, + tl_emojiCategoryTypeEmojiStatus()); +#if 0 // mtp MTPmessages_GetEmojiStatusGroups(MTP_int(_statusGroups.hash))); +#endif } void EmojiStatuses::requestProfilePhotoGroups() { requestGroups( &_profilePhotoGroups, + tl_emojiCategoryTypeChatPhoto()); +#if 0 // mtp MTPmessages_GetEmojiProfilePhotoGroups( MTP_int(_profilePhotoGroups.hash))); +#endif } [[nodiscard]] std::vector GroupsFromTL( + not_null owner, + const TLemojiCategories &categories) { +#if 0 // mtp const MTPDmessages_emojiGroups &data) { const auto &list = data.vgroups().v; +#endif + const auto &list = categories.data().vcategories().v; auto result = std::vector(); result.reserve(list.size()); for (const auto &group : list) { const auto &data = group.data(); auto emoticons = ranges::views::all( +#if 0 // mtp data.vemoticons().v ) | ranges::views::transform([](const MTPstring &emoticon) { return qs(emoticon); +#endif + data.vemojis().v + ) | ranges::views::transform([](const TLstring &emoticon) { + return emoticon.v; }) | ranges::to_vector; + const auto icon = owner->processDocument(data.vicon()); result.push_back({ +#if 0 // mtp .iconId = QString::number(data.vicon_emoji_id().v), +#endif + .iconId = QString::number(icon->id), .emoticons = std::move(emoticons), }); } @@ -190,20 +236,28 @@ void EmojiStatuses::requestGroups( if (type->requestId) { return; } +#if 0 // mtp 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&) { - }); + result.match([&](const MTPDmessages_emojiGroups &data) { + type->hash = data.vhash().v; + type->data = GroupsFromTL(data); + }, [](const MTPDmessages_emojiGroupsNotModified&) { + }); +#endif + type->requestId = _owner->session().sender().request( + TLgetEmojiCategories(request) + ).done([=](const TLemojiCategories &result) { + type->requestId = 0; + type->data = GroupsFromTL(_owner, result); }).fail([=] { type->requestId = 0; }).send(); } +#if 0 // mtp void EmojiStatuses::processClearing() { auto minWait = TimeId(0); const auto now = base::unixtime::now(); @@ -239,11 +293,13 @@ void EmojiStatuses::processClearingIn(TimeId wait) { const auto waitms = wait * crl::time(1000); _clearingTimer.callOnce(std::min(waitms, kMaxTimeout)); } +#endif void EmojiStatuses::requestRecent() { if (_recentRequestId) { return; } +#if 0 // mtp auto &api = _owner->session().api(); _recentRequestScheduled = false; _recentRequestId = api.request(MTPaccount_GetRecentEmojiStatuses( @@ -254,6 +310,13 @@ void EmojiStatuses::requestRecent() { updateRecent(data); }, [](const MTPDaccount_emojiStatusesNotModified&) { }); +#endif + auto &api = _owner->session().sender(); + _recentRequestScheduled = false; + _recentRequestId = api.request(TLgetRecentEmojiStatuses( + )).done([=](const TLDemojiStatuses &result) { + _recentRequestId = 0; + updateRecent(result); }).fail([=] { _recentRequestId = 0; _recentHash = 0; @@ -264,6 +327,7 @@ void EmojiStatuses::requestDefault() { if (_defaultRequestId) { return; } +#if 0 // mtp auto &api = _owner->session().api(); _defaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses( MTP_long(_recentHash) @@ -273,6 +337,12 @@ void EmojiStatuses::requestDefault() { updateDefault(data); }, [&](const MTPDaccount_emojiStatusesNotModified &) { }); +#endif + auto &api = _owner->session().sender(); + _defaultRequestId = api.request(TLgetDefaultEmojiStatuses( + )).done([=](const TLDemojiStatuses &result) { + _defaultRequestId = 0; + updateDefault(result); }).fail([=] { _defaultRequestId = 0; _defaultHash = 0; @@ -283,6 +353,7 @@ void EmojiStatuses::requestColored() { if (_coloredRequestId) { return; } +#if 0 // mtp auto &api = _owner->session().api(); _coloredRequestId = api.request(MTPmessages_GetStickerSet( MTP_inputStickerSetEmojiDefaultStatuses(), @@ -294,11 +365,18 @@ void EmojiStatuses::requestColored() { }, [](const MTPDmessages_stickerSetNotModified &) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); +#endif + auto &api = _owner->session().sender(); + _coloredRequestId = api.request(TLgetThemedEmojiStatuses( + )).done([=](const TLDemojiStatuses &result) { + _coloredRequestId = 0; + updateColored(result); }).fail([=] { _coloredRequestId = 0; }).send(); } +#if 0 // mtp void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) { _recentHash = data.vhash().v; _recent = ListFromMTP(data); @@ -320,8 +398,25 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) { } _coloredUpdated.fire({}); } +#endif + +void EmojiStatuses::updateRecent(const TLDemojiStatuses &data) { + _recent = ListFromTL(data); + _recentUpdated.fire({}); +} + +void EmojiStatuses::updateDefault(const TLDemojiStatuses &data) { + _default = ListFromTL(data); + _defaultUpdated.fire({}); +} + +void EmojiStatuses::updateColored(const TLDemojiStatuses &data) { + _colored = ListFromTL(data); + _coloredUpdated.fire({}); +} void EmojiStatuses::set(DocumentId id, TimeId until) { +#if 0 // mtp auto &api = _owner->session().api(); if (_sentRequestId) { api.request(base::take(_sentRequestId)).cancel(); @@ -333,6 +428,15 @@ void EmojiStatuses::set(DocumentId id, TimeId until) { : !until ? MTP_emojiStatus(MTP_long(id)) : MTP_emojiStatusUntil(MTP_long(id), MTP_int(until)) +#endif + auto &api = _owner->session().sender(); + if (_sentRequestId) { + api.request(base::take(_sentRequestId)).cancel(); + } + _owner->session().user()->setEmojiStatus(id, until); + _sentRequestId = api.request(TLsetEmojiStatus(id + ? tl_emojiStatus(tl_int64(id), tl_int32(until)) + : std::optional() )).done([=] { _sentRequestId = 0; }).fail([=] { diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.h b/Telegram/SourceFiles/data/data_emoji_statuses.h index 29bd8491c4085..06ba5b8a6e3ce 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.h +++ b/Telegram/SourceFiles/data/data_emoji_statuses.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "base/timer.h" +namespace Tdb { +class TLDemojiStatuses; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -51,7 +55,9 @@ class EmojiStatuses final { void set(DocumentId id, TimeId until = 0); [[nodiscard]] bool setting() const; +#if 0 // mtp void registerAutomaticClear(not_null user, TimeId until); +#endif using Groups = std::vector; [[nodiscard]] rpl::producer emojiGroupsValue() const; @@ -72,12 +78,18 @@ class EmojiStatuses final { void requestDefault(); void requestColored(); +#if 0 // mtp void updateRecent(const MTPDaccount_emojiStatuses &data); void updateDefault(const MTPDaccount_emojiStatuses &data); void updateColored(const MTPDmessages_stickerSet &data); void processClearingIn(TimeId wait); void processClearing(); +#endif + + void updateRecent(const Tdb::TLDemojiStatuses &data); + void updateDefault(const Tdb::TLDemojiStatuses &data); + void updateColored(const Tdb::TLDemojiStatuses &data); template void requestGroups(not_null type, Request &&request); @@ -102,8 +114,10 @@ class EmojiStatuses final { mtpRequestId _sentRequestId = 0; +#if 0 // mtp base::flat_map, TimeId> _clearing; base::Timer _clearingTimer; +#endif GroupsType _emojiGroups; GroupsType _statusGroups; diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index bbd4ce9872d94..9183af4fa57f2 100644 --- a/Telegram/SourceFiles/data/data_folder.cpp +++ b/Telegram/SourceFiles/data/data_folder.cpp @@ -29,9 +29,13 @@ For license and copyright information please follow this link: #include "mainwidget.h" #include "styles/style_dialogs.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kLoadedChatsMinCount = 20; constexpr auto kShowChatNamesCount = 8; @@ -156,9 +160,11 @@ void Folder::indexNameParts() { void Folder::registerOne(not_null history) { if (_chatsList.indexed()->size() == 1) { updateChatListSortPosition(); +#if 0 // mtp if (!_chatsList.cloudUnreadKnown()) { owner().histories().requestDialogEntry(this); } +#endif } else { updateChatListEntry(); } @@ -344,11 +350,15 @@ int Folder::storiesUnreadCount() const { } void Folder::requestChatListMessage() { +#if 0 // mtp if (!chatListMessageKnown()) { owner().histories().requestDialogEntry(this); } +#endif + Unexpected("Chat list message is always known for folders."); } +#if 0 // mtp TimeId Folder::adjustedChatListTimeId() const { return chatListTimeId(); } @@ -367,7 +377,20 @@ void Folder::applyDialog(const MTPDdialogFolder &data) { session().api().requestDialogs(this); } } +#endif + +void Folder::applyDialog(const TLDupdateUnreadChatCount &data) { + _chatsList.updateCloudUnread(data); + if (!data.vtotal_count().v) { + _chatsList.clear(); + updateChatListExistence(); + } + if (_chatsList.indexed()->size() < kLoadedChatsMinCount) { + session().api().requestDialogs(this); + } +} +#if 0 // mtp void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { const auto folderId = data.vfolder_id().value_or_empty(); if (folderId != 0) { @@ -375,14 +398,17 @@ void Folder::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { } owner().setChatPinned(this, FilterId(), data.is_pinned()); } +#endif int Folder::fixedOnTopIndex() const { return kArchiveFixOnTopIndex; } +#if 0 // mtp bool Folder::shouldBeInChatList() const { return !_chatsList.empty() || (_storiesCount > 0); } +#endif Dialogs::UnreadState Folder::chatListUnreadState() const { return _chatsList.unreadState(); diff --git a/Telegram/SourceFiles/data/data_folder.h b/Telegram/SourceFiles/data/data_folder.h index 7008adac0b852..6cffe5dba163f 100644 --- a/Telegram/SourceFiles/data/data_folder.h +++ b/Telegram/SourceFiles/data/data_folder.h @@ -12,6 +12,10 @@ For license and copyright information please follow this link: #include "data/data_messages.h" #include "base/weak_ptr.h" +namespace Tdb { +class TLDupdateUnreadChatCount; +} // namespace Tdb + class ChannelData; namespace Main { @@ -38,13 +42,18 @@ class Folder final : public Dialogs::Entry { void clearChatsList(); [[nodiscard]] not_null chatsList(); +#if 0 // mtp void applyDialog(const MTPDdialogFolder &data); void applyPinnedUpdate(const MTPDupdateDialogPinned &data); TimeId adjustedChatListTimeId() const override; +#endif + void applyDialog(const Tdb::TLDupdateUnreadChatCount &data); int fixedOnTopIndex() const override; +#if 0 // mtp bool shouldBeInChatList() const override; +#endif Dialogs::UnreadState chatListUnreadState() const override; Dialogs::BadgesState chatListBadgesState() const override; HistoryItem *chatListMessage() const override; diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index 361135d8855cc..9659b9088fe62 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -31,9 +31,13 @@ For license and copyright information please follow this link: #include "window/notifications_manager.h" #include "styles/style_boxes.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kTopicsFirstLoad = 20; constexpr auto kLoadedTopicsMinCount = 20; constexpr auto kTopicsPerPage = 500; @@ -58,6 +62,7 @@ Forum::Forum(not_null history) } Forum::~Forum() { +#if 0 // mtp for (const auto &request : _topicRequests) { if (request.second.id != _staleRequestId) { owner().histories().cancelRequest(request.second.id); @@ -69,6 +74,18 @@ Forum::~Forum() { if (_requestId) { session().api().request(_requestId).cancel(); } +#endif + for (const auto &request : _topicRequests) { + if (request.second.id != _staleRequestId) { + session().sender().request(request.second.id).cancel(); + } + } + if (_staleRequestId) { + session().sender().request(_staleRequestId).cancel(); + } + if (_requestId) { + session().sender().request(_requestId).cancel(); + } auto &storage = session().storage(); auto &changes = session().changes(); const auto peerId = _history->peer->id; @@ -122,7 +139,10 @@ void Forum::preloadTopics() { void Forum::reloadTopics() { _topicsList.setLoaded(false); +#if 0 // mtp session().api().request(base::take(_requestId)).cancel(); +#endif + session().sender().request(base::take(_requestId)).cancel(); _offset = {}; for (const auto &[rootId, topic] : _topics) { if (!topic->creating()) { @@ -138,6 +158,41 @@ void Forum::requestTopics() { } const auto firstLoad = !_offset.date; const auto loadCount = firstLoad ? kTopicsFirstLoad : kTopicsPerPage; + _requestId = session().sender().request(TLgetForumTopics( + peerToTdbChat(channel()->id), + tl_string(), // query + tl_int32(_offset.date), + tl_int53(_offset.id.bare), + tl_int53(_offset.topicId.bare), + tl_int32(loadCount) + )).done([=](const TLDforumTopics &result) { + const auto previousOffset = _offset; + const auto &list = result.vtopics().v; + applyReceivedTopics(list); + _offset.date = result.vnext_offset_date().v; + _offset.id = result.vnext_offset_message_id().v; + _offset.topicId = result.vnext_offset_message_thread_id().v; + if (list.isEmpty() + || list.size() == result.vtotal_count().v + || (_offset == previousOffset)) { + _topicsList.setLoaded(); + } + _requestId = 0; + _chatsListChanges.fire({}); + if (_topicsList.loaded()) { + _chatsListLoadedEvents.fire({}); + } + reorderLastTopics(); + requestSomeStale(); + }).fail([=](const Error &error) { + _requestId = 0; + _topicsList.setLoaded(); + if (error.message == u"CHANNEL_FORUM_MISSING"_q) { + const auto flags = channel()->flags() & ~ChannelDataFlag::Forum; + channel()->setFlags(flags); + } + }).send(); +#if 0 // mtp _requestId = session().api().request(MTPchannels_GetForumTopics( MTP_flags(0), channel()->inputChannel, @@ -170,6 +225,7 @@ void Forum::requestTopics() { channel()->setFlags(flags); } }).send(); +#endif } void Forum::applyTopicDeleted(MsgId rootId) { @@ -179,7 +235,7 @@ void Forum::applyTopicDeleted(MsgId rootId) { if (i != end(_topics)) { const auto raw = i->second.get(); Core::App().notifications().clearFromTopic(raw); - owner().removeChatListEntry(raw); + owner().removeChatListEntry(raw, FilterId()); if (ranges::contains(_lastTopics, not_null(raw))) { reorderLastTopics(); @@ -261,6 +317,37 @@ void Forum::listMessageChanged(HistoryItem *from, HistoryItem *to) { } } +void Forum::applyReceivedTopics( + const QVector &topics, + Fn)> callback) { + for (const auto &topic : topics) { + const auto &data = topic.data(); + const auto rootId = data.vinfo().data().vmessage_thread_id().v; + _staleRootIds.remove(rootId); + _topicsDeleted.remove(rootId); + const auto i = _topics.find(rootId); + const auto creating = (i == end(_topics)); + const auto raw = creating + ? _topics.emplace( + rootId, + std::make_unique(this, rootId) + ).first->second.get() + : i->second.get(); + raw->applyTopic(topic); + if (creating) { + if (const auto last = _history->chatListMessage() + ; last && last->topicRootId() == rootId) { + _history->lastItemDialogsView().itemInvalidated(last); + _history->updateChatListEntry(); + } + } + if (callback) { + callback(raw); + } + } +} + +#if 0 // mtp void Forum::applyReceivedTopics( const MTPmessages_ForumTopics &topics, ForumOffsets &updateOffsets) { @@ -322,6 +409,7 @@ void Forum::applyReceivedTopics( }); } } +#endif void Forum::requestSomeStale() { if (_staleRequestId @@ -330,13 +418,19 @@ void Forum::requestSomeStale() { return; } const auto type = Histories::RequestType::History; +#if 0 // mtp auto rootIds = QVector(); +#endif + auto rootIds = QVector(); rootIds.reserve(std::min(int(_staleRootIds.size()), kStalePerRequest)); for (auto i = begin(_staleRootIds); i != end(_staleRootIds);) { const auto rootId = *i; i = _staleRootIds.erase(i); +#if 0 // mtp rootIds.push_back(MTP_int(rootId)); +#endif + rootIds.push_back(rootId); if (rootIds.size() == kStalePerRequest) { break; } @@ -344,6 +438,7 @@ void Forum::requestSomeStale() { if (rootIds.empty()) { return; } +#if 0 // mtp const auto call = [=] { for (const auto &id : rootIds) { finishTopicRequest(id.v); @@ -370,6 +465,28 @@ void Forum::requestSomeStale() { for (const auto &id : rootIds) { _topicRequests[id.v].id = _staleRequestId; } +#endif + auto ids = std::make_shared>(); + ids->reserve(rootIds.size()); + for (const auto &id : rootIds) { + _staleRequestId = session().sender().request(TLgetForumTopic( + peerToTdbChat(channel()->id), + tl_int53(id.bare) + )).done([=](const TLforumTopic &result, RequestId id) { + if (ids->remove(id) && ids->empty()) { + _staleRequestId = 0; + } + applyReceivedTopics({ 1, result }); + finishTopicRequest(id); + }).fail([=](const Error &error, RequestId id) { + if (ids->remove(id) && ids->empty()) { + _staleRequestId = 0; + } + finishTopicRequest(id); + }).send(); + _topicRequests[id].id = _staleRequestId; + ids->emplace(_staleRequestId); + } } void Forum::finishTopicRequest(MsgId rootId) { diff --git a/Telegram/SourceFiles/data/data_forum.h b/Telegram/SourceFiles/data/data_forum.h index 732ef80097e1b..4b2971821ae50 100644 --- a/Telegram/SourceFiles/data/data_forum.h +++ b/Telegram/SourceFiles/data/data_forum.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "dialogs/dialogs_main_list.h" +namespace Tdb { +class TLforumTopic; +} // namespace Tdb + class History; class ChannelData; @@ -68,6 +72,10 @@ class Forum final { [[nodiscard]] ForumTopic *enforceTopicFor(MsgId rootId); [[nodiscard]] bool topicDeleted(MsgId rootId) const; + void applyReceivedTopics( + const QVector &topics, + Fn)> callback = nullptr); +#if 0 // mtp void applyReceivedTopics( const MTPmessages_ForumTopics &topics, ForumOffsets &updateOffsets); @@ -77,6 +85,7 @@ class Forum final { void applyReceivedTopics( const MTPVector &topics, Fn)> callback = nullptr); +#endif [[nodiscard]] MsgId reserveCreatingId( const QString &title, diff --git a/Telegram/SourceFiles/data/data_forum_icons.cpp b/Telegram/SourceFiles/data/data_forum_icons.cpp index 1469cf559605d..f6cb42de8ae09 100644 --- a/Telegram/SourceFiles/data/data_forum_icons.cpp +++ b/Telegram/SourceFiles/data/data_forum_icons.cpp @@ -14,9 +14,14 @@ For license and copyright information please follow this link: #include "data/data_forum_topic.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kRefreshDefaultListEach = 60 * 60 * crl::time(1000); constexpr auto kRecentRequestTimeout = 10 * crl::time(1000); constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000); @@ -56,6 +61,15 @@ void ForumIcons::requestDefault() { if (_defaultRequestId) { return; } + auto &sender = _owner->session().sender(); + _defaultRequestId = sender.request(TLgetForumTopicDefaultIcons( + )).done([=](const TLDstickers &result) { + _defaultRequestId = 0; + updateDefault(result); + }).fail([=] { + _defaultRequestId = 0; + }).send(); +#if 0 // mtp auto &api = _owner->session().api(); _defaultRequestId = api.request(MTPmessages_GetStickerSet( MTP_inputStickerSetEmojiDefaultTopicIcons(), @@ -70,10 +84,15 @@ void ForumIcons::requestDefault() { }).fail([=] { _defaultRequestId = 0; }).send(); +#endif } +void ForumIcons::updateDefault(const TLDstickers &data) { +#if 0 // mtp void ForumIcons::updateDefault(const MTPDmessages_stickerSet &data) { const auto &list = data.vdocuments().v; +#endif + const auto &list = data.vstickers().v; _default.clear(); _default.reserve(list.size()); for (const auto &sticker : list) { diff --git a/Telegram/SourceFiles/data/data_forum_icons.h b/Telegram/SourceFiles/data/data_forum_icons.h index 54f03a366bd89..b252bb2414d74 100644 --- a/Telegram/SourceFiles/data/data_forum_icons.h +++ b/Telegram/SourceFiles/data/data_forum_icons.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "base/timer.h" +namespace Tdb { +class TLDstickers; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -44,7 +48,10 @@ class ForumIcons final { void resetUserpics(); void resetUserpicsFor(not_null forum); +#if 0 // mtp void updateDefault(const MTPDmessages_stickerSet &data); +#endif + void updateDefault(const Tdb::TLDstickers &data); const not_null _owner; diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 7b5391a193396..e619b5e1ce359 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -35,11 +35,15 @@ For license and copyright information please follow this link: #include "styles/style_dialogs.h" #include "styles/style_chat_helpers.h" +#include "tdb/tdb_tl_scheme.h" + #include namespace Data { namespace { +using namespace Tdb; + using UpdateFlag = TopicUpdate::Flag; constexpr auto kUserpicLoopsCount = 1; @@ -320,6 +324,72 @@ void ForumTopic::readTillEnd() { _replies->readTill(_lastKnownServerMessageId); } +void ForumTopic::applyTopic(const TLforumTopic &topic) { + const auto &data = topic.data(); + + applyInfo(data.vinfo()); + + owner().setChatPinned(this, 0, data.vis_pinned().v); + owner().notifySettings().apply(this, data.vnotification_settings()); + + if (const auto draft = data.vdraft_message()) { + Data::ApplyPeerCloudDraft( + &session(), + channel()->id, + _rootId, + draft->data()); + } else { + Data::ClearPeerCloudDraft( + &session(), + channel()->id, + _rootId, + base::unixtime::now()); + } + + _replies->setInboxReadTill( + data.vlast_read_inbox_message_id().v, + data.vunread_count().v); + _replies->setOutboxReadTill(data.vlast_read_outbox_message_id().v); + + if (const auto message = data.vlast_message()) { + const auto &data = message->data(); + const auto id = data.vid().v; + history()->addMessage(*message, NewMessageType::Existing); + applyTopicTopMessage(id); + } else { + if (_lastServerMessage) { + if (lastMessage() == lastServerMessage()) { + if (lastMessage() == chatListMessage()) { + _chatListMessage = std::nullopt; + requestChatListMessage(); + } + _lastMessage = std::nullopt; + } + _lastServerMessage = std::nullopt; + } + } + unreadMentions().setCount(data.vunread_mention_count().v); + unreadReactions().setCount(data.vunread_reaction_count().v); +} + +void ForumTopic::applyInfo(const TLforumTopicInfo &info) { + Expects(_rootId == info.data().vmessage_thread_id().v); + + const auto &data = info.data(); + const auto &icon = data.vicon().data(); + + applyCreator(peerFromSender(data.vcreator_id())); + applyCreationDate(data.vcreation_date().v); + + applyTitle(data.vname().v); + applyIconId(icon.vcustom_emoji_id().v); + applyColorId(icon.vcolor().v); + + applyIsMy(data.vis_outgoing().v); + setClosed(data.vis_closed().v); +} + +#if 0 // mtp void ForumTopic::applyTopic(const MTPDforumTopic &data) { Expects(_rootId == data.vid().v); @@ -362,6 +432,7 @@ void ForumTopic::applyTopic(const MTPDforumTopic &data) { unreadReactions().setCount(data.vunread_reactions_count().v); } } +#endif void ForumTopic::applyCreator(PeerId creatorId) { if (_creatorId != creatorId) { @@ -402,6 +473,13 @@ void ForumTopic::setClosed(bool closed) { void ForumTopic::setClosedAndSave(bool closed) { setClosed(closed); + const auto sender = &session().sender(); + sender->request(TLtoggleForumTopicIsClosed( + peerToTdbChat(channel()->id), + tl_int53(_rootId.bare), + tl_bool(closed) + )).send(); +#if 0 // mtp const auto api = &session().api(); const auto weak = base::make_weak(this); api->request(MTPchannels_EditForumTopic( @@ -421,6 +499,7 @@ void ForumTopic::setClosedAndSave(bool closed) { } } }).send(); +#endif } bool ForumTopic::hidden() const { @@ -650,6 +729,7 @@ void ForumTopic::requestChatListMessage() { } } +// tdlib won't be needed when tdlib manages topics TimeId ForumTopic::adjustedChatListTimeId() const { const auto result = chatListTimeId(); if (const auto draft = history()->cloudDraft(_rootId)) { @@ -664,11 +744,13 @@ int ForumTopic::fixedOnTopIndex() const { return 0; } +#if 0 // mtp bool ForumTopic::shouldBeInChatList() const { return isPinnedDialog(FilterId()) || !lastMessageKnown() || (lastMessage() != nullptr); } +#endif HistoryItem *ForumTopic::lastMessage() const { return _lastMessage.value_or(nullptr); diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 66430a0a37ca5..01fc7b41ca9b8 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -11,6 +11,11 @@ For license and copyright information please follow this link: #include "data/notify/data_peer_notify_settings.h" #include "base/flags.h" +namespace Tdb { +class TLforumTopic; +class TLforumTopicInfo; +} // namespace Tdb + class ChannelData; enum class ChatRestriction; @@ -56,7 +61,10 @@ class Forum; class ForumTopic final : public Thread { public: +#if 0 // mtp static constexpr auto kGeneralId = 1; +#endif + static constexpr auto kGeneralId = (1 << 20); ForumTopic(not_null forum, MsgId rootId); ~ForumTopic(); @@ -99,12 +107,20 @@ class ForumTopic final : public Thread { void setRealRootId(MsgId realId); void readTillEnd(); + void applyTopic(const Tdb::TLforumTopic &topic); + void applyInfo(const Tdb::TLforumTopicInfo &info); +#if 0 // mtp void applyTopic(const MTPDforumTopic &data); TimeId adjustedChatListTimeId() const override; +#endif + // tdlib won't be needed when tdlib manages topics + TimeId adjustedChatListTimeId() const; int fixedOnTopIndex() const override; +#if 0 // mtp bool shouldBeInChatList() const override; +#endif Dialogs::UnreadState chatListUnreadState() const override; Dialogs::BadgesState chatListBadgesState() const override; HistoryItem *chatListMessage() const override; diff --git a/Telegram/SourceFiles/data/data_game.cpp b/Telegram/SourceFiles/data/data_game.cpp index 6861e69af71fc..29308a5d20d98 100644 --- a/Telegram/SourceFiles/data/data_game.cpp +++ b/Telegram/SourceFiles/data/data_game.cpp @@ -7,7 +7,27 @@ For license and copyright information please follow this link: */ #include "data/data_game.h" +#include "data/data_session.h" +#include "tdb/tdb_tl_scheme.h" + GameData::GameData(not_null owner, const GameId &id) : owner(owner) , id(id) { } + +GameId GameData::IdFromTdb(const Tdb::TLgame &data) { + return data.data().vid().v; +} + +void GameData::setFromTdb(const Tdb::TLgame &data) { + const auto &fields = data.data(); + if (const auto animation = fields.vanimation()) { + document = owner->processDocument(*animation); + } + if (!fields.vphoto().data().vsizes().v.empty()) { + photo = owner->processPhoto(fields.vphoto()); + } + description = fields.vdescription().v; + title = fields.vtitle().v; + shortName = fields.vshort_name().v; +} diff --git a/Telegram/SourceFiles/data/data_game.h b/Telegram/SourceFiles/data/data_game.h index b71fdd0f2ceee..8e6c705b2dff5 100644 --- a/Telegram/SourceFiles/data/data_game.h +++ b/Telegram/SourceFiles/data/data_game.h @@ -10,9 +10,16 @@ For license and copyright information please follow this link: #include "data/data_photo.h" #include "data/data_document.h" +namespace Tdb { +class TLgame; +} // namespace Tdb + struct GameData { GameData(not_null owner, const GameId &id); + [[nodiscard]] static GameId IdFromTdb(const Tdb::TLgame &data); + void setFromTdb(const Tdb::TLgame &data); + const not_null owner; GameId id = 0; uint64 accessHash = 0; diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 0567477c4d22c..ed01f256e1ead 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -19,6 +19,8 @@ For license and copyright information please follow this link: #include "core/application.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { @@ -28,11 +30,13 @@ constexpr auto kActiveAfterJoined = crl::time(1000); constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000); constexpr auto kReloadStaleTimeout = 16 * crl::time(1000); +#if 0 // goodToRemove [[nodiscard]] QString ExtractNextOffset(const MTPphone_GroupCall &call) { return call.match([&](const MTPDphone_groupCall &data) { return qs(data.vparticipants_next_offset()); }); } +#endif } // namespace @@ -57,16 +61,26 @@ bool GroupCallParticipant::screenPaused() const { return IsScreenPaused(videoParams); } +QString GroupCallParticipant::rowOrder() const { + return order.isEmpty() ? QString() : (speaking ? '1' : '0') + order; +} + GroupCall::GroupCall( not_null peer, CallId id, +#if 0 // goodToRemove CallId accessHash, +#endif TimeId scheduleDate, bool rtmp) : _id(id) +#if 0 // goodToRemove , _accessHash(accessHash) +#endif , _peer(peer) +#if 0 // goodToRemove , _reloadByQueuedUpdatesTimer([=] { reload(); }) +#endif , _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) , _scheduleDate(scheduleDate) , _rtmp(rtmp) @@ -74,18 +88,24 @@ GroupCall::GroupCall( } GroupCall::~GroupCall() { +#if 0 // mtp api().request(_unknownParticipantPeersRequestId).cancel(); api().request(_participantsRequestId).cancel(); api().request(_reloadRequestId).cancel(); +#endif + api().sender().request(_participantsRequestId).cancel(); + api().sender().request(_reloadRequestId).cancel(); } CallId GroupCall::id() const { return _id; } +#if 0 // mtp bool GroupCall::loaded() const { return _version > 0; } +#endif bool GroupCall::rtmp() const { return _rtmp; @@ -99,9 +119,11 @@ not_null GroupCall::peer() const { return _peer; } +#if 0 // goodToRemove MTPInputGroupCall GroupCall::input() const { return MTP_inputGroupCall(MTP_long(_id), MTP_long(_accessHash)); } +#endif void GroupCall::setPeer(not_null peer) { Expects(peer->migrateFrom() == _peer); @@ -116,6 +138,16 @@ auto GroupCall::participants() const } void GroupCall::requestParticipants() { + _participantsRequestId = api().sender().request( + Tdb::TLloadGroupCallParticipants( + Tdb::tl_int32(_id), + Tdb::tl_int32(kRequestPerPage)) + ).done([=] { + _participantsRequestId = 0; + }).fail([=] { + _participantsRequestId = 0; + }).send(); +#if 0 // goodToRemove if (!_savedFull) { if (_participantsRequestId || _reloadRequestId) { return; @@ -163,8 +195,10 @@ void GroupCall::requestParticipants() { _participantsReloaded.fire({}); } }).send(); +#endif } +#if 0 // goodToRemove bool GroupCall::processSavedFullCall() { if (!_savedFull) { return false; @@ -179,6 +213,7 @@ void GroupCall::finishParticipantsSliceRequest() { computeParticipantsCount(); processQueuedUpdates(); } +#endif void GroupCall::setServerParticipantsCount(int count) { _serverParticipantsCount = count; @@ -270,11 +305,20 @@ auto GroupCall::participantUpdated() const return _participantUpdates.events(); } +rpl::producer<> GroupCall::recentSpeakersUpdated() const { + return _recentSpeakers.updates.events_starting_with_copy({}); +} + +const GroupCall::RecentSpeakers &GroupCall::recentSpeakers() const { + return _recentSpeakers.current; +} + auto GroupCall::participantSpeaking() const -> rpl::producer> { return _participantSpeaking.events(); } +#if 0 // goodToRemove void GroupCall::enqueueUpdate(const MTPUpdate &update) { update.match([&](const MTPDupdateGroupCall &updateData) { updateData.vcall().match([&](const MTPDgroupCall &data) { @@ -391,7 +435,77 @@ void GroupCall::processFullCall(const MTPphone_GroupCall &call) { finishParticipantsSliceRequest(); _participantsReloaded.fire({}); } +#endif + +void GroupCall::applyCallFields(const Tdb::TLDgroupCall &data) { + _joinMuted = data.vmute_new_participants().v; + _listenersHidden = data.vhas_hidden_listeners().v; + _rtmp = data.vis_rtmp_stream().v; + _canChangeJoinMuted = data.vcan_toggle_mute_new_participants().v; + _isJoined = data.vis_joined().v; + + const auto wasParticipantsCount = _serverParticipantsCount; + const auto nowParticipantsCount = data.vparticipant_count().v; + + setServerParticipantsCount(nowParticipantsCount); + computeParticipantsCount(); + _title = data.vtitle().v; + _recordVideo = data.vis_video_recorded().v; + _recordStartDate = data.vrecord_duration().v + ? (base::unixtime::now() - data.vrecord_duration().v) + : 0; + _scheduleDate = data.vscheduled_start_date().v; + _scheduleStartSubscribed = data.venabled_start_notification().v; + _canEnableVideo = data.vcan_enable_video().v; + _allParticipantsLoaded = data.vloaded_all_participants().v; + + if (_isJoined) { + if (!wasParticipantsCount + || (!_allParticipantsLoaded + && (nowParticipantsCount < kRequestPerPage) + && (wasParticipantsCount != nowParticipantsCount))) { + // Request participants on every group call update for small calls. + requestParticipants(); + } + } + { + using Speaker = Tdb::TLgroupCallRecentSpeaker; + _recentSpeakers.current = ranges::views::all( + data.vrecent_speakers().v + ) | ranges::views::transform([=](const Speaker &speaker) { + return RecentSpeaker{ + .peer = _peer->owner().peer( + peerFromSender(speaker.data().vparticipant_id())), + .speaking = speaker.data().vis_speaking().v, + }; + }) | ranges::to_vector; + if (!_isJoined) { + _recentSpeakers.updates.fire({}); + } + } +} + +void GroupCall::applyUpdate(const Tdb::TLDupdateGroupCall &update) { + if (update.vgroup_call().data().vid().v != _id) { + return; + } + applyCallFields(update.vgroup_call().data()); +} + +void GroupCall::applyUpdate( + const Tdb::TLDupdateGroupCallParticipant &update, + bool local) { + if (update.vgroup_call_id().v != _id) { + return; + } + using Source = ApplySliceSource; + applyParticipant( + update.vparticipant().data(), + local ? Source::UpdateConstructed : Source::UpdateReceived); +} + +#if 0 // goodToRemove void GroupCall::applyCallFields(const MTPDgroupCall &data) { DEBUG_LOG(("Group Call Participants: " "Set from groupCall %1 -> %2" @@ -493,6 +607,7 @@ void GroupCall::processQueuedUpdates() { _reloadByQueuedUpdatesTimer.callOnce(kWaitForUpdatesTimeout); } } +#endif void GroupCall::computeParticipantsCount() { _fullCount = (_allParticipantsLoaded && !_listenersHidden) @@ -513,8 +628,24 @@ void GroupCall::reload() { if (_reloadRequestId || _applyingQueuedUpdates) { return; } +#if 0 // mtp api().request(base::take(_participantsRequestId)).cancel(); +#endif + api().sender().request(base::take(_participantsRequestId)).cancel(); + _reloadRequestId = api().sender().request( + Tdb::TLgetGroupCall(Tdb::tl_int32(_id)) + ).done([=](const Tdb::TLDgroupCall &data) { + if (!data.vloaded_all_participants().v && data.vis_joined().v) { + requestParticipants(); + } + _reloadRequestId = 0; + applyCallFields(data); + _participantsReloaded.fire({}); + }).fail([=](const Tdb::Error &error) { + _reloadRequestId = 0; + }).send(); +#if 0 // goodToRemove DEBUG_LOG(("Group Call Participants: " "Reloading with queued: %1" ).arg(_queuedUpdates.size())); @@ -544,8 +675,10 @@ void GroupCall::reload() { _reloadRequestId = 0; _reloadLastFinished = crl::now(); }).send(); +#endif } +#if 0 // goodToRemove bool GroupCall::requestParticipantsAfterReload( const MTPphone_GroupCall &call) const { return call.match([&](const MTPDphone_groupCall &data) { @@ -704,6 +837,166 @@ void GroupCall::applyParticipantsSlice( computeParticipantsCount(); } } +#endif + +void GroupCall::applyLocalVolume( + not_null participantPeer, + bool mute, + std::optional volume) { + const auto i = ranges::find( + _participants, + participantPeer, + &Participant::peer); + Assert(i != end(_participants)); + const auto was = std::make_optional(*i); + auto now = *i; + if (now.canBeMutedForAllUsers) { + now.muted = mute; + } + if (now.canBeMutedForCurrentUser) { + now.mutedByMe = mute; + } + now.volume = volume.value_or(was->volume); + _participantUpdates.fire({ + .was = was, + .now = now, + }); +} + +void GroupCall::applyParticipant( + const Tdb::TLDgroupCallParticipant &data, + ApplySliceSource sliceSource) { + const auto participantPeerId = peerFromSender(data.vparticipant_id()); + const auto participantPeer = _peer->owner().peer( + participantPeerId); + const auto i = ranges::find( + _participants, + participantPeer, + &Participant::peer); + if (data.vorder().v.isEmpty()) { + if (i != end(_participants)) { + auto update = ParticipantUpdate{ + .was = *i, + }; + _participantPeerByAudioSsrc.erase(i->ssrc); + _participantPeerByAudioSsrc.erase( + i->screencastSsrc); +#if 0 // mtp + GetAdditionalAudioSsrc(i->videoParams)); +#endif + _speakingByActiveFinishes.remove(participantPeer); + _participants.erase(i); + if (sliceSource != ApplySliceSource::FullReloaded) { + _participantUpdates.fire(std::move(update)); + } + } + if (_serverParticipantsCount > 0) { + --_serverParticipantsCount; + } + return; + } + participantPeer->setAbout(data.vbio().v); + const auto was = (i != end(_participants)) + ? std::make_optional(*i) + : std::nullopt; + const auto canSelfUnmute = data.vcan_unmute_self().v; + const auto canBeSpeaking = !data.vis_muted_for_all_users().v + || data.vcan_unmute_self().v; + const auto localUpdate = (sliceSource + == ApplySliceSource::UpdateConstructed); + const auto existingVideoParams = (i != end(_participants)) + ? i->videoParams + : nullptr; + auto videoParams = localUpdate + ? existingVideoParams + : Calls::ParseVideoParams( + data.vvideo_info(), + data.vscreen_sharing_video_info(), + existingVideoParams); + const auto value = Participant{ + .peer = participantPeer, + .videoParams = std::move(videoParams), + .screencastSsrc = uint32(data.vscreen_sharing_audio_source_id().v), + .ssrc = uint32(data.vaudio_source_id().v), + .volume = data.vvolume_level().v, + .sounding = canBeSpeaking && was && was->sounding, + .speaking = canBeSpeaking && was && was->speaking, + .additionalSounding = (canBeSpeaking + && was + && was->additionalSounding), + .additionalSpeaking = (canBeSpeaking + && was + && was->additionalSpeaking), + .muted = data.vis_muted_for_all_users().v, + .mutedByMe = data.vis_muted_for_current_user().v, + .canSelfUnmute = canSelfUnmute, + .canBeSpeaking = canBeSpeaking, + + .isHandRaised = data.vis_hand_raised().v, + .canBeMutedForAllUsers = data.vcan_be_muted_for_all_users().v, + .canBeUnmutedForAllUsers = data.vcan_be_unmuted_for_all_users().v, + .canBeMutedForCurrentUser = data.vcan_be_muted_for_current_user().v, + .canBeUnmutedForCurrentUser = + data.vcan_be_unmuted_for_current_user().v, + .order = data.vorder().v, + }; + if (i == end(_participants)) { + if (value.ssrc) { + _participantPeerByAudioSsrc.emplace( + value.ssrc, + participantPeer); + } +#if 0 // mtp + if (const auto additional = GetAdditionalAudioSsrc( + value.videoParams)) { +#endif + if (const auto additional = value.screencastSsrc) { + _participantPeerByAudioSsrc.emplace( + additional, + participantPeer); + } + _participants.push_back(value); + if (const auto user = participantPeer->asUser()) { + _peer->owner().unregisterInvitedToCallUser(_id, user); + } + } else { + if (i->ssrc != value.ssrc) { + _participantPeerByAudioSsrc.erase(i->ssrc); + if (value.ssrc) { + _participantPeerByAudioSsrc.emplace( + value.ssrc, + participantPeer); + } + } +#if 0 // mtp + if (GetAdditionalAudioSsrc(i->videoParams) + != GetAdditionalAudioSsrc(value.videoParams)) { + _participantPeerByAudioSsrc.erase( + GetAdditionalAudioSsrc(i->videoParams)); + if (const auto additional = GetAdditionalAudioSsrc( + value.videoParams)) { +#endif + if (i->screencastSsrc != value.screencastSsrc) { + _participantPeerByAudioSsrc.erase(i->screencastSsrc); + if (const auto additional = value.screencastSsrc) { + _participantPeerByAudioSsrc.emplace( + additional, + participantPeer); + } + } + *i = value; + } + if (sliceSource != ApplySliceSource::FullReloaded) { + _participantUpdates.fire({ + .was = was, + .now = value, + }); + } + if (sliceSource == ApplySliceSource::UpdateReceived) { + changePeerEmptyCallFlag(); + computeParticipantsCount(); + } +} void GroupCall::applyLastSpoke( uint32 ssrc, @@ -720,7 +1013,10 @@ void GroupCall::applyLastSpoke( _speakingByActiveFinishes.remove(participant->peer); const auto sounding = (when.anything + kSoundStatusKeptFor >= now) +#if 0 // goodToRemove && participant->canSelfUnmute; +#endif + && participant->canBeSpeaking; const auto speaking = sounding && (when.voice + kSoundStatusKeptFor >= now); if (speaking) { @@ -746,6 +1042,11 @@ void GroupCall::applyLastSpoke( .was = was, .now = *participant, }); + api().sender().request(Tdb::TLsetGroupCallParticipantIsSpeaking( + Tdb::tl_int32(_id), + Tdb::tl_int32(participant->peer->isSelf() ? 0 : ssrc), + Tdb::tl_bool(speaking) + )).send(); } } @@ -769,7 +1070,10 @@ void GroupCall::applyActiveUpdate( const auto participant = participantPeerLoaded ? findParticipant(participantPeerLoaded) : nullptr; +#if 0 // goodToRemove const auto loadByUserId = !participant || participant->onlyMinLoaded; +#endif + const auto loadByUserId = !participant; if (loadByUserId) { _unknownSpokenPeerIds[participantPeerId] = when; requestUnknownParticipants(); @@ -780,9 +1084,13 @@ void GroupCall::applyActiveUpdate( const auto was = std::make_optional(*participant); const auto now = crl::now(); const auto elapsed = TimeId((now - when.anything) / crl::time(1000)); +#if 0 // goodToRemove const auto lastActive = base::unixtime::now() - elapsed; const auto finishes = when.anything + kSpeakingAfterActive; if (lastActive <= participant->lastActive || finishes <= now) { +#endif + const auto finishes = when.anything + kSpeakingAfterActive; + if (finishes <= now) { return; } _speakingByActiveFinishes[participant->peer] = finishes; @@ -790,7 +1098,9 @@ void GroupCall::applyActiveUpdate( _speakingByActiveFinishTimer.callOnce(finishes - now); } +#if 0 // goodToRemove participant->lastActive = lastActive; +#endif participant->speaking = true; participant->canSelfUnmute = true; if (!was->speaking || !was->canSelfUnmute) { @@ -836,6 +1146,8 @@ void GroupCall::checkFinishSpeakingByActive() { } void GroupCall::requestUnknownParticipants() { + requestParticipants(); +#if 0 // goodToRemove if (_unknownParticipantPeersRequestId || (_unknownSpokenSsrcs.empty() && _unknownSpokenPeerIds.empty())) { return; @@ -938,6 +1250,7 @@ void GroupCall::requestUnknownParticipants() { } requestUnknownParticipants(); }).send(); +#endif } void GroupCall::setInCall() { @@ -978,9 +1291,11 @@ bool GroupCall::canChangeJoinMuted() const { return _canChangeJoinMuted; } +#if 0 // goodToRemove bool GroupCall::joinedToTop() const { return _joinedToTop; } +#endif ApiWrap &GroupCall::api() const { return _peer->session().api(); diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 6dbaf8debeea3..95411e9238d96 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -9,6 +9,13 @@ For license and copyright information please follow this link: #include "base/timer.h" +namespace Tdb { +class TLDupdateGroupCall; +class TLDupdateGroupCallParticipant; +class TLDgroupCallParticipant; +class TLDgroupCall; +} // namespace Tdb + class PeerData; class ApiWrap; @@ -29,9 +36,12 @@ struct LastSpokeTimes { struct GroupCallParticipant { not_null peer; std::shared_ptr videoParams; +#if 0 // goodToRemove TimeId date = 0; TimeId lastActive = 0; uint64 raisedHandRating = 0; +#endif + uint32 screencastSsrc = 0; uint32 ssrc = 0; int volume = 0; bool sounding : 1 = false; @@ -41,14 +51,26 @@ struct GroupCallParticipant { bool muted : 1 = false; bool mutedByMe : 1 = false; bool canSelfUnmute : 1 = false; +#if 0 // goodToRemove bool onlyMinLoaded : 1 = false; bool videoJoined = false; bool applyVolumeFromMin = true; +#endif + bool canBeSpeaking = false; + + bool isHandRaised = false; + bool canBeMutedForAllUsers = false; + bool canBeUnmutedForAllUsers = false; + bool canBeMutedForCurrentUser = false; + bool canBeUnmutedForCurrentUser = false; + QString order; [[nodiscard]] const std::string &cameraEndpoint() const; [[nodiscard]] const std::string &screenEndpoint() const; [[nodiscard]] bool cameraPaused() const; [[nodiscard]] bool screenPaused() const; + + [[nodiscard]] QString rowOrder() const; }; class GroupCall final { @@ -56,17 +78,23 @@ class GroupCall final { GroupCall( not_null peer, CallId id, +#if 0 // goodToRemove CallId accessHash, +#endif TimeId scheduleDate, bool rtmp); ~GroupCall(); [[nodiscard]] CallId id() const; +#if 0 // mtp [[nodiscard]] bool loaded() const; +#endif [[nodiscard]] bool rtmp() const; [[nodiscard]] bool listenersHidden() const; [[nodiscard]] not_null peer() const; +#if 0 // goodToRemove [[nodiscard]] MTPInputGroupCall input() const; +#endif [[nodiscard]] QString title() const { return _title.current(); } @@ -100,13 +128,26 @@ class GroupCall final { [[nodiscard]] rpl::producer scheduleStartSubscribedValue() const { return _scheduleStartSubscribed.value(); } +#if 0 // goodToRemove [[nodiscard]] int unmutedVideoLimit() const { return _unmutedVideoLimit.current(); } +#endif + [[nodiscard]] bool canEnableVideo() const { + return _canEnableVideo; + } [[nodiscard]] bool recordVideo() const { return _recordVideo.current(); } + struct RecentSpeaker { + not_null peer; + bool speaking = false; + }; + using RecentSpeakers = std::vector; + [[nodiscard]] rpl::producer<> recentSpeakersUpdated() const; + [[nodiscard]] const RecentSpeakers &recentSpeakers() const; + void setPeer(not_null peer); using Participant = GroupCallParticipant; @@ -133,9 +174,21 @@ class GroupCall final { [[nodiscard]] auto participantSpeaking() const -> rpl::producer>; +#if 0 // goodToRemove void enqueueUpdate(const MTPUpdate &update); void applyLocalUpdate( const MTPDupdateGroupCallParticipants &update); +#endif + + void applyLocalVolume( + not_null participantPeer, + bool mute, + std::optional volume); + + void applyUpdate(const Tdb::TLDupdateGroupCall &update); + void applyUpdate( + const Tdb::TLDupdateGroupCallParticipant &update, + bool local = false); void applyLastSpoke(uint32 ssrc, LastSpokeTimes when, crl::time now); void applyActiveUpdate( @@ -157,7 +210,9 @@ class GroupCall final { void setInCall(); void reload(); void reloadIfStale(); +#if 0 // goodToRemove void processFullCall(const MTPphone_GroupCall &call); +#endif void setJoinMutedLocally(bool muted); [[nodiscard]] bool joinMuted() const; @@ -179,18 +234,29 @@ class GroupCall final { }; [[nodiscard]] ApiWrap &api() const; +#if 0 // goodToRemove void discard(const MTPDgroupCallDiscarded &data); +#endif [[nodiscard]] bool inCall() const; +#if 0 // goodToRemove void applyParticipantsSlice( const QVector &list, ApplySliceSource sliceSource); +#endif + void applyParticipant( + const Tdb::TLDgroupCallParticipant &data, + ApplySliceSource sliceSource); void requestUnknownParticipants(); void changePeerEmptyCallFlag(); void checkFinishSpeakingByActive(); + void applyCallFields(const Tdb::TLDgroupCall &data); +#if 0 // goodToRemove void applyCallFields(const MTPDgroupCall &data); void applyEnqueuedUpdate(const MTPUpdate &update); +#endif void setServerParticipantsCount(int count); void computeParticipantsCount(); +#if 0 // goodToRemove void processQueuedUpdates(); void processFullCallUsersChats(const MTPphone_GroupCall &call); void processFullCallFields(const MTPphone_GroupCall &call); @@ -198,10 +264,13 @@ class GroupCall final { const MTPphone_GroupCall &call) const; [[nodiscard]] bool processSavedFullCall(); void finishParticipantsSliceRequest(); +#endif [[nodiscard]] Participant *findParticipant(not_null peer); const CallId _id = 0; +#if 0 // goodToRemove const CallId _accessHash = 0; +#endif not_null _peer; int _version = 0; @@ -210,11 +279,13 @@ class GroupCall final { crl::time _reloadLastFinished = 0; rpl::variable _title; +#if 0 // goodToRemove base::flat_multi_map< std::pair, MTPUpdate> _queuedUpdates; base::Timer _reloadByQueuedUpdatesTimer; std::optional _savedFull; +#endif std::vector _participants; base::flat_map> _participantPeerByAudioSsrc; @@ -223,11 +294,19 @@ class GroupCall final { QString _nextOffset; int _serverParticipantsCount = 0; rpl::variable _fullCount = 0; +#if 0 // goodToRemove rpl::variable _unmutedVideoLimit = 0; +#endif rpl::variable _recordVideo = 0; rpl::variable _recordStartDate = 0; rpl::variable _scheduleDate = 0; rpl::variable _scheduleStartSubscribed = false; + bool _canEnableVideo = false; + + struct { + rpl::event_stream<> updates; + RecentSpeakers current; + } _recentSpeakers; base::flat_map _unknownSpokenSsrcs; base::flat_map _unknownSpokenPeerIds; @@ -244,7 +323,10 @@ class GroupCall final { bool _joinMuted = false; bool _canChangeJoinMuted = true; bool _allParticipantsLoaded = false; +#if 0 // goodToRemove bool _joinedToTop = false; +#endif + bool _isJoined = false; bool _applyingQueuedUpdates = false; bool _rtmp = false; bool _listenersHidden = false; diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 0f252aa531898..96c185b558476 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -27,13 +27,21 @@ For license and copyright information please follow this link: #include "core/application.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" +#include "data/data_user.h" +#include "data/data_secret_chat.h" + namespace Data { namespace { constexpr auto kReadRequestTimeout = 3 * crl::time(1000); +using namespace Tdb; + } // namespace +#if 0 // mtp MTPInputReplyTo ReplyToForMTP( not_null history, FullReplyTo replyTo) { @@ -86,6 +94,7 @@ MTPInputReplyTo ReplyToForMTP( } return MTPInputReplyTo(); } +#endif MTPInputMedia WebPageForMTP( const Data::WebPageDraft &draft, @@ -324,6 +333,7 @@ void Histories::readClientSideMessage(not_null item) { } } +#if 0 // mtp void Histories::requestDialogEntry(not_null folder) { if (_dialogFolderRequests.contains(folder)) { return; @@ -342,6 +352,7 @@ void Histories::requestDialogEntry(not_null folder) { _dialogFolderRequests.remove(folder); }).send(); } +#endif void Histories::requestDialogEntry( not_null history, @@ -377,6 +388,66 @@ void Histories::sendDialogRequests() { if (_dialogRequestsPending.empty()) { return; } + const auto histories = ranges::views::all( + _dialogRequestsPending + ) | ranges::views::transform([](const auto &pair) { + return pair.first; + }) | ranges::to_vector; + + for (auto &[history, callbacks] : base::take(_dialogRequestsPending)) { + _dialogRequests.emplace(history, std::move(callbacks)); + + const auto peer = history->peer; + const auto fail = [=, history = history] { + dialogEntryApplied(history); + }; + const auto done = [=, history = history](const TLchat &result) { + const auto gotPeer = session().data().processPeer(result); + Assert(peer == gotPeer); + if (history->lastServerMessageKnown()) { + dialogEntryApplied(history); + } else { + session().sender().request(TLgetChatHistory( + peerToTdbChat(peer->id), + tl_int53(0), + tl_int32(0), + tl_int32(10), + tl_bool(false) // only_local + )).done([=](const TLmessages &result) { + const auto &list = result.data().vmessages().v; + if (!list.empty() && list.front()) { + session().data().processMessage( + *list.front(), + NewMessageType::Last); + } + dialogEntryApplied(history); + }).fail(fail).send(); + } + }; + if (const auto user = peer->asUser()) { + session().sender().request(TLcreatePrivateChat( + tl_int53(peerToUser(user->id).bare), + tl_bool(false) // force + )).done(done).fail(fail).send(); + } else if (const auto chat = peer->asChat()) { + session().sender().request(TLcreateBasicGroupChat( + tl_int53(peerToChat(chat->id).bare), + tl_bool(false) // force + )).done(done).fail(fail).send(); + } else if (const auto channel = peer->asChannel()) { + session().sender().request(TLcreateSupergroupChat( + tl_int53(peerToChannel(channel->id).bare), + tl_bool(false) // force + )).done(done).fail(fail).send(); + } else if (const auto secretChat = peer->asSecretChat()) { + session().sender().request(TLcreateSecretChat( + ToTdbSecretChatId(secretChat->id) + )).done(done).fail(fail).send(); + } else { + Unexpected("Chat type in Histories::sendDialogRequests."); + } + } +#if 0 // mtp const auto histories = ranges::views::all( _dialogRequestsPending ) | ranges::views::transform([](const auto &pair) { @@ -421,13 +492,16 @@ void Histories::sendDialogRequests() { }).fail([=] { finalize(); }).send(); +#endif } void Histories::dialogEntryApplied(not_null history) { +#if 0 // mtp const auto state = lookup(history); if (state && state->postponedRequestEntry) { return; } +#endif history->dialogEntryApplied(); if (const auto callbacks = _dialogRequestsPending.take(history)) { for (const auto &callback : *callbacks) { @@ -439,12 +513,15 @@ void Histories::dialogEntryApplied(not_null history) { callback(); } } +#if 0 // mtp if (state && state->sentReadTill && state->sentReadDone) { history->setInboxReadTill(base::take(state->sentReadTill)); checkEmptyState(history); } +#endif } +#if 0 // mtp void Histories::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) { Expects(dialogs.type() == mtpc_messages_peerDialogs); @@ -464,17 +541,24 @@ void Histories::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) { } _owner->sendHistoryChangeNotifications(); } +#endif void Histories::changeDialogUnreadMark( not_null history, bool unread) { history->setUnreadMark(unread); + session().sender().request(TLtoggleChatIsMarkedAsUnread( + peerToTdbChat(history->peer->id), + tl_bool(unread) + )).send(); +#if 0 // mtp using Flag = MTPmessages_MarkDialogUnread::Flag; session().api().request(MTPmessages_MarkDialogUnread( MTP_flags(unread ? Flag::f_unread : Flag(0)), MTP_inputDialogPeer(history->peer->input) )).send(); +#endif } void Histories::requestFakeChatListMessage( @@ -484,6 +568,7 @@ void Histories::requestFakeChatListMessage( } _fakeChatListRequests.emplace(history); +#if 0 // tdlib todo we can't make correct both message date and chat order :( sendRequest(history, RequestType::History, [=](Fn finish) { return session().api().request(MTPmessages_GetHistory( history->peer->input, @@ -507,6 +592,7 @@ void Histories::requestFakeChatListMessage( finish(); }).send(); }); +#endif } void Histories::requestGroupAround(not_null item) { @@ -518,11 +604,44 @@ void Histories::requestGroupAround(not_null item) { if (i->second.aroundId == id) { return; } else { +#if 0 // mtp cancelRequest(i->second.requestId); +#endif + session().sender().request(i->second.requestId).cancel(); _chatListGroupRequests.erase(i); } } constexpr auto kMaxAlbumCount = 10; + + const auto requestId = session().sender().request(TLgetChatHistory( + peerToTdbChat(history->peer->id), + tl_int53(id.bare), + tl_int32(0), + tl_int32(kMaxAlbumCount - 1), + tl_bool(false) + )).done([=](const TLDmessages &data) { + auto items = std::vector>(); + items.reserve(data.vmessages().v.size() + 1); + for (const auto &message : data.vmessages().v) { + if (message) { + items.push_back(_owner->processMessage( + *message, + NewMessageType::Existing)); + } + } + if (const auto item = history->owner().message(history->peer, id)) { + if (!ranges::contains(items, not_null{ item })) { + items.push_back(item); + } + } + _chatListGroupRequests.remove(key); + history->migrateToOrMe()->applyChatListGroup( + history->peer->id, + std::move(items)); + }).fail([=] { + _chatListGroupRequests.remove(key); + }).send(); +#if 0 // mtp const auto requestId = sendRequest(history, RequestType::History, [=]( Fn finish) { return session().api().request(MTPmessages_GetHistory( @@ -548,6 +667,7 @@ void Histories::requestGroupAround(not_null item) { finish(); }).send(); }); +#endif _chatListGroupRequests.emplace( key, ChatListGroupRequest{ .aroundId = id, .requestId = requestId }); @@ -600,6 +720,31 @@ void Histories::sendReadRequest(not_null history, State &state) { state.sentReadDone = false; DEBUG_LOG(("Reading: sending request now with till %1." ).arg(tillId.bare)); + + const auto finished = [=] { + const auto state = lookup(history); + Assert(state != nullptr); + + if (state->sentReadTill == tillId) { + state->sentReadDone = true; + if (history->unreadCountRefreshNeeded(tillId)) { + requestDialogEntry(history); + } else { + state->sentReadTill = 0; + } + } else { + Assert(!state->sentReadTill || state->sentReadTill > tillId); + } + sendReadRequests(); + }; + session().sender().request(TLviewMessages( + peerToTdbChat(history->peer->id), + tl_vector(1, tl_int53(tillId.bare)), + tl_messageSourceChatHistory(), + tl_bool(true) + )).done(finished).fail(finished).send(); + +#if 0 // mtp sendRequest(history, RequestType::ReadInbox, [=](Fn finish) { DEBUG_LOG(("Reading: sending request invoked with till %1." ).arg(tillId.bare)); @@ -637,6 +782,7 @@ void Histories::sendReadRequest(not_null history, State &state) { }).send(); } }); +#endif } void Histories::checkEmptyState(not_null history) { @@ -653,6 +799,7 @@ void Histories::checkEmptyState(not_null history) { } } +#if 0 // mtp bool Histories::postponeHistoryRequest(const State &state) const { const auto proj = [](const auto &pair) { return pair.second.type; @@ -691,12 +838,14 @@ void Histories::deleteMessages( } }); } +#endif void Histories::deleteAllMessages( not_null history, MsgId deleteTillId, bool justClear, bool revoke) { +#if 0 // mtp sendRequest(history, RequestType::Delete, [=](Fn finish) { const auto peer = history->peer; const auto chat = peer->asChat(); @@ -766,6 +915,29 @@ void Histories::deleteAllMessages( }).fail(finish).send(); } }); +#endif + + const auto peer = history->peer; + const auto chat = peer->asChat(); + const auto channel = peer->asChannel(); + if (peer->isSecretChat()) { + revoke = true; + } + if (!justClear + && revoke + && ((chat && chat->amCreator()) + || (channel && channel->canDelete()) + || peer->isSecretChat())) { + session().sender().request(TLdeleteChat( + peerToTdbChat(peer->id) + )).send(); + } else { + session().sender().request(TLdeleteChatHistory( + peerToTdbChat(peer->id), + tl_bool(!justClear), + tl_bool(revoke) + )).send(); + } } void Histories::deleteMessagesByDates( @@ -791,6 +963,13 @@ void Histories::deleteMessagesByDates( TimeId minDate, TimeId maxDate, bool revoke) { + history->session().sender().request(TLdeleteChatMessagesByDate( + peerToTdbChat(history->peer->id), + tl_int32(minDate), + tl_int32(maxDate), + tl_bool(revoke) + )).send(); +#if 0 // mtp sendRequest(history, RequestType::Delete, [=](Fn finish) { const auto peer = history->peer; using Flag = MTPmessages_DeleteHistory::Flag; @@ -815,9 +994,11 @@ void Histories::deleteMessagesByDates( }).fail(finish).send(); }); history->destroyMessagesByDates(minDate, maxDate); +#endif } void Histories::deleteMessages(const MessageIdsList &ids, bool revoke) { +#if 0 // mtp auto remove = std::vector>(); remove.reserve(ids.size()); base::flat_map, QVector> idsByPeer; @@ -865,8 +1046,26 @@ void Histories::deleteMessages(const MessageIdsList &ids, bool revoke) { history->requestChatListMessage(); } } +#endif + + base::flat_map, QVector> idsByPeer; + for (const auto &itemId : ids) { + if (const auto item = _owner->message(itemId)) { + const auto history = item->history(); + idsByPeer[history].push_back(tl_int53(itemId.msg.bare)); + } + } + + for (const auto &[history, ids] : idsByPeer) { + history->session().sender().request(TLdeleteMessages( + peerToTdbChat(history->peer->id), + tl_vector(ids), + tl_bool(revoke) + )).send(); + } } +#if 0 // mtp int Histories::sendRequest( not_null history, RequestType type, @@ -1138,6 +1337,7 @@ void Histories::finishSentRequest( } checkEmptyState(history); } +#endif Histories::State *Histories::lookup(not_null history) { const auto i = _states.find(history); diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index dd2900a41cd4b..e227c41d118e2 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -27,12 +27,14 @@ class Session; class Folder; struct WebPageDraft; +#if 0 // mtp [[nodiscard]] MTPInputReplyTo ReplyToForMTP( not_null history, FullReplyTo replyTo); [[nodiscard]] MTPInputMedia WebPageForMTP( const Data::WebPageDraft &draft, bool required = false); +#endif class Histories final { public: @@ -52,7 +54,9 @@ class Histories final { [[nodiscard]] History *find(PeerId peerId); [[nodiscard]] not_null findOrCreate(PeerId peerId); +#if 0 // mtp void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs); +#endif void unloadAll(); void clearAll(); @@ -64,7 +68,9 @@ class Histories final { void readClientSideMessage(not_null item); void sendPendingReadInbox(not_null history); +#if 0 // mtp void requestDialogEntry(not_null folder); +#endif void requestDialogEntry( not_null history, Fn callback = nullptr); @@ -74,10 +80,13 @@ class Histories final { void requestGroupAround(not_null item); +#if 0 // mtp void deleteMessages( not_null history, const QVector &ids, bool revoke); +#endif + void deleteAllMessages( not_null history, MsgId deleteTillId, @@ -97,6 +106,7 @@ class Histories final { void deleteMessages(const MessageIdsList &ids, bool revoke); +#if 0 // mtp int sendRequest( not_null history, RequestType type, @@ -126,6 +136,7 @@ class Histories final { return { ReplaceReplyIds(history, args, replyTo)... }; }; } +#endif void checkTopicCreated(FullMsgId rootId, MsgId realRoot); [[nodiscard]] FullMsgId convertTopicReplyToId( @@ -157,6 +168,7 @@ class Histories final { MsgId aroundId = 0; mtpRequestId requestId = 0; }; +#if 0 // mtp struct DelayedByTopicMessage { uint64 randomId = 0; FullMsgId replyTo; @@ -165,6 +177,7 @@ class Histories final { Fn fail; int requestId = 0; }; +#endif struct GroupRequestKey { not_null history; MsgId rootId = 0; @@ -174,6 +187,7 @@ class Histories final { GroupRequestKey) = default; }; +#if 0 // mtp template static auto ReplaceReplyIds( not_null history, @@ -185,12 +199,14 @@ class Histories final { return arg; } } +#endif void readInboxTill(not_null history, MsgId tillId, bool force); void sendReadRequests(); void sendReadRequest(not_null history, State &state); [[nodiscard]] State *lookup(not_null history); void checkEmptyState(not_null history); +#if 0 // mtp void checkPostponed(not_null history, int id); void finishSentRequest( not_null history, @@ -198,6 +214,7 @@ class Histories final { int id); [[nodiscard]] bool postponeHistoryRequest(const State &state) const; [[nodiscard]] bool postponeEntryRequest(const State &state) const; +#endif void postponeRequestDialogEntries(); void sendDialogRequests(); @@ -230,9 +247,11 @@ class Histories final { GroupRequestKey, ChatListGroupRequest> _chatListGroupRequests; +#if 0 // mtp base::flat_map< FullMsgId, std::vector> _creatingTopics; +#endif base::flat_map _createdTopicIds; base::flat_set _creatingTopicRequests; diff --git a/Telegram/SourceFiles/data/data_location.cpp b/Telegram/SourceFiles/data/data_location.cpp index c08c6688fd92d..de68cd083d14d 100644 --- a/Telegram/SourceFiles/data/data_location.cpp +++ b/Telegram/SourceFiles/data/data_location.cpp @@ -9,10 +9,13 @@ For license and copyright information please follow this link: #include "ui/image/image.h" #include "data/data_file_origin.h" +#include "tdb/tdb_tl_scheme.h" namespace Data { namespace { +using namespace Tdb; + [[nodiscard]] QString AsString(float64 value) { constexpr auto kPrecision = 6; return QString::number(value, 'f', kPrecision); @@ -26,6 +29,11 @@ LocationPoint::LocationPoint(const MTPDgeoPoint &point) , _access(point.vaccess_hash().v) { } +LocationPoint::LocationPoint(const TLlocation &point) +: _lat(point.data().vlatitude().v) +, _lon(point.data().vlongitude().v) { +} + QString LocationPoint::latAsString() const { return AsString(_lat); } diff --git a/Telegram/SourceFiles/data/data_location.h b/Telegram/SourceFiles/data/data_location.h index 7d9a59b4a76b4..d16d7f50c1018 100644 --- a/Telegram/SourceFiles/data/data_location.h +++ b/Telegram/SourceFiles/data/data_location.h @@ -7,6 +7,10 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLlocation; +} // namespace Tdb + namespace Data { struct FileOrigin; @@ -16,6 +20,8 @@ class LocationPoint { LocationPoint() = default; explicit LocationPoint(const MTPDgeoPoint &point); + explicit LocationPoint(const Tdb::TLlocation &point); + [[nodiscard]] QString latAsString() const; [[nodiscard]] QString lonAsString() const; [[nodiscard]] MTPGeoPoint toMTP() const; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 1132d75c49e73..de16e036c654f 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -72,6 +72,9 @@ For license and copyright information please follow this link: #include "styles/style_chat.h" #include "styles/style_dialogs.h" +#include "tdb/tdb_tl_scheme.h" +#include "api/api_text_entities.h" + namespace Data { namespace { @@ -79,6 +82,8 @@ constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60); constexpr auto kMaxPreviewImages = 3; constexpr auto kLoadingStoryPhotoId = PhotoId(0x7FFF'DEAD'FFFF'FFFFULL); +using namespace Tdb; + using ItemPreview = HistoryView::ItemPreview; using ItemPreviewImage = HistoryView::ItemPreviewImage; @@ -258,6 +263,7 @@ template return (i != end(*existing)) ? *i : ItemPreviewImage(); } +#if 0 // mtp bool UpdateExtendedMedia( Invoice &invoice, not_null item, @@ -300,6 +306,7 @@ bool UpdateExtendedMedia( return true; }); } +#endif } // namespace @@ -315,6 +322,7 @@ TextForMimeData WithCaptionClipboardText( return result; } +#if 0 // mtp Invoice ComputeInvoiceData( not_null item, const MTPDmessageMediaInvoice &data) { @@ -385,6 +393,73 @@ Giveaway ComputeGiveawayData( } return result; } +#endif + +Invoice ComputeInvoiceData( + not_null item, + const TLDmessageInvoice &data) { + return { + .receiptMsgId = data.vreceipt_message_id().v, + .amount = uint64(data.vtotal_amount().v), + .currency = data.vcurrency().v, + .title = TextUtilities::SingleLine(data.vtitle().v), + .description = Api::FormattedTextFromTdb( + data.vdescription()), + .photo = (data.vphoto() + ? item->history()->owner().processPhoto(*data.vphoto()).get() + : nullptr), + .isTest = data.vis_test().v, + }; +} + +Call ComputeCallData(const TLDmessageCall &call) { + auto result = Call(); + result.finishReason = call.vdiscard_reason().match([]( + const TLDcallDiscardReasonDeclined &) { + return CallFinishReason::Busy; + }, [](const TLDcallDiscardReasonDisconnected &) { + return CallFinishReason::Disconnected; + }, [](const TLDcallDiscardReasonHungUp &) { + return CallFinishReason::Hangup; + }, [](const TLDcallDiscardReasonMissed &) { + return CallFinishReason::Missed; + }, [](const TLDcallDiscardReasonEmpty &) { + return CallFinishReason::Hangup; + }); + result.duration = call.vduration().v; + result.video = call.vis_video().v; + return result; +} + +Giveaway ComputeGiveawayData( + not_null item, + const TLDmessagePremiumGiveaway &data) { + const auto &fields = data.vparameters().data(); + auto result = Giveaway{ + .untilDate = fields.vwinners_selection_date().v, + .quantity = data.vwinner_count().v, + .months = data.vmonth_count().v, + .all = !fields.vonly_new_members().v, + }; + const auto &list = fields.vadditional_chat_ids().v; + result.channels.reserve(1 + list.size()); + const auto owner = &item->history()->owner(); + const auto firstId = peerFromTdbChat(fields.vboosted_chat_id()); + if (const auto channelId = peerToChannel(firstId)) { + result.channels.push_back(owner->channel(channelId)); + } + for (const auto &id : list) { + if (const auto channelId = peerToChannel(peerFromTdbChat(id))) { + result.channels.push_back(owner->channel(channelId)); + } + } + const auto &countries = fields.vcountry_codes().v; + result.countries.reserve(countries.size()); + for (const auto &country : countries) { + result.countries.push_back(country.v); + } + return result; +} Media::Media(not_null parent) : _parent(parent) { } @@ -531,6 +606,10 @@ std::unique_ptr Media::createView( return createView(message, message->data(), replacing); } +bool Media::updateContent(const TLmessageContent &content) { + return false; +} + ItemPreview Media::toGroupPreview( const HistoryItemsList &items, ToPreviewOptions options) const { @@ -625,9 +704,11 @@ MediaPhoto::MediaPhoto( } MediaPhoto::~MediaPhoto() { +#if 0 // mtp if (uploading() && !Core::Quitting()) { parent()->history()->session().uploader().cancel(parent()->fullId()); } +#endif parent()->history()->owner().unregisterPhotoItem(_photo, parent()); } @@ -742,6 +823,7 @@ bool MediaPhoto::hasSpoiler() const { return _spoiler; } +#if 0 // mtp bool MediaPhoto::updateInlineResultMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaPhoto) { return false; @@ -779,6 +861,7 @@ bool MediaPhoto::updateSentMedia(const MTPMessageMedia &media) { parent()->history()->owner().photoConvert(_photo, *content); return true; } +#endif std::unique_ptr MediaPhoto::createView( not_null message, @@ -831,9 +914,11 @@ MediaFile::MediaFile( } MediaFile::~MediaFile() { +#if 0 // mtp if (uploading() && !Core::Quitting()) { parent()->history()->session().uploader().cancel(parent()->fullId()); } +#endif parent()->history()->owner().unregisterDocumentItem( _document, parent()); @@ -1091,6 +1176,7 @@ bool MediaFile::hasSpoiler() const { return _spoiler; } +#if 0 // mtp bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaDocument) { return false; @@ -1128,6 +1214,7 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) { parent()->history()->owner().documentConvert(_document, *content); return true; } +#endif std::unique_ptr MediaFile::createView( not_null message, @@ -1233,6 +1320,7 @@ TextForMimeData MediaContact::clipboardText() const { return TextForMimeData::Simple(text); } +#if 0 // mtp bool MediaContact::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -1253,6 +1341,7 @@ bool MediaContact::updateSentMedia(const MTPMessageMedia &media) { } return true; } +#endif std::unique_ptr MediaContact::createView( not_null message, @@ -1334,6 +1423,7 @@ TextForMimeData MediaLocation::clipboardText() const { return result; } +#if 0 // mtp bool MediaLocation::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -1341,6 +1431,7 @@ bool MediaLocation::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaLocation::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaLocation::createView( not_null message, @@ -1397,6 +1488,7 @@ bool MediaCall::allowsForward() const { return false; } +#if 0 // mtp bool MediaCall::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -1404,6 +1496,7 @@ bool MediaCall::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaCall::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaCall::createView( not_null message, @@ -1529,6 +1622,7 @@ bool MediaWebPage::allowsEdit() const { return true; } +#if 0 // mtp bool MediaWebPage::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -1536,6 +1630,7 @@ bool MediaWebPage::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaWebPage::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaWebPage::createView( not_null message, @@ -1623,6 +1718,7 @@ TextWithEntities MediaGame::consumedMessageText() const { return _consumedText; } +#if 0 // mtp bool MediaGame::updateInlineResultMedia(const MTPMessageMedia &media) { return updateSentMedia(media); } @@ -1635,6 +1731,7 @@ bool MediaGame::updateSentMedia(const MTPMessageMedia &media) { _game, media.c_messageMediaGame().vgame()); return true; } +#endif std::unique_ptr MediaGame::createView( not_null message, @@ -1712,6 +1809,7 @@ TextForMimeData MediaInvoice::clipboardText() const { return TextForMimeData(); } +#if 0 // mtp bool MediaInvoice::updateInlineResultMedia(const MTPMessageMedia &media) { return true; } @@ -1727,6 +1825,7 @@ bool MediaInvoice::updateExtendedMedia( return UpdateExtendedMedia(_invoice, item, media); } +#endif std::unique_ptr MediaInvoice::createView( not_null message, @@ -1791,6 +1890,7 @@ TextForMimeData MediaPoll::clipboardText() const { return TextForMimeData::Simple(text); } +#if 0 // mtp bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -1798,6 +1898,7 @@ bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaPoll::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaPoll::createView( not_null message, @@ -1812,6 +1913,13 @@ MediaDice::MediaDice(not_null parent, QString emoji, int value) , _value(value) { } +MediaDice::MediaDice( + not_null parent, + const Tdb::TLDmessageDice &data) +: MediaDice(parent, data.vemoji().v, data.vvalue().v) { + parent->history()->session().diceStickersPacks().apply(data); +} + std::unique_ptr MediaDice::clone(not_null parent) { return std::make_unique(parent, _emoji, _value); } @@ -1848,6 +1956,7 @@ bool MediaDice::forceForwardedInfo() const { return true; } +#if 0 // mtp bool MediaDice::updateInlineResultMedia(const MTPMessageMedia &media) { return updateSentMedia(media); } @@ -1860,6 +1969,7 @@ bool MediaDice::updateSentMedia(const MTPMessageMedia &media) { parent()->history()->owner().requestItemRepaint(parent()); return true; } +#endif std::unique_ptr MediaDice::createView( not_null message, @@ -1874,6 +1984,17 @@ std::unique_ptr MediaDice::createView( std::make_unique(message, this)); } +bool MediaDice::updateContent(const TLmessageContent &content) { + if (content.type() != id_messageDice) { + return false; + } + const auto &data = content.c_messageDice(); + parent()->history()->session().diceStickersPacks().apply(data); + _value = data.vvalue().v; + parent()->history()->owner().requestItemRepaint(parent()); + return true; +} + ClickHandlerPtr MediaDice::makeHandler() const { return MakeHandler(parent()->history(), _emoji); } @@ -1928,6 +2049,7 @@ ClickHandlerPtr MediaDice::MakeHandler( }); } +#if 0 // mtp MediaGiftBox::MediaGiftBox( not_null parent, not_null from, @@ -1943,9 +2065,31 @@ MediaGiftBox::MediaGiftBox( , _from(from) , _data(std::move(data)) { } +#endif +MediaGiftBox::MediaGiftBox( + not_null parent, + not_null from, + int months, + DocumentData *sticker) +: MediaGiftBox(parent, from, GiftCode{ .months = months }, sticker) { +} + +MediaGiftBox::MediaGiftBox( + not_null parent, + not_null from, + GiftCode data, + DocumentData *sticker) +: Media(parent) +, _from(from) +, _data(std::move(data)) +, _sticker(sticker) { +} std::unique_ptr MediaGiftBox::clone(not_null parent) { +#if 0 // mtp return std::make_unique(parent, _from, _data); +#endif + return std::make_unique(parent, _from, _data, _sticker); } not_null MediaGiftBox::from() const { @@ -1968,6 +2112,7 @@ TextForMimeData MediaGiftBox::clipboardText() const { return {}; } +#if 0 // mtp bool MediaGiftBox::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -1975,6 +2120,7 @@ bool MediaGiftBox::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaGiftBox::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaGiftBox::createView( not_null message, @@ -1982,7 +2128,10 @@ std::unique_ptr MediaGiftBox::createView( HistoryView::Element *replacing) { return std::make_unique( message, +#if 0 // mtp std::make_unique(message, this)); +#endif + std::make_unique(message, this, _sticker)); } MediaWallPaper::MediaWallPaper( @@ -2014,6 +2163,7 @@ TextForMimeData MediaWallPaper::clipboardText() const { return {}; } +#if 0 // mtp bool MediaWallPaper::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -2021,6 +2171,7 @@ bool MediaWallPaper::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaWallPaper::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaWallPaper::createView( not_null message, @@ -2121,6 +2272,7 @@ bool MediaStory::dropForwardedInfo() const { return true; } +#if 0 // mtp bool MediaStory::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -2128,6 +2280,7 @@ bool MediaStory::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaStory::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif not_null MediaStory::LoadingStoryPhoto( not_null owner) { @@ -2185,6 +2338,7 @@ std::unique_ptr MediaStory::createView( } } +#if 0 // mtp MediaGiveaway::MediaGiveaway( not_null parent, const Giveaway &data) @@ -2192,9 +2346,21 @@ MediaGiveaway::MediaGiveaway( , _giveaway(data) { parent->history()->session().giftBoxStickersPacks().load(); } +#endif +MediaGiveaway::MediaGiveaway( + not_null parent, + const Giveaway &data, + DocumentData *sticker) +: Media(parent) +, _giveaway(data) +, _sticker(sticker) { +} std::unique_ptr MediaGiveaway::clone(not_null parent) { +#if 0 // mtp return std::make_unique(parent, _giveaway); +#endif + return std::make_unique(parent, _giveaway, _sticker); } const Giveaway *MediaGiveaway::giveaway() const { @@ -2217,6 +2383,7 @@ TextForMimeData MediaGiveaway::clipboardText() const { return TextForMimeData(); } +#if 0 // mtp bool MediaGiveaway::updateInlineResultMedia(const MTPMessageMedia &media) { return true; } @@ -2224,12 +2391,19 @@ bool MediaGiveaway::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaGiveaway::updateSentMedia(const MTPMessageMedia &media) { return true; } +#endif std::unique_ptr MediaGiveaway::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { +#if 0 // mtp return std::make_unique(message, &_giveaway); +#endif + return std::make_unique( + message, + &_giveaway, + _sticker); } } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index b62cf98259d9b..506b222e77632 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -11,6 +11,14 @@ For license and copyright information please follow this link: #include "data/data_location.h" #include "data/data_wall_paper.h" +namespace Tdb { +class TLDmessageInvoice; +class TLDmessageCall; +class TLDmessageDice; +class TLmessageContent; +class TLDmessagePremiumGiveaway; +} // namespace Tdb + class Image; class History; class HistoryItem; @@ -162,6 +170,7 @@ class Media { const TextWithEntities &text); [[nodiscard]] virtual TextWithEntities consumedMessageText() const; +#if 0 // mtp // After sending an inline result we may want to completely recreate // the media (all media that was generated on client side, for example). virtual bool updateInlineResultMedia(const MTPMessageMedia &media) = 0; @@ -171,6 +180,7 @@ class Media { const MTPMessageExtendedMedia &media) { return false; } +#endif virtual std::unique_ptr createView( not_null message, not_null realParent, @@ -179,6 +189,8 @@ class Media { not_null message, HistoryView::Element *replacing = nullptr); + virtual bool updateContent(const Tdb::TLmessageContent &content); + protected: [[nodiscard]] ItemPreview toGroupPreview( const HistoryItemsList &items, @@ -219,8 +231,10 @@ class MediaPhoto final : public Media { bool allowsEditMedia() const override; bool hasSpoiler() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -262,8 +276,10 @@ class MediaFile final : public Media { bool dropForwardedInfo() const override; bool hasSpoiler() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -294,8 +310,10 @@ class MediaContact final : public Media { QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -325,8 +343,10 @@ class MediaLocation final : public Media { QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -353,8 +373,10 @@ class MediaCall final : public Media { TextForMimeData clipboardText() const override; bool allowsForward() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -394,8 +416,10 @@ class MediaWebPage final : public Media { TextForMimeData clipboardText() const override; bool allowsEdit() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -428,8 +452,10 @@ class MediaGame final : public Media { bool consumeMessageText(const TextWithEntities &text) override; TextWithEntities consumedMessageText() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -458,11 +484,13 @@ class MediaInvoice final : public Media { QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; bool updateExtendedMedia( not_null item, const MTPMessageExtendedMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -488,8 +516,10 @@ class MediaPoll final : public Media { QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -503,6 +533,9 @@ class MediaPoll final : public Media { class MediaDice final : public Media { public: MediaDice(not_null parent, QString emoji, int value); + MediaDice( + not_null parent, + const Tdb::TLDmessageDice &data); std::unique_ptr clone(not_null parent) override; @@ -515,13 +548,17 @@ class MediaDice final : public Media { TextForMimeData clipboardText() const override; bool forceForwardedInfo() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, HistoryView::Element *replacing = nullptr) override; + bool updateContent(const Tdb::TLmessageContent &content) override; + [[nodiscard]] ClickHandlerPtr makeHandler() const; [[nodiscard]] static ClickHandlerPtr MakeHandler( not_null history, @@ -535,6 +572,7 @@ class MediaDice final : public Media { class MediaGiftBox final : public Media { public: +#if 0 // mtp MediaGiftBox( not_null parent, not_null from, @@ -543,6 +581,17 @@ class MediaGiftBox final : public Media { not_null parent, not_null from, GiftCode data); +#endif + MediaGiftBox( + not_null parent, + not_null from, + int months, + DocumentData *sticker); + MediaGiftBox( + not_null parent, + not_null from, + GiftCode data, + DocumentData *sticker); std::unique_ptr clone(not_null parent) override; @@ -553,8 +602,10 @@ class MediaGiftBox final : public Media { QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -564,6 +615,8 @@ class MediaGiftBox final : public Media { not_null _from; GiftCode _data; + DocumentData *_sticker = nullptr; // later move up + }; class MediaWallPaper final : public Media { @@ -579,8 +632,10 @@ class MediaWallPaper final : public Media { QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -610,8 +665,10 @@ class MediaStory final : public Media, public base::has_weak_ptr { TextForMimeData clipboardText() const override; bool dropForwardedInfo() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -630,9 +687,15 @@ class MediaStory final : public Media, public base::has_weak_ptr { class MediaGiveaway final : public Media { public: +#if 0 // mtp MediaGiveaway( not_null parent, const Giveaway &data); +#endif + MediaGiveaway( + not_null parent, + const Giveaway &data, + DocumentData *sticker); std::unique_ptr clone(not_null parent) override; @@ -642,8 +705,10 @@ class MediaGiveaway final : public Media { QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; +#if 0 // mtp bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -652,12 +717,15 @@ class MediaGiveaway final : public Media { private: Giveaway _giveaway; + DocumentData *_sticker = nullptr; + }; [[nodiscard]] TextForMimeData WithCaptionClipboardText( const QString &attachType, TextForMimeData &&caption); +#if 0 // mtp [[nodiscard]] Invoice ComputeInvoiceData( not_null item, const MTPDmessageMediaInvoice &data); @@ -667,5 +735,16 @@ class MediaGiveaway final : public Media { [[nodiscard]] Giveaway ComputeGiveawayData( not_null item, const MTPDmessageMediaGiveaway &data); +#endif + +[[nodiscard]] Invoice ComputeInvoiceData( + not_null item, + const Tdb::TLDmessageInvoice &data); + +[[nodiscard]] Call ComputeCallData(const Tdb::TLDmessageCall &call); + +[[nodiscard]] Giveaway ComputeGiveawayData( + not_null item, + const Tdb::TLDmessagePremiumGiveaway &data); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.cpp b/Telegram/SourceFiles/data/data_message_reaction_id.cpp index 1103d2e150c25..accec787657df 100644 --- a/Telegram/SourceFiles/data/data_message_reaction_id.cpp +++ b/Telegram/SourceFiles/data/data_message_reaction_id.cpp @@ -9,7 +9,14 @@ For license and copyright information please follow this link: #include "data/stickers/data_custom_emoji.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { +namespace { + +using namespace Tdb; + +} // namespace QString ReactionEntityData(const ReactionId &id) { if (id.empty()) { @@ -20,6 +27,7 @@ QString ReactionEntityData(const ReactionId &id) { return u"default:"_q + id.emoji(); } +#if 0 // mtp ReactionId ReactionFromMTP(const MTPReaction &reaction) { return reaction.match([](MTPDreactionEmpty) { return ReactionId{ QString() }; @@ -39,5 +47,25 @@ MTPReaction ReactionToMTP(ReactionId id) { ? MTP_reactionEmpty() : MTP_reactionEmoji(MTP_string(emoji)); } +#endif + +ReactionId ReactionFromTL(const TLreactionType &reaction) { + return reaction.match([&](const TLDreactionTypeEmoji &data) { + return ReactionId{ data.vemoji().v }; + }, [&](const TLDreactionTypeCustomEmoji &data) { + return ReactionId{ DocumentId(data.vcustom_emoji_id().v) }; + }); +} + +TLreactionType ReactionToTL(ReactionId id) { + const auto custom = id.custom(); + return custom + ? tl_reactionTypeCustomEmoji(tl_int64(custom)) + : tl_reactionTypeEmoji(tl_string(id.emoji())); +} + +std::optional ReactionToMaybeTL(ReactionId id) { + return id.empty() ? std::optional() : ReactionToTL(id); +} } // namespace Data diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.h b/Telegram/SourceFiles/data/data_message_reaction_id.h index b885d5a6e759b..08de32cb4a98e 100644 --- a/Telegram/SourceFiles/data/data_message_reaction_id.h +++ b/Telegram/SourceFiles/data/data_message_reaction_id.h @@ -7,6 +7,10 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLreactionType; +} // namespace Tdb + namespace Data { struct ReactionId { @@ -41,8 +45,15 @@ inline bool operator==(const ReactionId &a, const ReactionId &b) { [[nodiscard]] QString ReactionEntityData(const ReactionId &id); +#if 0 // mtp [[nodiscard]] ReactionId ReactionFromMTP(const MTPReaction &reaction); [[nodiscard]] MTPReaction ReactionToMTP(ReactionId id); +#endif + +[[nodiscard]] ReactionId ReactionFromTL(const Tdb::TLreactionType &reaction); +[[nodiscard]] Tdb::TLreactionType ReactionToTL(ReactionId id); +[[nodiscard]] std::optional ReactionToMaybeTL( + ReactionId id); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index 7aa820101170e..e100e0df207b8 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -30,9 +30,14 @@ For license and copyright information please follow this link: #include "apiwrap.h" #include "styles/style_chat.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_account.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kRefreshFullListEach = 60 * 60 * crl::time(1000); constexpr auto kPollEach = 20 * crl::time(1000); constexpr auto kSizeForDownscale = 64; @@ -48,6 +53,7 @@ constexpr auto kTopReactionsLimit = 14; return id.emoji(); } +#if 0 // mtp [[nodiscard]] std::vector ListFromMTP( const MTPDmessages_reactions &data) { const auto &list = data.vreactions().v; @@ -63,6 +69,7 @@ constexpr auto kTopReactionsLimit = 14; } return result; } +#endif [[nodiscard]] Reaction CustomReaction(not_null document) { return Reaction{ @@ -194,6 +201,40 @@ PossibleItemReactionsRef LookupPossibleReactions( return result; } +PossibleItemReactionsRef ParsePossibleReactions( + not_null session, + const TLavailableReactions &available) { + const auto &data = available.data(); + const auto reactions = &session->data().reactions(); + auto result = PossibleItemReactionsRef(); + result.customAllowed = data.vallow_custom_emoji().v; + const auto &top = data.vtop_reactions().v; + const auto &recent = data.vrecent_reactions().v; + const auto &popular = data.vpopular_reactions().v; + auto added = base::flat_set(); + reactions->clearTemporary(); + result.recent.reserve(top.size() + recent.size() + popular.size()); + + auto &&all = ranges::views::concat(top, recent, popular); + for (const auto &reaction : all) { + const auto id = ReactionFromTL(reaction.data().vtype()); + if (added.emplace(id).second) { + if (const auto temp = reactions->lookupTemporary(id)) { + result.recent.push_back(temp); + } + } + } + const auto i = ranges::find( + result.recent, + reactions->favoriteId(), + &Reaction::id); + if (i != end(result.recent) && i != begin(result.recent)) { + std::rotate(begin(result.recent), i, i + 1); + } + return result; + +} + PossibleItemReactions::PossibleItemReactions( const PossibleItemReactionsRef &other) : recent(other.recent | ranges::views::transform([](const auto &value) { @@ -205,6 +246,7 @@ PossibleItemReactions::PossibleItemReactions( Reactions::Reactions(not_null owner) : _owner(owner) +#if 0 // mtp , _topRefreshTimer([=] { refreshTop(); }) , _repaintTimer([=] { repaintCollected(); }) { refreshDefault(); @@ -239,6 +281,8 @@ Reactions::Reactions(not_null owner) applyFavorite(id); }, _lifetime); }); +#endif +{ } Reactions::~Reactions() = default; @@ -247,6 +291,7 @@ Main::Session &Reactions::session() const { return _owner->session(); } +#if 0 // mtp void Reactions::refreshTop() { requestTop(); } @@ -270,6 +315,15 @@ void Reactions::refreshRecentDelayed() { void Reactions::refreshDefault() { requestDefault(); } +#endif + +void Reactions::refreshActive(const TLDupdateActiveEmojiReactions &data) { + updateFromData(data); +} + +void Reactions::refreshFavorite(const TLDupdateDefaultReactionType &data) { + applyFavorite(ReactionFromTL(data.vreaction_type())); +} const std::vector &Reactions::list(Type type) const { switch (type) { @@ -290,6 +344,7 @@ const Reaction *Reactions::favorite() const { } void Reactions::setFavorite(const ReactionId &id) { +#if 0 // mtp const auto api = &_owner->session().api(); if (_saveFaveRequestId) { api->request(_saveFaveRequestId).cancel(); @@ -301,6 +356,10 @@ void Reactions::setFavorite(const ReactionId &id) { }).fail([=] { _saveFaveRequestId = 0; }).send(); +#endif + _owner->session().sender().request(TLsetDefaultReactionType( + ReactionToTL(id) + )).send(); applyFavorite(id); } @@ -379,9 +438,13 @@ void Reactions::preloadImageFor(const ReactionId &id) { : i->selectAnimation.get(); if (document) { loadImage(set, document, !i->centerIcon); + } else { + resolveEmoji(id.emoji()); +#if 0 // mtp } else if (!_waitingForList) { _waitingForList = true; refreshDefault(); +#endif } } @@ -473,9 +536,6 @@ void Reactions::resolveImages() { : i->selectAnimation.get(); if (document) { loadImage(set, document, !i->centerIcon); - } else { - LOG(("API Error: Reaction '%1' not found!" - ).arg(ReactionIdToLog(id))); } } } @@ -527,6 +587,7 @@ void Reactions::downloadTaskFinished() { } } +#if 0 // mtp void Reactions::requestTop() { if (_topRequestId) { return; @@ -625,37 +686,181 @@ void Reactions::updateRecent(const MTPDmessages_reactions &data) { void Reactions::updateDefault(const MTPDmessages_availableReactions &data) { _defaultHash = data.vhash().v; - const auto &list = data.vreactions().v; - const auto oldCache = base::take(_iconsCache); - const auto toCache = [&](DocumentData *document) { - if (document) { - _iconsCache.emplace(document, document->createMediaView()); +#endif + +void Reactions::updateFromData( + const Tdb::TLDupdateActiveEmojiReactions &data) { + auto list = std::vector(); + list.reserve(data.vemojis().v.size() + _emojiReactions.size()); + for (const auto &entry : data.vemojis().v) { + const auto emoji = entry.v; + const auto i = ranges::find( + _emojiReactions, + emoji, + &EmojiResolved::emoji); + if (i == end(_emojiReactions)) { + list.push_back({ .emoji = emoji, .active = true }); + } else { + list.push_back(*i); + list.back().active = true; } - }; - _active.clear(); - _available.clear(); - _active.reserve(list.size()); - _available.reserve(list.size()); - _iconsCache.reserve(list.size() * 4); - for (const auto &reaction : list) { - if (const auto parsed = parse(reaction)) { - _available.push_back(*parsed); - if (parsed->active) { - _active.push_back(*parsed); + } + for (auto &entry : _emojiReactions) { + if (!ranges::contains(list, entry.emoji, &EmojiResolved::emoji)) { + list.push_back(entry); + list.back().active = false; + } + } + _emojiReactions = std::move(list); + resolveEmojiNext(); +} + +void Reactions::resolveEmojiNext() { + const auto sent = ranges::find_if( + _emojiReactions, + [](mtpRequestId id) { return id != 0; }, + &EmojiResolved::requestId); + const auto resolving = (sent != end(_emojiReactions)); + if (resolving) { + return; + } + const auto unknown = ranges::find( + _emojiReactions, + false, + &EmojiResolved::resolved); + if (unknown == end(_emojiReactions)) { + return; + } + resolveEmoji(&*unknown); +} + +void Reactions::resolveEmoji(const QString &emoji) { + const auto i = ranges::find( + _emojiReactions, + emoji, + &EmojiResolved::emoji); + if (i != end(_emojiReactions)) { + return; + } + _emojiReactions.push_back({ .emoji = emoji }); + resolveEmoji(&_emojiReactions.back()); +} + +void Reactions::resolveEmoji(not_null entry) { + const auto emoji = entry->emoji; + const auto finish = [=](std::optional parsed) { + const auto i = ranges::find( + _emojiReactions, + emoji, + &EmojiResolved::emoji); + Assert(i != end(_emojiReactions)); + i->resolved = true; + i->requestId = 0; + const auto active = i->active; + if (parsed) { + const auto id = ReactionId{ emoji }; + const auto i = ranges::find(_available, id, &Reaction::id); + if (i != end(_available)) { + *i = std::move(*parsed); + } else { + _available.reserve(_emojiReactions.size()); + _available.push_back(std::move(*parsed)); + } + const auto toCache = [&](DocumentData *document) { + if (document) { + _iconsCache.emplace(document, document->createMediaView()); + } + }; + if (active) { toCache(parsed->appearAnimation); toCache(parsed->selectAnimation); toCache(parsed->centerIcon); toCache(parsed->aroundAnimation); } + resolveImages(); + } + if (active) { + checkAllActiveResolved(); } + resolveEmojiNext(); + }; + auto &api = _owner->session().sender(); + entry->requestId = api.request(TLgetEmojiReaction( + tl_string(emoji) + )).done([=](const TLemojiReaction &result) { + finish(parse(result)); + }).fail([=](const Error &error) { + LOG(("API Error: Reaction '%1' not found!").arg(emoji)); + finish(std::nullopt); + }).send(); +} + +void Reactions::checkAllActiveResolved() { + if (!allActiveResolved()) { + return; } - if (_waitingForList) { - _waitingForList = false; - resolveImages(); + _active.clear(); + _active.reserve(_available.size()); + for (const auto &entry : _emojiReactions) { + if (!entry.active) { + break; + } + const auto i = ranges::find( + _available, + ReactionId{ entry.emoji }, + &Reaction::id); + if (i != end(_available)) { + _active.push_back(*i); + } } + resolveImages(); defaultUpdated(); } +bool Reactions::allActiveResolved() const { + for (const auto &entry : _emojiReactions) { + if (!entry.active) { + return true; + } else if (!entry.resolved) { + return false; + } + } + return true; +} + +void Reactions::requestGeneric() { + if (_genericRequestId) { + return; + } + auto &api = _owner->session().sender(); + _genericRequestId = api.request(TLgetCustomEmojiReactionAnimations( + )).done([=](const TLDstickers &result) { + _genericRequestId = 0; + updateGeneric(result); + }).fail([=] { + _genericRequestId = 0; + }).send(); +} + +void Reactions::updateGeneric(const TLDstickers &data) { + const auto oldCache = base::take(_genericCache); + const auto toCache = [&](not_null document) { + _genericAnimations.push_back(document); + _genericCache.emplace(document, document->createMediaView()); + }; + const auto &list = data.vstickers().v; + _genericAnimations.clear(); + _genericAnimations.reserve(list.size()); + _genericCache.reserve(list.size()); + for (const auto &sticker : list) { + toCache(_owner->processDocument(sticker)); + } + if (!_genericCache.empty()) { + _genericCache.front().second->checkStickerLarge(); + } +} + +#if 0 // mtp void Reactions::updateGeneric(const MTPDmessages_stickerSet &data) { const auto oldCache = base::take(_genericCache); const auto toCache = [&](not_null document) { @@ -680,10 +885,13 @@ void Reactions::recentUpdated() { _topRefreshTimer.callOnce(kTopRequestDelay); _recentUpdated.fire({}); } +#endif void Reactions::defaultUpdated() { +#if 0 // mtp refreshTop(); refreshRecent(); +#endif if (_genericAnimations.empty()) { requestGeneric(); } @@ -756,7 +964,7 @@ std::vector Reactions::resolveByIds( void Reactions::resolve(const ReactionId &id) { if (const auto emoji = id.emoji(); !emoji.isEmpty()) { - refreshDefault(); + resolveEmoji(emoji); } else if (const auto customId = id.custom()) { _owner->customEmojiManager().resolve( customId, @@ -764,6 +972,7 @@ void Reactions::resolve(const ReactionId &id) { } } +#if 0 // mtp std::optional Reactions::parse(const MTPAvailableReaction &entry) { return entry.match([&](const MTPDavailableReaction &data) { const auto emoji = qs(data.vreaction()); @@ -797,9 +1006,43 @@ std::optional Reactions::parse(const MTPAvailableReaction &entry) { : std::nullopt; }); } - +#endif + +std::optional Reactions::parse(const TLemojiReaction &entry) { + const auto &data = entry.data(); + const auto emoji = data.vemoji().v; + const auto known = (Ui::Emoji::Find(emoji) != nullptr); + if (!known) { + LOG(("API Error: Unknown emoji in reactions: %1").arg(emoji)); + } + const auto selectAnimation = _owner->processDocument( + data.vselect_animation()); + return known + ? std::make_optional(Reaction{ + .id = ReactionId{ emoji }, + .title = data.vtitle().v, + //.staticIcon = _owner->processDocument(data.vstatic_icon()), + .appearAnimation = _owner->processDocument( + data.vappear_animation()), + .selectAnimation = selectAnimation, + //.activateAnimation = _owner->processDocument( + // data.vactivate_animation()), + //.activateEffects = _owner->processDocument( + // data.veffect_animation()), + .centerIcon = (data.vcenter_animation() + ? _owner->processDocument(*data.vcenter_animation()).get() + : nullptr), + .aroundAnimation = (data.varound_animation() + ? _owner->processDocument( + *data.varound_animation()).get() + : nullptr), + .active = data.vis_active().v, + }) + : std::nullopt; +} + +#if 0 // mtp void Reactions::send(not_null item, bool addToRecent) { - const auto id = item->fullId(); auto &api = _owner->session().api(); auto i = _sentRequests.find(id); if (i != end(_sentRequests)) { @@ -856,6 +1099,7 @@ void Reactions::updateAllInHistory(not_null peer, bool enabled) { history->reactionsEnabledChanged(enabled); } } +#endif void Reactions::clearTemporary() { _temporary.clear(); @@ -883,6 +1127,7 @@ Reaction *Reactions::lookupTemporary(const ReactionId &id) { return nullptr; } +#if 0 // mtp void Reactions::repaintCollected() { const auto now = crl::now(); auto closest = crl::time(); @@ -969,6 +1214,7 @@ void Reactions::CheckUnknownForUnread( }, [](const auto &) { }); } +#endif MessageReactions::MessageReactions(not_null item) : _item(item) { @@ -1024,7 +1270,17 @@ void MessageReactions::add(const ReactionId &id, bool addToRecent) { _list.push_back({ .id = id, .count = 1, .my = true }); } auto &owner = history->owner(); +#if 0 // mtp owner.reactions().send(_item, addToRecent); +#endif + _item->history()->session().sender().request(TLaddMessageReaction( + peerToTdbChat(_item->history()->peer->id), + tl_int53(_item->id.bare), + ReactionToTL(id), + tl_bool(false), // is_big + tl_bool(addToRecent) + )).send(); + owner.notifyItemDataChange(_item); } @@ -1060,10 +1316,19 @@ void MessageReactions::remove(const ReactionId &id) { } } auto &owner = history->owner(); +#if 0 // mtp owner.reactions().send(_item, false); +#endif + _item->history()->session().sender().request(TLremoveMessageReaction( + peerToTdbChat(_item->history()->peer->id), + tl_int53(_item->id.bare), + ReactionToTL(id) + )).send(); + owner.notifyItemDataChange(_item); } +#if 0 // mtp bool MessageReactions::checkIfChanged( const QVector &list, const QVector &recent, @@ -1223,6 +1488,103 @@ bool MessageReactions::change( } return changed; } +#endif + +bool MessageReactions::change(const QVector &list) { + auto &owner = _item->history()->owner(); + auto changed = false; + auto existing = base::flat_set(); + auto parsed = base::flat_map>(); + for (const auto &tlReaction : list) { + const auto &data = tlReaction.data(); + const auto id = ReactionFromTL(data.vtype()); + const auto chosen = data.vis_chosen().v; + const auto i = ranges::find(_list, id, &MessageReaction::id); + const auto nowCount = data.vtotal_count().v; + if (i == end(_list)) { + changed = true; + _list.push_back({ + .id = id, + .count = nowCount, + .my = chosen + }); + } else { + if (i->count != nowCount || i->my != chosen) { + i->count = nowCount; + i->my = chosen; + changed = true; + } + } + existing.emplace(id); + const auto j = _recent.find(id); + for (const auto &sender : data.vrecent_sender_ids().v) { + const auto peer = owner.peer(peerFromSender(sender)); + auto recent = RecentReaction{ .peer = peer }; + if (j != end(_recent)) { + const auto k = ranges::find_if(j->second, [&]( + const RecentReaction &existing) { + return (existing.peer == peer) && existing.unread; + }); + if (k != end(j->second)) { + recent.unread = true; + recent.big = k->big; + } + } + parsed[id].push_back(std::move(recent)); + } + } + if (_list.size() != existing.size()) { + changed = true; + for (auto i = begin(_list); i != end(_list);) { + if (!existing.contains(i->id)) { + i = _list.erase(i); + } else { + ++i; + } + } + } + if (_recent != parsed) { + changed = true; + _recent = std::move(parsed); + } + return changed; +} + +bool MessageReactions::change(const QVector &list) { + enum class State { + Read, + Unread, + UnreadBig, + }; + const auto resolve = [&](not_null peer) { + const auto i = ranges::find(list, peer->id, []( + const TLunreadReaction &reaction) { + return peerFromSender(reaction.data().vsender_id()); + }); + return (i == list.end()) + ? State::Read + : i->data().vis_big().v + ? State::UnreadBig + : State::Unread; + }; + auto changed = false; + for (auto &[emoji, list] : _recent) { + for (auto &reaction : list) { + const auto now = resolve(reaction.peer); + const auto was = !reaction.unread + ? State::Read + : reaction.big + ? State::UnreadBig + : State::Unread; + if (now != was) { + changed = true; + reaction.unread = (now != State::Read); + reaction.big = (now == State::UnreadBig); + } + } + } + return changed; +} const std::vector &MessageReactions::list() const { return _list; diff --git a/Telegram/SourceFiles/data/data_message_reactions.h b/Telegram/SourceFiles/data/data_message_reactions.h index 61b29107d3dc6..de081260f4880 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -11,6 +11,16 @@ For license and copyright information please follow this link: #include "data/data_message_reaction_id.h" #include "data/stickers/data_custom_emoji.h" +namespace Tdb { +class TLDupdateActiveEmojiReactions; +class TLDupdateDefaultReactionType; +class TLemojiReaction; +class TLunreadReaction; +class TLmessageReaction; +class TLDstickers; +class TLavailableReactions; +} // namespace Tdb + namespace Ui { class AnimatedIcon; } // namespace Ui @@ -56,6 +66,10 @@ struct PossibleItemReactions { [[nodiscard]] PossibleItemReactionsRef LookupPossibleReactions( not_null item); +[[nodiscard]] PossibleItemReactionsRef ParsePossibleReactions( + not_null session, + const Tdb::TLavailableReactions &available); + class Reactions final : private CustomEmojiManager::Listener { public: explicit Reactions(not_null owner); @@ -66,10 +80,14 @@ class Reactions final : private CustomEmojiManager::Listener { } [[nodiscard]] Main::Session &session() const; +#if 0 // mtp void refreshTop(); void refreshRecent(); void refreshRecentDelayed(); void refreshDefault(); +#endif + void refreshActive(const Tdb::TLDupdateActiveEmojiReactions &data); + void refreshFavorite(const Tdb::TLDupdateDefaultReactionType &data); enum class Type { Active, @@ -99,20 +117,24 @@ class Reactions final : private CustomEmojiManager::Listener { const ReactionId &emoji, ImageSize size); +#if 0 // mtp void send(not_null item, bool addToRecent); [[nodiscard]] bool sending(not_null item) const; void poll(not_null item, crl::time now); void updateAllInHistory(not_null peer, bool enabled); +#endif void clearTemporary(); [[nodiscard]] Reaction *lookupTemporary(const ReactionId &id); +#if 0 // mtp [[nodiscard]] static bool HasUnread(const MTPMessageReactions &data); static void CheckUnknownForUnread( not_null owner, const MTPMessage &message); +#endif private: struct ImageSet { @@ -122,21 +144,31 @@ class Reactions final : private CustomEmojiManager::Listener { std::unique_ptr icon; bool fromSelectAnimation = false; }; + struct EmojiResolved { + QString emoji; + mtpRequestId requestId = 0; + bool resolved = false; + bool active = false; + }; [[nodiscard]] not_null resolveListener(); void customEmojiResolveDone(not_null document) override; +#if 0 // mtp void requestTop(); void requestRecent(); void requestDefault(); +#endif void requestGeneric(); +#if 0 // mtp void updateTop(const MTPDmessages_reactions &data); void updateRecent(const MTPDmessages_reactions &data); void updateDefault(const MTPDmessages_availableReactions &data); void updateGeneric(const MTPDmessages_stickerSet &data); void recentUpdated(); +#endif void defaultUpdated(); [[nodiscard]] std::optional resolveById(const ReactionId &id); @@ -146,8 +178,19 @@ class Reactions final : private CustomEmojiManager::Listener { void resolve(const ReactionId &id); void applyFavorite(const ReactionId &id); +#if 0 // mtp [[nodiscard]] std::optional parse( const MTPAvailableReaction &entry); +#endif + void updateFromData(const Tdb::TLDupdateActiveEmojiReactions &data); + [[nodiscard]] std::optional parse( + const Tdb::TLemojiReaction &entry); + void updateGeneric(const Tdb::TLDstickers &data); + void resolveEmojiNext(); + void resolveEmoji(const QString &emoji); + void resolveEmoji(not_null entry); + void checkAllActiveResolved(); + [[nodiscard]] bool allActiveResolved() const; void loadImage( ImageSet &set, @@ -157,8 +200,10 @@ class Reactions final : private CustomEmojiManager::Listener { void resolveImages(); void downloadTaskFinished(); +#if 0 // mtp void repaintCollected(); void pollCollected(); +#endif const not_null _owner; @@ -190,6 +235,7 @@ class Reactions final : private CustomEmojiManager::Listener { // Otherwise we could use flat_map>. std::map _temporary; +#if 0 // mtp base::Timer _topRefreshTimer; mtpRequestId _topRequestId = 0; uint64 _topHash = 0; @@ -200,6 +246,8 @@ class Reactions final : private CustomEmojiManager::Listener { mtpRequestId _defaultRequestId = 0; int32 _defaultHash = 0; +#endif + std::vector _emojiReactions; mtpRequestId _genericRequestId = 0; @@ -207,6 +255,7 @@ class Reactions final : private CustomEmojiManager::Listener { rpl::lifetime _imagesLoadLifetime; bool _waitingForList = false; +#if 0 // mtp base::flat_map _sentRequests; base::flat_map, crl::time> _repaintItems; @@ -216,6 +265,7 @@ class Reactions final : private CustomEmojiManager::Listener { mtpRequestId _pollRequestId = 0; mtpRequestId _saveFaveRequestId = 0; +#endif rpl::lifetime _lifetime; @@ -241,6 +291,8 @@ class MessageReactions final { void add(const ReactionId &id, bool addToRecent); void remove(const ReactionId &id); + +#if 0 // mtp bool change( const QVector &list, const QVector &recent, @@ -249,6 +301,10 @@ class MessageReactions final { const QVector &list, const QVector &recent, bool ignoreChosen) const; +#endif + bool change(const QVector &list); + bool change(const QVector &list); + [[nodiscard]] const std::vector &list() const; [[nodiscard]] auto recent() const -> const base::flat_map> &; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index caeb39aed0141..3aa0410fd9abc 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -49,12 +49,15 @@ For license and copyright information please follow this link: #include "storage/file_download.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" +#include "tdb/tdb_tl_scheme.h" +#include "data/data_secret_chat.h" namespace { constexpr auto kUpdateFullPeerTimeout = crl::time(5000); // Not more than once in 5 seconds. constexpr auto kUserpicSize = 160; +using namespace Tdb; using UpdateFlag = Data::PeerUpdate::Flag; } // namespace @@ -73,6 +76,7 @@ PeerId FakePeerIdForJustName(const QString &name) { return peerFromUser(kShift + std::abs(base)); } +#if 0 // mtp bool ApplyBotMenuButton( not_null info, const MTPBotMenuButton *button) { @@ -93,6 +97,24 @@ bool ApplyBotMenuButton( return changed; } +#endif + +bool ApplyBotMenuButton( + not_null info, + const TLbotMenuButton *button) { + auto text = QString(); + auto url = QString(); + if (button) { + const auto &data = button->data(); + const auto text = data.vtext().v; + const auto url = data.vurl().v; + } + const auto changed = (info->botMenuButtonText != text) + || (info->botMenuButtonUrl != url); + info->botMenuButtonText = text; + info->botMenuButtonUrl = url; + return changed; +} bool operator<( const AllowedReactions &a, @@ -106,6 +128,7 @@ bool operator==( return (a.type == b.type) && (a.some == b.some); } +#if 0 // mtp AllowedReactions Parse(const MTPChatReactions &value) { return value.match([&](const MTPDchatReactionsNone &) { return AllowedReactions(); @@ -126,7 +149,30 @@ AllowedReactions Parse(const MTPChatReactions &value) { }; }); } +#endif +AllowedReactions Parse( + not_null peer, + const TLchatAvailableReactions &value) { + return value.match([&](const TLDchatAvailableReactionsSome &data) { + return AllowedReactions{ + .some = ranges::views::all( + data.vreactions().v + ) | ranges::views::transform( + ReactionFromTL + ) | ranges::to_vector, + .type = AllowedReactionsType::Some, + }; + }, [&](const TLDchatAvailableReactionsAll &data) { + return AllowedReactions{ + .type = (peer->isBroadcast() + ? AllowedReactionsType::Default + : AllowedReactionsType::All), + }; + }); +} + +#if 0 // mtp PeerData *PeerFromInputMTP( not_null owner, const MTPInputPeer &input) { @@ -160,6 +206,7 @@ UserData *UserFromInputMTP( return (UserData*)nullptr; }); } +#endif } // namespace Data @@ -213,6 +260,8 @@ void PeerData::updateNameDelayed( const QString &newName, const QString &newNameOrPhone, const QString &newUsername) { + Expects(!isSecretChat()); + if (_name == newName && _nameVersion > 1) { if (isUser()) { if (asUser()->nameOrPhone == newNameOrPhone @@ -264,6 +313,8 @@ void PeerData::updateNameDelayed( } not_null PeerData::ensureEmptyUserpic() const { + Expects(!isSecretChat()); + if (!_userpicEmpty) { const auto user = asUser(); _userpicEmpty = std::make_unique( @@ -276,6 +327,9 @@ not_null PeerData::ensureEmptyUserpic() const { } void PeerData::invalidateEmptyUserpic() { + if (isSecretChat()) { + return; + } _userpicEmpty = nullptr; } @@ -287,11 +341,15 @@ void PeerData::setUserpic( PhotoId photoId, const ImageLocation &location, bool hasVideo) { + if (isSecretChat()) { + return; + } _userpicPhotoId = photoId; _userpicHasVideo = hasVideo ? 1 : 0; _userpic.set(&session(), ImageWithLocation{ .location = location }); } +#if 0 // mtp void PeerData::setUserpicPhoto(const MTPPhoto &data) { const auto photoId = data.match([&](const MTPDphoto &data) { const auto photo = owner().processPhoto(data); @@ -305,8 +363,12 @@ void PeerData::setUserpicPhoto(const MTPPhoto &data) { session().changes().peerUpdated(this, UpdateFlag::Photo); } } +#endif QImage *PeerData::userpicCloudImage(Ui::PeerUserpicView &view) const { + if (const auto user = secretChatUser()) { + return user->userpicCloudImage(view); + } if (!_userpic.isCurrentView(view.cloud)) { if (!_userpic.empty()) { view.cloud = _userpic.createView(); @@ -334,6 +396,9 @@ void PeerData::paintUserpic( int x, int y, int size) const { + if (const auto user = secretChatUser()) { + return user->paintUserpic(p, view, x, y, size); + } const auto cloud = userpicCloudImage(view); const auto ratio = style::DevicePixelRatio(); Ui::ValidateUserpicCache( @@ -346,18 +411,30 @@ void PeerData::paintUserpic( } void PeerData::loadUserpic() { + if (const auto user = secretChatUser()) { + return user->loadUserpic(); + } _userpic.load(&session(), userpicOrigin()); } bool PeerData::hasUserpic() const { + if (const auto user = secretChatUser()) { + return user->hasUserpic(); + } return !_userpic.empty(); } Ui::PeerUserpicView PeerData::activeUserpicView() { + if (const auto user = secretChatUser()) { + return user->activeUserpicView(); + } return { .cloud = _userpic.empty() ? nullptr : _userpic.activeView() }; } Ui::PeerUserpicView PeerData::createUserpicView() { + if (const auto user = secretChatUser()) { + return user->createUserpicView(); + } if (_userpic.empty()) { return {}; } @@ -367,10 +444,16 @@ Ui::PeerUserpicView PeerData::createUserpicView() { } bool PeerData::useEmptyUserpic(Ui::PeerUserpicView &view) const { + if (const auto user = secretChatUser()) { + return user->useEmptyUserpic(view); + } return !userpicCloudImage(view); } InMemoryKey PeerData::userpicUniqueKey(Ui::PeerUserpicView &view) const { + if (const auto user = secretChatUser()) { + return user->userpicUniqueKey(view); + } return useEmptyUserpic(view) ? ensureEmptyUserpic()->uniqueKey() : inMemoryKey(_userpic.location()); @@ -380,6 +463,9 @@ QImage PeerData::generateUserpicImage( Ui::PeerUserpicView &view, int size, std::optional radius) const { + if (const auto user = secretChatUser()) { + return user->generateUserpicImage(view, size, radius); + } if (const auto userpic = userpicCloudImage(view)) { auto image = userpic->scaled( { size, size }, @@ -443,15 +529,22 @@ bool PeerData::userpicHasVideo() const { } Data::FileOrigin PeerData::userpicOrigin() const { + if (const auto user = secretChatUser()) { + return user->userpicOrigin(); + } return Data::FileOriginPeerPhoto(id); } Data::FileOrigin PeerData::userpicPhotoOrigin() const { + if (const auto user = secretChatUser()) { + return user->userpicPhotoOrigin(); + } return (isUser() && userpicPhotoId()) ? Data::FileOriginUserPhoto(peerToUser(id).bare, userpicPhotoId()) : Data::FileOrigin(); } +#if 0 // mtp void PeerData::updateUserpic( PhotoId photoId, MTP::DcId dcId, @@ -470,11 +563,75 @@ void PeerData::updateUserpic( kUserpicSize), hasVideo); } +#endif + +void PeerData::updateUserpic(const TLchatPhotoInfo &photo) { + if (isSecretChat()) { + return; + } + updateUserpic(photo.data().vsmall(), photo.data().vhas_animation().v); +} + +void PeerData::updateUserpic(const TLprofilePhoto &photo) { + if (isSecretChat()) { + return; + } + updateUserpic(photo.data().vsmall(), photo.data().vhas_animation().v); +} + +void PeerData::updateUserpic(const TLfile &small, bool hasVideo) { + if (isSecretChat()) { + return; + } + const auto location = ImageLocation( + { TdbFileLocation{ small } }, + kUserpicSize, + kUserpicSize); + if (_userpic.location() != location || _userpicHasVideo != hasVideo) { + const auto wasKnown = !userpicPhotoUnknown(); + setUserpic(kUnknownPhotoId, location, hasVideo); + if (wasKnown && isPremium() && hasVideo && userpicPhotoUnknown()) { + updateFull(); + } + session().changes().peerUpdated(this, UpdateFlag::Photo); + } +} + +void PeerData::setPhotoFull(const TLchatPhoto &photo) { + if (isSecretChat()) { + return; + } + const auto &data = photo.data(); + if (data.vsizes().v.isEmpty()) { + clearPhoto(); + } else if (_userpicPhotoId != data.vid().v) { + owner().processPhoto(photo); + _userpicPhotoId = data.vid().v; + session().changes().peerUpdated(this, UpdateFlag::Photo); + } +} + +void PeerData::clearPhoto() { + if (isSecretChat()) { + return; + } + clearUserpic(); +} void PeerData::clearUserpic() { + if (isSecretChat()) { + return; + } +#if 0 // mtp setUserpicChecked(PhotoId(), ImageLocation(), false); +#endif + if (hasUserpic()) { + setUserpic(PhotoId(), ImageLocation(), false); + session().changes().peerUpdated(this, UpdateFlag::Photo); + } } +#if 0 // mtp void PeerData::setUserpicChecked( PhotoId photoId, const ImageLocation &location, @@ -490,6 +647,7 @@ void PeerData::setUserpicChecked( } } } +#endif auto PeerData::unavailableReasons() const -> const std::vector & { @@ -524,6 +682,8 @@ bool PeerData::canPinMessages() const { ? !channel->amRestricted(ChatRestriction::PinMessages) : ((channel->amCreator() || channel->adminRights() & ChatAdminRight::EditMessages)); + } else if (isSecretChat()) { + return false; } Unexpected("Peer type in PeerData::canPinMessages."); } @@ -531,6 +691,8 @@ bool PeerData::canPinMessages() const { bool PeerData::canCreatePolls() const { if (const auto user = asUser()) { return user->isBot() && !user->isSupport(); + } else if (isSecretChat()) { + return false; } return Data::CanSend(this, ChatRestriction::SendPolls); } @@ -595,6 +757,7 @@ bool PeerData::setAbout(const QString &newAbout) { return true; } +#if 0 // mtp void PeerData::checkFolder(FolderId folderId) { const auto folder = folderId ? owner().folderLoaded(folderId) @@ -605,6 +768,7 @@ void PeerData::checkFolder(FolderId folderId) { } } } +#endif void PeerData::setTranslationDisabled(bool disabled) { const auto flag = disabled @@ -625,13 +789,20 @@ PeerData::TranslationFlag PeerData::translationFlag() const { void PeerData::saveTranslationDisabled(bool disabled) { setTranslationDisabled(disabled); + session().sender().request(TLtoggleChatIsTranslatable( + peerToTdbChat(id), + tl_bool(!disabled) + )).send(); +#if 0 // mtp using Flag = MTPmessages_TogglePeerTranslations::Flag; session().api().request(MTPmessages_TogglePeerTranslations( MTP_flags(disabled ? Flag::f_disabled : Flag()), input )).send(); +#endif } +#if 0 // mtp void PeerData::setSettings(const MTPPeerSettings &data) { data.match([&](const MTPDpeerSettings &data) { _requestChatTitle = data.vrequest_chat_title().value_or_empty(); @@ -654,7 +825,51 @@ void PeerData::setSettings(const MTPPeerSettings &data) { : Flag())); }); } +#endif + +void PeerData::setActionBar(const TLchatActionBar *bar) { + _requestChatTitle = QString(); + _requestChatDate = TimeId(); + const auto existing = settings().value_or(PeerSettings(0)) + & PeerSetting::NeedContactsException; + if (!bar) { + setSettings(existing); + return; + } + using Flag = PeerSetting; + bar->match([&](const TLDchatActionBarReportSpam &data) { + setSettings(existing + | Flag::ReportSpam + | (data.vcan_unarchive().v ? Flag::AutoArchived : Flag())); + }, [&](const TLDchatActionBarReportUnrelatedLocation &data) { + // later + // setSettings(existing | Flag::ReportGeo); + }, [&](const TLDchatActionBarInviteMembers &data) { + // later + // setSettings(existing | Flag::InviteMembers); + }, [&](const TLDchatActionBarReportAddBlock &data) { + setSettings(existing + | Flag::AddContact + | Flag::BlockContact + | Flag::ReportSpam + | (data.vcan_unarchive().v ? Flag::AutoArchived : Flag(0))); + + }, [&](const TLDchatActionBarAddContact &data) { + setSettings(existing | Flag::AddContact); + }, [&](const TLDchatActionBarSharePhoneNumber &data) { + setSettings(existing | Flag::ShareContact); + }, [&](const TLDchatActionBarJoinRequest &data) { + _requestChatTitle = data.vtitle().v; + _requestChatDate = data.vrequest_date().v; + setSettings(existing + | Flag::RequestChat + | (data.vis_channel().v + ? Flag::RequestChatIsBroadcast + : Flag(0))); + }); +} +#if 0 // mtp bool PeerData::changeColorIndex( const tl::conditional &cloudColorIndex) { return cloudColorIndex @@ -668,6 +883,7 @@ bool PeerData::changeBackgroundEmojiId( ? cloudBackgroundEmoji->v : DocumentId()); } +#endif void PeerData::fillNames() { _nameWords.clear(); @@ -721,6 +937,10 @@ void PeerData::fillNames() { PeerData::~PeerData() = default; void PeerData::updateFull() { + if (const auto user = secretChatUser()) { + user->updateFull(); + return; + } if (!_lastFullUpdate || crl::now() > _lastFullUpdate + kUpdateFullPeerTimeout) { updateFullForced(); @@ -730,7 +950,7 @@ void PeerData::updateFull() { void PeerData::updateFullForced() { session().api().requestFullPeer(this); if (const auto channel = asChannel()) { - if (!channel->amCreator() && !channel->inviter) { + if (!channel->amCreator() && !channel->inviterLoaded) { session().api().chatParticipants().requestSelf(channel); } } @@ -847,7 +1067,39 @@ not_null PeerData::migrateToOrMe() const { return this; } +bool PeerData::isSecretChat() const { + return peerIsSecretChat(id); +} + +SecretChatData *PeerData::asSecretChat() { + return isSecretChat() ? static_cast(this) : nullptr; +} + +const SecretChatData *PeerData::asSecretChat() const { + return isSecretChat() + ? static_cast(this) + : nullptr; +} + +UserData *PeerData::secretChatUser() const { + const auto secretChat = asSecretChat(); + return secretChat ? secretChat->user().get() : nullptr; +} + +UserData *PeerData::asOneOnOne() { + const auto user = secretChatUser(); + return user ? user : asUser(); +} + +const UserData *PeerData::asOneOnOne() const { + const auto user = secretChatUser(); + return user ? user : asUser(); +} + const QString &PeerData::topBarNameText() const { + if (const auto user = secretChatUser()) { + return user->name(); + } if (const auto to = migrateTo()) { return to->topBarNameText(); } else if (const auto user = asUser()) { @@ -859,10 +1111,16 @@ const QString &PeerData::topBarNameText() const { } int PeerData::nameVersion() const { + if (const auto user = secretChatUser()) { + return user->nameVersion(); + } return _nameVersion; } const QString &PeerData::name() const { + if (const auto user = secretChatUser()) { + return user->name(); + } if (const auto to = migrateTo()) { return to->name(); } @@ -870,6 +1128,9 @@ const QString &PeerData::name() const { } const QString &PeerData::shortName() const { + if (const auto user = asOneOnOne()) { + return user->firstName.isEmpty() ? user->lastName : user->firstName; + } if (const auto user = asUser()) { return user->firstName.isEmpty() ? user->lastName : user->firstName; } @@ -1095,6 +1356,10 @@ Data::RestrictionCheckResult PeerData::amRestricted( : (chat->defaultRestrictions() & right) ? Result::WithEveryone() : Result::Allowed(); + } else if (const auto secretUser = secretChatUser()) { + return (right == ChatRestriction::PinMessages) + ? Result::Explicit() + : secretUser->amRestricted(right); } return Result::Allowed(); } @@ -1105,7 +1370,13 @@ bool PeerData::amAnonymous() const { && (asChannel()->adminRights() & ChatAdminRight::Anonymous)); } +void PeerData::setCanRevokeFullHistory(bool can) { + _canRevokeFullHistory = can; +} + bool PeerData::canRevokeFullHistory() const { + return _canRevokeFullHistory; +#if 0 // mtp if (const auto user = asUser()) { return !isSelf() && (!user->isBot() || user->isSupport()) @@ -1119,6 +1390,7 @@ bool PeerData::canRevokeFullHistory() const { && megagroup->canDelete(); } return false; +#endif } bool PeerData::slowmodeApplied() const { diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 894045fdd51f9..ce74de9e5b9cd 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -18,6 +18,7 @@ class PeerData; class UserData; class ChatData; class ChannelData; +class SecretChatData; enum class ChatRestriction; @@ -30,6 +31,18 @@ class Account; class Session; } // namespace Main +namespace Tdb { +class TLbotMenuButton; +class TLprofilePhoto; +class TLchatPhotoInfo; +class TLchatPhoto; +class TLbotMenuButton; +class TLchatNotificationSettings; +class TLchatActionBar; +class TLfile; +class TLchatAvailableReactions; +} // namespace Tdb + namespace Data { class Forum; @@ -97,9 +110,14 @@ struct UnavailableReason { } }; +#if 0 // mtp bool ApplyBotMenuButton( not_null info, const MTPBotMenuButton *button); +#endif +bool ApplyBotMenuButton( + not_null info, + const Tdb::TLbotMenuButton *button); enum class AllowedReactionsType { All, @@ -115,6 +133,7 @@ struct AllowedReactions { bool operator<(const AllowedReactions &a, const AllowedReactions &b); bool operator==(const AllowedReactions &a, const AllowedReactions &b); +#if 0 // mtp [[nodiscard]] AllowedReactions Parse(const MTPChatReactions &value); [[nodiscard]] PeerData *PeerFromInputMTP( not_null owner, @@ -122,6 +141,10 @@ bool operator==(const AllowedReactions &a, const AllowedReactions &b); [[nodiscard]] UserData *UserFromInputMTP( not_null owner, const MTPInputUser &input); +#endif +[[nodiscard]] AllowedReactions Parse( + not_null peer, + const Tdb::TLchatAvailableReactions &value); } // namespace Data @@ -244,6 +267,16 @@ class PeerData { [[nodiscard]] ChannelData *asChannelOrMigrated(); [[nodiscard]] const ChannelData *asChannelOrMigrated() const; + [[nodiscard]] bool isSecretChat() const; + [[nodiscard]] SecretChatData *asSecretChat(); + [[nodiscard]] const SecretChatData *asSecretChat() const; + [[nodiscard]] UserData *secretChatUser() const; + [[nodiscard]] bool isOneOnOne() const { + return isUser() || isSecretChat(); + } + [[nodiscard]] UserData *asOneOnOne(); + [[nodiscard]] const UserData *asOneOnOne() const; + [[nodiscard]] ChatData *migrateFrom() const; [[nodiscard]] ChannelData *migrateTo() const; [[nodiscard]] not_null migrateToOrMe(); @@ -269,11 +302,16 @@ class PeerData { return _nameFirstLetters; } + void setPhotoFull(const Tdb::TLchatPhoto &photo); + + void clearPhoto(); void setUserpic( PhotoId photoId, const ImageLocation &location, bool hasVideo); +#if 0 // mtp void setUserpicPhoto(const MTPPhoto &data); +#endif void paintUserpic( Painter &p, Ui::PeerUserpicView &view, @@ -335,7 +373,10 @@ class PeerData { return _about; } +#if 0 // mtp void checkFolder(FolderId folderId); +#endif + void setCanRevokeFullHistory(bool can); void setSettings(PeerSettings which) { _settings.set(which); @@ -366,10 +407,13 @@ class PeerData { [[nodiscard]] TranslationFlag translationFlag() const; void saveTranslationDisabled(bool disabled); +#if 0 // mtp void setSettings(const MTPPeerSettings &data); bool changeColorIndex(const tl::conditional &cloudColorIndex); bool changeBackgroundEmojiId( const tl::conditional &cloudBackgroundEmoji); +#endif + void setActionBar(const Tdb::TLchatActionBar *bar); enum class BlockStatus : char { Unknown, @@ -427,17 +471,26 @@ class PeerData { void setStoriesState(StoriesState state); const PeerId id; + +#if 0 // mtp MTPinputPeer input = MTP_inputPeerEmpty(); +#endif protected: void updateNameDelayed( const QString &newName, const QString &newNameOrPhone, const QString &newUsername); +#if 0 // mtp void updateUserpic(PhotoId photoId, MTP::DcId dcId, bool hasVideo); +#endif void clearUserpic(); void invalidateEmptyUserpic(); + void updateUserpic(const Tdb::TLchatPhotoInfo &photo); + void updateUserpic(const Tdb::TLprofilePhoto &photo); + void updateUserpic(const Tdb::TLfile &small, bool hasVideo); + private: void fillNames(); [[nodiscard]] not_null ensureEmptyUserpic() const; @@ -481,6 +534,8 @@ class PeerData { uint8 _colorIndexCloud : 1 = 0; uint8 _userpicHasVideo : 1 = 0; + bool _canRevokeFullHistory : 1 = false; + QString _about; QString _themeEmoticon; std::unique_ptr _wallPaper; diff --git a/Telegram/SourceFiles/data/data_peer_bot_command.cpp b/Telegram/SourceFiles/data/data_peer_bot_command.cpp index 9aef25403f276..b8ef004378f8c 100644 --- a/Telegram/SourceFiles/data/data_peer_bot_command.cpp +++ b/Telegram/SourceFiles/data/data_peer_bot_command.cpp @@ -7,8 +7,11 @@ For license and copyright information please follow this link: */ #include "data/data_peer_bot_command.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { +#if 0 // mtp BotCommand BotCommandFromTL(const MTPBotCommand &result) { return result.match([](const MTPDbotCommand &data) { return BotCommand { @@ -17,5 +20,14 @@ BotCommand BotCommandFromTL(const MTPBotCommand &result) { }; }); } +#endif + +BotCommand BotCommandFromTL(const Tdb::TLbotCommand &result) { + const auto &data = result.data(); + return BotCommand{ + .command = data.vcommand().v, + .description = data.vdescription().v, + }; +} } // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer_bot_command.h b/Telegram/SourceFiles/data/data_peer_bot_command.h index 6b87c1fd42b53..444249adb16a0 100644 --- a/Telegram/SourceFiles/data/data_peer_bot_command.h +++ b/Telegram/SourceFiles/data/data_peer_bot_command.h @@ -7,6 +7,10 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLbotCommand; +} // namespace Tdb + namespace Data { struct BotCommand final { @@ -22,6 +26,9 @@ struct BotCommand final { } }; +#if 0 // mtp [[nodiscard]] BotCommand BotCommandFromTL(const MTPBotCommand &result); +#endif +[[nodiscard]] BotCommand BotCommandFromTL(const Tdb::TLbotCommand &result); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer_bot_commands.cpp b/Telegram/SourceFiles/data/data_peer_bot_commands.cpp index 8adc31a9e3105..1f0d72dc6bff7 100644 --- a/Telegram/SourceFiles/data/data_peer_bot_commands.cpp +++ b/Telegram/SourceFiles/data/data_peer_bot_commands.cpp @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #include "data/data_peer_bot_commands.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { ChatBotCommands::Changed ChatBotCommands::update( @@ -27,6 +29,7 @@ ChatBotCommands::Changed ChatBotCommands::update( return changed; } +#if 0 // mtp BotCommands BotCommandsFromTL(const MTPBotInfo &result) { return result.match([](const MTPDbotInfo &data) { const auto userId = data.vuser_id() @@ -44,5 +47,15 @@ BotCommands BotCommandsFromTL(const MTPBotInfo &result) { }; }); } +#endif +BotCommands BotCommandsFromTL(const Tdb::TLbotCommands &result) { + auto commands = ranges::views::all( + result.data().vcommands().v + ) | ranges::views::transform(BotCommandFromTL) | ranges::to_vector; + return BotCommands{ + .userId = UserId(result.data().vbot_user_id().v), + .commands = std::move(commands), + }; +} } // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer_bot_commands.h b/Telegram/SourceFiles/data/data_peer_bot_commands.h index e0730b4e43045..4119f80c5858d 100644 --- a/Telegram/SourceFiles/data/data_peer_bot_commands.h +++ b/Telegram/SourceFiles/data/data_peer_bot_commands.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "data/data_peer_bot_command.h" +namespace Tdb { +class TLbotCommands; +} // namespace Tdb + namespace Data { struct BotCommands final { @@ -27,6 +31,9 @@ struct ChatBotCommands final : public base::flat_map< Changed update(const std::vector &list); }; +#if 0 // mtp [[nodiscard]] BotCommands BotCommandsFromTL(const MTPBotInfo &result); +#endif +[[nodiscard]] BotCommands BotCommandsFromTL(const Tdb::TLbotCommands &result); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer_id.cpp b/Telegram/SourceFiles/data/data_peer_id.cpp index b6a77237b02ae..eba4d07607a6b 100644 --- a/Telegram/SourceFiles/data/data_peer_id.cpp +++ b/Telegram/SourceFiles/data/data_peer_id.cpp @@ -7,6 +7,79 @@ For license and copyright information please follow this link: */ #include "data/data_peer_id.h" +#include "tdb/tdb_tl_scheme.h" + +PeerId peerFromTdbChat(Tdb::TLint53 id) noexcept { // From TDLib. + constexpr int64 MAX_CHANNEL_ID = 1000000000000ll - (1ll << 31); + constexpr int64 MAX_CHAT_ID = 999999999999ll; + constexpr int64 MAX_USER_ID = 999999999999ll; + constexpr int64 ZERO_SECRET_CHAT_ID = -2000000000000ll; + constexpr int64 ZERO_CHANNEL_ID = -1000000000000ll; + if (!id.v) { + return PeerId(); + } else if (id.v > 0 && id.v <= MAX_USER_ID) { + return UserId(BareId(id.v)); + } else if (id.v < 0 && id.v >= -MAX_CHAT_ID) { + return ChatId(BareId(-id.v)); + } else if (id.v != ZERO_CHANNEL_ID + && id.v >= ZERO_CHANNEL_ID - MAX_CHANNEL_ID) { + return ChannelId(BareId(ZERO_CHANNEL_ID - id.v)); + } else if (id.v != ZERO_SECRET_CHAT_ID + && id.v >= ZERO_SECRET_CHAT_ID + std::numeric_limits::min()) { + return SecretChatId(BareId(id.v - ZERO_SECRET_CHAT_ID)); + } + return PeerId(); +} + +Tdb::TLint53 peerToTdbChat(PeerId id) noexcept { + constexpr int64 ZERO_SECRET_CHAT_ID = -2000000000000ll; + constexpr int64 ZERO_CHANNEL_ID = -1000000000000ll; + if (const auto userId = peerToUser(id)) { + return Tdb::tl_int53(int64(userId.bare)); + } else if (const auto chatId = peerToChat(id)) { + return Tdb::tl_int53(-int64(chatId.bare)); + } else if (const auto channelId = peerToChannel(id)) { + return Tdb::tl_int53(ZERO_CHANNEL_ID - int64(channelId.bare)); + } else if (const auto secretChatId = peerToSecretChat(id)) { + const auto rawId = ToTdbSecretChatId(secretChatId).v; + return Tdb::tl_int53(ZERO_SECRET_CHAT_ID + rawId); + } + return Tdb::tl_int53(0); +} + +Tdb::TLint32 ToTdbSecretChatId(SecretChatId id) noexcept { + Expects(id.bare > 0); + Expects(id.bare < std::numeric_limits::max() + || ((id.bare & kSecretChatIdFlag) + && ((id.bare & ~kSecretChatIdFlag) < kSecretChatIdFlag) + && (-int64(id.bare & ~kSecretChatIdFlag) + >= std::numeric_limits::min()))); + + return Tdb::tl_int32((id.bare & kSecretChatIdFlag) + ? int32(-int64(id.bare & ~kSecretChatIdFlag)) + : int32(id.bare)); +} + +Tdb::TLint32 ToTdbSecretChatId(PeerId id) noexcept { + return ToTdbSecretChatId(peerToSecretChat(id)); +} + +PeerId peerFromSender(const Tdb::TLmessageSender &sender) noexcept { + return sender.match([&](const Tdb::TLDmessageSenderUser &data) { + return peerFromUser(data.vuser_id()); + }, [&](const Tdb::TLDmessageSenderChat &data) { + return peerFromTdbChat(data.vchat_id()); + }); +} + +Tdb::TLmessageSender peerToSender(PeerId id) noexcept { + if (const auto userId = peerToUser(id)) { + return Tdb::tl_messageSenderUser(peerToTdbChat(id)); + } else { + return Tdb::tl_messageSenderChat(peerToTdbChat(id)); + } +} + PeerId peerFromMTP(const MTPPeer &peer) { return peer.match([](const MTPDpeerUser &data) { return peerFromUser(data.vuser_id()); diff --git a/Telegram/SourceFiles/data/data_peer_id.h b/Telegram/SourceFiles/data/data_peer_id.h index 1f99d955f11f3..60161056972dc 100644 --- a/Telegram/SourceFiles/data/data_peer_id.h +++ b/Telegram/SourceFiles/data/data_peer_id.h @@ -7,12 +7,35 @@ For license and copyright information please follow this link: */ #pragma once +#include "base/assertion.h" + using BareId = uint64; +namespace tl { +class int_type; +class int64_type; +} // namespace tl + +namespace Tdb { +using TLint32 = tl::int_type; +using TLint53 = tl::int64_type; +class TLmessageSender; +} // namespace Tdb + struct PeerIdZeroHelper { }; using PeerIdZero = void(PeerIdZeroHelper::*)(); +// Secret chats have negative ids. So we store them as +// positive values with a special flag bit set to 1. +inline constexpr uint8 kUserIdShift = 0; +inline constexpr uint8 kChatIdShift = 1; +inline constexpr uint8 kChannelIdShift = 2; +inline constexpr uint8 kSecretChatIdShift = 3; +inline constexpr uint8 kFakeChatIdShift = 0x7F; + +inline constexpr auto kSecretChatIdFlag = BareId(1) << 47; + template struct ChatIdType { BareId bare = 0; @@ -25,9 +48,23 @@ struct ChatIdType { //constexpr ChatIdType(PeerIdZero) noexcept { // UserId id = 0; //} constexpr ChatIdType(BareId value) noexcept : bare(value) { + if constexpr (Shift == kSecretChatIdShift) { + const auto svalue = int64(value); + if (svalue < 0) { + if (svalue >= std::numeric_limits::min()) { + bare = BareId(-svalue) | kSecretChatIdFlag; + } else { + Unexpected("Large negative value in ChatIdType."); + } + } + } else if (value >= (BareId(1) << 48)) { + Unexpected("Large value in ChatIdType."); + } } constexpr ChatIdType(MTPlong value) noexcept : bare(value.v) { } + constexpr ChatIdType(tl::int64_type value) noexcept : bare(value.v) { + } friend inline constexpr auto operator<=>( ChatIdType, @@ -94,10 +131,10 @@ bool operator>=(ChatIdType, PeerIdZero) = delete; template bool operator>=(PeerIdZero, ChatIdType) = delete; -using UserId = ChatIdType<0>; -using ChatId = ChatIdType<1>; -using ChannelId = ChatIdType<2>; -using FakeChatId = ChatIdType<0x7F>; +using UserId = ChatIdType; +using ChatId = ChatIdType; +using ChannelId = ChatIdType; +using FakeChatId = ChatIdType; struct PeerIdHelper { BareId value = 0; @@ -130,7 +167,18 @@ struct PeerId { template [[nodiscard]] constexpr SomeChatIdType to() const noexcept { - return is() ? (value & kChatTypeMask) : 0; + if (!is()) { + return 0; + } else if constexpr (SomeChatIdType::kShift == kSecretChatIdShift) { + const auto result = (value & kChatTypeMask); + if (result & kSecretChatIdFlag) { + return uint64(-int64(result & ~kSecretChatIdFlag)); + } else { + return result; + } + } else { + return (value & kChatTypeMask); + } } [[nodiscard]] constexpr explicit operator bool() const noexcept { @@ -213,6 +261,11 @@ bool operator>=(PeerIdZero, PeerId) = delete; return peerFromChannel(channelId.v); } +[[nodiscard]] PeerId peerFromTdbChat(Tdb::TLint53 id) noexcept; +[[nodiscard]] PeerId peerFromSender( + const Tdb::TLmessageSender &sender) noexcept; +[[nodiscard]] Tdb::TLmessageSender peerToSender(PeerId id) noexcept; + [[nodiscard]] inline constexpr UserId peerToUser(PeerId id) noexcept { return id.to(); } @@ -225,6 +278,23 @@ bool operator>=(PeerIdZero, PeerId) = delete; return id.to(); } +[[nodiscard]] Tdb::TLint53 peerToTdbChat(PeerId id) noexcept; + +using SecretChatId = ChatIdType; +[[nodiscard]] inline constexpr bool peerIsSecretChat(PeerId id) noexcept { + return id.is(); +} +[[nodiscard]] inline constexpr PeerId peerFromSecretChat( + SecretChatId secretChatId) noexcept { + return secretChatId; +} +[[nodiscard]] inline constexpr SecretChatId peerToSecretChat( + PeerId id) noexcept { + return id.to(); +} +[[nodiscard]] Tdb::TLint32 ToTdbSecretChatId(SecretChatId id) noexcept; +[[nodiscard]] Tdb::TLint32 ToTdbSecretChatId(PeerId id) noexcept; + [[nodiscard]] inline MTPlong peerToBareMTPInt(PeerId id) { return MTP_long(id.value & PeerId::kChatTypeMask); } diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index 1670afa8f43e5..83f32f1117cc0 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -21,6 +21,8 @@ For license and copyright information please follow this link: #include "ui/image/image_prepare.h" #include "base/unixtime.h" +#include "data/data_secret_chat.h" + namespace Data { namespace { @@ -284,6 +286,13 @@ inline auto DefaultRestrictionValue( || (!(flags & Flag::Broadcast) && (rights & ~restricted))); }); + } else if (const auto secretChat = peer->asSecretChat()) { + return rpl::combine( + secretChat->flagsValue(), + CanSendAnyOfValue(secretChat->user(), rights, forbidInForums), + [](SecretChatData::Flags::Change flags, bool can) { + return (flags.value & SecretChatDataFlag::Ready) && can; + }); } Unexpected("Peer type in Data::CanSendAnyOfValue."); } diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index 963162fcfcf95..f1b5cdcbe3cc4 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -12,6 +12,7 @@ For license and copyright information please follow this link: #include "data/data_reply_preview.h" #include "data/data_photo_media.h" #include "ui/image/image.h" +#include "ui/image/image_location_factory.h" #include "main/main_session.h" #include "history/history.h" #include "history/history_item.h" @@ -21,10 +22,14 @@ For license and copyright information please follow this link: #include "storage/file_download.h" #include "core/application.h" +#include "tdb/tdb_tl_scheme.h" +#include "media/streaming/media_streaming_loader_tdb.h" + namespace { constexpr auto kPhotoSideLimit = 2560; +using namespace Tdb; using Data::PhotoMedia; using Data::PhotoSize; using Data::PhotoSizeIndex; @@ -39,6 +44,59 @@ using Data::kPhotoSizeCount; : image; } +[[nodiscard]] int VideoStartTime( + const tl::conditional &photo) { + return int( + std::clamp( + std::floor(photo + ? photo->data().vmain_frame_timestamp().v * 1000 + : 0.), + 0., + double(std::numeric_limits::max()))); +} + +struct Sizes { + const TLphotoSize *small = nullptr; + const TLphotoSize *thumbnail = nullptr; + ImageWithLocation thumbnailProgressive; + const TLphotoSize *large = nullptr; +}; +[[nodiscard]] Sizes LookupSizes(const TLvector &data) { + const auto &sizes = data.v; + const auto i = ranges::find_if(sizes, [](const TLphotoSize &data) { + return !data.data().vprogressive_sizes().v.isEmpty(); + }); + const auto progressive = (i != sizes.end()); + const auto find = [&](const QByteArray &levels) { + const auto kInvalidIndex = int(levels.size()); + const auto level = [&](const TLphotoSize &size) { + const auto type = size.data().vtype().v; + const auto letter = type.isEmpty() + ? 0 + : type[0].unicode(); + const auto index = levels.indexOf(char(letter)); + return (index >= 0) ? index : kInvalidIndex; + }; + const auto result = ranges::max_element( + sizes, + ranges::greater(), + level); + return (level(*result) == kInvalidIndex) ? sizes.end() : result; + }; + const auto image = [&](const QByteArray &levels) { + const auto i = find(levels); + return (i != sizes.end()) ? &*i : nullptr; + }; + return { + .small = (progressive ? nullptr : image("sa"_q)), + .thumbnail = (progressive ? nullptr : image("mbsa"_q)), + .thumbnailProgressive = (progressive + ? Images::FromProgressiveSize(*i, 1) + : ImageWithLocation()), + .large = (progressive ? &*i : image("ydxncwmbsai"_q)), + }; +} + } // namespace PhotoData::PhotoData(not_null owner, PhotoId id) @@ -53,6 +111,130 @@ PhotoData::~PhotoData() { base::take(_videoSizes); } +PhotoId PhotoData::IdFromTdb(const TLphoto &data) { + const auto &sizes = data.data().vsizes().v; + return sizes.isEmpty() + ? 0 + : sizes.front().data().vphoto().data().vid().v; +} + +PhotoId PhotoData::IdFromTdb(const TLchatPhoto &data) { + return data.data().vid().v; +} + +PhotoId PhotoData::IdFromTdb(const TLchatPhotoInfo &data) { + return data.data().vbig().data().vid().v; +} + +void PhotoData::setFromTdb(const TLphoto &data) { + const auto &fields = data.data(); + setHasAttachedStickers(fields.vhas_stickers().v); + auto sizes = LookupSizes(fields.vsizes()); + if (!sizes.large) { + return; + } + updateImages( + (fields.vminithumbnail() + ? fields.vminithumbnail()->data().vdata().v + : QByteArray()), + Images::FromPhotoSize(sizes.small), + (sizes.thumbnail + ? Images::FromPhotoSize(sizes.thumbnail) + : sizes.thumbnailProgressive), + Images::FromPhotoSize(sizes.large), + ImageWithLocation(), + ImageWithLocation(), + crl::time()); + applyTdbFile(sizes.large->data().vphoto()); +} + +void PhotoData::setFromTdb(const TLchatPhoto &data) { + const auto &fields = data.data(); + + date = fields.vadded_date().v; + + const auto sizes = LookupSizes(fields.vsizes()); + if (!sizes.large) { + return; + } + updateImages( + (fields.vminithumbnail() + ? fields.vminithumbnail()->data().vdata().v + : QByteArray()), + Images::FromPhotoSize(sizes.small), + (sizes.thumbnail + ? Images::FromPhotoSize(sizes.thumbnail) + : sizes.thumbnailProgressive), + Images::FromPhotoSize(sizes.large), + (fields.vsmall_animation() + ? Images::FromAnimationSize(*fields.vsmall_animation()) + : ImageWithLocation()), + (fields.vanimation() + ? Images::FromAnimationSize(*fields.vanimation()) + : ImageWithLocation()), + VideoStartTime(fields.vanimation())); + applyTdbFile(sizes.large->data().vphoto()); +} + +void PhotoData::setFromTdb(const TLchatPhotoInfo &data) { + constexpr auto kSmallSize = 160; + constexpr auto kBigSize = 640; + const auto &fields = data.data(); + updateImages( + (fields.vminithumbnail() + ? fields.vminithumbnail()->data().vdata().v + : QByteArray()), + Images::FromTdbFile(fields.vsmall(), kSmallSize, kSmallSize), + ImageWithLocation(), + Images::FromTdbFile(fields.vbig(), kBigSize, kBigSize), + ImageWithLocation(), + ImageWithLocation(), + crl::time()); + applyTdbFile(fields.vbig()); +} + +void PhotoData::applyTdbFile(const Tdb::TLfile &file) { + const auto size = file.data().vsize().v; + const auto &remote = file.data().vremote().data(); + if (!remote.vis_uploading_completed().v) { + if (!uploadingData) { + uploadingData = std::make_unique(size); + } + uploadingData->offset = remote.vuploaded_size().v; + _owner->requestPhotoViewRepaint(this); + } else if (uploadingData) { + uploadingData = nullptr; + _owner->requestPhotoViewRepaint(this); + } +} + +void PhotoData::setFromLocal(const Data::PhotoLocalData &data) { + Expects(id == data.id); + + const auto image = [&](char level) { + const auto proj = [&](const auto &pair) { + return pair.first; + }; + const auto i = ranges::find(data.thumbs, level, proj); + return (i == data.thumbs.end()) + ? ImageWithLocation() + : Images::FromImageInMemory( + i->second.image, + "JPG", + i->second.bytes); + }; + + date = data.added; + updateImages({}, {}, image('m'), image('y'), {}, {}, {}); +} + +uint64 PhotoData::persistentId() const { + constexpr auto large = PhotoSize::Large; + const auto tdb = std::get_if( + &location(large).file().data); + return tdb ? tdb->hash : id; +} + Data::Session &PhotoData::owner() const { return *_owner; } @@ -230,6 +412,7 @@ bool PhotoData::replyPreviewLoaded(bool spoiler) const { return _replyPreview->loaded(spoiler); } +#if 0 // mtp void PhotoData::setRemoteLocation( int32 dc, uint64 access, @@ -277,6 +460,7 @@ void PhotoData::collectLocalData(not_null local) { _owner->keepAlive(std::move(media)); } } +#endif bool PhotoData::isNull() const { return !_images[PhotoSizeIndex(PhotoSize::Large)].location.valid(); @@ -374,6 +558,11 @@ void PhotoData::updateImages( const ImageWithLocation &videoSmall, const ImageWithLocation &videoLarge, crl::time videoStartTime) { + _owner->photoFileIdUpdated( + this, + _images[PhotoSizeIndex(PhotoSize::Large)].location.tdbFileId(), + large.location.tdbFileId()); + if (!inlineThumbnailBytes.isEmpty() && _inlineThumbnailBytes.isEmpty()) { _inlineThumbnailBytes = inlineThumbnailBytes; @@ -545,6 +734,19 @@ auto PhotoData::createStreamingLoader( return Media::Streaming::MakeBytesLoader(bytes); } } + const auto tdb = std::get_if( + &videoLocation(large).file().data); + if (tdb) { + return std::make_unique( + &session().tdb(), + tdb->fileId, + TdbFileLocation::BigFileBaseCacheKey( + tdb->hash, + kTdbLocationTypePhoto), + videoByteSize(large)); + } + return nullptr; +#if 0 // mtp return v::is(videoLocation(large).file().data) ? std::make_unique( &session().downloader(), @@ -552,4 +754,5 @@ auto PhotoData::createStreamingLoader( videoByteSize(large), origin) : nullptr; +#endif } diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h index 33d990723e002..b3ccd94eed533 100644 --- a/Telegram/SourceFiles/data/data_photo.h +++ b/Telegram/SourceFiles/data/data_photo.h @@ -10,6 +10,13 @@ For license and copyright information please follow this link: #include "data/data_types.h" #include "data/data_cloud_file.h" +namespace Tdb { +class TLfile; +class TLphoto; +class TLchatPhoto; +class TLchatPhotoInfo; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -40,6 +47,12 @@ enum class PhotoSize : uchar { return static_cast(size); } +struct PhotoLocalData { + PhotoId id = 0; + TimeId added = 0; + PreparedPhotoThumbs thumbs; +}; + } // namespace Data class PhotoData final { @@ -47,6 +60,16 @@ class PhotoData final { PhotoData(not_null owner, PhotoId id); ~PhotoData(); + [[nodiscard]] static PhotoId IdFromTdb(const Tdb::TLphoto &data); + [[nodiscard]] static PhotoId IdFromTdb(const Tdb::TLchatPhoto &data); + [[nodiscard]] static PhotoId IdFromTdb(const Tdb::TLchatPhotoInfo &data); + void setFromTdb(const Tdb::TLphoto &data); + void setFromTdb(const Tdb::TLchatPhoto &data); + void setFromTdb(const Tdb::TLchatPhotoInfo &data); + void applyTdbFile(const Tdb::TLfile &file); + void setFromLocal(const Data::PhotoLocalData &data); + [[nodiscard]] uint64 persistentId() const; + [[nodiscard]] Data::Session &owner() const; [[nodiscard]] Main::Session &session() const; [[nodiscard]] bool isNull() const; @@ -71,6 +94,7 @@ class PhotoData final { [[nodiscard]] Image *getReplyPreview(not_null item); [[nodiscard]] bool replyPreviewLoaded(bool spoiler) const; +#if 0 // mtp void setRemoteLocation( int32 dc, uint64 access, @@ -84,6 +108,7 @@ class PhotoData final { // and it has downloaded full image, we can collect image from it // to (this) received from the server "same" photo. void collectLocalData(not_null local); +#endif [[nodiscard]] std::shared_ptr createMediaView(); [[nodiscard]] auto activeMediaView() const @@ -174,8 +199,11 @@ class PhotoData final { std::array _images; std::unique_ptr _videoSizes; +#if 0 // mtp int32 _dc = 0; uint64 _access = 0; +#endif + FileId _fileId = 0; bool _hasStickers = false; QByteArray _fileReference; std::unique_ptr _replyPreview; diff --git a/Telegram/SourceFiles/data/data_photo_media.cpp b/Telegram/SourceFiles/data/data_photo_media.cpp index be713b4a93f8f..0eee3f996cb8e 100644 --- a/Telegram/SourceFiles/data/data_photo_media.cpp +++ b/Telegram/SourceFiles/data/data_photo_media.cpp @@ -40,7 +40,7 @@ Image *PhotoMedia::thumbnailInline() const { if (!_inlineThumbnail) { const auto bytes = _owner->inlineThumbnailBytes(); if (!bytes.isEmpty()) { - auto image = Images::FromInlineBytes(bytes); + auto image = Images::Read({ .content = bytes }).image; if (image.isNull()) { _owner->clearInlineThumbnailBytes(); } else { @@ -189,6 +189,7 @@ void PhotoMedia::automaticLoad( true); } +#if 0 // mtp void PhotoMedia::collectLocalData(not_null local) { if (const auto image = local->_inlineThumbnail.get()) { _inlineThumbnail = std::make_unique(image->original()); @@ -202,6 +203,7 @@ void PhotoMedia::collectLocalData(not_null local) { } } } +#endif bool PhotoMedia::saveToFile(const QString &path) { constexpr auto large = PhotoSize::Large; diff --git a/Telegram/SourceFiles/data/data_poll.cpp b/Telegram/SourceFiles/data/data_poll.cpp index f781f95bc04eb..f910bc0bec4ec 100644 --- a/Telegram/SourceFiles/data/data_poll.cpp +++ b/Telegram/SourceFiles/data/data_poll.cpp @@ -13,12 +13,15 @@ For license and copyright information please follow this link: #include "main/main_session.h" #include "api/api_text_entities.h" #include "ui/text/text_options.h" +#include "tdb/tdb_tl_scheme.h" namespace { constexpr auto kShortPollTimeout = 30 * crl::time(1000); constexpr auto kReloadAfterAutoCloseDelay = crl::time(1000); +using namespace Tdb; + const PollAnswer *AnswerByOption( const std::vector &list, const QByteArray &option) { @@ -37,6 +40,14 @@ PollAnswer *AnswerByOption( option)); } +std::optional CorrectAnswerIndexFromTL(const TLpoll &poll) { + return poll.data().vtype().match([&](const TLDpollTypeQuiz &data) { + return std::make_optional(data.vcorrect_option_id().v); + }, [](auto &&) -> std::optional { + return std::nullopt; + }); +} + } // namespace PollData::PollData(not_null owner, PollId id) @@ -44,6 +55,10 @@ PollData::PollData(not_null owner, PollId id) , _owner(owner) { } +PollId PollData::IdFromTdb(const TLpoll &data) { + return data.data().vid().v; +} + Data::Session &PollData::owner() const { return *_owner; } @@ -117,6 +132,134 @@ bool PollData::applyChanges(const MTPDpoll &poll) { return true; } +bool PollData::applyChanges(const TLpoll &poll) { + Expects(poll.data().vid().v == id); + + const auto &fields = poll.data(); + const auto newQuestion = fields.vquestion().v; + const auto typeFlag = fields.vtype().match([&](const TLDpollTypeQuiz &) { + return Flag::Quiz; + }, [&](const TLDpollTypeRegular &data) { + return data.vallow_multiple_answers().v + ? Flag::MultiChoice + : Flag(0); + }); + const auto newFlags = typeFlag + | (fields.vis_closed().v ? Flag::Closed : Flag(0)) + | (fields.vis_anonymous().v ? Flag(0) : Flag::PublicVotes); + const auto newCloseDate = fields.vclose_date().v; + const auto newClosePeriod = fields.vopen_period().v; + auto newAnswers = ranges::views::all( + fields.voptions().v + ) | ranges::views::transform([](const TLpollOption &data) { + auto result = PollAnswer(); + result.option = data.data().vtext().v.toUtf8(); + result.text = data.data().vtext().v; + result.votes = data.data().vvoter_count().v; + result.chosen = data.data().vis_chosen().v + || data.data().vis_being_chosen().v; + return result; + }) | ranges::views::take( + kMaxOptions + ) | ranges::to_vector; + + const auto changed1 = (question != newQuestion) + || (closeDate != newCloseDate) + || (closePeriod != newClosePeriod) + || (_flags != newFlags); + const auto changed2 = (answers != newAnswers); + const auto changed3 = applyResults(poll); + if (!changed1 && !changed2 && !changed3) { + return false; + } + if (changed1) { + question = newQuestion; + closeDate = newCloseDate; + closePeriod = newClosePeriod; + _flags = newFlags; + } + if (changed2) { + std::swap(answers, newAnswers); + } + ++version; + return true; +} + +bool PollData::applyResults(const TLpoll &poll) { + return poll.match([&](const TLDpoll &results) { + _lastResultsUpdate = crl::now(); + + const auto newTotalVoters = results.vtotal_voter_count().v; + auto changed = (newTotalVoters != totalVoters); + + if (const auto correctOption = CorrectAnswerIndexFromTL(poll)) { + const auto set = [&](int id, bool v) { + if (id < answers.size()) { + answers[id].correct = v; + } + }; + const auto wasCorrectIt = ranges::find_if( + answers, + &PollAnswer::correct); + if (wasCorrectIt == end(answers)) { + changed = true; + } else { + const auto wasCorrectIndex = std::distance( + begin(answers), + wasCorrectIt); + if (wasCorrectIndex != (*correctOption)) { + set(wasCorrectIndex, false); + changed = true; + } + } + set(*correctOption, true); + } + + for (const auto &result : results.voptions().v) { + if (applyResultToAnswers(result.data(), false)) { + changed = true; + } + } + { + const auto &recent = results.vrecent_voter_ids(); + const auto recentChanged = !ranges::equal( + recentVoters, + recent.v, + ranges::equal_to(), + &PeerData::id, + &peerFromSender); + if (recentChanged) { + changed = true; + recentVoters = ranges::views::all( + recent.v + ) | ranges::views::transform([&](const TLmessageSender &d) { + const auto peer = _owner->peer(peerFromSender(d)); + return peer->isMinimalLoaded() ? peer.get() : nullptr; + }) | ranges::views::filter([](PeerData *peer) { + return peer != nullptr; + }) | ranges::views::transform([](PeerData *peer) { + return not_null(peer); + }) | ranges::to_vector; + } + } + results.vtype().match([&](const TLDpollTypeQuiz &data) { + auto newSolution = Api::FormattedTextFromTdb(data.vexplanation()); + if (solution != newSolution) { + solution = std::move(newSolution); + changed = true; + } + }, [](auto &&) { + }); + if (!changed) { + return false; + } + totalVoters = newTotalVoters; + ++version; + return changed; + }); +} + +#if 0 // mtp bool PollData::applyResults(const MTPPollResults &results) { return results.match([&](const MTPDpollResults &results) { _lastResultsUpdate = crl::now(); @@ -172,6 +315,7 @@ bool PollData::applyResults(const MTPPollResults &results) { return changed; }); } +#endif bool PollData::checkResultsReload(crl::time now) { if (_lastResultsUpdate > 0 @@ -192,6 +336,16 @@ const PollAnswer *PollData::answerByOption(const QByteArray &option) const { return AnswerByOption(answers, option); } +int PollData::indexByOption(const QByteArray &option) const { + const auto it = ranges::find(answers, option, &PollAnswer::option); + + return std::clamp( + int(std::distance(begin(answers), it)), + 0, + int(answers.size() - 1)); +} + +#if 0 // goodToRemove bool PollData::applyResultToAnswers( const MTPPollAnswerVoters &result, bool isMinResults) { @@ -218,6 +372,28 @@ bool PollData::applyResultToAnswers( return changed; }); } +#endif + +bool PollData::applyResultToAnswers( + const TLDpollOption &result, + bool isMinResults) { + const auto option = result.vtext().v.toUtf8(); + const auto answer = answerByOption(option); + if (!answer) { + return false; + } + auto changed = (answer->votes != result.vvoter_count().v); + if (changed) { + answer->votes = result.vvoter_count().v; + } + if (!isMinResults) { + if (answer->chosen != result.vis_chosen().v) { + answer->chosen = result.vis_chosen().v; + changed = true; + } + } + return changed; +} void PollData::setFlags(Flags flags) { if (_flags != flags) { @@ -250,6 +426,7 @@ bool PollData::quiz() const { return (_flags & Flag::Quiz); } +#if 0 // mtp MTPPoll PollDataToMTP(not_null poll, bool close) { const auto convert = [](const PollAnswer &answer) { return MTP_pollAnswer( @@ -313,3 +490,4 @@ MTPInputMedia PollDataToInputMedia( MTP_string(solution.text), sentEntities); } +#endif diff --git a/Telegram/SourceFiles/data/data_poll.h b/Telegram/SourceFiles/data/data_poll.h index 4f428479b536a..2a307ae9f1686 100644 --- a/Telegram/SourceFiles/data/data_poll.h +++ b/Telegram/SourceFiles/data/data_poll.h @@ -7,6 +7,11 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLpoll; +class TLDpollOption; +} // namespace Tdb + namespace Data { class Session; } // namespace Data @@ -35,6 +40,8 @@ inline bool operator!=(const PollAnswer &a, const PollAnswer &b) { struct PollData { PollData(not_null owner, PollId id); + [[nodiscard]] static PollId IdFromTdb(const Tdb::TLpoll &data); + [[nodiscard]] Data::Session &owner() const; [[nodiscard]] Main::Session &session() const; @@ -52,9 +59,13 @@ struct PollData { bool applyResults(const MTPPollResults &results); [[nodiscard]] bool checkResultsReload(crl::time now); + bool applyChanges(const Tdb::TLpoll &data); + bool applyResults(const Tdb::TLpoll &data); + [[nodiscard]] PollAnswer *answerByOption(const QByteArray &option); [[nodiscard]] const PollAnswer *answerByOption( const QByteArray &option) const; + [[nodiscard]] int indexByOption(const QByteArray &option) const; void setFlags(Flags flags); [[nodiscard]] Flags flags() const; @@ -81,6 +92,9 @@ struct PollData { bool applyResultToAnswers( const MTPPollAnswerVoters &result, bool isMinResults); + bool applyResultToAnswers( + const Tdb::TLDpollOption &result, + bool isMinResults); const not_null _owner; Flags _flags = Flags(); @@ -88,9 +102,11 @@ struct PollData { }; +#if 0 // mtp [[nodiscard]] MTPPoll PollDataToMTP( not_null poll, bool close = false); [[nodiscard]] MTPInputMedia PollDataToInputMedia( not_null poll, bool close = false); +#endif diff --git a/Telegram/SourceFiles/data/data_pts_waiter.cpp b/Telegram/SourceFiles/data/data_pts_waiter.cpp index 316ef0a41fd5c..477d3489c8ced 100644 --- a/Telegram/SourceFiles/data/data_pts_waiter.cpp +++ b/Telegram/SourceFiles/data/data_pts_waiter.cpp @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #include "data/data_pts_waiter.h" +#if 0 // mtp + #include "api/api_updates.h" PtsWaiter::PtsWaiter(not_null owner) : _owner(owner) { @@ -185,3 +187,5 @@ bool PtsWaiter::check(ChannelData *channel, int32 pts, int32 count) { } return !count; } + +#endif diff --git a/Telegram/SourceFiles/data/data_pts_waiter.h b/Telegram/SourceFiles/data/data_pts_waiter.h index e35309aa7b6af..31a9d5cf9541e 100644 --- a/Telegram/SourceFiles/data/data_pts_waiter.h +++ b/Telegram/SourceFiles/data/data_pts_waiter.h @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #pragma once +#if 0 // mtp + namespace Api { class Updates; } // namespace Api @@ -102,3 +104,5 @@ class PtsWaiter { uint32 _skippedKey = 0; }; + +#endif diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index 733a0ae05173d..c4db00284ac29 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -23,9 +23,14 @@ For license and copyright information please follow this link: #include "lang/lang_keys.h" #include "apiwrap.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kMessagesPerPage = 50; constexpr auto kReadRequestTimeout = 3 * crl::time(1000); constexpr auto kMaxMessagesToDeleteMyTopic = 10; @@ -81,8 +86,12 @@ RepliesList::RepliesList( } RepliesList::~RepliesList() { +#if 0 // mtp histories().cancelRequest(base::take(_beforeId)); histories().cancelRequest(base::take(_afterId)); +#endif + _history->session().sender().request(base::take(_beforeId)).cancel(); + _history->session().sender().request(base::take(_afterId)).cancel(); if (_readRequestTimer.isActive()) { sendReadTillRequest(); } @@ -519,6 +528,45 @@ void RepliesList::loadAround(MsgId id) { if (_loadingAround && *_loadingAround == id) { return; } + + const auto sender = &_history->session().sender(); + sender->request(base::take(_beforeId)).cancel(); + sender->request(base::take(_afterId)).cancel(); + + _loadingAround = id; + _beforeId = sender->request(TLgetMessageThreadHistory( + peerToTdbChat(_history->peer->id), + tl_int53(_rootId.bare), + tl_int53(id.bare), + tl_int32(id ? (-kMessagesPerPage / 2) : 0), // offset + tl_int32(kMessagesPerPage) // limit + )).done([=](const TLmessages &result) { + _beforeId = 0; + _loadingAround = std::nullopt; + + if (!id) { + _skippedAfter = 0; + } else { + _skippedAfter = std::nullopt; + } + _skippedBefore = std::nullopt; + _list.clear(); + if (processMessagesIsEmpty(result)) { + _fullCount = _skippedBefore = _skippedAfter = 0; + } else if (id) { + Assert(!_list.empty()); + if (_list.front() <= id) { + _skippedAfter = 0; + } else if (_list.back() >= id) { + _skippedBefore = 0; + } + } + checkReadTillEnd(); + }).fail([=] { + _beforeId = 0; + _loadingAround = std::nullopt; + }).send(); +#if 0 // mtp histories().cancelRequest(base::take(_beforeId)); histories().cancelRequest(base::take(_afterId)); @@ -567,11 +615,43 @@ void RepliesList::loadAround(MsgId id) { _history, Histories::RequestType::History, send); +#endif } void RepliesList::loadBefore() { Expects(!_list.empty()); + const auto sender = &_history->session().sender(); + if (_loadingAround) { + sender->request(base::take(_beforeId)).cancel(); + } else if (_beforeId) { + return; + } + + const auto last = _list.back(); + _beforeId = sender->request(TLgetMessageThreadHistory( + peerToTdbChat(_history->peer->id), + tl_int53(_rootId.bare), + tl_int53(last.bare), + tl_int32(0), // offset + tl_int32(kMessagesPerPage) + )).done([=](const TLmessages &result) { + _beforeId = 0; + + if (_list.empty()) { + return; + } else if (_list.back() != last) { + loadBefore(); + } else if (processMessagesIsEmpty(result)) { + _skippedBefore = 0; + if (_skippedAfter == 0) { + _fullCount = _list.size(); + } + } + }).fail([=] { + _beforeId = 0; + }).send(); +#if 0 // mtp if (_loadingAround) { histories().cancelRequest(base::take(_beforeId)); } else if (_beforeId) { @@ -613,6 +693,7 @@ void RepliesList::loadBefore() { _history, Histories::RequestType::History, send); +#endif } void RepliesList::loadAfter() { @@ -621,8 +702,32 @@ void RepliesList::loadAfter() { if (_afterId) { return; } - const auto first = _list.front(); + const auto sender = &_history->session().sender(); + _afterId = sender->request(TLgetMessageThreadHistory( + peerToTdbChat(_history->peer->id), + tl_int53(_rootId.bare), + tl_int53(first.bare + 1), + tl_int32(-kMessagesPerPage), // offset + tl_int32(kMessagesPerPage) + )).done([=](const TLmessages &result) { + _afterId = 0; + + if (_list.empty()) { + return; + } else if (_list.front() != first) { + loadAfter(); + } else if (processMessagesIsEmpty(result)) { + _skippedAfter = 0; + if (_skippedBefore == 0) { + _fullCount = _list.size(); + } + checkReadTillEnd(); + } + }).fail([=] { + _afterId = 0; + }).send(); +#if 0 // mtp const auto send = [=](Fn finish) { return _history->session().api().request(MTPmessages_GetReplies( _history->peer->input, @@ -658,8 +763,10 @@ void RepliesList::loadAfter() { _history, Histories::RequestType::History, send); +#endif } +#if 0 // mtp bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) { const auto guard = gsl::finally([&] { _listChanges.fire({}); }); @@ -724,6 +831,44 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) { ++skipped; } } +#endif +bool RepliesList::processMessagesIsEmpty(const TLmessages &result) { + const auto guard = gsl::finally([&] { _listChanges.fire({}); }); + + auto &owner = _history->owner(); + const auto list = ranges::views::all( + result.data().vmessages().v + ) | ranges::views::filter([](const std::optional &v) { + return v.has_value(); + }) | ranges::to_vector; + const auto fullCount = result.data().vtotal_count().v; + + if (list.empty()) { + return true; + } + + const auto maxId = list.front()->data().vid().v; + const auto wasSize = int(_list.size()); + const auto toFront = (wasSize > 0) && (maxId > _list.front()); + const auto localFlags = MessageFlags(); + const auto type = NewMessageType::Existing; + auto refreshed = std::vector(); + if (toFront) { + refreshed.reserve(_list.size() + list.size()); + } + auto skipped = int(result.data().vmessages().v.size() - list.size()); + for (const auto &message : list) { + const auto item = owner.processMessage(*message, type); + if (item->inThread(_rootId)) { + if (toFront && item->id > _list.front()) { + refreshed.push_back(item->id); + } else if (_list.empty() || item->id < _list.back()) { + _list.push_back(item->id); + } + } else { + ++skipped; + } + } if (toFront) { refreshed.insert(refreshed.end(), _list.begin(), _list.end()); _list = std::move(refreshed); @@ -766,8 +911,11 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) { } } +#if 0 // mtp Ensures(list.size() >= skipped); return (list.size() == skipped); +#endif + return false; } void RepliesList::setInboxReadTill( @@ -926,6 +1074,7 @@ void RepliesList::requestUnreadCount() { } } }; +#if 0 // tdlib getMessageThread doesn't provide unread info for topics. _reloadUnreadCountRequestId = session->api().request( MTPmessages_GetDiscussionMessage( _history->peer->input, @@ -942,6 +1091,7 @@ void RepliesList::requestUnreadCount() { data.vunread_count().v); }); }).send(); +#endif } void RepliesList::readTill(not_null item) { @@ -989,6 +1139,19 @@ void RepliesList::sendReadTillRequest() { _readRequestTimer.cancel(); } const auto api = &_history->session().api(); + const auto sender = &api->sender(); + sender->request(base::take(_readRequestId)).cancel(); + + _readRequestId = sender->request(TLviewMessages( + peerToTdbChat(_history->peer->id), + tl_vector(1, tl_int53(computeInboxReadTillFull().bare)), + tl_messageSourceChatHistory(), + tl_bool(true) + )).done(crl::guard(this, [=] { + _readRequestId = 0; + reloadUnreadCountIfNeeded(); + })).send(); +#if 0 // mtp api->request(base::take(_readRequestId)).cancel(); _readRequestId = api->request(MTPmessages_ReadDiscussion( @@ -999,6 +1162,7 @@ void RepliesList::sendReadTillRequest() { _readRequestId = 0; reloadUnreadCountIfNeeded(); })).send(); +#endif } void RepliesList::reloadUnreadCountIfNeeded() { diff --git a/Telegram/SourceFiles/data/data_replies_list.h b/Telegram/SourceFiles/data/data_replies_list.h index a33dee56b5264..a14ef25fb96ab 100644 --- a/Telegram/SourceFiles/data/data_replies_list.h +++ b/Telegram/SourceFiles/data/data_replies_list.h @@ -10,6 +10,10 @@ For license and copyright information please follow this link: #include "base/weak_ptr.h" #include "base/timer.h" +namespace Tdb { +class TLmessages; +} // namespace Tdb + class History; namespace Data { @@ -95,7 +99,10 @@ class RepliesList final : public base::has_weak_ptr { void injectRootDivider( not_null root, not_null slice); +#if 0 // mtp bool processMessagesIsEmpty(const MTPmessages_Messages &result); +#endif + bool processMessagesIsEmpty(const Tdb::TLmessages &result); void loadAround(MsgId id); void loadBefore(); void loadAfter(); diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index 7f5821b7c7649..4f44a8923b2d5 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -18,21 +18,31 @@ For license and copyright information please follow this link: #include "history/history_item_helpers.h" #include "apiwrap.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kRequestTimeLimit = 60 * crl::time(1000); [[nodiscard]] MsgId RemoteToLocalMsgId(MsgId id) { Expects(IsServerMsgId(id)); +#if 0 // mtp return ServerMaxMsgId + id + 1; +#endif + return id; } [[nodiscard]] MsgId LocalToRemoteMsgId(MsgId id) { +#if 0 // mtp Expects(IsScheduledMsgId(id)); return (id - ServerMaxMsgId - 1); +#endif + return id; } [[nodiscard]] bool TooEarlyForRequest(crl::time received) { @@ -44,6 +54,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); && (item->date() > base::unixtime::now()); } +#if 0 // mtp [[nodiscard]] MTPMessage PrepareMessage(const MTPMessage &message) { return message.match([&](const MTPDmessageEmpty &data) { return MTP_messageEmpty( @@ -89,13 +100,16 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); MTP_int(data.vttl_period().value_or_empty())); }); } +#endif } // namespace +#if 0 // mtp bool IsScheduledMsgId(MsgId id) { return (id > ServerMaxMsgId) && (id < ServerMaxMsgId + ScheduledMsgIdsRange); } +#endif ScheduledMessages::ScheduledMessages(not_null owner) : _session(&owner->session()) @@ -110,7 +124,10 @@ ScheduledMessages::ScheduledMessages(not_null owner) ScheduledMessages::~ScheduledMessages() { for (const auto &request : _requests) { +#if 0 // mtp _session->api().request(request.second.requestId).cancel(); +#endif + _session->sender().request(request.second.requestId).cancel(); } } @@ -171,6 +188,7 @@ int ScheduledMessages::count(not_null history) const { return (i != end(_data)) ? i->second.items.size() : 0; } +#if 0 // mtp void ScheduledMessages::sendNowSimpleMessage( const MTPDupdateShortSentMessage &update, not_null local) { @@ -341,7 +359,19 @@ void ScheduledMessages::apply( list.itemById.emplace(id, local); } } +#endif +void ScheduledMessages::append(not_null item) { + Expects(item->isScheduled()); + + const auto history = item->history(); + auto &list = _data[history]; + list.items.emplace_back(item); + sort(list); + _updates.fire_copy(history); +} + +#if 0 // mtp void ScheduledMessages::appendSending(not_null item) { Expects(item->isSending()); Expects(item->isScheduled()); @@ -359,6 +389,7 @@ void ScheduledMessages::removeSending(not_null item) { item->destroy(); } +#endif rpl::producer<> ScheduledMessages::updates(not_null history) { request(history); @@ -393,6 +424,9 @@ Data::MessagesSlice ScheduledMessages::list(not_null history) { void ScheduledMessages::request(not_null history) { const auto peer = history->peer; + if (peer->isSecretChat()) { + return; + } if (peer->isBroadcast() && !Data::CanSendAnything(peer)) { return; } @@ -400,6 +434,15 @@ void ScheduledMessages::request(not_null history) { if (request.requestId || TooEarlyForRequest(request.lastReceived)) { return; } + const auto i = _data.find(history); + request.requestId = _session->sender().request( + TLgetChatScheduledMessages(peerToTdbChat(peer->id)) + ).done([=](const TLmessages &result) { + parse(history, result); + }).fail([=] { + _requests.remove(history); + }).send(); +#if 0 // mtp const auto i = _data.find(history); const auto hash = (i != end(_data)) ? countListHash(i->second) @@ -411,8 +454,48 @@ void ScheduledMessages::request(not_null history) { }).fail([=] { _requests.remove(history); }).send(); +#endif +} + +void ScheduledMessages::parse( + not_null history, + const TLmessages &result) { + auto &request = _requests[history]; + request.lastReceived = crl::now(); + request.requestId = 0; + if (!_clearTimer.isActive()) { + _clearTimer.callOnce(kRequestTimeLimit * 2); + } + + const auto &data = result.data(); + const auto count = data.vtotal_count().v; + const auto &messages = data.vmessages().v; + + if (messages.isEmpty()) { + clearNotSending(history); + return; + } + auto received = base::flat_set>(); + auto clear = base::flat_set>(); + auto &list = _data.emplace(history, List()).first->second; + for (const auto &message : messages) { + if (message) { + received.emplace( + _session->data().processMessage( + *message, + NewMessageType::Existing)); + } + } + for (const auto &owned : list.items) { + const auto item = owned.get(); + if (!item->isSending() && !received.contains(item)) { + clear.emplace(item); + } + } + updated(history, received, clear); } +#if 0 // mtp void ScheduledMessages::parse( not_null history, const MTPmessages_Messages &list) { @@ -501,6 +584,7 @@ HistoryItem *ScheduledMessages::append( list.itemById.emplace(id, item); return item; } +#endif void ScheduledMessages::clearNotSending(not_null history) { const auto i = _data.find(history); @@ -544,9 +628,11 @@ void ScheduledMessages::remove(not_null item) { Assert(i != end(_data)); auto &list = i->second; +#if 0 // mtp if (!item->isSending() && !item->hasFailed()) { list.itemById.remove(lookupId(item)); } +#endif const auto k = ranges::find(list.items, item, &OwnedItem::get); Assert(k != list.items.end()); k->release(); diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.h b/Telegram/SourceFiles/data/data_scheduled_messages.h index e4abea70a5239..129dd0956a9d9 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.h +++ b/Telegram/SourceFiles/data/data_scheduled_messages.h @@ -10,6 +10,10 @@ For license and copyright information please follow this link: #include "history/history_item.h" #include "base/timer.h" +namespace Tdb { +class TLmessages; +} // namespace Tdb + class History; namespace Main { @@ -21,7 +25,9 @@ namespace Data { class Session; struct MessagesSlice; +#if 0 // mtp [[nodiscard]] bool IsScheduledMsgId(MsgId id); +#endif class ScheduledMessages final { public: @@ -36,19 +42,25 @@ class ScheduledMessages final { [[nodiscard]] int count(not_null history) const; [[nodiscard]] MsgId localMessageId(MsgId remoteId) const; +#if 0 // mtp void checkEntitiesAndUpdate(const MTPDmessage &data); void apply(const MTPDupdateNewScheduledMessage &update); void apply(const MTPDupdateDeleteScheduledMessages &update); void apply( const MTPDupdateMessageID &update, not_null local); +#endif + + void append(not_null item); +#if 0 // mtp void appendSending(not_null item); void removeSending(not_null item); void sendNowSimpleMessage( const MTPDupdateShortSentMessage &update, not_null local); +#endif [[nodiscard]] rpl::producer<> updates(not_null history); [[nodiscard]] Data::MessagesSlice list(not_null history); @@ -65,6 +77,9 @@ class ScheduledMessages final { }; void request(not_null history); + + void parse(not_null history, const Tdb::TLmessages &result); +#if 0 // mtp void parse( not_null history, const MTPmessages_Messages &list); @@ -72,6 +87,8 @@ class ScheduledMessages final { not_null history, List &list, const MTPMessage &message); +#endif + void clearNotSending(not_null history); void updated( not_null history, diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 08fae4983a480..746db71d77831 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -16,11 +16,17 @@ For license and copyright information please follow this link: #include "history/history_item.h" #include "apiwrap.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { constexpr auto kSharedMediaLimit = 100; +constexpr auto kFirstSharedMediaLimit = 10; +#if 0 // goodToRemove constexpr auto kFirstSharedMediaLimit = 0; +#endif constexpr auto kDefaultSearchTimeoutMs = crl::time(200); } // namespace @@ -32,6 +38,7 @@ std::optional PrepareSearchRequest( const QString &query, MsgId messageId, Data::LoadDirection direction) { +#if 0 // goodToRemove const auto filter = [&] { using Type = Storage::SharedMediaType; switch (type) { @@ -68,7 +75,51 @@ std::optional PrepareSearchRequest( const auto minId = 0; const auto maxId = 0; +#endif + const auto filter = [&] { + using Type = Storage::SharedMediaType; + switch (type) { + case Type::Photo: + return Tdb::tl_searchMessagesFilterPhoto(); + case Type::Video: + return Tdb::tl_searchMessagesFilterVideo(); + case Type::PhotoVideo: + return Tdb::tl_searchMessagesFilterPhotoAndVideo(); + case Type::MusicFile: + return Tdb::tl_searchMessagesFilterAudio(); + case Type::File: + return Tdb::tl_searchMessagesFilterDocument(); + case Type::VoiceFile: + return Tdb::tl_searchMessagesFilterVoiceNote(); + case Type::RoundVoiceFile: + return Tdb::tl_searchMessagesFilterVoiceAndVideoNote(); + case Type::RoundFile: + return Tdb::tl_searchMessagesFilterVideoNote(); + case Type::GIF: + return Tdb::tl_searchMessagesFilterAnimation(); + case Type::Link: + return Tdb::tl_searchMessagesFilterUrl(); + case Type::ChatPhoto: + return Tdb::tl_searchMessagesFilterChatPhoto(); + case Type::Pinned: + return Tdb::tl_searchMessagesFilterPinned(); + } + return Tdb::tl_searchMessagesFilterEmpty(); + }(); + if constexpr (Tdb::TLDsearchMessagesFilterEmpty::Is()) { + if (query.isEmpty()) { + return std::nullopt; + } + } +#if 0 // mtp const auto limit = messageId ? kSharedMediaLimit : kFirstSharedMediaLimit; +#endif + const auto limit = !messageId + ? kFirstSharedMediaLimit + : (direction == Data::LoadDirection::After) + ? (kSharedMediaLimit - 1) + : kSharedMediaLimit; +#if 0 // mtp const auto offsetId = [&] { switch (direction) { case Data::LoadDirection::Before: @@ -77,6 +128,10 @@ std::optional PrepareSearchRequest( } Unexpected("Direction in PrepareSearchRequest"); }(); +#endif + const auto offsetId = (messageId < 0 || messageId == ServerMaxMsgId - 1) + ? MsgId() + : messageId; const auto addOffset = [&] { switch (direction) { case Data::LoadDirection::Before: return 0; @@ -85,6 +140,18 @@ std::optional PrepareSearchRequest( } Unexpected("Direction in PrepareSearchRequest"); }(); + return Tdb::TLsearchChatMessages( + peerToTdbChat(peer->id), + Tdb::tl_string(query), + std::nullopt, // From. + Tdb::tl_int53(std::max(offsetId, MsgId(0)).bare), + Tdb::tl_int32(addOffset), + Tdb::tl_int32((direction == Data::LoadDirection::After) + ? (limit + 1) // Must be more than -addOffset. + : limit), + filter, + Tdb::tl_int53(topicRootId.bare)); +#if 0 // goodToRemove const auto hash = uint64(0); const auto mtpOffsetId = int(std::clamp( @@ -107,6 +174,7 @@ std::optional PrepareSearchRequest( MTP_int(maxId), MTP_int(minId), MTP_long(hash)); +#endif } SearchResult ParseSearchResult( @@ -118,6 +186,9 @@ SearchResult ParseSearchResult( auto result = SearchResult(); result.noSkipRange = MsgRange{ messageId, messageId }; + result.fullCount = data.data().vtotal_count().v; + +#if 0 // goodToRemove auto messages = [&] { switch (data.type()) { case mtpc_messages_messages: { @@ -161,25 +232,33 @@ SearchResult ParseSearchResult( }(); if (!messages) { +#endif + if (data.data().vmessages().v.empty()) { return result; } const auto addType = NewMessageType::Existing; + result.messageIds.reserve(data.data().vmessages().v.size()); +#if 0 // goodToRemove result.messageIds.reserve(messages->size()); for (const auto &message : *messages) { const auto item = peer->owner().addNewMessage( message, MessageFlags(), addType); - if (item) { - const auto itemId = item->id; - if ((type == Storage::SharedMediaType::kCount) - || item->sharedMediaTypes().test(type)) { - result.messageIds.push_back(itemId); - } - accumulate_min(result.noSkipRange.from, itemId); - accumulate_max(result.noSkipRange.till, itemId); +#endif + if (!messageId && !data.data().vmessages().v.empty()) { + result.noSkipRange.from = ServerMaxMsgId; + } + for (const auto &message : data.data().vmessages().v) { + const auto item = peer->owner().processMessage(message, addType); + const auto itemId = item->id; + if ((type == Storage::SharedMediaType::kCount) + || item->sharedMediaTypes().test(type)) { + result.messageIds.push_back(itemId); } + accumulate_min(result.noSkipRange.from, itemId); + accumulate_max(result.noSkipRange.till, itemId); } if (messageId && result.messageIds.empty()) { result.noSkipRange = [&]() -> MsgRange { @@ -193,6 +272,14 @@ SearchResult ParseSearchResult( } Unexpected("Direction in ParseSearchResult"); }(); + } else if (messageId + && direction == Data::LoadDirection::After + && result.messageIds.front() == messageId) { + result.noSkipRange.till = ServerMaxMsgId; + } else if (messageId + && direction == Data::LoadDirection::Before + && result.messageIds.back() == messageId) { + result.noSkipRange.from = 0; } return result; } @@ -386,12 +473,37 @@ void SearchController::requestMore( if (!prepared) { return; } + + const auto requestId = _session->sender().request( + base::duplicate(*prepared) + ).done([=](const SearchRequestResult &result) { + listData->requests.remove(key); + auto parsed = ParseSearchResult( + listData->peer, + query.type, + key.aroundId, + key.direction, + result); + listData->list.addSlice( + std::move(parsed.messageIds), + parsed.noSkipRange, + parsed.fullCount); + }).send(); + listData->requests.emplace(key, [=] { + _session->sender().request(requestId).cancel(); + }); +#if 0 // mtp auto &histories = _session->data().histories(); const auto type = ::Data::Histories::RequestType::History; const auto history = _session->data().history(listData->peer); auto requestId = histories.sendRequest(history, type, [=](Fn finish) { return _session->api().request( std::move(*prepared) +#endif + { + const auto finish = [] {}; + const auto requestId = _session->sender().request( + base::duplicate(*prepared) ).done([=](const SearchRequestResult &result) { listData->requests.remove(key); auto parsed = ParseSearchResult( @@ -408,10 +520,16 @@ void SearchController::requestMore( }).fail([=] { finish(); }).send(); + listData->requests.emplace(key, [=] { + _session->sender().request(requestId).cancel(); + }); + } +#if 0 // mtp }); listData->requests.emplace(key, [=] { _session->data().histories().cancelRequest(requestId); }); +#endif } DelayedSearchController::DelayedSearchController( diff --git a/Telegram/SourceFiles/data/data_search_controller.h b/Telegram/SourceFiles/data/data_search_controller.h index a938df6ef65fe..6235982f9ba77 100644 --- a/Telegram/SourceFiles/data/data_search_controller.h +++ b/Telegram/SourceFiles/data/data_search_controller.h @@ -17,6 +17,11 @@ namespace Main { class Session; } // namespace Main +namespace Tdb { +class TLfoundChatMessages; +class TLsearchChatMessages; +} // namespace Tdb + namespace Data { enum class LoadDirection : char; } // namespace Data @@ -29,8 +34,12 @@ struct SearchResult { int fullCount = 0; }; +#if 0 // goodToRemove using SearchRequest = MTPmessages_Search; using SearchRequestResult = MTPmessages_Messages; +#endif +using SearchRequest = Tdb::TLsearchChatMessages; +using SearchRequestResult = Tdb::TLfoundChatMessages; std::optional PrepareSearchRequest( not_null peer, diff --git a/Telegram/SourceFiles/data/data_secret_chat.cpp b/Telegram/SourceFiles/data/data_secret_chat.cpp new file mode 100644 index 0000000000000..f0a7ad082d86d --- /dev/null +++ b/Telegram/SourceFiles/data/data_secret_chat.cpp @@ -0,0 +1,69 @@ +/* +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_secret_chat.h" + +#include "data/data_session.h" +#include "data/data_user.h" +#include "tdb/tdb_tl_scheme.h" + +namespace { + +using namespace Tdb; + +} // namespace + +SecretChatData::SecretChatData(not_null owner, PeerId id) +: PeerData(owner, id) { +} + +bool SecretChatData::valid() const { + return _user != nullptr; +} + +not_null SecretChatData::user() const { + Expects(valid()); + + return _user; +} + +void SecretChatData::update(const TLDsecretChat &data) { + const auto outbound = data.vis_outbound().v; + if (!_user) { + _user = owner().user(data.vuser_id()); + } else { + Assert(_user->id == peerFromUser(data.vuser_id())); + Assert(out() == outbound); + } + + const auto state = data.vstate().match([]( + const TLDsecretChatStatePending &) { + return SecretChatDataFlag::Pending; + }, [](const TLDsecretChatStateReady &) { + return SecretChatDataFlag::Ready; + }, [](const TLDsecretChatStateClosed &) { + return SecretChatDataFlag::Closed; + }); + _keyHash = data.vkey_hash().v; + _layer = data.vlayer().v; + const auto mask = Flag::Out + | Flag::Pending + | Flag::Ready + | Flag::Closed; + setFlags((flags() & ~mask) | (outbound ? Flag::Out : Flag()) | state); +} + +SecretChatState SecretChatData::state() const { + if (flags() & Flag::Pending) { + return SecretChatState::Pending; + } else if (flags() & Flag::Ready) { + return SecretChatState::Ready; + } else if (flags() & Flag::Closed) { + return SecretChatState::Closed; + } + Unexpected("Flags in SecretChatData::state."); +} diff --git a/Telegram/SourceFiles/data/data_secret_chat.h b/Telegram/SourceFiles/data/data_secret_chat.h new file mode 100644 index 0000000000000..fa6d95abab592 --- /dev/null +++ b/Telegram/SourceFiles/data/data_secret_chat.h @@ -0,0 +1,81 @@ +/* +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 +*/ +#pragma once + +#include "base/flags.h" +#include "data/data_flags.h" +#include "data/data_peer.h" + +namespace Tdb { +class TLDsecretChat; +} // namespace Tdb + +enum class SecretChatDataFlag { + Out = (1 << 0), + Pending = (1 << 2), + Ready = (1 << 3), + Closed = (1 << 4), +}; +inline constexpr bool is_flag_type(SecretChatDataFlag) { return true; }; +using SecretChatDataFlags = base::flags; + +enum class SecretChatState : uchar { + Pending, + Ready, + Closed, +}; + +class SecretChatData final : public PeerData { +public: + using Flag = SecretChatDataFlag; + using Flags = Data::Flags; + + SecretChatData(not_null owner, PeerId id); + + [[nodiscard]] bool valid() const; + [[nodiscard]] not_null user() const; + + void setFlags(SecretChatDataFlags which) { + _flags.set(which); + } + void addFlags(SecretChatDataFlags which) { + _flags.add(which); + } + void removeFlags(SecretChatDataFlags which) { + _flags.remove(which); + } + [[nodiscard]] auto flags() const { + return _flags.current(); + } + [[nodiscard]] auto flagsValue() const { + return _flags.value(); + } + + [[nodiscard]] bool out() const { + return _flags.current() & Flag::Out; + } + [[nodiscard]] int layer() const { + return _layer; + } + [[nodiscard]] QByteArray keyHash() const { + return _keyHash.current(); + } + [[nodiscard]] rpl::producer keyHashValue() const { + return _keyHash.value(); + } + [[nodiscard]] SecretChatState state() const; + + void update(const Tdb::TLDsecretChat &data); + +private: + UserData *_user = nullptr; + rpl::variable _keyHash; + Flags _flags; + int _layer = 0; + +}; \ No newline at end of file diff --git a/Telegram/SourceFiles/data/data_send_action.cpp b/Telegram/SourceFiles/data/data_send_action.cpp index ea8f92bac7ea4..37cd1d4b28576 100644 --- a/Telegram/SourceFiles/data/data_send_action.cpp +++ b/Telegram/SourceFiles/data/data_send_action.cpp @@ -48,7 +48,10 @@ void SendActionManager::registerFor( not_null history, MsgId rootId, not_null user, + const Tdb::TLchatAction &action, +#if 0 // mtp const MTPSendMessageAction &action, +#endif TimeId when) { if (history->peer->isSelf()) { return; diff --git a/Telegram/SourceFiles/data/data_send_action.h b/Telegram/SourceFiles/data/data_send_action.h index 227ef65699518..59be2fa05fea6 100644 --- a/Telegram/SourceFiles/data/data_send_action.h +++ b/Telegram/SourceFiles/data/data_send_action.h @@ -11,6 +11,10 @@ For license and copyright information please follow this link: class History; +namespace Tdb { +class TLchatAction; +} // namespace Tdb + namespace HistoryView { class SendActionPainter; } // namespace HistoryView @@ -34,7 +38,10 @@ class SendActionManager final { not_null history, MsgId rootId, not_null user, + const Tdb::TLchatAction &action, +#if 0 // mtp const MTPSendMessageAction &action, +#endif TimeId when); [[nodiscard]] auto animationUpdated() const diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index bc124d71fbcf9..0d7ca71bec706 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -22,7 +22,7 @@ For license and copyright information please follow this link: #include "core/crash_reports.h" // CrashReports::SetAnnotation #include "ui/image/image.h" #include "ui/image/image_location_factory.h" // Images::FromPhotoSize -#include "ui/text/format_values.h" // Ui::FormatPhone +#include "tdb/tdb_format_phone.h" // Tdb::FormatPhone #include "export/export_manager.h" #include "export/view/export_view_panel_controller.h" #include "mtproto/mtproto_config.h" @@ -83,9 +83,15 @@ For license and copyright information please follow this link: #include "spellcheck/spellcheck_highlight_syntax.h" #include "styles/style_boxes.h" // st::backgroundSize +#include "tdb/tdb_tl_scheme.h" +#include "history/history_unread_things.h" +#include "main/session/send_as_peers.h" +#include "data/data_secret_chat.h" + namespace Data { namespace { +using namespace Tdb; using ViewElement = HistoryView::Element; // s: box 100x100 @@ -130,6 +136,20 @@ void CheckForSwitchInlineButton(not_null item) { } } +[[nodiscard]] ChatAdminRights AdminRightsFromChatMemberStatus( + const TLDchatMemberStatusAdministrator &data) { + return AdminRightsFromChatAdministratorRights(data.vrights()); +} + +[[nodiscard]] std::vector ExtractUnavailableReasons( + const TLstring &restriction) { + if (restriction.v.isEmpty()) { + return {}; + } + return { { u"tdlib"_q, restriction.v } }; +} + +#if 0 // mtp // We should get a full restriction in "{full}: {reason}" format and we // need to find an "-all" tag in {full}, otherwise ignore this restriction. std::vector ExtractUnavailableReasons( @@ -234,6 +254,7 @@ std::vector ExtractUnavailableReasons( 0., double(std::numeric_limits::max()))); } +#endif } // namespace @@ -251,8 +272,10 @@ Session::Session(not_null session) maxPinnedChatsLimitValue(nullptr)) , _contactsList(Dialogs::SortMode::Name) , _contactsNoChatsList(Dialogs::SortMode::Name) +#if 0 // mtp , _ttlCheckTimer([=] { checkTTLs(); }) , _selfDestructTimer([=] { checkSelfDestructItems(); }) +#endif , _pollsClosingTimer([=] { checkPollsClosings(); }) , _watchForOfflineTimer([=] { checkLocalUsersWentOffline(); }) , _groups(this) @@ -433,11 +456,15 @@ not_null Session::peer(PeerId id) { return std::make_unique(this, id); } else if (peerIsChannel(id)) { return std::make_unique(this, id); + } else if (peerIsSecretChat(id)) { + return std::make_unique(this, id); } Unexpected("Peer id type."); }(); +#if 0 // mtp result->input = MTPinputPeer(MTP_inputPeerEmpty()); +#endif return _peers.emplace(id, std::move(result)).first->second.get(); } @@ -484,6 +511,7 @@ ChannelData *Session::channelLoaded(ChannelId id) const { return nullptr; } +#if 0 // mtp not_null Session::processUser(const MTPUser &data) { const auto result = user(data.match([](const auto &data) { return data.vid().v; @@ -744,6 +772,7 @@ not_null Session::processUser(const MTPUser &data) { if (flags) { session().changes().peerUpdated(result, flags); } + return result; } @@ -760,7 +789,6 @@ not_null Session::processChat(const MTPChat &data) { return peer(peerFromChannel(data.vid().v)); }); auto minimal = false; - using UpdateFlag = Data::PeerUpdate::Flag; auto flags = UpdateFlag::None | UpdateFlag::None; data.match([&](const MTPDchat &data) { @@ -1058,7 +1086,487 @@ not_null Session::processChat(const MTPChat &data) { } return result; } +#endif + +not_null Session::processUser(const TLuser &user) { + const auto &data = user.data(); + const auto result = this->user(data.vid().v); + auto minimal = false; + + using UpdateFlag = PeerUpdate::Flag; + auto updateFlags = UpdateFlag::None | UpdateFlag::None; + + const auto canShareThisContact = result->canShareThisContactFast(); + + const auto firstName = data.vtype().match([](const TLDuserTypeDeleted &) { + return tr::lng_deleted(tr::now); + }, [&](const auto &) { + return TextUtilities::SingleLine(data.vfirst_name().v); + }); + const auto lastName = TextUtilities::SingleLine(data.vlast_name().v); + const auto phone = data.vphone_number().v; + const auto nameChanged = (result->firstName != firstName) + || (result->lastName != lastName); + + const auto phoneChanged = (result->phone() != data.vphone_number().v); + if (phoneChanged) { + result->setPhone(data.vphone_number().v); + updateFlags |= UpdateFlag::PhoneNumber; + } + const auto isSelf = (UserId(data.vid().v) == session().userId()); + const auto showPhone = !result->isServiceUser() + && !data.vis_support().v + && !isSelf + && !data.vis_contact().v + && !data.vis_mutual_contact().v; + const auto showPhoneChanged = !result->isServiceUser() + && !isSelf + && ((showPhone && result->isContact()) + || (!showPhone + && !result->isContact() + && !result->phone().isEmpty())); + const auto phoneName = (showPhoneChanged || phoneChanged || nameChanged) + ? ((showPhone && !phone.isEmpty()) + ? FormatPhone(phone) + : QString()) + : result->nameOrPhone; + const auto newUsernames = Api::Usernames::FromTL(data.vusernames()); + const auto userName = (newUsernames.empty() || !newUsernames[0].active) + ? QString() + : newUsernames[0].username; + result->setName(firstName, lastName, phoneName, userName); + result->setUsernames(newUsernames); + + if (const auto photo = data.vprofile_photo()) { + result->setPhoto(*photo); + } else { + result->clearPhoto(); + } + result->setUnavailableReasons( + ExtractUnavailableReasons(data.vrestriction_reason())); + + data.vtype().match([&](const TLDuserTypeRegular &) { + result->setFlags(result->flags() & ~UserDataFlag::Deleted); + result->setBotInfoVersion(-1); + }, [&](const TLDuserTypeDeleted &) { + result->setFlags(result->flags() | UserDataFlag::Deleted); + result->setBotInfoVersion(-1); + }, [&](const TLDuserTypeBot &data) { + auto flags = result->flags() & ~UserDataFlag::Deleted; + if (data.vneed_location().v) { + flags |= UserDataFlag::BotInlineGeo; + } else { + flags &= ~UserDataFlag::BotInlineGeo; + } + result->setFlags(flags); + result->setBotInfoVersion(1); + result->botInfo->cantJoinGroups = !data.vcan_join_groups().v; + result->botInfo->readsAllHistory + = data.vcan_read_all_group_messages().v; + result->botInfo->inlinePlaceholder = data.vis_inline().v + ? ('_' + data.vinline_query_placeholder().v) + : QString(); + result->botInfo->supportsAttachMenu + = data.vcan_be_added_to_attachment_menu().v; + result->botInfo->canEditInformation = data.vcan_be_edited().v; + }, [&](const TLDuserTypeUnknown) { + result->setFlags(result->flags() | UserDataFlag::Deleted); + result->setBotInfoVersion(-1); + }); + result->setIsContact(data.vis_contact().v); + if (canShareThisContact != result->canShareThisContactFast()) { + updateFlags |= UpdateFlag::CanShareContact; + } + + using Flag = UserDataFlag; + const auto setting = Flag::Premium + | Flag::Verified; + const auto flags = (result->flags() & ~setting) + | (data.vis_premium().v ? Flag::Premium : Flag()) + | (data.vis_verified().v ? Flag::Verified : Flag()); + result->setFlags(flags); + result->setStoriesState(data.vhas_unread_active_stories().v + ? UserData::StoriesState::HasUnread + : data.vhas_active_stories().v + ? UserData::StoriesState::HasRead + : UserData::StoriesState::None); + + if (minimal) { + if (!result->isMinimalLoaded()) { + result->setLoadedStatus(PeerData::LoadedStatus::Minimal); + } + } else if (!result->isFullLoaded() + && (!result->isSelf() || !result->phone().isEmpty())) { + result->setLoadedStatus(PeerData::LoadedStatus::Full); + } + + const auto oldOnlineTill = result->onlineTill; + const auto newOnlineTill = ApiWrap::OnlineTillFromStatus( + data.vstatus(), + oldOnlineTill); + if (oldOnlineTill != newOnlineTill) { + result->onlineTill = newOnlineTill; + updateFlags |= UpdateFlag::OnlineStatus; + } + if (const auto &status = data.vemoji_status()) { + const auto &data = status->data(); + result->setEmojiStatus( + data.vcustom_emoji_id().v, + data.vexpiration_date().v); + } else { + result->setEmojiStatus(0); + } + auto decorationsUpdated = false; + if (result->changeColorIndex(data.vaccent_color_id().v)) { + updateFlags |= UpdateFlag::Color; + decorationsUpdated = true; + } + if (result->changeBackgroundEmojiId( + data.vbackground_custom_emoji_id().v)) { + updateFlags |= UpdateFlag::BackgroundEmoji; + decorationsUpdated = true; + } + if (decorationsUpdated && result->isMinimalLoaded()) { + _peerDecorationsUpdated.fire_copy(result); + } + + if (updateFlags) { + session().changes().peerUpdated(result, updateFlags); + } + return result; +} + +not_null Session::processPeer(const TLchat &dialog) { + const auto &data = dialog.data(); + + const auto result = peer(peerFromTdbChat(data.vid())); + const auto checkId = data.vtype().match([](const TLDchatTypePrivate &d) { + return peerFromUser(d.vuser_id().v); + }, [](const TLDchatTypeBasicGroup &d) { + return peerFromChat(d.vbasic_group_id().v); + }, [](const TLDchatTypeSupergroup &d) { + return peerFromChannel(d.vsupergroup_id().v); + }, [](const TLDchatTypeSecret &d) { + return peerFromSecretChat(d.vsecret_chat_id().v); + }); + Assert(result->id == checkId); + + using UpdateFlag = Data::PeerUpdate::Flag; + auto updates = UpdateFlag::None | UpdateFlag::None; + + const auto history = this->history(result); + history->peer->setCanRevokeFullHistory( + data.vcan_be_deleted_for_all_users().v); + for (const auto &position : data.vpositions().v) { + history->applyPosition(position.data()); + } + if (const auto message = data.vlast_message()) { + history->applyLastMessage(*message); + } + history->applyUnreadInfo( + data.vunread_count().v, + data.vlast_read_inbox_message_id().v, + data.vlast_read_outbox_message_id().v); + history->unreadMentions().setCount(data.vunread_mention_count().v); + history->unreadReactions().setCount(data.vunread_reaction_count().v); + history->setUnreadMark(data.vis_marked_as_unread().v); + history->owner().notifySettings().apply( + history->peer, + data.vnotification_settings()); + + result->setTranslationDisabled(!data.vis_translatable().v); + + const auto availableReactions = [&] { + return Data::Parse(result, data.vavailable_reactions()); + }; + + if (const auto user = result->asUser()) { + result->setIsBlocked(data.vblock_list() + && data.vblock_list()->type() == id_blockListMain); + } else if (const auto chat = result->asChat()) { + using Flag = ChatDataFlag; + + const auto canAddMembers = chat->canAddMembers(); + + chat->setName(data.vtitle().v); + if (const auto photo = data.vphoto()) { + chat->setPhoto(*photo); + } else { + chat->clearPhoto(); + } + chat->setDefaultRestrictions( + RestrictionsFromPermissions(data.vpermissions())); + + const auto &videoChat = data.vvideo_chat().data(); + { + const auto tlAs = videoChat.vdefault_participant_id(); + const auto as = tlAs ? peerFromSender(*tlAs) : PeerId(0); + chat->setGroupCall(videoChat.vgroup_call_id().v); + chat->setGroupCallDefaultJoinAs(as); + } + const auto setFlags = Flag::CallNotEmpty | Flag::NoForwards; + chat->setFlags((chat->flags() & ~setFlags) + | (videoChat.vhas_participants().v + ? Flag::CallNotEmpty + : Flag()) + | (data.vhas_protected_content().v + ? Flag::NoForwards + : Flag())); + chat->setAllowedReactions(availableReactions()); + + if (canAddMembers != chat->canAddMembers()) { + updates |= UpdateFlag::Rights; + } + } else if (const auto channel = result->asChannel()) { + using Flag = ChannelDataFlag; + + const auto wasInChannel = channel->amIn(); + const auto canViewAdmins = channel->canViewAdmins(); + const auto canViewMembers = channel->canViewMembers(); + const auto canAddMembers = channel->canAddMembers(); + + if (!channel->isBroadcast()) { + channel->setDefaultRestrictions( + RestrictionsFromPermissions(data.vpermissions())); + } + channel->setName(data.vtitle().v, channel->username()); + + if (const auto photo = data.vphoto()) { + channel->setPhoto(*photo); + } else { + channel->clearPhoto(); + } + + if (wasInChannel != channel->amIn()) { + updates |= UpdateFlag::ChannelAmIn; + } + if (canViewAdmins != channel->canViewAdmins() + || canViewMembers != channel->canViewMembers() + || canAddMembers != channel->canAddMembers()) { + updates |= UpdateFlag::Rights; + } + const auto &videoChat = data.vvideo_chat().data(); + { + const auto tlAs = videoChat.vdefault_participant_id(); + const auto as = tlAs ? peerFromSender(*tlAs) : PeerId(0); + channel->setGroupCall(videoChat.vgroup_call_id().v); + channel->setGroupCallDefaultJoinAs(as); + } + const auto setFlags = Flag::CallNotEmpty | Flag::NoForwards; + channel->setFlags((channel->flags() & ~setFlags) + | (videoChat.vhas_participants().v + ? Flag::CallNotEmpty + : Flag()) + | (data.vhas_protected_content().v + ? Flag::NoForwards + : Flag())); + channel->setAllowedReactions(availableReactions()); + } + result->setActionBar(data.vaction_bar()); + result->setMessagesTTL(data.vmessage_auto_delete_time().v); + result->setThemeEmoji(data.vtheme_name().v); + result->setWallPaper(data.vbackground() + ? Data::WallPaper::Create(&result->session(), *data.vbackground()) + : std::nullopt); + + if (const auto sender = data.vmessage_sender_id()) { + session().sendAsPeers().setChosen(result, peerFromSender(*sender)); + } + + auto decorationsUpdated = false; + if (result->changeColorIndex(data.vaccent_color_id().v)) { + updates |= UpdateFlag::Color; + decorationsUpdated = true; + } + if (result->changeBackgroundEmojiId( + data.vbackground_custom_emoji_id().v)) { + updates |= UpdateFlag::BackgroundEmoji; + decorationsUpdated = true; + } + if (decorationsUpdated && result->isMinimalLoaded()) { + _peerDecorationsUpdated.fire_copy(result); + } + + if (!result->isFullLoaded()) { + result->setLoadedStatus(PeerData::LoadedStatus::Full); + } + if (updates) { + session().changes().peerUpdated(result, updates); + } + return result; +} + +not_null Session::processChat(const TLbasicGroup &chat) { + const auto &data = chat.match([]( + const auto &data) -> const TLDbasicGroup & { + return data; + }); + const auto result = this->chat(data.vid().v); + using Flag = ChatDataFlag; + const auto setting = Flag::Deactivated | Flag::Left; + auto flags = (result->flags() & ~setting) + | (!data.vis_active().v ? Flag::Deactivated : Flag()); + result->count = data.vmember_count().v; + if (const auto migratedTo = data.vupgraded_to_supergroup_id().v) { + const auto channel = this->channel(migratedTo); + channel->setFlags((channel->flags() & ~ChannelDataFlag::Broadcast) + | ChannelDataFlag::Megagroup); + ApplyMigration(result, channel); + } + data.vstatus().match([&](const TLDchatMemberStatusCreator &data) { + if (!data.vis_member().v) { + flags |= Flag::Left; + } + result->creator = session().userId(); + result->setAdminRights(data.vis_anonymous().v + ? ChatAdminRight::Anonymous + : ChatAdminRight()); + }, [&](const TLDchatMemberStatusAdministrator &data) { + result->setAdminRights(AdminRightsFromChatMemberStatus(data)); + }, [&](const TLDchatMemberStatusMember &data) { + result->setAdminRights(ChatAdminRights()); + }, [&](const TLDchatMemberStatusRestricted &data) { + LOG(("Tdb Error: Should not get restrictions in basic groups.")); + result->setAdminRights(ChatAdminRights()); + if (!data.vis_member().v) { + flags |= Flag::Left; + } + }, [&](const TLDchatMemberStatusLeft &data) { + result->setAdminRights(ChatAdminRights()); + flags |= Flag::Left; + }, [&](const TLDchatMemberStatusBanned &data) { + result->setAdminRights(ChatAdminRights()); + flags |= Flag::Forbidden; + }); + result->setFlags(flags); + return result; +} + +not_null Session::processChannel( + const TLsupergroup &channel) { + const auto &data = channel.data(); + const auto result = this->channel(data.vid().v); + + using UpdateFlag = Data::PeerUpdate::Flag; + auto updateFlags = UpdateFlag::None | UpdateFlag::None; + const auto wasInChannel = result->amIn(); + const auto canViewAdmins = result->canViewAdmins(); + const auto canViewMembers = result->canViewMembers(); + const auto canAddMembers = result->canAddMembers(); + + using Flag = ChannelDataFlag; + result->date = data.vdate().v; + const auto setting = Flag::Megagroup + | Flag::Broadcast + | Flag::Gigagroup + | Flag::Scam + | Flag::Fake + | Flag::Left + | Flag::Signatures + | Flag::SlowmodeEnabled + | Flag::Verified + //| Flag::f_has_geo + | Flag::HasLink + | Flag::Creator + | Flag::Forbidden + | Flag::Forum; + auto flags = (result->flags() & ~setting) + | (data.vis_channel().v ? Flag::Broadcast : Flag::Megagroup) + | (data.vis_broadcast_group().v + ? Flag::Gigagroup + : Flag()) + | (data.vis_fake().v ? Flag::Fake : Flag()) + | (data.vis_scam().v ? Flag::Scam : Flag()) + | (data.vis_verified().v ? Flag::Verified : Flag()) + | (data.vis_slow_mode_enabled().v + ? Flag::SlowmodeEnabled + : Flag()) + | (data.vsign_messages().v ? Flag::Signatures : Flag()) + | (data.vhas_linked_chat().v ? Flag::HasLink : Flag()) + //| (data.vhas_location().v ? Flag::f_has_geo : Flag()) + | ((data.vstatus().type() == id_chatMemberStatusCreator) + ? Flag::Creator + : Flag()) + | (data.vis_forum().v ? Flag::Forum : Flag()); + + result->setUnavailableReasons( + ExtractUnavailableReasons(data.vrestriction_reason())); + + data.vstatus().match([&](const TLDchatMemberStatusCreator &data) { + if (!data.vis_member().v) { + flags |= Flag::Left; + } + result->setAdminRights(data.vis_anonymous().v + ? ChatAdminRight::Anonymous + : ChatAdminRight()); + result->setRestrictions(ChatRestrictionsInfo()); + // custom_title is not set here, so ignored. + }, [&](const TLDchatMemberStatusAdministrator &data) { + using Flag = ChatAdminRight; + const auto bit = [&](const TLbool &check, Flag value) { + return check.v ? value : Flag(0); + }; + result->setAdminRights(AdminRightsFromChatMemberStatus(data)); + result->setRestrictions(ChatRestrictionsInfo()); + // custom_title is not set here, so ignored. + }, [&](const TLDchatMemberStatusMember &data) { + result->setAdminRights(ChatAdminRights()); + result->setRestrictions(ChatRestrictionsInfo()); + }, [&](const TLDchatMemberStatusRestricted &data) { + result->setAdminRights(ChatAdminRights()); + result->setRestrictions({ + RestrictionsFromPermissions(data.vpermissions()), + data.vrestricted_until_date().v, + }); + if (!data.vis_member().v) { + flags |= Flag::Left; + } + }, [&](const TLDchatMemberStatusLeft &data) { + result->setAdminRights(ChatAdminRights()); + result->setRestrictions(ChatRestrictionsInfo()); + flags |= Flag::Left; + }, [&](const TLDchatMemberStatusBanned &data) { + result->setAdminRights(ChatAdminRights()); + const auto flags = ChannelData::KickedRestrictedRights( + session().user() + ).flags; + result->setRestrictions({ flags, data.vbanned_until_date().v }); + }); + result->setFlags(flags); + + result->setMembersCount(data.vmember_count().v); + const auto newUsernames = Api::Usernames::FromTL(data.vusernames()); + const auto userName = (newUsernames.empty() || !newUsernames[0].active) + ? QString() + : newUsernames[0].username; + result->setName(result->name(), userName); + result->setUsernames(newUsernames); + + if (wasInChannel != result->amIn()) { + updateFlags |= UpdateFlag::ChannelAmIn; + } + if (canViewAdmins != result->canViewAdmins() + || canViewMembers != result->canViewMembers() + || canAddMembers != result->canAddMembers()) { + updateFlags |= UpdateFlag::Rights; + } + if (flags) { + session().changes().peerUpdated(result, updateFlags); + } + + return result; +} + +not_null Session::processSecretChat( + const TLsecretChat &secretChat) { + const auto &data = secretChat.data(); + const auto peerId = peerFromSecretChat(data.vid().v); + const auto result = this->peer(peerId)->asSecretChat(); + result->update(data); + return result; +} +#if 0 // mtp UserData *Session::processUsers(const MTPVector &data) { auto result = (UserData*)nullptr; for (const auto &user : data.v) { @@ -1074,7 +1582,42 @@ PeerData *Session::processChats(const MTPVector &data) { } return result; } +#endif +PeerData *Session::processPeers(const std::vector &data) { + auto result = (PeerData*)nullptr; + for (const auto &dialog : data) { + result = processPeer(dialog); + } + return result; +} + +UserData *Session::processUsers(const std::vector &data) { + auto result = (UserData*)nullptr; + for (const auto &user : data) { + result = processUser(user); + } + return result; +} + +ChatData *Session::processChats(const std::vector &data) { + auto result = (ChatData*)nullptr; + for (const auto &chat : data) { + result = processChat(chat); + } + return result; +} + +ChannelData *Session::processChannels( + const std::vector &data) { + auto result = (ChannelData*)nullptr; + for (const auto &channel : data) { + result = processChannel(channel); + } + return result; +} + +#if 0 // mtp void Session::applyMaximumChatVersions(const MTPVector &data) { for (const auto &chat : data.v) { chat.match([&](const MTPDchat &data) { @@ -1087,6 +1630,7 @@ void Session::applyMaximumChatVersions(const MTPVector &data) { }); } } +#endif void Session::registerGroupCall(not_null call) { _groupCalls.emplace(call->id(), call); @@ -1262,6 +1806,7 @@ History *Session::historyLoaded(const PeerData *peer) { return peer ? historyLoaded(peer->id) : nullptr; } +#if 0 // mtp void Session::deleteConversationLocally(not_null peer) { const auto history = historyLoaded(peer); if (history) { @@ -1283,6 +1828,7 @@ void Session::deleteConversationLocally(not_null peer) { } } } +#endif bool Session::chatsListLoaded(Data::Folder *folder) { return chatsList(folder)->loaded(); @@ -1394,6 +1940,7 @@ QString Session::nameSortKey(const QString &name) const { } void Session::setupMigrationViewer() { +#if 0 // mtp session().changes().peerUpdates( PeerUpdate::Flag::Migration ) | rpl::map([](const PeerUpdate &update) { @@ -1415,6 +1962,7 @@ void Session::setupMigrationViewer() { } } }, _lifetime); +#endif } void Session::setupChannelLeavingViewer() { @@ -1428,11 +1976,13 @@ void Session::setupChannelLeavingViewer() { } else { if (const auto history = historyLoaded(channel->id)) { history->removeJoinedMessage(); +#if 0 // mtp history->updateChatListExistence(); history->updateChatListSortPosition(); if (!history->inChatList()) { history->clearFolder(); } +#endif } } }, _lifetime); @@ -2025,6 +2575,7 @@ void Session::setChatPinned( notifyPinnedDialogsOrderUpdated(); } +#if 0 // mtp void Session::setPinnedFromEntryList(Dialogs::Key key, bool pinned) { Expects(key.entry()->folderKnown()); @@ -2113,6 +2664,100 @@ void Session::applyDialog( folder->applyDialog(data); setPinnedFromEntryList(folder, data.is_pinned()); } +#endif + +void Session::applyLastMessage(const TLDupdateChatLastMessage &data) { + const auto history = this->history(peerFromTdbChat(data.vchat_id())); + for (const auto &position : data.vpositions().v) { + history->applyPosition(position.data()); + } + if (const auto message = data.vlast_message()) { + history->applyLastMessage(*message); + } else { + history->clearLastMessage(); + } +} + +void Session::applyDialogPosition(const TLDupdateChatPosition &data) { + const auto &position = data.vposition().data(); + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto history = position.vorder().v + ? this->history(peerId).get() + : this->historyLoaded(peerId); + if (history) { + history->applyPosition(position); + } +} + +void Session::applyChatPermissions(const TLDupdateChatPermissions &data) { + if (const auto peer = peerLoaded(peerFromTdbChat(data.vchat_id()))) { + if (const auto chat = peer->asChat()) { + chat->setDefaultRestrictions( + RestrictionsFromPermissions(data.vpermissions())); + } else if (const auto channel = peer->asChannel()) { + channel->setDefaultRestrictions( + RestrictionsFromPermissions(data.vpermissions())); + } else { + Unexpected("updateChatPermissions for a user."); + } + } +} + +void Session::applyChatTitle(const TLDupdateChatTitle &data) { + if (const auto peer = peerLoaded(peerFromTdbChat(data.vchat_id()))) { + if (const auto chat = peer->asChat()) { + chat->setName(data.vtitle().v); + } else if (const auto channel = peer->asChannel()) { + channel->setName(data.vtitle().v, channel->username()); + } else { + // Process in updateUser. + } + } +} + +void Session::applyChatPhoto(const TLDupdateChatPhoto &data) { + if (const auto peer = peerLoaded(peerFromTdbChat(data.vchat_id()))) { + if (const auto chat = peer->asChat()) { + if (const auto photo = data.vphoto()) { + chat->setPhoto(*photo); + } else { + chat->clearPhoto(); + } + } else if (const auto channel = peer->asChannel()) { + if (const auto photo = data.vphoto()) { + channel->setPhoto(*photo); + } else { + channel->clearPhoto(); + } + } else { + // Process in updateUser. + } + } +} + +void Session::applyChatAccentColor(const TLDupdateChatAccentColor &data) { + if (const auto peer = peerLoaded(peerFromTdbChat(data.vchat_id()))) { + if (peer->changeColorIndex(data.vaccent_color_id().v)) { + peer->session().changes().peerUpdated( + peer, + PeerUpdate::Flag::Color); + _peerDecorationsUpdated.fire_copy(peer); + } + } +} + +void Session::applyChatBackgroundCustomEmoji( + const TLDupdateChatBackgroundCustomEmoji &data) { + if (const auto peer = peerLoaded(peerFromTdbChat(data.vchat_id()))) { + const auto id = data.vbackground_custom_emoji_id().v; + if (peer->changeBackgroundEmojiId(id)) { + peer->session().changes().peerUpdated( + peer, + PeerUpdate::Flag::BackgroundEmoji); + _peerDecorationsUpdated.fire_copy(peer); + } + } +} bool Session::pinnedCanPin(not_null thread) const { if (const auto topic = thread->asTopic()) { @@ -2228,6 +2873,7 @@ void Session::reorderTwoPinnedChats( notifyPinnedDialogsOrderUpdated(); } +#if 0 // mtp bool Session::updateExistingMessage(const MTPDmessage &data) { const auto peer = peerFromMTP(data.vpeer_id()); const auto existing = message(peer, data.vid().v); @@ -2318,6 +2964,7 @@ void Session::processExistingMessages( processMessages(data.vmessages(), NewMessageType::Existing); }); } +#endif const Session::Messages *Session::messagesList(PeerId peerId) const { const auto i = _messages.find(peerId); @@ -2345,6 +2992,7 @@ void Session::registerMessage(not_null item) { } } +#if 0 // mtp void Session::registerMessageTTL(TimeId when, not_null item) { Expects(when > 0); @@ -2438,6 +3086,7 @@ void Session::processNonChannelMessagesDeleted(const QVector &data) { history->requestChatListMessage(); } } +#endif void Session::removeDependencyMessage(not_null item) { const auto i = _dependentMessages.find(item); @@ -2591,6 +3240,7 @@ Session::SentData Session::messageSentData(uint64 randomId) const { return (i != end(_sentMessagesData)) ? i->second : SentData(); } +#if 0 // mtp HistoryItem *Session::addNewMessage( const MTPMessage &data, MessageFlags localFlags, @@ -2618,6 +3268,25 @@ HistoryItem *Session::addNewMessage( } return result; } +#endif + +not_null Session::processMessage( + const TLmessage &message, + MsgId oldMessageId) { + const auto peerId = peerFromTdbChat(message.data().vchat_id()); + const auto type = NewMessageType::Existing; + return history(peerId)->addMessage(message, type, oldMessageId); +} + +not_null Session::processMessage( + const TLmessage &message, + NewMessageType type) { + const auto peerId = peerFromTdbChat(message.data().vchat_id()); + const auto history = this->history(peerId); + const auto result = history->addMessage(message, type); + CheckForSwitchInlineButton(result); + return result; +} int Session::unreadBadge() const { return computeUnreadBadge(_chatsList.unreadState()); @@ -2687,6 +3356,7 @@ bool Session::computeUnreadBadgeMuted( : (state.chatsMuted >= state.chats)); } +#if 0 // mtp void Session::selfDestructIn(not_null item, crl::time delay) { _selfDestructItems.push_back(item->fullId()); if (!_selfDestructTimer.isActive() @@ -2718,6 +3388,7 @@ void Session::checkSelfDestructItems() { _selfDestructTimer.callOnce(nextDestructIn); } } +#endif not_null Session::photo(PhotoId id) { auto i = _photos.find(id); @@ -2729,6 +3400,7 @@ not_null Session::photo(PhotoId id) { return i->second.get(); } +#if 0 // mtp not_null Session::processPhoto(const MTPPhoto &data) { return data.match([&](const MTPDphoto &data) { return processPhoto(data); @@ -2792,6 +3464,7 @@ not_null Session::processPhoto( return photo(data.vid().v); }); } +#endif not_null Session::photo( PhotoId id, @@ -2825,6 +3498,26 @@ not_null Session::photo( return result; } +void Session::photoFileIdUpdated( + not_null photo, + FileId was, + FileId now) { + if (was != now) { + if (was) { + _photoByFileId.erase(was); + } + if (now) { + _photoByFileId.emplace(now, photo); + } + } +} + +PhotoData *Session::photoByFileId(FileId id) const { + const auto i = _photoByFileId.find(id); + return (i != end(_photoByFileId)) ? i->second.get() : nullptr; +} + +#if 0 // mtp void Session::photoConvert( not_null original, const MTPPhoto &data) { @@ -2992,6 +3685,7 @@ void Session::photoApplyFields( : 0)); } } +#endif void Session::photoApplyFields( not_null photo, @@ -3010,7 +3704,9 @@ void Session::photoApplyFields( if (!date) { return; } +#if 0 // mtp photo->setRemoteLocation(dc, access, fileReference); +#endif photo->date = date; photo->setHasAttachedStickers(hasStickers); photo->updateImages( @@ -3033,6 +3729,7 @@ not_null Session::document(DocumentId id) { return i->second.get(); } +#if 0 // mtp not_null Session::processDocument(const MTPDocument &data) { return data.match([&](const MTPDdocument &data) { return processDocument(data); @@ -3258,9 +3955,31 @@ void Session::documentApplyFields( // Uses 'type' that is computed from attributes. document->recountIsImage(); +#if 0 // mtp if (dc != 0 && access != 0) { document->setRemoteLocation(dc, access, fileReference); } +#endif +} +#endif + +void Session::documentFileIdUpdated( + not_null document, + FileId was, + FileId now) { + if (was != now) { + if (was) { + _documentByFileId.erase(was); + } + if (now) { + _documentByFileId.emplace(now, document); + } + } +} + +DocumentData *Session::documentByFileId(FileId id) const { + const auto i = _documentByFileId.find(id); + return (i != end(_documentByFileId)) ? i->second.get() : nullptr; } not_null Session::webpage(WebPageId id) { @@ -3273,6 +3992,7 @@ not_null Session::webpage(WebPageId id) { return i->second.get(); } +#if 0 // mtp not_null Session::processWebpage(const MTPWebPage &data) { switch (data.type()) { case mtpc_webPage: @@ -3327,6 +4047,7 @@ not_null Session::processWebpage( : (base::unixtime::now() + kDefaultPendingTimeout)); return result; } +#endif not_null Session::webpage( WebPageId id, @@ -3384,6 +4105,7 @@ not_null Session::webpage( return result; } +#if 0 // mtp void Session::webpageApplyFields( not_null page, const MTPDwebPage &data) { @@ -3482,6 +4204,7 @@ void Session::webpageApplyFields( data.is_has_large_media(), pendingTill); } +#endif void Session::webpageApplyFields( not_null page, @@ -3510,14 +4233,18 @@ void Session::webpageApplyFields( storyId, photo, document, +#if 0 // mtp std::move(collage), +#endif duration, author, hasLargeMedia, pendingTill); +#if 0 // mtp if (requestPending) { _session->api().requestWebPageDelayed(page); } +#endif if (changed) { notifyWebPageUpdateDelayed(page); } @@ -3531,11 +4258,13 @@ not_null Session::game(GameId id) { return i->second.get(); } +#if 0 // mtp not_null Session::processGame(const MTPDgame &data) { const auto result = game(data.vid().v); gameApplyFields(result, data); return result; } +#endif not_null Session::game( GameId id, @@ -3557,6 +4286,7 @@ not_null Session::game( return result; } +#if 0 // mtp void Session::gameConvert( not_null original, const MTPGame &data) { @@ -3596,6 +4326,7 @@ void Session::gameApplyFields( processPhoto(data.vphoto()), document ? processDocument(*document).get() : nullptr); } +#endif void Session::gameApplyFields( not_null game, @@ -3617,6 +4348,7 @@ void Session::gameApplyFields( notifyGameUpdateDelayed(game); } +#if 0 // mtp not_null Session::botApp(BotAppId id) { const auto i = _botApps.find(id); return (i != end(_botApps)) @@ -3655,6 +4387,7 @@ BotAppData *Session::processBotApp( return (BotAppData*)nullptr; }); } +#endif not_null Session::poll(PollId id) { auto i = _polls.find(id); @@ -3664,6 +4397,7 @@ not_null Session::poll(PollId id) { return i->second.get(); } +#if 0 // mtp not_null Session::processPoll(const MTPPoll &data) { return data.match([&](const MTPDpoll &data) { const auto id = data.vid().v; @@ -3688,6 +4422,7 @@ not_null Session::processPoll(const MTPDmessageMediaPoll &data) { } return result; } +#endif void Session::checkPollsClosings() { const auto now = base::unixtime::now(); @@ -3712,6 +4447,19 @@ void Session::checkPollsClosings() { } } +void Session::applyUpdate(const Tdb::TLpoll &update) { + const auto updated = [&] { + const auto i = _polls.find(update.data().vid().v); + return (i == end(_polls)) + ? nullptr + : processPoll(update).get(); + }(); + if (updated && updated->applyResults(update)) { + notifyPollUpdateDelayed(updated); + } +} + +#if 0 // mtp void Session::applyUpdate(const MTPDupdateMessagePoll &update) { const auto updated = [&] { const auto poll = update.vpoll(); @@ -3771,6 +4519,7 @@ void Session::applyUpdate(const MTPDupdateChatDefaultBannedRights &update) { } } } +#endif not_null Session::location(const LocationPoint &point) { const auto i = _locations.find(point); @@ -3791,6 +4540,71 @@ not_null Session::location(const LocationPoint &point) { prepared)).first->second.get(); } +not_null Session::processPhoto(const PhotoLocalData &data) { + const auto result = photo(data.id); + result->setFromLocal(data); + return result; +} + +not_null Session::processPhoto(const TLphoto &data) { + const auto result = photo(PhotoData::IdFromTdb(data)); + result->setFromTdb(data); + return result; +} + +not_null Session::processPhoto(const TLchatPhoto &data) { + const auto result = photo(PhotoData::IdFromTdb(data)); + result->setFromTdb(data); + return result; +} + +not_null Session::processSmallPhoto( + const TLchatPhotoInfo &data) { + const auto result = photo(PhotoData::IdFromTdb(data)); + result->setFromTdb(data); + return result; +} + +not_null Session::processDocument( + const DocumentLocalData &data) { + const auto result = document(data.id); + result->setFromLocal(data); + return result; +} + +not_null Session::processPlainDocument( + const TLfile &data, + SimpleDocumentType type) { + const auto result = document(data.data().vid().v); + result->setSimpleFromTdb(data, type); + return result; +} + +not_null Session::processWebpage(const TLwebPage &data) { + const auto result = webpage(WebPageData::IdFromTdb(data)); + result->setFromTdb(data); + return result; +} + +not_null Session::processGame(const TLgame &data) { + const auto result = game(GameData::IdFromTdb(data)); + result->setFromTdb(data); + return result; +} + +not_null Session::processPoll(const TLpoll &data) { + const auto result = poll(PollData::IdFromTdb(data)); + const auto changed = result->applyChanges(data); + if (changed) { + notifyPollUpdateDelayed(result); + } + if (result->closeDate > 0 && !result->closed()) { + _pollsClosings.emplace(result->closeDate, result); + checkPollsClosings(); + } + return result; +} + void Session::registerPhotoItem( not_null photo, not_null item) { @@ -4064,7 +4878,7 @@ QString Session::findContactPhone(not_null contact) const { const auto result = contact->phone(); return result.isEmpty() ? findContactPhone(peerToUser(contact->id)) - : Ui::FormatPhone(result); + : FormatPhone(result); } QString Session::findContactPhone(UserId contactId) const { @@ -4198,6 +5012,7 @@ Folder *Session::folderLoaded(FolderId id) const { return (it == end(_folders)) ? nullptr : it->second.get(); } +#if 0 // mtp not_null Session::processFolder(const MTPFolder &data) { return data.match([&](const MTPDfolder &data) { return processFolder(data); @@ -4207,6 +5022,7 @@ not_null Session::processFolder(const MTPFolder &data) { not_null Session::processFolder(const MTPDfolder &data) { return folder(data.vid().v); } +#endif not_null Session::chatsListFor( not_null entry) { @@ -4233,6 +5049,7 @@ not_null Session::contactsNoChatsList() { return &_contactsNoChatsList; } +#if 0 // mtp void Session::refreshChatListEntry(Dialogs::Key key) { Expects(key.entry()->folderKnown()); @@ -4330,6 +5147,67 @@ void Session::removeChatListEntry(Dialogs::Key key) { Core::App().notifications().clearFromHistory(history); } } +#endif + +void Session::refreshChatListEntry(Dialogs::Key key, FilterId filterId) { + Expects(filterId || key.entry()->folderKnown()); + Expects(!key.entry()->asTopic() || !filterId); + + using namespace Dialogs; + + const auto entry = key.entry(); + const auto history = entry->asHistory(); + const auto topic = entry->asTopic(); + const auto list = filterId + ? chatsFilters().chatsList(filterId) + : chatsListFor(entry); + auto event = ChatListEntryRefresh{ .key = key, .filterId = filterId }; + event.existenceChanged = !entry->inChatList(filterId); + if (event.existenceChanged) { + if (topic && topic->creating()) { + return; + } + const auto row = entry->addToChatList(filterId, list); + if (!filterId) { + _contactsNoChatsList.remove(key, row); + } + } else { + event.moved = entry->adjustByPosInChatList(filterId, list); + } + if (event) { + _chatListEntryRefreshes.fire(std::move(event)); + } + + if (history && event.existenceChanged) { + if (const auto from = history->peer->migrateFrom()) { + if (const auto migrated = historyLoaded(from)) { + if (migrated->inChatList(filterId)) { + removeChatListEntry(migrated, filterId); + } + } + } + if (const auto forum = history->peer->forum()) { + forum->preloadTopics(); + } + } +} + +void Session::removeChatListEntry(Dialogs::Key key, FilterId filterId) { + Expects(filterId || key.entry()->folderKnown()); + + const auto entry = key.entry(); + const auto list = filterId + ? chatsFilters().chatsList(filterId) + : chatsListFor(entry); + if (entry->inChatList(filterId)) { + entry->removeFromChatList(filterId, list); + _chatListEntryRefreshes.fire(ChatListEntryRefresh{ + .key = key, + .filterId = filterId, + .existenceChanged = true + }); + } +} auto Session::chatListEntryRefreshes() const -> rpl::producer { @@ -4345,6 +5223,21 @@ auto Session::dialogsRowReplacements() const return _dialogsRowReplacements.events(); } +void Session::serviceNotification(const TextWithEntities &message) { + _session->sender().request(TLaddLocalMessage( + peerToTdbChat(PeerData::kServiceNotificationsId), + tl_messageSenderUser( + tl_int53(peerToUser(PeerData::kServiceNotificationsId).bare)), + std::nullopt, + tl_bool(true), + tl_inputMessageText( + Api::FormattedTextToTdb(message), + std::nullopt, + tl_bool(false)) + )).send(); +} + +#if 0 // mtp void Session::serviceNotification( const TextWithEntities &message, const MTPMessageMedia &media, @@ -4431,6 +5324,7 @@ void Session::insertCheckedServiceNotification( } sendHistoryChangeNotifications(); } +#endif void Session::setMimeForwardIds(MessageIdsList &&list) { _mimeForwardIds = std::move(list); @@ -4470,6 +5364,18 @@ void Session::setTopPromoted( } } +void Session::setNotTopPromoted(not_null history) { + if (_topPromoted == history) { + setTopPromoted(nullptr, QString(), QString()); + } +} + +bool Session::updateWallpapers(const TLbackgrounds &papers) { + setWallpapers(papers.data().vbackgrounds().v); + return true; // later optimize check if there were changes +} + +#if 0 // mtp bool Session::updateWallpapers(const MTPaccount_WallPapers &data) { return data.match([&](const MTPDaccount_wallPapers &data) { setWallpapers(data.vwallpapers().v, data.vhash().v); @@ -4482,6 +5388,8 @@ bool Session::updateWallpapers(const MTPaccount_WallPapers &data) { void Session::setWallpapers(const QVector &data, uint64 hash) { _wallpapersHash = hash; +#endif +void Session::setWallpapers(const QVector &data) { _wallpapers.clear(); _wallpapers.reserve(data.size() + 2); @@ -4532,6 +5440,7 @@ uint64 Session::wallpapersHash() const { return _wallpapersHash; } +#if 0 // mtp MTP::DcId Session::statsDcId(not_null channel) { const auto it = _channelStatsDcIds.find(channel); return (it == end(_channelStatsDcIds)) ? MTP::DcId(0) : it->second; @@ -4544,6 +5453,7 @@ void Session::applyStatsDcId( _channelStatsDcIds[channel] = dcId; } } +#endif void Session::webViewResultSent(WebViewResultSent &&sent) { return _webViewResultSent.fire(std::move(sent)); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index df6dc5019a14d..c35dd52579286 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -17,6 +17,41 @@ For license and copyright information please follow this link: #include "base/timer.h" #include "base/flags.h" +namespace Tdb { +class TLsecretChat; +class TLbackground; +class TLbackgrounds; +class TLchat; +class TLuser; +class TLbasicGroup; +class TLsupergroup; +class TLphoto; +class TLchatPhoto; +class TLchatPhotoInfo; +class TLdocument; +class TLvideo; +class TLanimation; +class TLsticker; +class TLvoiceNote; +class TLvideoNote; +class TLgame; +class TLwebPage; +class TLfile; +class TLmessage; +class TLpoll; +class TLDupdateChatPosition; +class TLDupdateChatLastMessage; +class TLDupdateChatPermissions; +class TLDupdateChatTitle; +class TLDupdateChatPhoto; +class TLDupdateChatAccentColor; +class TLDupdateChatBackgroundCustomEmoji; +} // namespace Tdb + +enum class SimpleDocumentType; + +struct HistoryMessageMarkupData; + class Image; class HistoryItem; struct WebPageCollage; @@ -65,12 +100,26 @@ class NotifySettings; class CustomEmojiManager; class Stories; +struct PhotoLocalData; +struct DocumentLocalData; + struct RepliesReadTillUpdate { FullMsgId id; MsgId readTillId; bool out = false; }; +template +struct IsTdbDocumentHelper; + +template +[[nodiscard]] inline constexpr bool IsTdbDocument() { + return IsTdbDocumentHelper::value(); +} + +template +DocumentId DocumentIdFromTdb(const T &data); + class Session final { public: using ViewElement = HistoryView::Element; @@ -182,14 +231,33 @@ class Session final { [[nodiscard]] ChatData *chatLoaded(PeerId id) const = delete; [[nodiscard]] ChannelData *channelLoaded(PeerId id) const = delete; +#if 0 // mtp not_null processUser(const MTPUser &data); not_null processChat(const MTPChat &data); +#endif + + not_null processPeer(const Tdb::TLchat &dialog); + not_null processUser(const Tdb::TLuser &user); + not_null processChat(const Tdb::TLbasicGroup &chat); + not_null processChannel(const Tdb::TLsupergroup &channel); + not_null processSecretChat( + const Tdb::TLsecretChat &secretChat); +#if 0 // mtp // Returns last user, if there were any. UserData *processUsers(const MTPVector &data); PeerData *processChats(const MTPVector &data); +#endif + + // Returns last user, if there were any. + PeerData *processPeers(const std::vector &data); + UserData *processUsers(const std::vector &data); + ChatData *processChats(const std::vector &data); + ChannelData *processChannels(const std::vector &data); +#if 0 // mtp void applyMaximumChatVersions(const MTPVector &data); +#endif void registerGroupCall(not_null call); void unregisterGroupCall(not_null call); @@ -227,7 +295,9 @@ class Session final { [[nodiscard]] not_null history(not_null peer); [[nodiscard]] History *historyLoaded(const PeerData *peer); +#if 0 // mtp void deleteConversationLocally(not_null peer); +#endif [[nodiscard]] rpl::variable &contactsLoaded() { return _contactsLoaded; @@ -335,6 +405,8 @@ class Session final { MessageIdsList itemsToIds(const HistoryItemsList &items) const; MessageIdsList itemOrItsGroup(not_null item) const; + void applyUpdate(const Tdb::TLpoll &update); +#if 0 // mtp void applyUpdate(const MTPDupdateMessagePoll &update); void applyUpdate(const MTPDupdateChatParticipants &update); void applyUpdate(const MTPDupdateChatParticipantAdd &update); @@ -347,6 +419,7 @@ class Session final { const QVector &messages, const QVector &dialogs, std::optional count = std::nullopt); +#endif [[nodiscard]] bool pinnedCanPin(not_null thread) const; [[nodiscard]] bool pinnedCanPin( @@ -368,14 +441,18 @@ class Session final { [[nodiscard]] const std::vector &pinnedChatsOrder( FilterId filterId) const; void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned); +#if 0 // mtp void setPinnedFromEntryList(Dialogs::Key key, bool pinned); +#endif void clearPinnedChats(Folder *folder); +#if 0 // mtp void applyPinnedChats( Folder *folder, const QVector &list); void applyPinnedTopics( not_null forum, const QVector &list); +#endif void reorderTwoPinnedChats( FilterId filterId, Dialogs::Key key1, @@ -388,6 +465,7 @@ class Session final { void registerMessage(not_null item); void unregisterMessage(not_null item); +#if 0 // mtp void registerMessageTTL(TimeId when, not_null item); void unregisterMessageTTL(TimeId when, not_null item); @@ -407,6 +485,7 @@ class Session final { void processMessagesDeleted( PeerId peerId, const QVector &data); +#endif [[nodiscard]] MsgId nextLocalMessageId(); [[nodiscard]] HistoryItem *message( @@ -463,6 +542,7 @@ class Session final { return _documentLoadProgress.events(); } +#if 0 // mtp HistoryItem *addNewMessage( const MTPMessage &data, MessageFlags localFlags, @@ -472,6 +552,14 @@ class Session final { const MTPMessage &data, MessageFlags localFlags, NewMessageType type); +#endif + + not_null processMessage( + const Tdb::TLmessage &data, + MsgId oldMessageId = 0); + not_null processMessage( + const Tdb::TLmessage &data, + NewMessageType type); [[nodiscard]] int unreadBadge() const; [[nodiscard]] bool unreadBadgeMuted() const; @@ -485,14 +573,18 @@ class Session final { [[nodiscard]] auto repliesReadTillUpdates() const -> rpl::producer; +#if 0 // mtp void selfDestructIn(not_null item, crl::time delay); +#endif [[nodiscard]] not_null photo(PhotoId id); +#if 0 // mtp not_null processPhoto(const MTPPhoto &data); not_null processPhoto(const MTPDphoto &data); not_null processPhoto( const MTPPhoto &data, const PreparedPhotoThumbs &thumbs); +#endif [[nodiscard]] not_null photo( PhotoId id, const uint64 &access, @@ -507,19 +599,28 @@ class Session final { const ImageWithLocation &videoSmall, const ImageWithLocation &videoLarge, crl::time videoStartTime); +#if 0 // mtp void photoConvert( not_null original, const MTPPhoto &data); [[nodiscard]] PhotoData *photoFromWeb( const MTPWebDocument &data, const ImageLocation &thumbnailLocation); +#endif + void photoFileIdUpdated( + not_null photo, + FileId was, + FileId now); + [[nodiscard]] PhotoData *photoByFileId(FileId id) const; [[nodiscard]] not_null document(DocumentId id); +#if 0 // mtp not_null processDocument(const MTPDocument &data); not_null processDocument(const MTPDdocument &data); not_null processDocument( const MTPdocument &data, const ImageWithLocation &thumbnail); +#endif [[nodiscard]] not_null document( DocumentId id, const uint64 &access, @@ -533,6 +634,7 @@ class Session final { bool isPremiumSticker, int32 dc, int64 size); +#if 0 // mtp void documentConvert( not_null original, const MTPDocument &data); @@ -540,11 +642,19 @@ class Session final { const MTPWebDocument &data, const ImageLocation &thumbnailLocation, const ImageLocation &videoThumbnailLocation); +#endif + void documentFileIdUpdated( + not_null document, + FileId was, + FileId now); + [[nodiscard]] DocumentData *documentByFileId(FileId id) const; [[nodiscard]] not_null webpage(WebPageId id); +#if 0 // mtp not_null processWebpage(const MTPWebPage &data); not_null processWebpage(const MTPDwebPage &data); not_null processWebpage(const MTPDwebPagePending &data); +#endif [[nodiscard]] not_null webpage( WebPageId id, const QString &siteName, @@ -566,7 +676,9 @@ class Session final { TimeId pendingTill); [[nodiscard]] not_null game(GameId id); +#if 0 // mtp not_null processGame(const MTPDgame &data); +#endif [[nodiscard]] not_null game( GameId id, const uint64 &accessHash, @@ -575,23 +687,50 @@ class Session final { const QString &description, PhotoData *photo, DocumentData *document); +#if 0 // mtp void gameConvert( not_null original, const MTPGame &data); +#endif +#if 0 // mtp [[nodiscard]] not_null botApp(BotAppId id); BotAppData *findBotApp(PeerId botId, const QString &appName) const; BotAppData *processBotApp( PeerId botId, const MTPBotApp &data); +#endif [[nodiscard]] not_null poll(PollId id); +#if 0 // mtp not_null processPoll(const MTPPoll &data); not_null processPoll(const MTPDmessageMediaPoll &data); +#endif [[nodiscard]] not_null location( const LocationPoint &point); + not_null processPhoto(const PhotoLocalData &data); + not_null processPhoto(const Tdb::TLphoto &data); + not_null processPhoto(const Tdb::TLchatPhoto &data); + not_null processSmallPhoto(const Tdb::TLchatPhotoInfo &data); + + not_null processDocument(const DocumentLocalData &data); + template < + typename T, + typename = std::enable_if_t()>> + not_null processDocument(const T &data) { + const auto result = document(DocumentIdFromTdb(data)); + result->setFromTdb(data); + return result; + } + not_null processPlainDocument( + const Tdb::TLfile &data, + SimpleDocumentType type); + not_null processWebpage(const Tdb::TLwebPage &data); + not_null processGame(const Tdb::TLgame &data); + not_null processPoll(const Tdb::TLpoll &data); + void registerPhotoItem( not_null photo, not_null item); @@ -669,8 +808,10 @@ class Session final { [[nodiscard]] not_null folder(FolderId id); [[nodiscard]] Folder *folderLoaded(FolderId id) const; +#if 0 // mtp not_null processFolder(const MTPFolder &data); not_null processFolder(const MTPDfolder &data); +#endif [[nodiscard]] not_null chatsListFor( not_null entry); @@ -691,8 +832,10 @@ class Session final { return existenceChanged || (moved.from != moved.to); } }; +#if 0 // mtp void refreshChatListEntry(Dialogs::Key key); void removeChatListEntry(Dialogs::Key key); +#endif [[nodiscard]] auto chatListEntryRefreshes() const -> rpl::producer; @@ -703,10 +846,15 @@ class Session final { void dialogsRowReplaced(DialogsRowReplacement replacement); rpl::producer dialogsRowReplacements() const; + void refreshChatListEntry(Dialogs::Key key, FilterId filterId); + void removeChatListEntry(Dialogs::Key key, FilterId filterId); +#if 0 // mtp void serviceNotification( const TextWithEntities &message, const MTPMessageMedia &media = MTP_messageMediaEmpty(), bool invertMedia = false); +#endif + void serviceNotification(const TextWithEntities &message); void setMimeForwardIds(MessageIdsList &&list); MessageIdsList takeMimeForwardIds(); @@ -716,7 +864,12 @@ class Session final { const QString &type, const QString &message); + void setNotTopPromoted(not_null history); + +#if 0 // mtp bool updateWallpapers(const MTPaccount_WallPapers &data); +#endif + bool updateWallpapers(const Tdb::TLbackgrounds &papers); void removeWallpaper(const WallPaper &paper); const std::vector &wallpapers() const; uint64 wallpapersHash() const; @@ -730,11 +883,22 @@ class Session final { [[nodiscard]] auto peerDecorationsUpdated() const -> rpl::producer>; +#if 0 // mtp void applyStatsDcId(not_null, MTP::DcId); [[nodiscard]] MTP::DcId statsDcId(not_null); +#endif void clearLocalStorage(); + void applyLastMessage(const Tdb::TLDupdateChatLastMessage &data); + void applyDialogPosition(const Tdb::TLDupdateChatPosition &data); + void applyChatPermissions(const Tdb::TLDupdateChatPermissions &data); + void applyChatTitle(const Tdb::TLDupdateChatTitle &data); + void applyChatPhoto(const Tdb::TLDupdateChatPhoto &data); + void applyChatAccentColor(const Tdb::TLDupdateChatAccentColor &data); + void applyChatBackgroundCustomEmoji( + const Tdb::TLDupdateChatBackgroundCustomEmoji &data); + private: using Messages = std::unordered_map>; @@ -745,19 +909,25 @@ class Session final { void setupPeerNameViewer(); void setupUserIsContactViewer(); +#if 0 // mtp void checkSelfDestructItems(); +#endif void checkLocalUsersWentOffline(); +#if 0 // mtp void scheduleNextTTLs(); void checkTTLs(); +#endif int computeUnreadBadge(const Dialogs::UnreadState &state) const; bool computeUnreadBadgeMuted(const Dialogs::UnreadState &state) const; +#if 0 // mtp void applyDialog(Folder *requestFolder, const MTPDdialog &data); void applyDialog( Folder *requestFolder, const MTPDdialogFolder &data); +#endif const Messages *messagesList(PeerId peerId) const; not_null messagesListForInsert(PeerId peerId); @@ -766,12 +936,14 @@ class Session final { HistoryItem *changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId); void removeDependencyMessage(not_null item); +#if 0 // mtp void photoApplyFields( not_null photo, const MTPPhoto &data); void photoApplyFields( not_null photo, const MTPDphoto &data); +#endif void photoApplyFields( not_null photo, const uint64 &access, @@ -787,6 +959,7 @@ class Session final { const ImageWithLocation &videoLarge, crl::time videoStartTime); +#if 0 // mtp void documentApplyFields( not_null document, const MTPDocument &data); @@ -818,6 +991,7 @@ class Session final { void webpageApplyFields( not_null page, const MTPDwebPage &data); +#endif void webpageApplyFields( not_null page, WebPageType type, @@ -835,9 +1009,11 @@ class Session final { bool hasLargeMedia, TimeId pendingTill); +#if 0 // mtp void gameApplyFields( not_null game, const MTPDgame &data); +#endif void gameApplyFields( not_null game, const uint64 &accessHash, @@ -852,6 +1028,7 @@ class Session final { not_null item, Method method); +#if 0 // mtp void insertCheckedServiceNotification( const TextWithEntities &message, const MTPMessageMedia &media, @@ -859,6 +1036,8 @@ class Session final { bool invertMedia); void setWallpapers(const QVector &data, uint64 hash); +#endif + void setWallpapers(const QVector &data); void highlightProcessDone(uint64 processId); void checkPollsClosings(); @@ -910,26 +1089,32 @@ class Session final { std::map< not_null, base::flat_set>> _dependentMessages; +#if 0 // mtp std::map>> _ttlMessages; base::Timer _ttlCheckTimer; +#endif std::unordered_map> _nonChannelMessages; base::flat_map _messageByRandomId; base::flat_map _sentMessagesData; +#if 0 // mtp base::Timer _selfDestructTimer; std::vector _selfDestructItems; +#endif std::unordered_map< PhotoId, std::unique_ptr> _photos; + std::unordered_map> _photoByFileId; std::unordered_map< not_null, base::flat_set>> _photoItems; std::unordered_map< DocumentId, std::unique_ptr> _documents; + std::unordered_map> _documentByFileId; std::unordered_map< not_null, base::flat_set>> _documentItems; @@ -951,9 +1136,11 @@ class Session final { std::unordered_map< GameId, std::unique_ptr> _games; +#if 0 // mtp std::unordered_map< BotAppId, std::unique_ptr> _botApps; +#endif std::unordered_map< not_null, base::flat_set>> _gameViews; @@ -1017,7 +1204,9 @@ class Session final { base::flat_map, TimeId> _watchingForOffline; base::Timer _watchForOfflineTimer; +#if 0 // mtp base::flat_map, MTP::DcId> _channelStatsDcIds; +#endif rpl::event_stream _webViewResultSent; diff --git a/Telegram/SourceFiles/data/data_shared_media.cpp b/Telegram/SourceFiles/data/data_shared_media.cpp index 8033fba9cf819..ae561d4e05ca0 100644 --- a/Telegram/SourceFiles/data/data_shared_media.cpp +++ b/Telegram/SourceFiles/data/data_shared_media.cpp @@ -185,8 +185,10 @@ rpl::producer SharedScheduledMediaViewer( SharedMediaMergedKey key, int limitBefore, int limitAfter) { +#if 0 // mtp Expects(!key.mergedKey.universalId || Data::IsScheduledMsgId(key.mergedKey.universalId)); +#endif Expects((key.mergedKey.universalId != 0) || (limitBefore == 0 && limitAfter == 0)); diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.cpp b/Telegram/SourceFiles/data/data_sponsored_messages.cpp index ea42a0fe2fd05..a2182cf0ed07f 100644 --- a/Telegram/SourceFiles/data/data_sponsored_messages.cpp +++ b/Telegram/SourceFiles/data/data_sponsored_messages.cpp @@ -22,15 +22,40 @@ For license and copyright information please follow this link: #include "ui/image/image_location_factory.h" #include "ui/text/text_utilities.h" // Ui::Text::RichLangValue. +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" +#include "core/local_url_handlers.h" + namespace Data { namespace { +using namespace Tdb; +constexpr auto kSmallUserpicSize = 160; + constexpr auto kRequestTimeLimit = 5 * 60 * crl::time(1000); [[nodiscard]] bool TooEarlyForRequest(crl::time received) { return (received > 0) && (received + kRequestTimeLimit > crl::now()); } +[[nodiscard]] QByteArray SerializeRandomId( + not_null history, + int64 id) { + return QByteArray::number(history->peer->id.value) + + '_' + + QByteArray::number(id); +} + +[[nodiscard]] FullMsgId DeserializeRandomId(const QByteArray &randomId) { + const auto list = randomId.split('_'); + if (list.size() != 2) { + return FullMsgId(); + } + const auto peerId = PeerId(list[0].toLongLong()); + const auto msgId = MsgId(list[1].toLongLong()); + return FullMsgId(peerId, msgId); +} + } // namespace SponsoredMessages::SponsoredMessages(not_null owner) @@ -39,9 +64,14 @@ SponsoredMessages::SponsoredMessages(not_null owner) } SponsoredMessages::~SponsoredMessages() { + for (const auto &request : _requests) { + _session->sender().request(request.second.requestId).cancel(); + } +#if 0 // mtp for (const auto &request : _requests) { _session->api().request(request.second.requestId).cancel(); } +#endif } void SponsoredMessages::clearOldRequests() { @@ -201,6 +231,14 @@ void SponsoredMessages::request(not_null history, Fn done) { } const auto channel = history->peer->asChannel(); Assert(channel != nullptr); + request.requestId = _session->sender().request( + TLgetChatSponsoredMessages(peerToTdbChat(channel->id)) + ).done([=](const TLsponsoredMessages &result) { + parse(history, result); + if (done) { + done(); + } +#if 0 // mtp - chatInviteHash = link.internalLinkTypeChatInvite.invite_link request.requestId = _session->api().request( MTPchannels_GetSponsoredMessages( channel->inputChannel) @@ -209,6 +247,7 @@ void SponsoredMessages::request(not_null history, Fn done) { if (done) { done(); } +#endif }).fail([=] { _requests.remove(history); }).send(); @@ -216,7 +255,10 @@ void SponsoredMessages::request(not_null history, Fn done) { void SponsoredMessages::parse( not_null history, +#if 0 // mtp const MTPmessages_sponsoredMessages &list) { +#endif + const Tdb::TLsponsoredMessages &list) { auto &request = _requests[history]; request.lastReceived = crl::now(); request.requestId = 0; @@ -224,9 +266,12 @@ void SponsoredMessages::parse( _clearTimer.callOnce(kRequestTimeLimit * 2); } + list.match([&](const TLDsponsoredMessages &data) { +#if 0 // mtp list.match([&](const MTPDmessages_sponsoredMessages &data) { _session->data().processUsers(data.vusers()); _session->data().processChats(data.vchats()); +#endif const auto &messages = data.vmessages().v; auto &list = _data.emplace(history, List()).first->second; @@ -235,23 +280,35 @@ void SponsoredMessages::parse( for (const auto &message : messages) { append(history, list, message); } +#if 0 // mtp if (const auto postsBetween = data.vposts_between()) { list.postsBetween = postsBetween->v; +#endif + if (const auto postsBetween = data.vmessages_between().v) { + list.postsBetween = postsBetween; list.state = State::InjectToMiddle; } else { list.state = State::AppendToEnd; } +#if 0 // mtp }, [](const MTPDmessages_sponsoredMessagesEmpty &) { +#endif }); } void SponsoredMessages::append( not_null history, List &list, +#if 0 // mtp const MTPSponsoredMessage &message) { +#endif + const TLsponsoredMessage &message) { const auto &data = message.data(); +#if 0 // mtp const auto randomId = data.vrandom_id().v; const auto hash = qs(data.vchat_invite_hash().value_or_empty()); +#endif + const auto &sponsor = data.vsponsor().data(); const auto makeFrom = [&]( not_null peer, bool exactPost = false) { @@ -265,14 +322,35 @@ void SponsoredMessages::append( .isPublic = (channel && channel->isPublic()), .isBot = (peer->isUser() && peer->asUser()->isBot()), .isExactPost = exactPost, + .isRecommended = data.vis_recommended().v, + .userpic = {.location = peer->userpicLocation() }, + .isForceUserpicDisplay = sponsor.vphoto().has_value(), +#if 0 // mtp .isRecommended = data.is_recommended(), .userpic = { .location = peer->userpicLocation() }, .isForceUserpicDisplay = data.is_show_peer_photo(), +#endif }; }; - const auto externalLink = data.vwebpage() - ? qs(data.vwebpage()->data().vurl()) - : QString(); + auto externalLink = QString(); + auto internalLink = (const TLinternalLinkType*)nullptr; + auto inviteLink = QString(); + auto titleText = QString(); + auto peerId = PeerId(); + const auto sponsorPhoto = sponsor.vphoto(); + sponsor.vtype().match([&](const TLDmessageSponsorTypeBot &data) { + internalLink = &data.vlink(); + peerId = peerFromUser(data.vbot_user_id().v); + }, [&](const TLDmessageSponsorTypePublicChannel &data) { + internalLink = data.vlink(); + peerId = peerFromTdbChat(data.vchat_id()); + }, [&](const TLDmessageSponsorTypePrivateChannel &data) { + inviteLink = data.vinvite_link().v; + titleText = data.vtitle().v; + }, [&](const TLDmessageSponsorTypeWebsite &data) { + externalLink = data.vurl().v; + titleText = data.vname().v; + }); const auto userpicFromPhoto = [&](const MTPphoto &photo) { return photo.match([&](const MTPDphoto &data) { for (const auto &size : data.vsizes().v) { @@ -289,6 +367,7 @@ void SponsoredMessages::append( return ImageWithLocation{}; }); }; +#if 0 // mtp const auto from = [&]() -> SponsoredFrom { if (const auto webpage = data.vwebpage()) { const auto &data = webpage->data(); @@ -357,6 +436,53 @@ void SponsoredMessages::append( .sponsorInfo = std::move(sponsorInfo), .additionalInfo = std::move(additionalInfo), }; +#endif + const auto from = [&]() -> SponsoredFrom { + const auto exactPost = internalLink + && (internalLink->type() == id_internalLinkTypeMessage); + if (peerId) { + return makeFrom(_session->data().peer(peerId), exactPost); + } + auto userpic = sponsorPhoto + ? Images::FromTdbFile( + sponsorPhoto->data().vsmall(), + kSmallUserpicSize, + kSmallUserpicSize) + : ImageWithLocation{}; + + auto result = SponsoredFrom{ + .title = titleText, + .isChannel = true, + //.isMegagroup = ..., + //.isBroadcast = ..., + .userpic = std::move(userpic), + .isForceUserpicDisplay = sponsorPhoto.has_value(), + }; + return result; + }(); + Assert(data.vcontent().type() == id_messageText); + const auto invoke = internalLink + ? [=, link = *internalLink](QVariant context) { + return Core::HandleLocalUrl(link, context); + } : Fn(); + auto sponsorInfo = !sponsor.vinfo().v.isEmpty() + ? tr::lng_sponsored_info_submenu( + tr::now, + lt_text, + { .text = sponsor.vinfo().v }, + Ui::Text::RichLangValue) + : TextWithEntities(); + auto additionalInfo = TextWithEntities{ data.vadditional_info().v }; + auto sharedMessage = SponsoredMessage{ + .randomId = SerializeRandomId(history, data.vmessage_id().v), + .from = from, + .textWithEntities = Api::FormattedTextFromTdb( + data.vcontent().c_messageText().vtext()), + .history = history, + .invoke = invoke, + .sponsorInfo = std::move(sponsorInfo), + .additionalInfo = std::move(additionalInfo), + }; list.entries.push_back({ nullptr, std::move(sharedMessage) }); } @@ -405,6 +531,16 @@ void SponsoredMessages::view(const FullMsgId &fullId) { } const auto channel = entryPtr->item->history()->peer->asChannel(); Assert(channel != nullptr); + const auto [peerId, realId] = DeserializeRandomId(randomId); + Assert(peerId == channel->id); + Assert(realId != 0); + request.requestId = _session->sender().request(TLviewMessages( + peerToTdbChat(peerId), + tl_vector(1, tl_int53(realId.bare)), + tl_messageSourceChatHistory(), + tl_bool(false) + )).send(); +#if 0 // mtp request.requestId = _session->api().request( MTPchannels_ViewSponsoredMessage( channel->inputChannel, @@ -416,6 +552,7 @@ void SponsoredMessages::view(const FullMsgId &fullId) { }).fail([=] { _viewRequests.remove(randomId); }).send(); +#endif } SponsoredMessages::Details SponsoredMessages::lookupDetails( @@ -425,7 +562,9 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails( return {}; } const auto &data = entryPtr->sponsored; +#if 0 // mtp const auto &hash = data.chatInviteHash; +#endif using InfoList = std::vector; const auto info = (!data.sponsorInfo.text.isEmpty() @@ -436,12 +575,17 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails( : !data.additionalInfo.text.isEmpty() ? InfoList{ data.additionalInfo } : InfoList{}; +#if 0 // mtp return { .hash = hash.isEmpty() ? std::nullopt : std::make_optional(hash), .peer = data.from.peer, .msgId = data.msgId, +#endif + return { .info = std::move(info), .externalLink = data.externalLink, + .peer = entryPtr->sponsored.from.peer, + .invoke = entryPtr->sponsored.invoke, }; } @@ -450,6 +594,12 @@ void SponsoredMessages::clicked(const FullMsgId &fullId) { if (!entryPtr) { return; } + const auto id = DeserializeRandomId(entryPtr->sponsored.randomId); + _session->sender().request(TLclickChatSponsoredMessage( + peerToTdbChat(id.peer), + tl_int53(id.msg.bare) + )).send(); +#if 0 // mtp const auto randomId = entryPtr->sponsored.randomId; const auto channel = entryPtr->item->history()->peer->asChannel(); Assert(channel != nullptr); @@ -457,6 +607,7 @@ void SponsoredMessages::clicked(const FullMsgId &fullId) { channel->inputChannel, MTP_bytes(randomId) )).send(); +#endif } SponsoredMessages::State SponsoredMessages::state( diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.h b/Telegram/SourceFiles/data/data_sponsored_messages.h index 2c8b1e72b527a..2368a8d82de7e 100644 --- a/Telegram/SourceFiles/data/data_sponsored_messages.h +++ b/Telegram/SourceFiles/data/data_sponsored_messages.h @@ -13,6 +13,11 @@ For license and copyright information please follow this link: class History; +namespace Tdb { +class TLsponsoredMessage; +class TLsponsoredMessages; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -41,8 +46,11 @@ struct SponsoredMessage { SponsoredFrom from; TextWithEntities textWithEntities; History *history = nullptr; + Fn invoke; +#if 0 // mtp MsgId msgId; QString chatInviteHash; +#endif QString externalLink; TextWithEntities sponsorInfo; TextWithEntities additionalInfo; @@ -56,11 +64,15 @@ class SponsoredMessages final { InjectToMiddle, }; struct Details { +#if 0 // mtp std::optional hash; PeerData *peer = nullptr; MsgId msgId; +#endif std::vector info; QString externalLink; + PeerData *peer = nullptr; + Fn invoke; }; using RandomId = QByteArray; explicit SponsoredMessages(not_null owner); @@ -108,11 +120,17 @@ class SponsoredMessages final { void parse( not_null history, +#if 0 // mtp const MTPmessages_sponsoredMessages &list); +#endif + const Tdb::TLsponsoredMessages &list); void append( not_null history, List &list, +#if 0 // mtp const MTPSponsoredMessage &message); +#endif + const Tdb::TLsponsoredMessage &message); void clearOldRequests(); const Entry *find(const FullMsgId &fullId) const; diff --git a/Telegram/SourceFiles/data/data_statistics.h b/Telegram/SourceFiles/data/data_statistics.h index ce59e4d58f16b..aae4e79f94404 100644 --- a/Telegram/SourceFiles/data/data_statistics.h +++ b/Telegram/SourceFiles/data/data_statistics.h @@ -121,8 +121,11 @@ struct AnyStatistics final { struct PublicForwardsSlice final { struct OffsetToken final { + QString offset; +#if 0 // mtp int rate = 0; FullMsgId fullId; +#endif }; QVector list; int total = 0; diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index b37afa6d5ea9b..b32e1305b225e 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -25,9 +25,14 @@ For license and copyright information please follow this link: #include "ui/layers/show.h" #include "ui/text/text_utilities.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kMaxResolveTogether = 100; constexpr auto kIgnorePreloadAroundIfLoaded = 15; constexpr auto kPreloadAroundCount = 30; @@ -47,6 +52,7 @@ constexpr auto kPollingViewsPerPage = Story::kRecentViewersMax; using UpdateFlag = StoryUpdate::Flag; +#if 0 // mtp [[nodiscard]] std::optional ParseMedia( not_null owner, const MTPMessageMedia &media) { @@ -74,6 +80,30 @@ using UpdateFlag = StoryUpdate::Flag; return std::make_optional(StoryMedia{ v::null }); }, [](const auto &) { return std::optional(); }); } +#endif +[[nodiscard]] std::optional ParseMedia( + not_null owner, + const TLstoryContent &content) { + return content.match([&](const TLDstoryContentPhoto &data) + -> std::optional { + const auto result = owner->processPhoto(data.vphoto()); + if (!result->isNull()) { + return StoryMedia{ result }; + } + return {}; + }, [&](const TLDstoryContentVideo &data) + -> std::optional { + const auto result = owner->processDocument(data.vvideo()); + if (!result->isNull() + && (result->isGifv() || result->isVideoFile())) { + result->setStoryMedia(true); + return StoryMedia{ result }; + } + return {}; + }, [&](const TLDstoryContentUnsupported &data) { + return std::make_optional(StoryMedia{ v::null }); + }); +} } // namespace @@ -102,9 +132,11 @@ StoryIdDates StoriesSource::toOpen() const { Stories::Stories(not_null owner) : _owner(owner) +#if 0 // mtp , _expireTimer([=] { processExpired(); }) , _markReadTimer([=] { sendMarkAsReadRequests(); }) , _incrementViewsTimer([=] { sendIncrementViewsRequests(); }) +#endif , _pollingTimer([=] { sendPollingRequests(); }) , _pollingViewsTimer([=] { sendPollingViewsRequests(); }) { crl::on_main(this, [=] { @@ -147,6 +179,7 @@ Main::Session &Stories::session() const { return _owner->session(); } +#if 0 // mtp void Stories::apply(const MTPDupdateStory &data) { const auto peerId = peerFromMTP(data.vpeer()); const auto peer = _owner->peer(peerId); @@ -230,6 +263,7 @@ Story *Stories::applyFromWebpage(PeerId peerId, const MTPstoryItem &story) { : base::make_unexpected(NoStory::Deleted); return value ? value->get() : nullptr; } +#endif void Stories::requestPeerStories( not_null peer, @@ -248,6 +282,11 @@ void Stories::requestPeerStories( } } }; + session().sender().request(TLgetChatActiveStories( + peerToTdbChat(peer->id) + )).done([=](const TLchatActiveStories &result) { + parseAndApply(result); +#if 0 // mtp _owner->session().api().request(MTPstories_GetPeerStories( peer->input )).done([=](const MTPstories_PeerStories &result) { @@ -255,6 +294,7 @@ void Stories::requestPeerStories( _owner->processUsers(data.vusers()); _owner->processChats(data.vchats()); parseAndApply(data.vstories()); +#endif finish(); }).fail([=] { applyDeletedFromSources(peer->id, StorySourcesList::NotHidden); @@ -263,6 +303,7 @@ void Stories::requestPeerStories( }).send(); } +#if 0 // mtp void Stories::registerExpiring(TimeId expires, FullStoryId id) { for (auto i = _expiring.findFirst(expires) ; (i != end(_expiring)) && (i->first == expires) @@ -317,6 +358,7 @@ void Stories::processExpired() { scheduleExpireTimer(); } } +#endif Stories::Set *Stories::lookupArchive(not_null peer) { const auto peerId = peer->id; @@ -348,18 +390,31 @@ void Stories::clearArchive(not_null peer) { _archiveChanged.fire_copy(peerId); } +#if 0 // mtp void Stories::parseAndApply(const MTPPeerStories &stories) { const auto &data = stories.data(); const auto peerId = peerFromMTP(data.vpeer()); const auto already = _readTill.find(peerId); const auto readTill = std::max( data.vmax_read_id().value_or_empty(), +#endif +void Stories::parseAndApply(const TLchatActiveStories &stories) { + const auto &data = stories.data(); + const auto peerId = peerFromTdbChat(data.vchat_id()); + const auto hidden = data.vlist() + && (data.vlist()->type() == id_storyListArchive); + const auto already = _readTill.find(peerId); + const auto readTill = std::max( + data.vmax_read_story_id().v, (already != end(_readTill) ? already->second : 0)); const auto peer = _owner->peer(peerId); auto result = StoriesSource{ .peer = peer, .readTill = readTill, +#if 0 // mtp .hidden = peer->hasStoriesHidden(), +#endif + .hidden = hidden, }; const auto &list = data.vstories().v; const auto now = base::unixtime::now(); @@ -402,6 +457,8 @@ void Stories::parseAndApply(const MTPPeerStories &stories) { } sort(list); }; + +#if 0 // mtp if (result.peer->isSelf() || (result.peer->isChannel() && result.peer->asChannel()->amIn()) || (result.peer->isUser() @@ -409,6 +466,8 @@ void Stories::parseAndApply(const MTPPeerStories &stories) { || result.peer->asUser()->isContact())) || result.peer->isServiceUser()) { const auto hidden = result.peer->hasStoriesHidden(); +#endif + if (const auto list = data.vlist()) { using List = StorySourcesList; add(hidden ? List::Hidden : List::NotHidden); applyDeletedFromSources( @@ -419,22 +478,39 @@ void Stories::parseAndApply(const MTPPeerStories &stories) { applyDeletedFromSources(peerId, StorySourcesList::Hidden); } _sourceChanged.fire_copy(peerId); +#if 0 // mtp updatePeerStoriesState(result.peer); +#endif } +Story *Stories::parseAndApply(const TLstory &story, TimeId now) { + const auto peerId = peerFromTdbChat(story.data().vsender_chat_id()); + return parseAndApply(_owner->peer(peerId), story, now); +} + +#if 0 // mtp Story *Stories::parseAndApply( not_null peer, const MTPDstoryItem &data, TimeId now) { const auto media = ParseMedia(_owner, data.vmedia()); +#endif +Story *Stories::parseAndApply( + not_null peer, + const TLstory &story, + TimeId now) { + const auto &data = story.data(); + const auto media = ParseMedia(_owner, data.vcontent()); if (!media) { return nullptr; } +#if 0 // mtp const auto expires = data.vexpire_date().v; const auto expired = (expires <= now); if (expired && !data.is_pinned() && !hasArchive(peer)) { return nullptr; } +#endif const auto id = data.vid().v; const auto fullId = FullStoryId{ peer->id, id }; auto &stories = _stories[peer->id]; @@ -478,12 +554,14 @@ Story *Stories::parseAndApply( } } +#if 0 // mtp if (expired) { _expiring.remove(expires, fullId); applyExpired(fullId); } else { registerExpiring(expires, fullId); } +#endif if (wasDeleted) { _owner->refreshStoryItemViews(fullId); @@ -492,6 +570,7 @@ Story *Stories::parseAndApply( return result; } +#if 0 // mtp StoryIdDates Stories::parseAndApply( not_null peer, const MTPstoryItem &story, @@ -525,6 +604,18 @@ StoryIdDates Stories::parseAndApply( return StoryIdDates(); }); } +#endif + +StoryIdDates Stories::parseAndApply( + not_null peer, + const TLstoryInfo &story, + TimeId now) { + const auto &data = story.data(); + return StoryIdDates{ + data.vstory_id().v, + data.vdate().v, + }; +} void Stories::updateDependentMessages(not_null story) { const auto i = _dependentMessages.find(story); @@ -585,6 +676,17 @@ void Stories::loadMore(StorySourcesList list) { return; } const auto hidden = (list == StorySourcesList::Hidden); + const auto api = &session().sender(); + _loadMoreRequestId[index] = api->request(TLloadActiveStories( + hidden ? tl_storyListArchive() : tl_storyListMain() + )).done([=] { + _loadMoreRequestId[index] = 0; + preloadListsMore(); + }).fail([=] { + _loadMoreRequestId[index] = 0; + _sourcesLoaded[index] = true; + _sourcesChanged[index].fire({}); +#if 0 // mtp const auto api = &_owner->session().api(); using Flag = MTPstories_GetAllStories::Flag; _loadMoreRequestId[index] = api->request(MTPstories_GetAllStories( @@ -614,9 +716,45 @@ void Stories::loadMore(StorySourcesList list) { preloadListsMore(); }).fail([=] { _loadMoreRequestId[index] = 0; +#endif }).send(); } +void Stories::apply(const TLDupdateStory &data) { + parseAndApply(data.vstory(), base::unixtime::now()); +} + +void Stories::apply(const TLDupdateStoryDeleted &data) { + const auto peerId = peerFromTdbChat(data.vstory_sender_chat_id()); + if (const auto peer = session().data().peerLoaded(peerId)) { + applyDeleted(peer, data.vstory_id().v); + } +} + +void Stories::apply(const TLDupdateStoryListChatCount &data) { + const auto list = data.vstory_list().match([](TLDstoryListMain) { + return StorySourcesList::NotHidden; + }, [](TLDstoryListArchive) { + return StorySourcesList::Hidden; + }); + const auto index = static_cast(list); + const auto loaded = int(_sources[index].size()); + if (data.vchat_count().v > loaded && loaded < kMaxSegmentsCount) { + loadMore(list); + } +} + +void Stories::apply(const TLDupdateChatActiveStories &data) { + parseAndApply(data.vactive_stories()); +} + +void Stories::apply(const TLDupdateStoryStealthMode &data) { + _stealthMode = StealthMode{ + .enabledTill = data.vactive_until_date().v, + .cooldownTill = data.vcooldown_until_date().v, + }; +} + void Stories::preloadListsMore() { if (_loadMoreRequestId[static_cast(StorySourcesList::NotHidden)] || _loadMoreRequestId[static_cast(StorySourcesList::Hidden)]) { @@ -666,6 +804,7 @@ void Stories::pushHiddenCountsToFolder() { _folderForHidden->updateStoriesCount(count, unread); } +#if 0 // mtp void Stories::sendResolveRequests() { if (!_resolveSent.empty()) { return; @@ -745,6 +884,7 @@ void Stories::processResolvedStories( }); } } +#endif void Stories::finalizeResolve(FullStoryId id) { const auto already = lookup(id); @@ -775,7 +915,9 @@ void Stories::applyDeleted(not_null peer, StoryId id) { const auto &story = _deletingStories[fullId] = std::move(j->second); +#if 0 // mtp _expiring.remove(story->expires(), story->fullId()); +#endif i->second.erase(j); session().changes().storyUpdated( @@ -933,12 +1075,16 @@ rpl::producer Stories::stealthModeValue() const { } void Stories::activateStealthMode(Fn done) { +#if 0 // mtp const auto api = &session().api(); using Flag = MTPstories_ActivateStealthMode::Flag; api->request(MTPstories_ActivateStealthMode( MTP_flags(Flag::f_past | Flag::f_future) )).done([=](const MTPUpdates &result) { api->applyUpdates(result); +#endif + session().sender().request(TLactivateStoryStealthMode( + )).done([=] { if (done) done(); }).fail([=] { if (done) done(); @@ -950,6 +1096,15 @@ void Stories::sendReaction(FullStoryId id, Data::ReactionId reaction) { const auto story = *maybeStory; story->setReactionId(reaction); + session().sender().request(TLsetStoryReaction( + peerToTdbChat(id.peer), + tl_int32(id.story), + (reaction.empty() + ? std::optional() + : ReactionToTL(reaction)), + tl_bool(false) + )).send(); +#if 0 // mtp const auto api = &session().api(); api->request(MTPstories_SendReaction( MTP_flags(0), @@ -957,6 +1112,7 @@ void Stories::sendReaction(FullStoryId id, Data::ReactionId reaction) { MTP_int(id.story), ReactionToMTP(reaction) )).send(); +#endif } } @@ -1038,6 +1194,35 @@ void Stories::resolve(FullStoryId id, Fn done, bool force) { return; } } + const auto finish = [=] { + const auto i = _resolveSent.find(id.peer); + Assert(i != end(_resolveSent)); + const auto j = i->second.find(id.story); + Assert(j != end(i->second)); + const auto callbacks = std::move(j->second); + i->second.erase(j); + if (i->second.empty()) { + _resolveSent.erase(i); + } + finalizeResolve(id); + for (const auto &callback : callbacks) { + callback(); + } + _itemsChanged.fire_copy(id.peer); + }; + session().sender().request(TLgetStory( + peerToTdbChat(id.peer), + tl_int32(id.story), + tl_bool(false) // only_local + )).done([=](const TLstory &result) { + parseAndApply(result, base::unixtime::now()); + finish(); + }).fail(finish).send(); + auto &callbacks = _resolveSent[id.peer][id.story]; + if (done) { + callbacks.push_back(std::move(done)); + } +#if 0 // mtp auto &ids = _resolvePending[id.peer]; if (ids.empty()) { crl::on_main(&session(), [=] { @@ -1048,6 +1233,7 @@ void Stories::resolve(FullStoryId id, Fn done, bool force) { if (done) { callbacks.push_back(std::move(done)); } +#endif } void Stories::loadAround(FullStoryId id, StoriesContext context) { @@ -1099,20 +1285,24 @@ void Stories::markAsRead(FullStoryId id, bool viewed) { return; } const auto story = *maybeStory; +#if 0 // mtp if (story->expired() && story->pinned()) { _incrementViewsPending[id.peer].emplace(id.story); if (!_incrementViewsTimer.isActive()) { _incrementViewsTimer.callOnce(kIncrementViewsDelay); } } +#endif if (!bumpReadTill(id.peer, id.story)) { return; } +#if 0 // mtp if (!_markReadPending.contains(id.peer)) { sendMarkAsReadRequests(); } _markReadPending.emplace(id.peer); _markReadTimer.callOnce(kMarkAsReadDelay); +#endif } bool Stories::bumpReadTill(PeerId peerId, StoryId maxReadTill) { @@ -1171,6 +1361,11 @@ void Stories::toggleHidden( std::shared_ptr show) { const auto peer = _owner->peer(peerId); const auto justRemove = peer->isServiceUser() && hidden; + session().sender().request(TLsetChatActiveStoriesList( + peerToTdbChat(peerId), + (hidden ? tl_storyListArchive() : tl_storyListMain()) + )).send(); +#if 0 // mtp if (peer->hasStoriesHidden() != hidden) { if (!justRemove) { peer->setStoriesHidden(hidden); @@ -1180,6 +1375,7 @@ void Stories::toggleHidden( MTP_bool(hidden) )).send(); } +#endif const auto name = peer->shortName(); const auto guard = gsl::finally([&] { @@ -1196,7 +1392,14 @@ void Stories::toggleHidden( }); if (justRemove) { +#if 0 // mtp apply(peer, nullptr); +#endif + applyDeletedFromSources(peer->id, StorySourcesList::NotHidden); + applyDeletedFromSources(peer->id, StorySourcesList::Hidden); + _all.erase(peer->id); + _sourceChanged.fire_copy(peer->id); + peer->setStoriesState(PeerData::StoriesState::None); return; } @@ -1240,6 +1443,7 @@ void Stories::toggleHidden( } } +#if 0 // mtp void Stories::sendMarkAsReadRequest( not_null peer, StoryId tillId) { @@ -1260,16 +1464,20 @@ void Stories::sendMarkAsReadRequest( MTP_int(tillId) )).done(finish).fail(finish).send(); } +#endif void Stories::checkQuitPreventFinished() { +#if 0 // mtp if (_markReadRequests.empty() && _incrementViewsRequests.empty()) { if (Core::Quitting()) { LOG(("Stories doesn't prevent quit any more.")); } Core::App().quitPreventFinished(); } +#endif } +#if 0 // mtp void Stories::sendMarkAsReadRequests() { _markReadTimer.cancel(); for (auto i = begin(_markReadPending); i != end(_markReadPending);) { @@ -1324,6 +1532,7 @@ void Stories::sendIncrementViewsRequests() { _incrementViewsPending.remove(peer); } } +#endif void Stories::loadViewsSlice( not_null peer, @@ -1357,6 +1566,16 @@ void Stories::sendViewsSliceRequest() { Expects(_viewsStoryPeer != nullptr); Expects(_viewsStoryPeer->isSelf()); + session().sender().request(_viewsRequestId).cancel(); + _viewsRequestId = session().sender().request(TLgetStoryViewers( + tl_int32(_viewsStoryId), + tl_string(), // query + tl_bool(false), // only_contacts + tl_bool(true), // prefer_with_reaction + tl_string(_viewsOffset), + tl_int32(_viewsDone ? kViewsPerPage : kPollingViewsPerPage) + )).done([=](const TLstoryViewers &result) { +#if 0 // mtp using Flag = MTPstories_GetStoryViewsList::Flag; const auto api = &_owner->session().api(); _owner->session().api().request(_viewsRequestId).cancel(); @@ -1368,9 +1587,11 @@ void Stories::sendViewsSliceRequest() { MTP_string(_viewsOffset), MTP_int(_viewsDone ? kViewsPerPage : kPollingViewsPerPage) )).done([=](const MTPstories_StoryViewsList &result) { +#endif _viewsRequestId = 0; const auto &data = result.data(); +#if 0 // mtp auto slice = StoryViews{ .nextOffset = data.vnext_offset().value_or_empty(), .reactions = data.vreactions_count().v, @@ -1379,12 +1600,26 @@ void Stories::sendViewsSliceRequest() { _owner->processUsers(data.vusers()); slice.list.reserve(data.vviews().v.size()); for (const auto &view : data.vviews().v) { +#endif + auto slice = StoryViews{ + .nextOffset = data.vnext_offset().v, + //.reactions = data.vreactions_count().v, // tdlib + .total = data.vtotal_count().v, + }; + slice.list.reserve(data.vviewers().v.size()); + for (const auto &view : data.vviewers().v) { slice.list.push_back({ .peer = _owner->peer(peerFromUser(view.data().vuser_id())), +#if 0 // mtp .reaction = (view.data().vreaction() ? ReactionFromMTP(*view.data().vreaction()) : Data::ReactionId()), .date = view.data().vdate().v, +#endif + .reaction = (view.data().vchosen_reaction_type() + ? ReactionFromTL(*view.data().vchosen_reaction_type()) + : Data::ReactionId()), + .date = view.data().vview_date().v, }); } const auto fullId = FullStoryId{ @@ -1409,6 +1644,11 @@ void Stories::sendViewsCountsRequest() { Expects(_viewsStoryPeer != nullptr); Expects(!_viewsDone); + _viewsRequestId = -1; + resolve({ _viewsStoryPeer->id, _viewsStoryId }, [=] { + _viewsRequestId = 0; + }, true); +#if 0 // mtp const auto api = &_owner->session().api(); _owner->session().api().request(_viewsRequestId).cancel(); _viewsRequestId = api->request(MTPstories_GetStoriesViews( @@ -1431,6 +1671,7 @@ void Stories::sendViewsCountsRequest() { }).fail([=] { _viewsRequestId = 0; }).send(); +#endif } bool Stories::hasArchive(not_null peer) const { @@ -1498,12 +1739,22 @@ void Stories::archiveLoadMore(PeerId peerId) { if (!archive || archive->requestId || archive->loaded) { return; } + archive->requestId = session().sender().request( + TLgetChatArchivedStories( + peerToTdbChat(peerId), + tl_int32(archive->lastId), + tl_int32(archive->lastId + ? kArchivePerPage + : kArchiveFirstPerPage)) + ).done([=](const TLstories &result) { +#if 0 // mtp const auto api = &_owner->session().api(); archive->requestId = api->request(MTPstories_GetStoriesArchive( peer->input, MTP_int(archive->lastId), MTP_int(archive->lastId ? kArchivePerPage : kArchiveFirstPerPage) )).done([=](const MTPstories_Stories &result) { +#endif const auto archive = lookupArchive(peer); if (!archive) { return; @@ -1512,7 +1763,10 @@ void Stories::archiveLoadMore(PeerId peerId) { const auto &data = result.data(); const auto now = base::unixtime::now(); +#if 0 // mtp archive->total = data.vcount().v; +#endif + archive->total = data.vtotal_count().v; for (const auto &story : data.vstories().v) { const auto id = story.match([&](const auto &id) { return id.vid().v; @@ -1547,6 +1801,12 @@ void Stories::savedLoadMore(PeerId peerId) { if (saved.requestId || saved.loaded) { return; } + saved.requestId = session().sender().request(TLgetChatPinnedStories( + peerToTdbChat(peerId), + tl_int32(saved.lastId), + tl_int32(saved.lastId ? kSavedPerPage : kSavedFirstPerPage) + )).done([=](const TLstories &result) { +#if 0 // mtp const auto api = &_owner->session().api(); const auto peer = _owner->peer(peerId); saved.requestId = api->request(MTPstories_GetPinnedStories( @@ -1554,12 +1814,17 @@ void Stories::savedLoadMore(PeerId peerId) { MTP_int(saved.lastId), MTP_int(saved.lastId ? kSavedPerPage : kSavedFirstPerPage) )).done([=](const MTPstories_Stories &result) { +#endif auto &saved = _saved[peerId]; saved.requestId = 0; const auto &data = result.data(); const auto now = base::unixtime::now(); +#if 0 // mtp saved.total = data.vcount().v; +#endif + saved.total = data.vtotal_count().v; + const auto peer = _owner->peer(peerId); for (const auto &story : data.vstories().v) { const auto id = story.match([&](const auto &id) { return id.vid().v; @@ -1591,6 +1856,15 @@ void Stories::deleteList(const std::vector &ids) { return; } const auto peer = session().data().peer(ids.front().peer); + for (const auto &id : ids) { + if (id.peer == peer->id) { + session().sender().request(TLdeleteStory( + peerToTdbChat(id.peer), + tl_int32(id.story) + )).send(); + } + } +#if 0 // mtp auto list = QVector(); list.reserve(ids.size()); for (const auto &id : ids) { @@ -1607,6 +1881,7 @@ void Stories::deleteList(const std::vector &ids) { applyDeleted(peer, id.v); } }).send(); +#endif } void Stories::togglePinnedList( @@ -1616,6 +1891,54 @@ void Stories::togglePinnedList( return; } const auto peer = session().data().peer(ids.front().peer); + for (const auto &storyId : ids) { + if (storyId.peer == peer->id) { + const auto id = storyId.story; + session().sender().request(TLtoggleStoryIsPinned( + peerToTdbChat(storyId.peer), + tl_int32(id), + tl_bool(pinned) + )).done([=] { + const auto peerId = peer->id; + auto &saved = _saved[peerId]; + const auto loaded = saved.loaded; + const auto lastId = !saved.ids.list.empty() + ? saved.ids.list.back() + : saved.lastId + ? saved.lastId + : std::numeric_limits::max(); + auto dirty = false; + if (const auto maybeStory = lookup({ peerId, id })) { + const auto story = *maybeStory; + story->setPinned(pinned); + if (pinned) { + const auto add = loaded || (id >= lastId); + if (!add) { + dirty = true; + } else if (saved.ids.list.emplace(id).second) { + if (saved.total >= 0) { + ++saved.total; + } + } + } else if (saved.ids.list.remove(id)) { + if (saved.total > 0) { + --saved.total; + } + } else if (!loaded) { + dirty = true; + } + } else if (!loaded) { + dirty = true; + } + if (dirty) { + savedLoadMore(peerId); + } else { + _savedChanged.fire_copy(peerId); + } + }).send(); + } + } +#if 0 // mtp auto list = QVector(); list.reserve(ids.size()); for (const auto &id : ids) { @@ -1671,6 +1994,7 @@ void Stories::togglePinnedList( _savedChanged.fire_copy(peerId); } }).send(); +#endif } void Stories::report( @@ -1685,6 +2009,7 @@ void Stories::report( } bool Stories::isQuitPrevent() { +#if 0 // mtp if (!_markReadPending.empty()) { sendMarkAsReadRequests(); } @@ -1696,6 +2021,8 @@ bool Stories::isQuitPrevent() { } LOG(("Stories prevents quit, marking as read...")); return true; +#endif + return false; } void Stories::incrementPreloadingMainSources() { @@ -1744,6 +2071,7 @@ void Stories::setPreloadingInViewer(std::vector ids) { } } +#if 0 // mtp std::optional Stories::peerSourceState( not_null peer, StoryId storyMaxId) { @@ -1778,14 +2106,17 @@ void Stories::requestReadTills() { } }).send(); } +#endif bool Stories::isUnread(not_null story) { const auto till = _readTill.find(story->peer()->id); +#if 0 // mtp if (till == end(_readTill) && !_readTillReceived) { requestReadTills(); _pendingReadTillItems.emplace(story->fullId()); return false; } +#endif const auto readTill = (till != end(_readTill)) ? till->second : 0; return (story->id() > readTill); } @@ -1800,6 +2131,10 @@ void Stories::registerPolling(not_null story, Polling polling) { && _pollingViews.emplace(story).second) { sendPollingViewsRequests(); } + session().sender().request(TLopenStory( + peerToTdbChat(story->peer()->id), + tl_int32(story->id()) + )).send(); break; } maybeSchedulePolling(story, settings, base::unixtime::now()); @@ -1822,6 +2157,10 @@ void Stories::unregisterPolling(not_null story, Polling polling) { _pollingViewsTimer.cancel(); } } + session().sender().request(TLcloseStory( + peerToTdbChat(story->peer()->id), + tl_int32(story->id()) + )).send(); break; } if (!i->second.chat && !i->second.viewer) { @@ -1898,14 +2237,21 @@ void Stories::sendPollingViewsRequests() { void Stories::updatePeerStoriesState(not_null peer) { const auto till = _readTill.find(peer->id); const auto readTill = (till != end(_readTill)) ? till->second : 0; +#if 0 // mtp const auto pendingMaxId = [&] { const auto j = _pendingPeerStateMaxId.find(peer); return (j != end(_pendingPeerStateMaxId)) ? j->second : 0; }; +#endif + const auto pendingMaxId = [] { return 0; }; const auto i = _all.find(peer->id); const auto max = (i != end(_all)) ? (i->second.ids.empty() ? 0 : i->second.ids.back().id) : pendingMaxId(); + if (i == end(_all)) { + // With TDLib we don't update users with unknown stories, lib does. + return; + } peer->setStoriesState(!max ? PeerData::StoriesState::None : (max <= readTill) @@ -1963,6 +2309,10 @@ void Stories::continuePreloading() { return; } else if (const auto maybeStory = lookup(id)) { startPreloading(*maybeStory); + } else if (maybeStory.error() == NoStory::Unknown) { + resolve(id, [=] { + continuePreloading(); + }); } } diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 16cc27d38f971..c72522568dc19 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -13,6 +13,17 @@ For license and copyright information please follow this link: #include "base/weak_ptr.h" #include "data/data_story.h" +namespace Tdb { +class TLchatActiveStories; +class TLstoryInfo; +class TLstory; +class TLDupdateStory; +class TLDupdateStoryDeleted; +class TLDupdateStoryListChatCount; +class TLDupdateChatActiveStories; +class TLDupdateStoryStealthMode; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -145,11 +156,18 @@ class Stories final : public base::has_weak_ptr { not_null dependency); void loadMore(StorySourcesList list); + void apply(const Tdb::TLDupdateStory &data); + void apply(const Tdb::TLDupdateStoryDeleted &data); + void apply(const Tdb::TLDupdateStoryListChatCount &data); + void apply(const Tdb::TLDupdateChatActiveStories &data); + void apply(const Tdb::TLDupdateStoryStealthMode &data); +#if 0 // mtp void apply(const MTPDupdateStory &data); void apply(const MTPDupdateReadStories &data); void apply(const MTPStoriesStealthMode &stealthMode); void apply(not_null peer, const MTPPeerStories *data); Story *applyFromWebpage(PeerId peerId, const MTPstoryItem &story); +#endif void loadAround(FullStoryId id, StoriesContext context); const StoriesSource *source(PeerId id) const; @@ -213,6 +231,7 @@ class Stories final : public base::has_weak_ptr { void decrementPreloadingHiddenSources(); void setPreloadingInViewer(std::vector ids); +#if 0 // mtp struct PeerSourceState { StoryId maxId = 0; StoryId readTill = 0; @@ -220,6 +239,7 @@ class Stories final : public base::has_weak_ptr { [[nodiscard]] std::optional peerSourceState( not_null peer, StoryId storyMaxId); +#endif [[nodiscard]] bool isUnread(not_null story); enum class Polling { @@ -259,6 +279,17 @@ class Stories final : public base::has_weak_ptr { int viewer = 0; }; + void parseAndApply(const Tdb::TLchatActiveStories &stories); + Story *parseAndApply(const Tdb::TLstory &story, TimeId now); + Story *parseAndApply( + not_null peer, + const Tdb::TLstory &story, + TimeId now); + StoryIdDates parseAndApply( + not_null peer, + const Tdb::TLstoryInfo &story, + TimeId now); +#if 0 // mtp void parseAndApply(const MTPPeerStories &stories); [[nodiscard]] Story *parseAndApply( not_null peer, @@ -272,6 +303,7 @@ class Stories final : public base::has_weak_ptr { not_null peer, const QVector &list); void sendResolveRequests(); +#endif void finalizeResolve(FullStoryId id); void updatePeerStoriesState(not_null peer); @@ -285,16 +317,20 @@ class Stories final : public base::has_weak_ptr { void removeDependencyStory(not_null story); void sort(StorySourcesList list); bool bumpReadTill(PeerId peerId, StoryId maxReadTill); +#if 0 // mtp void requestReadTills(); void sendMarkAsReadRequests(); void sendMarkAsReadRequest(not_null peer, StoryId tillId); void sendIncrementViewsRequests(); +#endif void checkQuitPreventFinished(); +#if 0 // mtp void registerExpiring(TimeId expires, FullStoryId id); void scheduleExpireTimer(); void processExpired(); +#endif void preloadSourcesChanged(StorySourcesList list); bool rebuildPreloadSources(StorySourcesList list); @@ -327,15 +363,21 @@ class Stories final : public base::has_weak_ptr { std::unordered_map< PeerId, base::flat_map>> _items; +#if 0 // mtp base::flat_multi_map _expiring; base::flat_set _peersWithDeletedStories; base::flat_set _deleted; base::Timer _expireTimer; bool _expireSchedulePosted = false; +#endif + base::flat_set _peersWithDeletedStories; + base::flat_set _deleted; +#if 0 // mtp base::flat_map< PeerId, base::flat_map>>> _resolvePending; +#endif base::flat_map< PeerId, base::flat_map>>> _resolveSent; @@ -362,16 +404,20 @@ class Stories final : public base::has_weak_ptr { std::unordered_map _saved; rpl::event_stream _savedChanged; +#if 0 // mtp base::flat_set _markReadPending; base::Timer _markReadTimer; base::flat_set _markReadRequests; +#endif base::flat_map< not_null, std::vector>> _requestingPeerStories; +#if 0 // mtp base::flat_map> _incrementViewsPending; base::Timer _incrementViewsTimer; base::flat_set _incrementViewsRequests; +#endif PeerData *_viewsStoryPeer = nullptr; StoryId _viewsStoryId = 0; @@ -387,10 +433,12 @@ class Stories final : public base::has_weak_ptr { int _preloadingMainSourcesCounter = 0; base::flat_map _readTill; +#if 0 // mtp base::flat_set _pendingReadTillItems; base::flat_map, StoryId> _pendingPeerStateMaxId; mtpRequestId _readTillsRequestId = 0; bool _readTillReceived = false; +#endif base::flat_map, PollingSettings> _pollingSettings; base::flat_set> _pollingViews; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 6aba706b9ec31..daa590738cb5c 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -27,11 +27,18 @@ For license and copyright information please follow this link: #include "storage/file_download.h" // kMaxFileInMemory #include "ui/text/text_utilities.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_account.h" + namespace Data { namespace { +using namespace Tdb; + using UpdateFlag = StoryUpdate::Flag; +#if 0 // mtp [[nodiscard]] StoryArea ParseArea(const MTPMediaAreaCoordinates &area) { const auto &data = area.data(); const auto center = QPointF(data.vx().v, data.vy().v); @@ -104,9 +111,217 @@ using UpdateFlag = StoryUpdate::Flag; }); return result; } +#endif + +[[nodiscard]] StoryArea ParseArea(const TLstoryAreaPosition &position) { + const auto &data = position.data(); + const auto center = QPointF( + data.vx_percentage().v, + data.vy_percentage().v); + const auto size = QSizeF( + data.vwidth_percentage().v, + data.vheight_percentage().v); + const auto corner = center - QPointF(size.width(), size.height()) / 2.; + return { + .geometry = { corner / 100., size / 100. }, + .rotation = data.vrotation_angle().v, + }; +} + +[[nodiscard]] auto ParseLocation(const TLstoryArea &area) +-> std::optional { + const auto &data = area.data(); + const auto &position = data.vposition(); + auto result = std::optional(); + data.vtype().match([&](const TLDstoryAreaTypeLocation &data) { + result.emplace(StoryLocation{ + .area = ParseArea(position), + .point = Data::LocationPoint(data.vlocation()), + }); + }, [&](const TLDstoryAreaTypeVenue &data) { + const auto &fields = data.vvenue().data(); + result.emplace(StoryLocation{ + .area = ParseArea(position), + .point = Data::LocationPoint(fields.vlocation()), + .title = fields.vtitle().v, + .address = fields.vaddress().v, + .provider = fields.vprovider().v, + .venueId = fields.vid().v, + .venueType = fields.vtype().v, + }); + }, [](const TLDstoryAreaTypeSuggestedReaction &) { + }); + return result; +} + +[[nodiscard]] auto ParseSuggestedReaction(const TLstoryArea &area) +-> std::optional { + auto result = std::optional(); + const auto &data = area.data(); + const auto &position = data.vposition(); + data.vtype().match([](const TLDstoryAreaTypeLocation &) { + }, [](const TLDstoryAreaTypeVenue &) { + }, [&](const TLDstoryAreaTypeSuggestedReaction &data) { + result.emplace(SuggestedReaction{ + .area = ParseArea(position), + .reaction = Data::ReactionFromTL(data.vreaction_type()), + .count = data.vtotal_count().v, + .flipped = data.vis_flipped().v, + .dark = data.vis_dark().v, + }); + }); + return result; +} } // namespace +class StoryPreload::LoadTask final : public base::has_weak_ptr { +public: + LoadTask( + FullStoryId id, + not_null document, + Fn done); + ~LoadTask(); + +private: + void startWith(const TLDlocalFile &data); + bool continueWith(const TLDlocalFile &data); + void finishWith(const TLDlocalFile &data); + + const not_null _session; + Fn _done; + Sender _sender; + int64 _full = 0; + FileId _fileId = 0; + int _prefix = 0; + + rpl::lifetime _downloadLifetime; + +}; + +[[nodiscard]] int ChoosePreloadPrefix(not_null document) { + const auto prefix = document->videoPreloadPrefix(); + const auto part = Storage::kDownloadPartSize; + const auto parts = (prefix + part - 1) / part; + return std::min(int64(parts) * part, document->size); +} + +[[nodiscard]] QByteArray PackPreload(const QByteArray &bytes, int64 full) { + if (bytes.isEmpty()) { + return {}; + } + + auto parts = base::flat_map(); + const auto part = Storage::kDownloadPartSize; + const auto size = int(bytes.size()); + const auto count = (size + part - 1) / part; + parts.reserve(count); + const auto begin = bytes.constData(); + const auto end = begin + size; + for (auto data = begin; data < end; data += part) { + const auto offset = int(data - begin); + const auto length = std::min(part, size - offset); + parts.emplace(offset, QByteArray::fromRawData(data, length)); + } + auto result = ::Media::Streaming::SerializeComplexPartsMap(parts); + if (result.size() == full) { + // Make sure it is parsed as a complex map. + result.push_back(char(0)); + } + return result; +} + +StoryPreload::LoadTask::LoadTask( + FullStoryId id, + not_null document, + Fn done) +: _session(&document->session()) +, _done(std::move(done)) +, _sender(&_session->sender()) +, _full(document->size) +, _fileId(document->tdbFileId()) +, _prefix(ChoosePreloadPrefix(document)) { + Expects(_prefix > 0 && _prefix <= _full); + + if (!_fileId) { + crl::on_main(this, [=] { + if (const auto onstack = _done) { + onstack({}); + } + }); + return; + } + _sender.request(TLgetFile( + tl_int32(_fileId) + )).done([=](const TLDfile &data) { + startWith(data.vlocal().data()); + }).fail([=] { + _done({}); + }).send(); +} + +void StoryPreload::LoadTask::startWith(const TLDlocalFile &data) { + if (data.vdownloaded_prefix_size().v > 0 + || data.vis_downloading_active().v) { + finishWith(data); + } else { + _sender.request(TLdownloadFile( + tl_int32(_fileId), + tl_int32(kDefaultDownloadPriority), + tl_int53(0), // offset + tl_int53(_prefix), + tl_bool(false) // synchronous + )).done([=](const TLDfile &data) { + if (continueWith(data.vlocal().data())) { + _downloadLifetime = _session->tdb().updates( + ) | rpl::start_with_next([=](const Tdb::TLupdate &update) { + if (update.type() == Tdb::id_updateFile) { + const auto &file = update.c_updateFile().vfile(); + if (file.data().vid().v == _fileId) { + continueWith(file.data().vlocal().data()); + } + } + }); + } + }).fail([=] { + _done({}); + }).send(); + } +} + +bool StoryPreload::LoadTask::continueWith(const TLDlocalFile &data) { + if (!data.vis_downloading_active().v + || data.vdownload_offset().v > 0 + || data.vdownloaded_prefix_size().v >= _prefix) { + finishWith(data); + return false; + } + return true; +} + +void StoryPreload::LoadTask::finishWith(const TLDlocalFile &data) { + _downloadLifetime.destroy(); + const auto loaded = !data.vdownload_offset().v + && (data.vdownloaded_prefix_size().v >= _prefix); + if (loaded) { + _sender.request(TLreadFilePart( + tl_int32(_fileId), + tl_int53(0), // offset + tl_int53(_prefix) + )).done([=](const TLDfilePart &data) { + _done(PackPreload(data.vdata().v, _full)); + }).fail([=] { + _done({}); + }).send(); + } else { + _done({}); + } +} + +StoryPreload::LoadTask::~LoadTask() { +} + +#if 0 // mtp class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { public: LoadTask( @@ -207,17 +422,24 @@ bool StoryPreload::LoadTask::setWebFileSizeHook(int64 size) { _done({}); return false; } +#endif Story::Story( StoryId id, not_null peer, StoryMedia media, + const TLDstory &data, +#if 0 // mtp const MTPDstoryItem &data, +#endif TimeId now) : _id(id) , _peer(peer) , _date(data.vdate().v) +#if 0 // mtp , _expires(data.vexpire_date().v) { +#endif +{ applyFields(std::move(media), data, now, true); } @@ -242,7 +464,10 @@ bool Story::mine() const { } StoryIdDates Story::idDates() const { +#if 0 // mtp return { _id, _date, _expires }; +#endif + return { _id, _date }; } FullStoryId Story::fullId() const { @@ -253,12 +478,17 @@ TimeId Story::date() const { return _date; } +#if 0 // mtp TimeId Story::expires() const { return _expires; } +#endif bool Story::expired(TimeId now) const { +#if 0 // mtp return _expires <= (now ? now : base::unixtime::now()); +#endif + return _expired; } bool Story::unsupported() const { @@ -350,9 +580,15 @@ bool Story::edited() const { return _edited; } +#if 0 // mtp bool Story::out() const { return _out; } +#endif + +bool Story::canToggleIsPinned() const { + return _canToggleIsPinned; +} bool Story::canDownloadIfPremium() const { return !forbidsForward() || _peer->isSelf(); @@ -368,11 +604,14 @@ bool Story::canShare() const { } bool Story::canDelete() const { + return _canDelete; +#if 0 // mtp if (const auto channel = _peer->asChannel()) { return channel->canDeleteStories() || (out() && channel->canPostStories()); } return _peer->isSelf(); +#endif } bool Story::canReport() const { @@ -467,11 +706,14 @@ int Story::reactions() const { void Story::applyViewsSlice( const QString &offset, const StoryViews &slice) { +#if 0 // mtp const auto changed = (_views.reactions != slice.reactions) || (_views.total != slice.total); _views.reactions = slice.reactions; _views.total = slice.total; _views.known = true; +#endif + const auto changed = false; // We update counts only from TLstory. if (offset.isEmpty()) { _views = slice; } else if (_views.nextOffset == offset) { @@ -523,11 +765,15 @@ const std::vector &Story::suggestedReactions() const { void Story::applyChanges( StoryMedia media, + const TLDstory &data, +#if 0 // mtp const MTPDstoryItem &data, +#endif TimeId now) { applyFields(std::move(media), data, now, false); } +#if 0 // mtp Story::ViewsCounts Story::parseViewsCounts( const MTPDstoryViews &data, const Data::ReactionId &mine) { @@ -567,14 +813,38 @@ Story::ViewsCounts Story::parseViewsCounts( } return result; } +#endif +Story::ViewsCounts Story::parseViewsCounts( + const TLDstoryInteractionInfo &data, + const Data::ReactionId &mine) { + auto result = ViewsCounts{ + .views = data.vview_count().v, + .reactions = data.vreaction_count().v, + }; + const auto list = &data.vrecent_viewer_user_ids(); + { + result.viewers.reserve(list->v.size()); + auto &owner = _peer->owner(); + auto &&cut = list->v + | ranges::views::take(kRecentViewersMax); + for (const auto &id : cut) { + result.viewers.push_back(owner.peer(peerFromUser(id))); + } + } + return result; +} void Story::applyFields( StoryMedia media, + const TLDstory &data, +#if 0 // mtp const MTPDstoryItem &data, +#endif TimeId now, bool initial) { _lastUpdateTime = now; +#if 0 // mtp const auto reaction = data.is_min() ? _sentReactionId : data.vsent_reaction() @@ -604,9 +874,30 @@ void Story::applyFields( caption = StripLinks(std::move(caption)); } } +#endif + const auto reaction = data.vchosen_reaction_type() + ? Data::ReactionFromTL(*data.vchosen_reaction_type()) + : Data::ReactionId(); + const auto pinned = data.vis_pinned().v; + const auto edited = data.vis_edited().v; + const auto privacy = data.vprivacy_settings().match([]( + const TLDstoryPrivacySettingsEveryone &) { + return StoryPrivacy::Public; + }, [](const TLDstoryPrivacySettingsCloseFriends &) { + return StoryPrivacy::CloseFriends; + }, [](const TLDstoryPrivacySettingsContacts &) { + return StoryPrivacy::Contacts; + }, [](const TLDstoryPrivacySettingsSelectedUsers &) { + return StoryPrivacy::SelectedContacts; + }); + const auto noForwards = !data.vcan_be_forwarded().v; + auto caption = Api::FormattedTextFromTdb(data.vcaption()); auto counts = ViewsCounts(); auto viewsKnown = _views.known; +#if 0 // mtp if (const auto info = data.vviews()) { +#endif + if (const auto info = data.vinteraction_info()) { counts = parseViewsCounts(info->data(), reaction); viewsKnown = true; } else { @@ -621,18 +912,24 @@ void Story::applyFields( } auto locations = std::vector(); auto suggestedReactions = std::vector(); +#if 0 // mtp if (const auto areas = data.vmedia_areas()) { +#endif // mtp + { + const auto areas = &data.vareas(); locations.reserve(areas->v.size()); suggestedReactions.reserve(areas->v.size()); for (const auto &area : areas->v) { if (const auto location = ParseLocation(area)) { locations.push_back(*location); } else if (auto reaction = ParseSuggestedReaction(area)) { +#if 0 // mtp const auto i = counts.reactionsCounts.find( reaction->reaction); if (i != end(counts.reactionsCounts)) { reaction->count = i->second; } +#endif suggestedReactions.push_back(*reaction); } } @@ -647,7 +944,13 @@ void Story::applyFields( = (_suggestedReactions != suggestedReactions); const auto reactionChanged = (_sentReactionId != reaction); +#if 0 // mtp _out = out; +#endif + _canEdit = data.vcan_be_edited().v; + _canDelete = data.vcan_be_deleted().v; + _canToggleIsPinned = data.vcan_toggle_is_pinned().v; + _privacyPublic = (privacy == StoryPrivacy::Public); _privacyCloseFriends = (privacy == StoryPrivacy::CloseFriends); _privacyContacts = (privacy == StoryPrivacy::Contacts); @@ -715,6 +1018,7 @@ void Story::updateViewsCounts(ViewsCounts &&counts, bool known, bool initial) { } } +#if 0 // mtp void Story::applyViewsCounts(const MTPDstoryViews &data) { auto counts = parseViewsCounts(data, _sentReactionId); auto suggestedCountsChanged = false; @@ -731,6 +1035,7 @@ void Story::applyViewsCounts(const MTPDstoryViews &data) { _peer->session().changes().storyUpdated(this, UpdateFlag::Reaction); } } +#endif TimeId Story::lastUpdateTime() const { return _lastUpdateTime; @@ -807,7 +1112,10 @@ void StoryPreload::load() { Expects(_story->document() != nullptr); const auto video = _story->document(); +#if 0 // mtp const auto valid = video->videoPreloadLocation().valid(); +#endif + const auto valid = (video->tdbFileId() != 0); const auto prefix = video->videoPreloadPrefix(); const auto key = video->bigFileBaseCacheKey(); if (!valid || prefix <= 0 || prefix > video->size || !key) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 7ebe6bbdebe77..c34ccd942430b 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -11,6 +11,11 @@ For license and copyright information please follow this link: #include "data/data_location.h" #include "data/data_message_reaction_id.h" +namespace Tdb { +class TLDstory; +class TLDstoryInteractionInfo; +} // namespace Tdb + class Image; class PhotoData; class DocumentData; @@ -36,7 +41,9 @@ enum class StoryPrivacy : uchar { struct StoryIdDates { StoryId id = 0; TimeId date = 0; +#if 0 // mtp TimeId expires = 0; +#endif [[nodiscard]] bool valid() const { return id != 0; @@ -115,7 +122,10 @@ class Story final { StoryId id, not_null peer, StoryMedia media, + const Tdb::TLDstory &data, +#if 0 // mtp const MTPDstoryItem &data, +#endif TimeId now); static constexpr int kRecentViewersMax = 3; @@ -129,7 +139,9 @@ class Story final { [[nodiscard]] StoryIdDates idDates() const; [[nodiscard]] FullStoryId fullId() const; [[nodiscard]] TimeId date() const; +#if 0 // mtp [[nodiscard]] TimeId expires() const; +#endif [[nodiscard]] bool unsupported() const; [[nodiscard]] bool expired(TimeId now = 0) const; [[nodiscard]] const StoryMedia &media() const; @@ -145,7 +157,10 @@ class Story final { [[nodiscard]] StoryPrivacy privacy() const; [[nodiscard]] bool forbidsForward() const; [[nodiscard]] bool edited() const; +#if 0 // mtp [[nodiscard]] bool out() const; +#endif + [[nodiscard]] bool canToggleIsPinned() const; [[nodiscard]] bool canDownloadIfPremium() const; [[nodiscard]] bool canDownloadChecked() const; @@ -176,9 +191,14 @@ class Story final { void applyChanges( StoryMedia media, + const Tdb::TLDstory &data, +#if 0 // mtp const MTPDstoryItem &data, +#endif TimeId now); +#if 0 // mtp void applyViewsCounts(const MTPDstoryViews &data); +#endif [[nodiscard]] TimeId lastUpdateTime() const; private: @@ -192,15 +212,22 @@ class Story final { void changeSuggestedReactionCount(Data::ReactionId id, int delta); void applyFields( StoryMedia media, + const Tdb::TLDstory &data, +#if 0 // mtp const MTPDstoryItem &data, +#endif TimeId now, bool initial); void updateViewsCounts(ViewsCounts &&counts, bool known, bool initial); [[nodiscard]] ViewsCounts parseViewsCounts( +#if 0 // mtp const MTPDstoryViews &data, +#endif + const Tdb::TLDstoryInteractionInfo &data, const Data::ReactionId &mine); + const StoryId _id = 0; const not_null _peer; Data::ReactionId _sentReactionId; @@ -211,9 +238,16 @@ class Story final { std::vector _suggestedReactions; StoryViews _views; const TimeId _date = 0; +#if 0 // mtp const TimeId _expires = 0; +#endif TimeId _lastUpdateTime = 0; +#if 0 // mtp bool _out : 1 = false; +#endif + bool _canEdit : 1 = false; + bool _canDelete : 1 = false; + bool _canToggleIsPinned : 1 = false; bool _pinned : 1 = false; bool _privacyPublic : 1 = false; bool _privacyCloseFriends : 1 = false; @@ -221,6 +255,7 @@ class Story final { bool _privacySelectedContacts : 1 = false; bool _noForwards : 1 = false; bool _edited : 1 = false; + bool _expired : 1 = false; }; diff --git a/Telegram/SourceFiles/data/data_subscription_option.h b/Telegram/SourceFiles/data/data_subscription_option.h index 101c20248eacd..a2ff4f56e8163 100644 --- a/Telegram/SourceFiles/data/data_subscription_option.h +++ b/Telegram/SourceFiles/data/data_subscription_option.h @@ -16,6 +16,8 @@ struct SubscriptionOption { QString costTotal; QString total; QString botUrl; + + Fn startPayment; }; using SubscriptionOptions = std::vector; diff --git a/Telegram/SourceFiles/data/data_types.cpp b/Telegram/SourceFiles/data/data_types.cpp index 997d4282ebef2..9ac8c1ab45b14 100644 --- a/Telegram/SourceFiles/data/data_types.cpp +++ b/Telegram/SourceFiles/data/data_types.cpp @@ -22,6 +22,7 @@ constexpr auto kAudioAlbumThumbCacheTag = 0x0000000000000300ULL; constexpr auto kWebDocumentCacheTag = 0x0000020000000000ULL; constexpr auto kUrlCacheTag = 0x0000030000000000ULL; constexpr auto kGeoPointCacheTag = 0x0000040000000000ULL; +constexpr auto kTdbCacheTag = 0x0000050000000000ULL; } // namespace @@ -95,6 +96,13 @@ Storage::Cache::Key AudioAlbumThumbCacheKey( }; } +Storage::Cache::Key TdbFileCacheKey(FileId fileId) { + return Storage::Cache::Key{ + Data::kTdbCacheTag, + uint32(fileId) + }; +} + } // namespace Data void MessageCursor::fillFrom(not_null field) { diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 8029c5eeb1a11..ee2be9eb86dc7 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -51,6 +51,7 @@ Storage::Cache::Key UrlCacheKey(const QString &location); Storage::Cache::Key GeoPointCacheKey(const GeoPointLocation &location); Storage::Cache::Key AudioAlbumThumbCacheKey( const AudioAlbumThumbLocation &location); +Storage::Cache::Key TdbFileCacheKey(FileId fileId); constexpr auto kImageCacheTag = uint8(0x01); constexpr auto kStickerCacheTag = uint8(0x02); @@ -99,6 +100,7 @@ class UserData; class ChatData; class ChannelData; struct BotInfo; +class SecretChatData; namespace Data { class Folder; @@ -133,7 +135,10 @@ using WebPageId = uint64; using GameId = uint64; using PollId = uint64; using WallPaperId = uint64; +#if 0 // goodToRemove using CallId = uint64; +#endif +using CallId = int; using BotAppId = uint64; constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL); @@ -228,8 +233,12 @@ struct StickerSetIdentifier { }; enum class MessageFlag : uint64 { +#if 0 // mtp HideEdited = (1ULL << 0), Legacy = (1ULL << 1), +#endif + CanDeleteForAll = (1ULL << 0), + CanEdit = (1ULL << 1), HasReplyMarkup = (1ULL << 2), HasFromId = (1ULL << 3), HasPostAuthor = (1ULL << 4), @@ -259,7 +268,7 @@ enum class MessageFlag : uint64 { // Edited media is generated on the client // and should not update media from server. - IsLocalUpdateMedia = (1ULL << 22), + IsLocalUpdateMedia = (1ULL << 22), // mtp // Sent from inline bot, need to re-set media when sent. FromInlineBot = (1ULL << 23), @@ -309,6 +318,8 @@ enum class MessageFlag : uint64 { // If not set then we need to refresh _displayFrom value. DisplayFromChecked = (1ULL << 40), + + HideNotificationText = (1ULL << 63), // added for tdlib }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index f385b8c13fbca..516a4aa378de0 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -23,6 +23,9 @@ For license and copyright information please follow this link: #include "api/api_peer_photo.h" #include "apiwrap.h" #include "ui/text/text_options.h" + +#include "tdb/tdb_tl_scheme.h" +#include "apiwrap.h" #include "lang/lang_keys.h" #include "styles/style_chat.h" @@ -31,6 +34,7 @@ namespace { // User with hidden last seen stays online in UI for such amount of seconds. constexpr auto kSetOnlineAfterActivity = TimeId(30); +using namespace Tdb; using UpdateFlag = Data::PeerUpdate::Flag; } // namespace @@ -57,6 +61,7 @@ void UserData::setIsContact(bool is) { } } +#if 0 // mtp // see Serialize::readPeer as well void UserData::setPhoto(const MTPUserProfilePhoto &photo) { photo.match([&](const MTPDuserProfilePhoto &data) { @@ -74,18 +79,32 @@ void UserData::setPhoto(const MTPUserProfilePhoto &photo) { clearUserpic(); }); } +#endif +void UserData::setPhoto(const TLprofilePhoto &photo) { + if (photo.data().vis_personal().v) { + addFlags(UserDataFlag::PersonalPhoto); + } else { + removeFlags(UserDataFlag::PersonalPhoto); + } + updateUserpic(photo); +} + +#if 0 // mtp void UserData::setEmojiStatus(const MTPEmojiStatus &status) { const auto parsed = Data::ParseEmojiStatus(status); setEmojiStatus(parsed.id, parsed.until); } +#endif void UserData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) { if (_emojiStatusId != emojiStatusId) { _emojiStatusId = emojiStatusId; session().changes().peerUpdated(this, UpdateFlag::EmojiStatus); } +#if 0 // mtp owner().emojiStatuses().registerAutomaticClear(this, until); +#endif } DocumentId UserData::emojiStatusId() const { @@ -224,6 +243,7 @@ void UserData::setBotInfoVersion(int version) { } } +#if 0 // mtp void UserData::setBotInfo(const MTPBotInfo &info) { switch (info.type()) { case mtpc_botInfo: { @@ -280,7 +300,59 @@ void UserData::setBotInfo(const MTPBotInfo &info) { if (changedCommands || changedButton) { owner().botCommandsChanged(this); } +#if 0 // goodToRemove } break; +#endif + } +} +#endif + +void UserData::setBotInfo(const TLbotInfo &info) { + const auto &data = info.data(); + setBotInfoVersion(1); + const auto description = data.vdescription().v; + if (botInfo->description != description) { + botInfo->description = description; + ++botInfo->descriptionVersion; + } + if (const auto photo = data.vphoto()) { + const auto parsed = owner().processPhoto(*photo); + if (botInfo->photo != parsed) { + botInfo->photo = parsed; + ++botInfo->descriptionVersion; + } + } else if (botInfo->photo) { + botInfo->photo = nullptr; + ++botInfo->descriptionVersion; + } + if (const auto document = data.vanimation()) { + const auto parsed = owner().processDocument(*document); + if (botInfo->document != parsed) { + botInfo->document = parsed; + ++botInfo->descriptionVersion; + } + } else if (botInfo->document) { + botInfo->document = nullptr; + ++botInfo->descriptionVersion; + } + + auto commands = ranges::views::all( + data.vcommands().v + ) | ranges::views::transform( + Data::BotCommandFromTL + ) | ranges::to_vector; + const auto changedCommands = !ranges::equal( + botInfo->commands, + commands); + botInfo->commands = std::move(commands); + + const auto changedButton = Data::ApplyBotMenuButton( + botInfo.get(), + data.vmenu_button()); + botInfo->inited = true; + + if (changedCommands || changedButton) { + owner().botCommandsChanged(this); } } @@ -367,9 +439,11 @@ bool UserData::hasPersonalPhoto() const { return (flags() & UserDataFlag::PersonalPhoto); } +#if 0 // mtp bool UserData::hasStoriesHidden() const { return (flags() & UserDataFlag::StoriesHidden); } +#endif bool UserData::canAddContact() const { return canShareThisContact() && !isContact(); @@ -429,6 +503,7 @@ bool UserData::hasCalls() const { namespace Data { +#if 0 // mtp void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { const auto profilePhoto = update.vprofile_photo() ? user->owner().processPhoto(*update.vprofile_photo()).get() @@ -526,5 +601,123 @@ void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { user->fullUpdated(); } +#endif + +void ApplyUserUpdate( + not_null user, + const TLDuserFullInfo &update) { + if (const auto personal = update.vpersonal_photo()) { + user->setPhotoFull(*personal); + } + const auto peerPhotos = &user->session().api().peerPhoto(); + if (const auto photo = update.vphoto()) { + if (!update.vpersonal_photo()) { + user->setPhotoFull(*photo); + peerPhotos->unregisterNonPersonalPhoto(user); + } else { + peerPhotos->registerNonPersonalPhoto( + user, + user->owner().processPhoto(*photo)); + } + } else { + peerPhotos->unregisterNonPersonalPhoto(user); + } + if (const auto photo = update.vpublic_photo()) { + const auto data = user->owner().processPhoto(*photo); + if (!data->isNull()) { // Sometimes there is photoEmpty :shrug: + user->session().storage().add(Storage::UserPhotosSetBack( + peerToUser(user->id), + data->id + )); + } + } + + if (const auto settings = user->settings()) { + user->setSettings(update.vneed_phone_number_privacy_exception().v + ? (*settings | PeerSetting::NeedContactsException) + : (*settings & ~PeerSetting::NeedContactsException)); + } else if (update.vneed_phone_number_privacy_exception().v) { + user->setSettings(PeerSetting::NeedContactsException); + } + + if (const auto info = update.vbot_info()) { + user->setBotInfo(*info); + } else { + user->setBotInfoVersion(-1); + } + + using Flag = UserDataFlag; + const auto mask = Flag::Blocked + // | Flag::HasPhoneCalls // Unused. + | Flag::PhoneCallsPrivate + | Flag::CanReceiveGifts + | Flag::CanPinMessages + | Flag::VoiceMessagesForbidden; + const auto blocked = update.vblock_list() + && (update.vblock_list()->type() == id_blockListMain); + user->setFlags((user->flags() & ~mask) + | (update.vhas_private_calls().v ? Flag::PhoneCallsPrivate : Flag()) + // | (update.is_phone_calls_available() ? Flag::HasPhoneCalls : Flag()) + | (!update.vpremium_gift_options().v.empty() + ? Flag::CanReceiveGifts + : Flag()) + | (true /* Always true from TDLib. */ ? Flag::CanPinMessages : Flag()) + | (blocked ? Flag::Blocked : Flag()) + | (update.vhas_restricted_voice_and_video_note_messages().v + ? Flag::VoiceMessagesForbidden + : Flag())); + user->setIsBlocked(blocked); + user->setCallsStatus(update.vhas_private_calls().v + ? UserData::CallsStatus::Private + : update.vcan_be_called().v + ? UserData::CallsStatus::Enabled + : UserData::CallsStatus::Disabled); + user->setAbout(update.vbot_info() + ? update.vbot_info()->data().vshort_description().v + : update.vbio() + ? update.vbio()->data().vtext().v // later use tdlib entities + : QString()); + user->setCommonChatsCount(update.vgroup_in_common_count().v); + + if (const auto info = user->botInfo.get()) { + const auto &tlInfo = update.vbot_info(); + const auto group = [&] { + if (!tlInfo) { + return ChatAdminRights(); + } + const auto &d = tlInfo->data(); + return !d.vdefault_group_administrator_rights() + ? ChatAdminRights() + : ChatAdminRightsInfo( + tl_chatMemberStatusAdministrator( + tl_string(), // Dummy rank. + tl_bool(false), // Can be edited. + *(d.vdefault_group_administrator_rights()))).flags; + }(); + const auto channel = [&] { + if (!tlInfo) { + return ChatAdminRights(); + } + const auto &d = tlInfo->data(); + return !d.vdefault_channel_administrator_rights() + ? ChatAdminRights() + : ChatAdminRightsInfo( + tl_chatMemberStatusAdministrator( + tl_string(), // Dummy rank. + tl_bool(false), // Can be edited. + *(d.vdefault_channel_administrator_rights()))).flags; + }(); + if (info->groupAdminRights != group + || info->channelAdminRights != channel) { + info->groupAdminRights = group; + info->channelAdminRights = channel; + user->session().changes().peerUpdated( + user, + Data::PeerUpdate::Flag::Rights); + } + } + + user->fullUpdated(); +} } // namespace Data diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 96811051ee65d..d59a88d54c480 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -16,6 +16,12 @@ namespace Data { struct BotCommand; } // namespace Data +namespace Tdb { +class TLbotInfo; +class TLDuserFullInfo; +class TLbotInfo; +} // namespace Tdb + struct BotInfo { BotInfo(); @@ -52,7 +58,7 @@ enum class UserDataFlag { Fake = (1 << 5), BotInlineGeo = (1 << 6), Blocked = (1 << 7), - HasPhoneCalls = (1 << 8), + HasPhoneCalls = (1 << 8), // Unused. PhoneCallsPrivate = (1 << 9), Support = (1 << 10), CanPinMessages = (1 << 11), @@ -75,8 +81,14 @@ class UserData final : public PeerData { using Flags = Data::Flags; UserData(not_null owner, PeerId id); + +#if 0 // mtp void setPhoto(const MTPUserProfilePhoto &photo); void setEmojiStatus(const MTPEmojiStatus &status); +#endif + + void setPhoto(const Tdb::TLprofilePhoto &photo); + void setPhoto(const Tdb::TLchatPhoto &photo); void setName( const QString &newFirstName, @@ -91,7 +103,10 @@ class UserData final : public PeerData { void setUsername(const QString &username); void setPhone(const QString &newPhone); void setBotInfoVersion(int version); +#if 0 // mtp void setBotInfo(const MTPBotInfo &info); +#endif + void setBotInfo(const Tdb::TLbotInfo &info); void setNameOrPhone(const QString &newNameOrPhone); @@ -122,7 +137,9 @@ class UserData final : public PeerData { [[nodiscard]] bool isInaccessible() const; [[nodiscard]] bool applyMinPhoto() const; [[nodiscard]] bool hasPersonalPhoto() const; +#if 0 // mtp [[nodiscard]] bool hasStoriesHidden() const; +#endif [[nodiscard]] bool canShareThisContact() const; [[nodiscard]] bool canAddContact() const; @@ -134,7 +151,9 @@ class UserData final : public PeerData { // a full check by canShareThisContact() call. [[nodiscard]] bool canShareThisContactFast() const; +#if 0 // mtp MTPInputUser inputUser = MTP_inputUserEmpty(); +#endif QString firstName; QString lastName; @@ -205,6 +224,12 @@ class UserData final : public PeerData { namespace Data { +#if 0 // mtp void ApplyUserUpdate(not_null user, const MTPDuserFull &update); +#endif + +void ApplyUserUpdate( + not_null user, + const Tdb::TLDuserFullInfo &update); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_user_photos.cpp b/Telegram/SourceFiles/data/data_user_photos.cpp index 6271f9b4c9c38..579e5ed78140a 100644 --- a/Telegram/SourceFiles/data/data_user_photos.cpp +++ b/Telegram/SourceFiles/data/data_user_photos.cpp @@ -167,7 +167,11 @@ void UserPhotosSliceBuilder::sliceToLimits() { } } else if (removeFromBegin < 0 && (!_skippedBefore || *_skippedBefore > 0)) { + _insufficientPhotosAround.fire( + Api::PeerPhoto::UserPhotoId(_ids.size() + _skippedAfter)); +#if 0 // goodToRemove _insufficientPhotosAround.fire(_ids.empty() ? 0 : _ids.front()); +#endif } } diff --git a/Telegram/SourceFiles/data/data_wall_paper.cpp b/Telegram/SourceFiles/data/data_wall_paper.cpp index f8532a15860c8..eb876d7b279eb 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.cpp +++ b/Telegram/SourceFiles/data/data_wall_paper.cpp @@ -16,6 +16,12 @@ For license and copyright information please follow this link: #include "core/application.h" #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + +#include +#include + namespace Ui { QColor ColorFromSerialized(MTPint serialized) { @@ -32,6 +38,8 @@ std::optional MaybeColorFromSerialized( namespace Data { namespace { +using namespace Tdb; + constexpr auto FromLegacyBackgroundId(int32 legacyId) -> WallPaperId { return uint64(0xFFFFFFFF00000000ULL) | uint64(uint32(legacyId)); } @@ -64,6 +72,7 @@ using Ui::MaybeColorFromSerialized; return color ? SerializeColor(*color) : quint32(-1); } +#if 0 // mtp [[nodiscard]] std::vector ColorsFromMTP( const MTPDwallPaperSettings &data) { auto result = std::vector(); @@ -92,6 +101,66 @@ using Ui::MaybeColorFromSerialized; result.push_back(*c4); return result; } +#endif + +[[nodiscard]] std::vector ColorsFromTL( + const TLbackgroundFill &fill, + bool invertTopBottom = false) { + auto result = std::vector(); + const auto push = [&](const TLint32 &value) { + if (const auto color = MaybeColorFromSerialized(value.v)) { + result.push_back(*color); + } + }; + fill.match([&](const TLDbackgroundFillSolid &data) { + push(data.vcolor()); + }, [&](const TLDbackgroundFillGradient &data) { + result.reserve(2); + push(data.vtop_color()); + push(data.vbottom_color()); + if (invertTopBottom && result.size() == 2) { + std::swap(result[0], result[1]); + } + }, [&](const TLDbackgroundFillFreeformGradient &data) { + const auto &list = data.vcolors().v; + result.reserve(list.size()); + for (const auto &value : list) { + push(value); + } + }); + return result; +} + +[[nodiscard]] int RotationFromTL(const TLbackgroundFill &fill) { + return fill.match([&](const TLDbackgroundFillSolid &data) { + return 0; + }, [&](const TLDbackgroundFillGradient &data) { + return data.vrotation_angle().v; + }, [&](const TLDbackgroundFillFreeformGradient &data) { + return 0; + });; +} + +[[nodiscard]] TLbackgroundFill FillToTL( + const std::vector &colors, + int rotation) { + const auto wrap = [](const QColor &value) { + return tl_int32(static_cast(SerializeColor(value))); + }; + if (colors.empty()) { + return tl_backgroundFillSolid(tl_int32(0)); + } else if (colors.size() == 1) { + return tl_backgroundFillSolid(wrap(colors[0])); + } else if (colors.size() == 2) { + return tl_backgroundFillGradient( + wrap(colors[0]), + wrap(colors[1]), + tl_int32(rotation)); + } + return tl_backgroundFillFreeformGradient(tl_vector(colors + | ranges::views::transform(wrap) + | ranges::to())); +} [[nodiscard]] std::optional ColorFromString(QStringView string) { if (string.size() != 6) { @@ -228,9 +297,11 @@ bool WallPaper::isDefault() const { return _flags & WallPaperFlag::Default; } +#if 0 // mtp bool WallPaper::isCreator() const { return _flags & WallPaperFlag::Creator; } +#endif bool WallPaper::isDark() const { return _flags & WallPaperFlag::Dark; @@ -305,6 +376,7 @@ QString WallPaper::key() const { return params.isEmpty() ? base : (base + '?' + params.join('&')); } +#if 0 // mtp QString WallPaper::shareUrl(not_null session) const { if (!hasShareUrl()) { return QString(); @@ -313,6 +385,31 @@ QString WallPaper::shareUrl(not_null session) const { const auto params = collectShareParams(); return params.isEmpty() ? base : (base + '?' + params.join('&')); } +#endif + +TLbackgroundType WallPaper::tlType() const { + const auto moving = false; + return isPattern() + ? tl_backgroundTypePattern( + FillToTL(_backgroundColors, _rotation), + tl_int32(std::abs(_intensity)), + tl_bool(_intensity < 0), + tl_bool(moving)) + : _backgroundColors.empty() + ? tl_backgroundTypeWallpaper(tl_bool(_blurred), tl_bool(moving)) + : tl_backgroundTypeFill(FillToTL(_backgroundColors, _rotation)); +} + +void WallPaper::requestShareUrl( + not_null session, + Fn done) const { + session->sender().request(TLgetBackgroundUrl( + tl_string(_slug), + tlType() + )).done([=](const TLDhttpUrl &result) { + done(result.vurl().v); + }).send(); +} void WallPaper::loadDocumentThumbnail() const { if (_document) { @@ -462,6 +559,7 @@ WallPaper WallPaper::withoutImageData() const { return result; } +#if 0 // mtp std::optional WallPaper::Create( not_null session, const MTPWallPaper &data) { @@ -523,6 +621,57 @@ std::optional WallPaper::Create(const MTPDwallPaperNoFile &data) { } return result; } +#endif + +std::optional WallPaper::Create( + not_null session, + const TLbackground &paper) { + const auto &data = paper.data(); + const auto document = data.vdocument() + ? session->data().processDocument(*data.vdocument()).get() + : nullptr; + if (document && !document->checkWallPaperProperties()) { + return std::nullopt; + } + auto result = WallPaper(data.vid().v); + using Flag = WallPaperFlag; + result._ownerId = session->userId(); + result._flags = (data.vis_dark().v ? Flag::Dark : Flag(0)) + | (data.vis_default().v ? Flag::Default : Flag(0)); + result._slug = data.vname().v; + result._document = document; + result._blurred = false; + data.vtype().match([&](const TLDbackgroundTypeFill &data) { + result._backgroundColors = ColorsFromTL(data.vfill()); + result._rotation = RotationFromTL(data.vfill()); + }, [&](const TLDbackgroundTypePattern &data) { + result._backgroundColors = ColorsFromTL(data.vfill()); + if (result._backgroundColors.size() == 1 + && result._backgroundColors[0] == QColor(0, 0, 0, 255)) { + // This is really a bad pattern without colors at all. + result._backgroundColors = {}; + } + result._rotation = RotationFromTL(data.vfill()); + result._intensity = (data.vis_inverted().v ? -1 : 1) + * data.vintensity().v; + result._flags |= Flag::Pattern; + }, [&](const TLDbackgroundTypeWallpaper &data) { + result._blurred = data.vis_blurred().v; + }); + // mtp looks like Flag::Creator not used + return result; +} + +std::optional WallPaper::Create( + not_null session, + const TLchatBackground &paper) { + const auto &data = paper.data(); + auto result = WallPaper::Create(session, data.vbackground()); + if (!result || result->isPattern() || !result->document()) { + return result; + } + return result->withPatternIntensity(data.vdark_theme_dimming().v); +} QByteArray WallPaper::serialize() const { auto size = sizeof(quint64) // _id @@ -803,3 +952,17 @@ bool IsTestingEditorWallPaper(const WallPaper &paper) { } // namespace details } // namespace Data + +namespace Ui { + +std::vector ColorsFromFill( + const Tdb::TLbackgroundFill &fill, + bool invertTopBottom) { + return Data::ColorsFromTL(fill, invertTopBottom); +} + +Tdb::TLbackgroundFill ColorsToFill(const std::vector &colors) { + return Data::FillToTL(colors, 0); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/data/data_wall_paper.h b/Telegram/SourceFiles/data/data_wall_paper.h index fb98eb9de7a71..2459585a8d7d7 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.h +++ b/Telegram/SourceFiles/data/data_wall_paper.h @@ -11,6 +11,13 @@ For license and copyright information please follow this link: class Image; +namespace Tdb { +class TLbackground; +class TLbackgroundFill; +class TLbackgroundType; +class TLchatBackground; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -21,6 +28,12 @@ namespace Ui { [[nodiscard]] std::optional MaybeColorFromSerialized( const tl::conditional &mtp); +[[nodiscard]] std::vector ColorsFromFill( + const Tdb::TLbackgroundFill &fill, + bool invertTopBottom = false); +[[nodiscard]] Tdb::TLbackgroundFill ColorsToFill( + const std::vector &colors); + } // namespace Ui namespace Data { @@ -52,7 +65,9 @@ class WallPaper { [[nodiscard]] Image *localThumbnail() const; [[nodiscard]] bool isPattern() const; [[nodiscard]] bool isDefault() const; +#if 0 // mtp [[nodiscard]] bool isCreator() const; +#endif [[nodiscard]] bool isDark() const; [[nodiscard]] bool isLocal() const; [[nodiscard]] bool isBlurred() const; @@ -60,7 +75,13 @@ class WallPaper { [[nodiscard]] float64 patternOpacity() const; [[nodiscard]] int gradientRotation() const; [[nodiscard]] bool hasShareUrl() const; +#if 0 // mtp [[nodiscard]] QString shareUrl(not_null session) const; +#endif + [[nodiscard]] Tdb::TLbackgroundType tlType() const; + void requestShareUrl( + not_null session, + Fn done) const; void loadDocument() const; void loadDocumentThumbnail() const; @@ -81,6 +102,7 @@ class WallPaper { [[nodiscard]] WallPaper withParamsFrom(const WallPaper &other) const; [[nodiscard]] WallPaper withoutImageData() const; +#if 0 // mtp [[nodiscard]] static std::optional Create( not_null session, const MTPWallPaper &data); @@ -89,6 +111,13 @@ class WallPaper { const MTPDwallPaper &data); [[nodiscard]] static std::optional Create( const MTPDwallPaperNoFile &data); +#endif + [[nodiscard]] static std::optional Create( + not_null session, + const Tdb::TLbackground &paper); + [[nodiscard]] static std::optional Create( + not_null session, + const Tdb::TLchatBackground &paper); [[nodiscard]] QByteArray serialize() const; [[nodiscard]] static std::optional FromSerialized( diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index 95b042775c829..08ae448f1389d 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -17,9 +17,15 @@ For license and copyright information please follow this link: #include "lang/lang_keys.h" #include "ui/image/image.h" #include "ui/text/text_entity.h" +#include "tdb/tdb_tl_scheme.h" +#include "api/api_text_entities.h" + +#include namespace { +using namespace Tdb; + QString SiteNameFromUrl(const QString &url) { const auto u = QUrl(url); QString pretty = u.isValid() ? u.toDisplayString() : url; @@ -38,6 +44,7 @@ QString SiteNameFromUrl(const QString &url) { return QString(); } +#if 0 // mtp WebPageCollage ExtractCollage( not_null owner, const QVector &items, @@ -134,6 +141,80 @@ WebPageCollage ExtractCollage( return WebPageCollage(); }); } +#endif + +WebPageCollage ExtractCollage( + not_null owner, + const QVector &items) { + const auto count = items.size(); + if (count < 2) { + return {}; + } + const auto bad = ranges::find_if(items, [](uint32 type) { + return (type != id_pageBlockPhoto) && (type != id_pageBlockVideo); + }, &TLpageBlock::type); + if (bad != items.end()) { + return {}; + } + + auto result = WebPageCollage(); + result.items.reserve(count); + for (const auto &item : items) { + const auto good = item.match([&](const TLDpageBlockPhoto &data) { + if (const auto found = data.vphoto()) { + const auto photo = owner->processPhoto(*found); + if (photo->isNull()) { + return false; + } + result.items.emplace_back(photo); + return true; + } + return false; + }, [&](const TLDpageBlockVideo &data) { + if (const auto found = data.vvideo()) { + const auto document = owner->processDocument(*found); + if (!document->isVideoFile()) { + return false; + } + result.items.emplace_back(document); + return true; + } + return false; + }, [](const auto &) -> bool { + Unexpected("Type of block in Collage."); + }); + if (!good) { + return {}; + } + } + return result; +} + +WebPageCollage ExtractCollage( + not_null owner, + const TLDwebPageInstantView &data) { + for (const auto &block : data.vpage_blocks().v) { + switch (block.type()) { + case id_pageBlockPhoto: + case id_pageBlockVideo: + case id_pageBlockCover: + case id_pageBlockEmbedded: + case id_pageBlockEmbeddedPost: + case id_pageBlockAudio: + return WebPageCollage(); + case id_pageBlockSlideshow: + return ExtractCollage( + owner, + block.c_pageBlockSlideshow().vpage_blocks().v); + case id_pageBlockCollage: + return ExtractCollage( + owner, + block.c_pageBlockCollage().vpage_blocks().v); + default: break; + } + } + return WebPageCollage(); +} } // namespace @@ -193,17 +274,88 @@ WebPageType ParseWebPageType(const MTPDwebPage &page) { !!page.vcached_page()); } +#if 0 // mtp WebPageCollage::WebPageCollage( not_null owner, const MTPDwebPage &data) : WebPageCollage(ExtractCollage(owner, data)) { } +#endif WebPageData::WebPageData(not_null owner, const WebPageId &id) : id(id) , _owner(owner) { } +WebPageId WebPageData::IdFromTdb(const TLwebPage &data) { + const auto &url = data.data().vurl().v; + return XXH64(url.data(), url.size() * sizeof(ushort), 0); +} + +void WebPageData::setFromTdb(const TLwebPage &data) { + const auto &fields = data.data(); + applyChanges( + ParseWebPageType( + fields.vtype().v, + fields.vembed_url().v, + fields.vinstant_view_version().v > 0), + fields.vurl().v, + fields.vdisplay_url().v, + fields.vsite_name().v, + fields.vtitle().v, + Api::FormattedTextFromTdb(fields.vdescription()), + FullStoryId{ + peerFromTdbChat(fields.vstory_sender_chat_id()), + fields.vstory_id().v, + }, + (fields.vphoto() + ? _owner->processPhoto(*fields.vphoto()).get() + : nullptr), + (fields.vanimation() + ? _owner->processDocument(*fields.vanimation()).get() + : fields.vaudio() + ? _owner->processDocument(*fields.vaudio()).get() + : fields.vdocument() + ? _owner->processDocument(*fields.vdocument()).get() + : fields.vsticker() + ? _owner->processDocument(*fields.vsticker()).get() + : fields.vvideo() + ? _owner->processDocument(*fields.vvideo()).get() + : fields.vvideo_note() + ? _owner->processDocument(*fields.vvideo_note()).get() + : fields.vvoice_note() + ? _owner->processDocument(*fields.vvoice_note()).get() + : nullptr), + fields.vduration().v, + fields.vauthor().v, + fields.vhas_large_media().v, + 0); + + if (fields.vinstant_view_version().v > 0 && !_collageRequestId) { + const auto owner = _owner; + const auto sender = &_owner->session().sender(); + const auto requestId = sender->preallocateId(); + const auto finish = [owner, id = id, requestId](auto &&collage) { + const auto that = owner->webpage(id); + if (that->_collageRequestId == requestId) { + that->_collageRequestId = -1; + if (!collage.items.empty()) { + that->setCollage(std::move(collage)); + } + } + }; + sender->request(TLgetWebPageInstantView( + fields.vurl(), + tl_bool(false) // force_full + ), requestId).done([=](const TLDwebPageInstantView &data) { + finish(ExtractCollage(owner, data)); + }).fail([finish] { + finish(WebPageCollage()); + }).send(); + _collageRequestId = requestId; + } +} + Data::Session &WebPageData::owner() const { return *_owner; } @@ -222,7 +374,9 @@ bool WebPageData::applyChanges( FullStoryId newStoryId, PhotoData *newPhoto, DocumentData *newDocument, +#if 0 // mtp WebPageCollage &&newCollage, +#endif int newDuration, const QString &newAuthor, bool newHasLargeMedia, @@ -258,7 +412,9 @@ bool WebPageData::applyChanges( const auto hasTitle = !resultTitle.isEmpty() ? 1 : 0; const auto hasDescription = !newDescription.text.isEmpty() ? 1 : 0; if (newDocument +#if 0 // mtp || !newCollage.items.empty() +#endif || !newPhoto || (hasSiteName + hasTitle + hasDescription < 2)) { newHasLargeMedia = false; @@ -273,16 +429,20 @@ bool WebPageData::applyChanges( && storyId == newStoryId && photo == newPhoto && document == newDocument +#if 0 // mtp && collage.items == newCollage.items +#endif && duration == newDuration && author == resultAuthor && hasLargeMedia == (newHasLargeMedia ? 1 : 0) && pendingTill == newPendingTill) { return false; } +#if 0 // mtp if (pendingTill > 0 && newPendingTill <= 0) { _owner->session().api().clearWebPageRequest(this); } +#endif type = newType; hasLargeMedia = newHasLargeMedia ? 1 : 0; url = resultUrl; @@ -293,7 +453,9 @@ bool WebPageData::applyChanges( storyId = newStoryId; photo = newPhoto; document = newDocument; +#if 0 // mtp collage = std::move(newCollage); +#endif duration = newDuration; author = resultAuthor; pendingTill = newPendingTill; @@ -314,6 +476,15 @@ void WebPageData::replaceDocumentGoodThumbnail() { } } +void WebPageData::setCollage(WebPageCollage &&newCollage) { + if (collage.items != newCollage.items) { + collage = std::move(newCollage); + ++version; + _owner->notifyWebPageUpdateDelayed(this); + } +} + +#if 0 // mtp void WebPageData::ApplyChanges( not_null session, ChannelData *channel, @@ -356,6 +527,7 @@ void WebPageData::ApplyChanges( } session->data().sendWebPageGamePollNotifications(); } +#endif QString WebPageData::displayedSiteName() const { return (document && document->isWallPaper()) diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index 04f16a7b66737..e6c759c571d74 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -11,6 +11,10 @@ For license and copyright information please follow this link: #include "data/data_photo.h" #include "data/data_document.h" +namespace Tdb { +class TLwebPage; +} // namespace Tdb + class ChannelData; namespace Data { @@ -53,9 +57,11 @@ struct WebPageCollage { using Item = std::variant; WebPageCollage() = default; +#if 0 // mtp explicit WebPageCollage( not_null owner, const MTPDwebPage &data); +#endif std::vector items; @@ -64,6 +70,9 @@ struct WebPageCollage { struct WebPageData { WebPageData(not_null owner, const WebPageId &id); + [[nodiscard]] static WebPageId IdFromTdb(const Tdb::TLwebPage &data); + void setFromTdb(const Tdb::TLwebPage &data); + [[nodiscard]] Data::Session &owner() const; [[nodiscard]] Main::Session &session() const; @@ -77,16 +86,21 @@ struct WebPageData { FullStoryId newStoryId, PhotoData *newPhoto, DocumentData *newDocument, +#if 0 // mtp WebPageCollage &&newCollage, +#endif int newDuration, const QString &newAuthor, bool newHasLargeMedia, int newPendingTill); +#if 0 // mtp static void ApplyChanges( not_null session, ChannelData *channel, const MTPmessages_Messages &result); +#endif + void setCollage(WebPageCollage &&newCollage); [[nodiscard]] QString displayedSiteName() const; [[nodiscard]] bool computeDefaultSmallMedia() const; @@ -110,9 +124,11 @@ struct WebPageData { uint32 hasLargeMedia : 1 = 0; uint32 failed : 1 = 0; + private: void replaceDocumentGoodThumbnail(); const not_null _owner; + mtpRequestId _collageRequestId = 0; }; diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp index beb47698b75ee..633c6db930a2b 100644 --- a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp +++ b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp @@ -26,9 +26,13 @@ For license and copyright information please follow this link: #include "main/main_session.h" #include "window/notifications_manager.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kMaxNotifyCheckDelay = 24 * 3600 * crl::time(1000); [[nodiscard]] bool MutedFromUntil(TimeId until, crl::time *changesIn) { @@ -63,6 +67,7 @@ DefaultNotify DefaultNotifyType(not_null peer) { : DefaultNotify::Broadcast; } +#if 0 // mtp MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type) { switch (type) { case DefaultNotify::User: return MTP_inputNotifyUsers(); @@ -71,6 +76,19 @@ MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type) { } Unexpected("Default notify type in sendNotifySettingsUpdates"); } +#endif + +Tdb::TLnotificationSettingsScope DefaultNotifyScope(DefaultNotify type) { + switch (type) { + case DefaultNotify::User: + return tl_notificationSettingsScopePrivateChats(); + case DefaultNotify::Group: + return tl_notificationSettingsScopeGroupChats(); + case DefaultNotify::Broadcast: + return tl_notificationSettingsScopeChannelChats(); + } + Unexpected("Type in requestDefaultNotifySettings."); +} NotifySettings::NotifySettings(not_null owner) : _owner(owner) @@ -78,20 +96,30 @@ NotifySettings::NotifySettings(not_null owner) } void NotifySettings::request(not_null peer) { +#if 0 // mtp if (peer->notify().settingsUnknown()) { peer->session().api().requestNotifySettings( MTP_inputNotifyPeer(peer->input)); } +#endif if (defaultSettings(peer).settingsUnknown()) { +#if 0 // mtp peer->session().api().requestNotifySettings(peer->isUser() ? MTP_inputNotifyUsers() : (peer->isChat() || peer->isMegagroup()) ? MTP_inputNotifyChats() : MTP_inputNotifyBroadcasts()); +#endif + peer->session().api().requestDefaultNotifySettings(peer->isUser() + ? DefaultNotify::User + : peer->isBroadcast() + ? DefaultNotify::Broadcast + : DefaultNotify::Group); } } void NotifySettings::request(not_null thread) { +#if 0 // mtp if (const auto topic = thread->asTopic()) { if (topic->notify().settingsUnknown()) { topic->session().api().requestNotifySettings( @@ -100,9 +128,11 @@ void NotifySettings::request(not_null thread) { MTP_int(topic->rootId()))); } } +#endif request(thread->peer()); } +#if 0 // mtp void NotifySettings::apply( const MTPNotifyPeer ¬ifyPeer, const MTPPeerNotifySettings &settings) { @@ -198,6 +228,7 @@ void NotifySettings::apply( Core::App().notifications().checkDelayed(); } } +#endif void NotifySettings::update( not_null thread, @@ -218,6 +249,7 @@ void NotifySettings::update( } } +#if 0 // mtp void NotifySettings::resetToDefault(not_null thread) { // Duplicated in clearExceptions(type) and resetToDefault(peer). if (thread->notify().resetToDefault()) { @@ -229,6 +261,7 @@ void NotifySettings::resetToDefault(not_null thread) { Core::App().notifications().checkDelayed(); } } +#endif void NotifySettings::update( not_null peer, @@ -265,6 +298,46 @@ void NotifySettings::forumParentMuteUpdated(not_null forum) { }); } +void NotifySettings::apply( + not_null peer, + const TLchatNotificationSettings &settings) { + if (peer->notify().change(settings)) { + updateException(peer); + updateLocal(peer); + } +} + +void NotifySettings::apply( + not_null topic, + const TLchatNotificationSettings &settings) { + if (topic->notify().change(settings)) { + updateLocal(topic); + } +} + +void NotifySettings::apply( + DefaultNotify type, + const TLscopeNotificationSettings &settings) { + auto &forType = defaultValue(type).settings; + const auto &data = settings.data(); + const auto soundId = data.vsound_id().v; + const auto sound = NotifySound{ + .id = DocumentId((soundId == -1) ? 0 : soundId), + .none = !soundId, + }; + const auto mute = MuteValue{ + .unmute = !data.vmute_for().v, + .period = data.vmute_for().v, + }; + const auto muteStories = data.vuse_default_mute_stories().v + ? std::optional() + : data.vmute_stories().v; + if (forType.change(mute, std::nullopt, sound, muteStories)) { + updateLocal(type); + } + Core::App().notifications().checkDelayed(); +} + auto NotifySettings::defaultValue(DefaultNotify type) -> DefaultValue & { const auto index = static_cast(type); @@ -596,6 +669,7 @@ void NotifySettings::loadExceptions() { continue; } const auto type = static_cast(i); +#if 0 // mtp const auto api = &_owner->session().api(); const auto requestId = api->request(MTPaccount_GetNotifyExceptions( MTP_flags(MTPaccount_GetNotifyExceptions::Flag::f_peer), @@ -603,6 +677,13 @@ void NotifySettings::loadExceptions() { )).done([=](const MTPUpdates &result) { api->applyUpdates(result); }).send(); +#endif + const auto sender = &_owner->session().sender(); + const auto requestId = sender->request( + TLgetChatNotificationSettingsExceptions( + DefaultNotifyScope(type), + tl_bool(false)) + ).send(); _exceptionsRequestId[i] = requestId; } } diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.h b/Telegram/SourceFiles/data/notify/data_notify_settings.h index b1930f45bd364..6efec00c880a8 100644 --- a/Telegram/SourceFiles/data/notify/data_notify_settings.h +++ b/Telegram/SourceFiles/data/notify/data_notify_settings.h @@ -11,6 +11,12 @@ For license and copyright information please follow this link: #include "base/timer.h" +namespace Tdb { +class TLchatNotificationSettings; +class TLscopeNotificationSettings; +class TLnotificationSettingsScope; +} // namespace Tdb + class PeerData; namespace Data { @@ -31,6 +37,9 @@ enum class DefaultNotify { [[nodiscard]] MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type); +[[nodiscard]] Tdb::TLnotificationSettingsScope DefaultNotifyScope( + DefaultNotify type); + class NotifySettings final { public: NotifySettings(not_null owner); @@ -38,6 +47,7 @@ class NotifySettings final { void request(not_null peer); void request(not_null thread); +#if 0 // mtp void apply( const MTPNotifyPeer ¬ifyPeer, const MTPPeerNotifySettings &settings); @@ -56,6 +66,16 @@ class NotifySettings final { void apply( not_null topic, const MTPPeerNotifySettings &settings); +#endif + void apply( + not_null peer, + const Tdb::TLchatNotificationSettings &settings); + void apply( + not_null topic, + const Tdb::TLchatNotificationSettings &settings); + void apply( + DefaultNotify type, + const Tdb::TLscopeNotificationSettings &settings); void update( not_null thread, diff --git a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp index 48d198d6e55b4..02ef8e2f26fdf 100644 --- a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp +++ b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp @@ -9,9 +9,14 @@ For license and copyright information please follow this link: #include "base/unixtime.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + +#if 0 // mtp [[nodiscard]] MTPinputPeerNotifySettings DefaultSettings() { return MTP_inputPeerNotifySettings( MTP_flags(0), @@ -53,6 +58,43 @@ namespace { MTP_string(sound->data)) : MTP_notificationSoundDefault(); } +#endif + +[[nodiscard]] NotifySound ParseSound(int64 soundId) { + return !soundId + ? NotifySound{ .none = true } + : (soundId == -1) + ? NotifySound() + : NotifySound{ .id = DocumentId(soundId) }; +} + +[[nodiscard]] int64 SerializeSound(const std::optional &sound) { + return !sound + ? int64(-1) + : sound->none + ? int64(0) + : (sound->id == 0) + ? int64(-1) + : int64(sound->id); +} + +[[nodiscard]] int MuteForToMuteTill(int muteFor) { + return muteFor + ? int(std::min( + int64(base::unixtime::now()) + muteFor, + int64(std::numeric_limits::max()))) + : 0; +} + +[[nodiscard]] int MuteTillToMuteFor(int muteTill) { + constexpr auto max = std::numeric_limits::max(); + const auto now = base::unixtime::now(); + return (muteTill == max) + ? max + : (muteTill <= now) + ? 0 + : (muteTill - now); +} } // namespace @@ -70,9 +112,14 @@ int MuteValue::until() const { class NotifyPeerSettingsValue { public: +#if 0 // mtp NotifyPeerSettingsValue(const MTPDpeerNotifySettings &data); bool change(const MTPDpeerNotifySettings &data); +#endif + NotifyPeerSettingsValue(const TLchatNotificationSettings &settings); + + bool change(const TLchatNotificationSettings &settings); bool change( MuteValue muteForSeconds, std::optional silentPosts, @@ -82,7 +129,11 @@ class NotifyPeerSettingsValue { std::optional muteUntil() const; std::optional silentPosts() const; std::optional sound() const; +#if 0 // mtp MTPinputPeerNotifySettings serialize() const; +#endif + [[nodiscard]] TLchatNotificationSettings serialize() const; + [[nodiscard]] TLscopeNotificationSettings serializeDefault() const; private: bool change( @@ -100,6 +151,7 @@ class NotifyPeerSettingsValue { }; +#if 0 // mtp NotifyPeerSettingsValue::NotifyPeerSettingsValue( const MTPDpeerNotifySettings &data) { change(data); @@ -122,6 +174,32 @@ bool NotifyPeerSettingsValue::change(const MTPDpeerNotifySettings &data) { ? std::make_optional(mtpIsTrue(*storiesMuted)) : std::nullopt)); } +#endif + +NotifyPeerSettingsValue::NotifyPeerSettingsValue( + const TLchatNotificationSettings &settings) { + change(settings); +} + +bool NotifyPeerSettingsValue::change( + const TLchatNotificationSettings &settings) { + const auto &data = settings.data(); + const auto mute = data.vmute_for().v; + const auto till = MuteForToMuteTill(mute); + const auto sound = data.vsound_id().v; + return change( + (data.vuse_default_mute_for().v ? std::optional() : till), + (data.vuse_default_sound().v + ? std::optional() + : ParseSound(sound)), + (data.vuse_default_show_preview().v + ? std::optional() + : data.vshow_preview().v), + _silent, + (data.vuse_default_mute_stories().v + ? std::optional() + : data.vmute_stories().v)); +} bool NotifyPeerSettingsValue::change( MuteValue muteForSeconds, @@ -129,7 +207,7 @@ bool NotifyPeerSettingsValue::change( std::optional sound, std::optional storiesMuted) { const auto newMute = muteForSeconds - ? base::make_optional(muteForSeconds.until()) + ? base::make_optional(MuteForToMuteTill(muteForSeconds.until())) : _mute; const auto newSilentPosts = silentPosts ? base::make_optional(*silentPosts) @@ -181,6 +259,7 @@ std::optional NotifyPeerSettingsValue::sound() const { return _sound; } +#if 0 // mtp MTPinputPeerNotifySettings NotifyPeerSettingsValue::serialize() const { using Flag = MTPDinputPeerNotifySettings::Flag; const auto flag = [](auto &&optional, Flag flag) { @@ -200,9 +279,86 @@ MTPinputPeerNotifySettings NotifyPeerSettingsValue::serialize() const { MTP_bool(false), // stories_hide_sender SerializeSound(std::nullopt)); // stories_sound } +#endif + +TLchatNotificationSettings NotifyPeerSettingsValue::serialize() const { + const auto muteFor = _mute + ? MuteTillToMuteFor(*_mute) + : std::optional(); + const auto soundNow = sound(); + const auto previewsNow = _showPreviews; + return tl_chatNotificationSettings( + tl_bool(!muteFor.has_value()), + tl_int32(muteFor.value_or(0)), + tl_bool(!_sound), + tl_int64(SerializeSound(_sound)), + tl_bool(!_showPreviews), // use_default_show_preview + tl_bool(_showPreviews.value_or(true)), // show_preview + tl_bool(!_storiesMuted), // use_default_mute_stories + tl_bool(_storiesMuted.value_or(false)), // mute_stories + tl_bool(true), // use_default_story_sound + tl_int64(SerializeSound(std::nullopt)), // story_sound_id + tl_bool(true), // use_default_show_story_sender + tl_bool(true), // show_story_sender + tl_bool(true), // use_default_disable_pinned_message_notifications + tl_bool(false), // disable_pinned_message_notifications + tl_bool(true), // use_default_disable_mention_notifications + tl_bool(false)); // disable_mention_notifications +} + +auto NotifyPeerSettingsValue::serializeDefault() const +-> TLscopeNotificationSettings { + const auto muteFor = _mute + ? MuteTillToMuteFor(*_mute) + : std::optional(); + return tl_scopeNotificationSettings( + tl_int32(muteFor.value_or(0)), + tl_int64(SerializeSound(_sound)), + tl_bool(_showPreviews.value_or(true)), // show_preview + tl_bool(!_storiesMuted), // use_default_mute_stories + tl_bool(_storiesMuted.value_or(false)), // mute_stories + tl_int64(SerializeSound(std::nullopt)), // story_sound_id + tl_bool(true), // show_story_sender + tl_bool(false), // disable_pinned_message_notifications + tl_bool(false)); // disable_mention_notifications +} PeerNotifySettings::PeerNotifySettings() = default; +TLchatNotificationSettings PeerNotifySettings::Default() { + return tl_chatNotificationSettings( + tl_bool(true), // use_default_mute_for + tl_int32(0), // mute_for + tl_bool(true), // use_default_sound + tl_int64(-1), // sound_id + tl_bool(true), // use_default_show_preview + tl_bool(true), // show_preview + tl_bool(true), // use_default_mute_stories + tl_bool(false), // mute_stories + tl_bool(true), // use_default_story_sound + tl_int64(SerializeSound(std::nullopt)), // story_sound_id + tl_bool(true), // use_default_show_story_sender + tl_bool(true), // show_story_sender + tl_bool(true), // use_default_disable_pinned_message_notifications + tl_bool(false), // disable_pinned_message_notifications + tl_bool(true), // use_default_disable_mention_notifications + tl_bool(false)); // disable_mention_notifications +} + +TLscopeNotificationSettings PeerNotifySettings::ScopeDefault() { + return tl_scopeNotificationSettings( + tl_int32(0), // mute_for + tl_int64(SerializeSound(std::nullopt)), // sound_id + tl_bool(true), // show_preview + tl_bool(true), // use_default_mute_stories + tl_bool(false), // mute_stories + tl_int64(SerializeSound(std::nullopt)), // story_sound_id + tl_bool(true), // show_story_sender + tl_bool(false), // disable_pinned_message_notifications + tl_bool(false)); // disable_mention_notifications +} + +#if 0 // mtp bool PeerNotifySettings::change(const MTPPeerNotifySettings &settings) { auto &data = settings.data(); const auto empty = !data.vflags().v; @@ -221,6 +377,29 @@ bool PeerNotifySettings::change(const MTPPeerNotifySettings &settings) { _value = std::make_unique(data); return true; } +#endif + +bool PeerNotifySettings::change(const TLchatNotificationSettings &settings) { + auto &data = settings.data(); + const auto empty = data.vuse_default_mute_for().v + && data.vuse_default_show_preview().v + && data.vuse_default_sound().v + && data.vuse_default_mute_stories().v; + if (empty) { + if (!_known || _value) { + _known = true; + _value = nullptr; + return true; + } + return false; + } + if (_value) { + return _value->change(settings); + } + _known = true; + _value = std::make_unique(settings); + return true; +} bool PeerNotifySettings::change( MuteValue muteForSeconds, @@ -236,6 +415,7 @@ bool PeerNotifySettings::change( sound, storiesMuted); } +#if 0 // mtp using Flag = MTPDpeerNotifySettings::Flag; const auto flags = (muteForSeconds ? Flag::f_mute_until : Flag(0)) | (silentPosts ? Flag::f_silent : Flag(0)) @@ -254,6 +434,34 @@ bool PeerNotifySettings::change( MTPNotificationSound(), MTPNotificationSound(), SerializeSound(std::nullopt))); // stories_sound +#endif + const auto muteUntilNow = muteUntil(); + const auto muteFor = muteForSeconds + ? (muteForSeconds.forever + ? std::numeric_limits::max() + : muteForSeconds.period) + : muteUntilNow + ? MuteTillToMuteFor(*muteUntilNow) + : std::optional(); + const auto silent = silentPosts ? *silentPosts : this->silentPosts(); + const auto soundId = sound ? *sound : this->sound(); + return change(tl_chatNotificationSettings( + tl_bool(!muteFor.has_value()), + tl_int32(muteFor.value_or(0)), + tl_bool(!soundId.has_value()), + tl_int64(SerializeSound(soundId)), + tl_bool(true), // use_default_show_preview + tl_bool(true), // show_preview + tl_bool(!storiesMuted.has_value()), + tl_bool(storiesMuted.value_or(false)), + tl_bool(true), // use_default_story_sound + tl_int64(SerializeSound(std::nullopt)), // story_sound_id + tl_bool(true), // use_default_show_story_sender + tl_bool(true), // show_story_sender + tl_bool(true), // use_default_disable_pinned_message_notifications + tl_bool(false), // disable_pinned_message_notifications + tl_bool(true), // use_default_disable_mention_notifications + tl_bool(false))); // disable_mention_notifications } bool PeerNotifySettings::resetToDefault() { @@ -287,11 +495,21 @@ std::optional PeerNotifySettings::sound() const { : std::nullopt; } +#if 0 // mtp MTPinputPeerNotifySettings PeerNotifySettings::serialize() const { return _value ? _value->serialize() : DefaultSettings(); } +#endif + +TLchatNotificationSettings PeerNotifySettings::serialize() const { + return _value ? _value->serialize() : Default(); +} + +TLscopeNotificationSettings PeerNotifySettings::serializeDefault() const { + return _value ? _value->serializeDefault() : ScopeDefault(); +} PeerNotifySettings::~PeerNotifySettings() = default; diff --git a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h index b5de9e11959a5..2b5c689060a87 100644 --- a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h +++ b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h @@ -7,6 +7,11 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLchatNotificationSettings; +class TLscopeNotificationSettings; +} // namespace Tdb + namespace Data { class NotifyPeerSettingsValue; @@ -40,7 +45,13 @@ class PeerNotifySettings { public: PeerNotifySettings(); + [[nodiscard]] static Tdb::TLchatNotificationSettings Default(); + [[nodiscard]] static Tdb::TLscopeNotificationSettings ScopeDefault(); + +#if 0 // mtp bool change(const MTPPeerNotifySettings &settings); +#endif + bool change(const Tdb::TLchatNotificationSettings &settings); bool change( MuteValue muteForSeconds, std::optional silentPosts, @@ -52,7 +63,11 @@ class PeerNotifySettings { std::optional muteUntil() const; std::optional silentPosts() const; std::optional sound() const; +#if 0 // mtp MTPinputPeerNotifySettings serialize() const; +#endif + [[nodiscard]] Tdb::TLchatNotificationSettings serialize() const; + [[nodiscard]] Tdb::TLscopeNotificationSettings serializeDefault() const; ~PeerNotifySettings(); diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 123621b37d4c2..c424bc683231f 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -31,9 +31,13 @@ For license and copyright information please follow this link: #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kMaxPerRequest = 100; #if 0 // inject-to-on_main constexpr auto kUnsubscribeUpdatesDelay = 3 * crl::time(1000); @@ -695,21 +699,33 @@ QString CustomEmojiManager::lookupSetName(uint64 setId) { } void CustomEmojiManager::request() { +#if 0 // mtp auto ids = QVector(); +#endif + auto ids = QVector(); ids.reserve(std::min(kMaxPerRequest, int(_pendingForRequest.size()))); while (!_pendingForRequest.empty() && ids.size() < kMaxPerRequest) { const auto i = _pendingForRequest.end() - 1; +#if 0 // mtp ids.push_back(MTP_long(*i)); +#endif + ids.push_back(tl_int64(*i)); _pendingForRequest.erase(i); } if (ids.isEmpty()) { return; } +#if 0 // mtp const auto api = &_owner->session().api(); _requestId = api->request(MTPmessages_GetCustomEmojiDocuments( MTP_vector(ids) )).done([=](const MTPVector &result) { for (const auto &entry : result.v) { +#endif + _requestId = _owner->session().sender().request(TLgetCustomEmojiStickers( + tl_vector(ids) + )).done([=](const TLDstickers &result) { + for (const auto &entry : result.vstickers().v) { const auto document = _owner->processDocument(entry); fillColoredFlags(document); processLoaders(document); diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.cpp b/Telegram/SourceFiles/data/stickers/data_stickers.cpp index c18a1c3bf39d9..271abf531fe7b 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers.cpp @@ -38,11 +38,15 @@ For license and copyright information please follow this link: #include "boxes/abstract_box.h" // Ui::show(). #include "styles/style_chat_helpers.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { constexpr auto kPremiumToastDuration = 5 * crl::time(1000); +using namespace Tdb; + using SetFlag = StickersSetFlag; [[nodiscard]] TextWithEntities SavedGifsToast( @@ -137,6 +141,16 @@ void RemoveFromSet( } // namespace +StickersType TypeFromTL(const TLstickerType &type) { + return type.match([](const TLDstickerTypeRegular &) { + return StickersType::Stickers; + }, [](const TLDstickerTypeMask &) { + return StickersType::Masks; + }, [](const TLDstickerTypeCustomEmoji &) { + return StickersType::Emoji; + }); +} + Stickers::Stickers(not_null owner) : _owner(owner) { } @@ -340,6 +354,7 @@ void Stickers::addSavedGif( } void Stickers::checkSavedGif(not_null item) { +#if 0 // mtp if (item->Has() || (!item->out() && item->history()->peer != session().user())) { @@ -352,8 +367,10 @@ void Stickers::checkSavedGif(not_null item) { } } } +#endif } +#if 0 // mtp void Stickers::applyArchivedResult( const MTPDmessages_stickerSetInstallResultArchive &d) { auto &v = d.vsets().v; @@ -411,6 +428,7 @@ void Stickers::applyArchivedResult( notifyUpdated(StickersType::Masks); } } +#endif void Stickers::installLocally(uint64 setId) { auto &sets = setsRef(); @@ -625,6 +643,8 @@ void Stickers::requestSetToPushFaved( } setIsFaved(nullptr, document, std::move(list)); }; + addAnyway({}); +#if 0 // mtp session().api().request(MTPmessages_GetStickerSet( Data::InputStickerSet(document->sticker()->set), MTP_int(0) // hash @@ -651,6 +671,7 @@ void Stickers::requestSetToPushFaved( // Perhaps this is a deleted sticker pack. Add anyway. addAnyway({}); }).send(); +#endif } void Stickers::removeFromRecentSet(not_null document) { @@ -676,6 +697,91 @@ void Stickers::setFaved( } } +void Stickers::setsReceived(const QVector &data) { + somethingReceived(data, StickersType::Stickers); +} + +void Stickers::masksReceived(const QVector &data) { + somethingReceived(data, StickersType::Masks); +} + +void Stickers::emojiReceived(const QVector &data) { + somethingReceived(data, StickersType::Emoji); +} + +void Stickers::apply(const TLDupdateStickerSet &data) { + feedSetFull(data.vsticker_set()); +} + +void Stickers::apply(const TLDupdateInstalledStickerSets &data) { + // later optimize in case all sets are already known + const auto type = data.vsticker_type().match([&]( + const TLDstickerTypeCustomEmoji &) { + //setLastEmojiUpdate(crl::now()); + session().api().updateCustomEmoji(); + return StickersType::Emoji; + }, [&](const TLDstickerTypeMask &) { + //setLastMasksUpdate(crl::now()); + session().api().updateMasks(); + return StickersType::Masks; + }, [&](const TLDstickerTypeRegular &) { + //setLastUpdate(crl::now()); + session().api().updateStickers(); + return StickersType::Stickers; + }); + //somethingReceived(data.vsticker_set_ids().v, type); +} + +void Stickers::apply(const TLDupdateTrendingStickerSets &data) { + const auto type = data.vsticker_type().match([&]( + const TLDstickerTypeCustomEmoji &) { + setLastFeaturedEmojiUpdate(crl::now()); + return StickersType::Emoji; + }, [&](const TLDstickerTypeMask &) { + return StickersType::Masks; + }, [&](const TLDstickerTypeRegular &) { + setLastFeaturedUpdate(crl::now()); + return StickersType::Stickers; + }); + featuredReceived(data.vsticker_sets(), type); +} + +void Stickers::apply(const TLDupdateRecentStickers &data) { + // later optimize in case all stickers are already known + //if (data.vis_attached().v) { + // setLastRecentAttachedUpdate(crl::now()); + //} else { + // setLastRecentUpdate(crl::now()); + //} + //specialSetReceived( + // (data.vis_attached().v + // ? Data::Stickers::CloudRecentAttachedSetId + // : Data::Stickers::CloudRecentSetId), + // tr::lng_recent_stickers(tr::now), + // data.vsticker_ids().v); + session().api().requestRecentStickersForce(data.vis_attached().v); +} + +void Stickers::apply(const TLDupdateFavoriteStickers &data) { + // later optimize in case all stickers are already known + //setLastFavedUpdate(crl::now()); + //specialSetReceived( + // Data::Stickers::FavedSetId, + // Lang::Hard::FavedSetTitle(), + // data.vsticker_ids().v); + setLastFavedUpdate(0); + session().api().updateStickers(); +} + +void Stickers::apply(const TLDupdateSavedAnimations &data) { + // later optimize in case all gifs are already known + //setLastSavedGifsUpdate(crl::now()); + //gifsReceived(data.vanimation_ids().v); + setLastSavedGifsUpdate(0); + session().api().updateSavedGifs(); +} + +#if 0 // mtp void Stickers::setsReceived( const QVector &data, uint64 hash) { @@ -697,6 +803,9 @@ void Stickers::emojiReceived( void Stickers::somethingReceived( const QVector &list, uint64 hash, +#endif +void Stickers::somethingReceived( + const QVector &list, StickersType type) { auto &setsOrder = (type == StickersType::Emoji) ? emojiSetsOrderRef() @@ -772,6 +881,7 @@ void Stickers::somethingReceived( session().saveSettings(); } +#if 0 // mtp const auto counted = (type == StickersType::Emoji) ? Api::CountCustomEmojiHash(&session()) : (type == StickersType::Masks) @@ -787,10 +897,12 @@ void Stickers::somethingReceived( ).arg(hash ).arg(counted)); } +#endif notifyUpdated(type); } +#if 0 // mtp void Stickers::setPackAndEmoji( StickersSet &set, StickersPack &&pack, @@ -818,7 +930,175 @@ void Stickers::setPackAndEmoji( } } } +#endif +void Stickers::specialSetReceived( + uint64 setId, + const QString &setTitle, + const QVector &stickers) { + auto &sets = setsRef(); + auto it = sets.find(setId); + + if (stickers.isEmpty()) { + if (it != sets.cend()) { + sets.erase(it); + } + } else { + if (it == sets.cend()) { + it = sets.emplace(setId, std::make_unique( + &owner(), + setId, + uint64(0), // accessHash + uint64(0), // hash + setTitle, + QString(), + 0, // count + SetFlag::Special, + TimeId(0))).first; + } else { + it->second->title = setTitle; + } + const auto set = it->second.get(); + + auto customIt = sets.find(CustomSetId); + auto pack = StickersPack(); + pack.reserve(stickers.size()); + for (const auto &sticker : stickers) { + const auto document = owner().processDocument(sticker); + if (!document->sticker()) { + continue; + } + + pack.push_back(document); + if (customIt != sets.cend()) { + const auto custom = customIt->second.get(); + auto index = custom->stickers.indexOf(document); + if (index >= 0) { + custom->stickers.removeAt(index); + } + } + } + if (customIt != sets.cend() + && customIt->second->stickers.isEmpty()) { + sets.erase(customIt); + customIt = sets.end(); + } + + auto writeRecent = false; + auto &recent = getRecentPack(); + for (auto i = recent.begin(); i != recent.cend();) { + if (set->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) { + i = recent.erase(i); + writeRecent = true; + } else { + ++i; + } + } + + if (pack.isEmpty()) { + sets.erase(it); + } else { + set->stickers = std::move(pack); + } + + if (writeRecent) { + session().saveSettings(); + } + } + + switch (setId) { + case CloudRecentSetId: { + session().local().writeRecentStickers(); + } break; + case CloudRecentAttachedSetId: { + session().local().writeRecentMasks(); + } break; + case FavedSetId: { + session().local().writeFavedStickers(); + } break; + default: Unexpected("setId in SpecialSetReceived()"); + } + + notifyUpdated((setId == CloudRecentAttachedSetId) + ? StickersType::Masks + : StickersType::Stickers); +} + +void Stickers::featuredSetsReceived(const TLtrendingStickerSets &data) { + featuredReceived(data, StickersType::Stickers); +} + +void Stickers::featuredEmojiSetsReceived(const TLtrendingStickerSets &data) { + featuredReceived(data, StickersType::Emoji); +} + +void Stickers::featuredReceived( + const TLtrendingStickerSets &data, + StickersType type) { + if (type == StickersType::Masks) { + return; + } + const auto isEmoji = (type == StickersType::Emoji); + auto &featuredOrder = isEmoji + ? featuredEmojiSetsOrderRef() + : featuredSetsOrderRef(); + featuredOrder.clear(); + + auto &sets = setsRef(); + auto setsToRequest = base::flat_map(); + for (auto &[id, set] : sets) { + // Mark for removing. + if (set->type() == type) { + set->flags &= ~SetFlag::Featured; + } + } + for (const auto &entry : data.data().vsets().v) { + const auto set = feedSet(entry, StickersSetFlag::Featured); + featuredOrder.push_back(set->id); + if (set->stickers.isEmpty() + || (set->flags & SetFlag::NotLoaded)) { + setsToRequest.emplace(set->id, set->accessHash); + } + } + + auto unreadCount = 0; + for (auto it = sets.begin(); it != sets.end();) { + const auto set = it->second.get(); + const auto installed = (set->flags & SetFlag::Installed); + const auto featured = (set->flags & SetFlag::Featured); + const auto special = (set->flags & SetFlag::Special); + const auto archived = (set->flags & SetFlag::Archived); + const auto locked = (set->locked > 0); + if (installed || featured || special || archived || locked) { + if (featured && (set->flags & SetFlag::Unread)) { + if (!(set->flags & SetFlag::Emoji)) { + ++unreadCount; + } + } + ++it; + } else { + it = sets.erase(it); + } + } + setFeaturedSetsUnreadCount(unreadCount); + + if (!setsToRequest.empty()) { + auto &api = session().api(); + for (const auto &[setId, accessHash] : setsToRequest) { + api.scheduleStickerSetRequest(setId, accessHash); + } + api.requestStickerSets(); + } + if (isEmoji) { + session().local().writeFeaturedCustomEmoji(); + } else { + session().local().writeFeaturedStickers(); + } + + notifyUpdated(type); +} + +#if 0 // mtp void Stickers::specialSetReceived( uint64 setId, const QString &setTitle, @@ -1104,6 +1384,8 @@ void Stickers::featuredReceived( } void Stickers::gifsReceived(const QVector &items, uint64 hash) { +#endif +void Stickers::gifsReceived(const QVector &items, uint64 hash) { auto &saved = savedGifsRef(); saved.clear(); @@ -1301,10 +1583,16 @@ std::vector> Stickers::getListByEmoji( } if (forceAllResults || Core::App().settings().suggestStickersByEmoji()) { + const auto addWithSpaces = [](const QString &a, const QString &b) { + return a.isEmpty() ? b : b.isEmpty() ? a : (a + ' ' + b); + }; const auto key = ranges::accumulate( all, QString(), +#if 0 // mtp ranges::plus(), +#endif + addWithSpaces, &Ui::Emoji::One::text); const auto others = session().api().stickersByEmoji(key); if (others) { @@ -1409,6 +1697,7 @@ std::optional>> Stickers::getEmojiListFromSet( return std::nullopt; } +#if 0 // mtp not_null Stickers::feedSet(const MTPStickerSet &info) { auto &sets = setsRef(); const auto &data = info.data(); @@ -1665,6 +1954,265 @@ QString Stickers::getSetTitle(const MTPDstickerSet &s) { } return title; } +#endif + +QString Stickers::getSetTitle(const TLDstickerSetInfo &data) const { + const auto title = data.vtitle().v; + return (data.vis_official().v + && !title.compare(qstr("Great Minds"), Qt::CaseInsensitive)) + ? tr::lng_stickers_default_set(tr::now) + : title; +} + +QString Stickers::getSetTitle(const TLDstickerSet &data) const { + const auto title = data.vtitle().v; + return (data.vis_official().v + && !title.compare(qstr("Great Minds"), Qt::CaseInsensitive)) + ? tr::lng_stickers_default_set(tr::now) + : title; +} + +StickersSet *Stickers::feedSet( + const TLstickerSetInfo &value, + StickersSetFlag sectionFlag) { + const auto &data = value.data(); + auto &sets = setsRef(); + const auto setId = uint64(data.vid().v); + auto it = sets.find(setId); + auto title = getSetTitle(data); + auto oldFlags = StickersSetFlags(0); + const auto installDate = data.vis_installed().v + ? base::unixtime::now() + : TimeId(0); + const auto thumbnail = [&] { + if (const auto thumbnail = data.vthumbnail()) { + return Images::FromThumbnail(*thumbnail); + } + return ImageWithLocation(); + }(); + const auto flags = ParseStickersSetFlags(data); + if (it == sets.cend()) { + it = sets.emplace(data.vid().v, std::make_unique( + &owner(), + setId, + 0, // access_hash + 0, // hash + title, + data.vname().v, + data.vsize().v, + sectionFlag | flags | SetFlag::NotLoaded, + installDate)).first; + it->second->setThumbnail(thumbnail); + } else { + const auto set = it->second.get(); + set->title = title; + set->shortName = data.vname().v; + oldFlags = set->flags; + const auto clientFlags = set->flags + & (SetFlag::Featured + | SetFlag::Unread + | SetFlag::NotLoaded + | SetFlag::Special); + set->flags = sectionFlag | flags | clientFlags; + set->installDate = installDate; + it->second->setThumbnail(thumbnail); + if (set->count != data.vsize().v + || set->stickers.isEmpty() + || set->emoji.empty()) { + // Need to request this data. + set->count = data.vsize().v; + set->flags |= SetFlag::NotLoaded; + } + } + const auto set = it->second.get(); + if ((set->flags & SetFlag::Featured) && !data.vis_viewed().v) { + set->flags |= SetFlag::Unread; + } else { + set->flags &= ~SetFlag::Unread; + } + auto changedFlags = (oldFlags ^ set->flags); + if (changedFlags & SetFlag::Archived) { + const auto masks = !!(set->flags & SetFlag::Masks); + auto &archivedOrder = masks + ? archivedMaskSetsOrderRef() + : archivedSetsOrderRef(); + const auto index = archivedOrder.indexOf(set->id); + if (set->flags & SetFlag::Archived) { + if (index < 0) { + archivedOrder.push_front(set->id); + } + } else if (index >= 0) { + archivedOrder.removeAt(index); + } + } + return it->second.get(); +} + +StickersSet *Stickers::feedSetFull(const TLstickerSet &value) { + const auto &data = value.data(); + const auto id = uint64(data.vid().v); + + auto &sets = setsRef(); + auto it = sets.find(id); + const auto wasArchived = (it != sets.end()) + && (it->second->flags & SetFlag::Archived); + auto title = getSetTitle(data); + auto oldFlags = StickersSetFlags(0); + const auto thumbnail = [&] { + if (const auto thumbnail = data.vthumbnail()) { + return Images::FromThumbnail(*thumbnail); + } + return ImageWithLocation(); + }(); + const auto flags = ParseStickersSetFlags(data); + if (it == sets.cend()) { + it = sets.emplace(data.vid().v, std::make_unique( + &owner(), + data.vid().v, + 0, // access_hash + 0, // hash + title, + data.vname().v, + data.vstickers().v.size(), + flags, + (data.vis_installed().v + ? base::unixtime::now() + : TimeId(0)))).first; + it->second->setThumbnail(thumbnail); + } else { + const auto set = it->second.get(); + set->title = title; + set->shortName = data.vname().v; + oldFlags = set->flags; + const auto clientFlags = set->flags + & (SetFlag::Featured + | SetFlag::Unread + | SetFlag::NotLoaded + | SetFlag::Special); + set->flags = flags | clientFlags; + set->installDate = data.vis_installed().v + ? base::unixtime::now() + : TimeId(0); + it->second->setThumbnail(thumbnail); + } + auto set = it->second.get(); + auto changedFlags = (oldFlags ^ set->flags); + if (changedFlags & SetFlag::Archived) { + const auto masks = !!(set->flags & SetFlag::Masks); + auto &archivedOrder = masks + ? archivedMaskSetsOrderRef() + : archivedSetsOrderRef(); + const auto index = archivedOrder.indexOf(set->id); + if (set->flags & SetFlag::Archived) { + if (index < 0) { + archivedOrder.push_front(set->id); + } + } else if (index >= 0) { + archivedOrder.removeAt(index); + } + } + set->flags &= ~SetFlag::NotLoaded; + + const auto &stickers = data.vstickers().v; + auto customIt = sets.find(Stickers::CustomSetId); + const auto inputSet = set->identifier(); + + auto pack = StickersPack(); + pack.reserve(stickers.size()); + for (const auto &item : stickers) { + const auto document = owner().processDocument(item); + if (!document->sticker()) { + pack.push_back(nullptr); + continue; + } + + pack.push_back(document); + if (!document->sticker()->set.id) { + document->sticker()->set = inputSet; + } + if (customIt != sets.cend()) { + const auto custom = customIt->second.get(); + const auto index = custom->stickers.indexOf(document); + if (index >= 0) { + custom->stickers.removeAt(index); + } + } + } + if (customIt != sets.cend() && customIt->second->stickers.isEmpty()) { + sets.erase(customIt); + customIt = sets.end(); + } + + auto writeRecent = false; + auto &recent = getRecentPack(); + for (auto i = recent.begin(); i != recent.cend();) { + if (set->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) { + i = recent.erase(i); + writeRecent = true; + } else { + ++i; + } + } + + const auto isMasks = !!(set->flags & SetFlag::Masks); + if (ranges::count(pack, nullptr) == pack.size()) { + const auto removeIndex = (isMasks + ? maskSetsOrder() + : setsOrder()).indexOf(set->id); + if (removeIndex >= 0) { + (isMasks + ? maskSetsOrderRef() + : setsOrderRef()).removeAt(removeIndex); + } + sets.remove(set->id); + set = nullptr; + } else { + auto map = base::flat_map(); + const auto &emojis = data.vemojis().v; + Assert(emojis.size() == pack.size()); + for (auto i = 0, l = int(emojis.size()); i != l; ++i) { + if (const auto document = pack[i]) { + for (const auto &string : emojis[i].data().vemojis().v) { + if (const auto emoji = Ui::Emoji::Find(string.v)) { + map[emoji].push_back(document); + } + } + } + } + pack.erase(ranges::remove(pack, nullptr), pack.end()); + set->stickers = std::move(pack); + set->emoji = std::move(map); + } + + if (writeRecent) { + session().saveSettings(); + } + + if (set) { + const auto isArchived = !!(set->flags & SetFlag::Archived); + if (isMasks) { + session().local().writeInstalledMasks(); + } else if (set->flags & SetFlag::Installed) { + if (!isArchived) { + session().local().writeInstalledStickers(); + } + } + if (set->flags & SetFlag::Featured) { + session().local().writeFeaturedStickers(); + } + if (wasArchived != isArchived) { + if (isMasks) { + session().local().writeArchivedMasks(); + } else { + session().local().writeArchivedStickers(); + } + } + } + + notifyUpdated(set->type()); + + return set; +} RecentStickerPack &Stickers::getRecentPack() const { if (cRecentStickers().isEmpty() && !cRecentStickersPreload().isEmpty()) { diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.h b/Telegram/SourceFiles/data/stickers/data_stickers.h index f469f173c7e61..dfdf1f241f179 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers.h @@ -11,6 +11,23 @@ For license and copyright information please follow this link: #include "data/stickers/data_stickers_set.h" #include "settings.h" +namespace Tdb { +class TLanimation; +class TLstickerSet; +class TLstickerSetInfo; +class TLDstickerSet; +class TLDstickerSetInfo; +class TLsticker; +class TLtrendingStickerSets; +class TLstickerType; +class TLDupdateStickerSet; +class TLDupdateInstalledStickerSets; +class TLDupdateTrendingStickerSets; +class TLDupdateRecentStickers; +class TLDupdateFavoriteStickers; +class TLDupdateSavedAnimations; +} // namespace Tdb + class HistoryItem; class DocumentData; @@ -37,6 +54,8 @@ enum class StickersType : uchar { Emoji, }; +[[nodiscard]] StickersType TypeFromTL(const Tdb::TLstickerType &type); + class Stickers final { public: explicit Stickers(not_null owner); @@ -218,6 +237,18 @@ class Stickers final { not_null document, bool faved); + void setsReceived(const QVector &data); + void masksReceived(const QVector &data); + void emojiReceived(const QVector &data); + + void apply(const Tdb::TLDupdateStickerSet &data); + void apply(const Tdb::TLDupdateInstalledStickerSets &data); + void apply(const Tdb::TLDupdateTrendingStickerSets &data); + void apply(const Tdb::TLDupdateRecentStickers &data); + void apply(const Tdb::TLDupdateFavoriteStickers &data); + void apply(const Tdb::TLDupdateSavedAnimations &data); + +#if 0 // mtp void setsReceived(const QVector &data, uint64 hash); void masksReceived(const QVector &data, uint64 hash); void emojiReceived(const QVector &data, uint64 hash); @@ -232,6 +263,14 @@ class Stickers final { void featuredEmojiSetsReceived( const MTPmessages_FeaturedStickers &result); void gifsReceived(const QVector &items, uint64 hash); +#endif + void specialSetReceived( + uint64 setId, + const QString &setTitle, + const QVector &stickers); + void featuredSetsReceived(const Tdb::TLtrendingStickerSets &data); + void featuredEmojiSetsReceived(const Tdb::TLtrendingStickerSets &data); + void gifsReceived(const QVector &items, uint64 hash); std::vector> getListByEmoji( std::vector emoji, @@ -240,6 +279,7 @@ class Stickers final { std::optional>> getEmojiListFromSet( not_null document); +#if 0 // mtp not_null feedSet(const MTPStickerSet &data); not_null feedSet(const MTPStickerSetCovered &data); not_null feedSetFull(const MTPDmessages_stickerSet &data); @@ -253,6 +293,15 @@ class Stickers final { void newSetReceived(const MTPDmessages_stickerSet &set); QString getSetTitle(const MTPDstickerSet &s); +#endif + + StickersSet *feedSet( + const Tdb::TLstickerSetInfo &value, + StickersSetFlag sectionFlag = StickersSetFlag()); + StickersSet *feedSetFull(const Tdb::TLstickerSet &value); + [[nodiscard]] QString getSetTitle( + const Tdb::TLDstickerSetInfo &data) const; + [[nodiscard]] QString getSetTitle(const Tdb::TLDstickerSet &data) const; RecentStickerPack &getRecentPack() const; @@ -280,6 +329,7 @@ class Stickers final { void requestSetToPushFaved( std::shared_ptr show, not_null document); +#if 0 // mtp void setPackAndEmoji( StickersSet &set, StickersPack &&pack, @@ -292,6 +342,13 @@ class Stickers final { void featuredReceived( const MTPDmessages_featuredStickers &data, StickersType type); +#endif + void somethingReceived( + const QVector &list, + StickersType type); + void featuredReceived( + const Tdb::TLtrendingStickerSets &data, + StickersType type); const not_null _owner; rpl::event_stream _updated; diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp index 347a3f13ca26d..231edea9f251b 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp @@ -15,8 +15,12 @@ For license and copyright information please follow this link: #include "storage/file_download.h" #include "ui/image/image.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { +using namespace Tdb; + StickersSetThumbnailView::StickersSetThumbnailView( not_null owner) : _owner(owner) { @@ -46,6 +50,7 @@ QByteArray StickersSetThumbnailView::content() const { return _content; } +#if 0 // mtp StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) { using Flag = StickersSetFlag; return (data.is_archived() ? Flag::Archived : Flag()) @@ -56,6 +61,41 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) { | (data.is_videos() ? Flag::Webm : Flag()) | (data.is_text_color() ? Flag::TextColor : Flag()); } +#endif + +StickersSetFlags ParseStickersSetFlags(const TLDstickerSet &data) { + using Flag = StickersSetFlag; + return (data.vis_archived().v ? Flag::Archived : Flag()) + | (data.vis_official().v ? Flag::Official : Flag()) + | (data.vsticker_type().type() == id_stickerTypeMask + ? Flag::Masks + : Flag()) + | (data.vsticker_type().type() == id_stickerTypeCustomEmoji + ? Flag::Emoji + : Flag()) + | (data.vis_installed().v ? Flag::Installed : Flag()) + | (data.vsticker_format().type() == id_stickerFormatWebm + ? Flag::Webm + : Flag()) + | (data.vneeds_repainting().v ? Flag::TextColor : Flag()); +} + +StickersSetFlags ParseStickersSetFlags(const TLDstickerSetInfo &data) { + using Flag = StickersSetFlag; + return (data.vis_archived().v ? Flag::Archived : Flag()) + | (data.vis_official().v ? Flag::Official : Flag()) + | (data.vsticker_type().type() == id_stickerTypeMask + ? Flag::Masks + : Flag()) + | (data.vsticker_type().type() == id_stickerTypeCustomEmoji + ? Flag::Emoji + : Flag()) + | (data.vis_installed().v ? Flag::Installed : Flag()) + | (data.vsticker_format().type() == id_stickerFormatWebm + ? Flag::Webm + : Flag()) + | (data.vneeds_repainting().v ? Flag::TextColor : Flag()); +} StickersSet::StickersSet( not_null owner, @@ -88,11 +128,13 @@ Main::Session &StickersSet::session() const { return _owner->session(); } +#if 0 // mtp MTPInputStickerSet StickersSet::mtpInput() const { return (id && accessHash) ? MTP_inputStickerSetID(MTP_long(id), MTP_long(accessHash)) : MTP_inputStickerSetShortName(MTP_string(shortName)); } +#endif StickerSetIdentifier StickersSet::identifier() const { return StickerSetIdentifier{ @@ -174,6 +216,10 @@ Storage::Cache::Key StickersSet::thumbnailBigFileBaseCacheKey() const { const auto &location = _thumbnail.location.file().data; if (const auto storage = std::get_if(&location)) { return storage->bigFileBaseCacheKey(); + } else if (const auto file = std::get_if(&location)) { + return TdbFileLocation::BigFileBaseCacheKey( + file->hash, + kTdbLocationTypeStickerSetThumb); } return {}; } @@ -212,6 +258,7 @@ std::shared_ptr StickersSet::activeThumbnailView() { return _thumbnailView.lock(); } +#if 0 // mtp MTPInputStickerSet InputStickerSet(StickerSetIdentifier id) { return !id ? MTP_inputStickerSetEmpty() @@ -232,5 +279,6 @@ StickerSetIdentifier FromInputSet(const MTPInputStickerSet &id) { return StickerSetIdentifier(); }); } +#endif } // namespace Stickers diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.h b/Telegram/SourceFiles/data/stickers/data_stickers_set.h index bf9a6e4177f23..8808db041451b 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.h @@ -9,6 +9,11 @@ For license and copyright information please follow this link: #include "data/data_cloud_file.h" +namespace Tdb { +class TLDstickerSet; +class TLDstickerSetInfo; +} // namespace Tdb + class DocumentData; namespace Main { @@ -62,8 +67,15 @@ enum class StickersSetFlag { inline constexpr bool is_flag_type(StickersSetFlag) { return true; }; using StickersSetFlags = base::flags; +#if 0 // mtp [[nodiscard]] StickersSetFlags ParseStickersSetFlags( const MTPDstickerSet &data); +#endif + +[[nodiscard]] StickersSetFlags ParseStickersSetFlags( + const Tdb::TLDstickerSet &data); +[[nodiscard]] StickersSetFlags ParseStickersSetFlags( + const Tdb::TLDstickerSetInfo &data); class StickersSet final { public: @@ -82,7 +94,9 @@ class StickersSet final { [[nodiscard]] Data::Session &owner() const; [[nodiscard]] Main::Session &session() const; +#if 0 // mtp [[nodiscard]] MTPInputStickerSet mtpInput() const; +#endif [[nodiscard]] StickerSetIdentifier identifier() const; [[nodiscard]] StickersType type() const; [[nodiscard]] bool textColor() const; @@ -123,7 +137,9 @@ class StickersSet final { }; +#if 0 // mtp [[nodiscard]] MTPInputStickerSet InputStickerSet(StickerSetIdentifier id); [[nodiscard]] StickerSetIdentifier FromInputSet(const MTPInputStickerSet &id); +#endif } // namespace Data diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 5743e74d67d03..3d0e9669d8a70 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -348,6 +348,12 @@ dialogsArchiveUserpic: icon {{ "archive_userpic", historyPeerUserpicFg }}; dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }}; dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerUserpicFg }}; +dialogsSecretChatIcon: ThreeStateIcon { + icon: icon {{ "emoji/premium_lock", boxTextFgGood }}; + over: icon {{ "emoji/premium_lock", boxTextFgGood }}; + active: icon {{ "emoji/premium_lock", dialogsChatIconFgActive }}; +} + dialogsSendStateSkip: 20px; dialogsSendingIcon: ThreeStateIcon { icon: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }}; diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index f2d5ebcd433c4..86fb6b352d9c8 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -151,6 +151,9 @@ void Entry::pinnedIndexChanged(FilterId filterId, int was, int now) { // Force reorder in support mode. _sortKeyInChatList = 0; } + refreshChatListSortPositionFromTdb( + filterId, + tdbOrderInChatList(filterId)); updateChatListSortPosition(); updateChatListEntry(); if ((was != 0) != (now != 0)) { @@ -174,11 +177,14 @@ void Entry::cachePinnedIndex(FilterId filterId, int index) { pinnedIndexChanged(filterId, was, index); } +#if 0 // mtp bool Entry::needUpdateInChatList() const { return inChatList() || shouldBeInChatList(); } +#endif void Entry::updateChatListSortPosition() { +#if 0 // mtp if (session().supportMode() && _sortKeyInChatList != 0 && session().settings().supportFixChatsOrder()) { @@ -195,6 +201,106 @@ void Entry::updateChatListSortPosition() { } else { _sortKeyInChatList = _sortKeyByDate = 0; } +#endif + if (const auto folder = asFolder()) { + const auto order = folder->chatsList()->empty() + ? 0 + : FixedOnTopDialogPos(kArchiveFixOnTopIndex); + updateChatListSortPosition(FilterId(), order, false); + } else if (const auto topic = asTopic()) { + // tdlib won't be needed when tdlib manages topics + auto sortKeyByDate = DialogPosFromDate( + topic->adjustedChatListTimeId()); + const auto pinnedIndex = lookupPinnedIndex(FilterId()); + if (inChatList() + || (pinnedIndex != 0) + || !topic->lastMessageKnown() + || (topic->lastMessage() != nullptr)) { + const auto fixedIndex = fixedOnTopIndex(); + _sortKeyInChatList = fixedIndex + ? FixedOnTopDialogPos(fixedIndex) + : pinnedIndex + ? PinnedDialogPos(pinnedIndex) + : sortKeyByDate; + } else { + _sortKeyInChatList = 0; + } + if (_sortKeyInChatList) { + owner().refreshChatListEntry(this, FilterId()); + updateChatListEntry(); + } else { + owner().removeChatListEntry(this, FilterId()); + } + } +} + +void Entry::updateChatListSortPosition( + FilterId filterId, + uint64 order, + bool pinned) { + const auto pinnedUnchanged = [&] { + return (pinned == isPinnedDialog(filterId)); + }; + if (filterId) { + const auto i = _tdbOrderInFilterMap.find(filterId); + const auto unchanged = order + ? (i != end(_tdbOrderInFilterMap) && i->second == order) + : (i == end(_tdbOrderInFilterMap)); + if (unchanged && pinnedUnchanged()) { + return; + } else if (!order) { + _tdbOrderInFilterMap.erase(i); + } else if (i != end(_tdbOrderInFilterMap)) { + i->second = order; + } else { + _tdbOrderInFilterMap.emplace(filterId, order); + } + } else { + if (_tdbOrderInChatList == order && pinnedUnchanged()) { + return; + } + _tdbOrderInChatList = order; + } + if (const auto history = asHistory()) { + // Pinned index depends on Tdb order. + owner().setChatPinned(history, filterId, pinned); + } + refreshChatListSortPositionFromTdb(filterId, order); +} + +void Entry::refreshChatListSortPositionFromTdb( + FilterId filterId, + uint64 order) { + const auto fixedIndex = filterId ? 0 : fixedOnTopIndex(); + const auto index = order ? lookupPinnedIndex(filterId) : 0; + const auto sortKey = fixedIndex + ? FixedOnTopDialogPos(fixedIndex) + : index + ? PinnedDialogPos(index) + : order; + if (filterId) { + if (order) { + _sortKeyInFilterMap[filterId] = sortKey; + } else { + _sortKeyInFilterMap.remove(filterId); + } + } else { + _sortKeyInChatList = sortKey; + } + if (sortKey) { + owner().refreshChatListEntry(this, filterId); + updateChatListEntry(); + } else { + owner().removeChatListEntry(this, filterId); + } +} + +uint64 Entry::tdbOrderInChatList(FilterId filterId) const { + if (filterId) { + const auto i = _tdbOrderInFilterMap.find(filterId); + return (i != end(_tdbOrderInFilterMap)) ? i->second : uint64(0); + } + return _tdbOrderInChatList; } int Entry::lookupPinnedIndex(FilterId filterId) const { @@ -210,12 +316,25 @@ int Entry::lookupPinnedIndex(FilterId filterId) const { } uint64 Entry::computeSortPosition(FilterId filterId) const { + Expects(filterId != 0); +#if 0 // mtp const auto index = lookupPinnedIndex(filterId); return index ? PinnedDialogPos(index) : _sortKeyByDate; +#endif + const auto i = _sortKeyInFilterMap.find(filterId); + return (i != end(_sortKeyInFilterMap)) ? i->second : 0ULL; } void Entry::updateChatListExistence() { +#if 0 // mtp setChatListExistence(shouldBeInChatList()); +#endif + if (const auto folder = asFolder()) { + const auto order = folder->chatsList()->empty() + ? 0 + : FixedOnTopDialogPos(kArchiveFixOnTopIndex); + updateChatListSortPosition(FilterId(), order, false); + } } void Entry::notifyUnreadStateChange(const UnreadState &wasState) { @@ -258,6 +377,7 @@ const Ui::Text::String &Entry::chatListNameText() const { return _chatListNameText; } +#if 0 // mtp void Entry::setChatListExistence(bool exists) { if (exists && _sortKeyInChatList) { owner().refreshChatListEntry(this); @@ -270,6 +390,7 @@ void Entry::setChatListExistence(bool exists) { TimeId Entry::adjustedChatListTimeId() const { return chatListTimeId(); } +#endif void Entry::changedChatListPinHook() { } @@ -307,11 +428,18 @@ PositionChange Entry::adjustByPosInChatList( } void Entry::setChatListTimeId(TimeId date) { +#if 0 // mtp _timeId = date; updateChatListSortPosition(); if (const auto folder = this->folder()) { folder->updateChatListSortPosition(); } +#endif + if (const auto topic = asTopic()) { + // tdlib won't be needed when tdlib manages topics + _timeId = date; + updateChatListSortPosition(); + } } int Entry::posInChatList(FilterId filterId) const { diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index 3ad4281f3000a..b5060af11e6a5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -69,6 +69,8 @@ struct UnreadState { int reactions = 0; int reactionsMuted = 0; int mentions = 0; + bool knownMessages = false; + bool knownChats = false; bool known = false; UnreadState &operator+=(const UnreadState &other) { @@ -205,14 +207,24 @@ class Entry : public base::has_weak_ptr { void updateChatListSortPosition(); void setChatListTimeId(TimeId date); virtual void updateChatListExistence(); +#if 0 // mtp bool needUpdateInChatList() const; virtual TimeId adjustedChatListTimeId() const; +#endif + [[nodiscard]] uint64 tdbOrderInChatList(FilterId filterId) const; + + void updateChatListSortPosition( + FilterId filterId, + uint64 order, + bool pinned); virtual int fixedOnTopIndex() const = 0; static constexpr auto kArchiveFixOnTopIndex = 1; static constexpr auto kTopPromotionFixOnTopIndex = 2; +#if 0 // mtp virtual bool shouldBeInChatList() const = 0; +#endif virtual UnreadState chatListUnreadState() const = 0; virtual BadgesState chatListBadgesState() const = 0; virtual HistoryItem *chatListMessage() const = 0; @@ -236,6 +248,7 @@ class Entry : public base::has_weak_ptr { Ui::PeerUserpicView &view, const Ui::PaintContext &context) const = 0; + // tdlib later won't be needed when tdlib manages topics [[nodiscard]] TimeId chatListTimeId() const { return _timeId; } @@ -267,18 +280,28 @@ class Entry : public base::has_weak_ptr { [[nodiscard]] virtual int chatListNameVersion() const = 0; +#if 0 // mtp void setChatListExistence(bool exists); +#endif not_null mainChatListLink(FilterId filterId) const; Row *maybeMainChatListLink(FilterId filterId) const; + void refreshChatListSortPositionFromTdb(FilterId filterId, uint64 order); + const not_null _owner; base::flat_map _chatListLinks; uint64 _sortKeyInChatList = 0; + base::flat_map _sortKeyInFilterMap; + uint64 _tdbOrderInChatList = 0; + base::flat_map _tdbOrderInFilterMap; +#if 0 // mtp uint64 _sortKeyByDate = 0; +#endif base::flat_map _pinnedIndex; mutable Ui::PeerBadge _chatListPeerBadge; mutable Ui::Text::String _chatListNameText; mutable int _chatListNameVersion = 0; + // tdlib won't be needed when tdlib manages topics TimeId _timeId = 0; Flags _flags; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index a6863e35736a8..aa06402731124 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2654,8 +2654,11 @@ void InnerWidget::searchReceived( void InnerWidget::peerSearchReceived( const QString &query, + const std::vector> &result) { +#if 0 // mtp const QVector &my, const QVector &result) { +#endif if (_state != WidgetState::Filtered) { return; } @@ -2663,6 +2666,7 @@ void InnerWidget::peerSearchReceived( _peerSearchQuery = query.toLower().trimmed(); _peerSearchResults.clear(); _peerSearchResults.reserve(result.size()); +#if 0 // mtp for (const auto &mtpPeer : my) { if (const auto peer = session().data().peerLoaded(peerFromMTP(mtpPeer))) { appendToFiltered(peer->owner().history(peer)); @@ -2687,6 +2691,39 @@ void InnerWidget::peerSearchReceived( ).arg(peerFromMTP(mtpPeer).value)); } } +#endif + for (const auto &peer : result) { + if (const auto history = peer->owner().historyLoaded(peer)) { + if (history->inChatList()) { + continue; // skip existing chats + } + } + _peerSearchResults.push_back(std::make_unique( + peer)); + } + refresh(); +} + +void InnerWidget::cloudChatsReceived( + const QString &query, + const std::vector> &result) { + if (_state != WidgetState::Filtered) { + return; + } + const auto alreadyAdded = [&](not_null history) { + for (const auto &result : _filterResults) { + if (result.row->history() == history.get()) { + return true; + } + } + return false; + }; + for (const auto &history : result) { + if (alreadyAdded(history)) { + continue; + } + appendToFiltered(history); + } refresh(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 608513c853c66..7ed428666d96e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -96,10 +96,18 @@ class InnerWidget final : public Ui::RpWidget { HistoryItem *inject, SearchRequestType type, int fullCount); +#if 0 // mtp void peerSearchReceived( const QString &query, const QVector &my, const QVector &result); +#endif + void peerSearchReceived( + const QString &query, + const std::vector> &result); + void cloudChatsReceived( + const QString &query, + const std::vector> &result); [[nodiscard]] FilterId filterId() const; diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index 9c9ad67baea49..8c198a7cc82b9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -14,7 +14,14 @@ For license and copyright information please follow this link: #include "history/history_unread_things.h" #include "history/history.h" +#include "tdb/tdb_tl_scheme.h" + namespace Dialogs { +namespace { + +using namespace Tdb; + +} // namespace MainList::MainList( not_null session, @@ -167,17 +174,54 @@ void MainList::updateCloudUnread(const MTPDdialogFolder &data) { _cloudUnreadState.known = true; } +void MainList::updateCloudUnread(const TLDupdateUnreadMessageCount &data) { + const auto notifier = unreadStateChangeNotifier(!loaded()); + + const auto count = data.vunread_count().v; + const auto unmuted = data.vunread_unmuted_count().v; + + _cloudUnreadState.messages = count; + _cloudUnreadState.messagesMuted = count - unmuted; + finalizeCloudUnread(); + + _cloudUnreadState.knownMessages = true; + _cloudUnreadState.known = _cloudUnreadState.knownChats; +} + +void MainList::updateCloudUnread(const TLDupdateUnreadChatCount &data) { + const auto notifier = unreadStateChangeNotifier(!loaded()); + + const auto unreadMarks = data.vmarked_as_unread_count().v; + const auto unreadMarksUnmuted = data.vmarked_as_unread_unmuted_count().v; + const auto unreadChats = data.vunread_count().v; + const auto unreadChatsUnmuted = data.vunread_unmuted_count().v; + + _cloudUnreadState.chats = unreadChats; + _cloudUnreadState.chatsMuted = unreadChats - unreadChatsUnmuted; + _cloudUnreadState.marks = unreadMarks; + _cloudUnreadState.marksMuted = unreadMarks - unreadMarksUnmuted; + finalizeCloudUnread(); + + _cloudListSize = data.vtotal_count().v; + recomputeFullListSize(); + + _cloudUnreadState.knownChats = true; + _cloudUnreadState.known = _cloudUnreadState.knownMessages; +} + bool MainList::cloudUnreadKnown() const { return _cloudUnreadState.known; } void MainList::finalizeCloudUnread() { +#if 0 // mtp // Cloud state for archive folder always counts everything as muted. _cloudUnreadState.messagesMuted = _cloudUnreadState.messages; _cloudUnreadState.chatsMuted = _cloudUnreadState.chats; // We don't know the real value of marked chats counts in cloud unread. _cloudUnreadState.marksMuted = _cloudUnreadState.marks = 0; +#endif } UnreadState MainList::unreadState() const { diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.h b/Telegram/SourceFiles/dialogs/dialogs_main_list.h index 157e48e314d27..bcf3b6e553d7b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.h @@ -10,6 +10,11 @@ For license and copyright information please follow this link: #include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_pinned_list.h" +namespace Tdb { +class TLDupdateUnreadMessageCount; +class TLDupdateUnreadChatCount; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -41,6 +46,8 @@ class MainList final { const UnreadState &nowState); void unreadEntryChanged(const UnreadState &state, bool added); void updateCloudUnread(const MTPDdialogFolder &data); + void updateCloudUnread(const Tdb::TLDupdateUnreadMessageCount &data); + void updateCloudUnread(const Tdb::TLDupdateUnreadChatCount &data); [[nodiscard]] bool cloudUnreadKnown() const; [[nodiscard]] UnreadState unreadState() const; [[nodiscard]] rpl::producer unreadStateChanges() const; diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp index 1382201a84aef..9262241ff8375 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp @@ -31,6 +31,7 @@ void PinnedList::setLimit(int limit) { applyLimit(_limit); } +#if 0 // mtp void PinnedList::addPinned(Key key) { Expects(key.entry()->folderKnown()); @@ -48,11 +49,13 @@ int PinnedList::addPinnedGetPosition(Key key) { key.entry()->cachePinnedIndex(_filterId, position + 1); return position; } +#endif void PinnedList::setPinned(Key key, bool pinned) { Expects(key.entry()->folderKnown() || _filterId != 0); if (pinned) { +#if 0 // mtp const int position = addPinnedGetPosition(key); if (position) { const auto begin = _data.begin(); @@ -61,6 +64,30 @@ void PinnedList::setPinned(Key key, bool pinned) { _data[i].entry()->cachePinnedIndex(_filterId, i + 1); } } +#endif + const auto order = key.entry()->tdbOrderInChatList(_filterId); + const auto already = ranges::find(_data, key); + if (already != end(_data)) { + const auto reorderedAbove = (already != begin(_data)) + && (order + > (already - 1)->entry()->tdbOrderInChatList(_filterId)); + const auto reorderedBelow = (already + 1 != end(_data)) + && (order + < (already + 1)->entry()->tdbOrderInChatList(_filterId)); + const auto reordered = reorderedAbove || reorderedBelow; + if (!reordered) { + return; + } + } else { + applyLimit(_limit - 1); + _data.push_back(key); + } + ranges::sort(_data, std::greater<>(), [&](const Key &key) { + return key.entry()->tdbOrderInChatList(_filterId); + }); + for (auto i = 0, count = int(size(_data)); i != count; ++i) { + _data[i].entry()->cachePinnedIndex(_filterId, i + 1); + } } else if (const auto it = ranges::find(_data, key); it != end(_data)) { const auto index = int(it - begin(_data)); _data.erase(it); @@ -83,6 +110,7 @@ void PinnedList::clear() { applyLimit(0); } +#if 0 // mtp void PinnedList::applyList( not_null owner, const QVector &list) { @@ -106,6 +134,7 @@ void PinnedList::applyList( addPinned(forum->topicFor(topicId.v)); } } +#endif void PinnedList::applyList(const std::vector> &list) { Expects(_filterId != 0); diff --git a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h index ce18ae9c31f07..0644e5360e42a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h @@ -24,21 +24,26 @@ class PinnedList final { void setLimit(int limit); +#if 0 // mtp // Places on the last place in the list otherwise. // Does nothing if already pinned. void addPinned(Key key); +#endif // if (pinned) places on the first place in the list. void setPinned(Key key, bool pinned); void clear(); +#if 0 // mtp void applyList( not_null owner, const QVector &list); void applyList( not_null forum, const QVector &list); +#endif + void applyList(const std::vector> &list); void reorder(Key key1, Key key2); @@ -47,7 +52,9 @@ class PinnedList final { } private: +#if 0 // mtp int addPinnedGetPosition(Key key); +#endif void applyLimit(int limit); FilterId _filterId = 0; diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index a6459bbc46baa..156d247864654 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -296,7 +296,10 @@ void Row::setCornerBadgeShown( void Row::updateCornerBadgeShown( not_null peer, Fn updateCallback) const { +#if 0 // mtp const auto user = peer->asUser(); +#endif + const auto user = peer->asOneOnOne(); const auto now = user ? base::unixtime::now() : TimeId(); const auto nextLayer = [&] { if (user && Data::IsUserOnline(user, now)) { @@ -409,10 +412,14 @@ void Row::PaintCornerBadgeFrame( const auto online = peer && peer->isUser(); const auto size = online + ? st::dialogsOnlineBadgeSize + : peer->isSecretChat() ? st::dialogsOnlineBadgeSize : st::dialogsCallBadgeSize; const auto stroke = st::dialogsOnlineBadgeStroke; const auto skip = online + ? st::dialogsOnlineBadgeSkip + : peer->isSecretChat() ? st::dialogsOnlineBadgeSkip : st::dialogsCallBadgeSkip; const auto shrink = (size / 2) * (1. - topLayerProgress); @@ -536,6 +543,8 @@ void Row::paintUserpic( const auto history = _id.history(); if (!history || history->peer->isUser()) { return; + } else if (history->peer->isSecretChat()) { + return; } const auto actionPainter = history->sendActionPainter(); const auto bg = context.active diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 7bb0fb8059b80..9d901a869e636 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -77,6 +77,9 @@ For license and copyright information please follow this link: #include "styles/style_window.h" #include "base/qt/qt_common_adapters.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + #include #include #include @@ -84,6 +87,8 @@ For license and copyright information please follow this link: namespace Dialogs { namespace { +using namespace Tdb; + constexpr auto kSearchPerPage = 50; constexpr auto kStoriesExpandDuration = crl::time(200); @@ -200,7 +205,10 @@ Widget::Widget( not_null controller, Layout layout) : Window::AbstractSectionWidget(parent, controller, nullptr) +#if 0 // mtp , _api(&controller->session().mtp()) +#endif +, _api(&controller->session().sender()) , _chooseByDragTimer([=] { _inner->chooseRow(); }) , _layout(layout) , _narrowWidth(st::defaultDialogRow.padding.left() @@ -979,9 +987,12 @@ void Widget::fullSearchRefreshOn(rpl::producer<> events) { _searchTimer.cancel(); _searchCache.clear(); _singleMessageSearch.clear(); +#if 0 // mtp for (const auto &[requestId, query] : base::take(_searchQueries)) { session().api().request(requestId).cancel(); } +#endif + clearSearchCache(); _searchQuery = QString(); _scroll->scrollToY(0); cancelSearchRequest(); @@ -1711,7 +1722,9 @@ bool Widget::searchMessages(bool searchCache) { if (i != _searchCache.end()) { _searchQuery = q; _searchQueryFrom = _searchFromAuthor; +#if 0 // mtp _searchNextRate = 0; +#endif _searchFull = _searchFullMigrated = false; cancelSearchRequest(); searchReceived( @@ -1725,11 +1738,33 @@ bool Widget::searchMessages(bool searchCache) { } else if (_searchQuery != q || _searchQueryFrom != _searchFromAuthor) { _searchQuery = q; _searchQueryFrom = _searchFromAuthor; +#if 0 // mtp _searchNextRate = 0; +#endif _searchFull = _searchFullMigrated = false; cancelSearchRequest(); if (const auto peer = searchInPeer()) { const auto topic = searchInTopic(); + const auto type = SearchRequestType::PeerFromStart; + _searchRequest = _api.request(TLsearchChatMessages( + peerToTdbChat(peer->id), + tl_string(q), + (_searchQueryFrom + ? peerToSender(_searchQueryFrom->id) + : std::optional()), + tl_int53(0), // from_message_id + tl_int32(0), // offset + tl_int32(kSearchPerPage), + std::nullopt, // filter + tl_int53(topic ? topic->rootId().bare : 0) + )).done([=](const TLfoundChatMessages &result) { + _searchRequest = 0; + searchReceived(type, result); + }).fail([=] { + _searchRequest = 0; + _searchFull = true; + }).send(); +#if 0 // mtp auto &histories = session().data().histories(); const auto type = Data::Histories::RequestType::History; const auto history = session().data().history(peer); @@ -1766,8 +1801,27 @@ bool Widget::searchMessages(bool searchCache) { _searchQueries.emplace(_searchRequest, _searchQuery); return _searchRequest; }); +#endif } else { const auto type = SearchRequestType::FromStart; + _searchRequest = _api.request(TLsearchMessages( + (session().settings().skipArchiveInSearch() + ? tl_chatListMain() + : std::optional()), + tl_string(_searchQuery), + tl_string(), // offset + tl_int32(kSearchPerPage), + std::nullopt, + tl_int32(0), // min_date + tl_int32(0) + )).done([=](const TLfoundMessages &result) { + _searchRequest = 0; + searchReceived(type, result); + }).fail([=] { + _searchRequest = 0; + _searchFull = true; + }).send(); +#if 0 // mtp const auto flags = session().settings().skipArchiveInSearch() ? MTPmessages_SearchGlobal::Flag::f_folder_id : MTPmessages_SearchGlobal::Flag(0); @@ -1789,6 +1843,7 @@ bool Widget::searchMessages(bool searchCache) { searchFailed(type, error, _searchRequest); }).send(); _searchQueries.emplace(_searchRequest, _searchQuery); +#endif } } const auto query = Api::ConvertPeerSearchQuery(q); @@ -1804,6 +1859,12 @@ bool Widget::searchMessages(bool searchCache) { } else if (_peerSearchQuery != query) { _peerSearchQuery = query; _peerSearchFull = false; + _peerSearchRequest = _api.request(TLsearchPublicChats( + tl_string(_peerSearchQuery) + )).done([=](const TLchats &result) { + peerSearchReceived(query, result); + }).send(); +#if 0 // mtp _peerSearchRequest = _api.request(MTPcontacts_Search( MTP_string(_peerSearchQuery), MTP_int(SearchPeopleLimit) @@ -1812,12 +1873,36 @@ bool Widget::searchMessages(bool searchCache) { }).fail([=](const MTP::Error &error, mtpRequestId requestId) { peopleFailed(error, requestId); }).send(); +#endif _peerSearchQueries.emplace(_peerSearchRequest, _peerSearchQuery); } + if (searchCache) { + auto i = _cloudChatsCache.find(query); + if (i != _cloudChatsCache.end()) { + _cloudChatsQuery = query; + cloudChatsReceived(i->second); + result = true; + } + } else if (_cloudChatsQuery != query) { + _cloudChatsQuery = query; + if (!_cloudChatsQueries.contains(query)) { + const auto requestId = _api.request(TLsearchChatsOnServer( + tl_string(_cloudChatsQuery), + tl_int32(kSearchPerPage) + )).done([=](const TLchats &result) { + cloudChatsReceived(query, result); + }).send(); + _cloudChatsQueries.emplace(query, requestId); + } + } } else { _api.request(base::take(_peerSearchRequest)).cancel(); _peerSearchQuery = query; _peerSearchFull = true; + peerSearchReceived({}, 0); + _cloudChatsQuery = query; + cloudChatsReceived({}); +#if 0 // mtp peerSearchReceived( MTP_contacts_found( MTP_vector(0), @@ -1825,6 +1910,7 @@ bool Widget::searchMessages(bool searchCache) { MTP_vector(0), MTP_vector(0)), 0); +#endif } if (searchForTopicsRequired(query)) { if (searchCache) { @@ -1926,6 +2012,15 @@ void Widget::searchTopics() { return; } _api.request(base::take(_topicSearchRequest)).cancel(); + _topicSearchRequest = _api.request(TLgetForumTopics( + peerToTdbChat(_openedForum->channel()->id), + tl_string(_topicSearchQuery), + tl_int32(_topicSearchOffsetDate), + tl_int64(_topicSearchOffsetId.bare), + tl_int53(_topicSearchOffsetTopicId.bare), + tl_int32(kSearchPerPage) + )).done([=](const TLDforumTopics &result) { +#if 0 // mtp _topicSearchRequest = _api.request(MTPchannels_GetForumTopics( MTP_flags(MTPchannels_GetForumTopics::Flag::f_q), _openedForum->channel()->inputChannel, @@ -1952,6 +2047,12 @@ void Widget::searchTopics() { } _inner->appendToFiltered(topic); }); +#endif + const auto savedTopicId = _topicSearchOffsetTopicId; + _openedForum->applyReceivedTopics(result.vtopics().v); + _topicSearchOffsetDate = result.vnext_offset_date().v; + _topicSearchOffsetId = result.vnext_offset_message_id().v; + _topicSearchOffsetTopicId = result.vnext_offset_message_thread_id().v; if (_topicSearchOffsetTopicId != savedTopicId) { _inner->refresh(); } else { @@ -1968,6 +2069,26 @@ void Widget::searchMore() { } if (!_searchFull) { if (const auto peer = searchInPeer()) { + const auto topic = searchInTopic(); + _searchRequest = _api.request(TLsearchChatMessages( + peerToTdbChat(peer->id), + tl_string(_searchQuery), + (_searchQueryFrom + ? peerToSender(_searchQueryFrom->id) + : std::optional()), + tl_int53(_nextSearchMessageId.bare), + tl_int32(0), // offset + tl_int32(kSearchPerPage), + std::nullopt, // filter + tl_int53(topic ? topic->rootId().bare : 0) + )).done([=](const TLfoundChatMessages &result) { + _searchRequest = 0; + searchReceived(SearchRequestType::PeerFromOffset, result); + }).fail([=] { + _searchRequest = 0; + _searchFull = true; + }).send(); +#if 0 // mtp auto &histories = session().data().histories(); const auto topic = searchInTopic(); const auto type = Data::Histories::RequestType::History; @@ -2009,7 +2130,26 @@ void Widget::searchMore() { } return _searchRequest; }); +#endif } else { + _searchRequest = _api.request(TLsearchMessages( + (session().settings().skipArchiveInSearch() + ? tl_chatListMain() + : std::optional()), + tl_string(_searchQuery), + tl_string(_nextSearchOffset), + tl_int32(kSearchPerPage), + std::nullopt, + tl_int32(0), // min_date + tl_int32(0) + )).done([=](const TLfoundMessages &result) { + _searchRequest = 0; + searchReceived(SearchRequestType::FromOffset, result); + }).fail([=] { + _searchRequest = 0; + _searchFull = true; + }).send(); +#if 0 // mtp const auto type = _lastSearchId ? SearchRequestType::FromOffset : SearchRequestType::FromStart; @@ -2038,8 +2178,32 @@ void Widget::searchMore() { if (!_lastSearchId) { _searchQueries.emplace(_searchRequest, _searchQuery); } +#endif } } else if (_searchInMigrated && !_searchFullMigrated) { + auto offsetMigratedId = _nextSearchMigratedMessageId; + const auto type = offsetMigratedId + ? SearchRequestType::MigratedFromOffset + : SearchRequestType::MigratedFromStart; + _searchRequest = _api.request(TLsearchChatMessages( + peerToTdbChat(_searchInMigrated->peer->id), + tl_string(_searchQuery), + (_searchQueryFrom + ? peerToSender(_searchQueryFrom->id) + : std::optional()), + tl_int53(offsetMigratedId.bare), + tl_int32(0), // offset + tl_int32(kSearchPerPage), + std::nullopt, // filter + tl_int53(0) // message_thread_id + )).done([=](const TLfoundChatMessages &result) { + _searchRequest = 0; + searchReceived(type, result); + }).fail([=] { + _searchRequest = 0; + _searchFullMigrated = true; + }).send(); +#if 0 // mtp auto &histories = session().data().histories(); const auto type = Data::Histories::RequestType::History; const auto history = _searchInMigrated; @@ -2078,9 +2242,11 @@ void Widget::searchMore() { }).send(); return _searchRequest; }); +#endif } } +#if 0 // mtp void Widget::searchReceived( SearchRequestType type, const MTPmessages_Messages &result, @@ -2277,6 +2443,143 @@ void Widget::peopleFailed(const MTP::Error &error, mtpRequestId requestId) { _peerSearchFull = true; } } +#endif + +void Widget::searchReceived( + SearchRequestType type, + const Tdb::TLfoundMessages &result) { + const auto &data = result.data(); + auto slice = SearchSlice{ + .fullCount = data.vtotal_count().v, + }; + for (const auto &message : data.vmessages().v) { + slice.messages.push_back(session().data().processMessage( + message, + NewMessageType::Existing)); + } + _nextSearchOffset = data.vnext_offset().v; + if (_nextSearchOffset.isEmpty()) { + _searchFull = true; + } + searchReceived(type, std::move(slice), 0); +} + +void Widget::searchReceived( + SearchRequestType type, + const Tdb::TLfoundChatMessages &result) { + const auto &data = result.data(); + auto slice = SearchSlice{ + .fullCount = data.vtotal_count().v, + }; + for (const auto &message : data.vmessages().v) { + slice.messages.push_back(session().data().processMessage( + message, + NewMessageType::Existing)); + } + const auto migrated = (type == SearchRequestType::MigratedFromStart) + || (type == SearchRequestType::MigratedFromOffset); + auto &nextId = migrated + ? _nextSearchMigratedMessageId + : _nextSearchMessageId; + nextId = data.vnext_from_message_id().v; + if (!nextId) { + (migrated ? _searchFullMigrated : _searchFull) = true; + } + searchReceived(type, std::move(slice), 0); +} + +void Widget::searchReceived( + SearchRequestType type, + SearchSlice result, + int) { + auto slice = &result; + const auto state = _inner->state(); + if (state == WidgetState::Filtered) { + if (type == SearchRequestType::FromStart + || type == SearchRequestType::PeerFromStart) { + auto &entry = _searchCache[_searchQuery]; + entry = std::move(result); + slice = &entry; + } + } + + if (slice->messages.empty()) { + if (type == SearchRequestType::MigratedFromStart + || type == SearchRequestType::MigratedFromOffset) { + _searchFullMigrated = true; + } else { + _searchFull = true; + } + } + const auto inject = (type == SearchRequestType::FromStart + || type == SearchRequestType::PeerFromStart) + ? *_singleMessageSearch.lookup(_searchQuery) + : nullptr; + _inner->searchReceived(slice->messages, inject, type, slice->fullCount); + listScrollUpdated(); + update(); +} + +void Widget::peerSearchReceived( + const QString &query, + const TLchats &result) { + auto &owner = session().data(); + auto parsed = std::vector>(); + for (const auto &id : result.data().vchat_ids().v) { + const auto peerId = peerFromTdbChat(id); + if (const auto peer = owner.peerLoaded(peerId)) { + parsed.push_back(peer); + } + } + const auto state = _inner->state(); + if (state == WidgetState::Filtered) { + auto &entry = _peerSearchCache[query]; + entry = std::move(parsed); + if (_peerSearchQuery == query) { + peerSearchReceived(entry, 0); + } + } else if (_peerSearchQuery == query) { + peerSearchReceived(parsed, 0); + } +} + +void Widget::peerSearchReceived( + const std::vector> &result, + int) { + _inner->peerSearchReceived(_peerSearchQuery, result); + listScrollUpdated(); + update(); +} + +void Widget::cloudChatsReceived( + const QString &query, + const TLchats &result) { + auto &owner = session().data(); + auto parsed = std::vector>(); + for (const auto &id : result.data().vchat_ids().v) { + const auto peerId = peerFromTdbChat(id); + if (const auto peer = owner.peerLoaded(peerId)) { + parsed.push_back(owner.history(peer)); + } + } + const auto state = _inner->state(); + if (state == WidgetState::Filtered) { + auto &entry = _cloudChatsCache[query]; + entry = std::move(parsed); + if (_cloudChatsQuery == query) { + cloudChatsReceived(entry); + } + } else if (_cloudChatsQuery == query) { + cloudChatsReceived(parsed); + } +} + +void Widget::cloudChatsReceived( + const std::vector> &result) { + _inner->cloudChatsReceived(_cloudChatsQuery, result); + listScrollUpdated(); + update(); +} void Widget::dragEnterEvent(QDragEnterEvent *e) { using namespace Storage; @@ -2390,6 +2693,12 @@ void Widget::applyFilterUpdate(bool force) { _api.request(requestId).cancel(); } _peerSearchQuery = QString(); + + _cloudChatsCache.clear(); + for (const auto &[query, requestId] : base::take(_cloudChatsQueries)) { + _api.request(requestId).cancel(); + } + _cloudChatsQuery = QString(); } if (_chooseFromUser->toggled() || _searchFromAuthor) { @@ -2609,9 +2918,11 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) { void Widget::clearSearchCache() { _searchCache.clear(); _singleMessageSearch.clear(); +#if 0 // mtp for (const auto &[requestId, query] : base::take(_searchQueries)) { session().api().request(requestId).cancel(); } +#endif _searchQuery = QString(); _searchQueryFrom = nullptr; _topicSearchQuery = QString(); @@ -3017,9 +3328,12 @@ void Widget::scrollToEntry(const RowDescriptor &entry) { } void Widget::cancelSearchRequest() { + _api.request(base::take(_searchRequest)).cancel(); +#if 0 // mtp session().api().request(base::take(_searchRequest)).cancel(); session().data().histories().cancelRequest( base::take(_searchInHistoryRequest)); +#endif } PeerData *Widget::searchInPeer() const { @@ -3075,8 +3389,13 @@ bool Widget::cancelSearch() { setFocus(); clearingInChat = true; } + _nextSearchMessageId = _nextSearchMigratedMessageId = 0; + _nextSearchOffset = QString(); +#if 0 // mtp _lastSearchPeer = nullptr; _lastSearchId = _lastSearchMigratedId = 0; + _lastSearchDate = 0; +#endif _inner->clearFilter(); clearSearchField(); applyFilterUpdate(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 711509625daa5..03808e0af8bc9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -15,6 +15,14 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" #include "api/api_single_message_search.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLchats; +class TLfoundMessages; +class TLfoundChatMessages; +} // namespace Tdb + namespace MTP { class Error; } // namespace MTP @@ -153,6 +161,8 @@ class Widget final : public Window::AbstractSectionWidget { void needSearchMessages(); void slideFinished(); + +#if 0 // mtp void searchReceived( SearchRequestType type, const MTPmessages_Messages &result, @@ -160,6 +170,32 @@ class Widget final : public Window::AbstractSectionWidget { void peerSearchReceived( const MTPcontacts_Found &result, mtpRequestId requestId); +#endif + struct SearchSlice { + std::vector> messages; + int fullCount = 0; + }; + void searchReceived( + SearchRequestType type, + const Tdb::TLfoundMessages &result); + void searchReceived( + SearchRequestType type, + const Tdb::TLfoundChatMessages &result); + void searchReceived( + SearchRequestType type, + SearchSlice result, + int); + void peerSearchReceived( + const QString &query, + const Tdb::TLchats &result); + void peerSearchReceived( + const std::vector> &result, + int); + void cloudChatsReceived( + const QString &query, + const Tdb::TLchats &result); + void cloudChatsReceived(const std::vector> &result); + void escape(); void submit(); void cancelSearchRequest(); @@ -220,11 +256,13 @@ class Widget final : public Window::AbstractSectionWidget { void refreshLoadMoreButton(bool mayBlock, bool isBlocked); void loadMoreBlockedByDate(); +#if 0 // mtp void searchFailed( SearchRequestType type, const MTP::Error &error, mtpRequestId requestId); void peopleFailed(const MTP::Error &error, mtpRequestId requestId); +#endif void scrollToDefault(bool verytop = false); void scrollToDefaultChecked(bool verytop = false); @@ -234,7 +272,10 @@ class Widget final : public Window::AbstractSectionWidget { void updateScrollUpPosition(); void updateLockUnlockPosition(); +#if 0 // mtp MTP::Sender _api; +#endif + Tdb::Sender _api; bool _dragInScroll = false; bool _dragForward = false; @@ -313,22 +354,42 @@ class Widget final : public Window::AbstractSectionWidget { QString _searchQuery; PeerData *_searchQueryFrom = nullptr; +#if 0 // mtp int32 _searchNextRate = 0; +#endif bool _searchFull = false; bool _searchFullMigrated = false; int _searchInHistoryRequest = 0; // Not real mtpRequestId. mtpRequestId _searchRequest = 0; + MsgId _nextSearchMessageId = 0; + MsgId _nextSearchMigratedMessageId = 0; + QString _nextSearchOffset; +#if 0 // mtp PeerData *_lastSearchPeer = nullptr; MsgId _lastSearchId = 0; MsgId _lastSearchMigratedId = 0; + TimeId _lastSearchDate = 0; base::flat_map _searchCache; +#endif + base::flat_map _searchCache; Api::SingleMessageSearch _singleMessageSearch; +#if 0 // mtp base::flat_map _searchQueries; base::flat_map _peerSearchCache; +#endif + base::flat_map< + QString, + std::vector>> _peerSearchCache; base::flat_map _peerSearchQueries; + base::flat_map< + QString, + std::vector>> _cloudChatsCache; + base::flat_map _cloudChatsQueries; + QString _cloudChatsQuery; + QPixmap _widthAnimationCache; int _topDelta = 0; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 5299b648c9693..7351d3302d380 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -96,6 +96,8 @@ void PaintRowDate( return QLocale().toString(lastTime.time(), QLocale::ShortFormat); } else if (qAbs(lastDate.daysTo(nowDate)) < 7) { return langDayOfWeek(lastDate); + } else if (qAbs(lastDate.daysTo(nowDate)) < 150) { + return langDayOfMonth(lastDate); } else { return QLocale().toString(lastDate, QLocale::ShortFormat); } @@ -545,6 +547,9 @@ void PaintRow( color, context.now)) { // Empty history + if (const auto secretChat = thread->peer()->asSecretChat()) { + paintItemCallback(nameleft, namewidth); + } } } else if (!item->isEmpty()) { if (thread && !promoted) { @@ -656,6 +661,8 @@ void PaintRow( } p.setPen(context.active ? st::dialogsNameFgActive + : from->isSecretChat() + ? st::boxTextFgGood : context.selected ? st::dialogsNameFgOver : st::dialogsNameFg); @@ -715,6 +722,11 @@ const style::icon *ChatTypeIcon( st::dialogsForumIcon, context.active, context.selected); + } else if (peer->isSecretChat()) { + return &ThreeStateIcon( + st::dialogsSecretChatIcon, + context.active, + context.selected); } else { return &ThreeStateIcon( st::dialogsChatIcon, @@ -815,6 +827,10 @@ void RowPainter::Paint( const auto forum = context.st->topicsHeight ? row->history()->peer->forum() : nullptr; + if (!item) { + Assert(row->history()->peer->isSecretChat()); + view->prepare(row->history()->peer->asSecretChat()); + } else if (!view->prepared(item, forum)) { view->prepare( item, diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index 1202d5aaf49ff..b6b0f04732197 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -24,6 +24,9 @@ For license and copyright information please follow this link: #include "lang/lang_text_entity.h" #include "styles/style_dialogs.h" +#include "data/data_secret_chat.h" +#include "data/data_user.h" + namespace { constexpr auto kEmojiLoopCount = 2; @@ -151,6 +154,7 @@ void MessageView::prepare( Data::Forum *forum, Fn customEmojiRepaint, ToPreviewOptions options) { + _secretStateUsed = false; if (!forum) { _topics = nullptr; } else if (!_topics || _topics->forum() != forum) { @@ -222,6 +226,37 @@ void MessageView::prepare( } } +void MessageView::prepare(not_null secretChat) { + const auto state = secretChat->state(); + if (_secretStateUsed && !_textCache.isEmpty() && state == _secretState) { + return; + } + _secretStateUsed = true; + _secretState = state; + const auto text = (state == SecretChatState::Pending) + ? (u"Waiting for "_q + + secretChat->user()->shortName() + + u" to come online..."_q) + : (state == SecretChatState::Closed) + ? u"Secret chat is closed."_q + : secretChat->out() + ? (secretChat->user()->shortName() + u" joined your secret chat."_q) + : u"You joined the secret chat"_q; + const auto context = Core::MarkedTextContext{ + .session = &secretChat->session(), + }; + _textCache.setMarkedText( + st::dialogsTextStyle, + DialogsPreviewText(Ui::Text::Colorized(text)), + DialogTextOptions(), + context); + _textCachedFor = nullptr; + _imagesCache = {}; + _spoiler = nullptr; + _topics = nullptr; + _loadingContext = nullptr; +} + bool MessageView::isInTopicJump(int x, int y) const { return _topics && _topics->isInTopicJumpArea(x, y); } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h index 58c1966a8c3b2..49d58f857bcf5 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h @@ -9,6 +9,8 @@ For license and copyright information please follow this link: #include +enum class SecretChatState : uchar; + class Image; class HistoryItem; enum class ImageRoundRadius; @@ -62,6 +64,7 @@ class MessageView final { Data::Forum *forum, Fn customEmojiRepaint, ToPreviewOptions options); + void prepare(not_null secretChat); void paint( Painter &p, @@ -96,6 +99,10 @@ class MessageView final { mutable const style::DialogsMiniIcon *_leftIcon = nullptr; mutable bool _hasPlainLinkAtBegin = false; + SecretChatState _secretState = {}; + int _secretNameVersion = 0; + bool _secretStateUsed = false; + }; [[nodiscard]] HistoryView::ItemPreview PreviewWithSender( diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index bbfcd37aadd46..cfc370bb25e6e 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -10,6 +10,7 @@ For license and copyright information please follow this link: #include "export/export_settings.h" #include "export/output/export_output_file.h" #include "base/base_file_utilities.h" +#include "tdb/tdb_format_phone.h" // Tdb::FormatPhone #include "ui/text/format_values.h" #include "core/mime_type.h" #include "core/utils.h" @@ -2031,7 +2032,7 @@ bool SkipMessageByDate(const Message &message, const Settings &settings) { Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) { return phoneNumber.isEmpty() ? Utf8String() - : Ui::FormatPhone(QString::fromUtf8(phoneNumber)).toUtf8(); + : Tdb::FormatPhone(QString::fromUtf8(phoneNumber)).toUtf8(); } Utf8String FormatDateTime( diff --git a/Telegram/SourceFiles/export/export_manager.cpp b/Telegram/SourceFiles/export/export_manager.cpp index 7277adef3372f..dfc2563965772 100644 --- a/Telegram/SourceFiles/export/export_manager.cpp +++ b/Telegram/SourceFiles/export/export_manager.cpp @@ -22,7 +22,9 @@ Manager::Manager() = default; Manager::~Manager() = default; void Manager::start(not_null peer) { +#if 0 // tdlib todo export start(&peer->session(), peer->input); +#endif } void Manager::start( diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 89a2e6b496ef2..6225d581d617b 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -60,12 +60,16 @@ For license and copyright information please follow this link: #include "styles/style_chat.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_tl_scheme.h" + #include #include namespace AdminLog { namespace { +using namespace Tdb; + // If we require to support more admins we'll have to rewrite this anyway. constexpr auto kMaxChannelAdmins = 200; constexpr auto kScrollDateHideTimeout = 1000; @@ -241,7 +245,10 @@ InnerWidget::InnerWidget( , _controller(controller) , _channel(channel) , _history(channel->owner().history(channel)) +#if 0 // goodToRemove , _api(&_channel->session().mtp()) +#endif +, _api(&_channel->session().sender()) , _pathGradient( HistoryView::MakePathShiftGradient( controller->chatStyle(), @@ -451,6 +458,7 @@ void InnerWidget::applySearch(const QString &query) { void InnerWidget::requestAdmins() { const auto offset = 0; +#if 0 // goodToRemove const auto participantsHash = uint64(0); _api.request(MTPchannels_GetParticipants( _channel->inputChannel, @@ -460,6 +468,14 @@ void InnerWidget::requestAdmins() { MTP_long(participantsHash) )).done([=](const MTPchannels_ChannelParticipants &result) { result.match([&](const MTPDchannels_channelParticipants &data) { +#endif + _api.request(TLgetSupergroupMembers( + tl_int53(peerToChannel(_channel->id).bare), + tl_supergroupMembersFilterAdministrators(), + tl_int32(offset), + tl_int32(kMaxChannelAdmins) + )).done([=](const TLDchatMembers &data) { + { const auto &[availableCount, list] = Api::ChatParticipants::Parse( _channel, data); @@ -480,10 +496,13 @@ void InnerWidget::requestAdmins() { } } } + } +#if 0 // goodToRemove }, [&](const MTPDchannels_channelParticipantsNotModified &) { LOG(("API Error: c" "hannels.channelParticipantsNotModified received!")); }); +#endif if (_admins.empty()) { _admins.push_back(session().user()); } @@ -716,6 +735,52 @@ void InnerWidget::preloadMore(Direction direction) { return; } + const auto maxId = (direction == Direction::Up) ? _minId : 0; + // const auto minId = (direction == Direction::Up) ? 0 : _maxId; + const auto perPage = _items.empty() ? kEventsFirstPage : kEventsPerPage; + + auto filter = tl_chatEventLogFilters( + tl_bool(_filter.flags & FilterValue::Flag::Edit), + tl_bool(_filter.flags & FilterValue::Flag::Delete), + tl_bool(_filter.flags & FilterValue::Flag::Pinned), + tl_bool(_filter.flags & FilterValue::Flag::Join), + tl_bool(_filter.flags & FilterValue::Flag::Leave), + tl_bool(_filter.flags & FilterValue::Flag::Invites), + tl_bool(_filter.flags & FilterValue::Flag::Promote), + tl_bool(_filter.flags & FilterValue::Flag::Demote), + tl_bool(_filter.flags & FilterValue::Flag::Info), + tl_bool(_filter.flags & FilterValue::Flag::Settings), + tl_bool(_filter.flags & FilterValue::Flag::Invite), + tl_bool(_filter.flags & FilterValue::Flag::GroupCall), + tl_bool(_filter.flags & FilterValue::Flag::Topics)); + + auto admins = _filter.allUsers + ? QVector() + : ranges::views::all( + _filter.admins + ) | ranges::views::transform([](not_null user) { + return tl_int53(peerToUser(user->id).bare); + }) | ranges::to>(); + + requestId = _api.request(TLgetChatEventLog( + peerToTdbChat(_channel->id), + tl_string(_searchQuery), + tl_int64(maxId), + tl_int32(perPage), + std::move(filter), + tl_vector(std::move(admins)) + )).done([=, &requestId, &loadedFlag](const TLDchatEvents &data) { + requestId = 0; + + if (!loadedFlag) { + addEvents(direction, data.vevents().v); + } + }).fail([this, &requestId, &loadedFlag](const Error &error) { + requestId = 0; + loadedFlag = true; + update(); + }).send(); +#if 0 // goodToRemove auto flags = MTPchannels_GetAdminLog::Flags(0); const auto filter = [&] { using Flag = MTPDchannelAdminLogEventsFilter::Flag; @@ -782,9 +847,15 @@ void InnerWidget::preloadMore(Direction direction) { loadedFlag = true; update(); }).send(); +#endif } +#if 0 // goodToRemove void InnerWidget::addEvents(Direction direction, const QVector &events) { +#endif +void InnerWidget::addEvents( + Direction direction, + const QVector &events) { if (_filterChanged) { clearAfterFilterChange(); } @@ -813,7 +884,7 @@ void InnerWidget::addEvents(Direction direction, const QVectorid), + peerToSender(user->id) + )).done([=](const TLDchatMember &data) { + const auto type = data.vstatus().type(); + if (type == id_chatMemberStatusBanned) { + editRestrictions( + false, + ChatRestrictionsInfo(data.vstatus())); + } else { + const auto hasAdminRights = + (type == id_chatMemberStatusAdministrator) + || (type == id_chatMemberStatusCreator); + editRestrictions(hasAdminRights, ChatRestrictionsInfo()); + } + }).fail([=] { + editRestrictions(false, ChatRestrictionsInfo()); + }).send(); +#if 0 // goodToRemove _api.request(MTPchannels_GetParticipant( _channel->inputChannel, user->input @@ -1462,6 +1552,7 @@ void InnerWidget::suggestRestrictParticipant( }).fail([=] { editRestrictions(false, ChatRestrictionsInfo()); }).send(); +#endif } }, &st::menuIconPermissions); } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index da7653b1be3c0..9f425983c2d57 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -14,9 +14,13 @@ For license and copyright information please follow this link: #include "ui/rp_widget.h" #include "ui/effects/animations.h" #include "ui/widgets/tooltip.h" -#include "mtproto/sender.h" +#include "tdb/tdb_sender.h" #include "base/timer.h" +namespace Tdb { +class TLchatEvent; +} // namespace Tdb + struct ChatRestrictionsInfo; namespace Main { @@ -220,7 +224,12 @@ class InnerWidget final void paintEmpty(Painter &p, not_null st); void clearAfterFilterChange(); void clearAndRequestLog(); +#if 0 // goodToRemove void addEvents(Direction direction, const QVector &events); +#endif + void addEvents( + Direction direction, + const QVector &events); Element *viewForItem(const HistoryItem *item); void toggleScrollDateShown(); @@ -257,7 +266,10 @@ class InnerWidget final const not_null _controller; const not_null _channel; const not_null _history; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Tdb::Sender _api; const std::unique_ptr _pathGradient; std::shared_ptr _theme; diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index df8819d10e646..b3a937f28b95f 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -34,6 +34,8 @@ For license and copyright information please follow this link: #include "window/notifications_manager.h" #include "window/window_session_controller.h" +#include "tdb/tdb_tl_scheme.h" + namespace AdminLog { namespace { @@ -60,6 +62,7 @@ TextWithEntities PrepareText( return result; } +#if 0 // goodToRemove [[nodiscard]] TimeId ExtractSentDate(const MTPMessage &message) { return message.match([](const MTPDmessageEmpty &) { return 0; @@ -174,6 +177,126 @@ TextWithEntities ExtractEditedText( Api::EntitiesFromMTP(session, data.ventities().value_or_empty()) }; } +#endif + +Tdb::TLmessage PrepareLogMessage( + const Tdb::TLmessage &message, + TimeId newDate) { + return Tdb::tl_message( + message.data().vid(), + message.data().vsender_id(), + message.data().vchat_id(), + message.data().vsending_state() + ? std::make_optional(*message.data().vsending_state()) + : std::nullopt, + message.data().vscheduling_state() + ? std::make_optional(*message.data().vscheduling_state()) + : std::nullopt, + Tdb::tl_bool(false), // is_outgoing + message.data().vis_pinned(), + Tdb::tl_bool(false), // can_be_edited + Tdb::tl_bool(false), // can_be_forwarded + Tdb::tl_bool(false), // can_be_replied_in_another_chat + Tdb::tl_bool(false), // can_be_saved + Tdb::tl_bool(false), // can_be_deleted_only_for_self + Tdb::tl_bool(false), // can_be_deleted_for_all_users + Tdb::tl_bool(false), // can_get_added_reactions + Tdb::tl_bool(false), // can_get_statistics + Tdb::tl_bool(false), // can_get_message_thread + Tdb::tl_bool(false), // can_get_viewers + message.data().vcan_get_media_timestamp_links(), + Tdb::tl_bool(false), // can_report_reactions + message.data().vhas_timestamped_media(), + Tdb::tl_bool(false), // is_channel_post + Tdb::tl_bool(false), // is_topic_message + Tdb::tl_bool(false), // contains_unread_mention + Tdb::tl_int32(newDate), + Tdb::tl_int32(0), // edit_date + message.data().vforward_info() + ? std::make_optional(*message.data().vforward_info()) + : std::nullopt, + std::nullopt, // import_info + std::nullopt, // interaction_info + Tdb::tl_vector(), + std::nullopt, // reply_to + Tdb::tl_int53(0), // message_thread_id + std::nullopt, // self_destruct_type + Tdb::tl_double(0), // self_destruct_in + Tdb::tl_double(0), // auto_delete_in + message.data().vvia_bot_user_id(), + message.data().vauthor_signature(), + Tdb::tl_int64(0), // media_album_id + Tdb::tl_string(), // restriction_reason + message.data().vcontent(), + message.data().vreply_markup() + ? std::make_optional(*message.data().vreply_markup()) + : std::nullopt); +} + +bool MediaCanHaveCaption(const Tdb::TLmessage &message) { + using namespace Tdb; + return message.data().vcontent().match([](const auto &data) { + using T = decltype(data); + return TLDmessagePhoto::Is() + || TLDmessageDocument::Is() + || TLDmessageAudio::Is() + || TLDmessageAnimation::Is() + || TLDmessageExpiredPhoto::Is() + || TLDmessageVideo::Is() + || TLDmessageExpiredVideo::Is() + || TLDmessageVoiceNote::Is(); + }); +} + +FileId MediaId(const Tdb::TLmessage &message) { + if (!MediaCanHaveCaption(message)) { + return FileId(); + } + + using namespace Tdb; + const auto &content = message.data().vcontent(); + return content.match([&](const TLDmessageAnimation &data) { + return data.vanimation().data().vanimation().data().vid().v; + }, [&](const TLDmessageAudio &data) { + return data.vaudio().data().vaudio().data().vid().v; + }, [&](const TLDmessageDocument &data) { + return data.vdocument().data().vdocument().data().vid().v; + }, [&](const TLDmessagePhoto &data) { + const auto &sizes = data.vphoto().data().vsizes().v; + return sizes.empty() + ? FileId() + : sizes.front().data().vphoto().data().vid().v; + }, [&](const TLDmessageSticker &data) { + return data.vsticker().data().vsticker().data().vid().v; + }, [&](const TLDmessageVideo &data) { + return data.vvideo().data().vvideo().data().vid().v; + }, [&](const TLDmessageVideoNote &data) { + return data.vvideo_note().data().vvideo().data().vid().v; + }, [&](const TLDmessageVoiceNote &data) { + return data.vvoice_note().data().vvoice().data().vid().v; + }, [](const auto &data) { + return FileId(); + }); +} + +[[nodiscard]] MsgId ExtractRealMsgId(const Tdb::TLmessage &message) { + return message.data().vid().v; +} + +[[nodiscard]] TimeId ExtractSentDate(const Tdb::TLmessage &message) { + return message.data().vdate().v; +} + +TextWithEntities ExtractEditedText( + not_null session, + const Tdb::TLmessage &message) { + return message.data().vcontent().match([&]( + const Tdb::TLDmessageText &data) { + return Api::FormattedTextFromTdb(data.vtext()); + }, [](const auto &) { + return TextWithEntities(); + }); +} const auto CollectChanges = []( auto &phraseMap, @@ -331,6 +454,7 @@ QString PublicJoinLink() { return u"(public_join_link)"_q; } +#if 0 // goodToRemove QString ExtractInviteLink(const MTPExportedChatInvite &data) { return data.match([&](const MTPDchatInviteExported &data) { return qs(data.vlink()); @@ -349,12 +473,22 @@ QString ExtractInviteLinkLabel(const MTPExportedChatInvite &data) { QString InternalInviteLinkUrl(const MTPExportedChatInvite &data) { const auto base64 = ExtractInviteLink(data).toUtf8().toBase64(); +#endif +QString InternalInviteLinkUrl(const Tdb::TLDchatInviteLink &data) { + const auto base64 = data.vinvite_link().v.toUtf8().toBase64(); return "internal:show_invite_link/?link=" + QString::fromLatin1(base64); } +#if 0 // goodToRemove QString GenerateInviteLinkText(const MTPExportedChatInvite &data) { const auto label = ExtractInviteLinkLabel(data); +#endif +QString GenerateInviteLinkText(const Tdb::TLDchatInviteLink &data) { + const auto label = data.vname().v; +#if 0 // goodToRemove return label.isEmpty() ? ExtractInviteLink(data).replace( +#endif + return label.isEmpty() ? QString(data.vinvite_link().v).replace( u"https://"_q, QString() ).replace( @@ -363,16 +497,24 @@ QString GenerateInviteLinkText(const MTPExportedChatInvite &data) { ) : label; } +#if 0 // goodToRemove TextWithEntities GenerateInviteLinkLink(const MTPExportedChatInvite &data) { +#endif +TextWithEntities GenerateInviteLinkLink(const Tdb::TLDchatInviteLink &data) { const auto text = GenerateInviteLinkText(data); return text.endsWith(Ui::kQEllipsis) ? TextWithEntities{ .text = text } : Ui::Text::Link(text, InternalInviteLinkUrl(data)); } +#if 0 // goodToRemove TextWithEntities GenerateInviteLinkChangeText( const MTPExportedChatInvite &newLink, const MTPExportedChatInvite &prevLink) { +#endif +TextWithEntities GenerateInviteLinkChangeText( + const Tdb::TLDchatInviteLink &newLink, + const Tdb::TLDchatInviteLink &prevLink) { auto link = TextWithEntities{ GenerateInviteLinkText(newLink) }; if (!link.text.endsWith(Ui::kQEllipsis)) { link.entities.push_back({ @@ -388,6 +530,7 @@ TextWithEntities GenerateInviteLinkChangeText( Ui::Text::WithEntities); result.text.append('\n'); +#if 0 // goodToRemove const auto label = [](const MTPExportedChatInvite &link) { return link.match([](const MTPDchatInviteExported &data) { return qs(data.vtitle().value_or_empty()); @@ -416,6 +559,7 @@ TextWithEntities GenerateInviteLinkChangeText( return true; }); }; +#endif const auto wrapDate = [](TimeId date) { return date ? langDateTime(base::unixtime::parse(date)) @@ -426,6 +570,7 @@ TextWithEntities GenerateInviteLinkChangeText( ? QString::number(count) : tr::lng_group_invite_usage_any(tr::now); }; +#if 0 // goodToRemove const auto wasLabel = label(prevLink); const auto nowLabel = label(newLink); const auto wasExpireDate = expireDate(prevLink); @@ -434,6 +579,15 @@ TextWithEntities GenerateInviteLinkChangeText( const auto nowUsageLimit = usageLimit(newLink); const auto wasRequestApproval = requestApproval(prevLink); const auto nowRequestApproval = requestApproval(newLink); +#endif + const auto wasLabel = prevLink.vname().v; + const auto nowLabel = newLink.vname().v; + const auto wasExpireDate = prevLink.vexpiration_date().v; + const auto nowExpireDate = newLink.vexpiration_date().v; + const auto wasUsageLimit = prevLink.vmember_limit().v; + const auto nowUsageLimit = newLink.vmember_limit().v; + const auto wasRequestApproval = prevLink.vcreates_join_request().v; + const auto nowRequestApproval = newLink.vcreates_join_request().v; if (wasLabel != nowLabel) { result.text.append('\n').append( tr::lng_admin_log_invite_link_label( @@ -611,14 +765,23 @@ auto GenerateParticipantChangeText( TextWithEntities GenerateParticipantChangeText( not_null channel, + PeerId peerId, + const Tdb::TLchatMemberStatus &status, + std::optionaloldStatus = std::nullopt) { +#if 0 // goodToRemove const MTPChannelParticipant &participant, std::optionaloldParticipant = std::nullopt) { +#endif + const auto tlSender = peerToSender(peerId); + using namespace Tdb; return GenerateParticipantChangeText( channel, - Api::ChatParticipant(participant, channel), - oldParticipant + Api::ChatParticipant( + tl_chatMember(tlSender, tl_int53(0), tl_int32(0), status), + channel), + oldStatus ? std::make_optional(Api::ChatParticipant( - *oldParticipant, + tl_chatMember(tlSender, tl_int53(0), tl_int32(0), *oldStatus), channel)) : std::nullopt); } @@ -639,6 +802,7 @@ TextWithEntities GenerateDefaultBannedRightsChangeText( return result; } +#if 0 // mtp [[nodiscard]] bool IsTopicClosed(const MTPForumTopic &topic) { return topic.match([](const MTPDforumTopic &data) { return data.is_closed(); @@ -671,6 +835,21 @@ TextWithEntities GenerateDefaultBannedRightsChangeText( return TextWithEntities{ u"Deleted"_q }; }); } +#endif + +[[nodiscard]] TextWithEntities GenerateTopicLink( + not_null channel, + const Tdb::TLforumTopicInfo &topic) { + const auto &data = topic.data(); + return Ui::Text::Link( + Data::ForumTopicIconWithTitle( + data.vmessage_thread_id().v, + data.vicon().data().vcustom_emoji_id().v, + data.vname().v), + u"internal:url:https://t.me/c/%1/%2"_q.arg( + peerToChannel(channel->id).bare).arg( + data.vmessage_thread_id().v)); +} } // namespace @@ -714,10 +893,15 @@ void OwnedItem::clearView() { void GenerateItems( not_null delegate, not_null history, +#if 0 // goodToRemove const MTPDchannelAdminLogEvent &event, +#endif + const Tdb::TLDchatEvent &event, Fn callback) { Expects(history->peer->isChannel()); + using namespace Tdb; +#if 0 // goodToRemove using LogTitle = MTPDchannelAdminLogEventActionChangeTitle; using LogAbout = MTPDchannelAdminLogEventActionChangeAbout; using LogUsername = MTPDchannelAdminLogEventActionChangeUsername; @@ -769,10 +953,58 @@ void GenerateItems( using LogToggleAntiSpam = MTPDchannelAdminLogEventActionToggleAntiSpam; using LogChangeColor = MTPDchannelAdminLogEventActionChangeColor; using LogChangeBackgroundEmoji = MTPDchannelAdminLogEventActionChangeBackgroundEmoji; +#endif + using LogTitle = TLDchatEventTitleChanged; + using LogAbout = TLDchatEventDescriptionChanged; + using LogUsername = TLDchatEventUsernameChanged; + using LogPhoto = TLDchatEventPhotoChanged; + using LogInvites = TLDchatEventInvitesToggled; + using LogSign = TLDchatEventSignMessagesToggled; + using LogPin = TLDchatEventMessagePinned; + using LogUnpin = TLDchatEventMessageUnpinned; + using LogEdit = TLDchatEventMessageEdited; + using LogDelete = TLDchatEventMessageDeleted; + using LogJoin = TLDchatEventMemberJoined; + using LogLeave = TLDchatEventMemberLeft; + using LogInvite = TLDchatEventMemberInvited; + using LogBan = TLDchatEventMemberRestricted; + using LogPromote = TLDchatEventMemberPromoted; + using LogSticker = TLDchatEventStickerSetChanged; + using LogPreHistory = TLDchatEventIsAllHistoryAvailableToggled; + using LogPermissions = TLDchatEventPermissionsChanged; + using LogPoll = TLDchatEventPollStopped; + using LogDiscussion = TLDchatEventLinkedChatChanged; + using LogLocation = TLDchatEventLocationChanged; + using LogSlowMode = TLDchatEventSlowModeDelayChanged; + using LogStartCall = TLDchatEventVideoChatCreated; + using LogDiscardCall = TLDchatEventVideoChatEnded; + using LogMute = TLDchatEventVideoChatParticipantIsMutedToggled; + using LogCallSetting = TLDchatEventVideoChatMuteNewParticipantsToggled; + using LogJoinByInvite = TLDchatEventMemberJoinedByInviteLink; + using LogInviteDelete = TLDchatEventInviteLinkDeleted; + using LogInviteRevoke = TLDchatEventInviteLinkRevoked; + using LogInviteEdit = TLDchatEventInviteLinkEdited; + using LogVolume = TLDchatEventVideoChatParticipantVolumeLevelChanged; + using LogTTL = TLDchatEventMessageAutoDeleteTimeChanged; + using LogJoinByRequest = TLDchatEventMemberJoinedByRequest; + using LogNoForwards = TLDchatEventHasProtectedContentToggled; + using LogChangeAvailableReactions = TLDchatEventAvailableReactionsChanged; + using LogChangeUsernames = TLDchatEventActiveUsernamesChanged; + using LogToggleForum = TLDchatEventIsForumToggled; + using LogCreateTopic = TLDchatEventForumTopicCreated; + using LogEditTopic = TLDchatEventForumTopicEdited; + using LogDeleteTopic = TLDchatEventForumTopicDeleted; + using LogPinTopic = TLDchatEventForumTopicPinned; + using LogToggleAntiSpam = TLDchatEventHasAggressiveAntiSpamEnabledToggled; + using LogToggleTopicClosed = TLDchatEventForumTopicToggleIsClosed; + using LogToggleTopicHidden = TLDchatEventForumTopicToggleIsHidden; + using LogChangeColor = TLDchatEventAccentColorChanged; + using LogChangeBackgroundEmoji = TLDchatEventBackgroundCustomEmojiChanged; const auto session = &history->session(); const auto id = event.vid().v; - const auto from = history->owner().user(event.vuser_id().v); + const auto from = history->owner().peer( + peerFromSender(event.vmember_id())); const auto channel = history->peer->asChannel(); const auto broadcast = channel->isBroadcast(); const auto &action = event.vaction(); @@ -814,7 +1046,10 @@ void GenerateItems( lt_from, fromLinkText, lt_title, + { .text = action.vnew_title().v }, +#if 0 // goodToRemove { .text = qs(action.vnew_value()) }, +#endif Ui::Text::WithEntities); addSimpleServiceMessage(std::move(text)); }; @@ -831,10 +1066,12 @@ void GenerateItems( bodyReplyTo, bodyViaBotId, date, - peerToUser(from->id), + from->id, QString(), std::move(text), +#if 0 // mtp MTP_messageMediaEmpty(), +#endif HistoryMessageMarkupData(), bodyGroupedId); }; @@ -844,8 +1081,12 @@ void GenerateItems( }; const auto createChangeAbout = [&](const LogAbout &action) { +#if 0 // goodToRemove const auto newValue = qs(action.vnew_value()); const auto oldValue = qs(action.vprev_value()); +#endif + const auto newValue = action.vold_description().v; + const auto oldValue = action.vnew_description().v; const auto text = (channel->isMegagroup() ? (newValue.isEmpty() ? tr::lng_admin_log_removed_description_group @@ -869,8 +1110,12 @@ void GenerateItems( }; const auto createChangeUsername = [&](const LogUsername &action) { +#if 0 // goodToRemove const auto newValue = qs(action.vnew_value()); const auto oldValue = qs(action.vprev_value()); +#endif + const auto newValue = action.vold_username().v; + const auto oldValue = action.vnew_username().v; const auto text = (channel->isMegagroup() ? (newValue.isEmpty() ? tr::lng_admin_log_removed_link_group @@ -899,7 +1144,11 @@ void GenerateItems( }; const auto createChangePhoto = [&](const LogPhoto &action) { +#if 0 // mtp action.vnew_photo().match([&](const MTPDphoto &data) { +#endif + if (const auto newPhoto = action.vnew_photo()) { + const auto &data = *newPhoto; const auto photo = history->owner().processPhoto(data); const auto text = (channel->isMegagroup() ? tr::lng_admin_log_changed_photo_group @@ -909,7 +1158,10 @@ void GenerateItems( fromLinkText, Ui::Text::WithEntities); addSimpleServiceMessage(text, MsgId(), photo); + } else { +#if 0 // mtp }, [&](const MTPDphotoEmpty &data) { +#endif const auto text = (channel->isMegagroup() ? tr::lng_admin_log_removed_photo_group : tr::lng_admin_log_removed_photo_channel)( @@ -918,11 +1170,17 @@ void GenerateItems( fromLinkText, Ui::Text::WithEntities); addSimpleServiceMessage(text); + } +#if 0 // mtp }); +#endif }; const auto createToggleInvites = [&](const LogInvites &action) { +#if 0 // goodToRemove const auto enabled = (action.vnew_value().type() == mtpc_boolTrue); +#endif + const auto enabled = action.vcan_invite_users().v; const auto text = (enabled ? tr::lng_admin_log_invites_enabled : tr::lng_admin_log_invites_disabled)( @@ -934,7 +1192,10 @@ void GenerateItems( }; const auto createToggleSignatures = [&](const LogSign &action) { +#if 0 // goodToRemove const auto enabled = (action.vnew_value().type() == mtpc_boolTrue); +#endif + const auto enabled = action.vsign_messages().v; const auto text = (enabled ? tr::lng_admin_log_signatures_enabled : tr::lng_admin_log_signatures_disabled)( @@ -945,6 +1206,28 @@ void GenerateItems( addSimpleServiceMessage(text); }; + const auto createUpdatePinned = [&]( + const TLmessage &message, + bool pinned) { + const auto text = (pinned + ? tr::lng_admin_log_pinned_message + : tr::lng_admin_log_unpinned_message)( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); + + const auto detachExistingItem = false; + addPart( + history->createItem( + history->nextNonHistoryEntryId(), + PrepareLogMessage(message, date), + MessageFlag::AdminLogEntry, + detachExistingItem), + TimeId(message.data().vdate().v)); + }; +#if 0 // goodToRemove const auto createUpdatePinned = [&](const LogPin &action) { action.vmessage().match([&](const MTPDmessage &data) { const auto pinned = data.is_pinned(); @@ -976,6 +1259,7 @@ void GenerateItems( addSimpleServiceMessage(text); }); }; +#endif const auto createEditMessage = [&](const LogEdit &action) { const auto realId = ExtractRealMsgId(action.vnew_message()); @@ -983,15 +1267,24 @@ void GenerateItems( const auto newValue = ExtractEditedText( session, action.vnew_message()); +#if 0 // mtp auto oldValue = ExtractEditedText( session, action.vprev_message()); +#endif + auto oldValue = ExtractEditedText( + session, + action.vold_message()); const auto canHaveCaption = MediaCanHaveCaption( action.vnew_message()); const auto changedCaption = (newValue != oldValue); +#if 0 // mtp const auto changedMedia = MediaId(action.vnew_message()) != MediaId(action.vprev_message()); +#endif + const auto changedMedia = MediaId(action.vnew_message()) + != MediaId(action.vold_message()); const auto removedCaption = !oldValue.text.isEmpty() && newValue.text.isEmpty(); const auto text = (!canHaveCaption @@ -1078,21 +1371,37 @@ void GenerateItems( const auto createParticipantInvite = [&](const LogInvite &action) { addSimpleTextMessage( + GenerateParticipantChangeText( + channel, + peerFromUser(UserId(action.vuser_id().v)), + action.vstatus())); +#if 0 // goodToRemove GenerateParticipantChangeText(channel, action.vparticipant())); +#endif }; const auto createParticipantToggleBan = [&](const LogBan &action) { addSimpleTextMessage( GenerateParticipantChangeText( channel, + peerFromSender(action.vmember_id()), + action.vnew_status(), + action.vold_status())); +#if 0 // goodToRemove action.vnew_participant(), action.vprev_participant())); +#endif }; const auto createParticipantToggleAdmin = [&](const LogPromote &action) { + if ((action.vold_status().type() == Tdb::id_chatMemberStatusCreator) + && (action.vnew_status().type() + == Tdb::id_chatMemberStatusAdministrator)) { +#if 0 // goodToRemove if ((action.vnew_participant().type() == mtpc_channelParticipantAdmin) && (action.vprev_participant().type() == mtpc_channelParticipantCreator)) { +#endif // In case of ownership transfer we show that message in // the "User > Creator" part and skip the "Creator > Admin" part. return; @@ -1100,13 +1409,24 @@ void GenerateItems( addSimpleTextMessage( GenerateParticipantChangeText( channel, + peerFromUser(UserId(action.vuser_id().v)), + action.vnew_status(), + action.vold_status())); +#if 0 // goodToRemove action.vnew_participant(), action.vprev_participant())); +#endif }; const auto createChangeStickerSet = [&](const LogSticker &action) { +#if 0 // goodToRemove const auto set = action.vnew_stickerset(); +#endif + const auto newSetId = action.vnew_sticker_set_id().v; +#if 0 // goodToRemove const auto removed = (set.type() == mtpc_inputStickerSetEmpty); +#endif + const auto removed = (newSetId == 0); if (removed) { const auto text = tr::lng_admin_log_removed_stickers_group( tr::now, @@ -1131,7 +1451,10 @@ void GenerateItems( controller->show( Box( controller->uiShow(), + StickerSetIdentifier{ .id = uint64(newSetId) }, +#if 0 // goodToRemove Data::FromInputSet(set), +#endif Data::StickersType::Stickers), Ui::LayerOption::CloseOther); } @@ -1144,13 +1467,16 @@ void GenerateItems( MessageFlag::AdminLogEntry, date, std::move(message), - peerToUser(from->id))); + from->id)); } }; const auto createTogglePreHistoryHidden = [&]( const LogPreHistory &action) { +#if 0 // goodToRemove const auto hidden = (action.vnew_value().type() == mtpc_boolTrue); +#endif + const auto hidden = !action.vis_all_history_available().v; const auto text = (hidden ? tr::lng_admin_log_history_made_hidden : tr::lng_admin_log_history_made_visible)( @@ -1166,8 +1492,18 @@ void GenerateItems( addSimpleTextMessage( GenerateDefaultBannedRightsChangeText( channel, + ChatRestrictionsInfo(Tdb::tl_chatMemberStatusRestricted( + Tdb::tl_bool(false), + Tdb::tl_int32(0), + action.vold_permissions())), + ChatRestrictionsInfo(Tdb::tl_chatMemberStatusRestricted( + Tdb::tl_bool(false), + Tdb::tl_int32(0), + action.vnew_permissions())))); +#if 0 // goodToRemove ChatRestrictionsInfo(action.vnew_banned_rights()), ChatRestrictionsInfo(action.vprev_banned_rights()))); +#endif }; const auto createStopPoll = [&](const LogPoll &action) { @@ -1192,7 +1528,10 @@ void GenerateItems( const auto createChangeLinkedChat = [&](const LogDiscussion &action) { const auto now = history->owner().channelLoaded( + peerToChannel(peerFromTdbChat(action.vnew_linked_chat_id()))); +#if 0 // goodToRemove action.vnew_value().v); +#endif if (!now) { const auto text = (broadcast ? tr::lng_admin_log_removed_linked_chat @@ -1225,12 +1564,36 @@ void GenerateItems( MessageFlag::AdminLogEntry, date, std::move(message), - peerToUser(from->id))); + from->id)); } }; const auto createChangeLocation = [&](const LogLocation &action) { - action.vnew_value().match([&](const MTPDchannelLocation &data) { + if (!action.vnew_location()) { + const auto text = tr::lng_admin_log_removed_location_chat( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); + return; + } + const auto &data = action.vnew_location()->data(); + const auto address = data.vaddress().v; + const auto link = Ui::Text::Link( + address, + LocationClickHandler::Url( + Data::LocationPoint(data.vlocation()))); + const auto text = tr::lng_admin_log_changed_location_chat( + tr::now, + lt_from, + fromLinkText, + lt_address, + link, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); +#if 0 // goodToRemove + const auto createChangeLocation = [&](const LogLocation &action) { const auto address = qs(data.vaddress()); const auto link = data.vgeo_point().match([&]( const MTPDgeoPoint &data) { @@ -1256,10 +1619,14 @@ void GenerateItems( Ui::Text::WithEntities); addSimpleServiceMessage(text); }); +#endif }; const auto createToggleSlowMode = [&](const LogSlowMode &action) { + if (const auto seconds = action.vnew_slow_mode_delay().v) { +#if 0 // goodToRemove if (const auto seconds = action.vnew_value().v) { +#endif const auto duration = (seconds >= 60) ? tr::lng_minutes(tr::now, lt_count, seconds / 60) : tr::lng_seconds(tr::now, lt_count, seconds); @@ -1303,12 +1670,14 @@ void GenerateItems( addSimpleServiceMessage(text); }; +#if 0 // goodToRemove const auto groupCallParticipantPeer = [&]( const MTPGroupCallParticipant &data) { return data.match([&](const MTPDgroupCallParticipant &data) { return history->owner().peer(peerFromMTP(data.vpeer())); }); }; +#endif const auto addServiceMessageWithLink = [&]( const TextWithEntities &text, @@ -1321,12 +1690,17 @@ void GenerateItems( MessageFlag::AdminLogEntry, date, std::move(message), - peerToUser(from->id))); + from->id)); }; const auto createParticipantMute = [&](const LogMute &data) { + const auto participantPeer = history->owner().peer( + peerFromSender(data.vparticipant_id())); +#if 0 // goodToRemove + const auto createParticipantMute = [&](const LMemberMute &data) { const auto participantPeer = groupCallParticipantPeer( data.vparticipant()); +#endif const auto participantPeerLink = participantPeer->createOpenLink(); const auto participantPeerLinkText = Ui::Text::Link( participantPeer->name(), @@ -1343,9 +1717,14 @@ void GenerateItems( addServiceMessageWithLink(text, participantPeerLink); }; + const auto createParticipantUnmute = [&](const LogMute &data) { + const auto participantPeer = history->owner().peer( + peerFromSender(data.vparticipant_id())); +#if 0 // goodToRemove const auto createParticipantUnmute = [&](const LogUnmute &data) { const auto participantPeer = groupCallParticipantPeer( data.vparticipant()); +#endif const auto participantPeerLink = participantPeer->createOpenLink(); const auto participantPeerLinkText = Ui::Text::Link( participantPeer->name(), @@ -1364,7 +1743,10 @@ void GenerateItems( const auto createToggleGroupCallSetting = [&]( const LogCallSetting &data) { +#if 0 // goodToRemove const auto text = (mtpIsTrue(data.vjoin_muted()) +#endif + const auto text = (data.vmute_new_participants().v ? (broadcast ? tr::lng_admin_log_disallowed_unmute_self_channel : tr::lng_admin_log_disallowed_unmute_self) @@ -1380,11 +1762,17 @@ void GenerateItems( const auto addInviteLinkServiceMessage = [&]( const TextWithEntities &text, + const Tdb::TLDchatInviteLink &data, +#if 0 // goodToRemove const MTPExportedChatInvite &data, +#endif ClickHandlerPtr additional = nullptr) { auto message = PreparedServiceText{ text }; message.links.push_back(fromLink); +#if 0 // goodToRemove if (!ExtractInviteLink(data).endsWith(Ui::kQEllipsis)) { +#endif + if (!data.vinvite_link().v.endsWith(Ui::kQEllipsis)) { message.links.push_back(std::make_shared( InternalInviteLinkUrl(data))); } @@ -1396,13 +1784,16 @@ void GenerateItems( MessageFlag::AdminLogEntry, date, std::move(message), - peerToUser(from->id), + from->id, nullptr)); }; const auto createParticipantJoinByInvite = [&]( const LogJoinByInvite &data) { +#if 0 // mtp const auto text = data.is_via_chatlist() +#endif + const auto text = data.vvia_chat_folder_invite_link().v ? (channel->isMegagroup() ? tr::lng_admin_log_participant_joined_by_filter_link : tr::lng_admin_log_participant_joined_by_filter_link_channel) @@ -1415,9 +1806,14 @@ void GenerateItems( lt_from, fromLinkText, lt_link, + GenerateInviteLinkLink(data.vinvite_link().data()), + Ui::Text::WithEntities), + data.vinvite_link().data()); +#if 0 // goodToRemove GenerateInviteLinkLink(data.vinvite()), Ui::Text::WithEntities), data.vinvite()); +#endif }; const auto createExportedInviteDelete = [&](const LogInviteDelete &data) { @@ -1427,9 +1823,14 @@ void GenerateItems( lt_from, fromLinkText, lt_link, + GenerateInviteLinkLink(data.vinvite_link().data()), + Ui::Text::WithEntities), + data.vinvite_link().data()); +#if 0 // goodToRemove GenerateInviteLinkLink(data.vinvite()), Ui::Text::WithEntities), data.vinvite()); +#endif }; const auto createExportedInviteRevoke = [&](const LogInviteRevoke &data) { @@ -1439,29 +1840,45 @@ void GenerateItems( lt_from, fromLinkText, lt_link, + GenerateInviteLinkLink(data.vinvite_link().data()), + Ui::Text::WithEntities), + data.vinvite_link().data()); +#if 0 // goodToRemove GenerateInviteLinkLink(data.vinvite()), Ui::Text::WithEntities), data.vinvite()); +#endif }; const auto createExportedInviteEdit = [&](const LogInviteEdit &data) { addSimpleTextMessage( GenerateInviteLinkChangeText( + data.vnew_invite_link().data(), + data.vold_invite_link().data())); +#if 0 // goodToRemove data.vnew_invite(), data.vprev_invite())); +#endif }; const auto createParticipantVolume = [&](const LogVolume &data) { + const auto participantPeer = history->owner().peer( + peerFromSender(data.vparticipant_id())); +#if 0 // goodToRemove const auto participantPeer = groupCallParticipantPeer( data.vparticipant()); +#endif const auto participantPeerLink = participantPeer->createOpenLink(); const auto participantPeerLinkText = Ui::Text::Link( participantPeer->name(), QString()); +#if 0 // goodToRemove const auto volume = data.vparticipant().match([&]( const MTPDgroupCallParticipant &data) { return data.vvolume().value_or(10000); }); +#endif + const auto volume = data.vvolume_level().v; const auto volumeText = QString::number(volume / 100) + '%'; auto text = (broadcast ? tr::lng_admin_log_participant_volume_channel @@ -1478,8 +1895,12 @@ void GenerateItems( }; const auto createChangeHistoryTTL = [&](const LogTTL &data) { +#if 0 // goodToRemove const auto was = data.vprev_value().v; const auto now = data.vnew_value().v; +#endif + const auto was = data.vold_message_auto_delete_time().v; + const auto now = data.vnew_message_auto_delete_time().v; const auto wrap = [](int duration) -> TextWithEntities { const auto text = (duration == 5) ? u"5 seconds"_q @@ -1516,8 +1937,14 @@ void GenerateItems( const auto createParticipantJoinByRequest = [&]( const LogJoinByRequest &data) { +#if 0 // goodToRemove const auto user = channel->owner().user(UserId(data.vapproved_by())); const auto linkText = GenerateInviteLinkLink(data.vinvite()); +#endif + const auto user = channel->owner().user( + UserId(data.vapprover_user_id().v)); + const auto linkText = GenerateInviteLinkLink( + data.vinvite_link()->data()); const auto text = (linkText.text == PublicJoinLink()) ? (channel->isMegagroup() ? tr::lng_admin_log_participant_approved_by_request @@ -1541,12 +1968,18 @@ void GenerateItems( Ui::Text::WithEntities); addInviteLinkServiceMessage( text, + data.vinvite_link()->data(), +#if 0 // goodToRemove data.vinvite(), +#endif user->createOpenLink()); }; const auto createToggleNoForwards = [&](const LogNoForwards &data) { +#if 0 // goodToRemove const auto disabled = (data.vnew_value().type() == mtpc_boolTrue); +#endif + const auto disabled = data.vhas_protected_content().v; const auto text = (disabled ? tr::lng_admin_log_forwards_disabled : tr::lng_admin_log_forwards_enabled)( @@ -1557,6 +1990,7 @@ void GenerateItems( addSimpleServiceMessage(text); }; +#if 0 // later const auto createSendMessage = [&](const LogSendMessage &data) { const auto realId = ExtractRealMsgId(data.vmessage()); const auto text = tr::lng_admin_log_sent_message( @@ -1576,9 +2010,11 @@ void GenerateItems( ExtractSentDate(data.vmessage()), realId); }; +#endif const auto createChangeAvailableReactions = [&]( const LogChangeAvailableReactions &data) { +#if 0 // mtp const auto text = data.vnew_value().match([&]( const MTPDchatReactionsNone&) { return tr::lng_admin_log_reactions_disabled( @@ -1613,12 +2049,55 @@ void GenerateItems( fromLinkText, Ui::Text::WithEntities); }); +#endif + const auto text = data.vnew_available_reactions().match([&]( + const TLDchatAvailableReactionsSome &data) { + if (data.vreactions().v.isEmpty()) { + return tr::lng_admin_log_reactions_disabled( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + } + using namespace Window::Notifications; + auto list = TextWithEntities(); + for (const auto &one : data.vreactions().v) { + if (!list.empty()) { + list.append(", "); + } + list.append(Manager::ComposeReactionEmoji( + session, + Data::ReactionFromTL(one))); + } + return tr::lng_admin_log_reactions_updated( + tr::now, + lt_from, + fromLinkText, + lt_emoji, + list, + Ui::Text::WithEntities); + }, [&](const TLDchatAvailableReactionsAll &data) { + return (!history->peer->isBroadcast() + ? tr::lng_admin_log_reactions_allowed_all + : tr::lng_admin_log_reactions_allowed_official)( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + }); addSimpleServiceMessage(text); }; const auto createChangeUsernames = [&](const LogChangeUsernames &data) { +#if 0 // mtp const auto newValue = data.vnew_value().v; const auto oldValue = data.vprev_value().v; +#endif + const auto newValue = data.vnew_usernames().v; + const auto oldValue = data.vold_usernames().v; + const auto qs = [](const Tdb::TLstring &value) { + return value.v; + }; const auto list = [&](const auto &tlList) { auto result = TextWithEntities(); @@ -1633,6 +2112,11 @@ void GenerateItems( if (newValue.size() == oldValue.size()) { if (newValue.size() == 1) { + createChangeUsername(tl_chatEventUsernameChanged( + oldValue.front(), + newValue.front() + ).c_chatEventUsernameChanged()); +#if 0 // mtp const auto tl = MTP_channelAdminLogEventActionChangeUsername( newValue.front(), oldValue.front()); @@ -1640,6 +2124,7 @@ void GenerateItems( createChangeUsername(data); }, [](const auto &) { }); +#endif return; } else { const auto wasReordered = [&] { @@ -1705,7 +2190,10 @@ void GenerateItems( }; const auto createToggleForum = [&](const LogToggleForum &data) { +#if 0 // mtp const auto enabled = (data.vnew_value().type() == mtpc_boolTrue); +#endif + const auto enabled = data.vis_forum().v; const auto text = (enabled ? tr::lng_admin_log_topics_enabled : tr::lng_admin_log_topics_disabled)( @@ -1717,7 +2205,10 @@ void GenerateItems( }; const auto createCreateTopic = [&](const LogCreateTopic &data) { +#if 0 // mtp auto topicLink = GenerateTopicLink(channel, data.vtopic()); +#endif + auto topicLink = GenerateTopicLink(channel, data.vtopic_info()); addSimpleServiceMessage(tr::lng_admin_log_topics_created( tr::now, lt_from, @@ -1728,8 +2219,12 @@ void GenerateItems( }; const auto createEditTopic = [&](const LogEditTopic &data) { +#if 0 // mtp const auto prevLink = GenerateTopicLink(channel, data.vprev_topic()); const auto nowLink = GenerateTopicLink(channel, data.vnew_topic()); +#endif + const auto prevLink = GenerateTopicLink(channel, data.vold_topic_info()); + const auto nowLink = GenerateTopicLink(channel, data.vnew_topic_info()); if (prevLink != nowLink) { addSimpleServiceMessage(tr::lng_admin_log_topics_changed( tr::now, @@ -1741,6 +2236,7 @@ void GenerateItems( nowLink, Ui::Text::WithEntities)); } +#if 0 // mtp const auto wasClosed = IsTopicClosed(data.vprev_topic()); const auto nowClosed = IsTopicClosed(data.vnew_topic()); if (nowClosed != wasClosed) { @@ -1767,10 +2263,40 @@ void GenerateItems( nowLink, Ui::Text::WithEntities)); } +#endif + }; + + const auto createToggleTopicClosed = [&](const LogToggleTopicClosed &data) { + const auto nowLink = GenerateTopicLink(channel, data.vtopic_info()); + addSimpleServiceMessage((data.vtopic_info().data().vis_closed().v + ? tr::lng_admin_log_topics_closed + : tr::lng_admin_log_topics_reopened)( + tr::now, + lt_from, + fromLinkText, + lt_topic, + nowLink, + Ui::Text::WithEntities)); + }; + + const auto createToggleTopicHidden = [&](const LogToggleTopicHidden &data) { + const auto nowLink = GenerateTopicLink(channel, data.vtopic_info()); + addSimpleServiceMessage((data.vtopic_info().data().vis_hidden().v + ? tr::lng_admin_log_topics_hidden + : tr::lng_admin_log_topics_unhidden)( + tr::now, + lt_from, + fromLinkText, + lt_topic, + nowLink, + Ui::Text::WithEntities)); }; const auto createDeleteTopic = [&](const LogDeleteTopic &data) { +#if 0 // mtp auto topicLink = GenerateTopicLink(channel, data.vtopic()); +#endif + auto topicLink = GenerateTopicLink(channel, data.vtopic_info()); if (!topicLink.entities.empty()) { topicLink.entities.erase(topicLink.entities.begin()); } @@ -1784,7 +2310,10 @@ void GenerateItems( }; const auto createPinTopic = [&](const LogPinTopic &data) { + if (const auto &topic = data.vnew_topic_info()) { +#if 0 // mtp if (const auto &topic = data.vnew_topic()) { +#endif auto topicLink = GenerateTopicLink(channel, *topic); addSimpleServiceMessage(tr::lng_admin_log_topics_pinned( tr::now, @@ -1793,7 +2322,10 @@ void GenerateItems( lt_topic, topicLink, Ui::Text::WithEntities)); + } else if (const auto &previous = data.vold_topic_info()) { +#if 0 // mtp } else if (const auto &previous = data.vprev_topic()) { +#endif auto topicLink = GenerateTopicLink(channel, *previous); addSimpleServiceMessage(tr::lng_admin_log_topics_unpinned( tr::now, @@ -1806,7 +2338,10 @@ void GenerateItems( }; const auto createToggleAntiSpam = [&](const LogToggleAntiSpam &data) { +#if 0 // mtp const auto enabled = (data.vnew_value().type() == mtpc_boolTrue); +#endif + const auto enabled = data.vhas_aggressive_anti_spam_enabled().v; const auto text = (enabled ? tr::lng_admin_log_antispam_enabled : tr::lng_admin_log_antispam_disabled)( @@ -1823,16 +2358,25 @@ void GenerateItems( lt_from, fromLinkText, lt_previous, +#if 0 // mtp { '#' + QString::number(data.vprev_value().v + 1) }, lt_color, { '#' + QString::number(data.vnew_value().v + 1) }, +#endif + { '#' + QString::number(data.vold_accent_color_id().v + 1) }, + lt_color, + { '#' + QString::number(data.vnew_accent_color_id().v + 1) }, Ui::Text::WithEntities); addSimpleServiceMessage(text); }; const auto createChangeBackgroundEmoji = [&](const LogChangeBackgroundEmoji &data) { +#if 0 // mtp const auto was = data.vprev_value().v; const auto now = data.vnew_value().v; +#endif + const auto was = data.vold_background_custom_emoji_id().v; + const auto now = data.vnew_background_custom_emoji_id().v; const auto text = !was ? tr::lng_admin_log_set_background_emoji( tr::now, @@ -1872,7 +2416,11 @@ void GenerateItems( createChangePhoto, createToggleInvites, createToggleSignatures, +#if 0 // goodToRemove createUpdatePinned, +#endif + [&](const LogPin &data) { createUpdatePinned(data.vmessage(), true); }, + [&](const LogUnpin &data) { createUpdatePinned(data.vmessage(), false); }, createEditMessage, createDeleteMessage, createParticipantJoin, @@ -1889,8 +2437,17 @@ void GenerateItems( createToggleSlowMode, createStartGroupCall, createDiscardGroupCall, +#if 0 // goodToRemove createParticipantMute, createParticipantUnmute, +#endif + [&](const LogMute &data) { + if (data.vis_muted().v) { + createParticipantMute(data); + } else { + createParticipantUnmute(data); + } + }, createToggleGroupCallSetting, createParticipantJoinByInvite, createExportedInviteDelete, @@ -1900,12 +2457,16 @@ void GenerateItems( createChangeHistoryTTL, createParticipantJoinByRequest, createToggleNoForwards, +#if 0 // later createSendMessage, +#endif createChangeAvailableReactions, createChangeUsernames, createToggleForum, createCreateTopic, createEditTopic, + createToggleTopicClosed, + createToggleTopicHidden, createDeleteTopic, createPinTopic, createToggleAntiSpam, diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.h index 4be0f9cfc7ac3..03688cfd498f8 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.h @@ -7,6 +7,10 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLDchatEvent; +} // namespace Tdb + class History; namespace HistoryView { @@ -21,7 +25,10 @@ class OwnedItem; void GenerateItems( not_null delegate, not_null history, +#if 0 // goodToRemove const MTPDchannelAdminLogEvent &event, +#endif + const Tdb::TLDchatEvent &event, Fn callback); // Smart pointer wrapper for HistoryItem* that destroys the owned item. diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index e5c03f37dc890..49e503beab13f 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -63,11 +63,16 @@ For license and copyright information please follow this link: #include "base/qt/qt_common_adapters.h" #include "styles/style_dialogs.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_option.h" + namespace { constexpr auto kNewBlockEachMessage = 50; constexpr auto kSkipCloudDraftsFor = TimeId(2); +using namespace Tdb; using UpdateFlag = Data::HistoryUpdate::Flag; } // namespace @@ -159,11 +164,13 @@ void History::itemVanished(not_null item) { if (lastKeyboardId == item->id) { clearLastKeyboard(); } +#if 0 // mtp if ((!item->out() || item->isPost()) && item->unread(this) && unreadCount() > 0) { setUnreadCount(unreadCount() - 1); } +#endif } void History::takeLocalDraft(not_null from) { @@ -410,6 +417,7 @@ void History::setForwardDraft( } } +#if 0 // mtp not_null History::createItem( MsgId id, const MTPMessage &message, @@ -442,7 +450,52 @@ std::vector> History::createItems( } return result; } +#endif + +std::vector> History::createItems( + const QVector> &data) { + auto result = std::vector>(); + result.reserve(data.size()); + const auto localFlags = MessageFlags(); + const auto detachExistingItem = true; + for (auto i = data.cend(), e = data.cbegin(); i != e;) { + const auto &data = *--i; + if (!data) { + continue; + } + result.emplace_back(createItem( + data->data().vid().v, + *data, + localFlags, + detachExistingItem)); + } + return result; +} + +not_null History::createItem( + MsgId id, + const TLmessage &message, + MessageFlags localFlags, + bool detachExistingItem, + HistoryItem *replacing) { + if (const auto result = owner().message(peer, id)) { + if (detachExistingItem) { + result->removeMainView(); + } + return result; + } + const auto result = makeMessage( + id, + message.data(), + localFlags, + replacing); + if (result->isScheduled()) { + owner().scheduledMessages().append(result); + } + return result; +} +#if 0 // mtp not_null History::addNewMessage( MsgId id, const MTPMessage &msg, @@ -459,6 +512,45 @@ not_null History::addNewMessage( } return addNewItem(item, unread); } +#endif + +not_null History::addMessage( + const TLmessage &message, + NewMessageType type, + MsgId oldMessageId) { + const auto old = oldMessageId + ? owner().message(peer, oldMessageId) + : nullptr; + const auto detachExistingItem = (type == NewMessageType::Unread); + const auto &data = message.data(); + const auto item = createItem( + data.vid().v, + message, + MessageFlags(), + detachExistingItem, + old); + if (!item->mainView() && item->isHistoryEntry()) { + const auto unread = (type == NewMessageType::Unread); + if (unread && item->isHistoryEntry()) { + applyMessageChanges(item, message); + } + if (const auto max = maxMsgId()) { + if (item->id >= max) { + if (type != NewMessageType::Existing) { + addNewItem(item, unread); + } + } else if (item->id >= minMsgId()) { + insertNewItem(item, unread); + } + } else if (type != NewMessageType::Existing) { + addNewItem(item, unread); + } + } + if (old && old != item) { + old->destroy(); + } + return item; +} not_null History::insertItem( std::unique_ptr item) { @@ -490,9 +582,11 @@ void History::destroyMessage(not_null item) { } itemRemoved(item); } +#if 0 // mtp if (item->isSending()) { session().api().cancelLocalItem(item); } +#endif const auto documentToCancel = [&] { const auto media = item->isAdminLogEntry() @@ -582,12 +676,15 @@ not_null History::addNewItem( not_null item, bool unread) { if (item->isScheduled()) { +#if 0 // mtp owner().scheduledMessages().appendSending(item); +#endif return item; } else if (!item->isHistoryEntry()) { return item; } +#if 0 // mtp // In case we've loaded a new 'last' message // and it is not in blocks and we think that // we have all the messages till the bottom @@ -599,6 +696,7 @@ not_null History::addNewItem( if (shouldMarkBottomNotLoaded) { setNotLoadedAtBottom(); } +#endif if (!loadedAtBottom() || peer->migrateTo()) { setLastMessage(item); @@ -612,6 +710,17 @@ not_null History::addNewItem( return item; } +not_null History::insertNewItem( + not_null item, + bool unread) { + insertMessageToBlocks(item); + if (unread) { + newItemAdded(item); + } + owner().notifyHistoryChangeDelayed(this); + return item; +} + void History::checkForLoadedAtTop(not_null added) { if (peer->isChat()) { if (added->isGroupEssential() && !added->isGroupMigrate()) { @@ -637,7 +746,9 @@ not_null History::addNewLocalMessage( PeerId from, const QString &postAuthor, const TextWithEntities &text, +#if 0 // mtp const MTPMessageMedia &media, +#endif HistoryMessageMarkupData &&markup, uint64 groupedId) { return addNewItem( @@ -650,7 +761,9 @@ not_null History::addNewLocalMessage( from, postAuthor, text, +#if 0 // mtp media, +#endif std::move(markup), groupedId), true); @@ -988,6 +1101,7 @@ void History::applyMessageChanges( void History::applyServiceChanges( not_null item, const MTPDmessageService &data) { +#if 0 // mtp const auto replyTo = data.vreply_to(); const auto processJoinedUser = [&]( not_null megagroup, @@ -1222,6 +1336,20 @@ void History::applyServiceChanges( } }, [](const auto &) { }); +#endif +} + +void History::applyMessageChanges( + not_null item, + const TLmessage &data) { + session().changes().messageUpdated( + item, + Data::MessageUpdate::Flag::NewAdded); +} + +TimeId History::chatListTimeId() const { + const auto item = lastMessage(); + return item ? item->date() : TimeId(); } void History::mainViewRemoved( @@ -1250,6 +1378,7 @@ void History::newItemAdded(not_null item) { from->madeAction(item->date()); } item->contributeToSlowmode(); +#if 0 // mtp auto notification = Data::ItemNotification{ .item = item, .type = Data::ItemNotificationType::Message, @@ -1257,12 +1386,16 @@ void History::newItemAdded(not_null item) { if (item->showNotification()) { item->notificationThread()->pushNotification(notification); } +#endif owner().notifyNewItemAdded(item); +#if 0 // mtp const auto stillShow = item->showNotification(); // Could be read already. if (stillShow) { Core::App().notifications().schedule(notification); } +#endif if (item->out()) { +#if 0 // mtp if (item->isFromScheduled() && unreadCountRefreshNeeded(item->id)) { if (unreadCountKnown()) { setUnreadCount(unreadCount() + 1); @@ -1270,6 +1403,8 @@ void History::newItemAdded(not_null item) { owner().histories().requestDialogEntry(this); } } else { +#endif + { destroyUnreadBar(); } if (!item->unread(this)) { @@ -1280,19 +1415,23 @@ void History::newItemAdded(not_null item) { } } else { if (item->unread(this)) { +#if 0 // mtp if (unreadCountKnown()) { setUnreadCount(unreadCount() + 1); } else if (!isForum()) { owner().histories().requestDialogEntry(this); } +#endif } else { inboxRead(item); } } item->incrementReplyToTopCounter(); +#if 0 // mtp if (!folderKnown()) { owner().histories().requestDialogEntry(this); } +#endif if (const auto topic = item->topic()) { topic->applyItemAdded(item); } @@ -1392,6 +1531,7 @@ void History::addEdgesToSharedMedia() { } } +#if 0 // mtp void History::addOlderSlice(const QVector &slice) { if (slice.isEmpty()) { _loadedAtTop = true; @@ -1409,6 +1549,7 @@ void History::addOlderSlice(const QVector &slice) { checkLocalMessages(); checkLastMessage(); } +#endif void History::addCreatedOlderSlice( const std::vector> &items) { @@ -1425,6 +1566,7 @@ void History::addCreatedOlderSlice( addToSharedMedia(items); } +#if 0 // mtp void History::addNewerSlice(const QVector &slice) { bool wasLoadedAtBottom = loadedAtBottom(); @@ -1456,6 +1598,57 @@ void History::addNewerSlice(const QVector &slice) { checkLocalMessages(); checkLastMessage(); } +#endif + +void History::addOlderSlice(const QVector> &slice) { + if (slice.isEmpty()) { + _loadedAtTop = true; + checkLocalMessages(); + return; + } + + if (const auto added = createItems(slice); !added.empty()) { + addCreatedOlderSlice(added); + } else { + // If no items were added it means we've loaded everything old. + _loadedAtTop = true; + addEdgesToSharedMedia(); + } + checkLocalMessages(); + checkLastMessage(); +} + +void History::addNewerSlice(const QVector> &slice) { + bool wasLoadedAtBottom = loadedAtBottom(); + + if (slice.isEmpty()) { + _loadedAtBottom = true; + if (!lastMessage()) { + setLastMessage(lastAvailableMessage()); + } + } + + if (const auto added = createItems(slice); !added.empty()) { + Assert(!isBuildingFrontBlock()); + + for (const auto &item : added) { + addItemToBlock(item); + } + + addToSharedMedia(added); + } else { + _loadedAtBottom = true; + setLastMessage(lastAvailableMessage()); + addEdgesToSharedMedia(); + } + + if (!wasLoadedAtBottom) { + checkAddAllToUnreadMentions(); + } + + checkLocalMessages(); + checkLastMessage(); +} void History::checkLastMessage() { if (const auto last = lastMessage()) { @@ -1675,7 +1868,10 @@ std::optional History::countStillUnreadLocal(MsgId readTillId) const { for (const auto &message : block->messages) { const auto item = message->data(); if (!item->isRegular() +#if 0 // mtp || (item->out() && !item->isFromScheduled())) { +#endif + || item->out()) { continue; } else if (item->id > readTillId) { break; @@ -1724,23 +1920,35 @@ void History::applyInboxReadUpdate( MsgId upTo, int stillUnread, int32 channelPts) { + Expects(folderId == (folder() ? folder()->id() : 0)); + +#if 0 // mtp const auto folder = folderId ? owner().folderLoaded(folderId) : nullptr; if (folder && this->folder() != folder) { // If history folder is unknown or not synced, request both. owner().histories().requestDialogEntry(this); owner().histories().requestDialogEntry(folder); } +#endif if (_inboxReadBefore.value_or(1) <= upTo) { +#if 0 // mtp if (!peer->isChannel() || peer->asChannel()->pts() == channelPts) { inboxRead(upTo, stillUnread); } else { inboxRead(upTo); } +#endif + inboxRead(upTo, stillUnread); + } else if (inboxReadTillId() == upTo) { + setUnreadCount(stillUnread); } } void History::inboxRead(MsgId upTo, std::optional stillUnread) { +#if 0 // mtp if (stillUnread.has_value() && folderKnown()) { +#endif + if (stillUnread.has_value()) { setUnreadCount(*stillUnread); } else if (const auto still = countStillUnreadLocal(upTo)) { setUnreadCount(*still); @@ -1786,7 +1994,10 @@ void History::outboxRead(not_null wasRead) { MsgId History::loadAroundId() const { if (_unreadCount && *_unreadCount > 0 && _inboxReadBefore) { +#if 0 // mtp return *_inboxReadBefore; +#endif + return inboxReadTillId(); // We can't return non-existent message id. } return MsgId(0); } @@ -1812,7 +2023,9 @@ bool History::unreadCountKnown() const { } void History::setUnreadCount(int newUnreadCount) { +#if 0 // mtp Expects(folderKnown()); +#endif if (_unreadCount == newUnreadCount) { return; @@ -1835,9 +2048,11 @@ void History::setUnreadCount(int newUnreadCount) { ? blocks.back()->messages.back().get() : nullptr; } +#if 0 // mtp if (const auto last = msgIdForRead()) { setInboxReadTill(last - 1); } +#endif } else if (!newUnreadCount) { _firstUnreadView = nullptr; if (const auto last = msgIdForRead()) { @@ -1949,6 +2164,7 @@ Data::Folder *History::folder() const { return _folder.value_or(nullptr); } +#if 0 // mtp void History::setFolder( not_null folder, HistoryItem *folderDialogItem) { @@ -1966,6 +2182,15 @@ void History::setFolderPointer(Data::Folder *folder) { if (_folder == folder) { return; } +#endif +void History::setFolderPointer( + Data::Folder *folder, + int64 order, + bool pinned) { + if (_folder == folder) { + updateChatListSortPosition(FilterId(), order, pinned); + return; + } if (isPinnedDialog(FilterId())) { owner().setChatPinned(this, FilterId(), false); } @@ -1979,6 +2204,7 @@ void History::setFolderPointer(Data::Folder *folder) { if (was) { was->unregisterOne(this); } +#if 0 // mtp if (wasInList) { addToChatList(0, owner().chatsList(folder)); @@ -1990,10 +2216,33 @@ void History::setFolderPointer(Data::Folder *folder) { } else if (!wasKnown) { updateChatListSortPosition(); } +#endif + updateChatListSortPosition(FilterId(), order, pinned); + const auto nowInList = inChatList(); + if (wasInList) { + owner().chatsListChanged(was); + } + if (inChatList()) { + owner().chatsListChanged(folder); + } if (folder) { folder->registerOne(this); } session().changes().historyUpdated(this, UpdateFlag::Folder); + + if (!_pendingFilterPosition.empty()) { + const auto positions = base::take(_pendingFilterPosition); + for (const auto &[filterId, info] : positions) { + updateChatListSortPosition(filterId, info.order, info.pinned); + } + } +} + +void History::setLastKeyboardId(MsgId id) { + if (lastKeyboardId != id) { + lastKeyboardId = id; + session().changes().historyUpdated(this, UpdateFlag::BotKeyboard); + } } int History::chatListNameVersion() const { @@ -2027,6 +2276,7 @@ void History::hasUnreadReactionChanged(bool has) { notifyUnreadStateChange(was); } +#if 0 // mtp void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { const auto folderId = data.vfolder_id().value_or_empty(); if (!folderKnown()) { @@ -2050,6 +2300,7 @@ TimeId History::adjustedChatListTimeId() const { } return result; } +#endif void History::countScrollState(int top) { std::tie(scrollTopItem, scrollTopOffset) = findItemAndOffset(top); @@ -2518,6 +2769,7 @@ auto History::computeChatListMessageFromLast() const return _lastMessage; } +#if 0 // tdlib todo what with migration message // In migrated groups we want to skip essential message // about migration in the chats list and display the last // non-migration message from the original legacy group. @@ -2558,6 +2810,7 @@ auto History::computeChatListMessageFromLast() const ? std::make_optional(from->chatListMessage()) : std::nullopt; } +#endif return _lastMessage; } @@ -2615,6 +2868,7 @@ void History::setFakeChatListMessage() { } } +#if 0 // mtp void History::setFakeChatListMessageFrom(const MTPmessages_Messages &data) { if (!lastMessageKnown()) { requestChatListMessage(); @@ -2659,10 +2913,14 @@ void History::setFakeChatListMessageFrom(const MTPmessages_Messages &data) { } setChatListMessage(item); } +#endif void History::applyChatListGroup( PeerId dataPeerId, +#if 0 // mtp const MTPmessages_Messages &data) { +#endif + std::vector> items) { if (!isEmpty() || !_chatListMessage || !*_chatListMessage @@ -2673,6 +2931,7 @@ void History::applyChatListGroup( return; } // Apply loaded album as a last slice. +#if 0 // mtp const auto processMessages = [&](const MTPVector &messages) { auto items = std::vector>(); items.reserve(messages.v.size()); @@ -2682,6 +2941,7 @@ void History::applyChatListGroup( items.push_back(message); } } +#endif if (!ranges::contains(items, not_null(*_lastMessage)) || !ranges::contains(items, not_null(*_chatListMessage))) { return; @@ -2691,11 +2951,13 @@ void History::applyChatListGroup( addCreatedOlderSlice(items); checkLocalMessages(); checkLastMessage(); +#if 0 // mtp }; data.match([&](const MTPDmessages_messagesNotModified &) { }, [&](const auto &data) { processMessages(data.vmessages()); }); +#endif } HistoryItem *History::lastMessage() const { @@ -2740,6 +3002,7 @@ bool History::trackUnreadMessages() const { return true; } +#if 0 // mtp bool History::shouldBeInChatList() const { if (peer->migrateTo() || !folderKnown()) { return false; @@ -2767,6 +3030,7 @@ void History::unknownMessageDeleted(MsgId messageId) { owner().histories().requestDialogEntry(this); } } +#endif bool History::isServerSideUnread(not_null item) const { Expects(item->isRegular()); @@ -2776,6 +3040,7 @@ bool History::isServerSideUnread(not_null item) const { : (!_inboxReadBefore || (item->id >= *_inboxReadBefore)); } +#if 0 // mtp void History::applyDialog( Data::Folder *requestFolder, const MTPDdialog &data) { @@ -2826,6 +3091,86 @@ void History::applyDialog( } owner().histories().dialogEntryApplied(this); } +#endif + +void History::applyPosition(const TLDchatPosition &data) { + const auto pinned = data.vis_pinned().v; + const auto order = data.vorder().v; + if (const auto source = data.vsource()) { + source->match([&](const TLDchatSourceMtprotoProxy &) { + owner().setTopPromoted(this, QString(), QString()); + }, [&](const TLDchatSourcePublicServiceAnnouncement &data) { + owner().setTopPromoted(this, data.vtype().v, data.vtext().v); + }); + } else { + owner().setNotTopPromoted(this); + } + data.vlist().match([&](const TLDchatListMain &) { + if (order || !folder()) { + setFolderPointer(nullptr, order, pinned); + } + }, [&](const TLDchatListArchive &) { + if (order || folder()) { + const auto value = !order + ? nullptr + : (folderKnown() && folder()) + ? folder() + : owner().folder(Data::Folder::kId).get(); + setFolderPointer(value, order, pinned); + } + }, [&](const TLDchatListFolder &data) { + const auto filterId = data.vchat_folder_id().v; + if (folderKnown()) { + updateChatListSortPosition(filterId, order, pinned); + } else if (order) { + _pendingFilterPosition[filterId] = { order, pinned }; + } else { + _pendingFilterPosition.erase(filterId); + } + }); +} + +void History::finishSavingCloudDraftNow(MsgId topicRootId) { + session().sender().request(TLgetOption( + tl_string("unix_time") + )).done([=](const TLoptionValue &value) { + finishSavingCloudDraft( + topicRootId, + TimeId(OptionValue(value))); + }).fail([=] { + finishSavingCloudDraft(topicRootId, TimeId()); + }).send(); +} + +void History::applyUnreadInfo( + int unreadCount, + MsgId maxInboxRead, + MsgId maxOutboxRead) { + setOutboxReadTill(maxOutboxRead); + setInboxReadTill(maxInboxRead); + setUnreadCount(unreadCount); +} + +void History::applyLastMessage(const TLmessage &data) { + const auto &message = data.data(); + const auto id = message.vid().v; + addMessage(data, NewMessageType::Last); + applyDialogTopMessage(id); +} + +void History::clearLastMessage() { + if (_lastServerMessage) { + if (lastMessage() == lastServerMessage()) { + _lastMessage = std::nullopt; + } + if (chatListMessage() == lastServerMessage()) { + _chatListMessage = std::nullopt; + updateChatListEntry(); + } + _lastServerMessage = std::nullopt; + } + setNotLoadedAtBottom(); +} void History::dialogEntryApplied() { if (!lastServerMessageKnown()) { @@ -2833,16 +3178,18 @@ void History::dialogEntryApplied() { } else if (!lastMessageKnown()) { setLastMessage(nullptr); } +#if 0 // mtp if (peer->migrateTo()) { return; } else if (!chatListMessageKnown()) { requestChatListMessage(); return; } +#endif if (!chatListMessage()) { clear(ClearType::Unload); - addNewerSlice(QVector()); - addOlderSlice(QVector()); + addNewerSlice({}); + addOlderSlice({}); if (const auto channel = peer->asChannel()) { const auto inviter = channel->inviter; if (inviter && channel->amIn()) { @@ -2919,6 +3266,7 @@ bool History::skipUnreadUpdate() const { return clearUnreadOnClientSide(); } +#if 0 // mtp void History::applyDialogFields( Data::Folder *folder, int unreadCount, @@ -2936,6 +3284,7 @@ void History::applyDialogFields( } setOutboxReadTill(maxOutboxRead); } +#endif void History::applyDialogTopMessage(MsgId topMessageId) { if (topMessageId) { @@ -3147,7 +3496,7 @@ HistoryItem *History::insertJoinedMessage() { return _joinedMessage; } - const auto inviter = (channel->inviter.bare > 0) + const auto inviter = channel->inviter ? owner().userLoaded(channel->inviter) : nullptr; if (!inviter) { @@ -3232,6 +3581,7 @@ void History::removeJoinedMessage() { } } +#if 0 // mtp void History::reactionsEnabledChanged(bool enabled) { if (!enabled) { for (const auto &item : _messages) { @@ -3243,6 +3593,7 @@ void History::reactionsEnabledChanged(bool enabled) { } } } +#endif bool History::isEmpty() const { return blocks.empty(); @@ -3397,16 +3748,21 @@ void History::clear(ClearType type) { if (type == ClearType::DeleteChat) { setLastServerMessage(nullptr); } else if (_lastMessage && *_lastMessage) { +#if 0 // mtp if ((*_lastMessage)->isRegular()) { (*_lastMessage)->applyEditionToHistoryCleared(); } else { _lastMessage = std::nullopt; } +#endif + _lastMessage = std::nullopt; } const auto tillId = (_lastMessage && *_lastMessage) ? (*_lastMessage)->id : std::numeric_limits::max(); +#if 0 // mtp clearUpTill(tillId); +#endif if (blocks.empty() && _lastMessage && *_lastMessage) { addItemToBlock(*_lastMessage); } @@ -3426,6 +3782,7 @@ void History::clear(ClearType type) { owner().sendHistoryChangeNotifications(); } +#if 0 // mtp void History::clearUpTill(MsgId availableMinId) { auto remove = std::vector>(); remove.reserve(_messages.size()); @@ -3444,6 +3801,7 @@ void History::clearUpTill(MsgId availableMinId) { } requestChatListMessage(); } +#endif void History::applyGroupAdminChanges(const base::flat_set &changes) { for (const auto &block : blocks) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 2ef89cb460588..2209807e2ef21 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -16,6 +16,13 @@ For license and copyright information please follow this link: #include "base/flat_set.h" #include "base/flags.h" +namespace Tdb { +class TLDchatPosition; +class TLmessage; +class TLDmessages; +class TLDupdateMessageMentionRead; +} // namespace Tdb + class History; class HistoryBlock; class HistoryTranslation; @@ -102,7 +109,10 @@ class History final : public Data::Thread { void checkLocalMessages(); void removeJoinedMessage(); +#if 0 // mtp void reactionsEnabledChanged(bool enabled); +#endif + void setLastKeyboardId(MsgId id); bool isEmpty() const; bool isDisplayedEmpty() const; @@ -120,7 +130,9 @@ class History final : public Data::Thread { ClearHistory, }; void clear(ClearType type); +#if 0 // mtp void clearUpTill(MsgId availableMinId); +#endif void applyGroupAdminChanges(const base::flat_set &changes); @@ -139,11 +151,13 @@ class History final : public Data::Thread { void unpinMessagesFor(MsgId topicRootId); +#if 0 // mtp not_null addNewMessage( MsgId id, const MTPMessage &msg, MessageFlags localFlags, NewMessageType type); +#endif not_null addNewLocalMessage( MsgId id, MessageFlags flags, @@ -153,7 +167,9 @@ class History final : public Data::Thread { PeerId from, const QString &postAuthor, const TextWithEntities &text, +#if 0 // mtp const MTPMessageMedia &media, +#endif HistoryMessageMarkupData &&markup, uint64 groupedId = 0); not_null addNewLocalMessage( @@ -201,7 +217,13 @@ class History final : public Data::Thread { Data::SponsoredFrom from, const TextWithEntities &textWithEntities); // sponsored + not_null addMessage( + const Tdb::TLmessage &message, + NewMessageType type = NewMessageType::Existing, + MsgId oldMessageId = 0); + // Used only internally and for channel admin log. +#if 0 // mtp not_null createItem( MsgId id, const MTPMessage &message, @@ -209,9 +231,25 @@ class History final : public Data::Thread { bool detachExistingItem); std::vector> createItems( const QVector &data); +#endif + + std::vector> createItems( + const QVector> &data); + not_null createItem( + MsgId id, + const Tdb::TLmessage &message, + MessageFlags localFlags, + bool detachExistingItem, + HistoryItem *replacing = nullptr); + +#if 0 // mtp void addOlderSlice(const QVector &slice); void addNewerSlice(const QVector &slice); +#endif + + void addOlderSlice(const QVector> &slice); + void addNewerSlice(const QVector> &slice); void newItemAdded(not_null item); @@ -267,15 +305,31 @@ class History final : public Data::Thread { [[nodiscard]] HistoryItem *lastServerMessage() const; [[nodiscard]] bool lastMessageKnown() const; [[nodiscard]] bool lastServerMessageKnown() const; +#if 0 // mtp void unknownMessageDeleted(MsgId messageId); +#endif void applyDialogTopMessage(MsgId topMessageId); + +#if 0 // mtp void applyDialog(Data::Folder *requestFolder, const MTPDdialog &data); void applyPinnedUpdate(const MTPDupdateDialogPinned &data); +#endif + + void applyPosition(const Tdb::TLDchatPosition &data); + void finishSavingCloudDraftNow(MsgId topicRootId); + void applyUnreadInfo(int unreadCount, + MsgId maxInboxRead, + MsgId maxOutboxRead); + void applyLastMessage(const Tdb::TLmessage &data); + void clearLastMessage(); + +#if 0 // mtp void applyDialogFields( Data::Folder *folder, int unreadCount, MsgId maxInboxRead, MsgId maxOutboxRead); +#endif void dialogEntryApplied(); void cacheTopPromotion( @@ -376,7 +430,9 @@ class History final : public Data::Thread { [[nodiscard]] bool useTopPromotion() const; int fixedOnTopIndex() const override; void updateChatListExistence() override; +#if 0 // mtp bool shouldBeInChatList() const override; +#endif Dialogs::UnreadState chatListUnreadState() const override; Dialogs::BadgesState chatListBadgesState() const override; HistoryItem *chatListMessage() const override; @@ -394,12 +450,17 @@ class History final : public Data::Thread { void refreshChatListNameSortKey(); +#if 0 // mtp void setFakeChatListMessageFrom(const MTPmessages_Messages &data); +#endif void checkChatListMessageRemoved(not_null item); void applyChatListGroup( PeerId dataPeerId, +#if 0 // mtp const MTPmessages_Messages &data); +#endif + std::vector> items); void forgetScrollState() { scrollTopItem = nullptr; @@ -415,10 +476,12 @@ class History final : public Data::Thread { bool folderKnown() const override; Data::Folder *folder() const override; +#if 0 // mtp void setFolder( not_null folder, HistoryItem *folderDialogItem = nullptr); void clearFolder(); +#endif // Interface for Data::Histories. void setInboxReadTill(MsgId upTo); @@ -457,6 +520,12 @@ class History final : public Data::Thread { mtpRequestId sendRequestId = 0; private: + struct PendingFilterPosition { + int64 order = 0; + bool pinned = false; + }; + base::flat_map _pendingFilterPosition; + friend class HistoryBlock; enum class Flag : uchar { @@ -503,6 +572,9 @@ class History final : public Data::Thread { not_null item, int blockIndex, int itemIndex); + not_null insertNewItem( + not_null item, + bool unread); // All this methods add a new item to the first or last block // depending on if we are in isBuildingFronBlock() state. @@ -529,7 +601,10 @@ class History final : public Data::Thread { not_null block, not_null view); +#if 0 // mtp TimeId adjustedChatListTimeId() const override; +#endif + void changedChatListPinHook() override; void setOutboxReadTill(MsgId upTo); @@ -542,6 +617,11 @@ class History final : public Data::Thread { not_null item, const MTPDmessageService &data); + void applyMessageChanges( + not_null item, + const Tdb::TLmessage &original); + [[nodiscard]] TimeId chatListTimeId() const; + // After adding a new history slice check lastMessage / loadedAtBottom. void checkLastMessage(); void setLastMessage(HistoryItem *item); @@ -585,7 +665,10 @@ class History final : public Data::Thread { [[nodiscard]] Dialogs::BadgesState adjustBadgesStateByFolder( Dialogs::BadgesState state) const; [[nodiscard]] Dialogs::UnreadState computeUnreadState() const; +#if 0 // mtp void setFolderPointer(Data::Folder *folder); +#endif + void setFolderPointer(Data::Folder *folder, int64 order, bool pinned); int chatListNameVersion() const override; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 7c3fbbcaad73e..6b2c562872dba 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -102,6 +102,8 @@ For license and copyright information please follow this link: #include "payments/payments_form.h" #include "base/random.h" +#include "tdb/tdb_tl_scheme.h" + #include #include #include @@ -414,7 +416,20 @@ bool HistoryInner::BotAbout::refresh() { ? make(info->document, textWithEntities) : info->photo ? make(info->photo, textWithEntities) +#if 0 // mtp : make(textWithEntities, MTP_messageMediaEmpty(), groupedId); +#endif + : _history->makeMessage( + _history->nextNonHistoryEntryId(), + flags, + replyTo, + viaBotId, + date, + bot->id, + postAuthor, + textWithEntities, + HistoryMessageMarkupData(), + groupedId); _item = AdminLog::OwnedItem(_delegate, item); return true; } @@ -656,6 +671,9 @@ void HistoryInner::setupSharingDisallowed() { if (_peer->isUser()) { _sharingDisallowed = false; return; + } else if (_peer->isSecretChat()) { + _sharingDisallowed = false; + return; } const auto chat = _peer->asChat(); const auto channel = _peer->asChannel(); @@ -700,6 +718,7 @@ bool HistoryInner::hasSelectRestriction() const { return true; } +#if 0 // mtp void HistoryInner::messagesReceived( not_null peer, const QVector &messages) { @@ -734,6 +753,39 @@ void HistoryInner::messagesReceivedDown( _migrated->addNewerSlice(messages); } } +#endif + +void HistoryInner::messagesReceived( + not_null peer, + const QVector> &messages) { + if (_history->peer == peer) { + _history->addOlderSlice(messages); + } else if (_migrated && _migrated->peer == peer) { + const auto newLoaded = _migrated + && _migrated->isEmpty() + && !_history->isEmpty(); + _migrated->addOlderSlice(messages); + if (newLoaded) { + _migrated->addNewerSlice({}); + } + } +} + +void HistoryInner::messagesReceivedDown( + not_null peer, + const QVector> &messages) { + if (_history->peer == peer) { + const auto oldLoaded = _migrated + && _history->isEmpty() + && !_migrated->isEmpty(); + _history->addNewerSlice(messages); + if (oldLoaded) { + _history->addOlderSlice({}); + } + } else if (_migrated && _migrated->peer == peer) { + _migrated->addNewerSlice(messages); + } +} void HistoryInner::repaintItem(const HistoryItem *item) { if (const auto view = viewByItem(item)) { @@ -1136,7 +1188,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) { } else if (isUnread) { readTill = item; } - if (markingAsViewed && item->hasViews()) { + // Always inform TDLib about messages being viewed. + if (markingAsViewed && item->isRegular()) { session().api().views().scheduleIncrement(item); } if (withReaction) { @@ -1147,10 +1200,12 @@ void HistoryInner::paintEvent(QPaintEvent *e) { _widget->enqueueMessageHighlight(view, {}); } } +#if 0 // mtp session().data().reactions().poll(item, context.now); if (item->hasExtendedMediaPreview()) { session().api().views().pollExtendedMedia(item); } +#endif _reactionsManager->recordCurrentReactionEffect( item->fullId(), QPoint(0, top)); diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 810521f504c40..d4bf69286f199 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -20,6 +20,10 @@ For license and copyright information please follow this link: struct ClickContext; struct ClickHandlerContext; +namespace Tdb { +class TLmessage; +} // namespace Tdb + namespace Data { struct Group; } // namespace Data @@ -104,12 +108,21 @@ class HistoryInner Ui::ChatPaintContext preparePaintContext(const QRect &clip) const; +#if 0 // mtp void messagesReceived( not_null peer, const QVector &messages); void messagesReceivedDown( not_null peer, const QVector &messages); +#endif + + void messagesReceived( + not_null peer, + const QVector> &messages); + void messagesReceivedDown( + not_null peer, + const QVector> &messages); [[nodiscard]] TextForMimeData getSelectedText() const; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ef3cf5172baa7..c65888211a5bf 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -71,8 +71,15 @@ For license and copyright information please follow this link: #include "spellcheck/spellcheck_highlight_syntax.h" #include "styles/style_dialogs.h" +#include "tdb/tdb_tl_scheme.h" +#include "data/data_document.h" +#include "api/api_transcribes.h" +#include "chat_helpers/stickers_emoji_pack.h" + namespace { +using namespace Tdb; + constexpr auto kNotificationTextLimit = 255; constexpr auto kPinnedMessageTextLimit = 16; @@ -117,6 +124,19 @@ using ItemPreview = HistoryView::ItemPreview; return false; } +void ApplyTranscribe( + not_null item, + const TLspeechRecognitionResult *result, + bool roundview) { + if (!result) { + return; + } + item->history()->session().api().transcribes().apply( + item, + *result, + roundview); +} + } // namespace void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) { @@ -152,6 +172,7 @@ struct HistoryItem::CreateConfig { const HistoryMessageReplyMarkup *inlineMarkup = nullptr; }; +#if 0 // mtp void HistoryItem::FillForwardedInfo( CreateConfig &config, const MTPDmessageFwdHeader &data) { @@ -332,7 +353,10 @@ HistoryItem::HistoryItem( : MediaCheckResult::Good; if (checked == MediaCheckResult::Unsupported) { _flags &= ~MessageFlag::HasPostAuthor; +#if 0 // mtp _flags |= MessageFlag::Legacy; +#endif + _flags &= ~MessageFlag::CanEdit; createComponents(data); setText(UnsupportedMessageText()); } else if (checked == MediaCheckResult::Empty) { @@ -408,6 +432,100 @@ HistoryItem::HistoryItem( tr::now, Ui::Text::WithEntities) }) { } +#endif + +void HistoryItem::FillForwardedInfo( + CreateConfig &config, + const TLDmessageForwardInfo &data) { + config.originalDate = data.vdate().v; + config.forwardPsaType = data.vpublic_service_announcement_type().v; + data.vorigin().match([&](const TLDmessageOriginUser &data) { + config.originalSenderId = peerFromUser(data.vsender_user_id()); + }, [&](const TLDmessageOriginChat &data) { + config.originalSenderId = peerFromTdbChat(data.vsender_chat_id()); + }, [&](const TLDmessageOriginHiddenUser &data) { + config.originalSenderName = data.vsender_name().v; + }, [&](const TLDmessageOriginChannel &data) { + config.originalSenderId = peerFromTdbChat(data.vchat_id()); + config.originalId = data.vmessage_id().v; + config.originalPostAuthor = data.vauthor_signature().v; + }); + if (const auto savedFromMsgId = data.vfrom_message_id().v) { + config.savedFromPeer = peerFromTdbChat(data.vfrom_chat_id()); + config.savedFromMsgId = savedFromMsgId; + } +} + +void HistoryItem::FillImportInfo( + CreateConfig &config, + const TLDmessageImportInfo &data) { + config.originalSenderName = data.vsender_name().v; + config.imported = true; +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + const TLDmessage &data, + MessageFlags localFlags, + HistoryItem *replacing) +: HistoryItem( + history, + id, + FlagsFromTdb(data) | localFlags, + MessageDateFromTdb(data), + peerFromSender(data.vsender_id())) { + auto config = CreateConfig(); + if (const auto forwarded = data.vforward_info()) { + FillForwardedInfo(config, forwarded->data()); + } + if (const auto imported = data.vimport_info()) { + FillImportInfo(config, imported->data()); + } + if (const auto replyTo = data.vreply_to()) { + config.reply = ReplyFieldsFromTL(this, *replyTo); + } + config.reply.topMessageId = data.vmessage_thread_id().v; + config.reply.topicPost = data.vis_topic_message().v; + config.viaBotId = data.vvia_bot_user_id().v; + const auto interaction = data.vinteraction_info(); + if (interaction) { + config.viewsCount = interaction->data().vview_count().v; + if (!config.viewsCount) { + config.viewsCount = -1; + } + config.forwardsCount = interaction->data().vforward_count().v; + if (!config.forwardsCount) { + config.forwardsCount = -1; + } + config.replies = isScheduled() + ? HistoryMessageRepliesData() + : HistoryMessageRepliesData(interaction->data().vreply_info()); + } + config.markup = HistoryMessageMarkupData(data.vreply_markup()); + config.editDate = data.vedit_date().v; + config.postAuthor = data.vauthor_signature().v; + + createComponents(std::move(config)); + + setContent(data.vcontent()); + if (const auto groupId = data.vmedia_album_id().v) { + if (replacing && replacing->groupId()) { + _history->owner().groups().unregisterMessage(replacing); + } + setGroupId( + MessageGroupId::FromRaw( + history->peer->id, + groupId, + (_flags & MessageFlag::IsOrWasScheduled))); + } + if (interaction) { + setReactions( + interaction->data().vreactions().v, + data.vunread_reactions().v); + } + applyTTL(data); +} HistoryItem::HistoryItem( not_null history, @@ -547,7 +665,9 @@ HistoryItem::HistoryItem( PeerId from, const QString &postAuthor, const TextWithEntities &textWithEntities, +#if 0 // mtp const MTPMessageMedia &media, +#endif HistoryMessageMarkupData &&markup, uint64 groupedId) : HistoryItem( @@ -562,7 +682,9 @@ HistoryItem::HistoryItem( viaBotId, postAuthor, std::move(markup)); +#if 0 // mtp setMedia(media); +#endif setText(textWithEntities); if (groupedId) { setGroupId(MessageGroupId::FromRaw( @@ -1021,19 +1143,37 @@ void HistoryItem::setCommentsPossibleMaxId(MsgId possibleMaxId) { bool HistoryItem::areCommentsUnread() const { const auto views = Get(); +#if 0 // mtp if (!views || !views->commentsMegagroupId || !checkCommentsLinkedChat(views->commentsMegagroupId)) { +#endif + if (!views) { + return false; + } + const auto channel = _history->peer->asChannel(); + if (!channel + || !channel->linkedChatKnown() + || !(channel->flags() & ChannelDataFlag::HasLink)) { + return false; + } + const auto linked = channel->linkedChat(); + if (!linked) { return false; } const auto till = views->commentsInboxReadTillId; if (views->commentsInboxReadTillId < 2 || views->commentsMaxId <= till) { return false; } +#if 0 // mtp const auto group = views->commentsMegagroupId ? _history->owner().historyLoaded( peerFromChannel(views->commentsMegagroupId)) : _history.get(); +#endif + const auto group = (linked == _history->peer) + ? _history.get() + : _history->owner().historyLoaded(linked->id); return !group || (views->commentsMaxId > group->inboxReadTillId()); } @@ -1057,7 +1197,9 @@ void HistoryItem::setCommentsItemId(FullMsgId id) { if (const auto channelId = peerToChannel(id.peer)) { if (views->commentsMegagroupId != channelId) { views->commentsMegagroupId = channelId; +#if 0 // mtp _history->owner().requestItemResize(this); +#endif } views->commentsRootId = id.msg; } @@ -1250,6 +1392,7 @@ bool HistoryItem::isIncomingUnreadMedia() const { void HistoryItem::markMediaAndMentionRead() { _flags &= ~MessageFlag::MediaIsUnread; +#if 0 // mtp if (mentionsMe()) { _history->updateChatListEntry(); _history->unreadMentions().erase(id); @@ -1271,6 +1414,7 @@ void HistoryItem::markMediaAndMentionRead() { } } } +#endif } void HistoryItem::markReactionsRead() { @@ -1437,10 +1581,12 @@ bool HistoryItem::isAdminLogEntry() const { return (_flags & MessageFlag::AdminLogEntry); } +#if 0 // mtp bool HistoryItem::isFromScheduled() const { return isHistoryEntry() && (_flags & MessageFlag::IsOrWasScheduled); } +#endif bool HistoryItem::isScheduled() const { return !isHistoryEntry() @@ -1507,6 +1653,7 @@ void HistoryItem::clearMainView() { _mainView = nullptr; } +#if 0 // mtp void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { int keyboardTop = -1; //if (!pendingResize()) {// #TODO edit bot message @@ -1589,6 +1736,7 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { finishEdition(keyboardTop); } +#endif void HistoryItem::applyChanges(not_null story) { Expects(_flags & MessageFlag::StoryItem); @@ -1614,6 +1762,7 @@ void HistoryItem::setStoryFields(not_null story) { setText(story->caption()); } +#if 0 // mtp void HistoryItem::applyEdition(const MTPDmessageService &message) { if (message.vaction().type() == mtpc_messageActionHistoryClear) { const auto wasGrouped = history()->owner().groups().isGrouped(this); @@ -1778,6 +1927,23 @@ void HistoryItem::applyEditionToHistoryCleared() { MTPint() // ttl_period ).c_messageService()); } +#endif + +void HistoryItem::setIsSilent(bool silent) { + _flags |= MessageFlag::Silent; +} + +void HistoryItem::setHideNotificationText(bool hide) { + if (hide) { + _flags |= MessageFlag::HideNotificationText; + } else { + _flags &= ~MessageFlag::HideNotificationText; + } +} + +bool HistoryItem::hideNotificationText() const { + return (_flags & MessageFlag::HideNotificationText); +} void HistoryItem::updateReplyMarkup(HistoryMessageMarkupData &&markup) { setReplyMarkup(std::move(markup)); @@ -1841,6 +2007,7 @@ void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) { } void HistoryItem::destroyHistoryEntry() { +#if 0 // mtp if (isUnreadMention()) { history()->unreadMentions().erase(id); if (const auto topic = this->topic()) { @@ -1853,6 +2020,7 @@ void HistoryItem::destroyHistoryEntry() { topic->unreadReactions().erase(id); } } +#endif if (isRegular() && _history->peer->isMegagroup()) { if (const auto reply = Get()) { changeReplyToTopCounter(reply, -1); @@ -1947,7 +2115,10 @@ void HistoryItem::changeReplyToTopCounter( QString HistoryItem::notificationHeader() const { if (isService()) { return QString(); +#if 0 // mtp } else if (out() && isFromScheduled() && !_history->peer->isSelf()) { +#endif + } else if (out() && !_history->peer->isSelf()) { return tr::lng_from_you(tr::now); } else if (!_history->peer->isUser() && !isPost()) { return from()->name(); @@ -1955,6 +2126,7 @@ QString HistoryItem::notificationHeader() const { return QString(); } +#if 0 // mtp void HistoryItem::setRealId(MsgId newId) { Expects(_flags & MessageFlag::BeingSent); Expects(IsClientMsgId(id)); @@ -1983,6 +2155,7 @@ void HistoryItem::setRealId(MsgId newId) { incrementReplyToTopCounter(); } } +#endif bool HistoryItem::canPin() const { if (!isRegular() || isService()) { @@ -2009,18 +2182,23 @@ bool HistoryItem::allowsForward() const { && (!_media || _media->allowsForward()); } +#if 0 // mtp bool HistoryItem::isTooOldForEdit(TimeId now) const { return !_history->peer->canEditMessagesIndefinitely() && !isScheduled() && (now - date() >= _history->session().serverConfig().editTimeLimit); } +#endif bool HistoryItem::allowsEdit(TimeId now) const { +#if 0 // mtp return !isService() && canBeEdited() && !isTooOldForEdit(now) && (!_media || _media->allowsEdit()) && !isLegacyMessage() +#endif + return (_flags & MessageFlag::CanEdit) && !isEditingMedia(); } @@ -2098,6 +2276,11 @@ bool HistoryItem::canDelete() const { } bool HistoryItem::canDeleteForEveryone(TimeId now) const { + // For channels we don't consider this true, because we + // didn't in the original code. + return !_history->peer->isChannel() + && (_flags & MessageFlag::CanDeleteForAll); +#if 0 // mtp const auto peer = _history->peer; const auto &config = _history->session().serverConfig(); const auto messageToMyself = peer->isSelf(); @@ -2135,6 +2318,7 @@ bool HistoryItem::canDeleteForEveryone(TimeId now) const { } } return true; +#endif } bool HistoryItem::suggestReport() const { @@ -2334,9 +2518,76 @@ void HistoryItem::toggleReaction( _history->owner().notifyItemDataChange(this); } +void HistoryItem::setReactions( + const QVector &list, + const QVector &unread) { + Expects(!_reactions); + Expects(unread.empty() || !list.empty()); + + if (changeReactions(list) && changeUnreadReactions(unread)) { + _flags |= MessageFlag::HasUnreadReaction; + } +} + +bool HistoryItem::changeReactions( + const QVector &list) { + if (list.empty()) { + _flags &= ~MessageFlag::CanViewReactions; + return (base::take(_reactions) != nullptr); + } else if (!_reactions) { + _reactions = std::make_unique(this); + } + const auto result = _reactions->change(list); + if (!_reactions->recent().empty()) { + _flags |= MessageFlag::CanViewReactions; + } else { + _flags &= ~MessageFlag::CanViewReactions; + } + return result; +} + +bool HistoryItem::changeUnreadReactions( + const QVector &list) { + return _reactions && _reactions->change(list); +} + +void HistoryItem::updateReactions(const QVector &list) { + const auto hadUnread = hasUnreadReaction(); + const auto changed = changeReactions(list); + if (!changed) { + return; + } + const auto hasUnread = _reactions && _reactions->hasUnread(); + if (!hasUnread && hadUnread) { + markReactionsRead(); + } + history()->owner().notifyItemDataChange(this); +} + +void HistoryItem::updateUnreadReactions( + const QVector &list) { + const auto wasRecentUsers = LookupRecentUnreadReactedUsers(this); + const auto hadUnread = hasUnreadReaction(); + const auto changed = changeUnreadReactions(list); + if (!changed) { + return; + } + const auto hasUnread = _reactions && _reactions->hasUnread(); + if (hasUnread && !hadUnread) { + _flags |= MessageFlag::HasUnreadReaction; + + addToUnreadThings(HistoryUnreadThings::AddType::New); + } else if (!hasUnread && hadUnread) { + markReactionsRead(); + } + CheckReactionNotificationSchedule(this, wasRecentUsers); +} + +#if 0 // mtp void HistoryItem::updateReactionsUnknown() { _reactionsLastRefreshed = 1; } +#endif const std::vector &HistoryItem::reactions() const { static const auto kEmpty = std::vector(); @@ -2383,9 +2634,11 @@ Data::ReactionId HistoryItem::lookupUnreadReaction( return {}; } +#if 0 // mtp crl::time HistoryItem::lastReactionsRefreshTime() const { return _reactionsLastRefreshed; } +#endif bool HistoryItem::hasDirectLink() const { return isRegular() && _history->peer->isChannel(); @@ -2570,7 +2823,9 @@ void HistoryItem::setReplies(HistoryMessageRepliesData &&data) { } const auto &repliers = data.recentRepliers; const auto count = data.repliesCount; +#if 0 // mtp const auto channelId = data.channelId; +#endif const auto readTillId = data.readMaxId ? std::max({ views->commentsInboxReadTillId.bare, @@ -2582,7 +2837,10 @@ void HistoryItem::setReplies(HistoryMessageRepliesData &&data) { const auto countsChanged = (views->replies.count != count) || (views->commentsInboxReadTillId != readTillId) || (views->commentsMaxId != maxId); +#if 0 // mtp const auto megagroupChanged = (views->commentsMegagroupId != channelId); +#endif + const auto megagroupChanged = false; const auto recentChanged = (views->recentRepliers != repliers); if (!countsChanged && !megagroupChanged && !recentChanged) { return; @@ -2592,7 +2850,9 @@ void HistoryItem::setReplies(HistoryMessageRepliesData &&data) { views->recentRepliers = repliers; } const auto wasUnread = areCommentsUnread(); +#if 0 // mtp views->commentsMegagroupId = channelId; +#endif views->commentsInboxReadTillId = readTillId; views->commentsMaxId = maxId; if (wasUnread != areCommentsUnread()) { @@ -2619,7 +2879,10 @@ void HistoryItem::clearReplies() { void HistoryItem::refreshRepliesText( not_null views, bool forceResize) { +#if 0 // mtp if (views->commentsMegagroupId) { +#endif + if (isPost() && views->replies.count >= 0) { views->replies.text = (views->replies.count > 0) ? tr::lng_comments_open_count( tr::now, @@ -2659,7 +2922,10 @@ void HistoryItem::changeRepliesCount(int delta, PeerId replier) { return; } views->replies.count = std::max(views->replies.count + delta, 0); +#if 0 // mtp if (replier && views->commentsMegagroupId) { +#endif + if (replier && isPost() && views->replies.count >= 0) { if (delta < 0) { views->recentRepliers.erase( ranges::remove(views->recentRepliers, replier), @@ -2713,6 +2979,8 @@ bool HistoryItem::canUpdateDate() const { } void HistoryItem::applyTTL(TimeId destroyAt) { + _ttlDestroyAt = destroyAt; +#if 0 // mtp const auto previousDestroyAt = std::exchange(_ttlDestroyAt, destroyAt); if (previousDestroyAt) { _history->owner().unregisterMessageTTL(previousDestroyAt, this); @@ -2729,6 +2997,7 @@ void HistoryItem::applyTTL(TimeId destroyAt) { } else { _history->owner().registerMessageTTL(_ttlDestroyAt, this); } +#endif } void HistoryItem::replaceBuyWithReceiptInMarkup() { @@ -2767,9 +3036,11 @@ int HistoryItem::viewsCount() const { int HistoryItem::repliesCount() const { if (const auto views = Get()) { +#if 0 // mtp if (!checkCommentsLinkedChat(views->commentsMegagroupId)) { return 0; } +#endif return std::max(views->replies.count, 0); } return 0; @@ -2777,8 +3048,11 @@ int HistoryItem::repliesCount() const { bool HistoryItem::repliesAreComments() const { if (const auto views = Get()) { +#if 0 // mtp return (views->commentsMegagroupId != 0) && checkCommentsLinkedChat(views->commentsMegagroupId); +#endif + return isPost() && views->replies.count >= 0; } return false; } @@ -2801,6 +3075,12 @@ bool HistoryItem::hasExtendedMediaPreview() const { return false; } +void HistoryItem::applyTTL(const TLDmessage &data) { + if (const auto period = data.vauto_delete_in().v) { + applyTTL(base::unixtime::now() + period); + } +} + void HistoryItem::sendFailed() { Expects(_flags & MessageFlag::BeingSent); Expects(!(_flags & MessageFlag::SendingFailed)); @@ -2823,9 +3103,14 @@ bool HistoryItem::isService() const { bool HistoryItem::unread(not_null thread) const { // Messages from myself are always read, unless scheduled. +#if 0 // mtp if (_history->peer->isSelf() && !isFromScheduled()) { return false; } +#endif + if (_history->peer->isSelf()) { + return false; + } // All messages in converted chats are always read. if (_history->peer->migrateTo()) { @@ -2969,9 +3254,14 @@ bool HistoryItem::showNotification() const { if (channel && !channel->amIn()) { return false; } +#if 0 // mtp return (out() || _history->peer->isSelf()) ? isFromScheduled() : unread(notificationThread()); +#endif + return !out() + && !_history->peer->isSelf() + && unread(notificationThread()); } void HistoryItem::markClientSideAsRead() { @@ -3059,6 +3349,9 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { } else if (const auto sponsored = Get()) { return sponsored->sender->name; } else if (!_history->peer->isUser()) { + if (_history->peer->isSecretChat()) { + return {}; + } if (const auto from = displayFrom()) { return fromSender(from); } @@ -3171,7 +3464,9 @@ void HistoryItem::createComponents(CreateConfig &&config) { if (const auto broadcast = _history->peer->asBroadcast()) { if (const auto linked = broadcast->linkedChat()) { config.replies.isNull = false; +#if 0 // mtp config.replies.channelId = peerToChannel(linked->id); +#endif } } } @@ -3207,11 +3502,14 @@ void HistoryItem::createComponents(CreateConfig &&config) { bool HistoryItem::checkRepliesPts( const HistoryMessageRepliesData &data) const { +#if 0 // mtp const auto channel = _history->peer->asChannel(); const auto pts = channel ? channel->pts() : _history->session().updates().pts(); return (data.pts >= pts); +#endif + return true; } void HistoryItem::setupForwardedComponent(const CreateConfig &config) { @@ -3387,6 +3685,1263 @@ void HistoryItem::createComponentsHelper( createComponents(std::move(config)); } +void HistoryItem::createServiceFromTdb(const TLmessageContent &content) { + const auto topicId = topicRootId(); + + clearDependencyMessage(); + auto replyToMsgId = MsgId(); + auto replyInPeerId = PeerId(); + const auto reply = Get(); + if (reply) { + replyToMsgId = reply->messageId(); + replyInPeerId = reply->externalPeerId(); + reply->clearData(this); + } + RemoveComponents( + HistoryMessageReply::Bit() + | HistoryServiceData::Bit() + | HistoryServicePinned::Bit() + | HistoryServiceTopicInfo::Bit() + | HistoryServiceGameScore::Bit() + | HistoryServicePayment::Bit() + | HistoryServiceOngoingCall::Bit() + | HistoryServiceChatThemeChange::Bit() + | HistoryServiceTTLChange::Bit()); + + content.match([&](const TLDmessageGameScore &data) { + replyToMsgId = data.vgame_message_id().v; + + AddComponents(HistoryServiceGameScore::Bit()); + Get()->score = data.vscore().v; + }, [&](const TLDmessagePaymentSuccessful &data) { + replyToMsgId = data.vinvoice_message_id().v; + replyInPeerId = peerFromTdbChat(data.vinvoice_chat_id()); + + AddComponents(HistoryServicePayment::Bit()); + const auto amount = data.vtotal_amount().v; + const auto currency = data.vcurrency().v; + const auto payment = Get(); + const auto id = fullId(); + const auto owner = &history()->owner(); + payment->amount = Ui::FillAmountAndCurrency(amount, currency); + payment->invoiceLink = std::make_shared([=]( + ClickContext context) { + using namespace Payments; + const auto my = context.other.value(); + const auto weak = my.sessionWindow; + if (const auto item = owner->message(id)) { + CheckoutProcess::Start( + item, + Mode::Receipt, + crl::guard( + weak, + [=](auto) { weak->window().activate(); })); + } + }); + }, [&](const TLDmessageVideoChatStarted &data) { + AddComponents(HistoryServiceOngoingCall::Bit()); + const auto call = Get(); + call->id = uint32(data.vgroup_call_id().v); + call->link = GroupCallClickHandler(history()->peer, call->id); + }, [&](const TLDmessageVideoChatScheduled &data) { + AddComponents(HistoryServiceOngoingCall::Bit()); + const auto call = Get(); + call->id = uint32(data.vgroup_call_id().v); + call->link = GroupCallClickHandler(history()->peer, call->id); + }, [&](const TLDmessageInviteVideoChatParticipants &data) { + const auto id = uint32(data.vgroup_call_id().v); + const auto peer = history()->peer; + const auto has = PeerHasThisCall(peer, id); + auto hasLink = !has.has_value() + ? PeerHasThisCallValue(peer, id) + : (*has) + ? PeerHasThisCallValue( + peer, + id) | rpl::skip(1) | rpl::type_erased() + : rpl::producer(); + if (hasLink) { + AddComponents(HistoryServiceOngoingCall::Bit()); + const auto call = Get(); + call->id = id; + call->lifetime.destroy(); + + const auto users = data.vuser_ids().v; + std::move(hasLink) | rpl::start_with_next([=](bool has) { + updateServiceText(prepareInvitedToCallText( + ParseInvitedToCallUsers(this, users), + has ? id : 0)); + if (!has) { + RemoveComponents(HistoryServiceOngoingCall::Bit()); + } + }, call->lifetime); + } + }, [&](const TLDmessagePinMessage &data) { + replyToMsgId = data.vmessage_id().v; + + AddComponents(HistoryServicePinned::Bit()); + }, [&](const TLDmessageForumTopicCreated &data) { + AddComponents(HistoryServiceTopicInfo::Bit()); + const auto info = Get(); + info->topicPost = true; + info->title = data.vname().v; + info->iconId = data.vicon().data().vcustom_emoji_id().v; + }, [&](const TLDmessageForumTopicEdited &data) { + AddComponents(HistoryServiceTopicInfo::Bit()); + const auto info = Get(); + info->topicPost = true; + if (const auto name = data.vname().v; !name.isEmpty()) { + info->title = name; + info->renamed = true; + } + if (data.vedit_icon_custom_emoji_id().v) { + info->iconId = data.vicon_custom_emoji_id().v; + info->reiconed = true; + } + }, [&](const TLDmessageForumTopicIsClosedToggled &data) { + AddComponents(HistoryServiceTopicInfo::Bit()); + const auto info = Get(); + info->topicPost = true; + info->closed = data.vis_closed().v; + info->reopened = !info->closed; + }, [&](const TLDmessageForumTopicIsHiddenToggled &data) { + AddComponents(HistoryServiceTopicInfo::Bit()); + const auto info = Get(); + info->topicPost = true; + info->hidden = data.vis_hidden().v; + info->unhidden = !info->hidden; + }, [](const auto &) {}); + + if (const auto dependent = replyToMsgId ? GetServiceDependentData() : nullptr) { + dependent->peerId = (replyInPeerId != history()->peer->id) + ? replyInPeerId + : 0; + dependent->msgId = replyToMsgId; + if (dependent->topicPost + && topicId != Data::ForumTopic::kGeneralId) { + dependent->topId = topicId; + } + if (!updateServiceDependent()) { + RequestDependentMessageItem( + this, + (dependent->peerId + ? dependent->peerId + : _history->peer->id), + dependent->msgId); + } + } + + setServiceMessageByContent(content); +} + +void HistoryItem::setServiceMessageByContent( + const TLmessageContent &content) { + applyContent(content); + + const auto prepareServiceTextForSharedPeer = [&](PeerId peerId) { + const auto peer = history()->owner().peer(peerId); + auto result = PreparedServiceText{}; + result.text = tr::lng_action_shared_chat_with_bot( + tr::now, + lt_chat, + Ui::Text::Link(peer->name(), 1), + lt_bot, + Ui::Text::Link(history()->peer->name(), 2), + Ui::Text::WithEntities); + result.links.push_back(peer->createOpenLink()); + result.links.push_back(history()->peer->createOpenLink()); + return result; + }; + + auto prepared = PreparedServiceText(); + content.match([&](const TLDmessageAnimation &data) { + Expects(data.vis_secret().v); + + if (out()) { + prepared.text = { tr::lng_ttl_video_sent(tr::now) }; + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_ttl_video_received( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } + }, [&](const TLDmessagePhoto &data) { + Expects(data.vis_secret().v); + + if (out()) { + prepared.text = { tr::lng_ttl_photo_sent(tr::now) }; + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_ttl_photo_received( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } + }, [&](const TLDmessageVideo &data) { + Expects(data.vis_secret().v); + + if (out()) { + prepared.text = { tr::lng_ttl_video_sent(tr::now) }; + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_ttl_video_received( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } + }, [&](const TLDmessageVideoNote &data) { + Expects(data.vis_secret().v); + + if (out()) { + prepared.text = { tr::lng_ttl_video_sent(tr::now) }; + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_ttl_video_received( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } + }, [&](const TLDmessageExpiredPhoto &data) { + prepared.text = { tr::lng_ttl_photo_expired(tr::now) }; + }, [&](const TLDmessageExpiredVideo &data) { + prepared.text = { tr::lng_ttl_video_expired(tr::now) }; + }, [&](const TLDmessageVideoChatScheduled &data) { + prepared = prepareCallScheduledText(data.vstart_date().v); + }, [&](const TLDmessageVideoChatStarted &data) { + if (history()->peer->isBroadcast()) { + prepared.text = { + tr::lng_action_group_call_started_channel(tr::now) + }; + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_group_call_started_group( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } + }, [&](const TLDmessageVideoChatEnded &data) { + const auto seconds = data.vduration().v; + const auto days = seconds / 86400; + const auto hours = seconds / 3600; + const auto minutes = seconds / 60; + auto text = (days > 1) + ? tr::lng_days(tr::now, lt_count, days) + : (hours > 1) + ? tr::lng_hours(tr::now, lt_count, hours) + : (minutes > 1) + ? tr::lng_minutes(tr::now, lt_count, minutes) + : tr::lng_seconds(tr::now, lt_count, seconds); + if (history()->peer->isBroadcast()) { + prepared.text = tr::lng_action_group_call_finished( + tr::now, + lt_duration, + { .text = text }, + Ui::Text::WithEntities); + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_group_call_finished_group( + tr::now, + lt_from, + fromLinkText(), + lt_duration, + { .text = text }, + Ui::Text::WithEntities); + } + }, [&](const TLDmessageInviteVideoChatParticipants &data) { + const auto callId = uint32(data.vgroup_call_id().v); + const auto owner = &history()->owner(); + const auto peer = history()->peer; + for (const auto &id : data.vuser_ids().v) { + const auto user = owner->user(id.v); + if (callId) { + owner->registerInvitedToCallUser(callId, peer, user); + } + }; + const auto linkCallId = PeerHasThisCall(peer, callId).value_or(false) + ? callId + : 0; + prepared = prepareInvitedToCallText( + ParseInvitedToCallUsers(this, data.vuser_ids().v), + linkCallId); + }, [&](const TLDmessageBasicGroupChatCreate &data) { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_created_chat( + tr::now, + lt_from, + fromLinkText(), + lt_title, + { .text = data.vtitle().v }, + Ui::Text::WithEntities); + }, [&](const TLDmessageSupergroupChatCreate &data) { + if (isPost()) { + prepared.text = { tr::lng_action_created_channel(tr::now) }; + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_created_chat( + tr::now, + lt_from, + fromLinkText(), + lt_title, + { .text = data.vtitle().v }, + Ui::Text::WithEntities); + } + }, [&](const TLDmessageChatChangeTitle &data) { + if (isPost()) { + prepared.text = tr::lng_action_changed_title_channel( + tr::now, + lt_title, + { .text = data.vtitle().v }, + Ui::Text::WithEntities); + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_changed_title( + tr::now, + lt_from, + fromLinkText(), + lt_title, + { .text = data.vtitle().v }, + Ui::Text::WithEntities); + } + }, [&](const TLDmessageChatChangePhoto &data) { + if (isPost()) { + prepared.text = { tr::lng_action_changed_photo_channel(tr::now) }; + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_changed_photo( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } + }, [&](const TLDmessageChatDeletePhoto &data) { + if (isPost()) { + prepared.text = { tr::lng_action_removed_photo_channel(tr::now) }; + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_removed_photo( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } + }, [&](const TLDmessageChatAddMembers &data) { + const auto &users = data.vmember_user_ids().v; + if (users.size() == 1) { + const auto u = history()->owner().user(users[0].v); + if (u == _from) { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_user_joined( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } else { + prepared.links.push_back(fromLink()); + prepared.links.push_back(u->createOpenLink()); + prepared.text = tr::lng_action_add_user( + tr::now, + lt_from, + fromLinkText(), + lt_user, + Ui::Text::Link(u->name(), 2), + Ui::Text::WithEntities); + } + } else if (users.isEmpty()) { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_add_user( + tr::now, + lt_from, + fromLinkText(), + lt_user, + { .text = "somebody" }, + Ui::Text::WithEntities); + } else { + prepared.links.push_back(fromLink()); + for (auto i = 0, l = int(users.size()); i != l; ++i) { + auto user = history()->owner().user(users[i].v); + prepared.links.push_back(user->createOpenLink()); + + auto linkText = Ui::Text::Link(user->name(), 2 + i); + if (i == 0) { + prepared.text = linkText; + } else if (i + 1 == l) { + prepared.text = tr::lng_action_add_users_and_last( + tr::now, + lt_accumulated, + prepared.text, + lt_user, + linkText, + Ui::Text::WithEntities); + } else { + prepared.text = tr::lng_action_add_users_and_one( + tr::now, + lt_accumulated, + prepared.text, + lt_user, + linkText, + Ui::Text::WithEntities); + } + } + prepared.text = tr::lng_action_add_users_many( + tr::now, + lt_from, + fromLinkText(), + lt_users, + prepared.text, + Ui::Text::WithEntities); + } + }, [&](const TLDmessageChatJoinByLink &data) { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_user_joined_by_link( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + }, [&](const TLDmessageChatDeleteMember &data) { + if (peerFromUser(data.vuser_id()) == _from->id) { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_user_left( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } else { + auto user = history()->owner().user(data.vuser_id()); + prepared.links.push_back(fromLink()); + prepared.links.push_back(user->createOpenLink()); + prepared.text = tr::lng_action_kick_user( + tr::now, + lt_from, + fromLinkText(), + lt_user, + Ui::Text::Link(user->name(), 2), + Ui::Text::WithEntities); + } + }, [&](const TLDmessageChatUpgradeTo &data) { + }, [&](const TLDmessageChatUpgradeFrom &data) { + }, [&](const TLDmessagePinMessage &data) { + prepared = preparePinnedText(); + }, [&](const TLDmessageScreenshotTaken &data) { + if (out()) { + prepared.text = { tr::lng_action_you_took_screenshot(tr::now) }; + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_took_screenshot( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } + }, [&](const TLDmessageChatSetBackground &data) { + const auto isSelf = (_from->id == _from->session().userPeerId()); + const auto peer = isSelf ? history()->peer : _from; + const auto user = peer->asUser(); + const auto name = (user && !user->firstName.isEmpty()) + ? user->firstName + : peer->name(); + if (data.vold_background_message_id().v) { + if (!isSelf) { + prepared.links.push_back(peer->createOpenLink()); + } + prepared.text = isSelf + ? tr::lng_action_set_same_wallpaper_me( + tr::now, + Ui::Text::WithEntities) + : tr::lng_action_set_same_wallpaper( + tr::now, + lt_user, + Ui::Text::Link(name, 1), // Link 1. + Ui::Text::WithEntities); + } else { + prepared.links.push_back(peer->createOpenLink()); + prepared.text = isSelf + ? tr::lng_action_set_wallpaper_me( + tr::now, + Ui::Text::WithEntities) + : tr::lng_action_set_wallpaper( + tr::now, + lt_user, + Ui::Text::Link(name, 1), // Link 1. + Ui::Text::WithEntities); + } + }, [&](const TLDmessageChatSetTheme &data) { + const auto text = data.vtheme_name().v; + if (!text.isEmpty()) { + if (_from->isSelf()) { + prepared.text = tr::lng_action_you_theme_changed( + tr::now, + lt_emoji, + { .text = text }, + Ui::Text::WithEntities); + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_theme_changed( + tr::now, + lt_from, + fromLinkText(), + lt_emoji, + { .text = text }, + Ui::Text::WithEntities); + } + } else { + if (_from->isSelf()) { + prepared.text = { tr::lng_action_you_theme_disabled(tr::now) }; + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_theme_disabled( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } + } + }, [&](const TLDmessageChatSetMessageAutoDeleteTime &data) { + const auto period = data.vmessage_auto_delete_time().v; + const auto duration = (period == 5) + ? u"5 seconds"_q + : Ui::FormatTTL(period); + if (isPost()) { + if (!period) { + prepared.text = { tr::lng_action_ttl_removed_channel(tr::now) }; + } else { + prepared.text = tr::lng_action_ttl_changed_channel( + tr::now, + lt_duration, + { .text = duration }, + Ui::Text::WithEntities); + } + } else if (_from->isSelf()) { + if (!period) { + prepared.text = { tr::lng_action_ttl_removed_you(tr::now) }; + } else { + prepared.text = tr::lng_action_ttl_changed_you( + tr::now, + lt_duration, + { .text = duration }, + Ui::Text::WithEntities); + } + } else { + prepared.links.push_back(fromLink()); + if (!period) { + prepared.text = tr::lng_action_ttl_removed( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + } else { + prepared.text = tr::lng_action_ttl_changed( + tr::now, + lt_from, + fromLinkText(), + lt_duration, + { .text = duration }, + Ui::Text::WithEntities); + } + } + }, [&](const TLDmessageCustomServiceAction &data) { + prepared.text = { data.vtext().v }; + }, [&](const TLDmessageGameScore &data) { + prepared = prepareGameScoreText(); + }, [&](const TLDmessagePaymentSuccessful &data) { + prepared = preparePaymentSentText(); + }, [&](const TLDmessageContactRegistered &data) { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_user_registered( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + }, [&](const TLDmessageWebAppDataSent &data) { + prepared.text = tr::lng_action_webview_data_done( + tr::now, + lt_text, + { .text = data.vbutton_text().v }, + Ui::Text::WithEntities); + }, [&](const TLDmessagePassportDataSent &data) { + auto documents = QStringList(); + for (const auto &type : data.vtypes().v) { + documents.push_back([&] { + switch (type.type()) { + case id_passportElementTypePersonalDetails: + return tr::lng_action_secure_personal_details(tr::now); + case id_passportElementTypePassport: + case id_passportElementTypeDriverLicense: + case id_passportElementTypeIdentityCard: + case id_passportElementTypeInternalPassport: + return tr::lng_action_secure_proof_of_identity(tr::now); + case id_passportElementTypeAddress: + return tr::lng_action_secure_address(tr::now); + case id_passportElementTypeUtilityBill: + case id_passportElementTypeBankStatement: + case id_passportElementTypeRentalAgreement: + case id_passportElementTypePassportRegistration: + case id_passportElementTypeTemporaryRegistration: + return tr::lng_action_secure_proof_of_address(tr::now); + case id_passportElementTypePhoneNumber: + return tr::lng_action_secure_phone(tr::now); + case id_passportElementTypeEmailAddress: + return tr::lng_action_secure_email(tr::now); + } + Unexpected("Type in passportElementType."); + }()); + }; + prepared.links.push_back(history()->peer->createOpenLink()); + prepared.text = tr::lng_action_secure_values_sent( + tr::now, + lt_user, + Ui::Text::Link(history()->peer->name(), 1), + lt_documents, + { .text = documents.join(", ") }, + Ui::Text::WithEntities); + }, [&](const TLDmessageProximityAlertTriggered &data) { + const auto fromId = peerFromSender(data.vtraveler_id()); + const auto fromPeer = history()->owner().peer(fromId); + const auto toId = peerFromSender(data.vwatcher_id()); + const auto toPeer = history()->owner().peer(toId); + const auto selfId = _from->session().userPeerId(); + const auto distanceMeters = data.vdistance().v; + const auto distance = [&] { + if (distanceMeters >= 1000) { + const auto km = (10 * (distanceMeters / 10)) / 1000.; + return tr::lng_action_proximity_distance_km( + tr::now, + lt_count, + km, + Ui::Text::WithEntities); + } else { + return tr::lng_action_proximity_distance_m( + tr::now, + lt_count, + distanceMeters, + Ui::Text::WithEntities); + } + }(); + prepared.text = [&] { + if (fromId == selfId) { + prepared.links.push_back(toPeer->createOpenLink()); + return tr::lng_action_you_proximity_reached( + tr::now, + lt_distance, + distance, + lt_user, + Ui::Text::Link(toPeer->name(), 1), + Ui::Text::WithEntities); + } else if (toId == selfId) { + prepared.links.push_back(fromPeer->createOpenLink()); + return tr::lng_action_proximity_reached_you( + tr::now, + lt_from, + Ui::Text::Link(fromPeer->name(), 1), + lt_distance, + distance, + Ui::Text::WithEntities); + } else { + prepared.links.push_back(fromPeer->createOpenLink()); + prepared.links.push_back(toPeer->createOpenLink()); + return tr::lng_action_proximity_reached( + tr::now, + lt_from, + Ui::Text::Link(fromPeer->name(), 1), + lt_distance, + distance, + lt_user, + Ui::Text::Link(toPeer->name(), 2), + Ui::Text::WithEntities); + } + }(); + }, [&](const TLDmessageChatJoinByRequest &data) { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_user_joined_by_request( + tr::now, + lt_from, + fromLinkText(), + Ui::Text::WithEntities); + }, [&](const TLDmessageGiftedPremium &data) { + const auto isSelf = (_from->id == _from->session().userPeerId()); + const auto peer = isSelf ? history()->peer : _from; + const auto amount = data.vamount().v; + const auto currency = data.vcurrency().v; + prepared.links.push_back(peer->createOpenLink()); + prepared.text = (isSelf + ? tr::lng_action_gift_received_me + : tr::lng_action_gift_received)( + tr::now, + lt_user, + Ui::Text::Link(peer->name(), 1), // Link 1. + lt_cost, + { Ui::FillAmountAndCurrency(amount, currency) }, + Ui::Text::WithEntities); + }, [&](const TLDmessagePremiumGiftCode &action) { + prepared.text = { + (action.vis_unclaimed().v + ? tr::lng_prize_unclaimed_about + : action.vis_from_giveaway().v + ? tr::lng_prize_about + : tr::lng_prize_gift_about)( + tr::now, + lt_channel, + _from->owner().peer( + peerFromSender(action.vcreator_id()))->name()), + }; + }, [&](const TLDmessagePremiumGiveawayCreated &data) { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_giveaway_started( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + }, [&](const TLDmessageForumTopicCreated &data) { + const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q + .arg(peerToChannel(history()->peer->id).bare) + .arg(id.bare); + prepared.text = tr::lng_action_topic_created( + tr::now, + lt_topic, + Ui::Text::Link( + Data::ForumTopicIconWithTitle( + id, + data.vicon().data().vcustom_emoji_id().v, + data.vname().v), + topicUrl), + Ui::Text::WithEntities); + }, [&](const TLDmessageForumTopicEdited &data) { + if (data.vname().v.isEmpty()) { + if (data.vedit_icon_custom_emoji_id().v) { + if (const auto iconId = data.vicon_custom_emoji_id().v) { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_topic_icon_changed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_link, + { tr::lng_action_topic_placeholder(tr::now) }, + lt_emoji, + Data::SingleCustomEmoji(iconId), + Ui::Text::WithEntities); + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_topic_icon_removed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_link, + { tr::lng_action_topic_placeholder(tr::now) }, + Ui::Text::WithEntities); + } + } + } else { + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_topic_renamed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_link, + { tr::lng_action_topic_placeholder(tr::now) }, + lt_title, + Data::ForumTopicIconWithTitle( + topicRootId(), + data.vicon_custom_emoji_id().v, + data.vname().v), + Ui::Text::WithEntities); + } + }, [&](const TLDmessageForumTopicIsClosedToggled &data) { + prepared.text = { data.vis_closed().v + ? tr::lng_action_topic_closed_inside(tr::now) + : tr::lng_action_topic_reopened_inside(tr::now) }; + }, [&](const TLDmessageForumTopicIsHiddenToggled &data) { + prepared.text = { data.vis_hidden().v + ? tr::lng_action_topic_hidden_inside(tr::now) + : tr::lng_action_topic_unhidden_inside(tr::now) }; + }, [&](const TLDmessageSuggestProfilePhoto &data) { + const auto isSelf = (_from->id == _from->session().userPeerId()); + const auto isVideo = data.vphoto().data().vanimation().has_value(); + const auto peer = isSelf ? history()->peer : _from; + const auto user = peer->asUser(); + const auto name = (user && !user->firstName.isEmpty()) + ? user->firstName + : peer->name(); + prepared.links.push_back(peer->createOpenLink()); + prepared.text = (isSelf + ? (isVideo + ? tr::lng_action_suggested_video_me + : tr::lng_action_suggested_photo_me) + : (isVideo + ? tr::lng_action_suggested_video + : tr::lng_action_suggested_photo))( + tr::now, + lt_user, + Ui::Text::Link(name, 1), // Link 1. + Ui::Text::WithEntities); + }, [&](const TLDmessageBotWriteAccessAllowed &data) { + data.vreason().match([&]( + const TLDbotWriteAccessAllowReasonAcceptedRequest &) { + prepared.text = { + tr::lng_action_webapp_bot_allowed(tr::now) + }; + }, [&](const TLDbotWriteAccessAllowReasonAddedToAttachmentMenu &) { + prepared.text = { + tr::lng_action_attach_menu_bot_allowed(tr::now), + }; + }, [&](const TLDbotWriteAccessAllowReasonConnectedWebsite &data) { + const auto domain = data.vdomain_name().v; + prepared.text = tr::lng_action_bot_allowed_from_domain( + tr::now, + lt_domain, + Ui::Text::Link(domain, qstr("http://") + domain), + Ui::Text::WithEntities); + }, [&](const TLDbotWriteAccessAllowReasonLaunchedWebApp &data) { + const auto &app = data.vweb_app().data(); + const auto bot = history()->peer->asUser(); + const auto appName = app.vshort_name().v; + const auto url = (bot && !appName.isEmpty()) + ? history()->session().createInternalLinkFull( + bot->username() + '/' + appName) + : QString(); + prepared.text = tr::lng_action_bot_allowed_from_app( + tr::now, + lt_app, + (url.isEmpty() + ? TextWithEntities{ u"App"_q } + : Ui::Text::Link(app.vtitle().v, url)), + Ui::Text::WithEntities); + }); + }, [&](const TLDmessageUserShared &data) { + prepared = prepareServiceTextForSharedPeer( + peerFromUser(data.vuser_id())); + }, [&](const TLDmessageChatShared &data) { + prepared = prepareServiceTextForSharedPeer( + peerFromTdbChat(data.vchat_id())); + }, [&](const TLDmessageStory &data) { + prepared = prepareStoryMentionText(); + }, [](const auto &) { + Unexpected("Type in TLDmessage.content."); + }); + setServiceText(std::move(prepared)); +} + +void HistoryItem::applyContent(const TLmessageContent &content) { + content.match([&](const TLDmessageChatAddMembers &data) { + if (const auto channel = _history->peer->asMegagroup()) { + const auto selfUserId = _history->session().userId(); + for (const auto &item : data.vmember_user_ids().v) { + if (peerFromUser(item) == selfUserId) { + channel->mgInfo->joinedMessageFound = true; + break; + } + } + } + }, [&](const TLDmessageChatJoinByLink &data) { + if (_from->isSelf()) { + if (const auto channel = _history->peer->asMegagroup()) { + channel->mgInfo->joinedMessageFound = true; + } + } + }, [&](const TLDmessageChatChangePhoto &data) { + _media = std::make_unique( + this, + _history->peer, + _history->owner().processPhoto(data.vphoto())); + }, [&](const TLDmessageGiftedPremium &data) { + _media = std::make_unique( + this, + _from, + data.vmonth_count().v, + (data.vsticker() + ? _history->owner().processDocument(*data.vsticker()).get() + : nullptr)); + }, [&](const TLDmessagePremiumGiftCode &data) { + const auto boostedId = peerFromSender(data.vcreator_id()); + _media = std::make_unique( + this, + _from, + Data::GiftCode{ + .slug = data.vcode().v, + .channel = (peerIsChannel(boostedId) + ? _history->owner().channel( + peerToChannel(boostedId)).get() + : nullptr), + .months = data.vmonth_count().v, + .viaGiveaway = data.vis_from_giveaway().v, + .unclaimed = data.vis_unclaimed().v, + }, + (data.vsticker() + ? _history->owner().processDocument(*data.vsticker()).get() + : nullptr)); + }, [&](const TLDmessageBasicGroupChatCreate &) { + _flags |= MessageFlag::IsGroupEssential; + }, [&](const TLDmessageSupergroupChatCreate &) { + _flags |= MessageFlag::IsGroupEssential; + }, [&](const TLDmessageChatUpgradeTo &) { + _flags |= MessageFlag::IsGroupEssential; + }, [&](const TLDmessageChatUpgradeFrom &) { + _flags |= MessageFlag::IsGroupEssential; + }, [&](const TLDmessageContactRegistered &) { + _flags |= MessageFlag::IsContactSignUp; + }, [&](const TLDmessageSuggestProfilePhoto &data) { + _flags |= MessageFlag::IsUserpicSuggestion; + _media = std::make_unique( + this, + _history->peer, + _history->owner().processPhoto(data.vphoto())); + }, [&](const TLDmessageChatSetBackground &data) { + const auto session = &history()->session(); + const auto &attached = data.vbackground(); + if (const auto paper = Data::WallPaper::Create(session, attached)) { + _media = std::make_unique(this, *paper); + } + }, [&](const TLDmessageStory &data) { + setMedia(content); + }, [](const auto &) { + }); +} + +void HistoryItem::setMedia(const TLmessageContent &content) { + if (!_media || !_media->updateContent(content)) { + _media = CreateMedia(this, content); + } + checkBuyButton(); +} + +void HistoryItem::setMediaExplicit(std::unique_ptr media) { + _media = std::move(media); + checkBuyButton(); +} + +void HistoryItem::setContent(const TLmessageContent &content) { + _flags &= ~(MessageFlag::IsGroupEssential + | MessageFlag::IsContactSignUp + | MessageFlag::InvertMedia); + + const auto setFormattedText = [&](const TLformattedText &text) { + setText(Api::FormattedTextFromTdb(text)); + }; + content.match([&](const auto &data) { + using T = decltype(data); + if constexpr (TLDmessageAnimation::Is() + || TLDmessagePhoto::Is() + || TLDmessageVideo::Is() + || TLDmessageVideoNote::Is()) { + if (data.vis_secret().v) { + createServiceFromTdb(content); + return; + } + } + if constexpr (TLDmessageText::Is()) { + setMedia(content); + setFormattedText(data.vtext()); + if (const auto options = data.vlink_preview_options()) { + if (options->data().vshow_above_text().v) { + _flags |= MessageFlag::InvertMedia; + } + } + } else if constexpr (TLDmessageAnimatedEmoji::Is()) { + setMedia(content); + setAnimatedEmojiText(data); + } else if constexpr (TLDmessageGame::Is()) { + setMedia(content); + setFormattedText(data.vgame().data().vtext()); + } else if constexpr (TLDmessageAnimation::Is() + || TLDmessageAudio::Is() + || TLDmessageDocument::Is() + || TLDmessagePhoto::Is() + || TLDmessageVideo::Is() + || TLDmessageVoiceNote::Is()) { + setMedia(content); + setFormattedText(data.vcaption()); + } else if constexpr (TLDmessageSticker::Is() + || TLDmessageVideoNote::Is() + || TLDmessageLocation::Is() + || TLDmessageVenue::Is() + || TLDmessageContact::Is() + || TLDmessageDice::Is() + || TLDmessagePoll::Is() + || TLDmessageInvoice::Is() + || TLDmessageCall::Is() + || TLDmessagePremiumGiveaway::Is()) { + setMedia(content); + } else if constexpr (TLDmessageExpiredPhoto::Is() + || TLDmessageExpiredVideo::Is() + || TLDmessageVideoChatScheduled::Is() + || TLDmessageVideoChatStarted::Is() + || TLDmessageVideoChatEnded::Is() + || TLDmessageInviteVideoChatParticipants::Is() + || TLDmessageBasicGroupChatCreate::Is() + || TLDmessageSupergroupChatCreate::Is() + || TLDmessageChatChangeTitle::Is() + || TLDmessageChatChangePhoto::Is() + || TLDmessageChatDeletePhoto::Is() + || TLDmessageChatAddMembers::Is() + || TLDmessageChatJoinByLink::Is() + || TLDmessageChatDeleteMember::Is() + || TLDmessageChatUpgradeTo::Is() + || TLDmessageChatUpgradeFrom::Is() + || TLDmessagePinMessage::Is() + || TLDmessageScreenshotTaken::Is() + || TLDmessageChatSetBackground::Is() + || TLDmessageChatSetTheme::Is() + || TLDmessageChatSetMessageAutoDeleteTime::Is() + || TLDmessageCustomServiceAction::Is() + || TLDmessageGameScore::Is() + || TLDmessagePaymentSuccessful::Is() + || TLDmessageContactRegistered::Is() + || TLDmessageWebAppDataSent::Is() + || TLDmessagePassportDataSent::Is() + || TLDmessageProximityAlertTriggered::Is() + || TLDmessageChatJoinByRequest::Is() + || TLDmessageGiftedPremium::Is() + || TLDmessagePremiumGiveawayCreated::Is() + || TLDmessagePremiumGiftCode::Is() + || TLDmessageForumTopicCreated::Is() + || TLDmessageForumTopicEdited::Is() + || TLDmessageForumTopicIsClosedToggled::Is() + || TLDmessageForumTopicIsHiddenToggled::Is() + || TLDmessageSuggestProfilePhoto::Is() + || TLDmessageBotWriteAccessAllowed::Is() + || TLDmessageUserShared::Is() + || TLDmessageChatShared::Is()) { + createServiceFromTdb(content); + } else if constexpr (TLDmessageStory::Is()) { + if (data.vvia_mention().v) { + createServiceFromTdb(content); + } else { + setMedia(content); + //setFormattedText(data.vshare_comment()); // tdlib todo + } + } else if constexpr (TLDmessageUnsupported::Is()) { + setText(UnsupportedMessageText()); + } else { + not_implemented(T()); + } + }); +} + +void HistoryItem::setAnimatedEmojiText(const TLDmessageAnimatedEmoji &data) { + auto text = TextWithEntities{ data.vemoji().v }; + if (const auto sticker = data.vanimated_emoji().data().vsticker()) { + sticker->data().vfull_type().match([&]( + const TLDstickerFullTypeCustomEmoji &data) { + text.entities.push_back({ + EntityType::CustomEmoji, + 0, + int(text.text.size()), + QString::number( + uint64(data.vcustom_emoji_id().v)), + }); + }, [](const auto &) {}); + } + setText(std::move(text)); +} + +std::unique_ptr HistoryItem::CreateMedia( + not_null item, + const TLmessageContent &content) { + using Result = std::unique_ptr; + auto &owner = item->history()->owner(); + + const auto skipPremiumEffectDefault = false; + const auto hasSpoilerDefault = false; + return content.match([&](const TLDmessageText &data) -> Result { + auto flags = MediaWebPageFlags(); + const auto options = data.vweb_page() + ? data.vlink_preview_options() + : nullptr; + if (options) { + using Flag = MediaWebPageFlag; + const auto &data = options->data(); + Assert(!data.vis_disabled().v); + flags |= Flag() + | (data.vforce_large_media().v + ? Flag::ForceLargeMedia + : Flag()) + | (data.vforce_small_media().v + ? Flag::ForceSmallMedia + : Flag()); + } + return data.vweb_page() + ? std::make_unique( + item, + owner.processWebpage(*data.vweb_page()), + flags) + : nullptr; + }, [&](const TLDmessageAnimation &data) -> Result { + return std::make_unique( + item, + owner.processDocument(data.vanimation()), + skipPremiumEffectDefault, + data.vhas_spoiler().v); + }, [&](const TLDmessageAudio &data) -> Result { + return std::make_unique( + item, + owner.processDocument(data.vaudio()), + skipPremiumEffectDefault, + hasSpoilerDefault); + }, [&](const TLDmessageDocument &data) -> Result { + return std::make_unique( + item, + owner.processDocument(data.vdocument()), + skipPremiumEffectDefault, + hasSpoilerDefault); + }, [&](const TLDmessagePhoto &data) -> Result { + return std::make_unique( + item, + owner.processPhoto(data.vphoto()), + data.vhas_spoiler().v); + }, [&](const TLDmessageSticker &data) -> Result { + return std::make_unique( + item, + owner.processDocument(data.vsticker()), + !data.vis_premium().v, + hasSpoilerDefault); + }, [&](const TLDmessageVideo &data) -> Result { + return std::make_unique( + item, + owner.processDocument(data.vvideo()), + skipPremiumEffectDefault, + data.vhas_spoiler().v); + }, [&](const TLDmessageVideoNote &data) -> Result { + ApplyTranscribe( + item, + data.vvideo_note().data().vspeech_recognition_result(), + true); + return std::make_unique( + item, + owner.processDocument(data.vvideo_note()), + skipPremiumEffectDefault, + hasSpoilerDefault); + }, [&](const TLDmessageVoiceNote &data) -> Result { + ApplyTranscribe( + item, + data.vvoice_note().data().vspeech_recognition_result(), + false); + return std::make_unique( + item, + owner.processDocument(data.vvoice_note()), + skipPremiumEffectDefault, + hasSpoilerDefault); + }, [&](const TLDmessageLocation &data) -> Result { + return std::make_unique( + item, + Data::LocationPoint(data.vlocation())); + }, [&](const TLDmessageVenue &data) -> Result { + const auto &fields = data.vvenue().data(); + return std::make_unique( + item, + Data::LocationPoint(fields.vlocation()), + fields.vtitle().v, + fields.vaddress().v); + }, [&](const TLDmessageContact &data) -> Result { + const auto &fields = data.vcontact().data(); + return std::make_unique( + item, + UserId(fields.vuser_id()), + fields.vfirst_name().v, + fields.vlast_name().v, + fields.vphone_number().v); + }, [&](const TLDmessageDice &data) -> Result { + return std::make_unique(item, data); + }, [&](const TLDmessageGame &data) -> Result { + return std::make_unique( + item, + owner.processGame(data.vgame())); + }, [&](const TLDmessagePoll &data) -> Result { + return std::make_unique( + item, + owner.processPoll(data.vpoll())); + }, [&](const TLDmessageInvoice &data) -> Result { + return std::make_unique( + item, + Data::ComputeInvoiceData(item, data)); + }, [&](const TLDmessageCall &data) -> Result { + return std::make_unique( + item, + Data::ComputeCallData(data)); + }, [&](const TLDmessageAnimatedEmoji &data) -> Result { + const auto &fields = data.vanimated_emoji().data(); + item->AddComponents(HistoryMessageAnimatedEmoji::Bit()); + const auto animatedEmoji = item->Get(); + animatedEmoji->size = fields.vsticker_width().v; + animatedEmoji->replacements + = Stickers::ReplacementsByFitzpatrickType( + fields.vfitzpatrick_type().v); + if (const auto sticker = fields.vsticker()) { + animatedEmoji->document = owner.processDocument(*sticker); + } + return nullptr; + }, [&](const TLDmessageStory &data) -> Result { + return std::make_unique(item, FullStoryId{ + peerFromTdbChat(data.vstory_sender_chat_id()), + data.vstory_id().v, + }, data.vvia_mention().v); + }, [&](const TLDmessagePremiumGiveaway &data) -> Result { + return std::make_unique( + item, + Data::ComputeGiveawayData(item, data), + (data.vsticker() + ? owner.processDocument(*data.vsticker()).get() + : nullptr)); + }, [](const auto &) -> Result { + return nullptr; + }); +} + +void HistoryItem::updateContent(const TLmessageContent &content) { + int keyboardTop = -1; + //if (!pendingResize()) {// #TODO edit bot message + // if (auto keyboard = inlineReplyKeyboard()) { + // int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); + // keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom(); + // } + //} + + setContent(content); + + if (const auto views = Get()) { + refreshRepliesText(views); + } + + finishEdition(keyboardTop); +} + +void HistoryItem::updateInteractionInfo( + const TLmessageInteractionInfo *info) { + const auto fields = info ? &info->data() : nullptr; + if (fields) { + const auto viewsCount = fields->vview_count().v; + if (changeViewsCount(viewsCount > 0 ? viewsCount : -1)) { + history()->owner().notifyItemDataChange(this); + } + setForwardsCount(fields->vforward_count().v); + if (const auto &info = fields->vreply_info()) { + setReplies(HistoryMessageRepliesData(info)); + } else { + clearReplies(); + } + updateReactions(fields->vreactions().v); + } else { + setForwardsCount(0); + clearReplies(); + } +} + +void HistoryItem::updateEditedInfo( + TimeId editDate, + HistoryMessageMarkupData &&markup) { + setReplyMarkup(std::move(markup)); + + if (!Has()) { + AddComponents(HistoryMessageEdited::Bit()); + } + auto edited = Get(); + edited->date = editDate; + finishEdition(-1); +} + +#if 0 // mtp void HistoryItem::setReactions(const MTPMessageReactions *reactions) { Expects(!_reactions); @@ -4704,17 +6259,20 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { }, [](const auto &) { }); } +#endif void HistoryItem::setSelfDestruct( HistoryServiceSelfDestruct::Type type, MTPint mtpTTLvalue) { UpdateComponents(HistoryServiceSelfDestruct::Bit()); const auto selfdestruct = Get(); +#if 0 // mtp if (mtpTTLvalue.v == TimeId(0x7FFFFFFF)) { selfdestruct->timeToLive = TimeToLiveSingleView(); } else { selfdestruct->timeToLive = mtpTTLvalue.v * crl::time(1000); } +#endif selfdestruct->type = type; } @@ -5101,6 +6659,7 @@ ClickHandlerPtr HistoryItem::fromLink() const { return _from->createOpenLink(); } +#if 0 // mtp crl::time HistoryItem::getSelfDestructIn(crl::time now) { if (const auto selfdestruct = Get()) { const auto at = std::get_if(&selfdestruct->destructAt); @@ -5124,6 +6683,7 @@ crl::time HistoryItem::getSelfDestructIn(crl::time now) { } return 0; } +#endif void HistoryItem::cacheOnlyEmojiAndSpaces(bool only) { _flags |= MessageFlag::OnlyEmojiAndSpacesSet; diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index a5206bdcc0c39..77cb88a42286e 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -15,6 +15,18 @@ For license and copyright information please follow this link: #include +namespace Tdb { +class TLmessage; +class TLDmessage; +class TLmessageContent; +class TLDmessageForwardInfo; +class TLDmessageImportInfo; +class TLmessageInteractionInfo; +class TLmessageReaction; +class TLunreadReaction; +class TLDmessageAnimatedEmoji; +} // namespace Tdb + class HiddenSenderInfo; class History; struct HistoryMessageReply; @@ -87,6 +99,10 @@ class ServiceMessagePainter; class HistoryItem final : public RuntimeComposer { public: + [[nodiscard]] static std::unique_ptr CreateMedia( + not_null item, + const Tdb::TLmessageContent &content); +#if 0 // mtp [[nodiscard]] static std::unique_ptr CreateMedia( not_null item, const MTPMessageMedia &media); @@ -106,6 +122,7 @@ class HistoryItem final : public RuntimeComposer { MsgId id, const MTPDmessageEmpty &data, MessageFlags localFlags); +#endif HistoryItem( // Sponsored message. not_null history, @@ -124,7 +141,9 @@ class HistoryItem final : public RuntimeComposer { PeerId from, const QString &postAuthor, const TextWithEntities &textWithEntities, +#if 0 // mtp const MTPMessageMedia &media, +#endif HistoryMessageMarkupData &&markup, uint64 groupedId); HistoryItem( // Local service message. @@ -182,6 +201,13 @@ class HistoryItem final : public RuntimeComposer { HistoryItem(not_null history, not_null story); ~HistoryItem(); + HistoryItem( + not_null history, + MsgId id, + const Tdb::TLDmessage &data, + MessageFlags localFlags, + HistoryItem *replacing); + struct Destroyer { void operator()(HistoryItem *value); }; @@ -202,7 +228,9 @@ class HistoryItem final : public RuntimeComposer { [[nodiscard]] UserData *getMessageBot() const; [[nodiscard]] bool isHistoryEntry() const; [[nodiscard]] bool isAdminLogEntry() const; +#if 0 // mtp [[nodiscard]] bool isFromScheduled() const; +#endif [[nodiscard]] bool isScheduled() const; [[nodiscard]] bool isSponsored() const; [[nodiscard]] bool skipNotification() const; @@ -263,8 +291,10 @@ class HistoryItem final : public RuntimeComposer { [[nodiscard]] bool isEditingMedia() const; void clearSavedMedia(); +#if 0 // mtp // Zero result means this message is not self-destructing right now. [[nodiscard]] crl::time getSelfDestructIn(crl::time now); +#endif [[nodiscard]] bool definesReplyKeyboard() const; [[nodiscard]] ReplyMarkupFlags replyKeyboardFlags() const; @@ -309,7 +339,10 @@ class HistoryItem final : public RuntimeComposer { return _flags & MessageFlag::SendingFailed; } [[nodiscard]] bool hideEditedBadge() const { +#if 0 // mtp return (_flags & MessageFlag::HideEdited); +#endif + return false; } [[nodiscard]] bool isLocal() const { return _flags & MessageFlag::Local; @@ -339,9 +372,13 @@ class HistoryItem final : public RuntimeComposer { [[nodiscard]] bool needCheck() const; [[nodiscard]] bool isService() const; + +#if 0 // mtp void applyEdition(HistoryMessageEdition &&edition); +#endif void applyChanges(not_null story); +#if 0 // mtp void applyEdition(const MTPDmessageService &message); void applyEdition(const MTPMessageExtendedMedia &media); void updateForwardedInfo(const MTPMessageFwdHeader *fwd); @@ -354,6 +391,10 @@ class HistoryItem final : public RuntimeComposer { const MTPDupdateShortSentMessage &data, bool wasAlready); void updateReactions(const MTPMessageReactions *reactions); +#endif + void setIsSilent(bool silent); + void setHideNotificationText(bool hide); + [[nodiscard]] bool hideNotificationText() const; void applyEditionToHistoryCleared(); void updateReplyMarkup(HistoryMessageMarkupData &&markup); @@ -400,7 +441,9 @@ class HistoryItem final : public RuntimeComposer { MsgId replyToTop, bool isForumPost); void setPostAuthor(const QString &author); +#if 0 // mtp void setRealId(MsgId newId); +#endif void incrementReplyToTopCounter(); [[nodiscard]] bool emptyText() const { @@ -438,7 +481,11 @@ class HistoryItem final : public RuntimeComposer { void toggleReaction( const Data::ReactionId &reaction, ReactionSource source); +#if 0 // mtp void updateReactionsUnknown(); +#endif + void updateReactions(const QVector &list); + void updateUnreadReactions(const QVector &list); [[nodiscard]] auto reactions() const -> const std::vector &; [[nodiscard]] auto recentReactions() const @@ -449,7 +496,9 @@ class HistoryItem final : public RuntimeComposer { [[nodiscard]] std::vector chosenReactions() const; [[nodiscard]] Data::ReactionId lookupUnreadReaction( not_null from) const; +#if 0 // mtp [[nodiscard]] crl::time lastReactionsRefreshTime() const; +#endif [[nodiscard]] bool hasDirectLink() const; [[nodiscard]] bool changesWallPaper() const; @@ -511,6 +560,13 @@ class HistoryItem final : public RuntimeComposer { [[nodiscard]] bool canUpdateDate() const; void customEmojiRepaint(); + void updateContent(const Tdb::TLmessageContent &content); + void setMediaExplicit(std::unique_ptr media); + void updateInteractionInfo(const Tdb::TLmessageInteractionInfo *info); + void updateEditedInfo( + TimeId editDate, + HistoryMessageMarkupData &&markup); + [[nodiscard]] TimeId ttlDestroyAt() const { return _ttlDestroyAt; } @@ -545,10 +601,13 @@ class HistoryItem final : public RuntimeComposer { [[nodiscard]] TextWithEntities withLocalEntities( const TextWithEntities &textWithEntities) const; void setTextValue(TextWithEntities text, bool force = false); + +#if 0 // mtp [[nodiscard]] bool isTooOldForEdit(TimeId now) const; [[nodiscard]] bool isLegacyMessage() const { return _flags & MessageFlag::Legacy; } +#endif [[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const; @@ -588,7 +647,28 @@ class HistoryItem final : public RuntimeComposer { [[nodiscard]] ClickHandlerPtr fromLink() const; void setGroupId(MessageGroupId groupId); + void clearGroupId(); + static void FillForwardedInfo( + CreateConfig &config, + const Tdb::TLDmessageForwardInfo &data); + static void FillImportInfo( + CreateConfig &config, + const Tdb::TLDmessageImportInfo &data); + void createServiceFromTdb(const Tdb::TLmessageContent &content); + void setServiceMessageByContent(const Tdb::TLmessageContent &content); + void applyContent(const Tdb::TLmessageContent &content); + void setReactions( + const QVector &list, + const QVector &unread); + [[nodiscard]] bool changeReactions( + const QVector &list); + [[nodiscard]] bool changeUnreadReactions( + const QVector &list); + void setMedia(const Tdb::TLmessageContent &content); + void setContent(const Tdb::TLmessageContent &content); + void setAnimatedEmojiText(const Tdb::TLDmessageAnimatedEmoji &data); +#if 0 // mtp static void FillForwardedInfo( CreateConfig &config, const MTPDmessageFwdHeader &data); @@ -605,6 +685,7 @@ class HistoryItem final : public RuntimeComposer { void createServiceFromMtp(const MTPDmessageService &message); void applyTTL(const MTPDmessage &data); void applyTTL(const MTPDmessageService &data); +#endif void applyTTL(TimeId destroyAt); @@ -626,6 +707,8 @@ class HistoryItem final : public RuntimeComposer { [[nodiscard]] PeerData *computeDisplayFrom() const; + void applyTTL(const Tdb::TLDmessage &data); + const not_null _history; const not_null _from; mutable PeerData *_displayFrom = nullptr; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 66e30a58df5e8..a3ef0f2818f44 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -51,10 +51,14 @@ For license and copyright information please follow this link: #include "styles/style_chat.h" #include "styles/style_dialogs.h" // dialogsMiniReplyStory. +#include "tdb/tdb_tl_scheme.h" + #include namespace { +using namespace Tdb; + const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_"; } // namespace @@ -288,6 +292,7 @@ ReplyFields ReplyFields::clone(not_null parent) const { }; } +#if 0 // mtp ReplyFields ReplyFieldsFromMTP( not_null item, const MTPMessageReplyHeader &reply) { @@ -369,6 +374,45 @@ FullReplyTo ReplyToFromMTP( return FullReplyTo(); }); } +#endif + +ReplyFields ReplyFieldsFromTL( + not_null item, + const Tdb::TLmessageReplyTo &reply) { + return reply.match([&](const TLDmessageReplyToMessage &data) { + auto result = ReplyFields(); + result.messageId = data.vmessage_id().v; + const auto externalPeerId = peerFromTdbChat(data.vchat_id()); + if (externalPeerId != item->history()->peer->id) { + result.externalPeerId = externalPeerId; + } + if (const auto content = data.vcontent()) { + result.externalMedia = HistoryItem::CreateMedia(item, *content); + } + if (const auto quote = data.vquote()) { + result.quote = Api::FormattedTextFromTdb(*quote); + result.manualQuote = data.vis_quote_manual().v; + } + if (const auto origin = data.vorigin()) { + origin->match([&](const TLDmessageOriginUser &data) { + result.externalSenderId = peerFromUser(data.vsender_user_id()); + }, [&](const TLDmessageOriginChat &data) { + result.externalSenderId = peerFromTdbChat(data.vsender_chat_id()); + }, [&](const TLDmessageOriginHiddenUser &data) { + result.externalSenderName = data.vsender_name().v; + }, [&](const TLDmessageOriginChannel &data) { + result.externalSenderId = peerFromTdbChat(data.vchat_id()); + result.externalPostAuthor = data.vauthor_signature().v; + }); + } + return result; + }, [&](const TLDmessageReplyToStory &data) { + return ReplyFields{ + .externalPeerId = peerFromTdbChat(data.vstory_sender_chat_id()), + .storyId = data.vstory_id().v, + }; + }); +} HistoryMessageReply::HistoryMessageReply() = default; diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 3d363225f54c4..9ec40021a8eb6 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -15,6 +15,10 @@ For license and copyright information please follow this link: #include "ui/effects/ripple_animation.h" #include "ui/chat/message_bubble.h" +namespace Tdb { +class TLmessageReplyTo; +} // namespace Tdb + struct WebPageData; class VoiceSeekClickHandler; @@ -47,6 +51,17 @@ class Document; class TranscribeButton; } // namespace HistoryView +namespace Lottie { +struct ColorReplacements; +} // namespace Lottie + +struct HistoryMessageAnimatedEmoji + : public RuntimeComponent { + DocumentData *document = nullptr; + const Lottie::ColorReplacements *replacements = nullptr; + int size = 0; +}; + struct HistoryMessageVia : public RuntimeComponent { void create(not_null owner, UserId userId); void resize(int32 availw) const; @@ -248,6 +263,7 @@ struct ReplyFields { bool manualQuote = false; }; +#if 0 // mtp [[nodiscard]] ReplyFields ReplyFieldsFromMTP( not_null item, const MTPMessageReplyHeader &reply); @@ -255,6 +271,11 @@ struct ReplyFields { [[nodiscard]] FullReplyTo ReplyToFromMTP( not_null history, const MTPInputReplyTo &reply); +#endif + +[[nodiscard]] ReplyFields ReplyFieldsFromTL( + not_null item, + const Tdb::TLmessageReplyTo &reply); struct HistoryMessageReply : public RuntimeComponent { @@ -633,8 +654,10 @@ struct HistoryServiceSelfDestruct using Type = HistorySelfDestructType; Type type = Type::Photo; +#if 0 // mtp std::variant timeToLive = crl::time(); std::variant destructAt = crl::time(); +#endif }; struct HistoryServiceOngoingCall diff --git a/Telegram/SourceFiles/history/history_item_edition.cpp b/Telegram/SourceFiles/history/history_item_edition.cpp index 4349a3c937667..8c4c6ab9fead9 100644 --- a/Telegram/SourceFiles/history/history_item_edition.cpp +++ b/Telegram/SourceFiles/history/history_item_edition.cpp @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #include "history/history_item_edition.h" +#if 0 // mtp + #include "api/api_text_entities.h" #include "main/main_session.h" @@ -34,3 +36,5 @@ HistoryMessageEdition::HistoryMessageEdition( const auto period = message.vttl_period(); ttl = (period && period->v > 0) ? (message.vdate().v + period->v) : 0; } + +#endif diff --git a/Telegram/SourceFiles/history/history_item_edition.h b/Telegram/SourceFiles/history/history_item_edition.h index b8f577d807ad5..2dfa959adb8b5 100644 --- a/Telegram/SourceFiles/history/history_item_edition.h +++ b/Telegram/SourceFiles/history/history_item_edition.h @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #pragma once +#if 0 // mtp + #include "history/history_item_reply_markup.h" namespace Main { @@ -37,3 +39,5 @@ struct HistoryMessageEdition { const MTPMessageMedia *mtpMedia = nullptr; const MTPMessageReactions *mtpReactions = nullptr; }; + +#endif diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 15b5d5ad8e4bf..582b1a080646e 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -41,8 +41,12 @@ For license and copyright information please follow this link: #include "ui/item_text_options.h" #include "lang/lang_keys.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + bool PeerCallKnown(not_null peer) { if (peer->groupCall() != nullptr) { return true; @@ -56,6 +60,79 @@ bool PeerCallKnown(not_null peer) { } // namespace +MessageFlags FlagsFromTdb(const TLDmessage &data) { + using Flag = MessageFlag; + const auto mediaUnread = data.vcontent().match([&]( + const TLDmessageVoiceNote &data) { + return !data.vis_listened().v; + }, [&](const TLDmessageVideoNote &data) { + return !data.vis_viewed().v; + }, [](const auto &) { + return false; + }); + const auto sendingOrFailedFlag = [&] { + if (const auto state = data.vsending_state()) { + return state->match([](const TLDmessageSendingStatePending &) { + return Flag::BeingSent; + }, [](const TLDmessageSendingStateFailed &) { + return Flag::SendingFailed; + }); + } + return Flag(); + }(); + const auto views = [&] { + if (const auto info = data.vinteraction_info()) { + return info->data().vview_count().v; + } + return 0; + }(); + const auto invertMedia = data.vcontent().match([&]( + const TLDmessageText &text) { + if (const auto options = text.vlink_preview_options()) { + return options->data().vshow_above_text().v; + } + return false; + }, [](const auto &) { + return false; + }); + return sendingOrFailedFlag + | (int(sendingOrFailedFlag) ? Flag::Local : Flag()) + | (data.vscheduling_state() + ? Flag::IsOrWasScheduled + : Flag::HistoryEntry) + | (data.vis_outgoing().v ? Flag::Outgoing : Flag()) + | (data.vcan_be_saved().v ? Flag() : Flag::NoForwards) + | (data.vcontains_unread_mention().v + ? (Flag::MentionsMe | Flag::MediaIsUnread) + : Flag()) + | (mediaUnread ? Flag::MediaIsUnread : Flag()) + | (data.vis_channel_post().v ? Flag::Post : Flag()) + | (data.vcan_be_deleted_for_all_users().v + ? Flag::CanDeleteForAll + : Flag()) + | (data.vcan_be_edited().v ? Flag::CanEdit : Flag()) + | (data.vis_pinned().v ? Flag::Pinned : Flag()) + | (data.vreply_to() ? Flag::HasReplyInfo : Flag()) + | (data.vreply_markup() ? Flag::HasReplyMarkup : Flag()) + | (data.vcan_get_added_reactions().v + ? Flag::CanViewReactions + : Flag()) + | (views ? Flag::HasViews : Flag()) + | (invertMedia ? Flag::InvertMedia : Flag()); +} + +TimeId MessageDateFromTdb(const TLDmessage &data) { + if (const auto &state = data.vscheduling_state()) { + return state->match([&]( + const TLDmessageSchedulingStateSendAtDate &data) { + return data.vsend_date().v; + }, [&](const TLDmessageSchedulingStateSendWhenOnline &data) { + return Api::kScheduledUntilOnlineTimestamp; + }); + } + return data.vdate().v; +} + QString GetErrorTextForSending( not_null peer, SendingErrorRequest request) { @@ -156,10 +233,21 @@ void RequestDependentMessageItem( item->updateDependencyItem(); } }; +#if 0 // mtp history->session().api().requestMessageData( (peerId ? history->owner().peer(peerId) : history->peer), msgId, done); +#endif + session->sender().request(TLgetRepliedMessage( + peerToTdbChat(history->peer->id), + tl_int53(item->id.bare) + //peerToTdbChat(peerId), + //tl_int53(msgId.bare) + )).done([=](const TLmessage &result) { + session->data().processMessage(result, NewMessageType::Existing); + done(); + }).fail(done).send(); } void RequestDependentMessageStory( @@ -326,6 +414,18 @@ ClickHandlerPtr JumpToStoryClickHandler( }); } +std::vector> ParseInvitedToCallUsers( + not_null item, + const QVector &users) { + auto &owner = item->history()->owner(); + return ranges::views::all( + users + ) | ranges::views::transform([&](const TLint53 &id) { + return owner.user(id.v); + }) | ranges::to_vector; +} + +#if 0 // mtp MessageFlags FlagsFromMTP( MsgId id, MTPDmessage::Flags flags, @@ -503,6 +603,7 @@ std::vector> ParseInvitedToCallUsers( return owner.user(id.v); }) | ranges::to_vector; } +#endif PreparedServiceText GenerateJoinedText( not_null history, @@ -602,6 +703,10 @@ std::optional PeerHasThisCall( } [[nodiscard]] MessageFlags FinalizeMessageFlags(MessageFlags flags) { + if ((flags & MessageFlag::AdminLogEntry) + || (flags & MessageFlag::FakeHistoryItem)) { + flags &= ~MessageFlag::HistoryEntry; + } if (!(flags & MessageFlag::FakeHistoryItem) && !(flags & MessageFlag::IsOrWasScheduled) && !(flags & MessageFlag::AdminLogEntry)) { diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index cf1045abd61b9..cc14f0c38c6e7 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -9,6 +9,15 @@ For license and copyright information please follow this link: class History; +namespace tl { +class int64_type; +} // namespace tl + +namespace Tdb { +class TLDmessage; +using TLint53 = tl::int64_type; +} // namespace Tdb + namespace Api { struct SendOptions; struct SendAction; @@ -23,11 +32,19 @@ namespace Main { class Session; } // namespace Main +[[nodiscard]] MessageFlags FlagsFromTdb(const Tdb::TLDmessage &data); +[[nodiscard]] TimeId MessageDateFromTdb(const Tdb::TLDmessage &data); + +[[nodiscard]] std::vector> ParseInvitedToCallUsers( + not_null item, + const QVector &users); + struct PreparedServiceText { TextWithEntities text; std::vector links; }; +#if 0 // mtp [[nodiscard]] MessageFlags FlagsFromMTP( MsgId id, MTPDmessage::Flags flags, @@ -53,6 +70,7 @@ enum class MediaCheckResult { [[nodiscard]] std::vector> ParseInvitedToCallUsers( not_null item, const QVector &users); +#endif inline constexpr auto kMaxUnreadReactions = 5; // Now 3, but just in case. using OnStackUsers = std::array; diff --git a/Telegram/SourceFiles/history/history_item_reply_markup.cpp b/Telegram/SourceFiles/history/history_item_reply_markup.cpp index c15381e92a186..7c64a9284f0a6 100644 --- a/Telegram/SourceFiles/history/history_item_reply_markup.cpp +++ b/Telegram/SourceFiles/history/history_item_reply_markup.cpp @@ -12,8 +12,12 @@ For license and copyright information please follow this link: #include "history/history_item_components.h" #include "inline_bots/bot_attach_web_view.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + [[nodiscard]] InlineBots::PeerTypes PeerTypesFromMTP( const MTPvector &types) { using namespace InlineBots; @@ -36,6 +40,59 @@ namespace { return result; } +[[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL( + const TLDkeyboardButtonTypeRequestUser &data) { + const auto restriction = [](const TLbool &is, const TLbool &no) { + using Restriction = RequestPeerQuery::Restriction; + return is.v + ? Restriction::Yes + : no.v + ? Restriction::No + : Restriction::Any; + }; + return { + .type = RequestPeerQuery::Type::User, + .userIsBot = restriction( + data.vuser_is_bot(), + data.vrestrict_user_is_bot()), + .userIsPremium = restriction( + data.vuser_is_premium(), + data.vrestrict_user_is_premium()), + }; +} + +[[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL( + const TLDkeyboardButtonTypeRequestChat &data) { + using Type = RequestPeerQuery::Type; + const auto restriction = [](const TLbool &is, const TLbool &no) { + using Restriction = RequestPeerQuery::Restriction; + return is.v + ? Restriction::Yes + : no.v + ? Restriction::No + : Restriction::Any; + }; + const auto rights = [](const TLchatAdministratorRights *value) { + return value + ? AdminRightsFromChatAdministratorRights(*value) + : ChatAdminRights(); + }; + return { + .type = (data.vchat_is_channel().v ? Type::Broadcast : Type::Group), + .groupIsForum = restriction( + data.vchat_is_forum(), + data.vrestrict_chat_is_forum()), + .hasUsername = restriction( + data.vchat_has_username(), + data.vrestrict_chat_has_username()), + .amCreator = data.vchat_is_created().v, + .isBotParticipant = data.vbot_is_member().v, + .myRights = rights(data.vuser_administrator_rights()), + .botRights = rights(data.vbot_administrator_rights()), + }; +} + +#if 0 // mtp [[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL( const MTPRequestPeerType &query) { using Type = RequestPeerQuery::Type; @@ -72,6 +129,7 @@ namespace { }); return result; } +#endif } // namespace @@ -106,6 +164,7 @@ HistoryMessageMarkupButton *HistoryMessageMarkupButton::Get( return nullptr; } +#if 0 // mtp void HistoryMessageMarkupData::fillRows( const QVector &list) { rows.clear(); @@ -221,7 +280,125 @@ void HistoryMessageMarkupData::fillRows( flags |= ReplyMarkupFlag::OnlyBuyButton; } } +#endif + +template +void HistoryMessageMarkupData::fillRows( + const QVector> &list) { + rows.clear(); + if (list.isEmpty()) { + return; + } + + rows.reserve(list.size()); + for (const auto &buttons : list) { + auto row = std::vector