Webmentions senden mit HTMLy

Gestern hab ich ja im Blogartikel erklärt, wie ich den Empfang von Webmentions über webmention.io in HTMLy integriert habe. Heute kümmern wir uns um den Versand, weil Robert ganz direkt gefragt hat „Wie machst du das eigentlich mit dem Senden?“ Gute Frage.

Spoiler, HTMLy kann das von Haus aus nicht. Warum? Weil HTMLy ein minimalistisches Flat-File-Blogsystem ist. Kein Plugin-System, keine Hooks nach dem Speichern, kein „ich erledige das mal automatisch für dich“-Zauber. Ist ja auch irgendwie der Reiz daran, man muss es halt selber machen. Und genau das erledigen wir heute.

Vorweg, bevor wieder jemand den Klugscheißer-Modus aktiviert, weil ich nen falschen Fachbegriff verwende. Ja Bro, ich bin kein studierter Informatiker, kein Vollzeit-Dev und schon gar kein zertifizierter PHP-Guru. Ich bin einfach ein Dude, der irgendwann keinen Bock mehr auf Big Tech, Datenschnüffelei und Plattform-Abhängigkeit hatte. Also hab ich angefangen, mir den Kram selber beizubringen, learning by doing, Schritt für Schritt, Script für Script. Nicht jeder hat Zeit, Geld oder Möglichkeiten aufn Informatikstudium. Und das ist auch völlig okay. Also chill deine Basis und halt den Ball flach. Hier geht’s nicht um perfekte Codephilosophie, sondern um praktische Lösungen.

Warum der Versand fehlt (und das okay ist)

Webmentions funktionieren so: Wenn du jemand anderen verlinkst, dann solltest du ihm Bescheid sagen. Das geht per HTTP-POST an einen speziellen Webmention-Endpunkt. So nach dem Motto „Yo, ich hab dich erwähnt, hier ist der Link.“

HTMLy? Verlinken kann es, aber das Bescheid sagen? Fehlanzeige. Du musst den Versand also selbst auslösen. Automatisch würde das zb gehen, wenn man das Blogsystem auf GitHub hostet oder sich mit Cronjobs befreundet. Da ich aber aus chronischer Pleitigkeit auf nem minimalen Strato Space blogge und den nicht mit systemd oder Shellzugriff quälen kann, hab ich‘s simpel gelöst.

Mit einem kleinen PHP-Skript. Direkt im Blog. Läuft.

Schritt 1: Webmention-Send-Skript erstellen

Dieses Skript nutzt jetzt cURL statt file_get_contents(). Warum? Weil viele Hoster (Strato z. B.) aus Sicherheitsgründen das Laden von URLs mit file_get_contents() blockieren. cURL ist robuster, schneller und sorgt dafür, dass das Script nicht ewig hängt oder einfach abbricht.

Außerdem habe ich einen Passwortschutz eingebaut. So kann nicht jeder einfach so Webmention-Sendungen auslösen. Ich muss beim Aufruf das Passwort als Parameter auth mitgeben und damit ich im Blick hab, wer das Script wann nutzt (und wer scheitert), schreibt das Script automatisch eine Logdatei namens webmention-log.txt ins Blogverzeichnis, ganz automatisch, kein Extra-Aufwand.

Und weil man ja keine böswilligen Bots einladen will, gibt’s eine kleine Verzögerung von 2 Sekunden bei jedem Aufruf, um brutale Zugriffswellen zu drosseln.

Was macht das Ding?

  1. Nimmt eine Artikel-URL (source) als Parameter
  2. Es lädt die Seite via cURL
  3. Es findet alle < a href=“…“> Links
  4. Prüft für jeden Link per cURL, ob ein Webmention-Endpunkt existiert (im HTTP-Header oder HTML)
  5. Wenn ja, sendet es die Webmention an die Zielseite
  6. Wenn nein, gibt es ne nette Meldung wie „Kein Endpunkt gefunden“
  7. Loggt alle Zugriffe (erfolgreich oder fehlgeschlagen) mit IP und Zeit in webmention-log.txt
  8. Verzögert den Scriptstart um 2 Sekunden als einfache Schutzmaßnahme
  9. Verlangt ein Passwort (auth) zur Nutzung
<?php
// Einfaches PHP-Webmention-Sender-Skript mit Passwortschutz, cURL, Delay & IP-Logging

// 🔐 Passwortschutz aktivieren
$authKey = 'mein-geheimer-code'; // <- Hier dein Passwort reinschreiben
if (!isset($_GET['auth']) || $_GET['auth'] !== $authKey) {
    // Zugriff loggen
    file_put_contents('webmention-log.txt', date('Y-m-d H:i:s') . " - FAILED access from IP: " . $_SERVER['REMOTE_ADDR'] . "\n", FILE_APPEND);
    die('Unauthorized access');
}

// ✅ Erfolgreichen Zugriff loggen
file_put_contents('webmention-log.txt', date('Y-m-d H:i:s') . " - Access from IP: " . $_SERVER['REMOTE_ADDR'] . "\n", FILE_APPEND);

// 🕒 Anti-Brute-Force: kleine Verzögerung
sleep(2);

if (!isset($_GET['source']) || empty($_GET['source'])) {
    die('Bitte die Quelle als URL-Parameter "source" angeben.');
}

$source = $_GET['source'];

// cURL-Funktion zum Laden von URLs
function curl_get_contents($url) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Redirects folgen
    curl_setopt($ch, CURLOPT_USERAGENT, 'Webmention Sender by Piehnat'); // User-Agent setzen
    curl_setopt($ch, CURLOPT_TIMEOUT, 10); // Timeout 10 Sekunden
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}

// 1. Lade den Inhalt der Quellseite via cURL
$html = curl_get_contents($source);
if (!$html) {
    die("Fehler: Konnte Quelle $source nicht laden.");
}

// 2. Alle Links aus der Quelle extrahieren (einfach per Regex)
preg_match_all('/<a\s[^>]*href=["\']([^"\']+)["\']/i', $html, $matches);
$links = array_unique($matches[1]);

echo "<h2>Webmentions senden von:</h2>";
echo "<p>$source</p>";

if (empty($links)) {
    die("Keine Links gefunden.");
}

echo "<h3>Gefundene Links:</h3><ul>";

// Hilfsfunktion: Webmention-Endpunkt finden (mit cURL)
function find_webmention_endpoint($url) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_NOBODY, true);
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Webmention Sender by Piehnat');
    $headers = curl_exec($ch);
    curl_close($ch);

    if (!$headers) return false;

    if (preg_match_all('/Link:\s*<([^>]+)>;\s*rel="?webmention"?/i', $headers, $matches)) {
        return $matches[1][0];
    }

    $html = curl_get_contents($url);
    if ($html && preg_match('/<link[^>]+rel=["\']?webmention["\']?[^>]*href=["\']?([^"\']+)["\']?/i', $html, $m)) {
        return $m[1];
    }

    return false;
}

// Hilfsfunktion: Webmention senden
function send_webmention($source, $target, $endpoint) {
    $data = http_build_query([
        'source' => $source,
        'target' => $target
    ]);

    $opts = [
        'http' => [
            'method'  => 'POST',
            'header'  => "Content-Type: application/x-www-form-urlencoded\r\n",
            'content' => $data,
            'ignore_errors' => true
        ]
    ];
    $context = stream_context_create($opts);
    $result = @file_get_contents($endpoint, false, $context);
    return $result !== false;
}

// 3. Für jeden Link prüfen wir den Webmention-Endpunkt und senden, falls vorhanden
foreach ($links as $link) {
    echo "<li>$link — ";
    $endpoint = find_webmention_endpoint($link);
    if ($endpoint) {
        $ok = send_webmention($source, $link, $endpoint);
        echo $ok ? "<strong>Webmention gesendet</strong>" : "<strong>Fehler beim Senden</strong>";
    } else {
        echo "<em>Kein Webmention-Endpunkt gefunden</em>";
    }
    echo "</li>";
}

echo "</ul>";
?>

Und das alles in klartextiger HTML-Ausgabe, damit ich direkt sehe, was lief und was nicht.

Beispiel-Aufruf im Browser:

https://katja-maertens.de/send-webmentions.php?source=https://piehnat.de/post/webmentions-zuruck-in-die-blog-zukunft&auth=dein-geheimer-code

Das auth schützt das Skript vor Missbrauch. Einfach die URL deines neuen Artikels hinten anhängen und der Rest passiert automatisch. Kein Bullshit.

Schritt 2: Datei hochladen

Die Datei send-webmentions.php kommt per FTP einfach dahin, wo auch meine index.php liegt, also direkt ins Root-Verzeichnis des Blogs. Danach kannst du die Datei direkt im Browser aufrufen und loslegen.

Schritt 3: Komfort-Upgrade Webmention-Launcher

Weil ich aber faul bin und keinen Bock habe, die URL jedes Mal per Hand zusammenzubasteln, hab ich mir ein kleines HTML-Tool gebastelt.

Ein Feld für die Artikel-URL, ein Feld für das Passwort (auth), ein Button, fertig. Klick. Script wird aufgerufen.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>Webmention Sender</title>
<style>
  body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; }
  label, input, button { display: block; width: 100%; margin-bottom: 1em; font-size: 1.2em; }
  input { padding: 0.5em; }
  button { padding: 0.7em; cursor: pointer; }
</style>
</head>
<body>

<h1>Webmention Sender</h1>

<label for="articleUrl">Artikel-URL:</label>
<input type="url" id="articleUrl" placeholder="https://katja-maertens.de/post/dein-artikel" required />

<label for="authCode">Auth-Code:</label>
<input type="password" id="authCode" placeholder="Dein geheimer Code" required />

<button id="sendBtn">Webmentions senden</button>

<script>
  const scriptBaseUrl = "https://katja-maertens.de/send-webmentions.php";

  document.getElementById('sendBtn').addEventListener('click', () => {
    const articleUrl = document.getElementById('articleUrl').value.trim();
    const authCode = document.getElementById('authCode').value.trim();
    if (!articleUrl) {
      alert('Bitte die Artikel-URL ausfüllen!');
      return;
    }
    if (!authCode) {
      alert('Bitte den Auth-Code ausfüllen!');
      return;
    }
    const encodedUrl = encodeURIComponent(articleUrl);
    const encodedAuth = encodeURIComponent(authCode);
    const fullUrl = `${scriptBaseUrl}?source=${encodedUrl}&auth=${encodedAuth}`;
    window.open(fullUrl, '_blank');
  });
</script>

</body>
</html>

Als .html-Datei auf dem Rechner speichern und bei Bedarf starten.

So sieht das im Browser aus

 

Und so das Ergebnis

Und ja, ich weiß, das Script schickt auch Webmentions an eigene Seiten, Kategorien, Impressum, und sogar mailto:-Links werden versucht (die natürlich fehlschlagen). Kann man alles rausfiltern. Könnte man machen. Aber reicht für heute. Peace.

P.S. Noch mehr Webmentions-Lesestoff gibt es bei Jan.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

Latest

Ihr kotzt mich an

Ihr kotzt mich an

Junge, wie mich diese dummen, unreflektierten Sprüche wie „Mauer wieder drum“ oder „Verschenkt den Osten an Russland“ seit gestern überall abfucken. Mal davon abgesehen, dass all diese Verfasser damit auch auf all die stabilen Menschen wie mich scheißen, die aus persönlichen Gründen hier nicht wegkönnen, und uns damit ebenfalls nur das Ekelhafteste wünschen. Es ist […]
7. September 2026
Demokratiefestival Magdeburg
Demokatiefestival-Magdeburg Blick aus der Masse zur Bühne

Demokratiefestival Magdeburg

Schön, in diesen Zeiten mitten in Ostdeutschland mal wieder unter „normalen“ Menschen gewesen zu sein. Zwischen all den Abgedrifteten und Faschos verliert man ja schnell mal den Glauben an die Menschheit.
6. September 2026
MelonLand – Ein bisschen altes Internet

MelonLand – Ein bisschen altes Internet

Neulich stolperte ich über MelonLand sowie deren Surf Navigator und trieb seither schon mehrfach von Blog zu Blog. Kann nicht mal wirklich sagen, was mich dort so anzieht, hat aber sicher auch damit zu tun, dass gefühlt immer mehr Blogs optische Zwillinge sind oder einen auf ultra-minimalistisch machen. Je weniger, desto besser. Optisch ist das […]
6. September 2026