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.
This commit is contained in:
commit
f78773bb87
15 changed files with 1335 additions and 0 deletions
89
tools/directo.py
Executable file
89
tools/directo.py
Executable file
|
|
@ -0,0 +1,89 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue