data_web_page.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  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 "data/data_web_page.h"
  8. #include "main/main_session.h"
  9. #include "apiwrap.h"
  10. #include "mainwidget.h"
  11. #include "data/data_session.h"
  12. #include "data/data_photo.h"
  13. #include "data/data_channel.h"
  14. #include "data/data_document.h"
  15. #include "core/local_url_handlers.h"
  16. #include "lang/lang_keys.h"
  17. #include "iv/iv_data.h"
  18. #include "ui/image/image.h"
  19. #include "ui/text/text_entity.h"
  20. namespace {
  21. [[nodiscard]] WebPageCollage ExtractCollage(
  22. not_null<Data::Session*> owner,
  23. const QVector<MTPPageBlock> &items,
  24. const QVector<MTPPhoto> &photos,
  25. const QVector<MTPDocument> &documents) {
  26. const auto count = items.size();
  27. if (count < 2) {
  28. return {};
  29. }
  30. const auto bad = ranges::find_if(items, [](mtpTypeId type) {
  31. return (type != mtpc_pageBlockPhoto && type != mtpc_pageBlockVideo);
  32. }, [](const MTPPageBlock &item) {
  33. return item.type();
  34. });
  35. if (bad != items.end()) {
  36. return {};
  37. }
  38. for (const auto &photo : photos) {
  39. owner->processPhoto(photo);
  40. }
  41. for (const auto &document : documents) {
  42. owner->processDocument(document);
  43. }
  44. auto result = WebPageCollage();
  45. result.items.reserve(count);
  46. for (const auto &item : items) {
  47. const auto good = item.match([&](const MTPDpageBlockPhoto &data) {
  48. const auto photo = owner->photo(data.vphoto_id().v);
  49. if (photo->isNull()) {
  50. return false;
  51. }
  52. result.items.emplace_back(photo);
  53. return true;
  54. }, [&](const MTPDpageBlockVideo &data) {
  55. const auto document = owner->document(data.vvideo_id().v);
  56. if (!document->isVideoFile()) {
  57. return false;
  58. }
  59. result.items.emplace_back(document);
  60. return true;
  61. }, [](const auto &) -> bool {
  62. Unexpected("Type of block in Collage.");
  63. });
  64. if (!good) {
  65. return {};
  66. }
  67. }
  68. return result;
  69. }
  70. WebPageCollage ExtractCollage(
  71. not_null<Data::Session*> owner,
  72. const MTPDwebPage &data) {
  73. const auto page = data.vcached_page();
  74. if (!page) {
  75. return {};
  76. }
  77. const auto processMedia = [&] {
  78. if (const auto photo = data.vphoto()) {
  79. owner->processPhoto(*photo);
  80. }
  81. if (const auto document = data.vdocument()) {
  82. owner->processDocument(*document);
  83. }
  84. };
  85. return page->match([&](const auto &page) {
  86. for (const auto &block : page.vblocks().v) {
  87. switch (block.type()) {
  88. case mtpc_pageBlockPhoto:
  89. case mtpc_pageBlockVideo:
  90. case mtpc_pageBlockCover:
  91. case mtpc_pageBlockEmbed:
  92. case mtpc_pageBlockEmbedPost:
  93. case mtpc_pageBlockAudio:
  94. return WebPageCollage();
  95. case mtpc_pageBlockSlideshow:
  96. processMedia();
  97. return ExtractCollage(
  98. owner,
  99. block.c_pageBlockSlideshow().vitems().v,
  100. page.vphotos().v,
  101. page.vdocuments().v);
  102. case mtpc_pageBlockCollage:
  103. processMedia();
  104. return ExtractCollage(
  105. owner,
  106. block.c_pageBlockCollage().vitems().v,
  107. page.vphotos().v,
  108. page.vdocuments().v);
  109. default: break;
  110. }
  111. }
  112. return WebPageCollage();
  113. });
  114. }
  115. } // namespace
  116. WebPageType ParseWebPageType(
  117. const QString &type,
  118. const QString &embedUrl,
  119. bool hasIV) {
  120. if (type == u"video"_q || type == u"gif"_q || !embedUrl.isEmpty()) {
  121. return WebPageType::Video;
  122. } else if (type == u"photo"_q) {
  123. return WebPageType::Photo;
  124. } else if (type == u"document"_q) {
  125. return WebPageType::Document;
  126. } else if (type == u"profile"_q) {
  127. return WebPageType::Profile;
  128. } else if (type == u"telegram_background"_q) {
  129. return WebPageType::WallPaper;
  130. } else if (type == u"telegram_theme"_q) {
  131. return WebPageType::Theme;
  132. } else if (type == u"telegram_story"_q) {
  133. return WebPageType::Story;
  134. } else if (type == u"telegram_channel"_q) {
  135. return WebPageType::Channel;
  136. } else if (type == u"telegram_channel_request"_q) {
  137. return WebPageType::ChannelWithRequest;
  138. } else if (type == u"telegram_megagroup"_q
  139. || type == u"telegram_chat"_q) {
  140. return WebPageType::Group;
  141. } else if (type == u"telegram_megagroup_request"_q
  142. || type == u"telegram_chat_request"_q) {
  143. return WebPageType::GroupWithRequest;
  144. } else if (type == u"telegram_album"_q) {
  145. return WebPageType::Album;
  146. } else if (type == u"telegram_message"_q) {
  147. return WebPageType::Message;
  148. } else if (type == u"telegram_bot"_q) {
  149. return WebPageType::Bot;
  150. } else if (type == u"telegram_voicechat"_q) {
  151. return WebPageType::VoiceChat;
  152. } else if (type == u"telegram_livestream"_q) {
  153. return WebPageType::Livestream;
  154. } else if (type == u"telegram_user"_q) {
  155. return WebPageType::User;
  156. } else if (type == u"telegram_botapp"_q) {
  157. return WebPageType::BotApp;
  158. } else if (type == u"telegram_channel_boost"_q) {
  159. return WebPageType::ChannelBoost;
  160. } else if (type == u"telegram_group_boost"_q) {
  161. return WebPageType::GroupBoost;
  162. } else if (type == u"telegram_giftcode"_q) {
  163. return WebPageType::Giftcode;
  164. } else if (type == u"telegram_stickerset"_q) {
  165. return WebPageType::StickerSet;
  166. } else if (hasIV) {
  167. return WebPageType::ArticleWithIV;
  168. } else {
  169. return WebPageType::Article;
  170. }
  171. }
  172. bool IgnoreIv(WebPageType type) {
  173. return !Iv::ShowButton()
  174. || (type == WebPageType::Message)
  175. || (type == WebPageType::Album);
  176. }
  177. WebPageType ParseWebPageType(const MTPDwebPage &page) {
  178. return ParseWebPageType(
  179. qs(page.vtype().value_or_empty()),
  180. page.vembed_url().value_or_empty(),
  181. !!page.vcached_page());
  182. }
  183. WebPageCollage::WebPageCollage(
  184. not_null<Data::Session*> owner,
  185. const MTPDwebPage &data)
  186. : WebPageCollage(ExtractCollage(owner, data)) {
  187. }
  188. WebPageData::WebPageData(not_null<Data::Session*> owner, const WebPageId &id)
  189. : id(id)
  190. , _owner(owner) {
  191. }
  192. WebPageData::~WebPageData() = default;
  193. Data::Session &WebPageData::owner() const {
  194. return *_owner;
  195. }
  196. Main::Session &WebPageData::session() const {
  197. return _owner->session();
  198. }
  199. bool WebPageData::applyChanges(
  200. WebPageType newType,
  201. const QString &newUrl,
  202. const QString &newDisplayUrl,
  203. const QString &newSiteName,
  204. const QString &newTitle,
  205. const TextWithEntities &newDescription,
  206. FullStoryId newStoryId,
  207. PhotoData *newPhoto,
  208. DocumentData *newDocument,
  209. WebPageCollage &&newCollage,
  210. std::unique_ptr<Iv::Data> newIv,
  211. std::unique_ptr<WebPageStickerSet> newStickerSet,
  212. std::shared_ptr<Data::UniqueGift> newUniqueGift,
  213. int newDuration,
  214. const QString &newAuthor,
  215. bool newHasLargeMedia,
  216. bool newPhotoIsVideoCover,
  217. int newPendingTill) {
  218. if (newPendingTill != 0
  219. && (!url.isEmpty() || failed)
  220. && (!pendingTill
  221. || pendingTill == newPendingTill
  222. || newPendingTill < -1)) {
  223. return false;
  224. }
  225. const auto resultUrl = newUrl;
  226. const auto resultDisplayUrl = newDisplayUrl;
  227. const auto possibleSiteName = newSiteName;
  228. const auto resultTitle = TextUtilities::SingleLine(newTitle);
  229. const auto resultAuthor = newAuthor;
  230. const auto viewTitleText = resultTitle.isEmpty()
  231. ? TextUtilities::SingleLine(resultAuthor)
  232. : resultTitle;
  233. const auto resultSiteName = [&] {
  234. if (!possibleSiteName.isEmpty()) {
  235. return possibleSiteName;
  236. } else if (!newDescription.text.isEmpty()
  237. && viewTitleText.isEmpty()
  238. && !resultUrl.isEmpty()) {
  239. return Iv::SiteNameFromUrl(resultUrl);
  240. }
  241. return QString();
  242. }();
  243. const auto hasSiteName = !resultSiteName.isEmpty() ? 1 : 0;
  244. const auto hasTitle = !resultTitle.isEmpty() ? 1 : 0;
  245. const auto hasDescription = !newDescription.text.isEmpty() ? 1 : 0;
  246. if (newDocument
  247. || !newCollage.items.empty()
  248. || !newPhoto
  249. || (hasSiteName + hasTitle + hasDescription < 2)) {
  250. newHasLargeMedia = false;
  251. }
  252. if (!newDocument || !newDocument->isVideoFile() || !newPhoto) {
  253. newPhotoIsVideoCover = false;
  254. }
  255. if (type == newType
  256. && url == resultUrl
  257. && displayUrl == resultDisplayUrl
  258. && siteName == resultSiteName
  259. && title == resultTitle
  260. && description.text == newDescription.text
  261. && storyId == newStoryId
  262. && photo == newPhoto
  263. && document == newDocument
  264. && collage.items == newCollage.items
  265. && (!iv == !newIv)
  266. && (!iv || iv->partial() == newIv->partial())
  267. && (!stickerSet == !newStickerSet)
  268. && (!uniqueGift == !newUniqueGift)
  269. && duration == newDuration
  270. && author == resultAuthor
  271. && hasLargeMedia == (newHasLargeMedia ? 1 : 0)
  272. && photoIsVideoCover == (newPhotoIsVideoCover ? 1 : 0)
  273. && pendingTill == newPendingTill) {
  274. return false;
  275. }
  276. if (pendingTill > 0 && newPendingTill <= 0) {
  277. _owner->session().api().clearWebPageRequest(this);
  278. }
  279. type = newType;
  280. hasLargeMedia = newHasLargeMedia ? 1 : 0;
  281. photoIsVideoCover = newPhotoIsVideoCover ? 1 : 0;
  282. url = resultUrl;
  283. displayUrl = resultDisplayUrl;
  284. siteName = resultSiteName;
  285. title = resultTitle;
  286. description = newDescription;
  287. storyId = newStoryId;
  288. photo = newPhoto;
  289. document = newDocument;
  290. collage = std::move(newCollage);
  291. iv = std::move(newIv);
  292. stickerSet = std::move(newStickerSet);
  293. uniqueGift = std::move(newUniqueGift);
  294. duration = newDuration;
  295. author = resultAuthor;
  296. pendingTill = newPendingTill;
  297. ++version;
  298. if (type == WebPageType::WallPaper && document) {
  299. document->checkWallPaperProperties();
  300. }
  301. replaceDocumentGoodThumbnail();
  302. return true;
  303. }
  304. void WebPageData::replaceDocumentGoodThumbnail() {
  305. if (document && photo) {
  306. document->setGoodThumbnailPhoto(photo);
  307. }
  308. }
  309. void WebPageData::ApplyChanges(
  310. not_null<Main::Session*> session,
  311. ChannelData *channel,
  312. const MTPmessages_Messages &result) {
  313. result.match([&](
  314. const MTPDmessages_channelMessages &data) {
  315. if (channel) {
  316. channel->ptsReceived(data.vpts().v);
  317. channel->processTopics(data.vtopics());
  318. } else {
  319. LOG(("API Error: received messages.channelMessages "
  320. "when no channel was passed! (WebPageData::ApplyChanges)"));
  321. }
  322. }, [&](const auto &) {
  323. });
  324. const auto list = result.match([](
  325. const MTPDmessages_messagesNotModified &) {
  326. LOG(("API Error: received messages.messagesNotModified! "
  327. "(WebPageData::ApplyChanges)"));
  328. return static_cast<const QVector<MTPMessage>*>(nullptr);
  329. }, [&](const auto &data) {
  330. session->data().processUsers(data.vusers());
  331. session->data().processChats(data.vchats());
  332. return &data.vmessages().v;
  333. });
  334. if (!list) {
  335. return;
  336. }
  337. for (const auto &message : *list) {
  338. message.match([&](const MTPDmessage &data) {
  339. if (const auto media = data.vmedia()) {
  340. media->match([&](const MTPDmessageMediaWebPage &data) {
  341. session->data().processWebpage(data.vwebpage());
  342. }, [&](const auto &) {
  343. });
  344. }
  345. }, [&](const auto &) {
  346. });
  347. }
  348. session->data().sendWebPageGamePollNotifications();
  349. }
  350. QString WebPageData::displayedSiteName() const {
  351. return (document && document->isWallPaper())
  352. ? tr::lng_media_chat_background(tr::now)
  353. : (document && document->isTheme())
  354. ? tr::lng_media_color_theme(tr::now)
  355. : siteName;
  356. }
  357. TimeId WebPageData::extractVideoTimestamp() const {
  358. const auto take = [&](const QStringList &list, int index) {
  359. return (index >= 0 && index < list.size()) ? list[index] : QString();
  360. };
  361. const auto hashed = take(url.split('#'), 0);
  362. const auto params = take(hashed.split('?'), 1);
  363. const auto parts = params.split('&');
  364. for (const auto &part : parts) {
  365. if (part.startsWith(u"t="_q)) {
  366. return Core::ParseVideoTimestamp(part.mid(2));
  367. }
  368. }
  369. return 0;
  370. }
  371. bool WebPageData::computeDefaultSmallMedia() const {
  372. if (!collage.items.empty()) {
  373. return false;
  374. } else if (siteName.isEmpty()
  375. && title.isEmpty()
  376. && description.empty()
  377. && author.isEmpty()) {
  378. return false;
  379. } else if (!uniqueGift
  380. && !document
  381. && photo
  382. && type != WebPageType::Photo
  383. && type != WebPageType::Document
  384. && type != WebPageType::Story
  385. && type != WebPageType::Video) {
  386. if (type == WebPageType::Profile) {
  387. return true;
  388. } else if (siteName == u"Twitter"_q
  389. || siteName == u"Facebook"_q
  390. || type == WebPageType::ArticleWithIV) {
  391. return false;
  392. } else {
  393. return true;
  394. }
  395. }
  396. return false;
  397. }
  398. bool WebPageData::suggestEnlargePhoto() const {
  399. return !siteName.isEmpty() || !title.isEmpty() || !description.empty();
  400. }