Move some methods to ChannelRecommendationManager.
This commit is contained in:
parent
407bb811b3
commit
2f2dbc30cd
@ -6,14 +6,391 @@
|
|||||||
//
|
//
|
||||||
#include "td/telegram/ChannelRecommendationManager.h"
|
#include "td/telegram/ChannelRecommendationManager.h"
|
||||||
|
|
||||||
|
#include "td/telegram/Application.h"
|
||||||
|
#include "td/telegram/ContactsManager.h"
|
||||||
|
#include "td/telegram/Dependencies.h"
|
||||||
|
#include "td/telegram/DialogManager.h"
|
||||||
|
#include "td/telegram/Global.h"
|
||||||
|
#include "td/telegram/logevent/LogEvent.h"
|
||||||
|
#include "td/telegram/logevent/LogEventHelper.h"
|
||||||
|
#include "td/telegram/OptionManager.h"
|
||||||
|
#include "td/telegram/Td.h"
|
||||||
|
#include "td/telegram/TdDb.h"
|
||||||
|
|
||||||
|
#include "td/db/SqliteKeyValueAsync.h"
|
||||||
|
|
||||||
|
#include "td/utils/buffer.h"
|
||||||
|
#include "td/utils/logging.h"
|
||||||
|
#include "td/utils/SliceBuilder.h"
|
||||||
|
#include "td/utils/Time.h"
|
||||||
|
#include "td/utils/tl_helpers.h"
|
||||||
|
|
||||||
namespace td {
|
namespace td {
|
||||||
|
|
||||||
|
class GetChannelRecommendationsQuery final : public Td::ResultHandler {
|
||||||
|
Promise<std::pair<int32, vector<tl_object_ptr<telegram_api::Chat>>>> promise_;
|
||||||
|
ChannelId channel_id_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GetChannelRecommendationsQuery(
|
||||||
|
Promise<std::pair<int32, vector<tl_object_ptr<telegram_api::Chat>>>> &&promise)
|
||||||
|
: promise_(std::move(promise)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(ChannelId channel_id) {
|
||||||
|
channel_id_ = channel_id;
|
||||||
|
|
||||||
|
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
|
||||||
|
CHECK(input_channel != nullptr);
|
||||||
|
send_query(
|
||||||
|
G()->net_query_creator().create(telegram_api::channels_getChannelRecommendations(std::move(input_channel))));
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_result(BufferSlice packet) final {
|
||||||
|
auto result_ptr = fetch_result<telegram_api::channels_getChannelRecommendations>(packet);
|
||||||
|
if (result_ptr.is_error()) {
|
||||||
|
return on_error(result_ptr.move_as_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto chats_ptr = result_ptr.move_as_ok();
|
||||||
|
LOG(INFO) << "Receive result for GetChannelRecommendationsQuery: " << to_string(chats_ptr);
|
||||||
|
switch (chats_ptr->get_id()) {
|
||||||
|
case telegram_api::messages_chats::ID: {
|
||||||
|
auto chats = move_tl_object_as<telegram_api::messages_chats>(chats_ptr);
|
||||||
|
auto total_count = static_cast<int32>(chats->chats_.size());
|
||||||
|
return promise_.set_value({total_count, std::move(chats->chats_)});
|
||||||
|
}
|
||||||
|
case telegram_api::messages_chatsSlice::ID: {
|
||||||
|
auto chats = move_tl_object_as<telegram_api::messages_chatsSlice>(chats_ptr);
|
||||||
|
return promise_.set_value({chats->count_, std::move(chats->chats_)});
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
return promise_.set_error(Status::Error("Unreachable"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_error(Status status) final {
|
||||||
|
td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelRecommendationsQuery");
|
||||||
|
promise_.set_error(std::move(status));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class StorerT>
|
||||||
|
void ChannelRecommendationManager::RecommendedDialogs::store(StorerT &storer) const {
|
||||||
|
bool has_dialog_ids = !dialog_ids_.empty();
|
||||||
|
bool has_total_count = static_cast<size_t>(total_count_) != dialog_ids_.size();
|
||||||
|
BEGIN_STORE_FLAGS();
|
||||||
|
STORE_FLAG(has_dialog_ids);
|
||||||
|
STORE_FLAG(has_total_count);
|
||||||
|
END_STORE_FLAGS();
|
||||||
|
if (has_dialog_ids) {
|
||||||
|
td::store(dialog_ids_, storer);
|
||||||
|
}
|
||||||
|
store_time(next_reload_time_, storer);
|
||||||
|
if (has_total_count) {
|
||||||
|
td::store(total_count_, storer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class ParserT>
|
||||||
|
void ChannelRecommendationManager::RecommendedDialogs::parse(ParserT &parser) {
|
||||||
|
bool has_dialog_ids;
|
||||||
|
bool has_total_count;
|
||||||
|
BEGIN_PARSE_FLAGS();
|
||||||
|
PARSE_FLAG(has_dialog_ids);
|
||||||
|
PARSE_FLAG(has_total_count);
|
||||||
|
END_PARSE_FLAGS();
|
||||||
|
if (has_dialog_ids) {
|
||||||
|
td::parse(dialog_ids_, parser);
|
||||||
|
}
|
||||||
|
parse_time(next_reload_time_, parser);
|
||||||
|
if (has_total_count) {
|
||||||
|
td::parse(total_count_, parser);
|
||||||
|
} else {
|
||||||
|
total_count_ = static_cast<int32>(dialog_ids_.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ChannelRecommendationManager::ChannelRecommendationManager(Td *td, ActorShared<> parent)
|
ChannelRecommendationManager::ChannelRecommendationManager(Td *td, ActorShared<> parent)
|
||||||
: td_(td), parent_(std::move(parent)) {
|
: td_(td), parent_(std::move(parent)) {
|
||||||
|
if (G()->use_sqlite_pmc() && !G()->use_message_database()) {
|
||||||
|
G()->td_db()->get_sqlite_pmc()->erase_by_prefix("channel_recommendations", Auto());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelRecommendationManager::tear_down() {
|
void ChannelRecommendationManager::tear_down() {
|
||||||
parent_.reset();
|
parent_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChannelRecommendationManager::is_suitable_recommended_channel(DialogId dialog_id) const {
|
||||||
|
if (dialog_id.get_type() != DialogType::Channel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return is_suitable_recommended_channel(dialog_id.get_channel_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChannelRecommendationManager::is_suitable_recommended_channel(ChannelId channel_id) const {
|
||||||
|
auto status = td_->contacts_manager_->get_channel_status(channel_id);
|
||||||
|
return !status.is_member() && td_->contacts_manager_->have_input_peer_channel(channel_id, AccessRights::Read);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChannelRecommendationManager::are_suitable_recommended_dialogs(
|
||||||
|
const RecommendedDialogs &recommended_dialogs) const {
|
||||||
|
for (auto recommended_dialog_id : recommended_dialogs.dialog_ids_) {
|
||||||
|
if (!is_suitable_recommended_channel(recommended_dialog_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto is_premium = td_->option_manager_->get_option_boolean("is_premium");
|
||||||
|
auto have_all = recommended_dialogs.dialog_ids_.size() == static_cast<size_t>(recommended_dialogs.total_count_);
|
||||||
|
if (!have_all && is_premium) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRecommendationManager::get_channel_recommendations(
|
||||||
|
DialogId dialog_id, bool return_local, Promise<td_api::object_ptr<td_api::chats>> &&chats_promise,
|
||||||
|
Promise<td_api::object_ptr<td_api::count>> &&count_promise) {
|
||||||
|
if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_channel_recommendations")) {
|
||||||
|
if (chats_promise) {
|
||||||
|
chats_promise.set_error(Status::Error(400, "Chat not found"));
|
||||||
|
}
|
||||||
|
if (count_promise) {
|
||||||
|
count_promise.set_error(Status::Error(400, "Chat not found"));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dialog_id.get_type() != DialogType::Channel) {
|
||||||
|
if (chats_promise) {
|
||||||
|
chats_promise.set_value(td_api::make_object<td_api::chats>());
|
||||||
|
}
|
||||||
|
if (count_promise) {
|
||||||
|
count_promise.set_value(td_api::make_object<td_api::count>(0));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto channel_id = dialog_id.get_channel_id();
|
||||||
|
if (!td_->contacts_manager_->is_broadcast_channel(channel_id) ||
|
||||||
|
td_->contacts_manager_->get_input_channel(channel_id) == nullptr) {
|
||||||
|
if (chats_promise) {
|
||||||
|
chats_promise.set_value(td_api::make_object<td_api::chats>());
|
||||||
|
}
|
||||||
|
if (count_promise) {
|
||||||
|
count_promise.set_value(td_api::make_object<td_api::count>(0));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool use_database = true;
|
||||||
|
auto it = channel_recommended_dialogs_.find(channel_id);
|
||||||
|
if (it != channel_recommended_dialogs_.end()) {
|
||||||
|
if (are_suitable_recommended_dialogs(it->second)) {
|
||||||
|
auto next_reload_time = it->second.next_reload_time_;
|
||||||
|
if (chats_promise) {
|
||||||
|
chats_promise.set_value(td_->dialog_manager_->get_chats_object(it->second.total_count_, it->second.dialog_ids_,
|
||||||
|
"get_channel_recommendations"));
|
||||||
|
}
|
||||||
|
if (count_promise) {
|
||||||
|
count_promise.set_value(td_api::make_object<td_api::count>(it->second.total_count_));
|
||||||
|
}
|
||||||
|
if (next_reload_time > Time::now()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chats_promise = {};
|
||||||
|
count_promise = {};
|
||||||
|
} else {
|
||||||
|
LOG(INFO) << "Drop cache for similar chats of " << dialog_id;
|
||||||
|
channel_recommended_dialogs_.erase(it);
|
||||||
|
if (G()->use_message_database()) {
|
||||||
|
G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
use_database = false;
|
||||||
|
}
|
||||||
|
load_channel_recommendations(channel_id, use_database, return_local, std::move(chats_promise),
|
||||||
|
std::move(count_promise));
|
||||||
|
}
|
||||||
|
|
||||||
|
string ChannelRecommendationManager::get_channel_recommendations_database_key(ChannelId channel_id) {
|
||||||
|
return PSTRING() << "channel_recommendations" << channel_id.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRecommendationManager::load_channel_recommendations(
|
||||||
|
ChannelId channel_id, bool use_database, bool return_local,
|
||||||
|
Promise<td_api::object_ptr<td_api::chats>> &&chats_promise,
|
||||||
|
Promise<td_api::object_ptr<td_api::count>> &&count_promise) {
|
||||||
|
if (count_promise) {
|
||||||
|
get_channel_recommendation_count_queries_[return_local][channel_id].push_back(std::move(count_promise));
|
||||||
|
}
|
||||||
|
auto &queries = get_channel_recommendations_queries_[channel_id];
|
||||||
|
queries.push_back(std::move(chats_promise));
|
||||||
|
if (queries.size() == 1) {
|
||||||
|
if (G()->use_message_database() && use_database) {
|
||||||
|
G()->td_db()->get_sqlite_pmc()->get(
|
||||||
|
get_channel_recommendations_database_key(channel_id),
|
||||||
|
PromiseCreator::lambda([actor_id = actor_id(this), channel_id](string value) {
|
||||||
|
send_closure(actor_id, &ChannelRecommendationManager::on_load_channel_recommendations_from_database,
|
||||||
|
channel_id, std::move(value));
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
reload_channel_recommendations(channel_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRecommendationManager::fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error) {
|
||||||
|
for (int return_local = 0; return_local < 2; return_local++) {
|
||||||
|
auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id);
|
||||||
|
if (it != get_channel_recommendation_count_queries_[return_local].end()) {
|
||||||
|
auto promises = std::move(it->second);
|
||||||
|
CHECK(!promises.empty());
|
||||||
|
get_channel_recommendation_count_queries_[return_local].erase(it);
|
||||||
|
fail_promises(promises, error.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto it = get_channel_recommendations_queries_.find(channel_id);
|
||||||
|
CHECK(it != get_channel_recommendations_queries_.end());
|
||||||
|
auto promises = std::move(it->second);
|
||||||
|
CHECK(!promises.empty());
|
||||||
|
get_channel_recommendations_queries_.erase(it);
|
||||||
|
fail_promises(promises, std::move(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRecommendationManager::finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count,
|
||||||
|
vector<DialogId> dialog_ids) {
|
||||||
|
for (int return_local = 0; return_local < 2; return_local++) {
|
||||||
|
auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id);
|
||||||
|
if (it != get_channel_recommendation_count_queries_[return_local].end()) {
|
||||||
|
auto promises = std::move(it->second);
|
||||||
|
CHECK(!promises.empty());
|
||||||
|
get_channel_recommendation_count_queries_[return_local].erase(it);
|
||||||
|
for (auto &promise : promises) {
|
||||||
|
promise.set_value(td_api::make_object<td_api::count>(total_count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto it = get_channel_recommendations_queries_.find(channel_id);
|
||||||
|
CHECK(it != get_channel_recommendations_queries_.end());
|
||||||
|
auto promises = std::move(it->second);
|
||||||
|
CHECK(!promises.empty());
|
||||||
|
get_channel_recommendations_queries_.erase(it);
|
||||||
|
for (auto &promise : promises) {
|
||||||
|
if (promise) {
|
||||||
|
promise.set_value(td_->dialog_manager_->get_chats_object(total_count, dialog_ids,
|
||||||
|
"finish_load_channel_recommendations_queries"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRecommendationManager::on_load_channel_recommendations_from_database(ChannelId channel_id, string value) {
|
||||||
|
if (G()->close_flag()) {
|
||||||
|
return fail_load_channel_recommendations_queries(channel_id, G()->close_status());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.empty()) {
|
||||||
|
return reload_channel_recommendations(channel_id);
|
||||||
|
}
|
||||||
|
auto &recommended_dialogs = channel_recommended_dialogs_[channel_id];
|
||||||
|
if (log_event_parse(recommended_dialogs, value).is_error()) {
|
||||||
|
channel_recommended_dialogs_.erase(channel_id);
|
||||||
|
G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto());
|
||||||
|
return reload_channel_recommendations(channel_id);
|
||||||
|
}
|
||||||
|
Dependencies dependencies;
|
||||||
|
for (auto dialog_id : recommended_dialogs.dialog_ids_) {
|
||||||
|
dependencies.add_dialog_and_dependencies(dialog_id);
|
||||||
|
}
|
||||||
|
if (!dependencies.resolve_force(td_, "on_load_channel_recommendations_from_database") ||
|
||||||
|
!are_suitable_recommended_dialogs(recommended_dialogs)) {
|
||||||
|
channel_recommended_dialogs_.erase(channel_id);
|
||||||
|
G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto());
|
||||||
|
return reload_channel_recommendations(channel_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto next_reload_time = recommended_dialogs.next_reload_time_;
|
||||||
|
finish_load_channel_recommendations_queries(channel_id, recommended_dialogs.total_count_,
|
||||||
|
recommended_dialogs.dialog_ids_);
|
||||||
|
|
||||||
|
if (next_reload_time <= Time::now()) {
|
||||||
|
load_channel_recommendations(channel_id, false, false, Auto(), Auto());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRecommendationManager::reload_channel_recommendations(ChannelId channel_id) {
|
||||||
|
auto it = get_channel_recommendation_count_queries_[1].find(channel_id);
|
||||||
|
if (it != get_channel_recommendation_count_queries_[1].end()) {
|
||||||
|
auto promises = std::move(it->second);
|
||||||
|
CHECK(!promises.empty());
|
||||||
|
get_channel_recommendation_count_queries_[1].erase(it);
|
||||||
|
for (auto &promise : promises) {
|
||||||
|
promise.set_value(td_api::make_object<td_api::count>(-1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto query_promise =
|
||||||
|
PromiseCreator::lambda([actor_id = actor_id(this), channel_id](
|
||||||
|
Result<std::pair<int32, vector<tl_object_ptr<telegram_api::Chat>>>> &&result) {
|
||||||
|
send_closure(actor_id, &ChannelRecommendationManager::on_get_channel_recommendations, channel_id,
|
||||||
|
std::move(result));
|
||||||
|
});
|
||||||
|
td_->create_handler<GetChannelRecommendationsQuery>(std::move(query_promise))->send(channel_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRecommendationManager::on_get_channel_recommendations(
|
||||||
|
ChannelId channel_id, Result<std::pair<int32, vector<tl_object_ptr<telegram_api::Chat>>>> &&r_chats) {
|
||||||
|
G()->ignore_result_if_closing(r_chats);
|
||||||
|
|
||||||
|
if (r_chats.is_error()) {
|
||||||
|
return fail_load_channel_recommendations_queries(channel_id, r_chats.move_as_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto chats = r_chats.move_as_ok();
|
||||||
|
auto total_count = chats.first;
|
||||||
|
auto channel_ids = td_->contacts_manager_->get_channel_ids(std::move(chats.second), "on_get_channel_recommendations");
|
||||||
|
vector<DialogId> dialog_ids;
|
||||||
|
if (total_count < static_cast<int32>(channel_ids.size())) {
|
||||||
|
LOG(ERROR) << "Receive total_count = " << total_count << " and " << channel_ids.size() << " similar chats for "
|
||||||
|
<< channel_id;
|
||||||
|
total_count = static_cast<int32>(channel_ids.size());
|
||||||
|
}
|
||||||
|
for (auto recommended_channel_id : channel_ids) {
|
||||||
|
auto recommended_dialog_id = DialogId(recommended_channel_id);
|
||||||
|
td_->dialog_manager_->force_create_dialog(recommended_dialog_id, "on_get_channel_recommendations");
|
||||||
|
if (is_suitable_recommended_channel(recommended_channel_id)) {
|
||||||
|
dialog_ids.push_back(recommended_dialog_id);
|
||||||
|
} else {
|
||||||
|
total_count--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto &recommended_dialogs = channel_recommended_dialogs_[channel_id];
|
||||||
|
recommended_dialogs.total_count_ = total_count;
|
||||||
|
recommended_dialogs.dialog_ids_ = dialog_ids;
|
||||||
|
recommended_dialogs.next_reload_time_ = Time::now() + CHANNEL_RECOMMENDATIONS_CACHE_TIME;
|
||||||
|
|
||||||
|
if (G()->use_message_database()) {
|
||||||
|
G()->td_db()->get_sqlite_pmc()->set(get_channel_recommendations_database_key(channel_id),
|
||||||
|
log_event_store(recommended_dialogs).as_slice().str(), Promise<Unit>());
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_load_channel_recommendations_queries(channel_id, total_count, std::move(dialog_ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelRecommendationManager::open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id,
|
||||||
|
Promise<Unit> &&promise) {
|
||||||
|
if (!td_->dialog_manager_->have_dialog_force(dialog_id, "open_channel_recommended_channel") ||
|
||||||
|
!td_->dialog_manager_->have_dialog_force(opened_dialog_id, "open_channel_recommended_channel")) {
|
||||||
|
return promise.set_error(Status::Error(400, "Chat not found"));
|
||||||
|
}
|
||||||
|
if (dialog_id.get_type() != DialogType::Channel || opened_dialog_id.get_type() != DialogType::Channel) {
|
||||||
|
return promise.set_error(Status::Error(400, "Invalid chat specified"));
|
||||||
|
}
|
||||||
|
vector<telegram_api::object_ptr<telegram_api::jsonObjectValue>> data;
|
||||||
|
data.push_back(telegram_api::make_object<telegram_api::jsonObjectValue>(
|
||||||
|
"ref_channel_id", make_tl_object<telegram_api::jsonString>(to_string(dialog_id.get_channel_id().get()))));
|
||||||
|
data.push_back(telegram_api::make_object<telegram_api::jsonObjectValue>(
|
||||||
|
"open_channel_id", make_tl_object<telegram_api::jsonString>(to_string(opened_dialog_id.get_channel_id().get()))));
|
||||||
|
save_app_log(td_, "channels.open_recommended_channel", DialogId(),
|
||||||
|
telegram_api::make_object<telegram_api::jsonObject>(std::move(data)), std::move(promise));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace td
|
} // namespace td
|
||||||
|
@ -6,9 +6,19 @@
|
|||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "td/telegram/ChannelId.h"
|
||||||
|
#include "td/telegram/DialogId.h"
|
||||||
|
#include "td/telegram/td_api.h"
|
||||||
|
#include "td/telegram/telegram_api.h"
|
||||||
|
|
||||||
#include "td/actor/actor.h"
|
#include "td/actor/actor.h"
|
||||||
|
|
||||||
#include "td/utils/common.h"
|
#include "td/utils/common.h"
|
||||||
|
#include "td/utils/FlatHashMap.h"
|
||||||
|
#include "td/utils/Promise.h"
|
||||||
|
#include "td/utils/Status.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace td {
|
namespace td {
|
||||||
|
|
||||||
@ -18,9 +28,59 @@ class ChannelRecommendationManager final : public Actor {
|
|||||||
public:
|
public:
|
||||||
ChannelRecommendationManager(Td *td, ActorShared<> parent);
|
ChannelRecommendationManager(Td *td, ActorShared<> parent);
|
||||||
|
|
||||||
|
void get_channel_recommendations(DialogId dialog_id, bool return_local,
|
||||||
|
Promise<td_api::object_ptr<td_api::chats>> &&chats_promise,
|
||||||
|
Promise<td_api::object_ptr<td_api::count>> &&count_promise);
|
||||||
|
|
||||||
|
void open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id, Promise<Unit> &&promise);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr int32 CHANNEL_RECOMMENDATIONS_CACHE_TIME = 86400; // some reasonable limit
|
||||||
|
|
||||||
|
struct RecommendedDialogs {
|
||||||
|
int32 total_count_ = 0;
|
||||||
|
vector<DialogId> dialog_ids_;
|
||||||
|
double next_reload_time_ = 0.0;
|
||||||
|
|
||||||
|
template <class StorerT>
|
||||||
|
void store(StorerT &storer) const;
|
||||||
|
|
||||||
|
template <class ParserT>
|
||||||
|
void parse(ParserT &parser);
|
||||||
|
};
|
||||||
|
|
||||||
void tear_down() final;
|
void tear_down() final;
|
||||||
|
|
||||||
|
bool is_suitable_recommended_channel(DialogId dialog_id) const;
|
||||||
|
|
||||||
|
bool is_suitable_recommended_channel(ChannelId channel_id) const;
|
||||||
|
|
||||||
|
bool are_suitable_recommended_dialogs(const RecommendedDialogs &recommended_dialogs) const;
|
||||||
|
|
||||||
|
static string get_channel_recommendations_database_key(ChannelId channel_id);
|
||||||
|
|
||||||
|
void load_channel_recommendations(ChannelId channel_id, bool use_database, bool return_local,
|
||||||
|
Promise<td_api::object_ptr<td_api::chats>> &&chats_promise,
|
||||||
|
Promise<td_api::object_ptr<td_api::count>> &&count_promise);
|
||||||
|
|
||||||
|
void fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error);
|
||||||
|
|
||||||
|
void finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count,
|
||||||
|
vector<DialogId> dialog_ids);
|
||||||
|
|
||||||
|
void on_load_channel_recommendations_from_database(ChannelId channel_id, string value);
|
||||||
|
|
||||||
|
void reload_channel_recommendations(ChannelId channel_id);
|
||||||
|
|
||||||
|
void on_get_channel_recommendations(
|
||||||
|
ChannelId channel_id, Result<std::pair<int32, vector<telegram_api::object_ptr<telegram_api::Chat>>>> &&r_chats);
|
||||||
|
|
||||||
|
FlatHashMap<ChannelId, RecommendedDialogs, ChannelIdHash> channel_recommended_dialogs_;
|
||||||
|
FlatHashMap<ChannelId, vector<Promise<td_api::object_ptr<td_api::chats>>>, ChannelIdHash>
|
||||||
|
get_channel_recommendations_queries_;
|
||||||
|
FlatHashMap<ChannelId, vector<Promise<td_api::object_ptr<td_api::count>>>, ChannelIdHash>
|
||||||
|
get_channel_recommendation_count_queries_[2];
|
||||||
|
|
||||||
Td *td_;
|
Td *td_;
|
||||||
ActorShared<> parent_;
|
ActorShared<> parent_;
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#include "td/telegram/ContactsManager.h"
|
#include "td/telegram/ContactsManager.h"
|
||||||
|
|
||||||
#include "td/telegram/AnimationsManager.h"
|
#include "td/telegram/AnimationsManager.h"
|
||||||
#include "td/telegram/Application.h"
|
|
||||||
#include "td/telegram/AuthManager.h"
|
#include "td/telegram/AuthManager.h"
|
||||||
#include "td/telegram/BlockListId.h"
|
#include "td/telegram/BlockListId.h"
|
||||||
#include "td/telegram/BotMenuButton.h"
|
#include "td/telegram/BotMenuButton.h"
|
||||||
@ -2266,55 +2265,6 @@ class MigrateChatQuery final : public Td::ResultHandler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class GetChannelRecommendationsQuery final : public Td::ResultHandler {
|
|
||||||
Promise<std::pair<int32, vector<tl_object_ptr<telegram_api::Chat>>>> promise_;
|
|
||||||
ChannelId channel_id_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit GetChannelRecommendationsQuery(
|
|
||||||
Promise<std::pair<int32, vector<tl_object_ptr<telegram_api::Chat>>>> &&promise)
|
|
||||||
: promise_(std::move(promise)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void send(ChannelId channel_id) {
|
|
||||||
channel_id_ = channel_id;
|
|
||||||
|
|
||||||
auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
|
|
||||||
CHECK(input_channel != nullptr);
|
|
||||||
send_query(
|
|
||||||
G()->net_query_creator().create(telegram_api::channels_getChannelRecommendations(std::move(input_channel))));
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_result(BufferSlice packet) final {
|
|
||||||
auto result_ptr = fetch_result<telegram_api::channels_getChannelRecommendations>(packet);
|
|
||||||
if (result_ptr.is_error()) {
|
|
||||||
return on_error(result_ptr.move_as_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto chats_ptr = result_ptr.move_as_ok();
|
|
||||||
LOG(INFO) << "Receive result for GetChannelRecommendationsQuery: " << to_string(chats_ptr);
|
|
||||||
switch (chats_ptr->get_id()) {
|
|
||||||
case telegram_api::messages_chats::ID: {
|
|
||||||
auto chats = move_tl_object_as<telegram_api::messages_chats>(chats_ptr);
|
|
||||||
auto total_count = static_cast<int32>(chats->chats_.size());
|
|
||||||
return promise_.set_value({total_count, std::move(chats->chats_)});
|
|
||||||
}
|
|
||||||
case telegram_api::messages_chatsSlice::ID: {
|
|
||||||
auto chats = move_tl_object_as<telegram_api::messages_chatsSlice>(chats_ptr);
|
|
||||||
return promise_.set_value({chats->count_, std::move(chats->chats_)});
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
UNREACHABLE();
|
|
||||||
return promise_.set_error(Status::Error("Unreachable"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void on_error(Status status) final {
|
|
||||||
td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelRecommendationsQuery");
|
|
||||||
promise_.set_error(std::move(status));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class GetCreatedPublicChannelsQuery final : public Td::ResultHandler {
|
class GetCreatedPublicChannelsQuery final : public Td::ResultHandler {
|
||||||
Promise<Unit> promise_;
|
Promise<Unit> promise_;
|
||||||
PublicDialogType type_;
|
PublicDialogType type_;
|
||||||
@ -2806,9 +2756,6 @@ ContactsManager::ContactsManager(Td *td, ActorShared<> parent) : td_(td), parent
|
|||||||
}
|
}
|
||||||
if (G()->use_sqlite_pmc()) {
|
if (G()->use_sqlite_pmc()) {
|
||||||
G()->td_db()->get_sqlite_pmc()->erase_by_prefix("us_bot_info", Auto());
|
G()->td_db()->get_sqlite_pmc()->erase_by_prefix("us_bot_info", Auto());
|
||||||
if (!G()->use_message_database()) {
|
|
||||||
G()->td_db()->get_sqlite_pmc()->erase_by_prefix("channel_recommendations", Auto());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!td_->auth_manager_->is_bot()) {
|
if (!td_->auth_manager_->is_bot()) {
|
||||||
@ -4331,42 +4278,6 @@ void ContactsManager::SecretChat::parse(ParserT &parser) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class StorerT>
|
|
||||||
void ContactsManager::RecommendedDialogs::store(StorerT &storer) const {
|
|
||||||
bool has_dialog_ids = !dialog_ids_.empty();
|
|
||||||
bool has_total_count = static_cast<size_t>(total_count_) != dialog_ids_.size();
|
|
||||||
BEGIN_STORE_FLAGS();
|
|
||||||
STORE_FLAG(has_dialog_ids);
|
|
||||||
STORE_FLAG(has_total_count);
|
|
||||||
END_STORE_FLAGS();
|
|
||||||
if (has_dialog_ids) {
|
|
||||||
td::store(dialog_ids_, storer);
|
|
||||||
}
|
|
||||||
store_time(next_reload_time_, storer);
|
|
||||||
if (has_total_count) {
|
|
||||||
td::store(total_count_, storer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ParserT>
|
|
||||||
void ContactsManager::RecommendedDialogs::parse(ParserT &parser) {
|
|
||||||
bool has_dialog_ids;
|
|
||||||
bool has_total_count;
|
|
||||||
BEGIN_PARSE_FLAGS();
|
|
||||||
PARSE_FLAG(has_dialog_ids);
|
|
||||||
PARSE_FLAG(has_total_count);
|
|
||||||
END_PARSE_FLAGS();
|
|
||||||
if (has_dialog_ids) {
|
|
||||||
td::parse(dialog_ids_, parser);
|
|
||||||
}
|
|
||||||
parse_time(next_reload_time_, parser);
|
|
||||||
if (has_total_count) {
|
|
||||||
td::parse(total_count_, parser);
|
|
||||||
} else {
|
|
||||||
total_count_ = static_cast<int32>(dialog_ids_.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<tl_object_ptr<telegram_api::InputUser>> ContactsManager::get_input_user(UserId user_id) const {
|
Result<tl_object_ptr<telegram_api::InputUser>> ContactsManager::get_input_user(UserId user_id) const {
|
||||||
if (user_id == get_my_id()) {
|
if (user_id == get_my_id()) {
|
||||||
return make_tl_object<telegram_api::inputUserSelf>();
|
return make_tl_object<telegram_api::inputUserSelf>();
|
||||||
@ -7331,275 +7242,6 @@ vector<DialogId> ContactsManager::get_dialog_ids(vector<tl_object_ptr<telegram_a
|
|||||||
return dialog_ids;
|
return dialog_ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContactsManager::is_suitable_recommended_channel(DialogId dialog_id) const {
|
|
||||||
if (dialog_id.get_type() != DialogType::Channel) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return is_suitable_recommended_channel(dialog_id.get_channel_id());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ContactsManager::is_suitable_recommended_channel(ChannelId channel_id) const {
|
|
||||||
const Channel *c = get_channel(channel_id);
|
|
||||||
if (c == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return have_input_peer_channel(c, channel_id, AccessRights::Read) && !get_channel_status(c).is_member();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ContactsManager::are_suitable_recommended_dialogs(const RecommendedDialogs &recommended_dialogs) const {
|
|
||||||
for (auto recommended_dialog_id : recommended_dialogs.dialog_ids_) {
|
|
||||||
if (!is_suitable_recommended_channel(recommended_dialog_id)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto is_premium = td_->option_manager_->get_option_boolean("is_premium");
|
|
||||||
auto have_all = recommended_dialogs.dialog_ids_.size() == static_cast<size_t>(recommended_dialogs.total_count_);
|
|
||||||
if (!have_all && is_premium) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContactsManager::get_channel_recommendations(DialogId dialog_id, bool return_local,
|
|
||||||
Promise<td_api::object_ptr<td_api::chats>> &&chats_promise,
|
|
||||||
Promise<td_api::object_ptr<td_api::count>> &&count_promise) {
|
|
||||||
if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_channel_recommendations")) {
|
|
||||||
if (chats_promise) {
|
|
||||||
chats_promise.set_error(Status::Error(400, "Chat not found"));
|
|
||||||
}
|
|
||||||
if (count_promise) {
|
|
||||||
count_promise.set_error(Status::Error(400, "Chat not found"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (dialog_id.get_type() != DialogType::Channel) {
|
|
||||||
if (chats_promise) {
|
|
||||||
chats_promise.set_value(td_api::make_object<td_api::chats>());
|
|
||||||
}
|
|
||||||
if (count_promise) {
|
|
||||||
count_promise.set_value(td_api::make_object<td_api::count>(0));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto channel_id = dialog_id.get_channel_id();
|
|
||||||
if (!is_broadcast_channel(channel_id) || get_input_channel(channel_id) == nullptr) {
|
|
||||||
if (chats_promise) {
|
|
||||||
chats_promise.set_value(td_api::make_object<td_api::chats>());
|
|
||||||
}
|
|
||||||
if (count_promise) {
|
|
||||||
count_promise.set_value(td_api::make_object<td_api::count>(0));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bool use_database = true;
|
|
||||||
auto it = channel_recommended_dialogs_.find(channel_id);
|
|
||||||
if (it != channel_recommended_dialogs_.end()) {
|
|
||||||
if (are_suitable_recommended_dialogs(it->second)) {
|
|
||||||
auto next_reload_time = it->second.next_reload_time_;
|
|
||||||
if (chats_promise) {
|
|
||||||
chats_promise.set_value(td_->dialog_manager_->get_chats_object(it->second.total_count_, it->second.dialog_ids_,
|
|
||||||
"get_channel_recommendations"));
|
|
||||||
}
|
|
||||||
if (count_promise) {
|
|
||||||
count_promise.set_value(td_api::make_object<td_api::count>(it->second.total_count_));
|
|
||||||
}
|
|
||||||
if (next_reload_time > Time::now()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
chats_promise = {};
|
|
||||||
count_promise = {};
|
|
||||||
} else {
|
|
||||||
LOG(INFO) << "Drop cache for similar chats of " << dialog_id;
|
|
||||||
channel_recommended_dialogs_.erase(it);
|
|
||||||
if (G()->use_message_database()) {
|
|
||||||
G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
use_database = false;
|
|
||||||
}
|
|
||||||
load_channel_recommendations(channel_id, use_database, return_local, std::move(chats_promise),
|
|
||||||
std::move(count_promise));
|
|
||||||
}
|
|
||||||
|
|
||||||
string ContactsManager::get_channel_recommendations_database_key(ChannelId channel_id) {
|
|
||||||
return PSTRING() << "channel_recommendations" << channel_id.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContactsManager::load_channel_recommendations(ChannelId channel_id, bool use_database, bool return_local,
|
|
||||||
Promise<td_api::object_ptr<td_api::chats>> &&chats_promise,
|
|
||||||
Promise<td_api::object_ptr<td_api::count>> &&count_promise) {
|
|
||||||
if (count_promise) {
|
|
||||||
get_channel_recommendation_count_queries_[return_local][channel_id].push_back(std::move(count_promise));
|
|
||||||
}
|
|
||||||
auto &queries = get_channel_recommendations_queries_[channel_id];
|
|
||||||
queries.push_back(std::move(chats_promise));
|
|
||||||
if (queries.size() == 1) {
|
|
||||||
if (G()->use_message_database() && use_database) {
|
|
||||||
G()->td_db()->get_sqlite_pmc()->get(
|
|
||||||
get_channel_recommendations_database_key(channel_id),
|
|
||||||
PromiseCreator::lambda([actor_id = actor_id(this), channel_id](string value) {
|
|
||||||
send_closure(actor_id, &ContactsManager::on_load_channel_recommendations_from_database, channel_id,
|
|
||||||
std::move(value));
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
reload_channel_recommendations(channel_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContactsManager::fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error) {
|
|
||||||
for (int return_local = 0; return_local < 2; return_local++) {
|
|
||||||
auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id);
|
|
||||||
if (it != get_channel_recommendation_count_queries_[return_local].end()) {
|
|
||||||
auto promises = std::move(it->second);
|
|
||||||
CHECK(!promises.empty());
|
|
||||||
get_channel_recommendation_count_queries_[return_local].erase(it);
|
|
||||||
fail_promises(promises, error.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto it = get_channel_recommendations_queries_.find(channel_id);
|
|
||||||
CHECK(it != get_channel_recommendations_queries_.end());
|
|
||||||
auto promises = std::move(it->second);
|
|
||||||
CHECK(!promises.empty());
|
|
||||||
get_channel_recommendations_queries_.erase(it);
|
|
||||||
fail_promises(promises, std::move(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContactsManager::finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count,
|
|
||||||
vector<DialogId> dialog_ids) {
|
|
||||||
for (int return_local = 0; return_local < 2; return_local++) {
|
|
||||||
auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id);
|
|
||||||
if (it != get_channel_recommendation_count_queries_[return_local].end()) {
|
|
||||||
auto promises = std::move(it->second);
|
|
||||||
CHECK(!promises.empty());
|
|
||||||
get_channel_recommendation_count_queries_[return_local].erase(it);
|
|
||||||
for (auto &promise : promises) {
|
|
||||||
promise.set_value(td_api::make_object<td_api::count>(total_count));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto it = get_channel_recommendations_queries_.find(channel_id);
|
|
||||||
CHECK(it != get_channel_recommendations_queries_.end());
|
|
||||||
auto promises = std::move(it->second);
|
|
||||||
CHECK(!promises.empty());
|
|
||||||
get_channel_recommendations_queries_.erase(it);
|
|
||||||
for (auto &promise : promises) {
|
|
||||||
if (promise) {
|
|
||||||
promise.set_value(td_->dialog_manager_->get_chats_object(total_count, dialog_ids,
|
|
||||||
"finish_load_channel_recommendations_queries"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContactsManager::on_load_channel_recommendations_from_database(ChannelId channel_id, string value) {
|
|
||||||
if (G()->close_flag()) {
|
|
||||||
return fail_load_channel_recommendations_queries(channel_id, G()->close_status());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.empty()) {
|
|
||||||
return reload_channel_recommendations(channel_id);
|
|
||||||
}
|
|
||||||
auto &recommended_dialogs = channel_recommended_dialogs_[channel_id];
|
|
||||||
if (log_event_parse(recommended_dialogs, value).is_error()) {
|
|
||||||
channel_recommended_dialogs_.erase(channel_id);
|
|
||||||
G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto());
|
|
||||||
return reload_channel_recommendations(channel_id);
|
|
||||||
}
|
|
||||||
Dependencies dependencies;
|
|
||||||
for (auto dialog_id : recommended_dialogs.dialog_ids_) {
|
|
||||||
dependencies.add_dialog_and_dependencies(dialog_id);
|
|
||||||
}
|
|
||||||
if (!dependencies.resolve_force(td_, "on_load_channel_recommendations_from_database") ||
|
|
||||||
!are_suitable_recommended_dialogs(recommended_dialogs)) {
|
|
||||||
channel_recommended_dialogs_.erase(channel_id);
|
|
||||||
G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto());
|
|
||||||
return reload_channel_recommendations(channel_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto next_reload_time = recommended_dialogs.next_reload_time_;
|
|
||||||
finish_load_channel_recommendations_queries(channel_id, recommended_dialogs.total_count_,
|
|
||||||
recommended_dialogs.dialog_ids_);
|
|
||||||
|
|
||||||
if (next_reload_time <= Time::now()) {
|
|
||||||
load_channel_recommendations(channel_id, false, false, Auto(), Auto());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContactsManager::reload_channel_recommendations(ChannelId channel_id) {
|
|
||||||
auto it = get_channel_recommendation_count_queries_[1].find(channel_id);
|
|
||||||
if (it != get_channel_recommendation_count_queries_[1].end()) {
|
|
||||||
auto promises = std::move(it->second);
|
|
||||||
CHECK(!promises.empty());
|
|
||||||
get_channel_recommendation_count_queries_[1].erase(it);
|
|
||||||
for (auto &promise : promises) {
|
|
||||||
promise.set_value(td_api::make_object<td_api::count>(-1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto query_promise =
|
|
||||||
PromiseCreator::lambda([actor_id = actor_id(this), channel_id](
|
|
||||||
Result<std::pair<int32, vector<tl_object_ptr<telegram_api::Chat>>>> &&result) {
|
|
||||||
send_closure(actor_id, &ContactsManager::on_get_channel_recommendations, channel_id, std::move(result));
|
|
||||||
});
|
|
||||||
td_->create_handler<GetChannelRecommendationsQuery>(std::move(query_promise))->send(channel_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContactsManager::on_get_channel_recommendations(
|
|
||||||
ChannelId channel_id, Result<std::pair<int32, vector<tl_object_ptr<telegram_api::Chat>>>> &&r_chats) {
|
|
||||||
G()->ignore_result_if_closing(r_chats);
|
|
||||||
|
|
||||||
if (r_chats.is_error()) {
|
|
||||||
return fail_load_channel_recommendations_queries(channel_id, r_chats.move_as_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto chats = r_chats.move_as_ok();
|
|
||||||
auto total_count = chats.first;
|
|
||||||
auto channel_ids = get_channel_ids(std::move(chats.second), "on_get_channel_recommendations");
|
|
||||||
vector<DialogId> dialog_ids;
|
|
||||||
if (total_count < static_cast<int32>(channel_ids.size())) {
|
|
||||||
LOG(ERROR) << "Receive total_count = " << total_count << " and " << channel_ids.size() << " similar chats for "
|
|
||||||
<< channel_id;
|
|
||||||
total_count = static_cast<int32>(channel_ids.size());
|
|
||||||
}
|
|
||||||
for (auto recommended_channel_id : channel_ids) {
|
|
||||||
auto recommended_dialog_id = DialogId(recommended_channel_id);
|
|
||||||
td_->dialog_manager_->force_create_dialog(recommended_dialog_id, "on_get_channel_recommendations");
|
|
||||||
if (is_suitable_recommended_channel(recommended_channel_id)) {
|
|
||||||
dialog_ids.push_back(recommended_dialog_id);
|
|
||||||
} else {
|
|
||||||
total_count--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto &recommended_dialogs = channel_recommended_dialogs_[channel_id];
|
|
||||||
recommended_dialogs.total_count_ = total_count;
|
|
||||||
recommended_dialogs.dialog_ids_ = dialog_ids;
|
|
||||||
recommended_dialogs.next_reload_time_ = Time::now() + CHANNEL_RECOMMENDATIONS_CACHE_TIME;
|
|
||||||
|
|
||||||
if (G()->use_message_database()) {
|
|
||||||
G()->td_db()->get_sqlite_pmc()->set(get_channel_recommendations_database_key(channel_id),
|
|
||||||
log_event_store(recommended_dialogs).as_slice().str(), Promise<Unit>());
|
|
||||||
}
|
|
||||||
|
|
||||||
finish_load_channel_recommendations_queries(channel_id, total_count, std::move(dialog_ids));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContactsManager::open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id,
|
|
||||||
Promise<Unit> &&promise) {
|
|
||||||
if (!td_->dialog_manager_->have_dialog_force(dialog_id, "open_channel_recommended_channel") ||
|
|
||||||
!td_->dialog_manager_->have_dialog_force(opened_dialog_id, "open_channel_recommended_channel")) {
|
|
||||||
return promise.set_error(Status::Error(400, "Chat not found"));
|
|
||||||
}
|
|
||||||
if (dialog_id.get_type() != DialogType::Channel || opened_dialog_id.get_type() != DialogType::Channel) {
|
|
||||||
return promise.set_error(Status::Error(400, "Invalid chat specified"));
|
|
||||||
}
|
|
||||||
vector<telegram_api::object_ptr<telegram_api::jsonObjectValue>> data;
|
|
||||||
data.push_back(telegram_api::make_object<telegram_api::jsonObjectValue>(
|
|
||||||
"ref_channel_id", make_tl_object<telegram_api::jsonString>(to_string(dialog_id.get_channel_id().get()))));
|
|
||||||
data.push_back(telegram_api::make_object<telegram_api::jsonObjectValue>(
|
|
||||||
"open_channel_id", make_tl_object<telegram_api::jsonString>(to_string(opened_dialog_id.get_channel_id().get()))));
|
|
||||||
save_app_log(td_, "channels.open_recommended_channel", DialogId(),
|
|
||||||
telegram_api::make_object<telegram_api::jsonObject>(std::move(data)), std::move(promise));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContactsManager::return_created_public_dialogs(Promise<td_api::object_ptr<td_api::chats>> &&promise,
|
void ContactsManager::return_created_public_dialogs(Promise<td_api::object_ptr<td_api::chats>> &&promise,
|
||||||
const vector<ChannelId> &channel_ids) {
|
const vector<ChannelId> &channel_ids) {
|
||||||
if (!promise) {
|
if (!promise) {
|
||||||
@ -10503,9 +10145,6 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from
|
|||||||
if (c->had_read_access && !have_read_access) {
|
if (c->had_read_access && !have_read_access) {
|
||||||
send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id),
|
send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id),
|
||||||
Promise<Unit>());
|
Promise<Unit>());
|
||||||
if (G()->use_message_database()) {
|
|
||||||
G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Promise<Unit>());
|
|
||||||
}
|
|
||||||
} else if (!from_database && c->was_member != is_member) {
|
} else if (!from_database && c->was_member != is_member) {
|
||||||
DialogId dialog_id(channel_id);
|
DialogId dialog_id(channel_id);
|
||||||
send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "update channel",
|
send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "update channel",
|
||||||
|
@ -528,15 +528,9 @@ class ContactsManager final : public Actor {
|
|||||||
|
|
||||||
void migrate_dialog_to_megagroup(DialogId dialog_id, Promise<td_api::object_ptr<td_api::chat>> &&promise);
|
void migrate_dialog_to_megagroup(DialogId dialog_id, Promise<td_api::object_ptr<td_api::chat>> &&promise);
|
||||||
|
|
||||||
void get_channel_recommendations(DialogId dialog_id, bool return_local,
|
|
||||||
Promise<td_api::object_ptr<td_api::chats>> &&chats_promise,
|
|
||||||
Promise<td_api::object_ptr<td_api::count>> &&count_promise);
|
|
||||||
|
|
||||||
void get_created_public_dialogs(PublicDialogType type, Promise<td_api::object_ptr<td_api::chats>> &&promise,
|
void get_created_public_dialogs(PublicDialogType type, Promise<td_api::object_ptr<td_api::chats>> &&promise,
|
||||||
bool from_binlog);
|
bool from_binlog);
|
||||||
|
|
||||||
void open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id, Promise<Unit> &&promise);
|
|
||||||
|
|
||||||
void check_created_public_dialogs_limit(PublicDialogType type, Promise<Unit> &&promise);
|
void check_created_public_dialogs_limit(PublicDialogType type, Promise<Unit> &&promise);
|
||||||
|
|
||||||
void reload_created_public_dialogs(PublicDialogType type, Promise<td_api::object_ptr<td_api::chats>> &&promise);
|
void reload_created_public_dialogs(PublicDialogType type, Promise<td_api::object_ptr<td_api::chats>> &&promise);
|
||||||
@ -1127,18 +1121,6 @@ class ContactsManager final : public Actor {
|
|||||||
vector<PendingGetPhotoRequest> pending_requests;
|
vector<PendingGetPhotoRequest> pending_requests;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RecommendedDialogs {
|
|
||||||
int32 total_count_ = 0;
|
|
||||||
vector<DialogId> dialog_ids_;
|
|
||||||
double next_reload_time_ = 0.0;
|
|
||||||
|
|
||||||
template <class StorerT>
|
|
||||||
void store(StorerT &storer) const;
|
|
||||||
|
|
||||||
template <class ParserT>
|
|
||||||
void parse(ParserT &parser);
|
|
||||||
};
|
|
||||||
|
|
||||||
class UserLogEvent;
|
class UserLogEvent;
|
||||||
class ChatLogEvent;
|
class ChatLogEvent;
|
||||||
class ChannelLogEvent;
|
class ChannelLogEvent;
|
||||||
@ -1150,7 +1132,6 @@ class ContactsManager final : public Actor {
|
|||||||
static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat/channel description
|
static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat/channel description
|
||||||
|
|
||||||
static constexpr int32 MAX_ACTIVE_STORY_ID_RELOAD_TIME = 3600; // some reasonable limit
|
static constexpr int32 MAX_ACTIVE_STORY_ID_RELOAD_TIME = 3600; // some reasonable limit
|
||||||
static constexpr int32 CHANNEL_RECOMMENDATIONS_CACHE_TIME = 86400; // some reasonable limit
|
|
||||||
|
|
||||||
// the True fields aren't set for manually created telegram_api::user objects, therefore the flags must be used
|
// the True fields aren't set for manually created telegram_api::user objects, therefore the flags must be used
|
||||||
static constexpr int32 USER_FLAG_HAS_ACCESS_HASH = 1 << 0;
|
static constexpr int32 USER_FLAG_HAS_ACCESS_HASH = 1 << 0;
|
||||||
@ -1623,30 +1604,6 @@ class ContactsManager final : public Actor {
|
|||||||
void on_clear_imported_contacts(vector<Contact> &&contacts, vector<size_t> contacts_unique_id,
|
void on_clear_imported_contacts(vector<Contact> &&contacts, vector<size_t> contacts_unique_id,
|
||||||
std::pair<vector<size_t>, vector<Contact>> &&to_add, Promise<Unit> &&promise);
|
std::pair<vector<size_t>, vector<Contact>> &&to_add, Promise<Unit> &&promise);
|
||||||
|
|
||||||
bool is_suitable_recommended_channel(DialogId dialog_id) const;
|
|
||||||
|
|
||||||
bool is_suitable_recommended_channel(ChannelId channel_id) const;
|
|
||||||
|
|
||||||
bool are_suitable_recommended_dialogs(const RecommendedDialogs &recommended_dialogs) const;
|
|
||||||
|
|
||||||
static string get_channel_recommendations_database_key(ChannelId channel_id);
|
|
||||||
|
|
||||||
void load_channel_recommendations(ChannelId channel_id, bool use_database, bool return_local,
|
|
||||||
Promise<td_api::object_ptr<td_api::chats>> &&chats_promise,
|
|
||||||
Promise<td_api::object_ptr<td_api::count>> &&count_promise);
|
|
||||||
|
|
||||||
void fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error);
|
|
||||||
|
|
||||||
void finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count,
|
|
||||||
vector<DialogId> dialog_ids);
|
|
||||||
|
|
||||||
void on_load_channel_recommendations_from_database(ChannelId channel_id, string value);
|
|
||||||
|
|
||||||
void reload_channel_recommendations(ChannelId channel_id);
|
|
||||||
|
|
||||||
void on_get_channel_recommendations(ChannelId channel_id,
|
|
||||||
Result<std::pair<int32, vector<tl_object_ptr<telegram_api::Chat>>>> &&r_chats);
|
|
||||||
|
|
||||||
static bool is_channel_public(const Channel *c);
|
static bool is_channel_public(const Channel *c);
|
||||||
|
|
||||||
static bool is_suitable_created_public_channel(PublicDialogType type, const Channel *c);
|
static bool is_suitable_created_public_channel(PublicDialogType type, const Channel *c);
|
||||||
@ -1804,12 +1761,6 @@ class ContactsManager final : public Actor {
|
|||||||
|
|
||||||
FlatHashMap<UserId, vector<SecretChatId>, UserIdHash> secret_chats_with_user_;
|
FlatHashMap<UserId, vector<SecretChatId>, UserIdHash> secret_chats_with_user_;
|
||||||
|
|
||||||
FlatHashMap<ChannelId, RecommendedDialogs, ChannelIdHash> channel_recommended_dialogs_;
|
|
||||||
FlatHashMap<ChannelId, vector<Promise<td_api::object_ptr<td_api::chats>>>, ChannelIdHash>
|
|
||||||
get_channel_recommendations_queries_;
|
|
||||||
FlatHashMap<ChannelId, vector<Promise<td_api::object_ptr<td_api::count>>>, ChannelIdHash>
|
|
||||||
get_channel_recommendation_count_queries_[2];
|
|
||||||
|
|
||||||
bool created_public_channels_inited_[2] = {false, false};
|
bool created_public_channels_inited_[2] = {false, false};
|
||||||
vector<ChannelId> created_public_channels_[2];
|
vector<ChannelId> created_public_channels_[2];
|
||||||
vector<Promise<td_api::object_ptr<td_api::chats>>> get_created_public_channels_queries_[2];
|
vector<Promise<td_api::object_ptr<td_api::chats>>> get_created_public_channels_queries_[2];
|
||||||
|
@ -5041,21 +5041,22 @@ void Td::on_request(uint64 id, const td_api::clearAutosaveSettingsExceptions &re
|
|||||||
void Td::on_request(uint64 id, const td_api::getChatSimilarChats &request) {
|
void Td::on_request(uint64 id, const td_api::getChatSimilarChats &request) {
|
||||||
CHECK_IS_USER();
|
CHECK_IS_USER();
|
||||||
CREATE_REQUEST_PROMISE();
|
CREATE_REQUEST_PROMISE();
|
||||||
contacts_manager_->get_channel_recommendations(DialogId(request.chat_id_), false, std::move(promise), Auto());
|
channel_recommendation_manager_->get_channel_recommendations(DialogId(request.chat_id_), false, std::move(promise),
|
||||||
|
Auto());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Td::on_request(uint64 id, const td_api::getChatSimilarChatCount &request) {
|
void Td::on_request(uint64 id, const td_api::getChatSimilarChatCount &request) {
|
||||||
CHECK_IS_USER();
|
CHECK_IS_USER();
|
||||||
CREATE_REQUEST_PROMISE();
|
CREATE_REQUEST_PROMISE();
|
||||||
contacts_manager_->get_channel_recommendations(DialogId(request.chat_id_), request.return_local_, Auto(),
|
channel_recommendation_manager_->get_channel_recommendations(DialogId(request.chat_id_), request.return_local_,
|
||||||
std::move(promise));
|
Auto(), std::move(promise));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Td::on_request(uint64 id, const td_api::openChatSimilarChat &request) {
|
void Td::on_request(uint64 id, const td_api::openChatSimilarChat &request) {
|
||||||
CHECK_IS_USER();
|
CHECK_IS_USER();
|
||||||
CREATE_OK_REQUEST_PROMISE();
|
CREATE_OK_REQUEST_PROMISE();
|
||||||
contacts_manager_->open_channel_recommended_channel(DialogId(request.chat_id_), DialogId(request.opened_chat_id_),
|
channel_recommendation_manager_->open_channel_recommended_channel(
|
||||||
std::move(promise));
|
DialogId(request.chat_id_), DialogId(request.opened_chat_id_), std::move(promise));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Td::on_request(uint64 id, const td_api::getTopChats &request) {
|
void Td::on_request(uint64 id, const td_api::getTopChats &request) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user