// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2026 // // 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/StarManager.h" #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" #include "td/telegram/ChatManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileId.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/Global.h" #include "td/telegram/InputInvoice.h" #include "td/telegram/MessageExtendedMedia.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessageSender.h" #include "td/telegram/PasswordManager.h" #include "td/telegram/Photo.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StarAmount.h" #include "td/telegram/StarGift.h" #include "td/telegram/StarGiftManager.h" #include "td/telegram/StarSubscription.h" #include "td/telegram/StatisticsManager.h" #include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/TonAmount.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/ScopeGuard.h" #include "td/utils/SliceBuilder.h" #include namespace td { 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_, StarManager::get_star_count(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 GetStarsGiftOptionsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetStarsGiftOptionsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(telegram_api::object_ptr input_user) { int32 flags = 0; if (input_user != nullptr) { flags |= telegram_api::payments_getStarsGiftOptions::USER_ID_MASK; } send_query( G()->net_query_creator().create(telegram_api::payments_getStarsGiftOptions(flags, std::move(input_user)))); } 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_, StarManager::get_star_count(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 GetStarsGiveawayOptionsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetStarsGiveawayOptionsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send() { send_query(G()->net_query_creator().create(telegram_api::payments_getStarsGiveawayOptions())); } 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) { vector> winner_options; for (auto &winner : result->winners_) { winner_options.push_back(td_api::make_object( winner->users_, StarManager::get_star_count(winner->per_user_stars_), winner->default_)); } options.push_back(td_api::make_object( result->currency_, result->amount_, StarManager::get_star_count(result->stars_), result->store_product_, result->yearly_boosts_, std::move(winner_options), result->default_, 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_; DialogId dialog_id_; public: explicit GetStarsTransactionsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, const string &subscription_id, const string &offset, int32 limit, td_api::object_ptr &&direction) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Have no access to the chat")); } int32 flags = 0; if (!subscription_id.empty()) { flags |= telegram_api::payments_getStarsTransactions::SUBSCRIPTION_ID_MASK; } bool inbound = false; bool outbound = false; bool ascending = td_->auth_manager_->is_bot(); if (direction != nullptr) { switch (direction->get_id()) { case td_api::transactionDirectionIncoming::ID: inbound = true; break; case td_api::transactionDirectionOutgoing::ID: outbound = true; break; default: UNREACHABLE(); } } send_query(G()->net_query_creator().create(telegram_api::payments_getStarsTransactions( flags, inbound, outbound, ascending, false, subscription_id, std::move(input_peer), offset, limit))); } void send(DialogId dialog_id, const string &transaction_id, bool is_refund) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Have no access to the chat")); } vector> transaction_ids; transaction_ids.push_back( telegram_api::make_object(0, is_refund, transaction_id)); send_query(G()->net_query_creator().create( telegram_api::payments_getStarsTransactionsByID(0, false, std::move(input_peer), std::move(transaction_ids)))); } void on_result(BufferSlice packet) final { static_assert(std::is_same::value); 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 GetStarsTransactionsQuery: " << to_string(result); td_->user_manager_->on_get_users(std::move(result->users_), "GetStarsTransactionsQuery"); td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetStarsTransactionsQuery"); if (result->balance_->get_id() != telegram_api::starsAmount::ID) { LOG(ERROR) << "Receive " << to_string(result); return on_error(Status::Error(500, "Receive invalid response")); } bool for_bot = dialog_id_.get_type() == DialogType::User && td_->user_manager_->is_user_bot(dialog_id_.get_user_id()); bool for_user = dialog_id_.get_type() == DialogType::User && !for_bot; bool for_chat = !for_user && !for_bot; bool for_channel = for_chat && td_->dialog_manager_->is_broadcast_channel(dialog_id_); bool for_supergroup = for_chat && !for_channel; vector> transactions; for (auto &transaction : result->history_) { if (transaction->amount_->get_id() != telegram_api::starsAmount::ID) { LOG(ERROR) << "Receive " << to_string(transaction); continue; } vector file_ids; td_api::object_ptr product_info; string bot_payload; td_api::object_ptr affiliate; int32 commission_per_mille = 0; 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()); photo_append_file_ids(photo, file_ids); product_info = get_product_info_object(td_, transaction->title_, transaction->description_, photo); transaction->title_.clear(); transaction->description_.clear(); } if (!transaction->bot_payload_.empty()) { if (td_->auth_manager_->is_bot()) { bot_payload = transaction->bot_payload_.as_slice().str(); transaction->bot_payload_.clear(); } else if (!for_bot) { LOG(ERROR) << "Receive Star transaction with bot payload"; } } if (transaction->starref_peer_ != nullptr) { DialogId referrer_dialog_id(transaction->starref_peer_); if (transaction->starref_commission_permille_ > 0 && transaction->starref_commission_permille_ < 1000 && referrer_dialog_id.is_valid()) { td_->dialog_manager_->force_create_dialog(referrer_dialog_id, "affiliateInfo", true); if (transaction->starref_amount_->get_id() != telegram_api::starsAmount::ID) { LOG(ERROR) << "Receive invalid starref_amount: " << to_string(transaction); } else { auto referrer_star_amount = StarAmount(telegram_api::move_object_as(transaction->starref_amount_), true); affiliate = td_api::make_object( transaction->starref_commission_permille_, td_->dialog_manager_->get_chat_id_object(referrer_dialog_id, "affiliateInfo"), referrer_star_amount.get_star_amount_object()); transaction->starref_commission_permille_ = 0; } transaction->starref_peer_ = nullptr; } else { LOG(ERROR) << "Receive invalid affiliate info: " << to_string(transaction); } } else if (transaction->starref_commission_permille_ != 0) { if (transaction->starref_commission_permille_ > 0 && transaction->starref_commission_permille_ < 1000) { commission_per_mille = transaction->starref_commission_permille_; } else { LOG(ERROR) << "Receive invalid commission: " << to_string(transaction); } transaction->starref_commission_permille_ = 0; } auto get_paid_media_object = [&](DialogId dialog_id) -> vector> { auto extended_media = transform(std::move(transaction->extended_media_), [td = td_, dialog_id](auto &&media) { return MessageExtendedMedia(td, std::move(media), dialog_id); }); for (auto &media : extended_media) { media.append_file_ids(td_, file_ids); } auto extended_media_objects = transform(std::move(extended_media), [td = td_](auto &&media) { return media.get_paid_media_object(td); }); transaction->extended_media_.clear(); return extended_media_objects; }; auto get_message_id_object = [&] { auto message_id = MessageId(ServerMessageId(transaction->msg_id_)); if (message_id != MessageId() && !message_id.is_valid()) { LOG(ERROR) << "Receive " << message_id << " in " << to_string(transaction); message_id = MessageId(); } transaction->msg_id_ = 0; return message_id.get(); }; auto transaction_amount = StarAmount(telegram_api::move_object_as(transaction->amount_), true); auto is_refund = transaction->refund_; auto is_purchase = transaction_amount.is_positive() == is_refund; auto type = [&]() -> td_api::object_ptr { switch (transaction->peer_->get_id()) { case telegram_api::starsTransactionPeerUnsupported::ID: return td_api::make_object(); case telegram_api::starsTransactionPeerPremiumBot::ID: if (for_user) { return td_api::make_object(); } return nullptr; case telegram_api::starsTransactionPeerAppStore::ID: if (for_user) { return td_api::make_object(); } return nullptr; case telegram_api::starsTransactionPeerPlayMarket::ID: if (for_user) { return td_api::make_object(); } return nullptr; case telegram_api::starsTransactionPeerFragment::ID: { if (transaction->gift_) { if (for_user) { transaction->gift_ = false; return td_api::make_object( 0, td_->stickers_manager_->get_premium_gift_sticker_object(0, transaction_amount.get_star_count())); } return nullptr; } auto state = [&]() -> td_api::object_ptr { if (transaction->transaction_date_ > 0) { SCOPE_EXIT { transaction->transaction_date_ = 0; transaction->transaction_url_.clear(); }; return td_api::make_object(transaction->transaction_date_, transaction->transaction_url_); } if (transaction->pending_) { transaction->pending_ = false; return td_api::make_object(); } if (transaction->failed_) { transaction->failed_ = false; return td_api::make_object(); } return nullptr; }(); if (state != nullptr || is_refund) { if (for_user || for_bot || for_chat) { return td_api::make_object(std::move(state)); } return nullptr; } if (for_user || for_bot) { return td_api::make_object(); } return nullptr; } case telegram_api::starsTransactionPeer::ID: { DialogId dialog_id( static_cast(transaction->peer_.get())->peer_); if (!dialog_id.is_valid()) { return nullptr; } if (commission_per_mille != 0) { SCOPE_EXIT { commission_per_mille = 0; }; td_->dialog_manager_->force_create_dialog(dialog_id, "starsTransactionPeer", true); auto chat_id = td_->dialog_manager_->get_chat_id_object(dialog_id, "starTransactionTypeAffiliateProgramCommission"); return td_api::make_object(chat_id, commission_per_mille); } if (transaction->paid_messages_) { if (is_purchase) { if (for_user) { SCOPE_EXIT { product_info = nullptr; transaction->paid_messages_ = 0; }; td_->dialog_manager_->force_create_dialog(dialog_id, "starsTransactionPeer", true); auto chat_id = td_->dialog_manager_->get_chat_id_object(dialog_id, "starTransactionTypePaidMessageSend"); if (transaction->phonegroup_message_) { transaction->phonegroup_message_ = false; LOG_IF(ERROR, transaction->paid_messages_ != 1) << "Receive " << transaction->paid_messages_ << " paid group call messages"; if (transaction->reaction_) { transaction->reaction_ = false; return td_api::make_object(chat_id); } return td_api::make_object(chat_id); } if (product_info != nullptr && product_info->title_ == "Suggested Post" && td_->dialog_manager_->is_broadcast_channel(dialog_id)) { return td_api::make_object(chat_id); } return td_api::make_object(chat_id, transaction->paid_messages_); } } else { if ((for_user || for_supergroup || for_channel) && affiliate != nullptr) { SCOPE_EXIT { product_info = nullptr; transaction->paid_messages_ = 0; affiliate = nullptr; }; auto sender_id = get_message_sender_object(td_, dialog_id, "starTransactionTypePaidMessage"); if (transaction->phonegroup_message_ && !for_supergroup) { transaction->phonegroup_message_ = false; LOG_IF(ERROR, transaction->paid_messages_ != 1) << "Receive " << transaction->paid_messages_ << " received paid group call messages"; if (transaction->reaction_) { transaction->reaction_ = false; return td_api::make_object( std::move(sender_id), affiliate->commission_per_mille_, std::move(affiliate->star_amount_)); } return td_api::make_object( std::move(sender_id), affiliate->commission_per_mille_, std::move(affiliate->star_amount_)); } return td_api::make_object( std::move(sender_id), transaction->paid_messages_, affiliate->commission_per_mille_, std::move(affiliate->star_amount_)); } if (for_channel && dialog_id.get_type() == DialogType::User) { SCOPE_EXIT { product_info = nullptr; transaction->paid_messages_ = 0; }; return td_api::make_object( td_->user_manager_->get_user_id_object(dialog_id.get_user_id(), "starTransactionTypeSuggestedPostPaymentReceive")); } } return nullptr; } if (transaction->posts_search_) { if (for_user && is_purchase) { transaction->posts_search_ = false; product_info = nullptr; return td_api::make_object(); } return nullptr; } if (dialog_id.get_type() == DialogType::User) { auto user_id = dialog_id.get_user_id(); auto user_id_object = td_->user_manager_->get_user_id_object(user_id, "starsTransactionPeer"); if (transaction->business_transfer_) { transaction->business_transfer_ = false; if (is_purchase) { if (for_user) { product_info = nullptr; return td_api::make_object(user_id_object); } } else { if (for_bot) { product_info = nullptr; return td_api::make_object(user_id_object); } } return nullptr; } if (transaction->stargift_ != nullptr) { auto gift = StarGift(td_, std::move(transaction->stargift_), true); transaction->stargift_ = nullptr; if (!gift.is_valid()) { return nullptr; } td_->star_gift_manager_->on_get_star_gift(gift, true); if (is_purchase) { if (gift.is_unique()) { if (transaction->stargift_resale_) { if (for_user) { transaction->stargift_resale_ = false; return td_api::make_object( user_id_object, gift.get_upgraded_gift_object(td_)); } } else if (transaction->stargift_upgrade_) { if (for_user) { transaction->stargift_upgrade_ = false; transaction->msg_id_ = 0; // ignore product_info = nullptr; return td_api::make_object( user_id_object, gift.get_upgraded_gift_object(td_)); } } else if (transaction->stargift_drop_original_details_) { if (for_user) { product_info = nullptr; transaction->stargift_drop_original_details_ = false; return td_api::make_object( get_message_sender_object(td_, user_id, DialogId(), "starTransactionTypeGiftOriginalDetailsDrop"), gift.get_upgraded_gift_object(td_)); } } else if (transaction->offer_) { if (for_user) { transaction->offer_ = false; return td_api::make_object( gift.get_upgraded_gift_object(td_)); } } else { if (for_user) { return td_api::make_object( get_message_sender_object(td_, user_id, DialogId(), "starTransactionTypeGiftTransfer"), gift.get_upgraded_gift_object(td_)); } } } else { if (transaction->stargift_prepaid_upgrade_) { if (for_user) { transaction->stargift_prepaid_upgrade_ = false; product_info = nullptr; return td_api::make_object( td_api::make_object(user_id_object), gift.get_gift_object(td_)); } } else if (transaction->stargift_auction_bid_) { if (for_user) { transaction->stargift_auction_bid_ = false; product_info = nullptr; return td_api::make_object( td_api::make_object(user_id_object), gift.get_gift_object(td_)); } } else if (for_user || for_bot) { product_info = nullptr; return td_api::make_object( get_message_sender_object(td_, user_id, DialogId(), "starTransactionTypeGiftPurchase"), gift.get_gift_object(td_)); } } } else { if (gift.is_unique()) { if (transaction->stargift_resale_ || transaction->offer_) { if (for_user && affiliate != nullptr) { SCOPE_EXIT { affiliate = nullptr; transaction->stargift_resale_ = false; transaction->offer_ = false; }; return td_api::make_object( user_id_object, gift.get_upgraded_gift_object(td_), affiliate->commission_per_mille_, std::move(affiliate->star_amount_), transaction->offer_); } } else { LOG(ERROR) << "Receive sale of an upgraded gift"; } } else { if (for_user || for_channel) { product_info = nullptr; return td_api::make_object(user_id_object, gift.get_gift_object(td_)); } } } return nullptr; } if (transaction->subscription_period_ > 0) { SCOPE_EXIT { transaction->subscription_period_ = 0; }; if (is_purchase) { if (for_user) { return td_api::make_object( user_id_object, transaction->subscription_period_, std::move(product_info)); } } else { if (for_channel) { return td_api::make_object( user_id_object, transaction->subscription_period_); } else if (for_bot) { SCOPE_EXIT { bot_payload.clear(); }; return td_api::make_object( user_id_object, transaction->subscription_period_, std::move(product_info), bot_payload, std::move(affiliate)); } } return nullptr; } if (transaction->gift_) { if (for_user) { transaction->gift_ = false; return td_api::make_object( user_id == UserManager::get_service_notifications_user_id() ? 0 : td_->user_manager_->get_user_id_object(user_id, "starTransactionTypeUserDeposit"), td_->stickers_manager_->get_premium_gift_sticker_object(0, transaction_amount.get_star_count())); } return nullptr; } if (transaction->subscription_period_ > 0) { if (for_channel) { SCOPE_EXIT { transaction->subscription_period_ = 0; }; return td_api::make_object( user_id_object, transaction->subscription_period_); } return nullptr; } if (transaction->reaction_) { if (for_channel) { transaction->reaction_ = false; return td_api::make_object( user_id_object, get_message_id_object()); } return nullptr; } SCOPE_EXIT { bot_payload.clear(); }; if (transaction->premium_gift_months_ > 0 && is_purchase) { SCOPE_EXIT { transaction->premium_gift_months_ = 0; product_info = nullptr; }; if (for_user || for_bot) { return td_api::make_object( user_id_object, transaction->premium_gift_months_, td_->stickers_manager_->get_premium_gift_sticker_object(transaction->premium_gift_months_, 0)); } } if (product_info != nullptr) { if (is_purchase) { if (for_user) { return td_api::make_object(user_id_object, std::move(product_info)); } } else { if (for_bot) { return td_api::make_object( user_id_object, std::move(product_info), bot_payload, std::move(affiliate)); } } return nullptr; } if (!transaction->extended_media_.empty() || is_refund || for_bot) { // TODO if (is_purchase) { if (for_user) { return td_api::make_object( user_id_object, get_paid_media_object(dialog_id)); } } else { if (for_bot) { return td_api::make_object( user_id_object, get_paid_media_object(dialog_id_), bot_payload, std::move(affiliate)); } else if (for_channel) { return td_api::make_object( user_id_object, get_message_id_object(), get_paid_media_object(dialog_id_)); } } return nullptr; } return nullptr; } // partner isn't a user now td_->dialog_manager_->force_create_dialog(dialog_id, "starsTransactionPeer", true); auto chat_id = td_->dialog_manager_->get_chat_id_object(dialog_id, "starsTransactionPeer"); if (transaction->stargift_ != nullptr) { auto gift = StarGift(td_, std::move(transaction->stargift_), true); transaction->stargift_ = nullptr; if (!gift.is_valid()) { return nullptr; } td_->star_gift_manager_->on_get_star_gift(gift, true); if (is_purchase) { if (gift.is_unique()) { if (transaction->stargift_drop_original_details_) { if (for_user) { product_info = nullptr; transaction->stargift_drop_original_details_ = false; return td_api::make_object( get_message_sender_object(td_, UserId(), dialog_id, "starTransactionTypeGiftOriginalDetailsDrop"), gift.get_upgraded_gift_object(td_)); } } else if (!transaction->stargift_upgrade_ && !transaction->stargift_resale_) { if (for_user) { return td_api::make_object( get_message_sender_object(td_, UserId(), dialog_id, "starTransactionTypeGiftTransfer"), gift.get_upgraded_gift_object(td_)); } } } else { if (transaction->stargift_prepaid_upgrade_) { if (for_user) { transaction->stargift_prepaid_upgrade_ = false; product_info = nullptr; return td_api::make_object( get_message_sender_object(td_, UserId(), dialog_id, "starTransactionTypeGiftUpgradePurchase"), gift.get_gift_object(td_)); } } else if (for_user || for_bot) { product_info = nullptr; return td_api::make_object( get_message_sender_object(td_, UserId(), dialog_id, "starTransactionTypeGiftPurchase"), gift.get_gift_object(td_)); } } } return nullptr; } if (transaction->giveaway_post_id_ > 0) { if (for_user && dialog_id.get_type() == DialogType::Channel) { SCOPE_EXIT { transaction->giveaway_post_id_ = 0; }; return td_api::make_object( chat_id, MessageId(ServerMessageId(transaction->giveaway_post_id_)).get()); } return nullptr; } if (transaction->subscription_period_ > 0) { if (td_->dialog_manager_->is_broadcast_channel(dialog_id) && for_user) { SCOPE_EXIT { transaction->subscription_period_ = 0; }; return td_api::make_object( chat_id, transaction->subscription_period_); } return nullptr; } if (transaction->reaction_) { if (td_->dialog_manager_->is_broadcast_channel(dialog_id) && for_user) { transaction->reaction_ = false; return td_api::make_object(chat_id, get_message_id_object()); } return nullptr; } if (!transaction->extended_media_.empty() || is_refund) { // TODO if (td_->dialog_manager_->is_broadcast_channel(dialog_id) && for_user) { return td_api::make_object( chat_id, get_message_id_object(), get_paid_media_object(dialog_id)); } return nullptr; } return nullptr; } case telegram_api::starsTransactionPeerAds::ID: if (for_bot || for_channel) { return td_api::make_object(); } return nullptr; case telegram_api::starsTransactionPeerAPI::ID: { if (for_bot) { SCOPE_EXIT { transaction->floodskip_number_ = 0; }; return td_api::make_object(transaction->floodskip_number_); } return nullptr; } default: UNREACHABLE(); return nullptr; } }(); if (type == nullptr) { if (!(transaction->offer_ && transaction->date_ < 1765550000 && !is_purchase)) { LOG(ERROR) << "Receive unsupported Star transaction in " << dialog_id_ << ": " << to_string(transaction); } type = td_api::make_object(); } auto star_transaction = td_api::make_object(transaction->id_, transaction_amount.get_star_amount_object(), is_refund, transaction->date_, std::move(type)); if (star_transaction->type_->get_id() != td_api::starTransactionTypeUnsupported::ID) { auto name = StarManager::get_unused_star_transaction_field(transaction, product_info, bot_payload, affiliate, commission_per_mille); if (!name.empty()) { LOG(ERROR) << "Receive " << name << " with " << to_string(star_transaction); } } if (!file_ids.empty()) { auto file_source_id = td_->star_manager_->get_star_transaction_file_source_id(dialog_id_, transaction->id_, is_refund); for (auto file_id : file_ids) { td_->file_manager_->add_file_source(file_id, file_source_id, "GetStarsTransactionsQuery"); } } transactions.push_back(std::move(star_transaction)); } auto star_amount = StarAmount(telegram_api::move_object_as(result->balance_), true); if (dialog_id_ == td_->dialog_manager_->get_my_dialog_id()) { td_->star_manager_->on_update_owned_star_amount(star_amount); } promise_.set_value(td_api::make_object(star_amount.get_star_amount_object(), std::move(transactions), result->next_offset_)); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetStarsTransactionsQuery"); promise_.set_error(std::move(status)); } }; class GetTonTransactionsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetTonTransactionsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(const string &offset, int32 limit, td_api::object_ptr &&direction) { auto input_peer = td_->dialog_manager_->get_input_peer(td_->dialog_manager_->get_my_dialog_id(), AccessRights::Write); CHECK(input_peer != nullptr); bool inbound = false; bool outbound = false; if (direction != nullptr) { switch (direction->get_id()) { case td_api::transactionDirectionIncoming::ID: inbound = true; break; case td_api::transactionDirectionOutgoing::ID: outbound = true; break; default: UNREACHABLE(); } } send_query(G()->net_query_creator().create(telegram_api::payments_getStarsTransactions( 0, inbound, outbound, false, true, string(), std::move(input_peer), offset, limit))); } 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 GetTonTransactionsQuery: " << to_string(result); td_->user_manager_->on_get_users(std::move(result->users_), "GetTonTransactionsQuery"); td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetTonTransactionsQuery"); if (result->balance_->get_id() != telegram_api::starsTonAmount::ID) { LOG(ERROR) << "Receive " << to_string(result); return on_error(Status::Error(500, "Receive invalid response")); } vector> transactions; for (auto &transaction : result->history_) { if (transaction->amount_->get_id() != telegram_api::starsTonAmount::ID) { LOG(ERROR) << "Receive " << to_string(transaction); continue; } auto transaction_amount = TonAmount(telegram_api::move_object_as(transaction->amount_), true); auto is_refund = transaction->refund_; auto is_purchase = transaction_amount.is_positive() == is_refund; auto type = [&]() -> td_api::object_ptr { switch (transaction->peer_->get_id()) { case telegram_api::starsTransactionPeerUnsupported::ID: return td_api::make_object(); case telegram_api::starsTransactionPeerPremiumBot::ID: case telegram_api::starsTransactionPeerAppStore::ID: case telegram_api::starsTransactionPeerPlayMarket::ID: case telegram_api::starsTransactionPeerAds::ID: case telegram_api::starsTransactionPeerAPI::ID: return nullptr; case telegram_api::starsTransactionPeerFragment::ID: { auto state = [&]() -> td_api::object_ptr { if (transaction->transaction_date_ > 0) { SCOPE_EXIT { transaction->transaction_date_ = 0; transaction->transaction_url_.clear(); }; return td_api::make_object(transaction->transaction_date_, transaction->transaction_url_); } if (transaction->pending_) { transaction->pending_ = false; return td_api::make_object(); } if (transaction->failed_) { transaction->failed_ = false; return td_api::make_object(); } return nullptr; }(); if (state != nullptr || is_refund) { return td_api::make_object(std::move(state)); } SCOPE_EXIT { transaction->gift_ = false; }; return td_api::make_object( transaction->gift_, td_->stickers_manager_->get_ton_gift_sticker_object(transaction_amount.get_ton_amount())); } case telegram_api::starsTransactionPeer::ID: { DialogId dialog_id( static_cast(transaction->peer_.get())->peer_); if (!dialog_id.is_valid()) { return nullptr; } if (transaction->paid_messages_ && is_purchase && transaction->title_ == "Suggested Post" && td_->dialog_manager_->is_broadcast_channel(dialog_id)) { SCOPE_EXIT { transaction->paid_messages_ = 0; transaction->title_.clear(); }; td_->dialog_manager_->force_create_dialog(dialog_id, "starsTransactionPeer", true); auto chat_id = td_->dialog_manager_->get_chat_id_object(dialog_id, "tonTransactionTypeSuggestedPostPayment"); return td_api::make_object(chat_id); } if (transaction->stargift_ != nullptr && dialog_id.get_type() == DialogType::User) { auto gift = StarGift(td_, std::move(transaction->stargift_), true); transaction->stargift_ = nullptr; if (!gift.is_valid() || !gift.is_unique()) { return nullptr; } td_->star_gift_manager_->on_get_star_gift(gift, true); if (transaction->stargift_resale_ || transaction->offer_) { transaction->stargift_resale_ = false; auto user_id_object = td_->user_manager_->get_user_id_object(dialog_id.get_user_id(), "starsTransactionPeer"); if (is_purchase) { if (transaction->offer_) { transaction->offer_ = false; return td_api::make_object( gift.get_upgraded_gift_object(td_)); } return td_api::make_object( user_id_object, gift.get_upgraded_gift_object(td_)); } else if (transaction->starref_commission_permille_ > 0 && transaction->starref_commission_permille_ < 1000 && transaction->starref_amount_->get_id() == telegram_api::starsTonAmount::ID) { SCOPE_EXIT { transaction->starref_peer_ = nullptr; // ignore transaction->starref_commission_permille_ = 0; transaction->starref_amount_ = nullptr; transaction->offer_ = false; }; return td_api::make_object( user_id_object, gift.get_upgraded_gift_object(td_), transaction->starref_commission_permille_, TonAmount( telegram_api::move_object_as(transaction->starref_amount_), true) .get_ton_amount(), transaction->offer_); } } return nullptr; } if (dialog_id == DialogId(UserId(static_cast(G()->is_test_dc() ? 5001167034 : 8353936423)))) { transaction->title_.clear(); if (is_purchase) { return td_api::make_object(); } else { return td_api::make_object(); } } return nullptr; } default: UNREACHABLE(); return nullptr; } }(); if (type == nullptr) { LOG(ERROR) << "Receive unsupported TON transaction: " << to_string(transaction); type = td_api::make_object(); } auto ton_transaction = td_api::make_object( transaction->id_, transaction_amount.get_ton_amount(), is_refund, transaction->date_, std::move(type)); if (ton_transaction->type_->get_id() != td_api::tonTransactionTypeUnsupported::ID) { auto name = StarManager::get_unused_star_transaction_field(transaction, nullptr, string(), nullptr, 0); if (!name.empty()) { LOG(ERROR) << "Receive " << name << " with " << to_string(ton_transaction); } } transactions.push_back(std::move(ton_transaction)); } auto ton_amount = TonAmount(telegram_api::move_object_as(result->balance_), true); td_->star_manager_->on_update_owned_ton_amount(ton_amount); promise_.set_value(td_api::make_object(ton_amount.get_ton_amount(), std::move(transactions), result->next_offset_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetStarsSubscriptionsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetStarsSubscriptionsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(bool only_expiring, const string &offset) { send_query(G()->net_query_creator().create(telegram_api::payments_getStarsSubscriptions( 0, only_expiring, 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(); LOG(DEBUG) << "Receive result for GetStarsSubscriptionsQuery: " << to_string(result); td_->user_manager_->on_get_users(std::move(result->users_), "GetStarsSubscriptionsQuery"); td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetStarsSubscriptionsQuery"); if (result->balance_->get_id() != telegram_api::starsAmount::ID) { LOG(ERROR) << "Receive " << to_string(result); return on_error(Status::Error(500, "Receive invalid response")); } vector> subscriptions; for (auto &subscription : result->subscriptions_) { StarSubscription star_subscription(td_, std::move(subscription)); if (!star_subscription.is_valid()) { LOG(ERROR) << "Receive invalid subscription " << star_subscription; } else { subscriptions.push_back(star_subscription.get_star_subscription_object(td_)); } } auto star_amount = StarAmount(telegram_api::move_object_as(result->balance_), true); td_->star_manager_->on_update_owned_star_amount(star_amount); promise_.set_value(td_api::make_object( star_amount.get_star_amount_object(), std::move(subscriptions), StarManager::get_star_count(result->subscriptions_missing_balance_), result->subscriptions_next_offset_)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class ChangeStarsSubscriptionQuery final : public Td::ResultHandler { Promise promise_; public: explicit ChangeStarsSubscriptionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &subscription_id, bool is_canceled) { send_query(G()->net_query_creator().create(telegram_api::payments_changeStarsSubscription( telegram_api::payments_changeStarsSubscription::CANCELED_MASK, telegram_api::make_object(), subscription_id, is_canceled))); } 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()); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class BotCancelStarsSubscriptionQuery final : public Td::ResultHandler { Promise promise_; public: explicit BotCancelStarsSubscriptionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(telegram_api::object_ptr &&input_user, const string &telegram_payment_charge_id, bool is_canceled) { send_query(G()->net_query_creator().create(telegram_api::payments_botCancelStarsSubscription( 0, !is_canceled, std::move(input_user), telegram_payment_charge_id))); } 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()); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class FulfillStarsSubscriptionQuery final : public Td::ResultHandler { Promise promise_; public: explicit FulfillStarsSubscriptionQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(const string &subscription_id) { send_query(G()->net_query_creator().create(telegram_api::payments_fulfillStarsSubscription( telegram_api::make_object(), subscription_id))); } 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()); } promise_.set_value(Unit()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class RefundStarsChargeQuery final : public Td::ResultHandler { Promise promise_; public: explicit RefundStarsChargeQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(telegram_api::object_ptr &&input_user, const string &telegram_payment_charge_id) { send_query(G()->net_query_creator().create( telegram_api::payments_refundStarsCharge(std::move(input_user), telegram_payment_charge_id))); } 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(DEBUG) << "Receive result for RefundStarsChargeQuery: " << 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)); } }; static td_api::object_ptr convert_stars_revenue_status( telegram_api::object_ptr obj) { CHECK(obj != nullptr); int32 next_withdrawal_in = 0; if (obj->withdrawal_enabled_ && obj->next_withdrawal_at_ > 0) { next_withdrawal_in = max(obj->next_withdrawal_at_ - G()->unix_time(), 1); } StarAmount overall_revenue; StarAmount current_balance; StarAmount available_balance; if (obj->overall_revenue_->get_id() != telegram_api::starsAmount::ID || obj->current_balance_->get_id() != telegram_api::starsAmount::ID || obj->available_balance_->get_id() != telegram_api::starsAmount::ID) { LOG(ERROR) << "Receive " << to_string(obj); } else { overall_revenue = StarAmount(telegram_api::move_object_as(obj->overall_revenue_), true); current_balance = StarAmount(telegram_api::move_object_as(obj->current_balance_), true); available_balance = StarAmount(telegram_api::move_object_as(obj->available_balance_), true); } return td_api::make_object( overall_revenue.get_star_amount_object(), current_balance.get_star_amount_object(), available_balance.get_star_amount_object(), obj->withdrawal_enabled_, next_withdrawal_in); } class GetStarsRevenueStatsQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; public: explicit GetStarsRevenueStatsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, bool is_dark) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Have no access to the chat")); } send_query(G()->net_query_creator().create( telegram_api::payments_getStarsRevenueStats(0, is_dark, false, std::move(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 ptr = result_ptr.move_as_ok(); LOG(DEBUG) << "Receive result for GetStarsRevenueStatsQuery: " << to_string(ptr); promise_.set_value(td_api::make_object( StatisticsManager::convert_stats_graph(std::move(ptr->revenue_graph_)), convert_stars_revenue_status(std::move(ptr->status_)), ptr->usd_rate_ > 0 ? clamp(ptr->usd_rate_ * 1e2, 1e-18, 1e18) : 1.3)); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetStarsRevenueStatsQuery"); promise_.set_error(std::move(status)); } }; class GetStarsRevenueWithdrawalUrlQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit GetStarsRevenueWithdrawalUrlQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id, bool is_ton, int64 amount, telegram_api::object_ptr input_check_password) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Have no access to the chat")); } int32 flags = telegram_api::payments_getStarsRevenueWithdrawalUrl::AMOUNT_MASK; send_query(G()->net_query_creator().create(telegram_api::payments_getStarsRevenueWithdrawalUrl( flags, is_ton, std::move(input_peer), amount, std::move(input_check_password)))); } 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()); } promise_.set_value(std::move(result_ptr.ok_ref()->url_)); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetStarsRevenueWithdrawalUrlQuery"); promise_.set_error(std::move(status)); } }; class GetStarsRevenueAdsAccountUrlQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; public: explicit GetStarsRevenueAdsAccountUrlQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(DialogId dialog_id) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Have no access to the chat")); } send_query( G()->net_query_creator().create(telegram_api::payments_getStarsRevenueAdsAccountUrl(std::move(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()); } promise_.set_value(std::move(result_ptr.ok_ref()->url_)); } void on_error(Status status) final { td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetStarsRevenueAdsAccountUrlQuery"); promise_.set_error(std::move(status)); } }; static td_api::object_ptr convert_gram_revenue_status( telegram_api::object_ptr obj) { CHECK(obj != nullptr); if (obj->next_withdrawal_at_ != 0) { LOG(ERROR) << "Receive " << to_string(obj); } TonAmount overall_revenue; TonAmount current_balance; TonAmount available_balance; if (obj->overall_revenue_->get_id() != telegram_api::starsTonAmount::ID || obj->current_balance_->get_id() != telegram_api::starsTonAmount::ID || obj->available_balance_->get_id() != telegram_api::starsTonAmount::ID) { LOG(ERROR) << "Receive " << to_string(obj); } else { overall_revenue = TonAmount(telegram_api::move_object_as(obj->overall_revenue_), true); current_balance = TonAmount(telegram_api::move_object_as(obj->current_balance_), true); available_balance = TonAmount(telegram_api::move_object_as(obj->available_balance_), true); } return td_api::make_object(overall_revenue.get_ton_amount(), current_balance.get_ton_amount(), available_balance.get_ton_amount(), obj->withdrawal_enabled_); } class GetGramRevenueStatsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetGramRevenueStatsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(bool is_dark) { auto input_peer = td_->dialog_manager_->get_input_peer(td_->dialog_manager_->get_my_dialog_id(), AccessRights::Write); CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::payments_getStarsRevenueStats(0, is_dark, true, std::move(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 ptr = result_ptr.move_as_ok(); LOG(DEBUG) << "Receive result for GetGramRevenueStatsQuery: " << to_string(ptr); promise_.set_value(td_api::make_object( StatisticsManager::convert_stats_graph(std::move(ptr->revenue_graph_)), convert_gram_revenue_status(std::move(ptr->status_)), ptr->usd_rate_ > 0 ? clamp(ptr->usd_rate_ * 1e-7, 1e-18, 1e18) : 3e-7)); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetPaidMessageRevenueQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetPaidMessageRevenueQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(telegram_api::object_ptr &&input_user) { send_query(G()->net_query_creator().create( telegram_api::account_getPaidMessagesRevenue(0, nullptr, std::move(input_user)))); } 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(DEBUG) << "Receive result for GetPaidMessageRevenueQuery: " << to_string(ptr); promise_.set_value(td_api::make_object(StarManager::get_star_count(ptr->stars_amount_))); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; StarManager::StarManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { } void StarManager::start_up() { if (td_->auth_manager_->is_bot() || !td_->auth_manager_->is_authorized()) { return; } auto owned_star_count = G()->td_db()->get_binlog_pmc()->get("owned_star_count"); if (!owned_star_count.empty()) { is_owned_star_count_inited_ = true; auto star_count = split(owned_star_count); owned_star_count_ = to_integer(star_count.first); owned_nanostar_count_ = to_integer(star_count.second); sent_star_count_ = owned_star_count_; sent_nanostar_count_ = owned_nanostar_count_; send_closure(G()->td(), &Td::send_update, get_update_owned_star_count_object()); } auto owned_ton_count = G()->td_db()->get_binlog_pmc()->get("owned_ton_count"); if (!owned_ton_count.empty()) { is_owned_ton_count_inited_ = true; owned_ton_count_ = to_integer(owned_ton_count); sent_ton_count_ = owned_ton_count_; send_closure(G()->td(), &Td::send_update, get_update_owned_gram_count_object()); } } void StarManager::tear_down() { parent_.reset(); } td_api::object_ptr StarManager::get_update_owned_star_count_object() const { CHECK(is_owned_star_count_inited_); // sent_star_count_ can be negative as well as owned_star_count_ return td_api::make_object( td_api::make_object(sent_star_count_, sent_nanostar_count_)); } td_api::object_ptr StarManager::get_update_owned_gram_count_object() const { CHECK(is_owned_ton_count_inited_); // sent_ton_count_ can be negative as well as owned_ton_count_ return td_api::make_object(sent_ton_count_); } void StarManager::on_update_owned_star_amount(StarAmount star_amount) { if (td_->auth_manager_->is_bot()) { return; } auto star_count = star_amount.get_star_count(); auto nanostar_count = star_amount.get_nanostar_count(); if (is_owned_star_count_inited_ && star_count == owned_star_count_ && nanostar_count == owned_nanostar_count_) { return; } is_owned_star_count_inited_ = true; owned_star_count_ = star_count; owned_nanostar_count_ = nanostar_count; if (owned_star_count_ + pending_owned_star_count_ != sent_star_count_ || owned_nanostar_count_ != sent_nanostar_count_) { sent_star_count_ = owned_star_count_ + pending_owned_star_count_; sent_nanostar_count_ = owned_nanostar_count_; send_closure(G()->td(), &Td::send_update, get_update_owned_star_count_object()); } G()->td_db()->get_binlog_pmc()->set("owned_star_count", PSTRING() << owned_star_count_ << ' ' << owned_nanostar_count_); } void StarManager::on_update_owned_ton_amount(TonAmount ton_amount) { if (td_->auth_manager_->is_bot()) { return; } auto ton_count = ton_amount.get_ton_amount(); if (is_owned_ton_count_inited_ && ton_count == owned_ton_count_) { return; } is_owned_ton_count_inited_ = true; owned_ton_count_ = ton_count; if (owned_ton_count_ + pending_owned_ton_count_ != sent_ton_count_) { sent_ton_count_ = owned_ton_count_ + pending_owned_ton_count_; send_closure(G()->td(), &Td::send_update, get_update_owned_gram_count_object()); } G()->td_db()->get_binlog_pmc()->set("owned_ton_count", to_string(owned_ton_count_)); } void StarManager::add_pending_owned_star_count(int64 star_count, bool move_to_owned) { if (star_count == 0) { return; } pending_owned_star_count_ += star_count; if (is_owned_star_count_inited_) { if (move_to_owned) { owned_star_count_ -= star_count; G()->td_db()->get_binlog_pmc()->set("owned_star_count", PSTRING() << owned_star_count_ << ' ' << owned_nanostar_count_); } else { sent_star_count_ += star_count; send_closure(G()->td(), &Td::send_update, get_update_owned_star_count_object()); } } } void StarManager::add_pending_owned_ton_count(int64 ton_count, bool move_to_owned) { if (ton_count == 0) { return; } pending_owned_ton_count_ += ton_count; if (is_owned_ton_count_inited_) { if (move_to_owned) { owned_ton_count_ -= ton_count; G()->td_db()->get_binlog_pmc()->set("owned_ton_count", to_string(owned_ton_count_)); } else { sent_ton_count_ += ton_count; send_closure(G()->td(), &Td::send_update, get_update_owned_gram_count_object()); } } } void StarManager::add_pending_owned_amount(const StarGiftResalePrice &amount, int32 multiplier, bool move_to_owned) { if (amount.is_star()) { return add_pending_owned_star_count(multiplier * amount.get_star_count(), move_to_owned); } if (amount.is_ton()) { return add_pending_owned_ton_count(multiplier * amount.get_ton_count(), move_to_owned); } UNREACHABLE(); } bool StarManager::has_owned_star_count(int64 star_count) const { if (star_count <= 0 || !is_owned_star_count_inited_) { return true; } return sent_star_count_ >= star_count; } bool StarManager::has_owned_ton_count(int64 ton_count) const { if (ton_count <= 0 || !is_owned_ton_count_inited_) { return true; } return sent_ton_count_ >= ton_count; } bool StarManager::has_owned_amount(const StarGiftResalePrice &amount) const { if (amount.is_star()) { return has_owned_star_count(amount.get_star_count()); } if (amount.is_ton()) { return has_owned_ton_count(amount.get_ton_count()); } return false; } Status StarManager::can_manage_stars(DialogId dialog_id, bool allow_self) const { switch (dialog_id.get_type()) { case DialogType::User: { auto user_id = dialog_id.get_user_id(); if (allow_self && user_id == td_->user_manager_->get_my_id()) { break; } TRY_RESULT(bot_data, td_->user_manager_->get_bot_data(user_id)); if (!bot_data.can_be_edited) { return Status::Error(400, "The bot isn't owned"); } break; } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); if (!td_->chat_manager_->get_channel_permissions(channel_id).is_creator() && !allow_self) { return Status::Error(400, "Not enough rights"); } break; } default: return Status::Error(400, "Unallowed chat specified"); } return Status::OK(); } void StarManager::get_star_payment_options(Promise> &&promise) { td_->create_handler(std::move(promise))->send(); } void StarManager::get_star_gift_payment_options(UserId user_id, Promise> &&promise) { if (user_id == UserId()) { td_->create_handler(std::move(promise))->send(nullptr); return; } TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); td_->create_handler(std::move(promise))->send(std::move(input_user)); } void StarManager::get_star_giveaway_payment_options( Promise> &&promise) { td_->create_handler(std::move(promise))->send(); } void StarManager::get_star_transactions(td_api::object_ptr owner_id, const string &subscription_id, const string &offset, int32 limit, td_api::object_ptr &&direction, Promise> &&promise) { TRY_RESULT_PROMISE(promise, dialog_id, get_message_sender_dialog_id(td_, owner_id, true, false)); TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id, true)); if (limit < 0) { return promise.set_error(400, "Limit must be non-negative"); } td_->stickers_manager_->load_premium_gift_sticker_set(PromiseCreator::lambda( [actor_id = actor_id(this), dialog_id, subscription_id, offset, limit, direction = std::move(direction), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StarManager::do_get_star_transactions, dialog_id, subscription_id, offset, limit, std::move(direction), std::move(promise)); } })); } void StarManager::do_get_star_transactions(DialogId dialog_id, const string &subscription_id, const string &offset, int32 limit, td_api::object_ptr &&direction, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id, true)); td_->create_handler(std::move(promise)) ->send(dialog_id, subscription_id, offset, limit, std::move(direction)); } void StarManager::get_ton_transactions(const string &offset, int32 limit, td_api::object_ptr &&direction, Promise> &&promise) { if (limit < 0) { return promise.set_error(400, "Limit must be non-negative"); } td_->stickers_manager_->load_ton_gift_sticker_set( PromiseCreator::lambda([actor_id = actor_id(this), offset, limit, direction = std::move(direction), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StarManager::do_get_ton_transactions, offset, limit, std::move(direction), std::move(promise)); } })); } void StarManager::do_get_ton_transactions(const string &offset, int32 limit, td_api::object_ptr &&direction, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); td_->create_handler(std::move(promise))->send(offset, limit, std::move(direction)); } void StarManager::get_star_subscriptions(bool only_expiring, const string &offset, Promise> &&promise) { td_->create_handler(std::move(promise))->send(only_expiring, offset); } void StarManager::edit_star_subscription(const string &subscription_id, bool is_canceled, Promise &&promise) { td_->create_handler(std::move(promise))->send(subscription_id, is_canceled); } void StarManager::edit_user_star_subscription(UserId user_id, const string &telegram_payment_charge_id, bool is_canceled, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_user), telegram_payment_charge_id, is_canceled); } void StarManager::reuse_star_subscription(const string &subscription_id, Promise &&promise) { td_->create_handler(std::move(promise))->send(subscription_id); } void StarManager::refund_star_payment(UserId user_id, const string &telegram_payment_charge_id, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_user), telegram_payment_charge_id); } void StarManager::get_star_revenue_statistics(const td_api::object_ptr &owner_id, bool is_dark, Promise> &&promise) { TRY_RESULT_PROMISE(promise, dialog_id, get_message_sender_dialog_id(td_, owner_id, true, false)); TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id, true)); td_->create_handler(std::move(promise))->send(dialog_id, is_dark); } void StarManager::get_withdrawal_url(DialogId dialog_id, bool is_ton, int64 amount, const string &password, Promise &&promise) { if (password.empty()) { return promise.set_error(400, "PASSWORD_HASH_INVALID"); } send_closure( td_->password_manager_, &PasswordManager::get_input_check_password_srp, password, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, is_ton, amount, promise = std::move(promise)]( Result> result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &StarManager::send_get_withdrawal_url_query, dialog_id, is_ton, amount, result.move_as_ok(), std::move(promise)); })); } void StarManager::send_get_withdrawal_url_query( DialogId dialog_id, bool is_ton, int64 amount, telegram_api::object_ptr input_check_password, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); td_->create_handler(std::move(promise)) ->send(dialog_id, is_ton, amount, std::move(input_check_password)); } void StarManager::get_star_withdrawal_url(const td_api::object_ptr &owner_id, int64 star_count, const string &password, Promise &&promise) { TRY_RESULT_PROMISE(promise, dialog_id, get_message_sender_dialog_id(td_, owner_id, true, false)); TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id, true)); get_withdrawal_url(dialog_id, false, star_count, password, std::move(promise)); } void StarManager::get_ton_revenue_statistics(bool is_dark, Promise> &&promise) { td_->create_handler(std::move(promise))->send(is_dark); } void StarManager::get_ton_withdrawal_url(const string &password, Promise &&promise) { get_withdrawal_url(td_->dialog_manager_->get_my_dialog_id(), true, 0, password, std::move(promise)); } void StarManager::get_star_ad_account_url(const td_api::object_ptr &owner_id, Promise &&promise) { TRY_RESULT_PROMISE(promise, dialog_id, get_message_sender_dialog_id(td_, owner_id, true, false)); TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id)); td_->create_handler(std::move(promise))->send(dialog_id); } void StarManager::get_paid_message_revenue(UserId user_id, Promise> &&promise) { TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); td_->create_handler(std::move(promise))->send(std::move(input_user)); } void StarManager::reload_star_transaction(DialogId dialog_id, const string &transaction_id, bool is_refund, Promise &&promise) { TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id, true)); auto query_promise = PromiseCreator::lambda( [promise = std::move(promise)](Result> r_transactions) mutable { if (r_transactions.is_error()) { promise.set_error(r_transactions.move_as_error()); } else { promise.set_value(Unit()); } }); td_->create_handler(std::move(query_promise))->send(dialog_id, transaction_id, is_refund); } void StarManager::reload_owned_star_count() { do_get_star_transactions(td_->dialog_manager_->get_my_dialog_id(), string(), string(), 1, nullptr, Auto()); } void StarManager::reload_owned_ton_count() { do_get_ton_transactions(string(), 1, nullptr, Auto()); } void StarManager::on_update_stars_revenue_status( telegram_api::object_ptr &&update) { DialogId dialog_id(update->peer_); if (can_manage_stars(dialog_id, true).is_error()) { LOG(ERROR) << "Receive " << to_string(update); return; } if (update->status_->overall_revenue_->get_id() == telegram_api::starsTonAmount::ID) { if (dialog_id != td_->dialog_manager_->get_my_dialog_id()) { LOG(ERROR) << "Receive " << to_string(update); } else { send_closure(G()->td(), &Td::send_update, td_api::make_object( convert_gram_revenue_status(std::move(update->status_)))); } return; } send_closure(G()->td(), &Td::send_update, td_api::make_object( get_message_sender_object(td_, dialog_id, "updateStarRevenueStatus"), convert_stars_revenue_status(std::move(update->status_)))); } FileSourceId StarManager::get_star_transaction_file_source_id(DialogId dialog_id, const string &transaction_id, bool is_refund) { if (!dialog_id.is_valid() || transaction_id.empty()) { return FileSourceId(); } auto &source_id = star_transaction_file_source_ids_[is_refund][dialog_id][transaction_id]; if (!source_id.is_valid()) { source_id = td_->file_reference_manager_->create_star_transaction_file_source(dialog_id, transaction_id, is_refund); } VLOG(file_references) << "Return " << source_id << " for " << (is_refund ? "refund " : "") << "transaction " << transaction_id << " in " << dialog_id; return source_id; } int64 StarManager::get_star_count(int64 amount, bool allow_negative) { auto max_amount = static_cast(1) << 51; if (amount < 0) { if (!allow_negative) { LOG(ERROR) << "Receive Telegram Star amount = " << amount; return 0; } if (amount < -max_amount) { LOG(ERROR) << "Receive Telegram Star amount = " << amount; return -max_amount; } } if (amount > max_amount) { LOG(ERROR) << "Receive Telegram Star amount = " << amount; return max_amount; } return amount; } int32 StarManager::get_nanostar_count(int64 &star_count, int32 nanostar_count) { if (-1000000000 >= nanostar_count || nanostar_count >= 1000000000) { LOG(ERROR) << "Receive " << star_count << " + " << nanostar_count << " Telegram Stars"; return nanostar_count; } if (star_count < 0 && nanostar_count > 0) { LOG(ERROR) << "Receive " << star_count << " + " << nanostar_count << " Telegram Stars"; star_count++; nanostar_count -= 1000000000; } if (star_count > 0 && nanostar_count < 0) { LOG(ERROR) << "Receive " << star_count << " + " << nanostar_count << " Telegram Stars"; star_count--; nanostar_count += 1000000000; } if (!(star_count < 0 && nanostar_count > 0) && !(star_count > 0 && nanostar_count < 0)) { return nanostar_count; } LOG(ERROR) << "Receive " << star_count << " + " << nanostar_count << " Telegram Stars"; return 0; } int32 StarManager::get_months_by_star_count(int64 star_count) { return star_count <= 1000 ? 3 : (star_count < 2500 ? 6 : 12); } string StarManager::get_unused_star_transaction_field( const telegram_api::object_ptr &transaction, const td_api::object_ptr &product_info, const string &bot_payload, const td_api::object_ptr &affiliate, int32 commission_per_mille) { if (product_info != nullptr) { return "product info"; } if (!bot_payload.empty()) { return "bot payload"; } if (affiliate != nullptr) { return "affiliate"; } if (commission_per_mille != 0) { return "commission"; } if (!transaction->title_.empty()) { return "title"; } if (!transaction->description_.empty()) { return "description"; } if (transaction->photo_ != nullptr) { return "photo"; } if (!transaction->bot_payload_.empty()) { return "bot payload"; } if (transaction->transaction_date_ || !transaction->transaction_url_.empty() || transaction->pending_ || transaction->failed_) { return "withdrawal state"; } if (transaction->msg_id_ != 0) { return "message identifier"; } if (transaction->gift_) { return "gift"; } if (transaction->subscription_period_ != 0) { return "subscription period"; } if (transaction->reaction_) { return "reaction"; } if (!transaction->extended_media_.empty()) { return "paid media"; } if (transaction->giveaway_post_id_ != 0) { return "giveaway message"; } if (transaction->stargift_ != nullptr) { return "gift"; } if (transaction->floodskip_number_ != 0) { return "API payment"; } if (transaction->starref_peer_ != nullptr) { return "affiliate info"; } if (transaction->starref_amount_ != nullptr) { return "affiliate payment info"; } if (transaction->starref_commission_permille_ != 0) { return "affiliate commission info"; } if (transaction->stargift_upgrade_) { return "gift upgrade"; } if (transaction->paid_messages_) { return "paid messages"; } if (transaction->premium_gift_months_) { return "Telegram Premium purchase"; } if (transaction->business_transfer_) { return "business bot transfer"; } if (transaction->stargift_resale_) { return "gift resale"; } if (transaction->ads_proceeds_from_date_ != 0 || transaction->ads_proceeds_to_date_ != 0) { return "ads proceeds"; } if (transaction->posts_search_) { return "post search"; } if (transaction->stargift_prepaid_upgrade_) { return "prepaid gift upgrade"; } if (transaction->stargift_drop_original_details_) { return "gift original details drop"; } if (transaction->phonegroup_message_) { return "live story message"; } if (transaction->stargift_auction_bid_) { return "gift auction bid"; } if (transaction->offer_) { return "gift purchase offer"; } return string(); } void StarManager::get_current_state(vector> &updates) const { if (is_owned_star_count_inited_) { updates.push_back(get_update_owned_star_count_object()); } if (is_owned_ton_count_inited_) { updates.push_back(get_update_owned_gram_count_object()); } } } // namespace td