diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index f95791d8..77af12dd 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -3277,6 +3277,10 @@ setStickerPositionInSet sticker:InputFile position:int32 = Ok; removeStickerFromSet sticker:InputFile = Ok; +//@description Returns information about a file with a map thumbnail @location Location of the map center @zoom Map zoom level; 13-20 @width Map width in pixels before applying scale; 16-1024 @height Map height in pixels before applying scale; 16-1024 @scale Map scale; 1-3 @chat_id Identifier of a chat, in which the thumbnail will be shown. Use 0 if unknown +getMapThumbnailFile location:location zoom:int32 width:int32 height:int32 scale:int32 chat_id:int53 = File; + + //@description Accepts Telegram terms of services @terms_of_service_id Terms of service identifier acceptTermsOfService terms_of_service_id:string = Ok; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 4fa95560..f1685a8b 100644 Binary files a/td/generate/scheme/td_api.tlo and b/td/generate/scheme/td_api.tlo differ diff --git a/td/telegram/Location.cpp b/td/telegram/Location.cpp index 30c82cdc..b499ddb5 100644 --- a/td/telegram/Location.cpp +++ b/td/telegram/Location.cpp @@ -64,6 +64,11 @@ bool Location::empty() const { return is_empty_; } +bool Location::is_valid_map_point() const { + const double MAX_VALID_MAP_LATITUDE = 85.05112877; + return !empty() && std::abs(latitude_) <= MAX_VALID_MAP_LATITUDE; +} + tl_object_ptr Location::get_location_object() const { if (empty()) { return nullptr; diff --git a/td/telegram/Location.h b/td/telegram/Location.h index c2694869..8479e5f3 100644 --- a/td/telegram/Location.h +++ b/td/telegram/Location.h @@ -45,6 +45,8 @@ class Location { bool empty() const; + bool is_valid_map_point() const; + tl_object_ptr get_location_object() const; tl_object_ptr get_input_geo_point() const; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 5650699f..9de5131e 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -5988,6 +5988,21 @@ void Td::on_request(uint64 id, const td_api::resetAllNotificationSettings &reque send_closure(actor_id(this), &Td::send_result, id, make_tl_object()); } +void Td::on_request(uint64 id, const td_api::getMapThumbnailFile &request) { + DialogId dialog_id(request.chat_id_); + if (!messages_manager_->have_dialog_force(dialog_id)) { + dialog_id = DialogId(); + } + + auto r_file_id = file_manager_->get_map_thumbnail_file_id(Location(request.location_), request.zoom_, request.width_, + request.height_, request.scale_, dialog_id); + if (r_file_id.is_error()) { + send_closure(actor_id(this), &Td::send_error, id, r_file_id.move_as_error()); + } else { + send_closure(actor_id(this), &Td::send_result, id, file_manager_->get_file_object(r_file_id.ok())); + } +} + void Td::on_request(uint64 id, const td_api::getLanguagePackInfo &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index b82bb98d..0913766a 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -755,6 +755,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, td_api::reportChat &request); + void on_request(uint64 id, const td_api::getMapThumbnailFile &request); + void on_request(uint64 id, const td_api::getLanguagePackInfo &request); void on_request(uint64 id, td_api::getLanguagePackStrings &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 4aadcc17..2785c4a8 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2195,6 +2195,24 @@ class CliClient final : public Actor { send_request(make_tl_object(as_file_id(args))); } else if (op == "grf") { send_request(make_tl_object(args, nullptr)); + } else if (op == "gmtf") { + string latitude; + string longitude; + string zoom; + string width; + string height; + string scale; + string chat_id; + std::tie(latitude, args) = split(args); + std::tie(longitude, args) = split(args); + std::tie(zoom, args) = split(args); + std::tie(width, args) = split(args); + std::tie(height, args) = split(args); + std::tie(scale, chat_id) = split(args); + + send_request(make_tl_object( + as_location(latitude, longitude), to_integer(zoom), to_integer(width), + to_integer(height), to_integer(scale), as_chat_id(chat_id))); } else if (op == "df" || op == "DownloadFile") { string file_id_str; string priority; diff --git a/td/telegram/files/FileGenerateManager.cpp b/td/telegram/files/FileGenerateManager.cpp index db32d575..4e4b5dd2 100644 --- a/td/telegram/files/FileGenerateManager.cpp +++ b/td/telegram/files/FileGenerateManager.cpp @@ -12,6 +12,8 @@ #include "td/telegram/files/FileLoaderUtils.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/Global.h" +#include "td/telegram/net/NetQuery.h" +#include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/Td.h" #include "td/utils/common.h" @@ -20,6 +22,7 @@ #include "td/utils/port/path.h" #include "td/utils/Slice.h" +#include #include namespace td { @@ -56,7 +59,7 @@ class FileDownloadGenerateActor : public FileGenerateActor { ActorShared<> parent_; void start_up() override { - LOG(INFO) << "DOWNLOAD " << file_id_; + LOG(INFO) << "Generate by downloading " << file_id_; class Callback : public FileManager::DownloadCallback { public: explicit Callback(ActorId parent) : parent_(std::move(parent)) { @@ -91,7 +94,7 @@ class FileDownloadGenerateActor : public FileGenerateActor { callback->on_ok(location); } else { LOG(ERROR) << "Expected to have local location"; - callback->on_error(Status::Error("Unknown")); + callback->on_error(Status::Error(500, "Unknown")); } }); stop(); @@ -102,6 +105,129 @@ class FileDownloadGenerateActor : public FileGenerateActor { } }; +class MapDownloadGenerateActor : public FileGenerateActor { + public: + MapDownloadGenerateActor(string conversion, std::unique_ptr callback, ActorShared<> parent) + : conversion_(std::move(conversion)), callback_(std::move(callback)), parent_(std::move(parent)) { + } + void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) override { + UNREACHABLE(); + } + void file_generate_finish(Status status, Promise<> promise) override { + UNREACHABLE(); + } + + private: + string conversion_; + std::unique_ptr callback_; + ActorShared<> parent_; + string file_name_; + + class Callback : public NetQueryCallback { + ActorId parent_; + + public: + explicit Callback(ActorId parent) : parent_(parent) { + } + + void on_result(NetQueryPtr query) override { + send_closure(parent_, &MapDownloadGenerateActor::on_result, std::move(query)); + } + + void hangup_shared() override { + send_closure(parent_, &MapDownloadGenerateActor::hangup_shared); + } + }; + ActorOwn net_callback_; + + Result> parse_conversion() { + auto parts = full_split(Slice(conversion_), '#'); + if (parts.size() != 8 || !parts[0].empty() || parts[1] != "map") { + return Status::Error("Wrong conversion"); + } + + TRY_RESULT(zoom, to_integer_safe(parts[2])); + TRY_RESULT(x, to_integer_safe(parts[3])); + TRY_RESULT(y, to_integer_safe(parts[4])); + TRY_RESULT(width, to_integer_safe(parts[5])); + TRY_RESULT(height, to_integer_safe(parts[6])); + TRY_RESULT(scale, to_integer_safe(parts[7])); + int64 access_hash = 0; + + if (zoom < 13 || zoom > 20) { + return Status::Error("Wrong zoom"); + } + auto size = 256 * (1 << zoom); + if (x < 0 || x >= size) { + return Status::Error("Wrong x"); + } + if (y < 0 || y >= size) { + return Status::Error("Wrong y"); + } + if (width < 16 || height < 16 || width > 1024 || height > 1024) { + return Status::Error("Wrong dimensions"); + } + if (scale < 1 || scale > 3) { + return Status::Error("Wrong scale"); + } + + file_name_ = PSTRING() << "map_" << zoom << "_" << x << "_" << y << ".jpg"; + + const double PI = 3.14159265358979323846; + double longitude = (x + 0.1) * 360.0 / size - 180; + double latitude = 90 - 360 * std::atan(std::exp(((y + 0.1) / size - 0.5) * 2 * PI)) / PI; + + return make_tl_object( + make_tl_object(latitude, longitude), access_hash, width, height, zoom, scale); + } + + void start_up() override { + auto r_input_web_file = parse_conversion(); + if (r_input_web_file.is_error()) { + LOG(ERROR) << "Can't parse " << conversion_ << ": " << r_input_web_file.error(); + return on_error(r_input_web_file.move_as_error()); + } + + net_callback_ = create_actor("MapDownloadGenerateCallback", actor_id(this)); + + LOG(INFO) << "Download " << conversion_; + auto query = G()->net_query_creator().create( + create_storer(telegram_api::upload_getWebFile(r_input_web_file.move_as_ok(), 0, 1 << 20)), + G()->get_webfile_dc_id(), NetQuery::Type::DownloadSmall); + G()->net_query_dispatcher().dispatch_with_callback(std::move(query), {net_callback_.get(), 0}); + } + + void on_result(NetQueryPtr query) { + auto r_result = process_result(std::move(query)); + if (r_result.is_error()) { + return on_error(r_result.move_as_error()); + } + + callback_->on_ok(r_result.ok()); + stop(); + } + + Result process_result(NetQueryPtr query) { + TRY_RESULT(web_file, fetch_result(std::move(query))); + + if (static_cast(web_file->size_) != web_file->bytes_.size()) { + LOG(ERROR) << "Failed to download map of size " << web_file->size_; + return Status::Error("File is too big"); + } + + return save_file_bytes(FileType::Thumbnail, std::move(web_file->bytes_), file_name_); + } + + void on_error(Status error) { + callback_->on_error(std::move(error)); + stop(); + } + + void hangup_shared() { + on_error(Status::Error(1, "Cancelled")); + } +}; + class FileExternalGenerateActor : public FileGenerateActor { public: FileExternalGenerateActor(uint64 query_id, const FullGenerateFileLocation &generate_location, @@ -226,6 +352,9 @@ void FileGenerateManager::generate_file(uint64 query_id, const FullGenerateFileL auto file_id = FileId(to_integer(conversion.substr(file_id_query.size())), 0); query.worker_ = create_actor("FileDownloadGenerateActor", generate_location.file_type_, file_id, std::move(callback), std::move(parent)); + } else if (begins_with(conversion, "#map#") && generate_location.original_path_.empty()) { + query.worker_ = create_actor( + "MapDownloadGenerateActor", std::move(generate_location.conversion_), std::move(callback), std::move(parent)); } else { query.worker_ = create_actor("FileExternalGenerationActor", query_id, generate_location, local_location, std::move(name), std::move(callback), diff --git a/td/telegram/files/FileManager.cpp b/td/telegram/files/FileManager.cpp index 58d7954e..05351235 100644 --- a/td/telegram/files/FileManager.cpp +++ b/td/telegram/files/FileManager.cpp @@ -28,6 +28,7 @@ #include "td/utils/tl_helpers.h" #include +#include #include #include #include @@ -168,7 +169,8 @@ void FileNode::set_upload_priority(int8 priority) { } void FileNode::set_generate_priority(int8 download_priority, int8 upload_priority) { - if ((download_priority_ == 0) != (download_priority == 0) || (upload_priority_ == 0) != (upload_priority == 0)) { + if ((generate_download_priority_ == 0) != (download_priority == 0) || + (generate_upload_priority_ == 0) != (upload_priority == 0)) { VLOG(update_file) << "File " << main_file_id_ << " has changed generate priority to " << download_priority << "/" << upload_priority; on_info_changed(); @@ -561,7 +563,7 @@ Status FileManager::check_local_location(FullLocalFileLocation &location, int64 return Status::Error(PSLICE() << "File \"" << location.path_ << "\" was modified"); } if ((location.file_type_ == FileType::Thumbnail || location.file_type_ == FileType::EncryptedThumbnail) && - size >= MAX_THUMBNAIL_SIZE) { + size >= MAX_THUMBNAIL_SIZE && !begins_with(PathView(location.path_).file_name(), "map")) { return Status::Error(PSLICE() << "File \"" << location.path_ << "\" is too big for thumbnail " << tag("size", format::as_size(size))); } @@ -2058,6 +2060,38 @@ Result FileManager::get_input_file_id(FileType type, const tl_object_ptr return check_input_file_id(type, std::move(r_file_id), is_encrypted, allow_zero, is_secure); } +Result FileManager::get_map_thumbnail_file_id(Location location, int32 zoom, int32 width, int32 height, + int32 scale, DialogId owner_dialog_id) { + if (!location.is_valid_map_point()) { + return Status::Error(6, "Invalid location specified"); + } + if (zoom < 13 || zoom > 20) { + return Status::Error(6, "Wrong zoom"); + } + if (width < 16 || width > 1024) { + return Status::Error(6, "Wrong width"); + } + if (height < 16 || height > 1024) { + return Status::Error(6, "Wrong height"); + } + if (scale < 1 || scale > 3) { + return Status::Error(6, "Wrong scale"); + } + + const double PI = 3.14159265358979323846; + double sin_latitude = std::sin(location.get_latitude() * PI / 180); + int32 size = 256 * (1 << zoom); + int32 x = static_cast((location.get_longitude() + 180) / 360 * size); + int32 y = static_cast((0.5 - std::log((1 + sin_latitude) / (1 - sin_latitude)) / (4 * PI)) * size); + x = clamp(x, 0, size - 1); // just in case + y = clamp(y, 0, size - 1); // just in case + + string conversion = PSTRING() << "#map#" << zoom << "#" << x << "#" << y << "#" << width << "#" << height << "#" + << scale << "#"; + return register_generate(FileType::Thumbnail, FileLocationSource::FromUser, string(), std::move(conversion), + owner_dialog_id, 0); +} + vector> FileManager::get_input_documents(const vector &file_ids) { vector> result; result.reserve(file_ids.size()); diff --git a/td/telegram/files/FileManager.h b/td/telegram/files/FileManager.h index 30c619f0..379ec56d 100644 --- a/td/telegram/files/FileManager.h +++ b/td/telegram/files/FileManager.h @@ -16,6 +16,7 @@ #include "td/telegram/files/FileLoadManager.h" #include "td/telegram/files/FileLocation.h" #include "td/telegram/files/FileStats.h" +#include "td/telegram/Location.h" #include "td/actor/actor.h" #include "td/actor/PromiseFuture.h" @@ -350,6 +351,9 @@ class FileManager : public FileLoadManager::Callback { DialogId owner_dialog_id, bool allow_zero, bool is_encrypted, bool get_by_hash = false, bool is_secure = false) TD_WARN_UNUSED_RESULT; + Result get_map_thumbnail_file_id(Location location, int32 zoom, int32 width, int32 height, int32 scale, + DialogId owner_dialog_id) TD_WARN_UNUSED_RESULT; + vector> get_input_documents(const vector &file_ids); template