diff --git a/pruef-links.mjs b/pruef-links.mjs new file mode 100644 index 00000000..3c691453 --- /dev/null +++ b/pruef-links.mjs @@ -0,0 +1,98 @@ +/* ===================================================================== + 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;