// // 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/ConfigManager.h" #include "td/telegram/AccountManager.h" #include "td/telegram/AgeVerificationParameters.h" #include "td/telegram/AuthManager.h" #include "td/telegram/ConnectionState.h" #include "td/telegram/Global.h" #include "td/telegram/GroupCallManager.h" #include "td/telegram/JsonValue.h" #include "td/telegram/LinkManager.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/misc.h" #include "td/telegram/net/AuthDataShared.h" #include "td/telegram/net/ConnectionCreator.h" #include "td/telegram/net/DcId.h" #include "td/telegram/net/DcOptions.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/net/NetType.h" #include "td/telegram/net/PublicRsaKeySharedMain.h" #include "td/telegram/net/Session.h" #include "td/telegram/OptionManager.h" #include "td/telegram/Premium.h" #include "td/telegram/ReactionType.h" #include "td/telegram/StateManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/TranscriptionManager.h" #include "td/telegram/UserId.h" #include "td/telegram/UserManager.h" #include "td/mtproto/AuthData.h" #include "td/mtproto/AuthKey.h" #include "td/mtproto/RawConnection.h" #include "td/mtproto/RSA.h" #include "td/mtproto/TransportType.h" #if !TD_EMSCRIPTEN //FIXME #include "td/net/SslCtx.h" #include "td/net/Wget.h" #endif #include "td/net/HttpQuery.h" #include "td/actor/actor.h" #include "td/utils/algorithm.h" #include "td/utils/base64.h" #include "td/utils/buffer.h" #include "td/utils/common.h" #include "td/utils/crypto.h" #include "td/utils/emoji.h" #include "td/utils/FlatHashMap.h" #include "td/utils/FlatHashSet.h" #include "td/utils/format.h" #include "td/utils/HttpDate.h" #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/port/Clocks.h" #include "td/utils/Random.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" #include "td/utils/tl_parsers.h" #include "td/utils/UInt.h" #include #include #include namespace td { int VERBOSITY_NAME(config_recoverer) = VERBOSITY_NAME(INFO); Result decode_config(Slice input) { static auto rsa = mtproto::RSA::from_pem_public_key( "-----BEGIN RSA PUBLIC KEY-----\n" "MIIBCgKCAQEAyr+18Rex2ohtVy8sroGP\n" "BwXD3DOoKCSpjDqYoXgCqB7ioln4eDCFfOBUlfXUEvM/fnKCpF46VkAftlb4VuPD\n" "eQSS/ZxZYEGqHaywlroVnXHIjgqoxiAd192xRGreuXIaUKmkwlM9JID9WS2jUsTp\n" "zQ91L8MEPLJ/4zrBwZua8W5fECwCCh2c9G5IzzBm+otMS/YKwmR1olzRCyEkyAEj\n" "XWqBI9Ftv5eG8m0VkBzOG655WIYdyV0HfDK/NWcvGqa0w/nriMD6mDjKOryamw0O\n" "P9QuYgMN0C9xMW9y8SmP4h92OAWodTYgY1hZCxdv6cs5UnW9+PWvS+WIbkh+GaWY\n" "xwIDAQAB\n" "-----END RSA PUBLIC KEY-----\n") .move_as_ok(); if (input.size() < 344 || input.size() > 1024) { return Status::Error(PSLICE() << "Invalid " << tag("length", input.size())); } auto data_base64 = base64_filter(input); if (data_base64.size() != 344) { return Status::Error(PSLICE() << "Invalid " << tag("length", data_base64.size()) << " after base64_filter"); } TRY_RESULT(data_rsa, base64_decode(data_base64)); if (data_rsa.size() != 256) { return Status::Error(PSLICE() << "Invalid " << tag("length", data_rsa.size()) << " after base64_decode"); } MutableSlice data_rsa_slice(data_rsa); rsa.decrypt_signature(data_rsa_slice, data_rsa_slice); MutableSlice data_cbc = data_rsa_slice.substr(32); UInt256 key; UInt128 iv; as_mutable_slice(key).copy_from(data_rsa_slice.substr(0, 32)); as_mutable_slice(iv).copy_from(data_rsa_slice.substr(16, 16)); aes_cbc_decrypt(as_slice(key), as_mutable_slice(iv), data_cbc, data_cbc); CHECK(data_cbc.size() == 224); string hash(32, ' '); sha256(data_cbc.substr(0, 208), MutableSlice(hash)); if (data_cbc.substr(208) != Slice(hash).substr(0, 16)) { return Status::Error("SHA256 mismatch"); } TlParser len_parser{data_cbc}; int len = len_parser.fetch_int(); if (len < 8 || len > 208) { return Status::Error(PSLICE() << "Invalid " << tag("data length", len) << " after aes_cbc_decrypt"); } int constructor_id = len_parser.fetch_int(); if (constructor_id != telegram_api::help_configSimple::ID) { return Status::Error(PSLICE() << "Wrong " << tag("constructor", format::as_hex(constructor_id))); } BufferSlice raw_config(data_cbc.substr(8, len - 8)); TlBufferParser parser{&raw_config}; auto config = telegram_api::help_configSimple::fetch(parser); parser.fetch_end(); TRY_STATUS(parser.get_status()); return std::move(config); } static ActorOwn<> get_simple_config_impl(Promise promise, int32 scheduler_id, string url, string host, std::vector> headers, bool prefer_ipv6, std::function(HttpQuery &)> get_config, string content = string(), string content_type = string()) { VLOG(config_recoverer) << "Request simple config from " << url; #if TD_EMSCRIPTEN // FIXME return ActorOwn<>(); #else const int timeout = 10; const int ttl = 3; headers.emplace_back("Host", std::move(host)); headers.emplace_back("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/77.0.3865.90 Safari/537.36"); return ActorOwn<>(create_actor_on_scheduler( "Wget", scheduler_id, PromiseCreator::lambda([get_config = std::move(get_config), promise = std::move(promise)](Result> r_query) mutable { promise.set_result([&]() -> Result { TRY_RESULT(http_query, std::move(r_query)); SimpleConfigResult res; res.r_http_date = HttpDate::parse_http_date(http_query->get_header("date").str()); auto r_config = get_config(*http_query); if (r_config.is_error()) { res.r_config = r_config.move_as_error(); } else { res.r_config = decode_config(r_config.ok()); } return std::move(res); }()); }), std::move(url), std::move(headers), timeout, ttl, prefer_ipv6, SslCtx::VerifyPeer::Off, std::move(content), std::move(content_type))); #endif } ActorOwn<> get_simple_config_azure(Promise promise, bool prefer_ipv6, Slice domain_name, bool is_test, int32 scheduler_id) { string url = PSTRING() << "https://software-download.microsoft.com/" << (is_test ? "test" : "prod") << "v2/config.txt"; return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "tcdnb.azureedge.net", {}, prefer_ipv6, [](HttpQuery &http_query) -> Result { return http_query.content_.str(); }); } static ActorOwn<> get_simple_config_dns(Slice address, Slice host, Promise promise, bool prefer_ipv6, Slice domain_name, bool is_test, int32 scheduler_id) { if (domain_name.empty()) { domain_name = is_test ? Slice("tapv3.stel.com") : Slice("apv3.stel.com"); } auto get_config = [](HttpQuery &http_query) -> Result { auto get_data = [](JsonValue &answer) -> Result { auto &answer_array = answer.get_array(); vector parts; for (auto &answer_part : answer_array) { if (answer_part.type() != JsonValue::Type::Object) { return Status::Error("Expected JSON object"); } auto &data_object = answer_part.get_object(); TRY_RESULT(part, data_object.get_required_string_field("data")); parts.push_back(std::move(part)); } if (parts.size() != 2) { return Status::Error("Expected data in two parts"); } string data; if (parts[0].size() < parts[1].size()) { data = parts[1] + parts[0]; } else { data = parts[0] + parts[1]; } return data; }; if (!http_query.get_arg("Answer").empty()) { VLOG(config_recoverer) << "Receive DNS response " << http_query.get_arg("Answer"); TRY_RESULT(answer, json_decode(http_query.get_arg("Answer"))); if (answer.type() != JsonValue::Type::Array) { return Status::Error("Expected JSON array"); } return get_data(answer); } else { VLOG(config_recoverer) << "Receive DNS response " << http_query.content_; TRY_RESULT(json, json_decode(http_query.content_)); if (json.type() != JsonValue::Type::Object) { return Status::Error("Expected JSON object"); } auto &answer_object = json.get_object(); TRY_RESULT(answer, answer_object.extract_required_field("Answer", JsonValue::Type::Array)); return get_data(answer); } }; return get_simple_config_impl( std::move(promise), scheduler_id, PSTRING() << "https://" << address << "?name=" << url_encode(domain_name) << "&type=TXT", host.str(), {{"Accept", "application/dns-json"}}, prefer_ipv6, std::move(get_config)); } ActorOwn<> get_simple_config_google_dns(Promise promise, bool prefer_ipv6, Slice domain_name, bool is_test, int32 scheduler_id) { return get_simple_config_dns("dns.google/resolve", "dns.google", std::move(promise), prefer_ipv6, domain_name, is_test, scheduler_id); } ActorOwn<> get_simple_config_mozilla_dns(Promise promise, bool prefer_ipv6, Slice domain_name, bool is_test, int32 scheduler_id) { return get_simple_config_dns("mozilla.cloudflare-dns.com/dns-query", "mozilla.cloudflare-dns.com", std::move(promise), prefer_ipv6, domain_name, is_test, scheduler_id); } static string generate_firebase_remote_config_payload() { unsigned char buf[17]; Random::secure_bytes(buf, sizeof(buf)); buf[0] = static_cast((buf[0] & 0xF0) | 0x07); auto app_instance_id = base64url_encode(Slice(buf, sizeof(buf))); app_instance_id.resize(22); return PSTRING() << "{\"app_id\":\"1:560508485281:web:4ee13a6af4e84d49e67ae0\",\"app_instance_id\":\"" << app_instance_id << "\"}"; } ActorOwn<> get_simple_config_firebase_remote_config(Promise promise, bool prefer_ipv6, Slice domain_name, bool is_test, int32 scheduler_id) { if (is_test) { promise.set_error(400, "Test config is not supported"); return ActorOwn<>(); } static const string payload = generate_firebase_remote_config_payload(); auto url = PSTRING() << "https://firebaseremoteconfig.googleapis.com/v1/projects/peak-vista-421/namespaces/firebase:fetch?key=" << hex_decode("41497a61537943322d6b416b704473726f69785258772d7354772d5766716f344e786a4d77774d").move_as_ok(); auto get_config = [](HttpQuery &http_query) -> Result { TRY_RESULT(json, json_decode(http_query.get_arg("entries"))); if (json.type() != JsonValue::Type::Object) { return Status::Error("Expected JSON object"); } auto &entries_object = json.get_object(); TRY_RESULT(config, entries_object.get_required_string_field("ipconfigv3")); return std::move(config); }; return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "firebaseremoteconfig.googleapis.com", {}, prefer_ipv6, std::move(get_config), payload, "application/json"); } ActorOwn<> get_simple_config_firebase_realtime(Promise promise, bool prefer_ipv6, Slice domain_name, bool is_test, int32 scheduler_id) { if (is_test) { promise.set_error(400, "Test config is not supported"); return ActorOwn<>(); } string url = "https://reserve-5a846.firebaseio.com/ipconfigv3.json"; auto get_config = [](HttpQuery &http_query) -> Result { return http_query.get_arg("content").str(); }; return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "reserve-5a846.firebaseio.com", {}, prefer_ipv6, std::move(get_config)); } ActorOwn<> get_simple_config_firebase_firestore(Promise promise, bool prefer_ipv6, Slice domain_name, bool is_test, int32 scheduler_id) { if (is_test) { promise.set_error(400, "Test config is not supported"); return ActorOwn<>(); } string url = "https://www.google.com/v1/projects/reserve-5a846/databases/(default)/documents/ipconfig/v3"; auto get_config = [](HttpQuery &http_query) -> Result { TRY_RESULT(json, json_decode(http_query.get_arg("fields"))); if (json.type() != JsonValue::Type::Object) { return Status::Error("Expected JSON object"); } auto &json_object = json.get_object(); TRY_RESULT(data, json_object.extract_required_field("data", JsonValue::Type::Object)); auto &data_object = data.get_object(); TRY_RESULT(config, data_object.get_required_string_field("stringValue")); return std::move(config); }; return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "firestore.googleapis.com", {}, prefer_ipv6, std::move(get_config)); } static ActorOwn<> get_full_config(DcOption option, Promise> promise, ActorShared<> parent) { class SessionCallback final : public Session::Callback { public: SessionCallback(ActorShared<> parent, DcOption option) : parent_(std::move(parent)), option_(std::move(option)) { } void on_failed() final { } void on_closed() final { } void request_raw_connection(unique_ptr auth_data, Promise> promise) final { request_raw_connection_cnt_++; VLOG(config_recoverer) << "Request full config from " << option_.get_ip_address() << ", try = " << request_raw_connection_cnt_; if (request_raw_connection_cnt_ <= 2) { send_closure(G()->connection_creator(), &ConnectionCreator::request_raw_connection_by_ip, option_.get_ip_address(), mtproto::TransportType{mtproto::TransportType::ObfuscatedTcp, narrow_cast(option_.get_dc_id().get_raw_id()), option_.get_secret()}, std::move(promise)); } else { // Delay all queries except first forever delay_forever_.push_back(std::move(promise)); } } void on_tmp_auth_key_updated(mtproto::AuthKey auth_key) final { // nop } void on_server_salt_updated(std::vector server_salts) final { // nop } void on_update(BufferSlice &&update, uint64 auth_key_id) final { // nop } void on_result(NetQueryPtr net_query) final { G()->net_query_dispatcher().dispatch(std::move(net_query)); } private: ActorShared<> parent_; DcOption option_; size_t request_raw_connection_cnt_{0}; std::vector>> delay_forever_; }; class SimpleAuthData final : public AuthDataShared { public: explicit SimpleAuthData(DcId dc_id) : dc_id_(dc_id), public_rsa_key_(PublicRsaKeySharedMain::create(G()->is_test_dc())) { } DcId dc_id() const final { return dc_id_; } const std::shared_ptr &public_rsa_key() final { return public_rsa_key_; } mtproto::AuthKey get_auth_key() final { string dc_key = G()->td_db()->get_binlog_pmc()->get(auth_key_key()); mtproto::AuthKey res; if (!dc_key.empty()) { unserialize(res, dc_key).ensure(); } return res; } void set_auth_key(const mtproto::AuthKey &auth_key) final { G()->td_db()->get_binlog_pmc()->set(auth_key_key(), serialize(auth_key)); //notify(); } void update_server_time_difference(double diff, bool force) final { G()->update_server_time_difference(diff, force); } double get_server_time_difference() final { return G()->get_server_time_difference(); } void add_auth_key_listener(unique_ptr listener) final { CHECK(listener != nullptr); if (listener->notify()) { auth_key_listeners_.push_back(std::move(listener)); } } void set_future_salts(const std::vector &future_salts) final { G()->td_db()->get_binlog_pmc()->set(future_salts_key(), serialize(future_salts)); } std::vector get_future_salts() final { string future_salts = G()->td_db()->get_binlog_pmc()->get(future_salts_key()); std::vector res; if (!future_salts.empty()) { unserialize(res, future_salts).ensure(); } return res; } private: DcId dc_id_; std::shared_ptr public_rsa_key_; vector> auth_key_listeners_; /* void notify() { td::remove_if(auth_key_listeners_, [&](auto &listener) { CHECK(listener != nullptr); return !listener->notify(); }); } */ string auth_key_key() const { return PSTRING() << "config_recovery_auth" << dc_id().get_raw_id(); } string future_salts_key() const { return PSTRING() << "config_recovery_salt" << dc_id().get_raw_id(); } }; class GetConfigActor final : public NetQueryCallback { public: GetConfigActor(DcOption option, Promise> promise, ActorShared<> parent) : option_(std::move(option)), promise_(std::move(promise)), parent_(std::move(parent)) { } private: void start_up() final { auto auth_data = std::make_shared(option_.get_dc_id()); int32 raw_dc_id = option_.get_dc_id().get_raw_id(); auto session_callback = make_unique(actor_shared(this, 1), std::move(option_)); int32 int_dc_id = raw_dc_id; if (G()->is_test_dc()) { int_dc_id += 10000; } session_ = create_actor( "ConfigSession", std::move(session_callback), std::move(auth_data), raw_dc_id, int_dc_id, false /*is_primary*/, false /*is_main*/, true /*use_pfs*/, false /*persist_tmp_auth_key*/, false /*is_cdn*/, false /*need_destroy_auth_key*/, mtproto::AuthKey(), std::vector()); auto query = G()->net_query_creator().create_unauth(telegram_api::help_getConfig(), DcId::empty()); query->total_timeout_limit_ = 60 * 60 * 24; query->set_callback(actor_shared(this)); query->dispatch_ttl_ = 0; send_closure(session_, &Session::send, std::move(query)); set_timeout_in(10); } void on_result(NetQueryPtr query) final { promise_.set_result(fetch_result(std::move(query))); } void hangup_shared() final { if (get_link_token() == 1) { if (promise_) { promise_.set_error("Failed"); } stop(); } } void hangup() final { session_.reset(); } void timeout_expired() final { promise_.set_error("Timeout expired"); session_.reset(); } DcOption option_; ActorOwn session_; Promise> promise_; ActorShared<> parent_; }; return ActorOwn<>( create_actor("GetConfigActor", std::move(option), std::move(promise), std::move(parent))); } class ConfigRecoverer final : public Actor { public: explicit ConfigRecoverer(ActorShared<> parent) : parent_(std::move(parent)) { connecting_since_ = Time::now(); } void on_dc_options_update(DcOptions dc_options) { dc_options_update_ = std::move(dc_options); update_dc_options(); loop(); } private: void on_network(bool has_network, uint32 network_generation) { has_network_ = has_network; if (network_generation_ != network_generation) { if (has_network_) { has_network_since_ = Time::now_cached(); } } loop(); } void on_online(bool is_online) { if (is_online_ == is_online) { return; } is_online_ = is_online; if (is_online) { if (simple_config_.dc_options.empty()) { simple_config_expires_at_ = 0; } if (full_config_ == nullptr) { full_config_expires_at_ = 0; } } loop(); } void on_connecting(bool is_connecting) { VLOG(config_recoverer) << "On connecting " << is_connecting; if (is_connecting && !is_connecting_) { connecting_since_ = Time::now_cached(); } is_connecting_ = is_connecting; loop(); } static bool check_phone_number_rules(Slice phone_number, Slice rules) { if (rules.empty() || phone_number.empty()) { return true; } bool found = false; for (auto prefix : full_split(rules, ',')) { if (prefix.empty()) { found = true; } else if (prefix[0] == '+' && begins_with(phone_number, prefix.substr(1))) { found = true; } else if (prefix[0] == '-' && begins_with(phone_number, prefix.substr(1))) { return false; } else { LOG(ERROR) << "Invalid prefix rule " << prefix; } } return found; } void on_simple_config(Result r_simple_config_result, bool dummy) { simple_config_query_.reset(); dc_options_i_ = 0; SimpleConfigResult cfg; if (r_simple_config_result.is_error()) { cfg.r_http_date = r_simple_config_result.error().clone(); cfg.r_config = r_simple_config_result.move_as_error(); } else { cfg = r_simple_config_result.move_as_ok(); } if (cfg.r_http_date.is_ok() && (date_option_i_ == 0 || cfg.r_config.is_error())) { G()->update_dns_time_difference(cfg.r_http_date.ok() - Time::now()); } else if (cfg.r_config.is_ok()) { G()->update_dns_time_difference(cfg.r_config.ok()->date_ - Time::now()); } date_option_i_ = (date_option_i_ + 1) % 2; do_on_simple_config(std::move(cfg.r_config)); update_dc_options(); loop(); } void do_on_simple_config(Result r_simple_config) { if (r_simple_config.is_ok()) { auto config = r_simple_config.move_as_ok(); VLOG(config_recoverer) << "Receive raw " << to_string(config); if (config->expires_ >= G()->unix_time()) { string phone_number = G()->get_option_string("my_phone_number"); simple_config_.dc_options.clear(); for (auto &rule : config->rules_) { if (check_phone_number_rules(phone_number, rule->phone_prefix_rules_) && DcId::is_valid(rule->dc_id_)) { DcId dc_id = DcId::internal(rule->dc_id_); for (auto &ip_port : rule->ips_) { DcOption option(dc_id, *ip_port); if (option.is_valid()) { simple_config_.dc_options.push_back(std::move(option)); } } } } VLOG(config_recoverer) << "Receive SimpleConfig " << simple_config_; } else { VLOG(config_recoverer) << "Config has expired at " << config->expires_; } simple_config_expires_at_ = get_config_expire_time(); simple_config_at_ = Time::now_cached(); for (size_t i = 1; i < simple_config_.dc_options.size(); i++) { std::swap(simple_config_.dc_options[i], simple_config_.dc_options[Random::fast(0, static_cast(i))]); } } else { VLOG(config_recoverer) << "Get SimpleConfig error " << r_simple_config.error(); simple_config_ = DcOptions(); simple_config_expires_at_ = get_failed_config_expire_time(); } } void on_full_config(Result> r_full_config, bool dummy) { full_config_query_.reset(); if (r_full_config.is_ok()) { full_config_ = r_full_config.move_as_ok(); VLOG(config_recoverer) << "Receive " << to_string(full_config_); full_config_expires_at_ = get_config_expire_time(); send_closure(G()->connection_creator(), &ConnectionCreator::on_dc_options, DcOptions(full_config_->dc_options_)); } else { VLOG(config_recoverer) << "Failed to get config: " << r_full_config.error(); full_config_ = nullptr; full_config_expires_at_ = get_failed_config_expire_time(); } loop(); } static bool expect_blocking() { return G()->get_option_boolean("expect_blocking", true); } double get_config_expire_time() const { auto offline_delay = is_online_ ? 0 : 5 * 60; auto expire_time = expect_blocking() ? Random::fast(2 * 60, 3 * 60) : Random::fast(20 * 60, 30 * 60); return Time::now() + offline_delay + expire_time; } double get_failed_config_expire_time() const { auto offline_delay = is_online_ ? 0 : 5 * 60; auto expire_time = expect_blocking() ? Random::fast(5, 7) : Random::fast(15, 30); return Time::now() + offline_delay + expire_time; } bool is_connecting_{false}; double connecting_since_{0}; bool is_online_{false}; bool has_network_{false}; double has_network_since_{0}; uint32 network_generation_{0}; DcOptions simple_config_; double simple_config_expires_at_{0}; double simple_config_at_{0}; ActorOwn<> simple_config_query_; DcOptions dc_options_update_; DcOptions dc_options_; // dc_options_update_ + simple_config_ double dc_options_at_{0}; size_t dc_options_i_{0}; size_t date_option_i_{0}; tl_object_ptr full_config_; double full_config_expires_at_{0}; ActorOwn<> full_config_query_; uint32 ref_cnt_{1}; bool close_flag_{false}; uint32 simple_config_turn_{0}; ActorShared<> parent_; void hangup_shared() final { ref_cnt_--; try_stop(); } void hangup() final { ref_cnt_--; close_flag_ = true; full_config_query_.reset(); simple_config_query_.reset(); try_stop(); } void try_stop() { if (ref_cnt_ == 0) { stop(); } } double max_connecting_delay() const { return expect_blocking() ? 5 : 20; } void loop() final { if (close_flag_) { return; } if (Session::is_high_loaded()) { VLOG(config_recoverer) << "Skip config recoverer under high load"; set_timeout_in(Random::fast(200, 300)); return; } if (is_connecting_) { VLOG(config_recoverer) << "Failed to connect for " << Time::now() - connecting_since_ << " seconds"; } else { VLOG(config_recoverer) << "Successfully connected in " << Time::now() - connecting_since_ << " seconds"; } Timestamp wakeup_timestamp; auto check_timeout = [&](Timestamp timestamp) { if (timestamp.at() < Time::now_cached()) { return true; } wakeup_timestamp.relax(timestamp); return false; }; bool has_connecting_problem = is_connecting_ && check_timeout(Timestamp::at(connecting_since_ + max_connecting_delay())); bool is_valid_simple_config = !check_timeout(Timestamp::at(simple_config_expires_at_)); if (!is_valid_simple_config && !simple_config_.dc_options.empty()) { simple_config_ = DcOptions(); update_dc_options(); } bool need_simple_config = has_connecting_problem && !is_valid_simple_config && simple_config_query_.empty(); bool has_dc_options = !dc_options_.dc_options.empty(); bool is_valid_full_config = !check_timeout(Timestamp::at(full_config_expires_at_)); bool need_full_config = has_connecting_problem && has_dc_options && !is_valid_full_config && full_config_query_.empty() && check_timeout(Timestamp::at(dc_options_at_ + (expect_blocking() ? 5 : 10))); if (need_simple_config) { ref_cnt_++; VLOG(config_recoverer) << "Ask simple config with turn " << simple_config_turn_; auto promise = PromiseCreator::lambda([self = actor_shared(this)](Result r_simple_config) { send_closure(self, &ConfigRecoverer::on_simple_config, std::move(r_simple_config), false); }); auto get_simple_config = [&] { switch (simple_config_turn_ % 10) { case 6: return get_simple_config_azure; case 2: return get_simple_config_firebase_remote_config; case 4: return get_simple_config_firebase_firestore; case 9: return get_simple_config_firebase_realtime; case 0: case 3: case 8: return get_simple_config_google_dns; case 1: case 5: case 7: default: return get_simple_config_mozilla_dns; } }(); simple_config_query_ = get_simple_config(std::move(promise), G()->get_option_boolean("prefer_ipv6"), G()->get_option_string("dc_txt_domain_name"), G()->is_test_dc(), G()->get_gc_scheduler_id()); simple_config_turn_++; } if (need_full_config) { ref_cnt_++; VLOG(config_recoverer) << "Ask full config with dc_options_i_ = " << dc_options_i_; full_config_query_ = get_full_config( dc_options_.dc_options[dc_options_i_], PromiseCreator::lambda( [actor_id = actor_id(this)](Result> r_full_config) { send_closure(actor_id, &ConfigRecoverer::on_full_config, std::move(r_full_config), false); }), actor_shared(this)); dc_options_i_ = (dc_options_i_ + 1) % dc_options_.dc_options.size(); } if (wakeup_timestamp) { VLOG(config_recoverer) << "Wakeup in " << format::as_time(wakeup_timestamp.in()); set_timeout_at(wakeup_timestamp.at()); } } void start_up() final { class StateCallback final : public StateManager::Callback { public: explicit StateCallback(ActorId parent) : parent_(std::move(parent)) { } bool on_state(ConnectionState state) final { send_closure(parent_, &ConfigRecoverer::on_connecting, state == ConnectionState::Connecting); return parent_.is_alive(); } bool on_network(NetType network_type, uint32 network_generation) final { send_closure(parent_, &ConfigRecoverer::on_network, network_type != NetType::None, network_generation); return parent_.is_alive(); } bool on_online(bool online_flag) final { send_closure(parent_, &ConfigRecoverer::on_online, online_flag); return parent_.is_alive(); } private: ActorId parent_; }; send_closure(G()->state_manager(), &StateManager::add_callback, make_unique(actor_id(this))); } void update_dc_options() { auto new_dc_options = simple_config_.dc_options; new_dc_options.insert(new_dc_options.begin(), dc_options_update_.dc_options.begin(), dc_options_update_.dc_options.end()); if (new_dc_options != dc_options_.dc_options) { dc_options_.dc_options = std::move(new_dc_options); dc_options_i_ = 0; dc_options_at_ = Time::now(); } } }; template void ConfigManager::AppConfig::store(StorerT &storer) const { td::store(version_, storer); td::store(hash_, storer); config_->store(storer); } template void ConfigManager::AppConfig::parse(ParserT &parser) { td::parse(version_, parser); if (version_ != CURRENT_VERSION) { return parser.set_error("Invalid config version"); } td::parse(hash_, parser); auto buffer = parser.template fetch_string_raw(parser.get_left_len()); TlBufferParser buffer_parser{&buffer}; config_ = telegram_api::jsonObject::fetch(buffer_parser); buffer_parser.fetch_end(); if (buffer_parser.get_error() != nullptr) { return parser.set_error(buffer_parser.get_error()); } } ConfigManager::ConfigManager(ActorShared<> parent) : parent_(std::move(parent)) { lazy_request_flood_control_.add_limit(20, 1); if (log_event_parse(app_config_, G()->td_db()->get_binlog_pmc()->get("app_config")).is_error()) { app_config_ = AppConfig(); } } void ConfigManager::start_up() { config_recoverer_ = create_actor("Recoverer", create_reference()); send_closure(config_recoverer_, &ConfigRecoverer::on_dc_options_update, load_dc_options_update()); auto expire_time = load_config_expire_time(); auto auth_manager = G()->td().get_actor_unsafe()->auth_manager_.get(); bool reload_config_on_restart = auth_manager == nullptr || !auth_manager->is_bot(); if (expire_time.is_in_past() || reload_config_on_restart) { request_config(false); } else { expire_time_ = expire_time; set_timeout_in(expire_time_.in()); } } ActorShared<> ConfigManager::create_reference() { ref_cnt_++; return actor_shared(this, REFCNT_TOKEN); } void ConfigManager::hangup_shared() { LOG_CHECK(get_link_token() == REFCNT_TOKEN) << "Receive link token " << get_link_token(); ref_cnt_--; try_stop(); } void ConfigManager::hangup() { ref_cnt_--; config_recoverer_.reset(); try_stop(); } void ConfigManager::loop() { if (expire_time_ && expire_time_.is_in_past()) { request_config(reopen_sessions_after_get_config_); expire_time_ = {}; } } void ConfigManager::try_stop() { if (ref_cnt_ == 0) { stop(); } } void ConfigManager::request_config(bool reopen_sessions) { if (G()->close_flag()) { return; } if (config_sent_cnt_ != 0 && !reopen_sessions) { return; } lazy_request_flood_control_.add_event(Time::now()); request_config_from_dc_impl(DcId::main(), reopen_sessions); } void ConfigManager::lazy_request_config() { if (G()->close_flag()) { return; } if (config_sent_cnt_ != 0) { return; } expire_time_.relax(Timestamp::at(lazy_request_flood_control_.get_wakeup_at())); set_timeout_at(expire_time_.at()); } void ConfigManager::reload_config(Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); reload_config_queries_.push_back(std::move(promise)); if (reload_config_queries_.size() != 1) { return; } request_config_from_dc_impl(DcId::main(), false); } void ConfigManager::try_request_app_config() { if (get_app_config_queries_.size() + reload_app_config_queries_.size() != 1) { return; } auto query = G()->net_query_creator().create_unauth(telegram_api::help_getAppConfig(app_config_.hash_)); query->total_timeout_limit_ = 60 * 60 * 24; G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, 1)); } void ConfigManager::get_app_config(Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto auth_manager = G()->td().get_actor_unsafe()->auth_manager_.get(); if (auth_manager != nullptr && auth_manager->is_bot()) { return promise.set_value(nullptr); } get_app_config_queries_.push_back(std::move(promise)); try_request_app_config(); } void ConfigManager::reload_app_config(Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto auth_manager = G()->td().get_actor_unsafe()->auth_manager_.get(); if (auth_manager != nullptr && auth_manager->is_bot()) { return promise.set_value(Unit()); } reload_app_config_queries_.push_back(std::move(promise)); try_request_app_config(); } void ConfigManager::get_content_settings(Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); auto auth_manager = G()->td().get_actor_unsafe()->auth_manager_.get(); if (auth_manager == nullptr || !auth_manager->is_authorized() || auth_manager->is_bot()) { return promise.set_value(Unit()); } get_content_settings_queries_.push_back(std::move(promise)); if (get_content_settings_queries_.size() == 1) { G()->net_query_dispatcher().dispatch_with_callback( G()->net_query_creator().create(telegram_api::account_getContentSettings()), actor_shared(this, 2)); } } void ConfigManager::set_content_settings(bool ignore_sensitive_content_restrictions, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); last_set_content_settings_ = ignore_sensitive_content_restrictions; auto &queries = set_content_settings_queries_[ignore_sensitive_content_restrictions]; queries.push_back(std::move(promise)); if (!is_set_content_settings_request_sent_) { is_set_content_settings_request_sent_ = true; G()->net_query_dispatcher().dispatch_with_callback( G()->net_query_creator().create( telegram_api::account_setContentSettings(0, ignore_sensitive_content_restrictions)), actor_shared(this, 3 + static_cast(ignore_sensitive_content_restrictions))); } } void ConfigManager::on_dc_options_update(DcOptions dc_options) { save_dc_options_update(dc_options); if (!dc_options.dc_options.empty()) { expire_time_ = Timestamp::now(); save_config_expire(expire_time_); set_timeout_in(expire_time_.in()); } send_closure(config_recoverer_, &ConfigRecoverer::on_dc_options_update, std::move(dc_options)); } void ConfigManager::request_config_from_dc_impl(DcId dc_id, bool reopen_sessions) { config_sent_cnt_++; reopen_sessions_after_get_config_ |= reopen_sessions; auto query = G()->net_query_creator().create_unauth(telegram_api::help_getConfig(), dc_id); query->total_timeout_limit_ = 60 * 60 * 24; G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, 8 + static_cast(reopen_sessions))); } void ConfigManager::do_set_ignore_sensitive_content_restrictions(bool ignore_sensitive_content_restrictions) { if (G()->have_option("ignore_sensitive_content_restrictions") && G()->get_option_boolean("ignore_sensitive_content_restrictions") == ignore_sensitive_content_restrictions) { return; } G()->set_option_boolean("ignore_sensitive_content_restrictions", ignore_sensitive_content_restrictions); reload_app_config(Auto()); } void ConfigManager::on_result(NetQueryPtr net_query) { auto token = get_link_token(); if (token == 3 || token == 4) { is_set_content_settings_request_sent_ = false; bool ignore_sensitive_content_restrictions = (token == 4); auto result_ptr = fetch_result(std::move(net_query)); if (result_ptr.is_error()) { fail_promises(set_content_settings_queries_[ignore_sensitive_content_restrictions], result_ptr.move_as_error()); } else { if (G()->get_option_boolean("can_ignore_sensitive_content_restrictions") && last_set_content_settings_ == ignore_sensitive_content_restrictions) { do_set_ignore_sensitive_content_restrictions(ignore_sensitive_content_restrictions); } set_promises(set_content_settings_queries_[ignore_sensitive_content_restrictions]); } if (!set_content_settings_queries_[!ignore_sensitive_content_restrictions].empty()) { if (ignore_sensitive_content_restrictions == last_set_content_settings_) { set_promises(set_content_settings_queries_[!ignore_sensitive_content_restrictions]); } else { set_content_settings(!ignore_sensitive_content_restrictions, Auto()); } } return; } if (token == 2) { auto result_ptr = fetch_result(std::move(net_query)); if (result_ptr.is_error()) { fail_promises(get_content_settings_queries_, result_ptr.move_as_error()); return; } auto result = result_ptr.move_as_ok(); do_set_ignore_sensitive_content_restrictions(result->sensitive_enabled_); G()->set_option_boolean("can_ignore_sensitive_content_restrictions", result->sensitive_can_change_); set_promises(get_content_settings_queries_); return; } if (token == 1) { auto promises = std::move(get_app_config_queries_); get_app_config_queries_.clear(); auto unit_promises = std::move(reload_app_config_queries_); reload_app_config_queries_.clear(); CHECK(!promises.empty() || !unit_promises.empty()); auto result_ptr = fetch_result(std::move(net_query)); if (result_ptr.is_error()) { fail_promises(promises, result_ptr.error().clone()); fail_promises(unit_promises, result_ptr.move_as_error()); return; } auto app_config_ptr = result_ptr.move_as_ok(); if (app_config_ptr->get_id() == telegram_api::help_appConfigNotModified::ID) { if (app_config_.version_ == 0) { LOG(ERROR) << "Receive appConfigNotModified"; fail_promises(promises, Status::Error(500, "Receive unexpected response")); fail_promises(unit_promises, Status::Error(500, "Receive unexpected response")); return; } CHECK(app_config_.config_ != nullptr); } else { CHECK(app_config_ptr->get_id() == telegram_api::help_appConfig::ID); auto app_config = telegram_api::move_object_as(app_config_ptr); process_app_config(app_config->config_); app_config_.version_ = AppConfig::CURRENT_VERSION; app_config_.hash_ = app_config->hash_; app_config_.config_ = std::move(app_config->config_); CHECK(app_config_.config_ != nullptr); G()->td_db()->get_binlog_pmc()->set("app_config", log_event_store(app_config_).as_slice().str()); } G()->get_option_manager()->update_premium_options(); for (auto &promise : promises) { promise.set_value(convert_json_value_object(app_config_.config_)); } set_promises(unit_promises); return; } CHECK(token == 8 || token == 9); CHECK(config_sent_cnt_ > 0); config_sent_cnt_--; auto r_config = fetch_result(std::move(net_query)); if (r_config.is_error()) { if (!G()->close_flag()) { LOG(WARNING) << "Failed to get config: " << r_config.error(); expire_time_ = Timestamp::in(60.0); // try again in a minute set_timeout_in(expire_time_.in()); } fail_promises(reload_config_queries_, r_config.move_as_error()); } else { on_dc_options_update(DcOptions()); process_config(r_config.move_as_ok()); if (token == 9) { G()->net_query_dispatcher().update_mtproto_header(); reopen_sessions_after_get_config_ = false; } set_promises(reload_config_queries_); } } void ConfigManager::save_dc_options_update(const DcOptions &dc_options) { if (dc_options.dc_options.empty()) { G()->td_db()->get_binlog_pmc()->erase("dc_options_update"); return; } G()->td_db()->get_binlog_pmc()->set("dc_options_update", log_event_store(dc_options).as_slice().str()); } DcOptions ConfigManager::load_dc_options_update() { auto log_event_dc_options = G()->td_db()->get_binlog_pmc()->get("dc_options_update"); DcOptions dc_options; if (!log_event_dc_options.empty()) { log_event_parse(dc_options, log_event_dc_options).ensure(); } return dc_options; } Timestamp ConfigManager::load_config_expire_time() { auto expires_in = to_integer(G()->td_db()->get_binlog_pmc()->get("config_expire")) - Clocks::system(); if (expires_in < 0 || expires_in > 60 * 60 /* 1 hour */) { return Timestamp::now(); } else { return Timestamp::in(expires_in); } } void ConfigManager::save_config_expire(Timestamp timestamp) { G()->td_db()->get_binlog_pmc()->set("config_expire", to_string(static_cast(Clocks::system() + timestamp.in()))); } void ConfigManager::process_config(tl_object_ptr config) { bool is_from_main_dc = G()->net_query_dispatcher().get_main_dc_id().get_value() == config->this_dc_; LOG(INFO) << to_string(config); auto reload_in = clamp(config->expires_ - config->date_, 60, 86400); save_config_expire(Timestamp::in(reload_in)); reload_in -= Random::fast(0, reload_in / 5); if (!is_from_main_dc) { reload_in = 0; } expire_time_ = Timestamp::in(reload_in); set_timeout_at(expire_time_.at()); LOG_IF(ERROR, config->test_mode_ != G()->is_test_dc()) << "Wrong parameter is_test"; OptionManager &options = *G()->get_option_manager(); // Do not save dc_options in config, because it will be interpreted and saved by ConnectionCreator. DcOptions dc_options(config->dc_options_); send_closure(G()->connection_creator(), &ConnectionCreator::on_dc_options, std::move(dc_options)); options.set_option_integer("recent_stickers_limit", config->stickers_recent_limit_); options.set_option_integer("channels_read_media_period", config->channels_read_media_period_); send_closure(G()->link_manager(), &LinkManager::update_autologin_token, std::move(config->autologin_token_)); options.set_option_boolean("test_mode", config->test_mode_); options.set_option_integer("forwarded_message_count_max", config->forwarded_count_max_); options.set_option_integer("basic_group_size_max", config->chat_size_max_); options.set_option_integer("supergroup_size_max", config->megagroup_size_max_); if (is_from_main_dc || !options.have_option("expect_blocking")) { options.set_option_boolean("expect_blocking", config->blocked_mode_); } if (is_from_main_dc || !options.have_option("dc_txt_domain_name")) { options.set_option_string("dc_txt_domain_name", config->dc_txt_domain_name_); } if (is_from_main_dc || !options.have_option("t_me_url")) { auto url = config->me_url_prefix_; if (!url.empty()) { if (url.back() != '/') { url.push_back('/'); } options.set_option_string("t_me_url", url); } } if (is_from_main_dc) { options.set_option_integer("webfile_dc_id", config->webfile_dc_id_); if (config->tmp_sessions_ > 1) { options.set_option_integer("session_count", config->tmp_sessions_); } else { options.set_option_empty("session_count"); } if (!config->suggested_lang_code_.empty() || config->lang_pack_version_ > 0 || config->base_lang_pack_version_ > 0) { options.set_option_string("suggested_language_pack_id", config->suggested_lang_code_); options.set_option_integer("language_pack_version", config->lang_pack_version_); options.set_option_integer("base_language_pack_version", config->base_lang_pack_version_); } else { options.set_option_empty("suggested_language_pack_id"); options.set_option_empty("language_pack_version"); options.set_option_empty("base_language_pack_version"); } } if (is_from_main_dc) { options.set_option_integer("edit_time_limit", config->edit_time_limit_); options.set_option_boolean("revoke_pm_inbox", config->revoke_pm_inbox_); options.set_option_integer("revoke_time_limit", config->revoke_time_limit_); options.set_option_integer("revoke_pm_time_limit", config->revoke_pm_time_limit_); options.set_option_integer("rating_e_decay", config->rating_e_decay_); } options.set_option_integer("call_ring_timeout_ms", config->call_ring_timeout_ms_); options.set_option_integer("call_connect_timeout_ms", config->call_connect_timeout_ms_); options.set_option_integer("call_packet_timeout_ms", config->call_packet_timeout_ms_); options.set_option_integer("call_receive_timeout_ms", config->call_receive_timeout_ms_); options.set_option_integer("message_text_length_max", clamp(config->message_length_max_, 4096, 1000000)); options.set_option_integer("message_caption_length_max", clamp(config->caption_length_max_, 1024, 1000000)); if (config->gif_search_username_.empty()) { options.set_option_empty("animation_search_bot_username"); } else { options.set_option_string("animation_search_bot_username", config->gif_search_username_); } if (!options.have_option("venue_search_bot_username")) { if (config->venue_search_username_.empty()) { options.set_option_empty("venue_search_bot_username"); } else { options.set_option_string("venue_search_bot_username", config->venue_search_username_); } } if (config->img_search_username_.empty()) { options.set_option_empty("photo_search_bot_username"); } else { options.set_option_string("photo_search_bot_username", config->img_search_username_); } auto fix_timeout_ms = [](int32 timeout_ms) { return clamp(timeout_ms, 1000, 86400 * 1000); }; options.set_option_integer("online_update_period_ms", fix_timeout_ms(config->online_update_period_ms_)); options.set_option_integer("online_cloud_timeout_ms", fix_timeout_ms(config->online_cloud_timeout_ms_)); options.set_option_integer("notification_cloud_delay_ms", fix_timeout_ms(config->notify_cloud_delay_ms_)); options.set_option_integer("notification_default_delay_ms", fix_timeout_ms(config->notify_default_delay_ms_)); if (is_from_main_dc && !options.have_option("default_reaction_need_sync")) { ReactionType reaction_type(config->reactions_default_); if (!reaction_type.is_empty() && !reaction_type.is_paid_reaction()) { options.set_option_string("default_reaction", reaction_type.get_string()); } } // delete outdated options options.set_option_empty("suggested_language_code"); options.set_option_empty("chat_big_size"); options.set_option_empty("group_size_max"); options.set_option_empty("saved_gifs_limit"); options.set_option_empty("sessions_count"); options.set_option_empty("forwarded_messages_count_max"); options.set_option_empty("broadcast_size_max"); options.set_option_empty("group_chat_size_max"); options.set_option_empty("chat_size_max"); options.set_option_empty("megagroup_size_max"); options.set_option_empty("offline_blur_timeout_ms"); options.set_option_empty("offline_idle_timeout_ms"); options.set_option_empty("notify_cloud_delay_ms"); options.set_option_empty("notify_default_delay_ms"); options.set_option_empty("large_chat_size"); options.set_option_empty("calls_enabled"); // TODO implement online status updates // options.set_option_integer("offline_blur_timeout_ms", config->offline_blur_timeout_ms_); // options.set_option_integer("offline_idle_timeout_ms", config->offline_idle_timeout_ms_); // options.set_option_integer("push_chat_period_ms", config->push_chat_period_ms_); // options.set_option_integer("push_chat_limit", config->push_chat_limit_); if (is_from_main_dc) { reload_app_config(Auto()); if (!options.have_option("can_ignore_sensitive_content_restrictions") || !options.have_option("ignore_sensitive_content_restrictions")) { get_content_settings(Auto()); } } } void ConfigManager::process_app_config(tl_object_ptr &config) { CHECK(config != nullptr); LOG(INFO) << "Receive app config " << to_string(config); vector autologin_domains; vector url_auth_domains; vector whitelisted_domains; vector> new_values; string ignored_restriction_reasons; string restriction_add_platforms; vector dice_emojis; FlatHashMap dice_emoji_index; FlatHashMap dice_emoji_success_value; vector emoji_sounds; string animation_search_emojis; double animated_emoji_zoom = 0.0; vector premium_features; auto &premium_limit_keys = get_premium_limit_keys(); string premium_bot_username; string premium_invoice_slug; bool is_premium_available = false; vector fragment_prefixes; int32 transcribe_audio_trial_weekly_number = 0; int32 transcribe_audio_trial_duration_max = 0; int32 transcribe_audio_trial_cooldown_until = 0; vector business_features; bool need_premium_for_new_chat_privacy = true; vector starref_start_param_prefixes; int32 freeze_since_date = 0; int32 freeze_until_date = 0; string freeze_appeal_url; bool can_accept_calls = true; bool need_age_video_verification = false; string verify_age_bot_username; string verify_age_country; int32 verify_age_min = 0; string whitelisted_bots; string ton_stakedice_stake_suggested_amounts; string gift_craft_probabilities; // {"stories_all_hidden", "archive_all_stories"} static const FlatHashMap bool_keys = { {"autoarchive_setting_available", "can_archive_and_mute_new_chats_from_unknown_users"}, {"can_edit_factcheck", "can_edit_fact_check"}, {"channel_revenue_withdrawal_enabled", "can_withdraw_chat_revenue"}, {"message_primary_edited_date", "show_message_edit_date_by_default"}, {"premium_gift_attach_menu_icon", "gift_premium_from_attachment_menu"}, {"premium_gift_text_field_icon", "gift_premium_from_input_field"}, {"settings_display_passkeys", "can_use_login_passkey"}, {"stars_gifts_enabled", "can_gift_stars"}, {"stars_paid_messages_available", "can_enable_paid_messages"}, {"story_weather_preload", "can_preload_weather"}, {"video_ignore_alt_documents", ""}}; static const FlatHashMap integer_keys = { {"aicompose_tone_examples_num", "text_composition_style_example_count"}, {"aicompose_tone_prompt_length_max", "text_composition_style_prompt_length_max"}, {"aicompose_tone_title_length_max", "text_composition_style_title_length_max"}, {"authorization_autoconfirm_period", ""}, {"boosts_channel_level_max", "chat_boost_level_max"}, {"boosts_per_sent_gift", "premium_gift_boost_count"}, {"bot_preview_medias_max", "bot_media_preview_count_max"}, {"bot_verification_description_length_limit", "bot_verification_custom_description_length_max"}, {"business_chat_links_limit", "business_chat_link_count_max"}, {"channel_autotranslation_level_min", ""}, {"channel_bg_icon_level_min", ""}, {"channel_custom_wallpaper_level_min", ""}, {"channel_emoji_status_level_min", ""}, {"channel_profile_bg_icon_level_min", ""}, {"channel_restrict_sponsored_level_min", ""}, {"channel_wallpaper_level_min", ""}, {"chat_read_mark_expire_period", ""}, {"chat_read_mark_size_threshold", ""}, {"chatlist_update_period", "chat_folder_new_chats_update_period"}, {"conference_call_size_limit", "group_call_participant_count_max"}, {"contact_note_length_limit", "user_note_text_length_max"}, {"factcheck_length_limit", "fact_check_length_max"}, {"giveaway_add_peers_max", "giveaway_additional_chat_count_max"}, {"giveaway_boosts_per_premium", "giveaway_boost_count_per_premium"}, {"giveaway_countries_max", "giveaway_country_count_max"}, {"giveaway_period_max", "giveaway_duration_max"}, {"group_call_message_length_limit", "group_call_message_text_length_max"}, {"group_call_message_ttl", "group_call_message_show_time_max"}, {"group_custom_wallpaper_level_min", ""}, {"group_emoji_status_level_min", ""}, {"group_emoji_stickers_level_min", ""}, {"group_profile_bg_icon_level_min", ""}, {"group_transcribe_level_min", ""}, {"group_wallpaper_level_min", ""}, {"hidden_members_group_size_min", ""}, {"intro_description_length_limit", "business_start_page_message_length_max"}, {"intro_title_length_limit", "business_start_page_title_length_max"}, {"message_typing_draft_ttl", "pending_text_message_period"}, {"no_forwards_request_expire_period", "has_protected_content_disable_request_duration"}, {"passkeys_account_passkeys_max", "login_passkey_count_max"}, {"pm_read_date_expire_period", ""}, {"poll_answer_delete_period", ""}, {"poll_answers_max", "poll_answer_count_max"}, {"poll_close_period_max", "poll_open_period_max"}, {"poll_countries_max", "poll_country_count_max"}, {"quick_replies_limit", "quick_reply_shortcut_count_max"}, {"quick_reply_messages_limit", "quick_reply_shortcut_message_count_max"}, {"quote_length_max", "message_reply_quote_length_max"}, {"reactions_in_chat_max", "chat_available_reaction_count_max"}, {"reactions_uniq_max", ""}, {"reactions_user_max_default", ""}, {"reactions_user_max_premium", ""}, {"rich_message_length_limit", "rich_message_text_length_max"}, {"rich_message_max_blocks", "rich_message_block_count_max"}, {"rich_message_max_depth", "rich_message_depth_max"}, {"rich_message_max_media", "rich_message_media_count_max"}, {"rich_message_max_table_cols", "rich_message_table_column_count_max"}, {"ringtone_duration_max", "notification_sound_duration_max"}, {"ringtone_saved_count_max", "notification_sound_count_max"}, {"ringtone_size_max", "notification_sound_size_max"}, {"stargifts_collection_gifts_limit", "gift_collection_size_max"}, {"stargifts_collections_limit", "gift_collection_count_max"}, {"stargifts_convert_period_max", "gift_sell_period"}, {"stargifts_message_length_max", "gift_text_length_max"}, {"stargifts_pinned_to_top_limit", "pinned_gift_count_max"}, {"starref_max_commission_permille", "affiliate_program_commission_per_mille_max"}, {"starref_min_commission_permille", "affiliate_program_commission_per_mille_min"}, {"stars_groupcall_message_amount_max", "paid_group_call_message_star_count_max"}, {"stars_paid_message_amount_max", "paid_message_star_count_max"}, {"stars_paid_message_commission_permille", "paid_message_earnings_per_mille"}, {"stars_paid_messages_channel_amount_default", "direct_channel_message_star_count_default"}, {"stars_paid_post_amount_max", "paid_media_message_star_count_max"}, {"stars_paid_reaction_amount_max", "paid_reaction_star_count_max"}, {"stars_revenue_withdrawal_max", "star_withdrawal_count_max"}, {"stars_revenue_withdrawal_min", "star_withdrawal_count_min"}, {"stars_stargift_resale_amount_max", "gift_resale_star_count_max"}, {"stars_stargift_resale_amount_min", "gift_resale_star_count_min"}, {"stars_stargift_resale_commission_permille", "gift_resale_star_earnings_per_mille"}, {"stars_subscription_amount_max", "subscription_star_count_max"}, {"stars_suggested_post_age_min", "suggested_post_lifetime_min"}, {"stars_suggested_post_amount_min", "suggested_post_star_count_min"}, {"stars_suggested_post_amount_max", "suggested_post_star_count_max"}, {"stars_suggested_post_commission_permille", "suggested_post_star_earnings_per_mille"}, {"stars_suggested_post_future_min", "suggested_post_send_delay_min"}, {"stars_suggested_post_future_max", "suggested_post_send_delay_max"}, {"stars_usd_sell_rate_x1000", "usd_to_thousand_star_rate"}, {"stars_usd_withdraw_rate_x1000", "thousand_star_to_usd_rate"}, {"stickers_premium_by_emoji_num", ""}, {"stickers_normal_by_emoji_per_premium_num", ""}, {"stories_album_stories_limit", "story_album_size_max"}, {"stories_albums_limit", "story_album_count_max"}, {"stories_area_url_max", "story_link_area_count_max"}, {"stories_pinned_to_top_count_max", "pinned_story_count_max"}, {"stories_stealth_cooldown_period", "story_stealth_mode_cooldown_period"}, {"stories_stealth_future_period", "story_stealth_mode_future_period"}, {"stories_stealth_past_period", "story_stealth_mode_past_period"}, {"story_viewers_expire_period", "story_viewers_expiration_delay"}, {"telegram_antispam_group_size_min", "aggressive_anti_spam_supergroup_member_count_min"}, {"todo_item_length_max", "checklist_task_text_length_max"}, {"todo_items_max", "checklist_task_count_max"}, {"todo_title_length_max", "checklist_title_length_max"}, {"ton_stargift_resale_commission_permille", "gift_resale_gram_earnings_per_mille"}, {"ton_suggested_post_commission_permille", "suggested_post_gram_earnings_per_mille"}, {"topics_pinned_limit", "pinned_forum_topic_count_max"}, {"upload_premium_speedup_download", "premium_download_speedup"}, {"upload_premium_speedup_notify_period", ""}, {"upload_premium_speedup_upload", "premium_upload_speedup"}}; static const FlatHashMap long_keys = { {"stories_changelog_user_id", "stories_changelog_user_id"}, {"telegram_antispam_user_id", "anti_spam_bot_user_id"}, {"ton_stakedice_stake_amount_max", "stake_dice_stake_amount_max"}, {"ton_stakedice_stake_amount_min", "stake_dice_stake_amount_min"}}; static const FlatHashMap string_keys = { {"gif_search_branding", "animation_search_provider"}, {"music_search_username", "audio_search_bot_username"}, {"phone_country_iso2", ""}, {"premium_manage_subscription_url", ""}, {"stories_venue_search_username", "venue_search_bot_username"}, {"ton_blockchain_explorer_url", ""}, {"ton_proxy_address", ""}, {"ton_topup_url", "gram_top_up_url"}, {"weather_search_username", "weather_bot_username"}}; static const FlatHashSet ignored_options( {"channel_color_level_min", "default_emoji_statuses_stickerset_id", "dialog_filters_enabled", "dialog_filters_tooltip", "forum_upgrade_participants_min", "getfile_experimental_params", "giveaway_gifts_purchase_available", "groupcall_video_participants_max", "ios_display_passkeys", "message_animated_emoji_max", "qr_login_camera", "qr_login_code", "stargifts_blocked", "starref_connect_allowed", "starref_program_allowed", "stars_purchase_blocked", "stars_rating_learnmore_url", "stickers_emoji_cache_time", "stories_export_nopublic_link", "stories_posting", "story_expire_period", "test", "upload_max_fileparts_default", // "upload_max_fileparts_premium"}); if (config->get_id() == telegram_api::jsonObject::ID) { for (auto &key_value : static_cast(config.get())->value_) { Slice key = key_value->key_; { auto it = bool_keys.find(key); if (it != bool_keys.end()) { G()->set_option_boolean(it->second.empty() ? key : it->second, get_json_value_bool(std::move(key_value->value_), key)); continue; } } { auto it = integer_keys.find(key); if (it != integer_keys.end()) { G()->set_option_integer(it->second.empty() ? key : it->second, max(0, get_json_value_int(std::move(key_value->value_), key))); continue; } } { auto it = long_keys.find(key); if (it != long_keys.end()) { G()->set_option_integer(it->second.empty() ? key : it->second, max(static_cast(0), get_json_value_long(std::move(key_value->value_), key))); continue; } } { auto it = string_keys.find(key); if (it != string_keys.end()) { G()->set_option_string(it->second.empty() ? key : it->second, get_json_value_string(std::move(key_value->value_), key)); continue; } } if (ignored_options.count(key)) { continue; } telegram_api::JSONValue *value = key_value->value_.get(); if (key == "ignore_restriction_reasons") { if (value->get_id() == telegram_api::jsonArray::ID) { auto reasons = std::move(static_cast(value)->value_); for (auto &reason : reasons) { auto reason_name = get_json_value_string(std::move(reason), key); if (!reason_name.empty() && reason_name.find(',') == string::npos) { if (!ignored_restriction_reasons.empty()) { ignored_restriction_reasons += ','; } ignored_restriction_reasons += reason_name; } else { LOG(ERROR) << "Receive unexpected restriction reason " << reason_name; } } } else { LOG(ERROR) << "Receive unexpected ignore_restriction_reasons " << to_string(*value); } continue; } if (key == "restriction_add_platforms") { if (value->get_id() == telegram_api::jsonArray::ID) { auto platforms = std::move(static_cast(value)->value_); for (auto &platform : platforms) { auto platform_name = get_json_value_string(std::move(platform), key); if (!platform_name.empty() && platform_name.find(',') == string::npos) { if (!restriction_add_platforms.empty()) { restriction_add_platforms += ','; } restriction_add_platforms += platform_name; } else { LOG(ERROR) << "Receive unexpected restriction platform " << platform_name; } } } else { LOG(ERROR) << "Receive unexpected restriction_add_platforms " << to_string(*value); } continue; } if (key == "emojies_animated_zoom") { animated_emoji_zoom = get_json_value_double(std::move(key_value->value_), key); continue; } if (key == "emojies_send_dice") { if (value->get_id() == telegram_api::jsonArray::ID) { auto emojis = std::move(static_cast(value)->value_); for (auto &emoji : emojis) { auto emoji_text = get_json_value_string(std::move(emoji), key); if (!emoji_text.empty()) { dice_emoji_index[emoji_text] = dice_emojis.size(); dice_emojis.push_back(emoji_text); } else { LOG(ERROR) << "Receive empty dice emoji"; } } } else { LOG(ERROR) << "Receive unexpected emojies_send_dice " << to_string(*value); } continue; } if (key == "emojies_send_dice_success") { if (value->get_id() == telegram_api::jsonObject::ID) { auto success_values = std::move(static_cast(value)->value_); for (auto &success_value : success_values) { CHECK(success_value != nullptr); if (!success_value->key_.empty() && success_value->value_->get_id() == telegram_api::jsonObject::ID) { int32 dice_value = -1; int32 frame_start = -1; for (auto &dice_key_value : static_cast(success_value->value_.get())->value_) { if (dice_key_value->value_->get_id() != telegram_api::jsonNumber::ID) { continue; } auto current_value = get_json_value_int(std::move(dice_key_value->value_), Slice()); if (dice_key_value->key_ == "value") { dice_value = current_value; } if (dice_key_value->key_ == "frame_start") { frame_start = current_value; } } if (dice_value < 0 || frame_start < 0) { LOG(ERROR) << "Receive unexpected dice success value " << to_string(success_value); } else { dice_emoji_success_value[success_value->key_] = PSTRING() << dice_value << ':' << frame_start; } } else { LOG(ERROR) << "Receive unexpected dice success value " << to_string(success_value); } } } else { LOG(ERROR) << "Receive unexpected emojies_send_dice_success " << to_string(*value); } continue; } if (key == "emojies_sounds") { if (value->get_id() == telegram_api::jsonObject::ID) { auto sounds = std::move(static_cast(value)->value_); for (auto &sound : sounds) { CHECK(sound != nullptr); if (sound->value_->get_id() == telegram_api::jsonObject::ID) { string id; string access_hash; string file_reference_base64; for (auto &sound_key_value : static_cast(sound->value_.get())->value_) { if (sound_key_value->value_->get_id() != telegram_api::jsonString::ID) { continue; } auto current_value = get_json_value_string(std::move(sound_key_value->value_), Slice()); if (sound_key_value->key_ == "id") { id = std::move(current_value); } else if (sound_key_value->key_ == "access_hash") { access_hash = std::move(current_value); } else if (sound_key_value->key_ == "file_reference_base64") { file_reference_base64 = std::move(current_value); } } if (to_integer_safe(id).is_error() || to_integer_safe(access_hash).is_error() || !is_base64url(file_reference_base64) || !is_emoji(sound->key_)) { LOG(ERROR) << "Receive unexpected sound value " << to_string(sound); } else { emoji_sounds.push_back(sound->key_); emoji_sounds.push_back(PSTRING() << id << ':' << access_hash << ':' << file_reference_base64); } } else { LOG(ERROR) << "Receive unexpected emoji sound " << to_string(sound); } } } else { LOG(ERROR) << "Receive unexpected emojies_sounds " << to_string(*value); } continue; } if (key == "gif_search_emojies") { if (value->get_id() == telegram_api::jsonArray::ID) { auto emojis = std::move(static_cast(value)->value_); for (auto &emoji : emojis) { auto emoji_str = get_json_value_string(std::move(emoji), key); if (!emoji_str.empty() && emoji_str.find(',') == string::npos) { if (!animation_search_emojis.empty()) { animation_search_emojis += ','; } animation_search_emojis += emoji_str; } else { LOG(ERROR) << "Receive unexpected animation search emoji " << emoji_str; } } } else { LOG(ERROR) << "Receive unexpected gif_search_emojies " << to_string(*value); } continue; } if (key == "autologin_domains") { if (value->get_id() == telegram_api::jsonArray::ID) { auto domains = std::move(static_cast(value)->value_); for (auto &domain : domains) { autologin_domains.push_back(get_json_value_string(std::move(domain), key)); } } else { LOG(ERROR) << "Receive unexpected autologin_domains " << to_string(*value); } continue; } if (key == "url_auth_domains") { if (value->get_id() == telegram_api::jsonArray::ID) { auto domains = std::move(static_cast(value)->value_); for (auto &domain : domains) { url_auth_domains.push_back(get_json_value_string(std::move(domain), key)); } } else { LOG(ERROR) << "Receive unexpected url_auth_domains " << to_string(*value); } continue; } if (key == "whitelisted_domains") { if (value->get_id() == telegram_api::jsonArray::ID) { auto domains = std::move(static_cast(value)->value_); for (auto &domain : domains) { whitelisted_domains.push_back(get_json_value_string(std::move(domain), key)); } } else { LOG(ERROR) << "Receive unexpected whitelisted_domains " << to_string(*value); } continue; } if (key == "round_video_encoding") { if (value->get_id() == telegram_api::jsonObject::ID) { auto video_note_settings = std::move(static_cast(value)->value_); for (auto &video_note_setting : video_note_settings) { CHECK(video_note_setting != nullptr); if (video_note_setting->key_ != "diameter" && video_note_setting->key_ != "video_bitrate" && video_note_setting->key_ != "audio_bitrate" && video_note_setting->key_ != "max_size") { continue; } if (video_note_setting->value_->get_id() == telegram_api::jsonNumber::ID) { auto setting_value = get_json_value_int(std::move(video_note_setting->value_), Slice()); if (setting_value > 0) { if (video_note_setting->key_ == "diameter") { G()->set_option_integer("suggested_video_note_length", setting_value); } if (video_note_setting->key_ == "video_bitrate") { G()->set_option_integer("suggested_video_note_video_bitrate", setting_value); } if (video_note_setting->key_ == "audio_bitrate") { G()->set_option_integer("suggested_video_note_audio_bitrate", setting_value); } if (video_note_setting->key_ == "max_size") { G()->set_option_integer("video_note_size_max", setting_value); } } } else { LOG(ERROR) << "Receive unexpected video note setting " << to_string(video_note_setting); } } } else { LOG(ERROR) << "Receive unexpected round_video_encoding " << to_string(*value); } continue; } if (key == "premium_promo_order") { if (value->get_id() == telegram_api::jsonArray::ID) { auto features = std::move(static_cast(value)->value_); for (auto &feature : features) { auto premium_feature = get_json_value_string(std::move(feature), key); if (!td::contains(premium_feature, ',')) { premium_features.push_back(std::move(premium_feature)); } } } else { LOG(ERROR) << "Receive unexpected premium_promo_order " << to_string(*value); } continue; } bool is_premium_limit_key = false; for (auto premium_limit_key : premium_limit_keys) { if (begins_with(key, premium_limit_key)) { auto suffix = key.substr(premium_limit_key.size()); if (suffix == "_limit_default" || suffix == "_limit_premium") { auto setting_value = get_json_value_int(std::move(key_value->value_), key); if (setting_value > 0) { G()->set_option_integer(key, setting_value); } else { LOG(ERROR) << "Receive invalid value " << setting_value << " for " << key; } is_premium_limit_key = true; break; } } } if (is_premium_limit_key) { continue; } if (key == "premium_bot_username") { premium_bot_username = get_json_value_string(std::move(key_value->value_), key); continue; } if (key == "premium_invoice_slug") { premium_invoice_slug = get_json_value_string(std::move(key_value->value_), key); continue; } if (key == "premium_purchase_blocked") { is_premium_available = !get_json_value_bool(std::move(key_value->value_), key); continue; } if (key == "fragment_prefixes") { if (value->get_id() == telegram_api::jsonArray::ID) { auto prefixes = std::move(static_cast(value)->value_); for (auto &prefix : prefixes) { auto prefix_text = get_json_value_string(std::move(prefix), key); clean_phone_number(prefix_text); if (!prefix_text.empty()) { fragment_prefixes.push_back(prefix_text); } else { LOG(ERROR) << "Receive an invalid Fragment prefix"; } } } else { LOG(ERROR) << "Receive unexpected fragment_prefixes " << to_string(*value); } continue; } if (key == "stories_entities") { G()->set_option_boolean("need_premium_for_story_caption_entities", get_json_value_string(std::move(key_value->value_), key) == "premium"); continue; } if (key == "transcribe_audio_trial_weekly_number") { transcribe_audio_trial_weekly_number = get_json_value_int(std::move(key_value->value_), key); continue; } if (key == "transcribe_audio_trial_duration_max") { transcribe_audio_trial_duration_max = get_json_value_int(std::move(key_value->value_), key); continue; } if (key == "transcribe_audio_trial_cooldown_until") { transcribe_audio_trial_cooldown_until = get_json_value_int(std::move(key_value->value_), key); continue; } if (key == "business_promo_order") { if (value->get_id() == telegram_api::jsonArray::ID) { auto features = std::move(static_cast(value)->value_); for (auto &feature : features) { auto business_feature = get_json_value_string(std::move(feature), key); if (!td::contains(business_feature, ',')) { business_features.push_back(std::move(business_feature)); } } } else { LOG(ERROR) << "Receive unexpected business_promo_order " << to_string(*value); } continue; } if (key == "new_noncontact_peers_require_premium_without_ownpremium") { need_premium_for_new_chat_privacy = !get_json_value_bool(std::move(key_value->value_), key); continue; } if (key == "web_app_allowed_protocols") { if (value->get_id() == telegram_api::jsonArray::ID) { vector protocol_names; auto protocols = std::move(static_cast(value)->value_); for (auto &protocol : protocols) { auto protocol_name = get_json_value_string(std::move(protocol), key); if (!td::contains(protocol_name, ' ')) { protocol_names.push_back(std::move(protocol_name)); } } G()->set_option_string("web_app_allowed_protocols", implode(protocol_names, ' ')); } else { LOG(ERROR) << "Receive unexpected web_app_allowed_protocols " << to_string(*value); } continue; } if (key == "starref_start_param_prefixes") { if (value->get_id() == telegram_api::jsonArray::ID) { auto prefixes = std::move(static_cast(value)->value_); for (auto &prefix : prefixes) { auto prefix_text = get_json_value_string(std::move(prefix), key); if (!prefix_text.empty() && prefix_text.find(' ') == string::npos) { fragment_prefixes.push_back(prefix_text); } else { LOG(ERROR) << "Receive an invalid affiliate program link prefix"; } } } else { LOG(ERROR) << "Receive unexpected starref_start_param_prefixes " << to_string(*value); } continue; } if (key == "freeze_since_date") { freeze_since_date = get_json_value_int(std::move(key_value->value_), key); continue; } if (key == "freeze_until_date") { freeze_until_date = get_json_value_int(std::move(key_value->value_), key); continue; } if (key == "freeze_appeal_url") { freeze_appeal_url = get_json_value_string(std::move(key_value->value_), key); continue; } if (key == "call_requests_disabled") { can_accept_calls = !get_json_value_bool(std::move(key_value->value_), key); continue; } if (key == "ton_suggested_post_amount_min") { G()->set_option_integer("suggested_post_gram_cent_count_min", get_json_value_long(std::move(key_value->value_), key) / 10000000); continue; } if (key == "ton_suggested_post_amount_max") { G()->set_option_integer("suggested_post_gram_cent_count_max", get_json_value_long(std::move(key_value->value_), key) / 10000000); continue; } if (key == "ton_stargift_resale_amount_min") { G()->set_option_integer("gift_resale_gram_cent_count_min", get_json_value_long(std::move(key_value->value_), key) / 10000000); continue; } if (key == "ton_stargift_resale_amount_max") { G()->set_option_integer("gift_resale_gram_cent_count_max", get_json_value_long(std::move(key_value->value_), key) / 10000000); continue; } if (key == "ton_usd_rate") { G()->set_option_integer("million_gram_to_usd_rate", static_cast(get_json_value_double(std::move(key_value->value_), key) * 1000000)); continue; } if (key == "need_age_video_verification") { need_age_video_verification = get_json_value_bool(std::move(key_value->value_), key); continue; } if (key == "verify_age_bot_username") { verify_age_bot_username = get_json_value_string(std::move(key_value->value_), key); continue; } if (key == "verify_age_country") { verify_age_country = get_json_value_string(std::move(key_value->value_), key); continue; } if (key == "verify_age_min") { verify_age_min = get_json_value_int(std::move(key_value->value_), key); continue; } if (key == "whitelisted_bots") { if (value->get_id() == telegram_api::jsonArray::ID) { auto bot_user_ids = std::move(static_cast(value)->value_); for (auto &bot_user_id : bot_user_ids) { auto user_id = UserId(get_json_value_long(std::move(bot_user_id), key)); if (user_id.is_valid()) { if (!whitelisted_bots.empty()) { whitelisted_bots += ','; } whitelisted_bots += to_string(user_id.get()); } else { LOG(ERROR) << "Receive unexpected bot user identifier " << user_id; } } } else { LOG(ERROR) << "Receive unexpected whitelisted_bots " << to_string(*value); } continue; } if (key == "stars_groupcall_message_limits") { send_closure(G()->group_call_manager(), &GroupCallManager::on_update_group_call_message_limits, std::move(key_value->value_)); continue; } if (key == "ton_stakedice_stake_suggested_amounts") { if (value->get_id() == telegram_api::jsonArray::ID) { auto amounts = std::move(static_cast(value)->value_); for (auto &amount : amounts) { auto ton_amount = get_json_value_long(std::move(amount), key); if (ton_amount > 0) { if (!ton_stakedice_stake_suggested_amounts.empty()) { ton_stakedice_stake_suggested_amounts += ','; } ton_stakedice_stake_suggested_amounts += to_string(ton_amount); } else { LOG(ERROR) << "Receive unexpected ton amount " << ton_amount; } } } else { LOG(ERROR) << "Receive unexpected ton_stakedice_stake_suggested_amounts " << to_string(*value); } continue; } if (key == "stargifts_craft_attribute_permilles") { if (value->get_id() == telegram_api::jsonArray::ID) { int32 row_count = 0; auto probability_rows = std::move(static_cast(value)->value_); for (auto &probability_row : probability_rows) { row_count++; if (probability_row->get_id() == telegram_api::jsonArray::ID) { auto probabilities = std::move(static_cast(probability_row.get())->value_); int32 count = 0; for (auto &probability : probabilities) { auto per_mille = get_json_value_int(std::move(probability), key); if (0 < per_mille && per_mille <= 1000) { if (!gift_craft_probabilities.empty()) { gift_craft_probabilities += ','; } gift_craft_probabilities += to_string(per_mille); count++; } else { LOG(ERROR) << "Receive unexpected probability " << per_mille; } } if (count != row_count) { LOG(ERROR) << "Receive " << count << " gift craft probability in row " << row_count; gift_craft_probabilities.clear(); break; } } else { LOG(ERROR) << "Receive unexpected probability row"; break; } } if (row_count != 4u) { LOG(ERROR) << "Receive " << row_count << " gift craft probability rows"; gift_craft_probabilities.clear(); } } else { LOG(ERROR) << "Receive unexpected stargifts_craft_attribute_permilles " << to_string(*value); } continue; } new_values.push_back(std::move(key_value)); } } else { LOG(ERROR) << "Receive wrong app config " << to_string(config); } config = make_tl_object(std::move(new_values)); send_closure(G()->link_manager(), &LinkManager::update_autologin_domains, std::move(autologin_domains), std::move(url_auth_domains), std::move(whitelisted_domains)); send_closure(G()->transcription_manager(), &TranscriptionManager::on_update_trial_parameters, transcribe_audio_trial_weekly_number, transcribe_audio_trial_duration_max, transcribe_audio_trial_cooldown_until); send_closure(G()->user_manager(), &UserManager::on_update_freeze_state, freeze_since_date, freeze_until_date, std::move(freeze_appeal_url)); send_closure(G()->account_manager(), &AccountManager::on_update_age_verification_parameters, AgeVerificationParameters(need_age_video_verification, std::move(verify_age_bot_username), std::move(verify_age_country), verify_age_min)); Global &options = *G(); if (ignored_restriction_reasons.empty()) { options.set_option_empty("ignored_restriction_reasons"); if (options.get_option_boolean("ignore_sensitive_content_restrictions", true) || options.get_option_boolean("can_ignore_sensitive_content_restrictions", true)) { get_content_settings(Auto()); } } else { options.set_option_string("ignored_restriction_reasons", ignored_restriction_reasons); if (!options.get_option_boolean("can_ignore_sensitive_content_restrictions") || !options.get_option_boolean("ignore_sensitive_content_restrictions")) { get_content_settings(Auto()); } } if (restriction_add_platforms.empty()) { options.set_option_empty("restriction_add_platforms"); } else { options.set_option_string("restriction_add_platforms", restriction_add_platforms); } options.set_option_string("whitelisted_bots", whitelisted_bots); if (!dice_emojis.empty()) { vector dice_success_values(dice_emojis.size()); for (auto &it : dice_emoji_success_value) { auto dice_emoji_it = dice_emoji_index.find(it.first); if (dice_emoji_it == dice_emoji_index.end()) { LOG(ERROR) << "Can't find emoji " << it.first; continue; } dice_success_values[dice_emoji_it->second] = it.second; } options.set_option_string("dice_success_values", implode(dice_success_values, ',')); options.set_option_string("dice_emojis", implode(dice_emojis, '\x01')); } options.set_option_string("fragment_prefixes", implode(fragment_prefixes, ',')); if (starref_start_param_prefixes.empty()) { options.set_option_empty("starref_start_param_prefixes"); } else { options.set_option_string("starref_start_param_prefixes", implode(starref_start_param_prefixes, ' ')); } options.set_option_string("emoji_sounds", implode(emoji_sounds, ',')); if (animated_emoji_zoom <= 0 || animated_emoji_zoom > 2.0) { options.set_option_empty("animated_emoji_zoom"); } else { options.set_option_integer("animated_emoji_zoom", static_cast(animated_emoji_zoom * 1e9)); } if (animation_search_emojis.empty()) { options.set_option_empty("animation_search_emojis"); } else { options.set_option_string("animation_search_emojis", animation_search_emojis); } options.set_option_boolean("can_accept_calls", can_accept_calls); if (!is_premium_available) { premium_bot_username.clear(); // just in case premium_invoice_slug.clear(); // just in case premium_features.clear(); // just in case business_features.clear(); // just in case options.set_option_empty("is_premium_available"); } else { options.set_option_boolean("is_premium_available", is_premium_available); } options.set_option_string("premium_features", implode(premium_features, ',')); options.set_option_string("business_features", implode(business_features, ',')); if (premium_bot_username.empty()) { options.set_option_empty("premium_bot_username"); } else { options.set_option_string("premium_bot_username", premium_bot_username); } if (premium_invoice_slug.empty()) { options.set_option_empty("premium_invoice_slug"); } else { options.set_option_string("premium_invoice_slug", premium_invoice_slug); } options.set_option_string("ton_stakedice_stake_suggested_amounts", ton_stakedice_stake_suggested_amounts); if (gift_craft_probabilities.empty()) { options.set_option_empty("stargifts_craft_attribute_permilles"); } else { options.set_option_string("stargifts_craft_attribute_permilles", gift_craft_probabilities); } options.set_option_boolean("need_premium_for_new_chat_privacy", need_premium_for_new_chat_privacy); options.set_option_empty("default_ton_blockchain_config"); options.set_option_empty("default_ton_blockchain_name"); options.set_option_empty("story_viewers_expire_period"); } } // namespace td