api_sending.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  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_sending.h"
  8. #include "core/wallet_replacer.h"
  9. #include "api/api_text_entities.h"
  10. #include "base/random.h"
  11. #include "base/unixtime.h"
  12. #include "data/business/data_shortcut_messages.h"
  13. #include "data/data_document.h"
  14. #include "data/data_photo.h"
  15. #include "data/data_channel.h" // ChannelData::addsSignature.
  16. #include "data/data_user.h" // UserData::name
  17. #include "data/data_session.h"
  18. #include "data/data_file_origin.h"
  19. #include "data/data_histories.h"
  20. #include "data/data_changes.h"
  21. #include "data/stickers/data_stickers.h"
  22. #include "history/history.h"
  23. #include "history/history_item.h"
  24. #include "history/history_item_helpers.h" // NewMessageFlags.
  25. #include "chat_helpers/message_field.h" // ConvertTextTagsToEntities.
  26. #include "chat_helpers/stickers_dice_pack.h" // DicePacks::kDiceString.
  27. #include "ui/text/text_entity.h" // TextWithEntities.
  28. #include "ui/item_text_options.h" // Ui::ItemTextOptions.
  29. #include "main/main_session.h"
  30. #include "main/main_app_config.h"
  31. #include "storage/localimageloader.h"
  32. #include "storage/file_upload.h"
  33. #include "mainwidget.h"
  34. #include "apiwrap.h"
  35. namespace Api {
  36. namespace {
  37. void InnerFillMessagePostFlags(
  38. const SendOptions &options,
  39. not_null<PeerData*> peer,
  40. MessageFlags &flags) {
  41. if (ShouldSendSilent(peer, options)) {
  42. flags |= MessageFlag::Silent;
  43. }
  44. if (!peer->amAnonymous()
  45. || (!peer->isBroadcast()
  46. && options.sendAs
  47. && options.sendAs != peer)) {
  48. flags |= MessageFlag::HasFromId;
  49. }
  50. const auto channel = peer->asBroadcast();
  51. if (!channel) {
  52. return;
  53. }
  54. flags |= MessageFlag::Post;
  55. // Don't display views and author of a new post when it's scheduled.
  56. if (options.scheduled) {
  57. return;
  58. }
  59. flags |= MessageFlag::HasViews;
  60. if (channel->addsSignature()) {
  61. flags |= MessageFlag::HasPostAuthor;
  62. }
  63. }
  64. void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
  65. const auto history = action.history;
  66. const auto peer = history->peer;
  67. const auto session = &history->session();
  68. const auto api = &session->api();
  69. action.clearDraft = false;
  70. action.generateLocal = false;
  71. api->sendAction(action);
  72. const auto randomId = base::RandomValue<uint64>();
  73. auto flags = NewMessageFlags(peer);
  74. auto sendFlags = MTPmessages_SendMedia::Flags(0);
  75. if (action.replyTo) {
  76. flags |= MessageFlag::HasReplyInfo;
  77. sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
  78. }
  79. const auto silentPost = ShouldSendSilent(peer, action.options);
  80. InnerFillMessagePostFlags(action.options, peer, flags);
  81. if (silentPost) {
  82. sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
  83. }
  84. const auto sendAs = action.options.sendAs;
  85. if (sendAs) {
  86. sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
  87. }
  88. const auto messagePostAuthor = peer->isBroadcast()
  89. ? session->user()->name()
  90. : QString();
  91. const auto starsPaid = std::min(
  92. peer->starsPerMessageChecked(),
  93. action.options.starsApproved);
  94. if (action.options.scheduled) {
  95. flags |= MessageFlag::IsOrWasScheduled;
  96. sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
  97. }
  98. if (action.options.shortcutId) {
  99. flags |= MessageFlag::ShortcutMessage;
  100. sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
  101. }
  102. if (action.options.effectId) {
  103. sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
  104. }
  105. if (action.options.invertCaption) {
  106. flags |= MessageFlag::InvertMedia;
  107. sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
  108. }
  109. if (starsPaid) {
  110. action.options.starsApproved -= starsPaid;
  111. sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
  112. }
  113. auto &histories = history->owner().histories();
  114. histories.sendPreparedMessage(
  115. history,
  116. action.replyTo,
  117. randomId,
  118. Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
  119. MTP_flags(sendFlags),
  120. peer->input,
  121. Data::Histories::ReplyToPlaceholder(),
  122. std::move(inputMedia),
  123. MTPstring(),
  124. MTP_long(randomId),
  125. MTPReplyMarkup(),
  126. MTPvector<MTPMessageEntity>(),
  127. MTP_int(action.options.scheduled),
  128. (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
  129. Data::ShortcutIdToMTP(session, action.options.shortcutId),
  130. MTP_long(action.options.effectId),
  131. MTP_long(starsPaid)
  132. ), [=](const MTPUpdates &result, const MTP::Response &response) {
  133. }, [=](const MTP::Error &error, const MTP::Response &response) {
  134. api->sendMessageFail(error, peer, randomId);
  135. });
  136. api->finishForwarding(action);
  137. }
  138. template <typename MediaData>
  139. void SendExistingMedia(
  140. MessageToSend &&message,
  141. not_null<MediaData*> media,
  142. Fn<MTPInputMedia()> inputMedia,
  143. Data::FileOrigin origin,
  144. std::optional<MsgId> localMessageId) {
  145. const auto history = message.action.history;
  146. const auto peer = history->peer;
  147. const auto session = &history->session();
  148. const auto api = &session->api();
  149. message.action.clearDraft = false;
  150. message.action.generateLocal = true;
  151. api->sendAction(message.action);
  152. const auto newId = FullMsgId(
  153. peer->id,
  154. localMessageId
  155. ? (*localMessageId)
  156. : session->data().nextLocalMessageId());
  157. const auto randomId = base::RandomValue<uint64>();
  158. auto &action = message.action;
  159. auto flags = NewMessageFlags(peer);
  160. auto sendFlags = MTPmessages_SendMedia::Flags(0);
  161. if (action.replyTo) {
  162. flags |= MessageFlag::HasReplyInfo;
  163. sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
  164. }
  165. const auto silentPost = ShouldSendSilent(peer, action.options);
  166. InnerFillMessagePostFlags(action.options, peer, flags);
  167. if (silentPost) {
  168. sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
  169. }
  170. const auto sendAs = action.options.sendAs;
  171. if (sendAs) {
  172. sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
  173. }
  174. auto caption = TextWithEntities{
  175. message.textWithTags.text,
  176. TextUtilities::ConvertTextTagsToEntities(message.textWithTags.tags)
  177. };
  178. TextUtilities::Trim(caption);
  179. auto sentEntities = EntitiesToMTP(
  180. session,
  181. caption.entities,
  182. ConvertOption::SkipLocal);
  183. if (!sentEntities.v.isEmpty()) {
  184. sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
  185. }
  186. const auto captionText = caption.text;
  187. const auto starsPaid = std::min(
  188. peer->starsPerMessageChecked(),
  189. action.options.starsApproved);
  190. if (action.options.scheduled) {
  191. flags |= MessageFlag::IsOrWasScheduled;
  192. sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
  193. }
  194. if (action.options.shortcutId) {
  195. flags |= MessageFlag::ShortcutMessage;
  196. sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
  197. }
  198. if (action.options.effectId) {
  199. sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
  200. }
  201. if (action.options.invertCaption) {
  202. flags |= MessageFlag::InvertMedia;
  203. sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
  204. }
  205. if (starsPaid) {
  206. action.options.starsApproved -= starsPaid;
  207. sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
  208. }
  209. session->data().registerMessageRandomId(randomId, newId);
  210. history->addNewLocalMessage({
  211. .id = newId.msg,
  212. .flags = flags,
  213. .from = NewMessageFromId(action),
  214. .replyTo = action.replyTo,
  215. .date = NewMessageDate(action.options),
  216. .shortcutId = action.options.shortcutId,
  217. .starsPaid = starsPaid,
  218. .postAuthor = NewMessagePostAuthor(action),
  219. .effectId = action.options.effectId,
  220. }, media, caption);
  221. const auto performRequest = [=](const auto &repeatRequest) -> void {
  222. auto &histories = history->owner().histories();
  223. const auto session = &history->session();
  224. const auto usedFileReference = media->fileReference();
  225. histories.sendPreparedMessage(
  226. history,
  227. action.replyTo,
  228. randomId,
  229. Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
  230. MTP_flags(sendFlags),
  231. peer->input,
  232. Data::Histories::ReplyToPlaceholder(),
  233. inputMedia(),
  234. MTP_string(captionText),
  235. MTP_long(randomId),
  236. MTPReplyMarkup(),
  237. sentEntities,
  238. MTP_int(action.options.scheduled),
  239. (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
  240. Data::ShortcutIdToMTP(session, action.options.shortcutId),
  241. MTP_long(action.options.effectId),
  242. MTP_long(starsPaid)
  243. ), [=](const MTPUpdates &result, const MTP::Response &response) {
  244. }, [=](const MTP::Error &error, const MTP::Response &response) {
  245. if (error.code() == 400
  246. && error.type().startsWith(u"FILE_REFERENCE_"_q)) {
  247. api->refreshFileReference(origin, [=](const auto &result) {
  248. if (media->fileReference() != usedFileReference) {
  249. repeatRequest(repeatRequest);
  250. } else {
  251. api->sendMessageFail(error, peer, randomId, newId);
  252. }
  253. });
  254. } else {
  255. api->sendMessageFail(error, peer, randomId, newId);
  256. }
  257. });
  258. };
  259. performRequest(performRequest);
  260. api->finishForwarding(action);
  261. }
  262. } // namespace
  263. void SendExistingDocument(
  264. MessageToSend &&message,
  265. not_null<DocumentData*> document,
  266. std::optional<MsgId> localMessageId) {
  267. const auto inputMedia = [=] {
  268. return MTP_inputMediaDocument(
  269. MTP_flags(0),
  270. document->mtpInput(),
  271. MTPInputPhoto(), // video_cover
  272. MTPint(), // ttl_seconds
  273. MTPint(), // video_timestamp
  274. MTPstring()); // query
  275. };
  276. SendExistingMedia(
  277. std::move(message),
  278. document,
  279. inputMedia,
  280. document->stickerOrGifOrigin(),
  281. std::move(localMessageId));
  282. if (document->sticker()) {
  283. document->owner().stickers().incrementSticker(document);
  284. }
  285. }
  286. void SendExistingPhoto(
  287. MessageToSend &&message,
  288. not_null<PhotoData*> photo,
  289. std::optional<MsgId> localMessageId) {
  290. const auto inputMedia = [=] {
  291. return MTP_inputMediaPhoto(
  292. MTP_flags(0),
  293. photo->mtpInput(),
  294. MTPint());
  295. };
  296. SendExistingMedia(
  297. std::move(message),
  298. photo,
  299. inputMedia,
  300. Data::FileOrigin(),
  301. std::move(localMessageId));
  302. }
  303. bool SendDice(MessageToSend &message) {
  304. const auto full = QStringView(message.textWithTags.text).trimmed();
  305. auto length = 0;
  306. if (!Ui::Emoji::Find(full.data(), full.data() + full.size(), &length)
  307. || length != full.size()
  308. || !message.textWithTags.tags.isEmpty()) {
  309. return false;
  310. }
  311. auto &config = message.action.history->session().appConfig();
  312. static const auto hardcoded = std::vector<QString>{
  313. Stickers::DicePacks::kDiceString,
  314. Stickers::DicePacks::kDartString,
  315. Stickers::DicePacks::kSlotString,
  316. Stickers::DicePacks::kFballString,
  317. Stickers::DicePacks::kFballString + QChar(0xFE0F),
  318. Stickers::DicePacks::kBballString,
  319. };
  320. const auto list = config.get<std::vector<QString>>(
  321. "emojies_send_dice",
  322. hardcoded);
  323. const auto emoji = full.toString();
  324. if (!ranges::contains(list, emoji)) {
  325. return false;
  326. }
  327. const auto history = message.action.history;
  328. const auto peer = history->peer;
  329. const auto session = &history->session();
  330. const auto api = &session->api();
  331. message.textWithTags = TextWithTags();
  332. message.action.clearDraft = false;
  333. message.action.generateLocal = true;
  334. auto &action = message.action;
  335. api->sendAction(action);
  336. const auto newId = FullMsgId(
  337. peer->id,
  338. session->data().nextLocalMessageId());
  339. const auto randomId = base::RandomValue<uint64>();
  340. auto &histories = history->owner().histories();
  341. auto flags = NewMessageFlags(peer);
  342. auto sendFlags = MTPmessages_SendMedia::Flags(0);
  343. if (action.replyTo) {
  344. flags |= MessageFlag::HasReplyInfo;
  345. sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
  346. }
  347. const auto silentPost = ShouldSendSilent(peer, action.options);
  348. InnerFillMessagePostFlags(action.options, peer, flags);
  349. if (silentPost) {
  350. sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
  351. }
  352. const auto sendAs = action.options.sendAs;
  353. if (sendAs) {
  354. sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
  355. }
  356. if (action.options.scheduled) {
  357. flags |= MessageFlag::IsOrWasScheduled;
  358. sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
  359. }
  360. if (action.options.shortcutId) {
  361. flags |= MessageFlag::ShortcutMessage;
  362. sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
  363. }
  364. if (action.options.effectId) {
  365. sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
  366. }
  367. if (action.options.invertCaption) {
  368. flags |= MessageFlag::InvertMedia;
  369. sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
  370. }
  371. const auto starsPaid = std::min(
  372. peer->starsPerMessageChecked(),
  373. action.options.starsApproved);
  374. if (starsPaid) {
  375. action.options.starsApproved -= starsPaid;
  376. sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
  377. }
  378. session->data().registerMessageRandomId(randomId, newId);
  379. history->addNewLocalMessage({
  380. .id = newId.msg,
  381. .flags = flags,
  382. .from = NewMessageFromId(action),
  383. .replyTo = action.replyTo,
  384. .date = NewMessageDate(action.options),
  385. .shortcutId = action.options.shortcutId,
  386. .starsPaid = starsPaid,
  387. .postAuthor = NewMessagePostAuthor(action),
  388. .effectId = action.options.effectId,
  389. }, TextWithEntities(), MTP_messageMediaDice(
  390. MTP_int(0),
  391. MTP_string(emoji)));
  392. histories.sendPreparedMessage(
  393. history,
  394. action.replyTo,
  395. randomId,
  396. Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
  397. MTP_flags(sendFlags),
  398. peer->input,
  399. Data::Histories::ReplyToPlaceholder(),
  400. MTP_inputMediaDice(MTP_string(emoji)),
  401. MTP_string(),
  402. MTP_long(randomId),
  403. MTPReplyMarkup(),
  404. MTP_vector<MTPMessageEntity>(),
  405. MTP_int(action.options.scheduled),
  406. (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
  407. Data::ShortcutIdToMTP(session, action.options.shortcutId),
  408. MTP_long(action.options.effectId),
  409. MTP_long(starsPaid)
  410. ), [=](const MTPUpdates &result, const MTP::Response &response) {
  411. }, [=](const MTP::Error &error, const MTP::Response &response) {
  412. api->sendMessageFail(error, peer, randomId, newId);
  413. });
  414. api->finishForwarding(action);
  415. return true;
  416. }
  417. void SendLocation(SendAction action, float64 lat, float64 lon) {
  418. SendSimpleMedia(
  419. action,
  420. MTP_inputMediaGeoPoint(
  421. MTP_inputGeoPoint(
  422. MTP_flags(0),
  423. MTP_double(lat),
  424. MTP_double(lon),
  425. MTPint()))); // accuracy_radius
  426. }
  427. void SendVenue(SendAction action, Data::InputVenue venue) {
  428. SendSimpleMedia(
  429. action,
  430. MTP_inputMediaVenue(
  431. MTP_inputGeoPoint(
  432. MTP_flags(0),
  433. MTP_double(venue.lat),
  434. MTP_double(venue.lon),
  435. MTPint()), // accuracy_radius
  436. MTP_string(venue.title),
  437. MTP_string(venue.address),
  438. MTP_string(venue.provider),
  439. MTP_string(venue.id),
  440. MTP_string(venue.venueType)));
  441. }
  442. void FillMessagePostFlags(
  443. const SendAction &action,
  444. not_null<PeerData*> peer,
  445. MessageFlags &flags) {
  446. InnerFillMessagePostFlags(action.options, peer, flags);
  447. }
  448. void SendConfirmedFile(
  449. not_null<Main::Session*> session,
  450. const std::shared_ptr<FilePrepareResult> &file) {
  451. const auto isEditing = (file->type != SendMediaType::Audio)
  452. && (file->type != SendMediaType::Round)
  453. && (file->to.replaceMediaOf != 0);
  454. const auto newId = FullMsgId(
  455. file->to.peer,
  456. (isEditing
  457. ? file->to.replaceMediaOf
  458. : session->data().nextLocalMessageId()));
  459. const auto groupId = file->album ? file->album->groupId : uint64(0);
  460. if (file->album) {
  461. const auto proj = [](const SendingAlbum::Item &item) {
  462. return item.taskId;
  463. };
  464. const auto it = ranges::find(file->album->items, file->taskId, proj);
  465. Assert(it != file->album->items.end());
  466. it->msgId = newId;
  467. }
  468. const auto itemToEdit = isEditing
  469. ? session->data().message(newId)
  470. : nullptr;
  471. const auto history = session->data().history(file->to.peer);
  472. const auto peer = history->peer;
  473. if (!isEditing) {
  474. const auto histories = &session->data().histories();
  475. file->to.replyTo.messageId = histories->convertTopicReplyToId(
  476. history,
  477. file->to.replyTo.messageId);
  478. file->to.replyTo.topicRootId = histories->convertTopicReplyToId(
  479. history,
  480. file->to.replyTo.topicRootId);
  481. }
  482. session->uploader().upload(newId, file);
  483. auto action = SendAction(history, file->to.options);
  484. action.clearDraft = false;
  485. action.replyTo = file->to.replyTo;
  486. action.generateLocal = true;
  487. action.replaceMediaOf = file->to.replaceMediaOf;
  488. session->api().sendAction(action);
  489. auto caption = TextWithEntities{
  490. file->caption.text,
  491. TextUtilities::ConvertTextTagsToEntities(file->caption.tags)
  492. };
  493. const auto prepareFlags = Ui::ItemTextOptions(
  494. history,
  495. session->user()).flags;
  496. TextUtilities::PrepareForSending(caption, prepareFlags);
  497. TextUtilities::Trim(caption);
  498. auto flags = isEditing ? MessageFlags() : NewMessageFlags(peer);
  499. if (file->to.replyTo) {
  500. flags |= MessageFlag::HasReplyInfo;
  501. }
  502. FillMessagePostFlags(action, peer, flags);
  503. if (file->to.options.scheduled) {
  504. flags |= MessageFlag::IsOrWasScheduled;
  505. // Scheduled messages have no 'edited' badge.
  506. flags |= MessageFlag::HideEdited;
  507. }
  508. if (file->to.options.shortcutId) {
  509. flags |= MessageFlag::ShortcutMessage;
  510. // Shortcut messages have no 'edited' badge.
  511. flags |= MessageFlag::HideEdited;
  512. }
  513. if (file->type == SendMediaType::Audio
  514. || file->type == SendMediaType::Round) {
  515. if (!peer->isChannel() || peer->isMegagroup()) {
  516. flags |= MessageFlag::MediaIsUnread;
  517. }
  518. }
  519. if (file->to.options.invertCaption) {
  520. flags |= MessageFlag::InvertMedia;
  521. }
  522. const auto media = MTPMessageMedia([&] {
  523. if (file->type == SendMediaType::Photo) {
  524. using Flag = MTPDmessageMediaPhoto::Flag;
  525. return MTP_messageMediaPhoto(
  526. MTP_flags(Flag::f_photo
  527. | (file->spoiler ? Flag::f_spoiler : Flag())),
  528. file->photo,
  529. MTPint());
  530. } else if (file->type == SendMediaType::File) {
  531. using Flag = MTPDmessageMediaDocument::Flag;
  532. return MTP_messageMediaDocument(
  533. MTP_flags(Flag::f_document
  534. | (file->spoiler ? Flag::f_spoiler : Flag())
  535. | (file->videoCover ? Flag::f_video_cover : Flag())),
  536. file->document,
  537. MTPVector<MTPDocument>(), // alt_documents
  538. file->videoCover ? file->videoCover->photo : MTPPhoto(),
  539. MTPint(), // video_timestamp
  540. MTPint());
  541. } else if (file->type == SendMediaType::Audio) {
  542. const auto ttlSeconds = file->to.options.ttlSeconds;
  543. using Flag = MTPDmessageMediaDocument::Flag;
  544. return MTP_messageMediaDocument(
  545. MTP_flags(Flag::f_document
  546. | Flag::f_voice
  547. | (ttlSeconds ? Flag::f_ttl_seconds : Flag())
  548. | (file->videoCover ? Flag::f_video_cover : Flag())),
  549. file->document,
  550. MTPVector<MTPDocument>(), // alt_documents
  551. file->videoCover ? file->videoCover->photo : MTPPhoto(),
  552. MTPint(), // video_timestamp
  553. MTP_int(ttlSeconds));
  554. } else if (file->type == SendMediaType::Round) {
  555. using Flag = MTPDmessageMediaDocument::Flag;
  556. const auto ttlSeconds = file->to.options.ttlSeconds;
  557. return MTP_messageMediaDocument(
  558. MTP_flags(Flag::f_document
  559. | Flag::f_round
  560. | (ttlSeconds ? Flag::f_ttl_seconds : Flag())
  561. | (file->spoiler ? Flag::f_spoiler : Flag())),
  562. file->document,
  563. MTPVector<MTPDocument>(), // alt_documents
  564. MTPPhoto(), // video_cover
  565. MTPint(), // video_timestamp
  566. MTP_int(ttlSeconds));
  567. } else {
  568. Unexpected("Type in sendFilesConfirmed.");
  569. }
  570. }());
  571. if (itemToEdit) {
  572. auto edition = HistoryMessageEdition();
  573. edition.isEditHide = (flags & MessageFlag::HideEdited);
  574. edition.editDate = 0;
  575. edition.ttl = 0;
  576. edition.mtpMedia = &media;
  577. edition.textWithEntities = caption;
  578. edition.invertMedia = file->to.options.invertCaption;
  579. edition.useSameViews = true;
  580. edition.useSameForwards = true;
  581. edition.useSameMarkup = true;
  582. edition.useSameReplies = true;
  583. edition.useSameReactions = true;
  584. edition.savePreviousMedia = true;
  585. itemToEdit->applyEdition(std::move(edition));
  586. } else {
  587. history->addNewLocalMessage({
  588. .id = newId.msg,
  589. .flags = flags,
  590. .from = NewMessageFromId(action),
  591. .replyTo = file->to.replyTo,
  592. .date = NewMessageDate(file->to.options),
  593. .shortcutId = file->to.options.shortcutId,
  594. .starsPaid = std::min(
  595. history->peer->starsPerMessageChecked(),
  596. file->to.options.starsApproved),
  597. .postAuthor = NewMessagePostAuthor(action),
  598. .groupedId = groupId,
  599. .effectId = file->to.options.effectId,
  600. }, caption, media);
  601. }
  602. if (isEditing) {
  603. return;
  604. }
  605. session->data().sendHistoryChangeNotifications();
  606. if (!itemToEdit) {
  607. session->changes().historyUpdated(
  608. history,
  609. (action.options.scheduled
  610. ? Data::HistoryUpdate::Flag::ScheduledSent
  611. : Data::HistoryUpdate::Flag::MessageSent));
  612. }
  613. }
  614. void Api::SendMessage(
  615. not_null<History*> history,
  616. MessageToSend &&message,
  617. MessageFlags flags,
  618. MsgId replyTo,
  619. bool invertMedia) {
  620. // ... existing code ...
  621. // ... existing code ...
  622. }
  623. } // namespace Api