diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 4472db76b14494..f09e9dd8220c3e 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -617,6 +617,8 @@ PRIVATE data/data_saved_sublist.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 @@ -1160,6 +1162,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 @@ -1231,6 +1235,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 @@ -1449,6 +1454,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 e563e167534a53..f6d5267a91421b 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -495,11 +495,11 @@ int main(int argc, char *argv[]) cout << "Signature verified!\n"; RSA_free(pbKey); #ifdef Q_OS_WIN - QString outName((targetwinarm ? QString("tarm64upd%1") : targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version)); + QString outName((targetwinarm ? QString("update-win-arm-%1") : 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 0f18e76484a907..21bd31200bf614 100644 --- a/Telegram/SourceFiles/api/api_attached_stickers.cpp +++ b/Telegram/SourceFiles/api/api_attached_stickers.cpp @@ -16,12 +16,17 @@ 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) : _api(&api->instance()) { } +#if 0 // mtp void AttachedStickers::request( not_null controller, MTPmessages_GetAttachedStickers &&mtpRequest) { @@ -70,23 +75,80 @@ void AttachedStickers::request( } }).send(); } +#endif + +void AttachedStickers::request( + not_null controller, + FileId fileId) { + const auto weak = base::make_weak(controller.get()); + _api.request(_requestId).cancel(); + _requestId = _api.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 d0dd18bb48858c..cd86ac8df11436 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,13 +34,19 @@ class AttachedStickers final { not_null document); private: +#if 0 // mtp void request( not_null controller, MTPmessages_GetAttachedStickers &&mtpRequest); +#endif MTP::Sender _api; mtpRequestId _requestId = 0; + void request( + not_null controller, + FileId fileId); + }; } // namespace Api diff --git a/Telegram/SourceFiles/api/api_authorizations.cpp b/Telegram/SourceFiles/api/api_authorizations.cpp index 78e463c116fa64..6a1dc3444ccbac 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,6 +84,72 @@ 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 @@ -108,6 +179,7 @@ void Authorizations::reload() { return; } +#if 0 // goodToRemove _requestId = _api.request(MTPaccount_GetAuthorizations( )).done([=](const MTPaccount_Authorizations &result) { _requestId = 0; @@ -124,10 +196,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 +230,7 @@ void Authorizations::refreshCallsDisabledHereFromCloud() { } } +#if 0 // goodToRemove void Authorizations::requestTerminate( Fn &&done, Fn &&fail, @@ -167,6 +260,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 +313,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 +335,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 5e2a41c9f90193..29ab4e66d84970 100644 --- a/Telegram/SourceFiles/api/api_authorizations.h +++ b/Telegram/SourceFiles/api/api_authorizations.h @@ -9,6 +9,11 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" +namespace Tdb { +class TLok; +class Error; +} // namespace Tdb + class ApiWrap; namespace Api { @@ -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(); diff --git a/Telegram/SourceFiles/api/api_blocked_peers.cpp b/Telegram/SourceFiles/api/api_blocked_peers.cpp index 0a79eb733ba5d4..84eadb23314445 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,6 +54,25 @@ 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 @@ -72,6 +97,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 +190,7 @@ void BlockedPeers::unblock( i->second.callbacks.push_back(std::move(done)); } } +#endif bool BlockedPeers::blockAlreadySent( not_null peer, @@ -186,6 +213,7 @@ bool BlockedPeers::blockAlreadySent( return false; } +#if 0 // mtp void BlockedPeers::reload() { if (_requestId) { return; @@ -222,5 +250,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 ca2000f67e79d5..db38c68f08be65 100644 --- a/Telegram/SourceFiles/api/api_blocked_peers.h +++ b/Telegram/SourceFiles/api/api_blocked_peers.h @@ -37,8 +37,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 +49,8 @@ class BlockedPeers final { Fn done = nullptr, bool force = false); + rpl::producer total(); + private: struct Request { std::vector> callbacks; @@ -59,14 +63,23 @@ 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; MTP::Sender _api; - base::flat_map, Request> _blockRequests; + int _total = 0; + +#if 0 // goodToRemove 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 5342cbd2e00450..1034801ca94bd8 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -42,9 +42,13 @@ For license and copyright information please follow this link: #include #include +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + void SendBotCallbackData( not_null controller, not_null item, @@ -73,6 +77,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) { @@ -95,6 +100,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(); @@ -108,12 +134,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()) { @@ -144,10 +175,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); @@ -417,6 +454,30 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { const auto itemId = item->id; const auto id = int32(button->buttonId); const auto chosen = [=](std::vector> result) { + if (query.type == RequestPeerQuery::Type::User) { + auto ids = QVector(); + for (const auto &peer : result) { + if (const auto user = peer->asUser()) { + ids.push_back(tl_int53(peerToUser(user->id).bare)); + } + } + peer->session().sender().request(TLshareUsersWithBot( + peerToTdbChat(peer->id), + tl_int53(itemId.bare), + tl_int32(id), + tl_vector(ids), + tl_bool(false) + )).send(); + } else if (!result.empty()) { + peer->session().sender().request(TLshareChatWithBot( + peerToTdbChat(peer->id), + tl_int53(itemId.bare), + tl_int32(id), + peerToTdbChat(result.front()->id), + tl_bool(false) + )).send(); + } +#if 0 // mtp peer->session().api().request(MTPmessages_SendBotRequestedPeer( peer->input, MTP_int(itemId), @@ -429,6 +490,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 ee6b97b5849273..ff12357165bd0a 100644 --- a/Telegram/SourceFiles/api/api_chat_filters.cpp +++ b/Telegram/SourceFiles/api/api_chat_filters.cpp @@ -32,9 +32,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, @@ -215,13 +220,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()); }; @@ -239,6 +266,7 @@ void ImportInvite( MTP_vector(std::move(inputs)) )).done(callback).fail(error).handleFloodErrors().send(); } +#endif } ToggleChatsController::ToggleChatsController( @@ -672,6 +700,39 @@ 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.colorIndex(), + 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( @@ -679,6 +740,7 @@ void SaveNewFilterPinned( MTP_int(filterId), filter.tl() )).send(); +#endif } void CheckFilterInvite( @@ -687,7 +749,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; @@ -698,6 +763,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()); @@ -719,6 +799,7 @@ void CheckFilterInvite( peers = parseList(data.vmissing_peers()); already = parseList(data.valready_peers()); }); +#endif const auto notLoaded = filterId && !ranges::contains( @@ -737,7 +818,9 @@ void CheckFilterInvite( std::move(peers), std::move(already)); }, *lifetime); +#if 0 // mtp owner.chatsFilters().reload(); +#endif } else if (filterId) { ProcessFilterInvite( weak, @@ -755,8 +838,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 be07e72ac1d4f0..a6c1b41166814d 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -42,6 +42,11 @@ 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 "base/unixtime.h" + +using namespace Tdb; + namespace Api { namespace { @@ -51,6 +56,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) { @@ -89,6 +95,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) { @@ -118,13 +138,25 @@ void ConfirmSubscriptionBox( not_null box, not_null session, const QString &hash, +#if 0 // mtp const MTPDchatInvite *data) { +#endif + const TLDchatInviteLinkInfo *data) { box->setWidth(st::boxWideWidth); +#if 0 // mtp const auto amount = data->vsubscription_pricing()->data().vamount().v; const auto formId = data->vsubscription_form_id()->v; const auto name = qs(data->vtitle()); const auto maybePhoto = session->data().processPhoto(data->vphoto()); const auto photo = maybePhoto->isNull() ? nullptr : maybePhoto.get(); +#endif + const auto &pricing = data->vsubscription_info()->data().vpricing(); + const auto amount = pricing.data().vstar_count().v; + const auto formId = data->vsubscription_info()->data().vform_id().v; + const auto name = qs(data->vtitle()); + const auto photo = data->vphoto() + ? session->data().processSmallPhoto(*data->vphoto()).get() + : nullptr; struct State final { std::shared_ptr photoMedia; @@ -273,22 +305,40 @@ void ConfirmSubscriptionBox( const auto buttonWidth = state->saveButton ? state->saveButton->width() : 0; + state->api->request(TLsendPaymentForm( + tl_inputInvoiceTelegram( + tl_telegramPaymentPurposeJoinChat(tl_string(hash))), + tl_int64(formId), + tl_string(), // order_info_id + tl_string(), // shipping_option_id + null, // credentials + tl_int53(0) // tip_amount + )).done([=](const TLpaymentResult &result) { +#if 0 // mtp state->api->request( MTPpayments_SendStarsForm( MTP_long(formId), MTP_inputInvoiceChatInviteSubscription(MTP_string(hash))) ).done([=](const MTPpayments_PaymentResult &result) { +#endif state->api = std::nullopt; state->loading.force_assign(false); +#if 0 // mtp result.match([&](const MTPDpayments_paymentResult &data) { session->api().applyUpdates(data.vupdates()); }, [](const MTPDpayments_paymentVerificationNeeded &data) { }); +#endif if (weak) { box->closeBox(); } +#if 0 // mtp }).fail([=](const MTP::Error &error) { const auto id = error.type(); +#endif + + }).fail([=](const Error &error) { + const auto id = error.message; if (weak) { state->api = std::nullopt; } @@ -346,6 +396,88 @@ void CheckChatInvite( Fn loaded) { 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 hasPricing = data.vsubscription_info().has_value(); + const auto canRefulfill = hasPricing + && data.vsubscription_info()->data().vcan_reuse().v; + const auto formId = hasPricing + ? data.vsubscription_info()->data().vform_id().v + : 0; + if (hasPricing + && !canRefulfill + && !formId) { + strong->uiShow()->showToast( + tr::lng_confirm_phone_link_invalid(tr::now)); + return; + } + const auto box = (hasPricing && !canRefulfill) + ? strong->show(Box( + ConfirmSubscriptionBox, + session, + hash, + &data)) + : 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) { @@ -422,6 +554,7 @@ void CheckChatInvite( strong->show(Ui::MakeInformBox(tr::lng_group_invite_bad_link())); } }); +#endif } } // namespace Api @@ -431,6 +564,7 @@ struct ConfirmInviteBox::Participant { Ui::PeerUserpicView userpic; }; +#if 0 // mtp ConfirmInviteBox::ConfirmInviteBox( QWidget*, not_null session, @@ -443,6 +577,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, @@ -515,6 +662,7 @@ ConfirmInviteBox::ConfirmInviteBox( ConfirmInviteBox::~ConfirmInviteBox() = default; +#if 0 // mtp ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse( not_null session, const MTPDchatInvite &data) { @@ -544,6 +692,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 94eeab5e92220d..da7bd799e338ae 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; @@ -45,12 +49,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: @@ -76,9 +88,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_links.cpp b/Telegram/SourceFiles/api/api_chat_links.cpp index aa5be9f55a496a..afb1256950bf50 100644 --- a/Telegram/SourceFiles/api/api_chat_links.cpp +++ b/Telegram/SourceFiles/api/api_chat_links.cpp @@ -12,9 +12,15 @@ For license and copyright information please follow this link: #include "data/data_session.h" #include "main/main_session.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + +#if 0 // mtp [[nodiscard]] ChatLink FromMTP( not_null session, const MTPBusinessChatLink &link) { @@ -49,6 +55,17 @@ namespace { std::move(entities), MTP_string(title)); } +#endif + +[[nodiscard]] ChatLink FromTL(const TLbusinessChatLink &link) { + const auto &data = link.data(); + return { + .link = data.vlink().v, + .title = data.vtitle().v, + .message = FormattedTextFromTdb(data.vtext()), + .clicks = data.vview_count().v, + }; +} } // namespace @@ -60,15 +77,26 @@ void ChatLinks::create( const TextWithEntities &message, Fn done) { const auto session = &_api->session(); +#if 0 // mtp _api->request(MTPaccount_CreateBusinessChatLink( ToMTP(session, title, message) )).done([=](const MTPBusinessChatLink &result) { const auto link = FromMTP(session, result); +#endif + session->sender().request(TLcreateBusinessChatLink( + tl_inputBusinessChatLink( + FormattedTextToTdb(message), + tl_string(title)) + )).done([=](const TLbusinessChatLink &result) { + const auto link = FromTL(result); _list.push_back(link); _updates.fire({ .was = QString(), .now = link }); if (done) done(link); +#if 0 // mtp }).fail([=](const MTP::Error &error) { const auto type = error.type(); +#endif + }).fail([=] { if (done) done(Link()); }).send(); } @@ -79,11 +107,20 @@ void ChatLinks::edit( const TextWithEntities &message, Fn done) { const auto session = &_api->session(); +#if 0 // mtp _api->request(MTPaccount_EditBusinessChatLink( MTP_string(link), ToMTP(session, title, message) )).done([=](const MTPBusinessChatLink &result) { const auto parsed = FromMTP(session, result); +#endif + session->sender().request(TLeditBusinessChatLink( + tl_string(link), + tl_inputBusinessChatLink( + FormattedTextToTdb(message), + tl_string(title)) + )).done([=](const TLbusinessChatLink &result) { + const auto parsed = FromTL(result); if (parsed.link != link) { LOG(("API Error: EditBusinessChatLink changed the link.")); if (done) done(Link()); @@ -98,8 +135,11 @@ void ChatLinks::edit( LOG(("API Error: EditBusinessChatLink link not found.")); if (done) done(Link()); } +#if 0 // mtp }).fail([=](const MTP::Error &error) { const auto type = error.type(); +#endif + }).fail([=] { if (done) done(Link()); }).send(); } @@ -107,9 +147,14 @@ void ChatLinks::edit( void ChatLinks::destroy( const QString &link, Fn done) { +#if 0 // mtp _api->request(MTPaccount_DeleteBusinessChatLink( MTP_string(link) )).done([=] { +#endif + _api->sender().request(TLdeleteBusinessChatLink( + tl_string(link) + )).done([=] { const auto i = ranges::find(_list, link, &Link::link); if (i != end(_list)) { _list.erase(i); @@ -119,8 +164,11 @@ void ChatLinks::destroy( LOG(("API Error: DeleteBusinessChatLink link not found.")); if (done) done(); } +#if 0 // mtp }).fail([=](const MTP::Error &error) { const auto type = error.type(); +#endif + }).fail([=] { if (done) done(); }).send(); } @@ -129,6 +177,7 @@ void ChatLinks::preload() { if (_loaded || _requestId) { return; } +#if 0 // mtp _requestId = _api->request(MTPaccount_GetBusinessChatLinks( )).done([=](const MTPaccount_BusinessChatLinks &result) { const auto &data = result.data(); @@ -136,10 +185,17 @@ void ChatLinks::preload() { const auto owner = &session->data(); owner->processUsers(data.vusers()); owner->processChats(data.vchats()); +#endif + _requestId = _api->sender().request(TLgetBusinessChatLinks( + )).done([=](const TLbusinessChatLinks &result) { + const auto &data = result.data(); auto links = std::vector(); links.reserve(data.vlinks().v.size()); for (const auto &link : data.vlinks().v) { +#if 0 // mtp links.push_back(FromMTP(session, link)); +#endif + links.push_back(FromTL(link)); } _list = std::move(links); _loaded = true; diff --git a/Telegram/SourceFiles/api/api_chat_participants.cpp b/Telegram/SourceFiles/api/api_chat_participants.cpp index 10663d1de5e404..8ddb771aba8877 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) { @@ -211,6 +222,7 @@ void ApplyBotsList( Data::PeerUpdate::Flag::FullInfo); } +#if 0 // mtp [[nodiscard]] ChatParticipants::Channels ParseSimilar( not_null session, const MTPmessages_Chats &chats) { @@ -239,9 +251,38 @@ void ApplyBotsList( const MTPmessages_Chats &chats) { return ParseSimilar(&channel->session(), chats); } +#endif + +[[nodiscard]] ChatParticipants::Channels ParseSimilar( + not_null session, + const TLchats &chats) { + auto result = ChatParticipants::Channels(); + const auto &data = chats.data(); + const auto &list = data.vchat_ids().v; + result.list.reserve(list.size()); + for (const auto chatId : list) { + const auto peer = session->data().peer(peerFromTdbChat(chatId)); + if (const auto channel = peer->asChannel()) { + result.list.push_back(channel); + } + } + if (session->premiumPossible()) { + result.more = std::max( + int(data.vtotal_count().v) - int(data.vchat_ids().v.size()), + 0); + } + return result; +} + +[[nodiscard]] ChatParticipants::Channels ParseSimilar( + not_null channel, + const TLchats &chats) { + return ParseSimilar(&channel->session(), chats); +} } // namespace +#if 0 // goodToRemove ChatParticipant::ChatParticipant( const MTPChannelParticipant &p, not_null peer) { @@ -290,6 +331,36 @@ 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; + _subscriptionDate = data.vmember_until_date().v; + }, [&](const TLDchatMemberStatusBanned &data) { + _type = Type::Banned; + }, [&](const TLDchatMemberStatusRestricted &data) { + _type = Type::Restricted; + }); +} ChatParticipant::ChatParticipant( Type type, @@ -397,12 +468,14 @@ void ChatParticipants::requestForAdd( if (_forAdd.channel == channel) { return; } + _api.request(base::take(_forAdd.requestId)).cancel(); const auto offset = 0; const auto participantsHash = uint64(0); _forAdd.channel = channel; +#if 0 // goodToRemove _forAdd.requestId = _api.request(MTPchannels_GetParticipants( channel->inputChannel, MTP_channelParticipantsRecent(), @@ -417,6 +490,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(); @@ -430,6 +511,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, @@ -447,6 +529,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(); @@ -460,6 +552,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, @@ -476,6 +569,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(); @@ -489,6 +592,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, @@ -506,6 +610,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); @@ -529,6 +642,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, @@ -546,6 +660,25 @@ void ChatParticipants::add( ShowAddParticipantsError(show, type, peer, user); 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( + show, + error.message, + peer, + user); + if (done) { + done(false); + } + }).send(); } } else if (const auto channel = peer->asChannel()) { const auto hasBot = ranges::any_of(users, &UserData::isBot); @@ -557,10 +690,14 @@ void ChatParticipants::add( { .users = users }); 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) @@ -579,9 +716,31 @@ void ChatParticipants::add( }); 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( + show, + error.message, + peer, + { .users = 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(); @@ -598,13 +757,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( @@ -627,6 +791,21 @@ void ChatParticipants::Restrict( ChatRestrictionsInfo newRights, Fn onDone, Fn onFail) { + channel->session().sender().request(TLsetChatMemberStatus( + peerToTdbChat(channel->id), + peerToSender(participant->id), + ChatRestrictionsInfo::ToTL(newRights) + )).done([=] { + channel->applyEditBanned(participant, oldRights, newRights); + if (onDone) { + onDone(); + } + }).fail([=] { + if (onFail) { + onFail(); + } + }).send(); +#if 0 // mtp channel->session().api().request(MTPchannels_EditBanned( channel->inputChannel, participant->input, @@ -645,6 +824,7 @@ void ChatParticipants::Restrict( onFail(); } }).send(); +#endif } void ChatParticipants::requestSelf(not_null channel) { @@ -653,10 +833,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)) { @@ -669,6 +850,7 @@ void ChatParticipants::requestSelf(not_null channel) { } }; _selfParticipantRequests.emplace(channel); +#if 0 // goodToRemove _api.request(MTPchannels_GetParticipant( channel->inputChannel, MTP_inputPeerSelf() @@ -690,8 +872,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.")); @@ -711,6 +893,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( @@ -718,6 +939,7 @@ void ChatParticipants::kick( not_null participant) { Expects(participant->isUser()); +#if 0 // goodToRemove _api.request(MTPmessages_DeleteChatUser( MTP_flags(0), chat->inputChat, @@ -725,6 +947,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( @@ -732,9 +962,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, @@ -750,6 +983,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); } @@ -762,13 +1007,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(tl_int32(0)) // member_until_date + )).done([=] { _kickRequests.remove(KickRequest(channel, participant)); if (channel->kickedCount() > 0) { channel->setKickedCount(channel->kickedCount() - 1); @@ -792,12 +1044,17 @@ void ChatParticipants::loadSimilarChannels(not_null channel) { return; } } +#if 0 // mtp using Flag = MTPchannels_GetChannelRecommendations::Flag; _similar[channel].requestId = _api.request( MTPchannels_GetChannelRecommendations( MTP_flags(Flag::f_channel), channel->inputChannel) ).done([=](const MTPmessages_Chats &result) { +#endif + _similar[channel].requestId = _api.request(TLgetChatSimilarChats( + peerToTdbChat(channel->id) + )).done([=](const TLchats &result) { auto &similar = _similar[channel]; similar.requestId = 0; auto parsed = ParseSimilar(channel, result); @@ -835,6 +1092,15 @@ void ChatParticipants::loadRecommendations() { if (_recommendationsLoaded.current() || _recommendations.requestId) { return; } + _recommendations.requestId = _api.request(TLgetRecommendedChats( + )).done([=](const TLchats &result) { + _recommendations.requestId = 0; + auto parsed = ParseSimilar(_session, result); + _recommendations.channels = std::move(parsed); + _recommendations.channels.more = 0; + _recommendationsLoaded = true; + }).send(); +#if 0 // mtp _recommendations.requestId = _api.request( MTPchannels_GetChannelRecommendations( MTP_flags(0), @@ -846,6 +1112,7 @@ void ChatParticipants::loadRecommendations() { _recommendations.channels.more = 0; _recommendationsLoaded = true; }).send(); +#endif } const ChatParticipants::Channels &ChatParticipants::recommendations() const { diff --git a/Telegram/SourceFiles/api/api_chat_participants.h b/Telegram/SourceFiles/api/api_chat_participants.h index df332522d806e1..eec8598338acf7 100644 --- a/Telegram/SourceFiles/api/api_chat_participants.h +++ b/Telegram/SourceFiles/api/api_chat_participants.h @@ -11,6 +11,11 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" #include "base/timer.h" +namespace Tdb { +class TLDchatMembers; +class TLchatMember; +} // namespace Tdb + class ApiWrap; class ChannelData; @@ -35,9 +40,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, @@ -92,7 +102,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); diff --git a/Telegram/SourceFiles/api/api_cloud_password.cpp b/Telegram/SourceFiles/api/api_cloud_password.cpp index e974154b989cb3..edc1ad06324537 100644 --- a/Telegram/SourceFiles/api/api_cloud_password.cpp +++ b/Telegram/SourceFiles/api/api_cloud_password.cpp @@ -15,9 +15,44 @@ For license and copyright information please follow this link: #include "base/unixtime.h" #include "base/call_delayed.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) { @@ -25,6 +60,7 @@ namespace { return Core::ParseCloudPasswordState(data); }); } +#endif } // namespace @@ -32,12 +68,18 @@ CloudPassword::CloudPassword(not_null api) : _api(&api->instance()) { } +CloudPassword::CloudPassword(Tdb::Sender &sender) +: _authorized(false) +, _api(&sender) { +} + void CloudPassword::apply(Core::CloudPasswordState state) { if (_state) { *_state = std::move(state); } else { _state = std::make_unique(std::move(state)); } + _state->outdatedClient = false; _stateChanges.fire_copy(*_state); } @@ -45,6 +87,7 @@ void CloudPassword::reload() { if (_requestId) { return; } +#if 0 // goodToRemove _requestId = _api.request(MTPaccount_GetPassword( )).done([=](const MTPaccount_Password &result) { _requestId = 0; @@ -52,9 +95,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; @@ -63,6 +138,7 @@ void CloudPassword::clearUnconfirmedPassword() { _requestId = 0; reload(); }).send(); +#endif } rpl::producer CloudPassword::state() const { @@ -78,6 +154,7 @@ auto CloudPassword::stateCurrent() const : std::nullopt; } +#if 0 // goodToRemove auto CloudPassword::resetPassword() -> rpl::producer { return [=](auto consumer) { @@ -106,10 +183,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(); @@ -117,6 +225,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(); }; @@ -129,6 +245,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) { @@ -294,10 +436,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) { @@ -329,11 +496,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([=] { @@ -347,6 +527,7 @@ rpl::producer CloudPassword::confirmEmail( }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).handleFloodErrors().send(); +#endif return rpl::lifetime(); }; @@ -354,6 +535,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( @@ -366,6 +555,7 @@ rpl::producer CloudPassword::resendEmailCode() { }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).handleFloodErrors().send(); +#endif return rpl::lifetime(); }; @@ -374,6 +564,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) { @@ -443,13 +651,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) { @@ -512,10 +748,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) { @@ -525,6 +782,7 @@ rpl::producer CloudPassword::requestPasswordRecovery() { }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif return rpl::lifetime(); }; } @@ -532,6 +790,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([=] { @@ -539,6 +818,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 30bc37938268eb..978da703589834 100644 --- a/Telegram/SourceFiles/api/api_cloud_password.h +++ b/Telegram/SourceFiles/api/api_cloud_password.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" +namespace Tdb { +class TLpasswordState; +} // namespace Tdb + namespace Core { struct CloudPasswordState; } // namespace Core @@ -29,6 +33,7 @@ class CloudPassword final { using ResetRetryDate = int; explicit CloudPassword(not_null api); + explicit CloudPassword(Tdb::Sender &sender); void reload(); void clearUnconfirmedPassword(); @@ -63,6 +68,9 @@ class CloudPassword final { private: void apply(Core::CloudPasswordState state); + void apply(const Tdb::TLpasswordState &state); + + const bool _authorized = true; MTP::Sender _api; mtpRequestId _requestId = 0; std::unique_ptr _state; diff --git a/Telegram/SourceFiles/api/api_common.cpp b/Telegram/SourceFiles/api/api_common.cpp index cfb1e72207fd19..322617aa5a03e0 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 81f098d670c0e8..8e3bd12b96356a 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -55,7 +55,9 @@ struct SendAction { bool generateLocal = true; MsgId replaceMediaOf = 0; +#if 0 // mtp [[nodiscard]] MTPInputReplyTo mtpReplyTo() const; +#endif friend inline bool operator==( const SendAction &, diff --git a/Telegram/SourceFiles/api/api_confirm_phone.cpp b/Telegram/SourceFiles/api/api_confirm_phone.cpp index 3a42a793df602a..7513ef23b70f25 100644 --- a/Telegram/SourceFiles/api/api_confirm_phone.cpp +++ b/Telegram/SourceFiles/api/api_confirm_phone.cpp @@ -16,6 +16,8 @@ For license and copyright information please follow this link: #include "ui/text/format_values.h" // Ui::FormatPhone #include "window/window_session_controller.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { ConfirmPhone::ConfirmPhone(not_null api) @@ -29,6 +31,7 @@ void ConfirmPhone::resolve( if (_sendRequestId) { return; } +#if 0 // goodToRemove _sendRequestId = _api.request(MTPaccount_SendConfirmPhoneCode( MTP_string(hash), MTP_codeSettings( @@ -164,6 +167,122 @@ void ConfirmPhone::resolve( Ui::MakeInformBox(errorText), Ui::LayerOption::CloseOther); }).handleFloodErrors().send(); +#endif + _sendRequestId = _api.request(Tdb::TLsendPhoneNumberCode( + Tdb::tl_string(phone), + std::nullopt, + Tdb::tl_phoneNumberCodeTypeConfirmOwnership(Tdb::tl_string(hash)) + )).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::TLDauthenticationCodeTypeSmsWord &data) { + LOG(("Error: should not be smsword!")); + return 0; + }, [](const Tdb::TLDauthenticationCodeTypeSmsPhrase &data) { + LOG(("Error: should not be smsphrase!")); + return 0; + }, [](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::TLresendPhoneNumberCode( + Tdb::tl_resendCodeReasonUserRequest() + )).done([=] { + if (boxWeak) { + boxWeak->callDone(); + } + }).send(); + }, box->lifetime()); + box->checkRequests( + ) | rpl::start_with_next([=](const QString &code) { + if (_checkRequestId) { + return; + } + _checkRequestId = _api.request( + Tdb::TLcheckPhoneNumberCode(Tdb::tl_string(code)) + ).done([=] { + _checkRequestId = 0; + controller->show( + Ui::MakeInformBox( + tr::lng_confirm_phone_success( + tr::now, + lt_phone, + Ui::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_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 4ef4b09c85da9e..1b529a66c850c3 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -21,11 +21,20 @@ For license and copyright information please follow this link: #include "main/main_app_config.h" #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" +#include "api/api_text_entities.h" +#include "data/data_credits.h" + namespace Api { namespace { +using namespace Tdb; + constexpr auto kTransactionsLimit = 100; +constexpr auto kTransactionsPerStatus = 3; +constexpr auto kTransactionsPerPage = 50; +#if 0 // mtp [[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL( const MTPStarsTransaction &tl, not_null peer) { @@ -172,6 +181,183 @@ constexpr auto kTransactionsLimit = 100; status.data().vsubscriptions_next_offset().value_or_empty()), }; } +#endif + +[[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL( + const TLstarTransaction &tl, + not_null peer) { + const auto &data = tl.data(); + auto result = Data::CreditsHistoryEntry{ + .id = data.vid().v, + .date = base::unixtime::parse(data.vdate().v), + .credits = uint64(data.vstar_count().v), + .refunded = data.vis_refund().v, + .in = (int64(data.vstar_count().v) >= 0), + }; + const auto fillPaidMedia = [&](const TLvector &media) { + const auto list = media.v; + if (list.empty()) { + return; + } + using namespace Data; + const auto owner = &peer->owner(); + result.extended.reserve(list.size()); + for (const auto &media : list) { + media.match([&](const TLDpaidMediaPhoto &data) { + const auto photo = owner->processPhoto(data.vphoto()); + if (!photo->isNull()) { + result.extended.push_back(Data::CreditsHistoryMedia{ + .type = Data::CreditsHistoryMediaType::Photo, + .id = photo->id, + }); + } + }, [&](const TLDpaidMediaVideo &data) { + const auto document = owner->processDocument( + data.vvideo()); + if (document->isAnimation() + || document->isVideoFile() + || document->isGifv()) { + result.extended.push_back(Data::CreditsHistoryMedia{ + .type = Data::CreditsHistoryMediaType::Video, + .id = document->id, + }); + } + }, [&](const auto &) {}); + } + }; + + using Type = Data::CreditsHistoryEntry::PeerType; + data.vpartner().match([&](const TLDstarTransactionPartnerTelegram &) { + result.peerType = Type::PremiumBot; + }, [&](const TLDstarTransactionPartnerAppStore &) { + result.peerType = Type::AppStore; + }, [&](const TLDstarTransactionPartnerGooglePlay &) { + result.peerType = Type::PlayMarket; + }, [&](const TLDstarTransactionPartnerFragment &data) { + result.peerType = Type::Fragment; + if (const auto state = data.vwithdrawal_state()) { + state->match([&](const TLDrevenueWithdrawalStateFailed &) { + result.failed = true; + }, [&](const TLDrevenueWithdrawalStatePending &) { + result.pending = true; + }, [&](const TLDrevenueWithdrawalStateSucceeded &data) { + result.successDate = base::unixtime::parse(data.vdate().v); + result.successLink = data.vurl().v; + }); + } + }, [&](const TLDstarTransactionPartnerTelegramAds &data) { + result.peerType = Type::Ads; + }, [&](const TLDstarTransactionPartnerBot &data) { + data.vpurpose().match([&]( + const TLDbotTransactionPurposePaidMedia &data) { + fillPaidMedia(data.vmedia()); + }, [&](const TLDbotTransactionPurposeInvoicePayment &data) { + if (const auto product = data.vproduct_info()) { + const auto &data = product->data(); + result.title = data.vtitle().v; + result.description = Api::FormattedTextFromTdb( + data.vdescription()); + if (const auto photo = data.vphoto()) { + result.photoId = peer->owner().processPhoto(*photo)->id; + } + } + }); + result.barePeerId = peerFromUser(data.vuser_id().v).value; + result.peerType = Type::Peer; + }, [&](const TLDstarTransactionPartnerBusiness &data) { + fillPaidMedia(data.vmedia()); + result.barePeerId = peerFromUser(data.vuser_id().v).value; + result.peerType = Type::Peer; + }, [&](const TLDstarTransactionPartnerChat &data) { + using namespace Data; + result.barePeerId = peerFromTdbChat(data.vchat_id()).value; + data.vpurpose().match([&]( + const TLDchatTransactionPurposeJoin &data) { + result.subscriptionUntil = base::unixtime::parse( + base::unixtime::now() + data.vperiod().v); + }, [&](const TLDchatTransactionPurposePaidMedia &data) { + result.bareMsgId = data.vmessage_id().v; + fillPaidMedia(data.vmedia()); + }, [&](const TLDchatTransactionPurposeReaction &data) { + result.bareMsgId = data.vmessage_id().v; + result.reaction = true; + }, [&](const TLDchatTransactionPurposeGiveaway &data) { + result.bareGiveawayMsgId = uint64(data.vgiveaway_message_id().v); + }); + result.peerType = Type::Peer; + }, [&](const TLDstarTransactionPartnerUser &data) { + using namespace Data; + result.barePeerId = peerFromUser(data.vuser_id()).value; + data.vpurpose().match([&]( + const TLDuserTransactionPurposeGiftSell &data) { + const auto &gift = data.vgift().data(); + result.convertStars = gift.vdefault_sell_star_count().v; + result.converted = true; + }, [&](const TLDuserTransactionPurposeGiftSend &data) { + const auto &gift = data.vgift().data(); + result.convertStars = gift.vdefault_sell_star_count().v; + }, [&](const TLDuserTransactionPurposeGiftedStars &data) { + result.bareGiftStickerId = data.vsticker() + ? peer->owner().processDocument(*data.vsticker())->id + : 0; + }); + result.gift = true; + result.peerType = Type::Peer; + }, [&](const TLDstarTransactionPartnerUnsupported &) { + result.peerType = Type::Unsupported; + }); + return result; +} + +[[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL( + const TLstarSubscription &tl) { + const auto &data = tl.data(); + return Data::SubscriptionEntry{ + .id = data.vid().v, + .inviteHash = data.vinvite_link().v, + .until = base::unixtime::parse(data.vexpiration_date().v), + .subscription = Data::PeerSubscription{ + .credits = uint64(data.vpricing().data().vstar_count().v), + .period = data.vpricing().data().vperiod().v, + }, + .barePeerId = peerFromTdbChat(data.vchat_id()).value, + .cancelled = data.vis_canceled().v, + .expired = (base::unixtime::now() > data.vexpiration_date().v), + .canRefulfill = data.vcan_reuse().v, + }; +} + +[[nodiscard]] Data::CreditsStatusSlice StatusFromTL( + const TLstarTransactions &status, + not_null peer) { + const auto &data = status.data(); + return Data::CreditsStatusSlice{ + .list = ranges::views::all( + data.vtransactions().v + ) | ranges::views::transform([&](const TLstarTransaction &tl) { + return HistoryFromTL(tl, peer); + }) | ranges::to_vector, + .balance = uint64(data.vstar_count().v), + .allLoaded = data.vnext_offset().v.isEmpty(), + .token = data.vnext_offset().v, + }; +} + +[[nodiscard]] Data::CreditsStatusSlice StatusFromTL( + const TLstarSubscriptions &status, + not_null peer) { + const auto &data = status.data(); + return Data::CreditsStatusSlice{ + .subscriptions = ranges::views::all( + data.vsubscriptions().v + ) | ranges::views::transform([&](const TLstarSubscription &tl) { + return SubscriptionFromTL(tl); + }) | ranges::to_vector, + .balance = uint64(data.vstar_count().v), + .allLoaded = data.vnext_offset().v.isEmpty(), + .token = data.vnext_offset().v, + }; +} } // namespace @@ -190,17 +376,26 @@ rpl::producer CreditsTopupOptions::request() { return ranges::views::all( options ) | ranges::views::transform([=](const auto &option) { + const auto &data = option.data(); return Data::CreditTopupOption{ +#if 0 // mtp .credits = option.data().vstars().v, .product = qs( option.data().vstore_product().value_or_empty()), .currency = qs(option.data().vcurrency()), .amount = option.data().vamount().v, .extended = option.data().is_extended(), +#endif + .credits = uint64(data.vstar_count().v), + .product = data.vstore_product_id().v, + .currency = data.vcurrency().v, + .amount = uint64(data.vamount().v), + .extended = data.vis_additional().v, .giftBarePeerId = giftBarePeerId, }; }) | ranges::to_vector; }; +#if 0 // mtp const auto fail = [=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }; @@ -222,7 +417,24 @@ rpl::producer CreditsTopupOptions::request() { consumer.put_done(); }).fail(fail).send(); } - +#endif + const auto send = [&](auto &&request) { + _api.request( + std::move(request) + ).done([=](const TLDstarPaymentOptions &result) { + _options = optionsFromTL(result.voptions().v); + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); + }; + if (_peer->isSelf()) { + send(TLgetStarPaymentOptions()); + } else if (const auto user = _peer->asUser()) { + send(TLgetStarGiftPaymentOptions( + tl_int53(peerToUser(user->id).bare) + )); + } return lifetime; }; } @@ -243,13 +455,25 @@ void CreditsStatus::request( return; } +#if 0 // mtp using TLResult = MTPpayments_StarsStatus; _requestId = _api.request(MTPpayments_GetStarsStatus( _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input +#endif + using TLResult = TLstarTransactions; + _requestId = _api.request(TLgetStarTransactions( + peerToSender(_peer->id), + tl_string(), // subscription_id + null, + tl_string(), + tl_int32(kTransactionsPerStatus) )).done([=](const TLResult &result) { _requestId = 0; +#if 0 // mtp const auto balance = result.data().vbalance().v; +#endif + const auto balance = result.data().vstar_count().v; _peer->session().credits().apply(_peer->id, balance); if (const auto onstack = done) { onstack(StatusFromTL(result, _peer)); @@ -264,11 +488,15 @@ void CreditsStatus::request( CreditsHistory::CreditsHistory(not_null peer, bool in, bool out) : _peer(peer) +#if 0 // mtp , _flags((in == out) ? HistoryTL::Flags(0) : HistoryTL::Flags(0) | (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0)) | (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0))) +#endif +, _in(in) +, _out(out) , _api(&peer->session().api().instance()) { } @@ -278,6 +506,7 @@ void CreditsHistory::request( if (_requestId) { return; } +#if 0 // mtp _requestId = _api.request(MTPpayments_GetStarsTransactions( MTP_flags(_flags), MTPstring(), // subscription_id @@ -285,6 +514,18 @@ void CreditsHistory::request( MTP_string(token), MTP_int(kTransactionsLimit) )).done([=](const MTPpayments_StarsStatus &result) { +#endif + _requestId = _api.request(TLgetStarTransactions( + peerToSender(_peer->id), + tl_string(), // subscription_id + ((_in == _out) + ? std::optional() + : _in + ? tl_starTransactionDirectionIncoming() + : tl_starTransactionDirectionOutgoing()), + tl_string(token), + tl_int32(kTransactionsPerPage) + )).done([=](const TLstarTransactions &result) { _requestId = 0; done(StatusFromTL(result, _peer)); }).fail([=] { @@ -299,11 +540,18 @@ void CreditsHistory::requestSubscriptions( if (_requestId) { return; } +#if 0 // mtp _requestId = _api.request(MTPpayments_GetStarsSubscriptions( MTP_flags(0), _peer->isSelf() ? MTP_inputPeerSelf() : _peer->input, MTP_string(token) )).done([=](const MTPpayments_StarsStatus &result) { +#endif + Assert(_peer->isSelf()); + _requestId = _api.request(TLgetStarSubscriptions( + tl_bool(false), // only_expiring + tl_string(token) + )).done([=](const TLstarSubscriptions &result) { _requestId = 0; done(StatusFromTL(result, _peer)); }).fail([=] { @@ -314,6 +562,8 @@ void CreditsHistory::requestSubscriptions( rpl::producer> PremiumPeerBot( not_null session) { + return rpl::single(not_null(session->user())); +#if 0 // mtp this won't be used so just workaround it for now const auto username = session->appConfig().get( u"premium_bot_username"_q, QString()); @@ -343,6 +593,7 @@ rpl::producer> PremiumPeerBot( return lifetime; }; +#endif } CreditsEarnStatistics::CreditsEarnStatistics(not_null peer) @@ -355,6 +606,7 @@ rpl::producer CreditsEarnStatistics::request() { auto lifetime = rpl::lifetime(); const auto finish = [=](const QString &url) { +#if 0 // mtp makeRequest(MTPpayments_GetStarsRevenueStats( MTP_flags(0), (_isUser ? user()->input : channel()->input) @@ -380,8 +632,35 @@ rpl::producer CreditsEarnStatistics::request() { }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + api().request(TLgetStarRevenueStatistics( + peerToSender(_isUser ? user()->id : channel()->id), + tl_bool(false) + )).done([=](const TLstarRevenueStatistics &result) { + const auto &data = result.data(); + const auto &status = data.vstatus().data(); + _data = Data::CreditsEarnStatistics{ + .revenueGraph = StatisticalGraphFromTL( + data.vrevenue_by_day_graph()), + .currentBalance = uint64(status.vcurrent_count().v), + .availableBalance = uint64(status.vavailable_count().v), + .overallRevenue = uint64(status.vtotal_count().v), + .usdRate = data.vusd_rate().v, + .isWithdrawalEnabled = status.vwithdrawal_enabled().v, + .nextWithdrawalAt = (status.vwithdrawal_enabled().v + ? QDateTime::currentDateTime().addSecs( + status.vnext_withdrawal_in().v) + : QDateTime()), + .buyAdsUrl = url, + }; + + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); }; +#if 0 // mtp makeRequest( MTPpayments_GetStarsRevenueAdsAccountUrl( (_isUser ? user()->input : channel()->input)) @@ -390,7 +669,14 @@ rpl::producer CreditsEarnStatistics::request() { }).fail([=](const MTP::Error &error) { finish({}); }).send(); - +#endif + api().request(TLgetStarAdAccountUrl( + peerToSender(_isUser ? user()->id : channel()->id) + )).done([=](const TLhttpUrl &result) { + finish(result.data().vurl().v); + }).fail([=](const Error &error) { + finish({}); + }).send(); return lifetime; }; } @@ -408,22 +694,35 @@ rpl::producer CreditsGiveawayOptions::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); +#if 0 // mtp using TLOption = MTPStarsGiveawayOption; +#endif const auto optionsFromTL = [=](const auto &options) { return ranges::views::all( options ) | ranges::views::transform([=](const auto &option) { + const TLDstarGiveawayPaymentOption &data = option.data(); return Data::CreditsGiveawayOption{ .winners = ranges::views::all( +#if 0 // mtp option.data().vwinners().v +#endif + option.data().vwinner_options().v ) | ranges::views::transform([](const auto &winner) { return Data::CreditsGiveawayOption::Winner{ +#if 0 // mtp .users = winner.data().vusers().v, .perUserStars = winner.data().vper_user_stars().v, .isDefault = winner.data().is_default(), +#endif + .users = winner.data().vwinner_count().v, + .perUserStars = uint64( + winner.data().vwon_star_count().v), + .isDefault = winner.data().vis_default().v, }; }) | ranges::to_vector, +#if 0 // mtp .storeProduct = qs( option.data().vstore_product().value_or_empty()), .currency = qs(option.data().vcurrency()), @@ -432,15 +731,31 @@ rpl::producer CreditsGiveawayOptions::request() { .yearlyBoosts = option.data().vyearly_boosts().v, .isExtended = option.data().is_extended(), .isDefault = option.data().is_default(), +#endif + .storeProduct = data.vstore_product_id().v, + .currency = data.vcurrency().v, + .amount = uint64(data.vamount().v), + .credits = uint64(data.vstar_count().v), + .yearlyBoosts = data.vyearly_boost_count().v, + .isExtended = data.vis_additional().v, + .isDefault = data.vis_default().v, }; }) | ranges::to_vector; }; +#if 0 // mtp const auto fail = [=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }; _api.request(MTPpayments_GetStarsGiveawayOptions( )).done([=](const MTPVector &result) { +#endif + const auto fail = [=](const Error &error) { + consumer.put_error_copy(error.message); + }; + _api.request(TLgetStarGiveawayPaymentOptions( + )).done([=](const TLstarGiveawayPaymentOptions &got) { + const auto &result = got.data().voptions(); _options = optionsFromTL(result.v); consumer.put_done(); }).fail(fail).send(); diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index d2e819e08327fc..5684dee0356505 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -83,7 +83,11 @@ class CreditsHistory final { private: using HistoryTL = MTPpayments_GetStarsTransactions; const not_null _peer; +#if 0 // mtp const HistoryTL::Flags _flags; +#endif + const bool _in = false; + const bool _out = false; mtpRequestId _requestId = 0; diff --git a/Telegram/SourceFiles/api/api_earn.cpp b/Telegram/SourceFiles/api/api_earn.cpp index f05e5f61881930..0c486c6f11a39a 100644 --- a/Telegram/SourceFiles/api/api_earn.cpp +++ b/Telegram/SourceFiles/api/api_earn.cpp @@ -18,12 +18,25 @@ For license and copyright information please follow this link: #include "ui/basic_click_handlers.h" #include "ui/widgets/buttons.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Api { +using namespace Tdb; + void RestrictSponsored( not_null channel, bool restricted, Fn failed) { + channel->session().sender().request( + TLtoggleSupergroupCanHaveSponsoredMessages( + tl_int53(peerToChannel(channel->id).bare), + tl_bool(!restricted)) + ).fail([=](const Error &error) { + failed(error.message); + }).send(); +#if 0 // mtp channel->session().api().request(MTPchannels_RestrictSponsoredMessages( channel->inputChannel, MTP_bool(restricted)) @@ -32,6 +45,7 @@ void RestrictSponsored( }).fail([=](const MTP::Error &error) { failed(error.type()); }).send(); +#endif } void HandleWithdrawalButton( @@ -88,6 +102,25 @@ void HandleWithdrawalButton( } } }; + const auto ddone = [=](const TLhttpUrl &result) { + done(result.data().vurl().v); + }; + const auto fail = [=](const Error &error) { + show->showToast(error.message); + }; + if (channel) { + session->sender().request(TLgetChatRevenueWithdrawalUrl( + peerToTdbChat(channel->id), + tl_string(result.password) + )).done(ddone).fail(fail).send(); + } else if (peer) { + session->sender().request(TLgetStarWithdrawalUrl( + peerToSender(peer->id), + tl_int53(receiver.creditsAmount()), + tl_string(result.password) + )).done(ddone).fail(fail).send(); + } +#if 0 // mtp const auto fail = [=](const MTP::Error &error) { show->showToast(error.type()); }; @@ -109,6 +142,7 @@ void HandleWithdrawalButton( done(qs(r.data().vurl())); }).fail(fail).send(); } +#endif }); show->show(Box(session, fields)); }); @@ -117,6 +151,8 @@ void HandleWithdrawalButton( if (state->loading) { return; } + processOut(); +#if 0 // mtp const auto fail = [=](const MTP::Error &error) { auto box = PrePasswordErrorBox( error.type(), @@ -145,6 +181,7 @@ void HandleWithdrawalButton( MTP_inputCheckPasswordEmpty() )).fail(fail).send(); } +#endif }); } diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 662bfc98013be9..2501fcf6d72208 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -24,11 +24,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>; @@ -141,6 +147,7 @@ mtpRequestId EditMessage( t_bad_callback(fail); } }).send(); + return 0; } template @@ -196,17 +203,26 @@ 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 = [] {}; options.invertCaption = item->invertMedia(); EditMessage(item, options, empty, empty); +#endif } +#if 0 // mtp void EditMessageWithUploadedDocument( HistoryItem *item, RemoteFileInfo info, @@ -232,6 +248,7 @@ void EditMessageWithUploadedPhoto( options, PrepareUploadedPhoto(item, std::move(info))); } +#endif mtpRequestId EditCaption( not_null item, @@ -239,6 +256,19 @@ 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), + tl_bool(options.invertCaption) + )).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, @@ -246,6 +276,7 @@ mtpRequestId EditCaption( options, done, fail); +#endif } mtpRequestId EditTextMessage( @@ -256,6 +287,33 @@ mtpRequestId EditTextMessage( Fn done, Fn fail, bool spoilered) { + 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 media = item->media(); if (media && HistoryView::MediaEditManager::CanBeSpoilered(item) @@ -348,6 +406,7 @@ mtpRequestId EditTextMessage( callback, fail, std::nullopt); +#endif } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_editing.h b/Telegram/SourceFiles/api/api_editing.h index 630e1cd8d5a408..aa9b6b460bf5a7 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 83ad8d15657d25..1f00931bf4ab26 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_app_config.h" @@ -18,6 +20,29 @@ GlobalPrivacy::GlobalPrivacy(not_null api) , _api(&api->instance()) { } +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; + } else if (option.vname().v == u"is_paid_reaction_anonymous"_q) { + _paidReactionAnonymous = 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)); @@ -46,6 +71,48 @@ void GlobalPrivacy::reload(Fn callback) { false); }, _session->lifetime()); } +#endif + +void GlobalPrivacy::reload(Fn callback) { + if (callback) { + _callbacks.push_back(std::move(callback)); + } + if (_requesting) { + return; + } + _requesting = true; + + const auto finish = [=] { + _requesting = false; + for (const auto &callback : base::take(_callbacks)) { + callback(); + } + }; + + const auto requestNewChats = [=] { + _api.request(Tdb::TLgetNewChatPrivacySettings( + )).done([=](const Tdb::TLDnewChatPrivacySettings &data) { + _newRequirePremium + = !data.vallow_new_chats_from_unknown_users().v; + finish(); + }).fail(finish).send(); + }; + + const auto requestReadDate = [=] { + _api.request(Tdb::TLgetReadDatePrivacySettings( + )).done([=](const Tdb::TLDreadDatePrivacySettings &data) { + _hideReadTime = !data.vshow_read_date().v; + requestNewChats(); + }).fail(requestNewChats).send(); + }; + + _api.request(Tdb::TLgetArchiveChatListSettings( + )).done([=](const Tdb::TLDarchiveChatListSettings &data) { + _archiveAndMute + = data.varchive_and_mute_new_chats_from_unknown_users().v; + requestReadDate(); + }).fail(requestReadDate).send(); +} bool GlobalPrivacy::archiveAndMuteCurrent() const { return _archiveAndMute.current(); @@ -120,10 +187,12 @@ void GlobalPrivacy::loadPaidReactionAnonymous() { return; } _paidReactionAnonymousLoaded = true; +#if 0 // mtp _api.request(MTPmessages_GetPaidReactionPrivacy( )).done([=](const MTPUpdates &result) { _session->api().applyUpdates(result); }).send(); +#endif } void GlobalPrivacy::updatePaidReactionAnonymous(bool value) { @@ -155,6 +224,7 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage( newRequirePremiumCurrent()); } +#if 0 // goodToRemove void GlobalPrivacy::update( bool archiveAndMute, UnarchiveOnNewMessage unarchiveOnNewMessage, @@ -195,7 +265,29 @@ void GlobalPrivacy::update( _hideReadTime = hideReadTime; _newRequirePremium = newRequirePremium; } +#endif + +void GlobalPrivacy::update( + bool archiveAndMute, + UnarchiveOnNewMessage unarchiveOnNewMessage, + bool hideReadTime, + bool newRequirePremium) { + 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(); + _api.request(Tdb::TLsetReadDatePrivacySettings( + Tdb::tl_readDatePrivacySettings(Tdb::tl_bool(!hideReadTime)) + )).send(); + _api.request(Tdb::TLsetNewChatPrivacySettings( + Tdb::tl_newChatPrivacySettings(Tdb::tl_bool(!newRequirePremium)) + )).send(); +} +#if 0 // goodToRemove void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { data.match([&](const MTPDglobalPrivacySettings &data) { _archiveAndMute = data.is_archive_and_mute_new_noncontact_peers(); @@ -208,5 +300,6 @@ void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { _newRequirePremium = data.is_new_noncontact_peers_require_premium(); }); } +#endif } // namespace Api diff --git a/Telegram/SourceFiles/api/api_global_privacy.h b/Telegram/SourceFiles/api/api_global_privacy.h index aa7bb28fe3050a..b5aa44feb87662 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.h +++ b/Telegram/SourceFiles/api/api_global_privacy.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" +namespace Tdb { +class TLDupdateOption; +} // namespace Tdb + class ApiWrap; namespace Main { @@ -54,8 +58,12 @@ class GlobalPrivacy final { [[nodiscard]] bool paidReactionAnonymousCurrent() const; [[nodiscard]] rpl::producer paidReactionAnonymous() const; + bool apply(const Tdb::TLDupdateOption &option); + private: +#if 0 // goodToRemove void apply(const MTPGlobalPrivacySettings &data); +#endif void update( bool archiveAndMute, @@ -65,7 +73,9 @@ class GlobalPrivacy final { const not_null _session; MTP::Sender _api; +#if 0 // goodToRemove mtpRequestId _requestId = 0; +#endif rpl::variable _archiveAndMute = false; rpl::variable _unarchiveOnNewMessage = UnarchiveOnNewMessage::None; @@ -76,6 +86,8 @@ class GlobalPrivacy final { std::vector> _callbacks; bool _paidReactionAnonymousLoaded = false; + bool _requesting = false; + }; } // namespace Api diff --git a/Telegram/SourceFiles/api/api_invite_links.cpp b/Telegram/SourceFiles/api/api_invite_links.cpp index c896561032b885..53064c7d5e8fd8 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) { } @@ -89,6 +128,7 @@ void InviteLinks::performCreate( } const auto requestApproval = !args.subscription && args.requestApproval; +#if 0 // goodToRemove using Flag = MTPmessages_ExportChatInvite::Flag; _api->request(MTPmessages_ExportChatInvite( MTP_flags((revokeLegacyPermanent @@ -109,6 +149,14 @@ void InviteLinks::performCreate( MTP_int(args.subscription.period), MTP_long(args.subscription.credits)) )).done([=, peer = args.peer](const MTPExportedChatInvite &result) { +#endif + _api->sender().request(TLcreateChatInviteLink( + peerToTdbChat(args.peer->id), + tl_string(args.label), + tl_int32(args.expireDate), + tl_int32(args.usageLimit), + tl_bool(requestApproval) + )).done([=, peer = args.peer](const TLchatInviteLink &result) { const auto callbacks = _createCallbacks.take(peer); const auto link = prepend(peer, peer->session().user(), result); if (link && callbacks) { @@ -143,7 +191,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; @@ -260,6 +311,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)) @@ -282,6 +334,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; } @@ -312,6 +370,7 @@ void InviteLinks::performEdit( .now = link, }); +#if 0 // goodToRemove using Replaced = MTPDmessages_exportedChatInviteReplaced; if constexpr (Replaced::Is()) { prepend(peer, admin, data.vnew_invite()); @@ -320,6 +379,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( @@ -364,9 +454,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) { @@ -399,9 +494,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) { @@ -416,6 +516,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, @@ -424,6 +525,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); @@ -468,12 +578,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) { @@ -484,7 +601,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. @@ -599,6 +718,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, @@ -608,6 +738,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); @@ -619,7 +750,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: " @@ -720,12 +854,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; @@ -737,11 +880,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{ @@ -767,6 +927,7 @@ auto InviteLinks::parse( }, [&](const MTPDchatInvitePublicJoinRequests &data) { return std::optional(); }); +#endif } void InviteLinks::requestMoreLinks( @@ -776,6 +937,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 @@ -786,6 +956,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 dd52feb995840a..30aa82af00dcd4 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; #include "data/data_subscriptions.h" @@ -54,7 +61,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 struct CreateInviteLinkArgs { not_null peer; @@ -112,7 +125,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); @@ -170,17 +186,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 22fc376312a9f8..533968aee8c77b 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" @@ -137,3 +138,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 87912e9b7928ca..49b857b77cd198 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 4fbcaebf018901..548060e35a0e16 100644 --- a/Telegram/SourceFiles/api/api_messages_search.cpp +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -17,11 +17,30 @@ 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" +#include "data/data_saved_messages.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) { @@ -43,6 +62,7 @@ constexpr auto kSearchPerPage = 50; } return result; } +#endif [[nodiscard]] QString RequestToToken( const MessagesSearch::Request &request) { @@ -68,18 +88,22 @@ 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(Request request) { _request = std::move(request); _offsetId = {}; + _full = false; searchRequest(); } void MessagesSearch::searchMore() { - if (_searchInHistoryRequest || _requestId) { + if (_searchInHistoryRequest || _requestId || _full) { return; } searchRequest(); @@ -95,6 +119,33 @@ void MessagesSearch::searchRequest() { return; } } + if (_requestId) { + _history->session().sender().request(_requestId).cancel(); + } + const auto from = _request.from; + const auto fromPeer = _history->peer->isUser() ? nullptr : from; + const auto savedPeer = _history->peer->isSelf() ? from : nullptr; + _requestId = _history->session().sender().request(TLsearchChatMessages( + peerToTdbChat(_history->peer->id), + tl_string(_request.query), + (fromPeer + ? peerToSender(fromPeer->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 + tl_int53(_history->owner().savedMessages().sublistId(savedPeer)) + )).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) { using Flag = MTPmessages_Search::Flag; const auto from = _request.from; @@ -143,12 +194,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; } @@ -195,13 +254,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 76046aaa91aacd..e82153c910a935 100644 --- a/Telegram/SourceFiles/api/api_messages_search.h +++ b/Telegram/SourceFiles/api/api_messages_search.h @@ -10,6 +10,10 @@ For license and copyright information please follow this link: #include "base/qt/qt_compare.h" #include "data/data_message_reaction_id.h" +namespace Tdb { +class TLfoundChatMessages; +} // namespace Tdb + class HistoryItem; class History; class PeerData; @@ -24,6 +28,7 @@ struct FoundMessages { int total = -1; MessageIdsList messages; QString nextToken; + bool full = false; }; class MessagesSearch final { @@ -50,7 +55,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, @@ -67,6 +75,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 8451232f6089d8..232001ca6e7535 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.cpp +++ b/Telegram/SourceFiles/api/api_messages_search_merged.cpp @@ -32,6 +32,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_colors.cpp b/Telegram/SourceFiles/api/api_peer_colors.cpp index 5f3fe12d202bef..c3132ace7f7e8c 100644 --- a/Telegram/SourceFiles/api/api_peer_colors.cpp +++ b/Telegram/SourceFiles/api/api_peer_colors.cpp @@ -11,22 +11,30 @@ For license and copyright information please follow this link: #include "data/data_peer.h" #include "ui/chat/chat_style.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + constexpr auto kRequestEach = 3600 * crl::time(1000); } // namespace PeerColors::PeerColors(not_null api) +#if 0 // mtp : _api(&api->instance()) , _timer([=] { request(); }) { request(); _timer.callEach(kRequestEach); +#endif +{ } PeerColors::~PeerColors() = default; +#if 0 // mtp void PeerColors::request() { if (_requestId) { return; @@ -44,12 +52,17 @@ void PeerColors::request() { _requestId = 0; }).send(); } +#endif std::vector PeerColors::suggested() const { return _suggested.current(); } rpl::producer> PeerColors::suggestedValue() const { + if (_suggested.current().empty()) { + const_cast(this)->_suggested + = std::vector{ 0, 1, 2, 3, 4, 5, 6 }; + } return _suggested.value(); } @@ -96,6 +109,7 @@ int PeerColors::requiredChannelLevelFor(PeerId channel, uint8 index) const { return 1; } +#if 0 // mtp void PeerColors::apply(const MTPDhelp_peerColors &data) { auto suggested = std::vector(); auto colors = std::make_shared< @@ -164,5 +178,58 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) { } _suggested = std::move(suggested); } +#endif + +void PeerColors::apply(const TLDupdateAccentColors &update) { + using ThemeColors = std::array; + constexpr auto parseColors = [](const TLvector &values) { + const auto &list = values.v; + const auto count = int(list.size()); + if (count > Ui::kColorPatternsCount) { + LOG(("API Error: Bad accentColor list size: %1").arg(count)); + return ThemeColors(); + } + auto result = ThemeColors(); + for (auto i = 0; i != count; ++i) { + result[i] = list[i].v; + } + return result; + }; + const auto parseColorIndex = [&](const TLDaccentColor &data) { + return Ui::ColorIndexData{ + .light = parseColors(data.vlight_theme_colors()), + .dark = parseColors(data.vdark_theme_colors()), + }; + }; + auto colors = std::make_shared< + std::array>(); + for (const auto &color : update.vcolors().v) { + const auto &data = color.data(); + const auto index = data.vid().v; + if (index < Ui::kSimpleColorIndexCount + || index >= Ui::kColorIndexCount) { + LOG(("API Error: Bad index for accentColor: %1").arg(index)); + continue; + } + (*colors)[index] = parseColorIndex(data); + + if (const auto min = data.vmin_channel_chat_boost_level().v) { + _requiredLevelsChannel[min] = min; + } + } + + if (!_colorIndicesCurrent) { + _colorIndicesCurrent = std::make_unique( + Ui::ColorIndicesCompressed{ std::move(colors) }); + _colorIndicesChanged.fire({}); + } else if (*_colorIndicesCurrent->colors != *colors) { + _colorIndicesCurrent->colors = std::move(colors); + _colorIndicesChanged.fire({}); + } + + _suggested = update.vavailable_accent_color_ids().v + | ranges::views::transform([](TLint32 v) { return uint8(v.v); }) + | ranges::to_vector; +} } // namespace Api diff --git a/Telegram/SourceFiles/api/api_peer_colors.h b/Telegram/SourceFiles/api/api_peer_colors.h index f8d379020bfa1d..cbd05bf95846c2 100644 --- a/Telegram/SourceFiles/api/api_peer_colors.h +++ b/Telegram/SourceFiles/api/api_peer_colors.h @@ -8,7 +8,13 @@ For license and copyright information please follow this link: #pragma once #include "base/timer.h" +#if 0 // mtp #include "mtproto/sender.h" +#endif + +namespace Tdb { +class TLDupdateAccentColors; +} // namespace Tdb class ApiWrap; @@ -41,7 +47,10 @@ class PeerColors final { PeerId channel, uint8 index) const; + void apply(const Tdb::TLDupdateAccentColors &update); + private: +#if 0 // mtp void request(); void apply(const MTPDhelp_peerColors &data); @@ -50,6 +59,7 @@ class PeerColors final { mtpRequestId _requestId = 0; base::Timer _timer; +#endif rpl::variable> _suggested; base::flat_map _requiredLevelsGroup; base::flat_map _requiredLevelsChannel; diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp index 8e54c8248de201..e568302e15c7d9 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.cpp +++ b/Telegram/SourceFiles/api/api_peer_photo.cpp @@ -23,12 +23,14 @@ 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_sender.h" +#include "tdb/tdb_file_generator.h" #include "tdb/tdb_tl_scheme.h" +#include "data/data_wall_paper.h" #include @@ -39,6 +41,7 @@ using namespace Tdb; constexpr auto kSharedMediaLimit = 100; +#if 0 // mtp [[nodiscard]] std::shared_ptr PreparePeerPhoto( MTP::DcId dcId, PeerId peerId, @@ -148,12 +151,42 @@ 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()) , _api(&api->instance()) { +#if 0 // goodToRemove crl::on_main(_session, [=] { // You can't use _session->lifetime() in the constructor, // only queued, because it is not constructed yet. @@ -162,6 +195,7 @@ PeerPhoto::PeerPhoto(not_null api) ready(data.fullId, data.info.file, std::nullopt); }, _session->lifetime()); }); +#endif } void PeerPhoto::upload( @@ -179,6 +213,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( @@ -206,6 +250,7 @@ void PeerPhoto::updateSelf( }).send(); }; send(send); +#endif } void PeerPhoto::upload( @@ -214,6 +259,7 @@ void PeerPhoto::upload( UploadType type, Fn done) { peer = peer->migrateToOrMe(); +#if 0 // goodToRemove const auto mtpMarkup = PrepareMtpMarkup(_session, photo); const auto fakeId = FullMsgId( @@ -239,6 +285,70 @@ void PeerPhoto::upload( base::take(photo.image)); _session->uploader().upload(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) { @@ -246,8 +356,8 @@ void PeerPhoto::suggest(not_null peer, UserPhoto &&photo) { } void PeerPhoto::clear(not_null photo) { -#if 0 // #TODO legacy const auto self = _session->user(); +#if 0 // mtp if (self->userpicPhotoId() == photo->id) { _api.request(MTPphotos_UpdateProfilePhoto( MTP_flags(0), @@ -294,25 +404,37 @@ void PeerPhoto::clear(not_null photo) { #endif if (_session->user()->userpicPhotoId() == photo->id) { - _session->sender().request(TLdeleteProfilePhoto( + _api.request(TLdeleteProfilePhoto( tl_int64(photo->id) )).send(); } else if (photo->peer && photo->peer->userpicPhotoId() == photo->id) { - _session->sender().request(TLsetChatPhoto( + _api.request(TLsetChatPhoto( peerToTdbChat(photo->peer->id), null )).send(); } else { - _session->sender().request(TLdeleteProfilePhoto( + const auto fallbackPhotoId = SyncUserFallbackPhotoViewer(self); + _api.request(TLdeleteProfilePhoto( tl_int64(photo->id) )).send(); - _session->storage().remove(Storage::UserPhotosRemoveOne( - peerToUser(_session->userPeerId()), - photo->id)); + 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, @@ -326,6 +448,7 @@ void PeerPhoto::clearPersonal(not_null user) { _session->data().processUsers(data.vusers()); }); }).send(); +#endif if (!user->userpicPhotoUnknown() && user->hasPersonalPhoto()) { _session->storage().remove(Storage::UserPhotosRemoveOne( @@ -338,6 +461,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), @@ -365,8 +500,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, @@ -472,6 +609,7 @@ void PeerPhoto::ready( }).send(); } } +#endif void PeerPhoto::requestUserPhotos( not_null user, @@ -480,6 +618,7 @@ void PeerPhoto::requestUserPhotos( return; } +#if 0 // goodToRemove const auto requestId = _api.request(MTPphotos_GetUserPhotos( user->inputUser, MTP_int(0), @@ -493,10 +632,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()); @@ -548,6 +697,7 @@ void PeerPhoto::requestEmojiList(EmojiListType type) { if (list.requestId) { return; } +#if 0 // mtp const auto send = [&](auto &&request) { return _api.request( std::move(request) @@ -573,6 +723,33 @@ void PeerPhoto::requestEmojiList(EmojiListType type) { : (type == EmojiListType::NoChannelStatus) ? send(MTPaccount_GetChannelRestrictedStatusEmojis()) : 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 71d340a031f6b1..45106c432d50c1 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.h +++ b/Telegram/SourceFiles/api/api_peer_photo.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" +namespace Tdb { +class FileGenerator; +} // namespace Tdb + class ApiWrap; class PeerData; class UserData; @@ -25,7 +29,7 @@ namespace Api { class PeerPhoto final { public: - using UserPhotoId = PhotoId; + using UserPhotoId = uint; explicit PeerPhoto(not_null api); enum class EmojiListType { @@ -80,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, @@ -97,12 +103,18 @@ class PeerPhoto final { MTP::Sender _api; 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 + 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 52a5f6d6fd0414..88db9c77bc5b20 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/business/data_shortcut_messages.h" @@ -21,6 +22,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 { @@ -28,6 +31,37 @@ 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 FormattedTextToTdb(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( + FormattedTextToTdb(poll->question), + Tdb::tl_vector(std::move(options)), + Tdb::tl_bool(!poll->publicVotes()), + std::move(type)); +} + } // namespace Polls::Polls(not_null api) @@ -40,8 +74,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 @@ -114,6 +157,7 @@ void Polls::create( } fail(); }); +#endif } void Polls::sendVotes( @@ -143,6 +187,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( @@ -162,6 +227,7 @@ void Polls::sendVotes( hideSending(); }).send(); _pollVotesRequestIds.emplace(itemId, requestId); +#endif } void Polls::close(not_null item) { @@ -174,6 +240,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, @@ -191,9 +267,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; @@ -208,6 +287,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_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 84ce45783b9e48..385b2597b4dda5 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" @@ -24,9 +25,30 @@ 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 = (data.vcreator_id() + ? peerFromSender(*data.vcreator_id()) + : PeerId()), + .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 = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(), @@ -38,27 +60,144 @@ namespace { .giveaway = data.is_via_giveaway(), }; } +#endif +#if 0 // mtp [[nodiscard]] Data::PremiumSubscriptionOptions GiftCodesFromTL( const QVector &tlOptions) { +#endif +[[nodiscard]] Data::PremiumSubscriptionOptions GiftCodesFromTL( + const QVector &tlOptions) { auto options = PremiumSubscriptionOptionsFromTL(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.vwinner_count().v), + tlOption.vcurrency().v.toUtf8(), false); options[i].costPerMonth = perUserText + ' ' + QChar(0x00D7) + ' ' + + QString::number(tlOption.vwinner_count().v); +#if 0 // mtp + QString::number(tlOption.vusers().v); +#endif } return options; } } // namespace +std::optional FeatureFromTL( + const TLpremiumFeature &feature) { + const auto result = feature.match([]( + const TLDpremiumFeatureUpgradedStories &) { + return PremiumFeature::Stories; + }, [](const TLDpremiumFeatureChatBoost &) { + return PremiumFeature::kCount; + }, [](const TLDpremiumFeatureAccentColor &) { + return PremiumFeature::kCount; + }, [](const TLDpremiumFeatureIncreasedLimits &) { + return PremiumFeature::DoubleLimits; + }, [](const TLDpremiumFeatureIncreasedUploadFileSize &) { + return PremiumFeature::MoreUpload; + }, [](const TLDpremiumFeatureImprovedDownloadSpeed &) { + return PremiumFeature::FasterDownload; + }, [](const TLDpremiumFeatureVoiceRecognition &) { + return PremiumFeature::VoiceToText; + }, [](const TLDpremiumFeatureDisabledAds &) { + return PremiumFeature::NoAds; + }, [](const TLDpremiumFeatureUniqueReactions &) { + return PremiumFeature::InfiniteReactions; + }, [](const TLDpremiumFeatureUniqueStickers &) { + return PremiumFeature::Stickers; + }, [](const TLDpremiumFeatureCustomEmoji &) { + return PremiumFeature::AnimatedEmoji; + }, [](const TLDpremiumFeatureAdvancedChatManagement &) { + return PremiumFeature::AdvancedChatManagement; + }, [](const TLDpremiumFeatureProfileBadge &) { + return PremiumFeature::ProfileBadge; + }, [](const TLDpremiumFeatureEmojiStatus &) { + return PremiumFeature::EmojiStatus; + }, [](const TLDpremiumFeatureAnimatedProfilePhoto &) { + return PremiumFeature::AnimatedUserpics; + }, [](const TLDpremiumFeatureForumTopicIcon &) { + return PremiumFeature::kCount; + }, [](const TLDpremiumFeatureAppIcons &) { + return PremiumFeature::kCount; + }, [](const TLDpremiumFeatureRealTimeChatTranslation &) { + return PremiumFeature::RealTimeTranslation; + }, [](const TLDpremiumFeatureBackgroundForBoth &) { + return PremiumFeature::Wallpapers; + }, [](const TLDpremiumFeatureSavedMessagesTags &) { + return PremiumFeature::TagsForMessages; + }, [](const TLDpremiumFeatureMessagePrivacy &) { + return PremiumFeature::MessagePrivacy; + }, [](const TLDpremiumFeatureLastSeenTimes &) { + return PremiumFeature::LastSeen; + }, [](const TLDpremiumFeatureBusiness &) { + return PremiumFeature::Business; + }, [](const TLDpremiumFeatureMessageEffects &) { + return PremiumFeature::Effects; + }); + return (result != PremiumFeature::kCount) + ? result + : std::optional(); +} + +TLpremiumFeature FeatureToTL(PremiumFeature preview) { + Expects(preview != PremiumFeature::kCount); + + switch (preview) { + case PremiumFeature::Stories: + return tl_premiumFeatureUpgradedStories(); + case PremiumFeature::DoubleLimits: + return tl_premiumFeatureIncreasedLimits(); + case PremiumFeature::MoreUpload: + return tl_premiumFeatureIncreasedUploadFileSize(); + case PremiumFeature::FasterDownload: + return tl_premiumFeatureImprovedDownloadSpeed(); + case PremiumFeature::VoiceToText: + return tl_premiumFeatureVoiceRecognition(); + case PremiumFeature::NoAds: + return tl_premiumFeatureDisabledAds(); + case PremiumFeature::InfiniteReactions: + return tl_premiumFeatureUniqueReactions(); + case PremiumFeature::Stickers: + return tl_premiumFeatureUniqueStickers(); + case PremiumFeature::AnimatedEmoji: + return tl_premiumFeatureCustomEmoji(); + case PremiumFeature::AdvancedChatManagement: + return tl_premiumFeatureAdvancedChatManagement(); + case PremiumFeature::ProfileBadge: + return tl_premiumFeatureProfileBadge(); + case PremiumFeature::EmojiStatus: + return tl_premiumFeatureEmojiStatus(); + case PremiumFeature::AnimatedUserpics: + return tl_premiumFeatureAnimatedProfilePhoto(); + case PremiumFeature::RealTimeTranslation: + return tl_premiumFeatureRealTimeChatTranslation(); + case PremiumFeature::Wallpapers: + return tl_premiumFeatureBackgroundForBoth(); + case PremiumFeature::TagsForMessages: + return tl_premiumFeatureSavedMessagesTags(); + case PremiumFeature::MessagePrivacy: + return tl_premiumFeatureMessagePrivacy(); + case PremiumFeature::LastSeen: + return tl_premiumFeatureLastSeenTimes(); + case PremiumFeature::Business: + return tl_premiumFeatureBusiness(); + case PremiumFeature::Effects: + return tl_premiumFeatureMessageEffects(); + } + Unexpected("PremiumFeature value in PreviewToFeature."); +} + Premium::Premium(not_null api) : _session(&api->session()) , _api(&api->instance()) { @@ -82,7 +221,10 @@ rpl::producer Premium::statusTextValue() const { } auto Premium::videos() const +-> const base::flat_map> & { +#if 0 // mtp -> const base::flat_map> & { +#endif return _videos; } @@ -137,6 +279,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 = PremiumSubscriptionOptionsFromTL(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< + PremiumFeature, + 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 = FeatureFromTL(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; @@ -180,12 +371,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) @@ -207,12 +417,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) @@ -221,16 +433,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(); @@ -240,6 +456,7 @@ void Premium::reloadHelloStickers() { if (_helloStickersRequestId) { return; } +#if 0 // mtp _helloStickersRequestId = _api.request(MTPmessages_GetStickers( MTP_string("\xf0\x9f\x91\x8b\xe2\xad\x90\xef\xb8\x8f"), MTP_long(_helloStickersHash) @@ -248,6 +465,11 @@ void Premium::reloadHelloStickers() { result.match([&](const MTPDmessages_stickersNotModified &) { }, [&](const MTPDmessages_stickers &data) { _helloStickersHash = data.vhash().v; +#endif + _helloStickersRequestId = _api.request(TLgetGreetingStickers( + )).done([=](const TLstickers &result) { + _helloStickersRequestId = 0; + const auto &data = result.data(); const auto owner = &_session->data(); _helloStickers.clear(); for (const auto &sticker : data.vstickers().v) { @@ -257,7 +479,9 @@ void Premium::reloadHelloStickers() { } } _helloStickersUpdated.fire({}); +#if 0 // mtp }); +#endif }).fail([=] { _helloStickersRequestId = 0; }).send(); @@ -273,6 +497,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) { @@ -287,6 +521,7 @@ void Premium::checkGiftCode( done(updateGiftCode(slug, {})); }).send(); +#endif } GiftCode Premium::updateGiftCode( @@ -310,6 +545,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) { @@ -318,6 +561,7 @@ void Premium::applyGiftCode(const QString &slug, Fn done) { }).fail([=](const MTP::Error &error) { done(error.type()); }).send(); +#endif } void Premium::resolveGiveawayInfo( @@ -336,6 +580,7 @@ void Premium::resolveGiveawayInfo( } _giveawayInfoPeer = peer; _giveawayInfoMessageId = messageId; +#if 0 // mtp _giveawayInfoRequestId = _api.request(MTPpayments_GetGiveawayInfo( _giveawayInfoPeer->input, MTP_int(_giveawayInfoMessageId.bare) @@ -366,6 +611,49 @@ void Premium::resolveGiveawayInfo( info.startDate = data.vstart_date().v; info.credits = data.vstars_prize().value_or_empty(); }); +#endif + _giveawayInfoRequestId = _api.request(TLgetGiveawayInfo( + peerToTdbChat(_giveawayInfoPeer->id), + tl_int53(_giveawayInfoMessageId.bare) + )).done([=](const TLgiveawayInfo &result) { + _giveawayInfoRequestId = 0; + + auto info = GiveawayInfo(); + result.match([&](const TLDgiveawayInfoOngoing &data) { + using AlreadyWasMember + = TLDgiveawayParticipantStatusAlreadyWasMember; + using Participating + = TLDgiveawayParticipantStatusParticipating; + using Administrator + = TLDgiveawayParticipantStatusAdministrator; + using DisallowedCountry + = TLDgiveawayParticipantStatusDisallowedCountry; + + data.vstatus().match([&]( + const TLDgiveawayParticipantStatusEligible &) { + }, [&](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 TLDgiveawayInfoCompleted &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; @@ -399,12 +687,16 @@ void Premium::requestPremiumRequiredSlice() { return; } constexpr auto kPerRequest = 100; +#if 0 // mtp auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers | ranges::views::transform(&UserData::inputUser)); if (users.v.size() > kPerRequest) { auto shortened = users.v; shortened.resize(kPerRequest); users = MTP_vector(std::move(shortened)); +#endif + auto users = _resolvePremiumRequiredUsers | ranges::to_vector; + if (users.size() > kPerRequest) { const auto from = begin(_resolvePremiumRequiredUsers); _resolvePremiumRequestedUsers = { from, from + kPerRequest }; _resolvePremiumRequiredUsers.erase(from, from + kPerRequest); @@ -412,6 +704,7 @@ void Premium::requestPremiumRequiredSlice() { _resolvePremiumRequestedUsers = base::take(_resolvePremiumRequiredUsers); } +#if 0 // mtp const auto finish = [=](const QVector &list) { constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite; constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown; @@ -441,17 +734,59 @@ void Premium::requestPremiumRequiredSlice() { }).fail([=] { finish({}); }).send(); +#endif + const auto waiting = std::make_shared( + _resolvePremiumRequestedUsers.size()); + for (const auto &user : _resolvePremiumRequestedUsers) { + const auto finish = [=](bool require) { + constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite; + constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown; + constexpr auto mask = me | known; + + user->setFlags((user->flags() & ~mask) + | known + | (require ? me : UserDataFlag())); + if (!--*waiting) { + if (!_premiumRequiredRequestScheduled + && !_resolvePremiumRequiredUsers.empty()) { + _premiumRequiredRequestScheduled = true; + crl::on_main(_session, [=] { + requestPremiumRequiredSlice(); + }); + } + _somePremiumRequiredResolved.fire({}); + } + }; + user->session().sender().request(TLcanSendMessageToUser( + tl_int53(peerToUser(user->id).bare), + tl_bool(false) + )).done([=](const TLcanSendMessageToUserResult &result) { + result.match([&](const TLDcanSendMessageToUserResultOk &) { + finish(false); + }, [&](const TLDcanSendMessageToUserResultUserIsDeleted &) { + finish(false); + }, [&](const TLDcanSendMessageToUserResultUserRestrictsNewChats &) { + finish(true); + }); + }).fail([=] { + finish(false); + }).send(); + } } PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null peer) : _peer(peer) +#if 0 // mtp , _api(&peer->session().api().instance()) { +#endif +, _api(&peer->session().sender()) { } rpl::producer PremiumGiftCodeOptions::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); +#if 0 // mtp using TLOption = MTPPremiumGiftCodeOption; _api.request(MTPpayments_GetPremiumGiftCodeOptions( MTP_flags(_peer->isChannel() @@ -491,6 +826,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.vwinner_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; }; @@ -506,6 +879,7 @@ rpl::producer PremiumGiftCodeOptions::applyPrepaid( return lifetime; } +#if 0 // mtp _api.request(MTPpayments_LaunchPrepaidGiveaway( _peer->input, MTP_long(prepaidId), @@ -518,6 +892,17 @@ rpl::producer PremiumGiftCodeOptions::applyPrepaid( }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + _api.request(Tdb::TLlaunchPrepaidGiveaway( + Tdb::tl_int64(prepaidId), + Payments::InvoiceGiftCodeGiveawayToTL(invoice), + tl_int32(invoice.users), + tl_int53(invoice.creditsAmount.value_or(0)) + )).done([=](const Tdb::TLok &) { + consumer.put_done(); + }).fail([=](const Tdb::Error &error) { + consumer.put_error_copy(error.message); + }).send(); return lifetime; }; @@ -573,6 +958,7 @@ Data::PremiumSubscriptionOptions 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( @@ -585,6 +971,18 @@ Data::PremiumSubscriptionOptions 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]; } } @@ -594,6 +992,7 @@ auto PremiumGiftCodeOptions::requestStarGifts() return [=](auto consumer) { auto lifetime = rpl::lifetime(); +#if 0 // mtp _api.request(MTPpayments_GetStarGifts( MTP_int(0) )).done([=](const MTPpayments_StarGifts &result) { @@ -615,6 +1014,24 @@ auto PremiumGiftCodeOptions::requestStarGifts() }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + _api.request(TLgetAvailableGifts( + )).done([=](const TLgifts &result) { + const auto &data = result.data(); + const auto &list = data.vgifts().v; + const auto session = &_peer->session(); + auto gifts = std::vector(); + gifts.reserve(list.size()); + for (const auto &gift : list) { + if (auto parsed = FromTL(session, gift)) { + gifts.push_back(std::move(*parsed)); + } + } + _gifts = std::move(gifts); + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); return lifetime; }; @@ -659,18 +1076,28 @@ bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const { } SponsoredToggle::SponsoredToggle(not_null session) +#if 0 // mtp : _api(&session->api().instance()) { +#endif +: _session(session) +, _api(&session->sender()) { } rpl::producer SponsoredToggle::toggled() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); + _api.request(TLgetUserFullInfo( + tl_int53(peerToUser(_session->userPeerId()).bare) + )).done([=](const TLDuserFullInfo &data) { + consumer.put_next_copy(data.vhas_sponsored_messages_enabled().v); +#if 0 // mtp _api.request(MTPusers_GetFullUser( MTP_inputUserSelf() )).done([=](const MTPusers_UserFull &result) { consumer.put_next_copy( result.data().vfull_user().data().is_sponsored_enabled()); +#endif }).fail([=] { consumer.put_next(false); }).send(); return lifetime; @@ -681,6 +1108,7 @@ rpl::producer SponsoredToggle::setToggled(bool v) { return [=](auto consumer) { auto lifetime = rpl::lifetime(); +#if 0 // mtp _api.request(MTPaccount_ToggleSponsoredMessages( MTP_bool(v) )).done([=] { @@ -688,6 +1116,14 @@ rpl::producer SponsoredToggle::setToggled(bool v) { }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + _api.request(TLtoggleHasSponsoredMessagesEnabled( + tl_bool(v) + )).done([=] { + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); return lifetime; }; @@ -758,6 +1194,7 @@ rpl::producer RandomHelloStickerValue( }) | rpl::take(1) | rpl::map(random)); } +#if 0 // mtp std::optional FromTL( not_null session, const MTPstarGift &gift) { @@ -809,5 +1246,47 @@ std::optional FromTL( .mine = to->isSelf(), }; } +#endif + +std::optional FromTL( + not_null session, + const TLgift &gift) { + const auto &data = gift.data(); + const auto document = session->data().processDocument( + data.vsticker()); + if (!document->sticker()) { + return {}; + } + return StarGift{ + .id = uint64(data.vid().v), + .stars = int64(data.vstar_count().v), + .convertStars = int64(data.vdefault_sell_star_count().v), + .document = document, + .limitedLeft = data.vremaining_count().v, + .limitedCount = data.vtotal_count().v, + }; +} + +std::optional FromTL( + not_null to, + const TLuserGift &gift) { + const auto session = &to->session(); + const auto &data = gift.data(); + auto parsed = FromTL(session, data.vgift()); + if (!parsed) { + return {}; + } + return UserStarGift{ + .gift = std::move(*parsed), + .message = Api::FormattedTextFromTdb(data.vtext()), + .convertStars = data.vsell_star_count().v, + .fromId = peerFromUser(data.vsender_user_id()), + .messageId = data.vmessage_id().v, + .date = data.vdate().v, + .anonymous = data.vis_private().v, + .hidden = !data.vis_saved().v, + .mine = to->isSelf(), + }; +} } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 805b68e03880ec..c0113ebd010452 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -10,6 +10,14 @@ For license and copyright information please follow this link: #include "data/data_premium_subscription_option.h" #include "mtproto/sender.h" +namespace Tdb { +class TLpremiumFeature; +class TLgift; +class TLuserGift; +} // namespace Tdb + +enum class PremiumFeature; + class History; class ApiWrap; @@ -94,6 +102,10 @@ struct UserStarGift { bool mine = false; }; +[[nodiscard]] std::optional FeatureFromTL( + const Tdb::TLpremiumFeature &feature); +[[nodiscard]] Tdb::TLpremiumFeature FeatureToTL(PremiumFeature preview); + class Premium final { public: explicit Premium(not_null api); @@ -102,7 +114,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 @@ -153,7 +168,10 @@ class Premium final { 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; @@ -260,6 +278,7 @@ class SponsoredToggle final { [[nodiscard]] rpl::producer setToggled(bool); private: + const not_null _session; MTP::Sender _api; }; @@ -276,11 +295,19 @@ enum class RequirePremiumState { [[nodiscard]] rpl::producer RandomHelloStickerValue( not_null session); +#if 0 // mtp [[nodiscard]] std::optional FromTL( not_null session, const MTPstarGift &gift); [[nodiscard]] std::optional FromTL( not_null to, const MTPuserStarGift &gift); +#endif +[[nodiscard]] std::optional FromTL( + not_null session, + const Tdb::TLgift &gift); +[[nodiscard]] std::optional FromTL( + not_null to, + const Tdb::TLuserGift &gift); } // namespace Api diff --git a/Telegram/SourceFiles/api/api_premium_option.h b/Telegram/SourceFiles/api/api_premium_option.h index afe66ac4acbf72..e8b62220f20c01 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_premium_subscription_option.h" +#include "tdb/tdb_tl_scheme.h" +#include "core/local_url_handlers.h" + namespace Api { [[nodiscard]] Data::PremiumSubscriptionOption 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(tlOpts.size()); for (const auto &tlOption : tlOpts) { 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 309e2d240cbf04..572418e0abc27c 100644 --- a/Telegram/SourceFiles/api/api_report.cpp +++ b/Telegram/SourceFiles/api/api_report.cpp @@ -17,10 +17,31 @@ For license and copyright information please follow this link: #include "ui/boxes/report_box_graphics.h" #include "ui/layers/show.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.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) { @@ -37,6 +58,7 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) { } Unexpected("Bad reason group value."); } +#endif } // namespace @@ -49,6 +71,21 @@ void SendReport( auto done = [=] { show->showToast(tr::lng_report_thanks(tr::now)); }; + v::match(data, [&](v::null_t) { + Unexpected("This won't be here."); + }, [&](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(); + } + }); +#if 0 // mtp v::match(data, [&](v::null_t) { peer->session().api().request(MTPaccount_ReportPeer( peer->input, @@ -63,6 +100,7 @@ void SendReport( MTP_string(comment) )).done(std::move(done)).send(); }); +#endif } auto CreateReportMessagesOrStoriesCallback( @@ -87,6 +125,7 @@ auto CreateReportMessagesOrStoriesCallback( return [=]( Data::ReportInput reportInput, Fn done) { +#if 0 // mtp auto apiIds = QVector(); apiIds.reserve(reportInput.ids.size() + reportInput.stories.size()); for (const auto &id : reportInput.ids) { @@ -148,6 +187,83 @@ auto CreateReportMessagesOrStoriesCallback( MTP_string(reportInput.comment)) ).done(received).fail(fail).send(); } +#endif + const auto fail = [=](const Error &error) { + state->requestId = 0; + done({ .error = error.message }); + }; + const auto optionRequired = [=](const auto &data) { + const auto t = data.vtitle().v; + auto list = Result::Options(); + list.reserve(data.voptions().v.size()); + for (const auto &tl : data.voptions().v) { + list.emplace_back(Result::Option{ + .id = tl.data().vid().v, + .text = tl.data().vtext().v, + }); + } + return Result{ .options = std::move(list), .title = t }; + }; + const auto textRequired = [=](const auto &data) { + return Result{ + .commentOption = ReportResult::CommentOption{ + .optional = data.vis_optional().v, + .id = data.voption_id().v, + }, + }; + }; + if (!reportInput.stories.empty()) { + Assert(reportInput.stories.size() == 1); + state->requestId = peer->session().sender().request( + TLreportStory( + peerToTdbChat(peer->id), + tl_int32(reportInput.stories.front()), + tl_bytes(reportInput.optionId), + tl_string(reportInput.comment)) + ).done([=]( + const TLreportStoryResult &result, + mtpRequestId requestId) { + if (state->requestId != requestId) { + return; + } + state->requestId = 0; + done(result.match([](const TLDreportStoryResultOk &) { + return Result{ .successful = true }; + }, [&](const TLDreportStoryResultOptionRequired &data) { + return optionRequired(data); + }, [&](const TLDreportStoryResultTextRequired &data) { + return textRequired(data); + })); + }).fail(fail).send(); + } else { + state->requestId = peer->session().sender().request( + TLreportChat( + peerToTdbChat(peer->id), + tl_bytes(reportInput.optionId), + tl_vector(ranges::views::all( + reportInput.ids + ) | ranges::views::transform([](MsgId id) { + return tl_int53(id.bare); + }) | ranges::to>()), + tl_string(reportInput.comment)) + ).done([=]( + const TLreportChatResult &result, + mtpRequestId requestId) { + if (state->requestId != requestId) { + return; + } + state->requestId = 0; + done(result.match([](const TLDreportChatResultOk &) { + return Result{ .successful = true }; + }, [&](const TLDreportChatResultOptionRequired &data) { + return optionRequired(data); + }, [&](const TLDreportChatResultTextRequired &data) { + return textRequired(data); + }, [&](const TLDreportChatResultMessagesRequired &) { + return Result{ .error = u"MESSAGE_ID_REQUIRED"_q }; + })); + }).fail(fail).send(); + } }; } diff --git a/Telegram/SourceFiles/api/api_ringtones.cpp b/Telegram/SourceFiles/api/api_ringtones.cpp index 307ba580a775a9..b31495357f8869 100644 --- a/Telegram/SourceFiles/api/api_ringtones.cpp +++ b/Telegram/SourceFiles/api/api_ringtones.cpp @@ -21,9 +21,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 { + std::shared_ptr PrepareRingtoneDocument( MTP::DcId dcId, const QString &filename, @@ -58,10 +68,12 @@ std::shared_ptr PrepareRingtoneDocument( } } // namespace +#endif Ringtones::Ringtones(not_null api) : _session(&api->session()) , _api(&api->instance()) { +#if 0 // goodToRemove crl::on_main(_session, [=] { // You can't use _session->lifetime() in the constructor, // only queued, because it is not constructed yet. @@ -70,12 +82,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, @@ -98,8 +112,43 @@ void Ringtones::upload( } _uploads.emplace(fakeId, uploadedData); _session->uploader().upload(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)); + + _api.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) { @@ -121,11 +170,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) { @@ -142,6 +193,19 @@ void Ringtones::requestList() { _list.updates.fire({}); }, [&](const MTPDaccount_savedRingtonesNotModified &) { }); +#endif + _list.requestId = _api.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 ea97db3496a4c6..b3c3e1f800b57d 100644 --- a/Telegram/SourceFiles/api/api_ringtones.h +++ b/Telegram/SourceFiles/api/api_ringtones.h @@ -12,6 +12,10 @@ For license and copyright information please follow this link: class ApiWrap; class PeerData; +namespace Tdb { +class FileGenerator; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -43,17 +47,24 @@ class Ringtones final { [[nodiscard]] crl::time maxDuration() const; private: +#if 0 // goodToRemove struct UploadedData { QString filename; QString filemime; QByteArray content; }; void ready(const FullMsgId &msgId, const MTPInputFile &file); +#endif const not_null _session; MTP::Sender _api; +#if 0 // goodToRemove base::flat_map _uploads; +#endif + 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 4bc32f836cd56a..5d641170a57380 100644 --- a/Telegram/SourceFiles/api/api_self_destruct.cpp +++ b/Telegram/SourceFiles/api/api_self_destruct.cpp @@ -9,12 +9,15 @@ 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) : _api(&api->instance()) { } +#if 0 // goodToRemove void SelfDestruct::reload() { if (!_accountTTL.requestId) { _accountTTL.requestId = _api.request(MTPaccount_GetAccountTTL( @@ -36,6 +39,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 +76,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 +100,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_send_progress.cpp b/Telegram/SourceFiles/api/api_send_progress.cpp index 93d28792652ae3..025f471075f1bf 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 a3003bab43a298..c3f90a25bce872 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/components/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, @@ -65,6 +75,7 @@ void InnerFillMessagePostFlags( } } +#if 0 // mtp void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) { const auto history = action.history; const auto peer = history->peer; @@ -261,6 +272,306 @@ void SendExistingMedia( api->finishForwarding(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, + tl_bool(file->to.options.invertCaption), + null, // 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->to.options.invertCaption), + 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, + tl_bool(file->to.options.invertCaption), + null, // self_destruct_type + tl_bool(file->spoiler)); + case PreparedFileType::VoiceNote: + return tl_inputMessageVoiceNote( + fileById, + tl_int32(seconds), + tl_bytes(file->waveform), + formatted, + null); // self_destruct_type + } + Unexpected("FilePrepareResult::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()), + null); // self_destruct_type + } else if (document->isVoiceMessage()) { + return tl_inputMessageVoiceNote( + fileById, + tl_int32(document->duration() / 1000), + tl_bytes(documentWaveformEncode5bit(document->voice()->waveform)), + formatted, + null); // self_destruct_type + } 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), // show_caption_above_media + 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, + tl_bool(false), // show_caption_above_media + null, // 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, + tl_bool(false), // show_caption_above_media + null, // 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_int64(action.options.effectId), + 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({ + .id = localId, + .flags = flags, + .from = messageFromId, + .replyTo = action.replyTo, + .date = NewMessageDate(action.options.scheduled), + .viaBotId = viaBotId, + .postAuthor = messagePostAuthor, + }, media, caption); +} } // namespace @@ -268,6 +579,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), @@ -281,6 +602,7 @@ void SendExistingDocument( inputMedia, document->stickerOrGifOrigin(), std::move(localMessageId)); +#endif if (document->sticker()) { document->owner().stickers().incrementSticker(document); @@ -291,6 +613,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), @@ -303,6 +633,7 @@ void SendExistingPhoto( inputMedia, Data::FileOrigin(), std::move(localMessageId)); +#endif } bool SendDice(MessageToSend &message) { @@ -322,11 +653,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 = message.action.history->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; @@ -342,6 +677,22 @@ bool SendDice(MessageToSend &message) { const auto &action = message.action; api->sendAction(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()); @@ -415,10 +766,20 @@ bool SendDice(MessageToSend &message) { api->sendMessageFail(error, peer, randomId, newId); }); api->finishForwarding(action); +#endif + return true; } void SendLocation(SendAction action, float64 lat, float64 lon) { + SendPreparedMessage( + action, + tl_inputMessageLocation( + tl_location(tl_double(lat), tl_double(lon), tl_double(0)), + tl_int32(0), // live_period + tl_int32(0), // heading + tl_int32(0))); // proximity_alert_radius +#if 0 // mtp SendSimpleMedia( action, MTP_inputMediaGeoPoint( @@ -427,9 +788,23 @@ void SendLocation(SendAction action, float64 lat, float64 lon) { MTP_double(lat), MTP_double(lon), MTPint()))); // accuracy_radius +#endif } void SendVenue(SendAction action, Data::InputVenue venue) { + SendPreparedMessage( + action, + tl_inputMessageVenue(tl_venue( + tl_location( + tl_double(venue.lat), + tl_double(venue.lon), + tl_double(0)), + tl_string(venue.title), + tl_string(venue.address), + tl_string(venue.provider), + tl_string(venue.id), + tl_string(venue.venueType)))); +#if 0 // mtp SendSimpleMedia( action, MTP_inputMediaVenue( @@ -443,6 +818,7 @@ void SendVenue(SendAction action, Data::InputVenue venue) { MTP_string(venue.provider), MTP_string(venue.id), MTP_string(venue.venueType))); +#endif } void FillMessagePostFlags( @@ -455,6 +831,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( @@ -620,6 +997,203 @@ 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_int64(action.options.effectId), + 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); + if (external) { + return tl_inputMessageReplyToExternalMessage( + peerToTdbChat(messageId.peer), + tl_int53(messageId.msg.bare), + (replyTo.quote.empty() + ? std::optional() + : tl_inputTextQuote( + Api::FormattedTextToTdb(replyTo.quote), + tl_int32(replyTo.quoteOffset)))); + } + return tl_inputMessageReplyToMessage( + tl_int53(messageId.msg.bare), + (replyTo.quote.empty() + ? std::optional() + : tl_inputTextQuote( + Api::FormattedTextToTdb(replyTo.quote), + tl_int32(replyTo.quoteOffset)))); + } + 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, { + .id = localId, + .flags = flags, + .from = messageFromId, + .replyTo = action.replyTo, + .date = NewMessageDate(action.options.scheduled), + .viaBotId = ((bot && !action.options.hideViaBot) + ? peerToUser(bot->id) + : 0), + .postAuthor = messagePostAuthor, + }); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_sending.h b/Telegram/SourceFiles/api/api_sending.h index c4bafc53780650..4bb2017c050e6d 100644 --- a/Telegram/SourceFiles/api/api_sending.h +++ b/Telegram/SourceFiles/api/api_sending.h @@ -7,6 +7,10 @@ For license and copyright information please follow this link: */ #pragma once +namespace tl { +class int64_type; +} // namespace tl + class History; class PhotoData; class DocumentData; @@ -20,6 +24,18 @@ namespace Main { class Session; } // namespace Main +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; @@ -53,4 +69,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 31f83f5ebae907..bd62a3d697b495 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.cpp +++ b/Telegram/SourceFiles/api/api_sensitive_content.cpp @@ -8,12 +8,16 @@ 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_app_config.h" namespace Api { namespace { +constexpr auto kSensitiveContentOption = + "ignore_sensitive_content_restrictions"_cs; constexpr auto kRefreshAppConfigTimeout = crl::time(1); } // namespace @@ -30,6 +34,7 @@ void SensitiveContent::preload() { } } +#if 0 // goodToRemove void SensitiveContent::reload(bool force) { if (_loadRequestId) { if (force) { @@ -60,6 +65,25 @@ void SensitiveContent::reload(bool force) { _loadRequestId = 0; }).send(); } +#endif + +void SensitiveContent::reload(bool force) { + 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(); @@ -73,6 +97,7 @@ rpl::producer SensitiveContent::canChange() const { return _canChange.value(); } +#if 0 // goodToRemove void SensitiveContent::update(bool enabled) { if (!_canChange.current()) { return; @@ -97,5 +122,21 @@ void SensitiveContent::update(bool enabled) { )).done(finish).fail(finish).send(); _enabled = enabled; } +#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 576bf275f3daef..295b00227a029b 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.h +++ b/Telegram/SourceFiles/api/api_sensitive_content.h @@ -33,8 +33,10 @@ class SensitiveContent final { private: const not_null _session; MTP::Sender _api; +#if 0 // goodToRemove mtpRequestId _loadRequestId = 0; mtpRequestId _saveRequestId = 0; +#endif 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 0875091bc7cfd4..2a1caf0d8f3d82 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 48194d3f19e220..4c25edd71bf5bd 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 c960aca3335aba..372857491b2846 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -15,11 +15,17 @@ For license and copyright information please follow this link: #include "data/data_stories.h" #include "data/data_story.h" #include "history/history.h" +#include "history/history_item.h" #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { namespace { +using namespace Tdb; + +#if 0 // mtp [[nodiscard]] Data::StatisticalValue StatisticalValueFromTL( const MTPStatsAbsValueAndPrev &tl) { const auto current = tl.data().vcurrent().v; @@ -194,6 +200,163 @@ namespace { .topInviters = std::move(topInviters), }; } +#endif + +[[nodiscard]] Data::StatisticalValue StatisticalValueFromTL( + const 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 TLDchatStatisticsChannel &data) { + const auto unmuted = data.venabled_notifications_percentage().v; + using Recent = TLchatStatisticsInteractionInfo; + auto recentMessages = ranges::views::all( + data.vrecent_interactions().v + ) | ranges::views::transform([&](const Recent &interaction) { + const auto &data = interaction.data(); + auto result = Data::StatisticsMessageInteractionInfo{ + .viewsCount = data.vview_count().v, + .forwardsCount = data.vforward_count().v, + }; + data.vobject_type().match([&]( + const TLDchatStatisticsObjectTypeMessage &data) { + result.messageId = data.vmessage_id().v; + }, [&](const TLDchatStatisticsObjectTypeStory &data) { + result.storyId = data.vstory_id().v; + }); + return result; + }) | 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_message_view_count()), + .meanShareCount = StatisticalValueFromTL( + data.vmean_message_share_count()), + .meanReactionCount = StatisticalValueFromTL( + data.vmean_message_reaction_count()), + + .meanStoryViewCount = StatisticalValueFromTL( + data.vmean_story_view_count()), + .meanStoryShareCount = StatisticalValueFromTL( + data.vmean_story_share_count()), + .meanStoryReactionCount = StatisticalValueFromTL( + data.vmean_story_reaction_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 TLDchatStatisticsSupergroup &data) { + using Senders = TLchatStatisticsMessageSenderInfo; + using Administrators = TLchatStatisticsAdministratorActionsInfo; + using Inviters = 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 @@ -205,6 +368,7 @@ 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)), @@ -228,6 +392,20 @@ rpl::producer Statistics::request() { consumer.put_error_copy(error.type()); }).send(); } +#endif + api().request(TLgetChatStatistics( + peerToTdbChat(channel()->id), + tl_bool(false) + )).done([=](const TLchatStatistics &result) { + result.match([&](const TLDchatStatisticsChannel &data) { + _channelStats = ChannelStatisticsFromTL(data); + }, [&](const TLDchatStatisticsSupergroup &data) { + _supergroupStats = SupergroupStatisticsFromTL(data); + }); + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); return lifetime; }; @@ -240,6 +418,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 @@ -258,6 +437,23 @@ Statistics::GraphResult Statistics::requestZoom( }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + api().request(TLgetStatisticalGraph( + peerToTdbChat(channel()->id), + tl_string(token), + tl_int53(x) + )).done([=](const TLstatisticalGraph &result) { + consumer.put_next(StatisticalGraphFromTL(result)); + consumer.put_done(); + if (!_zoomDeque.empty()) { + _zoomDeque.pop_front(); + if (!_zoomDeque.empty()) { + _zoomDeque.front()(); + } + } + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); }); if (wasEmpty) { _zoomDeque.front()(); @@ -289,6 +485,63 @@ void PublicForwards::request( return; } const auto channel = StatisticsRequestSender::channel(); + + constexpr auto kLimit = tl::make_int(100); + const auto processResult = [=](const TLpublicForwards &result) { + using Messages = QVector; + _requestId = 0; + + const auto &data = result.data(); + auto nextToken = data.vnext_offset().v; + auto recentList = Messages(); + recentList.reserve(data.vforwards().v.size()); + for (const auto &forward : result.data().vforwards().v) { + forward.match([&](const TLDpublicForwardMessage &data) { + recentList.push_back({ + .messageId = channel->owner().processMessage( + data.vmessage(), + NewMessageType::Existing)->fullId(), + }); + }, [&](const TLDpublicForwardStory &data) { + const auto story = channel->owner().stories().applySingle( + data.vstory()); + if (story) { + recentList.push_back({ .storyId = story->fullId() }); + } + }); + } + + auto allLoaded = false; + auto fullCount = result.data().vtotal_count().v; + + if (nextToken == token || nextToken.isEmpty()) { + allLoaded = true; + } + + _lastTotal = std::max(_lastTotal, fullCount); + done({ + .list = std::move(recentList), + .total = _lastTotal, + .allLoaded = allLoaded, + .token = std::move(nextToken), + }); + }; + if (_fullId.messageId) { + _requestId = api().request(TLgetMessagePublicForwards( + peerToTdbChat(channel->id), + tl_int53(_fullId.messageId.msg.bare), + tl_string(token), + kLimit + )).done(processResult).fail([=] { _requestId = 0; }).send(); + } else if (_fullId.storyId) { + _requestId = api().request(TLgetStoryPublicForwards( + peerToTdbChat(channel->id), + tl_int32(_fullId.storyId.story), + tl_string(token), + kLimit + )).done(processResult).fail([=] { _requestId = 0; }).send(); + } +#if 0 // mtp const auto processResult = [=](const MTPstats_PublicForwards &tl) { using Messages = QVector; _requestId = 0; @@ -358,6 +611,7 @@ void PublicForwards::request( kLimit )).done(processResult).fail([=] { _requestId = 0; }).send(); } +#endif } MessageStatistics::MessageStatistics( @@ -406,6 +660,7 @@ void MessageStatistics::request(Fn done) { const auto requestPrivateForwards = [=]( const Data::StatisticalGraph &messageGraph, const Data::StatisticalGraph &reactionsGraph) { +#if 0 // mtp api().request(MTPchannels_GetMessages( channel()->inputChannel, MTP_vector( @@ -456,8 +711,83 @@ void MessageStatistics::request(Fn done) { }).fail([=](const MTP::Error &error) { requestFirstPublicForwards(messageGraph, reactionsGraph, {}); }).send(); +#endif + api().request(TLgetMessage( + peerToTdbChat(channel()->id), + tl_int53(_fullId.msg.bare) + )).done([=](const 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, + reactionsGraph, + std::move(info)); + }).fail([=](const Error &error) { + requestFirstPublicForwards(messageGraph, reactionsGraph, {}); + }).send(); }; + const auto requestStoryPrivateForwards = [=]( + const Data::StatisticalGraph &messageGraph, + const Data::StatisticalGraph &reactionsGraph) { + api().request(TLgetStory( + peerToTdbChat(channel()->id), + tl_int32(_storyId.story), + tl_bool(false) + )).done([=](const TLstory &result) { + auto info = Data::StatisticsMessageInteractionInfo(); + const auto &data = result.data(); + if (const auto interaction = data.vinteraction_info()) { + const auto &data = interaction->data(); + info = Data::StatisticsMessageInteractionInfo{ + .storyId = result.data().vid().v, + .viewsCount = data.vview_count().v, + .forwardsCount = data.vforward_count().v, + .reactionsCount = data.vreaction_count().v, + }; + } + requestFirstPublicForwards( + messageGraph, + reactionsGraph, + std::move(info)); + }).fail([=](const Error &error) { + requestFirstPublicForwards(messageGraph, reactionsGraph, {}); + }).send(); + }; + if (_storyId) { + api().request(TLgetStoryStatistics( + peerToTdbChat(channel()->id), + tl_int32(_storyId.story), + tl_bool(false) + )).done([=](const TLDstoryStatistics &data) { + requestStoryPrivateForwards( + StatisticalGraphFromTL(data.vstory_interaction_graph()), + StatisticalGraphFromTL(data.vstory_reaction_graph())); + }).fail([=](const Error &error) { + requestStoryPrivateForwards({}, {}); + }).send(); + } else { + api().request(TLgetMessageStatistics( + peerToTdbChat(channel()->id), + tl_int53(_fullId.msg.bare), + tl_bool(false) + )).done([=](const TLDmessageStatistics &data) { + requestPrivateForwards( + StatisticalGraphFromTL(data.vmessage_interaction_graph()), + StatisticalGraphFromTL(data.vmessage_reaction_graph())); + }).fail([=](const Error &error) { + requestPrivateForwards({}, {}); + }).send(); + } +#if 0 // mtp const auto requestStoryPrivateForwards = [=]( const Data::StatisticalGraph &messageGraph, const Data::StatisticalGraph &reactionsGraph) { @@ -517,6 +847,7 @@ void MessageStatistics::request(Fn done) { requestPrivateForwards({}, {}); }).send(); } +#endif } Boosts::Boosts(not_null peer) @@ -532,6 +863,7 @@ rpl::producer Boosts::request() { return lifetime; } +#if 0 // mtp _api.request(MTPpremium_GetBoostsStatus( _peer->input )).done([=](const MTPpremium_BoostsStatus &result) { @@ -601,6 +933,58 @@ rpl::producer Boosts::request() { }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif + _api.request(TLgetChatBoostStatus( + peerToTdbChat(_peer->id) + )).done([=](const 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 TLprepaidGiveaway &r) { + auto months = 0; + auto stars = uint64(); + r.data().vprize().match([&]( + const TLDgiveawayPrizePremium &data) { + months = data.vmonth_count().v; + }, [&](const TLDgiveawayPrizeStars &data) { + stars = data.vstar_count().v; + }); + return Data::BoostPrepaidGiveaway{ + .date = QDateTime::fromSecsSinceEpoch( + r.data().vpayment_date().v), + .id = uint64(r.data().vid().v), + .credits = stars, + .months = months, + .quantity = r.data().vwinner_count().v, + .boosts = r.data().vboost_count().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 Error &error) { + consumer.put_error_copy(error.message); + }).send(); return lifetime; }; @@ -615,6 +999,86 @@ 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(TLgetChatBoosts( + peerToTdbChat(_peer->id), + tl_bool(gifts), + tl_string(token.next), + token.next.isEmpty() ? kTlFirstSlice : kTlLimit + )).done([=](const 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 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 TLDchatBoostSourceGiveaway &d) { + return d.vis_unclaimed().v; + }, [](const auto &) { + return false; + }); + const auto giveawayMessage = data.vsource().match([&]( + const TLDchatBoostSourceGiveaway &d) { + return FullMsgId{ _peer->id, d.vgiveaway_message_id().v }; + }, [](const auto &) { + return FullMsgId(); + }); + + const auto stars = data.vsource().match([&]( + const TLDchatBoostSourceGiveaway &d) { + return d.vstar_count().v; + }, [&](const auto &) { + return int64(); + }); + + list.push_back({ + .id = data.vid().v.toUtf8(), + .userId = userId, + .giveawayMessage = giveawayMessage, + .date = QDateTime::fromSecsSinceEpoch(data.vstart_date().v), + .expiresAt = QDateTime::fromSecsSinceEpoch(data.vexpiration_date().v), + .expiresAfterMonths = ((data.vexpiration_date().v - data.vstart_date().v) + / kMonthsDivider), + .giftCodeLink = std::move(giftCodeLink), + .multiplier = (data.vcount().v == 1 ? 0 : data.vcount().v), + .credits = uint64(stars), + + .isGift = !giftCodeLink.slug.isEmpty(), + .isGiveaway = (!!giveawayMessage), + .isUnclaimed = isUnclaimed, + }); + } + 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) @@ -675,6 +1139,7 @@ void Boosts::requestBoosts( }).fail([=] { _requestId = 0; }).send(); +#endif } Data::BoostStatus Boosts::boostStatus() const { @@ -689,6 +1154,39 @@ rpl::producer ChannelEarnStatistics::request() { return [=](auto consumer) { auto lifetime = rpl::lifetime(); + api().request(TLgetChatRevenueStatistics( + peerToTdbChat(channel()->id), + tl_bool(false) + )).done([=](const TLchatRevenueStatistics &result) { + const auto &data = result.data(); + const auto &amount = data.vrevenue_amount().data(); + _data = Data::EarnStatistics{ + .topHoursGraph = StatisticalGraphFromTL( + data.vrevenue_by_hour_graph()), + .revenueGraph = StatisticalGraphFromTL( + data.vrevenue_graph()), + .currentBalance = uint64(amount.vbalance_amount().v), + .availableBalance = uint64(amount.vavailable_amount().v), + .overallRevenue = uint64(amount.vtotal_amount().v), + .usdRate = data.vusd_rate().v, + }; + requestHistory({}, [=](Data::EarnHistorySlice &&slice) { + _data.firstHistorySlice = std::move(slice); + + api().request(TLgetSupergroupFullInfo( + tl_int53(peerToChannel(channel()->id).bare) + )).done([=](const TLsupergroupFullInfo &result) { + _data.switchedOff + = !result.data().vcan_have_sponsored_messages().v; + consumer.put_done(); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); + }); + }).fail([=](const Error &error) { + consumer.put_error_copy(error.message); + }).send(); +#if 0 // mtp makeRequest(MTPstats_GetBroadcastRevenueStats( MTP_flags(0), channel()->inputChannel @@ -724,6 +1222,7 @@ rpl::producer ChannelEarnStatistics::request() { }).fail([=](const MTP::Error &error) { consumer.put_error_copy(error.type()); }).send(); +#endif return lifetime; }; @@ -737,6 +1236,7 @@ void ChannelEarnStatistics::requestHistory( } constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice); constexpr auto kTlLimit = tl::make_int(kLimit); +#if 0 // mtp _requestId = api().request(MTPstats_GetBroadcastRevenueTransactions( channel()->inputChannel, MTP_int(token), @@ -793,6 +1293,59 @@ void ChannelEarnStatistics::requestHistory( .allLoaded = (result.data().vcount().v == nextToken), .token = Data::EarnHistorySlice::OffsetToken(nextToken), }); +#endif + _requestId = api().request(TLgetChatRevenueTransactions( + peerToTdbChat(channel()->id), + tl_int32(token), + (!token) ? kTlFirstSlice : kTlLimit + )).done([=](const TLchatRevenueTransactions &result) { + _requestId = 0; + + const auto &data = result.data(); + const auto &tlTransactions = data.vtransactions().v; + + auto list = std::vector(); + list.reserve(tlTransactions.size()); + for (const auto &tlTransaction : tlTransactions) { + const auto &d = tlTransaction.data(); + list.push_back({ + .amount = uint64(d.vcryptocurrency_amount().v), + }); + auto &entry = list.back(); + d.vtype().match([&]( + const TLDchatRevenueTransactionTypeEarnings &d) { + entry.type = Data::EarnHistoryEntry::Type::In; + entry.date = base::unixtime::parse(d.vstart_date().v); + entry.dateTo = base::unixtime::parse(d.vend_date().v); + }, [&](const TLDchatRevenueTransactionTypeWithdrawal &d) { + entry.type = Data::EarnHistoryEntry::Type::Out; + entry.amount = (std::numeric_limits::max() + - entry.amount + + 1); + entry.date = base::unixtime::parse(d.vwithdrawal_date().v); + // entry.provider = d.vprovider().v; + d.vstate().match([&](const TLDrevenueWithdrawalStateFailed &d) { + entry.status = Data::EarnHistoryEntry::Status::Failed; + }, [&](const TLDrevenueWithdrawalStatePending &d) { + entry.status = Data::EarnHistoryEntry::Status::Pending; + }, [&](const TLDrevenueWithdrawalStateSucceeded &d) { + entry.successDate = base::unixtime::parse(d.vdate().v); + entry.successLink = d.vurl().v; + entry.status = Data::EarnHistoryEntry::Status::Success; + }); + }, [&](const TLDchatRevenueTransactionTypeRefund &d) { + entry.type = Data::EarnHistoryEntry::Type::Return; + entry.date = base::unixtime::parse(d.vrefund_date().v); + // entry.provider = d.vprovider().v; + }); + } + const auto nextToken = token + tlTransactions.size(); + done(Data::EarnHistorySlice{ + .list = std::move(list), + .total = data.vtotal_count().v, + .allLoaded = (data.vtotal_count().v <= nextToken), + .token = Data::EarnHistorySlice::OffsetToken(nextToken), + }); }).fail([=] { done({}); _requestId = 0; diff --git a/Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp b/Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp index 9ce75af7858b59..c3452234641187 100644 --- a/Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp +++ b/Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp @@ -10,8 +10,11 @@ For license and copyright information please follow this link: #include "data/data_statistics_chart.h" #include "statistics/statistics_data_deserialize.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { +#if 0 // mtp Data::StatisticalGraph StatisticalGraphFromTL(const MTPStatsGraph &tl) { return tl.match([&](const MTPDstatsGraph &d) { using namespace Statistic; @@ -30,6 +33,25 @@ Data::StatisticalGraph StatisticalGraphFromTL(const MTPStatsGraph &tl) { return Data::StatisticalGraph{ .error = qs(data.verror()) }; }); } +#endif +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(), + }; + }); +} } // namespace Api diff --git a/Telegram/SourceFiles/api/api_statistics_data_deserialize.h b/Telegram/SourceFiles/api/api_statistics_data_deserialize.h index 385b99d17d31df..a90f609becc2c4 100644 --- a/Telegram/SourceFiles/api/api_statistics_data_deserialize.h +++ b/Telegram/SourceFiles/api/api_statistics_data_deserialize.h @@ -7,13 +7,21 @@ For license and copyright information please follow this link: */ #pragma once +namespace Tdb { +class TLstatisticalGraph; +} // namespace Tdb + namespace Data { struct StatisticalGraph; } // namespace Data namespace Api { +#if 0 // mtp [[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL( const MTPStatsGraph &tl); +#endif +[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL( + const Tdb::TLstatisticalGraph &tl); } // namespace Api diff --git a/Telegram/SourceFiles/api/api_statistics_sender.cpp b/Telegram/SourceFiles/api/api_statistics_sender.cpp index 21d068e3d7453c..a7b257fb9a852c 100644 --- a/Telegram/SourceFiles/api/api_statistics_sender.cpp +++ b/Telegram/SourceFiles/api/api_statistics_sender.cpp @@ -20,7 +20,10 @@ StatisticsRequestSender::StatisticsRequestSender( , _channel(peer->asChannel()) , _user(peer->asUser()) , _api(&_peer->session().api().instance()) +#if 0 // mtp , _timer([=] { checkRequests(); }) { +#endif +{ } MTP::Sender &StatisticsRequestSender::api() { @@ -37,6 +40,7 @@ not_null StatisticsRequestSender::user() { return _user; } +#if 0 // mtp void StatisticsRequestSender::checkRequests() { for (auto i = begin(_requests); i != end(_requests);) { for (auto j = begin(i->second); j != end(i->second);) { @@ -82,5 +86,6 @@ StatisticsRequestSender::~StatisticsRequestSender() { } } } +#endif } // namespace Api diff --git a/Telegram/SourceFiles/api/api_statistics_sender.h b/Telegram/SourceFiles/api/api_statistics_sender.h index 2ad0b664d27d7c..510bd48479a7d5 100644 --- a/Telegram/SourceFiles/api/api_statistics_sender.h +++ b/Telegram/SourceFiles/api/api_statistics_sender.h @@ -19,6 +19,7 @@ namespace Api { class StatisticsRequestSender { protected: explicit StatisticsRequestSender(not_null peer); +#if 0 // mtp ~StatisticsRequestSender(); template < @@ -33,25 +34,30 @@ class StatisticsRequestSender { dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0 ).overrideId(id)); } +#endif [[nodiscard]] MTP::Sender &api(); [[nodiscard]] not_null channel(); [[nodiscard]] not_null user(); private: +#if 0 // mtp struct Registered final { mtpRequestId id; MTP::DcId dcId; }; [[nodiscard]] Registered ensureRequestIsRegistered(); void checkRequests(); +#endif const not_null _peer; ChannelData * const _channel; UserData * const _user; MTP::Sender _api; +#if 0 // mtp base::Timer _timer; base::flat_map> _requests; +#endif }; diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index 84f7fef7daee13..e2295bb5213043 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -14,12 +14,15 @@ For license and copyright information please follow this link: #include "data/stickers/data_stickers_set.h" #include "main/main_session.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); @@ -57,9 +60,11 @@ using namespace Tdb; MTP_long(parsed.userId), MTP_long(parsed.accessHash)))); } +#endif } // namespace +#if 0 // mtp EntitiesInText EntitiesFromMTP( Main::Session *session, const QVector &entities) { @@ -344,10 +349,9 @@ MTPVector EntitiesToMTP( } return MTP_vector(std::move(v)); } +#endif -EntitiesInText EntitiesFromTL( - Main::Session *session, - const QVector &entities) { +EntitiesInText EntitiesFromTdb(const QVector &entities) { auto result = EntitiesInText(); if (!entities.isEmpty()) { result.reserve(entities.size()); @@ -396,9 +400,20 @@ EntitiesInText EntitiesFromTL( .userId = uint64(data.vuser_id().v), }); return EntityType::MentionName; + }, [&](const TLDtextEntityTypeSpoiler &data) { + return EntityType::Spoiler; }, [&](const TLDtextEntityTypeMediaTimestamp &data) { - // #TODO entities media timestamp links + // 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; + }, [&](const TLDtextEntityTypeExpandableBlockQuote &data) { + additional = u"1"_q; + return EntityType::Blockquote; }); if (type != EntityType::Invalid) { result.push_back({ type, offset, length, additional }); @@ -409,4 +424,91 @@ EntitiesInText EntitiesFromTL( 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 entity.data().isEmpty() + ? tl_textEntityTypeBlockQuote() + : tl_textEntityTypeExpandableBlockQuote(); + } + 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 ca69a55a3ccd4e..2ffbab1c5ace54 100644 --- a/Telegram/SourceFiles/api/api_text_entities.h +++ b/Telegram/SourceFiles/api/api_text_entities.h @@ -8,7 +8,11 @@ For license and copyright information please follow this link: #pragma once #include "ui/text/text_entity.h" -#include "tdb/tdb_tl_scheme.h" + +namespace Tdb { +class TLtextEntity; +class TLformattedText; +} // namespace Tdb namespace Main { class Session; @@ -30,8 +34,16 @@ enum class ConvertOption { const EntitiesInText &entities, ConvertOption option = ConvertOption::WithLocal); -[[nodiscard]] EntitiesInText EntitiesFromTL( - Main::Session *session, +[[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 65d2ce317ff92f..1dbadf6456bad5 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 dd983dc37b4cad..ea3064b0215627 100644 --- a/Telegram/SourceFiles/api/api_transcribes.cpp +++ b/Telegram/SourceFiles/api/api_transcribes.cpp @@ -18,7 +18,14 @@ For license and copyright information please follow this link: #include "main/main_app_config.h" #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" + namespace Api { +namespace { + +using namespace Tdb; + +} // namespace Transcribes::Transcribes(not_null api) : _session(&api->session()) @@ -34,6 +41,7 @@ bool Transcribes::freeFor(not_null item) const { } bool Transcribes::trialsSupport() { +#if 0 // mtp if (!_trialsSupport) { const auto count = _session->appConfig().get( u"transcribe_audio_trial_weekly_number"_q, @@ -44,31 +52,41 @@ bool Transcribes::trialsSupport() { _trialsSupport = (count > 0) || (until > 0); } return *_trialsSupport; +#endif + return _trialsCount > 0 || _trialsRefreshAt > 0; } TimeId Transcribes::trialsRefreshAt() { if (_trialsRefreshAt < 0) { +#if 0 // mtp _trialsRefreshAt = _session->appConfig().get( u"transcribe_audio_trial_cooldown_until"_q, 0); +#endif + return std::max(_trialsRefreshAt, 0); } return _trialsRefreshAt; } int Transcribes::trialsCount() { if (_trialsCount < 0) { +#if 0 // mtp _trialsCount = _session->appConfig().get( u"transcribe_audio_trial_weekly_number"_q, -1); +#endif return std::max(_trialsCount, 0); } return _trialsCount; } crl::time Transcribes::trialsMaxLengthMs() const { + return _trialsMaxLengthMs; +#if 0 // mtp return 1000 * _session->appConfig().get( u"transcribe_audio_trial_duration_max"_q, 300); +#endif } void Transcribes::toggle(not_null item) { @@ -80,7 +98,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); @@ -94,6 +115,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); @@ -114,6 +136,48 @@ 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::apply(const TLDupdateSpeechRecognitionTrial &data) { + const auto trialsCount = data.vleft_count().v; + const auto trialsRefreshAt = data.vnext_reset_date().v; + const auto trialsCountChanged = (_trialsCount != trialsCount) + && (_trialsCount > 0); // Don't show toast on first update. + const auto refreshAtChanged = (_trialsRefreshAt != trialsRefreshAt); + _trialsMaxLengthMs = data.vmax_media_duration().v * crl::time(1000); + _trialsCount = trialsCount; + _trialsRefreshAt = trialsRefreshAt; + if (trialsCountChanged) { + ShowTrialTranscribesToast(_trialsCount, _trialsRefreshAt); + } +} void Transcribes::load(not_null item) { if (!item->isHistoryEntry() || item->isLocal()) { @@ -130,6 +194,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) @@ -162,11 +242,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)) { @@ -178,7 +262,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 b074e42f13f3b5..0bed461397f65a 100644 --- a/Telegram/SourceFiles/api/api_transcribes.h +++ b/Telegram/SourceFiles/api/api_transcribes.h @@ -9,6 +9,11 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" +namespace Tdb { +class TLspeechRecognitionResult; +class TLDupdateSpeechRecognitionTrial; +} // namespace Tdb + class ApiWrap; namespace Main { @@ -34,7 +39,14 @@ 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 + void apply(const Tdb::TLDupdateSpeechRecognitionTrial &data); [[nodiscard]] bool freeFor(not_null item) const; @@ -52,6 +64,7 @@ class Transcribes final { int _trialsCount = -1; std::optional _trialsSupport; TimeId _trialsRefreshAt = -1; + crl::time _trialsMaxLengthMs = 0; 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 dac998dfdce83c..63eae4e52ee8d9 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,26 @@ 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), + tl_int53(0) // saved_messages_topic_id + )).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 +155,7 @@ void UnreadThings::requestMentions( }).fail([=] { _mentionsRequests.remove(thread); }).send(); +#endif _mentionsRequests.emplace(thread, requestId); } @@ -135,6 +170,26 @@ 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), + tl_int53(0) // saved_messages_topic_id + )).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 +210,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 f3c7e1711eb10c..d6697d7babfaa6 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 7afe8628238ac3..00686b87092f9f 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -18,6 +18,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" @@ -46,6 +47,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" @@ -65,6 +67,16 @@ For license and copyright information please follow this link: #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" +#include "data/components/factchecks.h" +#include "mainwidget.h" namespace Api { namespace { @@ -82,6 +94,7 @@ constexpr auto kNoUpdatesAfterSleepTimeout = 60 * crl::time(1000); using namespace Tdb; +#if 0 // mtp enum class DataIsLoadedResult { NotLoaded = 0, FromNotLoaded = 1, @@ -229,9 +242,11 @@ DataIsLoadedResult AllDataLoadedForMessage( return DataIsLoadedResult::Ok; }); } +#endif } // namespace +#if 0 // mtp Updates::Updates(not_null session) : _session(session) , _noUpdatesTimer([=] { sendPing(); }) @@ -244,7 +259,6 @@ Updates::Updates(not_null session) , _idleFinishTimer([=] { checkIdleFinish(); }) { _ptsWaiter.setRequesting(true); -#if 0 // #TODO legacy session->account().mtpUpdates( ) | rpl::start_with_next([=](const MTPUpdates &updates) { mtpUpdateReceived(updates); @@ -261,6 +275,10 @@ Updates::Updates(not_null session) }).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); @@ -297,6 +315,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 @@ -747,6 +766,13 @@ 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; @@ -756,19 +782,46 @@ void Updates::addActiveChat(rpl::producer chat) { auto &active = _activeChats[key]; const auto was = active.peer; if (was != peer) { + const auto already = peer && inActiveChats(peer); active.peer = peer; + if (was && !inActiveChats(was)) { + _session->sender().request(TLcloseChat( + peerToTdbChat(was->id) + )).send(); + } + if (peer && !already) { + _session->sender().request(TLopenChat( + peerToTdbChat(peer->id) + )).send(); + } +#if 0 // mtp if (const auto channel = was ? was->asChannel() : nullptr) { if (!inActiveChats(channel)) { channel->ptsSetWaitingForShortPoll(-1); - } + } } if (const auto channel = peer ? peer->asChannel() : nullptr) { channel->ptsSetWaitingForShortPoll( 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); } @@ -779,6 +832,7 @@ bool Updates::inActiveChats(not_null peer) const { [](const auto &pair) { return pair.second.peer; }); } +#if 0 // mtp void Updates::requestChannelRangeDifference(not_null history) { Expects(history->peer->isChannel()); @@ -872,7 +926,6 @@ void Updates::channelRangeDifferenceDone( } } -#if 0 // #TODO legacy void Updates::mtpNewSessionCreated() { Core::App().checkAutoLock(); _updatesSeq = 0; @@ -892,7 +945,6 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) { applyGroupCallParticipantUpdates(updates); } } -#endif void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) { updates.match([&](const MTPDupdates &data) { @@ -918,6 +970,7 @@ void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) { int32 Updates::pts() const { return _ptsWaiter.current(); } +#endif void Updates::updateOnline(crl::time lastNonIdleTime) { updateOnline(lastNonIdleTime, false); @@ -960,10 +1013,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) @@ -977,6 +1033,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(); const auto onlineFor = (config.onlineUpdatePeriod / 1000); @@ -1029,6 +1105,7 @@ bool Updates::isQuitPrevent() { return true; } +#if 0 // mtp void Updates::handleSendActionUpdate( PeerId peerId, MsgId rootId, @@ -1079,6 +1156,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, @@ -1110,6 +1234,7 @@ void Updates::handleSpeakingInCall( } } +#if 0 // mtp void Updates::handleEmojiInteraction( not_null peer, MsgId messageId, @@ -1125,6 +1250,7 @@ void Updates::handleEmojiInteraction( emoticon, std::move(bunch)); } +#endif void Updates::handleEmojiInteraction( not_null peer, @@ -1136,6 +1262,7 @@ void Updates::handleEmojiInteraction( window->emojiInteractions().seenOutgoing(peer, emoticon); } +#if 0 // mtp void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { switch (updates.type()) { case mtpc_updateShortMessage: { @@ -2175,6 +2302,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) { @@ -2205,6 +2333,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { d.vrules(), allLoaded()); } break; +#endif case mtpc_updatePinnedDialogs: { const auto &d = update.c_updatePinnedDialogs(); @@ -2301,6 +2430,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() @@ -2612,6 +2742,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { }); } break; +#if 0 // goodToRemove ////// Cloud langpacks case mtpc_updateLangPack: { const auto &data = update.c_updateLangPack(); @@ -2625,6 +2756,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { Lang::CurrentCloudManager().requestLangPackDifference(code); } } break; +#endif ////// Cloud themes case mtpc_updateTheme: { @@ -2667,50 +2799,561 @@ void Updates::feedUpdate(const MTPUpdate &update) { } } +#endif bool IsWithdrawalNotification(const MTPDupdateServiceNotification &data) { return qs(data.vtype()).startsWith(u"API_WITHDRAWAL_FEATURE_DISABLED_"_q); } 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 TLDupdateUser &data) { - session().data().processUser(data.vuser()); - }, [&](const TLDupdateBasicGroup &data) { - session().data().processChat(data.vbasic_group()); - }, [&](const TLDupdateSupergroup &data) { - session().data().processChannel(data.vsupergroup()); + }, [&](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 TLDupdateMessageFactCheck &data) { + _session->factchecks().apply(data); + }, [&](const TLDupdateMessageLiveLocationViewed &data) { + }, [&](const TLDupdateActiveLiveLocationMessages &data) { + for (const auto &message : data.vmessages().v) { + owner.processMessage(message, NewMessageType::Existing); + } }, [&](const TLDupdateNewChat &data) { - session().data().processPeer(data.vchat()); + owner.processPeer(data.vchat()); + }, [&](const TLDupdateChatTitle &data) { + owner.applyChatTitle(data); + }, [&](const TLDupdateChatPhoto &data) { + owner.applyChatPhoto(data); + }, [&](const TLDupdateChatAccentColors &data) { + owner.applyChatAccentColors(data); + }, [&](const TLDupdateChatPermissions &data) { + owner.applyChatPermissions(data); }, [&](const TLDupdateChatLastMessage &data) { - session().data().applyLastMessage(data); + owner.applyLastMessage(data); }, [&](const TLDupdateChatPosition &data) { - session().data().applyDialogPosition(data); - }, [&](const TLDupdateUserFullInfo &data) { - const auto user = session().data().user(UserId(data.vuser_id())); - data.vuser_full_info().match([&](const TLDuserFullInfo &data) { - ::Data::ApplyUserUpdate(user, data); + owner.applyDialogPosition(data); + }, [&](const TLDupdateChatAddedToList &data) { + // ?? can be in chat list and without position in that list + }, [&](const TLDupdateChatRemovedFromList &data) { + // ?? can be not in chat list and with a position in that list + }, [&](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 TLDupdateChatViewAsTopics &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + if (const auto channel = peer->asChannel()) { + channel->setViewAsMessagesFlag(!data.vview_as_topics().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 TLDupdateAvailableMessageEffects &data) { + owner.reactions().refreshEffects(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 TLDupdateReactionNotificationSettings &data) { + }, [&](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 TLDupdateChatBusinessBotManageBar &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + peer->updateBusinessBot(data.vbusiness_bot_manage_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), + peer->wallPaperOverriden()); + } + }, [&](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 TLDupdateChatEmojiStatus &data) { + const auto peerId = peerFromTdbChat(data.vchat_id()); + if (const auto peer = owner.peerLoaded(peerId)) { + peer->setEmojiStatus(data.vemoji_status()); + } + }, [&](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 lastseen = LastseenFromTL( + data.vstatus(), + user->lastseen()); + if (user->updateLastseen(lastseen)) { + 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) { - const auto chat = session().data().chat( - ChatId(data.vbasic_group_id())); - data.vbasic_group_full_info().match([&]( - const TLDbasicGroupFullInfo &data) { - ::Data::ApplyChatUpdate(chat, data); - }); + ::Data::ApplyChatUpdate( + owner.chat(ChatId(data.vbasic_group_id())), + data.vbasic_group_full_info().data()); }, [&](const TLDupdateSupergroupFullInfo &data) { - const auto channel = session().data().channel( - ChannelId(data.vsupergroup_id())); - data.vsupergroup_full_info().match([&]( - const TLDsupergroupFullInfo &data) { - ::Data::ApplyChannelUpdate(channel, 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 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 TLDupdateDefaultBackground &data) { + }, [&](const TLDupdateChatThemes &data) { + session().data().cloudThemes().applyUpdate(data); + }, [&](const TLDupdateAccentColors &data) { + session().applyAccentColors(data); + }, [&](const TLDupdateProfileAccentColors &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 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 TLDupdateOwnedStarCount &data) { + session().credits().apply(data); + }, [&](const TLDupdateSpeechRecognitionTrial &data) { + session().api().transcribes().apply(data); + }, [&](const TLDupdateSpeedLimitNotification &data) { + if (const auto window = Core::App().activeWindow()) { + if (const auto controller = window->sessionController()) { + if (&controller->session() == &session()) { + controller->content()->showNonPremiumLimitToast( + !data.vis_upload().v); + } + } + } + }, [&](const TLDupdateContactCloseBirthdays &data) { + + }, [&](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); + + }, [&](const TLDupdateSavedMessagesTags &data) { + owner.reactions().refreshMyTags(data); + }, [&](const TLDupdateSavedMessagesTopic &data) { + owner.savedMessages().apply(data); + }, [&](const TLDupdateSavedMessagesTopicCount &data) { + owner.savedMessages().apply(data); + + }, [&](const TLDupdateQuickReplyShortcut &data) { + owner.shortcutMessages().apply(data); + }, [&](const TLDupdateQuickReplyShortcutDeleted &data) { + owner.shortcutMessages().apply(data); + }, [&](const TLDupdateQuickReplyShortcuts &data) { + owner.shortcutMessages().apply(data); + }, [&](const TLDupdateQuickReplyShortcutMessages &data) { + owner.shortcutMessages().apply(data); + + // Updates below are handled in a different place. + }, [&](const TLDupdateConnectionState &data) { + }, [&](const TLDupdateServiceNotification &data) { + }, [&](const TLDupdateChatRevenueAmount &data) { + }, [&](const TLDupdateStarRevenueStatus &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 44ac32724c4be7..acad3f6bed42c0 100644 --- a/Telegram/SourceFiles/api/api_updates.h +++ b/Telegram/SourceFiles/api/api_updates.h @@ -15,6 +15,8 @@ class History; namespace Tdb { class TLupdate; +class TLchatAction; +class TLDupdateAnimatedEmojiMessageClicked; } // namespace Tdb namespace MTP { @@ -38,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); @@ -45,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; @@ -54,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); @@ -65,11 +70,13 @@ class Updates final { void getDifference(); void requestChannelRangeDifference(not_null history); +#endif void addActiveChat(rpl::producer chat); [[nodiscard]] bool inActiveChats(not_null peer) const; private: +#if 0 // mtp enum class ChannelDifferenceRequest { Unknown, PtsGapOrShortPoll, @@ -81,6 +88,7 @@ class Updates final { SkipMessageIds, SkipExceptGroupCallParticipants, }; +#endif struct ActiveChatTracker { PeerData *peer = nullptr; @@ -89,6 +97,7 @@ class Updates final { void applyUpdate(const Tdb::TLupdate &update); +#if 0 // mtp void channelRangeDifferenceSend( not_null channel, MsgRange range, @@ -97,8 +106,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(); @@ -127,10 +140,8 @@ class Updates final { void failDifferenceStartTimerFor(ChannelData *channel); void feedChannelDifference(const MTPDupdates_channelDifference &data); -#if 0 // #TODO legacy void mtpUpdateReceived(const MTPUpdates &updates); void mtpNewSessionCreated(); -#endif void feedUpdateVector( const MTPVector &updates, @@ -156,21 +167,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; @@ -205,13 +228,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 fa040f17ea0b4a..791fb75be15168 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,6 +43,15 @@ 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 @@ -44,6 +65,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 +124,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 +191,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 +205,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 +260,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 +285,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 54b59ac2947edc..cac564ae71ccda 100644 --- a/Telegram/SourceFiles/api/api_user_names.h +++ b/Telegram/SourceFiles/api/api_user_names.h @@ -13,6 +13,10 @@ For license and copyright information please follow this link: class ApiWrap; class PeerData; +namespace Tdb { +class TLusernames; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -41,7 +45,10 @@ 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: diff --git a/Telegram/SourceFiles/api/api_user_privacy.cpp b/Telegram/SourceFiles/api/api_user_privacy.cpp index d0c17fa6b18469..f36d25ea9582fd 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.cpp +++ b/Telegram/SourceFiles/api/api_user_privacy.cpp @@ -17,16 +17,100 @@ 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) { using Exceptions = UserPrivacy::Exceptions; + const auto collectInputUsers = [](const Exceptions &exceptions) { + const auto &peers = exceptions.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 Exceptions &exceptions) { + const auto &peers = exceptions.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))); + } + using Option = UserPrivacy::Option; + if (rule.always.premiums && (rule.option != Option::Everyone)) { + result.push_back(tl_userPrivacySettingRuleAllowPremiumUsers()); + } + } + 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 Option::CloseFriends: + // return tl_storyPrivacySettingsCloseFriends(); + 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 Exceptions &exceptions) { const auto &peers = exceptions.peers; auto result = QVector(); @@ -97,12 +181,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; @@ -115,6 +206,7 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) { }; auto &always = result.always.peers; auto &never = result.never.peers; +#if 0 // goodToRemove const auto feed = [&](const MTPPrivacyRule &rule) { rule.match([&](const MTPDprivacyValueAllowAll &) { setOption(Option::Everyone); @@ -178,13 +270,79 @@ 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 TLDuserPrivacySettingRuleAllowPremiumUsers &) { + result.always.premiums = true; + }, [&](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) { @@ -231,6 +389,55 @@ 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(); + case Key::Birthday: return tl_userPrivacySettingShowBirthdate(); + } + 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 &) { + return UserPrivacy::Key::About; + }, [](const TLDuserPrivacySettingShowBirthdate &) { + return UserPrivacy::Key::Birthday; + }); +} } // namespace @@ -239,6 +446,7 @@ UserPrivacy::UserPrivacy(not_null api) , _api(&api->instance()) { } +#if 0 // goodToRemove void UserPrivacy::save( Key key, const UserPrivacy::Rule &rule) { @@ -270,7 +478,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, @@ -322,5 +575,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 471f41f4809487..e54892b45deb8b 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.h +++ b/Telegram/SourceFiles/api/api_user_privacy.h @@ -55,17 +55,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; +#if 0 // goodToRemove void pushPrivacy(Key key, const MTPVector &rules); base::flat_map _privacySaveRequests; @@ -73,6 +76,7 @@ class UserPrivacy final { base::flat_map _privacyRequestIds; base::flat_map _privacyValues; std::map> _privacyChanges; +#endif MTP::Sender _api; diff --git a/Telegram/SourceFiles/api/api_views.cpp b/Telegram/SourceFiles/api/api_views.cpp index e80f56e213b7b3..6f030f61ae68e4 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, bool force) { @@ -81,6 +91,7 @@ void ViewsManager::pollExtendedMedia( _pollTimer.callOnce(delay); } } +#endif void ViewsManager::viewsIncrement() { for (auto i = _toIncrement.begin(); i != _toIncrement.cend();) { @@ -89,6 +100,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) { @@ -105,12 +136,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>(); @@ -248,5 +281,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 f8d19d15bcca3a..ce1d884dcf9191 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, bool force = false); +#endif private: struct PollExtendedMediaRequest { @@ -38,6 +42,8 @@ class ViewsManager final { }; void viewsIncrement(); + +#if 0 // mtp void sendPollRequests(); void sendPollRequests( const base::flat_map< @@ -49,9 +55,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; @@ -59,10 +68,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 2ea6b2a094a1f0..55ad11ff7f018e 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,6 +44,26 @@ 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 @@ -48,6 +77,7 @@ void Websites::reload() { return; } +#if 0 // mtp _requestId = _api.request(MTPaccount_GetWebAuthorizations( )).done([=](const MTPaccount_WebAuthorizations &result) { _requestId = 0; @@ -64,12 +94,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 +151,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 1551ae4d4ed0b5..ed07e7384eb18d 100644 --- a/Telegram/SourceFiles/api/api_websites.h +++ b/Telegram/SourceFiles/api/api_websites.h @@ -22,7 +22,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 +36,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); diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index 6c9df39b2d9503..7b456e9df7c285 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.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 Api { namespace { +using namespace Tdb; + constexpr auto kContextReactionsLimit = 50; using Data::ReactionId; @@ -73,7 +77,10 @@ struct CachedRead { : data(Peers{ .state = WhoReadState::Unknown }) { } rpl::variable data; +#if 0 // mtp mtpRequestId requestId = 0; +#endif + RequestId requestId = 0; }; struct CachedReacted { @@ -81,7 +88,10 @@ struct CachedReacted { : data(PeersWithReactions{ .state = WhoReadState::Unknown }) { } rpl::variable data; +#if 0 // mtp mtpRequestId requestId = 0; +#endif + RequestId requestId = 0; }; struct Context { @@ -150,13 +160,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(); } } } @@ -177,13 +193,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); } @@ -250,6 +272,36 @@ struct State { auto &entry = context->cacheRead(item); if (entry.requestId) { } else if (const auto user = item->history()->peer->asUser()) { + entry.requestId = session->sender().request(TLgetMessageReadDate( + peerToTdbChat(user->id), + tl_int53(item->id.bare) + )).done([=](const TLmessageReadDate &result) { + auto &entry = context->cacheRead(item); + entry.requestId = 0; + auto parsed = Peers(); + result.match([&](const TLDmessageReadDateRead &data) { + parsed.list.push_back({ + .peer = user->id, + .date = data.vread_date().v, + }); + }, [&](const TLDmessageReadDateUnread &) { + parsed.state = WhoReadState::Empty; + }, [&](const TLDmessageReadDateTooOld &) { + parsed.state = WhoReadState::TooOld; + }, [&](const TLDmessageReadDateUserPrivacyRestricted &) { + parsed.state = WhoReadState::HisHidden; + }, [&](const TLDmessageReadDateMyPrivacyRestricted &) { + parsed.state = WhoReadState::MyHidden; + }); + entry.data = std::move(parsed); + }).fail([=] { + auto &entry = context->cacheRead(item); + entry.requestId = 0; + if (entry.data.current().state == WhoReadState::Unknown) { + entry.data = Peers{ .state = WhoReadState::Empty }; + } + }).send(); +#if 0 // mtp entry.requestId = session->api().request( MTPmessages_GetOutboxReadDate( user->input, @@ -279,7 +331,9 @@ struct State { : Peers{ .state = WhoReadState::Empty }; } }).send(); +#endif } else { +#if 0 // mtp entry.requestId = session->api().request( MTPmessages_GetMessageReadParticipants( item->history()->peer->input, @@ -296,6 +350,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); @@ -334,6 +406,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( @@ -373,6 +446,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 af8a8637d9542a..24c56d184eaa63 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -88,14 +88,21 @@ For license and copyright information please follow this link: #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" +#include "data/data_saved_messages.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); @@ -103,7 +110,9 @@ constexpr auto kStickersByEmojiInvalidateTimeout = crl::time(6 * 1000); constexpr auto kNotifySettingSaveTimeout = crl::time(1000); constexpr auto kDialogsFirstLoad = 20; constexpr auto kDialogsPerPage = 100; +#if 0 // mtp constexpr auto kStatsSessionKillTimeout = 10 * crl::time(1000); +#endif using namespace Tdb; using PhotoFileLocationId = Data::PhotoFileLocationId; @@ -162,17 +171,24 @@ struct ApiWrap::DialogsLoadState { }; 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)) @@ -198,19 +214,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 }); } @@ -248,6 +268,7 @@ void ApiWrap::setupSupportMode() { }, _session->lifetime()); } +#if 0 // mtp void ApiWrap::requestChangelog( const QString &sinceVersion, Fn callback) { @@ -324,10 +345,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) @@ -342,11 +365,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; @@ -395,15 +428,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, @@ -423,9 +485,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); @@ -445,10 +509,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); @@ -468,10 +562,23 @@ void ApiWrap::savePinnedOrder(not_null forum) { )).done([=](const MTPUpdates &result) { applyUpdates(result); }).send(); +#endif } void ApiWrap::savePinnedOrder(not_null saved) { const auto &order = _session->data().pinnedChatsOrder(saved); + const auto input = [&](Dialogs::Key key) { + if (const auto sublist = key.sublist()) { + return tl_int53(saved->sublistId(sublist)); + } + Unexpected("Key type in pinnedDialogsOrder()."); + }; + sender().request(TLsetPinnedSavedMessagesTopics( + tl_vector(order + | ranges::views::transform(input) + | ranges::to) + )).send(); +#if 0 // mtp const auto input = [](Dialogs::Key key) { if (const auto sublist = key.sublist()) { return MTP_inputDialogPeer(sublist->peer()->input); @@ -488,12 +595,32 @@ void ApiWrap::savePinnedOrder(not_null saved) { MTP_flags(MTPmessages_ReorderPinnedSavedDialogs::Flag::f_force), MTP_vector(peers) )).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(); } @@ -522,8 +649,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, @@ -531,6 +660,7 @@ void ApiWrap::sendMessageFail( FullMsgId itemId) { sendMessageFail(error.type(), peer, randomId, itemId); } +#endif void ApiWrap::sendMessageFail( const QString &error, @@ -538,6 +668,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( @@ -596,12 +728,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]; @@ -611,8 +757,10 @@ void ApiWrap::requestMessageData( if (!requests.requestId) { _messageDataResolveDelayed.call(); } +#endif } +#if 0 // mtp QVector ApiWrap::collectMessageIds( const MessageDataRequests &requests) { auto result = QVector(); @@ -842,11 +990,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) { @@ -869,6 +1019,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) { @@ -880,7 +1044,7 @@ void ApiWrap::requestDialogs(Data::Folder *folder) { void ApiWrap::requestMoreDialogs(Data::Folder *folder) { const auto state = dialogsLoadState(folder); - if (!state || !tdb().ready()) { + if (!state) { return; } else if (state->requestId) { return; @@ -888,7 +1052,7 @@ void ApiWrap::requestMoreDialogs(Data::Folder *folder) { return; } -#if 0 // #TODO legacy +#if 0 // mtp const auto firstLoad = !state->offsetDate; const auto loadCount = firstLoad ? kDialogsFirstLoad : kDialogsPerPage; const auto flags = MTPmessages_GetDialogs::Flag::f_exclude_pinned @@ -960,39 +1124,20 @@ void ApiWrap::requestMoreDialogs(Data::Folder *folder) { if (!state) { return; } + state->pinnedReceived = true; state->firstRequested = true; state->requestId = 0; - //const auto count = result.count; - //if (result.ids.isEmpty()) { - // state->listReceived = true; - // state->pinnedReceived = true; - // dialogsLoadFinish(folder); // may kill 'state'. - //} else { - // state->offsetChatId = result.ids.back().v; - //} - //_session->data().processUsers(result.users); - //_session->data().processChats(result.chats); - //_session->data().processChannels(result.channels); - //_session->data().processPeers(result.dialogs); - //_session->data().applyDialogs( - // folder, - // data.vmessages().v, - // data.vdialogs().v, - // count); - - //if (!folder - // && (!_dialogsLoadState || !_dialogsLoadState->listReceived)) { - // refreshDialogsLoadBlocked(); - //} - //requestMoreDialogsIfNeeded(); - //_session->data().chatsListChanged(folder); + 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) { @@ -1013,6 +1158,7 @@ void ApiWrap::refreshDialogsLoadBlocked() { } void ApiWrap::requestMoreDialogsIfNeeded() { +#if 0 // mtp const auto dialogsReady = !_dialogsLoadState || _dialogsLoadState->listReceived; if (_session->data().chatsFilters().loadNextExceptions(dialogsReady)) { @@ -1028,15 +1174,24 @@ void ApiWrap::requestMoreDialogsIfNeeded() { requestMoreDialogs(folder); } } +#endif + if (_dialogsLoadState && !_dialogsLoadState->listReceived) { + if (_dialogsLoadState->requestId) { + return; + } + requestDialogs(nullptr); + } requestContacts(); +#if 0 // mtp _session->data().shortcutMessages().preloadShortcuts(); +#endif } +#if 0 // mtp void ApiWrap::updateDialogsOffset( Data::Folder *folder, const QVector &dialogs, const QVector &messages) { -#if 0 // #TODO legacy auto lastDate = TimeId(0); auto lastPeer = PeerId(0); auto lastMsgId = MsgId(0); @@ -1078,8 +1233,8 @@ void ApiWrap::updateDialogsOffset( dialogsLoadFinish(folder); } } -#endif } +#endif auto ApiWrap::dialogsLoadState(Data::Folder *folder) -> DialogsLoadState* { if (!folder) { @@ -1108,6 +1263,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) { @@ -1140,6 +1296,7 @@ void ApiWrap::requestPinnedDialogs(Data::Folder *folder) { finalize(); }).send(); } +#endif void ApiWrap::requestMoreBlockedByDateDialogs() { if (!_dialogsLoadState) { @@ -1165,6 +1322,7 @@ void ApiWrap::requestWallPaper( const QString &slug, Fn done, Fn fail) { +#if 0 // mtp if (_wallPaperSlug != slug) { _wallPaperSlug = slug; if (_wallPaperRequestId) { @@ -1189,6 +1347,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)) { @@ -1198,10 +1381,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); @@ -1237,8 +1475,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) { @@ -1314,6 +1554,7 @@ void ApiWrap::requestPeerSettings(not_null peer) { _requestedPeerSettings.erase(peer); }).send(); } +#endif void ApiWrap::migrateChat( not_null chat, @@ -1355,6 +1596,7 @@ void ApiWrap::migrateChat( return; } +#if 0 // mtp request(MTPmessages_MigrateChat( chat->inputChat )).done([=](const MTPUpdates &result) { @@ -1374,6 +1616,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( @@ -1404,21 +1660,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) @@ -1432,12 +1709,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( @@ -1451,6 +1742,7 @@ void ApiWrap::markContentsRead(not_null item) { applyAffectedMessages(result); }).send(); } +#endif } void ApiWrap::deleteAllFromParticipant( @@ -1474,6 +1766,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 @@ -1485,6 +1782,7 @@ void ApiWrap::deleteAllFromParticipantSend( history->requestChatListMessage(); } }).send(); +#endif } void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) { @@ -1498,6 +1796,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), @@ -1508,6 +1815,7 @@ void ApiWrap::requestStickerSets() { }).fail([=, setId = id] { _stickerSetRequests.remove(setId); }).afterDelay(kSmallDelayMs).send(); +#endif } } @@ -1527,17 +1835,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)) { @@ -1553,6 +1883,7 @@ void ApiWrap::saveStickerSets( reorderRequestId() = request(MTPmessages_ReorderStickerSets( MTP_flags(flags), MTP_vector(mtpOrder) +#endif )).done([=] { reorderRequestId() = 0; }).fail([=] { @@ -1622,6 +1953,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); @@ -1636,6 +1979,7 @@ void ApiWrap::saveStickerSets( requestId = request(MTPmessages_ClearRecentStickers( MTP_flags(flags) )).done(finish).fail(finish).send(); +#endif continue; } @@ -1656,6 +2000,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( @@ -1665,6 +2020,7 @@ void ApiWrap::saveStickerSets( }).fail([=](const MTP::Error &error, mtpRequestId requestId) { stickerSetDisenabled(requestId); }).afterDelay(kSmallDelayMs).send(); +#endif setDisenableRequests.insert(requestId); @@ -1707,6 +2063,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( @@ -1721,6 +2087,7 @@ void ApiWrap::saveStickerSets( mtpRequestId requestId) { stickerSetDisenabled(requestId); }).afterDelay(kSmallDelayMs).send(); +#endif setDisenableRequests.insert(requestId); @@ -1783,7 +2150,9 @@ void ApiWrap::saveStickerSets( if (setDisenableRequests.empty()) { stickersSaveOrder(); } else { +#if 0 // mtp requestSendDelayed(); +#endif } } @@ -1793,6 +2162,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) { @@ -1830,6 +2239,7 @@ void ApiWrap::joinChannel(not_null channel) { } _channelAmInRequests.remove(channel); }).send(); +#endif _channelAmInRequests.emplace(channel, requestId); @@ -1840,6 +2250,17 @@ void ApiWrap::joinChannel(not_null channel) { } 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, @@ -1856,8 +2277,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; @@ -1939,6 +2362,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) { @@ -1970,27 +2427,48 @@ 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 } +#if 0 // mtp session().mtp().sendAnything(); +#endif } void ApiWrap::saveDraftToCloudDelayed(not_null thread) { @@ -2000,6 +2478,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(); if (!_session->premium()) { @@ -2057,6 +2546,17 @@ void ApiWrap::clearHistory(not_null peer, bool 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), @@ -2071,13 +2571,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. @@ -2093,6 +2597,7 @@ void ApiWrap::deleteHistory( break; } } +#if 0 // mtp if (!history->lastMessageKnown()) { history->owner().histories().requestDialogEntry(history, [=] { Expects(history->lastMessageKnown()); @@ -2102,10 +2607,13 @@ void ApiWrap::deleteHistory( return; } deleteTillId = history->lastMessage()->id; +#endif } if (const auto channel = peer->asChannel()) { if (!justClear && !revoke) { +#if 0 // mtp channel->ptsSetWaitingForShortPoll(-1); +#endif leaveChannel(channel); } else { if (const auto migrated = peer->migrateFrom()) { @@ -2127,12 +2635,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) const { @@ -2167,6 +2692,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(); @@ -2207,7 +2733,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); @@ -2215,6 +2741,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) { @@ -2232,8 +2760,62 @@ 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)), + tl_int64(0)); // effect_id + }(); 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), @@ -2278,6 +2860,7 @@ void ApiWrap::saveDraftsToCloud() { checkQuitPreventFinished(); } }).send(); +#endif i->second = cloudDraft->saveRequestId; ++i; @@ -2307,7 +2890,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); @@ -2318,6 +2904,7 @@ void ApiWrap::clearModifyRequest(const QString &key) { _modifyRequests.remove(key); } +#if 0 // mtp void ApiWrap::gotStickerSet( uint64 setId, const MTPmessages_StickerSet &result) { @@ -2658,6 +3245,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu } _session->data().sendWebPageGamePollNotifications(); } +#endif void ApiWrap::updateStickers() { const auto now = crl::now(); @@ -2701,10 +3289,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); } @@ -2714,10 +3308,16 @@ void ApiWrap::setGroupEmojiSet( Expects(megagroup->mgInfo != nullptr); megagroup->mgInfo->emojiSet = set; + sender().request(TLsetSupergroupCustomEmojiStickerSet( + tl_int53(peerToChannel(megagroup->id).bare), + tl_int64(set.id) + )).send(); +#if 0 // mtp request(MTPchannels_SetEmojiStickers( megagroup->inputChannel, Data::InputStickerSet(set) )).send(); +#endif _session->changes().peerUpdated( megagroup, Data::PeerUpdate::Flag::EmojiSet); @@ -2737,6 +3337,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); @@ -2764,6 +3384,7 @@ std::vector> *ApiWrap::stickersByEmoji( _session->data().stickers().notifyUpdated( Data::StickersType::Stickers); }).send(); +#endif } if (it == _stickersByEmoji.end()) { _stickersByEmoji.emplace(key, StickersByEmoji()); @@ -2778,6 +3399,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; @@ -2795,6 +3417,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) { @@ -2802,6 +3432,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; @@ -2819,6 +3450,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) { @@ -2826,6 +3465,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; @@ -2843,6 +3483,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( @@ -2873,6 +3522,21 @@ void ApiWrap::requestRecentStickers( } requestId() = 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 const auto flags = attached ? MTPmessages_getRecentStickers::Flag::f_attached : MTPmessages_getRecentStickers::Flags(0); @@ -2903,6 +3567,7 @@ void ApiWrap::requestRecentStickers( LOG(("App Fail: Failed to get recent stickers!")); }).send(); +#endif } void ApiWrap::requestFavedStickers(std::optional now) { @@ -2912,6 +3577,23 @@ void ApiWrap::requestFavedStickers(std::optional now) { 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(now ? Api::CountFavedStickersHash(_session) : 0) )).done([=](const MTPmessages_FavedStickers &result) { @@ -2937,6 +3619,7 @@ void ApiWrap::requestFavedStickers(std::optional now) { LOG(("App Fail: Failed to get faved stickers!")); }).send(); +#endif } void ApiWrap::requestFeaturedStickers(TimeId now) { @@ -2944,6 +3627,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) { @@ -2954,6 +3654,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) { @@ -2961,6 +3662,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))) @@ -2972,6 +3690,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) { @@ -2979,6 +3698,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) { @@ -3001,6 +3733,7 @@ void ApiWrap::requestSavedGifs(TimeId now) { LOG(("App Fail: Failed to get saved gifs!")); }).send(); +#endif } void ApiWrap::readFeaturedSetDelayed(uint64 setId) { @@ -3013,13 +3746,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; } @@ -3028,6 +3767,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([=] { @@ -3035,6 +3782,7 @@ void ApiWrap::readFeaturedSets() { _session->data().stickers().notifyUpdated( Data::StickersType::Stickers); }).send(); +#endif _session->data().stickers().setFeaturedSetsUnreadCount(count); } @@ -3057,6 +3805,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. @@ -3133,6 +3932,7 @@ void ApiWrap::requestMessageAfterDate( MTP_int(minId), MTP_long(historyHash))); } +#endif } void ApiWrap::resolveJumpToHistoryDate( @@ -3179,11 +3979,17 @@ void ApiWrap::requestHistory( return; } + auto prepared = Api::PrepareHistoryRequest(peer, messageId, slice); +#if 0 // mtp const auto prepared = Api::PrepareHistoryRequest(peer, messageId, slice); auto &histories = history->owner().histories(); const auto requestType = Data::Histories::RequestType::History; histories.sendRequest(history, requestType, [=](Fn finish) { return request( +#endif + { + const auto finish = [] {}; + sender().request( std::move(prepared) ).done([=](const Api::HistoryRequestResult &result) { _historyRequests.remove(key); @@ -3201,7 +4007,10 @@ void ApiWrap::requestHistory( _historyRequests.remove(key); finish(); }).send(); + } +#if 0 // mtp }); +#endif _historyRequests.emplace(key); } @@ -3222,7 +4031,7 @@ void ApiWrap::requestSharedMedia( return; } - const auto prepared = Api::PrepareSearchRequest( + auto prepared = Api::PrepareSearchRequest( peer, topicRootId, type, @@ -3233,12 +4042,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( @@ -3251,9 +4066,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); } @@ -3396,14 +4224,39 @@ 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_int64(action.options.effectId), + 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) { @@ -3437,13 +4290,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(); @@ -3469,13 +4326,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(); @@ -3527,6 +4389,7 @@ void ApiWrap::sendSharedContact( Fn done) { sendAction(action); +#if 0 // mtp const auto history = action.history; const auto peer = history->peer; @@ -3573,6 +4436,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( @@ -3629,9 +4501,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) { @@ -3654,7 +4528,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()); @@ -3682,6 +4558,7 @@ void ApiWrap::sendFile( spoiler)); } +#if 0 // mtp void ApiWrap::sendUploadedPhoto( FullMsgId localId, Api::RemoteFileInfo info, @@ -3723,10 +4600,18 @@ void ApiWrap::cancelLocalItem(not_null item) { sendAlbumWithCancelled(item, groupId); } } +#endif void ApiWrap::sendShortcutMessages( not_null peer, BusinessShortcutId id) { + sender().request(TLsendQuickReplyShortcutMessages( + peerToTdbChat(peer->id), + tl_int32(id), + tl_int32(0) // sending_id + )).fail([=](const Error &error) { + }).send(); +#if 0 // mtp auto ids = QVector(); auto randomIds = QVector(); request(MTPmessages_SendQuickReplyMessages( @@ -3738,6 +4623,7 @@ void ApiWrap::sendShortcutMessages( applyUpdates(result); }).fail([=](const MTP::Error &error) { }).send(); +#endif } void ApiWrap::sendMessage(MessageToSend &&message) { @@ -3787,13 +4673,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, @@ -3809,11 +4696,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; @@ -3958,6 +4847,18 @@ void ApiWrap::sendMessage(MessageToSend &&message) { MTP_long(action.options.effectId) ), 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; } @@ -3990,6 +4891,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(show, error.message, chat, bot); + } + }).send(); +#if 0 // mtp const auto randomId = base::RandomValue(); if (!chat) { info->startToken = QString(); @@ -4007,6 +4921,7 @@ void ApiWrap::sendBotStart( ShowAddParticipantsError(show, type, chat, bot); } }).send(); +#endif } void ApiWrap::sendInlineResult( @@ -4016,8 +4931,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 @@ -4100,8 +5043,10 @@ void ApiWrap::sendInlineResult( UnixtimeFromMsgId(response.outerMsgId)); }); finishForwarding(action); +#endif } +#if 0 // mtp void ApiWrap::uploadAlbumMedia( not_null item, const MessageGroupId &groupId, @@ -4448,17 +5393,27 @@ void ApiWrap::sendAlbumIfReady(not_null album) { } }); } +#endif 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(); @@ -4477,10 +5432,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; @@ -4513,6 +5475,7 @@ void ApiWrap::requestBotCommonGroups( } }; const auto limit = 100; +#if 0 // mtp request(MTPmessages_GetCommonChats( bot->inputUser, MTP_long(0), // max_id @@ -4529,6 +5492,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({}); @@ -4538,12 +5516,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(), @@ -4557,8 +5548,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); } @@ -4584,6 +5577,7 @@ void ApiWrap::checkStatsSessions() { } } } +#endif Api::Authorizations &ApiWrap::authorizations() { return *_authorizations; diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index b6c65637906ba5..4be7c52ca1f2fa 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -13,7 +13,6 @@ For license and copyright information please follow this link: #include "data/stickers/data_stickers_set.h" #include "data/data_messages.h" #include "tdb/tdb_sender.h" -#include "tdb/tdb_resolve_chats.h" class TaskQueue; struct MessageGroupId; @@ -25,6 +24,13 @@ 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 { @@ -136,7 +142,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; @@ -151,12 +160,20 @@ class ApiWrap final : public MTP::Sender { [[nodiscard]] Tdb::Account &tdb() const; [[nodiscard]] Tdb::Sender &sender() const; + // For using legacy code MTP::Sender _api; _api(&apiwrap->instance()). + [[nodiscard]] Tdb::Sender &instance() const { + return sender(); + } + + bool apply(const Tdb::TLDupdateOption &update); +#if 0 // mtp void applyUpdates( const MTPUpdates &updates, uint64 sentMessageRandomId = 0) const; int applyAffectedHistory( PeerData *peer, // May be nullptr, like for deletePhoneCallHistory. const MTPmessages_AffectedHistory &result) const; +#endif void registerModifyRequest(const QString &key, mtpRequestId requestId); void clearModifyRequest(const QString &key); @@ -172,14 +189,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; @@ -193,6 +214,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( @@ -208,12 +230,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, @@ -226,6 +260,7 @@ class ApiWrap final : public MTP::Sender { void processFullPeer( not_null peer, const MTPmessages_ChatFull &result); +#endif void migrateChat( not_null chat, @@ -240,9 +275,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(); @@ -270,12 +307,19 @@ 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); + void saveDraftToCloudNow(not_null thread); + void manualClearCloudDraft(not_null thread); + void clearHistory(not_null peer, bool revoke); void deleteConversation(not_null peer, bool revoke); @@ -319,9 +363,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, @@ -345,6 +392,7 @@ class ApiWrap final : public MTP::Sender { TextWithTags &&caption, const SendAction &action); +#if 0 // mtp void sendUploadedPhoto( FullMsgId localId, Api::RemoteFileInfo info, @@ -355,6 +403,7 @@ class ApiWrap final : public MTP::Sender { Api::SendOptions options); void cancelLocalItem(not_null item); +#endif void sendShortcutMessages( not_null peer, @@ -370,11 +419,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, @@ -392,8 +443,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(); @@ -442,10 +495,12 @@ class ApiWrap final : public MTP::Sender { 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); @@ -454,6 +509,7 @@ class ApiWrap final : public MTP::Sender { void saveDraftsToCloud(); +#if 0 // mtp void resolveMessageDatas(); void finalizeMessageDataRequest( ChannelData *channel, @@ -477,6 +533,7 @@ 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); @@ -520,13 +577,17 @@ class ApiWrap final : public MTP::Sender { not_null peer, bool justClear, bool revoke); + +#if 0 // mtp void applyAffectedMessages( const MTPmessages_AffectedMessages &result) const; +#endif void deleteAllFromParticipantSend( not_null channel, not_null from); +#if 0 // mtp void uploadAlbumMedia( not_null item, const MessageGroupId &groupId, @@ -557,42 +618,53 @@ class ApiWrap final : public MTP::Sender { 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; @@ -644,7 +716,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; @@ -682,12 +756,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; @@ -695,14 +772,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; @@ -720,8 +801,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 0aba9b6a5ee8df..ff151acd65dfc0 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -46,11 +46,17 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_format_phone.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) @@ -62,6 +68,7 @@ bool IsValidPhone(QString phone) { || phone == u"4242"_q)); } +#if 0 // mtp void ChatCreateDone( not_null navigation, QImage image, @@ -118,14 +125,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( @@ -293,7 +306,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); @@ -444,6 +460,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, @@ -470,6 +505,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()) { @@ -716,31 +752,63 @@ 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(MTPmessages_CreateChat::Flag::f_ttl_period), MTP_vector(inputs), MTP_string(title), MTP_int(ttlPeriod()) )).done([=](const MTPmessages_InvitedUsers &result) { +#endif + _creationRequestId = _api.request(TLcreateNewBasicGroupChat( + tl_vector(std::move(inputs)), + tl_string(title), + tl_int32(_ttlPeriod) + )).done([=](const TLcreatedBasicGroupChat &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 peerId = peerFromTdbChat(result.data().vchat_id()); + const auto peer = navigation->session().data().peer(peerId); + 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) { @@ -813,6 +881,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) @@ -876,6 +945,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) { @@ -1021,6 +1114,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") @@ -1028,6 +1129,7 @@ void SetupChannelBox::prepare() { _checkRequestId = 0; firstCheckFail(parseError(error.type())); }).send(); +#endif addButton(tr::lng_settings_save(), [=] { save(); }); @@ -1268,6 +1370,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) @@ -1285,6 +1407,7 @@ void SetupChannelBox::save() { _saveRequestId = 0; updateFail(parseError(error.type())); }).send(); +#endif }; if (_saveRequestId) { return; @@ -1352,6 +1475,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) @@ -1369,6 +1511,7 @@ void SetupChannelBox::check() { _checkRequestId = 0; checkFail(parseError(error.type())); }).send(); +#endif } } @@ -1400,6 +1543,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; @@ -1418,6 +1576,7 @@ SetupChannelBox::UsernameResult SetupChannelBox::parseError( } else { return UsernameResult::Unknown; } +#endif } void SetupChannelBox::updateFail(UsernameResult result) { @@ -1631,6 +1790,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( @@ -1645,6 +1814,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 9bc7de65209c72..af99ec00d0e07a 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -11,6 +11,10 @@ For license and copyright information please follow this link: #include "base/timer.h" #include "mtproto/sender.h" +namespace Tdb { +class TLcheckChatUsernameResult; +} // namespace Tdb + class PeerListBox; struct RequestPeerQuery; @@ -198,7 +202,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); diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index 59729b7feddd2e..f2d97a81324155 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) { @@ -334,6 +339,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), @@ -344,6 +350,15 @@ 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), + tl_bool(true) + )).send(); const auto weak = Ui::MakeWeak(this); _forPeer->setWallPaper({}); @@ -364,11 +379,16 @@ void BackgroundBox::removePaper(const Data::WallPaper &paper) { weak->_inner->removePaper(paper); } session->data().removeWallpaper(paper); + session->sender().request(TLremoveInstalledBackground( + 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(), @@ -436,6 +456,13 @@ void BackgroundBox::Inner::requestPapers() { _session->data().cloudThemes().refreshChatThemes(); return; } + _api.request(TLgetInstalledBackgrounds( + )).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) { @@ -443,6 +470,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 828b2d095090ed..b22de5704bfe28 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -53,8 +53,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; [[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) { @@ -105,7 +111,10 @@ constexpr auto kMaxWallPaperSlugLength = 255; ? history->session().userId() : peerToUser(history->peer->id)), .date = base::unixtime::now(), + }, TextWithEntities{ text }); +#if 0 // mtp }, TextWithEntities{ text }, MTP_messageMediaEmpty()); +#endif return AdminLog::OwnedItem(delegate, item); } @@ -590,6 +599,36 @@ void BackgroundPreviewBox::uploadForPeer(bool both) { } 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->content, + 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()), + tl_bool(!both) + )).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()); @@ -645,6 +684,7 @@ void BackgroundPreviewBox::uploadForPeer(bool both) { } }).send(); }, _uploadLifetime); +#endif _uploadProgress = 0.; _radial.start(_uploadProgress); @@ -661,6 +701,8 @@ void BackgroundPreviewBox::setExistingForPeer( return; } } + +#if 0 // mtp const auto api = &_controller->session().api(); using Flag = MTPmessages_SetChatWallPaper::Flag; api->request(MTPmessages_SetChatWallPaper( @@ -675,6 +717,19 @@ void BackgroundPreviewBox::setExistingForPeer( )).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()), + tl_bool(!both) + )).send(); _forPeer->setWallPaper(paper); _controller->finishChatThemeEdit(_forPeer); @@ -831,18 +886,33 @@ void BackgroundPreviewBox::applyForEveryone() { && Data::IsCloudWallPaper(_paper); _controller->content()->setChatBackground(_paper, std::move(_full)); if (install) { + _controller->session().sender().request(TLsetDefaultBackground( + 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 4e99a01ba855d5..158dbacd6aacee 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(), @@ -42,10 +49,16 @@ Data::ChatFilter ChangedFilter( filter.colorIndex(), 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()); @@ -75,6 +88,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 = not_null(&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); @@ -99,6 +164,7 @@ void ChangeFilterById( // Revert filter on fail. history->owner().chatsFilters().set(was); }).send(); +#endif } } @@ -109,17 +175,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)) { @@ -128,6 +199,7 @@ bool ChooseFilterValidator::canRemove(FilterId filterId) const { && ((filter.always().size() > 1) || filter.flags()); } return false; +#endif } ChooseFilterValidator::LimitData ChooseFilterValidator::limitReached( @@ -135,6 +207,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); @@ -145,6 +219,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 621fd2c41e25cb..49446905e138a1 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -45,11 +45,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; @@ -648,7 +653,14 @@ void ProxiesBox::setupTopButton() { using namespace qthelp; const auto options = RegExOption::CaseInsensitive; +#if 0 // mtp for (const auto &[expression, _] : Core::LocalUrlHandlers()) { +#endif + const auto proxyurls = std::array{ + u"^socks/?\\?(.+)(#|$)"_q, + u"^proxy/?\\?(.+)(#|$)"_q, + }; + for (const auto &expression : proxyurls) { const auto midExpression = base::StringViewMid( expression, 1); @@ -1166,6 +1178,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() @@ -1182,9 +1195,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( @@ -1276,7 +1325,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 @@ -1335,8 +1433,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(); @@ -1368,6 +1468,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) { @@ -1457,6 +1558,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)) { @@ -1531,7 +1636,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(); @@ -1569,8 +1685,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) { @@ -1608,7 +1724,9 @@ void ProxiesBoxController::setTryIPv6(bool enabled) { return; } Core::App().settings().proxy().setTryIPv6(enabled); +#if 0 // mtp _account->mtp().restart(); +#endif _settings.connectionTypeChangesNotify(); saveDelayed(); } @@ -1633,10 +1751,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; }(); @@ -1681,3 +1805,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 25da434586d8cf..f8f609f5b302ea 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; @@ -95,8 +102,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; @@ -111,6 +123,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); @@ -120,6 +134,7 @@ class ProxiesBoxController { const not_null _account; Core::SettingsProxy &_settings; + Tdb::Sender _sender; int _idCounter = 0; std::vector _list; rpl::event_stream _views; @@ -132,4 +147,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 247cad699a4108..5c6bf16540e6e6 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()) { @@ -552,12 +573,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 96d8fa7829ea20..9a732ce167954f 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -1054,7 +1054,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 df85e625443e04..e2f6f1dbd7dcdb 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -47,8 +47,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; @@ -668,6 +672,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), @@ -678,6 +687,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 591bdf5144bd67..3ab378df8db4a3 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp @@ -39,8 +39,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; @@ -1006,6 +1011,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>(); @@ -1026,6 +1049,7 @@ void ExportFilterLink( }).fail([=](const MTP::Error &error) { fail(error.type()); }).send(); +#endif } void EditLinkChats( @@ -1039,6 +1063,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>(); @@ -1054,6 +1093,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 fd640e49630fc9..235d4c252f607b 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -60,6 +60,8 @@ 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 namespace { @@ -318,8 +320,12 @@ void ShareWithFriend( const auto content = navigation->parentController()->content(); return content->shareUrl( thread, +#if 0 // mtp MakeGiftCodeLink(&navigation->session(), slug).link, QString()); +#endif + { MakeGiftCodeLink(&navigation->session(), slug).link }, + true); }; Window::ShowChooseRecipientBox(navigation, chosen); } diff --git a/Telegram/SourceFiles/boxes/local_storage_box.cpp b/Telegram/SourceFiles/boxes/local_storage_box.cpp index fcdbd8d6d28686..7bcaf84d024f58 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/moderate_messages_box.cpp b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp index 9c68881a1ea54e..f504373b69d912 100644 --- a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp @@ -47,8 +47,13 @@ For license and copyright information please follow this link: #include "styles/style_layers.h" #include "styles/style_window.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace { +using namespace Tdb; + struct ModerateOptions final { bool allCanBan = false; bool allCanDelete = false; @@ -243,6 +248,9 @@ void CreateModerateMessagesBox( auto filtered = ranges::views::all( ids ) | ranges::views::transform([](const FullMsgId &id) { + return tl_int53(id.msg.bare); + }) | ranges::to>(); +#if 0 // mtp return MTP_int(id.msg); }) | ranges::to>(); c->session().api().request( @@ -251,6 +259,11 @@ void CreateModerateMessagesBox( p->input, MTP_vector(std::move(filtered))) ).send(); +#endif + c->session().sender().request(TLreportSupergroupSpam( + tl_int53(peerToChannel(c->id).bare), + tl_vector(filtered) + )).send(); }); } diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp index 840e4872d44d64..54c8bd973d9513 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; @@ -171,6 +174,7 @@ PasscodeBox::PasscodeBox( bool turningOff) : _session(session) , _api(&_session->mtp()) +, _unauthCloudPassword(std::nullopt) , _turningOff(turningOff) , _about(st::boxWidth - st::boxPadding.left() * 1.5) , _oldPasscode(this, st::defaultInputField, tr::lng_passcode_enter_old()) @@ -188,11 +192,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 +239,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 +458,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 +512,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 +560,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 +574,7 @@ void PasscodeBox::setPasswordFail(const QString &type) { } } +#if 0 // goodToRemove void PasscodeBox::setPasswordFail( const QByteArray &newPasswordBytes, const QString &email, @@ -541,6 +591,7 @@ void PasscodeBox::setPasswordFail( setPasswordFail(error.type()); } } +#endif void PasscodeBox::validateEmail( const QString &email, @@ -550,6 +601,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 +654,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 +679,7 @@ void PasscodeBox::validateEmail( _setRequest = 0; errors->fire(Lang::Hard::ServerError()); }).send(); +#endif }); const auto box = _replacedBy = getDelegate()->show( Passport::VerifyEmailBox( @@ -615,6 +705,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 +718,7 @@ void PasscodeBox::handleSrpIdInvalid() { requestPasswordData(); } } +#endif void PasscodeBox::save(bool force) { if (_setRequest) return; @@ -737,7 +829,36 @@ 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() }, + Ui::MakeWeak(this)); + } else { + Assert(_cloudFields.turningOff); + sendClearCloudPassword(oldPassword); + } + }, lifetime()); +#if 0 // goodToRemove checkPassword(oldPassword, [=](const Core::CloudPasswordResult &check) { if (const auto onstack = _cloudFields.customCheckCallback) { onstack(check, Ui::MakeWeak(this)); @@ -746,8 +867,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 +903,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 +922,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 +949,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 +1031,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 +1132,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 +1180,9 @@ void PasscodeBox::resetSecret( } }).send(); } +#endif +#if 0 // goodToRemove void PasscodeBox::sendChangeCloudPassword( const Core::CloudPasswordResult &check, const QString &newPassword, @@ -1038,6 +1228,7 @@ void PasscodeBox::sendChangeCloudPassword( setPasswordFail(newPasswordBytes, QString(), error); }).handleFloodErrors().send(); } +#endif void PasscodeBox::badOldPasscode() { _oldPasscode->selectAll(); @@ -1083,12 +1274,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 +1316,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 +1336,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 +1346,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 +1461,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 +1483,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 +1554,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 +1580,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 +1645,7 @@ void RecoverBox::checkSubmitFail(const MTP::Error &error) { _recoverCode->setFocus(); } } +#endif RecoveryEmailValidation ConfirmRecoveryEmail( not_null session, @@ -1369,6 +1658,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 +1720,10 @@ RecoveryEmailValidation ConfirmRecoveryEmail( errors->fire(Lang::Hard::ServerError()); } }).handleFloodErrors().send(); +#endif }; const auto resend = [=] { +#if 0 // goodToRemove if (*requestId) { return; } @@ -1414,6 +1735,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 b651cda64a8580..be193bd7318c19 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.h +++ b/Telegram/SourceFiles/boxes/passcode_box.h @@ -11,6 +11,8 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" #include "core/core_cloud_password.h" +#include "api/api_cloud_password.h" + namespace MTP { class Instance; } // namespace MTP @@ -36,12 +38,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; @@ -61,7 +65,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( @@ -75,7 +82,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: @@ -101,34 +110,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, @@ -138,22 +161,31 @@ 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; MTP::Sender _api; + std::optional _unauthCloudPassword = std::nullopt; + + Api::CloudPassword &cloudPassword(); QString _pattern; @@ -193,7 +225,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, @@ -217,7 +252,10 @@ 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; diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 53d56080abbf55..0b183fbdcb9a02 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -50,8 +50,12 @@ 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" + namespace { +using namespace Tdb; + constexpr auto kSortByOnlineThrottle = 3 * crl::time(1000); constexpr auto kSearchPerPage = 50; @@ -206,6 +210,7 @@ bool PeerListGlobalSearchController::searchInCache() { } void PeerListGlobalSearchController::searchOnServer() { +#if 0 // goodToRemove _requestId = _api.request(MTPcontacts_Search( MTP_string(_query), MTP_int(SearchPeopleLimit) @@ -217,19 +222,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; @@ -237,6 +261,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( @@ -246,10 +271,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(); } } @@ -879,6 +915,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, @@ -905,6 +959,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 20479c2b47393e..4f207fb3b803fc 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -13,6 +13,14 @@ For license and copyright information please follow this link: #include "base/timer.h" #include "mtproto/sender.h" +namespace Tdb { +class TLchats; +} // namespace Tdb + +namespace Tdb { +class TLchats; +} // namespace Tdb + class History; namespace style { @@ -81,14 +89,20 @@ 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; MTP::Sender _api; base::Timer _timer; QString _query; mtpRequestId _requestId = 0; +#if 0 // goodToRemove std::map _cache; +#endif + std::map _cache; std::map _queries; }; 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 14e00151b4931c..75ad0122caca8b 100644 --- a/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_bot_to_chat_box.cpp @@ -29,8 +29,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 { @@ -157,6 +161,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( @@ -181,6 +200,7 @@ void AddBotToGroupBoxController::requestExistingRights( _promotedBy = 0; addBotToGroup(_existingRightsChannel); }).send(); +#endif } void AddBotToGroupBoxController::addBotToGroup(not_null chat) { @@ -197,7 +217,10 @@ void AddBotToGroupBoxController::addBotToGroup(not_null chat) { _existingRightsChannel = nullptr; _promotedSince = 0; _promotedBy = 0; +#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 ab48b6b8d826db..359fb86a04b72f 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -44,6 +44,8 @@ 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" + namespace { constexpr auto kParticipantsFirstPageCount = 16; @@ -213,6 +215,20 @@ void SimpleForbiddenBox( const auto &stButton = st::premiumGiftBox; box->setStyle(stButton); + const auto startSubscription = [=] { + // later do async - it is still slow if done after launch + const auto features = Settings::GetPremiumFeaturesSync( + &peer->session(), + u"invite_privacy"_q); + const auto window = ChatHelpers::ResolveWindowDefault()( + &peer->session(), + ChatHelpers::WindowUsage::PremiumPromo); + if (window) { + Settings::CreateStartSubscription( + window, + features ? &*features : nullptr)(); + } + }; auto raw = Settings::CreateSubscribeButton( sshow, ChatHelpers::ResolveWindowDefault(), @@ -221,6 +237,8 @@ void SimpleForbiddenBox( .computeRef = [] { return u"invite_privacy"_q; }, .text = tr::lng_messages_privacy_premium_button(), .showPromo = true, + + .startSubscription = startSubscription, }); auto button = object_ptr::fromRaw(raw); button->resizeToWidth(st::boxWideWidth @@ -380,6 +398,20 @@ void InviteForbiddenController::setComplexCover() { const auto show = delegate()->peerListUiShow(); FillUpgradeToPremiumCover(container, show, _peer, _forbidden); + const auto startSubscription = [peer = _peer] { + // later do async - it is still slow if done after launch + const auto features = Settings::GetPremiumFeaturesSync( + &peer->session(), + u"invite_privacy"_q); + const auto window = ChatHelpers::ResolveWindowDefault()( + &peer->session(), + ChatHelpers::WindowUsage::PremiumPromo); + if (window) { + Settings::CreateStartSubscription( + window, + features ? &*features : nullptr)(); + } + }; container->add( object_ptr::fromRaw( Settings::CreateSubscribeButton( @@ -389,6 +421,8 @@ void InviteForbiddenController::setComplexCover() { .parent = container, .computeRef = [] { return u"invite_privacy"_q; }, .text = tr::lng_messages_privacy_premium_button(), + + .startSubscription = startSubscription, })), st::inviteForbiddenSubscribePadding); @@ -1107,6 +1141,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(), @@ -1143,6 +1178,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) { @@ -1170,6 +1237,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 @@ -1180,6 +1248,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(); @@ -1475,6 +1550,11 @@ 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) @@ -1576,6 +1656,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)), @@ -1587,6 +1668,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; @@ -1603,7 +1693,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()); @@ -1622,6 +1715,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(); @@ -1629,12 +1725,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) { @@ -1657,6 +1755,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(); } @@ -1667,6 +1784,7 @@ void AddSpecialBoxSearchController::requestGlobal() { return; } +#if 0 // goodToRemove auto perPage = SearchPeopleLimit; _requestId = _api.request(MTPcontacts_Search( MTP_string(_query), @@ -1674,6 +1792,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; @@ -1683,16 +1810,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; @@ -1701,9 +1832,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); @@ -1715,8 +1851,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 24d5e1031d8708..ea77a0a9e32ae1 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.h +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.h @@ -181,10 +181,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; @@ -194,12 +197,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(); @@ -221,7 +230,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 4bc2848c787423..569be469c1db15 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp @@ -27,8 +27,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; + QString UserPhone(not_null user) { const auto phone = user->phone(); return phone.isEmpty() @@ -45,6 +49,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 +60,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->barSettings()) { const auto flags = PeerBarSetting::AddContact | PeerBarSetting::BlockContact diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index be668fcd3a8147..cbdf885ab9fc7e 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -39,8 +39,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); using DefaultIcon = Data::TopicIconDescriptor; @@ -547,6 +552,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()); @@ -573,6 +594,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 216a0431d77ad2..79d75dd7e5c394 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp @@ -22,8 +22,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().appConfig().get( u"hidden_members_group_size_min"_q, @@ -63,6 +68,7 @@ namespace { button->toggledValue( ) | rpl::start_with_next([=](bool toggled) { +#if 0 // mtp megagroup->session().api().request( MTPchannels_ToggleParticipantsHidden( megagroup->inputChannel, @@ -71,6 +77,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 e4166a82c0d6e7..39c0636ff4861b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -44,6 +44,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; @@ -589,6 +591,7 @@ void EditAdminBox::transferOwnership() { return; } +#if 0 // goodToRemove const auto channel = peer()->isChannel() ? peer()->asChannel()->inputChannel : MTP_inputChannelEmpty(); @@ -601,6 +604,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(); @@ -676,6 +698,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, @@ -683,6 +712,7 @@ void EditAdminBox::sendTransferRequestFrom( result.result )).done([=](const MTPUpdates &result) { api->applyUpdates(result); +#endif if (!box && !weak) { return; } @@ -695,6 +725,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; @@ -704,6 +744,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); @@ -733,7 +774,10 @@ void EditAdminBox::sendTransferRequestFrom( if (weak && !recoverable) { closeBox(); } +#if 0 // goodToRemove })).handleFloodErrors().send(); +#endif + })).send(); } EditRestrictedBox::EditRestrictedBox( diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index 345d9880ef1f09..b172105436af2d 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(tl_int32(0)) // member_until_date + )).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( @@ -77,6 +105,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(show, error.message, chat, user); + if (onFail) { + onFail(); + } + }).send(); +#if 0 // goodToRemove chat->session().api().request(MTPmessages_AddChatUser( chat->inputChat, user->inputUser, @@ -97,8 +140,10 @@ void AddChatParticipant( onFail(); } }).send(); +#endif } +#if 0 // goodToRemove void SaveChatAdmin( std::shared_ptr show, not_null chat, @@ -186,6 +231,7 @@ void SaveChatParticipantKick( } }).send(); } +#endif } // namespace @@ -205,6 +251,66 @@ 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( + show, + message, + peer->asChannel(), + user); + } else if (peer->isChat() + && isAdmin + && (message == u"USER_NOT_PARTICIPANT"_q)) { + AddChatParticipant(show, 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( show, @@ -233,6 +339,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) { Api::ChatParticipants::Restrict( channel, @@ -274,6 +421,7 @@ Fn 0) ? kParticipantsPerPage : kParticipantsFirstPageCount; +#if 0 // goodToRemove const auto participantsHash = uint64(0); _loadRequestId = _api.request(MTPchannels_GetParticipants( @@ -1526,6 +1688,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(); @@ -1931,7 +2121,10 @@ void ParticipantsBoxController::removeAdminSure(not_null user) { if (const auto chat = _peer->asChat()) { const auto show = delegate()->peerListUiShow(); +#if 0 // goodToRemove SaveChatAdmin(show, chat, user, false, crl::guard(this, [=] { +#endif + RemoveAdmin(chat, user, {}, crl::guard(this, [=] { editAdminDone( user, ChatAdminRightsInfo(), @@ -2169,6 +2362,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, @@ -2177,6 +2371,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(); } @@ -2184,10 +2385,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(); @@ -2212,6 +2416,11 @@ void ParticipantsBoxController::refreshRows() { delegate()->peerListRefreshRows(); } +struct ParticipantsBoxSearchController::CacheEntry { + TLchatMembers result; + int requestedCount = 0; +}; + ParticipantsBoxSearchController::ParticipantsBoxSearchController( not_null channel, Role role, @@ -2295,6 +2504,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. @@ -2308,11 +2518,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( @@ -2326,6 +2552,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; @@ -2342,7 +2577,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) { @@ -2358,6 +2596,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(); @@ -2365,12 +2606,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) { @@ -2394,6 +2637,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 b993c740c599f1..2b41b367ddcf06 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h @@ -13,6 +13,14 @@ For license and copyright information please follow this link: #include "base/weak_ptr.h" #include "info/profile/info_profile_members_controllers.h" +namespace Tdb { +class TLchatMembers; +} // namespace Tdb + +namespace Tdb { +class TLchatMembers; +} // namespace Tdb + class PeerListStories; struct ChatAdminRightsInfo; struct ChatRestrictionsInfo; @@ -334,10 +342,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; @@ -347,7 +358,10 @@ 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; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index b2e292a3cbeeea..a4ad056067c60e 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -60,8 +60,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); @@ -332,7 +338,10 @@ PreviewWrap::PreviewWrap( }, TextWithEntities{ _peer->isSelf() ? tr::lng_settings_color_reply(tr::now) : tr::lng_settings_color_reply_channel(tr::now), +})) +#if 0 // mtp }, MTP_messageMediaEmpty())) +#endif , _replyItem(_history->addNewLocalMessage({ .id = _history->nextNonHistoryEntryId(), .flags = (MessageFlag::FakeHistoryItem @@ -345,6 +354,8 @@ PreviewWrap::PreviewWrap( }, TextWithEntities{ _peer->isSelf() ? 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( @@ -353,9 +364,16 @@ PreviewWrap::PreviewWrap( MTPstring(), MTP_int(0))))) , _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); @@ -551,6 +569,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(values.colorIndex), + tl_int64(values.backgroundEmojiId))); + } else if (const auto channel = peer->asChannel()) { + send(TLsetChatAccentColor( + peerToTdbChat(channel->id), + tl_int32(values.colorIndex), + tl_int64(values.backgroundEmojiId))); +#if 0 // mtp const auto fail = [=](const MTP::Error &error) { const auto type = error.type(); if (type != u"CHAT_NOT_MODIFIED"_q) { @@ -577,6 +614,7 @@ void Set( channel->inputChannel, MTP_int(values.colorIndex), MTP_long(values.backgroundEmojiId))); +#endif if (values.statusChanged && (values.statusId || peer->emojiStatusId())) { @@ -1459,9 +1497,14 @@ void CheckBoostLevel( not_null peer, Fn(int level)> askMore, Fn cancel) { + peer->session().sender().request(TLgetChatBoostStatus( + peerToTdbChat(peer->id) + )).done([=](const TLchatBoostStatus &result) { +#if 0 // mtp peer->session().api().request(MTPpremium_GetBoostsStatus( peer->input )).done([=](const MTPpremium_BoostsStatus &result) { +#endif const auto &data = result.data(); if (const auto channel = peer->asChannel()) { channel->updateLevelHint(data.vlevel().v); @@ -1479,13 +1522,20 @@ void CheckBoostLevel( 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, .reason = *reason, }, 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(); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 4b11e3745d9739..bcf680ad6d46dd 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -77,10 +77,14 @@ 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" + #include namespace { +using namespace Tdb; + constexpr auto kBotManagerUsername = "BotFather"_cs; [[nodiscard]] auto ToPositiveNumberString() { @@ -153,7 +157,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, @@ -179,6 +201,7 @@ void SaveDefaultRestrictions( } done(); }).send(); +#endif api->registerModifyRequest(key, requestId); } @@ -189,7 +212,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) @@ -206,6 +240,7 @@ void SaveSlowmodeSeconds( channel->setSlowmodeSeconds(seconds); done(); }).send(); +#endif api->registerModifyRequest(key, requestId); } @@ -216,6 +251,20 @@ void SaveBoostsUnrestrict( Fn done) { const auto api = &channel->session().api(); const auto key = Api::RequestKey("boosts_unrestrict", channel->id); + const auto requestId = channel->session().sender().request( + TLsetSupergroupUnrestrictBoostCount( + tl_int53(peerToChannel(channel->id).bare), + tl_int32(boostsUnrestrict)) + ).done([=] { + api->clearModifyRequest(key); + channel->setBoostsUnrestrict( + channel->boostsApplied(), + boostsUnrestrict); + done(); + }).fail([=] { + api->clearModifyRequest(key); + }).send(); +#if 0 // mtp const auto requestId = api->request( MTPchannels_SetBoostsToUnblockRestrictions( channel->inputChannel, @@ -237,6 +286,7 @@ void SaveBoostsUnrestrict( boostsUnrestrict); done(); }).send(); +#endif api->registerModifyRequest(key, requestId); } @@ -416,7 +466,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; @@ -446,7 +499,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 @@ -848,6 +904,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) { @@ -860,6 +930,7 @@ void Controller::showEditLinkedChatBox() { for (const auto &item : list) { chats.emplace_back(_peer->owner().processChat(item)); } +#endif *box = _navigation->parentController()->show(EditLinkedChatBox( _navigation, channel, @@ -1463,9 +1534,14 @@ void Controller::editReactions() { return; } _controls.levelRequested = true; +#if 0 // mtp _api.request(MTPpremium_GetBoostsStatus( _peer->input )).done([=](const MTPpremium_BoostsStatus &result) { +#endif + _api.request(TLgetChatBoostStatus( + peerToTdbChat(_peer->id) + )).done([=](const TLchatBoostStatus &result) { _controls.levelRequested = false; if (const auto channel = _peer->asChannel()) { channel->updateLevelHint(result.data().vlevel().v); @@ -1902,8 +1978,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() @@ -1956,6 +2036,7 @@ void Controller::saveUsername() { } const auto newUsername = (*_savingData.username); +#if 0 // goodToRemove _api.request(MTPchannels_UpdateUsername( channel->inputChannel, MTP_string(newUsername) @@ -1973,7 +2054,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) { @@ -2008,12 +2100,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(); @@ -2026,7 +2126,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(); @@ -2094,6 +2194,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() { @@ -2101,6 +2224,7 @@ void Controller::saveDescription() { || *_savingData.description == _peer->about()) { return continueSave(); } +#if 0 // goodToRemove const auto successCallback = [=] { _peer->setAbout(*_savingData.description); continueSave(); @@ -2149,6 +2273,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() { @@ -2179,6 +2315,17 @@ 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) { + const auto show = controller->uiShow(); + _peer->session().api().sendBotStart(show, bot, bot, command); + controller->showPeerHistory(bot); + } + }).send(); +#if 0 // mtp _api.request(MTPcontacts_ResolveUsername( MTP_string(kBotManagerUsername.utf16()) )).done([=](const MTPcontacts_ResolvedPeer &result) { @@ -2192,6 +2339,7 @@ void Controller::toggleBotManager(const QString &command) { controller->showPeerHistory(bot); } }).send(); +#endif } void Controller::togglePreHistoryHidden( @@ -2199,6 +2347,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. @@ -2209,6 +2358,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) @@ -2222,6 +2376,7 @@ void Controller::togglePreHistoryHidden( fail(); } }).send(); +#endif } void Controller::saveForum() { @@ -2242,6 +2397,7 @@ void Controller::saveForum() { crl::guard(this, saveForChannel)); return; } +#if 0 // mtp _api.request(MTPchannels_ToggleForum( channel->inputChannel, MTP_bool(*_savingData.forum) @@ -2258,6 +2414,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() { @@ -2272,6 +2437,7 @@ void Controller::saveSignatures() { == channel->signatureProfiles()))) { return continueSave(); } +#if 0 // mtp using Flag = MTPchannels_ToggleSignatures::Flag; _api.request(MTPchannels_ToggleSignatures( MTP_flags(Flag() @@ -2292,6 +2458,17 @@ void Controller::saveSignatures() { cancelSave(); } }).send(); +#endif + _api.request(TLtoggleSupergroupSignMessages( + tl_int53(peerToChannel(channel->id).bare), + tl_bool(*_savingData.signatures), + tl_bool(*_savingData.signatureProfiles) + )).done([=] { + // CHAT_NOT_MODIFIED is processed as TLok. + continueSave(); + }).fail([=] { + cancelSave(); + }).send(); } void Controller::saveForwards() { @@ -2299,6 +2476,7 @@ void Controller::saveForwards() { || *_savingData.noForwards != _peer->allowsForwarding()) { return continueSave(); } +#if 0 // mtp _api.request(MTPmessages_ToggleNoForwards( _peer->input, MTP_bool(*_savingData.noForwards) @@ -2312,6 +2490,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() { @@ -2321,6 +2509,7 @@ void Controller::saveJoinToWrite() { || *_savingData.joinToWrite == joinToWrite) { return continueSave(); } +#if 0 // mtp _api.request(MTPchannels_ToggleJoinToSend( _peer->asChannel()->inputChannel, MTP_bool(*_savingData.joinToWrite) @@ -2334,6 +2523,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() { @@ -2343,6 +2545,7 @@ void Controller::saveRequestToJoin() { || *_savingData.requestToJoin == requestToJoin) { return continueSave(); } +#if 0 // mtp _api.request(MTPchannels_ToggleJoinRequest( _peer->asChannel()->inputChannel, MTP_bool(*_savingData.requestToJoin) @@ -2356,6 +2559,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() { @@ -2402,6 +2618,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) { @@ -2411,6 +2628,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 8e1a110303551d..4ca7c261a79733 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -57,12 +57,17 @@ For license and copyright information please follow this link: #include "styles/style_menu_icons.h" #include "styles/style_premium.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + #include #include #include namespace { +using namespace Tdb; + constexpr auto kFirstPage = 20; constexpr auto kPerPage = 100; // constexpr auto kShareQrSize = 768; @@ -256,7 +261,10 @@ class Controller final Ui::RpWidget *_headerWidget = nullptr; rpl::variable _addedHeight; +#if 0 // goodToRemove MTP::Sender _api; +#endif + Sender _api; rpl::lifetime _lifetime; }; @@ -396,7 +404,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; @@ -887,6 +898,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 @@ -898,14 +910,55 @@ 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_bool(false), // only_with_expired_subscription + 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 e3f387062d0b4b..4aa52602b7283d 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp @@ -35,6 +35,8 @@ For license and copyright information please follow this link: #include +#include "tdb/tdb_tl_scheme.h" + namespace { enum class Color { @@ -786,7 +788,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() { @@ -802,6 +807,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) { @@ -821,6 +841,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 2258f6c215320e..88075e9f29822d 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -45,6 +45,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; @@ -1231,10 +1233,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 f7b1a6b4c71c5c..a92cf1cf1b8b7b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp @@ -620,6 +620,15 @@ void AddReactionsText( } // namespace +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + +namespace { + +using namespace Tdb; + +} // namespace + void EditAllowedReactionsBox( not_null box, EditAllowedReactionsArgs &&args) { @@ -961,6 +970,24 @@ 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 max = Data::UniqueReactionsLimit(peer->session().user()); + const auto allowedMax = allowed.maxCount ? allowed.maxCount : max; + const auto updated = (allowed.type != Type::Some) + ? tl_chatAvailableReactionsAll(tl_int32(allowedMax)) + : tl_chatAvailableReactionsSome( + MTP_vector(ids), + tl_int32(allowedMax)); + peer->session().sender().request(TLsetChatAvailableReactions( + peerToTdbChat(peer->id), + updated + )).send(); + +#if 0 // mtp auto ids = allowed.some | ranges::views::transform( Data::ReactionToMTP ) | ranges::to>; @@ -1001,4 +1028,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 c28847bcc3ebb9..247d0264302393 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; @@ -308,6 +310,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 +338,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)) { @@ -597,6 +622,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 +672,7 @@ bool RequestsBoxSearchController::loadMoreRows() { }); } }); +#endif searchDone(requestId, items, limit); auto it = _queries.find(requestId); @@ -636,7 +685,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_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp index b77a9e1d294e40..5110b84297adac 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp @@ -47,8 +47,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( @@ -146,7 +150,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; @@ -178,7 +185,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()) @@ -552,6 +562,50 @@ 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->current() == Privacy::HasUsername) { + askUsernameRevoke(); + } + }, [&](const TLDcheckChatUsernameResultPublicGroupsUnavailable &) { + _usernameState = UsernameState::NotAvailable; + _controls.privacy->setValue(Privacy::NoUsername); + }, [&](const TLDcheckChatUsernameResultUsernamePurchasable &) { + _goodUsername = false; + _usernameCheckInfo.fire( + UsernameCheckInfo::PurchaseAvailable(checking, _peer)); + }); + }).fail([=](const Error &error) { + _checkUsernameRequestId = 0; + _usernameState = UsernameState::Normal; + const auto &type = error.message; + if (initial) { + if (_controls.privacy->current() == 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 +646,7 @@ void Controller::checkUsernameAvailability() { showUsernameError(tr::lng_create_channel_link_occupied()); } }).send(); +#endif } void Controller::askUsernameRevoke() { diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index a3bc8b69bd374a..e6ff07e0aa9119 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -36,8 +36,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 { @@ -382,18 +386,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) { @@ -401,7 +417,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, }); @@ -410,6 +429,7 @@ ForChannelBoostSlots ParseForChannelBoostSlots( return result; } +#if 0 // mtp Ui::BoostCounters ParseBoostCounters( const MTPpremium_BoostsStatus &status) { const auto &data = status.data(); @@ -422,6 +442,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()), + }; +} Ui::BoostFeatures LookupBoostFeatures(not_null channel) { auto nameColorsByLevel = base::flat_map(); diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h index c84b0d306cbf9f..de4370d0878a39 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 + class ChannelData; namespace Main { @@ -35,12 +40,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]] Ui::BoostFeatures LookupBoostFeatures( not_null channel); diff --git a/Telegram/SourceFiles/boxes/pin_messages_box.cpp b/Telegram/SourceFiles/boxes/pin_messages_box.cpp index 163e9dedc6f5f9..9c2486066e11a5 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 325e92c4438cde..23bfa592da646e 100644 --- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp @@ -39,8 +39,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; @@ -211,7 +218,10 @@ InactiveController::InactiveController(not_null session) InactiveController::~InactiveController() { if (_requestId) { + _session->sender().request(_requestId).cancel(); +#if 0 // mtp _session->api().request(_requestId).cancel(); +#endif } } @@ -220,6 +230,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; @@ -235,6 +259,7 @@ void InactiveController::prepare() { delegate()->peerListRefreshRows(); _count = delegate()->peerListFullRowsCount(); }).send(); +#endif } void InactiveController::rowClicked(not_null row) { @@ -305,7 +330,10 @@ PublicsController::PublicsController( PublicsController::~PublicsController() { if (_requestId) { + _navigation->session().sender().request(_requestId).cancel(); +#if 0 // mtp _navigation->session().api().request(_requestId).cancel(); +#endif } } @@ -318,6 +346,22 @@ rpl::producer PublicsController::countValue() 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) { @@ -338,6 +382,7 @@ void PublicsController::prepare() { } _count = delegate()->peerListFullRowsCount(); }).send(); +#endif } void PublicsController::rowClicked(not_null row) { @@ -365,6 +410,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() @@ -376,6 +433,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 f163be612296d7..c8c4c30dafae95 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -47,6 +47,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 { @@ -547,7 +549,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; } @@ -1123,12 +1128,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); @@ -1232,10 +1251,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 8a71e395b89d19..a5e3f630b10a32 100644 --- a/Telegram/SourceFiles/boxes/reactions_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/reactions_settings_box.cpp @@ -42,6 +42,8 @@ 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 { void AddMessage( diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index 3ac13da2017d45..b5eba9ae56ca11 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -41,9 +41,15 @@ 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 "tdb/tdb_sender.h" +#include "api/api_text_entities.h" + namespace Ui { namespace { +using namespace Tdb; + struct PaidMediaData { const Data::Invoice *invoice = nullptr; HistoryItem *item = nullptr; @@ -270,6 +276,7 @@ void SendCreditsBox( const auto show = box->uiShow(); const auto weak = MakeWeak(box.get()); state->confirmButtonBusy = true; +#if 0 // mtp session->api().request( MTPpayments_SendStarsForm( MTP_long(form->formId), @@ -305,6 +312,26 @@ void SendCreditsBox( show->showToast(id); } }).send(); +#endif + session->sender().request(TLsendPaymentForm( + tl_inputInvoiceTelegram( + tl_telegramPaymentPurposeStars( + tl_string(form->invoice.currency), + tl_int53(form->invoice.amount), + tl_int53(form->invoice.credits))), + tl_int64(form->formId), + tl_string(), + tl_string(), + null, + tl_int53(0) // tip_amount + )).done([=] { + state->confirmButtonBusy = false; + box->closeBox(); + sent(); + }).fail([=](const Error &error) { + state->confirmButtonBusy = false; + box->uiShow()->showToast(error.message); + }).send(); }); { using namespace Info::Statistics; @@ -444,6 +471,7 @@ void SendStarGift( not_null session, std::shared_ptr data, Fn)> done) { +#if 0 // mtp session->api().request(MTPpayments_SendStarsForm( MTP_long(data->formId), data->inputInvoice @@ -456,6 +484,18 @@ void SendStarGift( }).fail([=](const MTP::Error &error) { done(error.type()); }).send(); +#endif + const auto &gift = v::get(data->id.value); + session->sender().request(TLsendGift( + tl_int64(gift.giftId), + tl_int53(peerToUser(gift.user->id).bare), + Api::FormattedTextToTdb(gift.message), + tl_bool(gift.anonymous) + )).done([=] { + done(std::nullopt); + }).fail([=](const Error &error) { + done(error.message); + }).send(); } } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/sessions_box.cpp b/Telegram/SourceFiles/boxes/sessions_box.cpp index 4328fc4431d4fe..95d12b77702fa1 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 bd154f8c4da5d9..d001087603b6a4 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -55,9 +55,19 @@ For license and copyright information please follow this link: #include "styles/style_boxes.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" +#include "api/api_sending.h" // ScheduledToTL + #include #include +namespace { + +using namespace Tdb; + +} // namespace + class ShareBox::Inner final : public Ui::RpWidget { public: Inner( @@ -74,8 +84,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); @@ -365,6 +378,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); @@ -377,6 +409,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) @@ -385,6 +428,7 @@ bool ShareBox::searchByUsername(bool searchCache) { }).fail([=](const MTP::Error &error, mtpRequestId requestId) { peopleFail(error, requestId); }).send(); +#endif _peopleQueries.insert(_peopleRequest, _peopleQuery); } } @@ -397,6 +441,7 @@ void ShareBox::needSearchByUsername() { } } +#if 0 // goodToRemove void ShareBox::peopleDone( const MTPcontacts_Found &result, mtpRequestId requestId) { @@ -434,6 +479,7 @@ void ShareBox::peopleFail(const MTP::Error &error, mtpRequestId requestId) { _peopleFull = true; } } +#endif void ShareBox::setInnerFocus() { if (_comment->isHidden()) { @@ -1341,13 +1387,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) { @@ -1356,6 +1406,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; @@ -1374,8 +1433,11 @@ void ShareBox::Inner::peopleReceived( } } }; +#if 0 // goodToRemove feedList(my); feedList(people); +#endif + feedList(); _searching = false; refresh(); @@ -1468,6 +1530,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( return; } +#if 0 // goodToRemove using Flag = MTPmessages_ForwardMessages::Flag; const auto commonSendFlags = Flag(0) | Flag::f_with_my_score @@ -1490,6 +1553,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( } return result; }; +#endif auto &api = history->owner().session().api(); auto &histories = history->owner().histories(); const auto donePhraseArgs = CreateForwardedMessagePhraseArgs( @@ -1511,6 +1575,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) { const auto session = &threadHistory->session(); @@ -1560,6 +1625,81 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( return threadHistory->sendRequestId; }); state->requests.insert(threadHistory->sendRequestId); +#endif + const auto requestDone = [=] { + if (state->requests.empty()) { + if (show->valid()) { + show->showToast(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_int64(options.effectId), + 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()), + Tdb::tl_bool(false)); // new_show_caption_above_media + }(); + 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()); + } } }; } diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index d0bf28ce91e2ca..dcec6464e563ef 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -17,6 +17,10 @@ For license and copyright information please follow this link: class History; +namespace Tdb { +class TLchats; +} // namespace Tdb + namespace style { struct MultiSelect; struct InputField; @@ -146,10 +150,12 @@ 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); @@ -174,7 +180,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 14902a9eeed641..9837435f5efb1c 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -62,12 +62,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); @@ -78,7 +84,10 @@ constexpr auto kStickerMoveDuration = crl::time(200); using Data::StickersSet; using Data::StickersPack; using SetFlag = Data::StickersSetFlag; +#if 0 // mtp using TLStickerSet = MTPmessages_StickerSet; +#endif +using TLStickerSet = TLstickerSet; [[nodiscard]] std::optional ComputeImageColor( const style::icon &lockIcon, @@ -380,7 +389,10 @@ 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 installDone(const MTPmessages_StickerSetInstallResult &result); +#endif + void installDone(); void requestReorder(not_null document, int index); void fillDeleteStickerBox(not_null box, int index); @@ -674,6 +686,7 @@ void ChangeSetNameBox( const auto buttonWidth = state->saveButton ? state->saveButton->width() : 0; +#if 0 // mtp state->requestId = data->session().api().request( MTPstickers_RenameStickerSet( Data::InputStickerSet(input), @@ -690,6 +703,31 @@ void ChangeSetNameBox( show->showToast(error.type()); close(); }).send(); +#endif + const auto &sets = data->stickers().sets(); + const auto it = sets.find(input.id); + if (it == sets.end()) { + show->showToast(u"Set not found!"_q); + return; + } + state->requestId = data->session().sender().request( + TLsetStickerSetTitle( + tl_string(it->second->shortName), + tl_string(text)) + ).done([=] { + state->requestId = data->session().sender().request( + TLgetStickerSet(tl_int64(input.id)) + ).done([=](const TLstickerSet &result) { + done(result); + close(); + }).fail([=](const Error &error) { + show->showToast(error.message); + close(); + }).send(); + }).fail([=](const Error &error) { + show->showToast(error.message); + close(); + }).send(); if (state->saveButton) { state->saveButton->resizeToWidth(buttonWidth); } @@ -712,7 +750,11 @@ void ChangeSetNameBox( state->requestId.value() | rpl::map(rpl::mappers::_1 > 0)); } box->addButton(tr::lng_cancel(), [=] { +#if 0 // mtp data->session().api().request(state->requestId.current()).cancel(); +#endif + data->session().sender().request( + state->requestId.current()).cancel(); close(); }); } @@ -902,6 +944,7 @@ StickerSetBox::Inner::Inner( , _previewTimer([=] { showPreview(); }) { setAttribute(Qt::WA_OpaquePaintEvent); +#if 0 // mtp _api.request(MTPmessages_GetStickerSet( Data::InputStickerSet(_input), MTP_int(0) // hash @@ -911,6 +954,27 @@ 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) { + applySet(result); + }).fail([=] { + _loaded = true; + _errors.fire(Error::NotFound); + }).send(); + } else { + _api.request(TLsearchStickerSet( + tl_string(_input.shortName), + tl_bool(true) + )).done([=](const TLstickerSet &result) { + applySet(result); + }).fail([=] { + _loaded = true; + _errors.fire(Error::NotFound); + }).send(); + } _session->api().updateStickers(); @@ -930,14 +994,21 @@ void StickerSetBox::Inner::applySet(const TLStickerSet &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); @@ -948,6 +1019,46 @@ void StickerSetBox::Inner::applySet(const TLStickerSet &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(); + }(); + _setThumbnailType = [&] { + if (const auto thumbnail = data.vthumbnail()) { + return thumbnail->data().vformat().match([]( + const TLDthumbnailFormatTgs &) { + return StickerType::Tgs; + }, [](const TLDthumbnailFormatWebm &) { + return StickerType::Webm; + }, [](const auto &) { + return StickerType::Webp; + }); + } + return StickerType::Webp; + }(); +#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()))) { @@ -996,6 +1107,7 @@ void StickerSetBox::Inner::applySet(const TLStickerSet &set) { } return ImageWithLocation(); }(); +#endif const auto &sets = _session->data().stickers().sets(); const auto it = sets.find(_setId); if (it != sets.cend()) { @@ -1012,9 +1124,11 @@ void StickerSetBox::Inner::applySet(const TLStickerSet &set) { set->emoji = _emoji; set->setThumbnail(_setThumbnail, _setThumbnailType); } +#if 0 // mtp }; }, [&](const MTPDmessages_stickerSetNotModified &data) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); +#endif }); if (_pack.isEmpty()) { @@ -1050,8 +1164,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(); @@ -1121,10 +1238,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) { @@ -1275,6 +1395,11 @@ void StickerSetBox::Inner::requestReorder( _apiReorder.emplace(&_session->mtp()); } _reorderRequests.emplace_back([document, index, this] { + _apiReorder->request(TLsetStickerPositionInSet( + tl_inputFileId(tl_int32(document->tdbFileId())), + tl_int32(index) + )).done([this, document] { +#if 0 // mtp _apiReorder->request( MTPstickers_ChangeStickerPosition( document->mtpInput(), @@ -1286,16 +1411,30 @@ void StickerSetBox::Inner::requestReorder( Data::StickersType::Stickers); }, [](const auto &) { }); +#endif if (!_reorderRequests.empty()) { _reorderRequests.pop_front(); } if (_reorderRequests.empty()) { + _apiReorder->request(TLgetStickerSet( + tl_int64(_setId) + )).done([this, document](const TLstickerSet &result) { + document->owner().stickers().feedSetFull(result); + document->owner().stickers().notifyUpdated( + Data::StickersType::Stickers); + }).fail([show = _show](const Tdb::Error &error) { + show->showToast(error.message); + }).send(); // applySet(result); // Causes stickers blink. } else { _reorderRequests.front()(); } +#if 0 // mtp }).fail([show = _show](const MTP::Error &error) { show->showToast(error.type()); +#endif + }).fail([show = _show](const Tdb::Error &error) { + show->showToast(error.message); }).send(); }); if (_reorderRequests.size() == 1) { @@ -1557,6 +1696,7 @@ void StickerSetBox::Inner::fillDeleteStickerBox( const auto buttonWidth = state->saveButton ? state->saveButton->width() : 0; +#if 0 // mtp state->requestId = document->owner().session().api().request( MTPstickers_RemoveStickerFromSet(document->mtpInput() )).done([=](const TLStickerSet &result) { @@ -1577,6 +1717,33 @@ void StickerSetBox::Inner::fillDeleteStickerBox( strongBox->uiShow()->showToast(error.type()); } }).send(); +#endif + state->requestId = document->owner().session().sender().request( + TLremoveStickerFromSet( + tl_inputFileId(tl_int32(document->tdbFileId()))) + ).done([=] { + document->owner().session().sender().request(TLgetStickerSet( + tl_int64(_setId) + )).done([=](const TLstickerSet &result) { + document->owner().stickers().feedSetFull(result); + document->owner().stickers().notifyUpdated( + Data::StickersType::Stickers); + if (const auto strong = weak.data()) { + applySet(result); + } + if (const auto strongBox = weakBox.data()) { + strongBox->closeBox(); + } + }).fail([=](const Tdb::Error &error) { + if (const auto strongBox = weakBox.data()) { + strongBox->uiShow()->showToast(error.message); + } + }).send(); + }).fail([=](const Tdb::Error &error) { + if (const auto strongBox = weakBox.data()) { + strongBox->uiShow()->showToast(error.message); + } + }).send(); if (state->saveButton) { state->saveButton->resizeToWidth(buttonWidth); } @@ -1599,7 +1766,10 @@ void StickerSetBox::Inner::fillDeleteStickerBox( state->requestId.value() | rpl::map(rpl::mappers::_1 > 0)); } box->addButton(tr::lng_close(), [=] { +#if 0 // mtp document->owner().session().api().request( +#endif + document->owner().session().sender().request( state->requestId.current()).cancel(); box->closeBox(); }); @@ -2113,6 +2283,7 @@ void StickerSetBox::Inner::install() { if (_installRequest) { return; } +#if 0 // mtp _installRequest = _api.request(MTPmessages_InstallStickerSet( Data::InputStickerSet(_input), MTP_bool(false) @@ -2121,9 +2292,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() @@ -2134,6 +2316,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 7509b898c315c9..bd1f3f632b7493 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -46,8 +46,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; @@ -337,7 +342,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; }; @@ -452,6 +460,7 @@ StickersBox::StickersBox( }, lifetime()); } +#if 0 // mtp StickersBox::StickersBox( QWidget*, std::shared_ptr show, @@ -467,6 +476,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*, @@ -522,15 +546,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); @@ -797,6 +827,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); @@ -807,6 +845,7 @@ void StickersBox::loadMoreArchived() { )).done([=](const MTPmessages_ArchivedStickers &result) { getArchivedDone(result, lastId); }).send(); +#endif } void StickersBox::paintEvent(QPaintEvent *e) { @@ -945,6 +984,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() @@ -953,11 +1006,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) { @@ -975,12 +1030,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); @@ -991,6 +1048,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(); } } @@ -2216,6 +2281,18 @@ void StickersBox::Inner::handleMegagroupSetAddressChange() { } } } else if (!_megagroupSetRequestId) { + _megagroupSetRequestId = _api.request(TLsearchStickerSet( + tl_string(text), + tl_bool(true) + )).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 @@ -2231,6 +2308,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 89b31ccf516537..3d0c280a31ee63 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -13,6 +13,11 @@ For license and copyright information please follow this link: #include "data/stickers/data_stickers_set.h" #include "ui/effects/animations.h" +namespace Tdb { +class TLstickerSets; +class TLstickerSetInfo; +} // namespace Tdb + namespace style { struct RippleAnimation; struct PeerListItem; @@ -68,10 +73,16 @@ class StickersBox final : public Ui::BoxContent { std::shared_ptr show, not_null megagroup, bool isEmoji); +#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, @@ -127,14 +138,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(); @@ -167,7 +183,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; @@ -175,7 +194,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 39380bd3ed5002..0eee77aaa0bf96 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 { @@ -232,6 +237,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 +292,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 ef72584cf5f168..a97d11c43e5563 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 f48af2082696cd..1734a235b37baf 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 7df856d8dbd49e..57259714366d6c 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; + class UsernameEditor final : public Ui::RpWidget { public: UsernameEditor(not_null, not_null peer); @@ -137,6 +145,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) { @@ -147,6 +165,7 @@ rpl::producer<> UsernameEditor::save() { _saveRequestId = 0; updateFail(error.type()); }).send(); +#endif return _saved.events(); } @@ -172,6 +191,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) { @@ -190,6 +236,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 a3d0d536948a6b..a6f7401f966a41 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 { @@ -514,7 +516,17 @@ void BoxController::loadMoreRows() { if (_loadRequestId || _allLoaded) { return; } - + _loadRequestId = _api.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(), @@ -556,6 +568,7 @@ void BoxController::loadMoreRows() { }).fail([this] { _loadRequestId = 0; }).send(); +#endif } base::unique_qptr BoxController::rowContextMenu( @@ -603,6 +616,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; @@ -627,6 +666,7 @@ void BoxController::receivedCalls(const QVector &result) { refreshAbout(); delegate()->peerListRefreshRows(); } +#endif bool BoxController::insertRow( not_null item, @@ -719,6 +759,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( @@ -749,9 +790,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 860839cdf24ebb..3b05e3828c12bb 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.h +++ b/Telegram/SourceFiles/calls/calls_box_controller.h @@ -11,6 +11,14 @@ For license and copyright information please follow this link: #include "ui/layers/generic_box.h" #include "mtproto/sender.h" +namespace Tdb { +class TLDfoundMessages; +} // namespace Tdb + +namespace Tdb { +class TLDfoundMessages; +} // namespace Tdb + namespace Window { class SessionController; } // namespace Window @@ -53,7 +61,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; @@ -71,7 +82,10 @@ class BoxController : public PeerListController { const not_null _window; MTP::Sender _api; +#if 0 // goodToRemove 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 3a34f078369cd7..24fcd6d96a9e78 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 "webrtc/webrtc_video_track.h" #include "window/window_controller.h" +#include "tdb/tdb_tl_scheme.h" + #include #include #include @@ -57,6 +59,7 @@ const auto RegisterV2 = tgcalls::Register(); const auto RegV2Ref = tgcalls::Register(); const auto RegisterLegacy = tgcalls::Register(); +#if 0 // goodToRemove [[nodiscard]] base::flat_set CollectEndpointIds( const QVector &list) { auto result = base::flat_set(); @@ -167,7 +170,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); @@ -195,6 +268,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; @@ -206,10 +292,12 @@ 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) , _api(&_user->session().mtp()) , _type(type) , _discardByTimeoutTimer([=] { hangup(); }) @@ -240,12 +328,13 @@ Call::Call( } else { const auto &config = _user->session().serverConfig(); _discardByTimeoutTimer.callOnce(config.callRingTimeoutMs); + setState(State::WaitingIncoming); startWaitingTrack(); } setupMediaDevices(); setupOutgoingVideo(); } - +#if 0 // goodToRemove void Call::generateModExpFirst(bytes::const_span randomSeed) { auto first = MTP::CreateModExp(_dhConfig.g, _dhConfig.p, randomSeed); if (first.modexp.empty()) { @@ -262,6 +351,7 @@ void Call::generateModExpFirst(bytes::const_span randomSeed) { _gaHash = openssl::Sha256(_ga); } } +#endif bool Call::isIncomingWaiting() const { if (type() != Call::Type::Incoming) { @@ -271,6 +361,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. @@ -360,6 +451,7 @@ void Call::startIncoming() { handleRequestError(error.type()); }).send(); } +#endif void Call::applyUserConfirmation() { if (_state.current() == State::WaitingUserConfirmation) { @@ -374,6 +466,7 @@ void Call::answer() { }), video); } +#if 0 // goodToRemove void Call::actuallyAnswer() { Expects(_type == Type::Incoming); @@ -417,6 +510,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::captureMuteChanged(bool mute) { setMuted(mute); @@ -532,6 +649,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(); @@ -541,6 +659,8 @@ void Call::hangup() { ? MTP_phoneCallDiscardReasonBusy() : MTP_phoneCallDiscardReasonHangup(); finish(FinishType::Ended, reason); +#endif + finish(FinishType::Ended, false); } } @@ -551,7 +671,9 @@ void Call::redial() { Assert(_instance == nullptr); _type = Type::Outgoing; setState(State::Requesting); +#if 0 // goodToRemove _answerAfterDhConfigReceived = false; +#endif startWaitingTrack(); _delegate->callRedial(this); } @@ -574,6 +696,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), @@ -586,6 +715,7 @@ void Call::sendSignalingData(const QByteArray &data) { }).fail([=](const MTP::Error &error) { handleRequestError(error.type()); }).send(); +#endif } float64 Call::getWaitingSoundPeakValue() const { @@ -596,6 +726,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); } @@ -767,6 +905,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, @@ -792,9 +1025,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( @@ -805,7 +1043,7 @@ bool Call::handleSignalingData( _instance->receiveSignalingData(std::move(prepared)); return true; } - +#if 0 // goodToRemove void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { Expects(_type == Type::Outgoing); @@ -886,19 +1124,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; @@ -907,6 +1156,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(); @@ -946,7 +1201,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, @@ -1016,6 +1274,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); @@ -1023,6 +1282,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(); @@ -1041,8 +1308,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; } @@ -1079,6 +1349,14 @@ void Call::createAndStartController(const MTPDphoneCall &call) { }); }, _instanceLifetime); #endif + + { + _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) { @@ -1118,7 +1396,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] { @@ -1165,6 +1443,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(); @@ -1323,7 +1602,10 @@ rpl::producer Call::cameraDeviceIdValue() const { return _cameraDeviceId.value(); } +#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); @@ -1364,15 +1646,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( @@ -1389,6 +1685,7 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { }).fail(crl::guard(weak, [this, finalState] { setState(finalState); })).send(); +#endif } void Call::setStateQueued(State state) { @@ -1458,12 +1755,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 1d01d6d5f548ce..942308a83476db 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -14,6 +14,15 @@ For license and copyright information please follow this link: #include "mtproto/mtproto_auth_key.h" #include "webrtc/webrtc_device_resolver.h" +#include "ui/emoji_config.h" + +namespace Tdb { +class TLDcall; +class TLcallProtocol; +class TLDcallStateReady; +class TLDupdateNewCallSignalingData; +} // namespace Tdb + namespace Media { namespace Audio { class Track; @@ -35,12 +44,13 @@ struct DeviceResolvedId; } // namespace Webrtc namespace Calls { - +#if 0 // goodToRemove struct DhConfig { int32 version = 0; int32 g = 0; bytes::vector p; }; +#endif enum class ErrorType { NoCamera, @@ -66,7 +76,9 @@ class Call final 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; @@ -96,6 +108,7 @@ class Call final Call( not_null delegate, not_null user, + CallId id, Type type, bool video); @@ -110,9 +123,16 @@ class Call final } [[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, @@ -201,8 +221,12 @@ class Call final 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; @@ -232,6 +256,8 @@ class Call final return _lifetime; } + static Tdb::TLcallProtocol Protocol(); + ~Call(); private: @@ -243,28 +269,38 @@ class Call final 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); @@ -282,6 +318,8 @@ class Call final const not_null _delegate; const not_null _user; + const CallId _id = 0; + MTP::Sender _api; Type _type = Type::Outgoing; rpl::variable _state = State::Starting; @@ -292,7 +330,9 @@ class Call final = RemoteBatteryState::Normal; 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; @@ -305,6 +345,7 @@ class Call final rpl::variable _muted = false; +#if 0 // goodToRemove DhConfig _dhConfig; bytes::vector _ga; bytes::vector _gb; @@ -315,6 +356,9 @@ class Call final CallId _id = 0; uint64 _accessHash = 0; uint64 _keyFingerprint = 0; +#endif + + std::vector _fingerprint; std::unique_ptr _instance; std::shared_ptr _videoCapture; @@ -329,7 +373,8 @@ class Call final 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 d6b6ca32a3ddd2..3b5d8db0a1439b 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 / style::DevicePixelRatio(); auto count = print.size(); diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 118532d165e076..20a9706ee1b076 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) { @@ -559,6 +634,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) { @@ -609,7 +756,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) { @@ -652,7 +823,6 @@ void Instance::applyGroupCallUpdateChecked( _currentGroupCall->handleUpdate(update); } } - void Instance::handleSignalingData( not_null session, const MTPDupdatePhoneCallSignalingData &data) { @@ -663,6 +833,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 d52a0e9a238a2d..8d8e0623ea9bd6 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); @@ -115,12 +140,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, @@ -132,26 +168,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 6f0ab0f6a679b8..9dea57bc2b8595 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -1074,7 +1074,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 a3f57de1014e73..7f1779f85843d1 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 9cc83200f780c7..dd52acfabe0c03 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -33,6 +33,9 @@ For license and copyright information please follow this link: #include "webrtc/webrtc_create_adm.h" #include "webrtc/webrtc_environment.h" +#include "tdb/tdb_option.h" +#include "tdb/tdb_tl_scheme.h" + #include #include #include @@ -43,9 +46,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); @@ -63,6 +70,7 @@ constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums. : nullptr; } +#if 0 // goodToRemove [[nodiscard]] double TimestampFromMsgId(mtpMsgId msgId) { return msgId / double(1ULL << 32); } @@ -80,6 +88,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; @@ -276,11 +285,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(); @@ -292,6 +306,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) { @@ -370,6 +461,7 @@ struct ParticipantVideoParams { }); return result; } +#endif const std::string &GetCameraEndpoint( const std::shared_ptr ¶ms) { @@ -389,14 +481,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; @@ -568,7 +666,10 @@ 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)) @@ -579,10 +680,15 @@ GroupCall::GroupCall( , _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 , _playbackDeviceId( &Core::App().mediaDevices(), Webrtc::DeviceType::Playback, @@ -657,7 +763,10 @@ GroupCall::GroupCall( setupOutgoingVideo(); if (_id) { +#if 0 // goodToRemove join(inputCall); +#endif + join(id); } else { start(info.scheduleDate, info.rtmp); } @@ -776,7 +885,10 @@ void GroupCall::setScheduledDate(TimeId date) { const auto was = _scheduleDate; _scheduleDate = date; if (was && !date) { +#if 0 // goodToRemove join(inputCall()); +#endif + join(_id); } } @@ -814,7 +926,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(); } @@ -983,9 +1101,11 @@ void GroupCall::setState(State state) { _delegate->groupCallFailed(this); break; case State::Connecting: +#if 0 // goodToRemove if (!_checkJoinedTimer.isActive()) { _checkJoinedTimer.callOnce(kCheckJoinedTimeout); } +#endif break; } } @@ -1067,6 +1187,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)) @@ -1085,13 +1206,41 @@ void GroupCall::start(TimeId scheduleDate, bool rtmp) { ).arg(error.type())); hangup(); }).send(); +#endif + _createRequestId = _api.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) { @@ -1109,7 +1258,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); @@ -1315,10 +1467,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 + _api.request(TLsetVideoChatDefaultParticipant( + peerToTdbChat(_peer->id), + peerToSender(joinAs()->id) + )).send(); } void GroupCall::rejoin(not_null as) { @@ -1342,10 +1500,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) { @@ -1362,6 +1526,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 @@ -1383,6 +1548,20 @@ void GroupCall::rejoin(not_null as) { const MTP::Response &response) { _serverTimeMs = TimestampInMsFromMsgId(response.outerMsgId); _serverTimeMsGotAt = crl::now(); +#endif + _api.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); @@ -1391,9 +1570,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; @@ -1412,10 +1614,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") { @@ -1488,25 +1696,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 + _api.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(); @@ -1541,18 +1781,32 @@ void GroupCall::leavePresentation() { _screenJoinState.nextActionPending = true; return; } +#if 0 // goodToRemove _api.request( MTPphone_LeaveGroupCallPresentation(inputCall()) ).done([=](const MTPUpdates &updates) { +#endif + _api.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()); @@ -1560,6 +1814,7 @@ void GroupCall::leavePresentation() { }).send(); } +#if 0 // goodToRemove void GroupCall::applyMeInCallLocally() { const auto real = lookupReal(); if (!real) { @@ -1656,6 +1911,7 @@ void GroupCall::applyParticipantLocally( MTPGroupCallParticipantVideo())), MTP_int(0)).c_updateGroupCallParticipants()); } +#endif void GroupCall::hangup() { finish(FinishType::Ended); @@ -1667,13 +1923,20 @@ void GroupCall::discard() { hangup(); return; } +#if 0 // goodToRemove _api.request(MTPphone_DiscardGroupCall( inputCall() )).done([=](const MTPUpdates &result) { +#endif + _api.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(); @@ -1734,14 +1997,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(); @@ -1751,23 +2021,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 + _api.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 + _api.request(TLtoggleGroupCallEnabledStartNotification( + tl_int32(_id), + tl_bool(subscribed) + )).send(); } void GroupCall::setNoiseSuppression(bool enabled) { @@ -1797,9 +2078,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); @@ -1832,6 +2115,7 @@ void GroupCall::setMutedAndUpdate(MuteState mute) { } } +#if 0 // goodToRemove void GroupCall::handlePossibleCreateOrJoinResponse( const MTPDupdateGroupCall &data) { data.vcall().match([&](const MTPDgroupCall &data) { @@ -1927,6 +2211,7 @@ void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { hangup(); } } +#endif void GroupCall::checkMediaChannelDescriptions( Fn resolved) { @@ -1947,6 +2232,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); @@ -1989,6 +2312,7 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) { }); } } +#endif void GroupCall::applyQueuedSelfUpdates() { const auto weak = base::make_weak(this); @@ -1998,12 +2322,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) { @@ -2067,6 +2441,7 @@ void GroupCall::applyOtherParticipantUpdate( .mutedByMe = data.is_muted_by_you(), }); } +#endif void GroupCall::setupMediaDevices() { _playbackDeviceId.changes() | rpl::filter([=] { @@ -2149,8 +2524,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); @@ -2176,8 +2554,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); @@ -2248,7 +2629,9 @@ void GroupCall::setupOutgoingVideo() { _cameraEndpoint }, nowActive, nowPaused); sendSelfUpdate(SendUpdateType::CameraStopped); +#if 0 // goodToRemove applyMeInCallLocally(); +#endif }, _lifetime); _screenState.value( @@ -2326,6 +2709,7 @@ void GroupCall::changeTitle(const QString &title) { return; } +#if 0 // goodToRemove _api.request(MTPphone_EditGroupCallTitle( inputCall(), MTP_string(title) @@ -2333,6 +2717,13 @@ void GroupCall::changeTitle(const QString &title) { _peer->session().api().applyUpdates(result); _titleChanged.fire({}); }).send(); +#endif + _api.request(TLsetGroupCallTitle( + tl_int32(_id), + tl_string(title) + )).done([=] { + _titleChanged.fire({}); + }).send(); } void GroupCall::toggleRecording( @@ -2353,6 +2744,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)) @@ -2367,6 +2759,24 @@ void GroupCall::toggleRecording( }).fail([=] { _recordingStoppedByMe = false; }).send(); +#endif + + const auto finish = [=] { + _recordingStoppedByMe = false; + }; + + if (enabled) { + _api.request(TLstartGroupCallRecording( + tl_int32(_id), + tl_string(title), + tl_bool(video), + tl_bool(videoPortrait) + )).done(finish).fail(finish).send(); + } else { + _api.request(TLendGroupCallRecording( + tl_int32(_id) + )).done(finish).fail(finish).send(); + } } bool GroupCall::tryCreateController() { @@ -2550,6 +2960,14 @@ bool GroupCall::tryCreateScreencast() { return true; } +RequestId GroupCall::unixtime(Fn &&callback) { + return _api.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(); @@ -2562,6 +2980,55 @@ void GroupCall::broadcastPartStart(std::shared_ptr task) { }; using Status = tgcalls::BroadcastPart::Status; using Quality = tgcalls::VideoChannelDescription::Quality; + const auto requestId = _api.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) { + _api.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), @@ -2624,6 +3091,7 @@ 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) { @@ -2706,6 +3174,32 @@ void GroupCall::requestCurrentTimeStart( task->done(value); } }; + _requestCurrentTimeRequestId = _api.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) { + _api.request(part.requestId).cancel(); + } + setState(State::Joining); + rejoin(); + } + }).send(); +#if 0 // mtp _requestCurrentTimeRequestId = _api.request( MTPphone_GetGroupCallStreamChannels(inputCall()) ).done([=](const MTPphone_GroupCallStreamChannels &result) { @@ -2736,6 +3230,7 @@ void GroupCall::requestCurrentTimeStart( }).handleAllErrors().toDC( MTP::groupCallStreamDcId(_broadcastDcId) ).send(); +#endif } void GroupCall::requestCurrentTimeCancel( @@ -2886,7 +3381,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; @@ -2980,13 +3478,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)); @@ -3027,6 +3531,7 @@ void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) { if (level <= kSpeakLevelThreshold) { continue; } +#if 0 // mtp if (me && voice && (!_lastSendProgressUpdate @@ -3036,6 +3541,7 @@ void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) { _history, Api::SendProgressType::Speaking); } +#endif check = true; const auto i = _lastSpoke.find(ssrc); @@ -3104,6 +3610,7 @@ void GroupCall::checkLastSpoke() { } } +#if 0 // goodToRemove void GroupCall::checkJoined() { if (state() != State::Connecting || !_id || !_joinState.ssrc) { return; @@ -3140,6 +3647,7 @@ void GroupCall::checkJoined() { rejoin(); }).send(); } +#endif void GroupCall::setInstanceConnected( tgcalls::GroupNetworkState networkState) { @@ -3279,11 +3787,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: { + _api.request( + TLtoggleGroupCallParticipantIsHandRaised( + tlId, + tlSelf, + tl_bool(muted() == MuteState::RaisedHand)) + ).fail(failed).send(); + } break; + case SendUpdateType::CameraStopped: { + _api.request( + TLtoggleGroupCallIsMyVideoEnabled( + tlId, + tl_bool(isSharingCamera())) + ).fail(failed).send(); + } break; + case SendUpdateType::CameraPaused: { + _api.request( + TLtoggleGroupCallIsMyVideoPaused( + tlId, + tl_bool(isCameraPaused())) + ).fail(failed).send(); + } break; + case SendUpdateType::ScreenPaused: { + _api.request( + TLtoggleGroupCallScreenSharingIsPaused( + tlId, + tl_bool(isScreenPaused())) + ).fail(failed).send(); + } break; + case SendUpdateType::Mute: { + _api.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) @@ -3315,6 +3874,7 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { rejoin(); } }).send(); +#endif } void GroupCall::pinVideoEndpoint(VideoEndpoint endpoint) { @@ -3360,9 +3920,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 + _api.request(TLtoggleGroupCallParticipantIsMuted( + tl_int32(_id), + peerToSender(data.peer->id), + tl_bool(data.mute) + )).send(); } } @@ -3371,12 +3944,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 + _api.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, @@ -3410,6 +3994,7 @@ void GroupCall::editParticipant( } }).send(); } +#endif std::variant> GroupCall::inviteUsers( const std::vector> &users) { @@ -3420,17 +4005,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 + _api.request(TLinviteGroupCallParticipants( + tl_int32(_id), + tl_vector(slice) + )).send(); slice.clear(); }; for (const auto &user : users) { @@ -3438,7 +4032,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(); } @@ -3449,6 +4046,7 @@ std::variant> GroupCall::inviteUsers( if (!slice.empty()) { sendSlice(); } + return result; } @@ -3524,6 +4122,7 @@ auto GroupCall::otherParticipantStateValue() const return _otherParticipantStateValue.events(); } +#if 0 // goodToRemove MTPInputGroupCall GroupCall::inputCall() const { Expects(_id != 0); @@ -3531,6 +4130,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 8f93793bd967fc..4c921c1c72432a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -15,6 +15,22 @@ For license and copyright information please follow this link: #include "webrtc/webrtc_device_common.h" #include "webrtc/webrtc_device_resolver.h" +namespace Tdb { +class TLgroupCallParticipant; +class TLDgroupCallParticipant; +class TLgroupCallParticipantVideoInfo; +class TLDupdateGroupCall; +class TLDupdateGroupCallParticipant; +} // namespace Tdb + +namespace Tdb { +class TLgroupCallParticipant; +class TLDgroupCallParticipant; +class TLgroupCallParticipantVideoInfo; +class TLDupdateGroupCall; +class TLDupdateGroupCallParticipant; +} // namespace Tdb + class History; namespace tgcalls { @@ -161,8 +177,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( @@ -173,8 +193,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 @@ -216,7 +238,10 @@ class GroupCall final GroupCall( not_null delegate, Group::JoinInfo info, +#if 0 // goodToRemove const MTPInputGroupCall &inputCall); +#endif + CallId id); ~GroupCall(); [[nodiscard]] CallId id() const { @@ -254,11 +279,17 @@ class GroupCall final 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, @@ -429,7 +460,10 @@ class GroupCall final struct LoadingPart { std::shared_ptr task; +#if 0 // mtp mtpRequestId requestId = 0; +#endif + Tdb::RequestId requestId = 0; }; enum class FinishType { @@ -486,10 +520,12 @@ class GroupCall final 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(); @@ -507,7 +543,9 @@ class GroupCall final 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); @@ -528,7 +566,9 @@ class GroupCall final void pushToTalkCancel(); void checkGlobalShortcutAvailability(); +#if 0 // goodToRemove void checkJoined(); +#endif void checkFirstTimeJoined(); void notifyAboutAllowedToSpeak(); @@ -540,6 +580,7 @@ class GroupCall final void updateRequestedVideoChannelsDelayed(); void fillActiveVideoEndpoints(); +#if 0 // goodToRemove void editParticipant( not_null participantPeer, bool mute, @@ -548,10 +589,14 @@ class GroupCall final 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 captureMuteChanged(bool mute) override; rpl::producer captureMuteDeviceId() override; @@ -563,6 +608,8 @@ class GroupCall final void addVideoOutput(const std::string &endpoint, SinkPointer sink); void setVideoEndpointLarge(VideoEndpoint endpoint); + Tdb::RequestId unixtime(Fn &&callback); + void markEndpointActive( VideoEndpoint endpoint, bool active, @@ -572,7 +619,9 @@ class GroupCall final [[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. @@ -586,7 +635,9 @@ class GroupCall final 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, @@ -612,13 +663,20 @@ class GroupCall final 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; @@ -669,7 +727,9 @@ class GroupCall final 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 2853340a532f4e..685d6a7570212f 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( @@ -1319,7 +1354,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, @@ -1507,7 +1545,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 02ae9cd685c3f0..3b15bc0081be68 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 5ba910c8e23acb..4abdee29d58478 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. Ui::Text::String _about; +#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 51a9068a7396eb..1381c8069856e3 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 fded076144a48b..c757e2201befdb 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 4c60a3f886bcd4..30ef30ba135d71 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -51,11 +51,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; @@ -79,11 +83,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) { @@ -674,6 +685,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, @@ -686,6 +706,7 @@ void SettingsBox( .key = qs(data.vkey()), }; }); +#endif if (const auto call = weakCall.get()) { call->setRtmpInfo(data); } @@ -807,8 +828,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; @@ -842,6 +866,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), @@ -855,12 +890,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( @@ -878,6 +915,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 9f7fe7bb2803c8..7522f8797e695e 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 d4c52474d7f02b..a23759dbf837b8 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 500de20b6ccc92..8b1c153d8575ff 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,98 @@ 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), + }); + const auto send = [&](auto &&request, auto &&parse) { + using Response = std::decay_t::ResponseType; + _api->sender().request( + std::move(request), + requestId + ).done([=](const Response &result) { + const auto request = _requests.take(requestId); + if (!request) { + return; + } + auto cloud = parse(result); + if (!exact) { + AppendLegacySuggestions(cloud, request->query); + } + request->callback(ApplyVariants(PrioritizeRecent(std::move(cloud)))); + }).fail([=] { + if (const auto request = _requests.take(requestId)) { + request->callback({}); + } + }).send(); + }; + if (exact) { + send(TLgetKeywordEmojis( + tl_string(normalized), + tl_vector(std::move(langs)) + ), [](const TLemojis &result) { + 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 }); + } + } + return cloud; + }); + } else { + send(TLsearchEmojis( + tl_string(normalized), + tl_vector(std::move(langs)) + ), [](const TLemojiKeywords &result) { + const auto &list = result.data().vemoji_keywords().v; + auto cloud = std::vector(); + cloud.reserve(list.size()); + for (const auto &emoji : list) { + if (const auto found = Find(emoji.data().vemoji().v)) { + cloud.push_back(Result{ found }); + } + } + return cloud; + }); + } + return requestId; +} std::vector EmojiKeywords::PrioritizeRecent( std::vector list) { @@ -680,6 +791,7 @@ std::vector EmojiKeywords::ApplyVariants(std::vector list) { } int EmojiKeywords::maxQueryLength() const { +#if 0 // mtp if (_data.empty()) { return 0; } @@ -687,8 +799,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 +870,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 3df6c497db503a..7bb4118851b220 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 7ea95f736ea1be..1e9ed776b790f3 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -52,11 +52,15 @@ For license and copyright information please follow this link: #include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" +#include "tdb/tdb_tl_scheme.h" + #include namespace ChatHelpers { namespace { +using namespace Tdb; + constexpr auto kCollapsedRows = 3; constexpr auto kAppearDuration = 0.3; constexpr auto kCustomSearchLimit = 256; @@ -605,8 +609,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; @@ -615,6 +621,7 @@ void EmojiListWidget::applyNextSearchQuery() { clearSelection(); if (modeChanged) { _searchMode = searching; + invalidate_weak_ptrs(&_searchGuard); } if (!searching) { _searchResults.clear(); @@ -631,11 +638,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; } @@ -651,6 +667,12 @@ void EmojiListWidget::applyNextSearchQuery() { }); } } + + }); + _searchRequestId = SearchEmoji( + _searchRequestId, + _searchQuery, + callback); } void EmojiListWidget::showPreview() { @@ -662,9 +684,11 @@ void EmojiListWidget::showPreview() { } } +#if 0 // mtp std::vector EmojiListWidget::collectPlainSearchResults() { return SearchEmoji(_searchQuery, _searchEmoji); } +#endif void EmojiListWidget::appendPremiumSearchResults() { const auto test = session().isTestMode(); @@ -2440,11 +2464,16 @@ void EmojiListWidget::refreshMegagroupStickers( 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)) { refreshCustom(); if (set->id == _megagroupSetIdRequested) { @@ -2453,9 +2482,11 @@ void EmojiListWidget::refreshMegagroupStickers( LOG(("API Error: Got different set.")); } } +#if 0 // mtp }, [](const MTPDmessages_stickerSetNotModified &) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); +#endif }).send(); } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index ff474ad2370a88..7792f7166b7af6 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -279,7 +279,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(); @@ -438,6 +440,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 87fd388861e978..fa1dddaa85a6b2 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -70,12 +70,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; @@ -125,6 +129,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; @@ -191,6 +197,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( @@ -199,6 +206,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); } @@ -213,6 +225,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() { @@ -262,7 +314,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 }); } } @@ -278,7 +332,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); } @@ -312,10 +369,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()) { @@ -339,6 +400,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()); @@ -769,6 +831,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; @@ -776,6 +839,7 @@ SuggestionsController::SuggestionsController( showWithQuery(_lastShownQuery); } }, _lifetime); +#endif updateForceHidden(); @@ -837,10 +901,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 72865ea018acb0..afbd4edc3cbb4e 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -45,11 +45,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 kMinRepaintDelay = crl::time(33); constexpr auto kMinAfterScrollDelay = crl::time(33); @@ -288,18 +293,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( @@ -307,7 +323,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); } @@ -897,6 +916,20 @@ void GifsListWidget::searchForGifs(const QString &query) { } if (!_searchBot && !_searchBotRequestId) { + _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 const auto username = session().serverConfig().gifSearchUsername; _searchBotRequestId = _api.request(MTPcontacts_ResolveUsername( MTP_string(username) @@ -910,6 +943,7 @@ void GifsListWidget::searchForGifs(const QString &query) { _searchBot = user; } }).send(); +#endif } } @@ -946,6 +980,7 @@ void GifsListWidget::sendInlineRequest() { } _search->setLoading(true); +#if 0 // goodToRemove _inlineRequestId = _api.request(MTPmessages_GetInlineBotResults( MTP_flags(0), _searchBot->inputUser, @@ -960,6 +995,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 249ba5d0cd9e24..d3273147d27899 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h @@ -14,6 +14,10 @@ For license and copyright information please follow this link: #include +namespace Tdb { +class TLinlineQueryResults; +} // namespace Tdb + namespace style { struct ComposeIcons; } // namespace style @@ -149,7 +153,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); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index 8941f35aa8bf55..7874c2e1cbe19a 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -14,11 +14,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"); @@ -42,7 +47,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; } @@ -58,8 +88,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; @@ -96,6 +128,7 @@ void DicePack::applySet(const MTPDmessages_stickerSet &data) { }); } } +#endif void DicePack::tryGenerateLocalZero() { if (!_map.empty()) { @@ -144,4 +177,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 cb5210c3511076..baefb24df08e90 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(); const not_null _session; @@ -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 af07736a48a44a..34f3f4e83f7feb 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp @@ -32,9 +32,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; @@ -98,7 +103,9 @@ QSize LargeEmojiImage::Size() { EmojiPack::EmojiPack(not_null session) : _session(session) { +#if 0 // mtp refresh(); +#endif session->data().viewRemoved( ) | rpl::filter([](not_null view) { @@ -157,6 +164,7 @@ auto EmojiPack::stickerForEmoji(EmojiPtr emoji) -> Sticker { return { i->second.get(), nullptr }; } if (!emoji->colored()) { + request(emoji); return {}; } const auto j = _map.find(emoji->original()); @@ -164,9 +172,27 @@ auto EmojiPack::stickerForEmoji(EmojiPtr emoji) -> Sticker { const auto index = emoji->variantIndex(emoji); return { j->second.get(), ColorReplacements(index) }; } + request(emoji->original()); return {}; } +void EmojiPack::request(EmojiPtr emoji) { + if (!_requested.emplace(emoji).second) { + return; + } + _session->sender().request(TLgetAnimatedEmoji( + tl_string(emoji->id()) + )).done([=](const TLanimatedEmoji &result) { + const auto &data = result.data(); + if (const auto &sticker = data.vsticker()) { + const auto document = _session->data().processDocument(*sticker); + _map.emplace(emoji, document); + _refreshed.fire({}); + } + }).send(); +} + +#if 0 // mtp auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker { Expects(!emoji.empty()); @@ -177,6 +203,7 @@ auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker { } return {}; } +#endif std::shared_ptr EmojiPack::image(EmojiPtr emoji) { const auto i = _images.emplace( @@ -213,6 +240,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); @@ -264,6 +292,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, @@ -321,6 +350,7 @@ std::unique_ptr EmojiPack::effectPlayer( return std::make_unique(std::move(shared), request); } +#if 0 // mtp void EmojiPack::refresh() { if (_requestId) { return; @@ -439,6 +469,7 @@ auto EmojiPack::collectAnimationsIndices( } return result; } +#endif void EmojiPack::refreshAll() { auto items = base::flat_set>(); @@ -490,6 +521,7 @@ void EmojiPack::refreshItems( } } +#if 0 // mtp void EmojiPack::applyPack( const MTPDstickerPack &data, const base::flat_map> &map) { @@ -528,5 +560,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 133880e1a5de37..45ae9e219eafa1 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; @@ -79,9 +83,12 @@ class EmojiPack final { void remove(not_null view); [[nodiscard]] Sticker stickerForEmoji(EmojiPtr emoji); +#if 0 // mtp [[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( @@ -93,6 +100,7 @@ class EmojiPack final { [[nodiscard]] int animationsVersion() const { return _animationsVersion; } +#endif [[nodiscard]] rpl::producer<> refreshed() const { return _refreshed.events(); } @@ -103,6 +111,19 @@ class EmojiPack final { QString filepath, EffectType type); + 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; @@ -120,6 +141,7 @@ class EmojiPack final { void refresh(); void refreshDelayed(); +#if 0 // mtp void refreshAnimations(); void applySet(const MTPDmessages_stickerSet &data); void applyPack( @@ -131,11 +153,16 @@ 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); + void request(EmojiPtr emoji); + base::flat_set _requested; + const not_null _session; base::flat_map> _map; base::flat_map< @@ -158,8 +185,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 a4c98c416c47c8..d684f6fa2959d3 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp @@ -13,8 +13,13 @@ For license and copyright information please follow this link: #include "data/data_session.h" #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Stickers { +using namespace Tdb; + GiftBoxPack::GiftBoxPack(not_null session) : _session(session) , _localMonths({ 1, 3, 6, 12, 24 }) { @@ -37,6 +42,7 @@ int GiftBoxPack::monthsForStars(int stars) const { } DocumentData *GiftBoxPack::lookup(int months) const { +#if 0 // mtp const auto it = ranges::lower_bound(_localMonths, months); const auto fallback = _documents.empty() ? nullptr : _documents[0]; if (it == begin(_localMonths)) { @@ -51,12 +57,29 @@ DocumentData *GiftBoxPack::lookup(int months) const { : 0; const auto index = int(std::distance(begin(_localMonths), it - shift)); return (index >= _documents.size()) ? fallback : _documents[index]; +#endif + const auto i = _months.find(months); + if (i == end(_months)) { + _months.emplace(months, nullptr); + _session->sender().request(TLgetPremiumInfoSticker( + tl_int32(months) + )).done([=](const TLsticker &result) { + const auto document = _session->data().processDocument(result); + if (document->sticker()) { + _months[months] = document; + _updated.fire({}); + } + }).send(); + return nullptr; + } + return i->second; } Data::FileOrigin GiftBoxPack::origin() const { return Data::FileOriginStickerSet(_setId, _accessHash); } +#if 0 // mtp void GiftBoxPack::load() { if (_requestId || !_documents.empty()) { return; @@ -118,5 +141,6 @@ void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data) { } _updated.fire({}); } +#endif } // namespace Stickers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h index 798259c2572c7b..8913dc85702965 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h @@ -24,7 +24,9 @@ class GiftBoxPack final { explicit GiftBoxPack(not_null session); ~GiftBoxPack(); +#if 0 // mtp void load(); +#endif [[nodiscard]] int monthsForStars(int stars) const; [[nodiscard]] DocumentData *lookup(int months) const; [[nodiscard]] Data::FileOrigin origin() const; @@ -32,7 +34,11 @@ class GiftBoxPack final { private: using SetId = uint64; +#if 0 // mtp void applySet(const MTPDmessages_stickerSet &data); +#endif + + mutable base::flat_map _months; const not_null _session; const std::vector _localMonths; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 8d666249f146cd..e1f916d52d4781 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -31,11 +31,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; @@ -112,6 +116,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->appConfig(); return config->value( ) | rpl::map([=] { @@ -142,8 +153,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) { @@ -181,6 +194,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) { } @@ -1486,6 +1547,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()) { @@ -1497,10 +1565,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(); } @@ -1509,6 +1583,7 @@ bool LocalStickersManager::isInstalledLocally(uint64 setId) const { return _installedLocallySets.contains(setId); } +#if 0 // mtp void LocalStickersManager::sendInstallRequest( uint64 setId, const MTPInputStickerSet &input) { @@ -1521,6 +1596,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 a76b1ee21ab032..b49321951151a1 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h @@ -68,9 +68,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); @@ -327,9 +334,12 @@ 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); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 8eb165894471b5..172c94e14e5dfb 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -54,11 +54,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; @@ -389,6 +394,7 @@ void StickersListWidget::preloadMoreOfficial() { if (_officialRequestId) { return; } +#if 0 // mtp _officialRequestId = _api.request(MTPmessages_GetOldFeaturedStickers( MTP_int(_officialOffset), MTP_int(kOfficialLoadLimit), @@ -398,6 +404,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) { @@ -413,7 +428,11 @@ void StickersListWidget::preloadMoreOfficial() { externalLayout, AppendSkip::Installed); } + +#if 0 // mtp }); +#endif + resizeToWidth(width()); repaintItems(); }).send(); @@ -574,6 +593,17 @@ void StickersListWidget::sendSearchRequest() { return; } + _searchRequestId = _api.request(TLsearchStickerSets( + tl_stickerTypeRegular(), + tl_string(_searchQuery) + )).done([=](const TLstickerSets &result) { + searchResultsDone(result); + }).fail([=] { + // show error? + toggleSearchLoading(false); + _searchRequestId = 0; + }).send(); +#if 0 // mtp const auto hash = uint64(0); _searchRequestId = _api.request(MTPmessages_SearchStickerSets( MTP_flags(0), @@ -586,6 +616,7 @@ void StickersListWidget::sendSearchRequest() { toggleSearchLoading(false); _searchRequestId = 0; }).handleAllErrors().send(); +#endif } void StickersListWidget::searchForSets( @@ -630,6 +661,8 @@ void StickersListWidget::cancelSetsSearch() { _api.request(requestId).cancel(); } _searchRequestTimer.cancel(); + _searchEmojiRequestId = 0; + invalidate_weak_ptrs(&_searchEmojiGuard); _searchQuery = _searchNextQuery = QString(); _filteredStickers.clear(); _filterStickersCornerEmoji.clear(); @@ -857,10 +890,14 @@ auto StickersListWidget::shownSets() -> std::vector & { } void StickersListWidget::searchResultsDone( +#if 0 // mtp const MTPmessages_FoundStickerSets &result) { +#endif + const TLstickerSets &result) { toggleSearchLoading(false); _searchRequestId = 0; +#if 0 // mtp if (result.type() == mtpc_messages_foundStickerSetsNotModified) { LOG(("API Error: " "messages.foundStickerSetsNotModified not expected.")); @@ -868,6 +905,7 @@ void StickersListWidget::searchResultsDone( } Assert(result.type() == mtpc_messages_foundStickerSets); +#endif auto it = _searchCache.find(_searchQuery); if (it == _searchCache.cend()) { @@ -875,7 +913,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()) { @@ -2478,11 +2519,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) { @@ -2491,9 +2537,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(); } @@ -2803,7 +2851,21 @@ void StickersListWidget::applySearchQuery(std::vector &&query) { 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 } void StickersListWidget::displaySet(uint64 setId) { @@ -2928,6 +2990,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( @@ -2940,6 +3003,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 c436273fcfc6e4..86f5b75af29ec0 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -14,6 +14,14 @@ For license and copyright information please follow this link: #include "base/variant.h" #include "base/timer.h" +namespace Tdb { +class TLstickerSets; +} // namespace Tdb + +namespace Tdb { +class TLstickerSets; +} // namespace Tdb + class StickerPremiumMark; namespace Main { @@ -356,7 +364,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(); @@ -447,6 +458,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/stickers_lottie.cpp b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp index 095abbb5721e6d..6c6635d45af3d2 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_lottie.cpp @@ -341,12 +341,15 @@ not_null GenerateLocalTgsSticker( 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)); Ensures(document->sticker()); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 0ac62dd2e10a45..b1821de49bf8f6 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -342,7 +342,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 5dc8b7ce767c61..b2a54be3144bcf 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 c2ba5dccf34e07..bae528628a0162 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -106,6 +106,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 @@ -115,6 +118,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); @@ -322,6 +327,7 @@ void Application::run() { PowerSaving::SetForceAll(saving && !ignore); }, _lifetime); +#if 0 // mtp style::ShortAnimationPlaying( ) | rpl::start_with_next([=](bool playing) { if (playing) { @@ -330,6 +336,7 @@ void Application::run() { MTP::details::unpause(); } }, _lifetime); +#endif DEBUG_LOG(("Application Info: inited...")); @@ -930,9 +937,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() { @@ -1104,17 +1113,36 @@ void Application::checkStartUrl() { iv().showTonSite(url, {}); } else if (_lastActivePrimaryWindow) { 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 bfb330d351673d..592b4fbf1ec371 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -273,7 +273,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 2d0d52fe06a133..11e65d8be9958a 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,7 @@ void Changelogs::requestCloudLogs() { _session->api().requestChangelog( FormatVersionPrecise(_oldVersion), crl::guard(this, callback)); +#endif } void Changelogs::addLocalLogs() { diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 231d01972c8040..b74c2ee6dc7a90 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -107,14 +107,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([&] { @@ -127,6 +145,10 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) { ? QUrl(url) : QUrl::fromUserInput(url); if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) { +#endif + { + const auto parsedUrl = QUrl::fromUserInput(url); + if (force || UrlRequiresConfirmation(parsedUrl)) { const auto my = context.value(); if (!my.show) { Core::App().hideMediaView(); diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index b3aa0bae02f1ad..dc7363df12d466 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -77,6 +77,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 a835112dc9b2ad..1962b96e461c8d 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 2c4d06bc860f79..0c66e23895f0c2 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 21d912f63c8e80..14127614f2f945 100644 --- a/Telegram/SourceFiles/core/file_utilities.cpp +++ b/Telegram/SourceFiles/core/file_utilities.cpp @@ -18,6 +18,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 @@ -165,6 +167,9 @@ QString DefaultDownloadPathFolder(not_null session) { #if OS_MAC_STORE return u"Telegram Lite"_q; #else // OS_MAC_STORE + if (Core::IsTdxPathUsed()) { + return session->supportMode() ? u"Tdx Support"_q : u"Tdx"_q; + } return session->supportMode() ? u"Tsupport Desktop"_q : AppName.utf16(); #endif // OS_MAC_STORE } diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index 0b6afb7aa84deb..5e4e25596d26ce 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; } @@ -584,4 +619,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 ea5b1c97f35669..cb83b2964ca297 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 c07832e9a7ae41..25ff024ecaa023 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -71,11 +71,19 @@ For license and copyright information please follow this link: #include "history/history_item.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 "api/api_text_entities.h" + #include namespace Core { namespace { +using namespace Tdb; + using Match = qthelp::RegularExpressionMatch; class PersonalChannelController final : public PeerListController { @@ -103,7 +111,10 @@ PersonalChannelController::PersonalChannelController( PersonalChannelController::~PersonalChannelController() { if (_requestId) { +#if 0 // mtp _window->session().api().request(_requestId).cancel(); +#endif + _window->session().sender().request(_requestId).cancel(); } } @@ -117,6 +128,7 @@ void PersonalChannelController::prepare() { tr::lng_contacts_loading(), computeListSt().about)); +#if 0 // mtp using Flag = MTPchannels_GetAdminedPublicChannels::Flag; _requestId = _window->session().api().request( MTPchannels_GetAdminedPublicChannels( @@ -131,6 +143,16 @@ void PersonalChannelController::prepare() { const auto owner = &_window->session().data(); for (const auto &chat : chats) { if (const auto peer = owner->processChat(chat)) { +#endif + _requestId = _window->session().sender().request( + TLgetSuitablePersonalChats() + ).done([=](const TLchats &result) { + _requestId = 0; + + setDescription(nullptr); + const auto owner = &_window->session().data(); + for (const auto &chat : result.data().vchat_ids().v) { + if (const auto peer = owner->peerLoaded(peerFromTdbChat(chat))) { const auto rowId = peer->id.value; const auto channel = peer->asChannel(); if (channel && !delegate()->peerListFindRow(rowId)) { @@ -193,6 +215,7 @@ void SavePersonalChannel( || (messageId && self->personalChannelMessageId() != messageId)) { self->setPersonalChannel(channelId, messageId); +#if 0 // mtp self->session().api().request(MTPaccount_UpdatePersonalChannel( channel ? channel->inputChannel : MTP_inputChannelEmpty() )).done(crl::guard(window, [=] { @@ -202,9 +225,20 @@ void SavePersonalChannel( })).fail(crl::guard(window, [=](const MTP::Error &error) { window->showToast(u"Error: "_q + error.type()); })).send(); +#endif + self->session().sender().request(TLsetPersonalChat( + channel ? peerToTdbChat(channel->id) : tl_int53(0) + )).done(crl::guard(window, [=] { + window->showToast((channel + ? tr::lng_settings_channel_saved + : tr::lng_settings_channel_removed)(tr::now)); + })).fail(crl::guard(window, [=](const Error &error) { + window->showToast(error.message); + })).send(); } } +#if 0 // mtp bool JoinGroupByHash( Window::SessionController *controller, const Match &match, @@ -263,12 +297,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, @@ -713,6 +749,7 @@ bool HandleUnknown( controller->session().api().requestDeepLinkInfo(request, callback); return true; } +#endif bool OpenMediaTimestamp( Window::SessionController *controller, @@ -814,6 +851,7 @@ bool ShowEditBirthday( const auto save = [=](Data::Birthday result) { user->setBirthday(result); +#if 0 // mtp using Flag = MTPaccount_UpdateBirthday::Flag; using BFlag = MTPDbirthday::Flag; user->session().api().request(MTPaccount_UpdateBirthday( @@ -831,6 +869,18 @@ bool ShowEditBirthday( ? tr::lng_flood_error(tr::now) : (u"Error: "_q + error.type())); })).handleFloodErrors().send(); +#endif + user->session().sender().request(TLsetBirthdate(result + ? tl_birthdate( + tl_int32(result.day()), + tl_int32(result.month()), + tl_int32(result.year())) + : std::optional() + )).done(crl::guard(controller, [=] { + controller->showToast(tr::lng_settings_birthday_saved(tr::now)); + })).fail(crl::guard(controller, [=](const Error &error) { + controller->showToast(error.message); + })).send(); }; controller->show(Box( Ui::EditBirthdayBox, @@ -970,6 +1020,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); @@ -1077,6 +1128,7 @@ void ExportTestChatTheme( }).fail([=](const MTP::Error &error) { show->showToast(u"Error: "_q + error.type()); }).send(); +#endif } bool ResolveTestChatTheme( @@ -1113,6 +1165,7 @@ bool ResolveTestChatTheme( return true; } +#if 0 // mtp bool ResolveInvoice( Window::SessionController *controller, const Match &match, @@ -1153,6 +1206,7 @@ bool ResolvePremiumOffer( controller->window().activate(); return true; } +#endif bool ResolvePremiumMultigift( Window::SessionController *controller, @@ -1279,6 +1333,7 @@ bool ResolveChatLink( } // namespace +#if 0 // mtp const std::vector &LocalUrlHandlers() { static auto Result = std::vector{ { @@ -1376,6 +1431,7 @@ const std::vector &LocalUrlHandlers() { }; return Result; } +#endif const std::vector &InternalUrlHandlers() { static auto Result = std::vector{ @@ -1607,4 +1663,529 @@ 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(Window::PeerByLinkInfo{ + .usernameOrId = botUsername, + .attachBotToggleCommand = openWebAppUrl, + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDtargetChatChosen &data) { + using Type = InlineBots::PeerType; + controller->showPeerByLink(Window::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 TLDinternalLinkTypeMainWebApp &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Window::PeerByLinkInfo{ + .usernameOrId = data.vbot_username().v, + .attachBotToggleCommand = data.vstart_parameter().v, + .attachBotMainOpen = true, + .attachBotMainCompact = data.vis_compact().v, + .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(Window::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(Window::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(Window::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(Window::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(); }), + Payments::ProcessNonPanelPaymentFormFactory(controller)); + 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(Window::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; + }); + if (controller) { + ProxiesBoxController::ShowApplyConfirmation( + controller, + type, + fields); + controller->window().activate(); + } + return true; + }, [&](const TLDinternalLinkTypePublicChat &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Window::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(Window::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(Window::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(Window::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(Window::PeerByLinkInfo{ + .usernameOrId = data.vstory_sender_username().v, + .storyId = data.vstory_id().v, + .resolveType = Window::ResolveType::Default, + .clickFromMessageId = my.itemId, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeBusinessChat &data) { + if (!controller) { + return false; + } + controller->showPeerByLink(Window::PeerByLinkInfo{ + .chatLinkSlug = data.vlink_name().v, + .clickFromMessageId = my.itemId, + .clickFromBotWebviewContext = my.botWebviewContext, + }); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypePremiumGift &data) { + if (!controller) { + return false; + } + Ui::ChooseStarGiftRecipient(controller); + controller->window().activate(); + return true; + }, [&](const TLDinternalLinkTypeBuyStars &data) { + if (!controller) { + return false; + } + const auto amount = std::clamp( + data.vstar_count().v, + int64(1), + int64(1'000'000)); + const auto purpose = data.vpurpose().v; + const auto weak = base::make_weak(controller); + const auto done = [=](::Settings::SmallBalanceResult result) { + if (result == ::Settings::SmallBalanceResult::Already) { + if (const auto strong = weak.get()) { + const auto filter = [=](const auto &...) { + strong->showSettings(::Settings::CreditsId()); + return false; + }; + strong->showToast(Ui::Toast::Config{ + .text = tr::lng_credits_enough( + tr::now, + lt_link, + Ui::Text::Link( + Ui::Text::Bold( + tr::lng_credits_enough_link(tr::now))), + Ui::Text::RichLangValue), + .filter = filter, + .duration = 4 * crl::time(1000), + }); + } + } + }; + ::Settings::MaybeRequestBalanceIncrease( + controller->uiShow(), + amount, + ::Settings::SmallBalanceDeepLink{ .purpose = purpose }, + done); + return true; + }); +} + } // namespace Core diff --git a/Telegram/SourceFiles/core/local_url_handlers.h b/Telegram/SourceFiles/core/local_url_handlers.h index 9a5cc6a55e1edf..9f05b5710353f9 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/phone_click_handler.cpp b/Telegram/SourceFiles/core/phone_click_handler.cpp index 19d15b78a7e571..cfaafa8204fc80 100644 --- a/Telegram/SourceFiles/core/phone_click_handler.cpp +++ b/Telegram/SourceFiles/core/phone_click_handler.cpp @@ -26,8 +26,13 @@ For license and copyright information please follow this link: #include "styles/style_chat.h" // popupMenuExpandedSeparator. #include "styles/style_menu_icons.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace { +using namespace Tdb; + [[nodiscard]] QString Trim(QString text) { return text .replace('+', QString()) @@ -101,6 +106,7 @@ ResolvePhoneAction::ResolvePhoneAction( _peer = peer; _loaded.force_assign(true); } else { +#if 0 // mtp _api.request(MTPcontacts_ResolvePhone( MTP_string(phone) )).done([=](const MTPcontacts_ResolvedPeer &result) { @@ -118,6 +124,19 @@ ResolvePhoneAction::ResolvePhoneAction( _loaded.force_assign(true); } }).send(); +#endif + _api.request(TLsearchUserByPhoneNumber( + tl_string(phone), + tl_bool(false) + )).done([=](const TLuser &result) { + _peer = owner->processUser(result); + _loaded.force_assign(true); + }).fail([=](const Error &error) { + if (error.code == 404) { + _peer.force_assign(nullptr); + _loaded.force_assign(true); + } + }).send(); } paintRequest( diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index 2d15e4272793ef..51f2e61f9e0d94 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -31,9 +31,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; @@ -97,6 +106,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"; @@ -226,6 +236,53 @@ 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 isTonsite = !isEmail + && !isInternal + && url.startsWith(u"tonsite:"_q, Qt::CaseInsensitive); + const auto window = context.value().sessionWindow; + const auto session = (isEmail || isInternal || isTonsite) + ? 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 || isTonsite) + ? 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; @@ -259,6 +316,18 @@ bool UiIntegration::handleUrlClick( File::OpenUrl( UrlWithAutoLoginToken(url, std::move(parsed), domain, context)); } +#endif + if (isEmail) { + File::OpenEmailLink(url); + } else if (isTonsite) { + Core::App().iv().showTonSite(url, context); + } 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 777dde80c4ad73..8b5cb0f106bf8e 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,14 +253,7 @@ QString FindUpdateFile() { const auto list = updates.entryInfoList(QDir::Files); for (const auto &info : list) { static const auto RegExp = QRegularExpression( - "^(" - "tupdate|" - "tx64upd|" - "tarm64upd|" - "tmacupd|" - "tarmacupd|" - "tlinuxupd|" - ")\\d+(_[a-z\\d]+)?$", + "^update-(win|mac|linux)-(x86|x64|arm)-\\d+(_[a-z\\d]+)?$", QRegularExpression::CaseInsensitiveOption ); if (RegExp.match(info.fileName()).hasMatch()) { @@ -259,6 +263,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()) { @@ -934,10 +944,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( @@ -953,9 +992,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) { @@ -984,6 +1025,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 { @@ -1042,10 +1120,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 5b8c30c99b88cb..62d47e1d622e29 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/data/business/data_business_chatbots.cpp b/Telegram/SourceFiles/data/business/data_business_chatbots.cpp index ca894acf556b98..dc747b2ffdc17d 100644 --- a/Telegram/SourceFiles/data/business/data_business_chatbots.cpp +++ b/Telegram/SourceFiles/data/business/data_business_chatbots.cpp @@ -14,7 +14,15 @@ For license and copyright information please follow this link: #include "data/data_user.h" #include "main/main_session.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { +namespace { + +using namespace Tdb; + +} // namespace Chatbots::Chatbots(not_null owner) : _owner(owner) { @@ -26,6 +34,30 @@ void Chatbots::preload() { if (_loaded || _requestId) { return; } + _requestId = _owner->session().sender().request( + TLgetBusinessConnectedBot() + ).done([=](const TLbusinessConnectedBot &result) { + _requestId = 0; + _loaded = true; + + const auto &data = result.data(); + const auto botId = UserId(data.vbot_user_id().v); + _settings = ChatbotsSettings{ + .bot = _owner->session().data().user(botId), + .recipients = FromTL(_owner, data.vrecipients()), + .repliesAllowed = data.vcan_reply().v, + }; + }).fail([=](const Error &error) { + _requestId = 0; + if (error.code == 404) { + _loaded = true; + _settings.force_assign(ChatbotsSettings()); + } else { + LOG(("API Error: Could not get connected bot (%1)" + ).arg(error.message)); + } + }).send(); +#if 0 // mtp _requestId = _owner->session().api().request( MTPaccount_GetConnectedBots() ).done([=](const MTPaccount_ConnectedBots &result) { @@ -52,6 +84,7 @@ void Chatbots::preload() { ).arg(error.code() ).arg(error.type())); }).send(); +#endif } bool Chatbots::loaded() const { @@ -77,6 +110,33 @@ void Chatbots::save( const auto was = _settings.current(); if (was == settings) { return; + } else if (settings.bot) { + _owner->session().sender().request(TLsetBusinessConnectedBot( + tl_businessConnectedBot( + tl_int53(peerToUser(settings.bot->id).bare), + ForBotsToTL(settings.recipients), + tl_bool(settings.repliesAllowed)) + )).done([=] { + if (done) { + done(); + } + }).fail([=](const Error &error) { + if (fail) { + fail(error.message); + } + }).send(); + } else if (was.bot) { + _owner->session().sender().request(TLdeleteBusinessConnectedBot( + )).done([=] { + if (done) { + done(); + } + }).fail([=](const Error &error) { + if (fail) { + fail(error.message); + } + }).send(); +#if 0 // mtp } else if (was.bot || settings.bot) { using Flag = MTPaccount_UpdateConnectedBot::Flag; const auto api = &_owner->session().api(); @@ -99,6 +159,7 @@ void Chatbots::save( fail(error.type()); } }).send(); +#endif } _settings = settings; } @@ -107,7 +168,10 @@ void Chatbots::togglePaused(not_null peer, bool paused) { const auto type = paused ? SentRequestType::Pause : SentRequestType::Unpause; +#if 0 // mtp const auto api = &_owner->session().api(); +#endif + const auto api = &_owner->session().sender(); const auto i = _sentRequests.find(peer); if (i != end(_sentRequests)) { const auto already = i->second.type; @@ -117,10 +181,16 @@ void Chatbots::togglePaused(not_null peer, bool paused) { api->request(i->second.requestId).cancel(); _sentRequests.erase(i); } + const auto id = api->request(TLtoggleBusinessConnectedBotChatIsPaused( + peerToTdbChat(peer->id), + tl_bool(paused) + )).done([=] { +#if 0 // mtp const auto id = api->request(MTPaccount_ToggleConnectedBotPaused( peer->input, MTP_bool(paused) )).done([=] { +#endif if (_sentRequests[peer].type != type) { return; } else if (const auto settings = peer->barSettings()) { @@ -129,15 +199,19 @@ void Chatbots::togglePaused(not_null peer, bool paused) { & ~PeerBarSetting::BusinessBotCanReply) : ((*settings & ~PeerBarSetting::BusinessBotPaused) | PeerBarSetting::BusinessBotCanReply)); +#if 0 // mtp } else { api->requestPeerSettings(peer); +#endif } _sentRequests.remove(peer); }).fail([=] { if (_sentRequests[peer].type != type) { return; } +#if 0 // mtp api->requestPeerSettings(peer); +#endif _sentRequests.remove(peer); }).send(); _sentRequests[peer] = SentRequest{ type, id }; @@ -145,7 +219,10 @@ void Chatbots::togglePaused(not_null peer, bool paused) { void Chatbots::removeFrom(not_null peer) { const auto type = SentRequestType::Remove; +#if 0 // mtp const auto api = &_owner->session().api(); +#endif + const auto api = &_owner->session().sender(); const auto i = _sentRequests.find(peer); if (i != end(_sentRequests)) { const auto already = i->second.type; @@ -155,20 +232,29 @@ void Chatbots::removeFrom(not_null peer) { api->request(i->second.requestId).cancel(); _sentRequests.erase(i); } + const auto id = api->request(TLremoveBusinessConnectedBotFromChat( + peerToTdbChat(peer->id) + )).done([=] { +#if 0 // mtp const auto id = api->request(MTPaccount_DisablePeerConnectedBot( peer->input )).done([=] { +#endif if (_sentRequests[peer].type != type) { return; } else if (const auto settings = peer->barSettings()) { peer->clearBusinessBot(); +#if 0 // mtp } else { api->requestPeerSettings(peer); +#endif } _sentRequests.remove(peer); reload(); }).fail([=] { +#if 0 // mtp api->requestPeerSettings(peer); +#endif _sentRequests.remove(peer); }).send(); _sentRequests[peer] = SentRequest{ type, id }; @@ -176,7 +262,10 @@ void Chatbots::removeFrom(not_null peer) { void Chatbots::reload() { _loaded = false; +#if 0 // mtp _owner->session().api().request(base::take(_requestId)).cancel(); +#endif + _owner->session().sender().request(base::take(_requestId)).cancel(); preload(); } diff --git a/Telegram/SourceFiles/data/business/data_business_common.cpp b/Telegram/SourceFiles/data/business/data_business_common.cpp index 149a3ee1369fd6..40c55458defa8c 100644 --- a/Telegram/SourceFiles/data/business/data_business_common.cpp +++ b/Telegram/SourceFiles/data/business/data_business_common.cpp @@ -11,9 +11,13 @@ For license and copyright information please follow this link: #include "data/data_session.h" #include "data/data_user.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kDay = WorkingInterval::kDay; constexpr auto kWeek = WorkingInterval::kWeek; constexpr auto kInNextDayMax = WorkingInterval::kInNextDayMax; @@ -78,6 +82,7 @@ BusinessRecipients BusinessRecipients::MakeValid(BusinessRecipients value) { return value; } +#if 0 // mtp MTPInputBusinessRecipients ForMessagesToMTP(const BusinessRecipients &data) { using Flag = MTPDinputBusinessRecipients::Flag; const auto &chats = data.allButExcluded ? data.excluded : data.included; @@ -242,6 +247,153 @@ BusinessDetails FromMTP( .shortcutId = data.vshortcut_id().v, }; } +#endif + +TLbusinessRecipients ForMessagesToTL(const BusinessRecipients &data) { + auto copy = data; + (copy.allButExcluded ? copy.included : copy.excluded) = {}; + return ForBotsToTL(copy); +} + +TLbusinessRecipients ForBotsToTL(const BusinessRecipients &data) { + const auto &chats = data.allButExcluded ? data.excluded : data.included; + auto chatIds = QVector(); + chatIds.reserve(chats.list.size()); + for (const auto &user : chats.list) { + chatIds.push_back(peerToTdbChat(user->id)); + } + auto excludedChatIds = QVector(); + if (!data.allButExcluded) { + excludedChatIds.reserve(data.excluded.list.size()); + for (const auto &user : data.excluded.list) { + excludedChatIds.push_back(peerToTdbChat(user->id)); + } + } + using Type = BusinessChatType; + return tl_businessRecipients( + tl_vector(chatIds), + tl_vector(excludedChatIds), + tl_bool(chats.types & Type::ExistingChats), + tl_bool(chats.types & Type::NewChats), + tl_bool(chats.types & Type::Contacts), + tl_bool(chats.types & Type::NonContacts), + tl_bool(data.allButExcluded)); +} + +BusinessRecipients FromTL( + not_null owner, + const TLbusinessRecipients &recipients) { + using Type = BusinessChatType; + + const auto &data = recipients.data(); + auto result = BusinessRecipients{ + .allButExcluded = data.vexclude_selected().v, + }; + + auto &chats = result.allButExcluded + ? result.excluded + : result.included; + chats.types = Type() + | (data.vselect_new_chats().v ? Type::NewChats : Type()) + | (data.vselect_existing_chats().v ? Type::ExistingChats : Type()) + | (data.vselect_contacts().v ? Type::Contacts : Type()) + | (data.vselect_non_contacts().v ? Type::NonContacts : Type()); + for (const auto chatId : data.vchat_ids().v) { + if (const auto userId = peerToUser(peerFromTdbChat(chatId))) { + chats.list.push_back(owner->user(userId)); + } + } + for (const auto chatId : data.vexcluded_chat_ids().v) { + if (const auto userId = peerToUser(peerFromTdbChat(chatId))) { + result.excluded.list.push_back(owner->user(userId)); + } + } + return result; +} + +BusinessDetails FromTL( + not_null owner, + const tl::conditional &info) { + if (!info) { + return {}; + } + auto result = BusinessDetails(); + const auto &data = info->data(); + if (const auto hours = data.vopening_hours()) { + const auto &data = hours->data(); + result.hours.timezoneId = data.vtime_zone_id().v; + result.hours.intervals.list = ranges::views::all( + data.vopening_hours().v + ) | ranges::views::transform([]( + const TLbusinessOpeningHoursInterval &open) { + const auto &data = open.data(); + return WorkingInterval{ + data.vstart_minute().v * 60, + data.vend_minute().v * 60, + }; + }) | ranges::to_vector; + } + if (const auto location = data.vlocation()) { + const auto &data = location->data(); + result.location.address = data.vaddress().v; + if (const auto point = data.vlocation()) { + result.location.point = LocationPoint(*point); + } + } + if (const auto intro = data.vstart_page()) { + const auto &data = intro->data(); + result.intro.title = data.vtitle().v; + result.intro.description = data.vmessage().v; + if (const auto document = data.vsticker()) { + result.intro.sticker = owner->processDocument(*document); + if (!result.intro.sticker->sticker()) { + result.intro.sticker = nullptr; + } + } + } + return result; +} + +AwaySettings FromTL( + not_null owner, + const tl::conditional &settings) { + if (!settings) { + return AwaySettings(); + } + const auto &data = settings->data(); + auto result = AwaySettings{ + .recipients = FromTL(owner, data.vrecipients()), + .shortcutId = data.vshortcut_id().v, + .offlineOnly = data.voffline_only().v, + }; + data.vschedule().match([&]( + const TLDbusinessAwayMessageScheduleAlways &) { + result.schedule.type = AwayScheduleType::Always; + }, [&](const TLDbusinessAwayMessageScheduleOutsideOfOpeningHours &) { + result.schedule.type = AwayScheduleType::OutsideWorkingHours; + }, [&](const TLDbusinessAwayMessageScheduleCustom &data) { + result.schedule.type = AwayScheduleType::Custom; + result.schedule.customInterval = WorkingInterval{ + data.vstart_date().v, + data.vend_date().v, + }; + }); + return result; +} + +[[nodiscard]] GreetingSettings FromTL( + not_null owner, + const tl::conditional &settings) { + if (!settings) { + return GreetingSettings(); + } + const auto &data = settings->data(); + return GreetingSettings{ + .recipients = FromTL(owner, data.vrecipients()), + .noActivityDays = data.vinactivity_days().v, + .shortcutId = data.vshortcut_id().v, + }; +} WorkingIntervals WorkingIntervals::normalized() const { return SortAndMerge(MoveTailToFront(SortAndMerge(*this))); diff --git a/Telegram/SourceFiles/data/business/data_business_common.h b/Telegram/SourceFiles/data/business/data_business_common.h index 600d5ff79d81f8..d8540bd6539ace 100644 --- a/Telegram/SourceFiles/data/business/data_business_common.h +++ b/Telegram/SourceFiles/data/business/data_business_common.h @@ -10,6 +10,13 @@ For license and copyright information please follow this link: #include "base/flags.h" #include "data/data_location.h" +namespace Tdb { +class TLbusinessInfo; +class TLbusinessRecipients; +class TLbusinessAwayMessageSettings; +class TLbusinessGreetingMessageSettings; +} // namespace Tdb + class UserData; namespace Data { @@ -57,6 +64,7 @@ enum class BusinessRecipientsType : uchar { Bots, }; +#if 0 // mtp [[nodiscard]] MTPInputBusinessRecipients ForMessagesToMTP( const BusinessRecipients &data); [[nodiscard]] MTPInputBusinessBotRecipients ForBotsToMTP( @@ -67,6 +75,15 @@ enum class BusinessRecipientsType : uchar { [[nodiscard]] BusinessRecipients FromMTP( not_null owner, const MTPBusinessBotRecipients &recipients); +#endif + +[[nodiscard]] Tdb::TLbusinessRecipients ForMessagesToTL( + const BusinessRecipients &data); +[[nodiscard]] Tdb::TLbusinessRecipients ForBotsToTL( + const BusinessRecipients &data); +[[nodiscard]] BusinessRecipients FromTL( + not_null owner, + const Tdb::TLbusinessRecipients &recipients); struct Timezone { QString id; @@ -217,11 +234,17 @@ struct BusinessDetails { const BusinessDetails &b) = default; }; +#if 0 // mtp [[nodiscard]] BusinessDetails FromMTP( not_null owner, const tl::conditional &hours, const tl::conditional &location, const tl::conditional &intro); +#endif + +[[nodiscard]] BusinessDetails FromTL( + not_null owner, + const tl::conditional &info); enum class AwayScheduleType : uchar { Never = 0, @@ -254,9 +277,15 @@ struct AwaySettings { const AwaySettings &b) = default; }; +#if 0 // mtp [[nodiscard]] AwaySettings FromMTP( not_null owner, const tl::conditional &message); +#endif + +[[nodiscard]] AwaySettings FromTL( + not_null owner, + const tl::conditional &settings); struct GreetingSettings { BusinessRecipients recipients; @@ -272,8 +301,14 @@ struct GreetingSettings { const GreetingSettings &b) = default; }; +#if 0 // mtp [[nodiscard]] GreetingSettings FromMTP( not_null owner, const tl::conditional &message); +#endif + +[[nodiscard]] GreetingSettings FromTL( + not_null owner, + const tl::conditional &settings); } // namespace Data diff --git a/Telegram/SourceFiles/data/business/data_business_info.cpp b/Telegram/SourceFiles/data/business/data_business_info.cpp index d9bb6ec7a9b043..1b97781ce65aa7 100644 --- a/Telegram/SourceFiles/data/business/data_business_info.cpp +++ b/Telegram/SourceFiles/data/business/data_business_info.cpp @@ -15,9 +15,15 @@ For license and copyright information please follow this link: #include "data/data_user.h" #include "main/main_session.h" +#include "tdb/tdb_sender.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + +#if 0 // mtp [[nodiscard]] MTPBusinessWorkHours ToMTP(const WorkingHours &data) { const auto list = data.intervals.normalized().list; const auto proj = [](const WorkingInterval &data) { @@ -60,6 +66,83 @@ namespace { ForMessagesToMTP(data.recipients), MTP_int(data.noActivityDays)); } +#endif + +[[nodiscard]] std::optional ToTL( + const WorkingHours &data) { + if (!data) { + return {}; + } + const auto list = data.intervals.normalized().list; + const auto proj = [](const WorkingInterval &data) { + return MTPBusinessWeeklyOpen(MTP_businessWeeklyOpen( + MTP_int(data.start / 60), + MTP_int(data.end / 60))); + }; + auto intervals = QVector(); + intervals.reserve(list.size()); + for (const auto &interval : list) { + intervals.push_back(tl_businessOpeningHoursInterval( + tl_int32(interval.start / 60), + tl_int32(interval.end / 60))); + } + return tl_businessOpeningHours( + tl_string(data.timezoneId), + tl_vector(intervals)); +} + +[[nodiscard]] std::optional ToTL( + const GreetingSettings &data) { + if (!data) { + return {}; + } + return tl_businessGreetingMessageSettings( + tl_int32(data.shortcutId), + ForMessagesToTL(data.recipients), + tl_int32(data.noActivityDays)); +} +#if 0 // mtp +[[nodiscard]] MTPInputBusinessAwayMessage ToMTP(const AwaySettings &data) { + using Flag = MTPDinputBusinessAwayMessage::Flag; + return MTP_inputBusinessAwayMessage( + MTP_flags(data.offlineOnly ? Flag::f_offline_only : Flag()), + MTP_int(data.shortcutId), + ToMTP(data.schedule), + ForMessagesToMTP(data.recipients)); +} + +[[nodiscard]] MTPInputBusinessGreetingMessage ToMTP( + const GreetingSettings &data) { + return MTP_inputBusinessGreetingMessage( + MTP_int(data.shortcutId), + ForMessagesToMTP(data.recipients), + MTP_int(data.noActivityDays)); +} +#endif + +[[nodiscard]] TLbusinessAwayMessageSchedule ToTL(const AwaySchedule &data) { + Expects(data.type != AwayScheduleType::Never); + + return (data.type == AwayScheduleType::Always) + ? tl_businessAwayMessageScheduleAlways() + : (data.type == AwayScheduleType::OutsideWorkingHours) + ? tl_businessAwayMessageScheduleOutsideOfOpeningHours() + : tl_businessAwayMessageScheduleCustom( + tl_int32(data.customInterval.start), + tl_int32(data.customInterval.end)); +} + +[[nodiscard]] std::optional ToTL( + const AwaySettings &data) { + if (!data) { + return {}; + } + return tl_businessAwayMessageSettings( + tl_int32(data.shortcutId), + ForMessagesToTL(data.recipients), + ToTL(data.schedule), + tl_bool(data.offlineOnly)); +} } // namespace @@ -79,16 +162,24 @@ void BusinessInfo::saveWorkingHours( return; } +#if 0 // mtp using Flag = MTPaccount_UpdateBusinessWorkHours::Flag; session->api().request(MTPaccount_UpdateBusinessWorkHours( MTP_flags(data ? Flag::f_business_work_hours : Flag()), ToMTP(data) )).fail([=](const MTP::Error &error) { +#endif + session->sender().request(TLsetBusinessOpeningHours( + ToTL(data) + )).fail([=](const Error &error) { auto details = session->user()->businessDetails(); details.hours = was; session->user()->setBusinessDetails(std::move(details)); if (fail) { +#if 0 // mtp fail(error.type()); +#endif + fail(error.message); } }).send(); @@ -104,6 +195,7 @@ void BusinessInfo::saveChatIntro(ChatIntro data, Fn fail) { return; } else { const auto session = &_owner->session(); +#if 0 // mtp using Flag = MTPaccount_UpdateBusinessIntro::Flag; session->api().request(MTPaccount_UpdateBusinessIntro( MTP_flags(data ? Flag::f_intro : Flag()), @@ -117,11 +209,24 @@ void BusinessInfo::saveChatIntro(ChatIntro data, Fn fail) { ? data.sticker->mtpInput() : MTP_inputDocumentEmpty())) )).fail([=](const MTP::Error &error) { +#endif + session->sender().request(TLsetBusinessStartPage(data + ? tl_inputBusinessStartPage( + tl_string(data.title), + tl_string(data.description), + (data.sticker + ? tl_inputFileId(tl_int32(data.sticker->tdbFileId())) + : std::optional())) + : std::optional() + )).fail([=](const Error &error) { auto details = session->user()->businessDetails(); details.intro = was; session->user()->setBusinessDetails(std::move(details)); if (fail) { +#if 0 // mtp fail(error.type()); +#endif + fail(error.message); } }).send(); } @@ -140,6 +245,19 @@ void BusinessInfo::saveLocation( return; } else { const auto session = &_owner->session(); + session->sender().request(TLsetBusinessLocation( + ((data.point || !data.address.isEmpty()) + ? tl_businessLocation( + (data.point + ? tl_location( + tl_double(data.point->lat()), + tl_double(data.point->lon()), + tl_double(0)) + : std::optional()), + tl_string(data.address)) + : std::optional()) + )).fail([=](const Error &error) { +#if 0 // mtp using Flag = MTPaccount_UpdateBusinessLocation::Flag; session->api().request(MTPaccount_UpdateBusinessLocation( MTP_flags((data.point ? Flag::f_geo_point : Flag()) @@ -153,11 +271,15 @@ void BusinessInfo::saveLocation( : MTP_inputGeoPointEmpty()), MTP_string(data.address) )).fail([=](const MTP::Error &error) { +#endif auto details = session->user()->businessDetails(); details.location = was; session->user()->setBusinessDetails(std::move(details)); if (fail) { +#if 0 // mtp fail(error.type()); +#endif + fail(error.message); } }).send(); } @@ -181,16 +303,24 @@ void BusinessInfo::saveAwaySettings( if (was == data) { return; } else if (!data || data.shortcutId) { +#if 0 // mtp using Flag = MTPaccount_UpdateBusinessAwayMessage::Flag; const auto session = &_owner->session(); session->api().request(MTPaccount_UpdateBusinessAwayMessage( MTP_flags(data ? Flag::f_message : Flag()), data ? ToMTP(data) : MTPInputBusinessAwayMessage() )).fail([=](const MTP::Error &error) { +#endif + _owner->session().sender().request(TLsetBusinessAwayMessageSettings( + ToTL(data) + )).fail([=](const Error &error) { _awaySettings = was; _awaySettingsChanged.fire({}); if (fail) { +#if 0 // mtp fail(error.type()); +#endif + fail(error.message); } }).send(); } @@ -225,16 +355,24 @@ void BusinessInfo::saveGreetingSettings( if (was == data) { return; } else if (!data || data.shortcutId) { + _owner->session().sender().request( + TLsetBusinessGreetingMessageSettings(ToTL(data)) + ).fail([=](const Error &error) { +#if 0 // mtp using Flag = MTPaccount_UpdateBusinessGreetingMessage::Flag; _owner->session().api().request( MTPaccount_UpdateBusinessGreetingMessage( MTP_flags(data ? Flag::f_message : Flag()), data ? ToMTP(data) : MTPInputBusinessGreetingMessage()) ).fail([=](const MTP::Error &error) { +#endif _greetingSettings = was; _greetingSettingsChanged.fire({}); if (fail) { +#if 0 // mtp fail(error.type()); +#endif + fail(error.message); } }).send(); } @@ -262,27 +400,46 @@ void BusinessInfo::preloadTimezones() { if (!_timezones.current().list.empty() || _timezonesRequestId) { return; } + _timezonesRequestId = _owner->session().sender().request( + TLgetTimeZones() + ).done([=](const TLtimeZones &result) { + const auto &data = result.data(); + { + const auto proj = [](const TLtimeZone &result) { +#if 0 // mtp _timezonesRequestId = _owner->session().api().request( MTPhelp_GetTimezonesList(MTP_int(_timezonesHash)) ).done([=](const MTPhelp_TimezonesList &result) { result.match([&](const MTPDhelp_timezonesList &data) { _timezonesHash = data.vhash().v; const auto proj = [](const MTPtimezone &result) { +#endif return Timezone{ .id = qs(result.data().vid()), .name = qs(result.data().vname()), +#if 0 // mtp .utcOffset = result.data().vutc_offset().v, }; }; _timezones = Timezones{ .list = ranges::views::all( data.vtimezones().v +#endif + .utcOffset = result.data().vutc_time_offset().v + }; + }; + _timezones = Timezones{ + .list = ranges::views::all( + data.vtime_zones().v ) | ranges::views::transform( proj ) | ranges::to_vector, }; +#if 0 // mtp }, [](const MTPDhelp_timezonesListNotModified &) { }); +#endif + } }).send(); } diff --git a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp index ff7979c2e8211a..62a335c23b9fb5 100644 --- a/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp +++ b/Telegram/SourceFiles/data/business/data_shortcut_messages.cpp @@ -19,9 +19,13 @@ 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) { @@ -102,7 +106,10 @@ bool IsShortcutMsgId(MsgId id) { ShortcutMessages::ShortcutMessages(not_null owner) : _session(&owner->session()) , _history(owner->history(_session->userPeerId())) +#if 0 // mtp , _clearTimer([=] { clearOldRequests(); }) { +#endif +{ owner->itemRemoved( ) | rpl::filter([](not_null item) { return item->isBusinessShortcut(); @@ -113,10 +120,14 @@ ShortcutMessages::ShortcutMessages(not_null owner) ShortcutMessages::~ShortcutMessages() { for (const auto &request : _requests) { +#if 0 // mtp _session->api().request(request.second.requestId).cancel(); +#endif + _session->sender().request(request.second.requestId).cancel(); } } +#if 0 // mtp void ShortcutMessages::clearOldRequests() { const auto now = crl::now(); while (true) { @@ -133,6 +144,8 @@ void ShortcutMessages::clearOldRequests() { } void ShortcutMessages::updateShortcuts(const QVector &list) { +#endif +void ShortcutMessages::updateShortcuts(const QVector &list) { auto shortcuts = parseShortcuts(list); auto changes = std::vector(); for (auto &[id, shortcut] : _shortcuts.list) { @@ -200,6 +213,7 @@ void ShortcutMessages::mergeMessagesFromTo( } } +#if 0 // mtp Shortcuts ShortcutMessages::parseShortcuts( const QVector &list) const { auto result = Shortcuts(); @@ -219,6 +233,27 @@ Shortcut ShortcutMessages::parseShortcut(const MTPQuickReply &reply) const { .topMessageId = localMessageId(data.vtop_message().v), }; } +#endif + +Shortcuts ShortcutMessages::parseShortcuts( + const QVector &list) const { + auto result = Shortcuts(); + for (const auto &reply : list) { + result.list.emplace(reply.v); + } + return result; +} + +Shortcut ShortcutMessages::parseShortcut( + const TLquickReplyShortcut &shortcut) const { + const auto &data = shortcut.data(); + return Shortcut{ + .id = BusinessShortcutId(data.vid().v), + .count = data.vmessage_count().v, + .name = data.vname().v, + .topMessageId = data.vfirst_message().data().vid().v, + }; +} MsgId ShortcutMessages::localMessageId(MsgId remoteId) const { return RemoteToLocalMsgId(remoteId); @@ -237,6 +272,7 @@ int ShortcutMessages::count(BusinessShortcutId shortcutId) const { return (i != end(_data)) ? i->second.items.size() : 0; } +#if 0 // mtp void ShortcutMessages::apply(const MTPDupdateQuickReplies &update) { updateShortcuts(update.vquick_replies().v); scheduleShortcutsReload(); @@ -266,6 +302,10 @@ void ShortcutMessages::scheduleShortcutsReload() { void ShortcutMessages::apply(const MTPDupdateNewQuickReply &update) { const auto &reply = update.vquick_reply(); +#endif +void ShortcutMessages::apply(const TLDupdateQuickReplyShortcut &data) { + const auto &reply = data.vshortcut(); + auto foundId = BusinessShortcutId(); const auto shortcut = parseShortcut(reply); for (auto &[id, existing] : _shortcuts.list) { @@ -293,6 +333,7 @@ void ShortcutMessages::apply(const MTPDupdateNewQuickReply &update) { } } +#if 0 // mtp void ShortcutMessages::apply(const MTPDupdateQuickReplyMessage &update) { const auto &message = update.vmessage(); const auto shortcutId = BusinessShortcutIdFromMessage(message); @@ -309,6 +350,7 @@ void ShortcutMessages::apply(const MTPDupdateQuickReplyMessage &update) { request(shortcutId); } } +#endif void ShortcutMessages::updateCount(BusinessShortcutId shortcutId) { const auto i = _data.find(shortcutId); @@ -325,6 +367,7 @@ void ShortcutMessages::updateCount(BusinessShortcutId shortcutId) { } } +#if 0 // mtp void ShortcutMessages::apply( const MTPDupdateDeleteQuickReplyMessages &update) { const auto shortcutId = update.vshortcut_id().v; @@ -355,6 +398,10 @@ void ShortcutMessages::apply( void ShortcutMessages::apply(const MTPDupdateDeleteQuickReply &update) { const auto shortcutId = update.vshortcut_id().v; +#endif +void ShortcutMessages::apply( + const TLDupdateQuickReplyShortcutDeleted &data) { + const auto shortcutId = data.vshortcut_id().v; if (!shortcutId) { return; } @@ -372,6 +419,7 @@ void ShortcutMessages::apply(const MTPDupdateDeleteQuickReply &update) { } } +#if 0 // mtp void ShortcutMessages::apply( const MTPDupdateMessageID &update, not_null local) { @@ -388,6 +436,36 @@ void ShortcutMessages::apply( list.itemById.emplace(id, local); } } +#endif + +void ShortcutMessages::apply(const TLDupdateQuickReplyShortcuts &data) { + updateShortcuts(data.vshortcut_ids().v); +} + +void ShortcutMessages::apply( + const TLDupdateQuickReplyShortcutMessages &data) { + const auto shortcutId = data.vshortcut_id().v; + const auto &messages = data.vmessages().v; + if (messages.isEmpty()) { + clearNotSending(shortcutId); + return; + } + auto received = base::flat_set>(); + auto clear = base::flat_set>(); + auto &list = _data.emplace(shortcutId, List()).first->second; + for (const auto &message : messages) { + if (const auto item = append(shortcutId, list, message)) { + received.emplace(item); + } + } + for (const auto &owned : list.items) { + const auto item = owned.get(); + if (!item->isSending() && !received.contains(item)) { + clear.emplace(item); + } + } + updated(shortcutId, received, clear); +} void ShortcutMessages::appendSending(not_null item) { Expects(item->isSending()); @@ -438,6 +516,7 @@ Data::MessagesSlice ShortcutMessages::list(BusinessShortcutId shortcutId) { return result; } +#if 0 // mtp void ShortcutMessages::preloadShortcuts() { if (_shortcutsLoaded || _shortcutsRequestId) { return; @@ -458,6 +537,7 @@ void ShortcutMessages::preloadShortcuts() { }); }).send(); } +#endif const Shortcuts &ShortcutMessages::shortcuts() const { return _shortcuts; @@ -539,6 +619,7 @@ void ShortcutMessages::editShortcut( } } } +#if 0 // mtp _session->api().request(MTPmessages_EditQuickReplyShortcut( MTP_int(id), MTP_string(name) @@ -551,6 +632,11 @@ void ShortcutMessages::editShortcut( fail(type); } }).send(); +#endif + _session->sender().request(TLsetQuickReplyShortcutName( + tl_int32(id), + tl_string(name) + )).send(); } void ShortcutMessages::removeShortcut(BusinessShortcutId shortcutId) { @@ -566,15 +652,23 @@ void ShortcutMessages::removeShortcut(BusinessShortcutId shortcutId) { _shortcuts.list.remove(shortcutId); _shortcutIdChanges.fire({ shortcutId, 0 }); +#if 0 // mtp _session->api().request(MTPmessages_DeleteQuickReplyShortcut( MTP_int(shortcutId) )).send(); +#endif + _session->sender().request(TLdeleteQuickReplyShortcut( + tl_int32(shortcutId) + )).send(); } void ShortcutMessages::cancelRequest(BusinessShortcutId shortcutId) { const auto j = _requests.find(shortcutId); if (j != end(_requests)) { +#if 0 // mtp _session->api().request(j->second.requestId).cancel(); +#endif + _session->sender().request(j->second.requestId).cancel(); _requests.erase(j); } } @@ -584,6 +678,16 @@ void ShortcutMessages::request(BusinessShortcutId shortcutId) { if (request.requestId || TooEarlyForRequest(request.lastReceived)) { return; } + request.requestId = _session->sender().request( + TLloadQuickReplyShortcutMessages(tl_int32(shortcutId)) + ).done([=] { + auto &request = _requests[shortcutId]; + request.lastReceived = crl::now(); + request.requestId = 0; + }).fail([=] { + _requests.remove(shortcutId); + }).send(); +#if 0 // mtp const auto i = _data.find(shortcutId); const auto hash = (i != end(_data)) ? countListHash(i->second) @@ -599,8 +703,10 @@ void ShortcutMessages::request(BusinessShortcutId shortcutId) { }).fail([=] { _requests.remove(shortcutId); }).send(); +#endif } +#if 0 // mtp void ShortcutMessages::parse( BusinessShortcutId shortcutId, const MTPmessages_Messages &list) { @@ -646,9 +752,18 @@ HistoryItem *ShortcutMessages::append( const auto id = message.match([&](const auto &data) { return data.vid().v; }); +#endif +HistoryItem *ShortcutMessages::append( + BusinessShortcutId shortcutId, + List &list, + const TLquickReplyMessage &message) { + const auto &data = message.data(); + const auto id = data.vid().v; const auto i = list.itemById.find(id); if (i != end(list.itemById)) { const auto existing = i->second; + existing->updateContent(data.vcontent()); +#if 0 // mtp message.match([&](const MTPDmessage &data) { if (data.is_edit_hide()) { existing->applyEdition(HistoryMessageEdition(_session, data)); @@ -666,6 +781,7 @@ HistoryItem *ShortcutMessages::append( existing->updateDate(data.vdate().v); _history->owner().requestItemTextRefresh(existing); }, [&](const auto &data) {}); +#endif return existing; } @@ -673,11 +789,17 @@ HistoryItem *ShortcutMessages::append( LOG(("API Error: Bad id in quick reply messages: %1.").arg(id)); return nullptr; } +#if 0 // mtp const auto item = _session->data().addNewMessage( localMessageId(id), PrepareMessage(shortcutId, message), MessageFlags(), // localFlags NewMessageType::Existing); +#endif + const auto item = _session->data().processMessage( + shortcutId, + message, + NewMessageType::Existing); if (!item || item->history() != _history || item->shortcutId() != shortcutId) { @@ -766,6 +888,7 @@ uint64 ShortcutMessages::countListHash(const List &list) const { return HashFinalize(hash); } +#if 0 // mtp MTPInputQuickReplyShortcut ShortcutIdToMTP( not_null session, BusinessShortcutId id) { @@ -774,5 +897,6 @@ MTPInputQuickReplyShortcut ShortcutIdToMTP( session->data().shortcutMessages().lookupShortcut(id).name)) : MTPInputQuickReplyShortcut(); } +#endif } // namespace Data diff --git a/Telegram/SourceFiles/data/business/data_shortcut_messages.h b/Telegram/SourceFiles/data/business/data_shortcut_messages.h index 76a1df56d4fe89..0ca6d4fcbcdf61 100644 --- a/Telegram/SourceFiles/data/business/data_shortcut_messages.h +++ b/Telegram/SourceFiles/data/business/data_shortcut_messages.h @@ -10,6 +10,20 @@ For license and copyright information please follow this link: #include "history/history_item.h" #include "base/timer.h" +namespace tl { +class int_type; +} // namespace tl + +namespace Tdb { +using TLint32 = tl::int_type; +class TLquickReplyMessage; +class TLquickReplyShortcut; +class TLDupdateQuickReplyShortcut; +class TLDupdateQuickReplyShortcutDeleted; +class TLDupdateQuickReplyShortcuts; +class TLDupdateQuickReplyShortcutMessages; +} // namespace Tdb + class History; namespace Main { @@ -56,6 +70,7 @@ class ShortcutMessages final { [[nodiscard]] int count(BusinessShortcutId shortcutId) const; [[nodiscard]] MsgId localMessageId(MsgId remoteId) const; +#if 0 // mtp void apply(const MTPDupdateQuickReplies &update); void apply(const MTPDupdateNewQuickReply &update); void apply(const MTPDupdateQuickReplyMessage &update); @@ -64,6 +79,12 @@ class ShortcutMessages final { void apply( const MTPDupdateMessageID &update, not_null local); +#endif + + void apply(const Tdb::TLDupdateQuickReplyShortcut &data); + void apply(const Tdb::TLDupdateQuickReplyShortcutDeleted &data); + void apply(const Tdb::TLDupdateQuickReplyShortcuts &data); + void apply(const Tdb::TLDupdateQuickReplyShortcutMessages &data); void appendSending(not_null item); void removeSending(not_null item); @@ -99,6 +120,7 @@ class ShortcutMessages final { }; void request(BusinessShortcutId shortcutId); +#if 0 // mtp void parse( BusinessShortcutId shortcutId, const MTPmessages_Messages &list); @@ -106,6 +128,11 @@ class ShortcutMessages final { BusinessShortcutId shortcutId, List &list, const MTPMessage &message); +#endif + HistoryItem *append( + BusinessShortcutId shortcutId, + List &list, + const Tdb::TLquickReplyMessage &message); void clearNotSending(BusinessShortcutId shortcutId); void updated( BusinessShortcutId shortcutId, @@ -118,19 +145,30 @@ class ShortcutMessages final { void cancelRequest(BusinessShortcutId shortcutId); void updateCount(BusinessShortcutId shortcutId); +#if 0 // mtp void scheduleShortcutsReload(); +#endif void mergeMessagesFromTo( BusinessShortcutId fromId, BusinessShortcutId toId); +#if 0 // mtp void updateShortcuts(const QVector &list); [[nodiscard]] Shortcut parseShortcut(const MTPQuickReply &reply) const; [[nodiscard]] Shortcuts parseShortcuts( const QVector &list) const; +#endif + void updateShortcuts(const QVector &list); + [[nodiscard]] Shortcuts parseShortcuts( + const QVector &list) const; + [[nodiscard]] Shortcut parseShortcut( + const Tdb::TLquickReplyShortcut &shortcut) const; const not_null _session; const not_null _history; +#if 0 // mtp base::Timer _clearTimer; +#endif base::flat_map _data; base::flat_map _requests; rpl::event_stream _updates; @@ -147,8 +185,10 @@ class ShortcutMessages final { }; +#if 0 // mtp [[nodiscard]] MTPInputQuickReplyShortcut ShortcutIdToMTP( not_null session, BusinessShortcutId id); +#endif } // namespace Data diff --git a/Telegram/SourceFiles/data/components/credits.cpp b/Telegram/SourceFiles/data/components/credits.cpp index 4c2379a9501da2..6f9557164f2d10 100644 --- a/Telegram/SourceFiles/data/components/credits.cpp +++ b/Telegram/SourceFiles/data/components/credits.cpp @@ -12,6 +12,8 @@ For license and copyright information please follow this link: #include "main/main_app_config.h" #include "main/main_session.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { @@ -26,9 +28,14 @@ Credits::Credits(not_null session) Credits::~Credits() = default; +#if 0 // mtp void Credits::apply(const MTPDupdateStarsBalance &data) { apply(data.vbalance().v); } +#endif +void Credits::apply(const Tdb::TLDupdateOwnedStarCount &data) { + apply(data.vstar_count().v); +} rpl::producer Credits::rateValue( not_null ownedBotOrChannel) { diff --git a/Telegram/SourceFiles/data/components/credits.h b/Telegram/SourceFiles/data/components/credits.h index 62fefcc3c7b1b2..ee39a50e4aa9e7 100644 --- a/Telegram/SourceFiles/data/components/credits.h +++ b/Telegram/SourceFiles/data/components/credits.h @@ -15,6 +15,10 @@ namespace Main { class Session; } // namespace Main +namespace Tdb { +class TLDupdateOwnedStarCount; +} // namespace Tdb + namespace Data { class Credits final { @@ -40,7 +44,10 @@ class Credits final { void withdrawLocked(int count); void invalidate(); +#if 0 // mtp void apply(const MTPDupdateStarsBalance &data); +#endif + void apply(const Tdb::TLDupdateOwnedStarCount &data); private: void updateNonLockedValue(); diff --git a/Telegram/SourceFiles/data/components/factchecks.cpp b/Telegram/SourceFiles/data/components/factchecks.cpp index 77251925ef34bb..af6f9ad1ba38a0 100644 --- a/Telegram/SourceFiles/data/components/factchecks.cpp +++ b/Telegram/SourceFiles/data/components/factchecks.cpp @@ -22,18 +22,27 @@ For license and copyright information please follow this link: #include "main/main_session.h" #include "ui/layers/show.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kRequestDelay = crl::time(1000); } // namespace Factchecks::Factchecks(not_null session) : _session(session) +#if 0 // mtp , _requestTimer([=] { request(); }) { +#endif +{ } +#if 0 // mtp void Factchecks::requestFor(not_null item) { subscribeIfNotYet(); @@ -121,6 +130,28 @@ void Factchecks::request() { } }).send(); } +#endif + +void Factchecks::apply(const TLDupdateMessageFactCheck &data) { + const auto fullId = FullMsgId{ + peerFromTdbChat(data.vchat_id()), + data.vmessage_id().v + }; + if (const auto item = _session->data().message(fullId)) { + item->setFactcheck(FromTL(&data.vfact_check())); + } +} + +bool Factchecks::apply(const Tdb::TLDupdateOption &data) { + if (data.vname().v == u"can_edit_fact_check"_q) { + _canEdit = data.vvalue().c_optionValueBoolean().vvalue().v; + } else if (data.vname().v == u"fact_check_length_max"_q) { + _lengthLimit = data.vvalue().c_optionValueInteger().vvalue().v; + } else { + return false; + } + return true; +} std::unique_ptr Factchecks::makeMedia( not_null view, @@ -154,11 +185,17 @@ bool Factchecks::canEdit(not_null item) const { } bool Factchecks::canEdit() const { +#if 0 // mtp return _session->appConfig().get(u"can_edit_factcheck"_q, false); +#endif + return _canEdit; } int Factchecks::lengthLimit() const { +#if 0 // mtp return _session->appConfig().get(u"factcheck_length_limit"_q, 1024); +#endif + return _lengthLimit; } void Factchecks::save( @@ -168,6 +205,7 @@ void Factchecks::save( const auto item = _session->data().message(itemId); if (!item) { return; +#if 0 // mtp } else if (text.empty()) { _session->api().request(MTPmessages_DeleteFactCheck( item->history()->peer->input, @@ -194,7 +232,19 @@ void Factchecks::save( }).fail([=](const MTP::Error &error) { done(error.type()); }).send(); +#endif } + _session->sender().request(TLsetMessageFactCheck( + peerToTdbChat(item->history()->peer->id), + tl_int53(item->id.bare), + (text.empty() + ? std::optional() + : Api::FormattedTextToTdb(text)) + )).done([=] { + done(QString()); + }).fail([=](const Error &error) { + done(error.message); + }).send(); } void Factchecks::save( diff --git a/Telegram/SourceFiles/data/components/factchecks.h b/Telegram/SourceFiles/data/components/factchecks.h index a7eaca3d4023a2..c2cc544ee5bc95 100644 --- a/Telegram/SourceFiles/data/components/factchecks.h +++ b/Telegram/SourceFiles/data/components/factchecks.h @@ -9,6 +9,11 @@ For license and copyright information please follow this link: #include "base/timer.h" +namespace Tdb { +class TLDupdateMessageFactCheck; +class TLDupdateOption; +} // namespace Tdb + class HistoryItem; struct HistoryMessageFactcheck; @@ -31,7 +36,11 @@ class Factchecks final { public: explicit Factchecks(not_null session); +#if 0 // mtp void requestFor(not_null item); +#endif + void apply(const Tdb::TLDupdateMessageFactCheck &data); + bool apply(const Tdb::TLDupdateOption &data); [[nodiscard]] std::unique_ptr makeMedia( not_null view, not_null factcheck); @@ -52,16 +61,22 @@ class Factchecks final { private: [[nodiscard]] bool canEdit() const; +#if 0 // mtp void subscribeIfNotYet(); void request(); +#endif const not_null _session; +#if 0 // mtp base::Timer _requestTimer; base::flat_set> _pending; std::vector _requested; mtpRequestId _requestId = 0; bool _subscribed = false; +#endif + bool _canEdit = false; + int _lengthLimit = 1024; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/data/components/scheduled_messages.cpp b/Telegram/SourceFiles/data/components/scheduled_messages.cpp index 9e69dd35b75fc8..59b075a23e9d78 100644 --- a/Telegram/SourceFiles/data/components/scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/components/scheduled_messages.cpp @@ -19,21 +19,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) { @@ -45,6 +55,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( @@ -96,12 +107,15 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000); data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()); }); } +#endif } // namespace +#if 0 // mtp bool IsScheduledMsgId(MsgId id) { return (id > ServerMaxMsgId) && (id < ScheduledMaxMsgId); } +#endif ScheduledMessages::ScheduledMessages(not_null session) : _session(session) @@ -122,7 +136,10 @@ ScheduledMessages::~ScheduledMessages() { void ScheduledMessages::clear() { _lifetime.destroy(); for (const auto &request : base::take(_requests)) { +#if 0 // mtp _session->api().request(request.second.requestId).cancel(); +#endif + _session->sender().request(request.second.requestId).cancel(); } base::take(_data); } @@ -194,6 +211,7 @@ bool ScheduledMessages::hasFor(not_null topic) const { }); } +#if 0 // mtp void ScheduledMessages::sendNowSimpleMessage( const MTPDupdateShortSentMessage &update, not_null local) { @@ -373,7 +391,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()); @@ -391,6 +421,7 @@ void ScheduledMessages::removeSending(not_null item) { item->destroy(); } +#endif rpl::producer<> ScheduledMessages::updates(not_null history) { request(history); @@ -451,6 +482,9 @@ Data::MessagesSlice ScheduledMessages::list( void ScheduledMessages::request(not_null history) { const auto peer = history->peer; + if (peer->isSecretChat()) { + return; + } if (peer->isBroadcast() && !Data::CanSendAnything(peer)) { return; } @@ -458,6 +492,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) @@ -469,8 +512,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) { @@ -559,6 +642,7 @@ HistoryItem *ScheduledMessages::append( list.itemById.emplace(id, item); return item; } +#endif void ScheduledMessages::clearNotSending(not_null history) { const auto i = _data.find(history); @@ -602,9 +686,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/components/scheduled_messages.h b/Telegram/SourceFiles/data/components/scheduled_messages.h index 90d5b04151157a..e5a9ab235450fe 100644 --- a/Telegram/SourceFiles/data/components/scheduled_messages.h +++ b/Telegram/SourceFiles/data/components/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 { @@ -20,7 +24,9 @@ namespace Data { struct MessagesSlice; +#if 0 // mtp [[nodiscard]] bool IsScheduledMsgId(MsgId id); +#endif class ScheduledMessages final { public: @@ -36,19 +42,25 @@ class ScheduledMessages final { [[nodiscard]] bool hasFor(not_null topic) 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) const; @@ -69,6 +81,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); @@ -76,6 +91,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/components/sponsored_messages.cpp b/Telegram/SourceFiles/data/components/sponsored_messages.cpp index f4ec3a3565faf7..84e1ff6c85ec93 100644 --- a/Telegram/SourceFiles/data/components/sponsored_messages.cpp +++ b/Telegram/SourceFiles/data/components/sponsored_messages.cpp @@ -23,15 +23,40 @@ For license and copyright information please follow this link: #include "main/main_session.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 session) @@ -48,10 +73,16 @@ SponsoredMessages::~SponsoredMessages() { void SponsoredMessages::clear() { _lifetime.destroy(); for (const auto &request : base::take(_requests)) { + _session->sender().request(request.second.requestId).cancel(); +#if 0 // mtp _session->api().request(request.second.requestId).cancel(); +#endif } for (const auto &request : base::take(_viewRequests)) { + _session->sender().request(request.second.requestId).cancel(); +#if 0 // mtp _session->api().request(request.second.requestId).cancel(); +#endif } base::take(_data); } @@ -220,6 +251,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) ).done([=](const MTPmessages_sponsoredMessages &result) { @@ -227,6 +266,7 @@ void SponsoredMessages::request(not_null history, Fn done) { if (done) { done(); } +#endif }).fail([=] { _requests.remove(history); }).send(); @@ -234,7 +274,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; @@ -242,9 +285,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; @@ -253,21 +299,31 @@ 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; auto mediaPhoto = (PhotoData*)nullptr; auto mediaDocument = (DocumentData*)nullptr; @@ -389,6 +445,41 @@ void SponsoredMessages::append( entry.preload = std::move(preload); } } +#endif + const auto &sponsor = data.vsponsor().data(); + const auto from = SponsoredFrom{ + .title = qs(data.vtitle()), + .link = qs(sponsor.vurl()), + .buttonText = qs(data.vbutton_text()), + .photoId = sponsor.vphoto() + ? history->session().data().processPhoto(*sponsor.vphoto())->id + : PhotoId(0), + .backgroundEmojiId = uint64(data.vbackground_custom_emoji_id().v), + .colorIndex = uint8(data.vaccent_color_id().v), + .isLinkInternal = !UrlRequiresConfirmation(qs(sponsor.vurl())), + .isRecommended = data.vis_recommended().v, + .canReport = data.vcan_be_reported().v, + }; + Assert(data.vcontent().type() == id_messageText); + 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, + .link = from.link, + .sponsorInfo = std::move(sponsorInfo), + .additionalInfo = std::move(additionalInfo), + }; + list.entries.push_back({ nullptr, {}, std::move(sharedMessage) }); } void SponsoredMessages::clearItems(not_null history) { @@ -436,6 +527,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, @@ -447,6 +548,7 @@ void SponsoredMessages::view(const FullMsgId &fullId) { }).fail([=] { _viewRequests.remove(randomId); }).send(); +#endif } SponsoredMessages::Details SponsoredMessages::lookupDetails( @@ -488,6 +590,14 @@ void SponsoredMessages::clicked( if (!entryPtr) { return; } + const auto id = DeserializeRandomId(entryPtr->sponsored.randomId); + _session->sender().request(TLclickChatSponsoredMessage( + peerToTdbChat(id.peer), + tl_int53(id.msg.bare), + tl_bool(isMedia), + tl_bool(isFullscreen) + )).send(); +#if 0 // mtp const auto randomId = entryPtr->sponsored.randomId; const auto channel = entryPtr->item->history()->peer->asChannel(); Assert(channel != nullptr); @@ -499,14 +609,20 @@ void SponsoredMessages::clicked( channel->inputChannel, MTP_bytes(randomId) )).send(); +#endif } auto SponsoredMessages::createReportCallback(const FullMsgId &fullId) -> Fn)> { +#if 0 // mtp using TLChoose = MTPDchannels_sponsoredMessageReportResultChooseOption; using TLAdsHidden = MTPDchannels_sponsoredMessageReportResultAdsHidden; using TLReported = MTPDchannels_sponsoredMessageReportResultReported; +#endif + using TLChoose = TLDreportChatSponsoredMessageResultOptionRequired; + using TLAdsHidden = TLDreportChatSponsoredMessageResultAdsHidden; + using TLReported = TLDreportChatSponsoredMessageResultOk; using Result = SponsoredReportResult; struct State final { @@ -547,6 +663,49 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId) return; } + const auto id = DeserializeRandomId(entry->sponsored.randomId); + state->requestId = _session->sender().request( + TLreportChatSponsoredMessage( + peerToTdbChat(id.peer), + tl_int53(id.msg.bare), + tl_bytes(optionId)) + ).done([=]( + const TLreportChatSponsoredMessageResult &result, + mtpRequestId requestId) { + if (state->requestId != requestId) { + return; + } + state->requestId = 0; + done(result.match([&](const TLChoose &data) { + const auto t = qs(data.vtitle()); + auto list = Result::Options(); + list.reserve(data.voptions().v.size()); + for (const auto &tl : data.voptions().v) { + list.emplace_back(Result::Option{ + .id = tl.data().vid().v, + .text = qs(tl.data().vtext()), + }); + } + return Result{ .options = std::move(list), .title = t }; + }, [](const TLAdsHidden &data) -> Result { + return { .result = Result::FinalStep::Hidden }; + }, [&](const TLReported &data) -> Result { + erase(); + if (optionId == Result::Id("1")) { // I don't like it. + return { .result = Result::FinalStep::Silence }; + } + return { .result = Result::FinalStep::Reported }; + }, [&](const TLDreportChatSponsoredMessageResultPremiumRequired &) { + return Result{ .result = Result::FinalStep::Premium }; + }, [&](const TLDreportChatSponsoredMessageResultFailed &) { + erase(); + return Result{ .result = Result::FinalStep::Silence }; + })); + }).fail([=](const Error &error) { + state->requestId = 0; + done({ .error = error.message }); + }).send(); +#if 0 // mtp state->requestId = _session->api().request( MTPchannels_ReportSponsoredMessage( channel->inputChannel, @@ -587,6 +746,7 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId) done({ .error = error.type() }); } }).send(); +#endif }; } diff --git a/Telegram/SourceFiles/data/components/sponsored_messages.h b/Telegram/SourceFiles/data/components/sponsored_messages.h index 028861af9ac537..37643a5eba6c1a 100644 --- a/Telegram/SourceFiles/data/components/sponsored_messages.h +++ b/Telegram/SourceFiles/data/components/sponsored_messages.h @@ -14,6 +14,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 @@ -140,11 +145,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/components/top_peers.cpp b/Telegram/SourceFiles/data/components/top_peers.cpp index 1d630722078f23..e2315b3938f9ed 100644 --- a/Telegram/SourceFiles/data/components/top_peers.cpp +++ b/Telegram/SourceFiles/data/components/top_peers.cpp @@ -18,9 +18,15 @@ For license and copyright information please follow this link: #include "storage/serialize_peer.h" #include "storage/storage_account.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_sender.h" + namespace Data { namespace { +using namespace Tdb; + +constexpr auto kTopChatsLimit = 30; constexpr auto kLimit = 64; constexpr auto kRequestTimeLimit = 10 * crl::time(1000); @@ -107,10 +113,16 @@ void TopPeers::remove(not_null peer) { updated(); } +#if 0 // mtp _requestId = _session->api().request(MTPcontacts_ResetTopPeerRating( TypeToCategory(_type), peer->input )).send(); +#endif + _requestId = _session->sender().request(TLremoveTopChat( + tl_topChatCategoryUsers(), + peerToTdbChat(peer->id) + )).send(); } void TopPeers::increment(not_null peer, TimeId date) { @@ -127,6 +139,7 @@ void TopPeers::increment(not_null peer, TimeId date) { i = end(_list) - 1; changed = true; } +#if 0 // mtp const auto &config = peer->session().mtp().config(); const auto decay = config.values().ratingDecay; i->rating += RatingDelta(date, _lastReceivedDate, decay); @@ -138,6 +151,7 @@ void TopPeers::increment(not_null peer, TimeId date) { break; } } +#endif if (changed) { updated(); } else { @@ -169,8 +183,13 @@ void TopPeers::toggleDisabled(bool disabled) { updated(); } + _session->sender().request(TLsetOption( + tl_string("disable_top_chats"), + tl_optionValueBoolean(tl_bool(disabled)) +#if 0 // mtp _session->api().request(MTPcontacts_ToggleTopPeers( MTP_bool(!disabled) +#endif )).done([=] { if (!_disabled) { request(); @@ -183,6 +202,7 @@ void TopPeers::request() { return; } +#if 0 // mtp _requestId = _session->api().request(MTPcontacts_GetTopPeers( MTP_flags(TypeToGetFlags(_type)), MTP_int(0), @@ -228,6 +248,20 @@ void TopPeers::request() { } }, [](const MTPDcontacts_topPeersNotModified &) { }); +#endif + _session->sender().request(TLgetTopChats( + tl_topChatCategoryUsers(), + tl_int32(kTopChatsLimit) + )).done([=](const TLchats &result) { + _lastReceived = crl::now(); + _requestId = 0; + + _disabled = false; + const auto &ids = result.data().vchat_ids().v; + _list = ids | ranges::views::transform([&](const TLint53 &id) { + return TopPeer{ _session->data().peer(peerFromTdbChat(id)) }; + }) | ranges::to_vector; + updated(); }).fail([=] { _lastReceived = crl::now(); _requestId = 0; diff --git a/Telegram/SourceFiles/data/data_bot_app.cpp b/Telegram/SourceFiles/data/data_bot_app.cpp index ff9705cb64080f..b39096401039b9 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 7de4aefe86e2e3..69f8b8ce8aa9ea 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 f49a7d32d01d04..b9be140102c943 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -87,12 +87,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 // #TODO legacy +#if 0 // mtp void ChannelData::setPhoto(const MTPChatPhoto &photo) { photo.match([&](const MTPDchatPhoto & data) { updateUserpic( @@ -152,6 +155,7 @@ bool ChannelData::isUsernameEditable(QString username) const { return _username.isEditable(username); } +#if 0 // mtp void ChannelData::setAccessHash(uint64 accessHash) { access = accessHash; input = MTP_inputPeerChannel( @@ -161,6 +165,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; @@ -246,12 +251,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({ @@ -264,6 +273,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)) { @@ -330,6 +349,7 @@ void ChannelData::setKickedCount(int newKickedCount) { } } +#if 0 // mtp void ChannelData::setPendingRequestsCount( int count, const QVector &recentRequesters) { @@ -339,6 +359,7 @@ void ChannelData::setPendingRequestsCount( return UserId(value); }) | ranges::to_vector); } +#endif void ChannelData::setPendingRequestsCount( int count, @@ -517,6 +538,35 @@ void ChannelData::setViewAsMessagesFlag(bool enabled) { } 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 @@ -525,6 +575,7 @@ void ChannelData::markForbidden() { MTP_long(access), MTP_string(name()), MTPint())); +#endif } bool ChannelData::isGroupAdmin(not_null user) const { @@ -931,6 +982,7 @@ void ChannelData::migrateCall(std::unique_ptr call) { addFlags(Flag::CallActive); } +#if 0 // goodToRemove void ChannelData::setGroupCall( const MTPInputGroupCall &call, TimeId scheduleDate, @@ -959,6 +1011,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) { @@ -992,7 +1071,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); } @@ -1034,11 +1115,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 int ChannelData::levelHint() const { return _levelHint; @@ -1067,6 +1150,7 @@ void ApplyMigration( channel->setMigrateFromChat(chat); } +#if 0 // mtp void ApplyChannelUpdate( not_null channel, const MTPDupdateChatDefaultBannedRights &update) { @@ -1294,53 +1378,51 @@ void ApplyChannelUpdate( // For clearUpTill() call. channel->owner().sendHistoryChangeNotifications(); } +#endif void ApplyChannelUpdate( not_null channel, const TLDsupergroupFullInfo &update) { const auto session = &channel->session(); - //if (channel->isMegagroup()) { // #TODO tdlib - // const auto suggestions = update.vpending_suggestions().value_or_empty(); - // channel->owner().setSuggestToGigagroup( - // channel, - // ranges::contains( - // suggestions, - // "convert_to_gigagroup"_q, - // &MTPstring::v)); - //} - - //channel->setAvailableMinId(update.vavailable_min_id().value_or_empty()); auto canViewAdmins = channel->canViewAdmins(); auto canViewMembers = channel->canViewMembers(); auto canEditStickers = channel->canEditStickers(); - //if (const auto call = update.vcall()) { - // channel->setGroupCall(*call); - //} else { - // channel->clearGroupCall(); - //} - //if (const auto as = update.vgroupcall_default_join_as()) { - // channel->setGroupCallDefaultJoinAs(peerFromMTP(*as)); - //} else { - // channel->setGroupCallDefaultJoinAs(0); - //} - - //channel->setMessagesTTL(update.vttl_period().value_or_empty()); - - // #TODO tdlib - //update.vcan_get_members(); - //update.vcan_get_statistics(); - //update.vcan_set_location(); - //update.vcan_set_sticker_set(); - //update.vcan_set_username(); - //update.vis_all_history_available(); - //channel->setFullFlags(update.vflags().v); + //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); - } else { - channel->clearPhoto(); } if (const auto from = update.vupgraded_from_basic_group_id().v) { channel->addFlags(ChannelDataFlag::Megagroup); @@ -1355,81 +1437,58 @@ void ApplyChannelUpdate( channel->setSlowmodeSeconds(update.vslow_mode_delay().v); if (const auto in = update.vslow_mode_delay_expires_in().v; in > 0.) { - // #TODO tdlib better slowmode management + // later better slowmode management channel->growSlowmodeLastMessage( - base::unixtime::now() + int(std::round(in * 1000)) - channel->slowmodeSeconds()); - } - - // #TODO tdlib - //if (const auto invite = update.vinvite_link()) { - // channel->session().api().inviteLinks().setMyPermanent( - // channel, - // *invite); - //} else { - // channel->session().api().inviteLinks().clearMyPermanent(channel); - //} - //if (const auto location = update.vlocation()) { - // channel->setLocation(*location); - //} else { - // channel->setLocation(MTP_channelLocationEmpty()); - //} + 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 (const auto history = channel->owner().historyLoaded(channel)) { - // if (const auto available = update.vavailable_min_id()) { - // history->clearUpTill(available->v); - // } - // const auto folderId = update.vfolder_id().value_or_empty(); - // const auto folder = folderId - // ? channel->owner().folderLoaded(folderId) - // : nullptr; - // auto &histories = channel->owner().histories(); - // if (folder && history->folder() != folder) { - // // If history folder is unknown or not synced, request both. - // histories.requestDialogEntry(history); - // histories.requestDialogEntry(folder); - // } else if (!history->folderKnown() - // || channel->pts() != update.vpts().v) { - // histories.requestDialogEntry(history); - // } else { - // history->applyDialogFields( - // history->folder(), - // update.vunread_count().v, - // update.vread_inbox_max_id().v, - // update.vread_outbox_max_id().v); - // } - //} - //if (const auto pinned = update.vpinned_msg_id()) { - // SetTopPinnedMessageId(channel, pinned->v); - //} if (channel->isMegagroup()) { - //update.vbot_commands(); // #TODO tdlib - //if (channel->mgInfo->updateBotCommands(update.vbot_info())) { - // channel->owner().botCommandsChanged(channel); - //} - - //update.vsticker_set_id();// #TODO tdlib - //const auto stickerSet = update.vstickerset(); - //const auto set = stickerSet ? &stickerSet->c_stickerSet() : nullptr; - //const auto newSetId = (set ? set->vid().v : 0); - //const auto oldSetId = (channel->mgInfo->stickerSet.type() == mtpc_inputStickerSetID) - // ? channel->mgInfo->stickerSet.c_inputStickerSetID().vid().v - // : 0; - //const auto stickersChanged = (canEditStickers != channel->canEditStickers()) - // || (oldSetId != newSetId); - //if (oldSetId != newSetId) { - // channel->mgInfo->stickerSet = set - // ? MTP_inputStickerSetID(set->vid(), set->vaccess_hash()) - // : MTP_inputStickerSetEmpty(); - //} - //if (stickersChanged) { - // session->changes().peerUpdated(channel, UpdateFlag::StickersSet); - //} + 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); + } + } + { + const auto newSetId = uint64(update.vcustom_emoji_sticker_set_id().v); + const auto oldSetId = channel->mgInfo->emojiSet.id; + if (oldSetId != newSetId) { + channel->mgInfo->emojiSet = StickerSetIdentifier{ + .id = newSetId, + }; + session->changes().peerUpdated(channel, UpdateFlag::EmojiSet); + } + } } channel->fullUpdated(); @@ -1438,10 +1497,6 @@ void ApplyChannelUpdate( session->changes().peerUpdated(channel, UpdateFlag::Rights); } - //session->api().applyNotifySettings( - // MTP_inputNotifyPeer(channel->input), - // update.vnotify_settings()); - // For clearUpTill() call. channel->owner().sendHistoryChangeNotifications(); } diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 5deaf8f818db8a..f65f4ef693717d 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -16,6 +16,7 @@ For license and copyright information please follow this link: namespace Tdb { class TLDsupergroupFullInfo; +class TLchatLocation; } // namespace Tdb struct ChannelLocation { @@ -73,6 +74,9 @@ enum class ChannelDataFlag : uint64 { PaidMediaAllowed = (1ULL << 33), CanViewCreditsRevenue = (1ULL << 34), SignatureProfiles = (1ULL << 35), + + CanHideMembers = (1ULL << 54), + CanEnableAntiSpam = (1ULL << 55), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; @@ -174,10 +178,10 @@ class ChannelData final : public PeerData { void setUsername(const QString &username); void setUsernames(const Data::Usernames &newUsernames); -#if 0 // #TODO legacy +#if 0 // mtp void setPhoto(const MTPChatPhoto &photo); -#endif void setAccessHash(uint64 accessHash); +#endif void setPhoto(const Tdb::TLchatPhotoInfo &photo); @@ -225,9 +229,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); @@ -391,13 +397,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); } @@ -441,6 +451,7 @@ class ChannelData final : public PeerData { [[nodiscard]] bool ptsWaitingForShortPoll() const { return _ptsWaiter.waitingForShortPoll(); } +#endif [[nodiscard]] MsgId availableMinId() const { return _availableMinId; @@ -471,10 +482,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; @@ -490,7 +507,9 @@ class ChannelData final : public PeerData { return mgInfo ? mgInfo->forum() : nullptr; } +#if 0 // mtp void processTopics(const MTPVector &topics); +#endif [[nodiscard]] int levelHint() const; void updateLevelHint(int levelHint); @@ -501,15 +520,17 @@ class ChannelData final : public PeerData { // 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 { @@ -526,7 +547,9 @@ class ChannelData final : public PeerData { Flags _flags = ChannelDataFlags(Flag::Forbidden); +#if 0 // mtp PtsWaiter _ptsWaiter; +#endif Data::UsernamesInfo _username; @@ -563,6 +586,7 @@ void ApplyMigration( not_null chat, not_null channel); +#if 0 // mtp void ApplyChannelUpdate( not_null channel, const MTPDupdateChatDefaultBannedRights &update); @@ -570,6 +594,7 @@ void ApplyChannelUpdate( void ApplyChannelUpdate( not_null channel, const MTPDchannelFull &update); +#endif void ApplyChannelUpdate( not_null channel, diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 224a6e21321821..7f55e0c0772539 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -30,7 +30,10 @@ using UpdateFlag = Data::PeerUpdate::Flag; 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) { @@ -41,7 +44,7 @@ ChatData::ChatData(not_null owner, PeerId id) }, _lifetime); } -#if 0 // #TODO legacy +#if 0 // mtp void ChatData::setPhoto(const MTPChatPhoto &photo) { photo.match([&](const MTPDchatPhoto &data) { updateUserpic( @@ -217,6 +220,7 @@ void ChatData::setMigrateToChannel(ChannelData *channel) { } } +#if 0 // goodToRemove void ChatData::setGroupCall( const MTPInputGroupCall &call, TimeId scheduleDate, @@ -248,6 +252,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) { @@ -276,6 +307,7 @@ void ChatData::setBotCommands(const std::vector &list) { } } +#if 0 // mtp void ChatData::setPendingRequestsCount( int count, const QVector &recentRequesters) { @@ -285,6 +317,7 @@ void ChatData::setPendingRequestsCount( return UserId(value); }) | ranges::to_vector); } +#endif void ChatData::setPendingRequestsCount( int count, @@ -308,7 +341,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); } @@ -320,6 +355,7 @@ const Data::AllowedReactions &ChatData::allowedReactions() const { namespace Data { +#if 0 // mtp void ApplyChatUpdate( not_null chat, const MTPDupdateChatParticipants &update) { @@ -589,55 +625,102 @@ void ApplyChatUpdate( UpdateFlag::Members | UpdateFlag::Admins); }); } +#endif void ApplyChatUpdate( not_null chat, const TLDbasicGroupFullInfo &update) { - //update.vmembers(); // #TODO tdlib - //ApplyChatUpdate(chat, update.vparticipants()); + ApplyChatUpdate(chat, update.vmembers().v); chat->creator = UserId(update.vcreator_user_id()); - //if (const auto call = update.vcall()) { - // chat->setGroupCall(*call); - //} else { - // chat->clearGroupCall(); - //} - //if (const auto as = update.vgroupcall_default_join_as()) { - // chat->setGroupCallDefaultJoinAs(peerFromMTP(*as)); - //} else { - // chat->setGroupCallDefaultJoinAs(0); - //} - - //chat->setMessagesTTL(update.vttl_period().value_or_empty()); - - //update.vbot_commands(); // #TODO tdlib - //if (const auto info = update.vbot_info()) { - // chat->setBotCommands(*info); - //} else { - // chat->setBotCommands(MTP_vector()); - //} - - //chat->setFullFlags(update.vflags().v); + { + 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->clearPhoto(); - } - //if (const auto invite = update.vinvite_link()) { // #TODO tdlib - // chat->session().api().inviteLinks().setMyPermanent(chat, *invite); - //} else { - // chat->session().api().inviteLinks().clearMyPermanent(chat); - //} - //if (const auto pinned = update.vpinned_msg_id()) { - // SetTopPinnedMessageId(chat, pinned->v); - //} - //chat->checkFolder(update.vfolder_id().value_or_empty()); + chat->session().api().inviteLinks().clearMyPermanent(chat); + } chat->fullUpdated(); chat->setAbout(update.vdescription().v); +} - //chat->session().api().applyNotifySettings( - // MTP_inputNotifyPeer(chat->input), - // update.vnotify_settings()); +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 4b9d639abe6e03..63f5c1922bf155 100644 --- a/Telegram/SourceFiles/data/data_chat.h +++ b/Telegram/SourceFiles/data/data_chat.h @@ -15,6 +15,7 @@ enum class ChatAdminRight; namespace Tdb { class TLDbasicGroupFullInfo; +class TLchatMember; } // namespace Tdb enum class ChatDataFlag { @@ -27,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; @@ -39,7 +43,7 @@ class ChatData final : public PeerData { ChatData(not_null owner, PeerId id); void setName(const QString &newName); -#if 0 // #TODO legacy +#if 0 // mtp void setPhoto(const MTPChatPhoto &photo); #endif @@ -145,10 +149,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; @@ -164,9 +174,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); @@ -174,12 +186,14 @@ 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 // #TODO legacy? +#if 1 // mtp? TimeId date = 0; #endif @@ -215,6 +229,7 @@ class ChatData final : public PeerData { namespace Data { +#if 0 // mtp void ApplyChatUpdate( not_null chat, const MTPDupdateChatParticipants &update); @@ -236,9 +251,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 7a6f63a743c29e..301f1065287358 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -24,9 +24,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; @@ -58,6 +64,7 @@ ChatFilter::ChatFilter( , _flags(flags) { } +#if 0 // mtp ChatFilter ChatFilter::FromTL( const MTPDialogFilter &data, not_null owner) { @@ -155,6 +162,7 @@ ChatFilter ChatFilter::FromTL( {}); }); } +#endif ChatFilter ChatFilter::withId(FilterId id) const { auto result = *this; @@ -182,6 +190,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(); @@ -236,6 +245,111 @@ 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(), + data.vcolor_id().v, + 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.vcolor_id().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_int32(_colorIndex.value_or(0)), + 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; @@ -278,6 +392,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()) { @@ -324,7 +453,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; @@ -350,6 +481,7 @@ void ChatFilters::clear() { _list.clear(); } +#if 0 // mtp void ChatFilters::setPreloaded(const QVector &result) { _loadRequestId = -1; received(result); @@ -446,6 +578,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: " @@ -455,12 +591,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) { @@ -490,6 +634,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)), @@ -503,6 +653,7 @@ void ChatFilters::edit( }).fail([=](const MTP::Error &error) { //done({ .id = id }); }).send(); +#endif } } @@ -513,12 +664,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(); } } @@ -535,6 +693,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( @@ -549,8 +719,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; @@ -581,6 +769,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); @@ -592,6 +781,7 @@ void ChatFilters::moveAllToFront() { _list.insert(begin(_list), ChatFilter()); } +#if 0 // mtp void ChatFilters::applyRemove(int position) { Expects(position >= 0 && position < _list.size()); @@ -732,10 +922,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; } @@ -753,8 +965,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)) { @@ -763,6 +977,7 @@ bool ChatFilters::archiveNeeded() const { } return false; } +#endif const std::vector &ChatFilters::list() const { return _list; @@ -802,6 +1017,7 @@ rpl::producer ChatFilters::isChatlistChanged() const { return _isChatlistChanged.events(); } +#if 0 // mtp bool ChatFilters::loadNextExceptions(bool chatsListLoaded) { if (_exceptionsLoadRequestId) { return true; @@ -851,11 +1067,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() { @@ -866,6 +1091,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) { @@ -882,6 +1124,7 @@ void ChatFilters::requestSuggested() { }; }); }) | ranges::to_vector; +#endif _suggestedUpdated.fire({}); }).fail([=] { @@ -945,16 +1188,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(); @@ -983,6 +1235,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)) @@ -995,6 +1248,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 e123ab2c156848..dc2ce066dc89f1 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 { @@ -64,10 +71,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; @@ -128,6 +148,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(); @@ -135,6 +157,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; @@ -145,21 +168,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; @@ -167,9 +197,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, @@ -193,12 +228,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 38fa1a27e3b193..f51a5f464e7c53 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -18,6 +18,10 @@ For license and copyright information please follow this link: #include "main/main_session.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( @@ -51,6 +55,152 @@ 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_link_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_create_topics(), Flag::CreateTopics) + | 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 { @@ -163,6 +313,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 8dd7cd682ffa96..e1ee40c8d98d37 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 5ac1c2052cd52b..051b9c9d3988d2 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 1ac4ceff1940da..d5ccbec0629d46 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 5456ef2807afa0..ff986bbee376c6 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -40,12 +40,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; constexpr auto kDefaultWebmEmojiSize = 100; @@ -55,7 +64,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(); @@ -77,6 +88,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, @@ -85,6 +97,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 @@ -312,6 +364,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; } @@ -320,6 +699,7 @@ Main::Session &DocumentData::session() const { return _owner->session(); } +#if 0 // mtp void DocumentData::setattributes( const QVector &attributes) { _duration = -1; @@ -510,6 +890,7 @@ void DocumentData::setattributes( setMaybeSupportsStreaming(true); } } +#endif void DocumentData::validateLottieSticker() { if (type == FileDocument @@ -594,6 +975,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); } @@ -788,6 +1315,11 @@ PhotoData *DocumentData::goodThumbnailPhoto() const { } Storage::Cache::Key DocumentData::bigFileBaseCacheKey() const { + if (_tdbFile.fileId) { + return TdbFileLocation::BigFileBaseCacheKey( + _tdbFile.hash, + kTdbLocationTypeDocument); + } return hasRemoteLocation() ? StorageFileLocation( _dc, @@ -1044,6 +1576,7 @@ void DocumentData::save( fromCloud, autoLoading, cacheTag()); +#if 0 // mtp } else if (hasWebLocation()) { _loader = std::make_unique( &session(), @@ -1053,6 +1586,7 @@ void DocumentData::save( fromCloud, autoLoading, cacheTag()); +#endif } else if (!_access && !_url.isEmpty()) { _loader = std::make_unique( &session(), @@ -1061,6 +1595,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(), @@ -1081,6 +1628,7 @@ void DocumentData::save( fromCloud, autoLoading, cacheTag()); +#endif } handleLoaderUpdates(); } @@ -1385,6 +1933,9 @@ const RoundData *DocumentData::round() const { } bool DocumentData::hasRemoteLocation() const { + if (_tdbFile.fileId) { + return true; + } return (_dc != 0 && _access != 0); } @@ -1425,6 +1976,7 @@ int DocumentData::videoPreloadPrefix() const { return _videoPreloadPrefix; } +#if 0 // mtp StorageFileLocation DocumentData::videoPreloadLocation() const { return hasRemoteLocation() ? StorageFileLocation( @@ -1437,6 +1989,7 @@ StorageFileLocation DocumentData::videoPreloadLocation() const { MTP_string())) : StorageFileLocation(); } +#endif auto DocumentData::createStreamingLoader( Data::FileOrigin origin, @@ -1456,6 +2009,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(), @@ -1470,6 +2034,7 @@ auto DocumentData::createStreamingLoader( size, origin) : nullptr; +#endif } bool DocumentData::hasWebLocation() const { @@ -1484,6 +2049,7 @@ bool DocumentData::isNull() const { && _location.isEmpty(); } +#if 0 // mtp MTPInputDocument DocumentData::mtpInput() const { if (_access) { return MTP_inputDocument( @@ -1503,6 +2069,7 @@ void DocumentData::refreshFileReference(const QByteArray &value) { _thumbnail.location.refreshFileReference(value); _videoThumbnail.location.refreshFileReference(value); } +#endif QString DocumentData::filename() const { return _filename; @@ -1687,6 +2254,7 @@ void DocumentData::recountIsImage() { } } +#if 0 // mtp void DocumentData::setRemoteLocation( int32 dc, uint64 access, @@ -1711,6 +2279,7 @@ void DocumentData::setRemoteLocation( } } } +#endif void DocumentData::setStoryMedia(bool value) { if (value) { @@ -1733,6 +2302,7 @@ void DocumentData::setWebLocation(const WebFileLocation &location) { _urlLocation = location; } +#if 0 // mtp void DocumentData::collectLocalData(not_null local) { if (local == this) { return; @@ -1749,3 +2319,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 47237b3c4a396a..146f4a6b5d8019 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -13,6 +13,22 @@ For license and copyright information please follow this link: #include "data/data_cloud_file.h" #include "core/file_location.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; @@ -37,10 +53,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 { @@ -79,6 +126,8 @@ struct StickerData : public DocumentAdditionalData { StickerSetIdentifier set; StickerType type = StickerType::Webp; Data::StickersType setType = Data::StickersType(); + + Fn outlineGenerator; }; struct SongData : public DocumentAdditionalData { @@ -94,6 +143,11 @@ struct VoiceData : public DocumentAdditionalData { using RoundData = VoiceData; +enum class SimpleDocumentType { + BotAttachSvgIcon, + TgsSticker, +}; + namespace Serialize { class Document; } // namespace Serialize; @@ -103,11 +157,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(); @@ -239,15 +322,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); @@ -257,6 +343,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]] Core::NameType nameType() const; @@ -280,7 +367,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; @@ -334,7 +423,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(); @@ -351,6 +442,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; @@ -363,6 +460,9 @@ class DocumentData final { QString _mimeString; WebFileLocation _urlLocation; + // TDLib location type. + TdbFileLocation _tdbFile; + QByteArray _inlineThumbnailBytes; Data::CloudFile _thumbnail; Data::CloudFile _videoThumbnail; @@ -397,3 +497,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 e61030302b0192..8396a6bc9f1d8a 100644 --- a/Telegram/SourceFiles/data/data_document_media.cpp +++ b/Telegram/SourceFiles/data/data_document_media.cpp @@ -26,6 +26,8 @@ For license and copyright information please follow this link: #include "storage/file_download.h" #include "ui/chat/attach/attach_prepare.h" +#include "tdb/tdb_tl_scheme.h" + #include #include @@ -192,7 +194,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 { @@ -204,12 +206,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 +331,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 +349,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 9dfb3588a4f1f5..a6951a50eb3d3c 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 c40085968a3ffe..c87c1b3e53349b 100644 --- a/Telegram/SourceFiles/data/data_download_manager.cpp +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -37,9 +37,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; @@ -57,6 +62,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(); @@ -308,8 +318,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, @@ -634,12 +648,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;) { @@ -650,6 +669,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(); @@ -658,6 +678,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; @@ -673,6 +695,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( @@ -695,6 +727,7 @@ void DownloadManager::resolve( }).fail(requestFinished).send(); } } +#endif data.resolveSentRequests += prepared.size(); check(); } @@ -714,10 +747,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{ @@ -753,6 +792,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 @@ -769,6 +817,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 1bd7135e864076..4ab604cbbc7589 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -20,7 +20,51 @@ 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 quote = data.vquote()) { + result.quote = Api::FormattedTextFromTdb(quote->data().vtext()); + result.quoteOffset = quote->data().vposition().v; + } + return result; + }, [&](const TLDinputMessageReplyToExternalMessage &data) { + auto result = FullReplyTo{ + .messageId = { + peerFromTdbChat(data.vchat_id()), + data.vmessage_id().v, + }, + }; + if (const auto quote = data.vquote()) { + result.quote = Api::FormattedTextFromTdb(quote->data().vtext()); + result.quoteOffset = quote->data().vposition().v; + } + 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(); @@ -66,6 +110,7 @@ Draft::Draft( , webpage(webpage) { } +#if 0 // goodToRemove void ApplyPeerCloudDraft( not_null session, PeerId peerId, @@ -114,6 +159,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, @@ -157,4 +244,16 @@ void SetChatLinkDraft(not_null peer, TextWithEntities draft) { Data::EntryUpdate::Flag::LocalDraftSet); } +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 1330995ed02c07..1eda18253d61d5 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 e00dd8910bc943..3f2a092a8ef9f5 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.cpp +++ b/Telegram/SourceFiles/data/data_emoji_statuses.cpp @@ -19,13 +19,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; @@ -41,12 +47,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(); @@ -119,6 +140,7 @@ rpl::producer<> EmojiStatuses::channelDefaultUpdates() const { return _channelDefaultUpdated.events(); } +#if 0 // mtp void EmojiStatuses::registerAutomaticClear( not_null peer, TimeId until) { @@ -142,6 +164,7 @@ void EmojiStatuses::registerAutomaticClear( } } } +#endif auto EmojiStatuses::emojiGroupsValue() const -> rpl::producer { const_cast(this)->requestEmojiGroups(); @@ -167,50 +190,86 @@ 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::requestStickerGroups() { requestGroups( &_stickerGroups, + tl_emojiCategoryTypeRegularStickers()); +#if 0 // mtp MTPmessages_GetEmojiStickerGroups(MTP_int(_stickerGroups.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(); + const auto icon = owner->processDocument(data.vicon()); + const auto greeting = data.vis_greeting().v; + data.vsource().match([&](const TLDemojiCategorySourcePremium &) { + result.push_back({ + .iconId = QString::number(icon->id), +#if 0 // mtp group.match([&](const MTPDemojiGroupPremium &data) { result.push_back({ .iconId = QString::number(data.vicon_emoji_id().v), +#endif .type = Ui::EmojiGroupType::Premium, }); }, [&](const auto &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; result.push_back({ +#if 0 // mtp .iconId = QString::number(data.vicon_emoji_id().v), +#endif + .iconId = QString::number(icon->id), .emoticons = std::move(emoticons), +#if 0 // mtp .type = (MTPDemojiGroupGreeting::Is() +#endif + .type = (greeting ? Ui::EmojiGroupType::Greeting : Ui::EmojiGroupType::Normal), }); @@ -226,20 +285,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(); @@ -275,11 +342,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( @@ -290,6 +359,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; @@ -300,6 +376,7 @@ void EmojiStatuses::requestDefault() { if (_defaultRequestId) { return; } +#if 0 // mtp auto &api = _owner->session().api(); _defaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses( MTP_long(_defaultHash) @@ -309,6 +386,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; @@ -319,6 +402,7 @@ void EmojiStatuses::requestColored() { if (_coloredRequestId) { return; } +#if 0 // mtp auto &api = _owner->session().api(); _coloredRequestId = api.request(MTPmessages_GetStickerSet( MTP_inputStickerSetEmojiDefaultStatuses(), @@ -330,6 +414,12 @@ 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(); @@ -339,6 +429,7 @@ void EmojiStatuses::requestChannelDefault() { if (_channelDefaultRequestId) { return; } +#if 0 // mtp auto &api = _owner->session().api(); _channelDefaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses( MTP_long(_channelDefaultHash) @@ -348,6 +439,11 @@ void EmojiStatuses::requestChannelDefault() { updateChannelDefault(data); }, [&](const MTPDaccount_emojiStatusesNotModified &) { }); +#endif + _owner->session().sender().request(TLgetDefaultChatEmojiStatuses( + )).done([=](const TLemojiStatuses &result) { + _channelDefaultRequestId = 0; + updateChannelDefault(result.data()); }).fail([=] { _channelDefaultRequestId = 0; _channelDefaultHash = 0; @@ -358,6 +454,7 @@ void EmojiStatuses::requestChannelColored() { if (_channelColoredRequestId) { return; } +#if 0 // mtp auto &api = _owner->session().api(); _channelColoredRequestId = api.request(MTPmessages_GetStickerSet( MTP_inputStickerSetEmojiChannelDefaultStatuses(), @@ -369,11 +466,17 @@ void EmojiStatuses::requestChannelColored() { }, [](const MTPDmessages_stickerSetNotModified &) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); +#endif + _owner->session().sender().request(TLgetThemedChatEmojiStatuses( + )).done([=](const TLemojiStatuses &result) { + _channelColoredRequestId = 0; + updateChannelColored(result.data()); }).fail([=] { _channelColoredRequestId = 0; }).send(); } +#if 0 // mtp void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) { _recentHash = data.vhash().v; _recent = ListFromMTP(data); @@ -413,6 +516,32 @@ void EmojiStatuses::updateChannelColored( } _channelColoredUpdated.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::updateChannelDefault(const TLDemojiStatuses &data) { + _channelDefault = ListFromTL(data); + _channelDefaultUpdated.fire({}); +} + +void EmojiStatuses::updateChannelColored(const TLDemojiStatuses &data) { + _channelColored = ListFromTL(data); + _channelColoredUpdated.fire({}); +} void EmojiStatuses::set(DocumentId id, TimeId until) { set(_owner->session().user(), id, until); @@ -422,7 +551,10 @@ void EmojiStatuses::set( not_null peer, DocumentId id, TimeId until) { +#if 0 // mtp auto &api = _owner->session().api(); +#endif + auto &api = _owner->session().sender(); auto &requestId = _sentRequests[peer]; if (requestId) { api.request(base::take(requestId)).cancel(); @@ -437,6 +569,7 @@ void EmojiStatuses::set( _sentRequests.remove(peer); }).send(); }; +#if 0 // mtp const auto status = !id ? MTP_emojiStatusEmpty() : !until @@ -447,6 +580,18 @@ void EmojiStatuses::set( } else if (const auto channel = peer->asChannel()) { send(MTPchannels_UpdateEmojiStatus(channel->inputChannel, status)); } +#endif + if (peer->isSelf()) { + send(TLsetEmojiStatus(id + ? tl_emojiStatus(tl_int64(id), tl_int32(until)) + : std::optional())); + } else { + send(TLsetChatEmojiStatus( + peerToTdbChat(peer->id), + (id + ? tl_emojiStatus(tl_int64(id), tl_int32(until)) + : std::optional()))); + } } EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status) { diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.h b/Telegram/SourceFiles/data/data_emoji_statuses.h index 0a0b75ba222735..d525f5e19ae973 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 @@ -55,7 +59,9 @@ class EmojiStatuses final { void set(DocumentId id, TimeId until = 0); void set(not_null peer, DocumentId id, TimeId until = 0); +#if 0 // mtp void registerAutomaticClear(not_null peer, TimeId until); +#endif using Groups = std::vector; [[nodiscard]] rpl::producer emojiGroupsValue() const; @@ -80,6 +86,7 @@ class EmojiStatuses final { void requestChannelDefault(); void requestChannelColored(); +#if 0 // mtp void updateRecent(const MTPDaccount_emojiStatuses &data); void updateDefault(const MTPDaccount_emojiStatuses &data); void updateColored(const MTPDmessages_stickerSet &data); @@ -88,6 +95,13 @@ class EmojiStatuses final { 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); + void updateChannelDefault(const Tdb::TLDemojiStatuses &data); + void updateChannelColored(const Tdb::TLDemojiStatuses &data); template void requestGroups(not_null type, Request &&request); @@ -121,8 +135,10 @@ class EmojiStatuses final { base::flat_map, mtpRequestId> _sentRequests; +#if 0 // mtp base::flat_map, TimeId> _clearing; base::Timer _clearingTimer; +#endif GroupsType _emojiGroups; GroupsType _statusGroups; diff --git a/Telegram/SourceFiles/data/data_file_origin.cpp b/Telegram/SourceFiles/data/data_file_origin.cpp index 76838edc37a2d7..f74b66ead4f897 100644 --- a/Telegram/SourceFiles/data/data_file_origin.cpp +++ b/Telegram/SourceFiles/data/data_file_origin.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #include "data/data_file_origin.h" +#if 0 // mtp namespace Data { namespace { @@ -256,3 +257,4 @@ UpdatedFileReferences GetFileReferences(const MTPMessageMedia &data) { } } // namespace Data +#endif diff --git a/Telegram/SourceFiles/data/data_file_origin.h b/Telegram/SourceFiles/data/data_file_origin.h index 33e6a3f0d72bdd..61a9de6edc2a7c 100644 --- a/Telegram/SourceFiles/data/data_file_origin.h +++ b/Telegram/SourceFiles/data/data_file_origin.h @@ -205,6 +205,7 @@ struct UpdatedFileReferences { std::map data; }; +#if 0 // mtp UpdatedFileReferences GetFileReferences(const MTPmessages_Messages &data); UpdatedFileReferences GetFileReferences(const MTPphotos_Photos &data); UpdatedFileReferences GetFileReferences(const MTPusers_UserFull &data); @@ -224,5 +225,6 @@ UpdatedFileReferences GetFileReferences(const MTPstories_Stories &data); // Admin Log Event. UpdatedFileReferences GetFileReferences(const MTPMessageMedia &data); +#endif } // namespace Data diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index e6599a5eea68b9..a9452bd1e7f56d 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(); } @@ -343,6 +349,7 @@ int Folder::storiesUnreadCount() const { return _storiesUnreadCount; } +#if 0 // mtp TimeId Folder::adjustedChatListTimeId() const { return chatListTimeId(); } @@ -361,7 +368,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) { @@ -369,14 +389,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 7f6347e935a9de..5023c747dc480b 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 361135d8855cca..9659b9088fe62f 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 732ef80097e1b4..4b2971821ae503 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 c47c8e669cb6cc..60c3798abe93ef 100644 --- a/Telegram/SourceFiles/data/data_forum_icons.cpp +++ b/Telegram/SourceFiles/data/data_forum_icons.cpp @@ -14,7 +14,15 @@ 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; + +} // namespace ForumIcons::ForumIcons(not_null owner) : _owner(owner) @@ -49,6 +57,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(), @@ -63,10 +80,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 54f03a366bd89c..b252bb2414d745 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 7e7998897cc5fc..39386bd935c0b1 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; @@ -377,6 +381,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); @@ -419,6 +489,7 @@ void ForumTopic::applyTopic(const MTPDforumTopic &data) { unreadReactions().setCount(data.vunread_reactions_count().v); } } +#endif void ForumTopic::applyCreator(PeerId creatorId) { if (_creatorId != creatorId) { @@ -459,6 +530,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( @@ -478,6 +556,7 @@ void ForumTopic::setClosedAndSave(bool closed) { } } }).send(); +#endif } bool ForumTopic::hidden() const { @@ -707,6 +786,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)) { @@ -721,11 +801,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 06423e47505b25..4c956e9fd86954 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; @@ -77,7 +82,10 @@ struct TopicIconDescriptor { 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(); @@ -121,12 +129,20 @@ class ForumTopic final : public Thread { void readTillEnd(); void requestChatListMessage(); + 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 6861e69af71fcb..29308a5d20d988 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 b71fdd0f2ceee4..8e6c705b2dff53 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 72b4d387fc78d0..742eddf3dc3ece 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 6dbaf8debeea3a..95411e9238d960 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 0f428913acb727..9f8f85aadd0b60 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -28,13 +28,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(); @@ -767,6 +916,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( @@ -792,6 +964,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; @@ -816,9 +995,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; @@ -887,8 +1068,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, @@ -1161,6 +1360,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 dd2900a41cd4b8..e227c41d118e24 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 457bb641136a9d..4635481d18e196 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); @@ -31,6 +34,11 @@ LocationPoint::LocationPoint(float64 lat, float64 lon, IgnoreAccessHash) , _lon(lon) { } +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 114d21c08edb98..ab688e663f642c 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; @@ -21,6 +25,8 @@ class LocationPoint { }; LocationPoint(float64 lat, float64 lon, IgnoreAccessHash); + 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_preload.cpp b/Telegram/SourceFiles/data/data_media_preload.cpp index 8fb938067362d8..d75384f8bd07c8 100644 --- a/Telegram/SourceFiles/data/data_media_preload.cpp +++ b/Telegram/SourceFiles/data/data_media_preload.cpp @@ -18,9 +18,14 @@ For license and copyright information please follow this link: #include "media/streaming/media_streaming_reader.h" #include "storage/file_download.h" // kMaxFileInMemory. +#include "tdb/tdb_account.h" +#include "tdb/tdb_tl_scheme.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kDefaultPreloadPrefix = 4 * 1024 * 1024; [[nodiscard]] int64 ChoosePreloadPrefix(not_null video) { @@ -79,6 +84,7 @@ void PhotoPreload::start(FileOrigin origin) { } } +#if 0 // mtp VideoPreload::VideoPreload( not_null video, FileOrigin origin, @@ -205,5 +211,163 @@ bool VideoPreload::setWebFileSizeHook(int64 size) { done({}); return false; } +#endif + +[[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; +} + +VideoPreload::VideoPreload( + not_null video, + Fn done) +: MediaPreload(std::move(done)) +, _video(video) +, _session(&video->session()) +, _sender(&_session->sender()) +, _full(video->size) +, _fileId(video->tdbFileId()) +, _prefix(ChoosePreloadPrefix(video)) { + Expects(_prefix > 0 && _prefix <= _full); + + if (Can(video)) { + check(); + } else { + callDone(); + } +} + +bool VideoPreload::Can(not_null video) { + return video->canBeStreamed(nullptr) + && (video->tdbFileId() != 0) + && video->bigFileBaseCacheKey(); +} + +void VideoPreload::check() { + const auto key = _video->bigFileBaseCacheKey(); + const auto weak = base::make_weak(static_cast(this)); + _video->owner().cacheBigFile().get(key, [weak]( + const QByteArray &result) { + if (!result.isEmpty()) { + crl::on_main([weak] { + if (const auto strong = weak.get()) { + static_cast(strong)->callDone(); + } + }); + } else { + crl::on_main([weak] { + if (const auto strong = weak.get()) { + static_cast(strong)->load(); + } + }); + } + }); +} + +void VideoPreload::load() { + if (!Can(_video)) { + callDone(); + return; + } + _sender.request(TLgetFile( + tl_int32(_fileId) + )).done([=](const TLDfile &data) { + startWith(data.vlocal().data()); + }).fail([=] { + done({}); + }).send(); +} + +void VideoPreload::done(QByteArray result) { + const auto key = _video->bigFileBaseCacheKey(); + if (!result.isEmpty() && key) { + Assert(result.size() < Storage::kMaxFileInMemory); + _video->owner().cacheBigFile().putIfEmpty( + key, + Storage::Cache::Database::TaggedValue(std::move(result), 0)); + } + callDone(); +} + +void VideoPreload::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 VideoPreload::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 VideoPreload::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({}); + } +} + +VideoPreload::~VideoPreload() { +} } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_preload.h b/Telegram/SourceFiles/data/data_media_preload.h index 30a47f4fad99cd..09eb74eff6a747 100644 --- a/Telegram/SourceFiles/data/data_media_preload.h +++ b/Telegram/SourceFiles/data/data_media_preload.h @@ -9,6 +9,16 @@ For license and copyright information please follow this link: #include "storage/download_manager_mtproto.h" +#include "tdb/tdb_sender.h" + +namespace Tdb { +class TLDlocalFile; +} // namespace Tdb + +namespace Main { +class Session; +} // namespace Main + namespace Data { class PhotoMedia; @@ -47,6 +57,7 @@ class PhotoPreload final : public MediaPreload { }; +#if 0 // mtp class VideoPreload final : public MediaPreload , private Storage::DownloadMtprotoTask { @@ -78,6 +89,38 @@ class VideoPreload final bool _finished = false; bool _failed = false; +}; +#endif + +class VideoPreload final + : public MediaPreload + , public base::has_weak_ptr { +public: + [[nodiscard]] static bool Can(not_null video); + + VideoPreload( + not_null video, + Fn done); + ~VideoPreload(); + +private: + void check(); + void load(); + void done(QByteArray result); + + void startWith(const Tdb::TLDlocalFile &data); + bool continueWith(const Tdb::TLDlocalFile &data); + void finishWith(const Tdb::TLDlocalFile &data); + + const not_null _video; + const not_null _session; + Tdb::Sender _sender; + int64 _full = 0; + FileId _fileId = 0; + int64 _prefix = 0; + + rpl::lifetime _downloadLifetime; + }; } // namespace Data diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 81dc056e912e1c..207fa4c0643095 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -76,6 +76,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 { @@ -83,6 +86,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; @@ -287,8 +292,12 @@ template bool UpdateExtendedMedia( std::unique_ptr &media, not_null item, + const TLpaidMedia &extended) { + return extended.match([&](const TLDpaidMediaPreview &data) { +#if 0 // mtp const MTPMessageExtendedMedia &extended) { return extended.match([&](const MTPDmessageExtendedMediaPreview &data) { +#endif auto photo = (PhotoData*)nullptr; if (!media) { const auto id = base::RandomValue(); @@ -304,24 +313,37 @@ bool UpdateExtendedMedia( auto size = QSize(); auto thumbnail = QByteArray(); auto videoDuration = std::optional(); +#if 0 // mtp if (const auto &w = data.vw()) { const auto &h = data.vh(); Assert(h.has_value()); size = QSize(w->v, h->v); +#endif + if (const auto &w = data.vwidth().v) { + size = QSize(w, data.vheight().v); if (!changed && photo->size(PhotoSize::Large) != size) { changed = true; } } +#if 0 // mtp if (const auto &thumb = data.vthumb()) { if (thumb->type() == mtpc_photoStrippedSize) { thumbnail = thumb->c_photoStrippedSize().vbytes().v; +#endif + if (const auto &thumb = data.vminithumbnail()) { + thumbnail = thumb->data().vdata().v; + if (!thumbnail.isEmpty()) { if (!changed && photo->inlineThumbnailBytes() != thumbnail) { changed = true; } } } +#if 0 // mtp if (const auto &duration = data.vvideo_duration()) { videoDuration = duration->v; +#endif + if (const auto duration = data.vduration().v) { + videoDuration = duration; if (photo->extendedMediaVideoDuration() != videoDuration) { changed = true; } @@ -335,8 +357,26 @@ bool UpdateExtendedMedia( media = std::make_unique(item, photo, true); } return changed; +#if 0 // mtp }, [&](const MTPDmessageExtendedMedia &data) { media = HistoryItem::CreateMedia(item, data.vmedia()); +#endif + }, [&](const TLDpaidMediaPhoto &data) { + media = std::make_unique( + item, + item->history()->owner().processPhoto(data.vphoto()), + false); // spoiler + return true; + }, [&](const TLDpaidMediaVideo &data) { + media = std::make_unique( + item, + item->history()->owner().processDocument(data.vvideo()), + false, // skipPremiumEffect + false, // spoiler + 0); // ttlSeconds + return true; + }, [&](const TLDpaidMediaUnsupported &) { + media = nullptr; return true; }); } @@ -344,7 +384,10 @@ bool UpdateExtendedMedia( bool UpdateExtendedMedia( Invoice &invoice, not_null item, +#if 0 // mtp const QVector &media) { +#endif + const QVector &media) { auto changed = false; const auto count = int(media.size()); for (auto i = 0; i != count; ++i) { @@ -405,6 +448,7 @@ TextForMimeData WithCaptionClipboardText( } // namespace +#if 0 // mtp Invoice ComputeInvoiceData( not_null item, const MTPDmessageMediaInvoice &data) { @@ -518,6 +562,48 @@ GiveawayResults ComputeGiveawayResultsData( } return result; } +#endif + +Invoice ComputeInvoiceData( + not_null item, + const TLDmessageInvoice &data) { + const auto &info = data.vproduct_info().data(); + auto result = Invoice{ + .receiptMsgId = data.vreceipt_message_id().v, + .amount = uint64(data.vtotal_amount().v), + .currency = data.vcurrency().v, + .title = TextUtilities::SingleLine(info.vtitle().v), + .description = Api::FormattedTextFromTdb( + info.vdescription()), + .photo = (info.vphoto() + ? item->history()->owner().processPhoto(*info.vphoto()).get() + : nullptr), + .isTest = data.vis_test().v, + }; + if (const auto paid = data.vpaid_media()) { + UpdateExtendedMedia(result, item, { *paid }); + } + return result; +} + +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; +} bool HasExtendedMedia(const Invoice &invoice) { return !invoice.extendedMedia.empty(); @@ -540,6 +626,68 @@ bool IsFirstVideo(const Invoice &invoice) { return true; } +GiveawayStart ComputeGiveawayStartData( + not_null item, + const TLDmessageGiveaway &data) { + const auto &fields = data.vparameters().data(); + auto result = GiveawayStart{ + .untilDate = fields.vwinners_selection_date().v, + .quantity = data.vwinner_count().v, + .all = !fields.vonly_new_members().v, + }; + data.vprize().match([&](const TLDgiveawayPrizePremium &data) { + result.months = data.vmonth_count().v; + }, [&](const TLDgiveawayPrizeStars &data) { + result.credits = data.vstar_count().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; +} + +GiveawayResults ComputeGiveawayResultsData( + not_null item, + const TLDmessageGiveawayWinners &data) { + auto result = GiveawayResults{ + .channel = item->history()->owner().channel( + peerToChannel(peerFromTdbChat(data.vboosted_chat_id()))), + .untilDate = data.vactual_winners_selection_date().v, + .launchId = data.vgiveaway_message_id().v, + .additionalPeersCount = data.vadditional_chat_count().v, + .winnersCount = data.vwinner_count().v, + .unclaimedCount = data.vunclaimed_prize_count().v, + .refunded = data.vwas_refunded().v, + .all = !data.vonly_new_members().v, + }; + data.vprize().match([&](const TLDgiveawayPrizePremium &data) { + result.months = data.vmonth_count().v; + }, [&](const TLDgiveawayPrizeStars &data) { + result.credits = data.vstar_count().v; + }); + result.winners.reserve(data.vwinner_user_ids().v.size()); + const auto owner = &item->history()->owner(); + for (const auto &id : data.vwinner_user_ids().v) { + result.winners.push_back(owner->user(UserId(id.v))); + } + result.additionalPrize = data.vprize_description().v; + return result; +} + Media::Media(not_null parent) : _parent(parent) { } @@ -701,6 +849,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 { @@ -780,9 +932,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()); } @@ -895,6 +1049,7 @@ bool MediaPhoto::hasSpoiler() const { return _spoiler; } +#if 0 // mtp bool MediaPhoto::updateInlineResultMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaPhoto) { return false; @@ -932,6 +1087,7 @@ bool MediaPhoto::updateSentMedia(const MTPMessageMedia &media) { parent()->history()->owner().photoConvert(_photo, *content); return true; } +#endif std::unique_ptr MediaPhoto::createView( not_null message, @@ -986,9 +1142,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()); @@ -1239,6 +1397,7 @@ bool MediaFile::allowsForward() const { return !ttlSeconds(); } +#if 0 // mtp bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaDocument) { return false; @@ -1276,6 +1435,7 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) { parent()->history()->owner().documentConvert(_document, *content); return true; } +#endif std::unique_ptr MediaFile::createView( not_null message, @@ -1471,6 +1631,7 @@ TextForMimeData MediaContact::clipboardText() const { return TextForMimeData::Simple(text); } +#if 0 // mtp bool MediaContact::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -1491,6 +1652,7 @@ bool MediaContact::updateSentMedia(const MTPMessageMedia &media) { } return true; } +#endif std::unique_ptr MediaContact::createView( not_null message, @@ -1585,6 +1747,7 @@ TextForMimeData MediaLocation::clipboardText() const { return result; } +#if 0 // mtp bool MediaLocation::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -1592,6 +1755,7 @@ bool MediaLocation::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaLocation::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaLocation::createView( not_null message, @@ -1655,6 +1819,7 @@ bool MediaCall::allowsForward() const { return false; } +#if 0 // mtp bool MediaCall::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -1662,6 +1827,7 @@ bool MediaCall::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaCall::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaCall::createView( not_null message, @@ -1833,6 +1999,7 @@ bool MediaWebPage::allowsEdit() const { return true; } +#if 0 // mtp bool MediaWebPage::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -1840,6 +2007,7 @@ bool MediaWebPage::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaWebPage::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaWebPage::createView( not_null message, @@ -1927,6 +2095,7 @@ TextWithEntities MediaGame::consumedMessageText() const { return _consumedText; } +#if 0 // mtp bool MediaGame::updateInlineResultMedia(const MTPMessageMedia &media) { return updateSentMedia(media); } @@ -1939,6 +2108,7 @@ bool MediaGame::updateSentMedia(const MTPMessageMedia &media) { _game, media.c_messageMediaGame().vgame()); return true; } +#endif std::unique_ptr MediaGame::createView( not_null message, @@ -2097,6 +2267,7 @@ TextForMimeData MediaInvoice::clipboardText() const { return TextForMimeData(); } +#if 0 // mtp bool MediaInvoice::updateInlineResultMedia(const MTPMessageMedia &media) { return true; } @@ -2112,6 +2283,7 @@ bool MediaInvoice::updateExtendedMedia( return UpdateExtendedMedia(_invoice, item, media); } +#endif std::unique_ptr MediaInvoice::createView( not_null message, @@ -2174,6 +2346,7 @@ TextForMimeData MediaPoll::clipboardText() const { return TextForMimeData::Rich(std::move(result)); } +#if 0 // mtp bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -2181,6 +2354,7 @@ bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaPoll::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaPoll::createView( not_null message, @@ -2195,6 +2369,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); } @@ -2231,6 +2412,7 @@ bool MediaDice::forceForwardedInfo() const { return true; } +#if 0 // mtp bool MediaDice::updateInlineResultMedia(const MTPMessageMedia &media) { return updateSentMedia(media); } @@ -2243,6 +2425,7 @@ bool MediaDice::updateSentMedia(const MTPMessageMedia &media) { parent()->history()->owner().requestItemRepaint(parent()); return true; } +#endif std::unique_ptr MediaDice::createView( not_null message, @@ -2257,6 +2440,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); } @@ -2310,6 +2504,7 @@ ClickHandlerPtr MediaDice::MakeHandler( }); } +#if 0 // mtp MediaGiftBox::MediaGiftBox( not_null parent, not_null from, @@ -2326,9 +2521,32 @@ MediaGiftBox::MediaGiftBox( , _from(from) , _data(std::move(data)) { } +#endif +MediaGiftBox::MediaGiftBox( + not_null parent, + not_null from, + GiftType type, + int count, + DocumentData *sticker) +: MediaGiftBox(parent, from, { .count = count, .type = type }, 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 { @@ -2351,6 +2569,7 @@ TextForMimeData MediaGiftBox::clipboardText() const { return {}; } +#if 0 // mtp bool MediaGiftBox::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -2358,6 +2577,7 @@ bool MediaGiftBox::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaGiftBox::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaGiftBox::createView( not_null message, @@ -2365,7 +2585,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( @@ -2404,6 +2627,7 @@ TextForMimeData MediaWallPaper::clipboardText() const { return {}; } +#if 0 // mtp bool MediaWallPaper::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -2411,6 +2635,7 @@ bool MediaWallPaper::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaWallPaper::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif std::unique_ptr MediaWallPaper::createView( not_null message, @@ -2511,6 +2736,7 @@ bool MediaStory::dropForwardedInfo() const { return true; } +#if 0 // mtp bool MediaStory::updateInlineResultMedia(const MTPMessageMedia &media) { return false; } @@ -2518,6 +2744,7 @@ bool MediaStory::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaStory::updateSentMedia(const MTPMessageMedia &media) { return false; } +#endif not_null MediaStory::LoadingStoryPhoto( not_null owner) { @@ -2575,6 +2802,7 @@ std::unique_ptr MediaStory::createView( } } +#if 0 // mtp MediaGiveawayStart::MediaGiveawayStart( not_null parent, const GiveawayStart &data) @@ -2582,10 +2810,22 @@ MediaGiveawayStart::MediaGiveawayStart( , _data(data) { parent->history()->session().giftBoxStickersPacks().load(); } +#endif +MediaGiveawayStart::MediaGiveawayStart( + not_null parent, + const GiveawayStart &data, + DocumentData *sticker) +: Media(parent) +, _data(data) +, _sticker(sticker) { +} std::unique_ptr MediaGiveawayStart::clone( not_null parent) { +#if 0 // mtp return std::make_unique(parent, _data); +#endif + return std::make_unique(parent, _data, _sticker); } const GiveawayStart *MediaGiveawayStart::giveawayStart() const { @@ -2608,6 +2848,7 @@ TextForMimeData MediaGiveawayStart::clipboardText() const { return TextForMimeData(); } +#if 0 // mtp bool MediaGiveawayStart::updateInlineResultMedia(const MTPMessageMedia &media) { return true; } @@ -2615,6 +2856,7 @@ bool MediaGiveawayStart::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaGiveawayStart::updateSentMedia(const MTPMessageMedia &media) { return true; } +#endif std::unique_ptr MediaGiveawayStart::createView( not_null message, @@ -2622,7 +2864,10 @@ std::unique_ptr MediaGiveawayStart::createView( HistoryView::Element *replacing) { return std::make_unique( message, +#if 0 // mtp HistoryView::GenerateGiveawayStart(message, &_data)); +#endif + HistoryView::GenerateGiveawayStart(message, &_data, _sticker)); } MediaGiveawayResults::MediaGiveawayResults( @@ -2659,6 +2904,7 @@ TextForMimeData MediaGiveawayResults::clipboardText() const { return TextForMimeData(); } +#if 0 // mtp bool MediaGiveawayResults::updateInlineResultMedia(const MTPMessageMedia &media) { return true; } @@ -2666,6 +2912,7 @@ bool MediaGiveawayResults::updateInlineResultMedia(const MTPMessageMedia &media) bool MediaGiveawayResults::updateSentMedia(const MTPMessageMedia &media) { return true; } +#endif std::unique_ptr MediaGiveawayResults::createView( not_null message, diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index e1213e39a3873a..93b78bfa5741ed 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -11,6 +11,15 @@ 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 TLDmessageGiveaway; +class TLDmessageGiveawayWinners; +} // namespace Tdb + class Image; class History; class HistoryItem; @@ -210,6 +219,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; @@ -219,6 +229,7 @@ class Media { const QVector &media) { return false; } +#endif virtual std::unique_ptr createView( not_null message, not_null realParent, @@ -227,6 +238,8 @@ class Media { not_null message, HistoryView::Element *replacing = nullptr); + virtual bool updateContent(const Tdb::TLmessageContent &content); + protected: [[nodiscard]] ItemPreview toGroupPreview( const HistoryItemsList &items, @@ -267,8 +280,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, @@ -313,8 +328,10 @@ class MediaFile final : public Media { crl::time ttlSeconds() 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, @@ -349,8 +366,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, @@ -384,8 +403,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, @@ -424,8 +445,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, @@ -467,8 +490,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, @@ -501,8 +526,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, @@ -532,11 +559,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 QVector &media) override; +#endif std::unique_ptr createView( not_null message, not_null realParent, @@ -562,8 +591,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, @@ -577,6 +608,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; @@ -589,13 +623,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, @@ -609,6 +647,7 @@ class MediaDice final : public Media { class MediaGiftBox final : public Media { public: +#if 0 // mtp MediaGiftBox( not_null parent, not_null from, @@ -618,6 +657,18 @@ class MediaGiftBox final : public Media { not_null parent, not_null from, GiftCode data); +#endif + MediaGiftBox( + not_null parent, + not_null from, + GiftType type, + int count, + DocumentData *sticker); + MediaGiftBox( + not_null parent, + not_null from, + GiftCode data, + DocumentData *sticker); std::unique_ptr clone(not_null parent) override; @@ -628,8 +679,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, @@ -639,6 +692,8 @@ class MediaGiftBox final : public Media { not_null _from; GiftCode _data; + DocumentData *_sticker = nullptr; // later move up + }; class MediaWallPaper final : public Media { @@ -658,8 +713,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, @@ -690,8 +747,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, @@ -710,9 +769,15 @@ class MediaStory final : public Media, public base::has_weak_ptr { class MediaGiveawayStart final : public Media { public: +#if 0 // mtp MediaGiveawayStart( not_null parent, const GiveawayStart &data); +#endif + MediaGiveawayStart( + not_null parent, + const GiveawayStart &data, + DocumentData *sticker); std::unique_ptr clone(not_null parent) override; @@ -722,8 +787,10 @@ class MediaGiveawayStart 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, @@ -732,6 +799,8 @@ class MediaGiveawayStart final : public Media { private: GiveawayStart _data; + DocumentData *_sticker = nullptr; + }; class MediaGiveawayResults final : public Media { @@ -748,8 +817,10 @@ class MediaGiveawayResults 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, @@ -760,6 +831,7 @@ class MediaGiveawayResults final : public Media { }; +#if 0 // mtp [[nodiscard]] Invoice ComputeInvoiceData( not_null item, const MTPDmessageMediaInvoice &data); @@ -776,5 +848,20 @@ class MediaGiveawayResults final : public Media { [[nodiscard]] GiveawayResults ComputeGiveawayResultsData( not_null item, const MTPDmessageMediaGiveawayResults &data); +#endif + +[[nodiscard]] Invoice ComputeInvoiceData( + not_null item, + const Tdb::TLDmessageInvoice &data); + +[[nodiscard]] Call ComputeCallData(const Tdb::TLDmessageCall &call); + +[[nodiscard]] GiveawayStart ComputeGiveawayStartData( + not_null item, + const Tdb::TLDmessageGiveaway &data); + +[[nodiscard]] GiveawayResults ComputeGiveawayResultsData( + not_null item, + const Tdb::TLDmessageGiveawayWinners &data); } // namespace Data diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.cpp b/Telegram/SourceFiles/data/data_message_reaction_id.cpp index 1deac2bbb86d56..c8fd8e69a13415 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 SearchTagToQuery(const ReactionId &tagId) { if (const auto customId = tagId.custom()) { @@ -49,6 +56,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() }; @@ -72,5 +80,27 @@ MTPReaction ReactionToMTP(ReactionId id) { return MTP_reactionEmoji(MTP_string(id.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) }; + }, [](const TLDreactionTypePaid &) { + return ReactionId::Paid(); + }); +} + +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 c624ef865cbde4..f043d792deb2d1 100644 --- a/Telegram/SourceFiles/data/data_message_reaction_id.h +++ b/Telegram/SourceFiles/data/data_message_reaction_id.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "base/qt/qt_compare.h" +namespace Tdb { +class TLreactionType; +} // namespace Tdb + namespace Data { struct ReactionId { @@ -67,8 +71,15 @@ struct MessageReaction { [[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 d360ccf65a6d5e..3eacc90470773d 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -39,9 +39,15 @@ For license and copyright information please follow this link: #include "base/random.h" +#include "tdb/tdb_tl_scheme.h" +#include "tdb/tdb_account.h" +#include "data/data_saved_messages.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; @@ -59,6 +65,7 @@ constexpr auto kPaidAccumulatePeriod = 5 * crl::time(1000) + 500; return id.emoji(); } +#if 0 // mtp [[nodiscard]] std::vector ListFromMTP( const MTPDmessages_reactions &data) { const auto &list = data.vreactions().v; @@ -95,6 +102,27 @@ constexpr auto kPaidAccumulatePeriod = 5 * crl::time(1000) + 500; } return result; } +#endif + +[[nodiscard]] std::vector ListFromTL( + const QVector &list) { + auto result = std::vector(); + result.reserve(list.size()); + for (const auto &reaction : list) { + const auto &data = reaction.data(); + const auto id = ReactionFromTL(data.vtag()); + if (id.empty()) { + LOG(("API Error: reactionEmpty in savedMessagesTag.")); + } else { + result.push_back({ + .id = id, + .title = data.vlabel().v, + .count = data.vcount().v, + }); + } + } + return result; +} [[nodiscard]] Reaction CustomReaction(not_null document) { return Reaction{ @@ -291,6 +319,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) { @@ -305,9 +367,12 @@ PossibleItemReactions::PossibleItemReactions( Reactions::Reactions(not_null owner) : _owner(owner) +#if 0 // mtp , _topRefreshTimer([=] { refreshTop(); }) , _repaintTimer([=] { repaintCollected(); }) +#endif , _sendPaidTimer([=] { sendPaid(); }) { +#if 0 // mtp refreshDefault(); _myTags.emplace(nullptr); @@ -352,6 +417,7 @@ Reactions::Reactions(not_null owner) applyFavorite(id); }, _lifetime); }); +#endif } Reactions::~Reactions() = default; @@ -360,6 +426,7 @@ Main::Session &Reactions::session() const { return _owner->session(); } +#if 0 // mtp void Reactions::refreshTop() { requestTop(); } @@ -383,6 +450,80 @@ void Reactions::refreshRecentDelayed() { void Reactions::refreshDefault() { requestDefault(); } +#endif + +void Reactions::refreshActive(const TLDupdateActiveEmojiReactions &data) { + updateFromData(data); +} + +void Reactions::refreshEffects( + const TLDupdateAvailableMessageEffects &data) { + auto list = std::vector(); + list.reserve(data.vreaction_effect_ids().v.size() + + data.vsticker_effect_ids().v.size()); + for (const auto &effectId : data.vreaction_effect_ids().v) { + const auto id = uint64(effectId.v); + const auto i = ranges::find( + _resolveEffects, + id, + &EffectResolved::id); + if (i == end(_resolveEffects)) { + list.push_back({ .id = id, .sticker = false }); + } else { + list.push_back(*i); + } + } + for (const auto &effectId : data.vsticker_effect_ids().v) { + const auto id = uint64(effectId.v); + const auto i = ranges::find( + _resolveEffects, + id, + &EffectResolved::id); + if (i == end(_resolveEffects)) { + list.push_back({ .id = id, .sticker = true }); + } else { + list.push_back(*i); + } + } + _resolveEffects = std::move(list); + resolveEffectNext(); +} + +void Reactions::refreshMyTags(const TLDupdateSavedMessagesTags &data) { + const auto sublist = _owner->savedMessages().sublistById( + data.vsaved_messages_topic_id().v); + if (!sublist) { + return; + } + auto &my = _myTags[sublist]; + if (my.requestId) { + return; + } + updateMyTags(sublist, data.vtags()); +} + +void Reactions::updateMyTags( + SavedSublist *sublist, + const TLsavedMessagesTags &tags) { + auto &my = _myTags[sublist]; + auto list = ListFromTL(tags.data().vtags().v); + auto renamed = base::flat_set(); + if (!sublist) { + for (const auto &info : list) { + const auto j = ranges::find(my.info, info.id, &MyTagInfo::id); + const auto was = (j != end(my.info)) ? j->title : QString(); + if (info.title != was) { + renamed.emplace(info.id); + } + } + } + my.info = std::move(list); + my.tags = resolveByInfos(my.info, _unresolvedMyTags, sublist); + _myTagsUpdated.fire_copy(sublist); + for (const auto &id : renamed) { + _myTagRenamed.fire_copy(id); + } +} void Reactions::refreshMyTags(SavedSublist *sublist) { requestMyTags(sublist); @@ -401,6 +542,7 @@ void Reactions::refreshMyTagsDelayed() { }); } +#if 0 // mtp void Reactions::refreshTags() { requestTags(); } @@ -410,6 +552,11 @@ void Reactions::refreshEffects() { requestEffects(); } } +#endif + +void Reactions::refreshFavorite(const TLDupdateDefaultReactionType &data) { + applyFavorite(ReactionFromTL(data.vreaction_type())); +} const std::vector &Reactions::list(Type type) const { switch (type) { @@ -450,6 +597,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(); @@ -461,6 +609,10 @@ void Reactions::setFavorite(const ReactionId &id) { }).fail([=] { _saveFaveRequestId = 0; }).send(); +#endif + _owner->session().sender().request(TLsetDefaultReactionType( + ReactionToTL(id) + )).send(); applyFavorite(id); } @@ -523,12 +675,18 @@ void Reactions::renameTag(const ReactionId &id, const QString &name) { } _myTagRenamed.fire_copy(id); +#if 0 // mtp using Flag = MTPmessages_UpdateSavedReactionTag::Flag; _owner->session().api().request(MTPmessages_UpdateSavedReactionTag( MTP_flags(name.isEmpty() ? Flag(0) : Flag::f_title), ReactionToMTP(id), MTP_string(name) )).send(); +#endif + _owner->session().sender().request(TLsetSavedMessagesTagLabel( + ReactionToTL(id), + tl_string(name) + )).send(); } void Reactions::scheduleMyTagsUpdate(SavedSublist *sublist) { @@ -691,12 +849,16 @@ void Reactions::preloadImageFor(const ReactionId &id) { if (set.effect) { preloadEffect(*i); } + } else { + resolveEmoji(id.emoji()); +#if 0 // mtp } else if (set.effect && !_waitingForEffects) { _waitingForEffects = true; refreshEffects(); } else if (!set.effect && !_waitingForReactions) { _waitingForReactions = true; refreshDefault(); +#endif } } @@ -813,9 +975,6 @@ void Reactions::resolveReactionImages() { : i->selectAnimation.get(); if (document) { loadImage(set, document, !i->centerIcon); - } else { - LOG(("API Error: Reaction '%1' not found!" - ).arg(ReactionIdToLog(id))); } } } @@ -914,6 +1073,7 @@ void Reactions::downloadTaskFinished() { } } +#if 0 // mtp void Reactions::requestTop() { if (_topRequestId) { return; @@ -994,12 +1154,14 @@ void Reactions::requestGeneric() { _genericRequestId = 0; }).send(); } +#endif void Reactions::requestMyTags(SavedSublist *sublist) { auto &my = _myTags[sublist]; if (my.requestId) { return; } +#if 0 // mtp auto &api = _owner->session().api(); my.requestScheduled = false; using Flag = MTPmessages_GetSavedReactionTags::Flag; @@ -1014,6 +1176,15 @@ void Reactions::requestMyTags(SavedSublist *sublist) { updateMyTags(sublist, data); }, [](const MTPDmessages_savedReactionTagsNotModified&) { }); +#endif + my.requestScheduled = false; + my.requestId = _owner->session().sender().request( + TLgetSavedMessagesTags( + tl_int53(_owner->savedMessages().sublistId(sublist))) + ).done([=](const TLsavedMessagesTags &result) { + auto &my = _myTags[sublist]; + my.requestId = 0; + updateMyTags(sublist, result); }).fail([=] { auto &my = _myTags[sublist]; my.requestId = 0; @@ -1021,6 +1192,7 @@ void Reactions::requestMyTags(SavedSublist *sublist) { }).send(); } +#if 0 // mtp void Reactions::requestTags() { if (_tagsRequestId) { return; @@ -1077,37 +1249,264 @@ 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); } + resolveReactionImages(); + } + 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 (_waitingForReactions) { - _waitingForReactions = false; - resolveReactionImages(); + _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); + } } + resolveReactionImages(); 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::resolveEffectNext() { + const auto sent = ranges::find_if( + _resolveEffects, + [](mtpRequestId id) { return id != 0; }, + &EffectResolved::requestId); + const auto resolving = (sent != end(_resolveEffects)); + if (resolving) { + return; + } + const auto unknown = ranges::find( + _resolveEffects, + false, + &EffectResolved::resolved); + if (unknown == end(_resolveEffects)) { + return; + } + resolveEffect(&*unknown); +} + +void Reactions::resolveEffect(uint64 id) { + +} + +void Reactions::resolveEffect(not_null entry) { + const auto effectId = entry->id; + const auto finish = [=](std::optional parsed) { + const auto i = ranges::find( + _resolveEffects, + effectId, + &EffectResolved::id); + Assert(i != end(_resolveEffects)); + i->resolved = true; + i->requestId = 0; + if (parsed) { + const auto id = ReactionId{ DocumentId(effectId) }; + const auto i = ranges::find(_effects, id, &Reaction::id); + if (i != end(_effects)) { + *i = std::move(*parsed); + } else { + _effects.reserve(_resolveEffects.size()); + _effects.push_back(std::move(*parsed)); + } + const auto toCache = [&](DocumentData *document) { + if (document) { + _iconsCache.emplace( + document, + document->createMediaView()); + } + }; + toCache(parsed->appearAnimation); + toCache(parsed->selectAnimation); + toCache(parsed->centerIcon); + toCache(parsed->aroundAnimation); + resolveEffectImages(); + } + checkAllEffectsResolved(); + resolveEffectNext(); + }; + auto &api = _owner->session().sender(); + entry->requestId = api.request(TLgetMessageEffect( + tl_int64(effectId) + )).done([=](const TLmessageEffect &result) { + finish(parse(result)); + }).fail([=](const Error &error) { + LOG(("API Error: Effect '%1' not found!").arg(effectId)); + finish(std::nullopt); + }).send(); +} + +void Reactions::checkAllEffectsResolved() { + if (!allEffectsResolved()) { + return; + } + effectsUpdated(); +} + +bool Reactions::allEffectsResolved() const { + return !ranges::contains( + _resolveEffects, + false, + &EffectResolved::resolved); +} + +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) { @@ -1189,16 +1588,21 @@ void Reactions::recentUpdated() { _topRefreshTimer.callOnce(kTopRequestDelay); _recentUpdated.fire({}); } +#endif void Reactions::defaultUpdated() { +#if 0 // mtp refreshTop(); refreshRecent(); +#endif if (_genericAnimations.empty()) { requestGeneric(); } +#if 0 // mtp refreshMyTags(); refreshTags(); refreshEffects(); +#endif _defaultUpdated.fire({}); } @@ -1353,7 +1757,7 @@ std::vector Reactions::resolveByInfos( 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, @@ -1361,6 +1765,7 @@ void Reactions::resolve(const ReactionId &id) { } } +#if 0 // mtp std::optional Reactions::parse(const MTPAvailableReaction &entry) { const auto &data = entry.data(); const auto emoji = qs(data.vreaction()); @@ -1432,9 +1837,88 @@ std::optional Reactions::parse(const MTPAvailableEffect &entry) { .premium = data.is_premium_required(), }); } +#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; +} + +std::optional Reactions::parse(const TLmessageEffect &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 effects: %1").arg(emoji)); + return std::nullopt; + } + const auto id = DocumentId(data.vid().v); + auto around = (DocumentData*)nullptr; + auto document = (DocumentData*)nullptr; + data.vtype().match([&](const TLDmessageEffectTypeEmojiReaction &data) { + around = _owner->processDocument(data.veffect_animation()); + document = _owner->processDocument(data.vselect_animation()); + }, [&](const TLDmessageEffectTypePremiumSticker &data) { + document = _owner->processDocument(data.vsticker()); + }); + if (!document->sticker()) { + LOG(("API Error: Bad sticker in effects: %1").arg(document->id)); + return std::nullopt; + } + if (around && !around->sticker()) { + LOG(("API Error: Bad sticker in around: %1").arg(around->id)); + return std::nullopt; + } + const auto icon = data.vstatic_icon() + ? _owner->processDocument(*data.vstatic_icon()).get() + : nullptr; + if (icon && !icon->sticker()) { + LOG(("API Error: Bad sticker in effects icon: %1").arg(icon->id)); + return std::nullopt; + } + return std::make_optional(Reaction{ + .id = ReactionId{ id }, + .title = emoji, + .appearAnimation = document, + .selectAnimation = document, + .centerIcon = icon, + .aroundAnimation = around, + .active = true, + .effect = true, + .premium = data.vis_premium().v, + }); +} +#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)) { @@ -1494,6 +1978,7 @@ void Reactions::updateAllInHistory(not_null peer, bool enabled) { history->reactionsEnabledChanged(enabled); } } +#endif void Reactions::clearTemporary() { _temporary.clear(); @@ -1560,7 +2045,9 @@ not_null Reactions::paidToastAnimation() { rpl::producer> Reactions::myTagsValue( SavedSublist *sublist) { +#if 0 // mtp refreshMyTags(sublist); +#endif const auto list = [=] { return _myTags[sublist].tags; }; @@ -1611,6 +2098,7 @@ crl::time Reactions::ScheduledPaidDelay() { return kPaidAccumulatePeriod; } +#if 0 // mtp void Reactions::repaintCollected() { const auto now = crl::now(); auto closest = crl::time(); @@ -1698,6 +2186,7 @@ void Reactions::CheckUnknownForUnread( }, [](const auto &) { }); } +#endif void Reactions::sendPaid() { if (!_sendingPaid.empty()) { @@ -1743,12 +2232,19 @@ void Reactions::sendPaidPrivacyRequest( Expects(!send.count); const auto id = item->fullId(); +#if 0 // mtp auto &api = _owner->session().api(); const auto requestId = api.request( MTPmessages_TogglePaidReactionPrivacy( item->history()->peer->input, MTP_int(id.msg), MTP_bool(*send.anonymous)) +#endif + const auto requestId = _owner->session().sender().request( + TLtogglePaidMessageReactionIsAnonymous( + peerToTdbChat(item->history()->peer->id), + tl_int53(id.msg.bare), + tl_bool(*send.anonymous)) ).done([=] { if (const auto item = _owner->message(id)) { if (_sendingPaid.remove(item)) { @@ -1756,7 +2252,10 @@ void Reactions::sendPaidPrivacyRequest( } } checkQuitPreventFinished(); +#if 0 // mtp }).fail([=](const MTP::Error &error) { +#endif + }).fail([=](const Error &error) { if (const auto item = _owner->message(id)) { if (_sendingPaid.remove(item)) { sendPaidFinish(item, send, false); @@ -1778,6 +2277,7 @@ void Reactions::sendPaidRequest( } const auto id = item->fullId(); +#if 0 // mtp const auto randomId = base::unixtime::mtproto_msg_id(); auto &api = _owner->session().api(); using Flag = MTPmessages_SendPaidReaction::Flag; @@ -1789,19 +2289,55 @@ void Reactions::sendPaidRequest( MTP_long(randomId), MTP_bool(send.anonymous.value_or(false)) )).done([=](const MTPUpdates &result) { +#endif + const auto requestId = _owner->session().sender().request( + TLaddPendingPaidMessageReaction( + peerToTdbChat(id.peer), + tl_int53(id.msg.bare), + tl_int53(send.count), + tl_bool(!send.anonymous.has_value()), + tl_bool(send.anonymous.value_or(false))) + ).done([=] { + const auto item = _owner->message(id); + if (!item) { + checkQuitPreventFinished(); + return; + } + _sendingPaid[item] = _owner->session().sender().request( + TLcommitPendingPaidMessageReactions( + peerToTdbChat(id.peer), + tl_int53(id.msg.bare)) + ).done([=] { + if (const auto item = _owner->message(id)) { if (_sendingPaid.remove(item)) { sendPaidFinish(item, send, true); } } +#if 0 // mtp _owner->session().api().applyUpdates(result); +#endif checkQuitPreventFinished(); + + }).fail([=] { + if (const auto item = _owner->message(id)) { + _sendingPaid.remove(item); + sendPaidFinish(item, send, false); + } + checkQuitPreventFinished(); + }).send(); +#if 0 // mtp }).fail([=](const MTP::Error &error) { +#endif + }).fail([=] { if (const auto item = _owner->message(id)) { _sendingPaid.remove(item); +#if 0 // mtp if (error.type() == u"RANDOM_ID_EXPIRED"_q) { sendPaidRequest(item, send); } else { +#endif + { sendPaidFinish(item, send, false); } } @@ -1909,7 +2445,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); } @@ -1952,10 +2498,19 @@ void MessageReactions::remove(const ReactionId &id) { history->owner().reactions().decrementMyTag(id, sublist); } 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, @@ -2166,6 +2721,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 f1bf36f4641017..8212ac32abc334 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.h +++ b/Telegram/SourceFiles/data/data_message_reactions.h @@ -11,6 +11,20 @@ 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 TLDupdateAvailableMessageEffects; +class TLDupdateDefaultReactionType; +class TLDupdateSavedMessagesTags; +class TLsavedMessagesTags; +class TLemojiReaction; +class TLmessageEffect; +class TLunreadReaction; +class TLmessageReaction; +class TLDstickers; +class TLavailableReactions; +} // namespace Tdb + namespace Ui { class AnimatedIcon; } // namespace Ui @@ -74,6 +88,10 @@ struct PaidReactionSend { std::optional anonymous = false; }; +[[nodiscard]] PossibleItemReactionsRef ParsePossibleReactions( + not_null session, + const Tdb::TLavailableReactions &available); + class Reactions final : private CustomEmojiManager::Listener { public: explicit Reactions(not_null owner); @@ -84,14 +102,22 @@ class Reactions final : private CustomEmojiManager::Listener { } [[nodiscard]] Main::Session &session() const; +#if 0 // mtp void refreshTop(); void refreshRecent(); void refreshRecentDelayed(); void refreshDefault(); +#endif void refreshMyTags(SavedSublist *sublist = nullptr); void refreshMyTagsDelayed(); +#if 0 // mtp void refreshTags(); void refreshEffects(); +#endif + void refreshActive(const Tdb::TLDupdateActiveEmojiReactions &data); + void refreshEffects(const Tdb::TLDupdateAvailableMessageEffects &data); + void refreshFavorite(const Tdb::TLDupdateDefaultReactionType &data); + void refreshMyTags(const Tdb::TLDupdateSavedMessagesTags &data); enum class Type { Active, @@ -136,12 +162,14 @@ class Reactions final : private CustomEmojiManager::Listener { void preloadAnimationsFor(const ReactionId &emoji); +#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); @@ -158,10 +186,12 @@ class Reactions final : private CustomEmojiManager::Listener { not_null item) const; [[nodiscard]] static crl::time ScheduledPaidDelay(); +#if 0 // mtp [[nodiscard]] static bool HasUnread(const MTPMessageReactions &data); static void CheckUnknownForUnread( not_null owner, const MTPMessage &message); +#endif private: struct ImageSet { @@ -185,15 +215,30 @@ class Reactions final : private CustomEmojiManager::Listener { bool requestScheduled = false; bool updateScheduled = false; }; + struct EmojiResolved { + QString emoji; + mtpRequestId requestId = 0; + bool resolved = false; + bool active = false; + }; + struct EffectResolved { + uint64 id = 0; + mtpRequestId requestId = 0; + bool sticker = false; + bool resolved = false; + }; [[nodiscard]] not_null resolveListener(); void customEmojiResolveDone(not_null document) override; +#if 0 // mtp void requestTop(); void requestRecent(); void requestDefault(); +#endif void requestGeneric(); void requestMyTags(SavedSublist *sublist = nullptr); +#if 0 // mtp void requestTags(); void requestEffects(); @@ -208,10 +253,14 @@ class Reactions final : private CustomEmojiManager::Listener { void updateEffects(const MTPDmessages_availableEffects &data); void recentUpdated(); +#endif void defaultUpdated(); void myTagsUpdated(); void tagsUpdated(); void effectsUpdated(); + void updateMyTags( + SavedSublist *sublist, + const Tdb::TLsavedMessagesTags &tags); [[nodiscard]] std::optional resolveById(const ReactionId &id); [[nodiscard]] std::vector resolveByIds( @@ -230,10 +279,29 @@ class Reactions final : private CustomEmojiManager::Listener { void applyFavorite(const ReactionId &id); void scheduleMyTagsUpdate(SavedSublist *sublist); +#if 0 // mtp [[nodiscard]] std::optional parse( const MTPAvailableReaction &entry); [[nodiscard]] std::optional parse( const MTPAvailableEffect &entry); +#endif + void updateFromData(const Tdb::TLDupdateActiveEmojiReactions &data); + [[nodiscard]] std::optional parse( + const Tdb::TLemojiReaction &entry); + [[nodiscard]] std::optional parse( + const Tdb::TLmessageEffect &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 resolveEffectNext(); + void resolveEffect(uint64 id); + void resolveEffect(not_null entry); + void checkAllEffectsResolved(); + [[nodiscard]] bool allEffectsResolved() const; void preloadEffect(const Reaction &effect); void preloadImageFor(const ReactionId &id); @@ -252,8 +320,10 @@ class Reactions final : private CustomEmojiManager::Listener { [[nodiscard]] DocumentData *randomLoadedFrom( std::vector> list) const; +#if 0 // mtp void repaintCollected(); void pollCollected(); +#endif void sendPaid(); bool sendPaid(not_null item); @@ -317,6 +387,7 @@ class Reactions final : private CustomEmojiManager::Listener { std::optional _paid; DocumentData *_paidToastAnimation = nullptr; +#if 0 // mtp base::Timer _topRefreshTimer; mtpRequestId _topRequestId = 0; uint64 _topHash = 0; @@ -327,6 +398,9 @@ class Reactions final : private CustomEmojiManager::Listener { mtpRequestId _defaultRequestId = 0; int32 _defaultHash = 0; +#endif + std::vector _emojiReactions; + std::vector _resolveEffects; mtpRequestId _genericRequestId = 0; @@ -341,6 +415,7 @@ class Reactions final : private CustomEmojiManager::Listener { bool _waitingForReactions = false; bool _waitingForEffects = false; +#if 0 // mtp base::flat_map _sentRequests; base::flat_map, crl::time> _repaintItems; @@ -348,12 +423,15 @@ class Reactions final : private CustomEmojiManager::Listener { base::flat_set> _pollItems; base::flat_set> _pollingItems; mtpRequestId _pollRequestId = 0; +#endif base::flat_map, crl::time> _sendPaidItems; base::flat_map, mtpRequestId> _sendingPaid; base::Timer _sendPaidTimer; +#if 0 // mtp mtpRequestId _saveFaveRequestId = 0; +#endif rpl::lifetime _lifetime; @@ -390,6 +468,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, @@ -399,6 +479,10 @@ class MessageReactions final { const QVector &list, const QVector &recent, bool min) 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_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index b3ecb27b0bea0f..1bbccba9be266a 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -55,6 +55,7 @@ Q_DECLARE_METATYPE(MsgId); using StoryId = int32; using BusinessShortcutId = int32; +using SavedSublistId = int64; struct FullStoryId { PeerId peer = 0; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 9ff84255de9dc5..8b5a34e7bff4ab 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -52,6 +52,7 @@ For license and copyright information please follow this link: #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "tdb/tdb_tl_scheme.h" +#include "data/data_secret_chat.h" namespace { @@ -111,6 +112,15 @@ bool UnavailableReason::IgnoreSensitiveMark( UnavailableReason::Sensitive().reason); } +[[nodiscard]] std::vector UnavailableReason::Extract( + 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 UnavailableReason::Extract( @@ -158,7 +168,26 @@ 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; +} + +#if 0 // mtp AllowedReactions Parse( const MTPChatReactions &value, int maxCount, @@ -189,7 +218,30 @@ AllowedReactions Parse( }; }); } +#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) { @@ -223,6 +275,7 @@ UserData *UserFromInputMTP( return (UserData*)nullptr; }); } +#endif } // namespace Data @@ -276,6 +329,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 @@ -327,6 +382,8 @@ void PeerData::updateNameDelayed( } not_null PeerData::ensureEmptyUserpic() const { + Expects(!isSecretChat()); + if (!_userpicEmpty) { const auto user = asUser(); _userpicEmpty = std::make_unique( @@ -339,6 +396,9 @@ not_null PeerData::ensureEmptyUserpic() const { } void PeerData::invalidateEmptyUserpic() { + if (isSecretChat()) { + return; + } _userpicEmpty = nullptr; } @@ -350,11 +410,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); @@ -368,8 +432,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(); @@ -398,6 +466,9 @@ void PeerData::paintUserpic( int y, int size, bool forceCircle) const { + if (const auto user = secretChatUser()) { + return user->paintUserpic(p, view, x, y, size, forceCircle); + } const auto cloud = userpicCloudImage(view); const auto ratio = style::DevicePixelRatio(); Ui::ValidateUserpicCache( @@ -410,18 +481,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 {}; } @@ -431,10 +514,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()); @@ -445,6 +534,9 @@ QImage PeerData::GenerateUserpicImage( Ui::PeerUserpicView &view, int size, std::optional radius) { + if (const auto user = peer->secretChatUser()) { + return GenerateUserpicImage(user, view, size, radius); + } if (const auto userpic = peer->userpicCloudImage(view)) { auto image = userpic->scaled( { size, size }, @@ -509,16 +601,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 // #TODO legacy +#if 0 // mtp void PeerData::updateUserpic( PhotoId photoId, MTP::DcId dcId, @@ -539,55 +637,73 @@ void PeerData::updateUserpic( } #endif -void PeerData::updateUserpic(FileId fileId, PhotoId photoId) { - setUserpicChecked( - photoId, - ImageLocation( - { TdbFileLocation{ fileId } }, - kUserpicSize, - kUserpicSize)); -} - void PeerData::updateUserpic(const TLchatPhotoInfo &photo) { - photo.match([&](const TLDchatPhotoInfo &data) { - updateUserpic(data.vsmall().match([&](const TLDfile &data) { - return data.vid().v; - })); // #TODO tdlib set data to view. - }); + if (isSecretChat()) { + return; + } + updateUserpic(photo.data().vsmall(), photo.data().vhas_animation().v); } void PeerData::updateUserpic(const TLprofilePhoto &photo) { - photo.match([&](const TLDprofilePhoto &data) { - updateUserpic(data.vsmall().match([&](const TLDfile &data) { - return data.vid().v; - }), data.vid().v); // #TODO tdlib set data to view. - }); + if (isSecretChat()) { + return; + } + updateUserpic(photo.data().vsmall(), photo.data().vhas_animation().v); } -void PeerData::setPhotoFull(const TLchatPhoto &data) { - data.match([&](const TLDchatPhoto &data) { - // #TODO tdlib animated avatars. - if (data.vsizes().v.isEmpty()) { - clearPhoto(); - } else { - const auto &size = data.vsizes().v.front(); - updateUserpic(size.match([&](const TLDphotoSize &data) { - return data.vphoto().match([&](const TLDfile &data) { - return data.vid().v; - }); - }), data.vid().v); // #TODO tdlib set data to view. +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, @@ -603,6 +719,7 @@ void PeerData::setUserpicChecked( } } } +#endif auto PeerData::unavailableReasons() const -> const std::vector & { @@ -669,6 +786,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."); } @@ -679,6 +798,8 @@ bool PeerData::canCreatePolls() const { && !user->isSupport() && !user->isRepliesChat() && !user->isVerifyCodes(); + } else if (isSecretChat()) { + return false; } return Data::CanSend(this, ChatRestriction::SendPolls); } @@ -743,6 +864,7 @@ bool PeerData::setAbout(const QString &newAbout) { return true; } +#if 0 // mtp void PeerData::checkFolder(FolderId folderId) { const auto folder = folderId ? owner().folderLoaded(folderId) @@ -753,6 +875,30 @@ void PeerData::checkFolder(FolderId folderId) { } } } +#endif + +void PeerData::updateBusinessBot(const TLbusinessBotManageBar *bar) { + if (!bar) { + clearBusinessBot(); + return; + } + const auto &data = bar->data(); + if (!_barDetails) { + _barDetails = std::make_unique(); + } + _barDetails->businessBot = _owner->user(data.vbot_user_id().v); + _barDetails->businessBotManageUrl = data.vmanage_url().v; + if (const auto settings = barSettings()) { + setBarSettings(*settings + | PeerBarSetting::HasBusinessBot + | (data.vis_bot_paused().v + ? PeerBarSetting::BusinessBotPaused + : PeerBarSetting(0)) + | (data.vcan_bot_reply().v + ? PeerBarSetting::BusinessBotCanReply + : PeerBarSetting(0))); + } +} void PeerData::clearBusinessBot() { if (const auto details = _barDetails.get()) { @@ -790,13 +936,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::setBarSettings(const MTPPeerSettings &data) { data.match([&](const MTPDpeerSettings &data) { if (!data.vbusiness_bot_id() && !data.vrequest_chat_title()) { @@ -839,6 +992,8 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) { : Flag())); }); } +#endif + QString PeerData::requestChatTitle() const { return _barDetails ? _barDetails->requestChatTitle : QString(); } @@ -855,6 +1010,58 @@ QString PeerData::businessBotManageUrl() const { return _barDetails ? _barDetails->businessBotManageUrl : QString(); } +void PeerData::setActionBar(const TLchatActionBar *bar) { + if (const auto bar = _barDetails.get()) { + if (bar->businessBot || !bar->businessBotManageUrl.isEmpty()) { + bar->requestChatTitle = QString(); + bar->requestChatDate = TimeId(); + } else { + _barDetails = nullptr; + } + } + const auto existing = barSettings().value_or(PeerBarSettings(0)) + & PeerBarSetting::NeedContactsException; + if (!bar) { + setBarSettings(existing); + return; + } + using Flag = PeerBarSetting; + bar->match([&](const TLDchatActionBarReportSpam &data) { + setBarSettings(existing + | Flag::ReportSpam + | (data.vcan_unarchive().v ? Flag::AutoArchived : Flag())); + }, [&](const TLDchatActionBarReportUnrelatedLocation &data) { + // later + // setBarSettings(existing | Flag::ReportGeo); + }, [&](const TLDchatActionBarInviteMembers &data) { + // later + // setBarSettings(existing | Flag::InviteMembers); + }, [&](const TLDchatActionBarReportAddBlock &data) { + setBarSettings(existing + | Flag::AddContact + | Flag::BlockContact + | Flag::ReportSpam + | (data.vcan_unarchive().v ? Flag::AutoArchived : Flag(0))); + + }, [&](const TLDchatActionBarAddContact &data) { + setBarSettings(existing | Flag::AddContact); + }, [&](const TLDchatActionBarSharePhoneNumber &data) { + setBarSettings(existing | Flag::ShareContact); + }, [&](const TLDchatActionBarJoinRequest &data) { + if (!_barDetails) { + _barDetails = std::make_unique(); + } + _barDetails->requestChatTitle = data.vtitle().v; + _barDetails->requestChatDate = data.vrequest_date().v; + setBarSettings(existing + | Flag::RequestChat + | (data.vis_channel().v + ? Flag::RequestChatIsBroadcast + : Flag(0))); + }); +} + +#if 0 // mtp bool PeerData::changeColorIndex( const tl::conditional &cloudColorIndex) { return cloudColorIndex @@ -879,6 +1086,7 @@ bool PeerData::changeColor( : DocumentId()); return changed1 || changed2; } +#endif void PeerData::fillNames() { _nameWords.clear(); @@ -939,6 +1147,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(); @@ -948,7 +1160,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); } } @@ -1065,7 +1277,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()) { @@ -1077,10 +1321,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(); } @@ -1088,6 +1338,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; } @@ -1162,17 +1415,27 @@ bool PeerData::changeBackgroundEmojiId(DocumentId id) { return true; } +#if 0 // mtp void PeerData::setEmojiStatus(const MTPEmojiStatus &status) { const auto parsed = Data::ParseEmojiStatus(status); setEmojiStatus(parsed.id, parsed.until); } +#endif + +void PeerData::setEmojiStatus(const TLemojiStatus *status) { + setEmojiStatus( + status ? status->data().vcustom_emoji_id().v : 0, + status ? status->data().vexpiration_date().v : 0); +} void PeerData::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 PeerData::emojiStatusId() const { @@ -1254,7 +1517,10 @@ bool PeerData::isRepliesChat() const { if (id != kTestId && id != kProductionId) { return false; } +#if 0 // mtp return ((session().mtp().environment() == MTP::Environment::Production) +#endif + return (!session().isTestMode() ? kProductionId : kTestId) == id; } @@ -1372,6 +1638,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(); } @@ -1385,7 +1655,13 @@ bool PeerData::amAnonymous() const { return false; } +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()) @@ -1399,6 +1675,7 @@ bool PeerData::canRevokeFullHistory() const { && megagroup->canDelete(); } return false; +#endif } bool PeerData::slowmodeApplied() const { @@ -1518,6 +1795,15 @@ void PeerData::setWallPaper( } } +void PeerData::setWallPaperOverriden(bool overriden) { + const auto overridenValue = overriden ? 1 : 0; + const auto overridenChanged = (_wallPaperOverriden != overridenValue); + if (overridenChanged) { + _wallPaperOverriden = overridenValue; + session().changes().peerUpdated(this, UpdateFlag::ChatWallPaper); + } +} + bool PeerData::wallPaperOverriden() const { return _wallPaperOverriden != 0; } diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 6723b31532e746..c0ac36de1d950f 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; @@ -31,9 +32,18 @@ class Session; } // namespace Main namespace Tdb { +class TLbotMenuButton; class TLprofilePhoto; class TLchatPhotoInfo; class TLchatPhoto; +class TLbotMenuButton; +class TLchatNotificationSettings; +class TLchatActionBar; +class TLfile; +class TLchatAvailableReactions; +class TLstring; +class TLbusinessBotManageBar; +class TLemojiStatus; } // namespace Tdb namespace Data { @@ -108,13 +118,22 @@ struct UnavailableReason { [[nodiscard]] static bool IgnoreSensitiveMark( not_null session); + [[nodiscard]] static std::vector Extract( + const Tdb::TLstring &restriction); +#if 0 // mtp [[nodiscard]] static std::vector Extract( const MTPvector *list); +#endif }; +#if 0 // mtp bool ApplyBotMenuButton( not_null info, const MTPBotMenuButton *button); +#endif +bool ApplyBotMenuButton( + not_null info, + const Tdb::TLbotMenuButton *button); enum class AllowedReactionsType : uchar { All, @@ -133,6 +152,7 @@ struct AllowedReactions { const AllowedReactions &) = default; }; +#if 0 // mtp [[nodiscard]] AllowedReactions Parse( const MTPChatReactions &value, int maxCount, @@ -143,6 +163,10 @@ struct AllowedReactions { [[nodiscard]] UserData *UserFromInputMTP( not_null owner, const MTPInputUser &input); +#endif +[[nodiscard]] AllowedReactions Parse( + not_null peer, + const Tdb::TLchatAvailableReactions &value); } // namespace Data @@ -210,7 +234,10 @@ class PeerData { [[nodiscard]] DocumentId backgroundEmojiId() const; bool changeBackgroundEmojiId(DocumentId id); +#if 0 // mtp void setEmojiStatus(const MTPEmojiStatus &status); +#endif + void setEmojiStatus(const Tdb::TLemojiStatus *status); void setEmojiStatus(DocumentId emojiStatusId, TimeId until = 0); [[nodiscard]] DocumentId emojiStatusId() const; @@ -285,6 +312,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(); @@ -314,14 +351,16 @@ class PeerData { return _nameFirstLetters; } - void setPhotoFull(const Tdb::TLchatPhoto &data); + 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, @@ -395,7 +434,10 @@ class PeerData { return _about; } +#if 0 // mtp void checkFolder(FolderId folderId); +#endif + void setCanRevokeFullHistory(bool can); void setBarSettings(PeerBarSettings which) { _barSettings.set(which); @@ -415,6 +457,7 @@ class PeerData { [[nodiscard]] UserData *businessBot() const; [[nodiscard]] QString businessBotManageUrl() const; void clearBusinessBot(); + void updateBusinessBot(const Tdb::TLbusinessBotManageBar *bar); enum class TranslationFlag : uchar { Unknown, @@ -425,11 +468,14 @@ class PeerData { [[nodiscard]] TranslationFlag translationFlag() const; void saveTranslationDisabled(bool disabled); +#if 0 // mtp void setBarSettings(const MTPPeerSettings &data); bool changeColorIndex(const tl::conditional &cloudColorIndex); bool changeBackgroundEmojiId( const tl::conditional &cloudBackgroundEmoji); bool changeColor(const tl::conditional &cloudColor); +#endif + void setActionBar(const Tdb::TLchatActionBar *bar); enum class BlockStatus : char { Unknown, @@ -476,6 +522,7 @@ class PeerData { void setWallPaper( std::optional paper, bool overriden = false); + void setWallPaperOverriden(bool overriden); [[nodiscard]] bool wallPaperOverriden() const; [[nodiscard]] const Data::WallPaper *wallPaper() const; @@ -490,22 +537,25 @@ 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 // #TODO legacy +#if 0 // mtp void updateUserpic(PhotoId photoId, MTP::DcId dcId, bool hasVideo); #endif void clearUserpic(); void invalidateEmptyUserpic(); - void updateUserpic(FileId fileId, PhotoId photoId = 0); void updateUserpic(const Tdb::TLchatPhotoInfo &photo); void updateUserpic(const Tdb::TLprofilePhoto &photo); + void updateUserpic(const Tdb::TLfile &small, bool hasVideo); private: void fillNames(); @@ -556,6 +606,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 9aef25403f276f..b8ef004378f8cb 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 3ee5ebee3a64bf..28cf9c1fdcc976 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 { @@ -18,6 +22,9 @@ struct BotCommand final { const BotCommand &) = default; }; +#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 8adc31a9e31056..1f0d72dc6bff77 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 e0730b4e43045a..4119f80c5858d5 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 74cfcb2e44b402..eba4d07607a6b6 100644 --- a/Telegram/SourceFiles/data/data_peer_id.cpp +++ b/Telegram/SourceFiles/data/data_peer_id.cpp @@ -7,44 +7,79 @@ For license and copyright information please follow this link: */ #include "data/data_peer_id.h" -#include "tdb/details/tdb_tl_core.h" +#include "tdb/tdb_tl_scheme.h" -PeerId peerFromTdbChat(Tdb::TLint53 id) noexcept { - //constexpr int64 MIN_SECRET_ID = -2002147483648ll; // From TDLib. - //constexpr int64 ZERO_SECRET_ID = -2000000000000ll; - //constexpr int64 MAX_SECRET_ID = -1997852516353ll; - constexpr int64 MIN_CHANNEL_ID = -1997852516352ll; - constexpr int64 MAX_CHANNEL_ID = -1000000000000ll; - constexpr int64 MIN_CHAT_ID = -999999999999ll; - //constexpr int64 MAX_USER_ID = 999999999999ll; - if (id.v > 0) { +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 > MIN_CHAT_ID) { + } else if (id.v < 0 && id.v >= -MAX_CHAT_ID) { return ChatId(BareId(-id.v)); - } else if (id.v < MAX_CHANNEL_ID && id.v > MIN_CHANNEL_ID) { - return ChannelId(BareId(MAX_CHANNEL_ID - 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 MIN_SECRET_ID = -2002147483648ll; // From TDLib. - //constexpr int64 ZERO_SECRET_ID = -2000000000000ll; - //constexpr int64 MAX_SECRET_ID = -1997852516353ll; - //constexpr int64 MIN_CHANNEL_ID = -1997852516352ll; - constexpr int64 MAX_CHANNEL_ID = -1000000000000ll; - //constexpr int64 MIN_CHAT_ID = -999999999999ll; - //constexpr int64 MAX_USER_ID = 999999999999ll; + 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(MAX_CHANNEL_ID - int64(channelId.bare)); + 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 9f1d8d8af3f263..60161056972dce 100644 --- a/Telegram/SourceFiles/data/data_peer_id.h +++ b/Telegram/SourceFiles/data/data_peer_id.h @@ -7,20 +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; @@ -33,6 +48,18 @@ 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) { } @@ -104,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; @@ -140,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 { @@ -224,6 +262,9 @@ bool operator>=(PeerIdZero, PeerId) = delete; } [[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(); @@ -239,6 +280,21 @@ bool operator>=(PeerIdZero, PeerId) = delete; [[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 73d041c085e59e..f6d5b006a74fc5 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -20,6 +20,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 { @@ -302,6 +304,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 0a2d37f8f32c9c..432afd59f96f00 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -18,10 +18,15 @@ 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 "ui/image/image_location_factory.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; @@ -36,6 +41,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) @@ -82,6 +140,130 @@ std::optional PhotoData::extendedMediaVideoDuration() const { : std::optional(); } +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(); + + _dateOrExtendedVideoDuration = 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::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); + }; + + _dateOrExtendedVideoDuration = 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; +} + +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); + } +} + Data::Session &PhotoData::owner() const { return *_owner; } @@ -263,6 +445,7 @@ bool PhotoData::replyPreviewLoaded(bool spoiler) const { return _replyPreview->loaded(spoiler); } +#if 0 // mtp void PhotoData::setRemoteLocation( int32 dc, uint64 access, @@ -310,6 +493,7 @@ void PhotoData::collectLocalData(not_null local) { _owner->keepAlive(std::move(media)); } } +#endif bool PhotoData::isNull() const { return !_images[PhotoSizeIndex(PhotoSize::Large)].location.valid(); @@ -407,6 +591,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; @@ -578,6 +767,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(), @@ -585,4 +787,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 3cd749a71512ba..4da96f710fbe97 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; @@ -72,6 +95,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, @@ -85,6 +109,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 @@ -184,8 +209,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; bool _extendedMediaPreview = false; diff --git a/Telegram/SourceFiles/data/data_photo_media.cpp b/Telegram/SourceFiles/data/data_photo_media.cpp index af01588f455a80..e5eac10a66dba8 100644 --- a/Telegram/SourceFiles/data/data_photo_media.cpp +++ b/Telegram/SourceFiles/data/data_photo_media.cpp @@ -37,7 +37,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 { @@ -186,6 +186,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()); @@ -199,6 +200,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 64a3176d55979f..e8e4049db08367 100644 --- a/Telegram/SourceFiles/data/data_poll.cpp +++ b/Telegram/SourceFiles/data/data_poll.cpp @@ -14,12 +14,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) { @@ -38,6 +41,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) @@ -45,6 +56,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; } @@ -67,6 +82,7 @@ bool PollData::closeByTimer() { return true; } +#if 0 // mtp bool PollData::applyChanges(const MTPDpoll &poll) { Expects(poll.vid().v == id); @@ -127,7 +143,136 @@ bool PollData::applyChanges(const MTPDpoll &poll) { ++version; return true; } +#endif + +bool PollData::applyChanges(const TLpoll &poll) { + Expects(poll.data().vid().v == id); + + const auto &fields = poll.data(); + const auto newQuestion = Api::FormattedTextFromTdb(fields.vquestion()); + 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.text = Api::FormattedTextFromTdb(data.data().vtext()); + result.option = result.text.text.toUtf8(); + 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(); @@ -183,6 +328,7 @@ bool PollData::applyResults(const MTPPollResults &results) { return changed; }); } +#endif bool PollData::checkResultsReload(crl::time now) { if (_lastResultsUpdate > 0 @@ -203,6 +349,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) { @@ -229,6 +385,29 @@ bool PollData::applyResultToAnswers( return changed; }); } +#endif + +bool PollData::applyResultToAnswers( + const TLDpollOption &result, + bool isMinResults) { + const auto formatted = Api::FormattedTextFromTdb(result.vtext()); + const auto option = formatted.text.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) { @@ -261,6 +440,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( @@ -328,3 +508,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 49dd021734b156..7a4f3ba6835df2 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; @@ -48,13 +55,19 @@ struct PollData { using Flags = base::flags; bool closeByTimer(); +#if 0 // mtp bool applyChanges(const MTPDpoll &poll); bool applyResults(const MTPPollResults &results); +#endif [[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 +94,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 +104,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_premium_limits.cpp b/Telegram/SourceFiles/data/data_premium_limits.cpp index 6421f722a77e60..717573907520a2 100644 --- a/Telegram/SourceFiles/data/data_premium_limits.cpp +++ b/Telegram/SourceFiles/data/data_premium_limits.cpp @@ -10,6 +10,9 @@ For license and copyright information please follow this link: #include "main/main_app_config.h" #include "main/main_session.h" +#include "main/main_account.h" +#include "tdb/tdb_options.h" + namespace Data { PremiumLimits::PremiumLimits(not_null session) @@ -221,9 +224,13 @@ LevelLimits::LevelLimits(not_null session) } int LevelLimits::channelColorLevelMin() const { + const auto options = &_session->account().options(); + return options->channelCustomAccentColorBoostLevelMin(); +#if 0 // mtp return _session->appConfig().get( u"channel_color_level_min"_q, 5); +#endif } int LevelLimits::channelBgIconLevelMin() const { diff --git a/Telegram/SourceFiles/data/data_premium_subscription_option.h b/Telegram/SourceFiles/data/data_premium_subscription_option.h index 6d5bd5e7339e1c..6f288f788659da 100644 --- a/Telegram/SourceFiles/data/data_premium_subscription_option.h +++ b/Telegram/SourceFiles/data/data_premium_subscription_option.h @@ -16,6 +16,8 @@ struct PremiumSubscriptionOption { QString costTotal; QString total; QString botUrl; + + Fn startPayment; }; using PremiumSubscriptionOptions = std::vector; diff --git a/Telegram/SourceFiles/data/data_pts_waiter.cpp b/Telegram/SourceFiles/data/data_pts_waiter.cpp index 316ef0a41fd5c0..477d3489c8ced6 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 e35309aa7b6af5..31a9d5cf9541e8 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 2a962dc1b6bc1a..e01b1729d5fab2 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(); } @@ -529,6 +538,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)); @@ -577,11 +625,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) { @@ -623,6 +703,7 @@ void RepliesList::loadBefore() { _history, Histories::RequestType::History, send); +#endif } void RepliesList::loadAfter() { @@ -631,8 +712,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, @@ -668,8 +773,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({}); }); @@ -734,6 +841,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); @@ -776,8 +921,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( @@ -936,6 +1084,7 @@ void RepliesList::requestUnreadCount() { } } }; +#if 0 // tdlib getMessageThread doesn't provide unread info for topics. _reloadUnreadCountRequestId = session->api().request( MTPmessages_GetDiscussionMessage( _history->peer->input, @@ -952,6 +1101,7 @@ void RepliesList::requestUnreadCount() { data.vunread_count().v); }); }).send(); +#endif } void RepliesList::readTill(not_null item) { @@ -999,6 +1149,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( @@ -1009,6 +1172,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 42f56c1aec7ebf..a9a6f3a755ab16 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 { @@ -91,7 +95,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_saved_messages.cpp b/Telegram/SourceFiles/data/data_saved_messages.cpp index 9a4f4441f91897..fc5a3f9e08170c 100644 --- a/Telegram/SourceFiles/data/data_saved_messages.cpp +++ b/Telegram/SourceFiles/data/data_saved_messages.cpp @@ -15,9 +15,14 @@ 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" +#include "data/data_user.h" + namespace Data { namespace { +using namespace Tdb; + constexpr auto kPerPage = 50; constexpr auto kFirstPerPage = 10; constexpr auto kListPerPage = 100; @@ -72,9 +77,34 @@ void SavedMessages::loadMore(not_null sublist) { _loadMore.call(); } +SavedSublistId SavedMessages::sublistId(PeerData *savedSublistPeer) const { + if (!savedSublistPeer) { + return 0; + } + const auto i = _sublistIds.find(savedSublistPeer->id); + return (i != end(_sublistIds)) ? i->second : 0; +} + +SavedSublistId SavedMessages::sublistId(SavedSublist *sublist) const { + return sublistId(sublist ? sublist->peer().get() : nullptr); +} + +SavedSublist *SavedMessages::sublistById(SavedSublistId id) const { + for (const auto &[peerId, sublistId] : _sublistIds) { + if (sublistId == id) { + const auto peer = _owner->peer(peerId); + const auto i = _sublists.find(peer); + return (i != end(_sublists)) ? i->second.get() : nullptr; + } + } + return nullptr; +} + void SavedMessages::sendLoadMore() { if (_loadMoreRequestId || _chatsList.loaded()) { return; + } +#if 0 // mtp } else if (!_pinnedLoaded) { loadPinned(); } @@ -92,11 +122,19 @@ void SavedMessages::sendLoadMore() { if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) { _unsupported = true; } +#endif + _loadMoreRequestId = _owner->session().sender().request( + TLloadSavedMessagesTopics( + tl_int32(_sublists.empty() ? kListPerPage : kListFirstPerPage)) + ).done([=] { + _loadMoreRequestId = 0; + }).fail([=] { _chatsList.setLoaded(); _loadMoreRequestId = 0; }).send(); } +#if 0 // mtp void SavedMessages::loadPinned() { if (_pinnedRequestId) { return; @@ -114,6 +152,7 @@ void SavedMessages::loadPinned() { _pinnedRequestId = 0; }).send(); } +#endif void SavedMessages::sendLoadMore(not_null sublist) { if (_loadMoreRequests.contains(sublist) || sublist->isFullLoaded()) { @@ -123,6 +162,7 @@ void SavedMessages::sendLoadMore(not_null sublist) { const auto offsetId = list.empty() ? MsgId(0) : list.back()->id; const auto offsetDate = list.empty() ? MsgId(0) : list.back()->date(); const auto limit = offsetId ? kPerPage : kFirstPerPage; +#if 0 // mtp const auto requestId = _owner->session().api().request( MTPmessages_GetSavedHistory( sublist->peer()->input, @@ -150,8 +190,18 @@ void SavedMessages::sendLoadMore(not_null sublist) { count = data.vcount().v; } }); - +#endif + const auto requestId = _owner->session().sender().request( + TLgetSavedMessagesTopicHistory( + tl_int53(sublistId(sublist->peer())), + tl_int53(offsetId.bare), + tl_int32(0), // offset + tl_int32(limit)) + ).done([=](const TLmessages &result) { _loadMoreRequests.remove(sublist); + const auto &data = result.data(); + const auto count = data.vtotal_count().v; + const auto list = &data.vmessages().v; if (!list) { sublist->setFullLoaded(); return; @@ -159,15 +209,27 @@ void SavedMessages::sendLoadMore(not_null sublist) { auto items = std::vector>(); items.reserve(list->size()); for (const auto &message : *list) { +#if 0 // mtp const auto item = owner().addNewMessage( message, {}, NewMessageType::Existing); +#endif + if (!message) { + continue; + } + const auto item = owner().processMessage( + *message, + NewMessageType::Existing); if (item) { items.push_back(item); } } sublist->append(std::move(items), count); + if (list->empty()) { + sublist->setFullLoaded(); + } +#if 0 // mtp if (result.type() == mtpc_messages_messages) { sublist->setFullLoaded(); } @@ -175,12 +237,15 @@ void SavedMessages::sendLoadMore(not_null sublist) { if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) { _unsupported = true; } +#endif + }).fail([=] { sublist->setFullLoaded(); _loadMoreRequests.remove(sublist); }).send(); _loadMoreRequests[sublist] = requestId; } +#if 0 // mtp void SavedMessages::apply( const MTPmessages_SavedDialogs &result, bool pinned) { @@ -247,6 +312,7 @@ void SavedMessages::apply( _offsetPeer = offsetPeer; } } +#endif void SavedMessages::sendLoadMoreRequests() { if (_loadMoreScheduled) { @@ -257,6 +323,7 @@ void SavedMessages::sendLoadMoreRequests() { } } +#if 0 // mtp void SavedMessages::apply(const MTPDupdatePinnedSavedDialogs &update) { const auto list = update.vorder(); if (!list) { @@ -296,5 +363,35 @@ void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) { DEBUG_LOG(("API Error: Folder in updateSavedDialogPinned.")); }); } +#endif + +void SavedMessages::apply(const TLDupdateSavedMessagesTopic &update) { + const auto &data = update.vtopic().data(); + const auto id = data.vid().v; + auto peer = (PeerData*)nullptr; + data.vtype().match([&](const TLDsavedMessagesTopicTypeMyNotes &) { + peer = session().user(); + }, [&](const TLDsavedMessagesTopicTypeAuthorHidden &) { + peer = session().data().peer(PeerData::kSavedHiddenAuthorId); + }, [&](const TLDsavedMessagesTopicTypeSavedFromChat &data) { + peer = session().data().peer(peerFromTdbChat(data.vchat_id())); + }); + _sublistIds[peer->id] = id; + + const auto selfId = _owner->session().userPeerId(); + auto item = (HistoryItem*)nullptr; + if (const auto last = data.vlast_message()) { + item = _owner->processMessage(*last, NewMessageType::Existing); + } + if (item) { + const auto entry = sublist(peer); + const auto entryPinned = data.vis_pinned().v; + entry->applyMaybeLast(item); + } +} + +void SavedMessages::apply(const TLDupdateSavedMessagesTopicCount &update) { + _chatsList.setCloudListSize(update.vtopic_count().v); +} } // namespace Data diff --git a/Telegram/SourceFiles/data/data_saved_messages.h b/Telegram/SourceFiles/data/data_saved_messages.h index 3e09f4db0aac3e..f8a8dd656a2cae 100644 --- a/Telegram/SourceFiles/data/data_saved_messages.h +++ b/Telegram/SourceFiles/data/data_saved_messages.h @@ -9,6 +9,11 @@ For license and copyright information please follow this link: #include "dialogs/dialogs_main_list.h" +namespace Tdb { +class TLDupdateSavedMessagesTopic; +class TLDupdateSavedMessagesTopicCount; +} // namespace Tdb + namespace Main { class Session; } // namespace Main @@ -34,8 +39,16 @@ class SavedMessages final { void loadMore(); void loadMore(not_null sublist); +#if 0 // mtp void apply(const MTPDupdatePinnedSavedDialogs &update); void apply(const MTPDupdateSavedDialogPinned &update); +#endif + void apply(const Tdb::TLDupdateSavedMessagesTopic &update); + void apply(const Tdb::TLDupdateSavedMessagesTopicCount &update); + + [[nodiscard]] SavedSublistId sublistId(PeerData *savedSublistPeer) const; + [[nodiscard]] SavedSublistId sublistId(SavedSublist *sublist) const; + [[nodiscard]] SavedSublist *sublistById(SavedSublistId id) const; private: void loadPinned(); @@ -51,14 +64,17 @@ class SavedMessages final { base::flat_map< not_null, std::unique_ptr> _sublists; + base::flat_map _sublistIds; base::flat_map, mtpRequestId> _loadMoreRequests; mtpRequestId _loadMoreRequestId = 0; mtpRequestId _pinnedRequestId = 0; +#if 0 // mtp TimeId _offsetDate = 0; MsgId _offsetId = 0; PeerData *_offsetPeer = nullptr; +#endif SingleQueuedInvokation _loadMore; base::flat_set> _loadMoreSublistsScheduled; diff --git a/Telegram/SourceFiles/data/data_saved_sublist.cpp b/Telegram/SourceFiles/data/data_saved_sublist.cpp index 2d129a5f67e02c..ef14152f777592 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.cpp +++ b/Telegram/SourceFiles/data/data_saved_sublist.cpp @@ -170,9 +170,11 @@ int SavedSublist::fixedOnTopIndex() const { return 0; } +#if 0 // mtp bool SavedSublist::shouldBeInChatList() const { return isPinnedDialog(FilterId()) || !_items.empty(); } +#endif Dialogs::UnreadState SavedSublist::chatListUnreadState() const { return {}; diff --git a/Telegram/SourceFiles/data/data_saved_sublist.h b/Telegram/SourceFiles/data/data_saved_sublist.h index 15c3428fff42dc..c380512487ed68 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.h +++ b/Telegram/SourceFiles/data/data_saved_sublist.h @@ -43,7 +43,9 @@ class SavedSublist final : public Dialogs::Entry { } 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_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 4e33a9eaa5282e..2dc158485c23f1 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 kHistoryLimit = 50; constexpr auto kDefaultSearchTimeoutMs = crl::time(200); @@ -33,6 +39,7 @@ std::optional PrepareSearchRequest( const QString &query, MsgId messageId, Data::LoadDirection direction) { +#if 0 // goodToRemove const auto filter = [&] { using Type = Storage::SharedMediaType; switch (type) { @@ -69,7 +76,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: @@ -78,6 +129,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; @@ -86,6 +141,19 @@ 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), + Tdb::tl_int53(0)); // saved_messages_topic_id +#if 0 // goodToRemove const auto hash = uint64(0); const auto mtpOffsetId = int(std::clamp( @@ -110,6 +178,7 @@ std::optional PrepareSearchRequest( MTP_int(maxId), MTP_int(minId), MTP_long(hash)); +#endif } SearchResult ParseSearchResult( @@ -121,6 +190,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: { @@ -164,25 +236,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 { @@ -196,6 +276,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; } @@ -204,6 +292,7 @@ HistoryRequest PrepareHistoryRequest( not_null peer, MsgId messageId, Data::LoadDirection direction) { +#if 0 // mtp const auto minId = 0; const auto maxId = 0; const auto limit = kHistoryLimit; @@ -215,6 +304,11 @@ HistoryRequest PrepareHistoryRequest( } Unexpected("Direction in PrepareSearchRequest"); }(); +#endif + const auto offsetId = (messageId < 0 || messageId == ServerMaxMsgId - 1) + ? MsgId() + : messageId; + const auto limit = kHistoryLimit; const auto addOffset = [&] { switch (direction) { case Data::LoadDirection::Before: return 0; @@ -223,6 +317,15 @@ HistoryRequest PrepareHistoryRequest( } Unexpected("Direction in PrepareSearchRequest"); }(); + return Tdb::TLgetChatHistory( + peerToTdbChat(peer->id), + 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), + Tdb::tl_bool(false)); // only_local +#if 0 // mtp const auto hash = uint64(0); const auto offsetDate = int32(0); @@ -239,6 +342,7 @@ HistoryRequest PrepareHistoryRequest( MTP_int(maxId), MTP_int(minId), MTP_long(hash)); +#endif } HistoryResult ParseHistoryResult( @@ -246,12 +350,113 @@ HistoryResult ParseHistoryResult( MsgId messageId, Data::LoadDirection direction, const HistoryRequestResult &data) { +#if 0 // mtp return ParseSearchResult( peer, Storage::SharedMediaType::kCount, messageId, direction, data); +#endif + 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: { + auto &d = data.c_messages_messages(); + peer->owner().processUsers(d.vusers()); + peer->owner().processChats(d.vchats()); + result.fullCount = d.vmessages().v.size(); + return &d.vmessages().v; + } break; + + case mtpc_messages_messagesSlice: { + auto &d = data.c_messages_messagesSlice(); + peer->owner().processUsers(d.vusers()); + peer->owner().processChats(d.vchats()); + result.fullCount = d.vcount().v; + return &d.vmessages().v; + } break; + + case mtpc_messages_channelMessages: { + const auto &d = data.c_messages_channelMessages(); + if (const auto channel = peer->asChannel()) { + channel->ptsReceived(d.vpts().v); + channel->processTopics(d.vtopics()); + } else { + LOG(("API Error: received messages.channelMessages when " + "no channel was passed! (ParseSearchResult)")); + } + peer->owner().processUsers(d.vusers()); + peer->owner().processChats(d.vchats()); + result.fullCount = d.vcount().v; + return &d.vmessages().v; + } break; + + case mtpc_messages_messagesNotModified: { + LOG(("API Error: received messages.messagesNotModified! " + "(ParseSearchResult)")); + return (const QVector*)nullptr; + } break; + } + Unexpected("messages.Messages type in 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); +#endif + if (!messageId && !data.data().vmessages().v.empty()) { + result.noSkipRange.from = ServerMaxMsgId; + } + const auto owner = &peer->owner(); + for (const auto &message : data.data().vmessages().v) { + if (message) { + const auto item = owner->processMessage(*message, addType); + const auto itemId = item->id; + 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 { + switch (direction) { + case Data::LoadDirection::Before: // All old loaded. + return { 0, result.noSkipRange.till }; + case Data::LoadDirection::Around: // All loaded. + return { 0, ServerMaxMsgId }; + case Data::LoadDirection::After: // All new loaded. + return { result.noSkipRange.from, ServerMaxMsgId }; + } + 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; } SearchController::CacheEntry::CacheEntry( @@ -443,12 +648,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( @@ -465,10 +695,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 26b930b9b718a0..e4c2380ad5608e 100644 --- a/Telegram/SourceFiles/data/data_search_controller.h +++ b/Telegram/SourceFiles/data/data_search_controller.h @@ -17,6 +17,13 @@ namespace Main { class Session; } // namespace Main +namespace Tdb { +class TLfoundChatMessages; +class TLsearchChatMessages; +class TLgetChatHistory; +class TLmessages; +} // namespace Tdb + namespace Data { enum class LoadDirection : char; } // namespace Data @@ -29,12 +36,20 @@ 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; using HistoryResult = SearchResult; +#if 0 // mtp using HistoryRequest = MTPmessages_GetHistory; using HistoryRequestResult = MTPmessages_Messages; +#endif +using HistoryRequest = Tdb::TLgetChatHistory; +using HistoryRequestResult = Tdb::TLmessages; [[nodiscard]] 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 00000000000000..f0a7ad082d86d5 --- /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 00000000000000..fa6d95abab592e --- /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 ea8f92bac7ea4c..37cd1d4b285765 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 227ef656995183..59be2fa05fea6a 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 ca0e85948e20cc..7e398899b88aaa 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -79,6 +79,11 @@ For license and copyright information please follow this link: #include "base/random.h" #include "spellcheck/spellcheck_highlight_syntax.h" +#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 { @@ -127,48 +132,12 @@ void CheckForSwitchInlineButton(not_null item) { } } -[[nodiscard]] ChatRestrictions RestrictionsFromPermissions( - const TLchatPermissions & permissions) { - return permissions.match([&](const TLDchatPermissions &data) { - using Flag = ChatRestriction; - const auto bit = [&](const TLbool &check, Flag value) { - return check.v ? value : Flag(0); - }; - 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::InviteUsers) - | bit(data.vcan_pin_messages(), Flag::PinMessages) - | bit(data.vcan_send_media_messages(), Flag::SendMedia) - | bit(data.vcan_send_messages(), Flag::SendMessages) - | 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 AdminRightsFromChatMemberStatus( const TLDchatMemberStatusAdministrator &data) { - using Flag = ChatAdminRight; - const auto bit = [&](const 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_delete_messages(), Flag::DeleteMessages) - | bit(data.vcan_edit_messages(), Flag::EditMessages) - | bit(data.vcan_invite_users(), Flag::InviteUsers) - | bit(data.vcan_manage_voice_chats(), Flag::ManageCall) - | bit(data.vcan_pin_messages(), Flag::PinMessages) - | bit(data.vcan_post_messages(), Flag::PostMessages) - | bit(data.vcan_promote_members(), Flag::AddAdmins) - | bit(data.vcan_restrict_members(), Flag::BanUsers) - | bit(data.vis_anonymous(), Flag::Anonymous); + return AdminRightsFromChatAdministratorRights(data.vrights()); } +#if 0 // mtp [[nodiscard]] InlineImageLocation FindInlineThumbnail( const QVector &sizes) { const auto i = ranges::find( @@ -249,6 +218,7 @@ void CheckForSwitchInlineButton(not_null item) { 0., double(std::numeric_limits::max()))); } +#endif } // namespace @@ -270,8 +240,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) @@ -481,11 +453,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(); } @@ -532,11 +508,11 @@ 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; })); -#if 0 // #TODO legacy auto minimal = false; const MTPUserStatus *status = nullptr; const MTPUserStatus emptyStatus = MTP_userStatusEmpty(); @@ -795,7 +771,6 @@ not_null Session::processUser(const MTPUser &data) { if (flags) { session().changes().peerUpdated(result, flags); } -#endif return result; } @@ -813,7 +788,6 @@ not_null Session::processChat(const MTPChat &data) { return peer(peerFromChannel(data.vid().v)); }); auto minimal = false; -#if 0 // #TODO legacy using UpdateFlag = Data::PeerUpdate::Flag; auto flags = UpdateFlag::None | UpdateFlag::None; data.match([&](const MTPDchat &data) { @@ -1109,30 +1083,26 @@ not_null Session::processChat(const MTPChat &data) { if (flags) { session().changes().peerUpdated(result, flags); } -#endif return result; } +#endif not_null Session::processUser(const TLuser &user) { - const auto &data = user.match([](const auto &data) -> const TLDuser& { - return data; - }); + const auto &data = user.data(); const auto result = this->user(data.vid().v); auto minimal = false; using UpdateFlag = PeerUpdate::Flag; - auto flags = UpdateFlag::None | UpdateFlag::None; + auto updateFlags = UpdateFlag::None | UpdateFlag::None; const auto canShareThisContact = result->canShareThisContactFast(); - const auto firstName = data.vtype().match([]( - const TLDuserTypeDeleted &) { + 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 userName = data.vusername().v; const auto phone = data.vphone_number().v; const auto nameChanged = (result->firstName != firstName) || (result->lastName != lastName); @@ -1140,7 +1110,7 @@ not_null Session::processUser(const TLuser &user) { const auto phoneChanged = (result->phone() != data.vphone_number().v); if (phoneChanged) { result->setPhone(data.vphone_number().v); - flags |= UpdateFlag::PhoneNumber; + updateFlags |= UpdateFlag::PhoneNumber; } const auto isSelf = (UserId(data.vid().v) == session().userId()); const auto showPhone = !result->isServiceUser() @@ -1159,22 +1129,80 @@ not_null Session::processUser(const TLuser &user) { ? Ui::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({}); // #TODO tdlib - //result->setFlags(UserDataFlag::Inaccessible); - //result->setFlags(UserDataFlag::Deleted); - result->setBotInfoVersion(-1); // #TODO tdlib + result->setUnavailableReasons( + Data::UnavailableReason::Extract(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->supportsBusiness + = data.vcan_connect_to_business().v; + 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; + result->botInfo->hasMainApp = data.vhas_main_web_app().v; + }, [&](const TLDuserTypeUnknown) { + result->setFlags(result->flags() | UserDataFlag::Deleted); + result->setBotInfoVersion(-1); + }); result->setIsContact(data.vis_contact().v); if (canShareThisContact != result->canShareThisContactFast()) { - flags |= UpdateFlag::CanShareContact; + updateFlags |= UpdateFlag::CanShareContact; } + using Flag = UserDataFlag; + const auto setting = Flag::Premium + | Flag::Verified + | Flag::SomeRequirePremiumToWrite + | Flag::RequirePremiumToWriteKnown; + const auto flags = (result->flags() & ~setting) + | (data.vis_premium().v ? Flag::Premium : Flag()) + | (data.vis_verified().v ? Flag::Verified : Flag()) + | (data.vrestricts_new_chats().v + ? (Flag::SomeRequirePremiumToWrite + | (result->someRequirePremiumToWrite() + ? (result->requirePremiumToWriteKnown() + ? Flag::RequirePremiumToWriteKnown + : Flag()) + : Flag())) + : Flag()); + result->setFlags(flags); + result->setStoriesState(data.vhas_unread_active_stories().v + ? PeerData::StoriesState::HasUnread + : data.vhas_active_stories().v + ? PeerData::StoriesState::HasRead + : PeerData::StoriesState::None); + if (minimal) { if (!result->isMinimalLoaded()) { result->setLoadedStatus(PeerData::LoadedStatus::Minimal); @@ -1184,42 +1212,82 @@ not_null Session::processUser(const TLuser &user) { result->setLoadedStatus(PeerData::LoadedStatus::Full); } - const auto oldOnlineTill = result->onlineTill; - const auto newOnlineTill = data.vstatus().match([&]( - const TLDuserStatusEmpty &) { - return 0; - }, [&](const TLDuserStatusRecently &) { - return (oldOnlineTill > -10) ? -2 : oldOnlineTill; - }, [&](const TLDuserStatusLastWeek &) { - return -3; - }, [&](const TLDuserStatusLastMonth &) { - return -4; - }, [&](const TLDuserStatusOffline &data) { - return data.vwas_online().v; - }, [&](const TLDuserStatusOnline &data) { - return data.vexpires().v; - }); - if (oldOnlineTill != newOnlineTill) { - result->onlineTill = newOnlineTill; - flags |= UpdateFlag::OnlineStatus; + const auto lastseen = LastseenFromTL(data.vstatus(), result->lastseen()); + if (result->updateLastseen(lastseen)) { + updateFlags |= UpdateFlag::OnlineStatus; + } + result->setEmojiStatus(data.vemoji_status()); + 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 (flags) { - session().changes().peerUpdated(result, flags); + if (updateFlags) { + session().changes().peerUpdated(result, updateFlags); } return result; } not_null Session::processPeer(const TLchat &dialog) { - const auto &data = dialog.match([](const auto &data) -> const TLDchat & { - return data; + 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(data.vtype().type() != id_chatTypeSecret); - const auto result = this->peer(peerFromTdbChat(data.vid())); + 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->peer->setEmojiStatus(data.vemoji_status()); + 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); @@ -1230,25 +1298,40 @@ not_null Session::processPeer(const TLchat &dialog) { } chat->setDefaultRestrictions( RestrictionsFromPermissions(data.vpermissions())); - data.vvoice_chat().match([&](const TLDvoiceChat &data) { - const auto callFlag = ChatDataFlag::CallNotEmpty; - const auto callNotEmpty = data.vhas_participants().v; - chat->setFlags(chat->flags() // #TODO tdlib - | (callNotEmpty ? callFlag : ChatDataFlag(0))); - }); + + 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(); - channel->setDefaultRestrictions( - RestrictionsFromPermissions(data.vpermissions())); - channel->setName(data.vtitle().v, channel->username); + if (!channel->isBroadcast()) { + channel->setDefaultRestrictions( + RestrictionsFromPermissions(data.vpermissions())); + } + channel->setName(data.vtitle().v, channel->username()); if (const auto photo = data.vphoto()) { channel->setPhoto(*photo); @@ -1264,7 +1347,48 @@ not_null Session::processPeer(const TLchat &dialog) { || 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); } @@ -1281,15 +1405,14 @@ not_null Session::processChat(const TLbasicGroup &chat) { }); const auto result = this->chat(data.vid().v); using Flag = ChatDataFlag; - const auto setting = Flag::Deactivated - | Flag::Left - | Flag::Kicked; + 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->addFlags(ChannelDataFlag::Megagroup); + channel->setFlags((channel->flags() & ~ChannelDataFlag::Broadcast) + | ChannelDataFlag::Megagroup); ApplyMigration(result, channel); } data.vstatus().match([&](const TLDchatMemberStatusCreator &data) { @@ -1300,10 +1423,8 @@ not_null Session::processChat(const TLbasicGroup &chat) { result->setAdminRights(data.vis_anonymous().v ? ChatAdminRight::Anonymous : ChatAdminRight()); - //data.vcustom_title().v; // #TODO tdlib }, [&](const TLDchatMemberStatusAdministrator &data) { result->setAdminRights(AdminRightsFromChatMemberStatus(data)); - //data.vcustom_title().v; // #TODO tdlib }, [&](const TLDchatMemberStatusMember &data) { result->setAdminRights(ChatAdminRights()); }, [&](const TLDchatMemberStatusRestricted &data) { @@ -1317,7 +1438,7 @@ not_null Session::processChat(const TLbasicGroup &chat) { flags |= Flag::Left; }, [&](const TLDchatMemberStatusBanned &data) { result->setAdminRights(ChatAdminRights()); - flags |= Flag::Kicked; + flags |= Flag::Forbidden; }); result->setFlags(flags); return result; @@ -1325,14 +1446,20 @@ not_null Session::processChat(const TLbasicGroup &chat) { not_null Session::processChannel( const TLsupergroup &channel) { - const auto &data = channel.match([]( - const auto &data) -> const TLDsupergroup & { - return data; - }); + 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 @@ -1342,9 +1469,11 @@ not_null Session::processChannel( | Flag::Verified //| Flag::f_has_geo | Flag::HasLink - | Flag::Creator; + | Flag::Creator + | Flag::Forbidden + | Flag::Forum; auto flags = (result->flags() & ~setting) - | (!data.vis_channel().v ? Flag::Megagroup : Flag()) + | (data.vis_channel().v ? Flag::Broadcast : Flag::Megagroup) | (data.vis_broadcast_group().v ? Flag::Gigagroup : Flag()) @@ -1359,10 +1488,12 @@ not_null Session::processChannel( //| (data.vhas_location().v ? Flag::f_has_geo : Flag()) | ((data.vstatus().type() == id_chatMemberStatusCreator) ? Flag::Creator - : Flag()); - result->setMembersCount(data.vmember_count().v); - result->setName(result->name, data.vusername().v); - //data.vrestriction_reason(); // #TODO tdlib + : Flag()) + | (data.vis_forum().v ? Flag::Forum : Flag()); + + result->setUnavailableReasons( + Data::UnavailableReason::Extract(data.vrestriction_reason())); + data.vstatus().match([&](const TLDchatMemberStatusCreator &data) { if (!data.vis_member().v) { flags |= Flag::Left; @@ -1371,7 +1502,7 @@ not_null Session::processChannel( ? ChatAdminRight::Anonymous : ChatAdminRight()); result->setRestrictions(ChatRestrictionsInfo()); - //data.vcustom_title().v; // #TODO tdlib + // custom_title is not set here, so ignored. }, [&](const TLDchatMemberStatusAdministrator &data) { using Flag = ChatAdminRight; const auto bit = [&](const TLbool &check, Flag value) { @@ -1379,7 +1510,7 @@ not_null Session::processChannel( }; result->setAdminRights(AdminRightsFromChatMemberStatus(data)); result->setRestrictions(ChatRestrictionsInfo()); - //data.vcustom_title().v; // #TODO tdlib + // custom_title is not set here, so ignored. }, [&](const TLDchatMemberStatusMember &data) { result->setAdminRights(ChatAdminRights()); result->setRestrictions(ChatRestrictionsInfo()); @@ -1404,9 +1535,48 @@ not_null Session::processChannel( result->setRestrictions({ flags, data.vbanned_until_date().v }); }); result->setFlags(flags); + result->setStoriesState(data.vhas_unread_active_stories().v + ? PeerData::StoriesState::HasUnread + : data.vhas_active_stories().v + ? PeerData::StoriesState::HasRead + : PeerData::StoriesState::None); + + result->updateLevelHint(data.vboost_level().v); + if (const auto count = data.vmember_count().v) { + result->setMembersCount(count); + } + 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) { @@ -1422,6 +1592,7 @@ PeerData *Session::processChats(const MTPVector &data) { } return result; } +#endif PeerData *Session::processPeers(const std::vector &data) { auto result = (PeerData*)nullptr; @@ -1456,6 +1627,7 @@ ChannelData *Session::processChannels( return result; } +#if 0 // mtp void Session::applyMaximumChatVersions(const MTPVector &data) { for (const auto &chat : data.v) { chat.match([&](const MTPDchat &data) { @@ -1468,6 +1640,7 @@ void Session::applyMaximumChatVersions(const MTPVector &data) { }); } } +#endif void Session::registerGroupCall(not_null call) { _groupCalls.emplace(call->id(), call); @@ -1644,6 +1817,7 @@ History *Session::historyLoaded(const PeerData *peer) { return peer ? historyLoaded(peer->id) : nullptr; } +#if 0 // mtp void Session::deleteConversationLocally(not_null peer) { const auto markLeft = [&] { if (const auto channel = peer->asMegagroup()) { @@ -1672,6 +1846,7 @@ void Session::deleteConversationLocally(not_null peer) { markLeft(); } } +#endif bool Session::chatsListLoaded(Data::Folder *folder) { return chatsList(folder)->loaded(); @@ -1783,6 +1958,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) { @@ -1804,6 +1980,7 @@ void Session::setupMigrationViewer() { } } }, _lifetime); +#endif } void Session::setupChannelLeavingViewer() { @@ -1817,11 +1994,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); @@ -2479,6 +2658,7 @@ void Session::setChatPinned( notifyPinnedDialogsOrderUpdated(); } +#if 0 // mtp void Session::setPinnedFromEntryList(Dialogs::Key key, bool pinned) { Expects(key.entry()->folderKnown()); @@ -2567,6 +2747,7 @@ 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())); @@ -2591,6 +2772,69 @@ void Session::applyDialogPosition(const TLDupdateChatPosition &data) { } } +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::applyChatAccentColors(const TLDupdateChatAccentColors &data) { + if (const auto peer = peerLoaded(peerFromTdbChat(data.vchat_id()))) { + auto flags = PeerUpdate::Flags(); + if (peer->changeColorIndex(data.vaccent_color_id().v)) { + flags |= PeerUpdate::Flag::Color; + } + const auto id = data.vbackground_custom_emoji_id().v; + if (peer->changeBackgroundEmojiId(id)) { + flags |= PeerUpdate::Flag::BackgroundEmoji; + } + if (flags) { + peer->session().changes().peerUpdated(peer, flags); + _peerDecorationsUpdated.fire_copy(peer); + } + } +} + bool Session::pinnedCanPin(not_null entry) const { if (const auto sublist = entry->asSublist()) { const auto saved = &savedMessages(); @@ -2729,6 +2973,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); @@ -2819,6 +3064,7 @@ void Session::processExistingMessages( processMessages(data.vmessages(), NewMessageType::Existing); }); } +#endif const Session::Messages *Session::messagesList(PeerId peerId) const { const auto i = _messages.find(peerId); @@ -2846,6 +3092,7 @@ void Session::registerMessage(not_null item) { } } +#if 0 // mtp void Session::registerMessageTTL(TimeId when, not_null item) { Expects(when > 0); @@ -2939,6 +3186,7 @@ void Session::processNonChannelMessagesDeleted(const QVector &data) { history->requestChatListMessage(); } } +#endif void Session::removeDependencyMessage(not_null item) { const auto i = _dependentMessages.find(item); @@ -3095,6 +3343,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, @@ -3122,25 +3371,34 @@ HistoryItem *Session::addNewMessage( } return result; } +#endif -HistoryItem *Session::addNewMessage( - const TLmessage &data, - MessageFlags localFlags, - NewMessageType type) { - const auto peerId = peerFromTdbChat(data.data().vchat_id()); - Assert(peerId != 0); +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); +} - const auto result = history(peerId)->addNewMessage( - data.data().vid().v, - data, - localFlags, - type); - if (result && type == NewMessageType::Unread) { - CheckForSwitchInlineButton(result); - } +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; } +not_null Session::processMessage( + BusinessShortcutId shortcutId, + const TLquickReplyMessage &message, + NewMessageType type) { + const auto history = this->history(session().userPeerId()); + return history->addMessage(shortcutId, message); +} + int Session::unreadBadge() const { return computeUnreadBadge(_chatsList.unreadState()); } @@ -3209,6 +3467,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() @@ -3240,6 +3499,7 @@ void Session::checkSelfDestructItems() { _selfDestructTimer.callOnce(nextDestructIn); } } +#endif not_null Session::photo(PhotoId id) { auto i = _photos.find(id); @@ -3251,6 +3511,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); @@ -3314,6 +3575,7 @@ not_null Session::processPhoto( return photo(data.vid().v); }); } +#endif not_null Session::photo( PhotoId id, @@ -3347,6 +3609,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) { @@ -3514,6 +3796,7 @@ void Session::photoApplyFields( : 0)); } } +#endif void Session::photoApplyFields( not_null photo, @@ -3532,7 +3815,9 @@ void Session::photoApplyFields( if (!date) { return; } +#if 0 // mtp photo->setRemoteLocation(dc, access, fileReference); +#endif photo->setFields(date, hasStickers); photo->updateImages( inlineThumbnailBytes, @@ -3554,6 +3839,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); @@ -3779,16 +4065,20 @@ 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 not_null Session::venueIconDocument(const QString &icon) { const auto i = _venueIcons.find(icon); if (i != end(_venueIcons)) { return i->second; } +#if 0 // mtp const auto result = documentFromWeb(MTP_webDocumentNoProxy( MTP_string(u"https://ss3.4sqi.net/img/categories_v2/"_q + icon @@ -3796,10 +4086,36 @@ not_null Session::venueIconDocument(const QString &icon) { MTP_int(0), MTP_string("image/png"), MTP_vector()), {}, {}); +#endif + const auto result = document(base::RandomValue()); + result->setMimeString(u"image/png"_q); + result->setContentUrl(u"https://ss3.4sqi.net/img/categories_v2/"_q + + icon + + u"_64.png"_q); + _venueIcons.emplace(icon, result); return result; } +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) { auto i = _webpages.find(id); if (i == _webpages.cend()) { @@ -3810,6 +4126,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: @@ -3866,6 +4183,7 @@ not_null Session::processWebpage( : (base::unixtime::now() + kDefaultPendingTimeout)); return result; } +#endif not_null Session::webpage( WebPageId id, @@ -3929,6 +4247,7 @@ not_null Session::webpage( return result; } +#if 0 // mtp void Session::webpageApplyFields( not_null page, const MTPDwebPage &data) { @@ -4094,6 +4413,7 @@ void Session::webpageApplyFields( data.is_has_large_media(), pendingTill); } +#endif void Session::webpageApplyFields( not_null page, @@ -4124,16 +4444,20 @@ void Session::webpageApplyFields( storyId, photo, document, +#if 0 // mtp std::move(collage), std::move(iv), +#endif std::move(stickerSet), duration, author, hasLargeMedia, pendingTill); +#if 0 // mtp if (requestPending) { _session->api().requestWebPageDelayed(page); } +#endif if (changed) { notifyWebPageUpdateDelayed(page); } @@ -4147,11 +4471,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, @@ -4173,6 +4499,7 @@ not_null Session::game( return result; } +#if 0 // mtp void Session::gameConvert( not_null original, const MTPGame &data) { @@ -4212,6 +4539,7 @@ void Session::gameApplyFields( processPhoto(data.vphoto()), document ? processDocument(*document).get() : nullptr); } +#endif void Session::gameApplyFields( not_null game, @@ -4233,6 +4561,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)) @@ -4271,6 +4600,7 @@ BotAppData *Session::processBotApp( return (BotAppData*)nullptr; }); } +#endif not_null Session::poll(PollId id) { auto i = _polls.find(id); @@ -4280,6 +4610,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; @@ -4304,6 +4635,7 @@ not_null Session::processPoll(const MTPDmessageMediaPoll &data) { } return result; } +#endif void Session::checkPollsClosings() { const auto now = base::unixtime::now(); @@ -4328,6 +4660,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(); @@ -4387,6 +4732,7 @@ void Session::applyUpdate(const MTPDupdateChatDefaultBannedRights &update) { } } } +#endif not_null Session::location(const LocationPoint &point) { const auto i = _locations.find(point); @@ -4407,6 +4753,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 TLlinkPreview &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) { @@ -4812,6 +5223,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); @@ -4821,6 +5233,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) { @@ -4849,6 +5262,7 @@ not_null Session::contactsNoChatsList() { return &_contactsNoChatsList; } +#if 0 // mtp void Session::refreshChatListEntry(Dialogs::Key key) { Expects(key.entry()->folderKnown()); @@ -4946,6 +5360,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 { @@ -4961,6 +5436,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, @@ -5054,6 +5544,7 @@ void Session::insertCheckedServiceNotification( } sendHistoryChangeNotifications(); } +#endif void Session::setMimeForwardIds(MessageIdsList &&list) { _mimeForwardIds = std::move(list); @@ -5093,6 +5584,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); @@ -5105,6 +5608,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); @@ -5155,6 +5660,7 @@ uint64 Session::wallpapersHash() const { return _wallpapersHash; } +#if 0 // mtp MTP::DcId Session::statsDcId(not_null peer) { const auto it = _peerStatsDcIds.find(peer); return (it == end(_peerStatsDcIds)) ? MTP::DcId(0) : it->second; @@ -5167,18 +5673,28 @@ void Session::applyStatsDcId( _peerStatsDcIds[peer] = dcId; } } +#endif void Session::saveViewAsMessages( not_null forum, bool viewAsMessages) { const auto channel = forum->channel(); if (const auto requestId = _viewAsMessagesRequests.take(channel)) { +#if 0 // mtp _session->api().request(*requestId).cancel(); +#endif + _session->sender().request(*requestId).cancel(); } +#if 0 // mtp _viewAsMessagesRequests[channel] = _session->api().request( MTPchannels_ToggleViewForumAsMessages( channel->inputChannel, MTP_bool(viewAsMessages)) +#endif + _viewAsMessagesRequests[channel] = _session->sender().request( + TLtoggleChatViewAsTopics( + peerToTdbChat(channel->id), + tl_bool(!viewAsMessages)) ).done([=] { _viewAsMessagesRequests.remove(channel); }).fail([=] { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index fccfe8bc3b2808..1662395b0ca032 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -14,7 +14,40 @@ For license and copyright information please follow this link: #include "history/history_location_manager.h" #include "base/timer.h" -#include "tdb/tdb_tl_scheme.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 TLlinkPreview; +class TLfile; +class TLmessage; +class TLquickReplyMessage; +class TLpoll; +class TLDupdateChatPosition; +class TLDupdateChatLastMessage; +class TLDupdateChatPermissions; +class TLDupdateChatTitle; +class TLDupdateChatPhoto; +class TLDupdateChatAccentColors; +} // namespace Tdb + +enum class SimpleDocumentType; + +struct HistoryMessageMarkupData; class Image; class HistoryItem; @@ -73,6 +106,9 @@ class BusinessInfo; struct ReactionId; struct UnavailableReason; +struct PhotoLocalData; +struct DocumentLocalData; + struct RepliesReadTillUpdate { FullMsgId id; MsgId readTillId; @@ -91,6 +127,17 @@ struct GiftUpdate { Action action = {}; }; +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; @@ -208,17 +255,23 @@ 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); @@ -226,7 +279,9 @@ class Session final { 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); @@ -264,7 +319,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; @@ -384,6 +441,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); @@ -396,6 +455,7 @@ class Session final { const QVector &messages, const QVector &dialogs, std::optional count = std::nullopt); +#endif [[nodiscard]] bool pinnedCanPin(not_null entry) const; [[nodiscard]] bool pinnedCanPin( @@ -424,14 +484,18 @@ class Session final { [[nodiscard]] const std::vector &pinnedChatsOrder( not_null saved) 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, @@ -444,6 +508,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); @@ -463,6 +528,7 @@ class Session final { void processMessagesDeleted( PeerId peerId, const QVector &data); +#endif [[nodiscard]] MsgId nextLocalMessageId(); [[nodiscard]] HistoryItem *message( @@ -519,6 +585,7 @@ class Session final { return _documentLoadProgress.events(); } +#if 0 // mtp HistoryItem *addNewMessage( const MTPMessage &data, MessageFlags localFlags, @@ -528,10 +595,17 @@ class Session final { const MTPMessage &data, MessageFlags localFlags, NewMessageType type); +#endif - HistoryItem *addNewMessage( - const Tdb::TLmessage &data, - MessageFlags localFlags, + not_null processMessage( + const Tdb::TLmessage &message, + MsgId oldMessageId = 0); + not_null processMessage( + const Tdb::TLmessage &message, + NewMessageType type); + not_null processMessage( + BusinessShortcutId shortcutId, + const Tdb::TLquickReplyMessage &message, NewMessageType type); [[nodiscard]] int unreadBadge() const; @@ -546,14 +620,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, @@ -568,19 +646,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, @@ -594,6 +681,7 @@ class Session final { bool isPremiumSticker, int32 dc, int64 size); +#if 0 // mtp void documentConvert( not_null original, const MTPDocument &data); @@ -601,13 +689,22 @@ 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 venueIconDocument( const QString &icon); [[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, @@ -631,7 +728,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, @@ -640,23 +739,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::TLlinkPreview &data); + not_null processGame(const Tdb::TLgame &data); + not_null processPoll(const Tdb::TLpoll &data); + void registerPhotoItem( not_null photo, not_null item); @@ -734,8 +860,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); @@ -756,8 +884,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; @@ -768,10 +898,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(); @@ -781,7 +916,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; @@ -797,8 +937,10 @@ 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 viewTagsChanged( not_null view, @@ -809,6 +951,10 @@ class Session final { 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 applyChatAccentColors(const Tdb::TLDupdateChatAccentColors &data); private: using Messages = std::unordered_map>; @@ -820,19 +966,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); @@ -841,12 +993,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, @@ -862,6 +1016,7 @@ class Session final { const ImageWithLocation &videoLarge, crl::time videoStartTime); +#if 0 // mtp void documentApplyFields( not_null document, const MTPDocument &data); @@ -893,6 +1048,7 @@ class Session final { void webpageApplyFields( not_null page, const MTPDwebPage &data); +#endif void webpageApplyFields( not_null page, WebPageType type, @@ -912,9 +1068,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, @@ -929,6 +1087,7 @@ class Session final { not_null item, Method method); +#if 0 // mtp void insertCheckedServiceNotification( const TextWithEntities &message, const MTPMessageMedia &media, @@ -936,6 +1095,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(); @@ -990,26 +1151,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; @@ -1031,9 +1198,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; @@ -1107,7 +1276,9 @@ class Session final { base::flat_map, TimeId> _watchingForOffline; base::Timer _watchForOfflineTimer; +#if 0 // mtp base::flat_map, MTP::DcId> _peerStatsDcIds; +#endif rpl::event_stream _webViewResultSent; diff --git a/Telegram/SourceFiles/data/data_shared_media.cpp b/Telegram/SourceFiles/data/data_shared_media.cpp index 6834190bf70a92..b21955e11070bc 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_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 0d9671f539ba57..d01a848600b4fa 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,76 @@ 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 }); + }); +} + +[[nodiscard]] StoryViews FromTL( + not_null owner, + const TLstoryInteractions &views) { + const auto &data = views.data(); + auto slice = StoryViews{ + .nextOffset = data.vnext_offset().v, + .reactions = data.vtotal_reaction_count().v, + .forwards = data.vtotal_forward_count().v, + .total = data.vtotal_count().v, + }; + slice.list.reserve(data.vinteractions().v.size()); + for (const auto &reaction : data.vinteractions().v) { + const auto &data = reaction.data(); + const auto actorId = peerFromSender(data.vactor_id()); + const auto date = data.vinteraction_date().v; + data.vtype().match([&](const TLDstoryInteractionTypeView &data) { + slice.list.push_back({ + .peer = owner->peer(actorId), + .reaction = (data.vchosen_reaction_type() + ? ReactionFromTL(*data.vchosen_reaction_type()) + : Data::ReactionId()), + .date = date, + }); + }, [&](const TLDstoryInteractionTypeRepost &data) { + const auto story = owner->stories().applySingle(data.vstory()); + if (story) { + slice.list.push_back({ + .peer = story->peer(), + .repostId = story->id(), + }); + } + }, [&](const TLDstoryInteractionTypeForward &data) { + const auto item = owner->processMessage( + data.vmessage(), + NewMessageType::Existing); + if (item) { + slice.list.push_back({ + .peer = item->history()->peer, + .forwardId = item->id, + }); + } + }); + } + return slice; +} } // namespace @@ -117,9 +193,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, [=] { @@ -162,6 +240,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); @@ -245,6 +324,7 @@ Story *Stories::applySingle(PeerId peerId, const MTPstoryItem &story) { : base::make_unexpected(NoStory::Deleted); return value ? value->get() : nullptr; } +#endif void Stories::requestPeerStories( not_null peer, @@ -263,6 +343,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) { @@ -270,6 +355,7 @@ void Stories::requestPeerStories( _owner->processUsers(data.vusers()); _owner->processChats(data.vchats()); parseAndApply(data.vstories()); +#endif finish(); }).fail([=] { applyDeletedFromSources(peer->id, StorySourcesList::NotHidden); @@ -278,6 +364,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) @@ -332,6 +419,7 @@ void Stories::processExpired() { scheduleExpireTimer(); } } +#endif Stories::Set *Stories::lookupArchive(not_null peer) { const auto peerId = peer->id; @@ -363,18 +451,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(); @@ -417,6 +518,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() @@ -424,6 +527,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( @@ -434,22 +539,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]; @@ -493,12 +615,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); @@ -507,6 +631,7 @@ Story *Stories::parseAndApply( return result; } +#if 0 // mtp StoryIdDates Stories::parseAndApply( not_null peer, const MTPstoryItem &story, @@ -540,6 +665,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); @@ -600,6 +737,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( @@ -629,9 +777,49 @@ 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, + }; +} + +Story *Stories::applySingle(const Tdb::TLstory &story) { + return parseAndApply(story, base::unixtime::now()); +} + void Stories::preloadListsMore() { if (_loadMoreRequestId[static_cast(StorySourcesList::NotHidden)] || _loadMoreRequestId[static_cast(StorySourcesList::Hidden)]) { @@ -681,6 +869,7 @@ void Stories::pushHiddenCountsToFolder() { _folderForHidden->updateStoriesCount(count, unread); } +#if 0 // mtp void Stories::sendResolveRequests() { if (!_resolveSent.empty()) { return; @@ -760,6 +949,7 @@ void Stories::processResolvedStories( }); } } +#endif void Stories::finalizeResolve(FullStoryId id) { const auto already = lookup(id); @@ -790,7 +980,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( @@ -948,12 +1140,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(); @@ -965,6 +1161,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), @@ -972,6 +1177,7 @@ void Stories::sendReaction(FullStoryId id, Data::ReactionId reaction) { MTP_int(id.story), ReactionToMTP(reaction) )).send(); +#endif } } @@ -1053,6 +1259,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(), [=] { @@ -1063,6 +1298,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) { @@ -1114,20 +1350,24 @@ void Stories::markAsRead(FullStoryId id, bool viewed) { return; } const auto story = *maybeStory; +#if 0 // mtp if (story->expired() && story->inProfile()) { _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) { @@ -1186,6 +1426,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); @@ -1195,6 +1440,7 @@ void Stories::toggleHidden( MTP_bool(hidden) )).send(); } +#endif const auto name = peer->shortName(); const auto guard = gsl::finally([&] { @@ -1211,7 +1457,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; } @@ -1255,6 +1508,7 @@ void Stories::toggleHidden( } } +#if 0 // mtp void Stories::sendMarkAsReadRequest( not_null peer, StoryId tillId) { @@ -1275,16 +1529,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);) { @@ -1338,6 +1596,7 @@ void Stories::sendIncrementViewsRequests() { _incrementViewsPending.remove(peer); } } +#endif void Stories::loadViewsSlice( not_null peer, @@ -1387,6 +1646,7 @@ void Stories::loadReactionsSlice( _reactionsOffset = offset; _reactionsDone = std::move(done); +#if 0 // mtp using Flag = MTPstories_GetStoryReactionsList::Flag; const auto api = &_owner->session().api(); _owner->session().api().request(_reactionsRequestId).cancel(); @@ -1439,6 +1699,19 @@ void Stories::loadReactionsSlice( } }); } +#endif + const auto api = &_owner->session().sender(); + api->request(_reactionsRequestId).cancel(); + _reactionsRequestId = api->request(TLgetChatStoryInteractions( + peerToTdbChat(_reactionsStoryPeer->id), + tl_int32(_reactionsStoryId), + null, + tl_bool(false), // prefer_forwards + tl_string(_reactionsOffset), + tl_int32(kViewsPerPage) + )).done([=](const TLstoryInteractions &result) { + _reactionsRequestId = 0; + auto slice = FromTL(_owner, result); const auto fullId = FullStoryId{ .peer = _reactionsStoryPeer->id, .story = _reactionsStoryId, @@ -1461,6 +1734,17 @@ void Stories::sendViewsSliceRequest() { Expects(_viewsStoryPeer != nullptr); Expects(_viewsStoryPeer->isSelf()); + session().sender().request(_viewsRequestId).cancel(); + _viewsRequestId = session().sender().request(TLgetStoryInteractions( + tl_int32(_viewsStoryId), + tl_string(), // query + tl_bool(false), // only_contacts + tl_bool(false), // prefer_forwards + tl_bool(true), // prefer_with_reaction + tl_string(_viewsOffset), + tl_int32(_viewsDone ? kViewsPerPage : kPollingViewsPerPage) + )).done([=](const TLstoryInteractions &result) { +#if 0 // mtp using Flag = MTPstories_GetStoryViewsList::Flag; const auto api = &_owner->session().api(); _owner->session().api().request(_viewsRequestId).cancel(); @@ -1472,8 +1756,10 @@ void Stories::sendViewsSliceRequest() { MTP_string(_viewsOffset), MTP_int(_viewsDone ? kViewsPerPage : kPollingViewsPerPage) )).done([=](const MTPstories_StoryViewsList &result) { +#endif _viewsRequestId = 0; +#if 0 // mtp const auto &data = result.data(); auto slice = StoryViews{ .nextOffset = data.vnext_offset().value_or_empty(), @@ -1517,11 +1803,14 @@ void Stories::sendViewsSliceRequest() { } }); } +#endif + auto slice = FromTL(_owner, result); const auto fullId = FullStoryId{ .peer = _owner->session().userPeerId(), .story = _viewsStoryId, }; if (const auto story = lookup(fullId)) { + slice.views = (*story)->views(); (*story)->applyViewsSlice(_viewsOffset, slice); } if (const auto done = base::take(_viewsDone)) { @@ -1539,6 +1828,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( @@ -1561,6 +1855,7 @@ void Stories::sendViewsCountsRequest() { }).fail([=] { _viewsRequestId = 0; }).send(); +#endif } bool Stories::hasArchive(not_null peer) const { @@ -1628,12 +1923,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; @@ -1642,7 +1947,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; @@ -1677,6 +1985,13 @@ void Stories::savedLoadMore(PeerId peerId) { if (saved.requestId || saved.loaded) { return; } + saved.requestId = session().sender().request( + TLgetChatPostedToChatPageStories( + 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( @@ -1684,16 +1999,25 @@ 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 auto pinnedToTopIds = data.vpinned_to_top().value_or_empty(); auto pinnedToTop = pinnedToTopIds | ranges::views::transform(&MTPint::v) | ranges::to_vector; saved.total = data.vcount().v; +#endif + auto pinnedToTopIds = data.vpinned_story_ids().v; + auto pinnedToTop = pinnedToTopIds + | ranges::views::transform(&TLint32::v) + | ranges::to_vector; + 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; @@ -1753,6 +2077,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) { @@ -1769,6 +2102,7 @@ void Stories::deleteList(const std::vector &ids) { applyDeleted(peer, id.v); } }).send(); +#endif } void Stories::toggleInProfileList( @@ -1778,6 +2112,54 @@ void Stories::toggleInProfileList( 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(TLtoggleStoryIsPostedToChatPage( + peerToTdbChat(storyId.peer), + tl_int32(id), + tl_bool(inProfile) + )).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->setInProfile(inProfile); + if (inProfile) { + 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) { @@ -1833,6 +2215,7 @@ void Stories::toggleInProfileList( _savedChanged.fire_copy(peerId); } }).send(); +#endif } bool Stories::canTogglePinnedList( @@ -1873,11 +2256,17 @@ void Stories::togglePinnedList( } const auto peerId = ids.front().peer; auto &saved = _saved[peerId]; +#if 0 // mtp auto list = QVector(); +#endif + auto list = QVector(); list.reserve(maxPinnedCount()); for (const auto &id : saved.ids.pinnedToTop) { if (pin || !ranges::contains(ids, FullStoryId{ peerId, id })) { +#if 0 // mtp list.push_back(MTP_int(id)); +#endif + list.push_back(tl_int32(id)); } } if (pin) { @@ -1886,10 +2275,14 @@ void Stories::togglePinnedList( for (const auto &id : copy) { if (id.peer == peerId && !ranges::contains(saved.ids.pinnedToTop, id.story)) { +#if 0 // mtp list.push_back(MTP_int(id.story)); +#endif + list.push_back(tl_int32(id.story)); } } } +#if 0 // mtp const auto api = &_owner->session().api(); const auto peer = session().data().peer(peerId); api->request(MTPstories_TogglePinnedToTop( @@ -1899,12 +2292,21 @@ void Stories::togglePinnedList( setPinnedToTop(peerId, list | ranges::views::transform(&MTPint::v) | ranges::to_vector); +#endif + _owner->session().sender().request(TLsetChatPinnedStories( + peerToTdbChat(peerId), + tl_vector(list) + )).done([=] { + setPinnedToTop(peerId, list + | ranges::views::transform(&TLint32::v) + | ranges::to_vector); _savedChanged.fire_copy(peerId); }).send(); } bool Stories::isQuitPrevent() { +#if 0 // mtp if (!_markReadPending.empty()) { sendMarkAsReadRequests(); } @@ -1916,6 +2318,8 @@ bool Stories::isQuitPrevent() { } LOG(("Stories prevents quit, marking as read...")); return true; +#endif + return false; } void Stories::incrementPreloadingMainSources() { @@ -1964,6 +2368,7 @@ void Stories::setPreloadingInViewer(std::vector ids) { } } +#if 0 // mtp std::optional Stories::peerSourceState( not_null peer, StoryId storyMaxId) { @@ -1998,14 +2403,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); } @@ -2020,6 +2428,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()); @@ -2042,6 +2454,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) { @@ -2118,14 +2534,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) @@ -2183,6 +2606,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 4e6b0ce88d662d..fc12abaec199ad 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 @@ -147,11 +158,19 @@ 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); + Story *applySingle(const Tdb::TLstory &story); +#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 *applySingle(PeerId peerId, const MTPstoryItem &story); +#endif void loadAround(FullStoryId id, StoriesContext context); const StoriesSource *source(PeerId id) const; @@ -222,6 +241,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; @@ -229,6 +249,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 { @@ -268,6 +289,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, @@ -281,6 +313,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); @@ -294,16 +327,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); @@ -339,15 +376,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; @@ -374,16 +417,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; @@ -405,10 +452,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 9adcad0152ee21..bf577a15aa45ba 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -29,11 +29,18 @@ For license and copyright information please follow this link: #include "ui/text/text_utilities.h" #include "ui/color_int_conversion.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); @@ -117,7 +124,75 @@ 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 &) { + }, [](const TLDstoryAreaTypeMessage &) { + }, [](const TLDstoryAreaTypeLink &) { + }, [](const TLDstoryAreaTypeWeather &) { + }); + 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, + }); + }, [](const TLDstoryAreaTypeMessage &) { + }, [](const TLDstoryAreaTypeLink &) { + }, [](const TLDstoryAreaTypeWeather &) { + }); + return result; +} +#if 0 // mtp [[nodiscard]] auto ParseChannelPost(const MTPMediaArea &area) -> std::optional { auto result = std::optional(); @@ -187,7 +262,73 @@ using UpdateFlag = StoryUpdate::Flag; }); return result; } +#endif +[[nodiscard]] auto ParseChannelPost(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 &) { + }, [&](const TLDstoryAreaTypeMessage &data) { + result.emplace(ChannelPost{ + .area = ParseArea(position), + .itemId = FullMsgId( + peerFromTdbChat(data.vchat_id()), + data.vmessage_id().v), + }); + }, [](const TLDstoryAreaTypeLink &) { + }, [](const TLDstoryAreaTypeWeather &) { + }); + return result; +} + +[[nodiscard]] auto ParseUrlArea(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 &) { + }, [](const TLDstoryAreaTypeMessage &) { + }, [&](const TLDstoryAreaTypeLink &data) { + result.emplace(UrlArea{ + .area = ParseArea(position), + .url = data.vurl().v, + }); + }, [](const TLDstoryAreaTypeWeather &) { + }); + return result; +} + +[[nodiscard]] auto ParseWeatherArea(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 &) { + }, [](const TLDstoryAreaTypeMessage &) { + }, [](const TLDstoryAreaTypeLink &) { + }, [&](const TLDstoryAreaTypeWeather &data) { + result.emplace(WeatherArea{ + .area = ParseArea(position), + .emoji = data.vemoji().v, + .color = Ui::Color32FromSerialized(data.vbackground_color().v), + .millicelsius = int(1000. * std::clamp( + data.vtemperature().v, + -274., + 1'000'000.)), + }); + }); + return result; +} + +#if 0 // mtp [[nodiscard]] PeerData *RepostSourcePeer( not_null owner, const MTPDstoryItem &data) { @@ -228,6 +369,61 @@ using UpdateFlag = StoryUpdate::Flag; } return nullptr; } +#endif + +[[nodiscard]] PeerData *RepostSourcePeer( + not_null owner, + const TLDstory &data) { + if (const auto info = data.vrepost_info()) { + return info->data().vorigin().match([]( + const TLDstoryOriginHiddenUser &) { + return (PeerData*)nullptr; + }, [&](const TLDstoryOriginPublicStory &data) { + return owner->peer(peerFromTdbChat(data.vchat_id())).get(); + }); + } + return nullptr; +} + +[[nodiscard]] QString RepostSourceName(const TLDstory &data) { + if (const auto info = data.vrepost_info()) { + return info->data().vorigin().match([&]( + const TLDstoryOriginHiddenUser &data) { + return data.vsender_name().v; + }, [](const TLDstoryOriginPublicStory &) { + return QString(); + }); + } + return {}; +} + +[[nodiscard]] StoryId RepostSourceId(const TLDstory &data) { + if (const auto info = data.vrepost_info()) { + return info->data().vorigin().match([]( + const TLDstoryOriginHiddenUser &) { + return StoryId(); + }, [&](const TLDstoryOriginPublicStory &data) { + return StoryId(data.vstory_id().v); + }); + } + return {}; +} + +[[nodiscard]] bool RepostModified(const TLDstory &data) { + if (const auto info = data.vrepost_info()) { + return info->data().vis_content_modified().v; + } + return false; +} + +[[nodiscard]] PeerData *FromPeer( + not_null owner, + const TLDstory &data) { + if (const auto sender = data.vsender_id()) { + return owner->peer(peerFromSender(*sender)); + } + return nullptr; +} } // namespace @@ -235,7 +431,10 @@ 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) @@ -244,7 +443,9 @@ Story::Story( , _repostSourceId(RepostSourceId(data)) , _fromPeer(FromPeer(&peer->owner(), data)) , _date(data.vdate().v) +#if 0 // mtp , _expires(data.vexpire_date().v) +#endif , _repostModified(RepostModified(data)) { applyFields(std::move(media), data, now, true); } @@ -270,7 +471,10 @@ bool Story::mine() const { } StoryIdDates Story::idDates() const { +#if 0 // mtp return { _id, _date, _expires }; +#endif + return { _id, _date }; } FullStoryId Story::fullId() const { @@ -281,12 +485,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 { @@ -385,9 +594,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(); @@ -405,11 +620,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 { @@ -516,6 +734,7 @@ int Story::reactions() const { void Story::applyViewsSlice( const QString &offset, const StoryViews &slice) { +#if 0 // mtp const auto changed = (_views.reactions != slice.reactions) || (_views.views != slice.views) || (_views.forwards != slice.forwards) @@ -525,6 +744,8 @@ void Story::applyViewsSlice( _views.views = slice.views; _views.total = slice.total; _views.known = true; +#endif + const auto changed = false; // We update counts only from TLstory. if (offset.isEmpty()) { _views = slice; if (!_channelReactions.total) { @@ -627,11 +848,15 @@ const std::vector &Story::weatherAreas() 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) { @@ -672,14 +897,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() @@ -709,9 +958,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 inProfile = data.vis_posted_to_chat_page().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 { @@ -730,16 +1000,25 @@ void Story::applyFields( auto channelPosts = std::vector(); auto urlAreas = std::vector(); auto weatherAreas = 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()); + channelPosts.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); } else if (auto post = ParseChannelPost(area)) { channelPosts.push_back(*post); @@ -763,7 +1042,13 @@ void Story::applyFields( const auto weatherAreasChanged = (_weatherAreas != weatherAreas); 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_posted_to_chat_page().v; + _privacyPublic = (privacy == StoryPrivacy::Public); _privacyCloseFriends = (privacy == StoryPrivacy::CloseFriends); _privacyContacts = (privacy == StoryPrivacy::Contacts); @@ -853,6 +1138,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; @@ -869,6 +1155,7 @@ void Story::applyViewsCounts(const MTPDstoryViews &data) { _peer->session().changes().storyUpdated(this, UpdateFlag::Reaction); } } +#endif TimeId Story::lastUpdateTime() const { return _lastUpdateTime; @@ -913,7 +1200,9 @@ StoryPreload::StoryPreload(not_null story, Fn done) if (VideoPreload::Can(video)) { _task = std::make_unique( video, +#if 0 // mtp story->fullId(), +#endif std::move(done)); } else { done(); diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 490f979ce29f12..ba15847408661e 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; @@ -149,7 +156,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; @@ -163,7 +173,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; @@ -182,7 +194,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; @@ -225,9 +240,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; [[nodiscard]] bool repost() const; @@ -250,15 +270,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; PeerData * const _repostSourcePeer = nullptr; @@ -277,9 +304,16 @@ class Story final { StoryViews _views; StoryViews _channelReactions; 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 _inProfile : 1 = false; bool _pinnedToTop : 1 = false; bool _privacyPublic : 1 = false; @@ -289,6 +323,7 @@ class Story final { const bool _repostModified : 1 = false; bool _noForwards : 1 = false; bool _edited : 1 = false; + bool _expired : 1 = false; }; diff --git a/Telegram/SourceFiles/data/data_types.cpp b/Telegram/SourceFiles/data/data_types.cpp index ab792cd3052991..32de64fb7ea0cd 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 fbcdf3e3ffccca..388b64c2ab04d3 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; @@ -136,7 +138,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; using EffectId = uint64; @@ -232,8 +237,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), @@ -263,7 +272,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), @@ -327,6 +336,9 @@ enum class MessageFlag : uint64 { SensitiveContent = (1ULL << 47), HasRestrictions = (1ULL << 48), + + SelfDestructImmediate = (1ULL << 62), // added for tdlib + HideNotificationText = (1ULL << 63), }; 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 f3fc4d11ced4db..979c058d257668 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -40,6 +40,7 @@ using UpdateFlag = Data::PeerUpdate::Flag; BotInfo::BotInfo() = default; +#if 0 // mtp Data::LastseenStatus LastseenFromMTP( const MTPUserStatus &status, Data::LastseenStatus currentStatus) { @@ -62,6 +63,33 @@ Data::LastseenStatus LastseenFromMTP( return Data::LastseenStatus::OnlineTill(data.vwas_online().v); }); } +#endif + +Data::LastseenStatus LastseenFromTL( + const TLuserStatus &status, + Data::LastseenStatus currentStatus) { + return status.match([&](const TLDuserStatusEmpty &) { + return Data::LastseenStatus::LongAgo(); + }, [&](const TLDuserStatusRecently &data) { + return currentStatus.isLocalOnlineValue() + ? Data::LastseenStatus::OnlineTill( + currentStatus.onlineTill(), + true, + data.vby_my_privacy_settings().v) + : Data::LastseenStatus::Recently( + data.vby_my_privacy_settings().v); + }, [&](const TLDuserStatusLastWeek &data) { + return Data::LastseenStatus::WithinWeek( + data.vby_my_privacy_settings().v); + }, [&](const TLDuserStatusLastMonth &data) { + return Data::LastseenStatus::WithinMonth( + data.vby_my_privacy_settings().v); + }, [&](const TLDuserStatusOffline &data) { + return Data::LastseenStatus::OnlineTill(data.vwas_online().v); + }, [&](const TLDuserStatusOnline &data) { + return Data::LastseenStatus::OnlineTill(data.vexpires().v); + }); +} UserData::UserData(not_null owner, PeerId id) : PeerData(owner, id) @@ -119,6 +147,11 @@ void UserData::setPhoto(const MTPUserProfilePhoto &photo) { #endif void UserData::setPhoto(const TLprofilePhoto &photo) { + if (photo.data().vis_personal().v) { + addFlags(UserDataFlag::PersonalPhoto); + } else { + removeFlags(UserDataFlag::PersonalPhoto); + } updateUserpic(photo); } @@ -294,6 +327,7 @@ void UserData::setBotInfoVersion(int version) { } } +#if 0 // mtp void UserData::setBotInfo(const MTPBotInfo &info) { switch (info.type()) { case mtpc_botInfo: { @@ -354,7 +388,59 @@ void UserData::setBotInfo(const MTPBotInfo &info) { if (changedCommands || changedButton || privacyChanged) { 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); } } @@ -444,9 +530,11 @@ bool UserData::hasPersonalPhoto() const { return (flags() & UserDataFlag::PersonalPhoto); } +#if 0 // mtp bool UserData::hasStoriesHidden() const { return (flags() & UserDataFlag::StoriesHidden); } +#endif bool UserData::someRequirePremiumToWrite() const { return (flags() & UserDataFlag::SomeRequirePremiumToWrite); @@ -535,15 +623,21 @@ void UserData::setBirthday(Data::Birthday value) { } } +#if 0 // mtp void UserData::setBirthday(const tl::conditional &value) { - if (!value) { +#endif +void UserData::setBirthday(const tl::conditional &value) { + if (!value) { setBirthday(Data::Birthday()); } else { const auto &data = value->data(); setBirthday(Data::Birthday( data.vday().v, data.vmonth().v, + data.vyear().v)); +#if 0 // mtp data.vyear().value_or_empty())); +#endif } } @@ -554,6 +648,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() @@ -674,55 +769,137 @@ 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()) { - user->setPhotoFull(*photo); + if (!update.vpersonal_photo()) { + user->setPhotoFull(*photo); + peerPhotos->unregisterNonPersonalPhoto(user); + } else { + peerPhotos->registerNonPersonalPhoto( + user, + user->owner().processPhoto(*photo)); + } } else { - user->clearPhoto(); - } - //const auto settings = update.vsettings().match([&]( // #TODO tdlib - // const MTPDpeerSettings &data) { - // return data.vflags().v; - //}); - - //MTPDpeerSettings::Flag::f_need_contacts_exception; // #TODO tdlib - //update.vneed_phone_number_privacy_exception(); - //user->setSettings(settings); - - //user->session().api().applyNotifySettings( - // MTP_inputNotifyPeer(user->input), - // update.vnotify_settings()); - - //user->setMessagesTTL(update.vttl_period().value_or_empty()); - - //update.vcommands(); // #TODO tdlib - //update.vdescription(); - //if (const auto info = update.vbot_info()) { - // user->setBotInfo(*info); - //} else { - // user->setBotInfoVersion(-1); - //} - - //if (const auto pinned = update.vpinned_msg_id()) { - // SetTopPinnedMessageId(user, pinned->v); - //} - - //MTPDuserFull::Flag::f_video_calls_available; // #TODO tdlib - //update.vsupports_video_calls(); - //user->setFullFlags(update.vflags().v); - user->setIsBlocked(update.vis_blocked().v); + 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->barSettings()) { + user->setBarSettings(update.vneed_phone_number_privacy_exception().v + ? (*settings | PeerBarSetting::NeedContactsException) + : (*settings & ~PeerBarSetting::NeedContactsException)); + } else if (update.vneed_phone_number_privacy_exception().v) { + user->setBarSettings(PeerBarSetting::NeedContactsException); + } + user->setWallPaperOverriden(update.vset_chat_background().v); + user->setPersonalChannel( + peerToChannel(peerFromTdbChat(update.vpersonal_chat_id())), + MsgId()); + user->setBirthday(update.vbirthdate()); + user->setBusinessDetails(FromTL( + &user->owner(), + update.vbusiness_info())); + if (user->isSelf()) { + const auto info = update.vbusiness_info(); + user->owner().businessInfo().applyAwaySettings( + FromTL(&user->owner(), info + ? info->data().vaway_message_settings() + : nullptr)); + user->owner().businessInfo().applyGreetingSettings( + FromTL(&user->owner(), info + ? info->data().vgreeting_message_settings() + : nullptr)); + } + + 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::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()) + | (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.vbio().v); - //update.vshare_text(); // #TODO tdlib + 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); - //user->checkFolder(update.vfolder_id().value_or_empty()); + + 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(); } diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 135d6687d44afe..e42c671e27f260 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -20,7 +20,11 @@ struct BusinessDetails; } // namespace Data namespace Tdb { +class TLbotInfo; class TLDuserFullInfo; +class TLbotInfo; +class TLbirthdate; +class TLuserStatus; } // namespace Tdb struct BotInfo { @@ -64,7 +68,7 @@ enum class UserDataFlag : uint32 { 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), @@ -85,9 +89,15 @@ enum class UserDataFlag : uint32 { inline constexpr bool is_flag_type(UserDataFlag) { return true; }; using UserDataFlags = base::flags; +#if 0 // mtp [[nodiscard]] Data::LastseenStatus LastseenFromMTP( const MTPUserStatus &status, Data::LastseenStatus currentStatus); +#endif + +[[nodiscard]] Data::LastseenStatus LastseenFromTL( + const Tdb::TLuserStatus &status, + Data::LastseenStatus currentStatus); class UserData final : public PeerData { public: @@ -114,7 +124,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); @@ -145,7 +158,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 someRequirePremiumToWrite() const; [[nodiscard]] bool meRequiresPremiumToWrite() const; [[nodiscard]] bool requirePremiumToWriteKnown() const; @@ -190,7 +205,10 @@ class UserData final : public PeerData { [[nodiscard]] Data::Birthday birthday() const; void setBirthday(Data::Birthday value); +#if 0 // mtp void setBirthday(const tl::conditional &value); +#endif + void setBirthday(const tl::conditional &value); [[nodiscard]] int commonChatsCount() const; void setCommonChatsCount(int count); @@ -213,7 +231,9 @@ class UserData final : public PeerData { [[nodiscard]] MsgId personalChannelMessageId() const; void setPersonalChannel(ChannelId channelId, MsgId messageId); +#if 0 // mtp MTPInputUser inputUser = MTP_inputUserEmpty(); +#endif QString firstName; QString lastName; @@ -254,7 +274,9 @@ class UserData final : public PeerData { namespace Data { +#if 0 // mtp void ApplyUserUpdate(not_null user, const MTPDuserFull &update); +#endif void ApplyUserUpdate( not_null user, diff --git a/Telegram/SourceFiles/data/data_user_photos.cpp b/Telegram/SourceFiles/data/data_user_photos.cpp index 6271f9b4c9c382..579e5ed78140a2 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 0234bdbb410d49..f3ac030381ed64 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) { @@ -233,9 +302,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; @@ -310,6 +381,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(); @@ -318,6 +390,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) { @@ -469,6 +566,7 @@ WallPaper WallPaper::withoutImageData() const { return result; } +#if 0 // mtp std::optional WallPaper::Create( not_null session, const MTPWallPaper &data) { @@ -531,6 +629,59 @@ 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; + }, [&](const TLDbackgroundTypeChatTheme &data) { + result._emojiId = data.vtheme_name().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 @@ -817,3 +968,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 e7bfd2d511d486..69f3494e114313 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 { @@ -53,7 +66,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; @@ -61,7 +76,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; @@ -82,6 +103,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); @@ -90,6 +112,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 d4565627777eaf..cfb4b4df53e7c3 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -18,9 +18,18 @@ For license and copyright information please follow this link: #include "iv/iv_data.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 "data/stickers/data_stickers.h" + +#include namespace { +using namespace Tdb; + +#if 0 // mtp [[nodiscard]] WebPageCollage ExtractCollage( not_null owner, const QVector &items, @@ -117,9 +126,369 @@ 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(); +} + +WebPageType ParseWebPageType(const TLlinkPreviewType &type, bool hasIV) { + const auto article = hasIV + ? WebPageType::ArticleWithIV + : WebPageType::Article; + return type.match([&](const TLDlinkPreviewTypeAlbum &data) { + return WebPageType::Album; + }, [&](const TLDlinkPreviewTypeAnimation &data) { + return WebPageType::Video; + }, [&](const TLDlinkPreviewTypeApp &data) { + return article; + }, [&](const TLDlinkPreviewTypeArticle &data) { + return article; + }, [&](const TLDlinkPreviewTypeAudio &data) { + return WebPageType::Document; + }, [&](const TLDlinkPreviewTypeBackground &data) { + return WebPageType::WallPaper; + }, [&](const TLDlinkPreviewTypeChannelBoost &data) { + return WebPageType::ChannelBoost; + }, [&](const TLDlinkPreviewTypeChat &data) { + return data.vtype().match([&](const TLDinviteLinkChatTypeChannel &) { + return data.vcreates_join_request().v + ? WebPageType::ChannelWithRequest + : WebPageType::Channel; + }, [&](const auto &) { + return data.vcreates_join_request().v + ? WebPageType::GroupWithRequest + : WebPageType::Group; + }); + }, [&](const TLDlinkPreviewTypeDocument &data) { + return WebPageType::Document; + }, [&](const TLDlinkPreviewTypeEmbeddedAnimationPlayer &data) { + return WebPageType::Video; + }, [&](const TLDlinkPreviewTypeEmbeddedAudioPlayer &data) { + return article; + }, [&](const TLDlinkPreviewTypeEmbeddedVideoPlayer &data) { + return WebPageType::Video; + }, [&](const TLDlinkPreviewTypeExternalAudio &data) { + return article; + }, [&](const TLDlinkPreviewTypeExternalVideo &data) { + return WebPageType::Video; + }, [&](const TLDlinkPreviewTypeInvoice &data) { + return article; + }, [&](const TLDlinkPreviewTypeMessage &data) { + return WebPageType::Message; + }, [&](const TLDlinkPreviewTypePhoto &data) { + return WebPageType::Photo; + }, [&](const TLDlinkPreviewTypePremiumGiftCode &data) { + return WebPageType::Giftcode; + }, [&](const TLDlinkPreviewTypeShareableChatFolder &data) { + return article; + }, [&](const TLDlinkPreviewTypeSticker &data) { + return WebPageType::Document; + }, [&](const TLDlinkPreviewTypeStickerSet &data) { + return WebPageType::StickerSet; + }, [&](const TLDlinkPreviewTypeStory &data) { + return WebPageType::Story; + }, [&](const TLDlinkPreviewTypeSupergroupBoost &data) { + return WebPageType::GroupBoost; + }, [&](const TLDlinkPreviewTypeTheme &data) { + return WebPageType::Theme; + }, [&](const TLDlinkPreviewTypeUnsupported &data) { + return article; + }, [&](const TLDlinkPreviewTypeUser &data) { + return data.vis_bot().v ? WebPageType::Bot : WebPageType::User; + }, [&](const TLDlinkPreviewTypeVideo &data) { + return WebPageType::Video; + }, [&](const TLDlinkPreviewTypeVideoChat &data) { + return data.vis_live_stream().v + ? WebPageType::Livestream + : WebPageType::VoiceChat; + }, [&](const TLDlinkPreviewTypeVideoNote &data) { + return WebPageType::Document; + }, [&](const TLDlinkPreviewTypeVoiceNote &data) { + return WebPageType::Document; + }, [&](const TLDlinkPreviewTypeWebApp &data) { + return WebPageType::BotApp; + }); +} + +[[nodiscard]] FullStoryId ExtractStoryId(const TLlinkPreviewType &type) { + return type.match([&](const TLDlinkPreviewTypeStory &data) { + return FullStoryId{ + peerFromTdbChat(data.vstory_sender_chat_id()), + data.vstory_id().v, + }; + }, [&](const auto &) { + return FullStoryId(); + }); +} + +[[nodiscard]] PhotoData *ExtractPhoto( + not_null owner, + const TLlinkPreviewType &type) { + const auto null = (PhotoData*)nullptr; + const auto photo = [&](const auto &from) { + using Type = std::decay_t; + if constexpr (std::is_same_v) { + return owner->processPhoto(from.vphoto()).get(); + } else { + return from.vphoto() + ? owner->processPhoto(*from.vphoto()).get() + : nullptr; + } + }; + return type.match([&](const TLDlinkPreviewTypeAlbum &data) { + return null; + }, [&](const TLDlinkPreviewTypeAnimation &data) { + return null; + }, [&](const TLDlinkPreviewTypeApp &data) { + return photo(data); + }, [&](const TLDlinkPreviewTypeArticle &data) { + return photo(data); + }, [&](const TLDlinkPreviewTypeAudio &data) { + return null; + }, [&](const TLDlinkPreviewTypeBackground &data) { + return null; + }, [&](const TLDlinkPreviewTypeChannelBoost &data) { + return photo(data); + }, [&](const TLDlinkPreviewTypeChat &data) { + return photo(data); + }, [&](const TLDlinkPreviewTypeDocument &data) { + return null; + }, [&](const TLDlinkPreviewTypeEmbeddedAnimationPlayer &data) { + return null; + }, [&](const TLDlinkPreviewTypeEmbeddedAudioPlayer &data) { + return null; + }, [&](const TLDlinkPreviewTypeEmbeddedVideoPlayer &data) { + return null; + }, [&](const TLDlinkPreviewTypeExternalAudio &data) { + return null; + }, [&](const TLDlinkPreviewTypeExternalVideo &data) { + return null; + }, [&](const TLDlinkPreviewTypeInvoice &data) { + return null; + }, [&](const TLDlinkPreviewTypeMessage &data) { + return null; + }, [&](const TLDlinkPreviewTypePhoto &data) { + return photo(data); + }, [&](const TLDlinkPreviewTypePremiumGiftCode &data) { + return null; + }, [&](const TLDlinkPreviewTypeShareableChatFolder &data) { + return null; + }, [&](const TLDlinkPreviewTypeSticker &data) { + return null; + }, [&](const TLDlinkPreviewTypeStickerSet &data) { + return null; + }, [&](const TLDlinkPreviewTypeStory &data) { + return null; + }, [&](const TLDlinkPreviewTypeSupergroupBoost &data) { + return photo(data); + }, [&](const TLDlinkPreviewTypeTheme &data) { + return null; + }, [&](const TLDlinkPreviewTypeUnsupported &data) { + return null; + }, [&](const TLDlinkPreviewTypeUser &data) { + return photo(data); + }, [&](const TLDlinkPreviewTypeVideo &data) { + return null; + }, [&](const TLDlinkPreviewTypeVideoChat &data) { + return photo(data); + }, [&](const TLDlinkPreviewTypeVideoNote &data) { + return null; + }, [&](const TLDlinkPreviewTypeVoiceNote &data) { + return null; + }, [&](const TLDlinkPreviewTypeWebApp &data) { + return photo(data); + }); +} + +[[nodiscard]] DocumentData *ExtractDocument( + not_null owner, + const TLlinkPreviewType &type) { + const auto null = (DocumentData*)nullptr; + const auto document = [&](const auto &document) { + return owner->processDocument(document).get(); + }; + return type.match([&](const TLDlinkPreviewTypeAlbum &data) { + return null; + }, [&](const TLDlinkPreviewTypeAnimation &data) { + return document(data.vanimation()); + }, [&](const TLDlinkPreviewTypeApp &data) { + return null; + }, [&](const TLDlinkPreviewTypeArticle &data) { + return null; + }, [&](const TLDlinkPreviewTypeAudio &data) { + return document(data.vaudio()); + }, [&](const TLDlinkPreviewTypeBackground &data) { + return data.vdocument() ? document(*data.vdocument()) : null; + }, [&](const TLDlinkPreviewTypeChannelBoost &data) { + return null; + }, [&](const TLDlinkPreviewTypeChat &data) { + return null; + }, [&](const TLDlinkPreviewTypeDocument &data) { + return document(data.vdocument()); + }, [&](const TLDlinkPreviewTypeEmbeddedAnimationPlayer &data) { + return null; + }, [&](const TLDlinkPreviewTypeEmbeddedAudioPlayer &data) { + return null; + }, [&](const TLDlinkPreviewTypeEmbeddedVideoPlayer &data) { + return null; + }, [&](const TLDlinkPreviewTypeExternalAudio &data) { + return null; + }, [&](const TLDlinkPreviewTypeExternalVideo &data) { + return null; + }, [&](const TLDlinkPreviewTypeInvoice &data) { + return null; + }, [&](const TLDlinkPreviewTypeMessage &data) { + return null; + }, [&](const TLDlinkPreviewTypePhoto &data) { + return null; + }, [&](const TLDlinkPreviewTypePremiumGiftCode &data) { + return null; + }, [&](const TLDlinkPreviewTypeShareableChatFolder &data) { + return null; + }, [&](const TLDlinkPreviewTypeSticker &data) { + return document(data.vsticker()); + }, [&](const TLDlinkPreviewTypeStickerSet &data) { + return null; + }, [&](const TLDlinkPreviewTypeStory &data) { + return null; + }, [&](const TLDlinkPreviewTypeSupergroupBoost &data) { + return null; + }, [&](const TLDlinkPreviewTypeTheme &data) { + return null; + }, [&](const TLDlinkPreviewTypeUnsupported &data) { + return null; + }, [&](const TLDlinkPreviewTypeUser &data) { + return null; + }, [&](const TLDlinkPreviewTypeVideo &data) { + return document(data.vvideo()); + }, [&](const TLDlinkPreviewTypeVideoChat &data) { + return null; + }, [&](const TLDlinkPreviewTypeVideoNote &data) { + return document(data.vvideo_note()); + }, [&](const TLDlinkPreviewTypeVoiceNote &data) { + return document(data.vvoice_note()); + }, [&](const TLDlinkPreviewTypeWebApp &data) { + return null; + }); +} + +[[nodiscard]] std::unique_ptr ExtractStickerSet( + not_null owner, + const TLlinkPreviewType &type) { + return type.match([&](const TLDlinkPreviewTypeStickerSet &data) { + auto items = std::vector>(); + for (const auto &sticker : data.vstickers().v) { + if (const auto document = owner->processDocument(sticker)) { + if (document->sticker()) { + items.push_back(document); + } + } + } + if (!items.empty()) { + auto result = std::make_unique(); + const auto info = items.front()->sticker(); + result->isEmoji = info->setType == Data::StickersType::Emoji; + result->isTextColor = items.front()->emojiUsesTextColor(); + result->items = std::move(items); + return result; + } + return std::unique_ptr(); + }, [&](const auto &) { + return std::unique_ptr(); + }); +} + +[[nodiscard]] int ExtractDuration(const TLlinkPreviewType &type) { + return type.match([&](const TLDlinkPreviewTypeAnimation &data) { + return data.vanimation().data().vduration().v; + }, [&](const TLDlinkPreviewTypeAudio &data) { + return data.vaudio().data().vduration().v; + }, [&](const TLDlinkPreviewTypeEmbeddedVideoPlayer &data) { + return data.vduration().v; + }, [&](const TLDlinkPreviewTypeVideo &data) { + return data.vvideo().data().vduration().v; + }, [&](const TLDlinkPreviewTypeVideoNote &data) { + return data.vvideo_note().data().vduration().v; + }, [&](const TLDlinkPreviewTypeVoiceNote &data) { + return data.vvoice_note().data().vduration().v; + }, [&](const auto &) { + return 0; + }); +} } // namespace +#if 0 // mtp WebPageType ParseWebPageType( const QString &type, const QString &embedUrl, @@ -176,6 +545,7 @@ WebPageType ParseWebPageType( return WebPageType::Article; } } +#endif bool IgnoreIv(WebPageType type) { return !Iv::ShowButton() @@ -183,6 +553,7 @@ bool IgnoreIv(WebPageType type) { || (type == WebPageType::Album); } +#if 0 // mtp WebPageType ParseWebPageType(const MTPDwebPage &page) { return ParseWebPageType( qs(page.vtype().value_or_empty()), @@ -195,6 +566,7 @@ WebPageCollage::WebPageCollage( const MTPDwebPage &data) : WebPageCollage(ExtractCollage(owner, data)) { } +#endif WebPageData::WebPageData(not_null owner, const WebPageId &id) : id(id) @@ -203,6 +575,61 @@ WebPageData::WebPageData(not_null owner, const WebPageId &id) WebPageData::~WebPageData() = default; +WebPageId WebPageData::IdFromTdb(const TLlinkPreview &data) { + const auto &url = data.data().vurl().v; + return XXH64(url.data(), url.size() * sizeof(ushort), 0); +} + +void WebPageData::setFromTdb(const TLlinkPreview &data) { + const auto &fields = data.data(); + applyChanges( + ParseWebPageType( + fields.vtype(), + fields.vinstant_view_version().v > 0), + fields.vurl().v, + fields.vdisplay_url().v, + fields.vsite_name().v, + fields.vtitle().v, + Api::FormattedTextFromTdb(fields.vdescription()), + ExtractStoryId(fields.vtype()), + ExtractPhoto(_owner, fields.vtype()), + ExtractDocument(_owner, fields.vtype()), + ExtractStickerSet(_owner, fields.vtype()), + ExtractDuration(fields.vtype()), + fields.vauthor().v, + fields.vhas_large_media().v, + 0); + + if (fields.vinstant_view_version().v > 0 && !_collageRequestId) { + const auto ignore = IgnoreIv(type); + const auto owner = _owner; + const auto sender = &_owner->session().sender(); + const auto requestId = sender->preallocateId(); + const auto finish = [owner, id = id, requestId]( + std::unique_ptr iv, + WebPageCollage &&collage) { + const auto that = owner->webpage(id); + if (that->_collageRequestId == requestId) { + that->_collageRequestId = -1; + that->setIv(std::move(iv), std::move(collage)); + } + }; + sender->request(TLgetWebPageInstantView( + fields.vurl(), + tl_bool(false) // force_full + ), requestId).done([=](const TLwebPageInstantView &iv) { + finish( + (ignore + ? nullptr + : std::make_unique(data.data().vurl().v, iv)), + ExtractCollage(owner, iv.data())); + }).fail([finish] { + finish(nullptr, {}); + }).send(); + _collageRequestId = requestId; + } +} + Data::Session &WebPageData::owner() const { return *_owner; } @@ -221,8 +648,10 @@ bool WebPageData::applyChanges( FullStoryId newStoryId, PhotoData *newPhoto, DocumentData *newDocument, +#if 0 // mtp WebPageCollage &&newCollage, std::unique_ptr newIv, +#endif std::unique_ptr newStickerSet, int newDuration, const QString &newAuthor, @@ -259,7 +688,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; @@ -274,9 +705,11 @@ bool WebPageData::applyChanges( && storyId == newStoryId && photo == newPhoto && document == newDocument +#if 0 // mtp && collage.items == newCollage.items && (!iv == !newIv) && (!iv || iv->partial() == newIv->partial()) +#endif && (!stickerSet == !newStickerSet) && duration == newDuration && author == resultAuthor @@ -284,9 +717,11 @@ bool WebPageData::applyChanges( && 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; @@ -297,8 +732,10 @@ bool WebPageData::applyChanges( storyId = newStoryId; photo = newPhoto; document = newDocument; +#if 0 // mtp collage = std::move(newCollage); iv = std::move(newIv); +#endif stickerSet = std::move(newStickerSet); duration = newDuration; author = resultAuthor; @@ -320,6 +757,25 @@ void WebPageData::replaceDocumentGoodThumbnail() { } } +void WebPageData::setIv( + std::unique_ptr newIv, + WebPageCollage &&newCollage) { + const auto was = version; + if ((!iv != !newIv) + || (iv && iv->partial() != newIv->partial())) { + iv = std::move(newIv); + ++version; + } + if (collage.items != newCollage.items) { + collage = std::move(newCollage); + ++version; + } + if (version != was) { + _owner->notifyWebPageUpdateDelayed(this); + } +} + +#if 0 // mtp void WebPageData::ApplyChanges( not_null session, ChannelData *channel, @@ -362,6 +818,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 6497a0394da23f..89eb663b89a60e 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 TLlinkPreview; +} // namespace Tdb + class ChannelData; namespace Data { @@ -57,16 +61,20 @@ enum class WebPageType : uint8 { Factcheck, }; +#if 0 // mtp [[nodiscard]] WebPageType ParseWebPageType(const MTPDwebPage &type); +#endif [[nodiscard]] bool IgnoreIv(WebPageType type); struct WebPageCollage { using Item = std::variant; WebPageCollage() = default; +#if 0 // mtp explicit WebPageCollage( not_null owner, const MTPDwebPage &data); +#endif std::vector items; @@ -85,6 +93,9 @@ struct WebPageData { WebPageData(not_null owner, const WebPageId &id); ~WebPageData(); + [[nodiscard]] static WebPageId IdFromTdb(const Tdb::TLlinkPreview &data); + void setFromTdb(const Tdb::TLlinkPreview &data); + [[nodiscard]] Data::Session &owner() const; [[nodiscard]] Main::Session &session() const; @@ -98,18 +109,25 @@ struct WebPageData { FullStoryId newStoryId, PhotoData *newPhoto, DocumentData *newDocument, +#if 0 // mtp WebPageCollage &&newCollage, std::unique_ptr newIv, +#endif std::unique_ptr newStickerSet, 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 setIv( + std::unique_ptr newIv, + WebPageCollage &&newCollage); [[nodiscard]] QString displayedSiteName() const; [[nodiscard]] bool computeDefaultSmallMedia() const; @@ -135,9 +153,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 beb47698b75eef..633c6db930a2b3 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 b1930f45bd3647..6efec00c880a82 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 48d198d6e55b40..02ef8e2f26fdfe 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 b5de9e11959a58..2b5c689060a87a 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 bb90a581d81628..171f8806be8ad0 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -38,9 +38,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); @@ -739,21 +743,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 4133af7b67cd6e..a47488a4be4d2c 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers.cpp @@ -37,11 +37,28 @@ 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; + +[[nodiscard]] StickerType LookupFormat(const TLthumbnail *thumbnail) { + if (!thumbnail) { + return StickerType::Webp; + } + return thumbnail->data().vformat().match([](TLDthumbnailFormatTgs) { + return StickerType::Tgs; + }, [](const TLDthumbnailFormatWebm &) { + return StickerType::Webm; + }, [](const auto &) { + return StickerType::Webp; + }); +} + using SetFlag = StickersSetFlag; [[nodiscard]] TextWithEntities SavedGifsToast( @@ -136,6 +153,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) { } @@ -339,6 +366,7 @@ void Stickers::addSavedGif( } void Stickers::checkSavedGif(not_null item) { +#if 0 // mtp if (item->Has() || (!item->out() && item->history()->peer != session().user())) { @@ -351,8 +379,10 @@ void Stickers::checkSavedGif(not_null item) { } } } +#endif } +#if 0 // mtp void Stickers::applyArchivedResult( const MTPDmessages_stickerSetInstallResultArchive &d) { auto &v = d.vsets().v; @@ -409,6 +439,7 @@ void Stickers::applyArchivedResult( notifyUpdated(StickersType::Masks); } } +#endif void Stickers::installLocally(uint64 setId) { auto &sets = setsRef(); @@ -623,6 +654,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 @@ -649,6 +682,7 @@ void Stickers::requestSetToPushFaved( // Perhaps this is a deleted sticker pack. Add anyway. addAnyway({}); }).send(); +#endif } void Stickers::removeFromRecentSet(not_null document) { @@ -674,6 +708,94 @@ 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().requestSpecialStickersForce( + false, + true, + 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) { @@ -695,6 +817,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() @@ -770,6 +895,7 @@ void Stickers::somethingReceived( session().saveSettings(); } +#if 0 // mtp const auto counted = (type == StickersType::Emoji) ? Api::CountCustomEmojiHash(&session()) : (type == StickersType::Masks) @@ -785,10 +911,12 @@ void Stickers::somethingReceived( ).arg(hash ).arg(counted)); } +#endif notifyUpdated(type); } +#if 0 // mtp void Stickers::setPackAndEmoji( StickersSet &set, StickersPack &&pack, @@ -816,7 +944,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 +1400,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(); @@ -1433,10 +1731,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) { @@ -1541,6 +1845,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(); @@ -1799,6 +2104,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, LookupFormat(data.vthumbnail())); + } 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, LookupFormat(data.vthumbnail())); + 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, LookupFormat(data.vthumbnail())); + } 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, LookupFormat(data.vthumbnail())); + } + 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 426b034aaae800..4b2105e2ac7e3f 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; @@ -39,6 +56,8 @@ enum class StickersType : uchar { [[nodiscard]] StickerType ThumbnailTypeFromPhotoSize( const MTPPhotoSize &size); +[[nodiscard]] StickersType TypeFromTL(const Tdb::TLstickerType &type); + class Stickers final { public: explicit Stickers(not_null owner); @@ -220,6 +239,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); @@ -234,6 +265,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); [[nodiscard]] std::vector> getPremiumList( uint64 seed); @@ -244,6 +283,7 @@ class Stickers final { [[nodiscard]] auto getEmojiListFromSet(not_null document) -> std::optional>>; +#if 0 // mtp not_null feedSet(const MTPStickerSet &data); not_null feedSet(const MTPStickerSetCovered &data); not_null feedSetFull(const MTPDmessages_stickerSet &data); @@ -257,6 +297,15 @@ class Stickers final { void newSetReceived(const MTPDmessages_stickerSet &set); [[nodiscard]] 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; [[nodiscard]] RecentStickerPack &getRecentPack() const; @@ -283,6 +332,7 @@ class Stickers final { void requestSetToPushFaved( std::shared_ptr show, not_null document); +#if 0 // mtp void setPackAndEmoji( StickersSet &set, StickersPack &&pack, @@ -295,6 +345,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 75d98404e2e73d..d6442e21b6b8bd 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()) @@ -58,6 +63,35 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) { | (data.is_channel_emoji_status() ? Flag::ChannelStatus : Flag()) | (data.is_creator() ? Flag::AmCreator : 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.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.vneeds_repainting().v ? Flag::TextColor : Flag()); +} StickersSet::StickersSet( not_null owner, @@ -90,11 +124,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{ @@ -187,6 +223,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 {}; } @@ -225,6 +265,7 @@ std::shared_ptr StickersSet::activeThumbnailView() { return _thumbnailView.lock(); } +#if 0 // mtp MTPInputStickerSet InputStickerSet(StickerSetIdentifier id) { return !id ? MTP_inputStickerSetEmpty() @@ -245,5 +286,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 c218ce2fa89f71..bb47be20b8da39 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; enum class StickerType : uchar; @@ -64,8 +69,15 @@ enum class StickersSetFlag : ushort { 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: @@ -84,7 +96,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; @@ -132,7 +146,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 6451e79b4a0a7c..d39931a1050e38 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -359,6 +359,12 @@ dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerU dialogsHiddenAuthorUserpic: icon {{ "dialogs/avatar_hidden", premiumButtonFg }}; dialogsMyNotesUserpic: icon {{ "dialogs/avatar_notes", 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 6d227c69ad9560..3477399c0f0d19 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -165,6 +165,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)) { @@ -188,11 +191,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()) { @@ -209,6 +215,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 { @@ -224,12 +330,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) { @@ -272,6 +391,7 @@ const Ui::Text::String &Entry::chatListNameText() const { return _chatListNameText; } +#if 0 // mtp void Entry::setChatListExistence(bool exists) { if (exists && _sortKeyInChatList) { owner().refreshChatListEntry(this); @@ -284,6 +404,7 @@ void Entry::setChatListExistence(bool exists) { TimeId Entry::adjustedChatListTimeId() const { return chatListTimeId(); } +#endif void Entry::changedChatListPinHook() { } @@ -321,11 +442,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 56e84f48c77cbc..fdf00fdb8b1d03 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -70,6 +70,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) { @@ -209,14 +211,24 @@ class Entry : public base::has_weak_ptr { void updateChatListSortPosition(); void setChatListTimeId(TimeId date); virtual void updateChatListExistence(); +#if 0 // mtp bool needUpdateInChatList() const; [[nodiscard]] virtual TimeId adjustedChatListTimeId() const; +#endif + [[nodiscard]] uint64 tdbOrderInChatList(FilterId filterId) const; + + void updateChatListSortPosition( + FilterId filterId, + uint64 order, + bool pinned); [[nodiscard]] virtual int fixedOnTopIndex() const = 0; static constexpr auto kArchiveFixOnTopIndex = 1; static constexpr auto kTopPromotionFixOnTopIndex = 2; +#if 0 // mtp [[nodiscard]] virtual bool shouldBeInChatList() const = 0; +#endif [[nodiscard]] virtual UnreadState chatListUnreadState() const = 0; [[nodiscard]] virtual BadgesState chatListBadgesState() const = 0; [[nodiscard]] virtual HistoryItem *chatListMessage() const = 0; @@ -242,6 +254,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; } @@ -272,18 +285,28 @@ class Entry : public base::has_weak_ptr { void pinnedIndexChanged(FilterId filterId, int was, int now); [[nodiscard]] uint64 computeSortPosition(FilterId filterId) const; +#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 ab6577613e22c3..20cb13de87fc66 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -3077,8 +3077,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; } @@ -3086,6 +3089,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)); @@ -3110,6 +3114,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 a0b80ef67173e0..da8316291edfbe 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -108,10 +108,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 9c9ad67baea492..8c198a7cc82b9e 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 157e48e314d27a..bcf3b6e553d7ba 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 cac85b3b39302f..2b8b6864bf4e8c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.cpp @@ -32,6 +32,7 @@ void PinnedList::setLimit(int limit) { applyLimit(_limit); } +#if 0 // mtp void PinnedList::addPinned(Key key) { Expects(key.entry()->folderKnown()); @@ -49,11 +50,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(); @@ -62,6 +65,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); @@ -84,6 +111,7 @@ void PinnedList::clear() { applyLimit(0); } +#if 0 // mtp void PinnedList::applyList( not_null owner, const QVector &list) { @@ -128,6 +156,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 92a71108979ebb..3a725f07100fdb 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_pinned_list.h @@ -25,15 +25,18 @@ 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); @@ -43,6 +46,8 @@ class PinnedList final { void applyList( not_null forum, const QVector &list); +#endif + void applyList(const std::vector> &list); void reorder(Key key1, Key key2); @@ -51,7 +56,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 5a0730f86d96b6..f81f36a8fb7cfb 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -364,7 +364,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 channel = user ? nullptr : peer->asChannel(); const auto nextLayer = [&] { @@ -498,10 +501,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); @@ -628,6 +635,8 @@ void Row::paintUserpic( const auto history = _id.history(); if (!history || history->peer->isUser() || subscribed) { 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 3d9d1daf46175f..396b1defd629f5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -87,6 +87,10 @@ 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 "data/data_saved_messages.h" + #include #include #include @@ -95,6 +99,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); constexpr auto kSearchRequestDelay = crl::time(900); @@ -1201,9 +1207,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(); @@ -2120,6 +2129,7 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { _peerSearchQuery = QString(); peerSearchApplyEmpty(0); _api.request(base::take(_topicSearchRequest)).cancel(); + cloudChatsApplyEmpty(); return true; } else if (inCache) { const auto success = _singleMessageSearch.lookup(query, [=] { @@ -2134,7 +2144,9 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { _searchQueryFrom = fromPeer; _searchQueryTags = inTags; _searchQueryTab = tab; +#if 0 // mtp _searchNextRate = 0; +#endif _searchFull = _searchFullMigrated = false; cancelSearchRequest(); searchReceived(fromStartType, i->second, 0); @@ -2148,11 +2160,38 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { _searchQueryFrom = fromPeer; _searchQueryTags = inTags; _searchQueryTab = tab; +#if 0 // mtp _searchNextRate = 0; +#endif _searchFull = _searchFullMigrated = false; cancelSearchRequest(); if (inPeer) { const auto topic = searchInTopic(); + const auto type = SearchRequestType::PeerFromStart; + const auto sublist = _openedForum + ? nullptr + : _searchState.inChat.sublist(); + const auto fromPeer = sublist ? nullptr : _searchQueryFrom; + _searchRequest = _api.request(TLsearchChatMessages( + peerToTdbChat(inPeer->id), + tl_string(_searchQuery), + (fromPeer + ? peerToSender(fromPeer->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), + tl_int53(session().data().savedMessages().sublistId(sublist)) + )).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(inPeer); @@ -2203,8 +2242,21 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { _searchQueries.emplace(_searchRequest, _searchQuery); return _searchRequest; }); +#endif } else if (_searchState.tab == ChatSearchTab::PublicPosts) { const auto type = SearchRequestType::FromStart; + _searchRequest = _api.request(TLsearchPublicMessagesByTag( + tl_string(_searchState.query.trimmed().mid(1)), + tl_string(), // offset + tl_int32(kSearchPerPage) + )).done([=](const TLfoundMessages &result) { + _searchRequest = 0; + searchReceived(type, result); + }).fail([=] { + _searchRequest = 0; + _searchFull = true; + }).send(); +#if 0 // mtp _searchRequest = session().api().request(MTPchannels_SearchPosts( MTP_string(_searchState.query.trimmed().mid(1)), MTP_int(0), // offset_rate @@ -2217,8 +2269,28 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { searchFailed(type, error, _searchRequest); }).send(); _searchQueries.emplace(_searchRequest, _searchQuery); +#endif } else { const auto type = SearchRequestType::FromStart; + _searchRequest = _api.request(TLsearchMessages( + (session().settings().skipArchiveInSearch() + ? tl_chatListMain() + : std::optional()), + tl_bool(false), // only_in_channels + 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); @@ -2240,6 +2312,7 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { searchFailed(type, error, _searchRequest); }).send(); _searchQueries.emplace(_searchRequest, _searchQuery); +#endif } _inner->searchRequested(true); } else { @@ -2258,6 +2331,12 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { } else if (_peerSearchQuery != peerQuery) { _peerSearchQuery = peerQuery; _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) @@ -2266,12 +2345,33 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) { }).fail([=](const MTP::Error &error, mtpRequestId requestId) { peerSearchFailed(error, requestId); }).send(); +#endif _peerSearchQueries.emplace(_peerSearchRequest, _peerSearchQuery); } + if (inCache) { + 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 = peerQuery; peerSearchApplyEmpty(0); + cloudChatsApplyEmpty(); } if (searchForTopicsRequired(peerQuery)) { if (inCache) { @@ -2331,6 +2431,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, @@ -2357,6 +2466,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 { @@ -2374,6 +2489,31 @@ void Widget::searchMore() { return; } else if (!_searchFull) { if (const auto peer = searchInPeer()) { + const auto topic = searchInTopic(); + const auto sublist = _openedForum + ? nullptr + : _searchState.inChat.sublist(); + const auto fromPeer = sublist ? nullptr : _searchQueryFrom; + _searchRequest = _api.request(TLsearchChatMessages( + peerToTdbChat(peer->id), + tl_string(_searchQuery), + (fromPeer + ? peerToSender(fromPeer->id) + : std::optional()), + tl_int53(_nextSearchMessageId.bare), + tl_int32(0), // offset + tl_int32(kSearchPerPage), + std::nullopt, // filter + tl_int53(topic ? topic->rootId().bare : 0), + tl_int53(session().data().savedMessages().sublistId(sublist)) + )).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; @@ -2429,7 +2569,27 @@ void Widget::searchMore() { } return _searchRequest; }); +#endif } else { + _searchRequest = _api.request(TLsearchMessages( + (session().settings().skipArchiveInSearch() + ? tl_chatListMain() + : std::optional()), + tl_bool(false), // only_in_channels + 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; @@ -2458,8 +2618,33 @@ 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 + tl_int53(0) // saved_messages_topic_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; @@ -2500,9 +2685,11 @@ void Widget::searchMore() { }).send(); return _searchRequest; }); +#endif } } +#if 0 // mtp void Widget::searchReceived( SearchRequestType type, const MTPmessages_Messages &result, @@ -2670,29 +2857,41 @@ void Widget::peerSearchReceived( listScrollUpdated(); } } +#endif void Widget::searchApplyEmpty(SearchRequestType type, mtpRequestId id) { _searchFull = _searchFullMigrated = true; searchReceived( type, +#if 0 // mtp MTP_messages_messages( MTP_vector(), MTP_vector(), MTP_vector()), +#endif + {}, id); } void Widget::peerSearchApplyEmpty(mtpRequestId id) { _peerSearchFull = true; peerSearchReceived( +#if 0 // mtp MTP_contacts_found( MTP_vector(0), MTP_vector(0), MTP_vector(0), MTP_vector(0)), +#endif + {}, id); } +void Widget::cloudChatsApplyEmpty() { + cloudChatsReceived({}); +} + +#if 0 // mtp void Widget::searchFailed( SearchRequestType type, const MTP::Error &error, @@ -2715,6 +2914,143 @@ void Widget::peerSearchFailed(const MTP::Error &error, mtpRequestId id) { _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; @@ -3124,6 +3460,12 @@ bool Widget::applySearchState(SearchState state) { _api.request(requestId).cancel(); } _peerSearchQuery = QString(); + + _cloudChatsCache.clear(); + for (const auto &[query, requestId] : base::take(_cloudChatsQueries)) { + _api.request(requestId).cancel(); + } + _cloudChatsQuery = QString(); } if (_searchState.query != currentSearchQuery()) { @@ -3161,9 +3503,11 @@ bool Widget::applySearchState(SearchState state) { 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; _searchQueryTags.clear(); @@ -3691,9 +4035,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 { @@ -3810,8 +4157,13 @@ bool Widget::cancelSearch(CancelSearchOptions options) { // Don't create suggestions in unfocus case. setInnerFocus(true); } + _nextSearchMessageId = _nextSearchMigratedMessageId = 0; + _nextSearchOffset = QString(); +#if 0 // mtp _lastSearchPeer = nullptr; _lastSearchId = _lastSearchMigratedId = 0; + _lastSearchDate = 0; +#endif _inner->clearFilter(); applySearchState(std::move(updatedState)); if (_suggestions && clearSearchFocus) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index a1ee3950de3e65..bc91535117f7b2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -15,6 +15,18 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" #include "api/api_single_message_search.h" +namespace Tdb { +class TLchats; +class TLfoundMessages; +class TLfoundChatMessages; +} // namespace Tdb + +namespace Tdb { +class TLchats; +class TLfoundMessages; +class TLfoundChatMessages; +} // namespace Tdb + namespace MTP { class Error; } // namespace MTP @@ -165,6 +177,8 @@ class Widget final : public Window::AbstractSectionWidget { void searchMore(); void slideFinished(); + +#if 0 // mtp void searchReceived( SearchRequestType type, const MTPmessages_Messages &result, @@ -172,6 +186,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(); @@ -241,13 +281,16 @@ 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 peerSearchFailed(const MTP::Error &error, mtpRequestId requestId); +#endif void searchApplyEmpty(SearchRequestType type, mtpRequestId id); void peerSearchApplyEmpty(mtpRequestId id); + void cloudChatsApplyEmpty(); void updateForceDisplayWide(); void scrollToDefault(bool verytop = false); @@ -358,22 +401,42 @@ class Widget final : public Window::AbstractSectionWidget { PeerData *_searchQueryFrom = nullptr; std::vector _searchQueryTags; ChatSearchTab _searchQueryTab = {}; +#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 74ef869481bfe4..7ea2f58b92da76 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -554,6 +554,9 @@ void PaintRow( color, context.now)) { // Empty history + if (const auto secretChat = thread->peer()->asSecretChat()) { + paintItemCallback(nameleft, namewidth); + } } } else if (!item->isEmpty()) { if ((thread || sublist) && !promoted) { @@ -677,6 +680,8 @@ void PaintRow( } p.setPen(context.active ? st::dialogsNameFgActive + : from->isSecretChat() + ? st::boxTextFgGood : context.selected ? st::dialogsNameFgOver : st::dialogsNameFg); @@ -743,6 +748,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, @@ -860,6 +870,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 0060eae45857b1..d788968eee1dea 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 58c1966a8c3b2f..49d58f857bcf52 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/export_manager.cpp b/Telegram/SourceFiles/export/export_manager.cpp index 7277adef3372f0..2eaf2058d8ff07 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( @@ -32,9 +34,12 @@ void Manager::start( _panel->activatePanel(); return; } +#if 0 // tdlib todo export _controller = std::make_unique( &session->mtp(), singlePeer); +#endif + return; _panel = std::make_unique( session, _controller.get()); 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 b67629f92b4598..4074f104a92f39 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -69,12 +69,16 @@ 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 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; @@ -459,6 +463,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, @@ -468,6 +473,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); @@ -488,10 +501,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()); } @@ -814,6 +830,55 @@ 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; + + using LocalFlag = FilterValue::Flag; + const auto f = _filter.flags.value_or(LocalFlag()); + auto filter = tl_chatEventLogFilters( + tl_bool(f & LocalFlag::Edit), + tl_bool(f & LocalFlag::Delete), + tl_bool(f & LocalFlag::Pinned), + tl_bool(f & LocalFlag::Join), + tl_bool(f & LocalFlag::Leave), + tl_bool(f & LocalFlag::Invites), + tl_bool(f & LocalFlag::Promote), + tl_bool(f & LocalFlag::Demote), + tl_bool(f & LocalFlag::Info), + tl_bool(f & LocalFlag::Settings), + tl_bool(f & LocalFlag::Invite), + tl_bool(f & LocalFlag::GroupCall), + tl_bool(f & LocalFlag::Topics), + tl_bool(f & LocalFlag::SubExtend)); + + auto admins = !_filter.admins + ? 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; @@ -881,9 +946,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(); } @@ -912,7 +983,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()), + user->owner().userLoaded( + UserId(data.vinviter_user_id())), + data.vjoined_chat_date().v); + } else { + const auto hasAdminRights = + (type == id_chatMemberStatusAdministrator) + || (type == id_chatMemberStatusCreator); + editRestrictions( + hasAdminRights, + ChatRestrictionsInfo(), + (hasAdminRights + ? nullptr + : user->owner().userLoaded( + UserId(data.vinviter_user_id()))), + hasAdminRights ? 0 : data.vjoined_chat_date().v); + } + }).fail([=] { + editRestrictions(false, {}, nullptr, 0); + }).send(); +#if 0 // goodToRemove _api.request(MTPchannels_GetParticipant( _channel->inputChannel, user->input @@ -1583,6 +1683,7 @@ void InnerWidget::suggestRestrictParticipant( }).fail([=] { editRestrictions(false, {}, nullptr, 0); }).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 db52cac1cbd286..27a404f5cf4a89 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -17,6 +17,10 @@ For license and copyright information please follow this link: #include "mtproto/sender.h" #include "base/timer.h" +namespace Tdb { +class TLchatEvent; +} // namespace Tdb + struct ChatRestrictionsInfo; namespace Main { @@ -229,7 +233,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(); 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 09f22e3312285f..2d8cd8bfdd6c35 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; @@ -181,6 +184,122 @@ 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), // is_from_offline + Tdb::tl_bool(false), // can_be_saved + 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, // fact_check + std::nullopt, // reply_to + Tdb::tl_int53(0), // message_thread_id + Tdb::tl_int53(0), // saved_messages_topic_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(), + Tdb::tl_int53(0), // sender_business_bot_user_id + Tdb::tl_int32(0), // sender_boost_count + message.data().vauthor_signature(), + Tdb::tl_int64(0), // media_album_id + Tdb::tl_int64(0), // effect_id + message.data().vhas_sensitive_content(), + 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, @@ -338,6 +457,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()); @@ -356,12 +476,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( @@ -370,16 +500,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({ @@ -395,6 +533,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()); @@ -423,6 +562,7 @@ TextWithEntities GenerateInviteLinkChangeText( return true; }); }; +#endif const auto wrapDate = [](TimeId date) { return date ? langDateTime(base::unixtime::parse(date)) @@ -433,6 +573,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); @@ -441,6 +582,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( @@ -618,14 +768,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); } @@ -646,6 +805,7 @@ TextWithEntities GenerateDefaultBannedRightsChangeText( return result; } +#if 0 // mtp [[nodiscard]] bool IsTopicClosed(const MTPForumTopic &topic) { return topic.match([](const MTPDforumTopic &data) { return data.is_closed(); @@ -678,6 +838,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 @@ -721,10 +896,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; @@ -781,10 +961,63 @@ void GenerateItems( using LogChangeEmojiStatus = MTPDchannelAdminLogEventActionChangeEmojiStatus; using LogToggleSignatureProfiles = MTPDchannelAdminLogEventActionToggleSignatureProfiles; using LogParticipantSubExtend = MTPDchannelAdminLogEventActionParticipantSubExtend; +#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 LogEmoji = TLDchatEventCustomEmojiStickerSetChanged; + 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 LogChangePeerColor = TLDchatEventAccentColorChanged; + using LogChangeProfilePeerColor = TLDchatEventProfileAccentColorChanged; + using LogChangeWallpaper = TLDchatEventBackgroundChanged; + using LogChangeEmojiStatus = TLDchatEventEmojiStatusChanged; + using LogToggleSignatureProfiles = TLDchatEventShowMessageSenderToggled; + using LogParticipantSubExtend = TLDchatEventMemberSubscriptionExtended; 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(); @@ -825,7 +1058,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)); }; @@ -836,7 +1072,10 @@ void GenerateItems( .flags = MessageFlag::HasFromId | MessageFlag::AdminLogEntry, .from = from->id, .date = date, + }, std::move(text)); +#if 0 // mtp }, std::move(text), MTP_messageMediaEmpty()); +#endif }; const auto addSimpleTextMessage = [&](TextWithEntities &&text) { @@ -844,8 +1083,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 +1112,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 +1146,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 +1160,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 +1172,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 +1194,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 +1208,26 @@ 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); + + addPart( + history->createItem( + history->nextNonHistoryEntryId(), + PrepareLogMessage(message, date), + MessageFlag::AdminLogEntry), + 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(); @@ -974,6 +1257,7 @@ void GenerateItems( addSimpleServiceMessage(text); }); }; +#endif const auto createEditMessage = [&](const LogEdit &action) { const auto realId = ExtractRealMsgId(action.vnew_message()); @@ -981,15 +1265,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 @@ -1072,21 +1365,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; @@ -1094,13 +1403,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, @@ -1125,7 +1445,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); } @@ -1143,8 +1466,12 @@ void GenerateItems( }; const auto createChangeEmojiSet = [&](const LogEmoji &action) { +#if 0 // mtp const auto set = action.vnew_stickerset(); const auto removed = (set.type() == mtpc_inputStickerSetEmpty); +#endif + const auto set = action.vnew_sticker_set_id().v; + const auto removed = !set; if (removed) { const auto text = tr::lng_admin_log_removed_emoji_group( tr::now, @@ -1169,7 +1496,10 @@ void GenerateItems( controller->show( Box( controller->uiShow(), +#if 0 // mtp Data::FromInputSet(set), +#endif + StickerSetIdentifier{ .id = uint64(set) }, Data::StickersType::Emoji), Ui::LayerOption::CloseOther); } @@ -1188,7 +1518,10 @@ void GenerateItems( 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)( @@ -1204,8 +1537,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) { @@ -1228,7 +1571,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 @@ -1266,7 +1612,31 @@ void GenerateItems( }; 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) { @@ -1292,10 +1662,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); @@ -1339,12 +1713,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, @@ -1361,8 +1737,13 @@ void GenerateItems( }; 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(), @@ -1379,9 +1760,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(), @@ -1400,7 +1786,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) @@ -1416,11 +1805,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))); } @@ -1437,7 +1832,10 @@ void GenerateItems( 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) @@ -1450,9 +1848,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) { @@ -1462,9 +1865,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) { @@ -1474,29 +1882,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 @@ -1513,8 +1937,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 @@ -1551,8 +1979,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 @@ -1576,12 +2010,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)( @@ -1592,6 +2032,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( @@ -1609,9 +2050,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( @@ -1646,12 +2089,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(); @@ -1666,6 +2152,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()); @@ -1673,6 +2164,7 @@ void GenerateItems( createChangeUsername(data); }, [](const auto &) { }); +#endif return; } else { const auto wasReordered = [&] { @@ -1738,7 +2230,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)( @@ -1750,7 +2245,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, @@ -1761,8 +2259,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, @@ -1774,6 +2276,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) { @@ -1800,10 +2303,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()); } @@ -1817,7 +2350,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, @@ -1826,7 +2362,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, @@ -1839,7 +2378,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)( @@ -1851,20 +2393,35 @@ void GenerateItems( }; const auto createColorChange = [&]( +#if 0 // mtp const MTPPeerColor &was, const MTPPeerColor &now, +#endif + int32 prevColor, + uint64 prevEmoji, + int32 nextColor, + uint64 nextEmoji, const auto &colorPhrase, const auto &setEmoji, const auto &removeEmoji, const auto &changeEmoji) { +#if 0 // mtp const auto prevColor = was.data().vcolor(); const auto nextColor = now.data().vcolor(); +#endif if (prevColor != nextColor) { +#if 0 // mtp const auto wrap = [&](tl::conditional value) { return value ? value->v : Data::DecideColorIndex(history->peer->id); }; +#endif + const auto wrap = [&](int32 value) { + return value + ? value + : Data::DecideColorIndex(history->peer->id); + }; const auto text = colorPhrase( tr::now, lt_from, @@ -1876,8 +2433,10 @@ void GenerateItems( Ui::Text::WithEntities); addSimpleServiceMessage(text); } +#if 0 // mtp const auto prevEmoji = was.data().vbackground_emoji_id().value_or_empty(); const auto nextEmoji = now.data().vbackground_emoji_id().value_or_empty(); +#endif if (prevEmoji != nextEmoji) { const auto text = !prevEmoji ? setEmoji( @@ -1914,8 +2473,14 @@ void GenerateItems( const auto createChangePeerColor = [&](const LogChangePeerColor &data) { createColorChange( +#if 0 // mtp data.vprev_value(), data.vnew_value(), +#endif + data.vold_accent_color_id().v, + data.vold_background_custom_emoji_id().v, + data.vnew_accent_color_id().v, + data.vnew_background_custom_emoji_id().v, tr::lng_admin_log_change_color, tr::lng_admin_log_set_background_emoji, tr::lng_admin_log_removed_background_emoji, @@ -1925,8 +2490,14 @@ void GenerateItems( const auto createChangeProfilePeerColor = [&](const LogChangeProfilePeerColor &data) { const auto group = channel->isMegagroup(); createColorChange( +#if 0 // mtp data.vprev_value(), data.vnew_value(), +#endif + data.vold_profile_accent_color_id().v, + data.vold_profile_background_custom_emoji_id().v, + data.vnew_profile_accent_color_id().v, + data.vnew_profile_background_custom_emoji_id().v, (group ? tr::lng_admin_log_change_profile_color_group : tr::lng_admin_log_change_profile_color), @@ -1950,6 +2521,7 @@ void GenerateItems( }; const auto createChangeEmojiStatus = [&](const LogChangeEmojiStatus &data) { +#if 0 // mtp const auto parse = [](const MTPEmojiStatus &status) { return status.match([]( const MTPDemojiStatus &data) { @@ -1966,6 +2538,16 @@ void GenerateItems( const MTPDemojiStatusUntil &data) { return data.vuntil().v; }, [](const auto &) { return TimeId(); }); +#endif + const auto prevEmoji = data.vold_emoji_status() + ? data.vold_emoji_status()->data().vcustom_emoji_id().v + : DocumentId(); + const auto nextEmoji = data.vnew_emoji_status() + ? data.vnew_emoji_status()->data().vcustom_emoji_id().v + : DocumentId(); + const auto nextUntil = data.vnew_emoji_status() + ? data.vnew_emoji_status()->data().vexpiration_date().v + : TimeId(0); const auto text = !prevEmoji ? (nextUntil @@ -2027,7 +2609,10 @@ void GenerateItems( }; const auto createToggleSignatureProfiles = [&](const LogToggleSignatureProfiles &action) { + const auto enabled = action.vshow_message_sender().v; +#if 0 // mtp const auto enabled = (action.vnew_value().type() == mtpc_boolTrue); +#endif const auto text = (enabled ? tr::lng_admin_log_signature_profiles_enabled : tr::lng_admin_log_signature_profiles_disabled)( @@ -2040,7 +2625,14 @@ void GenerateItems( const auto createParticipantSubExtend = [&](const LogParticipantSubExtend &action) { const auto participant = Api::ChatParticipant( +#if 0 // mtp action.vnew_participant(), +#endif + tl_chatMember( + peerToSender(peerFromUser(action.vuser_id())), + tl_int53(0), // inviter_user_id + tl_int32(0), // joined_chat_date + action.vnew_status()), channel); if (!participant.subscriptionDate()) { return; @@ -2070,7 +2662,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, @@ -2088,8 +2684,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, @@ -2099,12 +2704,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 4be0f9cfc7ac3e..03688cfd498f82 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 4a47a8d512413c..faf142c33ae739 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -68,6 +68,10 @@ 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; @@ -171,11 +175,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 if (const auto media = item->media()) { if (const auto gift = media->gift()) { using GiftAction = Data::GiftUpdate::Action; @@ -431,6 +437,7 @@ void History::setForwardDraft( } } +#if 0 // mtp not_null History::createItem( MsgId id, const MTPMessage &message, @@ -468,21 +475,62 @@ std::vector> History::createItems( } return result; } +#endif -HistoryItem *History::createItem( +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) { + bool detachExistingItem, + HistoryItem *replacing) { if (const auto result = owner().message(peer, id)) { if (detachExistingItem) { result->removeMainView(); } return result; } - return makeMessage(id, message.data(), localFlags); + const auto result = makeMessage( + id, + message.data(), + localFlags, + replacing); + if (result->isScheduled()) { + session().scheduledMessages().append(result); + } + return result; +} + +not_null History::createItem( + BusinessShortcutId shortcutId, + const TLquickReplyMessage &message) { + const auto id = message.data().vid().v; + if (const auto result = owner().message(peer, id)) { + return result; + } + return makeMessage(id, message.data(), shortcutId); } +#if 0 // mtp not_null History::addNewMessage( MsgId id, const MTPMessage &message, @@ -504,25 +552,50 @@ not_null History::addNewMessage( } return addNewItem(item, newMessage); } +#endif -HistoryItem *History::addNewMessage( - MsgId id, - const TLmessage &msg, - MessageFlags localFlags, - NewMessageType type) { +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 item = createItem(id, msg, localFlags, detachExistingItem); - if (!item) { - return nullptr; - } - if (type == NewMessageType::Existing || item->mainView()) { - return item; + 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); + } } - const auto unread = (type == NewMessageType::Unread); - if (unread && item->isHistoryEntry()) { - applyMessageChanges(item, msg); + if (old && old != item) { + old->destroy(); } - return addNewItem(item, unread); + return item; +} + +not_null History::addMessage( + BusinessShortcutId shortcutId, + const Tdb::TLquickReplyMessage &message) { + return createItem(shortcutId, message); } not_null History::insertItem( @@ -558,9 +631,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() @@ -650,7 +725,9 @@ not_null History::addNewItem( not_null item, bool unread) { if (item->isScheduled()) { +#if 0 // mtp session().scheduledMessages().appendSending(item); +#endif return item; } else if (item->isBusinessShortcut()) { owner().shortcutMessages().appendSending(item); @@ -659,6 +736,7 @@ not_null History::addNewItem( 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 @@ -670,6 +748,7 @@ not_null History::addNewItem( if (shouldMarkBottomNotLoaded) { setNotLoadedAtBottom(); } +#endif if (!loadedAtBottom() || peer->migrateTo()) { setLastMessage(item); @@ -688,6 +767,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()) { @@ -706,10 +796,16 @@ void History::checkForLoadedAtTop(not_null added) { not_null History::addNewLocalMessage( HistoryItemCommonFields &&fields, +#if 0 // mtp const TextWithEntities &text, const MTPMessageMedia &media) { +#endif + const TextWithEntities &text) { return addNewItem( +#if 0 // mtp makeMessage(WithLocalFlag(std::move(fields)), text, media), +#endif + makeMessage(WithLocalFlag(std::move(fields)), text), true); } @@ -982,7 +1078,7 @@ void History::applyMessageChanges( void History::applyServiceChanges( not_null item, const MTPDmessageService &data) { -#if 0 // #TODO legacy +#if 0 // mtp const auto replyTo = data.vreply_to(); const auto processJoinedUser = [&]( not_null megagroup, @@ -1238,16 +1334,16 @@ void History::applyServiceChanges( void History::applyMessageChanges( not_null item, const TLmessage &data) { - //data.data().vcontent().match(); // #TODO tdlib - //if (data.type() == mtpc_messageService) { - // applyServiceChanges(item, data.c_messageService()); - //} - owner().stickers().checkSavedGif(item); session().changes().messageUpdated( item, Data::MessageUpdate::Flag::NewAdded); } +TimeId History::chatListTimeId() const { + const auto item = lastMessage(); + return item ? item->date() : TimeId(); +} + void History::mainViewRemoved( not_null block, not_null view) { @@ -1275,6 +1371,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, @@ -1282,12 +1379,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); @@ -1295,6 +1396,8 @@ void History::newItemAdded(not_null item) { owner().histories().requestDialogEntry(this); } } else { +#endif + { destroyUnreadBar(); } if (!item->unread(this)) { @@ -1305,19 +1408,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); } @@ -1417,6 +1524,7 @@ void History::addEdgesToSharedMedia() { } } +#if 0 // mtp void History::addOlderSlice(const QVector &slice) { if (slice.isEmpty()) { _loadedAtTop = true; @@ -1434,6 +1542,7 @@ void History::addOlderSlice(const QVector &slice) { checkLocalMessages(); checkLastMessage(); } +#endif void History::addCreatedOlderSlice( const std::vector> &items) { @@ -1456,6 +1565,7 @@ void History::addCreatedOlderSlice( addToSharedMedia(items); } +#if 0 // mtp void History::addNewerSlice(const QVector &slice) { bool wasLoadedAtBottom = loadedAtBottom(); @@ -1487,6 +1597,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()) { @@ -1706,7 +1867,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; @@ -1755,23 +1919,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); @@ -1817,7 +1993,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); } @@ -1847,7 +2026,9 @@ bool History::unreadCountKnown() const { } void History::setUnreadCount(int newUnreadCount) { +#if 0 // mtp Expects(folderKnown()); +#endif if (_unreadCount == newUnreadCount) { return; @@ -1870,9 +2051,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()) { @@ -1984,6 +2167,7 @@ Data::Folder *History::folder() const { return _folder.value_or(nullptr); } +#if 0 // mtp void History::setFolder( not_null folder, HistoryItem *folderDialogItem) { @@ -2001,6 +2185,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); } @@ -2014,6 +2207,7 @@ void History::setFolderPointer(Data::Folder *folder) { if (was) { was->unregisterOne(this); } +#if 0 // mtp if (wasInList) { addToChatList(0, owner().chatsList(folder)); @@ -2025,10 +2219,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 { @@ -2062,6 +2279,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()) { @@ -2085,6 +2303,7 @@ TimeId History::adjustedChatListTimeId() const { } return result; } +#endif void History::countScrollState(int top) { std::tie(scrollTopItem, scrollTopOffset) = findItemAndOffset(top); @@ -2556,6 +2775,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. @@ -2596,6 +2816,7 @@ auto History::computeChatListMessageFromLast() const ? std::make_optional(from->chatListMessage()) : std::nullopt; } +#endif return _lastMessage; } @@ -2653,6 +2874,7 @@ void History::setFakeChatListMessage() { } } +#if 0 // mtp void History::setFakeChatListMessageFrom(const MTPmessages_Messages &data) { if (!lastMessageKnown()) { requestChatListMessage(); @@ -2697,10 +2919,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 @@ -2711,6 +2937,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()); @@ -2720,6 +2947,7 @@ void History::applyChatListGroup( items.push_back(message); } } +#endif if (!ranges::contains(items, not_null(*_lastMessage)) || !ranges::contains(items, not_null(*_chatListMessage))) { return; @@ -2729,11 +2957,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 { @@ -2778,6 +3008,7 @@ bool History::trackUnreadMessages() const { return true; } +#if 0 // mtp bool History::shouldBeInChatList() const { if (peer->migrateTo() || !folderKnown()) { return false; @@ -2805,6 +3036,7 @@ void History::unknownMessageDeleted(MsgId messageId) { owner().histories().requestDialogEntry(this); } } +#endif bool History::isServerSideUnread(not_null item) const { Expects(item->isRegular()); @@ -2814,6 +3046,7 @@ bool History::isServerSideUnread(not_null item) const { : (!_inboxReadBefore || (item->id >= *_inboxReadBefore)); } +#if 0 // mtp void History::applyDialog( Data::Folder *requestFolder, const MTPDdialog &data) { @@ -2865,6 +3098,7 @@ void History::applyDialog( } owner().histories().dialogEntryApplied(this); } +#endif void History::applyPosition(const TLDchatPosition &data) { const auto pinned = data.vis_pinned().v; @@ -2875,27 +3109,74 @@ void History::applyPosition(const TLDchatPosition &data) { }, [&](const TLDchatSourcePublicServiceAnnouncement &data) { owner().setTopPromoted(this, data.vtype().v, data.vtext().v); }); + } else { + owner().setNotTopPromoted(this); } data.vlist().match([&](const TLDchatListMain &) { - clearFolder(); + if (order || !folder()) { + setFolderPointer(nullptr, order, pinned); + } }, [&](const TLDchatListArchive &) { - setFolder(owner().folder(Data::Folder::kId)); - }, [&](const TLDchatListFilter &data) { - const auto filterId = data.vchat_filter_id().v; + 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; - addNewMessage(id, data, MessageFlags(), NewMessageType::Last); + addMessage(data, NewMessageType::Last); applyDialogTopMessage(id); - owner().histories().dialogEntryApplied(this); } void History::clearLastMessage() { - applyDialogTopMessage(0); - owner().histories().dialogEntryApplied(this); + if (_lastServerMessage) { + if (lastMessage() == lastServerMessage()) { + _lastMessage = std::nullopt; + } + if (chatListMessage() == lastServerMessage()) { + _chatListMessage = std::nullopt; + updateChatListEntry(); + } + _lastServerMessage = std::nullopt; + } + setNotLoadedAtBottom(); } void History::dialogEntryApplied() { @@ -2904,16 +3185,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()) { @@ -2990,6 +3273,7 @@ bool History::skipUnreadUpdate() const { return clearUnreadOnClientSide(); } +#if 0 // mtp void History::applyDialogFields( Data::Folder *folder, int unreadCount, @@ -3007,6 +3291,7 @@ void History::applyDialogFields( } setOutboxReadTill(maxOutboxRead); } +#endif void History::applyDialogTopMessage(MsgId topMessageId) { if (topMessageId) { @@ -3263,7 +3548,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) { @@ -3353,6 +3638,7 @@ void History::removeJoinedMessage() { } } +#if 0 // mtp void History::reactionsEnabledChanged(bool enabled) { if (!enabled) { for (const auto &item : _items) { @@ -3364,6 +3650,7 @@ void History::reactionsEnabledChanged(bool enabled) { } } } +#endif bool History::isEmpty() const { return blocks.empty(); @@ -3518,16 +3805,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); } @@ -3550,6 +3842,7 @@ void History::clear(ClearType type) { owner().sendHistoryChangeNotifications(); } +#if 0 // mtp void History::clearUpTill(MsgId availableMinId) { auto remove = std::vector>(); remove.reserve(_items.size()); @@ -3568,6 +3861,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 5d11fa118e0e87..d6cb6c696c4348 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -19,6 +19,9 @@ For license and copyright information please follow this link: namespace Tdb { class TLDchatPosition; class TLmessage; +class TLquickReplyMessage; +class TLDmessages; +class TLDupdateMessageMentionRead; } // namespace Tdb class History; @@ -112,7 +115,10 @@ class History final : public Data::Thread { void checkLocalMessages(); void removeJoinedMessage(); +#if 0 // mtp void reactionsEnabledChanged(bool enabled); +#endif + void setLastKeyboardId(MsgId id); [[nodiscard]] bool isEmpty() const; [[nodiscard]] bool isDisplayedEmpty() const; @@ -130,7 +136,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); @@ -161,16 +169,21 @@ class History final : public Data::Thread { void unpinMessagesFor(MsgId topicRootId); +#if 0 // mtp not_null addNewMessage( MsgId id, const MTPMessage &message, MessageFlags localFlags, NewMessageType type); +#endif not_null addNewLocalMessage( HistoryItemCommonFields &&fields, + const TextWithEntities &text); +#if 0 // mtp const TextWithEntities &text, const MTPMessageMedia &media); +#endif not_null addNewLocalMessage( HistoryItemCommonFields &&fields, not_null forwardOriginal); @@ -191,13 +204,16 @@ class History final : public Data::Thread { Data::SponsoredFrom from, const TextWithEntities &textWithEntities); // sponsored - HistoryItem *addNewMessage( - MsgId id, - const Tdb::TLmessage &msg, - MessageFlags localFlags, - NewMessageType type); + not_null addMessage( + const Tdb::TLmessage &message, + NewMessageType type = NewMessageType::Existing, + MsgId oldMessageId = 0); + not_null addMessage( + BusinessShortcutId shortcutId, + const Tdb::TLquickReplyMessage &message); // Used only internally and for channel admin log. +#if 0 // mtp not_null createItem( MsgId id, const MTPMessage &message, @@ -206,15 +222,28 @@ class History final : public Data::Thread { bool newMessage = false); std::vector> createItems( const QVector &data); +#endif - HistoryItem *createItem( + std::vector> createItems( + const QVector> &data); + + not_null createItem( MsgId id, const Tdb::TLmessage &message, MessageFlags localFlags, - bool detachExistingItem); + bool detachExistingItem = false, + HistoryItem *replacing = nullptr); + not_null createItem( + BusinessShortcutId shortcutId, + const Tdb::TLquickReplyMessage &message); +#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); @@ -271,20 +300,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( @@ -386,7 +426,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; @@ -404,12 +446,18 @@ class History final : public Data::Thread { void refreshChatListNameSortKey(); + [[nodiscard]] TimeId chatListTimeId() const; +#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; @@ -425,10 +473,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); @@ -467,6 +517,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 { @@ -513,6 +569,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. @@ -539,7 +598,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); @@ -599,7 +661,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); void hasUnreadMentionChanged(bool has) override; void hasUnreadReactionChanged(bool has) override; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index dcc9d77e85f592..af5c716b1b08d5 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -96,6 +96,8 @@ 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 #include @@ -549,6 +551,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(); @@ -662,6 +667,7 @@ bool HistoryInner::hasSelectRestriction() const { return true; } +#if 0 // mtp void HistoryInner::messagesReceived( not_null peer, const QVector &messages) { @@ -696,6 +702,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)) { @@ -1115,7 +1154,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) { if (markingAsViewed && item->hasUnwatchedEffect()) { startEffects.emplace(view); } - if (markingAsViewed && item->hasViews()) { + // Always inform TDLib about messages being viewed. + if (markingAsViewed && item->isRegular()) { session().api().views().scheduleIncrement(item); } if (withReaction) { @@ -1126,10 +1166,12 @@ void HistoryInner::paintEvent(QPaintEvent *e) { _widget->enqueueMessageHighlight({ item }); } } +#if 0 // mtp session().data().reactions().poll(item, context.now); if (item->hasUnpaidContent()) { session().api().views().pollExtendedMedia(item); } +#endif _reactionsManager->recordCurrentReactionEffect( item->fullId(), QPoint(0, top)); @@ -2461,6 +2503,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } }; +#if 0 // mtp #ifdef _DEBUG // Sometimes we need to save emoji to files. if (const auto item = _dragStateItem) { const auto emojiStickers = &session->emojiStickersPack(); @@ -2473,6 +2516,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } } +#endif #endif const auto asGroup = !Element::Moused() diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index c19c3b8b0c7653..d52b4a626fcf05 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -22,6 +22,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 @@ -107,12 +111,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 de78c1ce658dab..12f07d25bb12dd 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -72,6 +72,11 @@ 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; @@ -153,6 +158,19 @@ template return { Ui::FillAmountAndCurrency(amount, currency) }; } +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) { @@ -198,6 +216,7 @@ struct HistoryItem::CreateConfig { std::vector restrictions; }; +#if 0 // mtp void HistoryItem::FillForwardedInfo( CreateConfig &config, const MTPDmessageFwdHeader &data) { @@ -403,7 +422,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) { @@ -483,6 +505,143 @@ 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 source = data.vsource()) { + const auto &data = source->data(); + config.savedFromPeer = peerFromTdbChat(data.vchat_id()); + config.savedFromMsgId = data.vmessage_id().v; + } +} + +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 = id, + .flags = FlagsFromTdb(data) | localFlags, + .from = peerFromSender(data.vsender_id()), + .date = MessageDateFromTdb(data), +}) { + 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)); + + if (const auto type = data.vself_destruct_type()) { + type->match([&](const TLDmessageSelfDestructTypeImmediately &) { + _flags |= MessageFlag::SelfDestructImmediate; + }, [](const TLDmessageSelfDestructTypeTimer &) { + }); + } + + 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))); + } + const auto emptyReactions = !interaction + || !interaction->data().vreactions(); + if (!emptyReactions) { + setReactions( + interaction->data().vreactions()->data().vreactions().v, + data.vunread_reactions().v, + interaction->data().vreactions()->data().vare_tags().v); + } else if (_history->peer->isSelf()) { + _flags |= MessageFlag::ReactionsAreTags; + } + applyTTL(data); + setFactcheck(FromTL(data.vfact_check())); +} + +HistoryItem::HistoryItem( + not_null history, + MsgId id, + const Tdb::TLDquickReplyMessage &data, + BusinessShortcutId shortcutId) +: HistoryItem(history, { + .id = id, + .flags = FlagsFromTdb(data), + .from = history->session().userPeerId(), +}) { + auto config = CreateConfig(); + if (const auto replyTo = data.vreply_to_message_id().v) { + config.reply.messageId = replyTo; + } + config.viaBotId = data.vvia_bot_user_id().v; + config.markup = HistoryMessageMarkupData(data.vreply_markup()); + + createComponents(std::move(config)); + + setContent(data.vcontent()); + if (const auto groupId = data.vmedia_album_id().v) { + setGroupId( + MessageGroupId::FromRaw( + history->peer->id, + groupId, + (_flags & MessageFlag::IsOrWasScheduled))); + } +} HistoryItem::HistoryItem( not_null history, @@ -605,11 +764,16 @@ HistoryItem::HistoryItem( HistoryItem::HistoryItem( not_null history, HistoryItemCommonFields &&fields, +#if 0 // mtp const TextWithEntities &textWithEntities, const MTPMessageMedia &media) +#endif + const TextWithEntities &textWithEntities) : HistoryItem(history, fields) { createComponentsHelper(std::move(fields)); +#if 0 // mtp setMedia(media); +#endif setText(textWithEntities); if (fields.groupedId) { setGroupId(MessageGroupId::FromRaw( @@ -1096,19 +1260,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()); } @@ -1132,7 +1314,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; } @@ -1361,6 +1545,7 @@ bool HistoryItem::isIncomingUnreadMedia() const { void HistoryItem::markMediaAndMentionRead() { _flags &= ~MessageFlag::MediaIsUnread; +#if 0 // mtp if (mentionsMe()) { _history->updateChatListEntry(); _history->unreadMentions().erase(id); @@ -1382,6 +1567,7 @@ void HistoryItem::markMediaAndMentionRead() { } } } +#endif } void HistoryItem::markReactionsRead() { @@ -1552,10 +1738,12 @@ void HistoryItem::setFactcheck(MessageFactcheck info) { } } +#if 0 // mtp bool HistoryItem::hasUnrequestedFactcheck() const { const auto factcheck = Get(); return factcheck && factcheck->data.needCheck && !factcheck->requested; } +#endif TextWithEntities HistoryItem::factcheckText() const { if (const auto factcheck = Get()) { @@ -1596,10 +1784,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() @@ -1683,6 +1873,7 @@ void HistoryItem::clearMainView() { _mainView = nullptr; } +#if 0 // mtp void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { int keyboardTop = -1; //if (!pendingResize()) {// #TODO edit bot message @@ -1767,6 +1958,7 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { finishEdition(keyboardTop); } +#endif void HistoryItem::applyChanges(not_null story) { Expects(_flags & MessageFlag::StoryItem); @@ -1798,6 +1990,7 @@ void HistoryItem::setStoryFields(not_null story) { } } +#if 0 // mtp void HistoryItem::applyEdition(const MTPDmessageService &message) { const auto wasSublist = savedSublist(); if (message.vaction().type() == mtpc_messageActionHistoryClear) { @@ -1977,6 +2170,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)); @@ -2040,6 +2250,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()) { @@ -2052,6 +2263,7 @@ void HistoryItem::destroyHistoryEntry() { topic->unreadReactions().erase(id); } } +#endif if (isRegular() && _history->peer->isMegagroup()) { if (const auto reply = Get()) { changeReplyToTopCounter(reply, -1); @@ -2153,7 +2365,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() && !isPostHidingAuthor()) { return from()->name(); @@ -2161,6 +2376,7 @@ QString HistoryItem::notificationHeader() const { return QString(); } +#if 0 // mtp void HistoryItem::setRealId(MsgId newId) { Expects(_flags & MessageFlag::BeingSent); Expects(IsClientMsgId(id)); @@ -2192,6 +2408,7 @@ void HistoryItem::setRealId(MsgId newId) { incrementReplyToTopCounter(); } } +#endif bool HistoryItem::canPin() const { if (!isRegular() || isService()) { @@ -2218,18 +2435,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(); } @@ -2309,6 +2531,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(); @@ -2346,6 +2573,7 @@ bool HistoryItem::canDeleteForEveryone(TimeId now) const { } } return true; +#endif } bool HistoryItem::suggestReport() const { @@ -2582,6 +2810,9 @@ void HistoryItem::toggleReaction( if (_reactions->empty() && !_reactions->localPaidData()) { _reactions = nullptr; _flags &= ~MessageFlag::CanViewReactions; + if (_history->peer->isSelf()) { + _flags |= MessageFlag::ReactionsAreTags; + } } } else { _reactions->add(reaction, addToRecent); @@ -2589,9 +2820,88 @@ void HistoryItem::toggleReaction( _history->owner().notifyItemDataChange(this); } +void HistoryItem::setReactions( + const QVector &list, + const QVector &unread, + bool areTags) { + Expects(!_reactions); + Expects(unread.empty() || !list.empty()); + + if (changeReactions(list, areTags) && changeUnreadReactions(unread)) { + _flags |= MessageFlag::HasUnreadReaction; + } +} + +bool HistoryItem::changeReactions( + const QVector &list, + bool areTags) { + if (list.empty()) { + _flags &= ~MessageFlag::CanViewReactions; + if (_history->peer->isSelf()) { + _flags |= MessageFlag::ReactionsAreTags; + } + return (base::take(_reactions) != nullptr); + } else if (!_reactions) { + _reactions = std::make_unique(this); + } + if (areTags) { + _flags |= MessageFlag::ReactionsAreTags; + } else { + _flags &= ~MessageFlag::ReactionsAreTags; + } + 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, + bool areTags) { + const auto hadUnread = hasUnreadReaction(); + const auto changed = changeReactions(list, areTags); + 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(); @@ -2631,6 +2941,10 @@ int HistoryItem::reactionsPaidScheduled() const { return _reactions ? _reactions->scheduledPaid() : 0; } +bool HistoryItem::selfDestructImmediate() const { + return _flags & MessageFlag::SelfDestructImmediate; +} + bool HistoryItem::reactionsAreTags() const { return _flags & MessageFlag::ReactionsAreTags; } @@ -2714,9 +3028,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(); @@ -2922,7 +3238,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, @@ -2934,7 +3252,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; @@ -2944,7 +3265,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()) { @@ -2971,7 +3294,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, @@ -3011,7 +3337,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), @@ -3065,6 +3394,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); @@ -3081,6 +3412,7 @@ void HistoryItem::applyTTL(TimeId destroyAt) { } else { _history->owner().registerMessageTTL(_ttlDestroyAt, this); } +#endif } void HistoryItem::replaceBuyWithReceiptInMarkup() { @@ -3136,9 +3468,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; @@ -3146,8 +3480,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; } @@ -3170,6 +3507,12 @@ bool HistoryItem::hasUnpaidContent() 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)); @@ -3192,9 +3535,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()) { @@ -3340,9 +3688,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() { @@ -3513,6 +3866,9 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { if (options.hideSender || isPostHidingAuthor() || isEmpty()) { return {}; } else if (!_history->peer->isUser()) { + if (_history->peer->isSecretChat()) { + return {}; + } if (const auto from = displayFrom()) { return fromSender(from); } @@ -3641,7 +3997,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 } } } @@ -3707,11 +4065,14 @@ void HistoryItem::flagSensitiveContent() { 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) { @@ -3894,73 +4255,1580 @@ void HistoryItem::createComponentsHelper(HistoryItemCommonFields &&fields) { createComponents(std::move(config)); } -void HistoryItem::setReactions(const MTPMessageReactions *reactions) { - Expects(!_reactions); +void HistoryItem::createServiceFromTdb(const TLmessageContent &content) { + const auto topicId = topicRootId(); - if (changeReactions(reactions) && _reactions->hasUnread()) { - _flags |= MessageFlag::HasUnreadReaction; + 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()); -void HistoryItem::updateReactions(const MTPMessageReactions *reactions) { - const auto wasRecentUsers = LookupRecentUnreadReactedUsers(this); - const auto hadUnread = hasUnreadReaction(); - const auto changed = changeReactions(reactions); - if (!changed) { - return; - } - const auto hasUnread = _reactions && _reactions->hasUnread(); - if (hasUnread && !hadUnread) { - _flags |= MessageFlag::HasUnreadReaction; + 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 = AmountAndStarCurrency( + &_history->session(), + 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(); }), + Payments::ProcessNonPanelPaymentFormFactory( + weak.get(), + item)); + } + }); + }, [&](const TLDmessagePaymentRefunded &data) { + UpdateComponents(HistoryServicePaymentRefund::Bit()); + const auto refund = Get(); + refund->peer = _history->owner().peer(peerFromSender(data.vowner_id())); + refund->amount = data.vtotal_amount().v; + refund->currency = qs(data.vcurrency()); + refund->transactionId = data.vtelegram_payment_charge_id().v; + const auto id = fullId(); + refund->link = std::make_shared([=]( + ClickContext context) { + const auto my = context.other.value(); + if (const auto window = my.sessionWindow.get()) { + Settings::ShowRefundInfoBox(window, id); + } + }); + }, [&](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(); - addToUnreadThings(HistoryUnreadThings::AddType::New); - } else if (!hasUnread && hadUnread) { - markReactionsRead(); - } - CheckReactionNotificationSchedule(this, wasRecentUsers); - _history->owner().notifyItemDataChange(this); -} + 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; -bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) { - if (reactions || _reactionsLastRefreshed) { - _reactionsLastRefreshed = crl::now(); - } - const auto changeToEmpty = [&] { - if (!_reactions) { - return false; - } else if (!_reactions->localPaidData()) { - _reactions = nullptr; - return true; + 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; } - return _reactions->clearCloudData(); - }; - if (!reactions) { - _flags &= ~MessageFlag::CanViewReactions; - if (_history->peer->isSelf()) { - _flags |= MessageFlag::ReactionsAreTags; + if (data.vedit_icon_custom_emoji_id().v) { + info->iconId = data.vicon_custom_emoji_id().v; + info->reiconed = true; } - return changeToEmpty(); - } - const auto &data = reactions->data(); - const auto empty = data.vresults().v.isEmpty(); - if (data.is_reactions_as_tags() - || (empty && _history->peer->isSelf())) { - _flags |= MessageFlag::ReactionsAreTags; - } else { - _flags &= ~MessageFlag::ReactionsAreTags; - } - if (data.is_can_see_list()) { - _flags |= MessageFlag::CanViewReactions; - } else { - _flags &= ~MessageFlag::CanViewReactions; - } - if (empty) { - return changeToEmpty(); - } else if (!_reactions) { - _reactions = std::make_unique(this); + }, [&](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; + } + updateServiceDependent(); } - const auto min = data.is_min(); - const auto &list = data.vresults().v; + + setServiceMessageByContent(content); +} + +void HistoryItem::setServiceMessageByContent( + const TLmessageContent &content) { + applyContent(content); + + const auto prepareServiceTextForSharedPeers = [&]( + const std::vector &ids) { + auto result = PreparedServiceText{}; + result.links.push_back(history()->peer->createOpenLink()); + + for (auto i = 0, count = int(ids.size()); i != count; ++i) { + auto user = _history->owner().peer(ids[i]); + result.links.push_back(user->createOpenLink()); + + auto linkText = Ui::Text::Link(user->name(), 2 + i); + if (i == 0) { + result.text = linkText; + } else if (i + 1 == count) { + result.text = tr::lng_action_add_users_and_last( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); + } else { + result.text = tr::lng_action_add_users_and_one( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); + } + } + result.text = tr::lng_action_shared_chat_with_bot( + tr::now, + lt_chat, + result.text, + lt_bot, + Ui::Text::Link(history()->peer->name(), 1), + Ui::Text::WithEntities); + 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 TLDmessageExpiredVoiceNote &data) { + prepared.text = { tr::lng_ttl_voice_expired(tr::now) }; + }, [&](const TLDmessageExpiredVideoNote &data) { + prepared.text = { tr::lng_ttl_round_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 TLDmessageChatBoost &data) { + const auto boosts = data.vboost_count().v; + prepared.links.push_back(fromLink()); + prepared.text = tr::lng_action_boost_apply( + tr::now, + lt_count, + boosts, + lt_from, + fromLinkText(), // Link 1. + 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 TLDmessagePaymentRefunded &data) { + prepared = preparePaymentRefundText(); + }, [&](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; + const auto session = &history()->session(); + const auto cost = AmountAndStarCurrency(session, amount, currency); + prepared.links.push_back(peer->createOpenLink()); + prepared.text = isSelf + ? tr::lng_action_gift_sent( + tr::now, + lt_cost, + cost, + Ui::Text::WithEntities) + : tr::lng_action_gift_received( + tr::now, + lt_user, + Ui::Text::Link(peer->shortName(), 1), // Link 1. + lt_cost, + cost, + Ui::Text::WithEntities); + }, [&](const TLDmessageGiftedStars &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; + const auto session = &history()->session(); + const auto cost = AmountAndStarCurrency(session, amount, currency); + prepared.links.push_back(peer->createOpenLink()); + prepared.text = isSelf + ? tr::lng_action_gift_sent( + tr::now, + lt_cost, + cost, + Ui::Text::WithEntities) + : tr::lng_action_gift_received( + tr::now, + lt_user, + Ui::Text::Link(peer->shortName(), 1), // Link 1. + lt_cost, + cost, + Ui::Text::WithEntities); + }, [&](const TLDmessageGiveawayPrizeStars &action) { + prepared.text = { + (action.vis_unclaimed().v + ? tr::lng_prize_unclaimed_about + : tr::lng_prize_about)( + tr::now, + lt_channel, + _from->owner().peer( + peerFromChat(action.vboosted_chat_id()))->name()), + }; + }, [&](const TLDmessageGift &action) { + const auto isSelf = _from->isSelf(); + const auto peer = isSelf ? _history->peer : _from; + const auto stars = action.vgift().data().vstar_count().v; + const auto cost = TextWithEntities{ + tr::lng_action_gift_for_stars(tr::now, lt_count, stars), + }; + const auto anonymous = _from->isServiceUser(); + if (anonymous) { + prepared.text = tr::lng_action_gift_received_anonymous( + tr::now, + lt_cost, + cost, + Ui::Text::WithEntities); + } else { + prepared.links.push_back(peer->createOpenLink()); + prepared.text = isSelf + ? tr::lng_action_gift_sent(tr::now, + lt_cost, + cost, + Ui::Text::WithEntities) + : tr::lng_action_gift_received( + tr::now, + lt_user, + Ui::Text::Link(peer->shortName(), 1), // Link 1. + lt_cost, + cost, + Ui::Text::WithEntities); + } + }, [&](const TLDmessagePremiumGiftCode &action) { + if (const auto creator = action.vcreator_id()) { + 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(*creator))->name()), + }; + } else { + const auto isSelf = (_from->id == _from->session().userPeerId()); + const auto peer = isSelf ? _history->peer : _from; + const auto session = &history()->session(); + const auto cost = AmountAndStarCurrency( + session, + action.vamount().v, + action.vcurrency().v); + prepared.links.push_back(peer->createOpenLink()); + prepared.text = isSelf + ? tr::lng_action_gift_sent( + tr::now, + lt_cost, + cost, + Ui::Text::WithEntities) + : tr::lng_action_gift_received( + tr::now, + lt_user, + Ui::Text::Link(peer->shortName(), 1), // Link 1. + lt_cost, + cost, + Ui::Text::WithEntities); + } + }, [&](const TLDmessageGiveawayCreated &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 TLDmessageUsersShared &data) { + auto peers = ranges::views::all( + data.vusers().v + ) | ranges::views::transform([](const TLsharedUser &user) { + return peerFromUser(user.data().vuser_id()); + }) | ranges::to_vector; + prepared = prepareServiceTextForSharedPeers(std::move(peers)); + }, [&](const TLDmessageChatShared &data) { + prepared = prepareServiceTextForSharedPeers( + { peerFromTdbChat(data.vchat().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::GiftCode{ + .message = Api::FormattedTextFromTdb(data.vtext()), + .count = data.vmonth_count().v, + .type = Data::GiftType::Premium, + }, + (data.vsticker() + ? _history->owner().processDocument(*data.vsticker()).get() + : nullptr)); + }, [&](const TLDmessageGiftedStars &data) { + _media = std::make_unique( + this, + _from, + Data::GiftType::Credits, + data.vstar_count().v, + (data.vsticker() + ? _history->owner().processDocument(*data.vsticker()).get() + : nullptr)); + }, [&](const TLDmessageGiveawayPrizeStars &data) { + _media = std::make_unique( + this, + _from, + Data::GiftCode{ + .slug = data.vtransaction_id().v, + .channel = history()->owner().channel( + peerToChannel(peerFromTdbChat(data.vboosted_chat_id()))), + .giveawayMsgId = data.vgiveaway_message_id().v, + .count = int(data.vstar_count().v), + .type = Data::GiftType::Credits, + .viaGiveaway = true, + .unclaimed = data.vis_unclaimed().v, + }, + (data.vsticker() + ? _history->owner().processDocument(*data.vsticker()).get() + : nullptr)); + }, [&](const TLDmessageGift &data) { + const auto &gift = data.vgift().data(); + const auto document = history()->owner().processDocument( + gift.vsticker()); + using Fields = Data::GiftCode; + _media = std::make_unique(this, _from, Fields{ + .document = document->sticker() ? document.get() : nullptr, + .message = Api::FormattedTextFromTdb(data.vtext()), + .convertStars = int(data.vsell_star_count().v), + .limitedCount = gift.vtotal_count().v, + .limitedLeft = gift.vremaining_count().v, + .count = int(gift.vstar_count().v), + .type = Data::GiftType::StarGift, + .anonymous = data.vis_private().v, + .converted = data.vwas_converted().v, + .saved = data.vis_saved().v, + }, _history->owner().processDocument(gift.vsticker())); + }, [&](const TLDmessagePremiumGiftCode &data) { + const auto boostedId = data.vcreator_id() + ? peerToChannel(peerFromSender(*data.vcreator_id())) + : ChannelId(); + _media = std::make_unique( + this, + _from, + Data::GiftCode{ + .slug = data.vcode().v, + .message = Api::FormattedTextFromTdb(data.vtext()), + .channel = (boostedId + ? _history->owner().channel(boostedId).get() + : nullptr), + .count = data.vmonth_count().v, + .type = Data::GiftType::Premium, + .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, + !data.vonly_for_self().v); + } + }, [&](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() + || TLDmessagePaidMedia::Is()) { + setMedia(content); + setFormattedText(data.vcaption()); + } else if constexpr (TLDmessageSticker::Is() + || TLDmessageVideoNote::Is() + || TLDmessageLocation::Is() + || TLDmessageVenue::Is() + || TLDmessageContact::Is() + || TLDmessageDice::Is() + || TLDmessagePoll::Is() + || TLDmessageCall::Is() + || TLDmessageGiveaway::Is() + || TLDmessageGiveawayWinners::Is()) { + setMedia(content); + } else if constexpr (TLDmessageInvoice::Is()) { + setMedia(content); + if (data.vpaid_media()) { + if (const auto caption = data.vpaid_media_caption()) { + setFormattedText(*caption); + } + } + } else if constexpr (TLDmessageExpiredPhoto::Is() + || TLDmessageExpiredVideo::Is() + || TLDmessageExpiredVoiceNote::Is() + || TLDmessageExpiredVideoNote::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() + || TLDmessageChatBoost::Is() + || TLDmessageCustomServiceAction::Is() + || TLDmessageGameScore::Is() + || TLDmessagePaymentSuccessful::Is() + || TLDmessagePaymentRefunded::Is() + || TLDmessageContactRegistered::Is() + || TLDmessageWebAppDataSent::Is() + || TLDmessagePassportDataSent::Is() + || TLDmessageProximityAlertTriggered::Is() + || TLDmessageChatJoinByRequest::Is() + || TLDmessageGiftedPremium::Is() + || TLDmessageGiftedStars::Is() + || TLDmessageGiveawayPrizeStars::Is() + || TLDmessageGift::Is() + || TLDmessageGiveawayCreated::Is() + || TLDmessageGiveawayCompleted::Is() + || TLDmessagePremiumGiftCode::Is() + || TLDmessageForumTopicCreated::Is() + || TLDmessageForumTopicEdited::Is() + || TLDmessageForumTopicIsClosedToggled::Is() + || TLDmessageForumTopicIsHiddenToggled::Is() + || TLDmessageSuggestProfilePhoto::Is() + || TLDmessageBotWriteAccessAllowed::Is() + || TLDmessageUsersShared::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.vlink_preview() + ? 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.vlink_preview() + ? std::make_unique( + item, + owner.processWebpage(*data.vlink_preview()), + flags) + : nullptr; + }, [&](const TLDmessageAnimation &data) -> Result { + return std::make_unique( + item, + owner.processDocument(data.vanimation()), + skipPremiumEffectDefault, + data.vhas_spoiler().v, + 0); // ttlSeconds + }, [&](const TLDmessageAudio &data) -> Result { + return std::make_unique( + item, + owner.processDocument(data.vaudio()), + skipPremiumEffectDefault, + hasSpoilerDefault, + 0); // ttlSeconds + }, [&](const TLDmessageDocument &data) -> Result { + return std::make_unique( + item, + owner.processDocument(data.vdocument()), + skipPremiumEffectDefault, + hasSpoilerDefault, + 0); // ttlSeconds + }, [&](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, + 0); // ttlSeconds + }, [&](const TLDmessageVideo &data) -> Result { + return std::make_unique( + item, + owner.processDocument(data.vvideo()), + skipPremiumEffectDefault, + data.vhas_spoiler().v, + 0); // ttlSeconds + }, [&](const TLDmessageVideoNote &data) -> Result { + ApplyTranscribe( + item, + data.vvideo_note().data().vspeech_recognition_result(), + true); + const auto ttlSeconds = item->selfDestructImmediate() + ? std::numeric_limits::max() + : 0; + return std::make_unique( + item, + owner.processDocument(data.vvideo_note()), + skipPremiumEffectDefault, + hasSpoilerDefault, + ttlSeconds); + }, [&](const TLDmessageVoiceNote &data) -> Result { + ApplyTranscribe( + item, + data.vvoice_note().data().vspeech_recognition_result(), + false); + const auto ttlSeconds = item->selfDestructImmediate() + ? std::numeric_limits::max() + : 0; + return std::make_unique( + item, + owner.processDocument(data.vvoice_note()), + skipPremiumEffectDefault, + hasSpoilerDefault, + ttlSeconds); + }, [&](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, + Data::SharedContact::ParseVcard(fields.vvcard().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 TLDmessageGiveawayCreated &data) -> Result { + return Result(); + }, [&](const TLDmessageGiveaway &data) -> Result { + return std::make_unique( + item, + Data::ComputeGiveawayStartData(item, data), + (data.vsticker() + ? owner.processDocument(*data.vsticker()).get() + : nullptr)); + }, [&](const TLDmessageGiveawayCompleted &data) -> Result { + return Result(); + }, [&](const TLDmessageGiveawayWinners &data) -> Result { + return std::make_unique( + item, + Data::ComputeGiveawayResultsData(item, data)); + }, [](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(); + } + if (const auto reactions = fields->vreactions()) { + updateReactions( + reactions->data().vreactions().v, + reactions->data().vare_tags().v); + } else if (_history->peer->isSelf()) { + _flags |= MessageFlag::ReactionsAreTags; + } + } else { + setForwardsCount(0); + clearReplies(); + if (_history->peer->isSelf()) { + _flags |= MessageFlag::ReactionsAreTags; + } + } +} + +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); + + if (changeReactions(reactions) && _reactions->hasUnread()) { + _flags |= MessageFlag::HasUnreadReaction; + } +} + +void HistoryItem::updateReactions(const MTPMessageReactions *reactions) { + const auto wasRecentUsers = LookupRecentUnreadReactedUsers(this); + const auto hadUnread = hasUnreadReaction(); + const auto changed = changeReactions(reactions); + 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); + _history->owner().notifyItemDataChange(this); +} + +bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) { + if (reactions || _reactionsLastRefreshed) { + _reactionsLastRefreshed = crl::now(); + } + const auto changeToEmpty = [&] { + if (!_reactions) { + return false; + } else if (!_reactions->localPaidData()) { + _reactions = nullptr; + return true; + } + return _reactions->clearCloudData(); + }; + if (!reactions) { + _flags &= ~MessageFlag::CanViewReactions; + if (_history->peer->isSelf()) { + _flags |= MessageFlag::ReactionsAreTags; + } + return changeToEmpty(); + } + const auto &data = reactions->data(); + const auto empty = data.vresults().v.isEmpty(); + if (data.is_reactions_as_tags() + || (empty && _history->peer->isSelf())) { + _flags |= MessageFlag::ReactionsAreTags; + } else { + _flags &= ~MessageFlag::ReactionsAreTags; + } + if (data.is_can_see_list()) { + _flags |= MessageFlag::CanViewReactions; + } else { + _flags &= ~MessageFlag::CanViewReactions; + } + if (empty) { + return changeToEmpty(); + } else if (!_reactions) { + _reactions = std::make_unique(this); + } + const auto min = data.is_min(); + const auto &list = data.vresults().v; const auto &recent = data.vrecent_reactions().value_or_empty(); const auto &top = data.vtop_reactors().value_or_empty(); if (min && hasUnreadReaction()) { @@ -5528,17 +7396,20 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { }, [](const auto &) { }); } +#endif void HistoryItem::setSelfDestruct( HistorySelfDestructType 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; } @@ -5829,6 +7700,25 @@ PreparedServiceText HistoryItem::preparePaymentSentText() { return result; } +PreparedServiceText HistoryItem::preparePaymentRefundText() { + auto result = PreparedServiceText(); + const auto refund = Get(); + Assert(refund != nullptr); + Assert(refund->peer != nullptr); + + const auto amount = refund->amount; + const auto currency = refund->currency; + result.links.push_back(refund->peer->createOpenLink()); + result.text = tr::lng_action_payment_refunded( + tr::now, + lt_peer, + Ui::Text::Link(refund->peer->name(), 1), // Link 1. + lt_amount, + { Ui::FillAmountAndCurrency(amount, currency) }, + Ui::Text::WithEntities); + return result; +} + PreparedServiceText HistoryItem::prepareStoryMentionText() { auto result = PreparedServiceText(); const auto peer = history()->peer; @@ -5925,6 +7815,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); @@ -5948,6 +7839,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 34cca5dc37bfdf..44c6d2ce26653f 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -16,6 +16,14 @@ For license and copyright information please follow this link: namespace Tdb { class TLmessage; class TLDmessage; +class TLDquickReplyMessage; +class TLmessageContent; +class TLDmessageForwardInfo; +class TLDmessageImportInfo; +class TLmessageInteractionInfo; +class TLmessageReaction; +class TLunreadReaction; +class TLDmessageAnimatedEmoji; } // namespace Tdb class HiddenSenderInfo; @@ -122,6 +130,10 @@ enum class HistoryReactionSource : char { 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); @@ -141,6 +153,7 @@ class HistoryItem final : public RuntimeComposer { MsgId id, const MTPDmessageEmpty &data, MessageFlags localFlags); +#endif HistoryItem( // Sponsored message. not_null history, @@ -156,8 +169,11 @@ class HistoryItem final : public RuntimeComposer { HistoryItem( // Local message. not_null history, HistoryItemCommonFields &&fields, + const TextWithEntities &textWithEntities); +#if 0 // mtp const TextWithEntities &textWithEntities, const MTPMessageMedia &media); +#endif HistoryItem( // Local service message. not_null history, HistoryItemCommonFields &&fields, @@ -187,7 +203,13 @@ class HistoryItem final : public RuntimeComposer { not_null history, MsgId id, const Tdb::TLDmessage &data, - MessageFlags localFlags); + MessageFlags localFlags, + HistoryItem *replacing); + HistoryItem( + not_null history, + MsgId id, + const Tdb::TLDquickReplyMessage &data, + BusinessShortcutId shortcutId); struct Destroyer { void operator()(HistoryItem *value); @@ -211,7 +233,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; @@ -226,7 +250,9 @@ class HistoryItem final : public RuntimeComposer { const QString &label, const TextWithEntities &content); void setFactcheck(MessageFactcheck info); +#if 0 // mtp [[nodiscard]] bool hasUnrequestedFactcheck() const; +#endif [[nodiscard]] TextWithEntities factcheckText() const; [[nodiscard]] not_null notificationThread() const; @@ -281,8 +307,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; @@ -327,7 +355,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; @@ -363,9 +394,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 QVector &media); void updateForwardedInfo(const MTPMessageFwdHeader *fwd); @@ -378,6 +413,11 @@ 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 overrideMedia(std::unique_ptr media); void applyEditionToHistoryCleared(); @@ -426,7 +466,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(); void applyEffectWatchedOnUnreadKnown(); @@ -466,7 +508,13 @@ class HistoryItem final : public RuntimeComposer { void finishPaidReactionSending( Data::PaidReactionSend send, bool success); +#if 0 // mtp void updateReactionsUnknown(); +#endif + void updateReactions( + const QVector &list, + bool areTags); + void updateUnreadReactions(const QVector &list); [[nodiscard]] auto reactions() const -> const std::vector &; [[nodiscard]] auto reactionsWithLocal() const @@ -482,7 +530,10 @@ 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 selfDestructImmediate() const; [[nodiscard]] bool reactionsAreTags() const; [[nodiscard]] bool hasDirectLink() const; @@ -561,6 +612,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; } @@ -587,10 +645,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; @@ -633,7 +694,30 @@ 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, + bool areTags); + [[nodiscard]] bool changeReactions( + const QVector &list, + bool areTags); + [[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); @@ -650,6 +734,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); @@ -660,6 +745,7 @@ class HistoryItem final : public RuntimeComposer { [[nodiscard]] PreparedServiceText preparePinnedText(); [[nodiscard]] PreparedServiceText prepareGameScoreText(); [[nodiscard]] PreparedServiceText preparePaymentSentText(); + [[nodiscard]] PreparedServiceText preparePaymentRefundText(); [[nodiscard]] PreparedServiceText prepareStoryMentionText(); [[nodiscard]] PreparedServiceText prepareInvitedToCallText( const std::vector> &users, @@ -670,6 +756,8 @@ class HistoryItem final : public RuntimeComposer { void flagSensitiveContent(); [[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 55e9e09a54f49a..cadc025d497c53 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -55,10 +55,14 @@ For license and copyright information please follow this link: #include "styles/style_settings.h" #include "styles/style_widgets.h" +#include "tdb/tdb_tl_scheme.h" + #include namespace { +using namespace Tdb; + const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_"; } // namespace @@ -318,6 +322,7 @@ ReplyFields ReplyFields::clone(not_null parent) const { }; } +#if 0 // mtp ReplyFields ReplyFieldsFromMTP( not_null item, const MTPMessageReplyHeader &reply) { @@ -403,6 +408,46 @@ 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->data().vtext()); + result.quoteOffset = quote->data().vposition().v; + result.manualQuote = quote->data().vis_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; @@ -1115,6 +1160,7 @@ HistoryMessageLogEntryOriginal &HistoryMessageLogEntryOriginal::operator=( HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default; +#if 0 // mtp MessageFactcheck FromMTP( not_null item, const tl::conditional &factcheck) { @@ -1143,6 +1189,18 @@ MessageFactcheck FromMTP( result.needCheck = data.is_need_check(); return result; } +#endif + +MessageFactcheck FromTL(const tl::conditional &factcheck) { + auto result = MessageFactcheck(); + if (!factcheck) { + return result; + } + const auto &data = factcheck->data(); + result.text = Api::FormattedTextFromTdb(data.vtext()); + result.country = data.vcountry_code().v; + return result; +} HistoryDocumentCaptioned::HistoryDocumentCaptioned() : caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) { diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 8b38c294da342b..b2f3c5aa5b4294 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -15,6 +15,11 @@ 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; +class TLfactCheck; +} // namespace Tdb + struct WebPageData; class VoiceSeekClickHandler; class ReplyKeyboard; @@ -54,6 +59,17 @@ namespace style { struct BotKeyboardButton; } // namespace style +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; @@ -261,6 +277,7 @@ struct ReplyFields { uint32 topicPost : 1 = 0; }; +#if 0 // mtp [[nodiscard]] ReplyFields ReplyFieldsFromMTP( not_null item, const MTPMessageReplyHeader &reply); @@ -268,6 +285,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 { @@ -583,12 +605,16 @@ struct MessageFactcheck { } }; +#if 0 // mtp [[nodiscard]] MessageFactcheck FromMTP( not_null item, const tl::conditional &factcheck); [[nodiscard]] MessageFactcheck FromMTP( not_null session, const tl::conditional &factcheck); +#endif +[[nodiscard]] MessageFactcheck FromTL( + const tl::conditional &factcheck); struct HistoryMessageFactcheck : public RuntimeComponent { @@ -705,8 +731,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 d66d0cf7bc0bf1..a9b5ef6ad83f93 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" @@ -35,3 +37,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 a44109299b556e..9fab9e1902d131 100644 --- a/Telegram/SourceFiles/history/history_item_edition.h +++ b/Telegram/SourceFiles/history/history_item_edition.h @@ -9,6 +9,8 @@ For license and copyright information please follow this link: #include "history/history_item_reply_markup.h" +#if 0 // mtp + namespace Main { class Session; } // namespace Main @@ -38,3 +40,5 @@ struct HistoryMessageEdition { const MTPMessageReactions *mtpReactions = nullptr; const MTPFactCheck *mtpFactcheck = nullptr; }; + +#endif diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 397da3d51c8163..7fd5d3226889a0 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -73,19 +73,98 @@ MessageFlags FlagsFromTdb(const TLDmessage &data) { }, [](const auto &) { return false; }); - return Flag() + 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.vcontains_unread_mention().v ? Flag::MentionsMe : Flag()) + | (data.vcan_be_saved().v ? Flag() : Flag::NoForwards) + | (data.vcontains_unread_mention().v + ? (Flag::MentionsMe | Flag::MediaIsUnread) + : Flag()) | (mediaUnread ? Flag::MediaIsUnread : Flag()) - //| ((flags & MTP::f_silent) ? Flag::Silent : Flag()) // #TODO tdlib | (data.vis_channel_post().v ? Flag::Post : Flag()) - //| ((flags & MTP::f_legacy) ? Flag::Legacy : Flag()) // #TODO tdlib | (data.vis_pinned().v ? Flag::Pinned : Flag()) - //| ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag()) // #TODO tdlib + | (data.vreply_to() ? Flag::HasReplyInfo : Flag()) + | (data.vreply_markup() ? Flag::HasReplyMarkup : Flag()) + | (views ? Flag::HasViews : Flag()) + | (invertMedia ? Flag::InvertMedia : Flag()); +} + +MessageFlags FlagsFromTdb(const TLDquickReplyMessage &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 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()) + | (mediaUnread ? Flag::MediaIsUnread : Flag()) + | (data.vcan_be_edited().v ? Flag::CanEdit : Flag()) | (data.vreply_to_message_id().v ? Flag::HasReplyInfo : Flag()) | (data.vreply_markup() ? Flag::HasReplyMarkup : Flag()) - | (data.vscheduling_state() ? Flag::IsOrWasScheduled : Flag()) // #TODO tdlib was scheduled, but now isn't? - | (data.vinteraction_info() ? 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( @@ -188,10 +267,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( @@ -443,6 +533,18 @@ ClickHandlerPtr ReportSponsoredClickHandler(not_null item) { }); } +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, @@ -635,6 +737,7 @@ std::vector> ParseInvitedToCallUsers( return owner.user(id.v); }) | ranges::to_vector; } +#endif PreparedServiceText GenerateJoinedText( not_null history, @@ -736,6 +839,10 @@ std::optional PeerHasThisCall( [[nodiscard]] MessageFlags FinalizeMessageFlags( not_null history, MessageFlags flags) { + if ((flags & MessageFlag::AdminLogEntry) + || (flags & MessageFlag::FakeHistoryItem)) { + flags &= ~MessageFlag::HistoryEntry; + } if (!(flags & MessageFlag::FakeHistoryItem) && !(flags & MessageFlag::IsOrWasScheduled) && !(flags & MessageFlag::ShortcutMessage) @@ -906,6 +1013,12 @@ void ClearMediaAsExpired(not_null item) { return; } if (const auto document = media->document()) { + item->updateContent(document->isVoiceMessage() + ? tl_messageExpiredVoiceNote() + : document->isVideoMessage() + ? tl_messageExpiredVideoNote() + : tl_messageExpiredVideo()); +#if 0 // mtp item->applyEditionToHistoryCleared(); auto text = (document->isVideoFile() ? tr::lng_ttl_video_expired @@ -915,11 +1028,15 @@ void ClearMediaAsExpired(not_null item) { ? tr::lng_ttl_round_expired : tr::lng_message_empty)(tr::now, Ui::Text::WithEntities); item->updateServiceText(PreparedServiceText{ std::move(text) }); +#endif } else if (const auto photo = media->photo()) { + item->updateContent(tl_messageExpiredPhoto()); +#if 0 // mtp item->applyEditionToHistoryCleared(); item->updateServiceText(PreparedServiceText{ tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities) }); +#endif } } } diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 7d96db3e386bc8..489079fc0a8b99 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -9,8 +9,14 @@ For license and copyright information please follow this link: class History; +namespace tl { +class int64_type; +} // namespace tl + namespace Tdb { class TLDmessage; +class TLDquickReplyMessage; +using TLint53 = tl::int64_type; } // namespace Tdb namespace Api { @@ -28,12 +34,20 @@ class Session; } // namespace Main [[nodiscard]] MessageFlags FlagsFromTdb(const Tdb::TLDmessage &data); +[[nodiscard]] MessageFlags FlagsFromTdb( + const Tdb::TLDquickReplyMessage &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, @@ -60,6 +74,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 5cd532ad5e8f98..10ab021864cd5e 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,60 @@ namespace { return result; } +[[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL( + const TLDkeyboardButtonTypeRequestUsers &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 { + .maxQuantity = data.vmax_quantity().v, + .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 MTPDkeyboardButtonRequestPeer &query) { using Type = RequestPeerQuery::Type; @@ -73,6 +131,7 @@ namespace { }); return result; } +#endif } // namespace @@ -107,6 +166,7 @@ HistoryMessageMarkupButton *HistoryMessageMarkupButton::Get( return nullptr; } +#if 0 // mtp void HistoryMessageMarkupData::fillRows( const QVector &list) { rows.clear(); @@ -230,7 +290,127 @@ 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