544 linhas
14 KiB
C++
544 linhas
14 KiB
C++
/*
|
|
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 "api/api_stickers_creator.h"
|
|
|
|
#include "apiwrap.h"
|
|
#include "base/random.h"
|
|
#include "base/unixtime.h"
|
|
#include "chat_helpers/compose/compose_show.h"
|
|
#include "data/data_document.h"
|
|
#include "data/data_file_origin.h"
|
|
#include "data/data_session.h"
|
|
#include "data/stickers/data_stickers.h"
|
|
#include "data/stickers/data_stickers_set.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "main/main_session.h"
|
|
#include "menu/menu_action_with_thumbnail.h"
|
|
#include "storage/file_upload.h"
|
|
#include "storage/localimageloader.h"
|
|
#include "styles/style_menu_icons.h"
|
|
#include "styles/style_widgets.h"
|
|
#include "ui/dynamic_thumbnails.h"
|
|
#include "ui/widgets/menu/menu_add_action_callback.h"
|
|
#include "ui/widgets/menu/menu_common.h"
|
|
#include "ui/widgets/popup_menu.h"
|
|
|
|
namespace Api {
|
|
namespace {
|
|
|
|
constexpr auto kStickerSide = 512;
|
|
|
|
[[nodiscard]] MTPInputStickerSetItem InputItem(
|
|
const MTPInputDocument &document,
|
|
const QString &emoji) {
|
|
return MTP_inputStickerSetItem(
|
|
MTP_flags(0),
|
|
document,
|
|
MTP_string(emoji),
|
|
MTPMaskCoords(),
|
|
MTPstring());
|
|
}
|
|
|
|
[[nodiscard]] std::shared_ptr<FilePrepareResult> PrepareStickerWebp(
|
|
MTP::DcId dcId,
|
|
DocumentId id,
|
|
const QByteArray &bytes) {
|
|
const auto filename = u"sticker.webp"_q;
|
|
auto attributes = QVector<MTPDocumentAttribute>(
|
|
1,
|
|
MTP_documentAttributeFilename(MTP_string(filename)));
|
|
attributes.push_back(MTP_documentAttributeImageSize(
|
|
MTP_int(kStickerSide),
|
|
MTP_int(kStickerSide)));
|
|
|
|
auto result = MakePreparedFile({
|
|
.id = id,
|
|
.type = SendMediaType::File,
|
|
});
|
|
result->filename = filename;
|
|
result->filemime = u"image/webp"_q;
|
|
result->content = bytes;
|
|
result->filesize = bytes.size();
|
|
result->setFileData(bytes);
|
|
result->document = MTP_document(
|
|
MTP_flags(0),
|
|
MTP_long(id),
|
|
MTP_long(0),
|
|
MTP_bytes(),
|
|
MTP_int(base::unixtime::now()),
|
|
MTP_string("image/webp"),
|
|
MTP_long(bytes.size()),
|
|
MTP_vector<MTPPhotoSize>(),
|
|
MTPVector<MTPVideoSize>(),
|
|
MTP_int(dcId),
|
|
MTP_vector<MTPDocumentAttribute>(std::move(attributes)));
|
|
return result;
|
|
}
|
|
|
|
void FeedSetIfFull(
|
|
not_null<Main::Session*> session,
|
|
const MTPmessages_StickerSet &result) {
|
|
result.match([&](const MTPDmessages_stickerSet &data) {
|
|
session->data().stickers().feedSetFull(data);
|
|
session->data().stickers().notifyUpdated(
|
|
Data::StickersType::Stickers);
|
|
}, [](const auto &) {
|
|
});
|
|
}
|
|
|
|
template <typename Callback>
|
|
void EnumerateOwnedSets(
|
|
not_null<Main::Session*> session,
|
|
Data::StickersType type,
|
|
Callback &&callback) {
|
|
const auto &stickers = session->data().stickers();
|
|
const auto &sets = stickers.sets();
|
|
const auto &order = (type == Data::StickersType::Emoji)
|
|
? stickers.emojiSetsOrder()
|
|
: stickers.setsOrder();
|
|
for (const auto setId : order) {
|
|
const auto it = sets.find(setId);
|
|
if (it == sets.end()) {
|
|
continue;
|
|
}
|
|
const auto set = it->second.get();
|
|
if (!(set->flags & Data::StickersSetFlag::AmCreator)
|
|
|| (set->type() != type)) {
|
|
continue;
|
|
}
|
|
using namespace Data;
|
|
if constexpr (std::is_same_v<
|
|
bool,
|
|
std::invoke_result_t<Callback, not_null<StickersSet*>>>) {
|
|
if (!callback(set)) {
|
|
return;
|
|
}
|
|
} else {
|
|
callback(set);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FillChooseOwnedSetMenu(
|
|
not_null<Ui::PopupMenu*> menu,
|
|
std::shared_ptr<ChatHelpers::Show> show,
|
|
not_null<DocumentData*> document,
|
|
Data::StickersType type) {
|
|
const auto session = &show->session();
|
|
const auto emoji = StickerEmojiOrDefault(document);
|
|
const auto isEmoji = (type == Data::StickersType::Emoji);
|
|
const auto maxCount = isEmoji
|
|
? kEmojiInOwnedSetMax
|
|
: kStickersInOwnedSetMax;
|
|
const auto fullMessage = isEmoji
|
|
? tr::lng_emoji_set_is_full
|
|
: tr::lng_stickers_set_is_full;
|
|
const auto addedMessage = isEmoji
|
|
? tr::lng_emoji_added
|
|
: tr::lng_stickers_create_added;
|
|
const auto alreadyMessage = isEmoji
|
|
? tr::lng_emoji_already_in_set
|
|
: tr::lng_stickers_already_in_set;
|
|
const auto failToast = [=](QString err) {
|
|
show->showToast(err.isEmpty()
|
|
? tr::lng_attach_failed(tr::now)
|
|
: err);
|
|
};
|
|
EnumerateOwnedSets(session, type, [&](not_null<Data::StickersSet*> set) {
|
|
const auto identifier = set->identifier();
|
|
const auto coverDocument = set->lookupThumbnailDocument();
|
|
auto thumbnail = coverDocument
|
|
? Ui::MakeDocumentThumbnailFit(
|
|
coverDocument,
|
|
Data::FileOriginStickerSet(set->id, set->accessHash))
|
|
: nullptr;
|
|
const auto targetSetId = set->id;
|
|
const auto handler = crl::guard(session, [=] {
|
|
const auto &map = session->data().stickers().sets();
|
|
const auto i = map.find(targetSetId);
|
|
if (i != map.end() && i->second->count >= maxCount) {
|
|
show->showToast(fullMessage(tr::now));
|
|
return;
|
|
}
|
|
const auto oldCount = (i != map.end())
|
|
? i->second->count
|
|
: 0;
|
|
AddExistingStickerToSet(
|
|
session,
|
|
identifier,
|
|
document,
|
|
emoji,
|
|
crl::guard(session, [=](MTPmessages_StickerSet) {
|
|
const auto &map = session->data().stickers().sets();
|
|
const auto i = map.find(targetSetId);
|
|
const auto newCount = (i != map.end())
|
|
? i->second->count
|
|
: oldCount;
|
|
show->showToast(newCount > oldCount
|
|
? addedMessage(tr::now)
|
|
: alreadyMessage(tr::now));
|
|
}),
|
|
crl::guard(session, failToast));
|
|
});
|
|
const auto rawAction = Ui::Menu::CreateAction(
|
|
menu.get(),
|
|
set->title,
|
|
handler);
|
|
auto item = base::make_unique_q<Menu::ActionWithThumbnail>(
|
|
menu->menu(),
|
|
menu->menu()->st(),
|
|
rawAction,
|
|
std::move(thumbnail),
|
|
st::menuIconStickerAdd.width());
|
|
menu->addAction(std::move(item));
|
|
});
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void AddExistingStickerToSet(
|
|
not_null<Main::Session*> session,
|
|
const StickerSetIdentifier &set,
|
|
not_null<DocumentData*> document,
|
|
const QString &emoji,
|
|
Fn<void(MTPmessages_StickerSet)> done,
|
|
Fn<void(QString)> fail) {
|
|
session->api().request(MTPstickers_AddStickerToSet(
|
|
Data::InputStickerSet(set),
|
|
InputItem(document->mtpInput(), emoji))
|
|
).done([=](const MTPmessages_StickerSet &result) {
|
|
FeedSetIfFull(session, result);
|
|
if (done) {
|
|
done(result);
|
|
}
|
|
}).fail([=](const MTP::Error &error) {
|
|
if (fail) {
|
|
fail(error.type());
|
|
}
|
|
}).handleFloodErrors().send();
|
|
}
|
|
|
|
QString StickerEmojiOrDefault(not_null<DocumentData*> document) {
|
|
if (const auto sticker = document->sticker()) {
|
|
if (!sticker->alt.isEmpty()) {
|
|
return sticker->alt;
|
|
}
|
|
}
|
|
return QString::fromUtf8("\xF0\x9F\x99\x82");
|
|
}
|
|
|
|
bool HasOwnedStickerSets(not_null<Main::Session*> session) {
|
|
auto found = false;
|
|
EnumerateOwnedSets(
|
|
session,
|
|
Data::StickersType::Stickers,
|
|
[&](not_null<Data::StickersSet*>) {
|
|
found = true;
|
|
return false;
|
|
});
|
|
return found;
|
|
}
|
|
|
|
bool HasOwnedEmojiSets(not_null<Main::Session*> session) {
|
|
auto found = false;
|
|
EnumerateOwnedSets(
|
|
session,
|
|
Data::StickersType::Emoji,
|
|
[&](not_null<Data::StickersSet*>) {
|
|
found = true;
|
|
return false;
|
|
});
|
|
return found;
|
|
}
|
|
|
|
void FillChooseStickerSetMenu(
|
|
not_null<Ui::PopupMenu*> menu,
|
|
std::shared_ptr<ChatHelpers::Show> show,
|
|
not_null<DocumentData*> document) {
|
|
using namespace Data;
|
|
FillChooseOwnedSetMenu(menu, show, document, StickersType::Stickers);
|
|
}
|
|
|
|
void FillChooseEmojiSetMenu(
|
|
not_null<Ui::PopupMenu*> menu,
|
|
std::shared_ptr<ChatHelpers::Show> show,
|
|
not_null<DocumentData*> document) {
|
|
FillChooseOwnedSetMenu(menu, show, document, Data::StickersType::Emoji);
|
|
}
|
|
|
|
void AddAddToStickerSetAction(
|
|
const Ui::Menu::MenuCallback &addAction,
|
|
std::shared_ptr<ChatHelpers::Show> show,
|
|
not_null<DocumentData*> document) {
|
|
const auto session = &show->session();
|
|
if (!HasOwnedStickerSets(session)) {
|
|
return;
|
|
}
|
|
addAction({
|
|
.text = tr::lng_stickers_add_to_set(tr::now),
|
|
.icon = &st::menuIconStickerAdd,
|
|
.fillSubmenu = [show, document](not_null<Ui::PopupMenu*> submenu) {
|
|
FillChooseStickerSetMenu(submenu, show, document);
|
|
},
|
|
.submenuSt = &st::popupMenuWithIcons,
|
|
});
|
|
}
|
|
|
|
void AddAddToEmojiSetAction(
|
|
const Ui::Menu::MenuCallback &addAction,
|
|
std::shared_ptr<ChatHelpers::Show> show,
|
|
not_null<DocumentData*> document) {
|
|
const auto session = &show->session();
|
|
if (!HasOwnedEmojiSets(session)) {
|
|
return;
|
|
}
|
|
addAction({
|
|
.text = tr::lng_emoji_add_to_set(tr::now),
|
|
.icon = &st::menuIconEmoji,
|
|
.fillSubmenu = [show, document](not_null<Ui::PopupMenu*> submenu) {
|
|
FillChooseEmojiSetMenu(submenu, show, document);
|
|
},
|
|
.submenuSt = &st::popupMenuWithIcons,
|
|
});
|
|
}
|
|
|
|
void AddAddToOwnedSetAction(
|
|
const Ui::Menu::MenuCallback &addAction,
|
|
std::shared_ptr<ChatHelpers::Show> show,
|
|
not_null<DocumentData*> document) {
|
|
const auto sticker = document->sticker();
|
|
const auto isLottie = sticker && sticker->isLottie();
|
|
const auto size = document->dimensions;
|
|
const auto fitsEmoji = !size.isEmpty()
|
|
&& (size.width() <= kEmojiStickerSideMax)
|
|
&& (size.height() <= kEmojiStickerSideMax);
|
|
if (isLottie || !fitsEmoji) {
|
|
AddAddToStickerSetAction(addAction, show, document);
|
|
}
|
|
if (isLottie || fitsEmoji) {
|
|
AddAddToEmojiSetAction(addAction, std::move(show), document);
|
|
}
|
|
}
|
|
|
|
void DeleteStickerSet(
|
|
not_null<Main::Session*> session,
|
|
const StickerSetIdentifier &set,
|
|
Fn<void()> done,
|
|
Fn<void(QString)> fail) {
|
|
session->api().request(MTPstickers_DeleteStickerSet(
|
|
Data::InputStickerSet(set))
|
|
).done([=] {
|
|
session->data().stickers().notifyUpdated(
|
|
Data::StickersType::Stickers);
|
|
if (done) {
|
|
done();
|
|
}
|
|
}).fail([=](const MTP::Error &error) {
|
|
if (fail) {
|
|
fail(error.type());
|
|
}
|
|
}).send();
|
|
}
|
|
|
|
StickerUpload::StickerUpload(
|
|
not_null<Main::Session*> session,
|
|
StickerSetIdentifier set,
|
|
QByteArray webpBytes,
|
|
QString emoji)
|
|
: _session(session)
|
|
, _set(std::move(set))
|
|
, _bytes(std::move(webpBytes))
|
|
, _emoji(std::move(emoji))
|
|
, _api(&session->mtp()) {
|
|
}
|
|
|
|
StickerUpload::~StickerUpload() {
|
|
cancel();
|
|
}
|
|
|
|
void StickerUpload::start(
|
|
Fn<void(MTPmessages_StickerSet)> done,
|
|
Fn<void(QString)> fail,
|
|
Fn<void(int)> progress) {
|
|
Expects(!_uploadId);
|
|
|
|
_done = std::move(done);
|
|
_fail = std::move(fail);
|
|
_progress = std::move(progress);
|
|
|
|
_documentId = base::RandomValue<DocumentId>();
|
|
auto ready = PrepareStickerWebp(
|
|
_session->mtp().mainDcId(),
|
|
_documentId,
|
|
_bytes);
|
|
_uploadId = FullMsgId(
|
|
_session->userPeerId(),
|
|
_session->data().nextLocalMessageId());
|
|
|
|
const auto document = _session->data().document(_documentId);
|
|
document->uploadingData = std::make_unique<Data::UploadState>(
|
|
document->size > 0 ? document->size : int64(_bytes.size()));
|
|
|
|
_session->uploader().documentReady(
|
|
) | rpl::filter([=](const Storage::UploadedMedia &data) {
|
|
return data.fullId == _uploadId;
|
|
}) | rpl::on_next([=](const Storage::UploadedMedia &data) {
|
|
uploadReady(data.info.file);
|
|
}, _uploadLifetime);
|
|
|
|
_session->uploader().documentFailed(
|
|
) | rpl::filter([=](const FullMsgId &id) {
|
|
return id == _uploadId;
|
|
}) | rpl::on_next([=] {
|
|
uploadFailed();
|
|
}, _uploadLifetime);
|
|
|
|
if (_progress) {
|
|
_session->uploader().documentProgress(
|
|
) | rpl::filter([=](const FullMsgId &id) {
|
|
return id == _uploadId;
|
|
}) | rpl::on_next([=] {
|
|
uploadProgressed();
|
|
}, _uploadLifetime);
|
|
}
|
|
|
|
_session->uploader().upload(_uploadId, ready);
|
|
}
|
|
|
|
void StickerUpload::cancel() {
|
|
if (_uploadId) {
|
|
_session->uploader().cancel(_uploadId);
|
|
_uploadId = FullMsgId();
|
|
}
|
|
if (_addRequestId) {
|
|
_api.request(_addRequestId).cancel();
|
|
_addRequestId = 0;
|
|
}
|
|
_uploadLifetime.destroy();
|
|
_done = nullptr;
|
|
_fail = nullptr;
|
|
_progress = nullptr;
|
|
}
|
|
|
|
void StickerUpload::uploadProgressed() {
|
|
if (!_progress) {
|
|
return;
|
|
}
|
|
const auto document = _session->data().document(_documentId);
|
|
if (!document->uploading() || !document->uploadingData) {
|
|
return;
|
|
}
|
|
const auto size = document->uploadingData->size;
|
|
if (size <= 0) {
|
|
return;
|
|
}
|
|
const auto percent = int(
|
|
(document->uploadingData->offset * 100) / size);
|
|
if (percent != _lastReportedPercent) {
|
|
_lastReportedPercent = percent;
|
|
_progress(percent);
|
|
}
|
|
}
|
|
|
|
void StickerUpload::uploadFailed() {
|
|
const auto fail = std::move(_fail);
|
|
cancel();
|
|
if (fail) {
|
|
fail(QString());
|
|
}
|
|
}
|
|
|
|
void StickerUpload::uploadReady(const MTPInputFile &file) {
|
|
_uploadLifetime.destroy();
|
|
_uploadId = FullMsgId();
|
|
|
|
auto attributes = QVector<MTPDocumentAttribute>();
|
|
attributes.push_back(MTP_documentAttributeSticker(
|
|
MTP_flags(0),
|
|
MTP_string(_emoji),
|
|
MTP_inputStickerSetEmpty(),
|
|
MTPMaskCoords()));
|
|
attributes.push_back(MTP_documentAttributeImageSize(
|
|
MTP_int(kStickerSide),
|
|
MTP_int(kStickerSide)));
|
|
|
|
const auto media = MTP_inputMediaUploadedDocument(
|
|
MTP_flags(0),
|
|
file,
|
|
MTPInputFile(),
|
|
MTP_string("image/webp"),
|
|
MTP_vector<MTPDocumentAttribute>(std::move(attributes)),
|
|
MTP_vector<MTPInputDocument>(),
|
|
MTPInputPhoto(),
|
|
MTP_int(0),
|
|
MTP_int(0));
|
|
|
|
_addRequestId = _api.request(MTPmessages_UploadMedia(
|
|
MTP_flags(0),
|
|
MTPstring(),
|
|
MTP_inputPeerSelf(),
|
|
media
|
|
)).done(crl::guard(this, [=](const MTPMessageMedia &result) {
|
|
_addRequestId = 0;
|
|
auto inputDoc = (MTPInputDocument*)(nullptr);
|
|
auto storage = MTPInputDocument();
|
|
result.match([&](const MTPDmessageMediaDocument &data) {
|
|
if (const auto doc = data.vdocument()) {
|
|
doc->match([&](const MTPDdocument &d) {
|
|
storage = MTP_inputDocument(
|
|
d.vid(),
|
|
d.vaccess_hash(),
|
|
d.vfile_reference());
|
|
inputDoc = &storage;
|
|
}, [](const auto &) {
|
|
});
|
|
}
|
|
}, [](const auto &) {
|
|
});
|
|
if (inputDoc) {
|
|
requestAddSticker(*inputDoc);
|
|
} else if (const auto fail = std::move(_fail)) {
|
|
cancel();
|
|
fail(QString());
|
|
}
|
|
})).fail(crl::guard(this, [=](const MTP::Error &error) {
|
|
_addRequestId = 0;
|
|
const auto fail = std::move(_fail);
|
|
const auto type = error.type();
|
|
cancel();
|
|
if (fail) {
|
|
fail(type);
|
|
}
|
|
})).handleFloodErrors().send();
|
|
}
|
|
|
|
void StickerUpload::requestAddSticker(const MTPInputDocument &document) {
|
|
_addRequestId = _api.request(MTPstickers_AddStickerToSet(
|
|
Data::InputStickerSet(_set),
|
|
InputItem(document, _emoji))
|
|
).done(crl::guard(this, [=](const MTPmessages_StickerSet &result) {
|
|
_addRequestId = 0;
|
|
FeedSetIfFull(_session, result);
|
|
const auto done = std::move(_done);
|
|
cancel();
|
|
if (done) {
|
|
done(result);
|
|
}
|
|
})).fail(crl::guard(this, [=](const MTP::Error &error) {
|
|
_addRequestId = 0;
|
|
const auto fail = std::move(_fail);
|
|
const auto type = error.type();
|
|
cancel();
|
|
if (fail) {
|
|
fail(type);
|
|
}
|
|
})).handleFloodErrors().send();
|
|
}
|
|
|
|
} // namespace Api
|