| 
									
										
										
										
											2021-10-20 19:54:22 +03:00
										 |  |  | /*
 | 
					
						
							|  |  |  | 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/data_channel.h"
 | 
					
						
							|  |  |  | #include "data/data_chat.h"
 | 
					
						
							|  |  |  | #include "data/data_peer.h"
 | 
					
						
							|  |  |  | #include "data/data_photo.h"
 | 
					
						
							|  |  |  | #include "data/data_session.h"
 | 
					
						
							|  |  |  | #include "data/data_user.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 { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-16 04:05:17 +03:00
										 |  |  | constexpr auto kSharedMediaLimit = 100; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-20 19:54:22 +03:00
										 |  |  | SendMediaReady 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)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	QString file, filename; | 
					
						
							| 
									
										
										
										
											2022-05-10 18:22:28 +04:00
										 |  |  | 	int64 filesize = 0; | 
					
						
							| 
									
										
										
										
											2021-10-20 19:54:22 +03:00
										 |  |  | 	QByteArray data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return SendMediaReady( | 
					
						
							|  |  |  | 		SendMediaType::Photo, | 
					
						
							|  |  |  | 		file, | 
					
						
							|  |  |  | 		filename, | 
					
						
							|  |  |  | 		filesize, | 
					
						
							|  |  |  | 		data, | 
					
						
							|  |  |  | 		id, | 
					
						
							|  |  |  | 		id, | 
					
						
							|  |  |  | 		u"jpg"_q, | 
					
						
							|  |  |  | 		peerId, | 
					
						
							|  |  |  | 		photo, | 
					
						
							|  |  |  | 		photoThumbs, | 
					
						
							|  |  |  | 		MTP_documentEmpty(MTP_long(0)), | 
					
						
							|  |  |  | 		jpeg, | 
					
						
							|  |  |  | 		0); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } // 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( | 
					
						
							| 
									
										
										
										
											2021-11-16 16:38:31 +04:00
										 |  |  | 		) | rpl::start_with_next([=](const Storage::UploadedMedia &data) { | 
					
						
							|  |  |  | 			ready(data.fullId, data.info.file); | 
					
						
							| 
									
										
										
										
											2021-10-20 19:54:22 +03:00
										 |  |  | 		}, _session->lifetime()); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PeerPhoto::upload(not_null<PeerData*> peer, QImage &&image) { | 
					
						
							|  |  |  | 	peer = peer->migrateToOrMe(); | 
					
						
							|  |  |  | 	const auto ready = PreparePeerPhoto( | 
					
						
							|  |  |  | 		_api.instance().mainDcId(), | 
					
						
							|  |  |  | 		peer->id, | 
					
						
							|  |  |  | 		std::move(image)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const auto fakeId = FullMsgId( | 
					
						
							| 
									
										
										
										
											2021-12-09 11:32:54 +04:00
										 |  |  | 		peer->id, | 
					
						
							| 
									
										
										
										
											2021-10-20 19:54:22 +03:00
										 |  |  | 		_session->data().nextLocalMessageId()); | 
					
						
							|  |  |  | 	const auto already = ranges::find( | 
					
						
							|  |  |  | 		_uploads, | 
					
						
							|  |  |  | 		peer, | 
					
						
							|  |  |  | 		[](const auto &pair) { return pair.second; }); | 
					
						
							|  |  |  | 	if (already != end(_uploads)) { | 
					
						
							|  |  |  | 		_session->uploader().cancel(already->first); | 
					
						
							|  |  |  | 		_uploads.erase(already); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	_uploads.emplace(fakeId, peer); | 
					
						
							|  |  |  | 	_session->uploader().uploadMedia(fakeId, ready); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PeerPhoto::clear(not_null<PhotoData*> photo) { | 
					
						
							|  |  |  | 	const auto self = _session->user(); | 
					
						
							|  |  |  | 	if (self->userpicPhotoId() == photo->id) { | 
					
						
							|  |  |  | 		_api.request(MTPphotos_UpdateProfilePhoto( | 
					
						
							|  |  |  | 			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 { | 
					
						
							|  |  |  | 		_api.request(MTPphotos_DeletePhotos( | 
					
						
							|  |  |  | 			MTP_vector<MTPInputPhoto>(1, photo->mtpInput()) | 
					
						
							|  |  |  | 		)).send(); | 
					
						
							|  |  |  | 		_session->storage().remove(Storage::UserPhotosRemoveOne( | 
					
						
							|  |  |  | 			peerToUser(self->id), | 
					
						
							|  |  |  | 			photo->id)); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 01:41:26 +03:00
										 |  |  | 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( | 
					
						
							|  |  |  | 			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(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-20 19:54:22 +03:00
										 |  |  | void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) { | 
					
						
							|  |  |  | 	const auto maybePeer = _uploads.take(msgId); | 
					
						
							|  |  |  | 	if (!maybePeer) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	const auto peer = *maybePeer; | 
					
						
							|  |  |  | 	const auto applier = [=](const MTPUpdates &result) { | 
					
						
							|  |  |  | 		_session->updates().applyUpdates(result); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	if (peer->isSelf()) { | 
					
						
							|  |  |  | 		_api.request(MTPphotos_UploadProfilePhoto( | 
					
						
							|  |  |  | 			MTP_flags(MTPphotos_UploadProfilePhoto::Flag::f_file), | 
					
						
							|  |  |  | 			file, | 
					
						
							|  |  |  | 			MTPInputFile(), // video
 | 
					
						
							|  |  |  | 			MTPdouble() // video_start_ts
 | 
					
						
							|  |  |  | 		)).done([=](const MTPphotos_Photo &result) { | 
					
						
							|  |  |  | 			result.match([&](const MTPDphotos_photo &data) { | 
					
						
							|  |  |  | 				_session->data().processPhoto(data.vphoto()); | 
					
						
							|  |  |  | 				_session->data().processUsers(data.vusers()); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}).send(); | 
					
						
							|  |  |  | 	} else if (const auto chat = peer->asChat()) { | 
					
						
							|  |  |  | 		const auto history = _session->data().history(chat); | 
					
						
							|  |  |  | 		history->sendRequestId = _api.request(MTPmessages_EditChatPhoto( | 
					
						
							|  |  |  | 			chat->inputChat, | 
					
						
							|  |  |  | 			MTP_inputChatUploadedPhoto( | 
					
						
							|  |  |  | 				MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file), | 
					
						
							|  |  |  | 				file, | 
					
						
							|  |  |  | 				MTPInputFile(), // video
 | 
					
						
							|  |  |  | 				MTPdouble()) // video_start_ts
 | 
					
						
							|  |  |  | 		)).done(applier).afterRequest(history->sendRequestId).send(); | 
					
						
							|  |  |  | 	} else if (const auto channel = peer->asChannel()) { | 
					
						
							|  |  |  | 		const auto history = _session->data().history(channel); | 
					
						
							|  |  |  | 		history->sendRequestId = _api.request(MTPchannels_EditPhoto( | 
					
						
							|  |  |  | 			channel->inputChannel, | 
					
						
							|  |  |  | 			MTP_inputChatUploadedPhoto( | 
					
						
							|  |  |  | 				MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file), | 
					
						
							|  |  |  | 				file, | 
					
						
							|  |  |  | 				MTPInputFile(), // video
 | 
					
						
							|  |  |  | 				MTPdouble()) // video_start_ts
 | 
					
						
							|  |  |  | 		)).done(applier).afterRequest(history->sendRequestId).send(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-16 04:05:17 +03:00
										 |  |  | 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); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		const auto fullCount = result.match([](const MTPDphotos_photos &d) { | 
					
						
							|  |  |  | 			return int(d.vphotos().v.size()); | 
					
						
							|  |  |  | 		}, [](const MTPDphotos_photosSlice &d) { | 
					
						
							|  |  |  | 			return d.vcount().v; | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		auto photoIds = result.match([&](const auto &data) { | 
					
						
							|  |  |  | 			auto &owner = _session->data(); | 
					
						
							|  |  |  | 			owner.processUsers(data.vusers()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			auto photoIds = std::vector<PhotoId>(); | 
					
						
							|  |  |  | 			photoIds.reserve(data.vphotos().v.size()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for (const auto &photo : data.vphotos().v) { | 
					
						
							|  |  |  | 				if (const auto photoData = owner.processPhoto(photo)) { | 
					
						
							|  |  |  | 					photoIds.push_back(photoData->id); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return photoIds; | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		_session->storage().add(Storage::UserPhotosAddSlice( | 
					
						
							|  |  |  | 			peerToUser(user->id), | 
					
						
							|  |  |  | 			std::move(photoIds), | 
					
						
							|  |  |  | 			fullCount | 
					
						
							|  |  |  | 		)); | 
					
						
							|  |  |  | 	}).fail([this, user] { | 
					
						
							|  |  |  | 		_userPhotosRequests.remove(user); | 
					
						
							|  |  |  | 	}).send(); | 
					
						
							|  |  |  | 	_userPhotosRequests.emplace(user, requestId); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-20 19:54:22 +03:00
										 |  |  | } // namespace Api
 |