From be83da28522ebe646db1109e0fee2e8376fd11fa Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 12 Jun 2026 09:05:05 +0400 Subject: [PATCH] Add Api::Communities with basic operations. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/api/api_communities.cpp | 334 +++++++++++++++++++ Telegram/SourceFiles/api/api_communities.h | 121 +++++++ Telegram/SourceFiles/apiwrap.cpp | 6 + Telegram/SourceFiles/apiwrap.h | 3 + 5 files changed, 466 insertions(+) create mode 100644 Telegram/SourceFiles/api/api_communities.cpp create mode 100644 Telegram/SourceFiles/api/api_communities.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index a024bfe0df..bc4389deec 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -119,6 +119,8 @@ PRIVATE api/api_chat_participants.h api/api_cloud_password.cpp api/api_cloud_password.h + api/api_communities.cpp + api/api_communities.h data/data_search_calendar.h data/data_search_calendar.cpp api/api_common.cpp diff --git a/Telegram/SourceFiles/api/api_communities.cpp b/Telegram/SourceFiles/api/api_communities.cpp new file mode 100644 index 0000000000..d4cd31b0ad --- /dev/null +++ b/Telegram/SourceFiles/api/api_communities.cpp @@ -0,0 +1,334 @@ +/* +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 "api/api_communities.h" + +#include "apiwrap.h" +#include "data/data_channel.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "main/main_session.h" + +namespace Api { +namespace { + +[[nodiscard]] ChannelData *ExtractCreatedCommunity( + not_null session, + const MTPUpdates &updates) { + const auto chats = updates.match([](const MTPDupdates &data) { + return &data.vchats().v; + }, [](const MTPDupdatesCombined &data) { + return &data.vchats().v; + }, [](const auto &) -> const QVector* { + return nullptr; + }); + if (!chats) { + LOG(("API Error: unexpected update cons %1 " + "(Communities::create)").arg(updates.type())); + return nullptr; + } + for (const auto &chat : *chats) { + if (chat.type() == mtpc_community) { + const auto channel = session->data().channelLoaded( + chat.c_community().vid().v); + if (channel && channel->isCommunity()) { + return channel; + } + } + } + return nullptr; +} + +} // namespace + +Communities::Communities(not_null api) +: _session(&api->session()) +, _api(&api->instance()) { +} + +void Communities::create( + const QString &title, + const QString &about, + not_null peer, + Fn)> done, + Fn fail) { + using Flag = MTPcommunities_Create::Flag; + _api.request(MTPcommunities_Create( + MTP_flags(about.isEmpty() ? Flag() : Flag::f_about), + MTP_string(title), + MTP_string(about), + peer->input() + )).done([=](const MTPUpdates &result) { + _session->api().applyUpdates(result); + if (const auto community = ExtractCreatedCommunity( + _session, + result)) { + _session->api().requestFullPeer(community); + if (done) { + done(community); + } + } else if (fail) { + fail(QString()); + } + }).fail([=](const MTP::Error &error) { + if (fail) { + fail(error.type()); + } + }).send(); +} + +void Communities::addPeerLink( + not_null community, + not_null peer, + bool visible, + Fn done, + Fn fail) { + togglePeerLink( + community, + peer, + visible, + false, + std::move(done), + std::move(fail)); +} + +void Communities::removePeerLink( + not_null community, + not_null peer, + Fn done, + Fn fail) { + togglePeerLink( + community, + peer, + std::nullopt, + true, + std::move(done), + std::move(fail)); +} + +void Communities::togglePeerLink( + not_null community, + not_null peer, + std::optional visible, + bool remove, + Fn done, + Fn fail) { + using Flag = MTPcommunities_TogglePeerLink::Flag; + const auto flags = (remove ? Flag::f_deleted : Flag()) + | (visible.value_or(false) ? Flag::f_visible : Flag()) + | ((visible && !*visible) ? Flag::f_hidden : Flag()); + _api.request(MTPcommunities_TogglePeerLink( + MTP_flags(flags), + community->inputChannel(), + peer->input() + )).done([=] { + _session->api().requestFullPeer(community); + if (done) { + done(); + } + }).fail([=](const MTP::Error &error) { + if (error.type() == kCommunityRequestCreated.utf16()) { + _session->api().requestFullPeer(community); + } + if (fail) { + fail(error.type()); + } + }).send(); +} + +void Communities::requestJoinedCommunities( + Fn> &)> done) { + if (_joinedRequestId) { + _api.request(_joinedRequestId).cancel(); + } + _joinedRequestId = _api.request(MTPcommunities_GetJoinedCommunities( + )).done([=](const MTPmessages_Chats &result) { + _joinedRequestId = 0; + auto list = std::vector>(); + const auto &chats = result.match([](const auto &data) { + return data.vchats().v; + }); + list.reserve(chats.size()); + for (const auto &chat : chats) { + const auto peer = _session->data().processChat(chat); + if (const auto channel = peer->asChannel()) { + if (channel->isCommunity() && !channel->isForbidden()) { + list.push_back(channel); + } + } + } + if (done) { + done(list); + } + }).fail([=] { + _joinedRequestId = 0; + if (done) { + done({}); + } + }).send(); +} + +void Communities::toggleCollapsedInDialogs( + not_null community, + bool collapsed) { + if (!_collapseRequests.emplace(community).second) { + return; + } + using Flag = MTPcommunities_ToggleCommunityCollapsedInDialogs::Flag; + _api.request(MTPcommunities_ToggleCommunityCollapsedInDialogs( + MTP_flags(collapsed ? Flag::f_collapsed : Flag()), + community->inputChannel() + )).done([=](const MTPUpdates &result) { + _collapseRequests.remove(community); + _session->api().applyUpdates(result); + }).fail([=] { + _collapseRequests.remove(community); + }).send(); +} + +void Communities::requestPeerLinkRequests( + not_null community, + const QString &offset, + int limit, + Fn done) { + _api.request(MTPcommunities_GetPeerLinkRequests( + community->inputChannel(), + MTP_string(offset), + MTP_int(limit) + )).done([=](const MTPcommunities_PeerLinkRequests &result) { + const auto &data = result.data(); + auto &owner = _session->data(); + owner.processUsers(data.vusers()); + owner.processChats(data.vchats()); + auto slice = CommunityPeerRequestsSlice(); + slice.totalCount = data.vtotal_count().v; + slice.nextOffset = qs(data.vnext_offset().value_or_empty()); + slice.list.reserve(data.vrequests().v.size()); + for (const auto &request : data.vrequests().v) { + const auto &fields = request.data(); + slice.list.push_back({ + .peer = owner.peer(peerFromMTP(fields.vpeer())), + .requestedBy = owner.userLoaded( + UserId(fields.vrequested_by())), + .date = fields.vdate().v, + .visible = fields.is_visible(), + }); + } + if (done) { + done(std::move(slice)); + } + }).fail([=] { + if (done) { + done({}); + } + }).send(); +} + +void Communities::togglePeerLinkRequestApproval( + not_null community, + not_null peer, + bool reject, + Fn done, + Fn fail) { + using Flag = MTPcommunities_TogglePeerLinkRequestApproval::Flag; + _api.request(MTPcommunities_TogglePeerLinkRequestApproval( + MTP_flags(reject ? Flag::f_reject : Flag()), + community->inputChannel(), + peer->input() + )).done([=] { + _session->api().requestFullPeer(community); + if (done) { + done(); + } + }).fail([=](const MTP::Error &error) { + if (fail) { + fail(error.type()); + } + }).send(); +} + +void Communities::toggleAllPeerLinkRequestApproval( + not_null community, + bool reject, + Fn done, + Fn fail) { + using Flag = MTPcommunities_ToggleAllPeerLinkRequestApproval::Flag; + _api.request(MTPcommunities_ToggleAllPeerLinkRequestApproval( + MTP_flags(reject ? Flag::f_reject : Flag()), + community->inputChannel() + )).done([=] { + _session->api().requestFullPeer(community); + if (done) { + done(); + } + }).fail([=](const MTP::Error &error) { + if (fail) { + fail(error.type()); + } + }).send(); +} + +void Communities::toggleParticipantBanned( + not_null community, + not_null participant, + bool unban, + Fn done, + Fn fail) { + using Flag = MTPcommunities_ToggleParticipantBanned::Flag; + _api.request(MTPcommunities_ToggleParticipantBanned( + MTP_flags(unban ? Flag::f_unban : Flag()), + community->inputChannel(), + participant->input() + )).done([=] { + _session->api().requestFullPeer(community); + if (done) { + done(); + } + }).fail([=](const MTP::Error &error) { + if (fail) { + fail(error.type()); + } + }).send(); +} + +void Communities::requestParticipantJoinedChats( + not_null community, + not_null participant, + Fn done) { + _api.request(MTPcommunities_GetParticipantJoinedChats( + community->inputChannel(), + participant->input() + )).done([=](const MTPcommunities_ParticipantJoinedChats &result) { + const auto &data = result.data(); + auto &owner = _session->data(); + owner.processUsers(data.vusers()); + owner.processChats(data.vchats()); + auto parsed = CommunityParticipantJoinedChats(); + const auto append = [&]( + std::vector> &to, + const QVector &ids) { + to.reserve(ids.size()); + for (const auto &id : ids) { + if (const auto chat = owner.channelLoaded(id.v)) { + to.push_back(chat); + } + } + }; + append(parsed.creatorChats, data.vcreator_chat_ids().v); + append(parsed.joinedChats, data.vjoined_chat_ids().v); + if (done) { + done(std::move(parsed)); + } + }).fail([=] { + if (done) { + done({}); + } + }).send(); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_communities.h b/Telegram/SourceFiles/api/api_communities.h new file mode 100644 index 0000000000..c8a142f75b --- /dev/null +++ b/Telegram/SourceFiles/api/api_communities.h @@ -0,0 +1,121 @@ +/* +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 "mtproto/sender.h" + +class ApiWrap; +class PeerData; +class UserData; +class ChannelData; + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +inline constexpr auto kCommunityRequestCreated + = "COMMUNITY_REQUEST_CREATED"_cs; + +struct CommunityPeerRequest { + not_null peer; + UserData *requestedBy = nullptr; + TimeId date = 0; + bool visible = false; +}; + +struct CommunityPeerRequestsSlice { + std::vector list; + QString nextOffset; + int totalCount = 0; +}; + +struct CommunityParticipantJoinedChats { + std::vector> creatorChats; + std::vector> joinedChats; +}; + +class Communities final { +public: + explicit Communities(not_null api); + + void create( + const QString &title, + const QString &about, + not_null peer, + Fn)> done, + Fn fail); + + void addPeerLink( + not_null community, + not_null peer, + bool visible, + Fn done, + Fn fail); + void removePeerLink( + not_null community, + not_null peer, + Fn done, + Fn fail); + + void requestJoinedCommunities( + Fn> &)> done); + + void toggleCollapsedInDialogs( + not_null community, + bool collapsed); + + void requestPeerLinkRequests( + not_null community, + const QString &offset, + int limit, + Fn done); + + void togglePeerLinkRequestApproval( + not_null community, + not_null peer, + bool reject, + Fn done, + Fn fail); + void toggleAllPeerLinkRequestApproval( + not_null community, + bool reject, + Fn done, + Fn fail); + + void toggleParticipantBanned( + not_null community, + not_null participant, + bool unban, + Fn done, + Fn fail); + + void requestParticipantJoinedChats( + not_null community, + not_null participant, + Fn done); + +private: + void togglePeerLink( + not_null community, + not_null peer, + std::optional visible, + bool remove, + Fn done, + Fn fail); + + const not_null _session; + MTP::Sender _api; + + base::flat_set> _collapseRequests; + mtpRequestId _joinedRequestId = 0; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 19a0381f57..7d7f1033a1 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_links.h" #include "api/api_chat_participants.h" #include "api/api_cloud_password.h" +#include "api/api_communities.h" #include "api/api_hash.h" #include "api/api_invite_links.h" #include "api/api_media.h" @@ -218,6 +219,7 @@ ApiWrap::ApiWrap(not_null session) , _polls(std::make_unique(this)) , _todoLists(std::make_unique(this)) , _chatParticipants(std::make_unique(this)) +, _communities(std::make_unique(this)) , _unreadThings(std::make_unique(this)) , _ringtones(std::make_unique(this)) , _composeWithAi(std::make_unique(this)) @@ -5545,6 +5547,10 @@ Api::ChatParticipants &ApiWrap::chatParticipants() { return *_chatParticipants; } +Api::Communities &ApiWrap::communities() { + return *_communities; +} + Api::UnreadThings &ApiWrap::unreadThings() { return *_unreadThings; } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 7a1e220361..ff87850843 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -86,6 +86,7 @@ class PeerColors; class Polls; class TodoLists; class ChatParticipants; +class Communities; class UnreadThings; class Ringtones; class ComposeWithAi; @@ -453,6 +454,7 @@ public: [[nodiscard]] Api::Polls &polls(); [[nodiscard]] Api::TodoLists &todoLists(); [[nodiscard]] Api::ChatParticipants &chatParticipants(); + [[nodiscard]] Api::Communities &communities(); [[nodiscard]] Api::UnreadThings &unreadThings(); [[nodiscard]] Api::Ringtones &ringtones(); [[nodiscard]] Api::ComposeWithAi &composeWithAi(); @@ -820,6 +822,7 @@ private: const std::unique_ptr _polls; const std::unique_ptr _todoLists; const std::unique_ptr _chatParticipants; + const std::unique_ptr _communities; const std::unique_ptr _unreadThings; const std::unique_ptr _ringtones; const std::unique_ptr _composeWithAi;