mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 23:53:58 +02:00
Support "Device Storage" web app feature.
This commit is contained in:
parent
4c2ec15f70
commit
59abfcbd6d
9 changed files with 441 additions and 5 deletions
|
@ -1083,6 +1083,8 @@ PRIVATE
|
||||||
inline_bots/inline_bot_result.h
|
inline_bots/inline_bot_result.h
|
||||||
inline_bots/inline_bot_send_data.cpp
|
inline_bots/inline_bot_send_data.cpp
|
||||||
inline_bots/inline_bot_send_data.h
|
inline_bots/inline_bot_send_data.h
|
||||||
|
inline_bots/inline_bot_storage.cpp
|
||||||
|
inline_bots/inline_bot_storage.h
|
||||||
inline_bots/inline_results_inner.cpp
|
inline_bots/inline_results_inner.cpp
|
||||||
inline_bots/inline_results_inner.h
|
inline_bots/inline_results_inner.h
|
||||||
inline_bots/inline_results_widget.cpp
|
inline_bots/inline_results_widget.cpp
|
||||||
|
|
|
@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "inline_bots/inline_bot_result.h"
|
#include "inline_bots/inline_bot_result.h"
|
||||||
#include "inline_bots/inline_bot_confirm_prepared.h"
|
#include "inline_bots/inline_bot_confirm_prepared.h"
|
||||||
#include "inline_bots/inline_bot_downloads.h"
|
#include "inline_bots/inline_bot_downloads.h"
|
||||||
|
#include "inline_bots/inline_bot_storage.h"
|
||||||
#include "iv/iv_instance.h"
|
#include "iv/iv_instance.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_app_config.h"
|
#include "main/main_app_config.h"
|
||||||
|
@ -1502,7 +1503,7 @@ void WebViewInstance::botHandleInvoice(QString slug) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Payments::CheckoutProcess::Start(
|
Payments::CheckoutProcess::Start(
|
||||||
&_bot->session(),
|
_session,
|
||||||
slug,
|
slug,
|
||||||
reactivate,
|
reactivate,
|
||||||
nonPanelPaymentFormFactory(reactivate));
|
nonPanelPaymentFormFactory(reactivate));
|
||||||
|
@ -1698,6 +1699,20 @@ void WebViewInstance::botAllowWriteAccess(Fn<void(bool allowed)> callback) {
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebViewInstance::botStorageWrite(
|
||||||
|
QString key,
|
||||||
|
std::optional<QString> value) {
|
||||||
|
return _session->attachWebView().storage().write(_bot->id, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<QString> WebViewInstance::botStorageRead(QString key) {
|
||||||
|
return _session->attachWebView().storage().read(_bot->id, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebViewInstance::botStorageClear() {
|
||||||
|
_session->attachWebView().storage().clear(_bot->id);
|
||||||
|
}
|
||||||
|
|
||||||
void WebViewInstance::botRequestEmojiStatusAccess(
|
void WebViewInstance::botRequestEmojiStatusAccess(
|
||||||
Fn<void(bool allowed)> callback) {
|
Fn<void(bool allowed)> callback) {
|
||||||
if (_bot->botInfo->canManageEmojiStatus) {
|
if (_bot->botInfo->canManageEmojiStatus) {
|
||||||
|
@ -1955,20 +1970,20 @@ void WebViewInstance::botDownloadFile(
|
||||||
callback(false);
|
callback(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_bot->session().attachWebView().downloads().start({
|
_session->attachWebView().downloads().start({
|
||||||
.bot = _bot,
|
.bot = _bot,
|
||||||
.url = request.url,
|
.url = request.url,
|
||||||
.path = path,
|
.path = path,
|
||||||
});
|
});
|
||||||
callback(true);
|
callback(true);
|
||||||
};
|
};
|
||||||
_bot->session().api().request(MTPbots_CheckDownloadFileParams(
|
_session->api().request(MTPbots_CheckDownloadFileParams(
|
||||||
_bot->inputUser,
|
_bot->inputUser,
|
||||||
MTP_string(request.name),
|
MTP_string(request.name),
|
||||||
MTP_string(request.url)
|
MTP_string(request.url)
|
||||||
)).done([=] {
|
)).done([=] {
|
||||||
_panel->showBox(Box(DownloadFileBox, DownloadBoxArgs{
|
_panel->showBox(Box(DownloadFileBox, DownloadBoxArgs{
|
||||||
.session = &_bot->session(),
|
.session = _session,
|
||||||
.bot = _bot->name(),
|
.bot = _bot->name(),
|
||||||
.name = base::FileNameFromUserString(request.name),
|
.name = base::FileNameFromUserString(request.name),
|
||||||
.url = request.url,
|
.url = request.url,
|
||||||
|
@ -2018,7 +2033,7 @@ void WebViewInstance::botOpenPrivacyPolicy() {
|
||||||
};
|
};
|
||||||
const auto openUrl = [=](const QString &url) {
|
const auto openUrl = [=](const QString &url) {
|
||||||
Core::App().iv().openWithIvPreferred(
|
Core::App().iv().openWithIvPreferred(
|
||||||
&_bot->session(),
|
_session,
|
||||||
url,
|
url,
|
||||||
makeOtherContext(false));
|
makeOtherContext(false));
|
||||||
};
|
};
|
||||||
|
@ -2092,6 +2107,7 @@ std::shared_ptr<Main::SessionShow> WebViewInstance::uiShow() {
|
||||||
AttachWebView::AttachWebView(not_null<Main::Session*> session)
|
AttachWebView::AttachWebView(not_null<Main::Session*> session)
|
||||||
: _session(session)
|
: _session(session)
|
||||||
, _downloads(std::make_unique<Downloads>(session))
|
, _downloads(std::make_unique<Downloads>(session))
|
||||||
|
, _storage(std::make_unique<Storage>(session))
|
||||||
, _refreshTimer([=] { requestBots(); }) {
|
, _refreshTimer([=] { requestBots(); }) {
|
||||||
_refreshTimer.callEach(kRefreshBotsTimeout);
|
_refreshTimer.callEach(kRefreshBotsTimeout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ namespace InlineBots {
|
||||||
|
|
||||||
class WebViewInstance;
|
class WebViewInstance;
|
||||||
class Downloads;
|
class Downloads;
|
||||||
|
class Storage;
|
||||||
|
|
||||||
enum class PeerType : uint8 {
|
enum class PeerType : uint8 {
|
||||||
SameBot = 0x01,
|
SameBot = 0x01,
|
||||||
|
@ -276,6 +277,9 @@ private:
|
||||||
QString query) override;
|
QString query) override;
|
||||||
void botCheckWriteAccess(Fn<void(bool allowed)> callback) override;
|
void botCheckWriteAccess(Fn<void(bool allowed)> callback) override;
|
||||||
void botAllowWriteAccess(Fn<void(bool allowed)> callback) override;
|
void botAllowWriteAccess(Fn<void(bool allowed)> callback) override;
|
||||||
|
bool botStorageWrite(QString key, std::optional<QString> value) override;
|
||||||
|
std::optional<QString> botStorageRead(QString key) override;
|
||||||
|
void botStorageClear() override;
|
||||||
void botRequestEmojiStatusAccess(
|
void botRequestEmojiStatusAccess(
|
||||||
Fn<void(bool allowed)> callback) override;
|
Fn<void(bool allowed)> callback) override;
|
||||||
void botSharePhone(Fn<void(bool shared)> callback) override;
|
void botSharePhone(Fn<void(bool shared)> callback) override;
|
||||||
|
@ -322,6 +326,9 @@ public:
|
||||||
[[nodiscard]] Downloads &downloads() const {
|
[[nodiscard]] Downloads &downloads() const {
|
||||||
return *_downloads;
|
return *_downloads;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] Storage &storage() const {
|
||||||
|
return *_storage;
|
||||||
|
}
|
||||||
|
|
||||||
void open(WebViewDescriptor &&descriptor);
|
void open(WebViewDescriptor &&descriptor);
|
||||||
void openByUsername(
|
void openByUsername(
|
||||||
|
@ -394,6 +401,7 @@ private:
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
const std::unique_ptr<Downloads> _downloads;
|
const std::unique_ptr<Downloads> _downloads;
|
||||||
|
const std::unique_ptr<Storage> _storage;
|
||||||
|
|
||||||
base::Timer _refreshTimer;
|
base::Timer _refreshTimer;
|
||||||
|
|
||||||
|
|
181
Telegram/SourceFiles/inline_bots/inline_bot_storage.cpp
Normal file
181
Telegram/SourceFiles/inline_bots/inline_bot_storage.cpp
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
/*
|
||||||
|
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 "inline_bots/inline_bot_storage.h"
|
||||||
|
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "storage/storage_account.h"
|
||||||
|
|
||||||
|
#include <xxhash.h>
|
||||||
|
|
||||||
|
namespace InlineBots {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kMaxStorageSize = (5 << 20);
|
||||||
|
|
||||||
|
[[nodiscard]] uint64 KeyHash(const QString &key) {
|
||||||
|
return XXH64(key.data(), key.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Storage::Storage(not_null<Main::Session*> session)
|
||||||
|
: _session(session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Storage::write(
|
||||||
|
PeerId botId,
|
||||||
|
const QString &key,
|
||||||
|
const std::optional<QString> &value) {
|
||||||
|
if (value && value->size() > kMaxStorageSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
readFromDisk(botId);
|
||||||
|
auto i = _lists.find(botId);
|
||||||
|
if (i == end(_lists)) {
|
||||||
|
if (!value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
i = _lists.emplace(botId).first;
|
||||||
|
}
|
||||||
|
auto &list = i->second;
|
||||||
|
const auto hash = KeyHash(key);
|
||||||
|
auto j = list.data.find(hash);
|
||||||
|
if (j == end(list.data)) {
|
||||||
|
if (!value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
j = list.data.emplace(hash).first;
|
||||||
|
}
|
||||||
|
auto &bykey = j->second;
|
||||||
|
const auto k = ranges::find(bykey, key, &Entry::key);
|
||||||
|
if (k == end(bykey) && !value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto size = list.totalSize
|
||||||
|
- (k != end(bykey) ? (key.size() + k->value.size()) : 0)
|
||||||
|
+ (value ? (key.size() + value->size()) : 0);
|
||||||
|
if (size > kMaxStorageSize) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (k == end(bykey)) {
|
||||||
|
bykey.emplace_back(key, *value);
|
||||||
|
++list.keysCount;
|
||||||
|
} else if (value) {
|
||||||
|
k->value = *value;
|
||||||
|
} else {
|
||||||
|
bykey.erase(k);
|
||||||
|
--list.keysCount;
|
||||||
|
}
|
||||||
|
if (bykey.empty()) {
|
||||||
|
list.data.erase(j);
|
||||||
|
if (list.data.empty()) {
|
||||||
|
Assert(size == 0);
|
||||||
|
_lists.erase(i);
|
||||||
|
} else {
|
||||||
|
list.totalSize = size;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list.totalSize = size;
|
||||||
|
}
|
||||||
|
saveToDisk(botId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<QString> Storage::read(PeerId botId, const QString &key) {
|
||||||
|
readFromDisk(botId);
|
||||||
|
const auto i = _lists.find(botId);
|
||||||
|
if (i == end(_lists)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto &list = i->second;
|
||||||
|
const auto j = list.data.find(KeyHash(key));
|
||||||
|
if (j == end(list.data)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto &bykey = j->second;
|
||||||
|
const auto k = ranges::find(bykey, key, &Entry::key);
|
||||||
|
if (k == end(bykey)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return k->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Storage::clear(PeerId botId) {
|
||||||
|
if (_lists.remove(botId)) {
|
||||||
|
saveToDisk(botId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Storage::saveToDisk(PeerId botId) {
|
||||||
|
const auto i = _lists.find(botId);
|
||||||
|
if (i != end(_lists)) {
|
||||||
|
_session->local().writeBotStorage(botId, Serialize(i->second));
|
||||||
|
} else {
|
||||||
|
_session->local().writeBotStorage(botId, QByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Storage::readFromDisk(PeerId botId) {
|
||||||
|
const auto serialized = _session->local().readBotStorage(botId);
|
||||||
|
if (!serialized.isEmpty()) {
|
||||||
|
_lists[botId] = Deserialize(serialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray Storage::Serialize(const List &list) {
|
||||||
|
auto result = QByteArray();
|
||||||
|
const auto size = sizeof(quint32)
|
||||||
|
+ (list.keysCount * sizeof(quint32))
|
||||||
|
+ (list.totalSize * sizeof(ushort));
|
||||||
|
result.reserve(size);
|
||||||
|
{
|
||||||
|
QDataStream stream(&result, QIODevice::WriteOnly);
|
||||||
|
auto count = 0;
|
||||||
|
stream.setVersion(QDataStream::Qt_5_1);
|
||||||
|
stream << quint32(list.keysCount);
|
||||||
|
for (const auto &[hash, bykey] : list.data) {
|
||||||
|
for (const auto &entry : bykey) {
|
||||||
|
stream << entry.key << entry.value;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert(count == list.keysCount);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::List Storage::Deserialize(const QByteArray &serialized) {
|
||||||
|
QDataStream stream(serialized);
|
||||||
|
stream.setVersion(QDataStream::Qt_5_1);
|
||||||
|
|
||||||
|
auto count = quint32();
|
||||||
|
auto result = List();
|
||||||
|
stream >> count;
|
||||||
|
if (count > kMaxStorageSize) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
for (auto i = 0; i != count; ++i) {
|
||||||
|
auto entry = Entry();
|
||||||
|
stream >> entry.key >> entry.value;
|
||||||
|
const auto hash = KeyHash(entry.key);
|
||||||
|
auto j = result.data.find(hash);
|
||||||
|
if (j == end(result.data)) {
|
||||||
|
j = result.data.emplace(hash).first;
|
||||||
|
}
|
||||||
|
auto &bykey = j->second;
|
||||||
|
const auto k = ranges::find(bykey, entry.key, &Entry::key);
|
||||||
|
if (k == end(bykey)) {
|
||||||
|
bykey.push_back(entry);
|
||||||
|
result.totalSize += entry.key.size() + entry.value.size();
|
||||||
|
++result.keysCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InlineBots
|
52
Telegram/SourceFiles/inline_bots/inline_bot_storage.h
Normal file
52
Telegram/SourceFiles/inline_bots/inline_bot_storage.h
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
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/chat/attach/attach_bot_webview.h"
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace InlineBots {
|
||||||
|
|
||||||
|
class Storage final {
|
||||||
|
public:
|
||||||
|
explicit Storage(not_null<Main::Session*> session);
|
||||||
|
|
||||||
|
bool write(
|
||||||
|
PeerId botId,
|
||||||
|
const QString &key,
|
||||||
|
const std::optional<QString> &value);
|
||||||
|
std::optional<QString> read(PeerId botId, const QString &key);
|
||||||
|
void clear(PeerId botId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Entry {
|
||||||
|
QString key;
|
||||||
|
QString value;
|
||||||
|
};
|
||||||
|
struct List {
|
||||||
|
base::flat_map<uint64, std::vector<Entry>> data;
|
||||||
|
int keysCount = 0;
|
||||||
|
int totalSize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void saveToDisk(PeerId botId);
|
||||||
|
void readFromDisk(PeerId botId);
|
||||||
|
|
||||||
|
[[nodiscard]] static QByteArray Serialize(const List &list);
|
||||||
|
[[nodiscard]] static List Deserialize(const QByteArray &serialized);
|
||||||
|
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
|
||||||
|
base::flat_map<PeerId, List> _lists;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InlineBots
|
|
@ -98,6 +98,7 @@ enum { // Local Storage Keys
|
||||||
lskRoundPlaceholder = 0x1a, // no data
|
lskRoundPlaceholder = 0x1a, // no data
|
||||||
lskInlineBotsDownloads = 0x1b, // no data
|
lskInlineBotsDownloads = 0x1b, // no data
|
||||||
lskMediaLastPlaybackPositions = 0x1c, // no data
|
lskMediaLastPlaybackPositions = 0x1c, // no data
|
||||||
|
lskBotStorages = 0x1d, // data: PeerId botId
|
||||||
};
|
};
|
||||||
|
|
||||||
auto EmptyMessageDraftSources()
|
auto EmptyMessageDraftSources()
|
||||||
|
@ -255,6 +256,9 @@ base::flat_set<QString> Account::collectGoodNames() const {
|
||||||
for (const auto &[key, value] : _draftCursorsMap) {
|
for (const auto &[key, value] : _draftCursorsMap) {
|
||||||
push(value);
|
push(value);
|
||||||
}
|
}
|
||||||
|
for (const auto &[key, value] : _botStoragesMap) {
|
||||||
|
push(value);
|
||||||
|
}
|
||||||
for (const auto &value : keys) {
|
for (const auto &value : keys) {
|
||||||
push(value);
|
push(value);
|
||||||
}
|
}
|
||||||
|
@ -308,6 +312,8 @@ Account::ReadMapResult Account::readMapWith(
|
||||||
base::flat_map<PeerId, FileKey> draftsMap;
|
base::flat_map<PeerId, FileKey> draftsMap;
|
||||||
base::flat_map<PeerId, FileKey> draftCursorsMap;
|
base::flat_map<PeerId, FileKey> draftCursorsMap;
|
||||||
base::flat_map<PeerId, bool> draftsNotReadMap;
|
base::flat_map<PeerId, bool> draftsNotReadMap;
|
||||||
|
base::flat_map<PeerId, FileKey> botStoragesMap;
|
||||||
|
base::flat_map<PeerId, bool> botStoragesNotReadMap;
|
||||||
quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedPeersKey = 0;
|
quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedPeersKey = 0;
|
||||||
quint64 recentStickersKeyOld = 0;
|
quint64 recentStickersKeyOld = 0;
|
||||||
quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, favedStickersKey = 0, archivedStickersKey = 0;
|
quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, favedStickersKey = 0, archivedStickersKey = 0;
|
||||||
|
@ -443,6 +449,18 @@ Account::ReadMapResult Account::readMapWith(
|
||||||
>> webviewStorageTokenBots
|
>> webviewStorageTokenBots
|
||||||
>> webviewStorageTokenOther;
|
>> webviewStorageTokenOther;
|
||||||
} break;
|
} break;
|
||||||
|
case lskBotStorages: {
|
||||||
|
quint32 count = 0;
|
||||||
|
map.stream >> count;
|
||||||
|
for (quint32 i = 0; i < count; ++i) {
|
||||||
|
FileKey key;
|
||||||
|
quint64 peerIdSerialized;
|
||||||
|
map.stream >> key >> peerIdSerialized;
|
||||||
|
const auto peerId = DeserializePeerId(peerIdSerialized);
|
||||||
|
botStoragesMap.emplace(peerId, key);
|
||||||
|
botStoragesNotReadMap.emplace(peerId, true);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
default:
|
default:
|
||||||
LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType));
|
LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType));
|
||||||
return ReadMapResult::Failed;
|
return ReadMapResult::Failed;
|
||||||
|
@ -457,6 +475,8 @@ Account::ReadMapResult Account::readMapWith(
|
||||||
_draftsMap = draftsMap;
|
_draftsMap = draftsMap;
|
||||||
_draftCursorsMap = draftCursorsMap;
|
_draftCursorsMap = draftCursorsMap;
|
||||||
_draftsNotReadMap = draftsNotReadMap;
|
_draftsNotReadMap = draftsNotReadMap;
|
||||||
|
_botStoragesMap = botStoragesMap;
|
||||||
|
_botStoragesNotReadMap = botStoragesNotReadMap;
|
||||||
|
|
||||||
_locationsKey = locationsKey;
|
_locationsKey = locationsKey;
|
||||||
_trustedPeersKey = trustedPeersKey;
|
_trustedPeersKey = trustedPeersKey;
|
||||||
|
@ -599,6 +619,7 @@ void Account::writeMap() {
|
||||||
if (_roundPlaceholderKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
if (_roundPlaceholderKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||||
if (_inlineBotsDownloadsKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
if (_inlineBotsDownloadsKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||||
if (_mediaLastPlaybackPositionsKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
if (_mediaLastPlaybackPositionsKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||||
|
if (!_botStoragesMap.empty()) mapSize += sizeof(quint32) * 2 + _botStoragesMap.size() * sizeof(quint64) * 2;
|
||||||
|
|
||||||
EncryptedDescriptor mapData(mapSize);
|
EncryptedDescriptor mapData(mapSize);
|
||||||
if (!self.isEmpty()) {
|
if (!self.isEmpty()) {
|
||||||
|
@ -681,6 +702,12 @@ void Account::writeMap() {
|
||||||
mapData.stream << quint32(lskMediaLastPlaybackPositions);
|
mapData.stream << quint32(lskMediaLastPlaybackPositions);
|
||||||
mapData.stream << quint64(_mediaLastPlaybackPositionsKey);
|
mapData.stream << quint64(_mediaLastPlaybackPositionsKey);
|
||||||
}
|
}
|
||||||
|
if (!_botStoragesMap.empty()) {
|
||||||
|
mapData.stream << quint32(lskBotStorages) << quint32(_botStoragesMap.size());
|
||||||
|
for (const auto &[key, value] : _botStoragesMap) {
|
||||||
|
mapData.stream << quint64(value) << SerializePeerId(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
map.writeEncrypted(mapData, _localKey);
|
map.writeEncrypted(mapData, _localKey);
|
||||||
|
|
||||||
_mapChanged = false;
|
_mapChanged = false;
|
||||||
|
@ -693,6 +720,8 @@ void Account::reset() {
|
||||||
_draftsMap.clear();
|
_draftsMap.clear();
|
||||||
_draftCursorsMap.clear();
|
_draftCursorsMap.clear();
|
||||||
_draftsNotReadMap.clear();
|
_draftsNotReadMap.clear();
|
||||||
|
_botStoragesMap.clear();
|
||||||
|
_botStoragesNotReadMap.clear();
|
||||||
_locationsKey = _trustedPeersKey = 0;
|
_locationsKey = _trustedPeersKey = 0;
|
||||||
_recentStickersKeyOld = 0;
|
_recentStickersKeyOld = 0;
|
||||||
_installedStickersKey = 0;
|
_installedStickersKey = 0;
|
||||||
|
@ -3473,6 +3502,63 @@ void Account::writeInlineBotsDownloads(const QByteArray &bytes) {
|
||||||
file.writeEncrypted(data, _localKey);
|
file.writeEncrypted(data, _localKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Account::writeBotStorage(PeerId botId, const QByteArray &serialized) {
|
||||||
|
if (serialized.isEmpty()) {
|
||||||
|
auto i = _botStoragesMap.find(botId);
|
||||||
|
if (i != _botStoragesMap.cend()) {
|
||||||
|
ClearKey(i->second, _basePath);
|
||||||
|
_botStoragesMap.erase(i);
|
||||||
|
writeMapDelayed();
|
||||||
|
}
|
||||||
|
_botStoragesNotReadMap.remove(botId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto i = _botStoragesMap.find(botId);
|
||||||
|
if (i == _botStoragesMap.cend()) {
|
||||||
|
i = _botStoragesMap.emplace(botId, GenerateKey(_basePath)).first;
|
||||||
|
writeMapQueued();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size = Serialize::bytearraySize(serialized);
|
||||||
|
|
||||||
|
EncryptedDescriptor data(size);
|
||||||
|
data.stream << serialized;
|
||||||
|
|
||||||
|
FileWriteDescriptor file(i->second, _basePath);
|
||||||
|
file.writeEncrypted(data, _localKey);
|
||||||
|
|
||||||
|
_botStoragesNotReadMap.remove(botId);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray Account::readBotStorage(PeerId botId) {
|
||||||
|
if (!_botStoragesNotReadMap.remove(botId)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto j = _botStoragesMap.find(botId);
|
||||||
|
if (j == _botStoragesMap.cend()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
FileReadDescriptor storage;
|
||||||
|
if (!ReadEncryptedFile(storage, j->second, _basePath, _localKey)) {
|
||||||
|
ClearKey(j->second, _basePath);
|
||||||
|
_botStoragesMap.erase(j);
|
||||||
|
writeMapDelayed();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = QByteArray();
|
||||||
|
storage.stream >> result;
|
||||||
|
if (storage.stream.status() != QDataStream::Ok) {
|
||||||
|
ClearKey(j->second, _basePath);
|
||||||
|
_botStoragesMap.erase(j);
|
||||||
|
writeMapDelayed();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
bool Account::encrypt(
|
bool Account::encrypt(
|
||||||
const void *src,
|
const void *src,
|
||||||
void *dst,
|
void *dst,
|
||||||
|
|
|
@ -191,6 +191,9 @@ public:
|
||||||
[[nodiscard]] QByteArray readInlineBotsDownloads();
|
[[nodiscard]] QByteArray readInlineBotsDownloads();
|
||||||
void writeInlineBotsDownloads(const QByteArray &bytes);
|
void writeInlineBotsDownloads(const QByteArray &bytes);
|
||||||
|
|
||||||
|
void writeBotStorage(PeerId botId, const QByteArray &serialized);
|
||||||
|
[[nodiscard]] QByteArray readBotStorage(PeerId botId);
|
||||||
|
|
||||||
[[nodiscard]] bool encrypt(
|
[[nodiscard]] bool encrypt(
|
||||||
const void *src,
|
const void *src,
|
||||||
void *dst,
|
void *dst,
|
||||||
|
@ -293,6 +296,8 @@ private:
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
not_null<History*>,
|
not_null<History*>,
|
||||||
base::flat_map<Data::DraftKey, MessageDraftSource>> _draftSources;
|
base::flat_map<Data::DraftKey, MessageDraftSource>> _draftSources;
|
||||||
|
base::flat_map<PeerId, FileKey> _botStoragesMap;
|
||||||
|
base::flat_map<PeerId, bool> _botStoragesNotReadMap;
|
||||||
|
|
||||||
QMultiMap<MediaKey, Core::FileLocation> _fileLocations;
|
QMultiMap<MediaKey, Core::FileLocation> _fileLocations;
|
||||||
QMap<QString, QPair<MediaKey, Core::FileLocation>> _fileLocationPairs;
|
QMap<QString, QPair<MediaKey, Core::FileLocation>> _fileLocationPairs;
|
||||||
|
|
|
@ -997,6 +997,20 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) {
|
||||||
processEmojiStatusRequest(arguments);
|
processEmojiStatusRequest(arguments);
|
||||||
} else if (command == "web_app_request_emoji_status_access") {
|
} else if (command == "web_app_request_emoji_status_access") {
|
||||||
processEmojiStatusAccessRequest();
|
processEmojiStatusAccessRequest();
|
||||||
|
} else if (command == "web_app_device_storage_save_key") {
|
||||||
|
processStorageSaveKey(arguments);
|
||||||
|
} else if (command == "web_app_device_storage_get_key") {
|
||||||
|
processStorageGetKey(arguments);
|
||||||
|
} else if (command == "web_app_device_storage_clear") {
|
||||||
|
processStorageClear(arguments);
|
||||||
|
} else if (command == "web_app_secure_storage_save_key") {
|
||||||
|
secureStorageFailed(arguments);
|
||||||
|
} else if (command == "web_app_secure_storage_get_key") {
|
||||||
|
secureStorageFailed(arguments);
|
||||||
|
} else if (command == "web_app_secure_storage_restore_key") {
|
||||||
|
secureStorageFailed(arguments);
|
||||||
|
} else if (command == "web_app_secure_storage_clear") {
|
||||||
|
secureStorageFailed(arguments);
|
||||||
} else if (command == "share_score") {
|
} else if (command == "share_score") {
|
||||||
_delegate->botHandleMenuButton(MenuButton::ShareGame);
|
_delegate->botHandleMenuButton(MenuButton::ShareGame);
|
||||||
}
|
}
|
||||||
|
@ -1201,6 +1215,63 @@ void Panel::processEmojiStatusAccessRequest() {
|
||||||
_delegate->botRequestEmojiStatusAccess(std::move(callback));
|
_delegate->botRequestEmojiStatusAccess(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Panel::processStorageSaveKey(const QJsonObject &args) {
|
||||||
|
const auto keyObject = args["key"];
|
||||||
|
const auto valueObject = args["value"];
|
||||||
|
const auto key = keyObject.toString();
|
||||||
|
if (!keyObject.isString()) {
|
||||||
|
deviceStorageFailed(args, u"KEY_INVALID"_q);
|
||||||
|
} else if (valueObject.isNull()) {
|
||||||
|
_delegate->botStorageWrite(key, std::nullopt);
|
||||||
|
replyDeviceStorage(args, u"device_storage_key_saved"_q, {});
|
||||||
|
} else if (!valueObject.isString()) {
|
||||||
|
deviceStorageFailed(args, u"VALUE_INVALID"_q);
|
||||||
|
} else if (_delegate->botStorageWrite(key, valueObject.toString())) {
|
||||||
|
replyDeviceStorage(args, u"device_storage_key_saved"_q, {});
|
||||||
|
} else {
|
||||||
|
deviceStorageFailed(args, u"QUOTA_EXCEEDED"_q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::processStorageGetKey(const QJsonObject &args) {
|
||||||
|
const auto keyObject = args["key"];
|
||||||
|
const auto key = keyObject.toString();
|
||||||
|
if (!keyObject.isString()) {
|
||||||
|
deviceStorageFailed(args, u"KEY_INVALID"_q);
|
||||||
|
} else {
|
||||||
|
const auto value = _delegate->botStorageRead(key);
|
||||||
|
replyDeviceStorage(args, u"device_storage_key_received"_q, {
|
||||||
|
{ u"value"_q, value ? QJsonValue(*value) : QJsonValue::Null },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::processStorageClear(const QJsonObject &args) {
|
||||||
|
_delegate->botStorageClear();
|
||||||
|
replyDeviceStorage(args, u"device_storage_cleared"_q, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::replyDeviceStorage(
|
||||||
|
const QJsonObject &args,
|
||||||
|
const QString &event,
|
||||||
|
QJsonObject response) {
|
||||||
|
response[u"req_id"_q] = args[u"req_id"_q];
|
||||||
|
postEvent(event, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::deviceStorageFailed(const QJsonObject &args, QString error) {
|
||||||
|
replyDeviceStorage(args, u"device_storage_failed"_q, {
|
||||||
|
{ u"error"_q, error },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::secureStorageFailed(const QJsonObject &args) {
|
||||||
|
postEvent(u"secure_storage_failed"_q, QJsonObject{
|
||||||
|
{ u"req_id"_q, args["req_id"] },
|
||||||
|
{ u"error"_q, u"UNSUPPORTED"_q },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Panel::openTgLink(const QJsonObject &args) {
|
void Panel::openTgLink(const QJsonObject &args) {
|
||||||
if (args.isEmpty()) {
|
if (args.isEmpty()) {
|
||||||
LOG(("BotWebView Error: Bad arguments in 'web_app_open_tg_link'."));
|
LOG(("BotWebView Error: Bad arguments in 'web_app_open_tg_link'."));
|
||||||
|
|
|
@ -91,6 +91,12 @@ public:
|
||||||
QString query) = 0;
|
QString query) = 0;
|
||||||
virtual void botCheckWriteAccess(Fn<void(bool allowed)> callback) = 0;
|
virtual void botCheckWriteAccess(Fn<void(bool allowed)> callback) = 0;
|
||||||
virtual void botAllowWriteAccess(Fn<void(bool allowed)> callback) = 0;
|
virtual void botAllowWriteAccess(Fn<void(bool allowed)> callback) = 0;
|
||||||
|
virtual bool botStorageWrite(
|
||||||
|
QString key,
|
||||||
|
std::optional<QString> value) = 0;
|
||||||
|
[[nodiscard]] virtual std::optional<QString> botStorageRead(
|
||||||
|
QString key) = 0;
|
||||||
|
virtual void botStorageClear() = 0;
|
||||||
virtual void botRequestEmojiStatusAccess(
|
virtual void botRequestEmojiStatusAccess(
|
||||||
Fn<void(bool allowed)> callback) = 0;
|
Fn<void(bool allowed)> callback) = 0;
|
||||||
virtual void botSharePhone(Fn<void(bool shared)> callback) = 0;
|
virtual void botSharePhone(Fn<void(bool shared)> callback) = 0;
|
||||||
|
@ -165,6 +171,9 @@ private:
|
||||||
void processSendMessageRequest(const QJsonObject &args);
|
void processSendMessageRequest(const QJsonObject &args);
|
||||||
void processEmojiStatusRequest(const QJsonObject &args);
|
void processEmojiStatusRequest(const QJsonObject &args);
|
||||||
void processEmojiStatusAccessRequest();
|
void processEmojiStatusAccessRequest();
|
||||||
|
void processStorageSaveKey(const QJsonObject &args);
|
||||||
|
void processStorageGetKey(const QJsonObject &args);
|
||||||
|
void processStorageClear(const QJsonObject &args);
|
||||||
void processButtonMessage(
|
void processButtonMessage(
|
||||||
std::unique_ptr<Button> &button,
|
std::unique_ptr<Button> &button,
|
||||||
const QJsonObject &args);
|
const QJsonObject &args);
|
||||||
|
@ -188,6 +197,12 @@ private:
|
||||||
void replyCustomMethod(QJsonValue requestId, QJsonObject response);
|
void replyCustomMethod(QJsonValue requestId, QJsonObject response);
|
||||||
void requestClipboardText(const QJsonObject &args);
|
void requestClipboardText(const QJsonObject &args);
|
||||||
void setupClosingBehaviour(const QJsonObject &args);
|
void setupClosingBehaviour(const QJsonObject &args);
|
||||||
|
void replyDeviceStorage(
|
||||||
|
const QJsonObject &args,
|
||||||
|
const QString &event,
|
||||||
|
QJsonObject response);
|
||||||
|
void deviceStorageFailed(const QJsonObject &args, QString error);
|
||||||
|
void secureStorageFailed(const QJsonObject &args);
|
||||||
void createButton(std::unique_ptr<Button> &button);
|
void createButton(std::unique_ptr<Button> &button);
|
||||||
void scheduleCloseWithConfirmation();
|
void scheduleCloseWithConfirmation();
|
||||||
void closeWithConfirmation();
|
void closeWithConfirmation();
|
||||||
|
|
Loading…
Add table
Reference in a new issue