diff --git a/gallery_dl/extractor/deviantart.py b/gallery_dl/extractor/deviantart.py index aec14cb6..22a74a02 100644 --- a/gallery_dl/extractor/deviantart.py +++ b/gallery_dl/extractor/deviantart.py @@ -402,7 +402,7 @@ class DeviantartExtractor(Extractor): if html["type"] == "tiptap": try: - return self._tiptap_to_html(markup) + return self.utils("tiptap").to_html(markup) except Exception as exc: self.log.traceback(exc) self.log.error("%s: '%s: %s'", deviation["index"], @@ -411,242 +411,6 @@ class DeviantartExtractor(Extractor): self.log.warning("%s: Unsupported '%s' markup.", deviation["index"], html["type"]) - def _tiptap_to_html(self, markup): - html = [] - - html.append('
') - data = util.json_loads(markup) - for block in data["document"]["content"]: - self._tiptap_process_content(html, block) - html.append("
") - - return "".join(html) - - def _tiptap_process_content(self, html, content): - type = content["type"] - - if type == "paragraph": - if children := content.get("content"): - html.append('

') - else: - html.append('margin-inline-start:0px">') - - for block in children: - self._tiptap_process_content(html, block) - html.append("

") - else: - html.append('


') - - elif type == "text": - self._tiptap_process_text(html, content) - - elif type == "heading": - attrs = content["attrs"] - level = str(attrs.get("level") or "3") - - html.append("') - html.append('') - self._tiptap_process_children(html, content) - html.append("") - - elif type in ("listItem", "bulletList", "orderedList", "blockquote"): - c = type[1] - tag = ( - "li" if c == "i" else - "ul" if c == "u" else - "ol" if c == "r" else - "blockquote" - ) - html.append("<" + tag + ">") - self._tiptap_process_children(html, content) - html.append("") - - elif type == "anchor": - attrs = content["attrs"] - html.append('') - - elif type == "hardBreak": - html.append("

") - - elif type == "horizontalRule": - html.append("
") - - elif type == "da-deviation": - self._tiptap_process_deviation(html, content) - - elif type == "da-mention": - user = content["attrs"]["user"]["username"] - html.append('@') - html.append(user) - html.append('') - - elif type == "da-gif": - attrs = content["attrs"] - width = str(attrs.get("width") or "") - height = str(attrs.get("height") or "") - url = text.escape(attrs.get("url") or "") - - html.append('
') - - elif type == "da-video": - src = text.escape(content["attrs"].get("src") or "") - html.append('
' - '
') - - else: - self.log.warning("Unsupported content type '%s'", type) - - def _tiptap_process_text(self, html, content): - if marks := content.get("marks"): - close = [] - for mark in marks: - type = mark["type"] - if type == "link": - attrs = mark.get("attrs") or {} - html.append('') - close.append("") - elif type == "bold": - html.append("") - close.append("") - elif type == "italic": - html.append("") - close.append("") - elif type == "underline": - html.append("") - close.append("") - elif type == "strike": - html.append("") - close.append("") - elif type == "textStyle" and len(mark) <= 1: - pass - else: - self.log.warning("Unsupported text marker '%s'", type) - close.reverse() - html.append(text.escape(content["text"])) - html.extend(close) - else: - html.append(text.escape(content["text"])) - - def _tiptap_process_children(self, html, content): - if children := content.get("content"): - for block in children: - self._tiptap_process_content(html, block) - - def _tiptap_process_indentation(self, html, attrs): - itype = ("text-indent" if attrs.get("indentType") == "line" else - "margin-inline-start") - isize = str((attrs.get("indentation") or 0) * 24) - html.append(itype + ":" + isize + "px") - - def _tiptap_process_deviation(self, html, content): - dev = content["attrs"]["deviation"] - media = dev.get("media") or () - - html.append('
') - html.append('
') - - if "baseUri" in media: - url, formats = self._eclipse_media(media) - full = formats["fullview"] - - html.append('') - - html.append('')
-            html.append(text.escape(dev[') - html.append("") - - elif "textContent" in dev: - html.append('') - - html.append('
') - def _extract_content(self, deviation): content = deviation["content"] @@ -827,25 +591,6 @@ x2="45.4107524%" y2="71.4898596%" id="app-root-3">\ self.log.info("Unwatching %s", username) self.api.user_friends_unwatch(username) - def _eclipse_media(self, media, format="preview"): - url = [media["baseUri"]] - - formats = { - fmt["t"]: fmt - for fmt in media["types"] - } - - if tokens := media.get("token") or (): - if len(tokens) <= 1: - fmt = formats[format] - if "c" in fmt: - url.append(fmt["c"].replace( - "", media["prettyName"])) - url.append("?token=") - url.append(tokens[-1]) - - return "".join(url), formats - def _eclipse_to_oauth(self, eclipse_api, deviations): for obj in deviations: deviation = obj["deviation"] if "deviation" in obj else obj @@ -1303,7 +1048,7 @@ class DeviantartDeviationExtractor(DeviantartExtractor): yield deviation for index, post in enumerate(additional_media): - uri = self._eclipse_media(post["media"], "fullview")[0] + uri = eclipse_media(post["media"], "fullview")[0] deviation["content"]["src"] = uri deviation["num"] += 1 deviation["index_file"] = post["fileId"] @@ -2245,3 +1990,23 @@ by {username}, {date} {content} """ + + +def eclipse_media(media, format="preview"): + url = [media["baseUri"]] + + formats = { + fmt["t"]: fmt + for fmt in media["types"] + } + + if tokens := media.get("token") or (): + if len(tokens) <= 1: + fmt = formats[format] + if "c" in fmt: + url.append(fmt["c"].replace( + "", media["prettyName"])) + url.append("?token=") + url.append(tokens[-1]) + + return "".join(url), formats diff --git a/gallery_dl/extractor/utils/deviantart_tiptap.py b/gallery_dl/extractor/utils/deviantart_tiptap.py new file mode 100644 index 00000000..36dfc6af --- /dev/null +++ b/gallery_dl/extractor/utils/deviantart_tiptap.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- + +# Copyright 2026 Mike Fährmann +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. + +from ... import text, util +from .. deviantart import eclipse_media + + +def to_html(markup): + html = [] + + html.append('
') + data = util.json_loads(markup) + for block in data["document"]["content"]: + process_content(html, block) + html.append("
") + + return "".join(html) + + +def process_content(html, content): + type = content["type"] + + if type == "paragraph": + if children := content.get("content"): + html.append('

') + else: + html.append('margin-inline-start:0px">') + + for block in children: + process_content(html, block) + html.append("

") + else: + html.append('


') + + elif type == "text": + process_text(html, content) + + elif type == "heading": + attrs = content["attrs"] + level = str(attrs.get("level") or "3") + + html.append("') + html.append('') + process_children(html, content) + html.append("") + + elif type in ("listItem", "bulletList", "orderedList", "blockquote"): + c = type[1] + tag = ( + "li" if c == "i" else + "ul" if c == "u" else + "ol" if c == "r" else + "blockquote" + ) + html.append("<" + tag + ">") + process_children(html, content) + html.append("") + + elif type == "anchor": + attrs = content["attrs"] + html.append('') + + elif type == "hardBreak": + html.append("

") + + elif type == "horizontalRule": + html.append("
") + + elif type == "da-deviation": + process_deviation(html, content) + + elif type == "da-mention": + user = content["attrs"]["user"]["username"] + html.append('@') + html.append(user) + html.append('') + + elif type == "da-gif": + attrs = content["attrs"] + width = str(attrs.get("width") or "") + height = str(attrs.get("height") or "") + url = text.escape(attrs.get("url") or "") + + html.append('
') + + elif type == "da-video": + src = text.escape(content["attrs"].get("src") or "") + html.append('
' + '
') + + else: + import logging + logging.getLogger("tiptap").warning( + "Unsupported content type '%s'", type) + + +def process_text(html, content): + if marks := content.get("marks"): + close = [] + for mark in marks: + type = mark["type"] + if type == "link": + attrs = mark.get("attrs") or {} + html.append('') + close.append("") + elif type == "bold": + html.append("") + close.append("") + elif type == "italic": + html.append("") + close.append("") + elif type == "underline": + html.append("") + close.append("") + elif type == "strike": + html.append("") + close.append("") + elif type == "textStyle" and len(mark) <= 1: + pass + else: + import logging + logging.getLogger("tiptap").warning( + "Unsupported text marker '%s'", type) + close.reverse() + html.append(text.escape(content["text"])) + html.extend(close) + else: + html.append(text.escape(content["text"])) + + +def process_children(html, content): + if children := content.get("content"): + for block in children: + process_content(html, block) + + +def process_indentation(html, attrs): + itype = ("text-indent" if attrs.get("indentType") == "line" else + "margin-inline-start") + isize = str((attrs.get("indentation") or 0) * 24) + html.append(itype + ":" + isize + "px") + + +def process_deviation(html, content): + dev = content["attrs"]["deviation"] + media = dev.get("media") or () + + html.append('
') + html.append('
') + + if "baseUri" in media: + url, formats = eclipse_media(media) + full = formats["fullview"] + + html.append('') + + html.append('')
+        html.append(text.escape(dev[') + html.append("") + + elif "textContent" in dev: + html.append('') + + html.append('
')