45e855f89d
I can't maintain anymore this amount of features while keeping the library constantly updated and without bugs. Every merge was taking me multiple hours of revisioning the code. I give up. From this commit onwards TDLight will only have small useful customizations that are easy to maintain. Now the people relying on the OptimizeMemory method can restart the session every N hours to free up the memory. The real way to keep a low memory usage must involve a huge refactoring to allow the unloading of the caches into the sqlite database, similar to what's already happening with messages data. Only Levlam has the ability to implement this without needing to merge the upstream everytime.
333 lines
13 KiB
C++
333 lines
13 KiB
C++
//
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
#include "td/telegram/CallbackQueriesManager.h"
|
|
|
|
#include "td/telegram/AccessRights.h"
|
|
#include "td/telegram/AuthManager.h"
|
|
#include "td/telegram/ContactsManager.h"
|
|
#include "td/telegram/Global.h"
|
|
#include "td/telegram/InlineQueriesManager.h"
|
|
#include "td/telegram/MessagesManager.h"
|
|
#include "td/telegram/PasswordManager.h"
|
|
#include "td/telegram/Td.h"
|
|
#include "td/telegram/td_api.h"
|
|
#include "td/telegram/telegram_api.h"
|
|
|
|
#include "td/actor/actor.h"
|
|
#include "td/actor/PromiseFuture.h"
|
|
|
|
#include "td/utils/common.h"
|
|
#include "td/utils/logging.h"
|
|
#include "td/utils/Random.h"
|
|
#include "td/utils/Status.h"
|
|
|
|
namespace td {
|
|
|
|
class GetBotCallbackAnswerQuery final : public Td::ResultHandler {
|
|
Promise<Unit> promise_;
|
|
int64 result_id_;
|
|
DialogId dialog_id_;
|
|
MessageId message_id_;
|
|
|
|
public:
|
|
explicit GetBotCallbackAnswerQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send(DialogId dialog_id, MessageId message_id, const tl_object_ptr<td_api::CallbackQueryPayload> &payload,
|
|
tl_object_ptr<telegram_api::InputCheckPasswordSRP> &&password, int64 result_id) {
|
|
result_id_ = result_id;
|
|
dialog_id_ = dialog_id;
|
|
message_id_ = message_id;
|
|
|
|
auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
|
|
CHECK(input_peer != nullptr);
|
|
|
|
int32 flags = 0;
|
|
BufferSlice data;
|
|
CHECK(payload != nullptr);
|
|
switch (payload->get_id()) {
|
|
case td_api::callbackQueryPayloadData::ID:
|
|
flags = telegram_api::messages_getBotCallbackAnswer::DATA_MASK;
|
|
data = BufferSlice(static_cast<const td_api::callbackQueryPayloadData *>(payload.get())->data_);
|
|
break;
|
|
case td_api::callbackQueryPayloadDataWithPassword::ID:
|
|
CHECK(password != nullptr);
|
|
flags = telegram_api::messages_getBotCallbackAnswer::DATA_MASK |
|
|
telegram_api::messages_getBotCallbackAnswer::PASSWORD_MASK;
|
|
data = BufferSlice(static_cast<const td_api::callbackQueryPayloadDataWithPassword *>(payload.get())->data_);
|
|
break;
|
|
case td_api::callbackQueryPayloadGame::ID:
|
|
flags = telegram_api::messages_getBotCallbackAnswer::GAME_MASK;
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
auto net_query = G()->net_query_creator().create(telegram_api::messages_getBotCallbackAnswer(
|
|
flags, false /*ignored*/, std::move(input_peer), message_id.get_server_message_id().get(), std::move(data),
|
|
std::move(password)));
|
|
net_query->need_resend_on_503_ = false;
|
|
send_query(std::move(net_query));
|
|
}
|
|
|
|
void on_result(uint64 id, BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::messages_getBotCallbackAnswer>(packet);
|
|
if (result_ptr.is_error()) {
|
|
return on_error(id, result_ptr.move_as_error());
|
|
}
|
|
|
|
td->callback_queries_manager_->on_get_callback_query_answer(result_id_, result_ptr.move_as_ok());
|
|
promise_.set_value(Unit());
|
|
}
|
|
|
|
void on_error(uint64 id, Status status) final {
|
|
if (status.message() == "DATA_INVALID") {
|
|
td->messages_manager_->get_message_from_server({dialog_id_, message_id_}, Auto(), "GetBotCallbackAnswerQuery");
|
|
} else if (status.message() == "BOT_RESPONSE_TIMEOUT") {
|
|
status = Status::Error(502, "The bot is not responding");
|
|
}
|
|
td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetBotCallbackAnswerQuery");
|
|
td->callback_queries_manager_->on_get_callback_query_answer(result_id_, nullptr);
|
|
promise_.set_error(std::move(status));
|
|
}
|
|
};
|
|
|
|
class SetBotCallbackAnswerQuery final : public Td::ResultHandler {
|
|
Promise<Unit> promise_;
|
|
|
|
public:
|
|
explicit SetBotCallbackAnswerQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
}
|
|
|
|
void send(int32 flags, int64 callback_query_id, const string &text, const string &url, int32 cache_time) {
|
|
send_query(G()->net_query_creator().create(telegram_api::messages_setBotCallbackAnswer(
|
|
flags, false /*ignored*/, callback_query_id, text, url, cache_time)));
|
|
}
|
|
|
|
void on_result(uint64 id, BufferSlice packet) final {
|
|
auto result_ptr = fetch_result<telegram_api::messages_setBotCallbackAnswer>(packet);
|
|
if (result_ptr.is_error()) {
|
|
return on_error(id, result_ptr.move_as_error());
|
|
}
|
|
|
|
bool result = result_ptr.ok();
|
|
if (!result) {
|
|
LOG(INFO) << "Sending answer to a callback query has failed";
|
|
}
|
|
promise_.set_value(Unit());
|
|
}
|
|
|
|
void on_error(uint64 id, Status status) final {
|
|
promise_.set_error(std::move(status));
|
|
}
|
|
};
|
|
|
|
CallbackQueriesManager::CallbackQueriesManager(Td *td) : td_(td) {
|
|
}
|
|
|
|
void CallbackQueriesManager::answer_callback_query(int64 callback_query_id, const string &text, bool show_alert,
|
|
const string &url, int32 cache_time, Promise<Unit> &&promise) const {
|
|
int32 flags = 0;
|
|
if (!text.empty()) {
|
|
flags |= BOT_CALLBACK_ANSWER_FLAG_HAS_MESSAGE;
|
|
}
|
|
if (show_alert) {
|
|
flags |= BOT_CALLBACK_ANSWER_FLAG_NEED_SHOW_ALERT;
|
|
}
|
|
if (!url.empty()) {
|
|
flags |= BOT_CALLBACK_ANSWER_FLAG_HAS_URL;
|
|
}
|
|
td_->create_handler<SetBotCallbackAnswerQuery>(std::move(promise))
|
|
->send(flags, callback_query_id, text, url, cache_time);
|
|
}
|
|
|
|
tl_object_ptr<td_api::CallbackQueryPayload> CallbackQueriesManager::get_query_payload(int32 flags, BufferSlice &&data,
|
|
string &&game_short_name) {
|
|
bool has_data = (flags & telegram_api::updateBotCallbackQuery::DATA_MASK) != 0;
|
|
bool has_game = (flags & telegram_api::updateBotCallbackQuery::GAME_SHORT_NAME_MASK) != 0;
|
|
if (has_data == has_game) {
|
|
LOG(ERROR) << "Receive wrong flags " << flags << " in a callback query";
|
|
return nullptr;
|
|
}
|
|
|
|
if (has_data) {
|
|
return make_tl_object<td_api::callbackQueryPayloadData>(data.as_slice().str());
|
|
}
|
|
if (has_game) {
|
|
return make_tl_object<td_api::callbackQueryPayloadGame>(game_short_name);
|
|
}
|
|
UNREACHABLE();
|
|
return nullptr;
|
|
}
|
|
|
|
void CallbackQueriesManager::on_new_query(int32 flags, int64 callback_query_id, UserId sender_user_id,
|
|
DialogId dialog_id, MessageId message_id, BufferSlice &&data,
|
|
int64 chat_instance, string &&game_short_name) {
|
|
if (!dialog_id.is_valid()) {
|
|
LOG(ERROR) << "Receive new callback query in invalid " << dialog_id;
|
|
return;
|
|
}
|
|
if (!sender_user_id.is_valid()) {
|
|
LOG(ERROR) << "Receive new callback query from invalid " << sender_user_id << " in " << dialog_id;
|
|
return;
|
|
}
|
|
LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Have no info about " << sender_user_id;
|
|
if (!td_->auth_manager_->is_bot()) {
|
|
LOG(ERROR) << "Receive new callback query";
|
|
return;
|
|
}
|
|
if (!message_id.is_valid()) {
|
|
LOG(ERROR) << "Receive new callback query from " << message_id << " in " << dialog_id << " sent by "
|
|
<< sender_user_id;
|
|
return;
|
|
}
|
|
|
|
auto payload = get_query_payload(flags, std::move(data), std::move(game_short_name));
|
|
if (payload == nullptr) {
|
|
return;
|
|
}
|
|
|
|
td_->messages_manager_->force_create_dialog(dialog_id, "on_new_callback_query", true);
|
|
send_closure(
|
|
G()->td(), &Td::send_update,
|
|
make_tl_object<td_api::updateNewCallbackQuery>(
|
|
callback_query_id, td_->contacts_manager_->get_user_id_object(sender_user_id, "updateNewCallbackQuery"),
|
|
dialog_id.get(), message_id.get(), chat_instance, std::move(payload)));
|
|
}
|
|
|
|
void CallbackQueriesManager::on_new_inline_query(
|
|
int32 flags, int64 callback_query_id, UserId sender_user_id,
|
|
tl_object_ptr<telegram_api::InputBotInlineMessageID> &&inline_message_id, BufferSlice &&data, int64 chat_instance,
|
|
string &&game_short_name) {
|
|
if (!sender_user_id.is_valid()) {
|
|
LOG(ERROR) << "Receive new callback query from invalid " << sender_user_id;
|
|
return;
|
|
}
|
|
LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Have no info about " << sender_user_id;
|
|
if (!td_->auth_manager_->is_bot()) {
|
|
LOG(ERROR) << "Receive new callback query";
|
|
return;
|
|
}
|
|
CHECK(inline_message_id != nullptr);
|
|
|
|
auto payload = get_query_payload(flags, std::move(data), std::move(game_short_name));
|
|
if (payload == nullptr) {
|
|
return;
|
|
}
|
|
send_closure(
|
|
G()->td(), &Td::send_update,
|
|
make_tl_object<td_api::updateNewInlineCallbackQuery>(
|
|
callback_query_id, td_->contacts_manager_->get_user_id_object(sender_user_id, "updateNewInlineCallbackQuery"),
|
|
InlineQueriesManager::get_inline_message_id(std::move(inline_message_id)), chat_instance,
|
|
std::move(payload)));
|
|
}
|
|
|
|
int64 CallbackQueriesManager::send_callback_query(FullMessageId full_message_id,
|
|
tl_object_ptr<td_api::CallbackQueryPayload> &&payload,
|
|
Promise<Unit> &&promise) {
|
|
if (td_->auth_manager_->is_bot()) {
|
|
promise.set_error(Status::Error(400, "Bot can't send callback queries to other bot"));
|
|
return 0;
|
|
}
|
|
|
|
if (payload == nullptr) {
|
|
promise.set_error(Status::Error(400, "Payload must be non-empty"));
|
|
return 0;
|
|
}
|
|
|
|
auto dialog_id = full_message_id.get_dialog_id();
|
|
td_->messages_manager_->have_dialog_force(dialog_id, "send_callback_query");
|
|
if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
|
|
promise.set_error(Status::Error(400, "Can't access the chat"));
|
|
return 0;
|
|
}
|
|
|
|
if (!td_->messages_manager_->have_message_force(full_message_id, "send_callback_query")) {
|
|
promise.set_error(Status::Error(400, "Message not found"));
|
|
return 0;
|
|
}
|
|
if (full_message_id.get_message_id().is_valid_scheduled()) {
|
|
promise.set_error(Status::Error(400, "Can't send callback queries from scheduled messages"));
|
|
return 0;
|
|
}
|
|
if (!full_message_id.get_message_id().is_server()) {
|
|
promise.set_error(Status::Error(400, "Bad message identifier"));
|
|
return 0;
|
|
}
|
|
|
|
int64 result_id;
|
|
do {
|
|
result_id = Random::secure_int64();
|
|
} while (callback_query_answers_.find(result_id) != callback_query_answers_.end());
|
|
callback_query_answers_[result_id]; // reserve place for result
|
|
|
|
if (payload->get_id() == td_api::callbackQueryPayloadDataWithPassword::ID) {
|
|
auto password = static_cast<const td_api::callbackQueryPayloadDataWithPassword *>(payload.get())->password_;
|
|
send_closure(td_->password_manager_, &PasswordManager::get_input_check_password_srp, std::move(password),
|
|
PromiseCreator::lambda(
|
|
[this, full_message_id, payload = std::move(payload), result_id, promise = std::move(promise)](
|
|
Result<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> result) mutable {
|
|
if (result.is_error()) {
|
|
return promise.set_error(result.move_as_error());
|
|
}
|
|
if (G()->close_flag()) {
|
|
return promise.set_error(Status::Error(500, "Request aborted"));
|
|
}
|
|
|
|
send_get_callback_answer_query(full_message_id, std::move(payload), result.move_as_ok(),
|
|
result_id, std::move(promise));
|
|
}));
|
|
} else {
|
|
send_get_callback_answer_query(full_message_id, std::move(payload), nullptr, result_id, std::move(promise));
|
|
}
|
|
|
|
return result_id;
|
|
}
|
|
|
|
void CallbackQueriesManager::send_get_callback_answer_query(
|
|
FullMessageId full_message_id, tl_object_ptr<td_api::CallbackQueryPayload> &&payload,
|
|
tl_object_ptr<telegram_api::InputCheckPasswordSRP> &&password, int64 result_id, Promise<Unit> &&promise) {
|
|
auto dialog_id = full_message_id.get_dialog_id();
|
|
if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
|
|
return promise.set_error(Status::Error(400, "Can't access the chat"));
|
|
}
|
|
if (!td_->messages_manager_->have_message_force(full_message_id, "send_callback_query")) {
|
|
return promise.set_error(Status::Error(400, "Message not found"));
|
|
}
|
|
|
|
td_->create_handler<GetBotCallbackAnswerQuery>(std::move(promise))
|
|
->send(dialog_id, full_message_id.get_message_id(), payload, std::move(password), result_id);
|
|
}
|
|
|
|
void CallbackQueriesManager::on_get_callback_query_answer(
|
|
int64 result_id, tl_object_ptr<telegram_api::messages_botCallbackAnswer> &&answer) {
|
|
LOG(INFO) << "Receive answer for callback query " << result_id;
|
|
auto it = callback_query_answers_.find(result_id);
|
|
CHECK(it != callback_query_answers_.end());
|
|
CHECK(it->second.text.empty());
|
|
if (answer == nullptr) {
|
|
callback_query_answers_.erase(it);
|
|
return;
|
|
}
|
|
|
|
LOG(INFO) << to_string(answer);
|
|
it->second = CallbackQueryAnswer{(answer->flags_ & BOT_CALLBACK_ANSWER_FLAG_NEED_SHOW_ALERT) != 0, answer->message_,
|
|
answer->url_};
|
|
}
|
|
|
|
tl_object_ptr<td_api::callbackQueryAnswer> CallbackQueriesManager::get_callback_query_answer_object(int64 result_id) {
|
|
auto it = callback_query_answers_.find(result_id);
|
|
CHECK(it != callback_query_answers_.end());
|
|
bool show_alert = it->second.show_alert;
|
|
auto text = std::move(it->second.text);
|
|
auto url = std::move(it->second.url);
|
|
callback_query_answers_.erase(it);
|
|
return make_tl_object<td_api::callbackQueryAnswer>(text, show_alert, url);
|
|
}
|
|
|
|
} // namespace td
|