init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,630 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "main/main_account.h"
#include "base/platform/base_platform_info.h"
#include "core/application.h"
#include "storage/storage_account.h"
#include "storage/storage_domain.h" // Storage::StartResult.
#include "storage/serialize_common.h"
#include "storage/serialize_peer.h"
#include "storage/localstorage.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_changes.h"
#include "window/window_controller.h"
#include "media/audio/media_audio.h"
#include "mtproto/mtproto_config.h"
#include "mainwidget.h"
#include "api/api_updates.h"
#include "ui/ui_utility.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "main/main_domain.h"
#include "main/main_session_settings.h"
namespace Main {
namespace {
constexpr auto kWideIdsTag = ~uint64(0);
[[nodiscard]] QString ComposeDataString(const QString &dataName, int index) {
auto result = dataName;
result.replace('#', QString());
if (index > 0) {
result += '#' + QString::number(index + 1);
}
return result;
}
} // namespace
Account::Account(not_null<Domain*> domain, const QString &dataName, int index)
: _domain(domain)
, _local(std::make_unique<Storage::Account>(
this,
ComposeDataString(dataName, index))) {
}
Account::~Account() {
if (const auto session = maybeSession()) {
session->saveSettingsNowIfNeeded();
_local->writeSearchSuggestionsIfNeeded();
}
destroySession(DestroyReason::Quitting);
}
Storage::Domain &Account::domainLocal() const {
return _domain->local();
}
[[nodiscard]] Storage::StartResult Account::legacyStart(
const QByteArray &passcode) {
Expects(!_appConfig);
return _local->legacyStart(passcode);
}
std::unique_ptr<MTP::Config> Account::prepareToStart(
std::shared_ptr<MTP::AuthKey> localKey) {
return _local->start(std::move(localKey));
}
void Account::start(std::unique_ptr<MTP::Config> config) {
_appConfig = std::make_unique<AppConfig>(this);
startMtp(config
? std::move(config)
: std::make_unique<MTP::Config>(
Core::App().fallbackProductionConfig()));
_appConfig->start();
watchProxyChanges();
watchSessionChanges();
}
void Account::prepareToStartAdded(
std::shared_ptr<MTP::AuthKey> localKey) {
_local->startAdded(std::move(localKey));
}
void Account::watchProxyChanges() {
using ProxyChange = Core::Application::ProxyChange;
Core::App().proxyChanges(
) | rpl::on_next([=](const ProxyChange &change) {
const auto key = [&](const MTP::ProxyData &proxy) {
return (proxy.type == MTP::ProxyData::Type::Mtproto)
? std::make_pair(proxy.host, proxy.port)
: std::make_pair(QString(), uint32(0));
};
if (_mtp) {
_mtp->restart();
if (key(change.was) != key(change.now)) {
_mtp->reInitConnection(_mtp->mainDcId());
}
}
if (_mtpForKeysDestroy) {
_mtpForKeysDestroy->restart();
}
}, _lifetime);
}
void Account::watchSessionChanges() {
sessionChanges(
) | rpl::on_next([=](Session *session) {
if (!session && _mtp) {
_mtp->setUserPhone(QString());
}
}, _lifetime);
}
uint64 Account::willHaveSessionUniqueId(MTP::Config *config) const {
// See also Session::uniqueId.
if (!_sessionUserId) {
return 0;
}
return _sessionUserId.bare
| (config && config->isTestMode() ? 0x0100'0000'0000'0000ULL : 0ULL);
}
void Account::createSession(
const MTPUser &user,
std::unique_ptr<SessionSettings> settings) {
createSession(
user,
QByteArray(),
0,
settings ? std::move(settings) : std::make_unique<SessionSettings>());
}
void Account::createSession(
UserId id,
QByteArray serialized,
int streamVersion,
std::unique_ptr<SessionSettings> settings) {
DEBUG_LOG(("sessionUserSerialized.size: %1").arg(serialized.size()));
QDataStream peekStream(serialized);
const auto phone = Serialize::peekUserPhone(streamVersion, peekStream);
const auto flags = MTPDuser::Flag::f_self | (phone.isEmpty()
? MTPDuser::Flag()
: MTPDuser::Flag::f_phone);
createSession(
MTP_user(
MTP_flags(flags),
MTP_long(base::take(_sessionUserId).bare),
MTPlong(), // access_hash
MTPstring(), // first_name
MTPstring(), // last_name
MTPstring(), // username
MTP_string(phone),
MTPUserProfilePhoto(),
MTPUserStatus(),
MTPint(), // bot_info_version
MTPVector<MTPRestrictionReason>(),
MTPstring(), // bot_inline_placeholder
MTPstring(), // lang_code
MTPEmojiStatus(),
MTPVector<MTPUsername>(),
MTPRecentStory(),
MTPPeerColor(), // color
MTPPeerColor(), // profile_color
MTPint(), // bot_active_users
MTPlong(), // bot_verification_icon
MTPlong()), // send_paid_messages_stars
serialized,
streamVersion,
std::move(settings));
}
void Account::createSession(
const MTPUser &user,
QByteArray serialized,
int streamVersion,
std::unique_ptr<SessionSettings> settings) {
Expects(_mtp != nullptr);
Expects(_session == nullptr);
Expects(_sessionValue.current() == nullptr);
_session = std::make_unique<Session>(this, user, std::move(settings));
if (!serialized.isEmpty()) {
local().readSelf(_session.get(), serialized, streamVersion);
}
_sessionValue = _session.get();
Ensures(_session != nullptr);
}
void Account::destroySession(DestroyReason reason) {
_storedSessionSettings.reset();
_sessionUserId = 0;
_sessionUserSerialized = {};
if (!sessionExists()) {
return;
}
_sessionValue = nullptr;
if (reason == DestroyReason::LoggedOut) {
_session->finishLogout();
}
_session = nullptr;
}
bool Account::sessionExists() const {
return (_sessionValue.current() != nullptr);
}
Session &Account::session() const {
Expects(sessionExists());
return *_sessionValue.current();
}
Session *Account::maybeSession() const {
return _sessionValue.current();
}
rpl::producer<Session*> Account::sessionValue() const {
return _sessionValue.value();
}
rpl::producer<Session*> Account::sessionChanges() const {
return _sessionValue.changes();
}
rpl::producer<not_null<MTP::Instance*>> Account::mtpValue() const {
return _mtpValue.value() | rpl::map([](MTP::Instance *instance) {
return not_null{ instance };
});
}
rpl::producer<not_null<MTP::Instance*>> Account::mtpMainSessionValue() const {
return mtpValue() | rpl::map([=](not_null<MTP::Instance*> instance) {
return instance->mainDcIdValue() | rpl::map_to(instance);
}) | rpl::flatten_latest();
}
rpl::producer<MTPUpdates> Account::mtpUpdates() const {
return _mtpUpdates.events();
}
rpl::producer<> Account::mtpNewSessionCreated() const {
return _mtpNewSessionCreated.events();
}
void Account::setMtpMainDcId(MTP::DcId mainDcId) {
Expects(!_mtp);
_mtpFields.mainDcId = mainDcId;
}
void Account::setLegacyMtpKey(std::shared_ptr<MTP::AuthKey> key) {
Expects(!_mtp);
Expects(key != nullptr);
_mtpFields.keys.push_back(std::move(key));
}
QByteArray Account::serializeMtpAuthorization() const {
const auto serialize = [&](
MTP::DcId mainDcId,
const MTP::AuthKeysList &keys,
const MTP::AuthKeysList &keysToDestroy) {
const auto keysSize = [](auto &list) {
const auto keyDataSize = MTP::AuthKey::Data().size();
return sizeof(qint32)
+ list.size() * (sizeof(qint32) + keyDataSize);
};
const auto writeKeys = [](
QDataStream &stream,
const MTP::AuthKeysList &keys) {
stream << qint32(keys.size());
for (const auto &key : keys) {
stream << qint32(key->dcId());
key->write(stream);
}
};
auto result = QByteArray();
// wide tag + userId + mainDcId
auto size = 2 * sizeof(quint64) + sizeof(qint32);
size += keysSize(keys) + keysSize(keysToDestroy);
result.reserve(size);
{
QDataStream stream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_1);
const auto currentUserId = sessionExists()
? session().userId()
: UserId();
stream
<< quint64(kWideIdsTag)
<< quint64(currentUserId.bare)
<< qint32(mainDcId);
writeKeys(stream, keys);
writeKeys(stream, keysToDestroy);
DEBUG_LOG(("MTP Info: Keys written, userId: %1, dcId: %2"
).arg(currentUserId.bare
).arg(mainDcId));
}
return result;
};
if (_mtp) {
const auto keys = _mtp->getKeysForWrite();
const auto keysToDestroy = _mtpForKeysDestroy
? _mtpForKeysDestroy->getKeysForWrite()
: MTP::AuthKeysList();
return serialize(_mtp->mainDcId(), keys, keysToDestroy);
}
const auto &keys = _mtpFields.keys;
const auto &keysToDestroy = _mtpKeysToDestroy;
return serialize(_mtpFields.mainDcId, keys, keysToDestroy);
}
void Account::setSessionUserId(UserId userId) {
Expects(!sessionExists());
_sessionUserId = userId;
}
void Account::setSessionFromStorage(
std::unique_ptr<SessionSettings> data,
QByteArray &&selfSerialized,
int32 selfStreamVersion) {
Expects(!sessionExists());
DEBUG_LOG(("sessionUserSerialized set: %1"
).arg(selfSerialized.size()));
_storedSessionSettings = std::move(data);
_sessionUserSerialized = std::move(selfSerialized);
_sessionUserStreamVersion = selfStreamVersion;
}
SessionSettings *Account::getSessionSettings() {
if (_sessionUserId) {
return _storedSessionSettings
? _storedSessionSettings.get()
: nullptr;
} else if (const auto session = maybeSession()) {
return &session->settings();
}
return nullptr;
}
void Account::setMtpAuthorization(const QByteArray &serialized) {
Expects(!_mtp);
QDataStream stream(serialized);
stream.setVersion(QDataStream::Qt_5_1);
auto legacyUserId = Serialize::read<qint32>(stream);
auto legacyMainDcId = Serialize::read<qint32>(stream);
auto userId = quint64();
auto mainDcId = qint32();
if (((uint64(legacyUserId) << 32) | uint64(legacyMainDcId))
== kWideIdsTag) {
userId = Serialize::read<quint64>(stream);
mainDcId = Serialize::read<qint32>(stream);
} else {
userId = legacyUserId;
mainDcId = legacyMainDcId;
}
if (stream.status() != QDataStream::Ok) {
LOG(("MTP Error: "
"Could not read main fields from mtp authorization."));
return;
}
setSessionUserId(userId);
_mtpFields.mainDcId = mainDcId;
const auto readKeys = [&](auto &keys) {
const auto count = Serialize::read<qint32>(stream);
if (stream.status() != QDataStream::Ok) {
LOG(("MTP Error: "
"Could not read keys count from mtp authorization."));
return;
}
keys.reserve(count);
for (auto i = 0; i != count; ++i) {
const auto dcId = Serialize::read<qint32>(stream);
const auto keyData = Serialize::read<MTP::AuthKey::Data>(stream);
if (stream.status() != QDataStream::Ok) {
LOG(("MTP Error: "
"Could not read key from mtp authorization."));
return;
}
keys.push_back(std::make_shared<MTP::AuthKey>(MTP::AuthKey::Type::ReadFromFile, dcId, keyData));
}
};
readKeys(_mtpFields.keys);
readKeys(_mtpKeysToDestroy);
LOG(("MTP Info: "
"read keys, current: %1, to destroy: %2"
).arg(_mtpFields.keys.size()
).arg(_mtpKeysToDestroy.size()));
}
void Account::startMtp(std::unique_ptr<MTP::Config> config) {
Expects(!_mtp);
auto fields = base::take(_mtpFields);
fields.config = std::move(config);
fields.deviceModel = Platform::DeviceModelPretty();
fields.systemVersion = Platform::SystemVersionPretty();
_mtp = std::make_unique<MTP::Instance>(
MTP::Instance::Mode::Normal,
std::move(fields));
const auto writingKeys = _mtp->lifetime().make_state<bool>(false);
_mtp->writeKeysRequests(
) | rpl::filter([=] {
return !*writingKeys;
}) | rpl::on_next([=] {
*writingKeys = true;
Ui::PostponeCall(_mtp.get(), [=] {
local().writeMtpData();
*writingKeys = false;
});
}, _mtp->lifetime());
const auto writingConfig = _lifetime.make_state<bool>(false);
rpl::merge(
_mtp->config().updates(),
_mtp->dcOptions().changed() | rpl::to_empty
) | rpl::filter([=] {
return !*writingConfig;
}) | rpl::on_next([=] {
*writingConfig = true;
Ui::PostponeCall(_mtp.get(), [=] {
local().writeMtpConfig();
*writingConfig = false;
});
}, _lifetime);
_mtpFields.mainDcId = _mtp->mainDcId();
_mtp->setUpdatesHandler([=](const MTP::Response &message) {
checkForUpdates(message) || checkForNewSession(message);
});
_mtp->setGlobalFailHandler([=](const MTP::Error &, const MTP::Response &) {
if (const auto session = maybeSession()) {
crl::on_main(session, [=] { logOut(); });
}
});
_mtp->setStateChangedHandler([=](MTP::ShiftedDcId dc, int32 state) {
if (dc == _mtp->mainDcId()) {
Core::App().settings().proxy().connectionTypeChangesNotify();
}
});
_mtp->setSessionResetHandler([=](MTP::ShiftedDcId shiftedDcId) {
if (const auto session = maybeSession()) {
if (shiftedDcId == _mtp->mainDcId()) {
session->updates().getDifference();
}
}
});
if (!_mtpKeysToDestroy.empty()) {
destroyMtpKeys(base::take(_mtpKeysToDestroy));
}
if (_sessionUserId) {
createSession(
_sessionUserId,
base::take(_sessionUserSerialized),
base::take(_sessionUserStreamVersion),
(_storedSessionSettings
? std::move(_storedSessionSettings)
: std::make_unique<SessionSettings>()));
}
_storedSessionSettings = nullptr;
if (const auto session = maybeSession()) {
// Skip all pending self updates so that we won't local().writeSelf.
session->changes().sendNotifications();
}
_mtpValue = _mtp.get();
}
bool Account::checkForUpdates(const MTP::Response &message) {
auto updates = MTPUpdates();
auto from = message.reply.constData();
if (!updates.read(from, from + message.reply.size())) {
return false;
}
_mtpUpdates.fire(std::move(updates));
return true;
}
bool Account::checkForNewSession(const MTP::Response &message) {
auto newSession = MTPNewSession();
auto from = message.reply.constData();
if (!newSession.read(from, from + message.reply.size())) {
return false;
}
_mtpNewSessionCreated.fire({});
return true;
}
void Account::logOut() {
if (_loggingOut) {
return;
}
_loggingOut = true;
if (_mtp) {
_mtp->logout([=] { loggedOut(); });
} else {
// We log out because we've forgotten passcode.
loggedOut();
}
}
bool Account::loggingOut() const {
return _loggingOut;
}
void Account::forcedLogOut() {
if (sessionExists()) {
resetAuthorizationKeys();
loggedOut();
}
}
void Account::loggedOut() {
_loggingOut = false;
Media::Player::mixer()->stopAndClear();
destroySession(DestroyReason::LoggedOut);
local().reset();
cSetOtherOnline(0);
}
void Account::destroyMtpKeys(MTP::AuthKeysList &&keys) {
Expects(_mtp != nullptr);
if (keys.empty()) {
return;
}
if (_mtpForKeysDestroy) {
_mtpForKeysDestroy->addKeysForDestroy(std::move(keys));
local().writeMtpData();
return;
}
auto destroyFields = MTP::Instance::Fields();
destroyFields.mainDcId = MTP::Instance::Fields::kNoneMainDc;
destroyFields.config = std::make_unique<MTP::Config>(_mtp->config());
destroyFields.keys = std::move(keys);
destroyFields.deviceModel = Platform::DeviceModelPretty();
destroyFields.systemVersion = Platform::SystemVersionPretty();
_mtpForKeysDestroy = std::make_unique<MTP::Instance>(
MTP::Instance::Mode::KeysDestroyer,
std::move(destroyFields));
_mtpForKeysDestroy->writeKeysRequests(
) | rpl::on_next([=] {
local().writeMtpData();
}, _mtpForKeysDestroy->lifetime());
_mtpForKeysDestroy->allKeysDestroyed(
) | rpl::on_next([=] {
LOG(("MTP Info: all keys scheduled for destroy are destroyed."));
crl::on_main(this, [=] {
_mtpForKeysDestroy = nullptr;
local().writeMtpData();
});
}, _mtpForKeysDestroy->lifetime());
}
void Account::suggestMainDcId(MTP::DcId mainDcId) {
Expects(_mtp != nullptr);
_mtp->suggestMainDcId(mainDcId);
if (_mtpFields.mainDcId != MTP::Instance::Fields::kNotSetMainDc) {
_mtpFields.mainDcId = mainDcId;
}
}
void Account::destroyStaleAuthorizationKeys() {
Expects(_mtp != nullptr);
for (const auto &key : _mtp->getKeysForWrite()) {
// Disable this for now.
if (key->type() == MTP::AuthKey::Type::ReadFromFile) {
_mtpKeysToDestroy = _mtp->getKeysForWrite();
LOG(("MTP Info: destroying stale keys, count: %1"
).arg(_mtpKeysToDestroy.size()));
resetAuthorizationKeys();
return;
}
}
}
void Account::setHandleLoginCode(Fn<void(QString)> callback) {
_handleLoginCode = std::move(callback);
}
void Account::handleLoginCode(const QString &code) const {
if (_handleLoginCode) {
_handleLoginCode(code);
}
}
void Account::resetAuthorizationKeys() {
Expects(_mtp != nullptr);
{
const auto old = base::take(_mtp);
auto config = std::make_unique<MTP::Config>(old->config());
startMtp(std::move(config));
}
local().writeMtpData();
}
} // namespace Main

View File

@@ -0,0 +1,172 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/mtproto_auth_key.h"
#include "mtproto/mtp_instance.h"
#include "base/weak_ptr.h"
namespace Storage {
class Account;
class Domain;
enum class StartResult : uchar;
} // namespace Storage
namespace MTP {
class AuthKey;
class Config;
} // namespace MTP
namespace Main {
class Domain;
class Session;
class SessionSettings;
class AppConfig;
class Account final : public base::has_weak_ptr {
public:
Account(not_null<Domain*> domain, const QString &dataName, int index);
~Account();
[[nodiscard]] Domain &domain() const {
return *_domain;
}
[[nodiscard]] Storage::Domain &domainLocal() const;
[[nodiscard]] Storage::StartResult legacyStart(
const QByteArray &passcode);
[[nodiscard]] std::unique_ptr<MTP::Config> prepareToStart(
std::shared_ptr<MTP::AuthKey> localKey);
void prepareToStartAdded(
std::shared_ptr<MTP::AuthKey> localKey);
void start(std::unique_ptr<MTP::Config> config);
[[nodiscard]] uint64 willHaveSessionUniqueId(MTP::Config *config) const;
void createSession(
const MTPUser &user,
std::unique_ptr<SessionSettings> settings = nullptr);
void createSession(
UserId id,
QByteArray serialized,
int streamVersion,
std::unique_ptr<SessionSettings> settings);
void logOut();
void forcedLogOut();
[[nodiscard]] bool loggingOut() const;
[[nodiscard]] AppConfig &appConfig() const {
Expects(_appConfig != nullptr);
return *_appConfig;
}
[[nodiscard]] Storage::Account &local() const {
return *_local;
}
[[nodiscard]] bool sessionExists() const;
[[nodiscard]] Session &session() const;
[[nodiscard]] Session *maybeSession() const;
[[nodiscard]] rpl::producer<Session*> sessionValue() const;
[[nodiscard]] rpl::producer<Session*> sessionChanges() const;
[[nodiscard]] MTP::Instance &mtp() const {
return *_mtp;
}
[[nodiscard]] rpl::producer<not_null<MTP::Instance*>> mtpValue() const;
// Each time the main session changes a new copy of the pointer is fired.
// This allows to resend the requests that were not requiring auth, and
// which could be forgotten without calling .done() or .fail() because
// of the main dc changing.
[[nodiscard]] auto mtpMainSessionValue() const
-> rpl::producer<not_null<MTP::Instance*>>;
// Set from legacy storage.
void setLegacyMtpKey(std::shared_ptr<MTP::AuthKey> key);
void setMtpMainDcId(MTP::DcId mainDcId);
void setSessionUserId(UserId userId);
void setSessionFromStorage(
std::unique_ptr<SessionSettings> data,
QByteArray &&selfSerialized,
int32 selfStreamVersion);
[[nodiscard]] SessionSettings *getSessionSettings();
[[nodiscard]] rpl::producer<> mtpNewSessionCreated() const;
[[nodiscard]] rpl::producer<MTPUpdates> mtpUpdates() const;
// Serialization.
[[nodiscard]] QByteArray serializeMtpAuthorization() const;
void setMtpAuthorization(const QByteArray &serialized);
void suggestMainDcId(MTP::DcId mainDcId);
void destroyStaleAuthorizationKeys();
void setHandleLoginCode(Fn<void(QString)> callback);
void handleLoginCode(const QString &code) const;
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
private:
static constexpr auto kDefaultSaveDelay = crl::time(1000);
enum class DestroyReason {
Quitting,
LoggedOut,
};
void startMtp(std::unique_ptr<MTP::Config> config);
void createSession(
const MTPUser &user,
QByteArray serialized,
int streamVersion,
std::unique_ptr<SessionSettings> settings);
void watchProxyChanges();
void watchSessionChanges();
bool checkForUpdates(const MTP::Response &message);
bool checkForNewSession(const MTP::Response &message);
void destroyMtpKeys(MTP::AuthKeysList &&keys);
void resetAuthorizationKeys();
void loggedOut();
void destroySession(DestroyReason reason);
const not_null<Domain*> _domain;
const std::unique_ptr<Storage::Account> _local;
std::unique_ptr<MTP::Instance> _mtp;
rpl::variable<MTP::Instance*> _mtpValue;
std::unique_ptr<MTP::Instance> _mtpForKeysDestroy;
rpl::event_stream<MTPUpdates> _mtpUpdates;
rpl::event_stream<> _mtpNewSessionCreated;
std::unique_ptr<AppConfig> _appConfig;
std::unique_ptr<Session> _session;
rpl::variable<Session*> _sessionValue;
Fn<void(QString)> _handleLoginCode = nullptr;
UserId _sessionUserId = 0;
QByteArray _sessionUserSerialized;
int32 _sessionUserStreamVersion = 0;
std::unique_ptr<SessionSettings> _storedSessionSettings;
MTP::Instance::Fields _mtpFields;
MTP::AuthKeysList _mtpKeysToDestroy;
bool _loggingOut = false;
rpl::lifetime _lifetime;
};
} // namespace Main

View File

@@ -0,0 +1,605 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "main/main_app_config.h"
#include "api/api_authorizations.h"
#include "apiwrap.h"
#include "base/call_delayed.h"
#include "calls/group/ui/calls_group_stars_coloring.h"
#include "data/data_session.h"
#include "main/main_account.h"
#include "main/main_session.h"
#include "ui/chat/chat_style.h"
namespace Main {
namespace {
constexpr auto kRefreshTimeout = 3600 * crl::time(1000);
} // namespace
AppConfig::AppConfig(not_null<Account*> account) : _account(account) {
account->sessionChanges(
) | rpl::filter([=](Session *session) {
return (session != nullptr);
}) | rpl::on_next([=] {
_lastFrozenRefresh = 0;
refresh();
}, _lifetime);
}
AppConfig::~AppConfig() = default;
void AppConfig::start() {
_account->mtpMainSessionValue(
) | rpl::on_next([=](not_null<MTP::Instance*> instance) {
_api.emplace(instance);
_requestId = 0;
refresh();
_frozenTrackLifetime = instance->frozenErrorReceived(
) | rpl::on_next([=] {
if (!get<int>(u"freeze_since_date"_q, 0)) {
const auto now = crl::now();
if (!_lastFrozenRefresh
|| now > _lastFrozenRefresh + kRefreshTimeout) {
_lastFrozenRefresh = now;
refresh();
}
}
});
}, _lifetime);
}
int AppConfig::quoteLengthMax() const {
return get<int>(u"quote_length_max"_q, 1024);
}
int AppConfig::stargiftConvertPeriodMax() const {
return get<int>(
u"stargifts_convert_period_max"_q,
_account->mtp().isTestMode() ? 300 : (90 * 86400));
}
const std::vector<QString> &AppConfig::startRefPrefixes() {
if (_startRefPrefixes.empty()) {
_startRefPrefixes = get<std::vector<QString>>(
u"starref_start_param_prefixes"_q,
std::vector<QString>());
}
return _startRefPrefixes;
}
bool AppConfig::starrefSetupAllowed() const {
return get<bool>(u"starref_program_allowed"_q, false);
}
bool AppConfig::starrefJoinAllowed() const {
return get<bool>(u"starref_connect_allowed"_q, false);
}
int AppConfig::starrefCommissionMin() const {
return get<int>(u"starref_min_commission_permille"_q, 1);
}
int AppConfig::starrefCommissionMax() const {
return get<int>(u"starref_max_commission_permille"_q, 900);
}
int AppConfig::starsWithdrawMax() const {
return get<int>(u"stars_revenue_withdrawal_max"_q, 100);
}
float64 AppConfig::starsWithdrawRate() const {
return get<float64>(u"stars_usd_withdraw_rate_x1000"_q, 1300) / 1000.;
}
float64 AppConfig::currencyWithdrawRate() const {
return get<float64>(u"ton_usd_rate"_q, 1);
}
float64 AppConfig::starsSellRate() const {
return get<float64>(u"stars_usd_sell_rate_x1000"_q, 1410) / 1000.;
}
float64 AppConfig::currencySellRate() const {
return get<float64>(u"ton_usd_rate"_q, 1);
}
bool AppConfig::paidMessagesAvailable() const {
return get<bool>(u"stars_paid_messages_available"_q, false);
}
int AppConfig::paidMessageStarsMax() const {
return get<int>(u"stars_paid_message_amount_max"_q, 10'000);
}
int AppConfig::paidMessageCommission() const {
return get<int>(u"stars_paid_message_commission_permille"_q, 850);
}
int AppConfig::paidMessageChannelStarsDefault() const {
return get<int>(u"stars_paid_messages_channel_amount_default"_q, 10);
}
int AppConfig::pinnedGiftsLimit() const {
return get<int>(u"stargifts_pinned_to_top_limit"_q, 6);
}
int AppConfig::giftCollectionsLimit() const {
return get<int>(u"stargifts_collections_limit"_q, 10);
}
int AppConfig::giftCollectionGiftsLimit() const {
return get<int>(u"stargifts_collection_gifts_limit"_q, 500);
}
bool AppConfig::callsDisabledForSession() const {
const auto authorizations = _account->sessionExists()
? &_account->session().api().authorizations()
: nullptr;
return get<bool>(
u"call_requests_disabled"_q,
authorizations->callsDisabledHere());
}
int AppConfig::confcallSizeLimit() const {
return get<int>(
u"conference_call_size_limit"_q,
_account->mtp().isTestMode() ? 5 : 100);
}
bool AppConfig::confcallPrioritizeVP8() const {
return get<bool>(u"confcall_use_vp8"_q, false);
}
int AppConfig::giftResaleStarsMin() const {
return get<int>(u"stars_stargift_resale_amount_min"_q, 125);
}
int AppConfig::giftResaleStarsMax() const {
return get<int>(u"stars_stargift_resale_amount_max"_q, 35000);
}
int AppConfig::giftResaleStarsThousandths() const {
return get<int>(u"stars_stargift_resale_commission_permille"_q, 800);
}
int64 AppConfig::giftResaleNanoTonMin() const {
return get<int64>(u"ton_stargift_resale_amount_min"_q, 250'000'000LL);
}
int64 AppConfig::giftResaleNanoTonMax() const {
return get<int64>(
u"ton_stargift_resale_amount_max"_q,
1'000'000'000'000'000LL);
}
int AppConfig::giftResaleNanoTonThousandths() const {
return get<int>(u"ton_stargift_resale_commission_permille"_q, 800);
}
int AppConfig::pollOptionsLimit() const {
return get<int>(u"poll_answers_max"_q, 12);
}
int AppConfig::todoListItemsLimit() const {
return get<int>(
u"todo_items_max"_q,
_account->mtp().isTestMode() ? 10 : 30);
}
int AppConfig::todoListTitleLimit() const {
return get<int>(u"todo_title_length_max"_q, 32);
}
int AppConfig::todoListItemTextLimit() const {
return get<int>(u"todo_item_length_max"_q, 64);
}
int AppConfig::suggestedPostCommissionStars() const {
return get<int>(u"stars_suggested_post_commission_permille"_q, 850);
}
int AppConfig::suggestedPostCommissionTon() const {
return get<int>(u"ton_suggested_post_commission_permille"_q, 850);
}
int AppConfig::suggestedPostStarsMin() const {
return get<int>(u"stars_suggested_post_amount_min"_q, 5);
}
int AppConfig::suggestedPostStarsMax() const {
return get<int>(u"stars_suggested_post_amount_max"_q, 100'000);
}
int64 AppConfig::suggestedPostNanoTonMin() const {
return get<int64>(u"ton_suggested_post_amount_min"_q, 10'000'000LL);
}
int64 AppConfig::suggestedPostNanoTonMax() const {
return get<int64>(
u"ton_suggested_post_amount_max"_q,
10'000'000'000'000LL);
}
int AppConfig::suggestedPostDelayMin() const {
return get<int>(u"stars_suggested_post_future_min"_q, 300);
}
int AppConfig::suggestedPostDelayMax() const {
return get<int>(u"appConfig.stars_suggested_post_future_max"_q, 2678400);
}
TimeId AppConfig::suggestedPostAgeMin() const {
return get<int>(u"stars_suggested_post_age_min"_q, 86400);
}
bool AppConfig::ageVerifyNeeded() const {
return get<bool>(u"need_age_video_verification"_q, false);
}
QString AppConfig::ageVerifyCountry() const {
return get<QString>(u"verify_age_country"_q, QString());
}
int AppConfig::ageVerifyMinAge() const {
return get<int>(u"verify_age_min"_q, 18);
}
QString AppConfig::ageVerifyBotUsername() const {
return get<QString>(u"verify_age_bot_username"_q, QString());
}
int AppConfig::storiesAlbumsLimit() const {
return get<int>(u"stories_albums_limit"_q, 100);
}
int AppConfig::storiesAlbumLimit() const {
return get<int>(u"stories_album_stories_limit"_q, 1000);
}
int AppConfig::groupCallMessageLengthLimit() const {
return get<int>(u"group_call_message_length_limit"_q, 128);
}
TimeId AppConfig::groupCallMessageTTL() const {
return get<int>(u"group_call_message_ttl"_q, 10);
}
int AppConfig::passkeysAccountPasskeysMax() const {
return get<int>(u"passkeys_account_passkeys_max"_q, 10);
}
bool AppConfig::settingsDisplayPasskeys() const {
return get<bool>(u"settings_display_passkeys"_q, false);
}
void AppConfig::refresh(bool force) {
if (_requestId || !_api) {
if (force) {
_pendingRefresh = true;
}
return;
}
_pendingRefresh = false;
_requestId = _api->request(MTPhelp_GetAppConfig(
MTP_int(_hash)
)).done([=](const MTPhelp_AppConfig &result) {
_requestId = 0;
result.match([&](const MTPDhelp_appConfig &data) {
_hash = data.vhash().v;
const auto &config = data.vconfig();
if (config.type() != mtpc_jsonObject) {
LOG(("API Error: Unexpected config type."));
return;
}
auto was = ignoredRestrictionReasons();
_data.clear();
for (const auto &element : config.c_jsonObject().vvalue().v) {
element.match([&](const MTPDjsonObjectValue &data) {
_data.emplace_or_assign(qs(data.vkey()), data.vvalue());
});
}
updateIgnoredRestrictionReasons(std::move(was));
_groupCallColorings = {};
DEBUG_LOG(("getAppConfig result handled."));
_refreshed.fire({});
}, [](const MTPDhelp_appConfigNotModified &) {});
if (base::take(_pendingRefresh)) {
refresh();
} else {
refreshDelayed();
}
}).fail([=] {
_requestId = 0;
refreshDelayed();
}).send();
}
void AppConfig::refreshDelayed() {
base::call_delayed(kRefreshTimeout, _account, [=] {
refresh();
});
}
void AppConfig::updateIgnoredRestrictionReasons(std::vector<QString> was) {
_ignoreRestrictionReasons = get<std::vector<QString>>(
u"ignore_restriction_reasons"_q,
std::vector<QString>());
ranges::sort(_ignoreRestrictionReasons);
if (_ignoreRestrictionReasons != was) {
for (const auto &reason : _ignoreRestrictionReasons) {
const auto i = ranges::remove(was, reason);
if (i != end(was)) {
was.erase(i, end(was));
} else {
was.push_back(reason);
}
}
_ignoreRestrictionChanges.fire(std::move(was));
}
}
rpl::producer<> AppConfig::refreshed() const {
return _refreshed.events();
}
rpl::producer<> AppConfig::value() const {
return _refreshed.events_starting_with({});
}
template <typename Extractor>
auto AppConfig::getValue(const QString &key, Extractor &&extractor) const {
const auto i = _data.find(key);
return extractor((i != end(_data))
? i->second
: MTPJSONValue(MTP_jsonNull()));
}
bool AppConfig::getBool(const QString &key, bool fallback) const {
return getValue(key, [&](const MTPJSONValue &value) {
return value.match([&](const MTPDjsonBool &data) {
return mtpIsTrue(data.vvalue());
}, [&](const auto &data) {
return fallback;
});
});
}
double AppConfig::getDouble(const QString &key, double fallback) const {
return getValue(key, [&](const MTPJSONValue &value) {
return value.match([&](const MTPDjsonNumber &data) {
return data.vvalue().v;
}, [&](const auto &data) {
return fallback;
});
});
}
QString AppConfig::getString(
const QString &key,
const QString &fallback) const {
return getValue(key, [&](const MTPJSONValue &value) {
return value.match([&](const MTPDjsonString &data) {
return qs(data.vvalue());
}, [&](const auto &data) {
return fallback;
});
});
}
std::vector<QString> AppConfig::getStringArray(
const QString &key,
std::vector<QString> &&fallback) const {
return getValue(key, [&](const MTPJSONValue &value) {
return value.match([&](const MTPDjsonArray &data) {
auto result = std::vector<QString>();
result.reserve(data.vvalue().v.size());
for (const auto &entry : data.vvalue().v) {
if (entry.type() != mtpc_jsonString) {
return std::move(fallback);
}
result.push_back(qs(entry.c_jsonString().vvalue()));
}
return result;
}, [&](const auto &data) {
return std::move(fallback);
});
});
}
base::flat_map<QString, QString> AppConfig::getStringMap(
const QString &key,
base::flat_map<QString, QString> &&fallback) const {
return getValue(key, [&](const MTPJSONValue &value) {
return value.match([&](const MTPDjsonObject &data) {
auto result = base::flat_map<QString, QString>();
result.reserve(data.vvalue().v.size());
for (const auto &entry : data.vvalue().v) {
const auto &data = entry.data();
const auto &value = data.vvalue();
if (value.type() != mtpc_jsonString) {
return std::move(fallback);
}
result.emplace(
qs(data.vkey()),
qs(value.c_jsonString().vvalue()));
}
return result;
}, [&](const auto &data) {
return std::move(fallback);
});
});
}
std::vector<int> AppConfig::getIntArray(
const QString &key,
std::vector<int> &&fallback) const {
return getValue(key, [&](const MTPJSONValue &value) {
return value.match([&](const MTPDjsonArray &data) {
auto result = std::vector<int>();
result.reserve(data.vvalue().v.size());
for (const auto &entry : data.vvalue().v) {
if (entry.type() != mtpc_jsonNumber) {
return std::move(fallback);
}
result.push_back(
int(base::SafeRound(entry.c_jsonNumber().vvalue().v)));
}
return result;
}, [&](const auto &data) {
return std::move(fallback);
});
});
}
bool AppConfig::newRequirePremiumFree() const {
return get<bool>(
u"new_noncontact_peers_require_premium_without_ownpremium"_q,
false);
}
auto AppConfig::groupCallColorings() const -> std::vector<StarsColoring> {
if (!_groupCallColorings.empty()) {
return _groupCallColorings;
}
const auto key = u"stars_groupcall_message_limits"_q;
getValue(key, [&](const MTPJSONValue &value) {
value.match([&](const MTPDjsonArray &data) {
const auto &list = data.vvalue().v;
_groupCallColorings.reserve(list.size());
for (const auto &entry : list) {
entry.match([&](const MTPDjsonObject &data) {
auto &entry = _groupCallColorings.emplace_back();
const auto &fields = data.vvalue().v;
for (const auto &field : fields) {
const auto &key = field.data().vkey().v;
const auto &value = field.data().vvalue();
const auto error = [&] {
LOG(("API Error: Incorrect value for %1."
).arg(qs(key)));
return std::nullopt;
};
const auto number = [&]() -> std::optional<int> {
if (value.type() != mtpc_jsonNumber) {
return error();
}
const auto &data = value.c_jsonNumber();
const auto v = base::SafeRound(data.vvalue().v);
if (v < 0) {
return error();
}
return int(v);
};
const auto color = [&]() -> std::optional<int> {
if (value.type() != mtpc_jsonString) {
return error();
}
const auto &data = value.c_jsonString();
const auto text = data.vvalue().v;
if (text.size() != 6) {
return error();
}
const auto digit = [&](int i) {
Expects(i >= 0 && i < 6);
const auto ch = text[i];
return (ch >= '0' && ch <= '9')
? int(ch - '0')
: (ch >= 'A' && ch <= 'F')
? (int(ch - 'A') + 10)
: (ch >= 'a' && ch <= 'f')
? (int(ch - 'a') + 10)
: std::optional<int>();
};
const auto component = [&](int i) {
const auto a = digit(i), b = digit(i + 1);
return (a && b)
? ((*a) * 16 + (*b))
: std::optional<int>();
};
const auto r = component(0);
const auto g = component(2);
const auto b = component(4);
if (!r || !g || !b) {
return error();
}
return ((*r) << 16) | ((*g) << 8) | (*b);
};
if (key == "stars"_q) {
if (const auto n = number()) {
entry.fromStars = *n;
} else {
return _groupCallColorings.pop_back();
}
} else if (key == "pin_period"_q) {
if (const auto n = number()) {
entry.secondsPin = *n;
} else {
return _groupCallColorings.pop_back();
}
} else if (key == "text_length_max"_q) {
if (const auto n = number()) {
entry.charactersMax = *n;
} else {
return _groupCallColorings.pop_back();
}
} else if (key == "emoji_max"_q) {
if (const auto n = number()) {
entry.emojiLimit = *n;
} else {
return _groupCallColorings.pop_back();
}
} else if (key == "color1"_q) {
if (const auto c = color()) {
entry.bgLight = *c;
} else {
return _groupCallColorings.pop_back();
}
} else if (key == "color_bg"_q) {
if (const auto c = color()) {
entry.bgDark = *c;
} else {
return _groupCallColorings.pop_back();
}
}
}
}, [](const auto &) {});
}
}, [](const auto &) {});
});
if (_groupCallColorings.empty()) {
_groupCallColorings = std::vector<StarsColoring>{
{ 0x955CDB, 0x49079B, 0, 30, 30, 0 }, // purple
{ 0x955CDB, 0x49079B, 10, 60, 60, 1 }, // still purple
{ 0x46A3EB, 0x00508E, 50, 120, 80, 2 }, // blue
{ 0x40A920, 0x176200, 100, 300, 110, 3 }, // green
{ 0xE29A09, 0x9A3E00, 250, 600, 150, 4 }, // yellow
{ 0xED771E, 0x9B3100, 500, 900, 200, 7 }, // orange
{ 0xE14542, 0x8B0503, 2'000, 1800, 280, 10 }, // red
{ 0x596473, 0x252C36, 10'000, 3600, 400, 20 }, // silver
};
} else {
const auto proj = &StarsColoring::fromStars;
if (!ranges::contains(_groupCallColorings, 0, proj)) {
_groupCallColorings.insert(
begin(_groupCallColorings),
{ 0x955CDB, 0x49079B, 0, 30, 30, 0 });
}
ranges::sort(_groupCallColorings, ranges::less(), proj);
}
return _groupCallColorings;
}
} // namespace Main

View File

@@ -0,0 +1,187 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/sender.h"
#include "base/algorithm.h"
namespace Ui {
struct ColorIndicesCompressed;
} // namespace Ui
namespace Calls::Group::Ui {
using namespace ::Ui;
struct StarsColoring;
} // namespace Calls::Group::Ui
namespace Main {
class Account;
class AppConfig final {
public:
explicit AppConfig(not_null<Account*> account);
~AppConfig();
void start();
template <typename Type>
[[nodiscard]] Type get(const QString &key, Type fallback) const {
if constexpr (std::is_same_v<Type, double>) {
return getDouble(key, fallback);
} else if constexpr (std::is_same_v<Type, int>) {
return int(base::SafeRound(getDouble(key, double(fallback))));
} else if constexpr (std::is_same_v<Type, int64>) {
return int64(base::SafeRound(getDouble(key, double(fallback))));
} else if constexpr (std::is_same_v<Type, QString>) {
return getString(key, fallback);
} else if constexpr (std::is_same_v<Type, std::vector<QString>>) {
return getStringArray(key, std::move(fallback));
} else if constexpr (
std::is_same_v<Type, base::flat_map<QString, QString>>) {
return getStringMap(key, std::move(fallback));
} else if constexpr (std::is_same_v<Type, std::vector<int>>) {
return getIntArray(key, std::move(fallback));
} else if constexpr (std::is_same_v<Type, bool>) {
return getBool(key, fallback);
}
}
[[nodiscard]] rpl::producer<> refreshed() const;
[[nodiscard]] rpl::producer<> value() const;
[[nodiscard]] bool newRequirePremiumFree() const;
[[nodiscard]] auto ignoredRestrictionReasons() const
-> const std::vector<QString> & {
return _ignoreRestrictionReasons;
}
[[nodiscard]] auto ignoredRestrictionReasonsChanges() const {
return _ignoreRestrictionChanges.events();
}
[[nodiscard]] int quoteLengthMax() const;
[[nodiscard]] int stargiftConvertPeriodMax() const;
[[nodiscard]] const std::vector<QString> &startRefPrefixes();
[[nodiscard]] bool starrefSetupAllowed() const;
[[nodiscard]] bool starrefJoinAllowed() const;
[[nodiscard]] int starrefCommissionMin() const;
[[nodiscard]] int starrefCommissionMax() const;
[[nodiscard]] int starsWithdrawMax() const;
[[nodiscard]] float64 starsWithdrawRate() const;
[[nodiscard]] float64 currencyWithdrawRate() const;
[[nodiscard]] float64 starsSellRate() const;
[[nodiscard]] float64 currencySellRate() const;
[[nodiscard]] bool paidMessagesAvailable() const;
[[nodiscard]] int paidMessageStarsMax() const;
[[nodiscard]] int paidMessageCommission() const;
[[nodiscard]] int paidMessageChannelStarsDefault() const;
[[nodiscard]] int pinnedGiftsLimit() const;
[[nodiscard]] int giftCollectionsLimit() const;
[[nodiscard]] int giftCollectionGiftsLimit() const;
[[nodiscard]] bool callsDisabledForSession() const;
[[nodiscard]] int confcallSizeLimit() const;
[[nodiscard]] bool confcallPrioritizeVP8() const;
[[nodiscard]] int giftResaleStarsMin() const;
[[nodiscard]] int giftResaleStarsMax() const;
[[nodiscard]] int giftResaleStarsThousandths() const;
[[nodiscard]] int64 giftResaleNanoTonMin() const;
[[nodiscard]] int64 giftResaleNanoTonMax() const;
[[nodiscard]] int giftResaleNanoTonThousandths() const;
[[nodiscard]] int pollOptionsLimit() const;
[[nodiscard]] int todoListItemsLimit() const;
[[nodiscard]] int todoListTitleLimit() const;
[[nodiscard]] int todoListItemTextLimit() const;
[[nodiscard]] int suggestedPostCommissionStars() const;
[[nodiscard]] int suggestedPostCommissionTon() const;
[[nodiscard]] int suggestedPostStarsMin() const;
[[nodiscard]] int suggestedPostStarsMax() const;
[[nodiscard]] int64 suggestedPostNanoTonMin() const;
[[nodiscard]] int64 suggestedPostNanoTonMax() const;
[[nodiscard]] int suggestedPostDelayMin() const;
[[nodiscard]] int suggestedPostDelayMax() const;
[[nodiscard]] TimeId suggestedPostAgeMin() const;
[[nodiscard]] bool ageVerifyNeeded() const;
[[nodiscard]] QString ageVerifyCountry() const;
[[nodiscard]] int ageVerifyMinAge() const;
[[nodiscard]] QString ageVerifyBotUsername() const;
[[nodiscard]] int storiesAlbumsLimit() const;
[[nodiscard]] int storiesAlbumLimit() const;
[[nodiscard]] int groupCallMessageLengthLimit() const;
[[nodiscard]] TimeId groupCallMessageTTL() const;
[[nodiscard]] int passkeysAccountPasskeysMax() const;
[[nodiscard]] bool settingsDisplayPasskeys() const;
using StarsColoring = Calls::Group::Ui::StarsColoring;
[[nodiscard]] std::vector<StarsColoring> groupCallColorings() const;
void refresh(bool force = false);
private:
void refreshDelayed();
template <typename Extractor>
[[nodiscard]] auto getValue(
const QString &key,
Extractor &&extractor) const;
[[nodiscard]] bool getBool(
const QString &key,
bool fallback) const;
[[nodiscard]] double getDouble(
const QString &key,
double fallback) const;
[[nodiscard]] QString getString(
const QString &key,
const QString &fallback) const;
[[nodiscard]] std::vector<QString> getStringArray(
const QString &key,
std::vector<QString> &&fallback) const;
[[nodiscard]] base::flat_map<QString, QString> getStringMap(
const QString &key,
base::flat_map<QString, QString> &&fallback) const;
[[nodiscard]] std::vector<int> getIntArray(
const QString &key,
std::vector<int> &&fallback) const;
void updateIgnoredRestrictionReasons(std::vector<QString> was);
const not_null<Account*> _account;
std::optional<MTP::Sender> _api;
mtpRequestId _requestId = 0;
int32 _hash = 0;
bool _pendingRefresh = false;
base::flat_map<QString, MTPJSONValue> _data;
rpl::event_stream<> _refreshed;
std::vector<QString> _ignoreRestrictionReasons;
rpl::event_stream<std::vector<QString>> _ignoreRestrictionChanges;
std::vector<QString> _startRefPrefixes;
mutable std::vector<StarsColoring> _groupCallColorings;
crl::time _lastFrozenRefresh = 0;
rpl::lifetime _frozenTrackLifetime;
rpl::lifetime _lifetime;
};
} // namespace Main

View File

@@ -0,0 +1,29 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "main/main_app_config_values.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
namespace AppConfig {
std::optional<QString> FragmentLink(not_null<Main::Session*> session) {
using Strings = std::vector<QString>;
const auto domains = session->appConfig().get<Strings>(
u"whitelisted_domains"_q,
std::vector<QString>());
const auto proj = [&, domain = u"fragment"_q](const QString &p) {
return p.contains(domain);
};
const auto it = ranges::find_if(domains, proj);
return (it == end(domains))
? std::nullopt
: std::make_optional<QString>(*it);
}
} // namespace AppConfig

View File

@@ -0,0 +1,18 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Main {
class Session;
} // namespace Main
namespace AppConfig {
[[nodiscard]] std::optional<QString> FragmentLink(not_null<Main::Session*>);
} // namespace AppConfig

View File

@@ -0,0 +1,517 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "main/main_domain.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/shortcuts.h"
#include "core/crash_reports.h"
#include "main/main_account.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_user.h"
#include "mtproto/mtproto_config.h"
#include "mtproto/mtproto_dc_options.h"
#include "storage/storage_domain.h"
#include "storage/storage_account.h"
#include "storage/localstorage.h"
#include "export/export_settings.h"
#include "window/notifications_manager.h"
#include "window/window_controller.h"
#include "data/data_peer_values.h" // Data::AmPremiumValue.
namespace Main {
Domain::Domain(const QString &dataName)
: _dataName(dataName)
, _local(std::make_unique<Storage::Domain>(this, dataName)) {
_active.changes(
) | rpl::take(1) | rpl::on_next([=] {
// In case we had a legacy passcoded app we start settings here.
Core::App().startSettingsAndBackground();
crl::on_main(this, [=] {
Core::App().notifications().createManager();
});
}, _lifetime);
_active.changes(
) | rpl::map([](Main::Account *account) {
return account ? account->sessionValue() : rpl::never<Session*>();
}) | rpl::flatten_latest(
) | rpl::map([](Main::Session *session) {
return session
? session->changes().peerFlagsValue(
session->user(),
Data::PeerUpdate::Flag::Username)
: rpl::never<Data::PeerUpdate>();
}) | rpl::flatten_latest(
) | rpl::on_next([](const Data::PeerUpdate &update) {
CrashReports::SetAnnotation("Username", update.peer->username());
}, _lifetime);
}
Domain::~Domain() = default;
bool Domain::started() const {
return !_accounts.empty();
}
Storage::StartResult Domain::start(const QByteArray &passcode) {
Expects(!started());
const auto result = _local->start(passcode);
if (result == Storage::StartResult::Success) {
activateAfterStarting();
crl::on_main(&Core::App(), [=] { suggestExportIfNeeded(); });
} else {
Assert(!started());
}
return result;
}
void Domain::finish() {
_accountToActivate = -1;
_active.reset(nullptr);
base::take(_accounts);
}
void Domain::suggestExportIfNeeded() {
Expects(started());
for (const auto &[index, account] : _accounts) {
if (const auto session = account->maybeSession()) {
const auto settings = session->local().readExportSettings();
if (const auto availableAt = settings.availableAt) {
session->data().suggestStartExport(availableAt);
}
}
}
}
void Domain::accountAddedInStorage(AccountWithIndex accountWithIndex) {
Expects(accountWithIndex.account != nullptr);
for (const auto &[index, _] : _accounts) {
if (index == accountWithIndex.index) {
Unexpected("Repeated account index.");
}
}
_accounts.push_back(std::move(accountWithIndex));
}
void Domain::activateFromStorage(int index) {
_accountToActivate = index;
}
int Domain::activeForStorage() const {
return _accountToActivate;
}
void Domain::resetWithForgottenPasscode() {
if (_accounts.empty()) {
_local->startFromScratch();
activateAfterStarting();
} else {
for (const auto &[index, account] : _accounts) {
account->logOut();
}
}
}
void Domain::activateAfterStarting() {
Expects(started());
auto toActivate = _accounts.front().account.get();
for (const auto &[index, account] : _accounts) {
if (index == _accountToActivate) {
toActivate = account.get();
}
watchSession(account.get());
}
activate(toActivate);
removePasscodeIfEmpty();
}
const std::vector<Domain::AccountWithIndex> &Domain::accounts() const {
return _accounts;
}
std::vector<not_null<Account*>> Domain::orderedAccounts() const {
const auto order = Core::App().settings().accountsOrder();
auto accounts = ranges::views::all(
_accounts
) | ranges::views::transform([](const Domain::AccountWithIndex &a) {
return not_null{ a.account.get() };
}) | ranges::to_vector;
ranges::stable_sort(accounts, [&](
not_null<Account*> a,
not_null<Account*> b) {
const auto aIt = a->sessionExists()
? ranges::find(order, a->session().uniqueId())
: end(order);
const auto bIt = b->sessionExists()
? ranges::find(order, b->session().uniqueId())
: end(order);
return aIt < bIt;
});
return accounts;
}
rpl::producer<> Domain::accountsChanges() const {
return _accountsChanges.events();
}
Account *Domain::maybeLastOrSomeAuthedAccount() {
auto result = (Account*)nullptr;
for (const auto &[index, account] : _accounts) {
if (!account->sessionExists()) {
continue;
} else if (index == _lastActiveIndex) {
return account.get();
} else if (!result) {
result = account.get();
}
}
return result;
}
int Domain::accountsAuthedCount() const {
auto result = 0;
for (const auto &[index, account] : _accounts) {
if (account->sessionExists()) {
++result;
}
}
return result;
}
rpl::producer<Account*> Domain::activeValue() const {
return _active.value();
}
Account &Domain::active() const {
Expects(!_accounts.empty());
Ensures(_active.current() != nullptr);
return *_active.current();
}
rpl::producer<not_null<Account*>> Domain::activeChanges() const {
return _active.changes() | rpl::map([](Account *value) {
return not_null{ value };
});
}
rpl::producer<Session*> Domain::activeSessionChanges() const {
return _activeSessions.events();
}
rpl::producer<Session*> Domain::activeSessionValue() const {
const auto current = _accounts.empty()
? nullptr
: active().maybeSession();
return rpl::single(current) | rpl::then(_activeSessions.events());
}
int Domain::unreadBadge() const {
return _unreadBadge;
}
bool Domain::unreadBadgeMuted() const {
return _unreadBadgeMuted;
}
rpl::producer<> Domain::unreadBadgeChanges() const {
return _unreadBadgeChanges.events();
}
void Domain::notifyUnreadBadgeChanged() {
for (const auto &[index, account] : _accounts) {
if (const auto session = account->maybeSession()) {
session->data().notifyUnreadBadgeChanged();
}
}
}
void Domain::updateUnreadBadge() {
_unreadBadge = 0;
_unreadBadgeMuted = true;
for (const auto &[index, account] : _accounts) {
if (const auto session = account->maybeSession()) {
const auto data = &session->data();
_unreadBadge += data->unreadBadge();
if (!data->unreadBadgeMuted()) {
_unreadBadgeMuted = false;
}
}
}
_unreadBadgeChanges.fire({});
}
void Domain::scheduleUpdateUnreadBadge() {
if (_unreadBadgeUpdateScheduled) {
return;
}
_unreadBadgeUpdateScheduled = true;
Core::App().postponeCall(crl::guard(&Core::App(), [=] {
_unreadBadgeUpdateScheduled = false;
updateUnreadBadge();
}));
}
not_null<Main::Account*> Domain::add(MTP::Environment environment) {
Expects(started());
Expects(_accounts.size() < kPremiumMaxAccounts);
static const auto cloneConfig = [](const MTP::Config &config) {
return std::make_unique<MTP::Config>(config);
};
auto mainDcId = MTP::Instance::Fields::kNotSetMainDc;
const auto accountConfig = [&](not_null<Account*> account) {
mainDcId = account->mtp().mainDcId();
return cloneConfig(account->mtp().config());
};
auto config = [&] {
if (_active.current()->mtp().environment() == environment) {
return accountConfig(_active.current());
}
for (const auto &[index, account] : _accounts) {
if (account->mtp().environment() == environment) {
return accountConfig(account.get());
}
}
return (environment == MTP::Environment::Production)
? cloneConfig(Core::App().fallbackProductionConfig())
: std::make_unique<MTP::Config>(environment);
}();
auto index = 0;
while (ranges::contains(_accounts, index, &AccountWithIndex::index)) {
++index;
}
_accounts.push_back(AccountWithIndex{
.index = index,
.account = std::make_unique<Account>(this, _dataName, index)
});
const auto account = _accounts.back().account.get();
account->setMtpMainDcId(mainDcId);
_local->startAdded(account, std::move(config));
watchSession(account);
_accountsChanges.fire({});
auto &settings = Core::App().settings();
if (_accounts.size() == 2 && !settings.mainMenuAccountsShown()) {
settings.setMainMenuAccountsShown(true);
Core::App().saveSettingsDelayed();
}
return account;
}
void Domain::addActivated(MTP::Environment environment, bool newWindow) {
const auto added = [&](not_null<Main::Account*> account) {
if (newWindow) {
Core::App().ensureSeparateWindowFor(account);
} else if (const auto window = Core::App().separateWindowFor(
account)) {
window->activate();
} else {
activate(account);
}
};
if (accounts().size() < maxAccounts()) {
added(add(environment));
} else {
for (auto &[index, account] : accounts()) {
if (!account->sessionExists()
&& account->mtp().environment() == environment) {
added(account.get());
break;
}
}
}
}
void Domain::watchSession(not_null<Account*> account) {
account->sessionValue(
) | rpl::filter([=](Session *session) {
return session != nullptr;
}) | rpl::on_next([=](Session *session) {
session->data().unreadBadgeChanges(
) | rpl::on_next([=] {
scheduleUpdateUnreadBadge();
}, session->lifetime());
Data::AmPremiumValue(
session
) | rpl::on_next([=] {
_lastMaxAccounts = maxAccounts();
}, session->lifetime());
}, account->lifetime());
account->sessionChanges(
) | rpl::filter([=](Session *session) {
return !session;
}) | rpl::on_next([=] {
scheduleUpdateUnreadBadge();
closeAccountWindows(account);
crl::on_main(&Core::App(), [=] {
removeRedundantAccounts();
});
}, account->lifetime());
}
void Domain::closeAccountWindows(not_null<Main::Account*> account) {
auto another = (Main::Account*)nullptr;
for (auto i = _accounts.begin(); i != _accounts.end(); ++i) {
const auto other = not_null(i->account.get());
if (other == account) {
continue;
} else if (Core::App().separateWindowFor(other)) {
const auto that = Core::App().separateWindowFor(account);
if (that) {
that->close();
}
} else if (!another
|| (other->sessionExists() && !another->sessionExists())) {
another = other;
}
}
if (another) {
activate(another);
}
}
bool Domain::removePasscodeIfEmpty() {
if (_accounts.size() != 1 || _active.current()->sessionExists()) {
return false;
}
Local::reset();
// We completely logged out, remove the passcode if it was there.
if (Core::App().passcodeLocked()) {
Core::App().unlockPasscode();
}
if (!_local->hasLocalPasscode()) {
return false;
}
_local->setPasscode(QByteArray());
Core::App().settings().setSystemUnlockEnabled(false);
Core::App().saveSettingsDelayed();
return true;
}
void Domain::removeRedundantAccounts() {
Expects(started());
const auto was = _accounts.size();
for (auto i = _accounts.begin(); i != _accounts.end();) {
if (Core::App().separateWindowFor(not_null(i->account.get()))
|| i->account->sessionExists()) {
++i;
continue;
}
checkForLastProductionConfig(i->account.get());
i = _accounts.erase(i);
}
if (!removePasscodeIfEmpty() && _accounts.size() != was) {
scheduleWriteAccounts();
_accountsChanges.fire({});
}
}
void Domain::checkForLastProductionConfig(
not_null<Main::Account*> account) {
const auto mtp = &account->mtp();
if (mtp->environment() != MTP::Environment::Production) {
return;
}
for (const auto &[index, other] : _accounts) {
if (other.get() != account
&& other->mtp().environment() == MTP::Environment::Production) {
return;
}
}
Core::App().refreshFallbackProductionConfig(mtp->config());
}
void Domain::maybeActivate(not_null<Main::Account*> account) {
if (Core::App().separateWindowFor(account)) {
activate(account);
} else {
Core::App().preventOrInvoke(crl::guard(account, [=] {
activate(account);
}));
}
}
void Domain::activate(not_null<Main::Account*> account) {
if (const auto window = Core::App().separateWindowFor(account)) {
window->activate();
}
if (_active.current() == account.get()) {
return;
}
const auto i = ranges::find(_accounts, account.get(), [](
const AccountWithIndex &value) {
return value.account.get();
});
Assert(i != end(_accounts));
const auto changed = (_accountToActivate != i->index);
auto wasAuthed = false;
_activeLifetime.destroy();
if (_active.current()) {
_lastActiveIndex = _accountToActivate;
wasAuthed = _active.current()->sessionExists();
}
_accountToActivate = i->index;
_active = account.get();
_active.current()->sessionValue(
) | rpl::start_to_stream(_activeSessions, _activeLifetime);
if (changed) {
if (wasAuthed) {
scheduleWriteAccounts();
} else {
crl::on_main(&Core::App(), [=] {
removeRedundantAccounts();
});
}
}
}
void Domain::scheduleWriteAccounts() {
if (_writeAccountsScheduled) {
return;
}
_writeAccountsScheduled = true;
crl::on_main(&Core::App(), [=] {
_writeAccountsScheduled = false;
_local->writeAccounts();
});
}
int Domain::maxAccounts() const {
const auto premiumCount = ranges::count_if(accounts(), [](
const Main::Domain::AccountWithIndex &d) {
return d.account->sessionExists()
&& (d.account->session().premium()
|| d.account->session().isTestMode());
});
return std::min(int(premiumCount) + kMaxAccounts, kPremiumMaxAccounts);
}
rpl::producer<int> Domain::maxAccountsChanges() const {
return _lastMaxAccounts.changes();
}
} // namespace Main

View File

@@ -0,0 +1,118 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
#include "base/weak_ptr.h"
namespace Storage {
class Domain;
enum class StartResult : uchar;
} // namespace Storage
namespace MTP {
enum class Environment : uchar;
} // namespace MTP
namespace Main {
class Account;
class Session;
class Domain final : public base::has_weak_ptr {
public:
struct AccountWithIndex {
int index = 0;
std::unique_ptr<Account> account;
};
static constexpr auto kMaxAccounts = 3;
static constexpr auto kPremiumMaxAccounts = 6;
explicit Domain(const QString &dataName);
~Domain();
[[nodiscard]] bool started() const;
[[nodiscard]] Storage::StartResult start(const QByteArray &passcode);
void resetWithForgottenPasscode();
void finish();
[[nodiscard]] int maxAccounts() const;
[[nodiscard]] rpl::producer<int> maxAccountsChanges() const;
[[nodiscard]] Storage::Domain &local() const {
return *_local;
}
[[nodiscard]] auto accounts() const
-> const std::vector<AccountWithIndex> &;
[[nodiscard]] std::vector<not_null<Account*>> orderedAccounts() const;
[[nodiscard]] rpl::producer<Account*> activeValue() const;
[[nodiscard]] rpl::producer<> accountsChanges() const;
[[nodiscard]] Account *maybeLastOrSomeAuthedAccount();
[[nodiscard]] int accountsAuthedCount() const;
// Expects(started());
[[nodiscard]] Account &active() const;
[[nodiscard]] rpl::producer<not_null<Account*>> activeChanges() const;
[[nodiscard]] rpl::producer<Session*> activeSessionValue() const;
[[nodiscard]] rpl::producer<Session*> activeSessionChanges() const;
[[nodiscard]] int unreadBadge() const;
[[nodiscard]] bool unreadBadgeMuted() const;
[[nodiscard]] rpl::producer<> unreadBadgeChanges() const;
void notifyUnreadBadgeChanged();
[[nodiscard]] not_null<Main::Account*> add(MTP::Environment environment);
void maybeActivate(not_null<Main::Account*> account);
void activate(not_null<Main::Account*> account);
void addActivated(MTP::Environment environment, bool newWindow = false);
// Interface for Storage::Domain.
void accountAddedInStorage(AccountWithIndex accountWithIndex);
void activateFromStorage(int index);
[[nodiscard]] int activeForStorage() const;
private:
void activateAfterStarting();
void closeAccountWindows(not_null<Main::Account*> account);
bool removePasscodeIfEmpty();
void removeRedundantAccounts();
void watchSession(not_null<Account*> account);
void scheduleWriteAccounts();
void checkForLastProductionConfig(not_null<Main::Account*> account);
void updateUnreadBadge();
void scheduleUpdateUnreadBadge();
void suggestExportIfNeeded();
const QString _dataName;
const std::unique_ptr<Storage::Domain> _local;
std::vector<AccountWithIndex> _accounts;
rpl::event_stream<> _accountsChanges;
rpl::variable<Account*> _active = nullptr;
int _accountToActivate = -1;
int _lastActiveIndex = -1;
bool _writeAccountsScheduled = false;
rpl::event_stream<Session*> _activeSessions;
rpl::event_stream<> _unreadBadgeChanges;
int _unreadBadge = 0;
bool _unreadBadgeMuted = true;
bool _unreadBadgeUpdateScheduled = false;
rpl::variable<int> _lastMaxAccounts;
rpl::lifetime _activeLifetime;
rpl::lifetime _lifetime;
};
} // namespace Main

View File

@@ -0,0 +1,598 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_peer_colors.h"
#include "api/api_updates.h"
#include "api/api_user_privacy.h"
#include "main/main_account.h"
#include "main/main_domain.h"
#include "main/main_session_settings.h"
#include "main/main_app_config.h"
#include "main/session/send_as_peers.h"
#include "mtproto/mtproto_config.h"
#include "chat_helpers/stickers_emoji_pack.h"
#include "chat_helpers/stickers_dice_pack.h"
#include "chat_helpers/stickers_gift_box_pack.h"
#include "history/view/reactions/history_view_reactions_strip.h"
#include "history/history.h"
#include "history/history_item.h"
#include "inline_bots/bot_attach_web_view.h"
#include "storage/file_download.h"
#include "storage/download_manager_mtproto.h"
#include "storage/file_upload.h"
#include "storage/storage_account.h"
#include "storage/storage_facade.h"
#include "data/components/credits.h"
#include "data/components/factchecks.h"
#include "data/components/gift_auctions.h"
#include "data/components/location_pickers.h"
#include "data/components/passkeys.h"
#include "data/components/promo_suggestions.h"
#include "data/components/recent_peers.h"
#include "data/components/recent_shared_media_gifts.h"
#include "data/components/scheduled_messages.h"
#include "data/components/sponsored_messages.h"
#include "data/components/top_peers.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_user.h"
#include "data/data_download_manager.h"
#include "data/stickers/data_stickers.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "window/window_lock_widgets.h"
#include "base/unixtime.h"
#include "calls/calls_instance.h"
#include "support/support_helper.h"
#include "lang/lang_keys.h"
#include "core/application.h"
#include "ui/text/text_utilities.h"
#include "ui/layers/generic_box.h"
#include "styles/style_layers.h"
#ifndef TDESKTOP_DISABLE_SPELLCHECK
#include "chat_helpers/spellchecker_common.h"
#endif // TDESKTOP_DISABLE_SPELLCHECK
namespace Main {
namespace {
constexpr auto kTmpPasswordReserveTime = TimeId(10);
[[nodiscard]] QString ValidatedInternalLinksDomain(
not_null<const Session*> session) {
// This domain should start with 'http[s]://' and end with '/'.
// Like 'https://telegram.me/' or 'https://t.me/'.
const auto &domain = session->serverConfig().internalLinksDomain;
const auto prefixes = {
u"https://"_q,
u"http://"_q,
};
for (const auto &prefix : prefixes) {
if (domain.startsWith(prefix, Qt::CaseInsensitive)) {
return domain.endsWith('/')
? domain
: MTP::ConfigFields(
session->mtp().environment()
).internalLinksDomain;
}
}
return MTP::ConfigFields(
session->mtp().environment()
).internalLinksDomain;
}
} // namespace
Session::Session(
not_null<Account*> account,
const MTPUser &user,
std::unique_ptr<SessionSettings> settings)
: _userId(user.c_user().vid())
, _account(account)
, _settings(std::move(settings))
, _changes(std::make_unique<Data::Changes>(this))
, _api(std::make_unique<ApiWrap>(this))
, _updates(std::make_unique<Api::Updates>(this))
, _sendProgressManager(std::make_unique<Api::SendProgressManager>(this))
, _downloader(std::make_unique<Storage::DownloadManagerMtproto>(_api.get()))
, _uploader(std::make_unique<Storage::Uploader>(_api.get()))
, _storage(std::make_unique<Storage::Facade>())
, _data(std::make_unique<Data::Session>(this))
, _user(_data->processUser(user))
, _emojiStickersPack(std::make_unique<Stickers::EmojiPack>(this))
, _diceStickersPacks(std::make_unique<Stickers::DicePacks>(this))
, _giftBoxStickersPacks(std::make_unique<Stickers::GiftBoxPack>(this))
, _sendAsPeers(std::make_unique<SendAsPeers>(this))
, _attachWebView(std::make_unique<InlineBots::AttachWebView>(this))
, _recentPeers(std::make_unique<Data::RecentPeers>(this))
, _recentSharedGifts(std::make_unique<Data::RecentSharedMediaGifts>(this))
, _giftAuctions(std::make_unique<Data::GiftAuctions>(this))
, _scheduledMessages(std::make_unique<Data::ScheduledMessages>(this))
, _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this))
, _topPeers(std::make_unique<Data::TopPeers>(this, Data::TopPeerType::Chat))
, _topBotApps(
std::make_unique<Data::TopPeers>(this, Data::TopPeerType::BotApp))
, _factchecks(std::make_unique<Data::Factchecks>(this))
, _locationPickers(std::make_unique<Data::LocationPickers>())
, _credits(std::make_unique<Data::Credits>(this))
, _promoSuggestions(std::make_unique<Data::PromoSuggestions>(this, [=] {
using State = Data::SetupEmailState;
if (_promoSuggestions->setupEmailState() == State::Setup
|| _promoSuggestions->setupEmailState() == State::SetupNoSkip) {
if (_settings->setupEmailState() == State::Setup
|| _settings->setupEmailState() == State::SetupNoSkip) {
crl::on_main([=] {
// base::call_delayed(5000, [=] {
Core::App().lockBySetupEmail();
});
const auto unlockLifetime = std::make_shared<rpl::lifetime>();
_promoSuggestions->setupEmailStateValue(
) | rpl::filter([](Data::SetupEmailState s) {
return s == Data::SetupEmailState::None;
}) | rpl::take(1) | rpl::on_next(crl::guard(this, [=] {
Core::App().unlockSetupEmail();
_settings->setSetupEmailState(State::None);
saveSettingsDelayed(200);
unlockLifetime->destroy();
}), *unlockLifetime);
} else {
_settings->setSetupEmailState(
_promoSuggestions->setupEmailState());
saveSettingsDelayed(200);
}
} else {
if (_settings->setupEmailState() == State::Setup
|| _settings->setupEmailState() == State::SetupNoSkip) {
_settings->setSetupEmailState(State::None);
saveSettingsDelayed(200);
}
}
}))
, _passkeys(std::make_unique<Data::Passkeys>(this))
, _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>())
, _supportHelper(Support::Helper::Create(this))
, _fastButtonsBots(std::make_unique<Support::FastButtonsBots>(this))
, _saveSettingsTimer([=] { saveSettings(); }) {
Expects(_settings != nullptr);
_api->requestTermsUpdate();
_api->requestFullPeer(_user);
_api->instance().setUserPhone(_user->phone());
// Load current userpic and keep it loaded.
_user->loadUserpic();
changes().peerFlagsValue(
_user,
Data::PeerUpdate::Flag::Photo
) | rpl::on_next([=] {
auto view = Ui::PeerUserpicView{ .cloud = _selfUserpicView };
[[maybe_unused]] const auto image = _user->userpicCloudImage(view);
_selfUserpicView = view.cloud;
}, lifetime());
crl::on_main(this, [=] {
using Flag = Data::PeerUpdate::Flag;
changes().peerUpdates(
_user,
Flag::Name
| Flag::Username
| Flag::Photo
| Flag::About
| Flag::PhoneNumber
) | rpl::on_next([=](const Data::PeerUpdate &update) {
local().writeSelf();
if (update.flags & Flag::PhoneNumber) {
const auto phone = _user->phone();
_api->instance().setUserPhone(phone);
if (!phone.isEmpty()) {
_api->instance().requestConfig();
}
}
}, _lifetime);
if (_settings->hadLegacyCallsPeerToPeerNobody()) {
api().userPrivacy().save(
Api::UserPrivacy::Key::CallsPeer2Peer,
Api::UserPrivacy::Rule{
.option = Api::UserPrivacy::Option::Nobody
});
saveSettingsDelayed();
}
// Storage::Account uses Main::Account::session() in those methods.
// So they can't be called during Main::Session construction.
local().readInstalledStickers();
local().readInstalledMasks();
local().readInstalledCustomEmoji();
local().readFeaturedStickers();
local().readFeaturedCustomEmoji();
local().readRecentStickers();
local().readRecentMasks();
local().readFavedStickers();
local().readSavedGifs();
data().stickers().notifyUpdated(Data::StickersType::Stickers);
data().stickers().notifyUpdated(Data::StickersType::Masks);
data().stickers().notifyUpdated(Data::StickersType::Emoji);
data().stickers().notifySavedGifsUpdated();
DEBUG_LOG(("Init: Account stored data load finished."));
});
#ifndef TDESKTOP_DISABLE_SPELLCHECK
Spellchecker::Start(this);
#endif // TDESKTOP_DISABLE_SPELLCHECK
_api->requestNotifySettings(MTP_inputNotifyUsers());
_api->requestNotifySettings(MTP_inputNotifyChats());
_api->requestNotifySettings(MTP_inputNotifyBroadcasts());
Core::App().downloadManager().trackSession(this);
appConfig().value(
) | rpl::on_next([=] {
appConfigRefreshed();
}, _lifetime);
}
void Session::appConfigRefreshed() {
const auto &config = appConfig();
_frozen = FreezeInfo{
.since = config.get<int>(u"freeze_since_date"_q, 0),
.until = config.get<int>(u"freeze_until_date"_q, 0),
.appealUrl = config.get<QString>(u"freeze_appeal_url"_q, QString()),
};
#ifndef OS_MAC_STORE
_premiumPossible = !config.get<bool>(
u"premium_purchase_blocked"_q,
true);
#endif // OS_MAC_STORE
}
void Session::setTmpPassword(const QByteArray &password, TimeId validUntil) {
if (_tmpPassword.isEmpty() || validUntil > _tmpPasswordValidUntil) {
_tmpPassword = password;
_tmpPasswordValidUntil = validUntil;
}
}
QByteArray Session::validTmpPassword() const {
return (_tmpPasswordValidUntil
>= base::unixtime::now() + kTmpPasswordReserveTime)
? _tmpPassword
: QByteArray();
}
// Can be called only right before ~Session.
void Session::finishLogout() {
unlockTerms();
data().clear();
data().clearLocalStorage();
}
Session::~Session() {
unlockTerms();
data().clear();
ClickHandler::clearActive();
ClickHandler::unpressed();
}
Account &Session::account() const {
return *_account;
}
Storage::Account &Session::local() const {
return _account->local();
}
Domain &Session::domain() const {
return _account->domain();
}
Storage::Domain &Session::domainLocal() const {
return _account->domainLocal();
}
AppConfig &Session::appConfig() const {
return _account->appConfig();
}
void Session::notifyDownloaderTaskFinished() {
downloader().notifyTaskFinished();
}
rpl::producer<> Session::downloaderTaskFinished() const {
return downloader().taskFinished();
}
bool Session::premium() const {
return _user->isPremium();
}
bool Session::premiumPossible() const {
return premium() || premiumCanBuy();
}
bool Session::premiumBadgesShown() const {
return supportMode() || premiumPossible();
}
rpl::producer<bool> Session::premiumPossibleValue() const {
using namespace rpl::mappers;
auto premium = _user->flagsValue(
) | rpl::filter([=](UserData::Flags::Change change) {
return (change.diff & UserDataFlag::Premium);
}) | rpl::map([=] {
return _user->isPremium();
});
return rpl::combine(
std::move(premium),
_premiumPossible.value(),
_1 || _2);
}
bool Session::premiumCanBuy() const {
return _premiumPossible.current();
}
bool Session::isTestMode() const {
return mtp().isTestMode();
}
uint64 Session::uniqueId() const {
// See also Account::willHaveSessionUniqueId.
return userId().bare
| (isTestMode() ? 0x0100'0000'0000'0000ULL : 0ULL);
}
UserId Session::userId() const {
return _userId;
}
PeerId Session::userPeerId() const {
return _userId;
}
bool Session::validateSelf(UserId id) {
if (id != userId()) {
LOG(("Auth Error: wrong self user received."));
crl::on_main(this, [=] { _account->logOut(); });
return false;
}
return true;
}
void Session::saveSettings() {
local().writeSessionSettings();
}
void Session::saveSettingsDelayed(crl::time delay) {
_saveSettingsTimer.callOnce(delay);
}
void Session::saveSettingsNowIfNeeded() {
if (_saveSettingsTimer.isActive()) {
_saveSettingsTimer.cancel();
saveSettings();
}
}
MTP::DcId Session::mainDcId() const {
return _account->mtp().mainDcId();
}
MTP::Instance &Session::mtp() const {
return _account->mtp();
}
const MTP::ConfigFields &Session::serverConfig() const {
return _account->mtp().configValues();
}
void Session::lockByTerms(const Window::TermsLock &data) {
if (!_termsLock || *_termsLock != data) {
_termsLock = std::make_unique<Window::TermsLock>(data);
_termsLockChanges.fire(true);
}
}
void Session::unlockTerms() {
if (_termsLock) {
_termsLock = nullptr;
_termsLockChanges.fire(false);
}
}
void Session::termsDeleteNow() {
api().request(MTPaccount_DeleteAccount(
MTP_flags(0),
MTP_string("Decline ToS update"),
MTPInputCheckPasswordSRP()
)).send();
}
std::optional<Window::TermsLock> Session::termsLocked() const {
return _termsLock ? base::make_optional(*_termsLock) : std::nullopt;
}
rpl::producer<bool> Session::termsLockChanges() const {
return _termsLockChanges.events();
}
rpl::producer<bool> Session::termsLockValue() const {
return rpl::single(
_termsLock != nullptr
) | rpl::then(termsLockChanges());
}
QString Session::createInternalLink(const QString &query) const {
return createInternalLink(TextWithEntities{ .text = query }).text;
}
QString Session::createInternalLinkFull(const QString &query) const {
return createInternalLinkFull(TextWithEntities{ .text = query }).text;
}
TextWithEntities Session::createInternalLink(
const TextWithEntities &query) const {
const auto result = createInternalLinkFull(query);
const auto prefixes = {
u"https://"_q,
u"http://"_q,
};
for (auto &prefix : prefixes) {
if (result.text.startsWith(prefix, Qt::CaseInsensitive)) {
return Ui::Text::Mid(result, prefix.size());
}
}
LOG(("Warning: bad internal url '%1'").arg(result.text));
return result;
}
TextWithEntities Session::createInternalLinkFull(
TextWithEntities query) const {
return TextWithEntities::Simple(ValidatedInternalLinksDomain(this))
.append(std::move(query));
}
bool Session::supportMode() const {
return (_supportHelper != nullptr);
}
Support::Helper &Session::supportHelper() const {
Expects(supportMode());
return *_supportHelper;
}
Support::Templates& Session::supportTemplates() const {
return supportHelper().templates();
}
Support::FastButtonsBots &Session::fastButtonsBots() const {
return *_fastButtonsBots;
}
FreezeInfo Session::frozen() const {
return _frozen.current();
}
rpl::producer<FreezeInfo> Session::frozenValue() const {
return _frozen.value();
}
void Session::addWindow(not_null<Window::SessionController*> controller) {
_windows.emplace(controller);
controller->lifetime().add([=] {
_windows.remove(controller);
});
updates().addActiveChat(controller->activeChatChanges(
) | rpl::map([=](Dialogs::Key chat) {
return chat.peer();
}) | rpl::distinct_until_changed());
}
bool Session::uploadsInProgress() const {
return !!_uploader->currentUploadId();
}
void Session::uploadsStopWithConfirmation(Fn<void()> done) {
const auto id = _uploader->currentUploadId();
const auto message = data().message(id);
const auto exists = (message != nullptr);
const auto window = message
? Core::App().windowFor(message->history()->peer)
: Core::App().activePrimaryWindow();
if (!window) {
done();
return;
}
auto box = Box([=](not_null<Ui::GenericBox*> box) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_upload_sure_stop(),
st::boxLabel),
st::boxPadding + QMargins(0, 0, 0, st::boxPadding.bottom()));
box->setStyle(st::defaultBox);
box->addButton(tr::lng_selected_upload_stop(), [=] {
box->closeBox();
uploadsStop();
if (done) {
done();
}
}, st::attentionBoxButton);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
if (exists) {
box->addLeftButton(tr::lng_upload_show_file(), [=] {
box->closeBox();
if (const auto item = data().message(id)) {
if (const auto window = tryResolveWindow()) {
window->showMessage(item);
}
}
});
}
});
window->show(std::move(box));
window->activate();
}
void Session::uploadsStop() {
_uploader->cancelAll();
}
auto Session::windows() const
-> const base::flat_set<not_null<Window::SessionController*>> & {
return _windows;
}
Window::SessionController *Session::tryResolveWindow(
PeerData *forPeer) const {
if (forPeer) {
auto primary = (Window::SessionController*)nullptr;
for (const auto &window : _windows) {
const auto thread = window->windowId().thread;
if (thread && thread->peer() == forPeer) {
return window;
} else if (window->isPrimary()) {
primary = window;
}
}
if (primary) {
return primary;
}
}
if (_windows.empty() || forPeer) {
domain().activate(_account);
if (_windows.empty()) {
return nullptr;
}
}
for (const auto &window : _windows) {
if (window->isPrimary()) {
return window;
}
}
return _windows.front();
}
auto Session::colorIndicesValue()
-> rpl::producer<Ui::ColorIndicesCompressed> {
return api().peerColors().indicesValue();
}
} // namespace Main

View File

@@ -0,0 +1,338 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <rpl/filter.h>
#include <rpl/variable.h>
#include "base/timer.h"
class ApiWrap;
namespace Api {
class Updates;
class SendProgressManager;
} // namespace Api
namespace MTP {
class Instance;
struct ConfigFields;
} // namespace MTP
namespace Support {
class Helper;
class Templates;
class FastButtonsBots;
} // namespace Support
namespace Data {
class Session;
class Changes;
class GiftAuctions;
class RecentPeers;
class RecentSharedMediaGifts;
class ScheduledMessages;
class SponsoredMessages;
class TopPeers;
class Factchecks;
class LocationPickers;
class Credits;
class PromoSuggestions;
class Passkeys;
} // namespace Data
namespace HistoryView::Reactions {
class CachedIconFactory;
} // namespace HistoryView::Reactions
namespace Storage {
class DownloadManagerMtproto;
class Uploader;
class Facade;
class Account;
class Domain;
} // namespace Storage
namespace Window {
class SessionController;
struct TermsLock;
} // namespace Window
namespace Stickers {
class EmojiPack;
class DicePacks;
class GiftBoxPack;
} // namespace Stickers;
namespace InlineBots {
class AttachWebView;
} // namespace InlineBots
namespace Ui {
struct ColorIndicesCompressed;
} // namespace Ui
namespace Main {
class Account;
class AppConfig;
class Domain;
class SessionSettings;
class SendAsPeers;
struct FreezeInfo {
TimeId since = 0;
TimeId until = 0;
QString appealUrl;
explicit operator bool() const {
return since != 0;
}
friend inline bool operator==(
const FreezeInfo &,
const FreezeInfo &) = default;
};
class Session final : public base::has_weak_ptr {
public:
Session(
not_null<Account*> account,
const MTPUser &user,
std::unique_ptr<SessionSettings> settings);
~Session();
Session(const Session &other) = delete;
Session &operator=(const Session &other) = delete;
[[nodiscard]] Account &account() const;
[[nodiscard]] Storage::Account &local() const;
[[nodiscard]] Domain &domain() const;
[[nodiscard]] Storage::Domain &domainLocal() const;
[[nodiscard]] AppConfig &appConfig() const;
[[nodiscard]] bool premium() const;
[[nodiscard]] bool premiumPossible() const;
[[nodiscard]] rpl::producer<bool> premiumPossibleValue() const;
[[nodiscard]] bool premiumBadgesShown() const;
[[nodiscard]] bool premiumCanBuy() const;
[[nodiscard]] bool isTestMode() const;
[[nodiscard]] uint64 uniqueId() const; // userId() with TestDC shift.
[[nodiscard]] UserId userId() const;
[[nodiscard]] PeerId userPeerId() const;
[[nodiscard]] not_null<UserData*> user() const {
return _user;
}
bool validateSelf(UserId id);
[[nodiscard]] Data::Changes &changes() const {
return *_changes;
}
[[nodiscard]] Data::RecentPeers &recentPeers() const {
return *_recentPeers;
}
[[nodiscard]] Data::RecentSharedMediaGifts &recentSharedGifts() const {
return *_recentSharedGifts;
}
[[nodiscard]] Data::GiftAuctions &giftAuctions() const {
return *_giftAuctions;
}
[[nodiscard]] Data::SponsoredMessages &sponsoredMessages() const {
return *_sponsoredMessages;
}
[[nodiscard]] Data::ScheduledMessages &scheduledMessages() const {
return *_scheduledMessages;
}
[[nodiscard]] Data::TopPeers &topPeers() const {
return *_topPeers;
}
[[nodiscard]] Data::TopPeers &topBotApps() const {
return *_topBotApps;
}
[[nodiscard]] Data::Factchecks &factchecks() const {
return *_factchecks;
}
[[nodiscard]] Data::LocationPickers &locationPickers() const {
return *_locationPickers;
}
[[nodiscard]] Data::Credits &credits() const {
return *_credits;
}
[[nodiscard]] Api::Updates &updates() const {
return *_updates;
}
[[nodiscard]] Api::SendProgressManager &sendProgressManager() const {
return *_sendProgressManager;
}
[[nodiscard]] Storage::DownloadManagerMtproto &downloader() const {
return *_downloader;
}
[[nodiscard]] Storage::Uploader &uploader() const {
return *_uploader;
}
[[nodiscard]] Storage::Facade &storage() const {
return *_storage;
}
[[nodiscard]] Stickers::EmojiPack &emojiStickersPack() const {
return *_emojiStickersPack;
}
[[nodiscard]] Stickers::DicePacks &diceStickersPacks() const {
return *_diceStickersPacks;
}
[[nodiscard]] Stickers::GiftBoxPack &giftBoxStickersPacks() const {
return *_giftBoxStickersPacks;
}
[[nodiscard]] Data::Session &data() const {
return *_data;
}
[[nodiscard]] SessionSettings &settings() const {
return *_settings;
}
[[nodiscard]] SendAsPeers &sendAsPeers() const {
return *_sendAsPeers;
}
[[nodiscard]] InlineBots::AttachWebView &attachWebView() const {
return *_attachWebView;
}
[[nodiscard]] Data::PromoSuggestions &promoSuggestions() const {
return *_promoSuggestions;
}
[[nodiscard]] Data::Passkeys &passkeys() const {
return *_passkeys;
}
[[nodiscard]] auto cachedReactionIconFactory() const
-> HistoryView::Reactions::CachedIconFactory & {
return *_cachedReactionIconFactory;
}
void saveSettings();
void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay);
void saveSettingsNowIfNeeded();
void addWindow(not_null<Window::SessionController*> controller);
[[nodiscard]] auto windows() const
-> const base::flat_set<not_null<Window::SessionController*>> &;
[[nodiscard]] Window::SessionController *tryResolveWindow(
PeerData *forPeer = nullptr) const;
// Shortcuts.
void notifyDownloaderTaskFinished();
[[nodiscard]] rpl::producer<> downloaderTaskFinished() const;
[[nodiscard]] MTP::DcId mainDcId() const;
[[nodiscard]] MTP::Instance &mtp() const;
[[nodiscard]] const MTP::ConfigFields &serverConfig() const;
[[nodiscard]] ApiWrap &api() {
return *_api;
}
// Terms lock.
void lockByTerms(const Window::TermsLock &data);
void unlockTerms();
void termsDeleteNow();
[[nodiscard]] std::optional<Window::TermsLock> termsLocked() const;
rpl::producer<bool> termsLockChanges() const;
rpl::producer<bool> termsLockValue() const;
[[nodiscard]] QString createInternalLink(const QString &query) const;
[[nodiscard]] QString createInternalLinkFull(const QString &query) const;
[[nodiscard]] TextWithEntities createInternalLink(
const TextWithEntities &query) const;
[[nodiscard]] TextWithEntities createInternalLinkFull(
TextWithEntities query) const;
void setTmpPassword(const QByteArray &password, TimeId validUntil);
[[nodiscard]] QByteArray validTmpPassword() const;
// Can be called only right before ~Session.
void finishLogout();
// Uploads cancel with confirmation.
[[nodiscard]] bool uploadsInProgress() const;
void uploadsStopWithConfirmation(Fn<void()> done);
void uploadsStop();
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
[[nodiscard]] bool supportMode() const;
[[nodiscard]] Support::Helper &supportHelper() const;
[[nodiscard]] Support::Templates &supportTemplates() const;
[[nodiscard]] Support::FastButtonsBots &fastButtonsBots() const;
[[nodiscard]] FreezeInfo frozen() const;
[[nodiscard]] rpl::producer<FreezeInfo> frozenValue() const;
[[nodiscard]] auto colorIndicesValue()
-> rpl::producer<Ui::ColorIndicesCompressed>;
private:
static constexpr auto kDefaultSaveDelay = crl::time(1000);
void appConfigRefreshed();
const UserId _userId;
const not_null<Account*> _account;
const std::unique_ptr<SessionSettings> _settings;
const std::unique_ptr<Data::Changes> _changes;
const std::unique_ptr<ApiWrap> _api;
const std::unique_ptr<Api::Updates> _updates;
const std::unique_ptr<Api::SendProgressManager> _sendProgressManager;
const std::unique_ptr<Storage::DownloadManagerMtproto> _downloader;
const std::unique_ptr<Storage::Uploader> _uploader;
const std::unique_ptr<Storage::Facade> _storage;
// _data depends on _downloader / _uploader.
const std::unique_ptr<Data::Session> _data;
const not_null<UserData*> _user;
// _emojiStickersPack depends on _data.
const std::unique_ptr<Stickers::EmojiPack> _emojiStickersPack;
const std::unique_ptr<Stickers::DicePacks> _diceStickersPacks;
const std::unique_ptr<Stickers::GiftBoxPack> _giftBoxStickersPacks;
const std::unique_ptr<SendAsPeers> _sendAsPeers;
const std::unique_ptr<InlineBots::AttachWebView> _attachWebView;
const std::unique_ptr<Data::RecentPeers> _recentPeers;
const std::unique_ptr<Data::RecentSharedMediaGifts> _recentSharedGifts;
const std::unique_ptr<Data::GiftAuctions> _giftAuctions;
const std::unique_ptr<Data::ScheduledMessages> _scheduledMessages;
const std::unique_ptr<Data::SponsoredMessages> _sponsoredMessages;
const std::unique_ptr<Data::TopPeers> _topPeers;
const std::unique_ptr<Data::TopPeers> _topBotApps;
const std::unique_ptr<Data::Factchecks> _factchecks;
const std::unique_ptr<Data::LocationPickers> _locationPickers;
const std::unique_ptr<Data::Credits> _credits;
const std::unique_ptr<Data::PromoSuggestions> _promoSuggestions;
const std::unique_ptr<Data::Passkeys> _passkeys;
using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory;
const std::unique_ptr<ReactionIconFactory> _cachedReactionIconFactory;
const std::unique_ptr<Support::Helper> _supportHelper;
const std::unique_ptr<Support::FastButtonsBots> _fastButtonsBots;
std::shared_ptr<QImage> _selfUserpicView;
rpl::variable<bool> _premiumPossible = false;
rpl::event_stream<bool> _termsLockChanges;
std::unique_ptr<Window::TermsLock> _termsLock;
base::flat_set<not_null<Window::SessionController*>> _windows;
base::Timer _saveSettingsTimer;
rpl::variable<FreezeInfo> _frozen;
QByteArray _tmpPassword;
TimeId _tmpPasswordValidUntil = 0;
rpl::lifetime _lifetime;
};
} // namespace Main

View File

@@ -0,0 +1,936 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "main/main_session_settings.h"
#include "chat_helpers/tabbed_selector.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/chat/attach/attach_send_files_way.h"
#include "window/section_widget.h"
#include "support/support_common.h"
#include "storage/serialize_common.h"
#include "boxes/send_files_box.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/components/promo_suggestions.h"
namespace Main {
namespace {
constexpr auto kLegacyCallsPeerToPeerNobody = 4;
constexpr auto kVersionTag = -1;
constexpr auto kVersion = 2;
} // namespace
SessionSettings::SessionSettings()
: _selectorTab(ChatHelpers::SelectorTab::Emoji)
, _supportSwitch(Support::SwitchSettings::Next)
, _setupEmailState(Data::SetupEmailState::None) {
}
QByteArray SessionSettings::serialize() const {
const auto autoDownload = _autoDownload.serialize();
auto size = sizeof(qint32) * 4
+ _groupStickersSectionHidden.size() * sizeof(quint64)
+ sizeof(qint32) * 4
+ Serialize::bytearraySize(autoDownload)
+ sizeof(qint32) * 11
+ (_mutePeriods.size() * sizeof(quint64))
+ sizeof(qint32) * 3
+ _groupEmojiSectionHidden.size() * sizeof(quint64)
+ sizeof(qint32) * 3
+ _hiddenPinnedMessages.size() * (sizeof(quint64) * 4)
+ sizeof(qint32)
+ _verticalSubsectionTabs.size() * sizeof(quint64)
+ sizeof(qint32) // _ringtoneDefaultVolumes size
+ (_ringtoneDefaultVolumes.size()
* (0
+ sizeof(uint8_t) * 1 // Data::DefaultNotify
+ sizeof(ushort))) // Volume
+ sizeof(qint32) // _ringtoneVolumes size
+ (_ringtoneVolumes.size()
* (0
+ sizeof(quint64) * 3 // ThreadId
+ sizeof(ushort))) // Volume
+ sizeof(qint32) // _ratedTranscriptions size
+ (_ratedTranscriptions.size() * sizeof(quint64))
+ sizeof(qint32); // _unreviewed size
for (const auto &auth : _unreviewed) {
size += sizeof(quint64) + sizeof(qint32) + sizeof(qint32)
+ Serialize::stringSize(auth.device)
+ Serialize::stringSize(auth.location);
}
size += sizeof(qint32); // _setupEmailState
auto result = QByteArray();
result.reserve(size);
{
QDataStream stream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_1);
stream
<< qint32(kVersionTag) << qint32(kVersion)
<< static_cast<qint32>(_selectorTab)
<< qint32(_groupStickersSectionHidden.size());
for (const auto &peerId : _groupStickersSectionHidden) {
stream << SerializePeerId(peerId);
}
stream
<< qint32(_supportSwitch)
<< qint32(_supportFixChatsOrder ? 1 : 0)
<< qint32(_supportTemplatesAutocomplete ? 1 : 0)
<< qint32(_supportChatsTimeSlice.current())
<< autoDownload
<< qint32(_supportAllSearchResults.current() ? 1 : 0)
<< qint32(_archiveCollapsed.current() ? 1 : 0)
<< qint32(_archiveInMainMenu.current() ? 1 : 0)
<< qint32(_skipArchiveInSearch.current() ? 1 : 0)
<< qint32(0) // old _mediaLastPlaybackPosition.size());
<< qint32(0) // very very old _hiddenPinnedMessages.size());
<< qint32(_dialogsFiltersEnabled ? 1 : 0)
<< qint32(_supportAllSilent ? 1 : 0)
<< qint32(_photoEditorHintShowsCount)
<< qint32(0) // very old _hiddenPinnedMessages.size());
<< qint32(_mutePeriods.size());
for (const auto &period : _mutePeriods) {
stream << quint64(period);
}
stream
<< qint32(0) // old _skipPremiumStickersSet
<< qint32(0) // old _hiddenPinnedMessages.size());
<< qint32(_groupEmojiSectionHidden.size());
for (const auto &peerId : _groupEmojiSectionHidden) {
stream << SerializePeerId(peerId);
}
stream
<< qint32(_lastNonPremiumLimitDownload)
<< qint32(_lastNonPremiumLimitUpload)
<< qint32(_hiddenPinnedMessages.size());
for (const auto &[key, value] : _hiddenPinnedMessages) {
stream
<< SerializePeerId(key.peerId)
<< qint64(key.topicRootId.bare)
<< SerializePeerId(key.monoforumPeerId)
<< qint64(value.bare);
}
stream << qint32(_verticalSubsectionTabs.size());
for (const auto &peerId : _verticalSubsectionTabs) {
stream << SerializePeerId(peerId);
}
stream << qint32(_ringtoneDefaultVolumes.size());
for (const auto &[key, value] : _ringtoneDefaultVolumes) {
stream << uint8_t(key) << ushort(value);
}
stream << qint32(_ringtoneVolumes.size());
for (const auto &[key, value] : _ringtoneVolumes) {
stream
<< SerializePeerId(key.peerId)
<< qint64(key.topicRootId.bare)
<< SerializePeerId(key.monoforumPeerId)
<< ushort(value);
}
stream << qint32(_ratedTranscriptions.size());
for (const auto &transcriptionId : _ratedTranscriptions) {
stream << quint64(transcriptionId);
}
stream << qint32(_unreviewed.size());
for (const auto &auth : _unreviewed) {
stream
<< quint64(auth.hash)
<< qint32(auth.unconfirmed ? 1 : 0)
<< qint32(auth.date)
<< auth.device
<< auth.location;
}
stream << qint32(static_cast<int>(_setupEmailState));
}
Ensures(result.size() == size);
return result;
}
void SessionSettings::addFromSerialized(const QByteArray &serialized) {
if (serialized.isEmpty()) {
return;
}
auto &app = Core::App().settings();
QDataStream stream(serialized);
stream.setVersion(QDataStream::Qt_5_1);
qint32 versionTag = 0;
qint32 version = 0;
qint32 selectorTab = static_cast<qint32>(ChatHelpers::SelectorTab::Emoji);
qint32 appLastSeenWarningSeen = app.lastSeenWarningSeen() ? 1 : 0;
qint32 appTabbedSelectorSectionEnabled = 1;
qint32 legacyTabbedSelectorSectionTooltipShown = 0;
qint32 appFloatPlayerColumn = static_cast<qint32>(Window::Column::Second);
qint32 appFloatPlayerCorner = static_cast<qint32>(RectPart::TopRight);
base::flat_map<QString, QString> appSoundOverrides;
base::flat_set<PeerId> groupStickersSectionHidden;
base::flat_set<PeerId> groupEmojiSectionHidden;
qint32 appThirdSectionInfoEnabled = 0;
qint32 legacySmallDialogsList = 0;
float64 appDialogsWidthRatio = app.dialogsWidthRatio(false);
int appThirdColumnWidth = app.thirdColumnWidth();
int appThirdSectionExtendedBy = app.thirdSectionExtendedBy();
qint32 appSendFilesWay = app.sendFilesWay().serialize();
qint32 legacyCallsPeerToPeer = qint32(0);
qint32 appSendSubmitWay = static_cast<qint32>(app.sendSubmitWay());
qint32 supportSwitch = static_cast<qint32>(_supportSwitch);
qint32 supportFixChatsOrder = _supportFixChatsOrder ? 1 : 0;
qint32 supportTemplatesAutocomplete = _supportTemplatesAutocomplete ? 1 : 0;
qint32 supportChatsTimeSlice = _supportChatsTimeSlice.current();
qint32 appIncludeMutedCounter = app.includeMutedCounter() ? 1 : 0;
qint32 appCountUnreadMessages = app.countUnreadMessages() ? 1 : 0;
qint32 legacyAppExeLaunchWarning = 1;
QByteArray autoDownload;
qint32 supportAllSearchResults = _supportAllSearchResults.current() ? 1 : 0;
qint32 archiveCollapsed = _archiveCollapsed.current() ? 1 : 0;
qint32 appNotifyAboutPinned = app.notifyAboutPinned() ? 1 : 0;
qint32 archiveInMainMenu = _archiveInMainMenu.current() ? 1 : 0;
qint32 skipArchiveInSearch = _skipArchiveInSearch.current() ? 1 : 0;
qint32 legacyAutoplayGifs = 1;
qint32 appLoopAnimatedStickers = app.loopAnimatedStickers() ? 1 : 0;
qint32 appLargeEmoji = app.largeEmoji() ? 1 : 0;
qint32 appReplaceEmoji = app.replaceEmoji() ? 1 : 0;
qint32 appSuggestEmoji = app.suggestEmoji() ? 1 : 0;
qint32 appSuggestStickersByEmoji = app.suggestStickersByEmoji() ? 1 : 0;
qint32 appSpellcheckerEnabled = app.spellcheckerEnabled() ? 1 : 0;
qint32 appVideoPlaybackSpeed = app.videoPlaybackSpeedSerialized();
QByteArray appVideoPipGeometry = app.videoPipGeometry();
std::vector<int> appDictionariesEnabled;
qint32 appAutoDownloadDictionaries = app.autoDownloadDictionaries() ? 1 : 0;
base::flat_map<ThreadId, MsgId> hiddenPinnedMessages;
base::flat_set<PeerId> verticalSubsectionTabs;
qint32 dialogsFiltersEnabled = _dialogsFiltersEnabled ? 1 : 0;
qint32 supportAllSilent = _supportAllSilent ? 1 : 0;
qint32 photoEditorHintShowsCount = _photoEditorHintShowsCount;
std::vector<TimeId> mutePeriods;
qint32 legacySkipPremiumStickersSet = 0;
qint32 lastNonPremiumLimitDownload = 0;
qint32 lastNonPremiumLimitUpload = 0;
base::flat_map<Data::DefaultNotify, ushort> ringtoneDefaultVolumes;
base::flat_map<ThreadId, ushort> ringtoneVolumes;
base::flat_set<uint64> ratedTranscriptions;
std::vector<Data::UnreviewedAuth> unreviewed;
qint32 setupEmailState = 0;
stream >> versionTag;
if (versionTag == kVersionTag) {
stream >> version;
stream >> selectorTab;
} else {
selectorTab = versionTag;
}
if (version < 2) {
stream >> appLastSeenWarningSeen;
if (!stream.atEnd()) {
stream >> appTabbedSelectorSectionEnabled;
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
QString key, value;
stream >> key >> value;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
appSoundOverrides.emplace(key, value);
}
}
}
if (!stream.atEnd()) {
stream >> legacyTabbedSelectorSectionTooltipShown;
}
if (!stream.atEnd()) {
stream >> appFloatPlayerColumn >> appFloatPlayerCorner;
}
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
quint64 peerId;
stream >> peerId;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
groupStickersSectionHidden.emplace(
DeserializePeerId(peerId));
}
}
}
if (version < 2) {
if (!stream.atEnd()) {
stream >> appThirdSectionInfoEnabled;
stream >> legacySmallDialogsList;
}
if (!stream.atEnd()) {
qint32 value = 0;
stream >> value;
appDialogsWidthRatio = std::clamp(value / 1000000., 0., 1.);
stream >> value;
appThirdColumnWidth = value;
stream >> value;
appThirdSectionExtendedBy = value;
}
if (!stream.atEnd()) {
stream >> appSendFilesWay;
}
if (!stream.atEnd()) {
stream >> legacyCallsPeerToPeer;
}
}
if (!stream.atEnd()) {
if (version < 2) {
stream >> appSendSubmitWay;
}
stream >> supportSwitch;
stream >> supportFixChatsOrder;
}
if (!stream.atEnd()) {
stream >> supportTemplatesAutocomplete;
}
if (!stream.atEnd()) {
stream >> supportChatsTimeSlice;
}
if (version < 2) {
if (!stream.atEnd()) {
stream >> appIncludeMutedCounter;
stream >> appCountUnreadMessages;
}
if (!stream.atEnd()) {
stream >> legacyAppExeLaunchWarning;
}
}
if (!stream.atEnd()) {
stream >> autoDownload;
}
if (!stream.atEnd()) {
stream >> supportAllSearchResults;
}
if (!stream.atEnd()) {
stream >> archiveCollapsed;
}
if (version < 2) {
if (!stream.atEnd()) {
stream >> appNotifyAboutPinned;
}
}
if (!stream.atEnd()) {
stream >> archiveInMainMenu;
}
if (!stream.atEnd()) {
stream >> skipArchiveInSearch;
}
if (version < 2) {
if (!stream.atEnd()) {
stream >> legacyAutoplayGifs;
stream >> appLoopAnimatedStickers;
stream >> appLargeEmoji;
stream >> appReplaceEmoji;
stream >> appSuggestEmoji;
stream >> appSuggestStickersByEmoji;
}
if (!stream.atEnd()) {
stream >> appSpellcheckerEnabled;
}
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
quint64 documentId;
qint64 time;
stream >> documentId >> time;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
// Old mediaLastPlaybackPosition.
}
}
}
if (version < 2) {
if (!stream.atEnd()) {
stream >> appVideoPlaybackSpeed;
}
if (!stream.atEnd()) {
stream >> appVideoPipGeometry;
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
qint64 langId;
stream >> langId;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
appDictionariesEnabled.emplace_back(langId);
}
}
}
if (!stream.atEnd()) {
stream >> appAutoDownloadDictionaries;
}
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
// Legacy.
for (auto i = 0; i != count; ++i) {
auto key = quint64();
auto value = qint32();
stream >> key >> value;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
hiddenPinnedMessages.emplace(
ThreadId{ DeserializePeerId(key), MsgId(0) },
value);
}
}
}
if (!stream.atEnd()) {
stream >> dialogsFiltersEnabled;
}
if (!stream.atEnd()) {
stream >> supportAllSilent;
}
if (!stream.atEnd()) {
stream >> photoEditorHintShowsCount;
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
auto key = quint64();
auto value = qint64();
stream >> key >> value;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
hiddenPinnedMessages.emplace(
ThreadId{ DeserializePeerId(key), MsgId(0) },
value);
}
}
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
auto period = quint64();
stream >> period;
mutePeriods.emplace_back(period);
}
}
}
if (!stream.atEnd()) {
stream >> legacySkipPremiumStickersSet;
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
// Legacy.
for (auto i = 0; i != count; ++i) {
auto keyPeerId = quint64();
auto keyTopicRootId = qint64();
auto value = qint64();
stream >> keyPeerId >> keyTopicRootId >> value;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
hiddenPinnedMessages.emplace(
ThreadId{ DeserializePeerId(keyPeerId), keyTopicRootId },
value);
}
}
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
quint64 peerId;
stream >> peerId;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
groupEmojiSectionHidden.emplace(DeserializePeerId(peerId));
}
}
}
if (!stream.atEnd()) {
stream
>> lastNonPremiumLimitDownload
>> lastNonPremiumLimitUpload;
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
auto keyPeerId = quint64();
auto keyTopicRootId = qint64();
auto keyMonoforumPeerId = quint64();
auto value = qint64();
stream
>> keyPeerId
>> keyTopicRootId
>> keyMonoforumPeerId
>> value;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
hiddenPinnedMessages.emplace(ThreadId{
DeserializePeerId(keyPeerId),
keyTopicRootId,
DeserializePeerId(keyMonoforumPeerId),
}, value);
}
}
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
auto peerId = quint64();
stream >> peerId;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
verticalSubsectionTabs.emplace(DeserializePeerId(peerId));
}
}
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
auto keyDefaultNotify = uint8_t();
auto value = ushort();
stream
>> keyDefaultNotify
>> value;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
ringtoneDefaultVolumes.emplace(
static_cast<Data::DefaultNotify>(keyDefaultNotify),
value);
}
}
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
auto keyPeerId = quint64();
auto keyTopicRootId = qint64();
auto keyMonoforumPeerId = quint64();
auto value = ushort();
stream
>> keyPeerId
>> keyTopicRootId
>> keyMonoforumPeerId
>> value;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
ringtoneVolumes.emplace(ThreadId{
DeserializePeerId(keyPeerId),
keyTopicRootId,
DeserializePeerId(keyMonoforumPeerId),
}, value);
}
}
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
auto transcriptionId = quint64();
stream >> transcriptionId;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"
"with ratedTranscriptions"));
return;
}
ratedTranscriptions.emplace(transcriptionId);
}
}
}
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
auto hash = quint64();
auto unconfirmed = qint32();
auto date = qint32();
QString device, location;
stream >> hash >> unconfirmed >> date >> device >> location;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"
"with unreviewed"));
return;
}
unreviewed.emplace_back(Data::UnreviewedAuth{
.hash = hash,
.unconfirmed = (unconfirmed == 1),
.date = TimeId(date),
.device = device,
.location = location,
});
}
}
}
if (!stream.atEnd()) {
stream >> setupEmailState;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
if (!autoDownload.isEmpty()
&& !_autoDownload.setFromSerialized(autoDownload)) {
return;
}
if (!version) {
if (!legacyAutoplayGifs) {
using namespace Data::AutoDownload;
_autoDownload = WithDisabledAutoPlay(_autoDownload);
}
}
auto uncheckedTab = static_cast<ChatHelpers::SelectorTab>(selectorTab);
switch (uncheckedTab) {
case ChatHelpers::SelectorTab::Emoji:
case ChatHelpers::SelectorTab::Stickers:
case ChatHelpers::SelectorTab::Gifs: _selectorTab = uncheckedTab; break;
}
_groupStickersSectionHidden = std::move(groupStickersSectionHidden);
_groupEmojiSectionHidden = std::move(groupEmojiSectionHidden);
auto uncheckedSupportSwitch = static_cast<Support::SwitchSettings>(
supportSwitch);
switch (uncheckedSupportSwitch) {
case Support::SwitchSettings::None:
case Support::SwitchSettings::Next:
case Support::SwitchSettings::Previous: _supportSwitch = uncheckedSupportSwitch; break;
}
_supportFixChatsOrder = (supportFixChatsOrder == 1);
_supportTemplatesAutocomplete = (supportTemplatesAutocomplete == 1);
_supportChatsTimeSlice = supportChatsTimeSlice;
_hadLegacyCallsPeerToPeerNobody = (legacyCallsPeerToPeer == kLegacyCallsPeerToPeerNobody);
_supportAllSearchResults = (supportAllSearchResults == 1);
_archiveCollapsed = (archiveCollapsed == 1);
_archiveInMainMenu = (archiveInMainMenu == 1);
_skipArchiveInSearch = (skipArchiveInSearch == 1);
_hiddenPinnedMessages = std::move(hiddenPinnedMessages);
_dialogsFiltersEnabled = (dialogsFiltersEnabled == 1);
_supportAllSilent = (supportAllSilent == 1);
_photoEditorHintShowsCount = std::move(photoEditorHintShowsCount);
_mutePeriods = std::move(mutePeriods);
_lastNonPremiumLimitDownload = lastNonPremiumLimitDownload;
_lastNonPremiumLimitUpload = lastNonPremiumLimitUpload;
_verticalSubsectionTabs = std::move(verticalSubsectionTabs);
_ringtoneDefaultVolumes = std::move(ringtoneDefaultVolumes);
_ringtoneVolumes = std::move(ringtoneVolumes);
_ratedTranscriptions = std::move(ratedTranscriptions);
_unreviewed = std::move(unreviewed);
auto uncheckedSetupEmailState = static_cast<Data::SetupEmailState>(
setupEmailState);
switch (uncheckedSetupEmailState) {
case Data::SetupEmailState::None:
case Data::SetupEmailState::Setup:
case Data::SetupEmailState::SetupNoSkip:
case Data::SetupEmailState::SettingUp:
case Data::SetupEmailState::SettingUpNoSkip:
_setupEmailState = uncheckedSetupEmailState;
break;
}
if (version < 2) {
app.setLastSeenWarningSeen(appLastSeenWarningSeen == 1);
for (const auto &[key, value] : appSoundOverrides) {
app.setSoundOverride(key, value);
}
if (const auto sendFilesWay = Ui::SendFilesWay::FromSerialized(appSendFilesWay)) {
app.setSendFilesWay(*sendFilesWay);
}
auto uncheckedSendSubmitWay = static_cast<Ui::InputSubmitSettings>(
appSendSubmitWay);
switch (uncheckedSendSubmitWay) {
case Ui::InputSubmitSettings::Enter:
case Ui::InputSubmitSettings::CtrlEnter: app.setSendSubmitWay(uncheckedSendSubmitWay); break;
}
app.setIncludeMutedCounter(appIncludeMutedCounter == 1);
app.setCountUnreadMessages(appCountUnreadMessages == 1);
app.setNotifyAboutPinned(appNotifyAboutPinned == 1);
app.setLoopAnimatedStickers(appLoopAnimatedStickers == 1);
app.setLargeEmoji(appLargeEmoji == 1);
app.setReplaceEmoji(appReplaceEmoji == 1);
app.setSuggestEmoji(appSuggestEmoji == 1);
app.setSuggestStickersByEmoji(appSuggestStickersByEmoji == 1);
app.setSpellcheckerEnabled(appSpellcheckerEnabled == 1);
app.setVideoPlaybackSpeedSerialized(appVideoPlaybackSpeed);
app.setVideoPipGeometry(appVideoPipGeometry);
app.setDictionariesEnabled(std::move(appDictionariesEnabled));
app.setAutoDownloadDictionaries(appAutoDownloadDictionaries == 1);
app.setTabbedSelectorSectionEnabled(appTabbedSelectorSectionEnabled == 1);
auto uncheckedColumn = static_cast<Window::Column>(appFloatPlayerColumn);
switch (uncheckedColumn) {
case Window::Column::First:
case Window::Column::Second:
case Window::Column::Third: app.setFloatPlayerColumn(uncheckedColumn); break;
}
auto uncheckedCorner = static_cast<RectPart>(appFloatPlayerCorner);
switch (uncheckedCorner) {
case RectPart::TopLeft:
case RectPart::TopRight:
case RectPart::BottomLeft:
case RectPart::BottomRight: app.setFloatPlayerCorner(uncheckedCorner); break;
}
app.setThirdSectionInfoEnabled(appThirdSectionInfoEnabled);
app.updateDialogsWidthRatio(appDialogsWidthRatio, false);
app.setThirdColumnWidth(appThirdColumnWidth);
app.setThirdSectionExtendedBy(appThirdSectionExtendedBy);
}
}
void SessionSettings::setSupportChatsTimeSlice(int slice) {
_supportChatsTimeSlice = slice;
}
int SessionSettings::supportChatsTimeSlice() const {
return _supportChatsTimeSlice.current();
}
rpl::producer<int> SessionSettings::supportChatsTimeSliceValue() const {
return _supportChatsTimeSlice.value();
}
void SessionSettings::setSupportAllSearchResults(bool all) {
_supportAllSearchResults = all;
}
bool SessionSettings::supportAllSearchResults() const {
return _supportAllSearchResults.current();
}
rpl::producer<bool> SessionSettings::supportAllSearchResultsValue() const {
return _supportAllSearchResults.value();
}
void SessionSettings::setArchiveCollapsed(bool collapsed) {
_archiveCollapsed = collapsed;
}
bool SessionSettings::archiveCollapsed() const {
return _archiveCollapsed.current();
}
rpl::producer<bool> SessionSettings::archiveCollapsedChanges() const {
return _archiveCollapsed.changes();
}
void SessionSettings::setArchiveInMainMenu(bool inMainMenu) {
_archiveInMainMenu = inMainMenu;
}
bool SessionSettings::archiveInMainMenu() const {
return _archiveInMainMenu.current();
}
rpl::producer<bool> SessionSettings::archiveInMainMenuChanges() const {
return _archiveInMainMenu.changes();
}
void SessionSettings::setSkipArchiveInSearch(bool skip) {
_skipArchiveInSearch = skip;
}
bool SessionSettings::skipArchiveInSearch() const {
return _skipArchiveInSearch.current();
}
rpl::producer<bool> SessionSettings::skipArchiveInSearchChanges() const {
return _skipArchiveInSearch.changes();
}
MsgId SessionSettings::hiddenPinnedMessageId(
PeerId peerId,
MsgId topicRootId,
PeerId monoforumPeerId) const {
const auto i = _hiddenPinnedMessages.find({
peerId,
topicRootId,
monoforumPeerId,
});
return (i != end(_hiddenPinnedMessages)) ? i->second : 0;
}
void SessionSettings::setHiddenPinnedMessageId(
PeerId peerId,
MsgId topicRootId,
PeerId monoforumPeerId,
MsgId msgId) {
const auto id = ThreadId{ peerId, topicRootId, monoforumPeerId };
if (msgId) {
_hiddenPinnedMessages[id] = msgId;
} else {
_hiddenPinnedMessages.remove(id);
}
}
bool SessionSettings::verticalSubsectionTabs(PeerId peerId) const {
return _verticalSubsectionTabs.contains(peerId);
}
void SessionSettings::setVerticalSubsectionTabs(
PeerId peerId,
bool vertical) {
if (vertical) {
_verticalSubsectionTabs.emplace(peerId);
} else {
_verticalSubsectionTabs.remove(peerId);
}
}
bool SessionSettings::photoEditorHintShown() const {
return _photoEditorHintShowsCount < kPhotoEditorHintMaxShowsCount;
}
void SessionSettings::incrementPhotoEditorHintShown() {
if (photoEditorHintShown()) {
_photoEditorHintShowsCount++;
}
}
std::vector<TimeId> SessionSettings::mutePeriods() const {
return _mutePeriods;
}
void SessionSettings::addMutePeriod(TimeId period) {
if (_mutePeriods.empty()) {
_mutePeriods.push_back(period);
} else if (_mutePeriods.back() != period) {
if (_mutePeriods.back() < period) {
_mutePeriods = { _mutePeriods.back(), period };
} else {
_mutePeriods = { period, _mutePeriods.back() };
}
}
}
ushort SessionSettings::ringtoneVolume(
Data::DefaultNotify defaultNotify) const {
const auto i = _ringtoneDefaultVolumes.find(defaultNotify);
return (i != end(_ringtoneDefaultVolumes)) ? i->second : 0;
}
void SessionSettings::setRingtoneVolume(
Data::DefaultNotify defaultNotify,
ushort volume) {
if (volume > 0 && volume <= 100) {
_ringtoneDefaultVolumes[defaultNotify] = volume;
} else {
_ringtoneDefaultVolumes.remove(defaultNotify);
}
}
ushort SessionSettings::ringtoneVolume(
PeerId peerId,
MsgId topicRootId,
PeerId monoforumPeerId) const {
const auto i = _ringtoneVolumes.find(
ThreadId{ peerId, topicRootId, monoforumPeerId });
return (i != end(_ringtoneVolumes)) ? i->second : 0;
}
void SessionSettings::setRingtoneVolume(
PeerId peerId,
MsgId topicRootId,
PeerId monoforumPeerId,
ushort volume) {
const auto id = ThreadId{ peerId, topicRootId, monoforumPeerId };
if (volume > 0 && volume <= 100) {
_ringtoneVolumes[id] = volume;
} else {
_ringtoneVolumes.remove(id);
}
}
void SessionSettings::markTranscriptionAsRated(uint64 transcriptionId) {
_ratedTranscriptions.emplace(transcriptionId);
}
bool SessionSettings::isTranscriptionRated(uint64 transcriptionId) const {
return _ratedTranscriptions.contains(transcriptionId);
}
void SessionSettings::setUnreviewed(std::vector<Data::UnreviewedAuth> auths) {
_unreviewed = std::move(auths);
}
const std::vector<Data::UnreviewedAuth> &SessionSettings::unreviewed() const {
return _unreviewed;
}
void SessionSettings::setSetupEmailState(Data::SetupEmailState state) {
_setupEmailState = state;
}
Data::SetupEmailState SessionSettings::setupEmailState() const {
return _setupEmailState;
}
} // namespace Main

View File

@@ -0,0 +1,227 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_auto_download.h"
#include "data/notify/data_peer_notify_settings.h"
#include "data/data_authorization.h"
#include "ui/rect_part.h"
namespace Support {
enum class SwitchSettings;
} // namespace Support
namespace ChatHelpers {
enum class SelectorTab;
} // namespace ChatHelpers
namespace Data {
enum class SetupEmailState;
} // namespace Data
namespace Main {
class SessionSettings final {
public:
SessionSettings();
[[nodiscard]] QByteArray serialize() const;
void addFromSerialized(const QByteArray &serialized);
void setSupportSwitch(Support::SwitchSettings value) {
_supportSwitch = value;
}
[[nodiscard]] Support::SwitchSettings supportSwitch() const {
return _supportSwitch;
}
void setSupportFixChatsOrder(bool fix) {
_supportFixChatsOrder = fix;
}
[[nodiscard]] bool supportFixChatsOrder() const {
return _supportFixChatsOrder;
}
void setSupportTemplatesAutocomplete(bool enabled) {
_supportTemplatesAutocomplete = enabled;
}
[[nodiscard]] bool supportTemplatesAutocomplete() const {
return _supportTemplatesAutocomplete;
}
void setSupportChatsTimeSlice(int slice);
[[nodiscard]] int supportChatsTimeSlice() const;
[[nodiscard]] rpl::producer<int> supportChatsTimeSliceValue() const;
void setSupportAllSearchResults(bool all);
[[nodiscard]] bool supportAllSearchResults() const;
[[nodiscard]] rpl::producer<bool> supportAllSearchResultsValue() const;
void setSupportAllSilent(bool enabled) {
_supportAllSilent = enabled;
}
[[nodiscard]] bool supportAllSilent() const {
return _supportAllSilent;
}
[[nodiscard]] ChatHelpers::SelectorTab selectorTab() const {
return _selectorTab;
}
void setSelectorTab(ChatHelpers::SelectorTab tab) {
_selectorTab = tab;
}
void setGroupStickersSectionHidden(PeerId peerId) {
_groupStickersSectionHidden.insert(peerId);
}
[[nodiscard]] bool isGroupStickersSectionHidden(PeerId peerId) const {
return _groupStickersSectionHidden.contains(peerId);
}
void removeGroupStickersSectionHidden(PeerId peerId) {
_groupStickersSectionHidden.remove(peerId);
}
void setGroupEmojiSectionHidden(PeerId peerId) {
_groupEmojiSectionHidden.insert(peerId);
}
[[nodiscard]] bool isGroupEmojiSectionHidden(PeerId peerId) const {
return _groupEmojiSectionHidden.contains(peerId);
}
void removeGroupEmojiSectionHidden(PeerId peerId) {
_groupEmojiSectionHidden.remove(peerId);
}
[[nodiscard]] Data::AutoDownload::Full &autoDownload() {
return _autoDownload;
}
[[nodiscard]] const Data::AutoDownload::Full &autoDownload() const {
return _autoDownload;
}
void setArchiveCollapsed(bool collapsed);
[[nodiscard]] bool archiveCollapsed() const;
[[nodiscard]] rpl::producer<bool> archiveCollapsedChanges() const;
void setArchiveInMainMenu(bool inMainMenu);
[[nodiscard]] bool archiveInMainMenu() const;
[[nodiscard]] rpl::producer<bool> archiveInMainMenuChanges() const;
void setSkipArchiveInSearch(bool skip);
[[nodiscard]] bool skipArchiveInSearch() const;
[[nodiscard]] rpl::producer<bool> skipArchiveInSearchChanges() const;
[[nodiscard]] bool hadLegacyCallsPeerToPeerNobody() const {
return _hadLegacyCallsPeerToPeerNobody;
}
[[nodiscard]] MsgId hiddenPinnedMessageId(
PeerId peerId,
MsgId topicRootId = 0,
PeerId monoforumPeerId = 0) const;
void setHiddenPinnedMessageId(
PeerId peerId,
MsgId topicRootId,
PeerId monoforumPeerId,
MsgId msgId);
[[nodiscard]] bool verticalSubsectionTabs(PeerId peerId) const;
void setVerticalSubsectionTabs(PeerId peerId, bool vertical);
[[nodiscard]] bool dialogsFiltersEnabled() const {
return _dialogsFiltersEnabled;
}
void setDialogsFiltersEnabled(bool value) {
_dialogsFiltersEnabled = value;
}
[[nodiscard]] bool photoEditorHintShown() const;
void incrementPhotoEditorHintShown();
[[nodiscard]] std::vector<TimeId> mutePeriods() const;
void addMutePeriod(TimeId period);
[[nodiscard]] TimeId lastNonPremiumLimitDownload() const {
return _lastNonPremiumLimitDownload;
}
[[nodiscard]] TimeId lastNonPremiumLimitUpload() const {
return _lastNonPremiumLimitUpload;
}
void setLastNonPremiumLimitDownload(TimeId when) {
_lastNonPremiumLimitDownload = when;
}
void setLastNonPremiumLimitUpload(TimeId when) {
_lastNonPremiumLimitUpload = when;
}
void setRingtoneVolume(
Data::DefaultNotify defaultNotify,
ushort volume);
[[nodiscard]] ushort ringtoneVolume(
Data::DefaultNotify defaultNotify) const;
void setRingtoneVolume(
PeerId peerId,
MsgId topicRootId,
PeerId monoforumPeerId,
ushort volume);
[[nodiscard]] ushort ringtoneVolume(
PeerId peerId,
MsgId topicRootId,
PeerId monoforumPeerId) const;
void markTranscriptionAsRated(uint64 transcriptionId);
[[nodiscard]] bool isTranscriptionRated(uint64 transcriptionId) const;
void setUnreviewed(std::vector<Data::UnreviewedAuth> auths);
[[nodiscard]] const std::vector<Data::UnreviewedAuth> &unreviewed() const;
void setSetupEmailState(Data::SetupEmailState state);
[[nodiscard]] Data::SetupEmailState setupEmailState() const;
private:
static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60;
static constexpr auto kPhotoEditorHintMaxShowsCount = 5;
struct ThreadId {
PeerId peerId;
MsgId topicRootId;
PeerId monoforumPeerId;
friend inline constexpr auto operator<=>(
ThreadId,
ThreadId) = default;
};
ChatHelpers::SelectorTab _selectorTab; // per-window
base::flat_set<PeerId> _groupStickersSectionHidden;
base::flat_set<PeerId> _groupEmojiSectionHidden;
bool _hadLegacyCallsPeerToPeerNobody = false;
Data::AutoDownload::Full _autoDownload;
rpl::variable<bool> _archiveCollapsed = false;
rpl::variable<bool> _archiveInMainMenu = false;
rpl::variable<bool> _skipArchiveInSearch = false;
base::flat_map<ThreadId, MsgId> _hiddenPinnedMessages;
base::flat_set<PeerId> _verticalSubsectionTabs;
base::flat_map<Data::DefaultNotify, ushort> _ringtoneDefaultVolumes;
base::flat_map<ThreadId, ushort> _ringtoneVolumes;
bool _dialogsFiltersEnabled = false;
int _photoEditorHintShowsCount = 0;
std::vector<TimeId> _mutePeriods;
TimeId _lastNonPremiumLimitDownload = 0;
TimeId _lastNonPremiumLimitUpload = 0;
Support::SwitchSettings _supportSwitch;
bool _supportFixChatsOrder = true;
bool _supportTemplatesAutocomplete = true;
bool _supportAllSilent = false;
rpl::variable<int> _supportChatsTimeSlice
= kDefaultSupportChatsLimitSlice;
rpl::variable<bool> _supportAllSearchResults = false;
base::flat_set<uint64> _ratedTranscriptions;
std::vector<Data::UnreviewedAuth> _unreviewed;
Data::SetupEmailState _setupEmailState;
};
} // namespace Main

View File

@@ -0,0 +1,188 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "main/session/send_as_peers.h"
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "main/main_session.h"
#include "apiwrap.h"
namespace Main {
namespace {
constexpr auto kRequestEach = 30 * crl::time(1000);
} // namespace
SendAsPeers::SendAsPeers(not_null<Session*> session)
: _session(session)
, _onlyMe({ { .peer = session->user(), .premiumRequired = false } })
, _onlyMePaid({ session->user() }) {
_session->changes().peerUpdates(
Data::PeerUpdate::Flag::Rights
) | rpl::map([=](const Data::PeerUpdate &update) {
const auto peer = update.peer;
const auto channel = peer->asChannel();
const auto bits = 0
| (peer->amAnonymous() ? (1 << 0) : 0)
| ((channel && channel->isPublic()) ? (1 << 1) : 0)
| ((channel && channel->addsSignature()) ? (1 << 2) : 0)
| ((channel && channel->signatureProfiles()) ? (1 << 3) : 0);
return std::tuple(peer, bits);
}) | rpl::distinct_until_changed(
) | rpl::filter([=](not_null<PeerData*> peer, int) {
return _lists.contains(peer) || _lastRequestTime.contains(peer);
}) | rpl::on_next([=](not_null<PeerData*> peer, int) {
refresh(peer, true);
}, _lifetime);
}
bool SendAsPeers::shouldChoose(SendAsKey key) {
refresh(key);
if (list(key).size() < 2) {
return false;
}
if (key.type == SendAsType::VideoStream) {
return true;
}
const auto channel = key.peer->asBroadcast();
return Data::CanSendAnything(key.peer, false)
&& (!channel
|| channel->addsSignature()
|| channel->signatureProfiles());
}
void SendAsPeers::refresh(SendAsKey key, bool force) {
if (key.type != SendAsType::VideoStream && !key.peer->isChannel()) {
return;
}
const auto now = crl::now();
const auto i = _lastRequestTime.find(key);
const auto when = (i == end(_lastRequestTime)) ? -1 : i->second;
if (!force && (when >= 0 && now < when + kRequestEach)) {
return;
}
_lastRequestTime[key] = now;
request(key);
if (key.type == SendAsType::Message) {
request({ key.peer, SendAsType::PaidReaction });
}
}
const std::vector<SendAsPeer> &SendAsPeers::list(SendAsKey key) const {
if (key.type != SendAsType::VideoStream && !key.peer->isChannel()) {
return _onlyMe;
}
const auto i = _lists.find(key);
return (i != end(_lists)) ? i->second : _onlyMe;
}
rpl::producer<SendAsKey> SendAsPeers::updated() const {
return _updates.events();
}
void SendAsPeers::saveChosen(
not_null<PeerData*> peer,
not_null<PeerData*> chosen) {
peer->session().api().request(MTPmessages_SaveDefaultSendAs(
peer->input(),
chosen->input()
)).send();
setChosen(peer, chosen->id);
}
void SendAsPeers::setChosen(not_null<PeerData*> peer, PeerId chosenId) {
if (chosen(peer) == chosenId) {
return;
}
const auto fallback = peer->amAnonymous()
? peer
: peer->session().user();
if (fallback->id == chosenId) {
_chosen.remove(peer);
} else {
_chosen[peer] = chosenId;
}
_updates.fire({ peer });
}
PeerId SendAsPeers::chosen(not_null<PeerData*> peer) const {
const auto i = _chosen.find(peer);
return (i != end(_chosen)) ? i->second : PeerId();
}
not_null<PeerData*> SendAsPeers::resolveChosen(
not_null<PeerData*> peer) const {
return ResolveChosen(peer, list({ peer }), chosen(peer));
}
not_null<PeerData*> SendAsPeers::ResolveChosen(
not_null<PeerData*> peer,
const std::vector<SendAsPeer> &list,
PeerId chosen) {
const auto fallback = peer->amAnonymous()
? peer
: peer->session().user();
if (!chosen) {
chosen = fallback->id;
}
const auto i = ranges::find(list, chosen, [](const SendAsPeer &as) {
return as.peer->id;
});
return (i != end(list))
? i->peer
: !list.empty()
? list.front().peer
: fallback;
}
void SendAsPeers::request(SendAsKey key) {
using Flag = MTPchannels_GetSendAs::Flag;
key.peer->session().api().request(MTPchannels_GetSendAs(
MTP_flags((key.type == SendAsType::PaidReaction)
? Flag::f_for_paid_reactions
: (key.type == SendAsType::VideoStream)
? Flag::f_for_live_stories
: Flag()),
key.peer->input()
)).done([=](const MTPchannels_SendAsPeers &result) {
auto parsed = std::vector<SendAsPeer>();
auto &owner = key.peer->owner();
result.match([&](const MTPDchannels_sendAsPeers &data) {
owner.processUsers(data.vusers());
owner.processChats(data.vchats());
const auto &list = data.vpeers().v;
parsed.reserve(list.size());
for (const auto &as : list) {
const auto &data = as.data();
const auto peerId = peerFromMTP(data.vpeer());
if (const auto peer = owner.peerLoaded(peerId)) {
parsed.push_back({
.peer = peer,
.premiumRequired = data.is_premium_required(),
});
}
}
});
if (parsed.size() > 1) {
auto &now = _lists[key];
if (now != parsed) {
now = std::move(parsed);
_updates.fire_copy(key);
}
} else if (const auto i = _lists.find(key); i != end(_lists)) {
_lists.erase(i);
_updates.fire_copy(key);
}
}).send();
}
} // namespace Main

View File

@@ -0,0 +1,86 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class PeerData;
namespace Main {
class Session;
struct SendAsPeer {
not_null<PeerData*> peer;
bool premiumRequired = false;
friend inline auto operator<=>(SendAsPeer, SendAsPeer) = default;
};
enum class SendAsType : uchar {
Message,
PaidReaction,
VideoStream,
};
struct SendAsKey {
SendAsKey(
not_null<PeerData*> peer,
SendAsType type = SendAsType::Message)
: peer(peer)
, type(type) {
}
friend inline auto operator<=>(SendAsKey, SendAsKey) = default;
friend inline bool operator==(SendAsKey, SendAsKey) = default;
not_null<PeerData*> peer;
SendAsType type = SendAsType::Message;
};
class SendAsPeers final {
public:
explicit SendAsPeers(not_null<Session*> session);
bool shouldChoose(SendAsKey key);
void refresh(SendAsKey key, bool force = false);
[[nodiscard]] const std::vector<SendAsPeer> &list(SendAsKey key) const;
[[nodiscard]] rpl::producer<SendAsKey> updated() const;
void saveChosen(not_null<PeerData*> peer, not_null<PeerData*> chosen);
void setChosen(not_null<PeerData*> peer, PeerId chosenId);
[[nodiscard]] PeerId chosen(not_null<PeerData*> peer) const;
[[nodiscard]] const std::vector<not_null<PeerData*>> &paidReactionList(
not_null<PeerData*> peer) const;
// If !list(peer).empty() then the result will be from that list.
[[nodiscard]] not_null<PeerData*> resolveChosen(
not_null<PeerData*> peer) const;
[[nodiscard]] static not_null<PeerData*> ResolveChosen(
not_null<PeerData*> peer,
const std::vector<SendAsPeer> &list,
PeerId chosen);
private:
void request(SendAsKey key);
const not_null<Session*> _session;
const std::vector<SendAsPeer> _onlyMe;
const std::vector<not_null<PeerData*>> _onlyMePaid;
base::flat_map<SendAsKey, std::vector<SendAsPeer>> _lists;
base::flat_map<SendAsKey, crl::time> _lastRequestTime;
base::flat_map<not_null<PeerData*>, PeerId> _chosen;
rpl::event_stream<SendAsKey> _updates;
rpl::lifetime _lifetime;
};
} // namespace Main

View File

@@ -0,0 +1,92 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "main/session/session_show.h"
#include "chat_helpers/message_field.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/layers/generic_box.h"
namespace Main {
namespace {
class SimpleSessionShow final : public SessionShow {
public:
SimpleSessionShow(
std::shared_ptr<Ui::Show> show,
not_null<Session*> session);
void showOrHideBoxOrLayer(
std::variant<
v::null_t,
object_ptr<Ui::BoxContent>,
std::unique_ptr<Ui::LayerWidget>> &&layer,
Ui::LayerOptions options,
anim::type animated) const override;
not_null<QWidget*> toastParent() const override;
bool valid() const override;
operator bool() const override;
Session &session() const override;
private:
const std::shared_ptr<Ui::Show> _show;
const not_null<Session*> _session;
};
SimpleSessionShow::SimpleSessionShow(
std::shared_ptr<Ui::Show> show,
not_null<Session*> session)
: _show(std::move(show))
, _session(session) {
}
void SimpleSessionShow::showOrHideBoxOrLayer(
std::variant<
v::null_t,
object_ptr<Ui::BoxContent>,
std::unique_ptr<Ui::LayerWidget>> &&layer,
Ui::LayerOptions options,
anim::type animated) const {
_show->showOrHideBoxOrLayer(std::move(layer), options, animated);
}
not_null<QWidget*> SimpleSessionShow::toastParent() const {
return _show->toastParent();
}
bool SimpleSessionShow::valid() const {
return _show->valid();
}
SimpleSessionShow::operator bool() const {
return _show->operator bool();
}
Session &SimpleSessionShow::session() const {
return *_session;
}
} // namespace
bool SessionShow::showFrozenError() {
if (!session().frozen()) {
return false;
}
showBox(Box(FrozenInfoBox, &session(), FreezeInfoStyleOverride()));
return true;
}
std::shared_ptr<SessionShow> MakeSessionShow(
std::shared_ptr<Ui::Show> show,
not_null<Session*> session) {
return std::make_shared<SimpleSessionShow>(std::move(show), session);
}
} // namespace Main

View File

@@ -0,0 +1,28 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/layers/show.h"
namespace Main {
class Session;
class SessionShow : public Ui::Show {
public:
[[nodiscard]] virtual Main::Session &session() const = 0;
bool showFrozenError();
};
[[nodiscard]] std::shared_ptr<SessionShow> MakeSessionShow(
std::shared_ptr<Ui::Show> show,
not_null<Session*> session);
} // namespace Main