2020-08-23 23:43:31 +02:00
|
|
|
//
|
2024-01-01 01:07:21 +01:00
|
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
2020-08-23 23:43:31 +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/CountryInfoManager.h"
|
|
|
|
|
2022-12-20 13:01:39 +01:00
|
|
|
#include "td/telegram/AuthManager.h"
|
2020-08-23 23:43:31 +02:00
|
|
|
#include "td/telegram/Global.h"
|
2020-08-24 22:38:45 +02:00
|
|
|
#include "td/telegram/LanguagePackManager.h"
|
2022-02-18 14:57:08 +01:00
|
|
|
#include "td/telegram/misc.h"
|
2022-12-20 13:01:39 +01:00
|
|
|
#include "td/telegram/OptionManager.h"
|
2020-08-23 23:43:31 +02:00
|
|
|
#include "td/telegram/Td.h"
|
|
|
|
#include "td/telegram/telegram_api.h"
|
|
|
|
|
2021-01-01 13:59:53 +01:00
|
|
|
#include "td/utils/algorithm.h"
|
2020-09-25 14:14:33 +02:00
|
|
|
#include "td/utils/base64.h"
|
2020-08-23 23:43:31 +02:00
|
|
|
#include "td/utils/buffer.h"
|
2020-09-25 14:14:33 +02:00
|
|
|
#include "td/utils/Gzip.h"
|
2020-09-24 18:15:42 +02:00
|
|
|
#include "td/utils/logging.h"
|
2020-08-24 22:38:45 +02:00
|
|
|
#include "td/utils/misc.h"
|
|
|
|
#include "td/utils/Random.h"
|
2020-09-24 18:15:42 +02:00
|
|
|
#include "td/utils/Time.h"
|
2020-09-25 14:14:33 +02:00
|
|
|
#include "td/utils/tl_parsers.h"
|
2024-01-25 18:06:07 +01:00
|
|
|
#include "td/utils/utf8.h"
|
2020-08-23 23:43:31 +02:00
|
|
|
|
|
|
|
namespace td {
|
|
|
|
|
2021-07-04 04:58:54 +02:00
|
|
|
class GetNearestDcQuery final : public Td::ResultHandler {
|
2020-08-23 23:43:31 +02:00
|
|
|
Promise<string> promise_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit GetNearestDcQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void send() {
|
|
|
|
send_query(G()->net_query_creator().create_unauth(telegram_api::help_getNearestDc()));
|
|
|
|
}
|
|
|
|
|
2021-11-08 12:19:57 +01:00
|
|
|
void on_result(BufferSlice packet) final {
|
2020-08-23 23:43:31 +02:00
|
|
|
auto result_ptr = fetch_result<telegram_api::help_getNearestDc>(packet);
|
|
|
|
if (result_ptr.is_error()) {
|
2021-11-08 12:19:57 +01:00
|
|
|
return on_error(result_ptr.move_as_error());
|
2020-08-23 23:43:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
auto result = result_ptr.move_as_ok();
|
|
|
|
promise_.set_value(std::move(result->country_));
|
|
|
|
}
|
|
|
|
|
2021-11-08 12:19:57 +01:00
|
|
|
void on_error(Status status) final {
|
2020-08-23 23:43:31 +02:00
|
|
|
if (!G()->is_expected_error(status) && status.message() != "BOT_METHOD_INVALID") {
|
|
|
|
LOG(ERROR) << "GetNearestDc returned " << status;
|
|
|
|
}
|
|
|
|
promise_.set_error(std::move(status));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-07-04 04:58:54 +02:00
|
|
|
class GetCountriesListQuery final : public Td::ResultHandler {
|
2020-08-24 22:38:45 +02:00
|
|
|
Promise<tl_object_ptr<telegram_api::help_CountriesList>> promise_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit GetCountriesListQuery(Promise<tl_object_ptr<telegram_api::help_CountriesList>> &&promise)
|
|
|
|
: promise_(std::move(promise)) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void send(const string &language_code, int32 hash) {
|
|
|
|
send_query(G()->net_query_creator().create_unauth(telegram_api::help_getCountriesList(language_code, hash)));
|
|
|
|
}
|
|
|
|
|
2021-11-08 12:19:57 +01:00
|
|
|
void on_result(BufferSlice packet) final {
|
2021-08-29 17:03:26 +02:00
|
|
|
// LOG(ERROR) << base64url_encode(gzencode(packet, 0.9));
|
2020-08-24 22:38:45 +02:00
|
|
|
auto result_ptr = fetch_result<telegram_api::help_getCountriesList>(packet);
|
|
|
|
if (result_ptr.is_error()) {
|
2021-11-08 12:19:57 +01:00
|
|
|
return on_error(result_ptr.move_as_error());
|
2020-08-24 22:38:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
promise_.set_value(result_ptr.move_as_ok());
|
|
|
|
}
|
|
|
|
|
2021-11-08 12:19:57 +01:00
|
|
|
void on_error(Status status) final {
|
2020-08-24 22:38:45 +02:00
|
|
|
if (!G()->is_expected_error(status)) {
|
|
|
|
LOG(ERROR) << "GetCountriesList returned " << status;
|
|
|
|
}
|
|
|
|
promise_.set_error(std::move(status));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct CountryInfoManager::CallingCodeInfo {
|
2020-08-25 12:42:14 +02:00
|
|
|
string calling_code;
|
2020-08-24 22:38:45 +02:00
|
|
|
vector<string> prefixes;
|
|
|
|
vector<string> patterns;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct CountryInfoManager::CountryInfo {
|
|
|
|
string country_code;
|
|
|
|
string default_name;
|
|
|
|
string name;
|
|
|
|
vector<CallingCodeInfo> calling_codes;
|
|
|
|
bool is_hidden = false;
|
|
|
|
|
|
|
|
td_api::object_ptr<td_api::countryInfo> get_country_info_object() const {
|
|
|
|
return td_api::make_object<td_api::countryInfo>(
|
|
|
|
country_code, name.empty() ? default_name : name, default_name, is_hidden,
|
2020-08-25 12:42:14 +02:00
|
|
|
transform(calling_codes, [](const CallingCodeInfo &info) { return info.calling_code; }));
|
2020-08-24 22:38:45 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct CountryInfoManager::CountryList {
|
2021-08-30 18:20:40 +02:00
|
|
|
vector<CountryInfo> countries;
|
2020-08-24 22:38:45 +02:00
|
|
|
int32 hash = 0;
|
|
|
|
double next_reload_time = 0.0;
|
|
|
|
|
|
|
|
td_api::object_ptr<td_api::countries> get_countries_object() const {
|
|
|
|
return td_api::make_object<td_api::countries>(
|
2021-08-30 18:20:40 +02:00
|
|
|
transform(countries, [](const CountryInfo &info) { return info.get_country_info_object(); }));
|
2020-08-24 22:38:45 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-24 13:39:03 +02:00
|
|
|
CountryInfoManager::CountryInfoManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
|
2022-12-20 13:01:39 +01:00
|
|
|
on_update_fragment_prefixes();
|
2020-08-24 13:39:03 +02:00
|
|
|
}
|
|
|
|
|
2020-09-14 17:02:19 +02:00
|
|
|
CountryInfoManager::~CountryInfoManager() = default;
|
|
|
|
|
2021-08-30 18:20:40 +02:00
|
|
|
void CountryInfoManager::start_up() {
|
|
|
|
std::lock_guard<std::mutex> country_lock(country_mutex_);
|
|
|
|
manager_count_++;
|
|
|
|
}
|
|
|
|
|
2020-08-24 13:39:03 +02:00
|
|
|
void CountryInfoManager::tear_down() {
|
|
|
|
parent_.reset();
|
2021-08-30 18:20:40 +02:00
|
|
|
|
|
|
|
std::lock_guard<std::mutex> country_lock(country_mutex_);
|
|
|
|
manager_count_--;
|
2021-10-06 12:10:28 +02:00
|
|
|
if (manager_count_ == 0 && !countries_.empty()) {
|
2021-08-30 18:20:40 +02:00
|
|
|
LOG(INFO) << "Clear country info";
|
|
|
|
countries_.clear();
|
|
|
|
}
|
2020-08-23 23:43:31 +02:00
|
|
|
}
|
|
|
|
|
2022-12-20 13:01:39 +01:00
|
|
|
bool CountryInfoManager::is_fragment_phone_number(string phone_number) {
|
|
|
|
if (phone_number.empty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (fragment_prefixes_.empty()) {
|
|
|
|
fragment_prefixes_str_ = "888";
|
|
|
|
fragment_prefixes_.push_back(fragment_prefixes_str_);
|
|
|
|
}
|
|
|
|
clean_phone_number(phone_number);
|
|
|
|
for (auto &prefix : fragment_prefixes_) {
|
|
|
|
if (begins_with(phone_number, prefix)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CountryInfoManager::on_update_fragment_prefixes() {
|
|
|
|
if (G()->close_flag()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (td_->auth_manager_->is_bot()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!td_->option_manager_->have_option("fragment_prefixes")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto fragment_prefixes_str = td_->option_manager_->get_option_string("fragment_prefixes", "888");
|
|
|
|
std::lock_guard<std::mutex> country_lock(country_mutex_);
|
|
|
|
if (fragment_prefixes_str == fragment_prefixes_str_) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
fragment_prefixes_str_ = std::move(fragment_prefixes_str);
|
|
|
|
fragment_prefixes_ = full_split(fragment_prefixes_str_, ',');
|
|
|
|
}
|
|
|
|
|
2020-08-24 22:38:45 +02:00
|
|
|
string CountryInfoManager::get_main_language_code() {
|
2022-10-01 10:29:09 +02:00
|
|
|
return to_lower(td_->language_pack_manager_.get_actor_unsafe()->get_main_language_code());
|
2020-08-24 22:38:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void CountryInfoManager::get_countries(Promise<td_api::object_ptr<td_api::countries>> &&promise) {
|
|
|
|
do_get_countries(get_main_language_code(), false, std::move(promise));
|
|
|
|
}
|
|
|
|
|
|
|
|
void CountryInfoManager::do_get_countries(string language_code, bool is_recursive,
|
|
|
|
Promise<td_api::object_ptr<td_api::countries>> &&promise) {
|
2020-08-25 12:42:14 +02:00
|
|
|
if (is_recursive) {
|
|
|
|
auto main_language_code = get_main_language_code();
|
|
|
|
if (language_code != main_language_code) {
|
|
|
|
language_code = std::move(main_language_code);
|
|
|
|
is_recursive = false;
|
|
|
|
}
|
|
|
|
}
|
2021-08-30 18:20:40 +02:00
|
|
|
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> country_lock(country_mutex_);
|
|
|
|
auto list = get_country_list(this, language_code);
|
|
|
|
if (list != nullptr) {
|
|
|
|
return promise.set_value(list->get_countries_object());
|
2020-08-24 22:38:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-30 18:20:40 +02:00
|
|
|
if (is_recursive) {
|
|
|
|
return promise.set_error(Status::Error(500, "Requested data is inaccessible"));
|
|
|
|
}
|
2022-02-09 22:59:52 +01:00
|
|
|
if (language_code.empty()) {
|
|
|
|
return promise.set_error(Status::Error(400, "Invalid language code specified"));
|
|
|
|
}
|
2021-08-30 18:20:40 +02:00
|
|
|
load_country_list(language_code, 0,
|
|
|
|
PromiseCreator::lambda([actor_id = actor_id(this), language_code,
|
|
|
|
promise = std::move(promise)](Result<Unit> &&result) mutable {
|
|
|
|
if (result.is_error()) {
|
|
|
|
return promise.set_error(result.move_as_error());
|
|
|
|
}
|
|
|
|
send_closure(actor_id, &CountryInfoManager::do_get_countries, std::move(language_code), true,
|
|
|
|
std::move(promise));
|
|
|
|
}));
|
2020-08-24 22:38:45 +02:00
|
|
|
}
|
|
|
|
|
2020-08-25 12:42:14 +02:00
|
|
|
void CountryInfoManager::get_phone_number_info(string phone_number_prefix,
|
|
|
|
Promise<td_api::object_ptr<td_api::phoneNumberInfo>> &&promise) {
|
2022-02-18 14:57:08 +01:00
|
|
|
clean_phone_number(phone_number_prefix);
|
2020-08-25 12:42:14 +02:00
|
|
|
if (phone_number_prefix.empty()) {
|
2022-12-20 13:01:39 +01:00
|
|
|
return promise.set_value(td_api::make_object<td_api::phoneNumberInfo>(nullptr, string(), string(), false));
|
2020-08-25 12:42:14 +02:00
|
|
|
}
|
|
|
|
do_get_phone_number_info(std::move(phone_number_prefix), get_main_language_code(), false, std::move(promise));
|
|
|
|
}
|
|
|
|
|
|
|
|
void CountryInfoManager::do_get_phone_number_info(string phone_number_prefix, string language_code, bool is_recursive,
|
|
|
|
Promise<td_api::object_ptr<td_api::phoneNumberInfo>> &&promise) {
|
|
|
|
if (is_recursive) {
|
|
|
|
auto main_language_code = get_main_language_code();
|
|
|
|
if (language_code != main_language_code) {
|
|
|
|
language_code = std::move(main_language_code);
|
|
|
|
is_recursive = false;
|
|
|
|
}
|
|
|
|
}
|
2021-08-30 18:20:40 +02:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> country_lock(country_mutex_);
|
|
|
|
auto list = get_country_list(this, language_code);
|
|
|
|
if (list != nullptr) {
|
|
|
|
return promise.set_value(get_phone_number_info_object(list, phone_number_prefix));
|
2020-08-25 12:42:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-30 18:20:40 +02:00
|
|
|
if (is_recursive) {
|
|
|
|
return promise.set_error(Status::Error(500, "Requested data is inaccessible"));
|
|
|
|
}
|
2022-02-09 22:59:52 +01:00
|
|
|
if (language_code.empty()) {
|
|
|
|
return promise.set_error(Status::Error(400, "Invalid language code specified"));
|
|
|
|
}
|
2021-08-30 18:20:40 +02:00
|
|
|
load_country_list(language_code, 0,
|
|
|
|
PromiseCreator::lambda([actor_id = actor_id(this), phone_number_prefix, language_code,
|
|
|
|
promise = std::move(promise)](Result<Unit> &&result) mutable {
|
|
|
|
if (result.is_error()) {
|
|
|
|
return promise.set_error(result.move_as_error());
|
|
|
|
}
|
|
|
|
send_closure(actor_id, &CountryInfoManager::do_get_phone_number_info,
|
|
|
|
std::move(phone_number_prefix), std::move(language_code), true, std::move(promise));
|
|
|
|
}));
|
2021-08-30 16:51:09 +02:00
|
|
|
}
|
|
|
|
|
2021-10-19 17:11:16 +02:00
|
|
|
td_api::object_ptr<td_api::phoneNumberInfo> CountryInfoManager::get_phone_number_info_sync(const string &language_code,
|
2021-08-30 21:26:02 +02:00
|
|
|
string phone_number_prefix) {
|
2022-02-18 14:57:08 +01:00
|
|
|
clean_phone_number(phone_number_prefix);
|
2021-08-30 21:26:02 +02:00
|
|
|
if (phone_number_prefix.empty()) {
|
2022-12-20 13:01:39 +01:00
|
|
|
return td_api::make_object<td_api::phoneNumberInfo>(nullptr, string(), string(), false);
|
2021-08-30 21:26:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> country_lock(country_mutex_);
|
|
|
|
auto list = get_country_list(nullptr, language_code);
|
|
|
|
if (list == nullptr) {
|
|
|
|
list = get_country_list(nullptr, "en");
|
|
|
|
}
|
|
|
|
|
|
|
|
return get_phone_number_info_object(list, phone_number_prefix);
|
|
|
|
}
|
|
|
|
|
2021-08-30 16:51:09 +02:00
|
|
|
td_api::object_ptr<td_api::phoneNumberInfo> CountryInfoManager::get_phone_number_info_object(const CountryList *list,
|
|
|
|
Slice phone_number) {
|
|
|
|
CHECK(list != nullptr);
|
2020-08-25 12:42:14 +02:00
|
|
|
const CountryInfo *best_country = nullptr;
|
|
|
|
const CallingCodeInfo *best_calling_code = nullptr;
|
|
|
|
size_t best_length = 0;
|
2021-08-30 16:11:45 +02:00
|
|
|
bool is_prefix = false; // is phone number a prefix of a valid country_code + prefix
|
2022-12-20 13:01:39 +01:00
|
|
|
bool is_anonymous = is_fragment_phone_number(phone_number.str());
|
2021-08-30 18:20:40 +02:00
|
|
|
for (auto &country : list->countries) {
|
2020-08-25 12:42:14 +02:00
|
|
|
for (auto &calling_code : country.calling_codes) {
|
|
|
|
if (begins_with(phone_number, calling_code.calling_code)) {
|
|
|
|
auto calling_code_size = calling_code.calling_code.size();
|
|
|
|
for (auto &prefix : calling_code.prefixes) {
|
|
|
|
if (begins_with(prefix, phone_number.substr(calling_code_size))) {
|
|
|
|
is_prefix = true;
|
|
|
|
}
|
|
|
|
if (calling_code_size + prefix.size() > best_length &&
|
|
|
|
begins_with(phone_number.substr(calling_code_size), prefix)) {
|
|
|
|
best_country = &country;
|
|
|
|
best_calling_code = &calling_code;
|
|
|
|
best_length = calling_code_size + prefix.size();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (begins_with(calling_code.calling_code, phone_number)) {
|
|
|
|
is_prefix = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (best_country == nullptr) {
|
2021-08-30 16:51:09 +02:00
|
|
|
return td_api::make_object<td_api::phoneNumberInfo>(nullptr, is_prefix ? phone_number.str() : string(),
|
2022-12-20 13:01:39 +01:00
|
|
|
is_prefix ? string() : phone_number.str(), is_anonymous);
|
2020-08-25 12:42:14 +02:00
|
|
|
}
|
|
|
|
|
2021-08-30 16:51:09 +02:00
|
|
|
Slice formatted_part = phone_number.substr(best_calling_code->calling_code.size());
|
|
|
|
string formatted_phone_number;
|
2020-08-25 12:42:14 +02:00
|
|
|
size_t max_matched_digits = 0;
|
|
|
|
for (auto &pattern : best_calling_code->patterns) {
|
|
|
|
string result;
|
|
|
|
size_t current_pattern_pos = 0;
|
|
|
|
bool is_failed_match = false;
|
|
|
|
size_t matched_digits = 0;
|
|
|
|
for (auto &c : formatted_part) {
|
|
|
|
while (current_pattern_pos < pattern.size() && pattern[current_pattern_pos] != 'X' &&
|
|
|
|
!is_digit(pattern[current_pattern_pos])) {
|
|
|
|
result += pattern[current_pattern_pos++];
|
|
|
|
}
|
|
|
|
if (current_pattern_pos == pattern.size()) {
|
2021-08-30 16:11:45 +02:00
|
|
|
// result += ' ';
|
2021-08-13 20:06:54 +02:00
|
|
|
}
|
|
|
|
if (current_pattern_pos >= pattern.size() || pattern[current_pattern_pos] == 'X') {
|
2020-08-25 12:42:14 +02:00
|
|
|
result += c;
|
|
|
|
current_pattern_pos++;
|
|
|
|
} else {
|
|
|
|
CHECK(is_digit(pattern[current_pattern_pos]));
|
|
|
|
if (c == pattern[current_pattern_pos]) {
|
|
|
|
matched_digits++;
|
|
|
|
result += c;
|
|
|
|
current_pattern_pos++;
|
|
|
|
} else {
|
|
|
|
is_failed_match = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-30 16:11:45 +02:00
|
|
|
for (size_t i = current_pattern_pos; i < pattern.size(); i++) {
|
|
|
|
if (is_digit(pattern[i])) {
|
|
|
|
is_failed_match = true;
|
|
|
|
}
|
|
|
|
}
|
2020-08-25 12:42:14 +02:00
|
|
|
if (!is_failed_match && matched_digits >= max_matched_digits) {
|
|
|
|
max_matched_digits = matched_digits;
|
2021-08-30 16:11:45 +02:00
|
|
|
while (current_pattern_pos < pattern.size()) {
|
|
|
|
if (pattern[current_pattern_pos] == 'X') {
|
|
|
|
result.push_back('-');
|
|
|
|
} else {
|
|
|
|
CHECK(!is_digit(pattern[current_pattern_pos]));
|
|
|
|
result.push_back(' ');
|
|
|
|
}
|
|
|
|
current_pattern_pos++;
|
|
|
|
}
|
2020-08-25 12:42:14 +02:00
|
|
|
formatted_phone_number = std::move(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-30 16:51:09 +02:00
|
|
|
return td_api::make_object<td_api::phoneNumberInfo>(
|
2020-08-25 12:42:14 +02:00
|
|
|
best_country->get_country_info_object(), best_calling_code->calling_code,
|
2022-12-20 13:01:39 +01:00
|
|
|
formatted_phone_number.empty() ? formatted_part.str() : formatted_phone_number, is_anonymous);
|
2020-08-25 12:42:14 +02:00
|
|
|
}
|
|
|
|
|
2020-08-23 23:43:31 +02:00
|
|
|
void CountryInfoManager::get_current_country_code(Promise<string> &&promise) {
|
|
|
|
td_->create_handler<GetNearestDcQuery>(std::move(promise))->send();
|
|
|
|
}
|
|
|
|
|
2020-08-24 22:38:45 +02:00
|
|
|
void CountryInfoManager::load_country_list(string language_code, int32 hash, Promise<Unit> &&promise) {
|
|
|
|
auto &queries = pending_load_country_queries_[language_code];
|
|
|
|
if (!promise && !queries.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
queries.push_back(std::move(promise));
|
|
|
|
if (queries.size() == 1) {
|
|
|
|
auto query_promise = PromiseCreator::lambda(
|
|
|
|
[actor_id = actor_id(this), language_code](Result<tl_object_ptr<telegram_api::help_CountriesList>> &&result) {
|
|
|
|
send_closure(actor_id, &CountryInfoManager::on_get_country_list, language_code, std::move(result));
|
|
|
|
});
|
|
|
|
td_->create_handler<GetCountriesListQuery>(std::move(query_promise))->send(language_code, hash);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CountryInfoManager::on_get_country_list(const string &language_code,
|
|
|
|
Result<tl_object_ptr<telegram_api::help_CountriesList>> r_country_list) {
|
|
|
|
auto query_it = pending_load_country_queries_.find(language_code);
|
|
|
|
CHECK(query_it != pending_load_country_queries_.end());
|
|
|
|
auto promises = std::move(query_it->second);
|
|
|
|
CHECK(!promises.empty());
|
|
|
|
pending_load_country_queries_.erase(query_it);
|
|
|
|
|
|
|
|
if (r_country_list.is_error()) {
|
2021-08-30 18:20:40 +02:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> country_lock(country_mutex_);
|
|
|
|
auto it = countries_.find(language_code);
|
|
|
|
if (it != countries_.end()) {
|
|
|
|
// don't try to reload countries more often than once in 1-2 minutes
|
|
|
|
it->second->next_reload_time = max(Time::now() + Random::fast(60, 120), it->second->next_reload_time);
|
|
|
|
|
|
|
|
// if we have data for the language, then we don't need to fail promises
|
2022-04-13 16:40:12 +02:00
|
|
|
set_promises(promises);
|
2021-08-30 18:20:40 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-08-24 22:38:45 +02:00
|
|
|
}
|
2022-04-13 16:40:12 +02:00
|
|
|
fail_promises(promises, r_country_list.move_as_error());
|
2020-08-24 22:38:45 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-30 18:20:40 +02:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> country_lock(country_mutex_);
|
|
|
|
on_get_country_list_impl(language_code, r_country_list.move_as_ok());
|
|
|
|
}
|
2020-09-25 14:14:33 +02:00
|
|
|
|
2022-04-13 16:40:12 +02:00
|
|
|
set_promises(promises);
|
2020-09-25 14:14:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void CountryInfoManager::on_get_country_list_impl(const string &language_code,
|
|
|
|
tl_object_ptr<telegram_api::help_CountriesList> country_list) {
|
2020-08-24 22:38:45 +02:00
|
|
|
CHECK(country_list != nullptr);
|
2021-11-02 17:04:02 +01:00
|
|
|
LOG(DEBUG) << "Receive " << to_string(country_list);
|
2020-09-25 14:14:33 +02:00
|
|
|
auto &countries = countries_[language_code];
|
2020-08-24 22:38:45 +02:00
|
|
|
switch (country_list->get_id()) {
|
|
|
|
case telegram_api::help_countriesListNotModified::ID:
|
|
|
|
if (countries == nullptr) {
|
|
|
|
LOG(ERROR) << "Receive countriesListNotModified for unknown list with language code " << language_code;
|
|
|
|
countries_.erase(language_code);
|
|
|
|
} else {
|
|
|
|
LOG(INFO) << "List of countries with language code " << language_code << " is not modified";
|
|
|
|
countries->next_reload_time = Time::now() + Random::fast(86400, 2 * 86400);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case telegram_api::help_countriesList::ID: {
|
|
|
|
auto list = move_tl_object_as<telegram_api::help_countriesList>(country_list);
|
|
|
|
if (countries == nullptr) {
|
|
|
|
countries = make_unique<CountryList>();
|
2021-11-02 17:04:02 +01:00
|
|
|
} else {
|
|
|
|
countries->countries.clear();
|
2020-08-24 22:38:45 +02:00
|
|
|
}
|
|
|
|
for (auto &c : list->countries_) {
|
|
|
|
CountryInfo info;
|
|
|
|
info.country_code = std::move(c->iso2_);
|
|
|
|
info.default_name = std::move(c->default_name_);
|
|
|
|
info.name = std::move(c->name_);
|
2021-10-19 17:11:16 +02:00
|
|
|
info.is_hidden = c->hidden_;
|
2020-08-24 22:38:45 +02:00
|
|
|
for (auto &code : c->country_codes_) {
|
|
|
|
auto r_calling_code = to_integer_safe<int32>(code->country_code_);
|
|
|
|
if (r_calling_code.is_error() || r_calling_code.ok() <= 0) {
|
|
|
|
LOG(ERROR) << "Receive invalid calling code " << code->country_code_ << " for country "
|
|
|
|
<< info.country_code;
|
|
|
|
} else {
|
|
|
|
CallingCodeInfo calling_code_info;
|
2020-08-25 12:42:14 +02:00
|
|
|
calling_code_info.calling_code = std::move(code->country_code_);
|
2020-08-24 22:38:45 +02:00
|
|
|
calling_code_info.prefixes = std::move(code->prefixes_);
|
2020-08-25 12:42:14 +02:00
|
|
|
if (calling_code_info.prefixes.empty()) {
|
|
|
|
calling_code_info.prefixes.resize(1);
|
|
|
|
}
|
2020-08-24 22:38:45 +02:00
|
|
|
calling_code_info.patterns = std::move(code->patterns_);
|
|
|
|
info.calling_codes.push_back(std::move(calling_code_info));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (info.calling_codes.empty()) {
|
|
|
|
LOG(ERROR) << "Receive empty list of calling codes for " << info.country_code;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-08-30 18:20:40 +02:00
|
|
|
countries->countries.push_back(std::move(info));
|
2020-08-24 22:38:45 +02:00
|
|
|
}
|
|
|
|
countries->hash = list->hash_;
|
|
|
|
countries->next_reload_time = Time::now() + Random::fast(86400, 2 * 86400);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-30 18:20:40 +02:00
|
|
|
const CountryInfoManager::CountryList *CountryInfoManager::get_country_list(CountryInfoManager *manager,
|
|
|
|
const string &language_code) {
|
2020-08-24 22:38:45 +02:00
|
|
|
auto it = countries_.find(language_code);
|
|
|
|
if (it == countries_.end()) {
|
2020-09-25 14:14:33 +02:00
|
|
|
if (language_code == "en") {
|
|
|
|
static const BufferSlice en = gzdecode(
|
|
|
|
base64url_decode(
|
2022-12-02 12:58:41 +01:00
|
|
|
"eJyNW81y40hyrib13-qe6d3ZWR8cE4xwxHod4dkg8UfgyH9RJCg2AervViRryBqBgAYEpKVeYG8--AH2sgfHHnyzn6Dtky9-Ar-"
|
|
|
|
"AD34FJ0hRAIlkoSO6pzVUZVVWVuaXX2YV_xz-959-8x___rf_Swj5u3_60xf4h-QqdXJccSee79Pod-_gs__7z3w1B__m5bK2-"
|
|
|
|
"ezk9raw-rMl2yDfDV0esEmh4tNRoTHnPg3YAn6XmssolzafvV9NtPp7G8_VJO8rP01n1OWLgLq78jlDJgn5twli-"
|
|
|
|
"Rb5VHEDPg1p4XeFKvVH4YSm9TgoSZqe0O9ko8jWvtrkpOJOQ-"
|
|
|
|
"44FNlLNIeSOUcX7OqMYDuIXVVVbAsTZP05Q2XLyuazs1g2se4VOQLdPdA8JSspitiGA3Ja8acMzOju2G71e1VJrGORj5U58_"
|
|
|
|
"mYugWLzr30egclTc-2kw17DReBn95rTonP_Gwju7XXIegbyVKHI2ed00oJn1lJ79j5hhxW_HCEyOYloyzW-56cVV6YP6L8Z-oi_"
|
|
|
|
"m4owjOuVsh3VW8BRwzuesH8Fzb1nlZmT88l62XhmVer5CTyeDrxsNgDf1W0rHOo1slZlbpTh07YYpbWQdeLYh0a5LjKnCkP56lzkKW3-"
|
|
|
|
"c5vN0CygyXVJjmvhv4DmKDQpAsPs4Mkads2Xf-J52iBHUJnSn2-a0eyjjsjHnsB-"
|
|
|
|
"tKZT3kKawCr5M1np7exsWLZNsiGfuhOeFpHdeesCts-W70kh1WIbBfdnyG2sRnZ2J8DsqXPWFFKmWfcI5-qoDbjhTr1w8WCOnSO6KHF-"
|
|
|
|
"8fnuQI9PIc_IfikGqV4D29un5D9TH6oei7lPvvHgsXdoNCACKYBDxcQCBYd0fS5qUbi3AbkqOrTF-"
|
|
|
|
"6k411V3z77sIGL1NlZq3Onc7rA4kTKtKEN68_CAI35sio-"
|
|
|
|
"vxvwTy9YPFM8ziUtI87vVjEWHR2SF8Q5pXoPejOHvzDEvsXS27hahRzVQL_J9jnk4d93pbXOv49klKKcV_"
|
|
|
|
"VyvqwX87qk5iVZA98v5rWiAv4j5ctlPS_Bz4pcgvm1fLlo5I0ifK5LURxDLjLykm7kZUXOy5qaV0oajC3n1ZKRVxU9r5XkvKbA_"
|
|
|
|
"EU1rxfLIBuN0WEM_IV11JKSV8swVi_ldZDRyzAPrKPJ8HNsx_NEnts6x1qdfKp5kCgLv6-z-R8KA_b4h3_"
|
|
|
|
"AzkSRhXatNcl3NciYkIMKlZ_W6TCaCz1fWYxftRY5f9XpVR1MH0mszwV5bz3zAFKTQ91JOqcmcyIm3yYfa1_-LWCFyd-"
|
|
|
|
"3nzwIUwyn1O04K6TjrNaJ9uI9FNqLSI_dvLTyO02X4vFdclibcYcheVzVxHm8ZpKTGgUy4nkubvc3f0DxvNZbrb3LeVZr61oSUxKrJ-"
|
|
|
|
"WvYH3P8eajVN5Z61_ezX-IPw7IWc0DJCwMwIfSe4AYEu9hSA5qKyqDrJ_kUQgm165hbfrICtfMn2DnLetCTKwBrtVCn375F7qbt9-"
|
|
|
|
"94vfms3dGUu4OsGb5CHCGcB4148zuwU9f2HgWRVs4cvg4PYciFRO-nua7deAsLcin1F2muK5ixGvVL8lJ_Wc-8sKAo_"
|
|
|
|
"6lysK4rnfIcZ25c-o_pOMxzlnoPuvg23Vvzt3IKzBuV9bKJCNn1a_Ix80cr-gUjc9vzaMXjT3zJMZIXzFG2Tcm1uc-"
|
|
|
|
"qo2mDOH8eakk78RKmis2auS4MQ6B7voI_"
|
|
|
|
"xDjdQPOvLEIPLy2kpJ6r7Nvct0WOWxMl48BEmNSMRnjSZhIyA9gbZ8HPkP2HfOm0zj1J2Qtcmg9Uow35mRFXBs3bHLSCGbce0TxCfw3w"
|
|
|
|
"dkQ3tEEvtvk7m4-Ies41eNxl-SgyX9GY0QrG0KfaHbIt03qPESLiHKGWizGMiY5M_nY91y24Ahv1IyY1zSvyIcm9T0WT46cwRvOHe_"
|
|
|
|
"asAm8s-lTd5zGx5wsJ3JEYctpI_l3K3k76k947nLuAdPthfMR8zEddH2lQ_Tzob72ARQXWhVy2KIjD60jlG0O_qpMLFslH1_"
|
|
|
|
"7Nh3uTifePL0nZdunNsV3PEcdsNNnEVFEapGynIUBrRX2ev4UiUPDUIX-3GrCWcLaAP6tkFOsVwE4oMTjL8BWs9S419wmxotWm5y2-"
|
|
|
|
"AjoXUB9rDZXi3HcYufUBXmfsVX0oD5n7PW5lkmOWnTFKhAOVhTbtweyIXcZKivuA7X65KwF8MocL3xE-"
|
|
|
|
"IBqvK29F6Nbn8mnxi8hDTxAeafwqgrmq-Ic3YK4i-yHxt0W5uK8qmWD_UENNqcOtr5alLZiZZcbtYBXgfwcwbSDklbOrLlbN-TDevM_"
|
|
|
|
"VjlU3CFqA1U8x110lsvIgZGziPWHn7V1mMayFx1yegE1RaED_8HwRhXv_6JHTkB-AvQO7euqRUXo_xeQ82q-BwU-"
|
|
|
|
"km91cb18YZPDC8oj0oWsa4jXHZLji9CdUn-Z9huNCH2uXSenbXfymlaQvmLcz0L7km3AtrbPsPoL8EKMN-0uOWovfMrS_"
|
|
|
|
"Y28URbXfm2oY0BvVGejtI3nr1rHslfkvM4Z1J4t6o_38ARFE_pp-zM5aPv0F0zW0JTMWG0PVvJYDZcz9OxYb1tg9_E-"
|
|
|
|
"uyti3cHX2gF1lhi_MrLXvjTJ8SWdU6DYaZzQy5n918srcnTp-RO0r6Qlzh2pfy_75PCSPlKMG-qlpO44L-"
|
|
|
|
"00yGGHuUsUn1Vxrui0yFln6U-XLwu8J2ZowljpXKxq95E3wX1OV8UY0WmTkw73-YjitZmmi-"
|
|
|
|
"vmDpxbzZt7vpfuqUmamLN2euTXFo16mB0eBFH_sseeONJX1LXMmqjTJ-97nh_MAKh36gOytkPMezsD8t6CYhQZuz5zKRnrb2cey9-"
|
|
|
|
"Qo074DMiK-Zoqttcd1N10CTXzG5FO71eGqjZrv_fgN_SFPsxSfrPqNZbX8tF877T4_9cc-"
|
|
|
|
"80RClu6dSvkoEvROxCwn5aQLyRcKSFfJcddNqJA01P2N7S4lujWwP6rc--"
|
|
|
|
"G43Reg5pczbxj7LbJhy5n41nA3EXA0PsARRL34buQ2y2fF7rUfUAxX9n1g23c6EJu7vIRXofL4vvarhXZauEFMw-"
|
|
|
|
"JG00Ys13gZF0ezMLodhTj1OUtTr3GjOS-h-SsG_6RAWqE_"
|
|
|
|
"hTj5Mk8mcas7jU56tLgCePVcrkkXvuOHILNcKws6UKbmRVybALSjMeIzUri3G7WyJHpuXTsYTqLe2VmPVrXmXhPWM9DFp6V2YAa23PBS"
|
|
|
|
"9nU3157FRdyoodrQi4woSCc0sUYiiXEL95se56MwaRPmxfkW5P6ixl1HGHv2Eis2yHfrLHTpGM2wXs7epIvbtJQYt0uOTCpg_"
|
|
|
|
"f3JPF9oAk5xAR6Pqf-rp45Q02MAy5tRv19B891Rnknbna4uHlFDmGHaA2hqxk69snfrGzEfBfM5Edl8x77bmqbYhaGmZ-j8_"
|
|
|
|
"YD7vJfQuxeydDisYNobOjzYBX0iI0lsf7W2g8XzPdpOm-BvtlvMkw7sh8U8mgdL76bMYfkdK0_"
|
|
|
|
"D9HekSzuA5jXcPbUmfCnPe9jNHE9b0LOBnn6zLG4Et_9mbcgy_7Ix-"
|
|
|
|
"n4zalSYo27lY50uUDvz3NaMTH2PjqPl6g7sXv2r7xRjIU9wMIenfMRlns08duJXo186LHnQo0662hH75bK8Xjgtz0-ZVj_"
|
|
|
|
"RpLKwnPvNclHiJufPGdzlYWsVY6xqNeCffE9vW05WQPhXLzXJqc9KCJ8Og0xP1WLYn7W6wKPZFGYr0I7XcvE9QDWq1zPAbUI7PmZLpG-"
|
|
|
|
"YHyXdfbGwRIY1YNapMceqYPiWzl7_wOQhzgLsTMtxz293pAc9Dh4Hnr2cjzuPrLHc-GeUfQ-"
|
|
|
|
"VIs5EmrPK5McXM3RmhRiVhfK9ivkqA84O0fv88Q5u98gB30WmQG5z9uu5VP8pt8k3772R_"
|
|
|
|
"ues3xtY2B2it9V9Fvk2z59DGkhstamXYedgRrLXJD3_Rl3-OMjDE_7mhb3xffWzf0OOenTh9WjP2yvRurtEDJHF-"
|
|
|
|
"zspWJz7a96hq1M8ts1l-9zSC0MijgzQjQHqQHUYnzX0YcarB8yP_CiC1tvU6skagB9371cPMaQM-vCvkVO-4BzC8iyyN04xNRu_3QbK_"
|
|
|
|
"s22BfyfjiNQhLLeyWxfW7IIay_yzle_SeuSft30TmuUGuJraNm9PQ_V8jhZ4r32I2MN4-DBjkZfPlr6PI99_-"
|
|
|
|
"auB4YXJHjgTfH3mrmlKLYfwYWObKYj_"
|
|
|
|
"boZV18rzYYkl8NwgVEp1tosgkDYhPtYHeeZO27NwYGwA8GzxACGLdSd31kew9WhZxbNJzw1TPePdxU2-"
|
|
|
|
"FHO35mVck3VvQGwntrDCDYEedkq0bOLLYcz5gDzo31GvXYdnGSiuXr5NAKJ1jPK3pbJNa1AWf2DAZP2zqnbPWGEdkW1NzcndJHz8feqm"
|
|
|
|
"gZ9_nWRWTrCG8umMPe3oHlknaSlHLifujt7I52_cdqkxPL8Z6Yi5-ZrIvrcKuzlgf4ReUVSYwNVhf2EqEmLXSZ5-"
|
|
|
|
"7m4lfOI4l1MMEP6LokcbH6shxjrtUjx1ZUiFIH4XHifoUFMW5BjDsYL1MzdByAnYD6QyZH96hmvBW2rE2_buWxSO2fobtNvre-_"
|
|
|
|
"MUr2N78y18hQ_X9L__qjjl2NyfJyZ7lpn2R0OWavG84BYs6T_j7iaK4jrRuozMH9zUpVH4Mw1vIfVKJZOQ16w7id-mj_R9Dk8V-"
|
|
|
|
"d09OGotnGhWeqM9l8DK7Rr63Q_8h6tjWKGTvhaCXqWW_Z7Hr5KA2oxPc_3dqs536wm6RA9ub7nnvrAv90r4gJ_aM8vXVMoJFGe_"
|
|
|
|
"m7EtyZtOf-Sv7Sp-DIe5J2R1ybHsPDNhBGuuNmBvYUJPYfO75P3aBxzAkLyTGmuQ8Opvo2w97OCHolfTRdC_"
|
|
|
|
"HBpywgQ0s0PdFYky0r8ih7QG-o9w37mfYA3IU6cmQGsn4intp2yafbIAUPqETcEPbG9HICdL-p2d_X8W-"
|
|
|
|
"jnR5ok6IcbQYP23gCDblz9hZ67omjDkbYs6m7gvdk2ukjO-0DKHeHz5Ez-1ZSlaOv1tw_ia70xcctsjRcLqP34i5ydAi-"
|
|
|
|
"aFVSfGq0lfwquEd6O2HEa1FcH-bo-z264b35Gz4MmL740vP3Pd1jfyw5gvX3B0z-Pd3BajuC-"
|
|
|
|
"u3N1HZheFv9vdurhvk9Bpy6QuUOggG59SvuG-9bpHvq1FHbDED7fwpF90JSV-"
|
|
|
|
"hU5t8Glq7U6H3S8XMuQADrjkLIGfvxkROT3yP6XoI46gb0gDBsAT3uGmSb26o4_AoaTTDIHTTd3SaHt8R3UTv9VZfi8JiclVDR5_"
|
|
|
|
"lblvkh5YD0e8UTG_EHQapOQBOzAP4aQloOU_L64l1bjvkqOMtvCfsXkDfm8vXa1-T79tuwHx3VXiACj0WPHuQGNOcVNelxM9xf-"
|
|
|
|
"WuQQ7v2JxhPNzQyvswZbX-HdTtdz-m6vbV7xQpvv9T1jXQ25r3Ub2y4lPrF_"
|
|
|
|
"cIHuWkMhFiwr1Jju73va2Ke7G47A05uefzER0945xXQ994_M8_z__r_wF2XX6H")
|
2020-09-25 14:14:33 +02:00
|
|
|
.ok());
|
|
|
|
TlBufferParser parser(&en);
|
|
|
|
auto result = telegram_api::help_getCountriesList::fetch_result(parser);
|
|
|
|
parser.fetch_end();
|
|
|
|
CHECK(parser.get_error() == nullptr);
|
|
|
|
on_get_country_list_impl(language_code, std::move(result));
|
|
|
|
|
|
|
|
it = countries_.find(language_code);
|
2023-05-22 18:31:58 +02:00
|
|
|
CHECK(it != countries_.end());
|
2020-09-25 14:14:33 +02:00
|
|
|
auto *country = it->second.get();
|
2021-08-30 18:20:40 +02:00
|
|
|
if (manager != nullptr) {
|
|
|
|
manager->load_country_list(language_code, country->hash, Auto());
|
|
|
|
}
|
2020-09-25 14:14:33 +02:00
|
|
|
return country;
|
|
|
|
}
|
2020-08-24 22:38:45 +02:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto *country = it->second.get();
|
|
|
|
CHECK(country != nullptr);
|
2021-08-30 18:20:40 +02:00
|
|
|
if (manager != nullptr && country->next_reload_time < Time::now()) {
|
|
|
|
manager->load_country_list(language_code, country->hash, Auto());
|
2020-08-24 22:38:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return country;
|
|
|
|
}
|
|
|
|
|
2024-01-05 11:42:46 +01:00
|
|
|
string CountryInfoManager::get_country_flag_emoji(const string &country_code) {
|
|
|
|
if (country_code.size() != 2 || !is_alpha(country_code[0]) || !is_alpha(country_code[1])) {
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
char first = to_upper(country_code[0]);
|
|
|
|
char second = to_upper(country_code[1]);
|
|
|
|
if (first == 'Y' && second == 'L') {
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
if (first == 'F' && second == 'T') {
|
|
|
|
return "\xF0\x9F\x8F\xB4\xE2\x80\x8D\xE2\x98\xA0\xEF\xB8\x8F"; // pirate flag
|
|
|
|
}
|
|
|
|
if (first == 'X' && second == 'G') {
|
|
|
|
return "\xF0\x9F\x9B\xB0"; // satellite
|
|
|
|
}
|
|
|
|
if (first == 'X' && second == 'V') {
|
|
|
|
return "\xF0\x9F\x8C\x8D"; // globe showing Europe-Africa
|
|
|
|
}
|
|
|
|
string result;
|
|
|
|
result.reserve(8);
|
|
|
|
append_utf8_character(result, 0x1F1A5 + first);
|
|
|
|
append_utf8_character(result, 0x1F1A5 + second);
|
|
|
|
CHECK(result.size() == 8);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-08-30 18:20:40 +02:00
|
|
|
int32 CountryInfoManager::manager_count_ = 0;
|
|
|
|
std::mutex CountryInfoManager::country_mutex_;
|
2022-02-07 20:41:07 +01:00
|
|
|
FlatHashMap<string, unique_ptr<CountryInfoManager::CountryList>> CountryInfoManager::countries_;
|
2022-12-20 13:01:39 +01:00
|
|
|
string CountryInfoManager::fragment_prefixes_str_;
|
|
|
|
vector<string> CountryInfoManager::fragment_prefixes_;
|
2021-08-30 18:20:40 +02:00
|
|
|
|
2020-08-23 23:43:31 +02:00
|
|
|
} // namespace td
|