mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 13:17:08 +02:00
Implement download of files in miniapps.
This commit is contained in:
parent
2fed657940
commit
341ab781b2
14 changed files with 721 additions and 31 deletions
|
@ -1042,6 +1042,8 @@ PRIVATE
|
|||
inline_bots/bot_attach_web_view.h
|
||||
inline_bots/inline_bot_confirm_prepared.cpp
|
||||
inline_bots/inline_bot_confirm_prepared.h
|
||||
inline_bots/inline_bot_downloads.cpp
|
||||
inline_bots/inline_bot_downloads.h
|
||||
inline_bots/inline_bot_layout_internal.cpp
|
||||
inline_bots/inline_bot_layout_internal.h
|
||||
inline_bots/inline_bot_layout_item.cpp
|
||||
|
|
|
@ -3428,6 +3428,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_bot_share_prepared_title" = "Share Message";
|
||||
"lng_bot_share_prepared_about" = "{bot} mini app suggests you to send this message to a chat you select.";
|
||||
"lng_bot_share_prepared_button" = "Share With...";
|
||||
"lng_bot_download_file" = "Download File";
|
||||
"lng_bot_download_file_sure" = "{bot} suggests you download the following file:";
|
||||
"lng_bot_download_file_button" = "Download";
|
||||
|
||||
"lng_bot_status_users#one" = "{count} monthly user";
|
||||
"lng_bot_status_users#other" = "{count} monthly users";
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "api/api_sending.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/base_file_utilities.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/random.h"
|
||||
#include "base/timer_rpl.h"
|
||||
|
@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "info/profile/info_profile_values.h"
|
||||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "inline_bots/inline_bot_confirm_prepared.h"
|
||||
#include "inline_bots/inline_bot_downloads.h"
|
||||
#include "iv/iv_instance.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
|
@ -569,7 +571,7 @@ void ConfirmEmojiStatusBox(
|
|||
not_null<Ui::GenericBox*> box,
|
||||
not_null<UserData*> bot,
|
||||
not_null<DocumentData*> document,
|
||||
TimeId until,
|
||||
TimeId duration,
|
||||
Fn<void(bool)> done) {
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
|
@ -608,7 +610,9 @@ void ConfirmEmojiStatusBox(
|
|||
object_ptr<Ui::RpWidget>::fromRaw(ownedSet.release()));
|
||||
|
||||
box->addButton(tr::lng_bot_emoji_status_confirm(), [=] {
|
||||
document->owner().emojiStatuses().set(document->id, until);
|
||||
document->owner().emojiStatuses().set(
|
||||
document->id,
|
||||
duration ? (base::unixtime::now() + duration) : 0);
|
||||
*set = true;
|
||||
box->closeBox();
|
||||
done(true);
|
||||
|
@ -1345,6 +1349,7 @@ void WebViewInstance::show(ShowArgs &&args) {
|
|||
|| v::is<WebViewSourceAttachMenu>(_source)
|
||||
|| (attached != end(bots)
|
||||
&& (attached->inAttachMenu || attached->inMainMenu));
|
||||
const auto downloads = &_session->attachWebView().downloads();
|
||||
_panelUrl = args.url;
|
||||
_panel = Ui::BotWebView::Show({
|
||||
.url = args.url,
|
||||
|
@ -1356,6 +1361,7 @@ void WebViewInstance::show(ShowArgs &&args) {
|
|||
.menuButtons = buttons,
|
||||
.fullscreen = args.fullscreen,
|
||||
.allowClipboardRead = allowClipboardRead,
|
||||
.downloadsProgress = downloads->downloadsProgress(_bot),
|
||||
});
|
||||
started(args.queryId);
|
||||
|
||||
|
@ -1862,7 +1868,7 @@ void WebViewInstance::botSetEmojiStatus(
|
|||
const auto bot = _bot;
|
||||
const auto panel = _panel.get();
|
||||
const auto callback = request.callback;
|
||||
const auto until = request.expirationDate;
|
||||
const auto duration = request.duration;
|
||||
if (!panel) {
|
||||
callback(u"UNKNOWN_ERROR"_q);
|
||||
return;
|
||||
|
@ -1879,10 +1885,40 @@ void WebViewInstance::botSetEmojiStatus(
|
|||
callback(success ? QString() : u"USER_DECLINED"_q);
|
||||
};
|
||||
panel->showBox(
|
||||
Box(ConfirmEmojiStatusBox, bot, document, until, done));
|
||||
Box(ConfirmEmojiStatusBox, bot, document, duration, done));
|
||||
}, [=] { callback(u"SUGGESTED_EMOJI_INVALID"_q); }, panel->lifetime());
|
||||
}
|
||||
|
||||
void WebViewInstance::botDownloadFile(
|
||||
Ui::BotWebView::DownloadFileRequest request) {
|
||||
const auto callback = request.callback;
|
||||
if (_confirmingDownload || !_panel) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
_confirmingDownload = true;
|
||||
const auto done = [=](QString path) {
|
||||
_confirmingDownload = false;
|
||||
if (path.isEmpty()) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
_bot->session().attachWebView().downloads().start({
|
||||
.bot = _bot,
|
||||
.url = request.url,
|
||||
.path = path,
|
||||
});
|
||||
callback(true);
|
||||
};
|
||||
_panel->showBox(Box(DownloadFileBox, DownloadBoxArgs{
|
||||
.session = &_bot->session(),
|
||||
.bot = _bot->name(),
|
||||
.name = base::FileNameFromUserString(request.name),
|
||||
.url = request.url,
|
||||
.done = done,
|
||||
}));
|
||||
}
|
||||
|
||||
void WebViewInstance::botOpenPrivacyPolicy() {
|
||||
const auto bot = _bot;
|
||||
const auto weak = _context.controller;
|
||||
|
@ -1995,6 +2031,7 @@ std::shared_ptr<Main::SessionShow> WebViewInstance::uiShow() {
|
|||
|
||||
AttachWebView::AttachWebView(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _downloads(std::make_unique<Downloads>(session))
|
||||
, _refreshTimer([=] { requestBots(); }) {
|
||||
_refreshTimer.callEach(kRefreshBotsTimeout);
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "api/api_common.h"
|
||||
#include "base/flags.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "dialogs/dialogs_key.h"
|
||||
#include "api/api_common.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "ui/chat/attach/attach_bot_webview.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
@ -51,6 +51,7 @@ enum class CheckoutResult;
|
|||
namespace InlineBots {
|
||||
|
||||
class WebViewInstance;
|
||||
class Downloads;
|
||||
|
||||
enum class PeerType : uint8 {
|
||||
SameBot = 0x01,
|
||||
|
@ -269,6 +270,8 @@ private:
|
|||
Ui::BotWebView::SendPreparedMessageRequest request) override;
|
||||
void botSetEmojiStatus(
|
||||
Ui::BotWebView::SetEmojiStatusRequest request) override;
|
||||
void botDownloadFile(
|
||||
Ui::BotWebView::DownloadFileRequest request) override;
|
||||
void botOpenPrivacyPolicy() override;
|
||||
void botClose() override;
|
||||
|
||||
|
@ -282,6 +285,7 @@ private:
|
|||
BotAppData *_app = nullptr;
|
||||
QString _appStartParam;
|
||||
bool _dataSent = false;
|
||||
bool _confirmingDownload = false;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
mtpRequestId _prolongId = 0;
|
||||
|
@ -300,6 +304,10 @@ public:
|
|||
explicit AttachWebView(not_null<Main::Session*> session);
|
||||
~AttachWebView();
|
||||
|
||||
[[nodiscard]] Downloads &downloads() const {
|
||||
return *_downloads;
|
||||
}
|
||||
|
||||
void open(WebViewDescriptor &&descriptor);
|
||||
void openByUsername(
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
@ -370,6 +378,7 @@ private:
|
|||
Fn<void(bool added)> callback = nullptr);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const std::unique_ptr<Downloads> _downloads;
|
||||
|
||||
base::Timer _refreshTimer;
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ namespace Data {
|
|||
class Thread;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
} // namespace Ui
|
||||
|
|
324
Telegram/SourceFiles/inline_bots/inline_bot_downloads.cpp
Normal file
324
Telegram/SourceFiles/inline_bots/inline_bot_downloads.cpp
Normal file
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
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_downloads.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer_id.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "storage/file_download_web.h"
|
||||
#include "storage/serialize_common.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/chat/attach/attach_bot_webview.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtCore/QDataStream>
|
||||
|
||||
namespace InlineBots {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDownloadsVersion = 1;
|
||||
constexpr auto kMaxDownloadsBots = 4096;
|
||||
constexpr auto kMaxDownloadsPerBot = 16384;
|
||||
|
||||
} // namespace
|
||||
|
||||
Downloads::Downloads(not_null<Main::Session*> session)
|
||||
: _session(session) {
|
||||
}
|
||||
|
||||
Downloads::~Downloads() = default;
|
||||
|
||||
DownloadId Downloads::start(StartArgs &&args) {
|
||||
read();
|
||||
|
||||
const auto botId = args.bot->id;
|
||||
const auto id = ++_autoIncrementId;
|
||||
auto &list = _lists[botId].list;
|
||||
list.push_back({
|
||||
.id = id,
|
||||
.url = std::move(args.url),
|
||||
.path = std::move(args.path),
|
||||
});
|
||||
auto &entry = list.back();
|
||||
auto &loader = _loaders[id];
|
||||
loader.botId = botId;
|
||||
loader.loader = std::make_unique<webFileLoader>(
|
||||
_session,
|
||||
entry.url,
|
||||
entry.path,
|
||||
WebRequestType::FullLoad);
|
||||
loader.loader->updates(
|
||||
) | rpl::start_with_next_error_done([=] {
|
||||
progress(botId, id);
|
||||
}, [=](FileLoader::Error) {
|
||||
fail(botId, id);
|
||||
}, [=] {
|
||||
done(botId, id);
|
||||
}, loader.loader->lifetime());
|
||||
loader.loader->start();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void Downloads::progress(PeerId botId, DownloadId id) {
|
||||
const auto i = _loaders.find(id);
|
||||
if (i == end(_loaders)) {
|
||||
return;
|
||||
}
|
||||
const auto &loader = i->second.loader;
|
||||
const auto total = loader->fullSize();
|
||||
const auto ready = loader->currentOffset();
|
||||
|
||||
auto &list = _lists[botId].list;
|
||||
const auto j = ranges::find(
|
||||
list,
|
||||
id,
|
||||
&DownloadsEntry::id);
|
||||
Assert(j != end(list));
|
||||
|
||||
if (total < 0
|
||||
|| ready > total
|
||||
|| (j->total && j->total != total)) {
|
||||
fail(botId, id);
|
||||
return;
|
||||
} else if (ready > total) {
|
||||
fail(botId, id);
|
||||
return;
|
||||
} else if (ready == total) {
|
||||
// Wait for 'done' signal.
|
||||
return;
|
||||
}
|
||||
applyProgress(botId, id, total, ready);
|
||||
}
|
||||
|
||||
void Downloads::fail(PeerId botId, DownloadId id) {
|
||||
const auto i = _loaders.find(id);
|
||||
if (i == end(_loaders)) {
|
||||
return;
|
||||
}
|
||||
auto loader = std::move(i->second.loader);
|
||||
_loaders.erase(i);
|
||||
loader = nullptr;
|
||||
|
||||
auto &list = _lists[botId].list;
|
||||
const auto k = ranges::find(
|
||||
list,
|
||||
id,
|
||||
&DownloadsEntry::id);
|
||||
Assert(k != end(list));
|
||||
k->ready = -1;
|
||||
}
|
||||
|
||||
void Downloads::done(PeerId botId, DownloadId id) {
|
||||
const auto i = _loaders.find(id);
|
||||
if (i == end(_loaders)) {
|
||||
return;
|
||||
}
|
||||
auto &list = _lists[botId].list;
|
||||
const auto j = ranges::find(
|
||||
list,
|
||||
id,
|
||||
&DownloadsEntry::id);
|
||||
Assert(j != end(list));
|
||||
|
||||
const auto total = i->second.loader->fullSize();
|
||||
if (total <= 0 || (j->total && j->total != total)) {
|
||||
fail(botId, id);
|
||||
return;
|
||||
}
|
||||
_loaders.erase(i);
|
||||
|
||||
applyProgress(botId, id, total, total);
|
||||
}
|
||||
|
||||
void Downloads::applyProgress(
|
||||
PeerId botId,
|
||||
DownloadId id,
|
||||
int64 total,
|
||||
int64 ready) {
|
||||
Expects(total > 0);
|
||||
Expects(ready >= 0 && ready <= total);
|
||||
|
||||
auto &list = _lists[botId].list;
|
||||
const auto j = ranges::find(
|
||||
list,
|
||||
id,
|
||||
&DownloadsEntry::id);
|
||||
Assert(j != end(list));
|
||||
|
||||
auto &progress = _progressView[botId];
|
||||
auto current = progress.current();
|
||||
if (!j->total) {
|
||||
j->total = total;
|
||||
current.total += total;
|
||||
}
|
||||
if (j->ready != ready) {
|
||||
const auto delta = ready - j->ready;
|
||||
j->ready = ready;
|
||||
current.ready += delta;
|
||||
}
|
||||
|
||||
if (total == ready) {
|
||||
write();
|
||||
}
|
||||
|
||||
progress = current;
|
||||
if (current.ready == current.total) {
|
||||
progress = DownloadsProgress();
|
||||
}
|
||||
}
|
||||
|
||||
void Downloads::cancel(DownloadId id) {
|
||||
const auto i = _loaders.find(id);
|
||||
if (i == end(_loaders)) {
|
||||
return;
|
||||
}
|
||||
const auto botId = i->second.botId;
|
||||
fail(botId, id);
|
||||
|
||||
auto &list = _lists[botId].list;
|
||||
list.erase(
|
||||
ranges::remove(list, id, &DownloadsEntry::id),
|
||||
end(list));
|
||||
|
||||
auto &progress = _progressView[botId];
|
||||
progress.force_assign(progress.current());
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Downloads::downloadsProgress(not_null<UserData*> bot)
|
||||
->rpl::producer<DownloadsProgress> {
|
||||
read();
|
||||
|
||||
return _progressView[bot->id].value();
|
||||
}
|
||||
|
||||
void Downloads::read() {
|
||||
auto bytes = _session->local().readInlineBotsDownloads();
|
||||
if (bytes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Assert(_lists.empty());
|
||||
|
||||
auto stream = QDataStream(&bytes, QIODevice::ReadOnly);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
quint32 version = 0, count = 0;
|
||||
stream >> version;
|
||||
if (version != kDownloadsVersion) {
|
||||
return;
|
||||
}
|
||||
stream >> count;
|
||||
if (!count || count > kMaxDownloadsBots) {
|
||||
return;
|
||||
}
|
||||
auto lists = base::flat_map<PeerId, List>();
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
quint64 rawBotId = 0;
|
||||
quint32 count = 0;
|
||||
stream >> rawBotId >> count;
|
||||
const auto botId = DeserializePeerId(rawBotId);
|
||||
if (!botId
|
||||
|| !peerIsUser(botId)
|
||||
|| count > kMaxDownloadsPerBot
|
||||
|| lists.contains(botId)) {
|
||||
return;
|
||||
}
|
||||
auto &list = lists[botId];
|
||||
list.list.reserve(count);
|
||||
for (auto j = 0; j != count; ++j) {
|
||||
auto entry = DownloadsEntry();
|
||||
stream >> entry.url >> entry.path >> entry.total;
|
||||
entry.ready = entry.total;
|
||||
entry.id = ++_autoIncrementId;
|
||||
list.list.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
_lists = std::move(lists);
|
||||
}
|
||||
|
||||
void Downloads::write() {
|
||||
auto size = sizeof(quint32) // version
|
||||
+ sizeof(quint32); // lists count
|
||||
|
||||
for (const auto &[botId, list] : _lists) {
|
||||
size += sizeof(quint64) // botId
|
||||
+ sizeof(quint32); // list count
|
||||
for (const auto &entry : list.list) {
|
||||
if (entry.total > 0 && entry.ready == entry.total) {
|
||||
size += Serialize::stringSize(entry.url)
|
||||
+ Serialize::stringSize(entry.path)
|
||||
+ sizeof(quint64); // total
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto bytes = QByteArray();
|
||||
bytes.reserve(size);
|
||||
auto buffer = QBuffer(&bytes);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
auto stream = QDataStream(&buffer);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
stream << quint32(kDownloadsVersion) << quint32(_lists.size());
|
||||
|
||||
for (const auto &[botId, list] : _lists) {
|
||||
stream << SerializePeerId(botId) << quint32(list.list.size());
|
||||
for (const auto &entry : list.list) {
|
||||
if (entry.total > 0 && entry.ready == entry.total) {
|
||||
stream << entry.url << entry.path << entry.total;
|
||||
}
|
||||
}
|
||||
}
|
||||
buffer.close();
|
||||
|
||||
_session->local().writeInlineBotsDownloads(bytes);
|
||||
}
|
||||
|
||||
void DownloadFileBox(not_null<Ui::GenericBox*> box, DownloadBoxArgs args) {
|
||||
Expects(!args.name.isEmpty());
|
||||
|
||||
box->setTitle(tr::lng_bot_download_file());
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_bot_download_file_sure(
|
||||
lt_bot,
|
||||
rpl::single(Ui::Text::Bold(args.bot)),
|
||||
Ui::Text::RichLangValue),
|
||||
st::botDownloadLabel));
|
||||
//box->addRow(MakeFilePreview(box, args));
|
||||
const auto done = std::move(args.done);
|
||||
const auto name = args.name;
|
||||
const auto session = args.session;
|
||||
box->addButton(tr::lng_bot_download_file_button(), [=] {
|
||||
const auto path = FileNameForSave(
|
||||
session,
|
||||
tr::lng_save_file(tr::now),
|
||||
QString(),
|
||||
u"file"_q,
|
||||
name,
|
||||
false,
|
||||
QDir());
|
||||
if (!path.isEmpty()) {
|
||||
box->closeBox();
|
||||
done(path);
|
||||
}
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
done(QString());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace InlineBots
|
99
Telegram/SourceFiles/inline_bots/inline_bot_downloads.h
Normal file
99
Telegram/SourceFiles/inline_bots/inline_bot_downloads.h
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
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 webFileLoader;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::BotWebView {
|
||||
struct DownloadsProgress;
|
||||
} // namespace Ui::BotWebView
|
||||
|
||||
namespace InlineBots {
|
||||
|
||||
using DownloadId = uint32;
|
||||
|
||||
using ::Ui::BotWebView::DownloadsProgress;
|
||||
|
||||
struct DownloadsEntry {
|
||||
DownloadId id = 0;
|
||||
QString url;
|
||||
QString path;
|
||||
uint64 ready = 0;
|
||||
uint64 total = 0;
|
||||
};
|
||||
|
||||
class Downloads final {
|
||||
public:
|
||||
explicit Downloads(not_null<Main::Session*> session);
|
||||
~Downloads();
|
||||
|
||||
struct StartArgs {
|
||||
not_null<UserData*> bot;
|
||||
QString url;
|
||||
QString path;
|
||||
};
|
||||
uint32 start(StartArgs &&args); // Returns download id.
|
||||
|
||||
void cancel(DownloadId id);
|
||||
|
||||
[[nodiscard]] auto downloadsProgress(not_null<UserData*> bot)
|
||||
-> rpl::producer<DownloadsProgress>;
|
||||
|
||||
private:
|
||||
struct List {
|
||||
std::vector<DownloadsEntry> list;
|
||||
bool checked = false;
|
||||
};
|
||||
struct Loader {
|
||||
std::unique_ptr<webFileLoader> loader;
|
||||
PeerId botId = 0;
|
||||
};
|
||||
|
||||
void read();
|
||||
void write();
|
||||
|
||||
void progress(PeerId botId, DownloadId id);
|
||||
void fail(PeerId botId, DownloadId id);
|
||||
void done(PeerId botId, DownloadId id);
|
||||
void applyProgress(
|
||||
PeerId botId,
|
||||
DownloadId id,
|
||||
int64 total,
|
||||
int64 ready);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::flat_map<PeerId, List> _lists;
|
||||
base::flat_map<DownloadId, Loader> _loaders;
|
||||
|
||||
base::flat_map<
|
||||
PeerId,
|
||||
rpl::variable<DownloadsProgress>> _progressView;
|
||||
|
||||
DownloadId _autoIncrementId = 0;
|
||||
|
||||
};
|
||||
|
||||
struct DownloadBoxArgs {
|
||||
not_null<Main::Session*> session;
|
||||
QString bot;
|
||||
QString name;
|
||||
QString url;
|
||||
Fn<void(QString)> done;
|
||||
};
|
||||
void DownloadFileBox(not_null<Ui::GenericBox*> box, DownloadBoxArgs args);
|
||||
|
||||
} // namespace InlineBots
|
|
@ -18,6 +18,7 @@ namespace {
|
|||
constexpr auto kMaxWebFileQueries = 8;
|
||||
constexpr auto kMaxHttpRedirects = 5;
|
||||
constexpr auto kResetDownloadPrioritiesTimeout = crl::time(200);
|
||||
constexpr auto kMaxWebFile = 4000 * int64(1024 * 1024);
|
||||
|
||||
std::weak_ptr<WebLoadManager> GlobalLoadManager;
|
||||
|
||||
|
@ -41,6 +42,7 @@ enum class Error {
|
|||
struct Progress {
|
||||
qint64 ready = 0;
|
||||
qint64 total = 0;
|
||||
QByteArray streamed;
|
||||
};
|
||||
|
||||
using Update = std::variant<Progress, QByteArray, Error>;
|
||||
|
@ -67,10 +69,12 @@ private:
|
|||
struct Enqueued {
|
||||
int id = 0;
|
||||
QString url;
|
||||
bool stream = false;
|
||||
};
|
||||
struct Sent {
|
||||
QString url;
|
||||
not_null<QNetworkReply*> reply;
|
||||
bool stream = false;
|
||||
QByteArray data;
|
||||
int64 ready = 0;
|
||||
int64 total = 0;
|
||||
|
@ -81,7 +85,7 @@ private:
|
|||
void handleNetworkErrors();
|
||||
|
||||
// Worker thread.
|
||||
void enqueue(int id, const QString &url);
|
||||
void enqueue(int id, const QString &url, bool stream);
|
||||
void remove(int id);
|
||||
void resetGeneration();
|
||||
void checkSendNext();
|
||||
|
@ -107,7 +111,11 @@ private:
|
|||
void failed(int id, not_null<QNetworkReply*> reply);
|
||||
void finished(int id, not_null<QNetworkReply*> reply);
|
||||
void deleteDeferred(not_null<QNetworkReply*> reply);
|
||||
void queueProgressUpdate(int id, int64 ready, int64 total);
|
||||
void queueProgressUpdate(
|
||||
int id,
|
||||
int64 ready,
|
||||
int64 total,
|
||||
QByteArray streamed);
|
||||
void queueFailedUpdate(int id);
|
||||
void queueFinishedUpdate(int id, const QByteArray &data);
|
||||
void clear();
|
||||
|
@ -187,8 +195,9 @@ void WebLoadManager::enqueue(not_null<webFileLoader*> loader) {
|
|||
: _ids.emplace(loader, ++_autoincrement).first->second;
|
||||
}();
|
||||
const auto url = loader->url();
|
||||
const auto stream = loader->streamLoading();
|
||||
InvokeQueued(_network.get(), [=] {
|
||||
enqueue(id, url);
|
||||
enqueue(id, url, stream);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -204,7 +213,7 @@ void WebLoadManager::remove(not_null<webFileLoader*> loader) {
|
|||
});
|
||||
}
|
||||
|
||||
void WebLoadManager::enqueue(int id, const QString &url) {
|
||||
void WebLoadManager::enqueue(int id, const QString &url, bool stream) {
|
||||
const auto i = ranges::find(_queue, id, &Enqueued::id);
|
||||
if (i != end(_queue)) {
|
||||
return;
|
||||
|
@ -212,7 +221,7 @@ void WebLoadManager::enqueue(int id, const QString &url) {
|
|||
_previousGeneration.erase(
|
||||
ranges::remove(_previousGeneration, id, &Enqueued::id),
|
||||
end(_previousGeneration));
|
||||
_queue.push_back(Enqueued{ id, url });
|
||||
_queue.push_back(Enqueued{ id, url, stream });
|
||||
if (!_resetGenerationTimer.isActive()) {
|
||||
_resetGenerationTimer.callOnce(kResetDownloadPrioritiesTimeout);
|
||||
}
|
||||
|
@ -253,7 +262,7 @@ void WebLoadManager::checkSendNext() {
|
|||
void WebLoadManager::send(const Enqueued &entry) {
|
||||
const auto id = entry.id;
|
||||
const auto url = entry.url;
|
||||
_sent.emplace(id, Sent{ url, send(id, url) });
|
||||
_sent.emplace(id, Sent{ url, send(id, url), entry.stream });
|
||||
}
|
||||
|
||||
void WebLoadManager::removeSent(int id) {
|
||||
|
@ -294,6 +303,13 @@ void WebLoadManager::progress(
|
|||
not_null<QNetworkReply*> reply,
|
||||
int64 ready,
|
||||
int64 total) {
|
||||
if (total <= 0) {
|
||||
const auto originalContentLength = reply->attribute(
|
||||
QNetworkRequest::OriginalContentLengthAttribute);
|
||||
if (originalContentLength.isValid()) {
|
||||
total = originalContentLength.toLongLong();
|
||||
}
|
||||
}
|
||||
const auto statusCode = reply->attribute(
|
||||
QNetworkRequest::HttpStatusCodeAttribute);
|
||||
const auto status = statusCode.isValid() ? statusCode.toInt() : 200;
|
||||
|
@ -338,10 +354,7 @@ void WebLoadManager::notify(
|
|||
if (const auto sent = findSent(id, reply)) {
|
||||
sent->ready = ready;
|
||||
sent->total = std::max(total, int64(0));
|
||||
sent->data.append(reply->readAll());
|
||||
if (total == 0
|
||||
|| total > Storage::kMaxFileInMemory
|
||||
|| sent->data.size() > Storage::kMaxFileInMemory) {
|
||||
if (total <= 0) {
|
||||
LOG(("Network Error: "
|
||||
"Bad size received for HTTP download progress "
|
||||
"in WebLoadManager::onProgress(): %1 / %2 (bytes %3)"
|
||||
|
@ -349,10 +362,43 @@ void WebLoadManager::notify(
|
|||
).arg(total
|
||||
).arg(sent->data.size()));
|
||||
failed(id, reply);
|
||||
} else if (total > 0 && ready >= total) {
|
||||
finished(id, reply);
|
||||
return;
|
||||
}
|
||||
auto bytes = reply->readAll();
|
||||
if (sent->stream) {
|
||||
if (total > kMaxWebFile) {
|
||||
LOG(("Network Error: "
|
||||
"Bad size received for HTTP download progress "
|
||||
"in WebLoadManager::onProgress(): %1 / %2"
|
||||
).arg(ready
|
||||
).arg(total));
|
||||
failed(id, reply);
|
||||
} else {
|
||||
queueProgressUpdate(
|
||||
id,
|
||||
sent->ready,
|
||||
sent->total,
|
||||
std::move(bytes));
|
||||
if (ready >= total) {
|
||||
finished(id, reply);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
queueProgressUpdate(id, sent->ready, sent->total);
|
||||
sent->data.append(std::move(bytes));
|
||||
if (total > Storage::kMaxFileInMemory
|
||||
|| sent->data.size() > Storage::kMaxFileInMemory) {
|
||||
LOG(("Network Error: "
|
||||
"Bad size received for HTTP download progress "
|
||||
"in WebLoadManager::onProgress(): %1 / %2 (bytes %3)"
|
||||
).arg(ready
|
||||
).arg(total
|
||||
).arg(sent->data.size()));
|
||||
failed(id, reply);
|
||||
} else if (ready >= total) {
|
||||
finished(id, reply);
|
||||
} else {
|
||||
queueProgressUpdate(id, sent->ready, sent->total, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -406,9 +452,13 @@ void WebLoadManager::clear() {
|
|||
}
|
||||
}
|
||||
|
||||
void WebLoadManager::queueProgressUpdate(int id, int64 ready, int64 total) {
|
||||
crl::on_main(this, [=] {
|
||||
sendUpdate(id, Progress{ ready, total });
|
||||
void WebLoadManager::queueProgressUpdate(
|
||||
int id,
|
||||
int64 ready,
|
||||
int64 total,
|
||||
QByteArray streamed) {
|
||||
crl::on_main(this, [=, bytes = std::move(streamed)]() mutable {
|
||||
sendUpdate(id, Progress{ ready, total, std::move(bytes) });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -458,6 +508,25 @@ webFileLoader::webFileLoader(
|
|||
, _url(url) {
|
||||
}
|
||||
|
||||
webFileLoader::webFileLoader(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &url,
|
||||
const QString &path,
|
||||
WebRequestType type)
|
||||
: FileLoader(
|
||||
session,
|
||||
path,
|
||||
0,
|
||||
0,
|
||||
UnknownFileLocation,
|
||||
LoadToFileOnly,
|
||||
LoadFromCloudOrLocal,
|
||||
false,
|
||||
0)
|
||||
, _url(url)
|
||||
, _requestType(type) {
|
||||
}
|
||||
|
||||
webFileLoader::~webFileLoader() {
|
||||
if (!_finished) {
|
||||
cancel();
|
||||
|
@ -468,6 +537,14 @@ QString webFileLoader::url() const {
|
|||
return _url;
|
||||
}
|
||||
|
||||
WebRequestType webFileLoader::requestType() const {
|
||||
return _requestType;
|
||||
}
|
||||
|
||||
bool webFileLoader::streamLoading() const {
|
||||
return (_toCache == LoadToFileOnly);
|
||||
}
|
||||
|
||||
void webFileLoader::startLoading() {
|
||||
if (_finished) {
|
||||
return;
|
||||
|
@ -477,7 +554,10 @@ void webFileLoader::startLoading() {
|
|||
this
|
||||
) | rpl::start_with_next([=](const Update &data) {
|
||||
if (const auto progress = std::get_if<Progress>(&data)) {
|
||||
loadProgress(progress->ready, progress->total);
|
||||
loadProgress(
|
||||
progress->ready,
|
||||
progress->total,
|
||||
progress->streamed);
|
||||
} else if (const auto bytes = std::get_if<QByteArray>(&data)) {
|
||||
loadFinished(*bytes);
|
||||
} else {
|
||||
|
@ -492,10 +572,19 @@ int64 webFileLoader::currentOffset() const {
|
|||
return _ready;
|
||||
}
|
||||
|
||||
void webFileLoader::loadProgress(qint64 ready, qint64 total) {
|
||||
void webFileLoader::loadProgress(
|
||||
qint64 ready,
|
||||
qint64 total,
|
||||
const QByteArray &streamed) {
|
||||
_fullSize = _loadSize = total;
|
||||
_ready = ready;
|
||||
notifyAboutProgress();
|
||||
if (!streamed.isEmpty()
|
||||
&& !writeResultPart(_streamedOffset, bytes::make_span(streamed))) {
|
||||
loadFailed();
|
||||
} else {
|
||||
_streamedOffset += streamed.size();
|
||||
notifyAboutProgress();
|
||||
}
|
||||
}
|
||||
|
||||
void webFileLoader::loadFinished(const QByteArray &data) {
|
||||
|
|
|
@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
class WebLoadManager;
|
||||
|
||||
enum class WebRequestType {
|
||||
FullLoad,
|
||||
OnlySize,
|
||||
};
|
||||
|
||||
class webFileLoader final : public FileLoader {
|
||||
public:
|
||||
webFileLoader(
|
||||
|
@ -20,9 +25,16 @@ public:
|
|||
LoadFromCloudSetting fromCloud,
|
||||
bool autoLoading,
|
||||
uint8 cacheTag);
|
||||
webFileLoader(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &url,
|
||||
const QString &path,
|
||||
WebRequestType type);
|
||||
~webFileLoader();
|
||||
|
||||
[[nodiscard]] QString url() const;
|
||||
[[nodiscard]] WebRequestType requestType() const;
|
||||
[[nodiscard]] bool streamLoading() const;
|
||||
|
||||
int64 currentOffset() const override;
|
||||
|
||||
|
@ -33,12 +45,17 @@ private:
|
|||
Storage::Cache::Key cacheKey() const override;
|
||||
std::optional<MediaKey> fileLocationKey() const override;
|
||||
|
||||
void loadProgress(qint64 ready, qint64 size);
|
||||
void loadProgress(
|
||||
qint64 ready,
|
||||
qint64 size,
|
||||
const QByteArray &streamed);
|
||||
void loadFinished(const QByteArray &data);
|
||||
void loadFailed();
|
||||
|
||||
const QString _url;
|
||||
int64 _ready = 0;
|
||||
int64 _streamedOffset = 0;
|
||||
WebRequestType _requestType = {};
|
||||
|
||||
std::shared_ptr<WebLoadManager> _manager;
|
||||
rpl::lifetime _managerLifetime;
|
||||
|
|
|
@ -95,6 +95,7 @@ enum { // Local Storage Keys
|
|||
lskSearchSuggestions = 0x18, // no data
|
||||
lskWebviewTokens = 0x19, // data: QByteArray bots, QByteArray other
|
||||
lskRoundPlaceholder = 0x1a, // no data
|
||||
lskInlineBotsDownloads = 0x1b, // no data
|
||||
};
|
||||
|
||||
auto EmptyMessageDraftSources()
|
||||
|
@ -222,6 +223,7 @@ base::flat_set<QString> Account::collectGoodNames() const {
|
|||
_archivedCustomEmojiKey,
|
||||
_searchSuggestionsKey,
|
||||
_roundPlaceholderKey,
|
||||
_inlineBotsDownloadsKey,
|
||||
};
|
||||
auto result = base::flat_set<QString>{
|
||||
"map0",
|
||||
|
@ -309,6 +311,7 @@ Account::ReadMapResult Account::readMapWith(
|
|||
quint64 userSettingsKey = 0, recentHashtagsAndBotsKey = 0, exportSettingsKey = 0;
|
||||
quint64 searchSuggestionsKey = 0;
|
||||
quint64 roundPlaceholderKey = 0;
|
||||
quint64 inlineBotsDownloadsKey = 0;
|
||||
QByteArray webviewStorageTokenBots, webviewStorageTokenOther;
|
||||
while (!map.stream.atEnd()) {
|
||||
quint32 keyType;
|
||||
|
@ -421,6 +424,9 @@ Account::ReadMapResult Account::readMapWith(
|
|||
case lskRoundPlaceholder: {
|
||||
map.stream >> roundPlaceholderKey;
|
||||
} break;
|
||||
case lskInlineBotsDownloads: {
|
||||
map.stream >> inlineBotsDownloadsKey;
|
||||
} break;
|
||||
case lskWebviewTokens: {
|
||||
map.stream
|
||||
>> webviewStorageTokenBots
|
||||
|
@ -463,6 +469,7 @@ Account::ReadMapResult Account::readMapWith(
|
|||
_exportSettingsKey = exportSettingsKey;
|
||||
_searchSuggestionsKey = searchSuggestionsKey;
|
||||
_roundPlaceholderKey = roundPlaceholderKey;
|
||||
_inlineBotsDownloadsKey = inlineBotsDownloadsKey;
|
||||
_oldMapVersion = mapData.version;
|
||||
_webviewStorageIdBots.token = webviewStorageTokenBots;
|
||||
_webviewStorageIdOther.token = webviewStorageTokenOther;
|
||||
|
@ -578,6 +585,7 @@ void Account::writeMap() {
|
|||
+ Serialize::bytearraySize(_webviewStorageIdOther.token);
|
||||
}
|
||||
if (_roundPlaceholderKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||
if (_inlineBotsDownloadsKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||
|
||||
EncryptedDescriptor mapData(mapSize);
|
||||
if (!self.isEmpty()) {
|
||||
|
@ -652,6 +660,10 @@ void Account::writeMap() {
|
|||
mapData.stream << quint32(lskRoundPlaceholder);
|
||||
mapData.stream << quint64(_roundPlaceholderKey);
|
||||
}
|
||||
if (_inlineBotsDownloadsKey) {
|
||||
mapData.stream << quint32(lskInlineBotsDownloads);
|
||||
mapData.stream << quint64(_inlineBotsDownloadsKey);
|
||||
}
|
||||
map.writeEncrypted(mapData, _localKey);
|
||||
|
||||
_mapChanged = false;
|
||||
|
@ -682,6 +694,7 @@ void Account::reset() {
|
|||
_settingsKey = _recentHashtagsAndBotsKey = _exportSettingsKey = 0;
|
||||
_searchSuggestionsKey = 0;
|
||||
_roundPlaceholderKey = 0;
|
||||
_inlineBotsDownloadsKey = 0;
|
||||
_oldMapVersion = 0;
|
||||
_fileLocations.clear();
|
||||
_fileLocationPairs.clear();
|
||||
|
@ -3239,6 +3252,44 @@ void Account::writeRoundPlaceholder(const QImage &placeholder) {
|
|||
file.writeEncrypted(data, _localKey);
|
||||
}
|
||||
|
||||
QByteArray Account::readInlineBotsDownloads() {
|
||||
if (_inlineBotsDownloadsRead) {
|
||||
return QByteArray();
|
||||
}
|
||||
_inlineBotsDownloadsRead = true;
|
||||
if (!_inlineBotsDownloadsKey) {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
FileReadDescriptor inlineBotsDownloads;
|
||||
if (!ReadEncryptedFile(
|
||||
inlineBotsDownloads,
|
||||
_inlineBotsDownloadsKey,
|
||||
_basePath,
|
||||
_localKey)) {
|
||||
ClearKey(_inlineBotsDownloadsKey, _basePath);
|
||||
_inlineBotsDownloadsKey = 0;
|
||||
writeMapDelayed();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
auto bytes = QByteArray();
|
||||
inlineBotsDownloads.stream >> bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void Account::writeInlineBotsDownloads(const QByteArray &bytes) {
|
||||
if (!_inlineBotsDownloadsKey) {
|
||||
_inlineBotsDownloadsKey = GenerateKey(_basePath);
|
||||
writeMapQueued();
|
||||
}
|
||||
quint32 size = Serialize::bytearraySize(bytes);
|
||||
EncryptedDescriptor data(size);
|
||||
data.stream << bytes;
|
||||
FileWriteDescriptor file(_inlineBotsDownloadsKey, _basePath);
|
||||
file.writeEncrypted(data, _localKey);
|
||||
}
|
||||
|
||||
bool Account::encrypt(
|
||||
const void *src,
|
||||
void *dst,
|
||||
|
|
|
@ -177,6 +177,9 @@ public:
|
|||
[[nodiscard]] QImage readRoundPlaceholder();
|
||||
void writeRoundPlaceholder(const QImage &placeholder);
|
||||
|
||||
[[nodiscard]] QByteArray readInlineBotsDownloads();
|
||||
void writeInlineBotsDownloads(const QByteArray &bytes);
|
||||
|
||||
[[nodiscard]] bool encrypt(
|
||||
const void *src,
|
||||
void *dst,
|
||||
|
@ -306,6 +309,7 @@ private:
|
|||
FileKey _archivedCustomEmojiKey = 0;
|
||||
FileKey _searchSuggestionsKey = 0;
|
||||
FileKey _roundPlaceholderKey = 0;
|
||||
FileKey _inlineBotsDownloadsKey = 0;
|
||||
|
||||
qint64 _cacheTotalSizeLimit = 0;
|
||||
qint64 _cacheBigFileTotalSizeLimit = 0;
|
||||
|
@ -317,6 +321,7 @@ private:
|
|||
bool _readingUserSettings = false;
|
||||
bool _recentHashtagsAndBotsWereRead = false;
|
||||
bool _searchSuggestionsRead = false;
|
||||
bool _inlineBotsDownloadsRead = false;
|
||||
|
||||
Webview::StorageId _webviewStorageIdBots;
|
||||
Webview::StorageId _webviewStorageIdOther;
|
||||
|
|
|
@ -786,6 +786,8 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) {
|
|||
} else {
|
||||
sendFullScreen();
|
||||
}
|
||||
} else if (command == "web_app_request_file_download") {
|
||||
processDownloadRequest(arguments);
|
||||
} else if (command == "web_app_exit_fullscreen") {
|
||||
if (_fullscreen.current()) {
|
||||
_fullscreen = false;
|
||||
|
@ -1008,17 +1010,17 @@ void Panel::processEmojiStatusRequest(const QJsonObject &args) {
|
|||
return;
|
||||
}
|
||||
const auto emojiId = args["custom_emoji_id"].toString().toULongLong();
|
||||
const auto expirationDate = TimeId(base::SafeRound(
|
||||
args["expiration_date"].toDouble()));
|
||||
const auto duration = TimeId(base::SafeRound(
|
||||
args["duration"].toDouble()));
|
||||
if (!emojiId) {
|
||||
postEvent(
|
||||
"emoji_status_failed",
|
||||
"{ error: \"SUGGESTED_EMOJI_INVALID\" }");
|
||||
return;
|
||||
} else if (expirationDate < 0) {
|
||||
} else if (duration < 0) {
|
||||
postEvent(
|
||||
"emoji_status_failed",
|
||||
"{ error: \"EXPIRATION_DATE_INVALID\" }");
|
||||
"{ error: \"DURATION_INVALID\" }");
|
||||
return;
|
||||
}
|
||||
auto callback = crl::guard(this, [=](QString error) {
|
||||
|
@ -1032,7 +1034,7 @@ void Panel::processEmojiStatusRequest(const QJsonObject &args) {
|
|||
});
|
||||
_delegate->botSetEmojiStatus({
|
||||
.customEmojiId = emojiId,
|
||||
.expirationDate = expirationDate,
|
||||
.duration = duration,
|
||||
.callback = std::move(callback),
|
||||
});
|
||||
}
|
||||
|
@ -1514,6 +1516,34 @@ void Panel::processBottomBarColor(const QJsonObject &args) {
|
|||
}
|
||||
}
|
||||
|
||||
void Panel::processDownloadRequest(const QJsonObject &args) {
|
||||
if (args.isEmpty()) {
|
||||
_delegate->botClose();
|
||||
return;
|
||||
}
|
||||
const auto url = args["url"].toString();
|
||||
const auto name = args["file_name"].toString();
|
||||
if (url.isEmpty()) {
|
||||
LOG(("BotWebView Error: Bad 'url' in download request."));
|
||||
_delegate->botClose();
|
||||
return;
|
||||
} else if (name.isEmpty()) {
|
||||
LOG(("BotWebView Error: Bad 'file_name' in download request."));
|
||||
_delegate->botClose();
|
||||
return;
|
||||
}
|
||||
const auto done = crl::guard(this, [=](bool started) {
|
||||
postEvent("file_download_requested", started
|
||||
? "{ status: \"downloading\" }"
|
||||
: "{ status: \"cancelled\" }");
|
||||
});
|
||||
_delegate->botDownloadFile({
|
||||
.url = url,
|
||||
.name = name,
|
||||
.callback = done,
|
||||
});
|
||||
}
|
||||
|
||||
void Panel::createButton(std::unique_ptr<Button> &button) {
|
||||
if (!_bottomButtonsBg) {
|
||||
_bottomButtonsBg = std::make_unique<RpWidget>(_widget.get());
|
||||
|
|
|
@ -53,10 +53,25 @@ struct CustomMethodRequest {
|
|||
|
||||
struct SetEmojiStatusRequest {
|
||||
uint64 customEmojiId = 0;
|
||||
TimeId expirationDate = 0;
|
||||
TimeId duration = 0;
|
||||
Fn<void(QString)> callback;
|
||||
};
|
||||
|
||||
struct DownloadFileRequest {
|
||||
QString url;
|
||||
QString name;
|
||||
Fn<void(bool)> callback;
|
||||
};
|
||||
|
||||
struct DownloadsProgress {
|
||||
uint64 ready = 0;
|
||||
uint64 total = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const DownloadsProgress &a,
|
||||
const DownloadsProgress &b) = default;
|
||||
};
|
||||
|
||||
struct SendPreparedMessageRequest {
|
||||
QString id = 0;
|
||||
Fn<void(QString)> callback;
|
||||
|
@ -81,6 +96,7 @@ public:
|
|||
virtual void botSharePhone(Fn<void(bool shared)> callback) = 0;
|
||||
virtual void botInvokeCustomMethod(CustomMethodRequest request) = 0;
|
||||
virtual void botSetEmojiStatus(SetEmojiStatusRequest request) = 0;
|
||||
virtual void botDownloadFile(DownloadFileRequest request) = 0;
|
||||
virtual void botSendPreparedMessage(
|
||||
SendPreparedMessageRequest request) = 0;
|
||||
virtual void botOpenPrivacyPolicy() = 0;
|
||||
|
@ -97,6 +113,7 @@ struct Args {
|
|||
MenuButtons menuButtons;
|
||||
bool fullscreen = false;
|
||||
bool allowClipboardRead = false;
|
||||
rpl::producer<DownloadsProgress> downloadsProgress;
|
||||
};
|
||||
|
||||
class Panel final : public base::has_weak_ptr {
|
||||
|
@ -155,6 +172,7 @@ private:
|
|||
void processHeaderColor(const QJsonObject &args);
|
||||
void processBackgroundColor(const QJsonObject &args);
|
||||
void processBottomBarColor(const QJsonObject &args);
|
||||
void processDownloadRequest(const QJsonObject &args);
|
||||
void openTgLink(const QJsonObject &args);
|
||||
void openExternalLink(const QJsonObject &args);
|
||||
void openInvoice(const QJsonObject &args);
|
||||
|
|
|
@ -1181,3 +1181,5 @@ botEmojiStatusName: FlatLabel(defaultFlatLabel) {
|
|||
botEmojiStatusEmoji: FlatLabel(botEmojiStatusName) {
|
||||
textFg: profileVerifiedCheckBg;
|
||||
}
|
||||
|
||||
botDownloadLabel: boxLabel;
|
||||
|
|
Loading…
Add table
Reference in a new issue