diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d38880..e16bbd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if (POLICY CMP0065) cmake_policy(SET CMP0065 NEW) endif() -project(TelegramBotApi VERSION 7.5 LANGUAGES CXX) +project(TelegramBotApi VERSION 7.6 LANGUAGES CXX) if (POLICY CMP0069) option(TELEGRAM_BOT_API_ENABLE_LTO "Use \"ON\" to enable Link Time Optimization.") diff --git a/td b/td index 8f19c75..35cfcf5 160000 --- a/td +++ b/td @@ -1 +1 @@ -Subproject commit 8f19c751dc296cedb9a921badb7a02a8c0cb1aeb +Subproject commit 35cfcf5d15981b99e8f31a2195641f035dd516c3 diff --git a/telegram-bot-api/Client.cpp b/telegram-bot-api/Client.cpp index 4e4ffaa..c4ab3a4 100644 --- a/telegram-bot-api/Client.cpp +++ b/telegram-bot-api/Client.cpp @@ -223,6 +223,7 @@ bool Client::init_methods() { methods_.emplace("sendvideo", &Client::process_send_video_query); methods_.emplace("sendvideonote", &Client::process_send_video_note_query); methods_.emplace("sendvoice", &Client::process_send_voice_query); + methods_.emplace("sendpaidmedia", &Client::process_send_paid_media_query); methods_.emplace("sendgame", &Client::process_send_game_query); methods_.emplace("sendinvoice", &Client::process_send_invoice_query); methods_.emplace("sendlocation", &Client::process_send_location_query); @@ -1092,6 +1093,9 @@ class Client::JsonChat final : public td::Jsonable { if (supergroup_info->location != nullptr) { object("location", JsonChatLocation(supergroup_info->location.get())); } + if (supergroup_info->has_paid_media_allowed && !supergroup_info->is_supergroup) { + object("can_send_paid_media", td::JsonTrue()); + } } photo = supergroup_info->photo.get(); break; @@ -1524,6 +1528,70 @@ class Client::JsonVoiceNote final : public td::Jsonable { const Client *client_; }; +class Client::JsonPaidMedia final : public td::Jsonable { + public: + JsonPaidMedia(const td_api::PaidMedia *paid_media, const Client *client) : paid_media_(paid_media), client_(client) { + } + void store(td::JsonValueScope *scope) const { + auto object = scope->enter_object(); + switch (paid_media_->get_id()) { + case td_api::paidMediaPreview::ID: { + auto media = static_cast(paid_media_); + object("type", "preview"); + if (media->width_) { + object("width", media->width_); + } + if (media->height_) { + object("height", media->height_); + } + if (media->duration_) { + object("duration", media->duration_); + } + break; + } + case td_api::paidMediaPhoto::ID: { + auto media = static_cast(paid_media_); + object("type", "photo"); + object("photo", JsonPhoto(media->photo_.get(), client_)); + break; + } + case td_api::paidMediaVideo::ID: { + auto media = static_cast(paid_media_); + object("type", "video"); + object("video", JsonVideo(media->video_.get(), client_)); + break; + } + case td_api::paidMediaUnsupported::ID: + object("type", "other"); + break; + default: + UNREACHABLE(); + } + } + + private: + const td_api::PaidMedia *paid_media_; + const Client *client_; +}; + +class Client::JsonPaidMediaInfo final : public td::Jsonable { + public: + JsonPaidMediaInfo(const td_api::messagePaidMedia *paid_media, const Client *client) + : paid_media_(paid_media), client_(client) { + } + void store(td::JsonValueScope *scope) const { + auto object = scope->enter_object(); + object("star_count", paid_media_->star_count_); + object("paid_media", td::json_array(paid_media_->media_, [client = client_](auto &media) { + return JsonPaidMedia(media.get(), client); + })); + } + + private: + const td_api::messagePaidMedia *paid_media_; + const Client *client_; +}; + class Client::JsonVenue final : public td::Jsonable { public: explicit JsonVenue(const td_api::venue *venue) : venue_(venue) { @@ -2591,6 +2659,11 @@ class Client::JsonExternalReplyInfo final : public td::Jsonable { object("document", JsonDocument(content->document_.get(), client_)); break; } + case td_api::messagePaidMedia::ID: { + auto content = static_cast(reply_->content_.get()); + object("paid_media", JsonPaidMediaInfo(content, client_)); + break; + } case td_api::messagePhoto::ID: { auto content = static_cast(reply_->content_.get()); CHECK(content->photo_ != nullptr); @@ -2840,6 +2913,12 @@ void Client::JsonMessage::store(td::JsonValueScope *scope) const { add_caption(object, content->caption_, false); break; } + case td_api::messagePaidMedia::ID: { + auto content = static_cast(message_->content.get()); + object("paid_media", JsonPaidMediaInfo(content, client_)); + add_caption(object, content->caption_, content->show_caption_above_media_); + break; + } case td_api::messagePhoto::ID: { auto content = static_cast(message_->content.get()); CHECK(content->photo_ != nullptr); @@ -3975,12 +4054,23 @@ class Client::JsonStarTransactionPartner final : public td::Jsonable { } break; } - case td_api::starTransactionPartnerUser::ID: { - auto source_user = static_cast(source_); + case td_api::starTransactionPartnerBot::ID: { + auto source_user = static_cast(source_); object("type", "user"); - object("user", JsonUser(source_user->user_id_, client_)); + object("user", JsonUser(source_user->bot_user_id_, client_)); + if (!source_user->invoice_payload_.empty()) { + if (!td::check_utf8(source_user->invoice_payload_)) { + LOG(WARNING) << "Receive non-UTF-8 invoice payload"; + object("invoice_payload", td::JsonRawString(source_user->invoice_payload_)); + } else { + object("invoice_payload", source_user->invoice_payload_); + } + } break; } + case td_api::starTransactionPartnerTelegramAds::ID: + object("type", "telegram_ads"); + break; case td_api::starTransactionPartnerTelegram::ID: case td_api::starTransactionPartnerAppStore::ID: case td_api::starTransactionPartnerGooglePlay::ID: @@ -4866,7 +4956,7 @@ class Client::TdOnGetChatMemberCallback final : public TdQueryCallback { void on_result(object_ptr result) final { if (result->get_id() == td_api::error::ID) { - return fail_query_with_error(std::move(query_), move_object_as(result), "user not found"); + return fail_query_with_error(std::move(query_), move_object_as(result)); } CHECK(result->get_id() == td_api::chatMember::ID); @@ -5198,7 +5288,7 @@ class Client::TdOnGetChatPinnedMessageToUnpinCallback final : public TdQueryCall if (error->code_ == 429) { return fail_query_with_error(std::move(query_), std::move(error)); } else { - return fail_query_with_error(std::move(query_), make_object(400, "Message to unpin not found")); + return fail_query_with_error(std::move(query_), 400, "Message to unpin not found"); } } @@ -5253,8 +5343,7 @@ class Client::TdOnGetMyDefaultAdministratorRightsCallback final : public TdQuery auto full_info = move_object_as(result); if (full_info->bot_info_ == nullptr) { LOG(ERROR) << "Have no bot info for self"; - return fail_query_with_error(std::move(query_), - make_object(500, "Requested data is inaccessible")); + return fail_query_with_error(std::move(query_), 500, "Requested data is inaccessible"); } auto bot_info = std::move(full_info->bot_info_); const auto *rights = for_channels_ ? bot_info->default_channel_administrator_rights_.get() @@ -6941,6 +7030,7 @@ void Client::on_update(object_ptr result) { supergroup_info->location = std::move(full_info->location_); supergroup_info->has_hidden_members = full_info->has_hidden_members_; supergroup_info->has_aggressive_anti_spam_enabled = full_info->has_aggressive_anti_spam_enabled_; + supergroup_info->has_paid_media_allowed = full_info->has_paid_media_allowed_; break; } case td_api::updateOption::ID: { @@ -7917,7 +8007,7 @@ td::Result> Client::get_input_me need_email_address, need_shipping_address, send_phone_number_to_provider, send_email_address_to_provider, is_flexible), title, description, photo_url, photo_size, photo_width, photo_height, payload, provider_token, provider_data, - td::string(), nullptr); + td::string(), nullptr, nullptr); } if (is_input_message_content_required) { @@ -9320,10 +9410,10 @@ td::Result> Client::get_input_me td::vector(), duration, width, height, supports_streaming, std::move(caption), show_caption_above_media, nullptr, has_spoiler); } - if (for_album && type == "animation") { - return td::Status::Error(PSLICE() << "type \"" << type << "\" can't be used in sendMediaGroup"); - } if (type == "animation") { + if (for_album) { + return td::Status::Error(PSLICE() << "type \"" << type << "\" can't be used in sendMediaGroup"); + } TRY_RESULT(width, object.get_optional_int_field("width")); TRY_RESULT(height, object.get_optional_int_field("height")); TRY_RESULT(duration, object.get_optional_int_field("duration")); @@ -9401,6 +9491,98 @@ td::Result>> Client:: return std::move(contents); } +td::Result> Client::get_input_paid_media(const Query *query, + td::JsonValue &&input_media) const { + if (input_media.type() != td::JsonValue::Type::Object) { + return td::Status::Error("expected an Object"); + } + + auto &object = input_media.get_object(); + TRY_RESULT(media, object.get_optional_string_field("media")); + + auto input_file = get_input_file(query, td::Slice(), media, false); + if (input_file == nullptr) { + return td::Status::Error("media not found"); + } + + object_ptr input_thumbnail; + TRY_RESULT(thumbnail, object.get_optional_string_field("thumbnail")); + auto thumbnail_input_file = get_input_file(query, "thumbnail", thumbnail, true); + if (thumbnail_input_file != nullptr) { + input_thumbnail = make_object(std::move(thumbnail_input_file), 0, 0); + } + + TRY_RESULT(width, object.get_optional_int_field("width")); + TRY_RESULT(height, object.get_optional_int_field("height")); + width = td::clamp(width, 0, MAX_LENGTH); + height = td::clamp(height, 0, MAX_LENGTH); + + object_ptr media_type; + TRY_RESULT(type, object.get_required_string_field("type")); + if (type == "photo") { + media_type = make_object(); + } else if (type == "video") { + TRY_RESULT(duration, object.get_optional_int_field("duration")); + TRY_RESULT(supports_streaming, object.get_optional_bool_field("supports_streaming")); + duration = td::clamp(duration, 0, MAX_DURATION); + media_type = make_object(duration, supports_streaming); + } else { + return td::Status::Error(PSLICE() << "type \"" << type << "\" is unsupported"); + } + + return make_object(std::move(media_type), std::move(input_file), std::move(input_thumbnail), + td::vector(), width, height); +} + +td::Result> Client::get_input_paid_media(const Query *query, + td::Slice field_name) const { + TRY_RESULT(media, get_required_string_arg(query, field_name)); + + LOG(INFO) << "Parsing JSON object: " << media; + auto r_value = json_decode(media); + if (r_value.is_error()) { + LOG(INFO) << "Can't parse JSON object: " << r_value.error(); + return td::Status::Error(400, "Can't parse input paid media JSON object"); + } + + auto r_input_paid_media = get_input_paid_media(query, r_value.move_as_ok()); + if (r_input_paid_media.is_error()) { + return td::Status::Error(400, PSLICE() << "Can't parse InputPaidMedia: " << r_input_paid_media.error().message()); + } + return r_input_paid_media.move_as_ok(); +} + +td::Result>> Client::get_paid_media(const Query *query, + td::Slice field_name) const { + TRY_RESULT(media, get_required_string_arg(query, field_name)); + + LOG(INFO) << "Parsing JSON object: " << media; + auto r_value = json_decode(media); + if (r_value.is_error()) { + LOG(INFO) << "Can't parse JSON object: " << r_value.error(); + return td::Status::Error(400, "Can't parse paid media JSON object"); + } + + return get_paid_media(query, r_value.move_as_ok()); +} + +td::Result>> Client::get_paid_media(const Query *query, + td::JsonValue &&value) const { + if (value.type() != td::JsonValue::Type::Array) { + return td::Status::Error(400, "Expected an Array of InputPaidMedia"); + } + + td::vector> paid_media; + for (auto &input_media : value.get_array()) { + auto r_paid_media = get_input_paid_media(query, std::move(input_media)); + if (r_paid_media.is_error()) { + return td::Status::Error(400, PSLICE() << "Can't parse InputPaidMedia: " << r_paid_media.error().message()); + } + paid_media.push_back(r_paid_media.move_as_ok()); + } + return std::move(paid_media); +} + td::Result> Client::get_input_message_invoice( const Query *query) const { TRY_RESULT(title, get_required_string_arg(query, "title")); @@ -9458,10 +9640,15 @@ td::Result> Client::get_input_me auto send_email_address_to_provider = to_bool(query->arg("send_email_to_provider")); auto is_flexible = to_bool(query->arg("is_flexible")); - object_ptr extended_media; - if (!query->arg("extended_media").empty()) { - TRY_RESULT_ASSIGN(extended_media, get_input_media(query, "extended_media")); + object_ptr paid_media; + if (!query->arg("paid_media").empty()) { + TRY_RESULT_ASSIGN(paid_media, get_input_paid_media(query, "paid_media")); + } else if (!query->arg("extended_media").empty()) { + TRY_RESULT_ASSIGN(paid_media, get_input_paid_media(query, "extended_media")); } + TRY_RESULT(paid_media_caption, get_formatted_text(query->arg("paid_media_caption").str(), + query->arg("paid_media_caption_parse_mode").str(), + get_input_entities(query, "paid_media_caption_entities"))); return make_object( make_object(currency.str(), std::move(prices), max_tip_amount, std::move(suggested_tip_amounts), @@ -9469,7 +9656,8 @@ td::Result> Client::get_input_me need_shipping_address, send_phone_number_to_provider, send_email_address_to_provider, is_flexible), title.str(), description.str(), photo_url.str(), photo_size, photo_width, photo_height, payload.str(), - provider_token.str(), provider_data.str(), start_parameter.str(), std::move(extended_media)); + provider_token.str(), provider_data.str(), start_parameter.str(), std::move(paid_media), + std::move(paid_media_caption)); } td::Result>> Client::get_poll_options(const Query *query) { @@ -10100,6 +10288,17 @@ td::Status Client::process_send_voice_query(PromisedQueryPtr &query) { return td::Status::OK(); } +td::Status Client::process_send_paid_media_query(PromisedQueryPtr &query) { + int32 star_count = get_integer_arg(query.get(), "star_count", 0, 0, 1000000000); + TRY_RESULT(paid_media, get_paid_media(query.get(), "media")); + TRY_RESULT(caption, get_caption(query.get())); + auto show_caption_above_media = to_bool(query->arg("show_caption_above_media")); + do_send_message(make_object(star_count, std::move(paid_media), std::move(caption), + show_caption_above_media), + std::move(query)); + return td::Status::OK(); +} + td::Status Client::process_send_game_query(PromisedQueryPtr &query) { TRY_RESULT(game_short_name, get_required_string_arg(query.get(), "game_short_name")); do_send_message(make_object(my_id_, game_short_name.str()), std::move(query)); diff --git a/telegram-bot-api/Client.h b/telegram-bot-api/Client.h index 06a051d..a0b82cb 100644 --- a/telegram-bot-api/Client.h +++ b/telegram-bot-api/Client.h @@ -121,6 +121,8 @@ class Client final : public WebhookActor::Callback { class JsonVideo; class JsonVideoNote; class JsonVoiceNote; + class JsonPaidMedia; + class JsonPaidMediaInfo; class JsonContact; class JsonDice; class JsonGame; @@ -568,6 +570,17 @@ class Client final : public WebhookActor::Callback { td::Result>> get_input_message_contents( const Query *query, td::JsonValue &&value) const; + td::Result> get_input_paid_media(const Query *query, + td::JsonValue &&input_media) const; + + td::Result> get_input_paid_media(const Query *query, td::Slice field_name) const; + + td::Result>> get_paid_media(const Query *query, + td::Slice field_name) const; + + td::Result>> get_paid_media(const Query *query, + td::JsonValue &&value) const; + td::Result> get_input_message_invoice(const Query *query) const; static object_ptr get_message_send_options(bool disable_notification, @@ -635,6 +648,7 @@ class Client final : public WebhookActor::Callback { td::Status process_send_video_query(PromisedQueryPtr &query); td::Status process_send_video_note_query(PromisedQueryPtr &query); td::Status process_send_voice_query(PromisedQueryPtr &query); + td::Status process_send_paid_media_query(PromisedQueryPtr &query); td::Status process_send_game_query(PromisedQueryPtr &query); td::Status process_send_invoice_query(PromisedQueryPtr &query); td::Status process_send_location_query(PromisedQueryPtr &query); @@ -874,6 +888,7 @@ class Client final : public WebhookActor::Callback { bool join_by_request = false; bool has_hidden_members = false; bool has_aggressive_anti_spam_enabled = false; + bool has_paid_media_allowed = false; }; static void add_supergroup(SupergroupInfo *supergroup_info, object_ptr &&supergroup); SupergroupInfo *add_supergroup_info(int64 supergroup_id); diff --git a/telegram-bot-api/telegram-bot-api.cpp b/telegram-bot-api/telegram-bot-api.cpp index a5af3f6..02fdb54 100644 --- a/telegram-bot-api/telegram-bot-api.cpp +++ b/telegram-bot-api/telegram-bot-api.cpp @@ -165,7 +165,7 @@ int main(int argc, char *argv[]) { auto start_time = td::Time::now(); auto shared_data = std::make_shared(); auto parameters = std::make_unique(); - parameters->version_ = "7.5"; + parameters->version_ = "7.6"; parameters->shared_data_ = shared_data; parameters->start_time_ = start_time; auto net_query_stats = td::create_net_query_stats();