2022-05-24 00:21:03 +02:00
|
|
|
//
|
2024-01-01 01:07:21 +01:00
|
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
2022-05-24 00:21:03 +02:00
|
|
|
//
|
|
|
|
// 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"
|
|
|
|
|
2023-10-30 17:04:14 +01:00
|
|
|
#include "td/telegram/AccessRights.h"
|
2022-06-06 17:50:50 +02:00
|
|
|
#include "td/telegram/AnimationsManager.h"
|
2022-05-24 14:42:46 +02:00
|
|
|
#include "td/telegram/Application.h"
|
2023-10-30 17:04:14 +01:00
|
|
|
#include "td/telegram/ChannelId.h"
|
2022-06-08 14:50:11 +02:00
|
|
|
#include "td/telegram/ContactsManager.h"
|
2022-06-02 15:19:16 +02:00
|
|
|
#include "td/telegram/DialogId.h"
|
2024-01-03 21:07:50 +01:00
|
|
|
#include "td/telegram/DialogManager.h"
|
2022-06-02 15:19:16 +02:00
|
|
|
#include "td/telegram/Document.h"
|
2022-06-01 14:14:09 +02:00
|
|
|
#include "td/telegram/DocumentsManager.h"
|
2023-10-12 16:05:03 +02:00
|
|
|
#include "td/telegram/GiveawayParameters.h"
|
2022-05-24 00:36:55 +02:00
|
|
|
#include "td/telegram/Global.h"
|
2022-06-01 14:14:09 +02:00
|
|
|
#include "td/telegram/MessageEntity.h"
|
2023-10-30 17:04:14 +01:00
|
|
|
#include "td/telegram/MessageId.h"
|
2023-09-28 21:21:25 +02:00
|
|
|
#include "td/telegram/MessageSender.h"
|
2023-09-28 16:54:03 +02:00
|
|
|
#include "td/telegram/MessagesManager.h"
|
2022-09-22 18:33:43 +02:00
|
|
|
#include "td/telegram/misc.h"
|
2022-10-12 14:59:58 +02:00
|
|
|
#include "td/telegram/PremiumGiftOption.h"
|
2023-10-06 17:28:02 +02:00
|
|
|
#include "td/telegram/ServerMessageId.h"
|
2023-01-23 14:03:32 +01:00
|
|
|
#include "td/telegram/SuggestedAction.h"
|
2022-06-01 14:14:09 +02:00
|
|
|
#include "td/telegram/Td.h"
|
2022-06-02 15:19:16 +02:00
|
|
|
#include "td/telegram/telegram_api.h"
|
2022-06-24 16:27:03 +02:00
|
|
|
#include "td/telegram/UpdatesManager.h"
|
2022-10-12 14:59:58 +02:00
|
|
|
#include "td/telegram/UserId.h"
|
2022-05-24 00:36:55 +02:00
|
|
|
|
2022-05-24 01:55:03 +02:00
|
|
|
#include "td/utils/algorithm.h"
|
2022-06-01 14:14:09 +02:00
|
|
|
#include "td/utils/buffer.h"
|
2022-07-01 13:33:59 +02:00
|
|
|
#include "td/utils/JsonBuilder.h"
|
2022-06-02 15:19:16 +02:00
|
|
|
#include "td/utils/logging.h"
|
|
|
|
#include "td/utils/misc.h"
|
2022-05-24 00:36:55 +02:00
|
|
|
#include "td/utils/SliceBuilder.h"
|
2022-06-02 15:19:16 +02:00
|
|
|
#include "td/utils/Status.h"
|
2022-05-24 00:36:55 +02:00
|
|
|
|
2022-05-24 00:21:03 +02:00
|
|
|
namespace td {
|
|
|
|
|
2022-06-01 14:14:09 +02:00
|
|
|
static td_api::object_ptr<td_api::PremiumFeature> get_premium_feature_object(Slice premium_feature) {
|
|
|
|
if (premium_feature == "double_limits") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureIncreasedLimits>();
|
|
|
|
}
|
|
|
|
if (premium_feature == "more_upload") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureIncreasedUploadFileSize>();
|
|
|
|
}
|
|
|
|
if (premium_feature == "faster_download") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureImprovedDownloadSpeed>();
|
|
|
|
}
|
|
|
|
if (premium_feature == "voice_to_text") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureVoiceRecognition>();
|
|
|
|
}
|
|
|
|
if (premium_feature == "no_ads") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureDisabledAds>();
|
|
|
|
}
|
2022-09-06 22:47:11 +02:00
|
|
|
if (premium_feature == "unique_reactions" || premium_feature == "infinite_reactions") {
|
2022-06-01 14:14:09 +02:00
|
|
|
return td_api::make_object<td_api::premiumFeatureUniqueReactions>();
|
|
|
|
}
|
|
|
|
if (premium_feature == "premium_stickers") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureUniqueStickers>();
|
|
|
|
}
|
2022-07-16 13:33:54 +02:00
|
|
|
if (premium_feature == "animated_emoji") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureCustomEmoji>();
|
|
|
|
}
|
2022-06-01 14:14:09 +02:00
|
|
|
if (premium_feature == "advanced_chat_management") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureAdvancedChatManagement>();
|
|
|
|
}
|
|
|
|
if (premium_feature == "profile_badge") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureProfileBadge>();
|
|
|
|
}
|
2022-09-06 22:47:11 +02:00
|
|
|
if (premium_feature == "emoji_status") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureEmojiStatus>();
|
|
|
|
}
|
2022-06-01 14:14:09 +02:00
|
|
|
if (premium_feature == "animated_userpics") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureAnimatedProfilePhoto>();
|
|
|
|
}
|
2022-10-25 11:46:07 +02:00
|
|
|
if (premium_feature == "forum_topic_icon") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureForumTopicIcon>();
|
|
|
|
}
|
2022-06-01 16:19:05 +02:00
|
|
|
if (premium_feature == "app_icons") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureAppIcons>();
|
|
|
|
}
|
2023-01-25 19:26:09 +01:00
|
|
|
if (premium_feature == "translations") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureRealTimeChatTranslation>();
|
|
|
|
}
|
2023-07-30 11:42:24 +02:00
|
|
|
if (premium_feature == "stories") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureUpgradedStories>();
|
|
|
|
}
|
2023-09-18 18:57:48 +02:00
|
|
|
if (premium_feature == "channel_boost") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureChatBoost>();
|
|
|
|
}
|
2023-11-29 11:33:14 +01:00
|
|
|
if (premium_feature == "peer_colors") {
|
2023-10-29 02:20:29 +02:00
|
|
|
return td_api::make_object<td_api::premiumFeatureAccentColor>();
|
|
|
|
}
|
2023-11-29 11:33:14 +01:00
|
|
|
if (premium_feature == "wallpapers") {
|
2023-11-28 12:11:46 +01:00
|
|
|
return td_api::make_object<td_api::premiumFeatureBackgroundForBoth>();
|
|
|
|
}
|
2024-01-30 01:11:23 +01:00
|
|
|
if (premium_feature == "saved_tags") {
|
|
|
|
return td_api::make_object<td_api::premiumFeatureSavedMessagesTags>();
|
|
|
|
}
|
2022-06-01 14:14:09 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2023-10-03 17:27:56 +02:00
|
|
|
Result<telegram_api::object_ptr<telegram_api::InputPeer>> get_boost_input_peer(Td *td, DialogId dialog_id) {
|
2023-09-28 20:09:49 +02:00
|
|
|
if (dialog_id == DialogId()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2024-01-04 13:26:42 +01:00
|
|
|
if (!td->dialog_manager_->have_dialog_force(dialog_id, "get_boost_input_peer")) {
|
2023-09-28 20:09:49 +02:00
|
|
|
return Status::Error(400, "Chat to boost not found");
|
|
|
|
}
|
2024-02-09 12:54:57 +01:00
|
|
|
if (dialog_id.get_type() != DialogType::Channel) {
|
2023-09-28 20:26:33 +02:00
|
|
|
return Status::Error(400, "Can't boost the chat");
|
2023-09-28 20:09:49 +02:00
|
|
|
}
|
|
|
|
if (!td->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_administrator()) {
|
|
|
|
return Status::Error(400, "Not enough rights in the chat");
|
|
|
|
}
|
2024-01-03 21:07:50 +01:00
|
|
|
auto boost_input_peer = td->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write);
|
2023-09-28 20:09:49 +02:00
|
|
|
CHECK(boost_input_peer != nullptr);
|
|
|
|
return std::move(boost_input_peer);
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:27:59 +02:00
|
|
|
static Result<tl_object_ptr<telegram_api::InputStorePaymentPurpose>> get_input_store_payment_purpose(
|
|
|
|
Td *td, const td_api::object_ptr<td_api::StorePaymentPurpose> &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<const td_api::storePaymentPurposePremiumSubscription *>(purpose.get());
|
|
|
|
int32 flags = 0;
|
|
|
|
if (p->is_restore_) {
|
|
|
|
flags |= telegram_api::inputStorePaymentPremiumSubscription::RESTORE_MASK;
|
|
|
|
}
|
2023-01-19 14:49:57 +01:00
|
|
|
if (p->is_upgrade_) {
|
|
|
|
flags |= telegram_api::inputStorePaymentPremiumSubscription::UPGRADE_MASK;
|
|
|
|
}
|
2023-01-06 10:42:16 +01:00
|
|
|
return make_tl_object<telegram_api::inputStorePaymentPremiumSubscription>(flags, false /*ignored*/,
|
|
|
|
false /*ignored*/);
|
2022-07-01 18:27:59 +02:00
|
|
|
}
|
|
|
|
case td_api::storePaymentPurposeGiftedPremium::ID: {
|
|
|
|
auto p = static_cast<const td_api::storePaymentPurposeGiftedPremium *>(purpose.get());
|
|
|
|
UserId user_id(p->user_id_);
|
|
|
|
TRY_RESULT(input_user, td->contacts_manager_->get_input_user(user_id));
|
2022-07-30 02:58:46 +02:00
|
|
|
if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) {
|
|
|
|
return Status::Error(400, "Invalid amount of the currency specified");
|
|
|
|
}
|
2022-07-01 18:27:59 +02:00
|
|
|
return make_tl_object<telegram_api::inputStorePaymentGiftPremium>(std::move(input_user), p->currency_,
|
|
|
|
p->amount_);
|
|
|
|
}
|
2023-09-28 16:54:03 +02:00
|
|
|
case td_api::storePaymentPurposePremiumGiftCodes::ID: {
|
|
|
|
auto p = static_cast<const td_api::storePaymentPurposePremiumGiftCodes *>(purpose.get());
|
|
|
|
vector<telegram_api::object_ptr<telegram_api::InputUser>> input_users;
|
|
|
|
for (auto user_id : p->user_ids_) {
|
|
|
|
TRY_RESULT(input_user, td->contacts_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");
|
|
|
|
}
|
2023-09-29 14:52:23 +02:00
|
|
|
DialogId boosted_dialog_id(p->boosted_chat_id_);
|
|
|
|
TRY_RESULT(boost_input_peer, get_boost_input_peer(td, boosted_dialog_id));
|
2023-09-28 16:54:03 +02:00
|
|
|
int32 flags = 0;
|
|
|
|
if (boost_input_peer != nullptr) {
|
|
|
|
flags |= telegram_api::inputStorePaymentPremiumGiftCode::BOOST_PEER_MASK;
|
|
|
|
}
|
|
|
|
return telegram_api::make_object<telegram_api::inputStorePaymentPremiumGiftCode>(
|
|
|
|
flags, std::move(input_users), std::move(boost_input_peer), p->currency_, p->amount_);
|
|
|
|
}
|
2023-09-29 14:52:23 +02:00
|
|
|
case td_api::storePaymentPurposePremiumGiveaway::ID: {
|
|
|
|
auto p = static_cast<const td_api::storePaymentPurposePremiumGiveaway *>(purpose.get());
|
|
|
|
if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) {
|
|
|
|
return Status::Error(400, "Invalid amount of the currency specified");
|
|
|
|
}
|
2023-10-12 16:05:03 +02:00
|
|
|
TRY_RESULT(parameters, GiveawayParameters::get_giveaway_parameters(td, p->parameters_.get()));
|
|
|
|
return parameters.get_input_store_payment_premium_giveaway(td, p->currency_, p->amount_);
|
2023-09-29 14:52:23 +02:00
|
|
|
}
|
2022-07-01 18:27:59 +02:00
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-01 14:14:09 +02:00
|
|
|
class GetPremiumPromoQuery final : public Td::ResultHandler {
|
|
|
|
Promise<td_api::object_ptr<td_api::premiumState>> promise_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit GetPremiumPromoQuery(Promise<td_api::object_ptr<td_api::premiumState>> &&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<telegram_api::help_getPremiumPromo>(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);
|
|
|
|
|
2022-06-08 14:50:11 +02:00
|
|
|
td_->contacts_manager_->on_get_users(std::move(promo->users_), "GetPremiumPromoQuery");
|
|
|
|
|
2022-06-01 14:14:09 +02:00
|
|
|
auto state = get_message_text(td_->contacts_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"));
|
|
|
|
}
|
|
|
|
|
2022-06-06 17:50:50 +02:00
|
|
|
vector<td_api::object_ptr<td_api::premiumFeaturePromotionAnimation>> animations;
|
2022-06-01 14:14:09 +02:00
|
|
|
for (size_t i = 0; i < promo->video_sections_.size(); i++) {
|
|
|
|
auto feature = get_premium_feature_object(promo->video_sections_[i]);
|
|
|
|
if (feature == nullptr) {
|
|
|
|
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<telegram_api::document>(video),
|
2022-06-06 17:50:50 +02:00
|
|
|
DialogId(), nullptr, Document::Type::Animation);
|
2022-06-01 14:14:09 +02:00
|
|
|
|
2022-06-06 17:50:50 +02:00
|
|
|
if (parsed_document.type != Document::Type::Animation) {
|
2022-06-01 14:14:09 +02:00
|
|
|
LOG(ERROR) << "Receive " << parsed_document.type << " for " << promo->video_sections_[i];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-06-06 17:50:50 +02:00
|
|
|
auto animation_object = td_->animations_manager_->get_animation_object(parsed_document.file_id);
|
|
|
|
animations.push_back(td_api::make_object<td_api::premiumFeaturePromotionAnimation>(std::move(feature),
|
|
|
|
std::move(animation_object)));
|
2022-06-01 14:14:09 +02:00
|
|
|
}
|
|
|
|
|
2022-08-26 17:03:56 +02:00
|
|
|
auto period_options = get_premium_gift_options(std::move(promo->period_options_));
|
2023-01-19 22:41:18 +01:00
|
|
|
promise_.set_value(td_api::make_object<td_api::premiumState>(
|
|
|
|
get_formatted_text_object(state, true, 0), get_premium_state_payment_options_object(period_options),
|
|
|
|
std::move(animations)));
|
2022-06-01 14:14:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void on_error(Status status) final {
|
|
|
|
promise_.set_error(std::move(status));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-09-28 20:26:33 +02:00
|
|
|
class GetPremiumGiftCodeOptionsQuery final : public Td::ResultHandler {
|
|
|
|
Promise<td_api::object_ptr<td_api::premiumGiftCodePaymentOptions>> promise_;
|
|
|
|
DialogId boosted_dialog_id_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit GetPremiumGiftCodeOptionsQuery(Promise<td_api::object_ptr<td_api::premiumGiftCodePaymentOptions>> &&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<telegram_api::payments_getPremiumGiftCodeOptions>(packet);
|
|
|
|
if (result_ptr.is_error()) {
|
|
|
|
return on_error(result_ptr.move_as_error());
|
|
|
|
}
|
|
|
|
|
|
|
|
auto results = result_ptr.move_as_ok();
|
|
|
|
vector<td_api::object_ptr<td_api::premiumGiftCodePaymentOption>> 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<td_api::premiumGiftCodePaymentOption>(
|
|
|
|
result->currency_, result->amount_, result->users_, result->months_, result->store_product_,
|
|
|
|
result->store_quantity_));
|
|
|
|
}
|
|
|
|
|
|
|
|
promise_.set_value(td_api::make_object<td_api::premiumGiftCodePaymentOptions>(std::move(options)));
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:21:25 +02:00
|
|
|
void on_error(Status status) final {
|
2024-01-03 21:07:50 +01:00
|
|
|
td_->dialog_manager_->on_get_dialog_error(boosted_dialog_id_, status, "GetPremiumGiftCodeOptionsQuery");
|
2023-09-28 21:21:25 +02:00
|
|
|
promise_.set_error(std::move(status));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class CheckGiftCodeQuery final : public Td::ResultHandler {
|
|
|
|
Promise<td_api::object_ptr<td_api::premiumGiftCodeInfo>> promise_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit CheckGiftCodeQuery(Promise<td_api::object_ptr<td_api::premiumGiftCodeInfo>> &&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<telegram_api::payments_checkGiftCode>(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_->contacts_manager_->on_get_users(std::move(result->users_), "CheckGiftCodeQuery");
|
|
|
|
td_->contacts_manager_->on_get_chats(std::move(result->chats_), "CheckGiftCodeQuery");
|
|
|
|
|
2023-12-05 10:30:12 +01:00
|
|
|
if (result->date_ <= 0 || result->months_ <= 0 || result->used_date_ < 0) {
|
2023-09-28 21:21:25 +02:00
|
|
|
LOG(ERROR) << "Receive " << to_string(result);
|
|
|
|
return on_error(Status::Error(500, "Receive invalid response"));
|
|
|
|
}
|
2023-12-05 10:30:12 +01:00
|
|
|
|
|
|
|
DialogId creator_dialog_id;
|
|
|
|
if (result->from_id_ != nullptr) {
|
|
|
|
creator_dialog_id = DialogId(result->from_id_);
|
|
|
|
if (!creator_dialog_id.is_valid() ||
|
2024-01-03 21:07:50 +01:00
|
|
|
!td_->dialog_manager_->have_dialog_info_force(creator_dialog_id, "CheckGiftCodeQuery")) {
|
2023-12-05 10:30:12 +01:00
|
|
|
LOG(ERROR) << "Receive " << to_string(result);
|
|
|
|
return on_error(Status::Error(500, "Receive invalid response"));
|
|
|
|
}
|
|
|
|
if (creator_dialog_id.get_type() != DialogType::User) {
|
2024-01-04 13:38:01 +01:00
|
|
|
td_->dialog_manager_->force_create_dialog(creator_dialog_id, "CheckGiftCodeQuery", true);
|
2023-12-05 10:30:12 +01:00
|
|
|
}
|
2023-09-28 21:21:25 +02:00
|
|
|
}
|
|
|
|
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();
|
|
|
|
}
|
2023-11-14 12:45:38 +01:00
|
|
|
if (message_id != MessageId() && creator_dialog_id.get_type() != DialogType::Channel) {
|
|
|
|
LOG(ERROR) << "Receive " << to_string(result);
|
|
|
|
message_id = MessageId();
|
|
|
|
}
|
2023-09-28 21:21:25 +02:00
|
|
|
promise_.set_value(td_api::make_object<td_api::premiumGiftCodeInfo>(
|
2023-12-05 10:30:12 +01:00
|
|
|
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_->contacts_manager_->get_user_id_object(user_id, "premiumGiftCodeInfo"), result->used_date_));
|
2023-09-28 21:21:25 +02:00
|
|
|
}
|
|
|
|
|
2023-09-28 20:26:33 +02:00
|
|
|
void on_error(Status status) final {
|
|
|
|
promise_.set_error(std::move(status));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-09-28 21:35:57 +02:00
|
|
|
class ApplyGiftCodeQuery final : public Td::ResultHandler {
|
|
|
|
Promise<Unit> promise_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit ApplyGiftCodeQuery(Promise<Unit> &&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<telegram_api::payments_applyGiftCode>(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));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-10-12 17:19:08 +02:00
|
|
|
class LaunchPrepaidGiveawayQuery final : public Td::ResultHandler {
|
|
|
|
Promise<Unit> promise_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit LaunchPrepaidGiveawayQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void send(int64 giveaway_id, const GiveawayParameters ¶meters) {
|
|
|
|
auto dialog_id = parameters.get_boosted_dialog_id();
|
2024-01-03 21:07:50 +01:00
|
|
|
auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write);
|
2023-10-12 17:19:08 +02:00
|
|
|
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<telegram_api::payments_launchPrepaidGiveaway>(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));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-10-06 17:28:02 +02:00
|
|
|
class GetGiveawayInfoQuery final : public Td::ResultHandler {
|
|
|
|
Promise<td_api::object_ptr<td_api::PremiumGiveawayInfo>> promise_;
|
|
|
|
DialogId dialog_id_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit GetGiveawayInfoQuery(Promise<td_api::object_ptr<td_api::PremiumGiveawayInfo>> &&promise)
|
|
|
|
: promise_(std::move(promise)) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void send(DialogId dialog_id, ServerMessageId server_message_id) {
|
|
|
|
dialog_id_ = dialog_id;
|
2024-01-03 21:07:50 +01:00
|
|
|
auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read);
|
2023-10-06 17:28:02 +02:00
|
|
|
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<telegram_api::payments_getGiveawayInfo>(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<telegram_api::payments_giveawayInfo>(ptr);
|
|
|
|
auto status = [&]() -> td_api::object_ptr<td_api::PremiumGiveawayParticipantStatus> {
|
|
|
|
if (info->joined_too_early_date_ > 0) {
|
|
|
|
return td_api::make_object<td_api::premiumGiveawayParticipantStatusAlreadyWasMember>(
|
|
|
|
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_->contacts_manager_->have_channel_force(channel_id, "GetGiveawayInfoQuery")) {
|
|
|
|
LOG(ERROR) << "Receive " << to_string(info);
|
|
|
|
} else {
|
|
|
|
DialogId dialog_id(channel_id);
|
2024-01-04 13:38:01 +01:00
|
|
|
td_->dialog_manager_->force_create_dialog(dialog_id, "GetGiveawayInfoQuery");
|
2023-10-06 17:28:02 +02:00
|
|
|
return td_api::make_object<td_api::premiumGiveawayParticipantStatusAdministrator>(
|
2024-01-04 14:13:20 +01:00
|
|
|
td_->dialog_manager_->get_chat_id_object(dialog_id, "premiumGiveawayParticipantStatusAdministrator"));
|
2023-10-06 17:28:02 +02:00
|
|
|
}
|
|
|
|
}
|
2023-10-13 10:48:10 +02:00
|
|
|
if (!info->disallowed_country_.empty()) {
|
|
|
|
return td_api::make_object<td_api::premiumGiveawayParticipantStatusDisallowedCountry>(
|
|
|
|
info->disallowed_country_);
|
|
|
|
}
|
2023-10-06 17:28:02 +02:00
|
|
|
if (info->participating_) {
|
|
|
|
return td_api::make_object<td_api::premiumGiveawayParticipantStatusParticipating>();
|
|
|
|
}
|
|
|
|
return td_api::make_object<td_api::premiumGiveawayParticipantStatusEligible>();
|
|
|
|
}();
|
2023-10-13 10:57:43 +02:00
|
|
|
promise_.set_value(td_api::make_object<td_api::premiumGiveawayInfoOngoing>(
|
|
|
|
max(0, info->start_date_), std::move(status), info->preparing_results_));
|
2023-10-06 17:28:02 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case telegram_api::payments_giveawayInfoResults::ID: {
|
|
|
|
auto info = telegram_api::move_object_as<telegram_api::payments_giveawayInfoResults>(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<td_api::premiumGiveawayInfoCompleted>(
|
2023-10-13 10:57:43 +02:00
|
|
|
max(0, info->start_date_), max(0, info->finish_date_), info->refunded_, winner_count, activated_count,
|
|
|
|
info->gift_code_slug_));
|
2023-10-06 17:28:02 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void on_error(Status status) final {
|
2024-01-03 21:07:50 +01:00
|
|
|
td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetGiveawayInfoQuery");
|
2023-10-06 17:28:02 +02:00
|
|
|
promise_.set_error(std::move(status));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-06-24 16:07:10 +02:00
|
|
|
class CanPurchasePremiumQuery final : public Td::ResultHandler {
|
|
|
|
Promise<Unit> promise_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit CanPurchasePremiumQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
|
|
}
|
|
|
|
|
2022-07-15 13:09:37 +02:00
|
|
|
void send(td_api::object_ptr<td_api::StorePaymentPurpose> &&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())));
|
2022-06-24 16:07:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
|
|
auto result_ptr = fetch_result<telegram_api::payments_canPurchasePremium>(packet);
|
|
|
|
if (result_ptr.is_error()) {
|
|
|
|
return on_error(result_ptr.move_as_error());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool result = result_ptr.ok();
|
2023-01-18 15:10:08 +01:00
|
|
|
if (!result) {
|
|
|
|
return on_error(Status::Error(400, "Premium can't be purchased"));
|
2022-06-24 16:07:10 +02:00
|
|
|
}
|
2023-01-18 15:10:08 +01:00
|
|
|
promise_.set_value(Unit());
|
2022-06-24 16:07:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void on_error(Status status) final {
|
|
|
|
promise_.set_error(std::move(status));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-06-24 17:14:00 +02:00
|
|
|
class AssignAppStoreTransactionQuery final : public Td::ResultHandler {
|
|
|
|
Promise<Unit> promise_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit AssignAppStoreTransactionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
|
|
}
|
|
|
|
|
2022-07-01 18:27:59 +02:00
|
|
|
void send(const string &receipt, td_api::object_ptr<td_api::StorePaymentPurpose> &&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());
|
2022-06-24 17:14:00 +02:00
|
|
|
}
|
2022-07-01 18:27:59 +02:00
|
|
|
|
|
|
|
send_query(G()->net_query_creator().create(
|
|
|
|
telegram_api::payments_assignAppStoreTransaction(BufferSlice(receipt), r_input_purpose.move_as_ok())));
|
2022-06-24 17:14:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
|
|
auto result_ptr = fetch_result<telegram_api::payments_assignAppStoreTransaction>(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));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-06-24 16:27:03 +02:00
|
|
|
class AssignPlayMarketTransactionQuery final : public Td::ResultHandler {
|
|
|
|
Promise<Unit> promise_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit AssignPlayMarketTransactionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
|
|
|
|
}
|
|
|
|
|
2022-07-18 14:17:01 +02:00
|
|
|
void send(const string &package_name, const string &store_product_id, const string &purchase_token,
|
|
|
|
td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose) {
|
2022-07-01 18:27:59 +02:00
|
|
|
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());
|
|
|
|
}
|
2022-07-01 13:33:59 +02:00
|
|
|
auto receipt = make_tl_object<telegram_api::dataJSON>(string());
|
2022-07-18 14:17:01 +02:00
|
|
|
receipt->data_ = json_encode<string>(json_object([&](auto &o) {
|
|
|
|
o("packageName", package_name);
|
|
|
|
o("purchaseToken", purchase_token);
|
|
|
|
o("productId", store_product_id);
|
|
|
|
}));
|
2022-07-01 18:27:59 +02:00
|
|
|
send_query(G()->net_query_creator().create(
|
|
|
|
telegram_api::payments_assignPlayMarketTransaction(std::move(receipt), r_input_purpose.move_as_ok())));
|
2022-06-24 16:27:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void on_result(BufferSlice packet) final {
|
|
|
|
auto result_ptr = fetch_result<telegram_api::payments_assignPlayMarketTransaction>(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));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-05-24 00:21:03 +02:00
|
|
|
const vector<Slice> &get_premium_limit_keys() {
|
2022-05-24 01:59:11 +02:00
|
|
|
static const vector<Slice> limit_keys{"channels",
|
|
|
|
"saved_gifs",
|
|
|
|
"stickers_faved",
|
|
|
|
"dialog_filters",
|
|
|
|
"dialog_filters_chats",
|
|
|
|
"dialogs_pinned",
|
|
|
|
"dialogs_folder_pinned",
|
|
|
|
"channels_public",
|
2022-05-30 01:20:12 +02:00
|
|
|
"caption_length",
|
2023-04-06 13:24:14 +02:00
|
|
|
"about_length",
|
|
|
|
"chatlist_invites",
|
2023-06-15 19:38:46 +02:00
|
|
|
"chatlists_joined",
|
2023-07-21 14:42:32 +02:00
|
|
|
"story_expiring",
|
2023-08-11 14:35:13 +02:00
|
|
|
"story_caption_length",
|
|
|
|
"stories_sent_weekly",
|
2023-09-18 18:47:34 +02:00
|
|
|
"stories_sent_monthly",
|
2023-11-28 12:22:08 +01:00
|
|
|
"stories_suggested_reactions",
|
2024-01-18 16:34:39 +01:00
|
|
|
"recommended_channels",
|
|
|
|
"saved_dialogs_pinned"};
|
2022-05-24 00:21:03 +02:00
|
|
|
return limit_keys;
|
|
|
|
}
|
|
|
|
|
2022-05-24 01:55:03 +02:00
|
|
|
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");
|
2023-04-03 16:28:51 +02:00
|
|
|
case td_api::premiumLimitTypeChatFolderCount::ID:
|
2022-05-24 01:55:03 +02:00
|
|
|
return Slice("dialog_filters");
|
2023-04-03 16:28:51 +02:00
|
|
|
case td_api::premiumLimitTypeChatFolderChosenChatCount::ID:
|
2022-05-24 01:55:03 +02:00
|
|
|
return Slice("dialog_filters_chats");
|
|
|
|
case td_api::premiumLimitTypePinnedChatCount::ID:
|
|
|
|
return Slice("dialogs_pinned");
|
|
|
|
case td_api::premiumLimitTypePinnedArchivedChatCount::ID:
|
|
|
|
return Slice("dialogs_folder_pinned");
|
2024-01-18 16:34:39 +01:00
|
|
|
case td_api::premiumLimitTypePinnedSavedMessagesTopicCount::ID:
|
|
|
|
return Slice("saved_dialogs_pinned");
|
2022-05-24 01:55:03 +02:00
|
|
|
case td_api::premiumLimitTypeCreatedPublicChatCount::ID:
|
|
|
|
return Slice("channels_public");
|
|
|
|
case td_api::premiumLimitTypeCaptionLength::ID:
|
|
|
|
return Slice("caption_length");
|
2022-05-30 01:20:12 +02:00
|
|
|
case td_api::premiumLimitTypeBioLength::ID:
|
|
|
|
return Slice("about_length");
|
2023-04-06 13:24:14 +02:00
|
|
|
case td_api::premiumLimitTypeChatFolderInviteLinkCount::ID:
|
|
|
|
return Slice("chatlist_invites");
|
|
|
|
case td_api::premiumLimitTypeShareableChatFolderCount::ID:
|
|
|
|
return Slice("chatlists_joined");
|
2023-06-15 19:38:46 +02:00
|
|
|
case td_api::premiumLimitTypeActiveStoryCount::ID:
|
|
|
|
return Slice("story_expiring");
|
2023-07-21 14:42:32 +02:00
|
|
|
case td_api::premiumLimitTypeStoryCaptionLength::ID:
|
|
|
|
return Slice("story_caption_length");
|
2023-08-11 14:35:13 +02:00
|
|
|
case td_api::premiumLimitTypeWeeklySentStoryCount::ID:
|
|
|
|
return Slice("stories_sent_weekly");
|
|
|
|
case td_api::premiumLimitTypeMonthlySentStoryCount::ID:
|
|
|
|
return Slice("stories_sent_monthly");
|
2023-09-18 18:47:34 +02:00
|
|
|
case td_api::premiumLimitTypeStorySuggestedReactionAreaCount::ID:
|
|
|
|
return Slice("stories_suggested_reactions");
|
2023-11-28 12:22:08 +01:00
|
|
|
case td_api::premiumLimitTypeSimilarChatCount::ID:
|
|
|
|
return Slice("recommended_channels");
|
2022-05-24 01:55:03 +02:00
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
return Slice();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-24 01:19:30 +02:00
|
|
|
static string get_premium_source(const td_api::PremiumLimitType *limit_type) {
|
|
|
|
if (limit_type == nullptr) {
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
|
2022-05-24 01:55:03 +02:00
|
|
|
return PSTRING() << "double_limits__" << get_limit_type_key(limit_type);
|
2022-05-24 01:19:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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";
|
2022-05-24 19:17:51 +02:00
|
|
|
case td_api::premiumFeatureIncreasedUploadFileSize::ID:
|
2022-05-24 01:19:30 +02:00
|
|
|
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:
|
2022-09-06 22:47:11 +02:00
|
|
|
return "infinite_reactions";
|
2022-05-24 01:19:30 +02:00
|
|
|
case td_api::premiumFeatureUniqueStickers::ID:
|
|
|
|
return "premium_stickers";
|
2022-07-16 13:33:54 +02:00
|
|
|
case td_api::premiumFeatureCustomEmoji::ID:
|
|
|
|
return "animated_emoji";
|
2022-05-24 01:19:30 +02:00
|
|
|
case td_api::premiumFeatureAdvancedChatManagement::ID:
|
|
|
|
return "advanced_chat_management";
|
|
|
|
case td_api::premiumFeatureProfileBadge::ID:
|
|
|
|
return "profile_badge";
|
2022-09-06 22:47:11 +02:00
|
|
|
case td_api::premiumFeatureEmojiStatus::ID:
|
|
|
|
return "emoji_status";
|
2022-05-24 01:19:30 +02:00
|
|
|
case td_api::premiumFeatureAnimatedProfilePhoto::ID:
|
|
|
|
return "animated_userpics";
|
2022-10-25 11:46:07 +02:00
|
|
|
case td_api::premiumFeatureForumTopicIcon::ID:
|
|
|
|
return "forum_topic_icon";
|
2022-06-01 16:19:05 +02:00
|
|
|
case td_api::premiumFeatureAppIcons::ID:
|
|
|
|
return "app_icons";
|
2023-01-25 19:26:09 +01:00
|
|
|
case td_api::premiumFeatureRealTimeChatTranslation::ID:
|
|
|
|
return "translations";
|
2023-07-30 11:42:24 +02:00
|
|
|
case td_api::premiumFeatureUpgradedStories::ID:
|
|
|
|
return "stories";
|
2023-09-18 18:57:48 +02:00
|
|
|
case td_api::premiumFeatureChatBoost::ID:
|
|
|
|
return "channel_boost";
|
2023-10-29 02:20:29 +02:00
|
|
|
case td_api::premiumFeatureAccentColor::ID:
|
2023-11-29 11:33:14 +01:00
|
|
|
return "peer_colors";
|
2023-11-28 12:11:46 +01:00
|
|
|
case td_api::premiumFeatureBackgroundForBoth::ID:
|
2023-11-29 11:33:14 +01:00
|
|
|
return "wallpapers";
|
2024-01-30 01:11:23 +01:00
|
|
|
case td_api::premiumFeatureSavedMessagesTags::ID:
|
|
|
|
return "saved_tags";
|
2022-05-24 01:19:30 +02:00
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
|
2023-08-02 13:50:54 +02:00
|
|
|
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";
|
2024-01-30 01:29:50 +01:00
|
|
|
case td_api::premiumStoryFeatureVideoQuality::ID:
|
|
|
|
return "stories__quality";
|
2023-08-02 13:50:54 +02:00
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-24 01:19:30 +02:00
|
|
|
static string get_premium_source(const td_api::object_ptr<td_api::PremiumSource> &source) {
|
|
|
|
if (source == nullptr) {
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
switch (source->get_id()) {
|
|
|
|
case td_api::premiumSourceLimitExceeded::ID: {
|
|
|
|
auto *limit_type = static_cast<const td_api::premiumSourceLimitExceeded *>(source.get())->limit_type_.get();
|
|
|
|
return get_premium_source(limit_type);
|
|
|
|
}
|
|
|
|
case td_api::premiumSourceFeature::ID: {
|
|
|
|
auto *feature = static_cast<const td_api::premiumSourceFeature *>(source.get())->feature_.get();
|
|
|
|
return get_premium_source(feature);
|
|
|
|
}
|
2023-08-02 13:50:54 +02:00
|
|
|
case td_api::premiumSourceStoryFeature::ID: {
|
|
|
|
auto *feature = static_cast<const td_api::premiumSourceStoryFeature *>(source.get())->feature_.get();
|
|
|
|
return get_premium_source(feature);
|
|
|
|
}
|
2022-05-24 01:19:30 +02:00
|
|
|
case td_api::premiumSourceLink::ID: {
|
|
|
|
auto &referrer = static_cast<const td_api::premiumSourceLink *>(source.get())->referrer_;
|
|
|
|
if (referrer.empty()) {
|
|
|
|
return "deeplink";
|
|
|
|
}
|
|
|
|
return PSTRING() << "deeplink_" << referrer;
|
|
|
|
}
|
|
|
|
case td_api::premiumSourceSettings::ID:
|
|
|
|
return "settings";
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-24 01:55:03 +02:00
|
|
|
static td_api::object_ptr<td_api::premiumLimit> get_premium_limit_object(Slice key) {
|
2022-08-18 22:31:14 +02:00
|
|
|
auto default_limit = static_cast<int32>(G()->get_option_integer(PSLICE() << key << "_limit_default"));
|
|
|
|
auto premium_limit = static_cast<int32>(G()->get_option_integer(PSLICE() << key << "_limit_premium"));
|
2022-05-24 01:55:03 +02:00
|
|
|
if (default_limit <= 0 || premium_limit <= default_limit) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
auto type = [&]() -> td_api::object_ptr<td_api::PremiumLimitType> {
|
2022-05-24 01:59:11 +02:00
|
|
|
if (key == "channels") {
|
2022-05-24 01:55:03 +02:00
|
|
|
return td_api::make_object<td_api::premiumLimitTypeSupergroupCount>();
|
|
|
|
}
|
2022-05-24 01:59:11 +02:00
|
|
|
if (key == "saved_gifs") {
|
2022-05-24 01:55:03 +02:00
|
|
|
return td_api::make_object<td_api::premiumLimitTypeSavedAnimationCount>();
|
|
|
|
}
|
2022-05-24 01:59:11 +02:00
|
|
|
if (key == "stickers_faved") {
|
2022-05-24 01:55:03 +02:00
|
|
|
return td_api::make_object<td_api::premiumLimitTypeFavoriteStickerCount>();
|
|
|
|
}
|
2022-05-24 01:59:11 +02:00
|
|
|
if (key == "dialog_filters") {
|
2023-04-03 16:28:51 +02:00
|
|
|
return td_api::make_object<td_api::premiumLimitTypeChatFolderCount>();
|
2022-05-24 01:55:03 +02:00
|
|
|
}
|
2022-05-24 01:59:11 +02:00
|
|
|
if (key == "dialog_filters_chats") {
|
2023-04-03 16:28:51 +02:00
|
|
|
return td_api::make_object<td_api::premiumLimitTypeChatFolderChosenChatCount>();
|
2022-05-24 01:55:03 +02:00
|
|
|
}
|
2022-05-24 01:59:11 +02:00
|
|
|
if (key == "dialogs_pinned") {
|
2022-05-24 01:55:03 +02:00
|
|
|
return td_api::make_object<td_api::premiumLimitTypePinnedChatCount>();
|
|
|
|
}
|
2022-05-24 01:59:11 +02:00
|
|
|
if (key == "dialogs_folder_pinned") {
|
2022-05-24 01:55:03 +02:00
|
|
|
return td_api::make_object<td_api::premiumLimitTypePinnedArchivedChatCount>();
|
|
|
|
}
|
2024-01-18 16:34:39 +01:00
|
|
|
if (key == "saved_dialogs_pinned") {
|
|
|
|
return td_api::make_object<td_api::premiumLimitTypePinnedSavedMessagesTopicCount>();
|
|
|
|
}
|
2022-05-24 01:59:11 +02:00
|
|
|
if (key == "channels_public") {
|
2022-05-24 01:55:03 +02:00
|
|
|
return td_api::make_object<td_api::premiumLimitTypeCreatedPublicChatCount>();
|
|
|
|
}
|
2022-05-24 01:59:11 +02:00
|
|
|
if (key == "caption_length") {
|
2022-05-24 01:55:03 +02:00
|
|
|
return td_api::make_object<td_api::premiumLimitTypeCaptionLength>();
|
|
|
|
}
|
2022-05-30 01:20:12 +02:00
|
|
|
if (key == "about_length") {
|
|
|
|
return td_api::make_object<td_api::premiumLimitTypeBioLength>();
|
|
|
|
}
|
2023-04-06 13:24:14 +02:00
|
|
|
if (key == "chatlist_invites") {
|
|
|
|
return td_api::make_object<td_api::premiumLimitTypeChatFolderInviteLinkCount>();
|
|
|
|
}
|
|
|
|
if (key == "chatlists_joined") {
|
|
|
|
return td_api::make_object<td_api::premiumLimitTypeShareableChatFolderCount>();
|
|
|
|
}
|
2023-06-15 19:38:46 +02:00
|
|
|
if (key == "story_expiring") {
|
|
|
|
return td_api::make_object<td_api::premiumLimitTypeActiveStoryCount>();
|
|
|
|
}
|
2023-07-21 14:42:32 +02:00
|
|
|
if (key == "story_caption_length") {
|
|
|
|
return td_api::make_object<td_api::premiumLimitTypeStoryCaptionLength>();
|
|
|
|
}
|
2023-08-11 14:35:13 +02:00
|
|
|
if (key == "stories_sent_weekly") {
|
|
|
|
return td_api::make_object<td_api::premiumLimitTypeWeeklySentStoryCount>();
|
|
|
|
}
|
|
|
|
if (key == "stories_sent_monthly") {
|
|
|
|
return td_api::make_object<td_api::premiumLimitTypeMonthlySentStoryCount>();
|
|
|
|
}
|
2023-09-18 18:47:34 +02:00
|
|
|
if (key == "stories_suggested_reactions") {
|
|
|
|
return td_api::make_object<td_api::premiumLimitTypeStorySuggestedReactionAreaCount>();
|
|
|
|
}
|
2023-11-28 12:22:08 +01:00
|
|
|
if (key == "recommended_channels") {
|
|
|
|
return td_api::make_object<td_api::premiumLimitTypeSimilarChatCount>();
|
|
|
|
}
|
2022-05-24 01:55:03 +02:00
|
|
|
UNREACHABLE();
|
|
|
|
return nullptr;
|
|
|
|
}();
|
|
|
|
return td_api::make_object<td_api::premiumLimit>(std::move(type), default_limit, premium_limit);
|
|
|
|
}
|
|
|
|
|
|
|
|
void get_premium_limit(const td_api::object_ptr<td_api::PremiumLimitType> &limit_type,
|
|
|
|
Promise<td_api::object_ptr<td_api::premiumLimit>> &&promise) {
|
|
|
|
if (limit_type == nullptr) {
|
|
|
|
return promise.set_error(Status::Error(400, "Limit type must be non-empty"));
|
|
|
|
}
|
|
|
|
|
2022-05-24 01:59:11 +02:00
|
|
|
promise.set_value(get_premium_limit_object(get_limit_type_key(limit_type.get())));
|
2022-05-24 01:55:03 +02:00
|
|
|
}
|
|
|
|
|
2022-05-24 14:42:46 +02:00
|
|
|
void get_premium_features(Td *td, const td_api::object_ptr<td_api::PremiumSource> &source,
|
2022-05-24 01:19:30 +02:00
|
|
|
Promise<td_api::object_ptr<td_api::premiumFeatures>> &&promise) {
|
2023-09-18 19:22:09 +02:00
|
|
|
auto premium_features = full_split(
|
|
|
|
G()->get_option_string(
|
|
|
|
"premium_features",
|
|
|
|
"stories,double_limits,animated_emoji,translations,more_upload,faster_download,voice_to_text,no_ads,infinite_"
|
|
|
|
"reactions,premium_stickers,advanced_chat_management,profile_badge,animated_userpics,app_icons,emoji_status"),
|
|
|
|
',');
|
2022-05-24 00:36:55 +02:00
|
|
|
vector<td_api::object_ptr<td_api::PremiumFeature>> features;
|
|
|
|
for (const auto &premium_feature : premium_features) {
|
2022-06-01 14:14:09 +02:00
|
|
|
auto feature = get_premium_feature_object(premium_feature);
|
2022-05-24 00:36:55 +02:00
|
|
|
if (feature != nullptr) {
|
|
|
|
features.push_back(std::move(feature));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-24 01:55:03 +02:00
|
|
|
auto limits = transform(get_premium_limit_keys(), get_premium_limit_object);
|
|
|
|
td::remove_if(limits, [](auto &limit) { return limit == nullptr; });
|
2022-05-24 01:19:30 +02:00
|
|
|
|
|
|
|
auto source_str = get_premium_source(source);
|
2022-05-24 14:42:46 +02:00
|
|
|
if (!source_str.empty()) {
|
|
|
|
vector<tl_object_ptr<telegram_api::jsonObjectValue>> data;
|
|
|
|
vector<tl_object_ptr<telegram_api::JSONValue>> promo_order;
|
|
|
|
for (const auto &premium_feature : premium_features) {
|
|
|
|
promo_order.push_back(make_tl_object<telegram_api::jsonString>(premium_feature));
|
|
|
|
}
|
|
|
|
data.push_back(make_tl_object<telegram_api::jsonObjectValue>(
|
|
|
|
"premium_promo_order", make_tl_object<telegram_api::jsonArray>(std::move(promo_order))));
|
|
|
|
data.push_back(
|
|
|
|
make_tl_object<telegram_api::jsonObjectValue>("source", make_tl_object<telegram_api::jsonString>(source_str)));
|
|
|
|
save_app_log(td, "premium.promo_screen_show", DialogId(), make_tl_object<telegram_api::jsonObject>(std::move(data)),
|
|
|
|
Promise<Unit>());
|
|
|
|
}
|
2022-05-24 01:19:30 +02:00
|
|
|
|
2022-05-24 15:30:01 +02:00
|
|
|
td_api::object_ptr<td_api::InternalLinkType> payment_link;
|
2022-08-17 15:11:13 +02:00
|
|
|
auto premium_bot_username = G()->get_option_string("premium_bot_username");
|
2022-05-24 15:30:01 +02:00
|
|
|
if (!premium_bot_username.empty()) {
|
2022-07-01 15:21:29 +02:00
|
|
|
payment_link = td_api::make_object<td_api::internalLinkTypeBotStart>(premium_bot_username, source_str, true);
|
2022-05-24 15:30:01 +02:00
|
|
|
} else {
|
2022-08-17 15:11:13 +02:00
|
|
|
auto premium_invoice_slug = G()->get_option_string("premium_invoice_slug");
|
2022-05-24 15:30:01 +02:00
|
|
|
if (!premium_invoice_slug.empty()) {
|
|
|
|
payment_link = td_api::make_object<td_api::internalLinkTypeInvoice>(premium_invoice_slug);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
promise.set_value(
|
|
|
|
td_api::make_object<td_api::premiumFeatures>(std::move(features), std::move(limits), std::move(payment_link)));
|
2022-05-24 00:36:55 +02:00
|
|
|
}
|
|
|
|
|
2022-05-24 19:45:03 +02:00
|
|
|
void view_premium_feature(Td *td, const td_api::object_ptr<td_api::PremiumFeature> &feature, Promise<Unit> &&promise) {
|
|
|
|
auto source = get_premium_source(feature.get());
|
|
|
|
if (source.empty()) {
|
|
|
|
return promise.set_error(Status::Error(400, "Feature must be non-empty"));
|
|
|
|
}
|
|
|
|
|
|
|
|
vector<tl_object_ptr<telegram_api::jsonObjectValue>> data;
|
|
|
|
data.push_back(
|
|
|
|
make_tl_object<telegram_api::jsonObjectValue>("item", make_tl_object<telegram_api::jsonString>(source)));
|
|
|
|
save_app_log(td, "premium.promo_screen_tap", DialogId(), make_tl_object<telegram_api::jsonObject>(std::move(data)),
|
|
|
|
std::move(promise));
|
|
|
|
}
|
|
|
|
|
2022-05-25 14:34:36 +02:00
|
|
|
void click_premium_subscription_button(Td *td, Promise<Unit> &&promise) {
|
|
|
|
vector<tl_object_ptr<telegram_api::jsonObjectValue>> data;
|
|
|
|
save_app_log(td, "premium.promo_screen_accept", DialogId(), make_tl_object<telegram_api::jsonObject>(std::move(data)),
|
|
|
|
std::move(promise));
|
|
|
|
}
|
|
|
|
|
2022-06-01 14:14:09 +02:00
|
|
|
void get_premium_state(Td *td, Promise<td_api::object_ptr<td_api::premiumState>> &&promise) {
|
|
|
|
td->create_handler<GetPremiumPromoQuery>(std::move(promise))->send();
|
|
|
|
}
|
|
|
|
|
2023-09-28 20:26:33 +02:00
|
|
|
void get_premium_gift_code_options(Td *td, DialogId boosted_dialog_id,
|
|
|
|
Promise<td_api::object_ptr<td_api::premiumGiftCodePaymentOptions>> &&promise) {
|
|
|
|
td->create_handler<GetPremiumGiftCodeOptionsQuery>(std::move(promise))->send(boosted_dialog_id);
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:21:25 +02:00
|
|
|
void check_premium_gift_code(Td *td, const string &code,
|
|
|
|
Promise<td_api::object_ptr<td_api::premiumGiftCodeInfo>> &&promise) {
|
|
|
|
td->create_handler<CheckGiftCodeQuery>(std::move(promise))->send(code);
|
|
|
|
}
|
|
|
|
|
2023-09-28 21:35:57 +02:00
|
|
|
void apply_premium_gift_code(Td *td, const string &code, Promise<Unit> &&promise) {
|
|
|
|
td->create_handler<ApplyGiftCodeQuery>(std::move(promise))->send(code);
|
|
|
|
}
|
|
|
|
|
2023-10-12 17:19:08 +02:00
|
|
|
void launch_prepaid_premium_giveaway(Td *td, int64 giveaway_id,
|
|
|
|
td_api::object_ptr<td_api::premiumGiveawayParameters> &¶meters,
|
|
|
|
Promise<Unit> &&promise) {
|
|
|
|
TRY_RESULT_PROMISE(promise, giveaway_parameters, GiveawayParameters::get_giveaway_parameters(td, parameters.get()));
|
|
|
|
td->create_handler<LaunchPrepaidGiveawayQuery>(std::move(promise))->send(giveaway_id, giveaway_parameters);
|
|
|
|
}
|
|
|
|
|
2023-10-06 17:28:02 +02:00
|
|
|
void get_premium_giveaway_info(Td *td, MessageFullId message_full_id,
|
|
|
|
Promise<td_api::object_ptr<td_api::PremiumGiveawayInfo>> &&promise) {
|
|
|
|
TRY_RESULT_PROMISE(promise, server_message_id, td->messages_manager_->get_giveaway_message_id(message_full_id));
|
|
|
|
td->create_handler<GetGiveawayInfoQuery>(std::move(promise))
|
|
|
|
->send(message_full_id.get_dialog_id(), server_message_id);
|
|
|
|
}
|
|
|
|
|
2022-07-15 13:09:37 +02:00
|
|
|
void can_purchase_premium(Td *td, td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose, Promise<Unit> &&promise) {
|
|
|
|
td->create_handler<CanPurchasePremiumQuery>(std::move(promise))->send(std::move(purpose));
|
2022-06-24 16:07:10 +02:00
|
|
|
}
|
|
|
|
|
2022-07-01 18:27:59 +02:00
|
|
|
void assign_app_store_transaction(Td *td, const string &receipt,
|
|
|
|
td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose, Promise<Unit> &&promise) {
|
2023-01-23 14:03:32 +01:00
|
|
|
if (purpose != nullptr && purpose->get_id() == td_api::storePaymentPurposePremiumSubscription::ID) {
|
|
|
|
dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::UpgradePremium}, Promise<Unit>());
|
2023-01-23 14:11:26 +01:00
|
|
|
dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::SubscribeToAnnualPremium}, Promise<Unit>());
|
2023-04-25 15:48:16 +02:00
|
|
|
dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::RestorePremium}, Promise<Unit>());
|
2023-01-23 14:03:32 +01:00
|
|
|
}
|
2022-07-01 18:27:59 +02:00
|
|
|
td->create_handler<AssignAppStoreTransactionQuery>(std::move(promise))->send(receipt, std::move(purpose));
|
2022-06-24 17:14:00 +02:00
|
|
|
}
|
|
|
|
|
2022-07-18 14:17:01 +02:00
|
|
|
void assign_play_market_transaction(Td *td, const string &package_name, const string &store_product_id,
|
|
|
|
const string &purchase_token,
|
2022-07-01 18:27:59 +02:00
|
|
|
td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose,
|
|
|
|
Promise<Unit> &&promise) {
|
2023-01-23 14:03:32 +01:00
|
|
|
if (purpose != nullptr && purpose->get_id() == td_api::storePaymentPurposePremiumSubscription::ID) {
|
|
|
|
dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::UpgradePremium}, Promise<Unit>());
|
2023-01-23 14:11:26 +01:00
|
|
|
dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::SubscribeToAnnualPremium}, Promise<Unit>());
|
2023-04-25 15:48:16 +02:00
|
|
|
dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::RestorePremium}, Promise<Unit>());
|
2023-01-23 14:03:32 +01:00
|
|
|
}
|
2022-07-18 14:17:01 +02:00
|
|
|
td->create_handler<AssignPlayMarketTransactionQuery>(std::move(promise))
|
|
|
|
->send(package_name, store_product_id, purchase_token, std::move(purpose));
|
2022-06-24 16:27:03 +02:00
|
|
|
}
|
|
|
|
|
2022-05-24 00:21:03 +02:00
|
|
|
} // namespace td
|