diff --git a/flux.vision/content/flimmerkiste-2-wildscreening.md b/flux.vision/content/flimmerkiste-2-wildscreening.md index 5402a4c..81597fd 100755 --- a/flux.vision/content/flimmerkiste-2-wildscreening.md +++ b/flux.vision/content/flimmerkiste-2-wildscreening.md @@ -33,7 +33,7 @@ generator, a nice 5000 ANSI projector, manfrotto tripod, V4 and laptops we walked through the Kreuzberg'ish 'Jungbusch' area towards the center marketplace where the huge city hall is located. -![p1010789](http://visualberlin.org/wp-content/uploads/2009/05/p1010789-492x276.jpg "p1010789") +![p1010789](/library/p1010789-492x276.jpg "p1010789") Equipment was set up quickly and we had a lot of fun, doing spontaneous mapping masks, jamming along, spreading Flimmerkiste flyers to curious diff --git a/flux.vision/content/library/p1010789-492x276.jpg b/flux.vision/content/library/p1010789-492x276.jpg new file mode 100644 index 0000000..624c0de Binary files /dev/null and b/flux.vision/content/library/p1010789-492x276.jpg differ diff --git a/flux.vision/pelicanconf.py b/flux.vision/pelicanconf.py index c3f37ee..16c316d 100644 --- a/flux.vision/pelicanconf.py +++ b/flux.vision/pelicanconf.py @@ -7,9 +7,18 @@ TIMEZONE = 'Europe/Berlin' DEFAULT_LANG = 'en' THEME = 'flux' -PLUGIN_PATHS = ['plugins', '../tobias.raayoni-last.com/plugins'] +PLUGIN_PATHS = ['plugins', '../plugins'] PLUGINS = ['assetcheck', 'tileweight', 'lostimages'] +# Hosts that no longer exist, so their hotlinked images can never resolve. +# Checked against Wayback: of twelve such images only one was ever captured, +# and it now lives in content/library/. See lostimages.py. +LOSTIMAGES_DEAD_HOSTS = [ + 'visualberlin.org', 'mutanten.org', 'web.mac.com', + 'kino.web.de', 'lh4.google.it', 'l.yimg.com', +] + + ARTICLE_PATHS = [''] ARTICLE_EXCLUDES = ['pages', 'library', 'images', 'recover'] PAGE_PATHS = ['pages'] diff --git a/plugins/lostimages.py b/plugins/lostimages.py new file mode 100644 index 0000000..e5ab17f --- /dev/null +++ b/plugins/lostimages.py @@ -0,0 +1,84 @@ +"""Drop tags in article bodies whose image is gone for good. + +Two kinds of gone, same symptom: + +**Local.** The same situation as assetcheck.py, one level down: the recovered +bodies kept their original /library/ references, and 22 of 200 point at files no +archive holds (checked against Wayback; the CDX API returns no captures). + +**Remote.** Older posts hotlink images from other people's servers, and some of +those servers no longer exist: visualberlin.org, mutanten.org, web.mac.com, +kino.web.de, lh4.google.it, l.yimg.com. Name them in LOSTIMAGES_DEAD_HOSTS and +their images are treated exactly like a lost local file. The host is matched +rather than the URL fetched, because a build must not depend on the network and +a dead host is a fact about the world, not about this document. + +Left alone either kind renders as a broken-image box, which on a phone reads as +an unexplained 40px hole between paragraphs. On a gemtext capsule it is worse: +an image becomes a visible link line, so the page advertises a dead host. + +The reference is still worth keeping in the Markdown source, so this strips it +only from the generated output and leaves a comment naming the file, which keeps +the output self-documenting for anyone who later finds the originals. +""" +import os +import re + +from pelican import signals + +IMG = re.compile(r']*\bsrc="([^"]+)"[^>]*>') +REMOTE = re.compile(r'^https?://([^/]+)/', re.IGNORECASE) +ANCHOR = re.compile(r']*\bhref="(/library/[^"]+)"[^>]*>(.*?)', re.S) + + +def _strip(content): + body = getattr(content, "_content", None) + if not body or "" % name_of(src) + + body = IMG.sub(sub, body) + # a thumbnail that linked to the full-size file leaves the behind, and + # its href 404s just as the did + body = ANCHOR.sub(anchor, body) + if not lost: + return + # a paragraph that held nothing but the lost image would still occupy space + body = re.sub(r"

\s*((?:\s*)+)

", r"\1", body) + content._content = body + content.lost_images = lost + + +def register(): + signals.content_object_init.connect(_strip) diff --git a/plugins/smolnet.py b/plugins/smolnet.py index a34c6e4..b757234 100644 --- a/plugins/smolnet.py +++ b/plugins/smolnet.py @@ -69,6 +69,12 @@ INTERNAL = re.compile(r"\{(filename|static|attach)\}([^)\s]+)") SUMMARY_MARKER = "" +# Markdown image syntax, for dropping the ones whose host is gone. Matched on +# the source rather than the rendered HTML because this plugin reads the +# Markdown directly, so lostimages.py, which works on Pelican's output, never +# sees the capsule. +MD_IMAGE = re.compile(r'!\[[^\]]*\]\(\s*(https?://[^\s)]+)[^)]*\)') + def rot13(text): """The obfuscation used on addresses in the HTML templates.""" @@ -156,6 +162,32 @@ def reveal_rot13(text, attr): return "".join(out) +def drop_dead_images(text, dead): + """Remove images hotlinked from hosts that no longer exist. + + It matters more here than on the web. A browser shows a broken image as a + silent gap, but gemtext has no inline images: every one becomes a visible + `=>` link line, so the page would advertise a dead host as though it were + somewhere worth going. + + Shares LOSTIMAGES_DEAD_HOSTS with lostimages.py so there is one list, and + matches on the host rather than fetching, because a build must not depend on + the network. + """ + if not dead: + return text + + def sub(match): + host = match.group(1).split("//", 1)[1].split("/")[0].lower().split(":")[0] + if any(host == d or host.endswith("." + d) for d in dead): + return "" + return match.group(0) + + text = MD_IMAGE.sub(sub, text) + # a line that held nothing but the dropped image would leave a blank + return re.sub(r"\n[ \t]*\n[ \t]*\n+", "\n\n", text) + + def toml_escape(value): return str(value).replace("\\", "\\\\").replace('"', '\\"') @@ -193,6 +225,8 @@ class CapsuleGenerator(Generator): order = s.get("SMOLNET_SECTION_ORDER", {}) or {} latest_n = s.get("SMOLNET_LATEST", 15) rot_attr = s.get("SMOLNET_ROT13_ATTR") + dead_hosts = {h.lower().lstrip(".") for h in + s.get("LOSTIMAGES_DEAD_HOSTS", [])} sidecar_name = s.get("SMOLNET_SIDECAR", ".itsybitsy.toml") articles = [a for a in self.context.get("articles", [])] @@ -214,6 +248,7 @@ class CapsuleGenerator(Generator): body = body.replace(SUMMARY_MARKER, "") body = rewrite_internal(body, by_source, self._warn) body = reveal_rot13(body, rot_attr) + body = drop_dead_images(body, dead_hosts) # Blank lines, not newlines: a heading and the line under it are # separate blocks only if a blank line divides them, otherwise the # date runs into the first sentence as one paragraph. diff --git a/randogoth.com/pelicanconf.py b/randogoth.com/pelicanconf.py index 5d7914f..b7f1455 100644 --- a/randogoth.com/pelicanconf.py +++ b/randogoth.com/pelicanconf.py @@ -10,7 +10,16 @@ DEFAULT_LANG = 'en' # and the one actually wanted here. mc-pelican stays in the tree as the fallback. THEME = 'bbs' PLUGIN_PATHS = ['plugins', '../plugins'] -PLUGINS = ['crtimages', 'smolnet'] +PLUGINS = ['crtimages', 'smolnet', 'lostimages'] + +# Hosts that no longer exist, so their hotlinked images can never resolve. +# Checked against Wayback: of twelve such images only one was ever captured, +# and it now lives in content/library/. See lostimages.py. +LOSTIMAGES_DEAD_HOSTS = [ + 'visualberlin.org', 'mutanten.org', 'web.mac.com', + 'kino.web.de', 'lh4.google.it', 'l.yimg.com', +] + # The sections are message areas, numbered so they can be dialled by key the way # a board's main menu works. No descriptions: the names are the whole listing, diff --git a/tobias.raayoni-last.com/pelicanconf.py b/tobias.raayoni-last.com/pelicanconf.py index b07879f..a45c59d 100644 --- a/tobias.raayoni-last.com/pelicanconf.py +++ b/tobias.raayoni-last.com/pelicanconf.py @@ -6,7 +6,7 @@ TIMEZONE = 'Asia/Jerusalem' DEFAULT_LANG = 'en' THEME = 'raayoni' -PLUGIN_PATHS = ['plugins'] +PLUGIN_PATHS = ['plugins', '../plugins'] PLUGINS = ['assetcheck', 'lostimages'] # Dark is the register this work belongs to, so it is the default rather than # following the system. An explicit toggle (persisted in localStorage) wins. diff --git a/tobias.raayoni-last.com/plugins/lostimages.py b/tobias.raayoni-last.com/plugins/lostimages.py deleted file mode 100644 index c7b36ef..0000000 --- a/tobias.raayoni-last.com/plugins/lostimages.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Drop tags in article bodies whose file did not survive the recovery. - -The same situation as assetcheck.py, one level down: the recovered bodies kept -their original /library/ references, and 22 of 200 point at files no archive -holds (checked against Wayback; the CDX API returns no captures for them). - -Left alone they render as a broken-image box, which on a phone reads as an -unexplained 40px hole between paragraphs. The reference is still worth keeping -in the Markdown source, so this strips it only from the generated HTML and -leaves a comment naming the file, which keeps the output self-documenting for -anyone who later finds the originals on the offline drive. -""" -import os -import re - -from pelican import signals - -IMG = re.compile(r']*\bsrc="(/[^"]+)"[^>]*>') -ANCHOR = re.compile(r']*\bhref="(/library/[^"]+)"[^>]*>(.*?)
', re.S) - - -def _strip(content): - body = getattr(content, "_content", None) - if not body or "" % os.path.basename(rel) - - body = IMG.sub(sub, body) - # a thumbnail that linked to the full-size file leaves the behind, and - # its href 404s just as the did - body = ANCHOR.sub(anchor, body) - if not lost: - return - # a paragraph that held nothing but the lost image would still occupy space - body = re.sub(r"

\s*((?:\s*)+)

", r"\1", body) - content._content = body - content.lost_images = lost - - -def register(): - signals.content_object_init.connect(_strip)