26s/tools/directo.py
hacklab f78773bb87 Web de la acampada del 26S
Home con directo, comunicados y organización. HTML y CSS, sin JS.
Configuración de nginx, timer del directo y limpieza de EXIF.
2026-09-29 10:10:54 +02:00

89 lines
2.7 KiB
Python
Executable file

#!/usr/bin/env python3
"""Reescribe el bloque DIRECTO de index.html con los últimos posts del pub.
Lee de stdin el JSON de tools/ultimos.js y sustituye lo que haya entre
<!-- directo:inicio --> y <!-- directo:fin -->. Escritura atómica: el
servidor web nunca ve un fichero a medias.
node tools/ultimos.js @feed.ed25519 5 | python3 tools/directo.py /var/www/acampada26s.net/index.html
"""
import html
import json
import os
import re
import sys
import tempfile
from datetime import datetime
from urllib.parse import quote
from zoneinfo import ZoneInfo
INI = "<!-- directo:inicio -->"
FIN = "<!-- directo:fin -->"
MAX = 400 # caracteres por post
TZ = ZoneInfo("Europe/Madrid")
def limpia(texto):
# fuera markdown de imágenes/enlaces y referencias ssb, queda el texto
texto = re.sub(r"!\[[^\]]*\]\([^)]*\)", "", texto)
texto = re.sub(r"\[([^\]]*)\]\([^)]*\)", r"\1", texto)
texto = " ".join(texto.split())
if len(texto) > MAX:
texto = texto[:MAX].rsplit(" ", 1)[0] + "…"
return texto
def bloque(posts):
li = []
for p in posts:
t = datetime.fromtimestamp(p["ts"] / 1000, tz=TZ)
img = ""
if p.get("img"):
src = "/c/blob/" + quote(p["img"], safe="")
img = f'\n <img src="{html.escape(src)}" alt="" loading="lazy">'
li.append(
" <li>\n"
f' <time datetime="{t.isoformat(timespec="minutes")}">{t:%d.%m %H:%M}</time>{img}\n'
f" <p>{html.escape(limpia(p['text']))}</p>\n"
" </li>"
)
if not li:
return f"{INI}\n {FIN}"
return (
f'{INI}\n <section id="directo">\n <h2>Directo</h2>\n'
f' <ol class="lista">\n{chr(10).join(li)}\n </ol>\n'
f' <p><a href="/c?type=feed">Todo en OASIS</a></p>\n </section>\n {FIN}'
)
def main():
if len(sys.argv) != 2:
sys.exit("uso: ... | directo.py ruta/index.html")
ruta = sys.argv[1]
posts = json.load(sys.stdin)
if not isinstance(posts, list):
sys.exit("entrada no es una lista")
with open(ruta, encoding="utf-8") as f:
doc = f.read()
i, j = doc.find(INI), doc.find(FIN)
if i < 0 or j < i:
sys.exit("no están las marcas directo:inicio / directo:fin")
nuevo = doc[:i] + bloque(posts) + doc[j + len(FIN):]
if nuevo == doc:
return
d = os.path.dirname(os.path.abspath(ruta))
fd, tmp = tempfile.mkstemp(dir=d, prefix=".index.")
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.write(nuevo)
os.chmod(tmp, 0o644)
os.replace(tmp, ruta)
except BaseException:
os.unlink(tmp)
raise
if __name__ == "__main__":
main()