mastodonapi.php aktualisiert

This commit is contained in:
simonpipe 2025-08-13 16:45:08 +02:00
parent 1db48837f6
commit 0fe5607616

View file

@ -1,40 +1,40 @@
<?php
// MastodonAPI extension, https://codeberg.org/simonpipe/yellow-mastodonapi
// MastodonAPI & GoToSocial extension
// Kompatibel mit Mastodon-Servern & GoToSocial-Instanzen
// https://codeberg.org/simonpipe/yellow-mastodonapi (angepasst)
class YellowMastodonAPI {
const VERSION = "0.4";
const VERSION = "0.6";
public $yellow;
// Initialisierung
public function onLoad($yellow) {
$this->yellow = $yellow;
$this->yellow->system->setDefault("mastodonapiInstanceUrl", "https://example.social");
$this->yellow->system->setDefault("mastodonapiAccountId", "");
$this->yellow->system->setDefault("mastodonapiAccessToken", ""); // optional
$this->yellow->system->setDefault("mastodonapiAccessToken", "");
$this->yellow->system->setDefault("mastodonapiHashtag", "");
$this->yellow->system->setDefault("mastodonapiAuthor", "YellowUser");
$this->yellow->system->setDefault("mastodonapiTag", "Fediverse");
$this->yellow->system->setDefault("mastodonapiFolder", "content/1-blog/");
$this->yellow->system->setDefault("mastodonapiTriggerPath", "/mastodonapi-webhook-CHANGE_THIS_SECRET_KEY");
$this->yellow->system->setDefault("mastodonapiLimit", "20"); // neu: Anzahl Posts
$this->yellow->system->setDefault("mastodonapiLimit", "20");
}
// Webhook-Request
public function onRequest($scheme, $address, $base, $location, $fileName) {
$triggerPath = $this->yellow->system->get("mastodonapiTriggerPath");
if ($location == $triggerPath) {
$postsCreated = $this->processMastodonFeed();
if ($postsCreated !== false) {
$this->sendResponse("Mastodon API feed processed. Created " . $postsCreated . " posts.", 200);
} else {
$this->sendResponse("Error processing Mastodon API feed. Check logs for details.", 500);
}
$this->sendResponse(
$postsCreated !== false
? "Mastodon/GoToSocial API feed processed. Created $postsCreated posts."
: "Error processing Mastodon/GoToSocial API feed. Check logs for details.",
$postsCreated !== false ? 200 : 500
);
return 1;
}
return 0;
}
// CLI Command
public function onCommand($command, $text) {
if (!$this->yellow) return 500;
switch ($command) {
@ -47,7 +47,6 @@ class YellowMastodonAPI {
}
}
// API-Logik
private function processMastodonFeed() {
$instanceUrl = $this->yellow->system->get("mastodonapiInstanceUrl");
$accountId = $this->yellow->system->get("mastodonapiAccountId");
@ -65,38 +64,28 @@ class YellowMastodonAPI {
$apiUrl = rtrim($instanceUrl, "/") . "/api/v1/accounts/" . $accountId . "/statuses?limit=" . $limit;
$headers = [
"User-Agent: YellowMastodonAPI"
];
$headers = ["User-Agent: YellowMastodonAPI"];
if (!empty($accessToken)) {
$headers[] = "Authorization: Bearer " . $accessToken;
}
$context = stream_context_create([
'http' => [
'method' => "GET",
'header' => implode("\r\n", $headers) . "\r\n",
'timeout' => 30
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true
]
// Daten via cURL abrufen (funktioniert bei Mastodon & GoToSocial)
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2
]);
$json = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
$json = @file_get_contents($apiUrl, false, $context);
// Debug-Ausgabe
if (isset($http_response_header)) {
error_log("MastodonAPI DEBUG: HTTP Response Headers:");
foreach ($http_response_header as $header) {
error_log(" " . $header);
}
}
error_log("MastodonAPI DEBUG: Raw API Response: " . substr($json, 0, 500));
if ($json === false) {
error_log("ERROR: MastodonAPI: Could not fetch API data from $apiUrl");
if ($json === false || $httpCode < 200 || $httpCode >= 300) {
error_log("ERROR: Could not fetch API data from $apiUrl (HTTP $httpCode, Error: $curlError)");
return false;
}
@ -110,18 +99,13 @@ class YellowMastodonAPI {
$postsCreated = 0;
foreach ($posts as $post) {
// Keine Boosts / Antworten
if (!empty($post['reblog']) || (!empty($post['reblogged']) && $post['reblogged'] === true)) continue;
if (!is_null($post['in_reply_to_id'])) continue;
// Lokaler Filter: keine Boosts, keine Antworten
if (!empty($post['reblog']) || (!empty($post['reblogged']) && $post['reblogged'] === true)) {
continue;
}
if (!is_null($post['in_reply_to_id'])) {
continue;
}
$contentHtml = $post['content'];
$createdAt = $post['created_at'];
$tags = array_column($post['tags'], 'name');
$contentHtml = $post['content'] ?? '';
$createdAt = $post['created_at'] ?? '';
$tags = array_column($post['tags'] ?? [], 'name');
if (!empty($hashtag) && !in_array(strtolower($hashtag), array_map('strtolower', $tags))) {
continue;
@ -134,18 +118,17 @@ class YellowMastodonAPI {
$fullPath = $config->get("coreServerDocument") . $targetFolder . $fileName;
if (file_exists($fullPath)) continue;
if (!is_dir(dirname($fullPath))) mkdir(dirname($fullPath), 0755, true);
$authorName = $post['account']['display_name'] ?: $defaultAuthor;
$authorUrl = $post['account']['url'] ?: "#";
// Bilder + Alt-Text
$imageFilenames = [];
$imageAltTexts = [];
foreach ($post['media_attachments'] as $media) {
if ($media['type'] === 'image' && !empty($media['url'])) {
$filename = $this->downloadAndSaveImage($media['url'], $config->get("coreServerDocument") . 'media/images/');
foreach ($post['media_attachments'] ?? [] as $media) {
if (($media['type'] ?? '') === 'image') {
$filename = $this->downloadAndSaveImage($media, $config->get("coreServerDocument") . 'media/images/');
if ($filename) {
$imageFilenames[] = $filename;
$imageAltTexts[] = $media['description'] ?? '';
@ -173,17 +156,54 @@ class YellowMastodonAPI {
return $postsCreated;
}
private function downloadAndSaveImage($imageUrl, $dirPath) {
private function downloadAndSaveImage($media, $dirPath) {
$imageUrl = $media['remote_url']
?? $media['url']
?? $media['preview_url']
?? ($media['meta']['original']['url'] ?? null)
?? ($media['meta']['small']['url'] ?? null);
if (empty($imageUrl)) {
error_log("ERROR: No usable image URL found in media attachment: " . json_encode($media));
return false;
}
if (!is_dir($dirPath)) mkdir($dirPath, 0755, true);
$fileName = basename(parse_url($imageUrl, PHP_URL_PATH));
$fullPath = $dirPath . $fileName;
if (file_exists($fullPath)) return $fileName;
$img = @file_get_contents($imageUrl);
if ($img === false) return false;
file_put_contents($fullPath, $img);
$ch = curl_init($imageUrl);
$headers = ["User-Agent: YellowMastodonAPI"];
$accessToken = $this->yellow->system->get("mastodonapiAccessToken");
if (!empty($accessToken)) {
$headers[] = "Authorization: Bearer " . $accessToken;
}
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$imgData = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($imgData === false || $httpCode < 200 || $httpCode >= 300) {
error_log("ERROR: Could not download image from $imageUrl (HTTP $httpCode, Error: $curlError)");
return false;
}
file_put_contents($fullPath, $imgData);
return $fileName;
}
// Rest der Helper-Methoden wie bei dir (generateTitle, generateTitleSlug, generateMarkdown, getYellowConfig, sendResponse) ...
private function generateTitle($content) {
$text = strip_tags($content);
$text = preg_replace('/https?:\/\/[^\s]+|#\w+/u', '', $text);
@ -207,11 +227,16 @@ class YellowMastodonAPI {
$content = strip_tags($html);
$content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Hashtag-Block extrahieren
$hashtagString = '';
if (preg_match('/(\s*(#\w+))+$/u', $content, $matches)) {
$content = substr($content, 0, -strlen($matches[0]));
if (preg_match_all('/#(\w+)/u', $matches[0], $tags)) {
$hashtagString = "\n> " . implode(' ', $tags[0]);
// Ersetzt '#' durch '&num;' für die Ausgabe
$safeHashtags = array_map(function($tag) {
return str_replace('#', '&num;', $tag);
}, $tags[0]);
$hashtagString = implode(' ', $safeHashtags);
}
}
@ -235,6 +260,13 @@ class YellowMastodonAPI {
$htmlSafeAuthorName = str_replace('@', '&#64;', $origAuthor);
// Baut den Blockquote-String robust und schrittweise zusammen
$blockquote = "> \"$content\"";
if (!empty($hashtagString)) {
$blockquote .= "\n>\n> " . $hashtagString;
}
$blockquote .= "\n>\n> — <a target=\"_blank\" href=\"$origUrl\">$htmlSafeAuthorName</a>";
return <<<MD
---
Title: $title
@ -245,9 +277,7 @@ Layout: blog
Tag: $yellowTag
$featuredImage$featuredImageAlt---
$imageMarkdown
> "$content"$hashtagString
>
> <a target="_blank" href="$origUrl">$htmlSafeAuthorName</a>
$blockquote
MD;
}