// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // 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/Premium.h" #include "td/telegram/AccessRights.h" #include "td/telegram/AnimationsManager.h" #include "td/telegram/Application.h" #include "td/telegram/ChannelId.h" #include "td/telegram/ChatManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" #include "td/telegram/GiveawayParameters.h" #include "td/telegram/Global.h" #include "td/telegram/InputInvoice.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/Photo.h" #include "td/telegram/PremiumGiftOption.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/SuggestedAction.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/UserId.h" #include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/FlatHashSet.h" #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" namespace td { static td_api::object_ptr get_premium_feature_object(Slice premium_feature) { if (premium_feature == "double_limits") { return td_api::make_object(); } if (premium_feature == "more_upload") { return td_api::make_object(); } if (premium_feature == "faster_download") { return td_api::make_object(); } if (premium_feature == "voice_to_text") { return td_api::make_object(); } if (premium_feature == "no_ads") { return td_api::make_object(); } if (premium_feature == "unique_reactions" || premium_feature == "infinite_reactions") { return td_api::make_object(); } if (premium_feature == "premium_stickers") { return td_api::make_object(); } if (premium_feature == "animated_emoji") { return td_api::make_object(); } if (premium_feature == "advanced_chat_management") { return td_api::make_object(); } if (premium_feature == "profile_badge") { return td_api::make_object(); } if (premium_feature == "emoji_status") { return td_api::make_object(); } if (premium_feature == "animated_userpics") { return td_api::make_object(); } if (premium_feature == "forum_topic_icon") { return td_api::make_object(); } if (premium_feature == "app_icons") { return td_api::make_object(); } if (premium_feature == "translations") { return td_api::make_object(); } if (premium_feature == "stories") { return td_api::make_object(); } if (premium_feature == "channel_boost") { return td_api::make_object(); } if (premium_feature == "peer_colors") { return td_api::make_object(); } if (premium_feature == "wallpapers") { return td_api::make_object(); } if (premium_feature == "saved_tags") { return td_api::make_object(); } if (premium_feature == "message_privacy") { return td_api::make_object(); } if (premium_feature == "last_seen") { return td_api::make_object(); } if (premium_feature == "business") { return td_api::make_object(); } if (G()->is_test_dc()) { LOG(ERROR) << "Receive unsupported premium feature " << premium_feature; } return nullptr; } static td_api::object_ptr get_business_feature_object(Slice business_feature) { if (business_feature == "business_location") { return td_api::make_object(); } if (business_feature == "business_hours") { return td_api::make_object(); } if (business_feature == "quick_replies") { return td_api::make_object(); } if (business_feature == "greeting_message") { return td_api::make_object(); } if (business_feature == "away_message") { return td_api::make_object(); } if (business_feature == "business_links") { return td_api::make_object(); } if (business_feature == "business_intro") { return td_api::make_object(); } if (business_feature == "business_bots") { return td_api::make_object(); } if (business_feature == "emoji_status") { return td_api::make_object(); } if (business_feature == "folder_tags") { return td_api::make_object(); } if (business_feature == "stories") { return td_api::make_object(); } if (G()->is_test_dc()) { LOG(ERROR) << "Receive unsupported business feature " << business_feature; } return nullptr; } Result> get_boost_input_peer(Td *td, DialogId dialog_id) { if (dialog_id == DialogId()) { return nullptr; } if (!td->dialog_manager_->have_dialog_force(dialog_id, "get_boost_input_peer")) { return Status::Error(400, "Chat to boost not found"); } if (dialog_id.get_type() != DialogType::Channel) { return Status::Error(400, "Can't boost the chat"); } if (!td->chat_manager_->get_channel_status(dialog_id.get_channel_id()).is_administrator()) { return Status::Error(400, "Not enough rights in the chat"); } auto boost_input_peer = td->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(boost_input_peer != nullptr); return std::move(boost_input_peer); } static Result> get_input_store_payment_purpose( Td *td, td_api::object_ptr &purpose) { if (purpose == nullptr) { return Status::Error(400, "Purchase purpose must be non-empty"); } switch (purpose->get_id()) { case td_api::storePaymentPurposePremiumSubscription::ID: { auto p = static_cast(purpose.get()); int32 flags = 0; if (p->is_restore_) { flags |= telegram_api::inputStorePaymentPremiumSubscription::RESTORE_MASK; } if (p->is_upgrade_) { flags |= telegram_api::inputStorePaymentPremiumSubscription::UPGRADE_MASK; } return make_tl_object(flags, false /*ignored*/, false /*ignored*/); } case td_api::storePaymentPurposeGiftedPremium::ID: { auto p = static_cast(purpose.get()); UserId user_id(p->user_id_); TRY_RESULT(input_user, td->user_manager_->get_input_user(user_id)); if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { return Status::Error(400, "Invalid amount of the currency specified"); } if (!clean_input_string(p->currency_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } return make_tl_object(std::move(input_user), p->currency_, p->amount_); } case td_api::storePaymentPurposePremiumGiftCodes::ID: { auto p = static_cast(purpose.get()); vector> input_users; for (auto user_id : p->user_ids_) { TRY_RESULT(input_user, td->user_manager_->get_input_user(UserId(user_id))); input_users.push_back(std::move(input_user)); } if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { return Status::Error(400, "Invalid amount of the currency specified"); } if (!clean_input_string(p->currency_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } DialogId boosted_dialog_id(p->boosted_chat_id_); TRY_RESULT(boost_input_peer, get_boost_input_peer(td, boosted_dialog_id)); int32 flags = 0; if (boost_input_peer != nullptr) { flags |= telegram_api::inputStorePaymentPremiumGiftCode::BOOST_PEER_MASK; } return telegram_api::make_object( flags, std::move(input_users), std::move(boost_input_peer), p->currency_, p->amount_); } case td_api::storePaymentPurposePremiumGiveaway::ID: { auto p = static_cast(purpose.get()); if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { return Status::Error(400, "Invalid amount of the currency specified"); } if (!clean_input_string(p->currency_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } TRY_RESULT(parameters, GiveawayParameters::get_giveaway_parameters(td, p->parameters_.get())); return parameters.get_input_store_payment_premium_giveaway(td, p->currency_, p->amount_); } case td_api::storePaymentPurposeStars::ID: { auto p = static_cast(purpose.get()); if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { return Status::Error(400, "Invalid amount of the currency specified"); } if (!clean_input_string(p->currency_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } return telegram_api::make_object(0, p->star_count_, p->currency_, p->amount_); } default: UNREACHABLE(); return nullptr; } } class GetPremiumPromoQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetPremiumPromoQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::help_getPremiumPromo())); } 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 promo = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetPremiumPromoQuery: " << to_string(promo); td_->user_manager_->on_get_users(std::move(promo->users_), "GetPremiumPromoQuery"); auto state = get_message_text(td_->user_manager_.get(), std::move(promo->status_text_), std::move(promo->status_entities_), true, true, 0, false, "GetPremiumPromoQuery"); if (promo->video_sections_.size() != promo->videos_.size()) { return on_error(Status::Error(500, "Receive wrong number of videos")); } vector> animations; vector> business_animations; FlatHashSet video_sections; for (size_t i = 0; i < promo->video_sections_.size(); i++) { if (promo->video_sections_[i].empty() || !video_sections.insert(promo->video_sections_[i]).second) { LOG(ERROR) << "Receive duplicate Premium feature animation " << promo->video_sections_[i]; continue; } auto video = std::move(promo->videos_[i]); if (video->get_id() != telegram_api::document::ID) { LOG(ERROR) << "Receive " << to_string(video) << " for " << promo->video_sections_[i]; continue; } auto parsed_document = td_->documents_manager_->on_get_document(move_tl_object_as(video), DialogId(), nullptr, Document::Type::Animation); if (parsed_document.type != Document::Type::Animation) { LOG(ERROR) << "Receive " << parsed_document.type << " for " << promo->video_sections_[i]; continue; } auto feature = get_premium_feature_object(promo->video_sections_[i]); if (feature != nullptr) { auto animation_object = td_->animations_manager_->get_animation_object(parsed_document.file_id); animations.push_back(td_api::make_object( std::move(feature), std::move(animation_object))); } else { auto business_feature = get_business_feature_object(promo->video_sections_[i]); if (business_feature != nullptr) { auto animation_object = td_->animations_manager_->get_animation_object(parsed_document.file_id); business_animations.push_back(td_api::make_object( std::move(business_feature), std::move(animation_object))); } else if (G()->is_test_dc()) { LOG(ERROR) << "Receive unsupported feature " << promo->video_sections_[i]; } } } auto period_options = get_premium_gift_options(std::move(promo->period_options_)); promise_.set_value(td_api::make_object( get_formatted_text_object(state, true, 0), get_premium_state_payment_options_object(period_options), std::move(animations), std::move(business_animations))); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetPremiumGiftCodeOptionsQuery final : public Td::ResultHandler { Promise> promise_; DialogId boosted_dialog_id_; public: explicit GetPremiumGiftCodeOptionsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(DialogId boosted_dialog_id) { auto r_boost_input_peer = get_boost_input_peer(td_, boosted_dialog_id); if (r_boost_input_peer.is_error()) { return on_error(r_boost_input_peer.move_as_error()); } auto boost_input_peer = r_boost_input_peer.move_as_ok(); int32 flags = 0; if (boost_input_peer != nullptr) { flags |= telegram_api::payments_getPremiumGiftCodeOptions::BOOST_PEER_MASK; } send_query(G()->net_query_creator().create( telegram_api::payments_getPremiumGiftCodeOptions(flags, std::move(boost_input_peer)))); } 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 results = result_ptr.move_as_ok(); vector> options; for (auto &result : results) { if (result->store_product_.empty()) { result->store_quantity_ = 0; } else if (result->store_quantity_ <= 0) { result->store_quantity_ = 1; } options.push_back(td_api::make_object( result->currency_, result->amount_, result->users_, result->months_, result->store_product_, result->store_quantity_)); } promise_.set_value(td_api::make_object(std::move(options))); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(boosted_dialog_id_, status, "GetPremiumGiftCodeOptionsQuery"); promise_.set_error(std::move(status)); } }; class CheckGiftCodeQuery final : public Td::ResultHandler { Promise> promise_; public: explicit CheckGiftCodeQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &code) { send_query(G()->net_query_creator().create(telegram_api::payments_checkGiftCode(code))); } 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 result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for CheckGiftCodeQuery: " << to_string(result); td_->user_manager_->on_get_users(std::move(result->users_), "CheckGiftCodeQuery"); td_->chat_manager_->on_get_chats(std::move(result->chats_), "CheckGiftCodeQuery"); if (result->date_ <= 0 || result->months_ <= 0 || result->used_date_ < 0) { LOG(ERROR) << "Receive " << to_string(result); return on_error(Status::Error(500, "Receive invalid response")); } DialogId creator_dialog_id; if (result->from_id_ != nullptr) { creator_dialog_id = DialogId(result->from_id_); if (!creator_dialog_id.is_valid() || !td_->dialog_manager_->have_dialog_info_force(creator_dialog_id, "CheckGiftCodeQuery")) { LOG(ERROR) << "Receive " << to_string(result); return on_error(Status::Error(500, "Receive invalid response")); } if (creator_dialog_id.get_type() != DialogType::User) { td_->dialog_manager_->force_create_dialog(creator_dialog_id, "CheckGiftCodeQuery", true); } } UserId user_id(result->to_id_); if (!user_id.is_valid() && user_id != UserId()) { LOG(ERROR) << "Receive " << to_string(result); user_id = UserId(); } MessageId message_id(ServerMessageId(result->giveaway_msg_id_)); if (!message_id.is_valid() && message_id != MessageId()) { LOG(ERROR) << "Receive " << to_string(result); message_id = MessageId(); } if (message_id != MessageId() && creator_dialog_id.get_type() != DialogType::Channel) { LOG(ERROR) << "Receive " << to_string(result); message_id = MessageId(); } promise_.set_value(td_api::make_object( creator_dialog_id == DialogId() ? nullptr : get_message_sender_object(td_, creator_dialog_id, "premiumGiftCodeInfo"), result->date_, result->via_giveaway_, message_id.get(), result->months_, td_->user_manager_->get_user_id_object(user_id, "premiumGiftCodeInfo"), result->used_date_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class ApplyGiftCodeQuery final : public Td::ResultHandler { Promise promise_; public: explicit ApplyGiftCodeQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &code) { send_query(G()->net_query_creator().create(telegram_api::payments_applyGiftCode(code))); } 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 ApplyGiftCodeQuery: " << 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 LaunchPrepaidGiveawayQuery final : public Td::ResultHandler { Promise promise_; public: explicit LaunchPrepaidGiveawayQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(int64 giveaway_id, const GiveawayParameters ¶meters) { auto dialog_id = parameters.get_boosted_dialog_id(); auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::payments_launchPrepaidGiveaway( std::move(input_peer), giveaway_id, parameters.get_input_store_payment_premium_giveaway(td_, string(), 0)))); } 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 LaunchPrepaidGiveawayQuery: " << 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 GetGiveawayInfoQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; public: explicit GetGiveawayInfoQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, ServerMessageId server_message_id) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } send_query(G()->net_query_creator().create( telegram_api::payments_getGiveawayInfo(std::move(input_peer), server_message_id.get()))); } 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 GetGiveawayInfoQuery: " << to_string(ptr); switch (ptr->get_id()) { case telegram_api::payments_giveawayInfo::ID: { auto info = telegram_api::move_object_as(ptr); auto status = [&]() -> td_api::object_ptr { if (info->joined_too_early_date_ > 0) { return td_api::make_object( info->joined_too_early_date_); } if (info->admin_disallowed_chat_id_ > 0) { ChannelId channel_id(info->admin_disallowed_chat_id_); if (!channel_id.is_valid() || !td_->chat_manager_->have_channel_force(channel_id, "GetGiveawayInfoQuery")) { LOG(ERROR) << "Receive " << to_string(info); } else { DialogId dialog_id(channel_id); td_->dialog_manager_->force_create_dialog(dialog_id, "GetGiveawayInfoQuery"); return td_api::make_object( td_->dialog_manager_->get_chat_id_object(dialog_id, "premiumGiveawayParticipantStatusAdministrator")); } } if (!info->disallowed_country_.empty()) { return td_api::make_object( info->disallowed_country_); } if (info->participating_) { return td_api::make_object(); } return td_api::make_object(); }(); promise_.set_value(td_api::make_object( max(0, info->start_date_), std::move(status), info->preparing_results_)); break; } case telegram_api::payments_giveawayInfoResults::ID: { auto info = telegram_api::move_object_as(ptr); auto winner_count = info->winners_count_; auto activated_count = info->activated_count_; if (activated_count < 0 || activated_count > winner_count) { LOG(ERROR) << "Receive " << to_string(info); if (activated_count < 0) { activated_count = 0; } if (winner_count < 0) { winner_count = 0; } if (activated_count > winner_count) { activated_count = winner_count; } } promise_.set_value(td_api::make_object( max(0, info->start_date_), max(0, info->finish_date_), info->refunded_, winner_count, activated_count, info->gift_code_slug_)); break; } default: UNREACHABLE(); } } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetGiveawayInfoQuery"); promise_.set_error(std::move(status)); } }; class GetStarsTopupOptionsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetStarsTopupOptionsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::payments_getStarsTopupOptions())); } 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 results = result_ptr.move_as_ok(); vector> options; for (auto &result : results) { options.push_back(td_api::make_object( result->currency_, result->amount_, result->stars_, result->store_product_, result->extended_)); } promise_.set_value(td_api::make_object(std::move(options))); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetStarsTransactionsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetStarsTransactionsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &offset, td_api::object_ptr &&direction) { int32 flags = 0; if (direction != nullptr) { switch (direction->get_id()) { case td_api::starTransactionDirectionIncoming::ID: flags |= telegram_api::payments_getStarsTransactions::INBOUND_MASK; break; case td_api::starTransactionDirectionOutgoing::ID: flags |= telegram_api::payments_getStarsTransactions::OUTBOUND_MASK; break; default: UNREACHABLE(); } } send_query(G()->net_query_creator().create( telegram_api::payments_getStarsTransactions(flags, false /*ignored*/, false /*ignored*/, telegram_api::make_object(), offset))); } 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 result = result_ptr.move_as_ok(); td_->user_manager_->on_get_users(std::move(result->users_), "GetStarsTransactionsQuery"); td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetStarsTransactionsQuery"); vector> transactions; for (auto &transaction : result->history_) { td_api::object_ptr product_info; if (!transaction->title_.empty() || !transaction->description_.empty() || transaction->photo_ != nullptr) { auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(transaction->photo_), DialogId()); product_info = get_product_info_object(td_, transaction->title_, transaction->description_, photo); } auto source = [&]() -> td_api::object_ptr { switch (transaction->peer_->get_id()) { case telegram_api::starsTransactionPeerUnsupported::ID: return td_api::make_object(); case telegram_api::starsTransactionPeerPremiumBot::ID: return td_api::make_object(); case telegram_api::starsTransactionPeerAppStore::ID: return td_api::make_object(); case telegram_api::starsTransactionPeerPlayMarket::ID: return td_api::make_object(); case telegram_api::starsTransactionPeerFragment::ID: return td_api::make_object(); case telegram_api::starsTransactionPeer::ID: { DialogId dialog_id( static_cast(transaction->peer_.get())->peer_); if (dialog_id.get_type() == DialogType::User) { return td_api::make_object( td_->user_manager_->get_user_id_object(dialog_id.get_user_id(), "starTransactionSourceUser"), std::move(product_info)); } return td_api::make_object(); } default: UNREACHABLE(); } }(); transactions.push_back(td_api::make_object(transaction->id_, transaction->stars_, transaction->date_, std::move(source))); } promise_.set_value( td_api::make_object(result->balance_, std::move(transactions), result->next_offset_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class CanPurchasePremiumQuery final : public Td::ResultHandler { Promise promise_; public: explicit CanPurchasePremiumQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(td_api::object_ptr &&purpose) { auto r_input_purpose = get_input_store_payment_purpose(td_, purpose); if (r_input_purpose.is_error()) { return on_error(r_input_purpose.move_as_error()); } send_query( G()->net_query_creator().create(telegram_api::payments_canPurchasePremium(r_input_purpose.move_as_ok()))); } 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) { return on_error(Status::Error(400, "Premium can't be purchased")); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class AssignAppStoreTransactionQuery final : public Td::ResultHandler { Promise promise_; public: explicit AssignAppStoreTransactionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &receipt, td_api::object_ptr &&purpose) { auto r_input_purpose = get_input_store_payment_purpose(td_, purpose); if (r_input_purpose.is_error()) { return on_error(r_input_purpose.move_as_error()); } send_query(G()->net_query_creator().create( telegram_api::payments_assignAppStoreTransaction(BufferSlice(receipt), r_input_purpose.move_as_ok()))); } 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 AssignAppStoreTransactionQuery: " << 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 AssignPlayMarketTransactionQuery final : public Td::ResultHandler { Promise promise_; public: explicit AssignPlayMarketTransactionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &package_name, const string &store_product_id, const string &purchase_token, td_api::object_ptr &&purpose) { auto r_input_purpose = get_input_store_payment_purpose(td_, purpose); if (r_input_purpose.is_error()) { return on_error(r_input_purpose.move_as_error()); } auto receipt = make_tl_object(string()); receipt->data_ = json_encode(json_object([&](auto &o) { o("packageName", package_name); o("purchaseToken", purchase_token); o("productId", store_product_id); })); send_query(G()->net_query_creator().create( telegram_api::payments_assignPlayMarketTransaction(std::move(receipt), r_input_purpose.move_as_ok()))); } 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 AssignPlayMarketTransactionQuery: " << 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)); } }; const vector &get_premium_limit_keys() { static const vector limit_keys{"channels", "saved_gifs", "stickers_faved", "dialog_filters", "dialog_filters_chats", "dialogs_pinned", "dialogs_folder_pinned", "channels_public", "caption_length", "about_length", "chatlist_invites", "chatlists_joined", "story_expiring", "story_caption_length", "stories_sent_weekly", "stories_sent_monthly", "stories_suggested_reactions", "recommended_channels", "saved_dialogs_pinned"}; return limit_keys; } static Slice get_limit_type_key(const td_api::PremiumLimitType *limit_type) { CHECK(limit_type != nullptr); switch (limit_type->get_id()) { case td_api::premiumLimitTypeSupergroupCount::ID: return Slice("channels"); case td_api::premiumLimitTypeSavedAnimationCount::ID: return Slice("saved_gifs"); case td_api::premiumLimitTypeFavoriteStickerCount::ID: return Slice("stickers_faved"); case td_api::premiumLimitTypeChatFolderCount::ID: return Slice("dialog_filters"); case td_api::premiumLimitTypeChatFolderChosenChatCount::ID: return Slice("dialog_filters_chats"); case td_api::premiumLimitTypePinnedChatCount::ID: return Slice("dialogs_pinned"); case td_api::premiumLimitTypePinnedArchivedChatCount::ID: return Slice("dialogs_folder_pinned"); case td_api::premiumLimitTypePinnedSavedMessagesTopicCount::ID: return Slice("saved_dialogs_pinned"); case td_api::premiumLimitTypeCreatedPublicChatCount::ID: return Slice("channels_public"); case td_api::premiumLimitTypeCaptionLength::ID: return Slice("caption_length"); case td_api::premiumLimitTypeBioLength::ID: return Slice("about_length"); case td_api::premiumLimitTypeChatFolderInviteLinkCount::ID: return Slice("chatlist_invites"); case td_api::premiumLimitTypeShareableChatFolderCount::ID: return Slice("chatlists_joined"); case td_api::premiumLimitTypeActiveStoryCount::ID: return Slice("story_expiring"); case td_api::premiumLimitTypeStoryCaptionLength::ID: return Slice("story_caption_length"); case td_api::premiumLimitTypeWeeklySentStoryCount::ID: return Slice("stories_sent_weekly"); case td_api::premiumLimitTypeMonthlySentStoryCount::ID: return Slice("stories_sent_monthly"); case td_api::premiumLimitTypeStorySuggestedReactionAreaCount::ID: return Slice("stories_suggested_reactions"); case td_api::premiumLimitTypeSimilarChatCount::ID: return Slice("recommended_channels"); default: UNREACHABLE(); return Slice(); } } static string get_premium_source(const td_api::PremiumLimitType *limit_type) { if (limit_type == nullptr) { return string(); } return PSTRING() << "double_limits__" << get_limit_type_key(limit_type); } static string get_premium_source(const td_api::PremiumFeature *feature) { if (feature == nullptr) { return string(); } switch (feature->get_id()) { case td_api::premiumFeatureIncreasedLimits::ID: return "double_limits"; case td_api::premiumFeatureIncreasedUploadFileSize::ID: return "more_upload"; case td_api::premiumFeatureImprovedDownloadSpeed::ID: return "faster_download"; case td_api::premiumFeatureVoiceRecognition::ID: return "voice_to_text"; case td_api::premiumFeatureDisabledAds::ID: return "no_ads"; case td_api::premiumFeatureUniqueReactions::ID: return "infinite_reactions"; case td_api::premiumFeatureUniqueStickers::ID: return "premium_stickers"; case td_api::premiumFeatureCustomEmoji::ID: return "animated_emoji"; case td_api::premiumFeatureAdvancedChatManagement::ID: return "advanced_chat_management"; case td_api::premiumFeatureProfileBadge::ID: return "profile_badge"; case td_api::premiumFeatureEmojiStatus::ID: return "emoji_status"; case td_api::premiumFeatureAnimatedProfilePhoto::ID: return "animated_userpics"; case td_api::premiumFeatureForumTopicIcon::ID: return "forum_topic_icon"; case td_api::premiumFeatureAppIcons::ID: return "app_icons"; case td_api::premiumFeatureRealTimeChatTranslation::ID: return "translations"; case td_api::premiumFeatureUpgradedStories::ID: return "stories"; case td_api::premiumFeatureChatBoost::ID: return "channel_boost"; case td_api::premiumFeatureAccentColor::ID: return "peer_colors"; case td_api::premiumFeatureBackgroundForBoth::ID: return "wallpapers"; case td_api::premiumFeatureSavedMessagesTags::ID: return "saved_tags"; case td_api::premiumFeatureMessagePrivacy::ID: return "message_privacy"; case td_api::premiumFeatureLastSeenTimes::ID: return "last_seen"; case td_api::premiumFeatureBusiness::ID: return "business"; default: UNREACHABLE(); } return string(); } static string get_premium_source(const td_api::BusinessFeature *feature) { if (feature == nullptr) { return "business"; } switch (feature->get_id()) { case td_api::businessFeatureLocation::ID: return "business_location"; case td_api::businessFeatureOpeningHours::ID: return "business_hours"; case td_api::businessFeatureQuickReplies::ID: return "quick_replies"; case td_api::businessFeatureGreetingMessage::ID: return "greeting_message"; case td_api::businessFeatureAwayMessage::ID: return "away_message"; case td_api::businessFeatureAccountLinks::ID: return "business_links"; case td_api::businessFeatureStartPage::ID: return "business_intro"; case td_api::businessFeatureBots::ID: return "business_bots"; case td_api::businessFeatureEmojiStatus::ID: return "emoji_status"; case td_api::businessFeatureChatFolderTags::ID: return "folder_tags"; case td_api::businessFeatureUpgradedStories::ID: return "stories"; default: UNREACHABLE(); } return string(); } static string get_premium_source(const td_api::PremiumStoryFeature *feature) { if (feature == nullptr) { return string(); } switch (feature->get_id()) { case td_api::premiumStoryFeaturePriorityOrder::ID: return "stories__priority_order"; case td_api::premiumStoryFeatureStealthMode::ID: return "stories__stealth_mode"; case td_api::premiumStoryFeaturePermanentViewsHistory::ID: return "stories__permanent_views_history"; case td_api::premiumStoryFeatureCustomExpirationDuration::ID: return "stories__expiration_durations"; case td_api::premiumStoryFeatureSaveStories::ID: return "stories__save_stories_to_gallery"; case td_api::premiumStoryFeatureLinksAndFormatting::ID: return "stories__links_and_formatting"; case td_api::premiumStoryFeatureVideoQuality::ID: return "stories__quality"; default: UNREACHABLE(); return string(); } } static string get_premium_source(const td_api::object_ptr &source) { if (source == nullptr) { return string(); } switch (source->get_id()) { case td_api::premiumSourceLimitExceeded::ID: { auto *limit_type = static_cast(source.get())->limit_type_.get(); return get_premium_source(limit_type); } case td_api::premiumSourceFeature::ID: { auto *feature = static_cast(source.get())->feature_.get(); return get_premium_source(feature); } case td_api::premiumSourceBusinessFeature::ID: { auto *feature = static_cast(source.get())->feature_.get(); return get_premium_source(feature); } case td_api::premiumSourceStoryFeature::ID: { auto *feature = static_cast(source.get())->feature_.get(); return get_premium_source(feature); } case td_api::premiumSourceLink::ID: { auto &referrer = static_cast(source.get())->referrer_; if (referrer.empty()) { return "deeplink"; } return PSTRING() << "deeplink_" << referrer; } case td_api::premiumSourceSettings::ID: return "settings"; default: UNREACHABLE(); return string(); } } static td_api::object_ptr get_premium_limit_object(Slice key) { auto default_limit = static_cast(G()->get_option_integer(PSLICE() << key << "_limit_default")); auto premium_limit = static_cast(G()->get_option_integer(PSLICE() << key << "_limit_premium")); if (default_limit <= 0 || premium_limit <= default_limit) { return nullptr; } auto type = [&]() -> td_api::object_ptr { if (key == "channels") { return td_api::make_object(); } if (key == "saved_gifs") { return td_api::make_object(); } if (key == "stickers_faved") { return td_api::make_object(); } if (key == "dialog_filters") { return td_api::make_object(); } if (key == "dialog_filters_chats") { return td_api::make_object(); } if (key == "dialogs_pinned") { return td_api::make_object(); } if (key == "dialogs_folder_pinned") { return td_api::make_object(); } if (key == "saved_dialogs_pinned") { return td_api::make_object(); } if (key == "channels_public") { return td_api::make_object(); } if (key == "caption_length") { return td_api::make_object(); } if (key == "about_length") { return td_api::make_object(); } if (key == "chatlist_invites") { return td_api::make_object(); } if (key == "chatlists_joined") { return td_api::make_object(); } if (key == "story_expiring") { return td_api::make_object(); } if (key == "story_caption_length") { return td_api::make_object(); } if (key == "stories_sent_weekly") { return td_api::make_object(); } if (key == "stories_sent_monthly") { return td_api::make_object(); } if (key == "stories_suggested_reactions") { return td_api::make_object(); } if (key == "recommended_channels") { return td_api::make_object(); } UNREACHABLE(); return nullptr; }(); return td_api::make_object(std::move(type), default_limit, premium_limit); } void get_premium_limit(const td_api::object_ptr &limit_type, Promise> &&promise) { if (limit_type == nullptr) { return promise.set_error(Status::Error(400, "Limit type must be non-empty")); } promise.set_value(get_premium_limit_object(get_limit_type_key(limit_type.get()))); } void get_premium_features(Td *td, const td_api::object_ptr &source, Promise> &&promise) { auto premium_features = full_split(G()->get_option_string( "premium_features", "stories,more_upload,double_limits,last_seen,voice_to_text,faster_download,translations,animated_" "emoji,emoji_status,saved_tags,peer_colors,wallpapers,profile_badge,message_privacy,advanced_chat_" "management,no_ads,app_icons,infinite_reactions,animated_userpics,premium_stickers"), ','); vector> features; for (const auto &premium_feature : premium_features) { auto feature = get_premium_feature_object(premium_feature); if (feature != nullptr) { features.push_back(std::move(feature)); } } auto limits = transform(get_premium_limit_keys(), get_premium_limit_object); td::remove_if(limits, [](auto &limit) { return limit == nullptr; }); auto source_str = get_premium_source(source); if (!source_str.empty()) { vector> data; vector> promo_order; for (const auto &premium_feature : premium_features) { promo_order.push_back(make_tl_object(premium_feature)); } data.push_back(make_tl_object( "premium_promo_order", make_tl_object(std::move(promo_order)))); data.push_back( make_tl_object("source", make_tl_object(source_str))); save_app_log(td, "premium.promo_screen_show", DialogId(), make_tl_object(std::move(data)), Promise()); } td_api::object_ptr payment_link; auto premium_bot_username = G()->get_option_string("premium_bot_username"); if (!premium_bot_username.empty()) { payment_link = td_api::make_object(premium_bot_username, source_str, true); } else { auto premium_invoice_slug = G()->get_option_string("premium_invoice_slug"); if (!premium_invoice_slug.empty()) { payment_link = td_api::make_object(premium_invoice_slug); } } promise.set_value( td_api::make_object(std::move(features), std::move(limits), std::move(payment_link))); } void get_business_features(Td *td, const td_api::object_ptr &source, Promise> &&promise) { auto business_features = full_split(G()->get_option_string("business_features", "business_location,business_hours,quick_replies,greeting_message,away_message," "business_links,business_intro,business_bots,emoji_status,folder_tags,stories"), ','); vector> features; for (const auto &business_feature : business_features) { auto feature = get_business_feature_object(business_feature); if (feature != nullptr) { features.push_back(std::move(feature)); } } auto source_str = get_premium_source(source.get()); if (!source_str.empty()) { vector> data; vector> promo_order; for (const auto &business_feature : business_features) { promo_order.push_back(telegram_api::make_object(business_feature)); } data.push_back(telegram_api::make_object( "business_promo_order", telegram_api::make_object(std::move(promo_order)))); data.push_back(telegram_api::make_object( "source", telegram_api::make_object(source_str))); save_app_log(td, "business.promo_screen_show", DialogId(), telegram_api::make_object(std::move(data)), Promise()); } promise.set_value(td_api::make_object(std::move(features))); } void view_premium_feature(Td *td, const td_api::object_ptr &feature, Promise &&promise) { auto source = get_premium_source(feature.get()); if (source.empty()) { return promise.set_error(Status::Error(400, "Feature must be non-empty")); } vector> data; data.push_back( make_tl_object("item", make_tl_object(source))); save_app_log(td, "premium.promo_screen_tap", DialogId(), make_tl_object(std::move(data)), std::move(promise)); } void click_premium_subscription_button(Td *td, Promise &&promise) { vector> data; save_app_log(td, "premium.promo_screen_accept", DialogId(), make_tl_object(std::move(data)), std::move(promise)); } void get_premium_state(Td *td, Promise> &&promise) { td->create_handler(std::move(promise))->send(); } void get_premium_gift_code_options(Td *td, DialogId boosted_dialog_id, Promise> &&promise) { td->create_handler(std::move(promise))->send(boosted_dialog_id); } void check_premium_gift_code(Td *td, const string &code, Promise> &&promise) { td->create_handler(std::move(promise))->send(code); } void apply_premium_gift_code(Td *td, const string &code, Promise &&promise) { td->create_handler(std::move(promise))->send(code); } void launch_prepaid_premium_giveaway(Td *td, int64 giveaway_id, td_api::object_ptr &¶meters, Promise &&promise) { TRY_RESULT_PROMISE(promise, giveaway_parameters, GiveawayParameters::get_giveaway_parameters(td, parameters.get())); td->create_handler(std::move(promise))->send(giveaway_id, giveaway_parameters); } void get_premium_giveaway_info(Td *td, MessageFullId message_full_id, Promise> &&promise) { TRY_RESULT_PROMISE(promise, server_message_id, td->messages_manager_->get_giveaway_message_id(message_full_id)); td->create_handler(std::move(promise)) ->send(message_full_id.get_dialog_id(), server_message_id); } void get_star_payment_options(Td *td, Promise> &&promise) { td->create_handler(std::move(promise))->send(); } void get_star_transactions(Td *td, const string &offset, td_api::object_ptr &&direction, Promise> &&promise) { td->create_handler(std::move(promise))->send(offset, std::move(direction)); } void can_purchase_premium(Td *td, td_api::object_ptr &&purpose, Promise &&promise) { td->create_handler(std::move(promise))->send(std::move(purpose)); } void assign_app_store_transaction(Td *td, const string &receipt, td_api::object_ptr &&purpose, Promise &&promise) { if (purpose != nullptr && purpose->get_id() == td_api::storePaymentPurposePremiumSubscription::ID) { dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::UpgradePremium}, Promise()); dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::SubscribeToAnnualPremium}, Promise()); dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::RestorePremium}, Promise()); } td->create_handler(std::move(promise))->send(receipt, std::move(purpose)); } void assign_play_market_transaction(Td *td, const string &package_name, const string &store_product_id, const string &purchase_token, td_api::object_ptr &&purpose, Promise &&promise) { if (purpose != nullptr && purpose->get_id() == td_api::storePaymentPurposePremiumSubscription::ID) { dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::UpgradePremium}, Promise()); dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::SubscribeToAnnualPremium}, Promise()); dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::RestorePremium}, Promise()); } td->create_handler(std::move(promise)) ->send(package_name, store_product_id, purchase_token, std::move(purpose)); } } // namespace td