aboutsummaryrefslogtreecommitdiffhomepage
path: root/td/telegram/CommunityManager.cpp
diff options
context:
space:
mode:
authorlevlam <levlam@telegram.org>2026-07-09 23:44:33 +0300
committerlevlam <levlam@telegram.org>2026-07-09 23:44:33 +0300
commitc0983b4c1dd7c01abd6553dd114b2d940beab070 (patch)
treeb7de04d62f7b191ffa0196c26b9c3adca4ac36b3 /td/telegram/CommunityManager.cpp
parentb6c84162520d0c9136aa694b3d7ec86d2c1e1a54 (diff)
Add td_api::community.
Diffstat (limited to 'td/telegram/CommunityManager.cpp')
-rw-r--r--td/telegram/CommunityManager.cpp584
1 files changed, 584 insertions, 0 deletions
diff --git a/td/telegram/CommunityManager.cpp b/td/telegram/CommunityManager.cpp
index 3c5625b37..c28e997ed 100644
--- a/td/telegram/CommunityManager.cpp
+++ b/td/telegram/CommunityManager.cpp
@@ -6,8 +6,69 @@
//
#include "td/telegram/CommunityManager.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/DialogPhoto.hpp"
+#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/logevent/LogEventHelper.h"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/SqliteKeyValue.h"
+#include "td/db/SqliteKeyValueAsync.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/utf8.h"
+
namespace td {
+template <class StorerT>
+void CommunityManager::Community::store(StorerT &storer) const {
+ using td::store;
+ bool has_photo = photo.small_file_id.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_photo);
+ STORE_FLAG(collapsed_in_dialogs);
+ END_STORE_FLAGS();
+ store(access_hash, storer);
+ store(title, storer);
+ store(date, storer);
+ store(status, storer);
+ store(cache_version, storer);
+ store(default_permissions, storer);
+ if (has_photo) {
+ store(photo, storer);
+ }
+}
+
+template <class ParserT>
+void CommunityManager::Community::parse(ParserT &parser) {
+ using td::parse;
+ bool has_photo = photo.small_file_id.is_valid();
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_photo);
+ PARSE_FLAG(collapsed_in_dialogs);
+ END_PARSE_FLAGS();
+ parse(access_hash, parser);
+ parse(title, parser);
+ parse(date, parser);
+ parse(status, parser);
+ parse(cache_version, parser);
+ parse(default_permissions, parser);
+ if (has_photo) {
+ parse(photo, parser);
+ }
+
+ if (!check_utf8(title)) {
+ LOG(ERROR) << "Have invalid title \"" << title << '"';
+ title.clear();
+ cache_version = 0;
+ }
+}
+
CommunityManager::CommunityManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
}
@@ -15,4 +76,527 @@ void CommunityManager::tear_down() {
parent_.reset();
}
+class CommunityManager::CommunityLogEvent {
+ public:
+ CommunityId community_id;
+ const Community *c_in = nullptr;
+ unique_ptr<Community> c_out;
+
+ CommunityLogEvent() = default;
+
+ CommunityLogEvent(CommunityId community_id, const Community *c) : community_id(community_id), c_in(c) {
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(community_id, storer);
+ td::store(*c_in, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(community_id, parser);
+ td::parse(c_out, parser);
+ }
+};
+
+void CommunityManager::save_community(Community *c, CommunityId community_id, bool from_binlog) {
+ if (!G()->use_chat_info_database()) {
+ return;
+ }
+ CHECK(c != nullptr);
+ if (!c->is_saved) {
+ if (!from_binlog) {
+ auto log_event = CommunityLogEvent(community_id, c);
+ auto storer = get_log_event_storer(log_event);
+ if (c->log_event_id == 0) {
+ c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Communities, storer);
+ } else {
+ binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::Communities, storer);
+ }
+ }
+
+ save_community_to_database(c, community_id);
+ return;
+ }
+}
+
+void CommunityManager::on_binlog_community_event(BinlogEvent &&event) {
+ if (!G()->use_chat_info_database()) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ return;
+ }
+
+ CommunityLogEvent log_event;
+ if (log_event_parse(log_event, event.get_data()).is_error()) {
+ LOG(ERROR) << "Failed to load a community from binlog";
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ return;
+ }
+
+ auto community_id = log_event.community_id;
+ if (have_community(community_id) || !community_id.is_valid()) {
+ LOG(ERROR) << "Skip adding already added " << community_id;
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ return;
+ }
+
+ LOG(INFO) << "Add " << community_id << " from binlog";
+ communities_.set(community_id, std::move(log_event.c_out));
+
+ Community *c = get_community(community_id);
+ CHECK(c != nullptr);
+ c->log_event_id = event.id_;
+
+ update_community(c, community_id, true, false);
+}
+
+string CommunityManager::get_community_database_key(CommunityId community_id) {
+ return PSTRING() << "community" << community_id.get();
+}
+
+string CommunityManager::get_community_database_value(const Community *c) {
+ return log_event_store(*c).as_slice().str();
+}
+
+void CommunityManager::save_community_to_database(Community *c, CommunityId community_id) {
+ CHECK(c != nullptr);
+ if (c->is_being_saved) {
+ return;
+ }
+ if (loaded_from_database_communities_.count(community_id)) {
+ save_community_to_database_impl(c, community_id, get_community_database_value(c));
+ return;
+ }
+ if (load_community_from_database_queries_.count(community_id) != 0) {
+ return;
+ }
+
+ load_community_from_database_impl(community_id, false, Auto());
+}
+
+void CommunityManager::save_community_to_database_impl(Community *c, CommunityId community_id, string value) {
+ CHECK(c != nullptr);
+ CHECK(load_community_from_database_queries_.count(community_id) == 0);
+ CHECK(!c->is_being_saved);
+ c->is_being_saved = true;
+ c->is_saved = true;
+ LOG(INFO) << "Trying to save to database " << community_id;
+ G()->td_db()->get_sqlite_pmc()->set(get_community_database_key(community_id), std::move(value),
+ PromiseCreator::lambda([community_id](Result<> result) {
+ send_closure(G()->community_manager(),
+ &CommunityManager::on_save_community_to_database, community_id,
+ result.is_ok());
+ }));
+}
+
+void CommunityManager::on_save_community_to_database(CommunityId community_id, bool success) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ Community *c = get_community(community_id);
+ CHECK(c != nullptr);
+ CHECK(c->is_being_saved);
+ CHECK(load_community_from_database_queries_.count(community_id) == 0);
+ c->is_being_saved = false;
+
+ if (!success) {
+ LOG(ERROR) << "Failed to save " << community_id << " to database";
+ c->is_saved = false;
+ } else {
+ LOG(INFO) << "Successfully saved " << community_id << " to database";
+ }
+ if (c->is_saved) {
+ if (c->log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), c->log_event_id);
+ c->log_event_id = 0;
+ }
+ } else {
+ save_community(c, community_id, c->log_event_id != 0);
+ }
+}
+
+void CommunityManager::load_community_from_database(Community *c, CommunityId community_id, Promise<Unit> promise) {
+ if (loaded_from_database_communities_.count(community_id)) {
+ promise.set_value(Unit());
+ return;
+ }
+
+ CHECK(c == nullptr || !c->is_being_saved);
+ load_community_from_database_impl(community_id, false, std::move(promise));
+}
+
+void CommunityManager::load_community_from_database_impl(CommunityId community_id, bool is_recursive,
+ Promise<Unit> promise) {
+ LOG(INFO) << "Load " << community_id << " from database";
+ auto &load_community_queries = load_community_from_database_queries_[community_id];
+ load_community_queries.push_back(std::move(promise));
+ if (load_community_queries.size() == 1u) {
+ G()->td_db()->get_sqlite_pmc()->get(
+ get_community_database_key(community_id), PromiseCreator::lambda([community_id, is_recursive](string value) {
+ send_closure(G()->community_manager(), &CommunityManager::on_load_community_from_database, community_id,
+ std::move(value), false, is_recursive);
+ }));
+ }
+}
+
+void CommunityManager::on_load_community_from_database(CommunityId community_id, string value, bool force,
+ bool is_recursive) {
+ if (G()->close_flag() && !force) {
+ // the community is in Binlog and will be saved after restart
+ return;
+ }
+
+ CHECK(community_id.is_valid());
+ if (!loaded_from_database_communities_.insert(community_id).second) {
+ return;
+ }
+
+ auto it = load_community_from_database_queries_.find(community_id);
+ vector<Promise<Unit>> promises;
+ if (it != load_community_from_database_queries_.end()) {
+ promises = std::move(it->second);
+ CHECK(!promises.empty());
+ load_community_from_database_queries_.erase(it);
+ }
+
+ LOG(INFO) << "Successfully loaded " << community_id << " of size " << value.size() << " from database";
+ // G()->td_db()->get_sqlite_pmc()->erase(get_community_database_key(community_id), Auto());
+ // return;
+
+ Community *c = get_community(community_id);
+ if (c == nullptr) {
+ if (!value.empty()) {
+ c = add_community(community_id, "on_load_community_from_database");
+
+ if (log_event_parse(*c, value).is_error()) {
+ LOG(ERROR) << "Failed to load " << community_id << " from database";
+ communities_.erase(community_id);
+ } else {
+ c->is_saved = true;
+ update_community(c, community_id, true, true);
+ }
+ }
+ } else {
+ CHECK(!c->is_saved); // community can't be saved before load completes
+ CHECK(!c->is_being_saved);
+ auto new_value = get_community_database_value(c);
+ if (value != new_value) {
+ save_community_to_database_impl(c, community_id, std::move(new_value));
+ } else if (c->log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), c->log_event_id);
+ c->log_event_id = 0;
+ }
+ }
+ set_promises(promises);
+}
+
+bool CommunityManager::have_community(CommunityId community_id) const {
+ return communities_.count(community_id) > 0;
+}
+
+const CommunityManager::Community *CommunityManager::get_community(CommunityId community_id) const {
+ return communities_.get_pointer(community_id);
+}
+
+CommunityManager::Community *CommunityManager::get_community(CommunityId community_id) {
+ return communities_.get_pointer(community_id);
+}
+
+CommunityManager::Community *CommunityManager::add_community(CommunityId community_id, const char *source) {
+ CHECK(community_id.is_valid());
+ auto &community_ptr = communities_[community_id];
+ if (community_ptr == nullptr) {
+ community_ptr = make_unique<Community>();
+ }
+ return community_ptr.get();
+}
+
+void CommunityManager::reload_community(CommunityId community_id, Promise<Unit> &&promise, const char *source) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (!community_id.is_valid()) {
+ return promise.set_error(400, "Invalid community identifier");
+ }
+
+ have_community_force(community_id, source);
+ //get_community_queries_.add_query(community_id.get(), std::move(promise), source);
+}
+
+bool CommunityManager::have_community_force(CommunityId community_id, const char *source) {
+ return get_community_force(community_id, source) != nullptr;
+}
+
+CommunityManager::Community *CommunityManager::get_community_force(CommunityId community_id, const char *source,
+ bool is_recursive) {
+ if (!community_id.is_valid()) {
+ return nullptr;
+ }
+
+ Community *c = get_community(community_id);
+ if (c != nullptr) {
+ return c;
+ }
+ if (!G()->use_chat_info_database()) {
+ return nullptr;
+ }
+ if (loaded_from_database_communities_.count(community_id)) {
+ return nullptr;
+ }
+
+ LOG(INFO) << "Trying to load " << community_id << " from database from " << source;
+ on_load_community_from_database(community_id,
+ G()->td_db()->get_sqlite_sync_pmc()->get(get_community_database_key(community_id)),
+ true, is_recursive);
+ return get_community(community_id);
+}
+
+void CommunityManager::update_community(Community *c, CommunityId community_id, bool from_binlog, bool from_database) {
+ CHECK(c != nullptr);
+
+ if (c->is_being_updated) {
+ LOG(ERROR) << "Detected recursive update of " << community_id;
+ }
+ c->is_being_updated = true;
+ SCOPE_EXIT {
+ c->is_being_updated = false;
+ };
+
+ LOG(DEBUG) << "Update " << community_id << ": need_save_to_database = " << c->need_save_to_database
+ << ", is_changed = " << c->is_changed;
+ c->need_save_to_database |= c->is_changed;
+ if (c->need_save_to_database) {
+ if (!from_database) {
+ c->is_saved = false;
+ }
+ c->need_save_to_database = false;
+ }
+ if (c->is_changed) {
+ send_closure(G()->td(), &Td::send_update, get_update_community_object(community_id, c));
+ c->is_changed = false;
+ c->is_update_community_sent = true;
+ }
+
+ if (!from_database) {
+ save_community(c, community_id, from_binlog);
+ }
+
+ if (c->cache_version != Community::CACHE_VERSION && !c->is_repaired && !c->status.is_banned() &&
+ c->access_hash != 0 && !G()->close_flag()) {
+ c->is_repaired = true;
+
+ LOG(INFO) << "Repairing cache of " << community_id;
+ reload_community(community_id, Promise<Unit>(), "update_community");
+ }
+}
+
+void CommunityManager::on_get_community(telegram_api::community &community, const char *source) {
+ CommunityId community_id(community.id_);
+ if (!community_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << community_id << " from " << source << ": " << to_string(community);
+ return;
+ }
+
+ if (community.flags_ == 0 && community.access_hash_ == 0 && community.title_.empty()) {
+ Community *c = get_community_force(community_id, source);
+ if (c != nullptr) {
+ LOG(ERROR) << "Receive from " << source << " empty " << community_id << ": " << to_string(community) << ", have "
+ << to_string(get_community_object(community_id, c));
+ }
+ return;
+ }
+
+ bool is_min = community.min_;
+ auto access_hash = community.access_hash_;
+ if (access_hash == 0 && !is_min) {
+ LOG(ERROR) << "Receive non-min " << community_id << " without access_hash from " << source;
+ return;
+ }
+ DialogParticipantStatus status = [&] {
+ if (community.creator_) {
+ return DialogParticipantStatus::Creator(!community.left_, false, string());
+ } else if (community.admin_rights_ != nullptr) {
+ return DialogParticipantStatus(false, std::move(community.admin_rights_), string(), ChannelType::Unknown);
+ } else if (community.left_) {
+ return DialogParticipantStatus::Left();
+ } else {
+ return DialogParticipantStatus::Member(0, string());
+ }
+ }();
+ Community *c = add_community(community_id, "on_get_community");
+ if (c->access_hash != access_hash && (!is_min || c->access_hash == 0)) {
+ c->access_hash = access_hash;
+ if (access_hash == 0 || c->access_hash == 0) {
+ c->is_changed = true;
+ } else {
+ c->need_save_to_database = true;
+ }
+ }
+ if (c->date != community.date_ && (!is_min || c->date == 0)) {
+ c->date = community.date_;
+ c->is_changed = true;
+ }
+ if (c->collapsed_in_dialogs != community.collapsed_in_dialogs_ && !is_min) {
+ c->collapsed_in_dialogs = community.collapsed_in_dialogs_;
+ c->need_save_to_database = true;
+ }
+ if (!is_min) {
+ on_update_community_status(c, community_id, std::move(status));
+ }
+ on_update_community_title(c, community_id, std::move(community.title_));
+ if (!c->status.is_banned()) {
+ on_update_community_photo(c, community_id, std::move(community.photo_));
+ }
+ on_update_community_default_permissions(c, community_id,
+ RestrictedRights(community.default_banned_rights_, ChannelType::Megagroup));
+
+ if (c->cache_version != Community::CACHE_VERSION) {
+ c->cache_version = Community::CACHE_VERSION;
+ c->need_save_to_database = true;
+ }
+ c->is_received_from_server = true;
+ update_community(c, community_id);
+}
+
+void CommunityManager::on_get_community_forbidden(telegram_api::communityForbidden &community, const char *source) {
+ CommunityId community_id(community.id_);
+ if (!community_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << community_id << " from " << source << ": " << to_string(community);
+ return;
+ }
+
+ if (community.flags_ == 0 && community.access_hash_ == 0 && community.title_.empty()) {
+ Community *c = get_community_force(community_id, source);
+ if (c != nullptr) {
+ LOG(ERROR) << "Receive from " << source << " empty " << community_id << ": " << to_string(community) << ", have "
+ << to_string(get_community_object(community_id, c));
+ }
+ return;
+ }
+
+ auto access_hash = community.access_hash_;
+ Community *c = add_community(community_id, "on_get_community");
+ if (c->access_hash != access_hash) {
+ c->access_hash = access_hash;
+ if (access_hash == 0 || c->access_hash == 0) {
+ c->is_changed = true;
+ } else {
+ c->need_save_to_database = true;
+ }
+ }
+ if (c->date != 0) {
+ c->date = 0;
+ c->is_changed = true;
+ }
+ on_update_community_status(c, community_id, DialogParticipantStatus::Banned(0, string()));
+ on_update_community_title(c, community_id, std::move(community.title_));
+ on_update_community_photo(c, community_id, nullptr);
+
+ telegram_api::object_ptr<telegram_api::chatBannedRights> banned_rights; // == nullptr
+ on_update_community_default_permissions(c, community_id, RestrictedRights(banned_rights, ChannelType::Megagroup));
+
+ if (c->cache_version != Community::CACHE_VERSION) {
+ c->cache_version = Community::CACHE_VERSION;
+ c->need_save_to_database = true;
+ }
+ c->is_received_from_server = true;
+ update_community(c, community_id);
+}
+
+void CommunityManager::on_update_community_photo(Community *c, CommunityId community_id,
+ telegram_api::object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr) {
+ on_update_community_photo(c, community_id,
+ get_dialog_photo(td_->file_manager_.get(), DialogId(ChannelId(community_id.get())),
+ c->access_hash, std::move(chat_photo_ptr)),
+ true);
+}
+
+void CommunityManager::on_update_community_photo(Community *c, CommunityId community_id, DialogPhoto &&photo,
+ bool invalidate_photo_cache) {
+ if (td_->auth_manager_->is_bot()) {
+ photo.minithumbnail.clear();
+ }
+
+ if (need_update_dialog_photo(c->photo, photo)) {
+ LOG(DEBUG) << "Update photo of " << community_id << " from " << c->photo << " to " << photo;
+ c->photo = std::move(photo);
+ c->is_changed = true;
+
+ if (invalidate_photo_cache) {
+ /*
+ auto community_full = get_community_full(community_id, true, "on_update_community_photo"); // must not load CommunityFull
+ if (community_full != nullptr) {
+ on_update_community_full_photo(community_full, community_id, Photo());
+ if (c->photo.small_file_id.is_valid()) {
+ if (community_full->expires_at > 0.0) {
+ community_full->expires_at = 0.0;
+ community_full->need_save_to_database = true;
+ }
+ reload_community_full(community_id, Auto(), "on_update_community_photo");
+ }
+ update_community_full(community_full, community_id, "on_update_community_photo");
+ }
+ */
+ }
+ } else if (need_update_dialog_photo_minithumbnail(c->photo.minithumbnail, photo.minithumbnail)) {
+ c->photo.minithumbnail = std::move(photo.minithumbnail);
+ c->is_changed = true;
+ }
+}
+
+void CommunityManager::on_update_community_title(Community *c, CommunityId community_id, string &&title) {
+ if (c->title != title) {
+ c->title = std::move(title);
+ c->is_changed = true;
+ }
+}
+
+void CommunityManager::on_update_community_status(Community *c, CommunityId community_id,
+ DialogParticipantStatus &&status) {
+ if (c->status != status) {
+ LOG(INFO) << "Update " << community_id << " status from " << c->status << " to " << status;
+ c->status = status;
+ c->is_changed = true;
+ }
+}
+
+void CommunityManager::on_update_community_default_permissions(Community *c, CommunityId community_id,
+ RestrictedRights default_permissions) {
+ if (c->default_permissions != default_permissions) {
+ LOG(INFO) << "Update " << community_id << " default permissions from " << c->default_permissions << " to "
+ << default_permissions;
+ c->default_permissions = default_permissions;
+ c->is_changed = true;
+ }
+}
+
+td_api::object_ptr<td_api::community> CommunityManager::get_community_object(CommunityId community_id) const {
+ return get_community_object(community_id, get_community(community_id));
+}
+
+td_api::object_ptr<td_api::community> CommunityManager::get_community_object(CommunityId community_id,
+ const Community *c) const {
+ if (c == nullptr) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::community>(community_id.get(), c->access_hash != 0, c->title,
+ get_chat_photo_info_object(td_->file_manager_.get(), &c->photo),
+ c->date);
+}
+
+td_api::object_ptr<td_api::updateCommunity> CommunityManager::get_update_community_object(CommunityId community_id,
+ const Community *c) const {
+ if (c == nullptr) {
+ return get_update_unknown_community_object(community_id);
+ }
+ return td_api::make_object<td_api::updateCommunity>(get_community_object(community_id, c));
+}
+
+td_api::object_ptr<td_api::updateCommunity> CommunityManager::get_update_unknown_community_object(
+ CommunityId community_id) const {
+ return td_api::make_object<td_api::updateCommunity>(
+ td_api::make_object<td_api::community>(community_id.get(), false, string(), nullptr, 0));
+}
+
} // namespace td