| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- /*
- 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_peer_photo.h"
- #include "api/api_updates.h"
- #include "apiwrap.h"
- #include "base/random.h"
- #include "base/unixtime.h"
- #include "data/stickers/data_stickers.h"
- #include "data/data_channel.h"
- #include "data/data_chat.h"
- #include "data/data_document.h"
- #include "data/data_file_origin.h"
- #include "data/data_peer.h"
- #include "data/data_photo.h"
- #include "data/data_session.h"
- #include "data/data_user.h"
- #include "data/data_user_photos.h"
- #include "history/history.h"
- #include "main/main_session.h"
- #include "storage/file_upload.h"
- #include "storage/localimageloader.h"
- #include "storage/storage_user_photos.h"
- #include <QtCore/QBuffer>
- namespace Api {
- namespace {
- constexpr auto kSharedMediaLimit = 100;
- [[nodiscard]] std::shared_ptr<FilePrepareResult> PreparePeerPhoto(
- MTP::DcId dcId,
- PeerId peerId,
- QImage &&image) {
- PreparedPhotoThumbs photoThumbs;
- QVector<MTPPhotoSize> photoSizes;
- QByteArray jpeg;
- QBuffer jpegBuffer(&jpeg);
- image.save(&jpegBuffer, "JPG", 87);
- const auto scaled = [&](int size) {
- return image.scaled(
- size,
- size,
- Qt::KeepAspectRatio,
- Qt::SmoothTransformation);
- };
- const auto push = [&](
- const char *type,
- QImage &&image,
- QByteArray bytes = QByteArray()) {
- photoSizes.push_back(MTP_photoSize(
- MTP_string(type),
- MTP_int(image.width()),
- MTP_int(image.height()), MTP_int(0)));
- photoThumbs.emplace(type[0], PreparedPhotoThumb{
- .image = std::move(image),
- .bytes = std::move(bytes)
- });
- };
- push("a", scaled(160));
- push("b", scaled(320));
- push("c", std::move(image), jpeg);
- const auto id = base::RandomValue<PhotoId>();
- const auto photo = MTP_photo(
- MTP_flags(0),
- MTP_long(id),
- MTP_long(0),
- MTP_bytes(),
- MTP_int(base::unixtime::now()),
- MTP_vector<MTPPhotoSize>(photoSizes),
- MTPVector<MTPVideoSize>(),
- MTP_int(dcId));
- auto result = MakePreparedFile({
- .id = id,
- .type = SendMediaType::Photo,
- });
- result->type = SendMediaType::Photo;
- result->setFileData(jpeg);
- result->thumbId = id;
- result->thumbname = "thumb.jpg";
- result->photo = photo;
- result->photoThumbs = photoThumbs;
- return result;
- }
- [[nodiscard]] std::optional<MTPVideoSize> PrepareMtpMarkup(
- not_null<Main::Session*> session,
- const PeerPhoto::UserPhoto &d) {
- const auto &documentId = d.markupDocumentId;
- const auto &colors = d.markupColors;
- if (!documentId || colors.empty()) {
- return std::nullopt;
- }
- const auto document = session->data().document(documentId);
- if (const auto sticker = document->sticker()) {
- if (sticker->isStatic()) {
- return std::nullopt;
- }
- const auto serializeColor = [](const QColor &color) {
- return (quint32(std::clamp(color.red(), 0, 255)) << 16)
- | (quint32(std::clamp(color.green(), 0, 255)) << 8)
- | quint32(std::clamp(color.blue(), 0, 255));
- };
- auto mtpColors = QVector<MTPint>();
- mtpColors.reserve(colors.size());
- ranges::transform(
- colors,
- ranges::back_inserter(mtpColors),
- [&](const QColor &c) { return MTP_int(serializeColor(c)); });
- if (sticker->setType == Data::StickersType::Emoji) {
- return MTP_videoSizeEmojiMarkup(
- MTP_long(document->id),
- MTP_vector(mtpColors));
- } else if (sticker->set.id && sticker->set.accessHash) {
- return MTP_videoSizeStickerMarkup(
- MTP_inputStickerSetID(
- MTP_long(sticker->set.id),
- MTP_long(sticker->set.accessHash)),
- MTP_long(document->id),
- MTP_vector(mtpColors));
- } else if (!sticker->set.shortName.isEmpty()) {
- return MTP_videoSizeStickerMarkup(
- MTP_inputStickerSetShortName(
- MTP_string(sticker->set.shortName)),
- MTP_long(document->id),
- MTP_vector(mtpColors));
- } else {
- return MTP_videoSizeEmojiMarkup(
- MTP_long(document->id),
- MTP_vector(mtpColors));
- }
- }
- return std::nullopt;
- }
- } // namespace
- PeerPhoto::PeerPhoto(not_null<ApiWrap*> api)
- : _session(&api->session())
- , _api(&api->instance()) {
- crl::on_main(_session, [=] {
- // You can't use _session->lifetime() in the constructor,
- // only queued, because it is not constructed yet.
- _session->uploader().photoReady(
- ) | rpl::start_with_next([=](const Storage::UploadedMedia &data) {
- ready(data.fullId, data.info.file, std::nullopt);
- }, _session->lifetime());
- });
- }
- void PeerPhoto::upload(
- not_null<PeerData*> peer,
- UserPhoto &&photo,
- Fn<void()> done) {
- upload(peer, std::move(photo), UploadType::Default, std::move(done));
- }
- void PeerPhoto::uploadFallback(not_null<PeerData*> peer, UserPhoto &&photo) {
- upload(peer, std::move(photo), UploadType::Fallback, nullptr);
- }
- void PeerPhoto::updateSelf(
- not_null<PhotoData*> photo,
- Data::FileOrigin origin,
- Fn<void()> done) {
- const auto send = [=](auto resend) -> void {
- const auto usedFileReference = photo->fileReference();
- _api.request(MTPphotos_UpdateProfilePhoto(
- MTP_flags(0),
- MTPInputUser(), // bot
- photo->mtpInput()
- )).done([=](const MTPphotos_Photo &result) {
- result.match([&](const MTPDphotos_photo &data) {
- _session->data().processPhoto(data.vphoto());
- _session->data().processUsers(data.vusers());
- });
- if (done) {
- done();
- }
- }).fail([=](const MTP::Error &error) {
- if (error.code() == 400
- && error.type().startsWith(u"FILE_REFERENCE_"_q)) {
- photo->session().api().refreshFileReference(origin, [=](
- const auto &) {
- if (photo->fileReference() != usedFileReference) {
- resend(resend);
- }
- });
- }
- }).send();
- };
- send(send);
- }
- void PeerPhoto::upload(
- not_null<PeerData*> peer,
- UserPhoto &&photo,
- UploadType type,
- Fn<void()> done) {
- peer = peer->migrateToOrMe();
- const auto mtpMarkup = PrepareMtpMarkup(_session, photo);
- const auto fakeId = FullMsgId(
- peer->id,
- _session->data().nextLocalMessageId());
- const auto already = ranges::find(
- _uploads,
- peer,
- [](const auto &pair) { return pair.second.peer; });
- if (already != end(_uploads)) {
- _session->uploader().cancel(already->first);
- _uploads.erase(already);
- }
- _uploads.emplace(
- fakeId,
- UploadValue{ peer, type, std::move(done) });
- if (mtpMarkup) {
- ready(fakeId, std::nullopt, mtpMarkup);
- } else {
- const auto ready = PreparePeerPhoto(
- _api.instance().mainDcId(),
- peer->id,
- base::take(photo.image));
- _session->uploader().upload(fakeId, ready);
- }
- }
- void PeerPhoto::suggest(not_null<PeerData*> peer, UserPhoto &&photo) {
- upload(peer, std::move(photo), UploadType::Suggestion, nullptr);
- }
- void PeerPhoto::clear(not_null<PhotoData*> photo) {
- const auto self = _session->user();
- if (self->userpicPhotoId() == photo->id) {
- _api.request(MTPphotos_UpdateProfilePhoto(
- MTP_flags(0),
- MTPInputUser(), // bot
- MTP_inputPhotoEmpty()
- )).done([=](const MTPphotos_Photo &result) {
- self->setPhoto(MTP_userProfilePhotoEmpty());
- }).send();
- } else if (photo->peer && photo->peer->userpicPhotoId() == photo->id) {
- const auto applier = [=](const MTPUpdates &result) {
- _session->updates().applyUpdates(result);
- };
- if (const auto chat = photo->peer->asChat()) {
- _api.request(MTPmessages_EditChatPhoto(
- chat->inputChat,
- MTP_inputChatPhotoEmpty()
- )).done(applier).send();
- } else if (const auto channel = photo->peer->asChannel()) {
- _api.request(MTPchannels_EditPhoto(
- channel->inputChannel,
- MTP_inputChatPhotoEmpty()
- )).done(applier).send();
- }
- } else {
- const auto fallbackPhotoId = SyncUserFallbackPhotoViewer(self);
- if (fallbackPhotoId && (*fallbackPhotoId) == photo->id) {
- _api.request(MTPphotos_UpdateProfilePhoto(
- MTP_flags(MTPphotos_UpdateProfilePhoto::Flag::f_fallback),
- MTPInputUser(), // bot
- MTP_inputPhotoEmpty()
- )).send();
- _session->storage().add(Storage::UserPhotosSetBack(
- peerToUser(self->id),
- PhotoId()));
- } else {
- _api.request(MTPphotos_DeletePhotos(
- MTP_vector<MTPInputPhoto>(1, photo->mtpInput())
- )).send();
- _session->storage().remove(Storage::UserPhotosRemoveOne(
- peerToUser(self->id),
- photo->id));
- }
- }
- }
- void PeerPhoto::clearPersonal(not_null<UserData*> user) {
- _api.request(MTPphotos_UploadContactProfilePhoto(
- MTP_flags(MTPphotos_UploadContactProfilePhoto::Flag::f_save),
- user->inputUser,
- MTPInputFile(),
- MTPInputFile(), // video
- MTPdouble(), // video_start_ts
- MTPVideoSize() // video_emoji_markup
- )).done([=](const MTPphotos_Photo &result) {
- result.match([&](const MTPDphotos_photo &data) {
- _session->data().processPhoto(data.vphoto());
- _session->data().processUsers(data.vusers());
- });
- }).send();
- if (!user->userpicPhotoUnknown() && user->hasPersonalPhoto()) {
- _session->storage().remove(Storage::UserPhotosRemoveOne(
- peerToUser(user->id),
- user->userpicPhotoId()));
- }
- }
- void PeerPhoto::set(not_null<PeerData*> peer, not_null<PhotoData*> photo) {
- if (peer->userpicPhotoId() == photo->id) {
- return;
- }
- if (peer == _session->user()) {
- _api.request(MTPphotos_UpdateProfilePhoto(
- MTP_flags(0),
- MTPInputUser(), // bot
- photo->mtpInput()
- )).done([=](const MTPphotos_Photo &result) {
- result.match([&](const MTPDphotos_photo &data) {
- _session->data().processPhoto(data.vphoto());
- _session->data().processUsers(data.vusers());
- });
- }).send();
- } else {
- const auto applier = [=](const MTPUpdates &result) {
- _session->updates().applyUpdates(result);
- };
- if (const auto chat = peer->asChat()) {
- _api.request(MTPmessages_EditChatPhoto(
- chat->inputChat,
- MTP_inputChatPhoto(photo->mtpInput())
- )).done(applier).send();
- } else if (const auto channel = peer->asChannel()) {
- _api.request(MTPchannels_EditPhoto(
- channel->inputChannel,
- MTP_inputChatPhoto(photo->mtpInput())
- )).done(applier).send();
- }
- }
- }
- void PeerPhoto::ready(
- const FullMsgId &msgId,
- std::optional<MTPInputFile> file,
- std::optional<MTPVideoSize> videoSize) {
- const auto maybeUploadValue = _uploads.take(msgId);
- if (!maybeUploadValue) {
- return;
- }
- const auto peer = maybeUploadValue->peer;
- const auto type = maybeUploadValue->type;
- const auto done = maybeUploadValue->done;
- const auto applier = [=](const MTPUpdates &result) {
- _session->updates().applyUpdates(result);
- if (done) {
- done();
- }
- };
- const auto botUserInput = [&] {
- const auto user = peer->asUser();
- return (user && user->botInfo && user->botInfo->canEditInformation)
- ? std::make_optional<MTPInputUser>(user->inputUser)
- : std::nullopt;
- }();
- if (peer->isSelf() || botUserInput) {
- using Flag = MTPphotos_UploadProfilePhoto::Flag;
- const auto none = MTPphotos_UploadProfilePhoto::Flags(0);
- _api.request(MTPphotos_UploadProfilePhoto(
- MTP_flags((file ? Flag::f_file : none)
- | (botUserInput ? Flag::f_bot : none)
- | (videoSize ? Flag::f_video_emoji_markup : none)
- | ((type == UploadType::Fallback) ? Flag::f_fallback : none)),
- botUserInput ? (*botUserInput) : MTPInputUser(), // bot
- file ? (*file) : MTPInputFile(),
- MTPInputFile(), // video
- MTPdouble(), // video_start_ts
- videoSize ? (*videoSize) : MTPVideoSize() // video_emoji_markup
- )).done([=](const MTPphotos_Photo &result) {
- const auto photoId = _session->data().processPhoto(
- result.data().vphoto())->id;
- _session->data().processUsers(result.data().vusers());
- if (type == UploadType::Fallback) {
- _session->storage().add(Storage::UserPhotosSetBack(
- peerToUser(peer->id),
- photoId));
- }
- if (done) {
- done();
- }
- }).send();
- } else if (const auto chat = peer->asChat()) {
- const auto history = _session->data().history(chat);
- using Flag = MTPDinputChatUploadedPhoto::Flag;
- const auto none = MTPDinputChatUploadedPhoto::Flags(0);
- history->sendRequestId = _api.request(MTPmessages_EditChatPhoto(
- chat->inputChat,
- MTP_inputChatUploadedPhoto(
- MTP_flags((file ? Flag::f_file : none)
- | (videoSize ? Flag::f_video_emoji_markup : none)),
- file ? (*file) : MTPInputFile(),
- MTPInputFile(), // video
- MTPdouble(), // video_start_ts
- videoSize ? (*videoSize) : MTPVideoSize()) // video_emoji_markup
- )).done(applier).afterRequest(history->sendRequestId).send();
- } else if (const auto channel = peer->asChannel()) {
- using Flag = MTPDinputChatUploadedPhoto::Flag;
- const auto none = MTPDinputChatUploadedPhoto::Flags(0);
- const auto history = _session->data().history(channel);
- history->sendRequestId = _api.request(MTPchannels_EditPhoto(
- channel->inputChannel,
- MTP_inputChatUploadedPhoto(
- MTP_flags((file ? Flag::f_file : none)
- | (videoSize ? Flag::f_video_emoji_markup : none)),
- file ? (*file) : MTPInputFile(),
- MTPInputFile(), // video
- MTPdouble(), // video_start_ts
- videoSize ? (*videoSize) : MTPVideoSize()) // video_emoji_markup
- )).done(applier).afterRequest(history->sendRequestId).send();
- } else if (const auto user = peer->asUser()) {
- using Flag = MTPphotos_UploadContactProfilePhoto::Flag;
- const auto none = MTPphotos_UploadContactProfilePhoto::Flags(0);
- _api.request(MTPphotos_UploadContactProfilePhoto(
- MTP_flags((file ? Flag::f_file : none)
- | (videoSize ? Flag::f_video_emoji_markup : none)
- | ((type == UploadType::Suggestion)
- ? Flag::f_suggest
- : Flag::f_save)),
- user->inputUser,
- file ? (*file) : MTPInputFile(),
- MTPInputFile(), // video
- MTPdouble(), // video_start_ts
- videoSize ? (*videoSize) : MTPVideoSize() // video_emoji_markup
- )).done([=](const MTPphotos_Photo &result) {
- result.match([&](const MTPDphotos_photo &data) {
- _session->data().processPhoto(data.vphoto());
- _session->data().processUsers(data.vusers());
- });
- if (type != UploadType::Suggestion) {
- user->updateFullForced();
- }
- if (done) {
- done();
- }
- }).send();
- }
- }
- void PeerPhoto::requestUserPhotos(
- not_null<UserData*> user,
- UserPhotoId afterId) {
- if (_userPhotosRequests.contains(user)) {
- return;
- }
- const auto requestId = _api.request(MTPphotos_GetUserPhotos(
- user->inputUser,
- MTP_int(0),
- MTP_long(afterId),
- MTP_int(kSharedMediaLimit)
- )).done([this, user](const MTPphotos_Photos &result) {
- _userPhotosRequests.remove(user);
- auto fullCount = result.match([](const MTPDphotos_photos &d) {
- return int(d.vphotos().v.size());
- }, [](const MTPDphotos_photosSlice &d) {
- return d.vcount().v;
- });
- auto &owner = _session->data();
- auto photoIds = result.match([&](const auto &data) {
- owner.processUsers(data.vusers());
- auto photoIds = std::vector<PhotoId>();
- photoIds.reserve(data.vphotos().v.size());
- for (const auto &single : data.vphotos().v) {
- const auto photo = owner.processPhoto(single);
- if (!photo->isNull()) {
- photoIds.push_back(photo->id);
- }
- }
- return photoIds;
- });
- if (!user->userpicPhotoUnknown() && user->hasPersonalPhoto()) {
- const auto photo = owner.photo(user->userpicPhotoId());
- if (!photo->isNull()) {
- ++fullCount;
- photoIds.insert(begin(photoIds), photo->id);
- }
- }
- _session->storage().add(Storage::UserPhotosAddSlice(
- peerToUser(user->id),
- std::move(photoIds),
- fullCount
- ));
- }).fail([this, user] {
- _userPhotosRequests.remove(user);
- }).send();
- _userPhotosRequests.emplace(user, requestId);
- }
- auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
- switch (type) {
- case EmojiListType::Profile: return _profileEmojiList;
- case EmojiListType::Group: return _groupEmojiList;
- case EmojiListType::Background: return _backgroundEmojiList;
- case EmojiListType::NoChannelStatus: return _noChannelStatusEmojiList;
- }
- Unexpected("Type in PeerPhoto::emojiList.");
- }
- auto PeerPhoto::emojiList(EmojiListType type) const
- -> const EmojiListData & {
- return const_cast<PeerPhoto*>(this)->emojiList(type);
- }
- void PeerPhoto::requestEmojiList(EmojiListType type) {
- auto &list = emojiList(type);
- if (list.requestId) {
- return;
- }
- const auto send = [&](auto &&request) {
- return _api.request(
- std::move(request)
- ).done([=](const MTPEmojiList &result) {
- auto &list = emojiList(type);
- list.requestId = 0;
- result.match([](const MTPDemojiListNotModified &data) {
- }, [&](const MTPDemojiList &data) {
- list.list = ranges::views::all(
- data.vdocument_id().v
- ) | ranges::views::transform(
- &MTPlong::v
- ) | ranges::to_vector;
- });
- }).fail([=] {
- emojiList(type).requestId = 0;
- }).send();
- };
- list.requestId = (type == EmojiListType::Profile)
- ? send(MTPaccount_GetDefaultProfilePhotoEmojis())
- : (type == EmojiListType::Group)
- ? send(MTPaccount_GetDefaultGroupPhotoEmojis())
- : (type == EmojiListType::NoChannelStatus)
- ? send(MTPaccount_GetChannelRestrictedStatusEmojis())
- : send(MTPaccount_GetDefaultBackgroundEmojis());
- }
- rpl::producer<PeerPhoto::EmojiList> PeerPhoto::emojiListValue(
- EmojiListType type) {
- auto &list = emojiList(type);
- if (list.list.current().empty() && !list.requestId) {
- requestEmojiList(type);
- }
- return list.list.value();
- }
- // Non-personal photo in case a personal photo is set.
- void PeerPhoto::registerNonPersonalPhoto(
- not_null<UserData*> user,
- not_null<PhotoData*> photo) {
- _nonPersonalPhotos.emplace_or_assign(user, photo);
- }
- void PeerPhoto::unregisterNonPersonalPhoto(not_null<UserData*> user) {
- _nonPersonalPhotos.erase(user);
- }
- PhotoData *PeerPhoto::nonPersonalPhoto(
- not_null<UserData*> user) const {
- const auto i = _nonPersonalPhotos.find(user);
- return (i != end(_nonPersonalPhotos)) ? i->second.get() : nullptr;
- }
- } // namespace Api
|