api_editing.cpp 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "api/api_editing.h"
  8. #include "apiwrap.h"
  9. #include "api/api_media.h"
  10. #include "api/api_text_entities.h"
  11. #include "ui/boxes/confirm_box.h"
  12. #include "data/business/data_shortcut_messages.h"
  13. #include "data/components/scheduled_messages.h"
  14. #include "data/data_file_origin.h"
  15. #include "data/data_histories.h"
  16. #include "data/data_session.h"
  17. #include "data/data_web_page.h"
  18. #include "history/view/controls/history_view_compose_media_edit_manager.h"
  19. #include "history/history.h"
  20. #include "lang/lang_keys.h"
  21. #include "main/main_session.h"
  22. #include "mtproto/mtproto_response.h"
  23. #include "boxes/abstract_box.h" // Ui::show().
  24. namespace Api {
  25. namespace {
  26. using namespace rpl::details;
  27. template <typename T>
  28. constexpr auto WithId
  29. = is_callable_plain_v<T, Fn<void()>, mtpRequestId>;
  30. template <typename T>
  31. constexpr auto WithoutId
  32. = is_callable_plain_v<T, Fn<void()>>;
  33. template <typename T>
  34. constexpr auto WithoutCallback
  35. = is_callable_plain_v<T>;
  36. template <typename T>
  37. constexpr auto ErrorWithId
  38. = is_callable_plain_v<T, QString, mtpRequestId>;
  39. template <typename T>
  40. constexpr auto ErrorWithoutId
  41. = is_callable_plain_v<T, QString>;
  42. template <typename DoneCallback, typename FailCallback>
  43. mtpRequestId EditMessage(
  44. not_null<HistoryItem*> item,
  45. const TextWithEntities &textWithEntities,
  46. Data::WebPageDraft webpage,
  47. SendOptions options,
  48. DoneCallback &&done,
  49. FailCallback &&fail,
  50. std::optional<MTPInputMedia> inputMedia = std::nullopt) {
  51. const auto session = &item->history()->session();
  52. const auto api = &session->api();
  53. const auto text = textWithEntities.text;
  54. const auto sentEntities = EntitiesToMTP(
  55. session,
  56. textWithEntities.entities,
  57. ConvertOption::SkipLocal);
  58. const auto media = item->media();
  59. const auto updateRecentStickers = inputMedia.has_value()
  60. ? Api::HasAttachedStickers(*inputMedia)
  61. : false;
  62. const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
  63. const auto flags = emptyFlag
  64. | ((!text.isEmpty() || media)
  65. ? MTPmessages_EditMessage::Flag::f_message
  66. : emptyFlag)
  67. | ((media && inputMedia.has_value())
  68. ? MTPmessages_EditMessage::Flag::f_media
  69. : emptyFlag)
  70. | (webpage.removed
  71. ? MTPmessages_EditMessage::Flag::f_no_webpage
  72. : emptyFlag)
  73. | ((!webpage.removed && !webpage.url.isEmpty())
  74. ? MTPmessages_EditMessage::Flag::f_media
  75. : emptyFlag)
  76. | (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
  77. || options.invertCaption)
  78. ? MTPmessages_EditMessage::Flag::f_invert_media
  79. : emptyFlag)
  80. | (!sentEntities.v.isEmpty()
  81. ? MTPmessages_EditMessage::Flag::f_entities
  82. : emptyFlag)
  83. | (options.scheduled
  84. ? MTPmessages_EditMessage::Flag::f_schedule_date
  85. : emptyFlag)
  86. | (item->isBusinessShortcut()
  87. ? MTPmessages_EditMessage::Flag::f_quick_reply_shortcut_id
  88. : emptyFlag);
  89. const auto id = item->isScheduled()
  90. ? session->scheduledMessages().lookupId(item)
  91. : item->isBusinessShortcut()
  92. ? session->data().shortcutMessages().lookupId(item)
  93. : item->id;
  94. return api->request(MTPmessages_EditMessage(
  95. MTP_flags(flags),
  96. item->history()->peer->input,
  97. MTP_int(id),
  98. MTP_string(text),
  99. inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())),
  100. MTPReplyMarkup(),
  101. sentEntities,
  102. MTP_int(options.scheduled),
  103. MTP_int(item->shortcutId())
  104. )).done([=](
  105. const MTPUpdates &result,
  106. [[maybe_unused]] mtpRequestId requestId) {
  107. const auto apply = [=] { api->applyUpdates(result); };
  108. if constexpr (WithId<DoneCallback>) {
  109. done(apply, requestId);
  110. } else if constexpr (WithoutId<DoneCallback>) {
  111. done(apply);
  112. } else if constexpr (WithoutCallback<DoneCallback>) {
  113. done();
  114. apply();
  115. } else {
  116. t_bad_callback(done);
  117. }
  118. if (updateRecentStickers) {
  119. api->requestSpecialStickersForce(false, false, true);
  120. }
  121. }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
  122. if constexpr (ErrorWithId<FailCallback>) {
  123. fail(error.type(), requestId);
  124. } else if constexpr (ErrorWithoutId<FailCallback>) {
  125. fail(error.type());
  126. } else if constexpr (WithoutCallback<FailCallback>) {
  127. fail();
  128. } else {
  129. t_bad_callback(fail);
  130. }
  131. }).send();
  132. }
  133. template <typename DoneCallback, typename FailCallback>
  134. mtpRequestId EditMessage(
  135. not_null<HistoryItem*> item,
  136. SendOptions options,
  137. DoneCallback &&done,
  138. FailCallback &&fail,
  139. std::optional<MTPInputMedia> inputMedia = std::nullopt) {
  140. const auto &text = item->originalText();
  141. const auto webpage = (!item->media() || !item->media()->webpage())
  142. ? Data::WebPageDraft{ .removed = true }
  143. : Data::WebPageDraft::FromItem(item);
  144. return EditMessage(
  145. item,
  146. text,
  147. webpage,
  148. options,
  149. std::forward<DoneCallback>(done),
  150. std::forward<FailCallback>(fail),
  151. inputMedia);
  152. }
  153. void EditMessageWithUploadedMedia(
  154. not_null<HistoryItem*> item,
  155. SendOptions options,
  156. MTPInputMedia media) {
  157. const auto done = [=](Fn<void()> applyUpdates) {
  158. if (item) {
  159. item->removeFromSharedMediaIndex();
  160. item->clearSavedMedia();
  161. item->setIsLocalUpdateMedia(true);
  162. applyUpdates();
  163. item->setIsLocalUpdateMedia(false);
  164. }
  165. };
  166. const auto fail = [=](const QString &error) {
  167. const auto session = &item->history()->session();
  168. const auto notModified = (error == u"MESSAGE_NOT_MODIFIED"_q);
  169. const auto mediaInvalid = (error == u"MEDIA_NEW_INVALID"_q);
  170. if (notModified || mediaInvalid) {
  171. item->returnSavedMedia();
  172. session->data().sendHistoryChangeNotifications();
  173. if (mediaInvalid) {
  174. Ui::show(
  175. Ui::MakeInformBox(tr::lng_edit_media_invalid_file()),
  176. Ui::LayerOption::KeepOther);
  177. }
  178. } else {
  179. session->api().sendMessageFail(error, item->history()->peer);
  180. }
  181. };
  182. EditMessage(item, options, done, fail, media);
  183. }
  184. } // namespace
  185. void RescheduleMessage(
  186. not_null<HistoryItem*> item,
  187. SendOptions options) {
  188. const auto empty = [] {};
  189. options.invertCaption = item->invertMedia();
  190. EditMessage(item, options, empty, empty);
  191. }
  192. void EditMessageWithUploadedDocument(
  193. HistoryItem *item,
  194. RemoteFileInfo info,
  195. SendOptions options) {
  196. if (!item || !item->media() || !item->media()->document()) {
  197. return;
  198. }
  199. EditMessageWithUploadedMedia(
  200. item,
  201. options,
  202. PrepareUploadedDocument(item, std::move(info)));
  203. }
  204. void EditMessageWithUploadedPhoto(
  205. HistoryItem *item,
  206. RemoteFileInfo info,
  207. SendOptions options) {
  208. if (!item || !item->media() || !item->media()->photo()) {
  209. return;
  210. }
  211. EditMessageWithUploadedMedia(
  212. item,
  213. options,
  214. PrepareUploadedPhoto(item, std::move(info)));
  215. }
  216. mtpRequestId EditCaption(
  217. not_null<HistoryItem*> item,
  218. const TextWithEntities &caption,
  219. SendOptions options,
  220. Fn<void()> done,
  221. Fn<void(const QString &)> fail) {
  222. return EditMessage(
  223. item,
  224. caption,
  225. Data::WebPageDraft(),
  226. options,
  227. done,
  228. fail);
  229. }
  230. mtpRequestId EditTextMessage(
  231. not_null<HistoryItem*> item,
  232. const TextWithEntities &caption,
  233. Data::WebPageDraft webpage,
  234. SendOptions options,
  235. Fn<void(mtpRequestId requestId)> done,
  236. Fn<void(const QString &error, mtpRequestId requestId)> fail,
  237. bool spoilered) {
  238. const auto media = item->media();
  239. if (media
  240. && HistoryView::MediaEditManager::CanBeSpoilered(item)
  241. && spoilered != media->hasSpoiler()) {
  242. auto takeInputMedia = Fn<std::optional<MTPInputMedia>()>(nullptr);
  243. auto takeFileReference = Fn<QByteArray()>(nullptr);
  244. if (const auto photo = media->photo()) {
  245. using Flag = MTPDinputMediaPhoto::Flag;
  246. const auto flags = Flag()
  247. | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
  248. | (spoilered ? Flag::f_spoiler : Flag());
  249. takeInputMedia = [=] {
  250. return MTP_inputMediaPhoto(
  251. MTP_flags(flags),
  252. photo->mtpInput(),
  253. MTP_int(media->ttlSeconds()));
  254. };
  255. takeFileReference = [=] { return photo->fileReference(); };
  256. } else if (const auto document = media->document()) {
  257. using Flag = MTPDinputMediaDocument::Flag;
  258. const auto videoCover = media->videoCover();
  259. const auto videoTimestamp = media->videoTimestamp();
  260. const auto flags = Flag()
  261. | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
  262. | (spoilered ? Flag::f_spoiler : Flag())
  263. | (videoTimestamp ? Flag::f_video_timestamp : Flag())
  264. | (videoCover ? Flag::f_video_cover : Flag());
  265. takeInputMedia = [=] {
  266. return MTP_inputMediaDocument(
  267. MTP_flags(flags),
  268. document->mtpInput(),
  269. (videoCover
  270. ? videoCover->mtpInput()
  271. : MTPInputPhoto()),
  272. MTP_int(media->ttlSeconds()),
  273. MTP_int(videoTimestamp),
  274. MTPstring()); // query
  275. };
  276. takeFileReference = [=] { return document->fileReference(); };
  277. }
  278. const auto usedFileReference = takeFileReference
  279. ? takeFileReference()
  280. : QByteArray();
  281. const auto origin = item->fullId();
  282. const auto api = &item->history()->session().api();
  283. const auto performRequest = [=](
  284. const auto &repeatRequest,
  285. mtpRequestId originalRequestId) -> mtpRequestId {
  286. const auto handleReference = [=](
  287. const QString &error,
  288. mtpRequestId requestId) {
  289. if (error.startsWith(u"FILE_REFERENCE_"_q)) {
  290. api->refreshFileReference(origin, [=](const auto &) {
  291. if (takeFileReference &&
  292. (takeFileReference() != usedFileReference)) {
  293. repeatRequest(
  294. repeatRequest,
  295. originalRequestId
  296. ? originalRequestId
  297. : requestId);
  298. } else {
  299. fail(error, requestId);
  300. }
  301. });
  302. } else {
  303. fail(error, requestId);
  304. }
  305. };
  306. const auto callback = [=](
  307. Fn<void()> applyUpdates,
  308. mtpRequestId requestId) {
  309. applyUpdates();
  310. done(originalRequestId ? originalRequestId : requestId);
  311. };
  312. const auto requestId = EditMessage(
  313. item,
  314. caption,
  315. webpage,
  316. options,
  317. callback,
  318. handleReference,
  319. takeInputMedia ? takeInputMedia() : std::nullopt);
  320. return originalRequestId ? originalRequestId : requestId;
  321. };
  322. return performRequest(performRequest, 0);
  323. }
  324. const auto callback = [=](Fn<void()> applyUpdates, mtpRequestId id) {
  325. applyUpdates();
  326. done(id);
  327. };
  328. return EditMessage(
  329. item,
  330. caption,
  331. webpage,
  332. options,
  333. callback,
  334. fail,
  335. std::nullopt);
  336. }
  337. } // namespace Api