/* ===================================================================== Zeigt irgendein interner Link ins Leere? Ein toter Link in der Navigation oder im Text schickt Besucher auf die 404-Seite. Beim normalen Klicken merkt man das kaum -- man klickt ja die Links, von denen man weiß, dass sie gehen. Dieser Test sammelt ALLE internen Links von den öffentlichen Seiten ein und prüft für jedes Ziel EINMAL, ob es wirklich existiert (kein 404/5xx). Gegen die echte Domain, damit auch die serverseitige Auslieferung (Weiterleitungen, Zugangswände) mitgeprüft wird. Aufruf: node pruef-links.mjs ===================================================================== */ import { chromium } from "playwright"; const BASIS = "https://dogfather-universe.com"; /* Die öffentlich verlinkten Seiten -- von hier aus wird die Verlinkung aufgesammelt. Unterseiten, die nur von diesen aus verlinkt sind, werden über ihre Links automatisch mitgeprüft. */ const SEITEN = [ "/", "/dogfather.html", "/hasidog.html", "/manager.html", "/casper.html", "/vanvan.html", "/tt-muddi.html", "/zeitreise.html", "/streamer.html", "/community.html", "/stimmen.html", "/streamplan.html", "/team-modis.html", "/team-scouts.html", "/creator.html", "/werte.html", "/bewerben.html", "/kontakt.html", "/links.html", "/supporter.html", "/abonnieren.html", "/medien-dogfather.html", "/medien-hasidog.html", "/medien-casper.html", ]; const browser = await chromium.launch(); /* 1. Alle internen Ziele einsammeln. */ const ziele = new Map(); // pfad -> Set der Seiten, die darauf verlinken const seiteFehler = []; for (const pfad of SEITEN) { const seite = await browser.newPage(); try { const antwort = await seite.goto(BASIS + pfad, { waitUntil: "domcontentloaded", timeout: 30000 }); if (!antwort || antwort.status() >= 400) { seiteFehler.push(`${pfad} selbst -> ${antwort ? antwort.status() : "keine Antwort"}`); } const links = await seite.evaluate(() => { return [...document.querySelectorAll('a[href]')] .map((a) => a.getAttribute("href")) .filter((h) => h && !h.startsWith("#") && !h.startsWith("mailto:") && !h.startsWith("tel:") && !h.startsWith("javascript:")); }); for (const h of links) { /* Nur interne Links prüfen. Externe (andere Domains) lassen wir aus -- die kann diese Seite nicht garantieren, und sie ändern sich außerhalb unserer Kontrolle. */ let url; try { url = new URL(h, BASIS + pfad); } catch { continue; } if (url.origin !== BASIS) continue; const ziel = url.pathname + url.search; if (!ziele.has(ziel)) ziele.set(ziel, new Set()); ziele.get(ziel).add(pfad); } } catch (e) { seiteFehler.push(`${pfad} selbst -> ${String(e.message).slice(0, 40)}`); } await seite.close(); } console.log(`\n${ziele.size} verschiedene interne Ziele von ${SEITEN.length} Seiten gesammelt.\n`); /* 2. Jedes Ziel EINMAL prüfen. */ const pruefSeite = await browser.newPage(); let ok = 0; const kaputt = []; for (const [ziel, quellen] of [...ziele].sort()) { try { const a = await pruefSeite.goto(BASIS + ziel, { waitUntil: "commit", timeout: 20000 }); const code = a ? a.status() : 0; /* < 400 gilt als in Ordnung -- auch Weiterleitungen (3xx) auf eine Zugangswand sind ein gültiges Ziel, kein toter Link. */ if (code < 400) { ok++; } else { kaputt.push({ ziel, code, quellen: [...quellen] }); } } catch (e) { kaputt.push({ ziel, code: String(e.message).slice(0, 30), quellen: [...quellen] }); } } await browser.close(); console.log(`===== ${ok} Ziele in Ordnung, ${kaputt.length} kaputt, ${seiteFehler.length} Seiten selbst fehlerhaft =====`); if (seiteFehler.length) { console.log("\nSeiten, die selbst nicht luden:"); for (const f of seiteFehler) console.log(" - " + f); } if (kaputt.length) { console.log("\nTote Links (Ziel -> Code, verlinkt von):"); for (const k of kaputt) console.log(` - ${k.ziel} -> ${k.code} (von: ${k.quellen.join(", ")})`); } process.exitCode = kaputt.length || seiteFehler.length ? 1 : 0;