| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296 |
- /*
- 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 "iv/iv_prepare.h"
- #include "base/openssl_help.h"
- #include "base/unixtime.h"
- #include "iv/iv_data.h"
- #include "lang/lang_keys.h"
- #include "ui/image/image_prepare.h"
- #include "ui/grouped_layout.h"
- #include "styles/palette.h"
- #include "styles/style_chat.h"
- #include <QtCore/QSize>
- namespace Iv {
- namespace {
- struct Attribute {
- QByteArray name;
- std::optional<QByteArray> value;
- };
- using Attributes = std::vector<Attribute>;
- struct Photo {
- uint64 id = 0;
- int width = 0;
- int height = 0;
- QByteArray minithumbnail;
- };
- struct Document {
- uint64 id = 0;
- int width = 0;
- int height = 0;
- QByteArray minithumbnail;
- };
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- [[nodiscard]] QByteArray Number(T value) {
- return QByteArray::number(value);
- }
- template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
- [[nodiscard]] QByteArray Percent(T value) {
- return Number(base::SafeRound(value * 10000.) / 100.);
- };
- [[nodiscard]] QByteArray Escape(QByteArray value) {
- auto result = QByteArray();
- result.reserve(value.size());
- for (const auto &ch : value) {
- switch (ch) {
- case '&': result.append("&"); break;
- case '<': result.append("<"); break;
- case '>': result.append(">"); break;
- case '"': result.append("""); break;
- case '\'': result.append("'"); break;
- default: result.append(ch); break;
- }
- }
- return result;
- }
- [[nodiscard]] QByteArray Date(TimeId date) {
- return Escape(langDateTimeFull(base::unixtime::parse(date)).toUtf8());
- }
- class Parser final {
- public:
- Parser(const Source &source, const Options &options);
- [[nodiscard]] Prepared result();
- private:
- void process(const Source &source);
- void process(const MTPPhoto &photo);
- void process(const MTPDocument &document);
- template <typename Inner>
- [[nodiscard]] QByteArray list(const MTPVector<Inner> &data);
- [[nodiscard]] QByteArray collage(
- const QVector<MTPPageBlock> &list,
- const std::vector<QSize> &dimensions,
- int offset = 0);
- [[nodiscard]] QByteArray slideshow(
- const QVector<MTPPageBlock> &list,
- QSize dimensions);
- [[nodiscard]] QByteArray block(const MTPDpageBlockUnsupported &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockTitle &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockSubtitle &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockAuthorDate &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockHeader &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockSubheader &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockParagraph &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockPreformatted &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockFooter &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockDivider &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockAnchor &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockList &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockBlockquote &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockPullquote &data);
- [[nodiscard]] QByteArray block(
- const MTPDpageBlockPhoto &data,
- const Ui::GroupMediaLayout &layout = {},
- QSize outer = {});
- [[nodiscard]] QByteArray block(
- const MTPDpageBlockVideo &data,
- const Ui::GroupMediaLayout &layout = {},
- QSize outer = {});
- [[nodiscard]] QByteArray block(const MTPDpageBlockCover &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockEmbed &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockEmbedPost &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockCollage &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockSlideshow &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockChannel &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockAudio &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockKicker &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockTable &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockOrderedList &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockDetails &data);
- [[nodiscard]] QByteArray block(
- const MTPDpageBlockRelatedArticles &data);
- [[nodiscard]] QByteArray block(const MTPDpageBlockMap &data);
- [[nodiscard]] QByteArray block(const MTPDpageRelatedArticle &data);
- [[nodiscard]] QByteArray block(const MTPDpageTableRow &data);
- [[nodiscard]] QByteArray block(const MTPDpageTableCell &data);
- [[nodiscard]] QByteArray block(const MTPDpageListItemText &data);
- [[nodiscard]] QByteArray block(const MTPDpageListItemBlocks &data);
- [[nodiscard]] QByteArray block(const MTPDpageListOrderedItemText &data);
- [[nodiscard]] QByteArray block(
- const MTPDpageListOrderedItemBlocks &data);
- [[nodiscard]] QByteArray wrap(const QByteArray &content, int views);
- [[nodiscard]] QByteArray tag(
- const QByteArray &name,
- const QByteArray &body = {});
- [[nodiscard]] QByteArray tag(
- const QByteArray &name,
- const Attributes &attributes,
- const QByteArray &body = {});
- [[nodiscard]] QByteArray utf(const MTPstring &text);
- [[nodiscard]] QByteArray utf(const tl::conditional<MTPstring> &text);
- [[nodiscard]] QByteArray rich(const MTPRichText &text);
- [[nodiscard]] QByteArray caption(const MTPPageCaption &caption);
- [[nodiscard]] Photo parse(const MTPPhoto &photo);
- [[nodiscard]] Document parse(const MTPDocument &document);
- [[nodiscard]] Geo parse(const MTPGeoPoint &geo);
- [[nodiscard]] Photo photoById(uint64 id);
- [[nodiscard]] Document documentById(uint64 id);
- [[nodiscard]] QByteArray photoFullUrl(const Photo &photo);
- [[nodiscard]] QByteArray documentFullUrl(const Document &document);
- [[nodiscard]] QByteArray embedUrl(const QByteArray &html);
- [[nodiscard]] QByteArray mapUrl(
- const Geo &geo,
- int width,
- int height,
- int zoom);
- [[nodiscard]] QByteArray resource(QByteArray id);
- [[nodiscard]] std::vector<QSize> computeCollageDimensions(
- const QVector<MTPPageBlock> &items);
- [[nodiscard]] QSize computeSlideshowDimensions(
- const QVector<MTPPageBlock> &items);
- //const Options _options;
- const QByteArray _fileOriginPostfix;
- base::flat_set<QByteArray> _resources;
- Prepared _result;
- base::flat_map<uint64, Photo> _photosById;
- base::flat_map<uint64, Document> _documentsById;
- };
- [[nodiscard]] bool IsVoidElement(const QByteArray &name) {
- // Thanks https://developer.mozilla.org/en-US/docs/Glossary/Void_element
- static const auto voids = base::flat_set<QByteArray>{
- "area"_q,
- "base"_q,
- "br"_q,
- "col"_q,
- "embed"_q,
- "hr"_q,
- "img"_q,
- "input"_q,
- "link"_q,
- "meta"_q,
- "source"_q,
- "track"_q,
- "wbr"_q,
- };
- return voids.contains(name);
- }
- [[nodiscard]] QByteArray ArrowSvg(bool left) {
- const auto rotate = QByteArray(left ? "180" : "0");
- return R"(
- <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
- <path
- d="M14.9972363,18 L9.13865768,12.1414214 C9.06055283,12.0633165 9.06055283,11.9366835 9.13865768,11.8585786 L14.9972363,6 L14.9972363,6"
- transform="translate(11.997236, 12) scale(-1, -1) rotate()" + rotate + ") translate(-11.997236, -12)" + R"(">
- </path>
- </svg>)";
- }
- Parser::Parser(const Source &source, const Options &options)
- : /*_options(options)
- , */_fileOriginPostfix('/' + Number(source.pageId)) {
- process(source);
- _result.pageId = source.pageId;
- _result.name = source.name;
- _result.rtl = source.page.data().is_rtl();
- const auto views = std::max(
- source.page.data().vviews().value_or_empty(),
- source.updatedCachedViews);
- const auto content = list(source.page.data().vblocks());
- _result.content = wrap(content, views);
- }
- Prepared Parser::result() {
- return _result;
- }
- void Parser::process(const Source &source) {
- const auto &data = source.page.data();
- for (const auto &photo : data.vphotos().v) {
- process(photo);
- }
- for (const auto &document : data.vdocuments().v) {
- process(document);
- }
- if (source.webpagePhoto) {
- process(*source.webpagePhoto);
- }
- if (source.webpageDocument) {
- process(*source.webpageDocument);
- }
- }
- void Parser::process(const MTPPhoto &photo) {
- _photosById.emplace(
- photo.match([](const auto &data) { return data.vid().v; }),
- parse(photo));
- }
- void Parser::process(const MTPDocument &document) {
- _documentsById.emplace(
- document.match([](const auto &data) { return data.vid().v; }),
- parse(document));
- }
- template <typename Inner>
- QByteArray Parser::list(const MTPVector<Inner> &data) {
- auto result = QByteArrayList();
- result.reserve(data.v.size());
- for (const auto &item : data.v) {
- result.append(item.match([&](const auto &data) {
- return block(data);
- }));
- }
- return result.join(QByteArray());
- }
- QByteArray Parser::collage(
- const QVector<MTPPageBlock> &list,
- const std::vector<QSize> &dimensions,
- int offset) {
- Expects(list.size() == dimensions.size());
- constexpr auto kPerCollage = 10;
- const auto last = (offset + kPerCollage >= int(dimensions.size()));
- auto result = QByteArray();
- auto slice = ((offset > 0) || (dimensions.size() > kPerCollage))
- ? (dimensions
- | ranges::views::drop(offset)
- | ranges::views::take(kPerCollage)
- | ranges::to_vector)
- : dimensions;
- const auto layout = Ui::LayoutMediaGroup(
- slice,
- st::historyGroupWidthMax,
- st::historyGroupWidthMin,
- st::historyGroupSkip);
- auto size = QSize();
- for (const auto &part : layout) {
- const auto &rect = part.geometry;
- size = QSize(
- std::max(size.width(), rect.x() + rect.width()),
- std::max(size.height(), rect.y() + rect.height()));
- }
- for (auto i = 0, count = int(layout.size()); i != count; ++i) {
- const auto &part = layout[i];
- list[offset + i].match([&](const MTPDpageBlockPhoto &data) {
- result += block(data, part, size);
- }, [&](const MTPDpageBlockVideo &data) {
- result += block(data, part, size);
- }, [](const auto &) {
- Unexpected("Block type in collage layout.");
- });
- }
- const auto aspectHeight = size.height() / float64(size.width());
- const auto aspectSkip = st::historyGroupSkip / float64(size.width());
- auto wrapped = tag("figure", {
- { "class", "collage" },
- {
- "style",
- ("padding-top: " + Percent(aspectHeight) + "%; "
- + "margin-bottom: " + Percent(last ? 0 : aspectSkip) + "%;")
- },
- }, result);
- if (offset + kPerCollage < int(dimensions.size())) {
- wrapped += collage(list, dimensions, offset + kPerCollage);
- }
- return wrapped;
- }
- QByteArray Parser::slideshow(
- const QVector<MTPPageBlock> &list,
- QSize dimensions) {
- auto result = QByteArray();
- for (auto i = 0, count = int(list.size()); i != count; ++i) {
- list[i].match([&](const MTPDpageBlockPhoto &data) {
- result += block(data, {}, dimensions);
- }, [&](const MTPDpageBlockVideo &data) {
- result += block(data, {}, dimensions);
- }, [](const auto &) {
- Unexpected("Block type in collage layout.");
- });
- }
- auto inputs = QByteArrayList();
- for (auto i = 0; i != int(list.size()); ++i) {
- auto attributes = Attributes{
- { "type", "radio" },
- { "name", "s" },
- { "value", Number(i) },
- { "onchange", "return IV.slideshowSlide(this);" },
- };
- if (!i) {
- attributes.push_back({ "checked", std::nullopt });
- }
- inputs.append(tag("label", tag("input", attributes, tag("i"))));
- }
- const auto form = tag(
- "form",
- { { "class", "slideshow-buttons" } },
- tag("fieldset", inputs.join(QByteArray())));
- const auto navigation = tag("a", {
- { "class", "slideshow-prev" },
- { "onclick", "IV.slideshowSlide(this, -1);" },
- }, ArrowSvg(true)) + tag("a", {
- { "class", "slideshow-next" },
- { "onclick", "IV.slideshowSlide(this, 1);" },
- }, ArrowSvg(false));
- auto wrapStyle = "padding-top: calc(min("
- + Percent(dimensions.height() / float64(dimensions.width()))
- + "%, 480px));";
- result = form + tag("figure", {
- { "class", "slideshow" },
- }, result) + navigation;
- return tag("figure", {
- { "class", "slideshow-wrap" },
- { "style", wrapStyle },
- }, result);
- }
- QByteArray Parser::block(const MTPDpageBlockUnsupported &data) {
- return QByteArray();
- }
- QByteArray Parser::block(const MTPDpageBlockTitle &data) {
- return tag("h1", {
- { "class", "title" },
- { "dir", "auto" },
- }, rich(data.vtext()));
- }
- QByteArray Parser::block(const MTPDpageBlockSubtitle &data) {
- return tag("h2", {
- { "class", "subtitle" },
- { "dir", "auto" },
- }, rich(data.vtext()));
- }
- QByteArray Parser::block(const MTPDpageBlockAuthorDate &data) {
- auto inner = rich(data.vauthor());
- if (const auto date = data.vpublished_date().v) {
- inner += " \xE2\x80\xA2 " + tag("time", Date(date));
- }
- return tag("address", { { "dir", "auto" } }, inner);
- }
- QByteArray Parser::block(const MTPDpageBlockHeader &data) {
- return tag("h3", {
- { "class", "header" },
- { "dir", "auto" },
- }, rich(data.vtext()));
- }
- QByteArray Parser::block(const MTPDpageBlockSubheader &data) {
- return tag("h4", {
- { "class", "subheader" },
- { "dir", "auto" },
- }, rich(data.vtext()));
- }
- QByteArray Parser::block(const MTPDpageBlockParagraph &data) {
- return tag("p", { { "dir", "auto" } }, rich(data.vtext()));
- }
- QByteArray Parser::block(const MTPDpageBlockPreformatted &data) {
- auto list = Attributes{ { "dir", "auto" } };
- const auto language = utf(data.vlanguage());
- if (!language.isEmpty()) {
- list.push_back({ "data-language", language });
- list.push_back({ "class", "lang-" + language });
- _result.hasCode = true;
- }
- return tag("pre", list, rich(data.vtext()));
- }
- QByteArray Parser::block(const MTPDpageBlockFooter &data) {
- return tag("footer", {
- { "class", "footer" },
- { "dir", "auto" },
- }, rich(data.vtext()));
- }
- QByteArray Parser::block(const MTPDpageBlockDivider &data) {
- return tag("hr", Attributes{ { "class", "divider" } });
- }
- QByteArray Parser::block(const MTPDpageBlockAnchor &data) {
- return tag("a", { { "name", utf(data.vname()) } });
- }
- QByteArray Parser::block(const MTPDpageBlockList &data) {
- return tag("ul", list(data.vitems()));
- }
- QByteArray Parser::block(const MTPDpageBlockBlockquote &data) {
- const auto caption = rich(data.vcaption());
- const auto cite = caption.isEmpty()
- ? QByteArray()
- : tag("cite", { { "dir", "auto" } }, caption);
- return tag("blockquote", {
- { "dir", "auto" }
- }, rich(data.vtext()) + cite);
- }
- QByteArray Parser::block(const MTPDpageBlockPullquote &data) {
- const auto caption = rich(data.vcaption());
- const auto cite = caption.isEmpty()
- ? QByteArray()
- : tag("cite", { { "dir", "auto" } }, caption);
- return tag("div", {
- { "class", "pullquote" },
- { "dir", "auto" },
- }, rich(data.vtext()) + cite);
- }
- QByteArray Parser::block(
- const MTPDpageBlockPhoto &data,
- const Ui::GroupMediaLayout &layout,
- QSize outer) {
- const auto collage = !layout.geometry.isEmpty();
- const auto slideshow = !collage && !outer.isEmpty();
- const auto photo = photoById(data.vphoto_id().v);
- if (!photo.id) {
- return "Photo not found.";
- }
- const auto src = photoFullUrl(photo);
- auto wrapStyle = QByteArray();
- if (collage) {
- const auto wcoef = 1. / outer.width();
- const auto hcoef = 1. / outer.height();
- wrapStyle += "left: " + Percent(layout.geometry.x() * wcoef) + "%; "
- + "top: " + Percent(layout.geometry.y() * hcoef) + "%; "
- + "width: " + Percent(layout.geometry.width() * wcoef) + "%; "
- + "height: " + Percent(layout.geometry.height() * hcoef) + "%";
- } else if (!slideshow && photo.width) {
- wrapStyle += "max-width:" + Number(photo.width) + "px";
- }
- const auto dimension = collage
- ? (layout.geometry.height() / float64(layout.geometry.width()))
- : (photo.width && photo.height)
- ? (photo.height / float64(photo.width))
- : (3 / 4.);
- const auto paddingTop = collage
- ? Percent(dimension) + "%"
- : "calc(min(480px, " + Percent(dimension) + "%))";
- const auto style = "background-image:url('" + src + "');"
- "padding-top: " + paddingTop + ";";
- auto inner = tag("div", {
- { "class", "photo" },
- { "style", style } });
- const auto minithumb = Images::ExpandInlineBytes(photo.minithumbnail);
- if (!minithumb.isEmpty()) {
- const auto image = Images::Read({ .content = minithumb });
- inner = tag("div", {
- { "class", "photo-bg" },
- { "style", "background-image:url('data:image/jpeg;base64,"
- + minithumb.toBase64()
- + "');" },
- }) + inner;
- }
- auto attributes = Attributes{
- { "class", "photo-wrap" },
- { "style", wrapStyle }
- };
- auto result = tag("div", attributes, inner);
- const auto href = data.vurl() ? utf(*data.vurl()) : photoFullUrl(photo);
- const auto id = Number(photo.id);
- result = tag("a", {
- { "href", href },
- { "oncontextmenu", data.vurl() ? QByteArray() : "return false;" },
- { "data-context", data.vurl() ? QByteArray() : "viewer-photo" + id },
- }, result);
- if (!slideshow) {
- result += caption(data.vcaption());
- if (!collage) {
- result = tag("div", { { "class", "media-outer" } }, result);
- }
- }
- return result;
- }
- QByteArray Parser::block(
- const MTPDpageBlockVideo &data,
- const Ui::GroupMediaLayout &layout,
- QSize outer) {
- const auto collage = !layout.geometry.isEmpty();
- const auto slideshow = !collage && !outer.isEmpty();
- const auto collageSmall = collage
- && (layout.geometry.width() < outer.width());
- const auto video = documentById(data.vvideo_id().v);
- if (!video.id) {
- return "Video not found.";
- }
- auto inner = tag("div", {
- { "class", "video" },
- { "data-src", documentFullUrl(video) },
- { "data-autoplay", data.is_autoplay() ? "1" : "0" },
- { "data-loop", data.is_loop() ? "1" : "0" },
- { "data-small", collageSmall ? "1" : "0" },
- });
- const auto minithumb = Images::ExpandInlineBytes(video.minithumbnail);
- if (!minithumb.isEmpty()) {
- const auto image = Images::Read({ .content = minithumb });
- inner = tag("div", {
- { "class", "video-bg" },
- { "style", "background-image:url('data:image/jpeg;base64,"
- + minithumb.toBase64()
- + "');" },
- }) + inner;
- }
- auto wrapStyle = QByteArray();
- if (collage) {
- const auto wcoef = 1. / outer.width();
- const auto hcoef = 1. / outer.height();
- wrapStyle += "left: " + Percent(layout.geometry.x() * wcoef) + "%; "
- + "top: " + Percent(layout.geometry.y() * hcoef) + "%; "
- + "width: " + Percent(layout.geometry.width() * wcoef) + "%; "
- + "height: " + Percent(layout.geometry.height() * hcoef) + "%; ";
- } else {
- const auto dimension = (video.width && video.height)
- ? (video.height / float64(video.width))
- : (3 / 4.);
- wrapStyle += "padding-top: calc(min(480px, "
- + Percent(dimension)
- + "%));";
- }
- auto attributes = Attributes{
- { "class", "video-wrap" },
- { "style", wrapStyle },
- };
- auto result = tag("div", attributes, inner);
- if (data.is_autoplay() || collageSmall) {
- const auto id = Number(video.id);
- const auto href = resource("video" + id);
- result = tag("a", {
- { "href", href },
- { "oncontextmenu", "return false;" },
- { "data-context", "viewer-video" + id },
- }, result);
- }
- if (!slideshow) {
- result += caption(data.vcaption());
- if (!collage) {
- result = tag("div", { { "class", "media-outer" } }, result);
- }
- }
- return result;
- }
- QByteArray Parser::block(const MTPDpageBlockCover &data) {
- return tag("figure", data.vcover().match([&](const auto &data) {
- return block(data);
- }));
- }
- QByteArray Parser::block(const MTPDpageBlockEmbed &data) {
- _result.hasEmbeds = true;
- auto eclass = data.is_full_width() ? QByteArray() : "nowide";
- auto width = QByteArray();
- auto height = QByteArray();
- auto iframeWidth = QByteArray();
- auto iframeHeight = QByteArray();
- const auto autosize = !data.vw();
- if (autosize) {
- iframeWidth = "100%";
- eclass = "nowide";
- } else if (data.is_full_width() || !data.vw()->v) {
- width = "100%";
- height = Number(data.vh()->v) + "px";
- iframeWidth = "100%";
- iframeHeight = height;
- } else {
- width = Number(data.vw()->v) + "px";
- height = Percent(data.vh()->v / float64(data.vw()->v)) + "%";
- }
- auto attributes = Attributes();
- if (autosize) {
- attributes.push_back({ "class", "autosize" });
- }
- attributes.push_back({ "width", iframeWidth });
- attributes.push_back({ "height", iframeHeight });
- if (const auto url = data.vurl()) {
- if (!autosize) {
- attributes.push_back({ "src", utf(url) });
- } else {
- attributes.push_back({ "srcdoc", utf(url) });
- }
- } else if (const auto html = data.vhtml()) {
- attributes.push_back({ "src", embedUrl(html->v) });
- }
- if (!data.is_allow_scrolling()) {
- attributes.push_back({ "scrolling", "no" });
- }
- attributes.push_back({ "frameborder", "0" });
- attributes.push_back({ "allowtransparency", "true" });
- attributes.push_back({ "allowfullscreen", "true" });
- auto result = tag("iframe", attributes);
- if (!autosize) {
- result = tag("div", {
- { "class", "iframe-wrap" },
- { "style", "width:" + width },
- }, tag("div", {
- { "style", "padding-bottom: " + height },
- }, result));
- }
- result += caption(data.vcaption());
- return tag("figure", { { "class", eclass } }, result);
- }
- QByteArray Parser::block(const MTPDpageBlockEmbedPost &data) {
- auto result = QByteArray();
- if (!data.vblocks().v.isEmpty()) {
- auto address = QByteArray();
- const auto photo = photoById(data.vauthor_photo_id().v);
- if (photo.id) {
- const auto src = photoFullUrl(photo);
- address += tag(
- "figure",
- { { "style", "background-image:url('" + src + "')" } });
- }
- address += tag(
- "a",
- { { "rel", "author" }, { "onclick", "return false;" } },
- utf(data.vauthor()));
- if (const auto date = data.vdate().v) {
- const auto parsed = base::unixtime::parse(date);
- address += tag("time", Date(date));
- }
- const auto inner = tag("address", address) + list(data.vblocks());
- result = tag("blockquote", { { "class", "embed-post" } }, inner);
- } else {
- const auto url = utf(data.vurl());
- const auto inner = tag("strong", utf(data.vauthor()))
- + tag(
- "small",
- tag("a", { { "href", url } }, url));
- result = tag("section", { { "class", "embed-post" } }, inner);
- }
- result += caption(data.vcaption());
- return tag("figure", result);
- }
- QByteArray Parser::block(const MTPDpageBlockCollage &data) {
- const auto &items = data.vitems().v;
- const auto dimensions = computeCollageDimensions(items);
- if (dimensions.empty()) {
- return tag(
- "figure",
- tag("figure", list(data.vitems())) + caption(data.vcaption()));
- }
- return tag(
- "figure",
- { { "class", "collage-wrap" } },
- collage(items, dimensions) + caption(data.vcaption()));
- }
- QByteArray Parser::block(const MTPDpageBlockSlideshow &data) {
- const auto &items = data.vitems().v;
- const auto dimensions = computeSlideshowDimensions(items);
- if (dimensions.isEmpty()) {
- return list(data.vitems());
- }
- const auto result = slideshow(items, dimensions);
- return tag("figure", result + caption(data.vcaption()));
- }
- QByteArray Parser::block(const MTPDpageBlockChannel &data) {
- auto name = QByteArray();
- auto username = QByteArray();
- auto id = data.vchannel().match([](const auto &data) {
- return Number(data.vid().v);
- });
- data.vchannel().match([&](const MTPDchannel &data) {
- if (const auto has = data.vusername()) {
- username = utf(*has);
- }
- name = utf(data.vtitle());
- }, [&](const MTPDchat &data) {
- name = utf(data.vtitle());
- }, [](const auto &) {
- });
- auto result = tag(
- "div",
- { { "class", "join" }, { "data-context", "join_link" + id } },
- tag("span")
- ) + tag("h4", name);
- const auto link = username.isEmpty()
- ? "javascript:alert('Channel Link');"
- : "https://t.me/" + username;
- result = tag(
- "a",
- { { "href", link }, { "data-context", "channel" + id } },
- result);
- _result.channelIds.emplace(id);
- return tag("section", {
- { "class", "channel joined" },
- { "data-context", "channel" + id },
- }, result);
- }
- QByteArray Parser::block(const MTPDpageBlockAudio &data) {
- const auto audio = documentById(data.vaudio_id().v);
- if (!audio.id) {
- return "Audio not found.";
- }
- const auto src = documentFullUrl(audio);
- return tag("figure", tag("audio", {
- { "src", src },
- { "oncontextmenu", "return false;" },
- { "controls", std::nullopt },
- }) + caption(data.vcaption()));
- }
- QByteArray Parser::block(const MTPDpageBlockKicker &data) {
- return tag("h5", {
- { "class", "kicker" },
- { "dir", "auto" },
- }, rich(data.vtext()));
- }
- QByteArray Parser::block(const MTPDpageBlockTable &data) {
- auto classes = QByteArrayList();
- if (data.is_bordered()) {
- classes.push_back("bordered");
- }
- if (data.is_striped()) {
- classes.push_back("striped");
- }
- auto attibutes = Attributes();
- if (!classes.isEmpty()) {
- attibutes.push_back({ "class", classes.join(" ") });
- }
- auto title = rich(data.vtitle());
- if (!title.isEmpty()) {
- title = tag("caption", { { "dir", "auto" } }, title);
- }
- auto result = tag("table", attibutes, title + list(data.vrows()));
- result = tag("figure", { { "class", "table" } }, result);
- result = tag("figure", { { "class", "table-wrap" } }, result);
- return tag("figure", result);
- }
- QByteArray Parser::block(const MTPDpageBlockOrderedList &data) {
- return tag("ol", list(data.vitems()));
- }
- QByteArray Parser::block(const MTPDpageBlockDetails &data) {
- auto attributes = Attributes();
- if (data.is_open()) {
- attributes.push_back({ "open", std::nullopt });
- }
- return tag(
- "details",
- attributes,
- (tag("summary", { { "dir", "auto" } }, rich(data.vtitle()))
- + list(data.vblocks())));
- }
- QByteArray Parser::block(const MTPDpageBlockRelatedArticles &data) {
- const auto result = list(data.varticles());
- if (result.isEmpty()) {
- return QByteArray();
- }
- auto title = rich(data.vtitle());
- if (!title.isEmpty()) {
- title = tag("h4", {
- { "class", "related-title" },
- { "dir", "auto" },
- }, title);
- }
- return tag("section", { { "class", "related" } }, title + result);
- }
- QByteArray Parser::block(const MTPDpageBlockMap &data) {
- const auto geo = parse(data.vgeo());
- if (!geo.access) {
- return "Map not found.";
- }
- const auto width = 650;
- const auto height = std::min(450, (data.vh().v * width / data.vw().v));
- return tag("figure", tag("img", {
- { "src", mapUrl(geo, width, height, data.vzoom().v) },
- }) + caption(data.vcaption()));
- }
- QByteArray Parser::block(const MTPDpageRelatedArticle &data) {
- auto result = QByteArray();
- const auto photo = photoById(data.vphoto_id().value_or_empty());
- if (photo.id) {
- const auto src = photoFullUrl(photo);
- result += tag("i", {
- { "class", "related-link-thumb" },
- { "style", "background-image:url('" + src + "')" },
- });
- }
- const auto title = data.vtitle();
- const auto description = data.vdescription();
- const auto author = data.vauthor();
- const auto published = data.vpublished_date();
- if (title || description || author || published) {
- auto inner = QByteArray();
- if (title) {
- inner += tag(
- "span",
- { { "class", "related-link-title" } },
- utf(*title));
- }
- if (description) {
- inner += tag(
- "span",
- { { "class", "related-link-desc" } },
- utf(*description));
- }
- if (author || published) {
- inner += tag(
- "span",
- { { "class", "related-link-source" } },
- ((author ? utf(*author) : QByteArray())
- + ((author && published) ? ", " : QByteArray())
- + (published ? Date(published->v) : QByteArray())));
- }
- result += tag("span", {
- { "class", "related-link-content" },
- }, inner);
- }
- const auto webpageId = data.vwebpage_id().v;
- const auto context = webpageId
- ? ("webpage" + Number(webpageId))
- : QByteArray();
- return tag("a", {
- { "class", "related-link" },
- { "href", utf(data.vurl()) },
- { "data-context", context },
- }, result);
- }
- QByteArray Parser::block(const MTPDpageTableRow &data) {
- return tag("tr", list(data.vcells()));
- }
- QByteArray Parser::block(const MTPDpageTableCell &data) {
- const auto text = data.vtext() ? rich(*data.vtext()) : QByteArray();
- auto style = QByteArray();
- if (data.is_align_right()) {
- style += "text-align:right;";
- } else if (data.is_align_center()) {
- style += "text-align:center;";
- } else {
- style += "text-align:left;";
- }
- if (data.is_valign_bottom()) {
- style += "vertical-align:bottom;";
- } else if (data.is_valign_middle()) {
- style += "vertical-align:middle;";
- } else {
- style += "vertical-align:top;";
- }
- auto attributes = Attributes{ { "style", style }, { "dir", "auto" } };
- if (const auto cs = data.vcolspan()) {
- attributes.push_back({ "colspan", Number(cs->v) });
- }
- if (const auto rs = data.vrowspan()) {
- attributes.push_back({ "rowspan", Number(rs->v) });
- }
- return tag(data.is_header() ? "th" : "td", attributes, text);
- }
- QByteArray Parser::block(const MTPDpageListItemText &data) {
- return tag("li", { { "dir", "auto" } }, rich(data.vtext()));
- }
- QByteArray Parser::block(const MTPDpageListItemBlocks &data) {
- return tag("li", list(data.vblocks()));
- }
- QByteArray Parser::block(const MTPDpageListOrderedItemText &data) {
- return tag(
- "li",
- { { "value", utf(data.vnum()) }, { "dir", "auto" } },
- rich(data.vtext()));
- }
- QByteArray Parser::block(const MTPDpageListOrderedItemBlocks &data) {
- return tag(
- "li",
- { { "value", utf(data.vnum()) } },
- list(data.vblocks()));
- }
- QByteArray Parser::utf(const MTPstring &text) {
- return Escape(text.v);
- }
- QByteArray Parser::utf(const tl::conditional<MTPstring> &text) {
- return text ? utf(*text) : QByteArray();
- }
- QByteArray Parser::wrap(const QByteArray &content, int views) {
- const auto sep = " \xE2\x80\xA2 ";
- const auto viewsText = views
- ? (tr::lng_stories_views(tr::now, lt_count_decimal, views) + sep)
- : QString();
- return R"(
- <div class="page-slide">
- <article>)"_q + content + R"(</article>
- </div>
- <div class="page-footer">
- <div class="content">
- )"_q
- + viewsText.toUtf8()
- + R"(<a class="wrong" data-context="report-iv">)"_q
- + tr::lng_iv_wrong_layout(tr::now).toUtf8()
- + R"(</a>
- </div>
- </div>)"_q;
- }
- QByteArray Parser::tag(
- const QByteArray &name,
- const QByteArray &body) {
- return tag(name, {}, body);
- }
- QByteArray Parser::tag(
- const QByteArray &name,
- const Attributes &attributes,
- const QByteArray &body) {
- auto list = QByteArrayList();
- list.reserve(attributes.size());
- for (auto &[name, value] : attributes) {
- list.push_back(' ' + name + (value ? "=\"" + *value + "\"" : ""));
- }
- const auto serialized = list.join(QByteArray());
- return (IsVoidElement(name) && body.isEmpty())
- ? ('<' + name + serialized + " />")
- : ('<' + name + serialized + '>' + body + "</" + name + '>');
- }
- QByteArray Parser::rich(const MTPRichText &text) {
- return text.match([&](const MTPDtextEmpty &data) {
- return QByteArray();
- }, [&](const MTPDtextPlain &data) {
- struct Replacement {
- QByteArray from;
- QByteArray to;
- };
- const auto replacements = std::vector<Replacement>{
- { "\xE2\x81\xA6", "<span dir=\"ltr\">" },
- { "\xE2\x81\xA7", "<span dir=\"rtl\">" },
- { "\xE2\x81\xA8", "<span dir=\"auto\">" },
- { "\xE2\x81\xA9", "</span>" },
- };
- auto text = utf(data.vtext());
- for (const auto &[from, to] : replacements) {
- text.replace(from, to);
- }
- return text;
- }, [&](const MTPDtextConcat &data) {
- const auto &list = data.vtexts().v;
- auto result = QByteArrayList();
- result.reserve(list.size());
- for (const auto &item : list) {
- result.append(rich(item));
- }
- return result.join(QByteArray());
- }, [&](const MTPDtextImage &data) {
- const auto image = documentById(data.vdocument_id().v);
- if (!image.id) {
- return "Image not found."_q;
- }
- auto attributes = Attributes{
- { "class", "pic" },
- { "src", documentFullUrl(image) },
- };
- if (const auto width = data.vw().v) {
- attributes.push_back({ "width", Number(width) });
- }
- if (const auto height = data.vh().v) {
- attributes.push_back({ "height", Number(height) });
- }
- return tag("img", attributes);
- }, [&](const MTPDtextBold &data) {
- return tag("b", rich(data.vtext()));
- }, [&](const MTPDtextItalic &data) {
- return tag("i", rich(data.vtext()));
- }, [&](const MTPDtextUnderline &data) {
- return tag("u", rich(data.vtext()));
- }, [&](const MTPDtextStrike &data) {
- return tag("s", rich(data.vtext()));
- }, [&](const MTPDtextFixed &data) {
- return tag("code", rich(data.vtext()));
- }, [&](const MTPDtextUrl &data) {
- const auto webpageId = data.vwebpage_id().v;
- const auto context = webpageId
- ? ("webpage" + Number(webpageId))
- : QByteArray();
- return tag("a", {
- { "href", utf(data.vurl()) },
- { "class", webpageId ? "internal-iv-link" : "" },
- { "data-context", context },
- }, rich(data.vtext()));
- }, [&](const MTPDtextEmail &data) {
- return tag("a", {
- { "href", "mailto:" + utf(data.vemail()) },
- }, rich(data.vtext()));
- }, [&](const MTPDtextSubscript &data) {
- return tag("sub", rich(data.vtext()));
- }, [&](const MTPDtextSuperscript &data) {
- return tag("sup", rich(data.vtext()));
- }, [&](const MTPDtextMarked &data) {
- return tag("mark", rich(data.vtext()));
- }, [&](const MTPDtextPhone &data) {
- return tag("a", {
- { "href", "tel:" + utf(data.vphone()) },
- }, rich(data.vtext()));
- }, [&](const MTPDtextAnchor &data) {
- const auto inner = rich(data.vtext());
- const auto name = utf(data.vname());
- return inner.isEmpty()
- ? tag("a", { { "name", name } })
- : tag(
- "span",
- { { "class", "reference" } },
- tag("a", { { "name", name } }) + inner);
- });
- }
- QByteArray Parser::caption(const MTPPageCaption &caption) {
- auto text = rich(caption.data().vtext());
- const auto credit = rich(caption.data().vcredit());
- if (!credit.isEmpty()) {
- text += tag("cite", { { "dir", "auto" } }, credit);
- } else if (text.isEmpty()) {
- return QByteArray();
- }
- return tag("figcaption", { { "dir", "auto" } }, text);
- }
- Photo Parser::parse(const MTPPhoto &photo) {
- auto result = Photo{
- .id = photo.match([&](const auto &d) { return d.vid().v; }),
- };
- auto sizes = base::flat_map<QByteArray, QSize>();
- photo.match([](const MTPDphotoEmpty &) {
- }, [&](const MTPDphoto &data) {
- for (const auto &size : data.vsizes().v) {
- size.match([&](const MTPDphotoSizeEmpty &data) {
- }, [&](const MTPDphotoSize &data) {
- sizes.emplace(
- data.vtype().v,
- QSize(data.vw().v, data.vh().v));
- }, [&](const MTPDphotoCachedSize &data) {
- sizes.emplace(
- data.vtype().v,
- QSize(data.vw().v, data.vh().v));
- }, [&](const MTPDphotoStrippedSize &data) {
- result.minithumbnail = data.vbytes().v;
- }, [&](const MTPDphotoSizeProgressive &data) {
- sizes.emplace(
- data.vtype().v,
- QSize(data.vw().v, data.vh().v));
- }, [&](const MTPDphotoPathSize &data) {
- });
- }
- });
- for (const auto attempt : { "y", "x", "w" }) {
- const auto i = sizes.find(QByteArray(attempt));
- if (i != end(sizes)) {
- result.width = i->second.width();
- result.height = i->second.height();
- break;
- }
- }
- return result;
- }
- Document Parser::parse(const MTPDocument &document) {
- auto result = Document{
- .id = document.match([&](const auto &d) { return d.vid().v; }),
- };
- document.match([](const MTPDdocumentEmpty &) {
- }, [&](const MTPDdocument &data) {
- for (const auto &attribute : data.vattributes().v) {
- attribute.match([&](const MTPDdocumentAttributeImageSize &data) {
- result.width = data.vw().v;
- result.height = data.vh().v;
- }, [&](const MTPDdocumentAttributeVideo &data) {
- result.width = data.vw().v;
- result.height = data.vh().v;
- }, [](const auto &) {});
- }
- if (const auto sizes = data.vthumbs()) {
- for (const auto &size : sizes->v) {
- size.match([&](const MTPDphotoStrippedSize &data) {
- result.minithumbnail = data.vbytes().v;
- }, [&](const auto &data) {
- });
- }
- }
- });
- return result;
- }
- Geo Parser::parse(const MTPGeoPoint &geo) {
- return geo.match([](const MTPDgeoPointEmpty &) {
- return Geo();
- }, [&](const MTPDgeoPoint &data) {
- return Geo{
- .lat = data.vlat().v,
- .lon = data.vlong().v,
- .access = data.vaccess_hash().v,
- };
- });
- }
- Photo Parser::photoById(uint64 id) {
- const auto i = _photosById.find(id);
- return (i != end(_photosById)) ? i->second : Photo();
- }
- Document Parser::documentById(uint64 id) {
- const auto i = _documentsById.find(id);
- return (i != end(_documentsById)) ? i->second : Document();
- }
- QByteArray Parser::photoFullUrl(const Photo &photo) {
- return resource("photo/" + Number(photo.id) + _fileOriginPostfix);
- }
- QByteArray Parser::documentFullUrl(const Document &document) {
- return resource("document/" + Number(document.id) + _fileOriginPostfix);
- }
- QByteArray Parser::embedUrl(const QByteArray &html) {
- auto binary = std::array<uchar, SHA256_DIGEST_LENGTH>{};
- SHA256(
- reinterpret_cast<const unsigned char*>(html.data()),
- html.size(),
- binary.data());
- const auto hex = [](uchar value) -> char {
- return (value >= 10) ? ('a' + (value - 10)) : ('0' + value);
- };
- auto result = QByteArray();
- result.reserve(binary.size() * 2);
- for (const auto byte : binary) {
- result.push_back(hex(byte / 16));
- result.push_back(hex(byte % 16));
- }
- result += ".html";
- _result.embeds.emplace(result, html);
- return resource("html/" + result);
- }
- QByteArray Parser::mapUrl(const Geo &geo, int width, int height, int zoom) {
- return resource("map/"
- + GeoPointId(geo) + "&"
- + Number(width) + ","
- + Number(height) + "&"
- + Number(zoom));
- }
- QByteArray Parser::resource(QByteArray id) {
- return '/' + id;
- }
- std::vector<QSize> Parser::computeCollageDimensions(
- const QVector<MTPPageBlock> &items) {
- if (items.size() < 2) {
- return {};
- }
- auto result = std::vector<QSize>(items.size());
- for (auto i = 0, count = int(items.size()); i != count; ++i) {
- items[i].match([&](const MTPDpageBlockPhoto &data) {
- const auto photo = photoById(data.vphoto_id().v);
- if (photo.id && photo.width > 0 && photo.height > 0) {
- result[i] = QSize(photo.width, photo.height);
- }
- }, [&](const MTPDpageBlockVideo &data) {
- const auto document = documentById(data.vvideo_id().v);
- if (document.id && document.width > 0 && document.height > 0) {
- result[i] = QSize(document.width, document.height);
- }
- }, [](const auto &) {});
- if (result[i].isEmpty()) {
- return {};
- }
- }
- return result;
- }
- QSize Parser::computeSlideshowDimensions(
- const QVector<MTPPageBlock> &items) {
- if (items.size() < 2) {
- return {};
- }
- auto result = QSize();
- for (const auto &item : items) {
- auto size = QSize();
- item.match([&](const MTPDpageBlockPhoto &data) {
- const auto photo = photoById(data.vphoto_id().v);
- if (photo.id && photo.width > 0 && photo.height > 0) {
- size = QSize(photo.width, photo.height);
- }
- }, [&](const MTPDpageBlockVideo &data) {
- const auto document = documentById(data.vvideo_id().v);
- if (document.id && document.width > 0 && document.height > 0) {
- size = QSize(document.width, document.height);
- }
- }, [](const auto &) {});
- if (size.isEmpty()) {
- return {};
- } else if (result.height() * size.width()
- < result.width() * size.height()) {
- result = size;
- }
- }
- return result;
- }
- } // namespace
- Prepared Prepare(const Source &source, const Options &options) {
- auto parser = Parser(source, options);
- return parser.result();
- }
- } // namespace Iv
|