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
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:
630
Telegram/SourceFiles/main/main_account.cpp
Normal file
630
Telegram/SourceFiles/main/main_account.cpp
Normal 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
|
||||
172
Telegram/SourceFiles/main/main_account.h
Normal file
172
Telegram/SourceFiles/main/main_account.h
Normal 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
|
||||
605
Telegram/SourceFiles/main/main_app_config.cpp
Normal file
605
Telegram/SourceFiles/main/main_app_config.cpp
Normal 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
|
||||
187
Telegram/SourceFiles/main/main_app_config.h
Normal file
187
Telegram/SourceFiles/main/main_app_config.h
Normal 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
|
||||
29
Telegram/SourceFiles/main/main_app_config_values.cpp
Normal file
29
Telegram/SourceFiles/main/main_app_config_values.cpp
Normal 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
|
||||
18
Telegram/SourceFiles/main/main_app_config_values.h
Normal file
18
Telegram/SourceFiles/main/main_app_config_values.h
Normal 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
|
||||
517
Telegram/SourceFiles/main/main_domain.cpp
Normal file
517
Telegram/SourceFiles/main/main_domain.cpp
Normal 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
|
||||
118
Telegram/SourceFiles/main/main_domain.h
Normal file
118
Telegram/SourceFiles/main/main_domain.h
Normal 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
|
||||
598
Telegram/SourceFiles/main/main_session.cpp
Normal file
598
Telegram/SourceFiles/main/main_session.cpp
Normal 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
|
||||
338
Telegram/SourceFiles/main/main_session.h
Normal file
338
Telegram/SourceFiles/main/main_session.h
Normal 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
|
||||
936
Telegram/SourceFiles/main/main_session_settings.cpp
Normal file
936
Telegram/SourceFiles/main/main_session_settings.cpp
Normal 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
|
||||
227
Telegram/SourceFiles/main/main_session_settings.h
Normal file
227
Telegram/SourceFiles/main/main_session_settings.h
Normal 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
|
||||
188
Telegram/SourceFiles/main/session/send_as_peers.cpp
Normal file
188
Telegram/SourceFiles/main/session/send_as_peers.cpp
Normal 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
|
||||
86
Telegram/SourceFiles/main/session/send_as_peers.h
Normal file
86
Telegram/SourceFiles/main/session/send_as_peers.h
Normal 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
|
||||
92
Telegram/SourceFiles/main/session/session_show.cpp
Normal file
92
Telegram/SourceFiles/main/session/session_show.cpp
Normal 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
|
||||
28
Telegram/SourceFiles/main/session/session_show.h
Normal file
28
Telegram/SourceFiles/main/session/session_show.h
Normal 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
|
||||
Reference in New Issue
Block a user