Die Seite wurde zuletzt überarbeitet am:

Anleitung: Fediverse-Kommentare aus Friendica in Publii einbinden

Wie viele wissen, nutze ich Publii für meinen Blog und bin äußerst zufrieden damit. Was mir aber sehr fehlt, ist die Möglichkeit, meinen Blog irgendwie mit dem Fediverse zu verbinden - klar, mit Publii geht das "natürlich" nicht, da Publii "nur" statische Seiten generiert. Es gibt aber bereits einige Scripte, mit denen man Kommentare aus dem Fediverse unter seinem Blog eintragen kann, wie immer natürlich nur für Mastodon, das kann ich für Friendica nicht brauchen. Also musste was eigenes her.

Gut 3 Stunden haben die KI und ich nun ein Script erstellt und daran gefeilt. Nun kann ich mit Publii die passenden Kommentare aus dem Fediverse einblenden lassen, auch die Anzahl der Likes und Shares werden angezeigt. Dabei wird nichts auf dem Blogserver gespeichert. Die Kommentare werden Live aus dem Fediverse geladen - sobald ein User seinen Kommentar im Fediverse löscht, ist dieser auch nicht mehr auf dem Blog sichtbar! Auch Profilbilder habe ich bewusst nicht eingebunden, wobei das selbstverständlich möglich ist.

Wer Interesse hat, darf sich gerne daran bedienen, das Script kann natürlich auch gerne abgewandelt und weiterentwickelt werden. Voraussetzung: PHP-Unterstützung auf dem Webspace/Server und "allow_url_fopen" muss auf "On" stehen.


In dieser Anleitung nehme ich Friendica als meine Fediverse-Instanz und Publii als meine Blog-Software.

Als erstes habe ich eine Datei namens "fedi-proxy.php" im Hauptverzeichnis meines Publii-Blogs erstellt/hochgeladen (also da, wo auch die index.html von Publii liegt!). Inhalt der "fedi-proxy.php":

<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

// --- KONFIGURATION ---
$host = "domain.de"; // <--- HIER DEINE ECHTE FRIENDICA-DOMAIN EINTRAGEN
// ---------------------

$fediId = $_GET['id'] ?? '';
if (!preg_match('/^[0-9]+$/', $fediId)) {
    die(json_encode(["error" => "Ungültige ID"]));
}

function fetch_data($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Publii-Proxy/1.0');
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);
    curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    return ($httpCode === 200) ? $response : false;
}

// 1. Hauptbeitrag abrufen
$mainUrl = "https://{$host}/api/v1/statuses/{$fediId}";
$mainResponse = fetch_data($mainUrl);

// 2. Kommentare abrufen
$contextUrl = "https://{$host}/api/v1/statuses/{$fediId}/context";
$contextResponse = fetch_data($contextUrl);

if ($mainResponse === false || $contextResponse === false) {
    http_response_code(404);
    echo json_encode(["error" => "Inhalt bei Friendica nicht gefunden oder Timeout"]);
} else {
    echo json_encode([
        "post" => json_decode($mainResponse),
                     "details" => json_decode($contextResponse)
    ]);
}

Hier bitte noch unbedingt die eigene (Friendica-) Instanz bei $host eintragen, sonst funktioniert das Script nicht! 


Als zweites habe ich in Publii selbst unter "Werkzeuge & Plugins" -> "Benutzerdefiniertes HTML" -> "Kommentare" folgendes eingetragen:

<div id="fediverse-comments-area"></div>

<style>
    /* Grund-Container */
    #fediverse-comments-area { color: #eee; max-width: 900px; margin: 2rem auto; font-family: sans-serif; line-height: 1.5; }
    
    /* Beitrags-Statistik direkt unter dem Artikel */
    .fedi-post-stats { 
        display: flex; 
        gap: 25px; 
        margin-bottom: 1.5rem; 
        padding: 10px 0; 
        border-bottom: 1px solid #333; 
        font-size: 1rem; 
        color: #ff69b4; 
        font-weight: bold; 
    }
    .fedi-post-stats span { display: flex; align-items: center; gap: 8px; }

    /* Interaktions-Box */
    .fedi-interaction-box { 
        background: #252525; border: 1px solid #444; padding: 1.5rem; 
        border-radius: 8px; margin-bottom: 2rem; border-left: 4px solid #ff69b4; 
    }
    .fedi-interaction-box h4 { margin-top: 0; color: #fff; margin-bottom: 10px; }
    .fedi-interaction-box p { font-size: 0.9rem; color: #ccc; margin-bottom: 12px; line-height: 1.4; }
    .fedi-interaction-box strong { color: #fff; display: block; margin-bottom: 5px; }

    /* Eingabefelder und Buttons */
    .fedi-helper-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 8px; margin-bottom: 20px; }
    .fedi-input { background: #1a1a1a; border: 1px solid #555; color: #fff; padding: 10px 12px; border-radius: 4px; flex-grow: 1; min-width: 200px; font-size: 0.9rem; }
    .fedi-button { background: #ff69b4; border: none; color: #fff; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.9rem; transition: background 0.2s; }
    .fedi-button:hover { background: #ff1493; }
    .fedi-copy-row { display: flex; gap: 5px; align-items: center; margin-top: 8px; }

    /* Der erweiterte Datenschutz-Hinweis */
    .fedi-privacy-notice { 
        font-size: 0.8rem; color: #999; border-top: 1px solid #333; 
        padding-top: 15px; margin-top: 15px; line-height: 1.4;
    }

    /* Kommentare & Design */
    .fedi-comment { border-left: 2px solid #444; margin-top: 1.5rem; padding-left: 1.5rem; }
    .fedi-comment.top-level { border-left: none; padding-left: 0; border-top: 1px solid #333; padding-top: 1.5rem; }
    .fedi-user-icon { width: 32px; height: 32px; background: #444; color: #888; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-right: 12px; font-size: 14px; }
    .fedi-author { display: block; font-weight: bold; color: #fff; text-decoration: none; font-size: 0.95rem; }
    .fedi-author:hover { text-decoration: underline; }
    .fedi-date { font-size: 0.75rem; color: #888; text-decoration: none; }
    .fedi-body { color: #ddd; line-height: 1.6; font-size: 0.95rem; margin-top: 5px; }
    .fedi-body a { color: #ff69b4; text-decoration: none; }

    /* Statistik für einzelne Kommentare */
    .fedi-comment-stats { display: flex; gap: 12px; margin-top: 8px; font-size: 0.8rem; color: #888; }
</style>

<script>
document.addEventListener("DOMContentLoaded", function() {
    const idElement = document.querySelector('[data-fedi-id]');
    const wrapper = document.getElementById('fediverse-comments-area');
    if (!idElement || !wrapper) return;

    const fediId = idElement.getAttribute('data-fedi-id');

    function formatDate(dateString) {
        const options = { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' };
        return new Date(dateString).toLocaleDateString('de-DE', options) + " Uhr";
    }

    window.fediRedirect = function(uri) {
        const instance = document.getElementById('fedi-instance-input').value.trim().replace(/^https?:\/\//, '');
        if (!instance) return alert("Bitte Instanz-Domain eingeben.");
        window.open(`https://${instance}/authorize_interaction?uri=${encodeURIComponent(uri)}`, '_blank');
    };

    window.copyFediLink = function() {
        const copyText = document.getElementById("fedi-link-field");
        copyText.select();
        navigator.clipboard.writeText(copyText.value);
        alert("Beitrags-URL kopiert!");
    };

    function renderComment(item, isTopLevel = false) {
        let statsHtml = '';
        if (item.favourites_count > 0 || item.reblogs_count > 0) {
            statsHtml = `<div class="fedi-comment-stats">
                ${item.favourites_count > 0 ? `<span>❤️ ${item.favourites_count}</span>` : ''}
                ${item.reblogs_count > 0 ? `<span>🔄 ${item.reblogs_count}</span>` : ''}
            </div>`;
        }

        return `
            <div class="fedi-comment ${isTopLevel ? 'top-level' : ''}">
                <div style="display: flex; align-items: center; margin-bottom: 0.8rem;">
                    <div class="fedi-user-icon" title="Privatsphäre: Avatare deaktiviert">👤</div>
                    <div style="line-height: 1.2;">
                        <a href="${item.account.url}" class="fedi-author" target="_blank" rel="nofollow noopener">${item.account.display_name}</a>
                        <a href="${item.url}" class="fedi-date" target="_blank" rel="nofollow noopener">${formatDate(item.created_at)}</a>
                    </div>
                </div>
                <div class="fedi-body">${item.content}</div>
                ${statsHtml}
                ${item.children && item.children.length > 0 ? 
                    item.children.map(child => renderComment(child)).join('') : ''}
            </div>`;
    }

    fetch(`./fedi-proxy.php?id=${fediId}`)
        .then(response => response.json())
        .then(res => {
            const data = res.details;
            const postInfo = res.post;
            const fediPostUrl = postInfo.url;

            const publicComments = data.descendants.filter(comment => 
                comment.visibility === 'public' || comment.visibility === 'unlisted'
            );

            let html = `
                <div class="fedi-post-stats">
                    <span>❤️ ${postInfo.favourites_count} Likes</span>
                    <span>🔄 ${postInfo.reblogs_count} Shares</span>
                </div>

                <div class="fedi-interaction-box">
                    <h4>Mitdiskutieren</h4>
                    <p>Dieser Blog ist mit dem Fediverse verbunden. Du kannst von jedem Server aus antworten (z. B. Mastodon oder Friendica).</p>
                    
                    <div style="margin-bottom: 20px;">
                        <strong>1. Für Nutzer von Mastodon, Pleroma oder Sharkey:</strong>
                        <div class="fedi-helper-row">
                            <input type="text" id="fedi-instance-input" class="fedi-input" placeholder="Deine Instanz (z. B. mastodon.social)" onkeydown="if(event.key==='Enter') fediRedirect('${fediPostUrl}')">
                            <button class="fedi-button" onclick="fediRedirect('${fediPostUrl}')">LOS!</button>
                        </div>
                    </div>

                    <div style="border-top: 1px solid #444; padding-top: 15px;">
                        <strong>2. Für Friendica, Hubzilla & andere:</strong>
                        <p>Kopiere diese URL in das Suchfeld deiner Instanz, um den Beitrag zu finden:</p>
                        <div style="display: flex; gap: 5px;">
                            <input type="text" id="fedi-link-field" class="fedi-input" value="${fediPostUrl}" readonly style="font-family: monospace; background: #000; font-size: 0.8rem; flex-grow: 1;">
                            <button class="fedi-button" onclick="copyFediLink()" style="background: #555;">KOPIEREN</button>
                        </div>
                    </div>

                    <div class="fedi-privacy-notice">
                        <strong>Hinweis zum Datenschutz:</strong> Sämtliche Interaktionen (Kommentare, Likes, Shares) werden live aus dem Fediverse geladen und zu keinem Zeitpunkt auf diesem Blog-Server gespeichert. Wenn du deinen Beitrag oder deine Interaktion auf deiner Heimat-Instanz löschst, verschwindet sie automatisch auch hier. Du behältst die volle Souveränität über deine Daten.
                    </div>
                </div>

                <h3>Diskussion (${publicComments.length})</h3>
            `;

            if (publicComments.length > 0) {
                const commentMap = {};
                publicComments.forEach(d => { commentMap[d.id] = { ...d, children: [] }; });
                const tree = [];
                publicComments.forEach(d => {
                    const parentId = d.in_reply_to_id;
                    if (parentId && commentMap[parentId]) {
                        commentMap[parentId].children.push(commentMap[d.id]);
                    } else {
                        tree.push(commentMap[d.id]);
                    }
                });
                html += tree.map(item => renderComment(item, true)).join('');
                wrapper.innerHTML = html;
            } else {
                wrapper.innerHTML = html + "<p>Bisher noch keine öffentlichen Antworten vorhanden.</p>";
            }
        })
        .catch(err => {
            wrapper.innerHTML = ""; 
        });
});
</script>

 

Das war eigentlich schon alles, was an Vorbereitung gemacht werden muss.

Nun einfach einen neuen Beitrag in Publii schreiben, diesen auf seinem Publii-Blog veröffentlichen. Die URL des gerade geschriebenen Blog-Beitrags auf seiner Fediverse-Instanz (in meinem Fall Friendica) veröffentlichen. Nun brauchen wir von Friendica die "uri_id" dieses Beitrags! Diese lässt sich relativ einfach finden, indem man auf das Datum des Fediverse-Artikels klickt und dann den Quelltext dieser Seite ausliest. Bei mir ist diese z.B. immer in Zeile 200 beim Atom-Feed zu finden:

<link href="display/feed-item/8631941.atom" rel="alternate" type="application/atom+xml">

Die Zahl 8631941 ist hier die uri_id.



Nun ruft man in Publii seinen gerade eben geschriebenen Blog-Beitrag noch einmal auf und geht ganz rechts auf den Button "<HTML>". Im HTML-Quelltext fügt man ganz unten als letzte Zeile folgendes ein:

<div data-fedi-id="8631941"> </div>

Die Zahl 8631941 ist hier die uri_id des Blog-Beitrags aus Friendica, den wir zuvor ja geteilt hatten.


Nun in Publii den Beitrag speichern und dann erneut auf "Webseite synchronisieren" klicken.


Das war alles. Ab jetzt werden Likes, Shares und Kommentare aus dem Fediverse unter diesem Beitrag im Publii-Blog angezeigt. Natürlich kann man das auch nachträglich mit älteren Blog-Beiträgen genauso machen, man muss sich nur die alten Beiträge aus dem Fediverse raussuchen.




Das Ergebnis kann man sich gerne auf meinem Blog ansehen, ich habe es nicht bei allen Beiträgen nachträglich hinzugefügt, das war mir dann doch zuviel, aber bei diesen Beiträgen z.B. kann man das Ergebnis sehr schön sehen:


https://blogzwo.me/das-fediverse-keine-alternative-und-kein-nischenprodukt-sondern-die-bessere-wahl.html

https://blogzwo.me/was-die-sozialen-medien-mit-uns-machen.html

https://blogzwo.me/mein-blog-meine-spielwiese.html

https://blogzwo.me/ein-privates-archiv-fuer-friendica-posts-im-heimnetzwerk-erstellen-mit-wordpress.html

 

 

Wem die Anleitung nun zu ausführlich war, hier die Zusammenfassung:



Vorbereitung (einmalig):

  • fedi-proxy.php hochladen (Domain anpassen!)
  • Script in Publii (Werkzeuge & Plugins -> Benutzerdefiniertes HTML) einfügen


Jeder neue Beitrag:

  • In Publii schreiben und veröffentlichen
  • Blog-Link auf Friendica posten
  • Vom Friendica-Post die URI-ID holen (die Nummer aus dem Atom-Feed-Link)
  • In Publii den Artikel nochmal öffnen, im HTML-Modus <div data-fedi-id="ID_HIER"></div> ganz unten einfügen
  • In Publii auf Synchronisieren/Hochladen klicken

 

Gerne anpassen, an die eigenen Wünsche, Vorstellungen, Farben, Design und sonstiges!