From 925f996db3513426d6fe539461da860fbd8a7c86 Mon Sep 17 00:00:00 2001 From: simonpipe Date: Wed, 7 Jan 2026 09:10:22 +0100 Subject: [PATCH] Update mastodonapi.php --- mastodonapi.php | 218 ++++++++++++++---------------------------------- 1 file changed, 64 insertions(+), 154 deletions(-) diff --git a/mastodonapi.php b/mastodonapi.php index 61c7027..7c5b756 100644 --- a/mastodonapi.php +++ b/mastodonapi.php @@ -16,20 +16,30 @@ class YellowMastodonapi { $this->yellow->system->setDefault("mastodonapiTag", "Fediverse"); $this->yellow->system->setDefault("mastodonapiFolder", "content/2-blog/"); $this->yellow->system->setDefault("mastodonapiLimit", "20"); - $this->yellow->system->setDefault("mastodonapiStatus", "public"); // public; draft = page is not visible, user needs to log in, requires draft extension; unlisted = page is not visible, but can be accessed with the correct link - $this->yellow->system->setDefault("mastodonapiMaxAge", "0"); // In Stunden. 0 = alle Toots. + $this->yellow->system->setDefault("mastodonapiStatus", "public"); + $this->yellow->system->setDefault("mastodonapiMaxAge", "0"); $this->yellow->system->setDefault("mastodonapiUpdateInterval", "30"); $this->yellow->system->setDefault("mastodonapiLastUpdate", "0"); } - // Reagiert auf 'php yellow.php mastodon' oder das System-Event 'daily' + // Reagiert auf System-Events und manuelle Aufrufe public function onUpdate($action) { - if ($action == "mastodon" || $action == "daily" || $action == "clean") { + if ($action == "mastodon" || $action == "daily" || $action == "install") { $this->updateMastodonPosts(); } } - // Prüft bei Seitenaufrufen, ob das Intervall abgelaufen ist + // Ermöglicht 'php yellow.php mastodonapi' im Terminal + public function onCommand($command, $text) { + if ($command == "mastodonapi") { + // Wir leiten den Befehl einfach an onUpdate weiter + $this->onUpdate("mastodon"); + return 0; // Erfolg + } + return 0; + } + + // Der "Lazy Cron" für Nutzer ohne echten Cronjob public function onTick() { $interval = intval($this->yellow->system->get("mastodonapiUpdateInterval")) * 60; $lastUpdate = intval($this->yellow->system->get("mastodonapiLastUpdate")); @@ -40,26 +50,11 @@ class YellowMastodonapi { } public function updateMastodonPosts() { - $instance = $this->yellow->system->get("mastodonapiInstance"); - $userId = $this->yellow->system->get("mastodonapiUserId"); + $success = $this->processMastodonFeed(); - // Dein bestehender Code zum Abrufen der Mastodon-Daten... - // Beispiel: $response = $this->yellow->toolbox->getHttpClient()->getContent($url); - - // Am Ende den Zeitstempel speichern, damit der nächste Tick bescheid weiß - $this->yellow->system->set("mastodonapiLastUpdate", time()); - $this->yellow->system->save(); - } - - public function onCommand($command, $text) { - if (!$this->yellow) return 500; - switch ($command) { - case "mastodonapi": - if (!empty($text)) return 400; - $postsCreated = $this->processMastodonFeed(); - return ($postsCreated !== false) ? 0 : 500; - default: - return 0; + if ($success !== false) { + $this->yellow->system->set("mastodonapiLastUpdate", time()); + $this->yellow->system->save(); } } @@ -75,16 +70,11 @@ class YellowMastodonapi { $maxAgeHours = intval($this->yellow->system->get("mastodonapiMaxAge")); $limit = intval($this->yellow->system->get("mastodonapiLimit")); - if (empty($instanceUrl) || empty($accountId)) { - return false; - } + if (empty($instanceUrl) || empty($accountId)) return false; $apiUrl = rtrim($instanceUrl, "/") . "/api/v1/accounts/" . $accountId . "/statuses?limit=" . $limit; - $headers = ["User-Agent: YellowMastodonAPI"]; - if (!empty($accessToken)) { - $headers[] = "Authorization: Bearer " . $accessToken; - } + if (!empty($accessToken)) $headers[] = "Authorization: Bearer " . $accessToken; $ch = curl_init($apiUrl); curl_setopt_array($ch, [ @@ -99,18 +89,12 @@ class YellowMastodonapi { $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); - if ($json === false || $httpCode < 200 || $httpCode >= 300) { - return false; - } + if ($json === false || $httpCode < 200 || $httpCode >= 300) return false; $posts = json_decode($json, true); - if (!is_array($posts)) { - return false; - } + if (!is_array($posts)) return false; - $config = $this->getYellowConfig(); $postsCreated = 0; - foreach ($posts as $post) { if (!empty($post['reblog']) || (!empty($post['reblogged']) && $post['reblogged'] === true)) continue; if (!is_null($post['in_reply_to_id'])) continue; @@ -118,34 +102,25 @@ class YellowMastodonapi { $contentHtml = $post['content'] ?? ''; $createdAt = $post['created_at'] ?? ''; if ($maxAgeHours > 0) { - $postTimestamp = strtotime($createdAt); - $threshold = time() - ($maxAgeHours * 3600); - if ($postTimestamp < $threshold) continue; - } - $tags = array_column($post['tags'] ?? [], 'name'); - - if (!empty($hashtag) && !in_array(strtolower($hashtag), array_map('strtolower', $tags))) { - continue; + if (strtotime($createdAt) < (time() - ($maxAgeHours * 3600))) continue; } + + $tags = array_column($post['tags'] ?? [], 'name'); + if (!empty($hashtag) && !in_array(strtolower($hashtag), array_map('strtolower', $tags))) continue; $title = $this->generateTitle($contentHtml); $titleSlug = $this->generateTitleSlug($title); - $datePrefix = date('Y-m-d', strtotime($createdAt)); - $fileName = $datePrefix . '-' . $titleSlug . '.md'; - $fullPath = $config->get("coreServerDocument") . $targetFolder . $fileName; + $fileName = date('Y-m-d', strtotime($createdAt)) . '-' . $titleSlug . '.md'; + $fullPath = $this->yellow->system->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'] ?: "#"; - $imageFilenames = []; $imageAltTexts = []; - foreach ($post['media_attachments'] ?? [] as $media) { if (($media['type'] ?? '') === 'image') { - $filename = $this->downloadAndSaveImage($media, $config->get("coreServerDocument") . 'media/images/'); + $filename = $this->downloadAndSaveImage($media, $this->yellow->system->get("coreServerDocument") . 'media/images/'); if ($filename) { $imageFilenames[] = $filename; $imageAltTexts[] = $media['description'] ?? ''; @@ -154,37 +129,21 @@ class YellowMastodonapi { } $markdownContent = $this->generateMarkdown( - $title, - $titleSlug, - $createdAt, - $contentHtml, - $authorName, - $authorUrl, - $defaultAuthor, - $defaultTag, - $imageFilenames, - $imageAltTexts, - $status + $title, $titleSlug, $createdAt, $contentHtml, + $post['account']['display_name'] ?: $defaultAuthor, + $post['account']['url'] ?: "#", + $defaultAuthor, $defaultTag, $imageFilenames, $imageAltTexts, $status ); file_put_contents($fullPath, $markdownContent); $postsCreated++; } - return $postsCreated; } 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)) { - return false; - } - + $imageUrl = $media['remote_url'] ?? $media['url'] ?? $media['preview_url'] ?? null; + if (empty($imageUrl)) return false; if (!is_dir($dirPath)) mkdir($dirPath, 0755, true); $fileName = basename(parse_url($imageUrl, PHP_URL_PATH)); @@ -192,117 +151,68 @@ class YellowMastodonapi { if (file_exists($fullPath)) return $fileName; $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_USERAGENT => "YellowMastodonAPI", CURLOPT_SSL_VERIFYPEER => true, - CURLOPT_SSL_VERIFYHOST => 2, ]); $imgData = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); - - if ($imgData === false || $httpCode < 200 || $httpCode >= 300) { - return false; - } - + + if (!$imgData) return false; file_put_contents($fullPath, $imgData); return $fileName; } private function generateTitle($content) { - $text = strip_tags($content); + $text = trim(strip_tags($content)); $text = preg_replace('/https?:\/\/[^\s]+|#\w+/u', '', $text); - $text = preg_replace('/[^\p{L}\p{N}\s-]/u', '', $text); - $text = trim(preg_replace('/\s+/', ' ', $text)); $words = explode(' ', $text); return trim(mb_substr(implode(' ', array_slice($words, 0, 7)), 0, 100, 'UTF-8')) ?: "Mastodon Post"; } private function generateTitleSlug($title) { $slug = mb_strtolower($title, 'UTF-8'); - $slug = str_replace(['ä','ö','ü','Ä','Ö','Ü','ß',' '], ['ae','oe','ue','ae','oe','ue','ss','-'], $slug); + $slug = str_replace(['ä','ö','ü','ß',' '], ['ae','oe','ue','ss','-'], $slug); return trim(preg_replace('/[^a-z0-9-]+/', '', $slug), '-'); } - private function generateMarkdown($title, $slug, $dateTime, $html, $origAuthor, $origUrl, $yellowAuthor, $yellowTag, $images = [], $alts = [], $status = "public") { - $date = new DateTime($dateTime); - $formattedDate = $date->format('Y-m-d'); - $formattedTime = $date->format('H:i:s'); - - // 1. Schritt: HTML-Umbrüche in Newlines umwandeln - $content = str_replace(["
", "
", "
", "

"], ["\n", "\n", "\n", "

\n"], $html); - - // 2. Schritt: Tags entfernen - $content = strip_tags($content); - - // 3. Schritt: HTML-Entities dekodieren - $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8'); - - $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)) { - $safeHashtags = array_map(function($tag) { - return str_replace('#', '#', $tag); - }, $tags[0]); - $hashtagString = implode(' ', $safeHashtags); + private function generateMarkdown($title, $slug, $dateTime, $html, $origAuthor, $origUrl, $yellowAuthor, $yellowTag, $images, $alts, $status) { + $date = new DateTime($dateTime); + $pub = $date->format('Y-m-d H:i:s'); + $content = trim(html_entity_decode(strip_tags(str_replace(["
", "

"], ["\n", "

\n"], $html)), ENT_QUOTES | ENT_HTML5, 'UTF-8')); + $content = str_replace("\n", "\n> ", $content); + + $imageMarkdown = ''; + $featuredImage = $featuredImageAlt = ''; + foreach ($images as $i => $filename) { + $alt = isset($alts[$i]) ? htmlspecialchars($alts[$i], ENT_QUOTES) : ''; + $imageMarkdown .= "\n\"$alt\"/\n"; + if ($i === 0) { + $featuredImage = "Image: $filename\n"; + $featuredImageAlt = "ImageAlt: $alt\n"; + } } - } - // WICHTIG: Nur mehrfache LEERZEICHEN kürzen, aber NEWLINES (\n) behalten! - $content = trim(preg_replace('/[ \t]+/', ' ', $content)); + $authorSafe = str_replace('@', '@', $origAuthor); - // Jetzt die Zeilenumbrüche für das Zitat-Layout vorbereiten - $content = str_replace("\n", "\n> ", $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"; - } - } - - $htmlSafeAuthorName = str_replace('@', '@', $origAuthor); - - // Blockquote final zusammenbauen - $blockquote = "> $content"; - if (!empty($hashtagString)) { - $blockquote .= "\n>\n> " . $hashtagString; - } - $blockquote .= "\n>\n> — $htmlSafeAuthorName"; - - return << $content +> +> — $authorSafe MD; } - - private function getYellowConfig() { - return $this->yellow ? $this->yellow->system : null; - } } \ No newline at end of file