// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 // // 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/InlineQueriesManager.h" #include "td/telegram/AccessRights.h" #include "td/telegram/AnimationsManager.h" #include "td/telegram/AudiosManager.h" #include "td/telegram/AuthManager.h" #include "td/telegram/Contact.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" #include "td/telegram/Game.h" #include "td/telegram/Global.h" #include "td/telegram/InputInvoice.h" #include "td/telegram/InputMessageText.h" #include "td/telegram/LinkManager.h" #include "td/telegram/Location.h" #include "td/telegram/MessageContent.h" #include "td/telegram/MessageContentType.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/net/DcId.h" #include "td/telegram/Photo.h" #include "td/telegram/PhotoFormat.h" #include "td/telegram/PhotoSize.h" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" #include "td/telegram/td_api.hpp" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.hpp" #include "td/telegram/ThemeManager.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/Venue.h" #include "td/telegram/VideosManager.h" #include "td/telegram/VoiceNotesManager.h" #include "td/utils/algorithm.h" #include "td/utils/base64.h" #include "td/utils/buffer.h" #include "td/utils/HashTableUtils.h" #include "td/utils/HttpUrl.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" #include "td/utils/tl_parsers.h" #include namespace td { class GetInlineBotResultsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; UserId bot_user_id_; uint64 query_hash_; static constexpr int32 GET_INLINE_BOT_RESULTS_FLAG_HAS_LOCATION = 1 << 0; public: explicit GetInlineBotResultsQuery(Promise &&promise) : promise_(std::move(promise)) { } NetQueryRef send(UserId bot_user_id, DialogId dialog_id, tl_object_ptr bot_input_user, tl_object_ptr input_peer, Location user_location, const string &query, const string &offset, uint64 query_hash) { CHECK(input_peer != nullptr); bot_user_id_ = bot_user_id; dialog_id_ = dialog_id; query_hash_ = query_hash; int32 flags = 0; if (!user_location.empty()) { flags |= GET_INLINE_BOT_RESULTS_FLAG_HAS_LOCATION; } auto net_query = G()->net_query_creator().create(telegram_api::messages_getInlineBotResults( flags, std::move(bot_input_user), std::move(input_peer), user_location.empty() ? nullptr : user_location.get_input_geo_point(), query, offset)); auto result = net_query.get_weak(); net_query->need_resend_on_503_ = false; send_query(std::move(net_query)); return result; } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } td_->inline_queries_manager_->on_get_inline_query_results(dialog_id_, bot_user_id_, query_hash_, result_ptr.move_as_ok()); promise_.set_value(Unit()); } void on_error(Status status) final { if (status.code() == NetQuery::Canceled) { status = Status::Error(406, "Request canceled"); } else if (status.message() == "BOT_RESPONSE_TIMEOUT") { status = Status::Error(502, "The bot is not responding"); } LOG(INFO) << "Receive error for GetInlineBotResultsQuery: " << status; td_->inline_queries_manager_->on_get_inline_query_results(dialog_id_, bot_user_id_, query_hash_, nullptr); promise_.set_error(std::move(status)); } }; class SetInlineBotResultsQuery final : public Td::ResultHandler { Promise promise_; public: explicit SetInlineBotResultsQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(int64 inline_query_id, bool is_gallery, bool is_personal, telegram_api::object_ptr switch_pm, telegram_api::object_ptr web_view, vector> &&results, int32 cache_time, const string &next_offset) { int32 flags = 0; if (is_gallery) { flags |= telegram_api::messages_setInlineBotResults::GALLERY_MASK; } if (is_personal) { flags |= telegram_api::messages_setInlineBotResults::PRIVATE_MASK; } if (!next_offset.empty()) { flags |= telegram_api::messages_setInlineBotResults::NEXT_OFFSET_MASK; } if (switch_pm != nullptr) { flags |= telegram_api::messages_setInlineBotResults::SWITCH_PM_MASK; } if (web_view != nullptr) { flags |= telegram_api::messages_setInlineBotResults::SWITCH_WEBVIEW_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_setInlineBotResults( flags, false /*ignored*/, false /*ignored*/, inline_query_id, std::move(results), cache_time, next_offset, std::move(switch_pm), std::move(web_view)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } bool result = result_ptr.ok(); if (!result) { LOG(ERROR) << "Sending answer to an inline query has failed"; } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class RequestSimpleWebViewQuery final : public Td::ResultHandler { Promise promise_; public: explicit RequestSimpleWebViewQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(tl_object_ptr &&input_user, const string &url, const td_api::object_ptr &theme, string &&platform) { tl_object_ptr theme_parameters; int32 flags = 0; if (theme != nullptr) { flags |= telegram_api::messages_requestSimpleWebView::THEME_PARAMS_MASK; theme_parameters = make_tl_object(string()); theme_parameters->data_ = ThemeManager::get_theme_parameters_json_string(theme, false); } if (ends_with(url, "#kb")) { // a URL from keyboard button } else if (ends_with(url, "#iq")) { // a URL from inline query results button flags |= telegram_api::messages_requestSimpleWebView::FROM_SWITCH_WEBVIEW_MASK; } else { return on_error(Status::Error(400, "Invalid URL specified")); } send_query(G()->net_query_creator().create(telegram_api::messages_requestSimpleWebView( flags, false /*ignored*/, std::move(input_user), url.substr(0, url.size() - 3), std::move(theme_parameters), platform))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for RequestSimpleWebViewQuery: " << to_string(ptr); promise_.set_value(std::move(ptr->url_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class SendWebViewDataQuery final : public Td::ResultHandler { Promise promise_; public: explicit SendWebViewDataQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(tl_object_ptr &&input_user, int64 random_id, const string &button_text, const string &data) { send_query(G()->net_query_creator().create( telegram_api::messages_sendWebViewData(std::move(input_user), random_id, button_text, data))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SendWebViewDataQuery: " << to_string(ptr); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class SendWebViewResultMessageQuery final : public Td::ResultHandler { Promise> promise_; public: explicit SendWebViewResultMessageQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &bot_query_id, tl_object_ptr &&result) { send_query(G()->net_query_creator().create( telegram_api::messages_sendWebViewResultMessage(bot_query_id, std::move(result)))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SendWebViewResultMessageQuery: " << to_string(ptr); promise_.set_value(td_api::make_object( InlineQueriesManager::get_inline_message_id(std::move(ptr->msg_id_)))); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; InlineQueriesManager::InlineQueriesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { drop_inline_query_result_timeout_.set_callback(on_drop_inline_query_result_timeout_callback); drop_inline_query_result_timeout_.set_callback_data(static_cast(this)); next_inline_query_time_ = Time::now(); } void InlineQueriesManager::tear_down() { parent_.reset(); } void InlineQueriesManager::on_drop_inline_query_result_timeout_callback(void *inline_queries_manager_ptr, int64 query_hash) { if (G()->close_flag()) { return; } auto inline_queries_manager = static_cast(inline_queries_manager_ptr); auto it = inline_queries_manager->inline_query_results_.find(query_hash); CHECK(it != inline_queries_manager->inline_query_results_.end()); CHECK(it->second.results != nullptr); CHECK(it->second.pending_request_count >= 0); if (it->second.pending_request_count == 0) { inline_queries_manager->inline_query_results_.erase(it); } } void InlineQueriesManager::after_get_difference() { if (td_->auth_manager_->is_bot()) { return; } if (recently_used_bots_loaded_ < 2) { Promise promise; load_recently_used_bots(promise); } } int32 InlineQueriesManager::get_inline_message_dc_id( const tl_object_ptr &inline_message_id) { CHECK(inline_message_id != nullptr); switch (inline_message_id->get_id()) { case telegram_api::inputBotInlineMessageID::ID: return static_cast(inline_message_id.get())->dc_id_; case telegram_api::inputBotInlineMessageID64::ID: return static_cast(inline_message_id.get())->dc_id_; default: UNREACHABLE(); return 0; } } tl_object_ptr InlineQueriesManager::get_input_bot_inline_message_id( const string &inline_message_id) { auto r_binary = base64url_decode(inline_message_id); if (r_binary.is_error()) { return nullptr; } BufferSlice buffer_slice(r_binary.ok()); TlBufferParser parser(&buffer_slice); auto result = buffer_slice.size() == 20 ? telegram_api::inputBotInlineMessageID::fetch(parser) : telegram_api::inputBotInlineMessageID64::fetch(parser); parser.fetch_end(); if (parser.get_error()) { return nullptr; } if (!DcId::is_valid(get_inline_message_dc_id(result))) { return nullptr; } LOG(INFO) << "Have inline message identifier: " << to_string(result); return result; } string InlineQueriesManager::get_inline_message_id( tl_object_ptr &&input_bot_inline_message_id) { if (input_bot_inline_message_id == nullptr) { return string(); } LOG(INFO) << "Receive inline message identifier: " << to_string(input_bot_inline_message_id); return base64url_encode(serialize(*input_bot_inline_message_id)); } Result> InlineQueriesManager::get_inline_message( tl_object_ptr &&input_message_content, tl_object_ptr &&reply_markup_ptr, int32 allowed_media_content_id) const { if (input_message_content == nullptr) { return Status::Error(400, "Inline message must be non-empty"); } TRY_RESULT(reply_markup, get_reply_markup(std::move(reply_markup_ptr), true, true, false, true)); auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), reply_markup); auto constructor_id = input_message_content->get_id(); if (constructor_id == td_api::inputMessageText::ID) { TRY_RESULT(input_message_text, process_input_message_text(td_, DialogId(td_->contacts_manager_->get_my_id()), std::move(input_message_content), true)); int32 flags = 0; if (input_reply_markup != nullptr) { flags |= telegram_api::inputBotInlineMessageText::REPLY_MARKUP_MASK; } if (input_message_text.disable_web_page_preview) { flags |= telegram_api::inputBotInlineMessageText::NO_WEBPAGE_MASK; } if (!input_message_text.text.entities.empty()) { flags |= telegram_api::inputBotInlineMessageText::ENTITIES_MASK; } return make_tl_object( flags, false /*ignored*/, std::move(input_message_text.text.text), get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities, "get_inline_message"), std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageContact::ID) { TRY_RESULT(contact, process_input_message_contact(std::move(input_message_content))); return contact.get_input_bot_inline_message_media_contact(std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageInvoice::ID) { TRY_RESULT(input_invoice, InputInvoice::process_input_message_invoice(std::move(input_message_content), td_, DialogId(), false)); return input_invoice.get_input_bot_inline_message_media_invoice(std::move(input_reply_markup), td_); } if (constructor_id == td_api::inputMessageLocation::ID) { TRY_RESULT(location, process_input_message_location(std::move(input_message_content))); int32 flags = 0; if (input_reply_markup != nullptr) { flags |= telegram_api::inputBotInlineMessageMediaGeo::REPLY_MARKUP_MASK; } if (location.heading != 0) { flags |= telegram_api::inputBotInlineMessageMediaGeo::HEADING_MASK; } if (location.live_period != 0) { flags |= telegram_api::inputBotInlineMessageMediaGeo::PERIOD_MASK; flags |= telegram_api::inputBotInlineMessageMediaGeo::PROXIMITY_NOTIFICATION_RADIUS_MASK; } return make_tl_object( flags, location.location.get_input_geo_point(), location.heading, location.live_period, location.proximity_alert_radius, std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageVenue::ID) { TRY_RESULT(venue, process_input_message_venue(std::move(input_message_content))); return venue.get_input_bot_inline_message_media_venue(std::move(input_reply_markup)); } if (constructor_id == allowed_media_content_id) { TRY_RESULT(caption, get_formatted_text(td_, DialogId(td_->contacts_manager_->get_my_id()), extract_input_caption(input_message_content), true, true, true, false)); int32 flags = 0; if (input_reply_markup != nullptr) { flags |= telegram_api::inputBotInlineMessageMediaAuto::REPLY_MARKUP_MASK; } auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "get_inline_message"); if (!entities.empty()) { flags |= telegram_api::inputBotInlineMessageMediaAuto::ENTITIES_MASK; } return make_tl_object(flags, caption.text, std::move(entities), std::move(input_reply_markup)); } return Status::Error(400, "Unallowed inline message content type"); } bool InlineQueriesManager::register_inline_message_content( int64 query_id, const string &result_id, FileId file_id, tl_object_ptr &&inline_message, int32 allowed_media_content_id, bool allow_invoice, Photo *photo, Game *game) { CHECK(query_id != 0); if (result_id.empty()) { return false; } InlineMessageContent content = create_inline_message_content(td_, file_id, std::move(inline_message), allowed_media_content_id, photo, game); if (content.message_content != nullptr) { if (!allow_invoice && content.message_content->get_type() == MessageContentType::Invoice) { return false; } inline_message_contents_[query_id].emplace(result_id, std::move(content)); return true; } return false; } const InlineMessageContent *InlineQueriesManager::get_inline_message_content(int64 query_id, const string &result_id) { auto it = inline_message_contents_.find(query_id); if (it == inline_message_contents_.end()) { return nullptr; } auto result_it = it->second.find(result_id); if (result_it == it->second.end()) { return nullptr; } if (update_bot_usage(get_inline_bot_user_id(query_id))) { save_recently_used_bots(); } return &result_it->second; } UserId InlineQueriesManager::get_inline_bot_user_id(int64 query_id) const { auto it = query_id_to_bot_user_id_.find(query_id); if (it == query_id_to_bot_user_id_.end()) { return UserId(); } return it->second; } void InlineQueriesManager::answer_inline_query( int64 inline_query_id, bool is_personal, td_api::object_ptr &&button, vector> &&input_results, int32 cache_time, const string &next_offset, Promise &&promise) const { CHECK(td_->auth_manager_->is_bot()); telegram_api::object_ptr switch_pm; telegram_api::object_ptr web_view; if (button != nullptr) { if (!clean_input_string(button->text_)) { return promise.set_error(Status::Error(400, "Strings must be encoded in UTF-8")); } if (button->type_ == nullptr) { return promise.set_error(Status::Error(400, "Button type must be non-empty")); } switch (button->type_->get_id()) { case td_api::inlineQueryResultsButtonTypeStartBot::ID: { auto type = td_api::move_object_as(button->type_); if (type->parameter_.empty()) { return promise.set_error(Status::Error(400, "Can't use empty switch_pm_parameter")); } if (type->parameter_.size() > 64) { return promise.set_error(Status::Error(400, "Too long switch_pm_parameter specified")); } if (!is_base64url_characters(type->parameter_)) { return promise.set_error(Status::Error(400, "Unallowed characters in switch_pm_parameter are used")); } switch_pm = telegram_api::make_object(button->text_, type->parameter_); break; } case td_api::inlineQueryResultsButtonTypeWebApp::ID: { auto type = td_api::move_object_as(button->type_); auto user_id = LinkManager::get_link_user_id(type->url_); if (user_id.is_valid()) { return promise.set_error(Status::Error(400, "Link to a user can't be used in the Web App button")); } auto r_url = LinkManager::check_link(type->url_, true, !G()->is_test_dc()); if (r_url.is_error()) { return promise.set_error( Status::Error(400, PSLICE() << "Inline query button Web App " << r_url.error().message())); } web_view = telegram_api::make_object(button->text_, type->url_); break; } default: UNREACHABLE(); } } vector> results; bool is_gallery = false; bool force_vertical = false; for (auto &input_result : input_results) { TRY_RESULT_PROMISE(promise, result, get_input_bot_inline_result(std::move(input_result), &is_gallery, &force_vertical)); results.push_back(std::move(result)); } td_->create_handler(std::move(promise)) ->send(inline_query_id, is_gallery && !force_vertical, is_personal, std::move(switch_pm), std::move(web_view), std::move(results), cache_time, next_offset); } void InlineQueriesManager::get_simple_web_view_url(UserId bot_user_id, string &&url, const td_api::object_ptr &theme, string &&platform, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id)); TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_user), url, theme, std::move(platform)); } void InlineQueriesManager::send_web_view_data(UserId bot_user_id, string &&button_text, string &&data, Promise &&promise) const { TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); int64 random_id; do { random_id = Random::secure_int64(); } while (random_id == 0); TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_user), random_id, button_text, data); } void InlineQueriesManager::answer_web_view_query( const string &web_view_query_id, td_api::object_ptr &&input_result, Promise> &&promise) const { CHECK(td_->auth_manager_->is_bot()); TRY_RESULT_PROMISE(promise, result, get_input_bot_inline_result(std::move(input_result), nullptr, nullptr)); td_->create_handler(std::move(promise))->send(web_view_query_id, std::move(result)); } Result> InlineQueriesManager::get_input_bot_inline_result( td_api::object_ptr &&result, bool *is_gallery, bool *force_vertical) const { if (result == nullptr) { return Status::Error(400, "Inline query result must be non-empty"); } string id; string url; string type; string title; string description; string thumbnail_url; string thumbnail_type = "image/jpeg"; string content_url; string content_type; int32 thumbnail_width = 0; int32 thumbnail_height = 0; int32 width = 0; int32 height = 0; int32 duration = 0; FileType file_type = FileType::Temp; Result> r_inline_message = Status::Error(500, "Uninited"); switch (result->get_id()) { case td_api::inputInlineQueryResultAnimation::ID: { auto animation = move_tl_object_as(result); type = "gif"; id = std::move(animation->id_); title = std::move(animation->title_); thumbnail_url = std::move(animation->thumbnail_url_); if (!animation->thumbnail_mime_type_.empty()) { thumbnail_type = std::move(animation->thumbnail_mime_type_); } content_url = std::move(animation->video_url_); content_type = std::move(animation->video_mime_type_); if (content_type != "image/gif" && content_type != "video/mp4") { return Status::Error(400, "Wrong animation MIME type specified"); } duration = animation->video_duration_; width = animation->video_width_; height = animation->video_height_; if (is_gallery != nullptr) { *is_gallery = true; } file_type = FileType::Animation; r_inline_message = get_inline_message(std::move(animation->input_message_content_), std::move(animation->reply_markup_), td_api::inputMessageAnimation::ID); break; } case td_api::inputInlineQueryResultArticle::ID: { auto article = move_tl_object_as(result); type = "article"; id = std::move(article->id_); if (!article->url_.empty()) { content_url = std::move(article->url_); content_type = "text/html"; if (!article->hide_url_) { url = content_url; } } title = std::move(article->title_); description = std::move(article->description_); thumbnail_url = std::move(article->thumbnail_url_); if (!thumbnail_url.empty()) { thumbnail_width = article->thumbnail_width_; thumbnail_height = article->thumbnail_height_; } if (force_vertical != nullptr) { *force_vertical = true; } r_inline_message = get_inline_message(std::move(article->input_message_content_), std::move(article->reply_markup_), -1); break; } case td_api::inputInlineQueryResultAudio::ID: { auto audio = move_tl_object_as(result); type = "audio"; id = std::move(audio->id_); title = std::move(audio->title_); description = std::move(audio->performer_); content_url = std::move(audio->audio_url_); content_type = "audio/mpeg"; duration = audio->audio_duration_; if (force_vertical != nullptr) { *force_vertical = true; } file_type = FileType::Audio; r_inline_message = get_inline_message(std::move(audio->input_message_content_), std::move(audio->reply_markup_), td_api::inputMessageAudio::ID); break; } case td_api::inputInlineQueryResultContact::ID: { auto contact = move_tl_object_as(result); if (contact->contact_ == nullptr) { return Status::Error(400, "Contact must be non-empty"); } type = "contact"; id = std::move(contact->id_); string phone_number = trim(contact->contact_->phone_number_); string first_name = trim(contact->contact_->first_name_); string last_name = trim(contact->contact_->last_name_); if (phone_number.empty()) { return Status::Error(400, "Field \"phone_number\" must contain a valid phone number"); } if (first_name.empty()) { return Status::Error(400, "Field \"first_name\" must be non-empty"); } if (last_name.empty()) { title = std::move(first_name); } else { title = PSTRING() << first_name << ' ' << last_name; } description = std::move(phone_number); thumbnail_url = std::move(contact->thumbnail_url_); if (!thumbnail_url.empty()) { thumbnail_width = contact->thumbnail_width_; thumbnail_height = contact->thumbnail_height_; } if (force_vertical != nullptr) { *force_vertical = true; } r_inline_message = get_inline_message(std::move(contact->input_message_content_), std::move(contact->reply_markup_), -1); break; } case td_api::inputInlineQueryResultDocument::ID: { auto document = move_tl_object_as(result); type = "file"; id = std::move(document->id_); title = std::move(document->title_); description = std::move(document->description_); thumbnail_url = std::move(document->thumbnail_url_); content_url = std::move(document->document_url_); content_type = std::move(document->mime_type_); thumbnail_width = document->thumbnail_width_; thumbnail_height = document->thumbnail_height_; if (content_url.find('.') != string::npos) { if (begins_with(content_type, "application/pdf")) { content_type = "application/pdf"; } else if (begins_with(content_type, "application/zip")) { content_type = "application/zip"; } else { return Status::Error(400, "Unallowed document MIME type"); } } file_type = FileType::Document; r_inline_message = get_inline_message(std::move(document->input_message_content_), std::move(document->reply_markup_), td_api::inputMessageDocument::ID); break; } case td_api::inputInlineQueryResultGame::ID: { auto game = move_tl_object_as(result); auto r_reply_markup = get_reply_markup(std::move(game->reply_markup_), true, true, false, true); if (r_reply_markup.is_error()) { return r_reply_markup.move_as_error(); } auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_reply_markup.ok()); int32 flags = 0; if (input_reply_markup != nullptr) { flags |= telegram_api::inputBotInlineMessageGame::REPLY_MARKUP_MASK; } return make_tl_object( game->id_, game->game_short_name_, make_tl_object(flags, std::move(input_reply_markup))); } case td_api::inputInlineQueryResultLocation::ID: { auto location = move_tl_object_as(result); if (location->location_ == nullptr) { return Status::Error(400, "Location must be non-empty"); } type = "geo"; id = std::move(location->id_); title = std::move(location->title_); description = PSTRING() << location->location_->latitude_ << ' ' << location->location_->longitude_; thumbnail_url = std::move(location->thumbnail_url_); // duration = location->live_period_; if (!thumbnail_url.empty()) { thumbnail_width = location->thumbnail_width_; thumbnail_height = location->thumbnail_height_; } r_inline_message = get_inline_message(std::move(location->input_message_content_), std::move(location->reply_markup_), -1); break; } case td_api::inputInlineQueryResultPhoto::ID: { auto photo = move_tl_object_as(result); type = "photo"; id = std::move(photo->id_); title = std::move(photo->title_); description = std::move(photo->description_); thumbnail_url = std::move(photo->thumbnail_url_); content_url = std::move(photo->photo_url_); content_type = "image/jpeg"; width = photo->photo_width_; height = photo->photo_height_; if (is_gallery != nullptr) { *is_gallery = true; } file_type = FileType::Photo; r_inline_message = get_inline_message(std::move(photo->input_message_content_), std::move(photo->reply_markup_), td_api::inputMessagePhoto::ID); break; } case td_api::inputInlineQueryResultSticker::ID: { auto sticker = move_tl_object_as(result); type = "sticker"; id = std::move(sticker->id_); thumbnail_url = std::move(sticker->thumbnail_url_); content_url = std::move(sticker->sticker_url_); content_type = "image/webp"; // or "application/x-tgsticker"/"video/webm"; not used for previously uploaded files width = sticker->sticker_width_; height = sticker->sticker_height_; if (is_gallery != nullptr) { *is_gallery = true; } if (content_url.find('.') != string::npos) { return Status::Error(400, "Wrong sticker_file_id specified"); } file_type = FileType::Sticker; r_inline_message = get_inline_message(std::move(sticker->input_message_content_), std::move(sticker->reply_markup_), td_api::inputMessageSticker::ID); break; } case td_api::inputInlineQueryResultVenue::ID: { auto venue = move_tl_object_as(result); if (venue->venue_ == nullptr) { return Status::Error(400, "Venue must be non-empty"); } type = "venue"; id = std::move(venue->id_); title = std::move(venue->venue_->title_); description = std::move(venue->venue_->address_); thumbnail_url = std::move(venue->thumbnail_url_); if (!thumbnail_url.empty()) { thumbnail_width = venue->thumbnail_width_; thumbnail_height = venue->thumbnail_height_; } r_inline_message = get_inline_message(std::move(venue->input_message_content_), std::move(venue->reply_markup_), -1); break; } case td_api::inputInlineQueryResultVideo::ID: { auto video = move_tl_object_as(result); type = "video"; id = std::move(video->id_); title = std::move(video->title_); description = std::move(video->description_); thumbnail_url = std::move(video->thumbnail_url_); content_url = std::move(video->video_url_); content_type = std::move(video->mime_type_); width = video->video_width_; height = video->video_height_; duration = video->video_duration_; if (content_url.find('.') != string::npos) { if (begins_with(content_type, "video/mp4")) { content_type = "video/mp4"; } else if (begins_with(content_type, "text/html")) { content_type = "text/html"; } else { return Status::Error(400, "Unallowed video MIME type"); } } file_type = FileType::Video; r_inline_message = get_inline_message(std::move(video->input_message_content_), std::move(video->reply_markup_), td_api::inputMessageVideo::ID); break; } case td_api::inputInlineQueryResultVoiceNote::ID: { auto voice_note = move_tl_object_as(result); type = "voice"; id = std::move(voice_note->id_); title = std::move(voice_note->title_); content_url = std::move(voice_note->voice_note_url_); content_type = "audio/ogg"; duration = voice_note->voice_note_duration_; if (force_vertical != nullptr) { *force_vertical = true; } file_type = FileType::VoiceNote; r_inline_message = get_inline_message(std::move(voice_note->input_message_content_), std::move(voice_note->reply_markup_), td_api::inputMessageVoiceNote::ID); break; } default: UNREACHABLE(); break; } if (r_inline_message.is_error()) { return r_inline_message.move_as_error(); } auto inline_message = r_inline_message.move_as_ok(); if (inline_message->get_id() == telegram_api::inputBotInlineMessageMediaAuto::ID && file_type == FileType::Temp) { return Status::Error(400, "Sent message content must be explicitly specified"); } if (duration < 0) { duration = 0; } int32 flags = 0; if (!title.empty()) { flags |= telegram_api::inputBotInlineResult::TITLE_MASK; if (!clean_input_string(title)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } } if (!description.empty()) { flags |= telegram_api::inputBotInlineResult::DESCRIPTION_MASK; if (!clean_input_string(description)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } } if (file_type != FileType::Temp && content_url.find('.') == string::npos) { TRY_RESULT(file_id, td_->file_manager_->get_input_file_id( file_type, make_tl_object(content_url), DialogId(), false, false)); FileView file_view = td_->file_manager_->get_file_view(file_id); CHECK(file_view.has_remote_location()); if (file_view.is_encrypted()) { return Status::Error(400, "Can't send encrypted file"); } if (file_view.main_remote_location().is_web()) { return Status::Error(400, "Can't send web file"); } if (file_type == FileType::Photo) { return make_tl_object( id, type, file_view.main_remote_location().as_input_photo(), std::move(inline_message)); } return make_tl_object( flags, id, type, title, description, file_view.main_remote_location().as_input_document(), std::move(inline_message)); } if (!url.empty()) { flags |= telegram_api::inputBotInlineResult::URL_MASK; if (!clean_input_string(url)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } } tl_object_ptr thumbnail; if (!thumbnail_url.empty()) { flags |= telegram_api::inputBotInlineResult::THUMB_MASK; if (!clean_input_string(thumbnail_url)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } vector> attributes; if (thumbnail_width > 0 && thumbnail_height > 0) { attributes.push_back(make_tl_object(thumbnail_width, thumbnail_height)); } thumbnail = make_tl_object(thumbnail_url, 0, thumbnail_type, std::move(attributes)); } tl_object_ptr content; if (!content_url.empty() || !content_type.empty()) { flags |= telegram_api::inputBotInlineResult::CONTENT_MASK; if (!clean_input_string(content_url)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } if (!clean_input_string(content_type)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } vector> attributes; if (width > 0 && height > 0) { if ((duration > 0 || type == "video" || content_type == "video/mp4") && !begins_with(content_type, "image/")) { attributes.push_back(make_tl_object( 0, false /*ignored*/, false /*ignored*/, duration, width, height)); } else { attributes.push_back(make_tl_object(width, height)); } } else if (type == "audio") { attributes.push_back(make_tl_object( telegram_api::documentAttributeAudio::TITLE_MASK | telegram_api::documentAttributeAudio::PERFORMER_MASK, false /*ignored*/, duration, title, description, BufferSlice())); } else if (type == "voice") { attributes.push_back(make_tl_object( telegram_api::documentAttributeAudio::VOICE_MASK, false /*ignored*/, duration, "", "", BufferSlice())); } attributes.push_back(make_tl_object(get_url_file_name(content_url))); content = make_tl_object(content_url, 0, content_type, std::move(attributes)); } return make_tl_object( flags, id, type, title, description, url, std::move(thumbnail), std::move(content), std::move(inline_message)); } uint64 InlineQueriesManager::send_inline_query(UserId bot_user_id, DialogId dialog_id, Location user_location, const string &query, const string &offset, Promise &&promise) { if (td_->auth_manager_->is_bot()) { promise.set_error(Status::Error(400, "Bot can't send inline queries to other bot")); return 0; } auto r_bot_data = td_->contacts_manager_->get_bot_data(bot_user_id); if (r_bot_data.is_error()) { promise.set_error(r_bot_data.move_as_error()); return 0; } if (!r_bot_data.ok().is_inline) { promise.set_error(Status::Error(400, "Bot doesn't support inline queries")); return 0; } auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { input_peer = make_tl_object(); } auto peer_type = [&] { switch (input_peer->get_id()) { case telegram_api::inputPeerEmpty::ID: return 0; case telegram_api::inputPeerSelf::ID: return 1; case telegram_api::inputPeerChat::ID: return 2; case telegram_api::inputPeerUser::ID: case telegram_api::inputPeerUserFromMessage::ID: return dialog_id == DialogId(bot_user_id) ? 3 : 4; case telegram_api::inputPeerChannel::ID: case telegram_api::inputPeerChannelFromMessage::ID: return 5 + static_cast(td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id())); default: UNREACHABLE(); return -1; } }(); uint64 query_hash = Hash()(trim(query)); query_hash = query_hash * 2023654985u + bot_user_id.get(); query_hash = query_hash * 2023654985u + static_cast(peer_type); query_hash = query_hash * 2023654985u + Hash()(offset); if (r_bot_data.ok().need_location) { query_hash = query_hash * 2023654985u + static_cast(user_location.get_latitude() * 1e4); query_hash = query_hash * 2023654985u + static_cast(user_location.get_longitude() * 1e4); } query_hash &= 0x7FFFFFFFFFFFFFFF; if (query_hash == 0) { query_hash = 1; } auto it = inline_query_results_.find(query_hash); if (it != inline_query_results_.end()) { it->second.pending_request_count++; if (Time::now() < it->second.cache_expire_time) { promise.set_value(Unit()); return query_hash; } } else { inline_query_results_[query_hash] = {nullptr, -1.0, 1}; } if (pending_inline_query_ != nullptr) { LOG(INFO) << "Drop inline query " << pending_inline_query_->query_hash; on_get_inline_query_results(pending_inline_query_->dialog_id, pending_inline_query_->bot_user_id, pending_inline_query_->query_hash, nullptr); pending_inline_query_->promise.set_error(Status::Error(406, "Request canceled")); } pending_inline_query_ = make_unique(PendingInlineQuery{ query_hash, bot_user_id, dialog_id, std::move(input_peer), user_location, query, offset, std::move(promise)}); loop(); return query_hash; } void InlineQueriesManager::loop() { LOG(INFO) << "Inline query loop"; if (pending_inline_query_ == nullptr) { return; } auto now = Time::now(); if (now >= next_inline_query_time_) { LOG(INFO) << "Send inline query " << pending_inline_query_->query_hash; auto r_bot_input_user = td_->contacts_manager_->get_input_user(pending_inline_query_->bot_user_id); if (r_bot_input_user.is_ok()) { if (!sent_query_.empty()) { LOG(INFO) << "Cancel inline query request"; cancel_query(sent_query_); } sent_query_ = td_->create_handler(std::move(pending_inline_query_->promise)) ->send(pending_inline_query_->bot_user_id, pending_inline_query_->dialog_id, r_bot_input_user.move_as_ok(), std::move(pending_inline_query_->input_peer), pending_inline_query_->user_location, pending_inline_query_->query, pending_inline_query_->offset, pending_inline_query_->query_hash); next_inline_query_time_ = now + INLINE_QUERY_DELAY_MS * 1e-3; } pending_inline_query_ = nullptr; } else { if (!has_timeout()) { LOG(INFO) << "Schedule send inline query " << pending_inline_query_->query_hash << " at " << G()->to_server_time(next_inline_query_time_); set_timeout_at(next_inline_query_time_); } } } template static tl_object_ptr copy(const T &obj) { // see https://bugs.llvm.org/show_bug.cgi?id=17537 static_assert(sizeof(T) == 0, "Only specializations of can be used"); } template static tl_object_ptr copy(const tl_object_ptr &obj) { return obj == nullptr ? nullptr : copy(*obj); } template <> td_api::object_ptr copy(const td_api::localFile &obj) { return td_api::make_object( obj.path_, obj.can_be_downloaded_, obj.can_be_deleted_, obj.is_downloading_active_, obj.is_downloading_completed_, obj.download_offset_, obj.downloaded_prefix_size_, obj.downloaded_size_); } template <> td_api::object_ptr copy(const td_api::remoteFile &obj) { return td_api::make_object(obj.id_, obj.unique_id_, obj.is_uploading_active_, obj.is_uploading_completed_, obj.uploaded_size_); } template <> td_api::object_ptr copy(const td_api::file &obj) { FileId file_id(obj.id_, 0); // wrong, but there should be no difference for get_file_object if (file_id.is_valid()) { return G()->td().get_actor_unsafe()->file_manager_.get()->get_file_object(file_id); } else { return td_api::make_object(obj.id_, obj.size_, obj.expected_size_, copy(obj.local_), copy(obj.remote_)); } } template <> tl_object_ptr copy(const td_api::minithumbnail &obj) { return td_api::make_object(obj.width_, obj.height_, obj.data_); } template <> tl_object_ptr copy(const td_api::photoSize &obj) { return td_api::make_object(obj.type_, copy(obj.photo_), obj.width_, obj.height_, vector(obj.progressive_sizes_)); } static tl_object_ptr copy_photo_size(const tl_object_ptr &obj) { return copy(obj); } template <> tl_object_ptr copy(const td_api::thumbnail &obj) { auto format = [&]() -> td_api::object_ptr { switch (obj.format_->get_id()) { case td_api::thumbnailFormatJpeg::ID: return td_api::make_object(); case td_api::thumbnailFormatPng::ID: return td_api::make_object(); case td_api::thumbnailFormatWebp::ID: return td_api::make_object(); case td_api::thumbnailFormatTgs::ID: return td_api::make_object(); case td_api::thumbnailFormatMpeg4::ID: return td_api::make_object(); case td_api::thumbnailFormatGif::ID: return td_api::make_object(); default: UNREACHABLE(); return nullptr; } }(); return td_api::make_object(std::move(format), obj.width_, obj.height_, copy(obj.file_)); } static tl_object_ptr copy_thumbnail(const tl_object_ptr &obj) { return copy(obj); } template <> tl_object_ptr copy(const td_api::StickerFormat &obj) { switch (obj.get_id()) { case td_api::stickerFormatWebp::ID: return td_api::make_object(); case td_api::stickerFormatTgs::ID: return td_api::make_object(); case td_api::stickerFormatWebm::ID: return td_api::make_object(); default: UNREACHABLE(); } return nullptr; } template <> tl_object_ptr copy(const td_api::MaskPoint &obj) { switch (obj.get_id()) { case td_api::maskPointForehead::ID: return td_api::make_object(); case td_api::maskPointEyes::ID: return td_api::make_object(); case td_api::maskPointMouth::ID: return td_api::make_object(); case td_api::maskPointChin::ID: return td_api::make_object(); default: UNREACHABLE(); } return nullptr; } template <> tl_object_ptr copy(const td_api::maskPosition &obj) { return td_api::make_object(copy(obj.point_), obj.x_shift_, obj.y_shift_, obj.scale_); } template <> tl_object_ptr copy(const td_api::StickerFullType &obj) { switch (obj.get_id()) { case td_api::stickerFullTypeRegular::ID: { auto &info = static_cast(obj); return td_api::make_object(copy(info.premium_animation_)); } case td_api::stickerFullTypeMask::ID: { auto &info = static_cast(obj); return td_api::make_object(copy(info.mask_position_)); } case td_api::stickerFullTypeCustomEmoji::ID: { auto &info = static_cast(obj); return td_api::make_object(info.custom_emoji_id_, info.needs_repainting_); } default: UNREACHABLE(); } return nullptr; } template <> tl_object_ptr copy(const td_api::point &obj) { return td_api::make_object(obj.x_, obj.y_); } template <> tl_object_ptr copy(const td_api::VectorPathCommand &obj) { switch (obj.get_id()) { case td_api::vectorPathCommandLine::ID: { auto &command = static_cast(obj); return td_api::make_object(copy(command.end_point_)); } case td_api::vectorPathCommandCubicBezierCurve::ID: { auto &command = static_cast(obj); return td_api::make_object( copy(command.start_control_point_), copy(command.end_control_point_), copy(command.end_point_)); } default: UNREACHABLE(); return nullptr; } } static tl_object_ptr copy_vector_path_command( const tl_object_ptr &obj) { return copy(obj); } template <> tl_object_ptr copy(const td_api::closedVectorPath &obj) { return td_api::make_object(transform(obj.commands_, copy_vector_path_command)); } static tl_object_ptr copy_closed_vector_path( const tl_object_ptr &obj) { return copy(obj); } template <> tl_object_ptr copy(const td_api::SpeechRecognitionResult &obj) { switch (obj.get_id()) { case td_api::speechRecognitionResultPending::ID: return td_api::make_object( static_cast(obj).partial_text_); case td_api::speechRecognitionResultText::ID: return td_api::make_object( static_cast(obj).text_); case td_api::speechRecognitionResultError::ID: { auto *error = static_cast(obj).error_.get(); return td_api::make_object( td_api::make_object(error->code_, error->message_)); } default: UNREACHABLE(); } return nullptr; } template <> tl_object_ptr copy(const td_api::animation &obj) { return td_api::make_object(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_, obj.has_stickers_, copy(obj.minithumbnail_), copy(obj.thumbnail_), copy(obj.animation_)); } template <> tl_object_ptr copy(const td_api::audio &obj) { return td_api::make_object(obj.duration_, obj.title_, obj.performer_, obj.file_name_, obj.mime_type_, copy(obj.album_cover_minithumbnail_), copy(obj.album_cover_thumbnail_), transform(obj.external_album_covers_, copy_thumbnail), copy(obj.audio_)); } template <> tl_object_ptr copy(const td_api::document &obj) { return td_api::make_object(obj.file_name_, obj.mime_type_, copy(obj.minithumbnail_), copy(obj.thumbnail_), copy(obj.document_)); } template <> tl_object_ptr copy(const td_api::photo &obj) { return td_api::make_object(obj.has_stickers_, copy(obj.minithumbnail_), transform(obj.sizes_, copy_photo_size)); } template <> tl_object_ptr copy(const td_api::sticker &obj) { return td_api::make_object( obj.id_, obj.set_id_, obj.width_, obj.height_, obj.emoji_, copy(obj.format_), copy(obj.full_type_), transform(obj.outline_, copy_closed_vector_path), copy(obj.thumbnail_), copy(obj.sticker_)); } template <> tl_object_ptr copy(const td_api::video &obj) { return td_api::make_object(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_, obj.has_stickers_, obj.supports_streaming_, copy(obj.minithumbnail_), copy(obj.thumbnail_), copy(obj.video_)); } template <> tl_object_ptr copy(const td_api::voiceNote &obj) { return td_api::make_object(obj.duration_, obj.waveform_, obj.mime_type_, copy(obj.speech_recognition_result_), copy(obj.voice_)); } template <> tl_object_ptr copy(const td_api::contact &obj) { return td_api::make_object(obj.phone_number_, obj.first_name_, obj.last_name_, obj.vcard_, obj.user_id_); } template <> tl_object_ptr copy(const td_api::location &obj) { return td_api::make_object(obj.latitude_, obj.longitude_, obj.horizontal_accuracy_); } template <> tl_object_ptr copy(const td_api::venue &obj) { return td_api::make_object(copy(obj.location_), obj.title_, obj.address_, obj.provider_, obj.id_, obj.type_); } template <> tl_object_ptr copy(const td_api::formattedText &obj) { // there are no entities in the game text return td_api::make_object(obj.text_, vector>()); } template <> tl_object_ptr copy(const td_api::game &obj) { return td_api::make_object(obj.id_, obj.short_name_, obj.title_, copy(obj.text_), obj.description_, copy(obj.photo_), copy(obj.animation_)); } template <> tl_object_ptr copy(const td_api::InlineQueryResultsButtonType &obj) { switch (obj.get_id()) { case td_api::inlineQueryResultsButtonTypeStartBot::ID: return td_api::make_object( static_cast(obj).parameter_); case td_api::inlineQueryResultsButtonTypeWebApp::ID: return td_api::make_object( static_cast(obj).url_); default: UNREACHABLE(); } return nullptr; } template <> tl_object_ptr copy(const td_api::inlineQueryResultsButton &obj) { return td_api::make_object(obj.text_, copy(obj.type_)); } template <> tl_object_ptr copy(const td_api::inlineQueryResultArticle &obj) { return td_api::make_object(obj.id_, obj.url_, obj.hide_url_, obj.title_, obj.description_, copy(obj.thumbnail_)); } template <> tl_object_ptr copy(const td_api::inlineQueryResultContact &obj) { return td_api::make_object(obj.id_, copy(obj.contact_), copy(obj.thumbnail_)); } template <> tl_object_ptr copy(const td_api::inlineQueryResultLocation &obj) { return td_api::make_object(obj.id_, copy(obj.location_), obj.title_, copy(obj.thumbnail_)); } template <> tl_object_ptr copy(const td_api::inlineQueryResultVenue &obj) { return td_api::make_object(obj.id_, copy(obj.venue_), copy(obj.thumbnail_)); } template <> tl_object_ptr copy(const td_api::inlineQueryResultGame &obj) { return td_api::make_object(obj.id_, copy(obj.game_)); } template <> tl_object_ptr copy(const td_api::inlineQueryResultAnimation &obj) { return td_api::make_object(obj.id_, copy(obj.animation_), obj.title_); } template <> tl_object_ptr copy(const td_api::inlineQueryResultAudio &obj) { return td_api::make_object(obj.id_, copy(obj.audio_)); } template <> tl_object_ptr copy(const td_api::inlineQueryResultDocument &obj) { return td_api::make_object(obj.id_, copy(obj.document_), obj.title_, obj.description_); } template <> tl_object_ptr copy(const td_api::inlineQueryResultPhoto &obj) { return td_api::make_object(obj.id_, copy(obj.photo_), obj.title_, obj.description_); } template <> tl_object_ptr copy(const td_api::inlineQueryResultSticker &obj) { return td_api::make_object(obj.id_, copy(obj.sticker_)); } template <> tl_object_ptr copy(const td_api::inlineQueryResultVideo &obj) { return td_api::make_object(obj.id_, copy(obj.video_), obj.title_, obj.description_); } template <> tl_object_ptr copy(const td_api::inlineQueryResultVoiceNote &obj) { return td_api::make_object(obj.id_, copy(obj.voice_note_), obj.title_); } static tl_object_ptr copy_result(const tl_object_ptr &obj_ptr) { tl_object_ptr result; downcast_call(const_cast(*obj_ptr), [&result](const auto &obj) { result = copy(obj); }); return result; } template <> tl_object_ptr copy(const td_api::inlineQueryResults &obj) { return td_api::make_object(obj.inline_query_id_, copy(obj.button_), transform(obj.results_, copy_result), obj.next_offset_); } tl_object_ptr InlineQueriesManager::decrease_pending_request_count(uint64 query_hash) { auto it = inline_query_results_.find(query_hash); CHECK(it != inline_query_results_.end()); CHECK(it->second.pending_request_count > 0); it->second.pending_request_count--; LOG(INFO) << "Inline query " << query_hash << " is awaited by " << it->second.pending_request_count << " pending requests"; if (it->second.pending_request_count == 0) { auto left_time = it->second.cache_expire_time - Time::now(); if (left_time < 0) { LOG(INFO) << "Drop cache for inline query " << query_hash; drop_inline_query_result_timeout_.cancel_timeout(static_cast(query_hash)); auto result = std::move(it->second.results); inline_query_results_.erase(it); return result; } else { drop_inline_query_result_timeout_.set_timeout_at(static_cast(query_hash), it->second.cache_expire_time); } } return copy(it->second.results); } tl_object_ptr InlineQueriesManager::register_thumbnail( tl_object_ptr &&web_document_ptr) const { PhotoSize thumbnail = get_web_document_photo_size(td_->file_manager_.get(), FileType::Thumbnail, DialogId(), std::move(web_document_ptr)); if (!thumbnail.file_id.is_valid() || thumbnail.type == 'v') { return nullptr; } return get_thumbnail_object(td_->file_manager_.get(), thumbnail, thumbnail.type == 'g' ? PhotoFormat::Gif : PhotoFormat::Jpeg); } string InlineQueriesManager::get_web_document_url(const tl_object_ptr &web_document_ptr) { if (web_document_ptr == nullptr) { return {}; } Slice url; switch (web_document_ptr->get_id()) { case telegram_api::webDocument::ID: { auto web_document = static_cast(web_document_ptr.get()); url = web_document->url_; break; } case telegram_api::webDocumentNoProxy::ID: { auto web_document = static_cast(web_document_ptr.get()); url = web_document->url_; break; } default: UNREACHABLE(); } auto r_http_url = parse_url(url); if (r_http_url.is_error()) { LOG(ERROR) << "Can't parse URL " << url; return {}; } return r_http_url.ok().get_url(); } string InlineQueriesManager::get_web_document_content_type( const tl_object_ptr &web_document_ptr) { if (web_document_ptr == nullptr) { return {}; } switch (web_document_ptr->get_id()) { case telegram_api::webDocument::ID: return static_cast(web_document_ptr.get())->mime_type_; case telegram_api::webDocumentNoProxy::ID: return static_cast(web_document_ptr.get())->mime_type_; default: UNREACHABLE(); } return {}; } void InlineQueriesManager::on_get_inline_query_results(DialogId dialog_id, UserId bot_user_id, uint64 query_hash, tl_object_ptr &&results) { LOG(INFO) << "Receive results for inline query " << query_hash; if (results == nullptr || results->query_id_ == 0) { decrease_pending_request_count(query_hash); return; } LOG(INFO) << to_string(results); td_->contacts_manager_->on_get_users(std::move(results->users_), "on_get_inline_query_results"); auto dialog_type = dialog_id.get_type(); bool allow_invoice = dialog_type != DialogType::SecretChat; vector> output_results; for (auto &result_ptr : results->results_) { tl_object_ptr output_result; switch (result_ptr->get_id()) { case telegram_api::botInlineMediaResult::ID: { auto result = move_tl_object_as(result_ptr); auto flags = result->flags_; bool has_document = (flags & BOT_INLINE_MEDIA_RESULT_FLAG_HAS_DOCUMENT) != 0; bool has_photo = (flags & BOT_INLINE_MEDIA_RESULT_FLAG_HAS_PHOTO) != 0; bool is_photo = result->type_ == "photo"; if (result->type_ == "game") { if (!has_photo) { LOG(ERROR) << "Receive game without photo in the result of inline query: " << to_string(result); break; } if (dialog_type == DialogType::Channel && td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { continue; } if (dialog_type == DialogType::SecretChat) { continue; } auto game = make_tl_object(); Game inline_game(td_, std::move(result->title_), std::move(result->description_), std::move(result->photo_), std::move(result->document_), DialogId()); game->id_ = std::move(result->id_); game->game_ = inline_game.get_game_object(td_, true); if (!register_inline_message_content(results->query_id_, game->id_, FileId(), std::move(result->send_message_), td_api::inputMessageGame::ID, allow_invoice, nullptr, &inline_game)) { continue; } output_result = std::move(game); } else if (has_document && !(has_photo && is_photo)) { auto document_ptr = std::move(result->document_); int32 document_id = document_ptr->get_id(); if (document_id == telegram_api::documentEmpty::ID) { LOG(ERROR) << "Receive empty cached document in the result of inline query"; break; } CHECK(document_id == telegram_api::document::ID); auto parsed_document = td_->documents_manager_->on_get_document( move_tl_object_as(document_ptr), DialogId()); switch (parsed_document.type) { case Document::Type::Animation: { LOG_IF(WARNING, result->type_ != "gif") << "Wrong result type " << result->type_; auto animation = make_tl_object(); animation->id_ = std::move(result->id_); animation->animation_ = td_->animations_manager_->get_animation_object(parsed_document.file_id); animation->title_ = std::move(result->title_); if (!register_inline_message_content(results->query_id_, animation->id_, parsed_document.file_id, std::move(result->send_message_), td_api::inputMessageAnimation::ID, allow_invoice)) { continue; } output_result = std::move(animation); break; } case Document::Type::Audio: { LOG_IF(WARNING, result->type_ != "audio") << "Wrong result type " << result->type_; auto audio = make_tl_object(); audio->id_ = std::move(result->id_); audio->audio_ = td_->audios_manager_->get_audio_object(parsed_document.file_id); if (!register_inline_message_content(results->query_id_, audio->id_, parsed_document.file_id, std::move(result->send_message_), td_api::inputMessageAudio::ID, allow_invoice)) { continue; } output_result = std::move(audio); break; } case Document::Type::General: { LOG_IF(WARNING, result->type_ != "file") << "Wrong result type " << result->type_; auto document = make_tl_object(); document->id_ = std::move(result->id_); document->document_ = td_->documents_manager_->get_document_object(parsed_document.file_id, PhotoFormat::Jpeg); document->title_ = std::move(result->title_); document->description_ = std::move(result->description_); if (!register_inline_message_content(results->query_id_, document->id_, parsed_document.file_id, std::move(result->send_message_), td_api::inputMessageDocument::ID, allow_invoice)) { continue; } output_result = std::move(document); break; } case Document::Type::Sticker: { LOG_IF(WARNING, result->type_ != "sticker") << "Wrong result type " << result->type_; auto sticker = make_tl_object(); sticker->id_ = std::move(result->id_); sticker->sticker_ = td_->stickers_manager_->get_sticker_object(parsed_document.file_id); if (!register_inline_message_content(results->query_id_, sticker->id_, parsed_document.file_id, std::move(result->send_message_), td_api::inputMessageSticker::ID, allow_invoice)) { continue; } output_result = std::move(sticker); break; } case Document::Type::Video: { LOG_IF(WARNING, result->type_ != "video") << "Wrong result type " << result->type_; auto video = make_tl_object(); video->id_ = std::move(result->id_); video->video_ = td_->videos_manager_->get_video_object(parsed_document.file_id); video->title_ = std::move(result->title_); video->description_ = std::move(result->description_); if (!register_inline_message_content(results->query_id_, video->id_, parsed_document.file_id, std::move(result->send_message_), td_api::inputMessageVideo::ID, allow_invoice)) { continue; } output_result = std::move(video); break; } case Document::Type::VideoNote: // FIXME break; case Document::Type::VoiceNote: { LOG_IF(WARNING, result->type_ != "voice") << "Wrong result type " << result->type_; auto voice_note = make_tl_object(); voice_note->id_ = std::move(result->id_); voice_note->voice_note_ = td_->voice_notes_manager_->get_voice_note_object(parsed_document.file_id); voice_note->title_ = std::move(result->title_); if (!register_inline_message_content(results->query_id_, voice_note->id_, parsed_document.file_id, std::move(result->send_message_), td_api::inputMessageVoiceNote::ID, allow_invoice)) { continue; } output_result = std::move(voice_note); break; } case Document::Type::Unknown: // invalid document break; default: UNREACHABLE(); break; } } else if (has_photo) { LOG_IF(ERROR, !is_photo) << "Wrong result type " << result->type_; auto photo = make_tl_object(); photo->id_ = std::move(result->id_); Photo p = get_photo(td_, std::move(result->photo_), DialogId()); if (p.is_empty()) { LOG(ERROR) << "Receive empty cached photo in the result of inline query"; break; } photo->photo_ = get_photo_object(td_->file_manager_.get(), p); photo->title_ = std::move(result->title_); photo->description_ = std::move(result->description_); if (!register_inline_message_content(results->query_id_, photo->id_, FileId(), std::move(result->send_message_), td_api::inputMessagePhoto::ID, allow_invoice, &p)) { continue; } output_result = std::move(photo); } else { LOG(ERROR) << "Receive inline query media result without photo and document: " << to_string(result); } break; } case telegram_api::botInlineResult::ID: { auto result = move_tl_object_as(result_ptr); auto content_type = get_web_document_content_type(result->content_); if (result->type_ == "article") { auto article = make_tl_object(); article->id_ = std::move(result->id_); article->url_ = get_web_document_url(result->content_); if (result->url_.empty()) { article->hide_url_ = true; } else { LOG_IF(ERROR, result->url_ != article->url_) << "URL has changed from " << article->url_ << " to " << result->url_; article->hide_url_ = false; } article->title_ = std::move(result->title_); article->description_ = std::move(result->description_); article->thumbnail_ = register_thumbnail(std::move(result->thumb_)); if (!register_inline_message_content(results->query_id_, article->id_, FileId(), std::move(result->send_message_), -1, allow_invoice)) { continue; } output_result = std::move(article); } else if (result->type_ == "contact") { auto contact = make_tl_object(); contact->id_ = std::move(result->id_); if (result->send_message_->get_id() == telegram_api::botInlineMessageMediaContact::ID) { auto inline_message_contact = static_cast(result->send_message_.get()); Contact c(inline_message_contact->phone_number_, inline_message_contact->first_name_, inline_message_contact->last_name_, inline_message_contact->vcard_, UserId()); contact->contact_ = c.get_contact_object(); } else { Contact c(std::move(result->description_), std::move(result->title_), string(), string(), UserId()); contact->contact_ = c.get_contact_object(); } contact->thumbnail_ = register_thumbnail(std::move(result->thumb_)); if (!register_inline_message_content(results->query_id_, contact->id_, FileId(), std::move(result->send_message_), -1, allow_invoice)) { continue; } output_result = std::move(contact); } else if (result->type_ == "geo") { auto location = make_tl_object(); location->id_ = std::move(result->id_); location->title_ = std::move(result->title_); if (result->send_message_->get_id() == telegram_api::botInlineMessageMediaGeo::ID) { auto inline_message_geo = static_cast(result->send_message_.get()); Location l(inline_message_geo->geo_); location->location_ = l.get_location_object(); } else { auto latitude_longitude = split(Slice(result->description_)); Location l(to_double(latitude_longitude.first), to_double(latitude_longitude.second), 0.0, 0); location->location_ = l.get_location_object(); } location->thumbnail_ = register_thumbnail(std::move(result->thumb_)); if (!register_inline_message_content(results->query_id_, location->id_, FileId(), std::move(result->send_message_), -1, allow_invoice)) { continue; } output_result = std::move(location); } else if (result->type_ == "venue") { auto venue = make_tl_object(); venue->id_ = std::move(result->id_); if (result->send_message_->get_id() == telegram_api::botInlineMessageMediaVenue::ID) { auto inline_message_venue = static_cast(result->send_message_.get()); Venue v(inline_message_venue->geo_, inline_message_venue->title_, inline_message_venue->address_, inline_message_venue->provider_, inline_message_venue->venue_id_, inline_message_venue->venue_type_); venue->venue_ = v.get_venue_object(); } else if (result->send_message_->get_id() == telegram_api::botInlineMessageMediaGeo::ID) { auto inline_message_geo = static_cast(result->send_message_.get()); Venue v(inline_message_geo->geo_, std::move(result->title_), std::move(result->description_), string(), string(), string()); venue->venue_ = v.get_venue_object(); } else { Venue v(nullptr, std::move(result->title_), std::move(result->description_), string(), string(), string()); venue->venue_ = v.get_venue_object(); } venue->thumbnail_ = register_thumbnail(std::move(result->thumb_)); if (!register_inline_message_content(results->query_id_, venue->id_, FileId(), std::move(result->send_message_), -1, allow_invoice)) { continue; } output_result = std::move(venue); } else if (result->type_ == "photo" && content_type == "image/jpeg") { auto photo = make_tl_object(); photo->id_ = std::move(result->id_); PhotoSize photo_size = get_web_document_photo_size(td_->file_manager_.get(), FileType::Temp, DialogId(), std::move(result->content_)); if (!photo_size.file_id.is_valid() || photo_size.type == 'v' || photo_size.type == 'g') { LOG(ERROR) << "Receive invalid web document photo"; continue; } Photo new_photo; new_photo.id = 0; PhotoSize thumbnail = get_web_document_photo_size(td_->file_manager_.get(), FileType::Thumbnail, DialogId(), std::move(result->thumb_)); if (thumbnail.file_id.is_valid() && thumbnail.type != 'v' && thumbnail.type != 'g') { new_photo.photos.push_back(std::move(thumbnail)); } new_photo.photos.push_back(std::move(photo_size)); photo->photo_ = get_photo_object(td_->file_manager_.get(), new_photo); photo->title_ = std::move(result->title_); photo->description_ = std::move(result->description_); if (!register_inline_message_content(results->query_id_, photo->id_, FileId(), std::move(result->send_message_), td_api::inputMessagePhoto::ID, allow_invoice, &new_photo)) { continue; } output_result = std::move(photo); } else { if (result->content_ == nullptr) { LOG(ERROR) << "Unsupported inline query result without content " << to_string(result); continue; } vector> attributes; downcast_call(*result->content_, [&attributes](auto &web_document) { attributes = std::move(web_document.attributes_); }); bool is_animation = result->type_ == "gif" && (content_type == "image/gif" || content_type == "video/mp4"); if (is_animation) { attributes.push_back(make_tl_object()); } auto default_document_type = [type = result->type_, is_animation] { if (type == "audio") { return Document::Type::Audio; } if (is_animation) { return Document::Type::Animation; } if (type == "sticker") { return Document::Type::Sticker; } if (type == "video") { return Document::Type::Video; } if (type == "voice") { return Document::Type::VoiceNote; } return Document::Type::General; }(); auto parsed_document = td_->documents_manager_->on_get_document( {std::move(result->content_), get_web_document_photo_size(td_->file_manager_.get(), FileType::Thumbnail, DialogId(), std::move(result->thumb_)), std::move(attributes)}, DialogId(), nullptr, default_document_type); auto file_id = parsed_document.file_id; if (!file_id.is_valid()) { continue; } if (result->type_ == "audio" && parsed_document.type == Document::Type::Audio) { auto audio = make_tl_object(); audio->id_ = std::move(result->id_); audio->audio_ = td_->audios_manager_->get_audio_object(file_id); if (!register_inline_message_content(results->query_id_, audio->id_, file_id, std::move(result->send_message_), td_api::inputMessageAudio::ID, allow_invoice)) { continue; } output_result = std::move(audio); } else if (result->type_ == "file" && parsed_document.type == Document::Type::General) { auto document = make_tl_object(); document->id_ = std::move(result->id_); document->document_ = td_->documents_manager_->get_document_object(file_id, PhotoFormat::Jpeg); document->title_ = std::move(result->title_); document->description_ = std::move(result->description_); if (!register_inline_message_content(results->query_id_, document->id_, file_id, std::move(result->send_message_), td_api::inputMessageDocument::ID, allow_invoice)) { continue; } output_result = std::move(document); } else if (is_animation && parsed_document.type == Document::Type::Animation) { auto animation = make_tl_object(); animation->id_ = std::move(result->id_); animation->animation_ = td_->animations_manager_->get_animation_object(file_id); animation->title_ = std::move(result->title_); if (!register_inline_message_content(results->query_id_, animation->id_, file_id, std::move(result->send_message_), td_api::inputMessageAnimation::ID, allow_invoice)) { continue; } output_result = std::move(animation); } else if (result->type_ == "sticker" && parsed_document.type == Document::Type::Sticker) { auto sticker = make_tl_object(); sticker->id_ = std::move(result->id_); sticker->sticker_ = td_->stickers_manager_->get_sticker_object(file_id); if (!register_inline_message_content(results->query_id_, sticker->id_, file_id, std::move(result->send_message_), td_api::inputMessageSticker::ID, allow_invoice)) { continue; } output_result = std::move(sticker); } else if (result->type_ == "video" && parsed_document.type == Document::Type::Video) { auto video = make_tl_object(); video->id_ = std::move(result->id_); video->video_ = td_->videos_manager_->get_video_object(file_id); video->title_ = std::move(result->title_); video->description_ = std::move(result->description_); if (!register_inline_message_content(results->query_id_, video->id_, file_id, std::move(result->send_message_), td_api::inputMessageVideo::ID, allow_invoice)) { continue; } output_result = std::move(video); } else if (result->type_ == "voice" && parsed_document.type == Document::Type::VoiceNote) { auto voice_note = make_tl_object(); voice_note->id_ = std::move(result->id_); voice_note->voice_note_ = td_->voice_notes_manager_->get_voice_note_object(file_id); voice_note->title_ = std::move(result->title_); if (!register_inline_message_content(results->query_id_, voice_note->id_, file_id, std::move(result->send_message_), td_api::inputMessageVoiceNote::ID, allow_invoice)) { continue; } output_result = std::move(voice_note); } else { LOG(WARNING) << "Unsupported inline query result " << to_string(result); } } break; } default: UNREACHABLE(); } if (output_result != nullptr) { output_results.push_back(std::move(output_result)); } } auto it = inline_query_results_.find(query_hash); CHECK(it != inline_query_results_.end()); query_id_to_bot_user_id_[results->query_id_] = bot_user_id; td_api::object_ptr button; if (results->switch_pm_ != nullptr) { button = td_api::make_object( results->switch_pm_->text_, td_api::make_object(results->switch_pm_->start_param_)); } else if (results->switch_webview_) { button = td_api::make_object( results->switch_webview_->text_, td_api::make_object(results->switch_webview_->url_ + "#iq")); } it->second.results = make_tl_object(results->query_id_, std::move(button), std::move(output_results), results->next_offset_); it->second.cache_expire_time = Time::now() + results->cache_time_; } vector InlineQueriesManager::get_recent_inline_bots(Promise &&promise) { if (!load_recently_used_bots(promise)) { return vector(); } promise.set_value(Unit()); return recently_used_bot_user_ids_; } void InlineQueriesManager::save_recently_used_bots() { if (recently_used_bots_loaded_ < 2) { return; } string value; string value_ids; for (auto &bot_user_id : recently_used_bot_user_ids_) { if (!value.empty()) { value += ','; value_ids += ','; } value += td_->contacts_manager_->get_user_first_username(bot_user_id); value_ids += to_string(bot_user_id.get()); } G()->td_db()->get_binlog_pmc()->set("recently_used_inline_bot_usernames", value); G()->td_db()->get_binlog_pmc()->set("recently_used_inline_bots", value_ids); } bool InlineQueriesManager::load_recently_used_bots(Promise &promise) { if (recently_used_bots_loaded_ >= 2) { return true; } string saved_bot_ids = G()->td_db()->get_binlog_pmc()->get("recently_used_inline_bots"); auto bot_ids = full_split(saved_bot_ids, ','); string saved_bots = G()->td_db()->get_binlog_pmc()->get("recently_used_inline_bot_usernames"); auto bot_usernames = full_split(saved_bots, ','); if (bot_ids.empty()) { recently_used_bots_loaded_ = 2; if (!recently_used_bot_user_ids_.empty()) { save_recently_used_bots(); } return true; } LOG(INFO) << "Load recently used inline bots " << saved_bots << '/' << saved_bot_ids; if (recently_used_bots_loaded_ == 1 && resolve_recent_inline_bots_multipromise_.promise_count() == 0) { // queries was sent and have already been finished auto newly_used_bots = std::move(recently_used_bot_user_ids_); recently_used_bot_user_ids_.clear(); for (auto it = bot_ids.rbegin(); it != bot_ids.rend(); ++it) { UserId user_id(to_integer(*it)); if (td_->contacts_manager_->have_user(user_id)) { update_bot_usage(user_id); } else { LOG(ERROR) << "Can't find " << user_id; } } for (auto it = newly_used_bots.rbegin(); it != newly_used_bots.rend(); ++it) { update_bot_usage(*it); } recently_used_bots_loaded_ = 2; if (!newly_used_bots.empty()) { save_recently_used_bots(); } return true; } resolve_recent_inline_bots_multipromise_.add_promise(std::move(promise)); if (recently_used_bots_loaded_ == 0) { resolve_recent_inline_bots_multipromise_.set_ignore_errors(true); auto lock = resolve_recent_inline_bots_multipromise_.get_promise(); if (!G()->use_chat_info_database()) { for (auto &bot_username : bot_usernames) { td_->messages_manager_->search_public_dialog(bot_username, false, resolve_recent_inline_bots_multipromise_.get_promise()); } } else { for (auto &bot_id : bot_ids) { UserId user_id(to_integer(bot_id)); td_->contacts_manager_->get_user(user_id, 3, resolve_recent_inline_bots_multipromise_.get_promise()); } } lock.set_value(Unit()); recently_used_bots_loaded_ = 1; } return false; } tl_object_ptr InlineQueriesManager::get_inline_query_results_object(uint64 query_hash) { return decrease_pending_request_count(query_hash); } void InlineQueriesManager::on_new_query(int64 query_id, UserId sender_user_id, Location user_location, tl_object_ptr peer_type, const string &query, const string &offset) { if (!sender_user_id.is_valid()) { LOG(ERROR) << "Receive new inline query from invalid " << sender_user_id; return; } LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id; if (!td_->auth_manager_->is_bot()) { LOG(ERROR) << "Receive new inline query"; return; } auto chat_type = [&]() -> td_api::object_ptr { if (peer_type == nullptr) { return nullptr; } switch (peer_type->get_id()) { case telegram_api::inlineQueryPeerTypeSameBotPM::ID: return td_api::make_object(sender_user_id.get()); case telegram_api::inlineQueryPeerTypePM::ID: return td_api::make_object(0); case telegram_api::inlineQueryPeerTypeChat::ID: return td_api::make_object(0); case telegram_api::inlineQueryPeerTypeMegagroup::ID: return td_api::make_object(0, false); case telegram_api::inlineQueryPeerTypeBroadcast::ID: return td_api::make_object(0, true); default: UNREACHABLE(); return nullptr; } }(); send_closure(G()->td(), &Td::send_update, make_tl_object( query_id, td_->contacts_manager_->get_user_id_object(sender_user_id, "updateNewInlineQuery"), user_location.get_location_object(), std::move(chat_type), query, offset)); } void InlineQueriesManager::on_chosen_result( UserId user_id, Location user_location, const string &query, const string &result_id, tl_object_ptr &&input_bot_inline_message_id) { if (!user_id.is_valid()) { LOG(ERROR) << "Receive chosen inline query result from invalid " << user_id; return; } LOG_IF(ERROR, !td_->contacts_manager_->have_user(user_id)) << "Receive unknown " << user_id; if (!td_->auth_manager_->is_bot()) { LOG(ERROR) << "Receive chosen inline query result"; return; } send_closure(G()->td(), &Td::send_update, make_tl_object( td_->contacts_manager_->get_user_id_object(user_id, "updateNewChosenInlineResult"), user_location.get_location_object(), query, result_id, get_inline_message_id(std::move(input_bot_inline_message_id)))); } bool InlineQueriesManager::update_bot_usage(UserId bot_user_id) { if (!bot_user_id.is_valid()) { return false; } if (!recently_used_bot_user_ids_.empty() && recently_used_bot_user_ids_[0] == bot_user_id) { return false; } auto r_bot_data = td_->contacts_manager_->get_bot_data(bot_user_id); if (r_bot_data.is_error()) { return false; } if (r_bot_data.ok().username.empty() || !r_bot_data.ok().is_inline) { return false; } auto it = std::find(recently_used_bot_user_ids_.begin(), recently_used_bot_user_ids_.end(), bot_user_id); if (it == recently_used_bot_user_ids_.end()) { if (static_cast(recently_used_bot_user_ids_.size()) == MAX_RECENT_INLINE_BOTS) { CHECK(!recently_used_bot_user_ids_.empty()); recently_used_bot_user_ids_.back() = bot_user_id; } else { recently_used_bot_user_ids_.push_back(bot_user_id); } it = recently_used_bot_user_ids_.end() - 1; } std::rotate(recently_used_bot_user_ids_.begin(), it, it + 1); return true; } void InlineQueriesManager::remove_recent_inline_bot(UserId bot_user_id, Promise &&promise) { if (td::remove(recently_used_bot_user_ids_, bot_user_id)) { save_recently_used_bots(); } promise.set_value(Unit()); } } // namespace td