Home con directo, comunicados y organización. HTML y CSS, sin JS. Configuración de nginx, timer del directo y limpieza de EXIF.
89 lines
2.7 KiB
Python
Executable file
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()
|