diff --git a/seo_link.py b/seo_link.py index 52bfb53..3ba8727 100644 --- a/seo_link.py +++ b/seo_link.py @@ -225,25 +225,34 @@ def main(): + (f"\n …{ctx}…" if ok else " (no aparece en la prosa)")) campo, valor = "lexical", json.dumps(lex, ensure_ascii=False) else: + # Los dos formatos acaban en diccionarios con clave "html", así que se + # tratan igual: una LISTA de trozos. Antes el mobiledoc solo miraba + # cards[0], y un cuerpo repartido en varias tarjetas —que es justo lo + # que pasa cuando el artículo lleva una imagen intercalada— respondía + # «no aparece en la prosa» estando el anchor en la segunda. if fmt == "lexical-html": doc = json.loads(post["lexical"]) - trozo = doc["root"]["children"][0] - get_html, set_html = (lambda: trozo["html"]), (lambda h: trozo.__setitem__("html", h)) + trozos = [doc["root"]["children"][0]] + campo = "lexical" else: doc = json.loads(post["mobiledoc"]) - cards = [c for c in doc.get("cards", []) if c and c[0] == "html"] - if not cards: + trozos = [c[1] for c in doc.get("cards", []) if c and c[0] == "html"] + if not trozos: sys.exit(f"✗ {a.slug}: mobiledoc sin tarjetas html") - tarjeta = cards[0][1] - get_html, set_html = (lambda: tarjeta["html"]), (lambda h: tarjeta.__setitem__("html", h)) - html = get_html() + campo = "mobiledoc" + if len(trozos) > 1: + print(f" ({len(trozos)} trozos de html; se busca en todos)") + for anchor, url in pares: - html, ok, ctx = link_in_html(html, anchor, url) + ok, ctx = False, "" + for t in trozos: + t["html"], ok, ctx = link_in_html(t["html"], anchor, url) + if ok: + break print(f" {'+' if ok else '⚠'} «{anchor}» → {url.split('/')[-2][:34]}" + (f"\n …{ctx}…" if ok else " (no aparece en la prosa)")) - revisar_html(html, a.slug) - set_html(html) - campo = "lexical" if fmt == "lexical-html" else "mobiledoc" + for t in trozos: + revisar_html(t["html"], a.slug) valor = json.dumps(doc, ensure_ascii=False) if not a.apply: diff --git a/seo_watch.py b/seo_watch.py index 9f2ff1b..603166b 100644 --- a/seo_watch.py +++ b/seo_watch.py @@ -477,18 +477,27 @@ def main(): partes.append(txt); huellas[s] = fp; hallazgos |= hall report = "🔎 SEO watch\n\n" + "\n\n".join(partes) - fp = json.dumps(huellas, sort_keys=True) - prev = "" + # Estado POR SITIO, y solo se toca el de los sitios revisados. Antes se + # guardaba una única huella de los dos juntos, así que una ejecución manual + # con --site en la machacaba con media huella y el siguiente disparo del + # timer creía que TODO había cambiado: te reenviaba hallazgos viejos como si + # fueran nuevos. Un vigilante que cría falsas alarmas se acaba ignorando, + # que es exactamente lo que este no puede permitirse. + previo = {} try: with open(STATE) as f: - prev = json.load(f).get("fingerprint", "") - except FileNotFoundError: + guardado = json.load(f) + previo = guardado.get("sites") or {} + except (FileNotFoundError, ValueError): pass - changed = fp != prev + + cambiados = [s for s in sitios if huellas[s] != previo.get(s)] + changed = bool(cambiados) print(report) - print(f"\n[cambios vs última ejecución: {'sí' if changed else 'no'}]") + print(f"\n[cambios vs última ejecución: " + f"{', '.join(cambiados) if cambiados else 'ninguno'}]") if a.dry_run: return 0 @@ -500,8 +509,9 @@ def main(): print(f"[telegram: {'enviado' if ok else 'FALLO'}]") os.makedirs(os.path.dirname(STATE), exist_ok=True) + previo.update(huellas) # fusiona: no pisa el sitio que no se revisó with open(STATE, "w") as f: - json.dump({"fingerprint": fp}, f) + json.dump({"sites": previo}, f) return 0