diff --git a/seo_watch.py b/seo_watch.py index de1b382..a78e100 100644 --- a/seo_watch.py +++ b/seo_watch.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Tool D — vigilante SEO periódico de theexclusionzone.com (EN, Ghost). +Tool D — vigilante SEO periódico de los dos blogs (EN + ES, Ghost). READ-ONLY sobre Ghost. Corre los chequeos que detectan REGRESIONES nuevas a medida que se publica (2 posts/semana), no el inventario completo de siempre: @@ -45,12 +45,34 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import seo_rules as R import seo_exceptions as X -HOST = "https://www.theexclusionzone.com/" +# Los dos sitios. Ojo al canónico: EN es www, ES es el APEX — al revés. +SITES = { + "en": {"cli": "ghst-en", "host": "theexclusionzone.com", + "url": "https://www.theexclusionzone.com/"}, + "es": {"cli": "ghst-es", "host": "zonadeexclusion.com", + "url": "https://zonadeexclusion.com/"}, +} +HOST = SITES["en"]["url"] # el sitio activo; lo fija use_site() +CLI = SITES["en"]["cli"] STATE = os.path.expanduser("~/.local/state/seo-watch/last.json") TG_SECRET = ("monitoring", "grafana-telegram-infisical") TIMEOUT = 20 +def use_site(site): + """Fija el sitio activo para todos los checks. + + seo_rules.SITE_HOST está clavado a EN y NO se parametriza ahí a propósito: + ese fichero se vendoriza byte a byte en ResearchOwl y la CI lo verifica. + Se reasigna en runtime, que deja intacto el contrato compartido. + """ + global HOST, CLI + cfg = SITES[site] + HOST, CLI = cfg["url"], cfg["cli"] + R.SITE_HOST = cfg["host"] + return cfg + + # ---------- utilidades ---------- def sh(cmd, timeout=180): @@ -63,7 +85,7 @@ def fetch_posts(): fd, path = tempfile.mkstemp(suffix=".json", prefix="seo_watch_") os.close(fd) try: - r = sh(f"ghst-en post list --limit 100 --formats html --json > {path}", timeout=240) + r = sh(f"{CLI} post list --limit 100 --formats html --json > {path}", timeout=240) with open(path) as f: data = json.load(f) return data.get("posts", data) if isinstance(data, dict) else data @@ -258,12 +280,9 @@ def telegram(text): return json.loads(resp.read()).get("ok", False) -def main(): - ap = argparse.ArgumentParser() - ap.add_argument("--dry-run", action="store_true", help="imprime, no notifica ni guarda estado") - ap.add_argument("--force", action="store_true", help="notifica aunque no haya cambios") - a = ap.parse_args() - +def run_site(site): + """Corre todos los checks sobre un sitio. Devuelve (informe, huella, hay_hallazgos).""" + cfg = use_site(site) posts = fetch_posts() roto, nocanon, prematuro = check_links(posts) envejecido = check_link_aging(posts) @@ -272,10 +291,32 @@ def main(): body = build_report(roto, nocanon, prematuro, envejecido, n_sm, sm_malos, reglas) pub = sum(1 for p in posts if p.get("status") == "published") - header = f"🔎 SEO watch — theexclusionzone.com\n({pub} publicados, {len(posts)} totales, sitemap {n_sm} URLs)\n" - report = header + "\n" + body - + header = (f"── [{site.upper()}] {cfg['host']}\n" + f" {pub} publicados, {len(posts)} totales, sitemap {n_sm} URLs") fp = fingerprint(roto, nocanon, prematuro, envejecido, sm_malos, reglas) + hallazgos = bool(roto or nocanon or prematuro or envejecido or sm_malos) + return header + "\n" + body, fp, hallazgos + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--dry-run", action="store_true", help="imprime, no notifica ni guarda estado") + ap.add_argument("--force", action="store_true", help="notifica aunque no haya cambios") + ap.add_argument("--site", choices=("en", "es", "both"), default="both") + a = ap.parse_args() + + sitios = ["en", "es"] if a.site == "both" else [a.site] + partes, huellas, hallazgos = [], {}, False + for s in sitios: + try: + txt, fp, hall = run_site(s) + except Exception as e: # un sitio caído no debe tumbar el informe del otro + txt, fp, hall = f"── [{s.upper()}] ⚠ ERROR al revisar: {e}", f"error:{e}", True + 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 = "" try: with open(STATE) as f: @@ -283,7 +324,6 @@ def main(): except FileNotFoundError: pass changed = fp != prev - hallazgos = bool(roto or nocanon or prematuro or envejecido or sm_malos) print(report) print(f"\n[cambios vs última ejecución: {'sí' if changed else 'no'}]")