diff --git a/mastodonapi.php b/mastodonapi.php
index 02471a5..82788fb 100644
--- a/mastodonapi.php
+++ b/mastodonapi.php
@@ -1,40 +1,40 @@
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,50 +227,60 @@ 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 '#' für die Ausgabe
+ $safeHashtags = array_map(function($tag) {
+ return str_replace('#', '#', $tag);
+ }, $tags[0]);
+ $hashtagString = implode(' ', $safeHashtags);
}
}
- $content = trim(preg_replace('/\s+/', ' ', $content));
+ $content = trim(preg_replace('/\s+/', ' ', $content));
- $imageMarkdown = '';
- foreach ($images as $i => $filename) {
- $alt = isset($alts[$i]) ? htmlspecialchars($alts[$i], ENT_QUOTES) : '';
- $imageMarkdown .= "\n
\n";
- }
-
- $featuredImage = '';
- $featuredImageAlt = '';
-
- if (!empty($images)) {
- $featuredImage = "Image: " . $images[0] . "\n";
- if (isset($alts[0]) && !empty($alts[0])) {
- $featuredImageAlt = "ImageAlt: " . htmlspecialchars($alts[0], ENT_QUOTES) . "\n";
+ $imageMarkdown = '';
+ foreach ($images as $i => $filename) {
+ $alt = isset($alts[$i]) ? htmlspecialchars($alts[$i], ENT_QUOTES) : '';
+ $imageMarkdown .= "\n
\n";
}
+
+ $featuredImage = '';
+ $featuredImageAlt = '';
+
+ if (!empty($images)) {
+ $featuredImage = "Image: " . $images[0] . "\n";
+ if (isset($alts[0]) && !empty($alts[0])) {
+ $featuredImageAlt = "ImageAlt: " . htmlspecialchars($alts[0], ENT_QUOTES) . "\n";
+ }
+ }
+
+ $htmlSafeAuthorName = str_replace('@', '@', $origAuthor);
+
+ // Baut den Blockquote-String robust und schrittweise zusammen
+ $blockquote = "> \"$content\"";
+ if (!empty($hashtagString)) {
+ $blockquote .= "\n>\n> " . $hashtagString;
+ }
+ $blockquote .= "\n>\n> — $htmlSafeAuthorName";
+
+ return << "$content"$hashtagString
->
-> — $htmlSafeAuthorName
-MD;
-}
-
private function getYellowConfig() {
return $this->yellow ? $this->yellow->system : null;
}