// // 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/Photo.h" #include "td/telegram/files/FileEncryptionKey.h" #include "td/telegram/files/FileLocation.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/net/DcId.h" #include "td/telegram/ConfigShared.h" #include "td/utils/algorithm.h" #include "td/utils/base64.h" #include "td/utils/common.h" #include "td/utils/format.h" #include "td/utils/HttpUrl.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Random.h" #include "td/utils/SliceBuilder.h" #include #include #include namespace td { static uint16 get_dimension(int32 size, const char *source) { if (size < 0 || size > 65535) { LOG(ERROR) << "Wrong image dimension = " << size << " from " << source; return 0; } return narrow_cast(size); } Dimensions get_dimensions(int32 width, int32 height, const char *source) { Dimensions result; result.width = get_dimension(width, source); result.height = get_dimension(height, source); if (result.width == 0 || result.height == 0) { result.width = 0; result.height = 0; } return result; } static uint32 get_pixel_count(const Dimensions &dimensions) { return static_cast(dimensions.width) * static_cast(dimensions.height); } bool operator==(const Dimensions &lhs, const Dimensions &rhs) { return lhs.width == rhs.width && lhs.height == rhs.height; } bool operator!=(const Dimensions &lhs, const Dimensions &rhs) { return !(lhs == rhs); } StringBuilder &operator<<(StringBuilder &string_builder, const Dimensions &dimensions) { return string_builder << "(" << dimensions.width << ", " << dimensions.height << ")"; } td_api::object_ptr get_minithumbnail_object(const string &packed) { if (G()->shared_config().get_option_boolean("disable_minithumbnails")) { return nullptr; } if (packed.size() < 3) { return nullptr; } if (packed[0] == '\x01') { static const string header = base64_decode( "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////" "m8H///" "/6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/" "wAARCAAAAAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/" "8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0R" "FRkd" "ISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2" "uHi4" "+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/" "8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkN" "ERUZ" "HSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2" "Nna4" "uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwA=") .move_as_ok(); static const string footer = base64_decode("/9k=").move_as_ok(); auto result = td_api::make_object(); result->height_ = static_cast(packed[1]); result->width_ = static_cast(packed[2]); result->data_ = PSTRING() << header.substr(0, 164) << packed[1] << header[165] << packed[2] << header.substr(167) << packed.substr(3) << footer; return result; } return nullptr; } static td_api::object_ptr get_thumbnail_format_object(PhotoFormat format) { switch (format) { case PhotoFormat::Jpeg: return td_api::make_object(); case PhotoFormat::Png: return td_api::make_object(); case PhotoFormat::Webp: return td_api::make_object(); case PhotoFormat::Gif: return td_api::make_object(); case PhotoFormat::Tgs: return td_api::make_object(); case PhotoFormat::Mpeg4: return td_api::make_object(); default: UNREACHABLE(); return nullptr; } } static StringBuilder &operator<<(StringBuilder &string_builder, PhotoFormat format) { switch (format) { case PhotoFormat::Jpeg: return string_builder << "jpg"; case PhotoFormat::Png: return string_builder << "png"; case PhotoFormat::Webp: return string_builder << "webp"; case PhotoFormat::Gif: return string_builder << "gif"; case PhotoFormat::Tgs: return string_builder << "tgs"; case PhotoFormat::Mpeg4: return string_builder << "mp4"; default: UNREACHABLE(); return string_builder; } } static FileId register_photo(FileManager *file_manager, const PhotoSizeSource &source, int64 id, int64 access_hash, std::string file_reference, DialogId owner_dialog_id, int32 file_size, DcId dc_id, PhotoFormat format) { LOG(DEBUG) << "Receive " << format << " photo " << id << " of type " << source.get_file_type() << " from " << dc_id; auto suggested_name = PSTRING() << source.get_unique_name(id) << '.' << format; auto file_location_source = owner_dialog_id.get_type() == DialogType::SecretChat ? FileLocationSource::FromUser : FileLocationSource::FromServer; return file_manager->register_remote( FullRemoteFileLocation(source, id, access_hash, dc_id, std::move(file_reference)), file_location_source, owner_dialog_id, file_size, 0, std::move(suggested_name)); } ProfilePhoto get_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash, tl_object_ptr &&profile_photo_ptr) { ProfilePhoto result; int32 profile_photo_id = profile_photo_ptr == nullptr ? telegram_api::userProfilePhotoEmpty::ID : profile_photo_ptr->get_id(); switch (profile_photo_id) { case telegram_api::userProfilePhotoEmpty::ID: break; case telegram_api::userProfilePhoto::ID: { auto profile_photo = move_tl_object_as(profile_photo_ptr); auto dc_id = DcId::create(profile_photo->dc_id_); result.has_animation = profile_photo->has_video_; result.id = profile_photo->photo_id_; if (!G()->shared_config().get_option_boolean("disable_minithumbnails")) { result.minithumbnail = profile_photo->stripped_thumb_.as_slice().str(); } result.small_file_id = register_photo( file_manager, PhotoSizeSource::dialog_photo(DialogId(user_id), user_access_hash, false), result.id, 0 /*access_hash*/, "" /*file_reference*/, DialogId(), 0 /*file_size*/, dc_id, PhotoFormat::Jpeg); result.big_file_id = register_photo( file_manager, PhotoSizeSource::dialog_photo(DialogId(user_id), user_access_hash, true), result.id, 0 /*access_hash*/, "" /*file_reference*/, DialogId(), 0 /*file_size*/, dc_id, PhotoFormat::Jpeg); break; } default: UNREACHABLE(); break; } return result; } tl_object_ptr get_profile_photo_object(FileManager *file_manager, const ProfilePhoto &profile_photo) { if (!profile_photo.small_file_id.is_valid()) { return nullptr; } return td_api::make_object( profile_photo.id, file_manager->get_file_object(profile_photo.small_file_id), file_manager->get_file_object(profile_photo.big_file_id), get_minithumbnail_object(profile_photo.minithumbnail), profile_photo.has_animation); } bool operator==(const ProfilePhoto &lhs, const ProfilePhoto &rhs) { bool location_differs = lhs.small_file_id != rhs.small_file_id || lhs.big_file_id != rhs.big_file_id; bool id_differs = lhs.id != rhs.id; if (location_differs) { return false; } return lhs.has_animation == rhs.has_animation && lhs.minithumbnail == rhs.minithumbnail && !id_differs; } bool operator!=(const ProfilePhoto &lhs, const ProfilePhoto &rhs) { return !(lhs == rhs); } StringBuilder &operator<<(StringBuilder &string_builder, const ProfilePhoto &profile_photo) { return string_builder << ""; } DialogPhoto get_dialog_photo(FileManager *file_manager, DialogId dialog_id, int64 dialog_access_hash, tl_object_ptr &&chat_photo_ptr) { int32 chat_photo_id = chat_photo_ptr == nullptr ? telegram_api::chatPhotoEmpty::ID : chat_photo_ptr->get_id(); DialogPhoto result; switch (chat_photo_id) { case telegram_api::chatPhotoEmpty::ID: break; case telegram_api::chatPhoto::ID: { auto chat_photo = move_tl_object_as(chat_photo_ptr); auto dc_id = DcId::create(chat_photo->dc_id_); result.has_animation = chat_photo->has_video_; result.minithumbnail = chat_photo->stripped_thumb_.as_slice().str(); result.small_file_id = register_photo(file_manager, PhotoSizeSource::dialog_photo(dialog_id, dialog_access_hash, false), chat_photo->photo_id_, 0, "", DialogId(), 0, dc_id, PhotoFormat::Jpeg); result.big_file_id = register_photo(file_manager, PhotoSizeSource::dialog_photo(dialog_id, dialog_access_hash, true), chat_photo->photo_id_, 0, "", DialogId(), 0, dc_id, PhotoFormat::Jpeg); break; } default: UNREACHABLE(); break; } return result; } tl_object_ptr get_chat_photo_info_object(FileManager *file_manager, const DialogPhoto *dialog_photo) { if (dialog_photo == nullptr || !dialog_photo->small_file_id.is_valid()) { return nullptr; } return td_api::make_object(file_manager->get_file_object(dialog_photo->small_file_id), file_manager->get_file_object(dialog_photo->big_file_id), get_minithumbnail_object(dialog_photo->minithumbnail), dialog_photo->has_animation); } vector dialog_photo_get_file_ids(const DialogPhoto &dialog_photo) { vector result; if (dialog_photo.small_file_id.is_valid()) { result.push_back(dialog_photo.small_file_id); } if (dialog_photo.big_file_id.is_valid()) { result.push_back(dialog_photo.big_file_id); } return result; } DialogPhoto as_fake_dialog_photo(const Photo &photo, DialogId dialog_id) { DialogPhoto result; if (!photo.is_empty()) { for (auto &size : photo.photos) { if (size.type == 'a') { result.small_file_id = size.file_id; } else if (size.type == 'c') { result.big_file_id = size.file_id; } } result.minithumbnail = photo.minithumbnail; result.has_animation = !photo.animations.empty(); if (!result.small_file_id.is_valid() || !result.big_file_id.is_valid()) { LOG(ERROR) << "Failed to convert " << photo << " to chat photo of " << dialog_id; return DialogPhoto(); } } return result; } ProfilePhoto as_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash, const Photo &photo) { ProfilePhoto result; static_cast(result) = as_fake_dialog_photo(photo, DialogId(user_id)); if (!result.small_file_id.is_valid()) { return result; } auto reregister_photo = [&](bool is_big, FileId file_id) { auto file_view = file_manager->get_file_view(file_id); CHECK(file_view.has_remote_location()); auto remote = file_view.remote_location(); CHECK(remote.is_photo()); CHECK(!remote.is_web()); remote.set_source(PhotoSizeSource::dialog_photo(DialogId(user_id), user_access_hash, is_big)); return file_manager->register_remote(std::move(remote), FileLocationSource::FromServer, DialogId(), file_view.size(), file_view.expected_size(), file_view.remote_name()); }; result.id = photo.id.get(); result.small_file_id = reregister_photo(false, result.small_file_id); result.big_file_id = reregister_photo(true, result.big_file_id); return result; } bool operator==(const DialogPhoto &lhs, const DialogPhoto &rhs) { return lhs.small_file_id == rhs.small_file_id && lhs.big_file_id == rhs.big_file_id && lhs.minithumbnail == rhs.minithumbnail && lhs.has_animation == rhs.has_animation; } bool operator!=(const DialogPhoto &lhs, const DialogPhoto &rhs) { return !(lhs == rhs); } StringBuilder &operator<<(StringBuilder &string_builder, const DialogPhoto &dialog_photo) { return string_builder << ""; } PhotoSize get_secret_thumbnail_photo_size(FileManager *file_manager, BufferSlice bytes, DialogId owner_dialog_id, int32 width, int32 height) { if (bytes.empty()) { return PhotoSize(); } PhotoSize res; res.type = 't'; res.dimensions = get_dimensions(width, height, "get_secret_thumbnail_photo_size"); res.size = narrow_cast(bytes.size()); // generate some random remote location to save auto dc_id = DcId::invalid(); auto photo_id = -(Random::secure_int64() & std::numeric_limits::max()); res.file_id = file_manager->register_remote( FullRemoteFileLocation(PhotoSizeSource::thumbnail(FileType::EncryptedThumbnail, 't'), photo_id, 0, dc_id, string()), FileLocationSource::FromServer, owner_dialog_id, res.size, 0, PSTRING() << static_cast(photo_id) << ".jpg"); file_manager->set_content(res.file_id, std::move(bytes)); return res; } Variant get_photo_size(FileManager *file_manager, PhotoSizeSource source, int64 id, int64 access_hash, std::string file_reference, DcId dc_id, DialogId owner_dialog_id, tl_object_ptr &&size_ptr, PhotoFormat format) { CHECK(size_ptr != nullptr); string type; PhotoSize res; BufferSlice content; switch (size_ptr->get_id()) { case telegram_api::photoSizeEmpty::ID: return std::move(res); case telegram_api::photoSize::ID: { auto size = move_tl_object_as(size_ptr); type = std::move(size->type_); res.dimensions = get_dimensions(size->w_, size->h_, "photoSize"); res.size = size->size_; break; } case telegram_api::photoCachedSize::ID: { auto size = move_tl_object_as(size_ptr); type = std::move(size->type_); CHECK(size->bytes_.size() <= static_cast(std::numeric_limits::max())); res.dimensions = get_dimensions(size->w_, size->h_, "photoCachedSize"); res.size = static_cast(size->bytes_.size()); content = std::move(size->bytes_); break; } case telegram_api::photoStrippedSize::ID: { auto size = move_tl_object_as(size_ptr); if (format != PhotoFormat::Jpeg) { if (G()->shared_config().get_option_boolean("disable_minithumbnails")) { LOG(DEBUG) << "Receive unexpected JPEG minithumbnail"; } else { LOG(ERROR) << "Receive unexpected JPEG minithumbnail in photo " << id << " from " << source << " of format " << format; } if (G()->shared_config().get_option_boolean("disable_minithumbnails")) { return std::string(""); } else { return std::move(res); } } if (G()->shared_config().get_option_boolean("disable_minithumbnails")) { return std::string(""); } else { return size->bytes_.as_slice().str(); } } case telegram_api::photoSizeProgressive::ID: { auto size = move_tl_object_as(size_ptr); if (size->sizes_.empty()) { LOG(ERROR) << "Receive photo " << id << " from " << source << " with empty size " << to_string(size); return std::move(res); } std::sort(size->sizes_.begin(), size->sizes_.end()); type = std::move(size->type_); res.dimensions = get_dimensions(size->w_, size->h_, "photoSizeProgressive"); res.size = size->sizes_.back(); size->sizes_.pop_back(); res.progressive_sizes = std::move(size->sizes_); break; } case telegram_api::photoPathSize::ID: { auto size = move_tl_object_as(size_ptr); if (format != PhotoFormat::Tgs && format != PhotoFormat::Webp) { LOG(ERROR) << "Receive unexpected SVG minithumbnail in photo " << id << " from " << source << " of format " << format; return std::move(res); } return size->bytes_.as_slice().str(); } default: UNREACHABLE(); break; } if (type.size() != 1) { LOG(ERROR) << "Wrong photoSize \"" << type << "\" " << res; res.type = 0; } else { res.type = static_cast(type[0]); if (res.type >= 128) { LOG(ERROR) << "Wrong photoSize \"" << type << "\" " << res; res.type = 0; } } if (source.get_type() == PhotoSizeSource::Type::Thumbnail) { source.thumbnail().thumbnail_type = res.type; } res.file_id = register_photo(file_manager, source, id, access_hash, std::move(file_reference), owner_dialog_id, res.size, dc_id, format); if (!content.empty()) { file_manager->set_content(res.file_id, std::move(content)); } return std::move(res); } AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource source, int64 id, int64 access_hash, std::string file_reference, DcId dc_id, DialogId owner_dialog_id, tl_object_ptr &&size) { CHECK(size != nullptr); AnimationSize res; if (size->type_ != "v" && size->type_ != "u") { LOG(ERROR) << "Wrong videoSize \"" << size->type_ << "\" in " << to_string(size); } res.type = static_cast(size->type_[0]); if (res.type >= 128) { LOG(ERROR) << "Wrong videoSize \"" << res.type << "\" " << res; res.type = 0; } res.dimensions = get_dimensions(size->w_, size->h_, "get_animation_size"); res.size = size->size_; if ((size->flags_ & telegram_api::videoSize::VIDEO_START_TS_MASK) != 0) { res.main_frame_timestamp = size->video_start_ts_; } if (source.get_type() == PhotoSizeSource::Type::Thumbnail) { source.thumbnail().thumbnail_type = res.type; } res.file_id = register_photo(file_manager, source, id, access_hash, std::move(file_reference), owner_dialog_id, res.size, dc_id, PhotoFormat::Mpeg4); return res; } PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_type, DialogId owner_dialog_id, tl_object_ptr web_document_ptr) { if (web_document_ptr == nullptr) { return {}; } FileId file_id; vector> attributes; int32 size = 0; string mime_type; switch (web_document_ptr->get_id()) { case telegram_api::webDocument::ID: { auto web_document = move_tl_object_as(web_document_ptr); auto r_http_url = parse_url(web_document->url_); if (r_http_url.is_error()) { LOG(ERROR) << "Can't parse URL " << web_document->url_; return {}; } auto http_url = r_http_url.move_as_ok(); auto url = http_url.get_url(); file_id = file_manager->register_remote(FullRemoteFileLocation(file_type, url, web_document->access_hash_), FileLocationSource::FromServer, owner_dialog_id, 0, web_document->size_, get_url_query_file_name(http_url.query_)); size = web_document->size_; mime_type = std::move(web_document->mime_type_); attributes = std::move(web_document->attributes_); break; } case telegram_api::webDocumentNoProxy::ID: { auto web_document = move_tl_object_as(web_document_ptr); if (web_document->url_.find('.') == string::npos) { LOG(ERROR) << "Receive invalid URL " << web_document->url_; return {}; } auto r_file_id = file_manager->from_persistent_id(web_document->url_, file_type); if (r_file_id.is_error()) { LOG(ERROR) << "Can't register URL: " << r_file_id.error(); return {}; } file_id = r_file_id.move_as_ok(); size = web_document->size_; mime_type = std::move(web_document->mime_type_); attributes = std::move(web_document->attributes_); break; } default: UNREACHABLE(); } CHECK(file_id.is_valid()); bool is_animation = mime_type == "video/mp4"; bool is_gif = mime_type == "image/gif"; Dimensions dimensions; for (auto &attribute : attributes) { switch (attribute->get_id()) { case telegram_api::documentAttributeImageSize::ID: { auto image_size = move_tl_object_as(attribute); dimensions = get_dimensions(image_size->w_, image_size->h_, "web documentAttributeImageSize"); break; } case telegram_api::documentAttributeAnimated::ID: case telegram_api::documentAttributeHasStickers::ID: case telegram_api::documentAttributeSticker::ID: case telegram_api::documentAttributeVideo::ID: case telegram_api::documentAttributeAudio::ID: LOG(ERROR) << "Unexpected web document attribute " << to_string(attribute); break; case telegram_api::documentAttributeFilename::ID: break; default: UNREACHABLE(); } } PhotoSize s; s.type = is_animation ? 'v' : (is_gif ? 'g' : (file_type == FileType::Thumbnail ? 't' : 'n')); s.dimensions = dimensions; s.size = size; s.file_id = file_id; return s; } td_api::object_ptr get_thumbnail_object(FileManager *file_manager, const PhotoSize &photo_size, PhotoFormat format) { if (!photo_size.file_id.is_valid()) { return nullptr; } if (format == PhotoFormat::Jpeg && photo_size.type == 'g') { format = PhotoFormat::Gif; } return td_api::make_object(get_thumbnail_format_object(format), photo_size.dimensions.width, photo_size.dimensions.height, file_manager->get_file_object(photo_size.file_id)); } static tl_object_ptr get_photo_size_object(FileManager *file_manager, const PhotoSize *photo_size) { if (photo_size == nullptr || !photo_size->file_id.is_valid()) { return nullptr; } return td_api::make_object( photo_size->type ? std::string(1, static_cast(photo_size->type)) : std::string(), // TODO replace string type with integer type file_manager->get_file_object(photo_size->file_id), photo_size->dimensions.width, photo_size->dimensions.height, vector(photo_size->progressive_sizes)); } static vector> get_photo_sizes_object(FileManager *file_manager, const vector &photo_sizes) { auto sizes = transform(photo_sizes, [file_manager](const PhotoSize &photo_size) { return get_photo_size_object(file_manager, &photo_size); }); std::stable_sort(sizes.begin(), sizes.end(), [](const auto &lhs, const auto &rhs) { if (lhs->photo_->expected_size_ != rhs->photo_->expected_size_) { return lhs->photo_->expected_size_ < rhs->photo_->expected_size_; } return static_cast(lhs->width_) * static_cast(lhs->height_) < static_cast(rhs->width_) * static_cast(rhs->height_); }); td::remove_if(sizes, [](const auto &size) { return !size->photo_->local_->can_be_downloaded_ && !size->photo_->local_->is_downloading_completed_; }); return sizes; } bool operator==(const PhotoSize &lhs, const PhotoSize &rhs) { return lhs.type == rhs.type && lhs.dimensions == rhs.dimensions && lhs.size == rhs.size && lhs.file_id == rhs.file_id && lhs.progressive_sizes == rhs.progressive_sizes; } bool operator!=(const PhotoSize &lhs, const PhotoSize &rhs) { return !(lhs == rhs); } bool operator<(const PhotoSize &lhs, const PhotoSize &rhs) { if (lhs.size != rhs.size) { return lhs.size < rhs.size; } auto lhs_pixels = get_pixel_count(lhs.dimensions); auto rhs_pixels = get_pixel_count(rhs.dimensions); if (lhs_pixels != rhs_pixels) { return lhs_pixels < rhs_pixels; } int32 lhs_type = lhs.type == 't' ? -1 : lhs.type; int32 rhs_type = rhs.type == 't' ? -1 : rhs.type; if (lhs_type != rhs_type) { return lhs_type < rhs_type; } if (lhs.file_id != rhs.file_id) { return lhs.file_id.get() < rhs.file_id.get(); } return lhs.dimensions.width < rhs.dimensions.width; } StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSize &photo_size) { return string_builder << "{type = " << photo_size.type << ", dimensions = " << photo_size.dimensions << ", size = " << photo_size.size << ", file_id = " << photo_size.file_id << ", progressive_sizes = " << photo_size.progressive_sizes << "}"; } static tl_object_ptr get_animated_chat_photo_object(FileManager *file_manager, const AnimationSize *animation_size) { if (animation_size == nullptr || !animation_size->file_id.is_valid()) { return nullptr; } return td_api::make_object(animation_size->dimensions.width, file_manager->get_file_object(animation_size->file_id), animation_size->main_frame_timestamp); } bool operator==(const AnimationSize &lhs, const AnimationSize &rhs) { return static_cast(lhs) == static_cast(rhs) && fabs(lhs.main_frame_timestamp - rhs.main_frame_timestamp) < 1e-3; } bool operator!=(const AnimationSize &lhs, const AnimationSize &rhs) { return !(lhs == rhs); } StringBuilder &operator<<(StringBuilder &string_builder, const AnimationSize &animation_size) { return string_builder << static_cast(animation_size) << " from " << animation_size.main_frame_timestamp; } Photo get_encrypted_file_photo(FileManager *file_manager, unique_ptr &&file, tl_object_ptr &&photo, DialogId owner_dialog_id) { FileId file_id = file_manager->register_remote( FullRemoteFileLocation(FileType::Encrypted, file->id_, file->access_hash_, DcId::create(file->dc_id_), string()), FileLocationSource::FromServer, owner_dialog_id, photo->size_, 0, PSTRING() << static_cast(file->id_) << ".jpg"); file_manager->set_encryption_key(file_id, FileEncryptionKey{photo->key_.as_slice(), photo->iv_.as_slice()}); Photo res; res.id = 0; res.date = 0; if (!photo->thumb_.empty()) { res.photos.push_back(get_secret_thumbnail_photo_size(file_manager, std::move(photo->thumb_), owner_dialog_id, photo->thumb_w_, photo->thumb_h_)); } PhotoSize s; s.type = 'i'; s.dimensions = get_dimensions(photo->w_, photo->h_, "get_encrypted_file_photo"); s.size = photo->size_; s.file_id = file_id; res.photos.push_back(s); return res; } Photo get_photo(FileManager *file_manager, tl_object_ptr &&photo, DialogId owner_dialog_id) { if (photo == nullptr || photo->get_id() == telegram_api::photoEmpty::ID) { return Photo(); } CHECK(photo->get_id() == telegram_api::photo::ID); return get_photo(file_manager, move_tl_object_as(photo), owner_dialog_id); } Photo get_photo(FileManager *file_manager, tl_object_ptr &&photo, DialogId owner_dialog_id) { CHECK(photo != nullptr); Photo res; res.id = photo->id_; res.date = photo->date_; res.has_stickers = photo->has_stickers_; if (res.is_empty()) { LOG(ERROR) << "Receive photo with identifier " << res.id.get(); res.id = -3; } DcId dc_id = DcId::create(photo->dc_id_); for (auto &size_ptr : photo->sizes_) { auto photo_size = get_photo_size(file_manager, PhotoSizeSource::thumbnail(FileType::Photo, 0), photo->id_, photo->access_hash_, photo->file_reference_.as_slice().str(), dc_id, owner_dialog_id, std::move(size_ptr), PhotoFormat::Jpeg); if (photo_size.get_offset() == 0) { PhotoSize &size = photo_size.get<0>(); if (size.type == 0 || size.type == 't' || size.type == 'i' || size.type == 'u' || size.type == 'v') { LOG(ERROR) << "Skip unallowed photo size " << size; continue; } res.photos.push_back(std::move(size)); } else { if (G()->shared_config().get_option_boolean("disable_minithumbnails")) { res.minithumbnail = ""; } else { res.minithumbnail = std::move(photo_size.get<1>()); } } } for (auto &size_ptr : photo->video_sizes_) { auto animation = get_animation_size(file_manager, PhotoSizeSource::thumbnail(FileType::Photo, 0), photo->id_, photo->access_hash_, photo->file_reference_.as_slice().str(), dc_id, owner_dialog_id, std::move(size_ptr)); if (animation.type != 0 && animation.dimensions.width == animation.dimensions.height) { res.animations.push_back(std::move(animation)); } } return res; } Photo get_web_document_photo(FileManager *file_manager, tl_object_ptr web_document, DialogId owner_dialog_id) { PhotoSize s = get_web_document_photo_size(file_manager, FileType::Photo, owner_dialog_id, std::move(web_document)); Photo photo; if (s.file_id.is_valid() && s.type != 'v' && s.type != 'g') { photo.id = 0; photo.photos.push_back(s); } return photo; } tl_object_ptr get_photo_object(FileManager *file_manager, const Photo &photo) { if (photo.is_empty()) { return nullptr; } return td_api::make_object(photo.has_stickers, get_minithumbnail_object(photo.minithumbnail), get_photo_sizes_object(file_manager, photo.photos)); } tl_object_ptr get_chat_photo_object(FileManager *file_manager, const Photo &photo) { if (photo.is_empty()) { return nullptr; } const AnimationSize *animation = photo.animations.empty() ? nullptr : &photo.animations.back(); return td_api::make_object( photo.id.get(), photo.date, get_minithumbnail_object(photo.minithumbnail), get_photo_sizes_object(file_manager, photo.photos), get_animated_chat_photo_object(file_manager, animation)); } void photo_delete_thumbnail(Photo &photo) { for (size_t i = 0; i < photo.photos.size(); i++) { if (photo.photos[i].type == 't') { photo.photos.erase(photo.photos.begin() + i); return; } } } bool photo_has_input_media(FileManager *file_manager, const Photo &photo, bool is_secret, bool is_bot) { if (photo.photos.empty() || photo.photos.back().type != 'i') { LOG(ERROR) << "Wrong photo: " << photo; return false; } auto file_id = photo.photos.back().file_id; auto file_view = file_manager->get_file_view(file_id); if (is_secret) { if (!file_view.is_encrypted_secret() || !file_view.has_remote_location()) { return false; } for (const auto &size : photo.photos) { if (size.type == 't' && size.file_id.is_valid()) { return false; } } return true; } else { if (file_view.is_encrypted()) { return false; } if (is_bot && file_view.has_remote_location()) { return true; } return /* file_view.has_remote_location() || */ file_view.has_url(); } } tl_object_ptr photo_get_input_media(FileManager *file_manager, const Photo &photo, tl_object_ptr input_file, int32 ttl) { if (!photo.photos.empty()) { auto file_id = photo.photos.back().file_id; auto file_view = file_manager->get_file_view(file_id); if (file_view.is_encrypted()) { return nullptr; } if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) { int32 flags = 0; if (ttl != 0) { flags |= telegram_api::inputMediaPhoto::TTL_SECONDS_MASK; } return make_tl_object(flags, file_view.main_remote_location().as_input_photo(), ttl); } if (file_view.has_url()) { int32 flags = 0; if (ttl != 0) { flags |= telegram_api::inputMediaPhotoExternal::TTL_SECONDS_MASK; } LOG(INFO) << "Create inputMediaPhotoExternal with a URL " << file_view.url() << " and TTL " << ttl; return make_tl_object(flags, file_view.url(), ttl); } if (input_file == nullptr) { CHECK(!file_view.has_remote_location()); } } if (input_file != nullptr) { int32 flags = 0; vector> added_stickers; if (photo.has_stickers) { flags |= telegram_api::inputMediaUploadedPhoto::STICKERS_MASK; added_stickers = file_manager->get_input_documents(photo.sticker_file_ids); } if (ttl != 0) { flags |= telegram_api::inputMediaUploadedPhoto::TTL_SECONDS_MASK; } return make_tl_object(flags, std::move(input_file), std::move(added_stickers), ttl); } return nullptr; } SecretInputMedia photo_get_secret_input_media(FileManager *file_manager, const Photo &photo, tl_object_ptr input_file, const string &caption, BufferSlice thumbnail) { FileId file_id; int32 width = 0; int32 height = 0; FileId thumbnail_file_id; int32 thumbnail_width = 0; int32 thumbnail_height = 0; for (const auto &size : photo.photos) { if (size.type == 'i') { file_id = size.file_id; width = size.dimensions.width; height = size.dimensions.height; } if (size.type == 't') { thumbnail_file_id = size.file_id; thumbnail_width = size.dimensions.width; thumbnail_height = size.dimensions.height; } } if (file_id.empty()) { LOG(ERROR) << "NO SIZE"; return {}; } auto file_view = file_manager->get_file_view(file_id); auto &encryption_key = file_view.encryption_key(); if (!file_view.is_encrypted_secret() || encryption_key.empty()) { return {}; } if (file_view.has_remote_location()) { LOG(INFO) << "Photo has remote location"; input_file = file_view.main_remote_location().as_input_encrypted_file(); } if (input_file == nullptr) { return {}; } if (thumbnail_file_id.is_valid() && thumbnail.empty()) { return {}; } return SecretInputMedia{ std::move(input_file), make_tl_object( std::move(thumbnail), thumbnail_width, thumbnail_height, width, height, narrow_cast(file_view.size()), BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()), caption)}; } vector photo_get_file_ids(const Photo &photo) { auto result = transform(photo.photos, [](auto &size) { return size.file_id; }); if (!photo.animations.empty()) { // photo file IDs must be first append(result, transform(photo.animations, [](auto &size) { return size.file_id; })); } return result; } bool operator==(const Photo &lhs, const Photo &rhs) { return lhs.id.get() == rhs.id.get() && lhs.photos == rhs.photos && lhs.animations == rhs.animations; } bool operator!=(const Photo &lhs, const Photo &rhs) { return !(lhs == rhs); } StringBuilder &operator<<(StringBuilder &string_builder, const Photo &photo) { string_builder << "[ID = " << photo.id.get() << ", photos = " << format::as_array(photo.photos); if (!photo.animations.empty()) { string_builder << ", animations = " << format::as_array(photo.animations); } return string_builder << ']'; } tl_object_ptr convert_photo_to_profile_photo( const tl_object_ptr &photo) { if (photo == nullptr) { return nullptr; } bool have_photo_small = false; bool have_photo_big = false; for (auto &size_ptr : photo->sizes_) { switch (size_ptr->get_id()) { case telegram_api::photoSizeEmpty::ID: break; case telegram_api::photoSize::ID: { auto size = static_cast(size_ptr.get()); if (size->type_ == "a") { have_photo_small = true; } else if (size->type_ == "c") { have_photo_big = true; } break; } case telegram_api::photoCachedSize::ID: { auto size = static_cast(size_ptr.get()); if (size->type_ == "a") { have_photo_small = true; } else if (size->type_ == "c") { have_photo_big = true; } break; } case telegram_api::photoStrippedSize::ID: break; case telegram_api::photoSizeProgressive::ID: { auto size = static_cast(size_ptr.get()); if (size->type_ == "a") { have_photo_small = true; } else if (size->type_ == "c") { have_photo_big = true; } break; } default: UNREACHABLE(); break; } } if (!have_photo_small || !have_photo_big) { return nullptr; } bool has_video = !photo->video_sizes_.empty(); return make_tl_object(0, has_video, photo->id_, BufferSlice(), photo->dc_id_); } } // namespace td