From a78a5de09e1e9a1cca3ebea7db49e3e3f15da4fa Mon Sep 17 00:00:00 2001 From: simonpipe Date: Sat, 9 Aug 2025 16:46:18 +0200 Subject: [PATCH] mastodonapi.php aktualisiert --- mastodonapi.php | 372 ------------------------------------------------ 1 file changed, 372 deletions(-) diff --git a/mastodonapi.php b/mastodonapi.php index 77358f0..9a8c881 100644 --- a/mastodonapi.php +++ b/mastodonapi.php @@ -18,376 +18,4 @@ class YellowMastodonAPI { $this->yellow->system->setDefault("mastodonapiFolder", "content/1-blog/"); $this->yellow->system->setDefault("mastodonapiTriggerPath", "/mastodonapi-webhook-CHANGE_THIS_SECRET_KEY"); } - - // Handle web requests (NUR für den Webhook-Trigger) - public function onRequest($scheme, $address, $base, $location, $fileName) { - $triggerPath = $this->yellow->system->get("mastodonapiTriggerPath"); // Angepasster Konfigurationsname - 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); - } - return 1; - } - return 0; - } - - // Handle command (CLI) - public function onCommand($command, $text) { - if (!$this->yellow) { - return 500; - } - switch ($command) { - case "mastodonapi": // Angepasster Kommando-Name - if (!empty($text)) { - return 400; - } - $postsCreated = $this->processMastodonFeed(); - if ($postsCreated !== false) { - return 0; - } else { - return 500; - } - case "clean": - $this->processCommandClean($command, $text); - return 0; - default: - return 0; - } - } - - // --- Core logic for fetching and processing Mastodon API feed --- - private function processMastodonFeed() { - $instanceUrl = $this->yellow->system->get("mastodonapiInstanceUrl"); - $userId = $this->yellow->system->get("mastodonapiUserId"); - $accessToken = $this->yellow->system->get("mastodonapiAccessToken"); - $hashtag = $this->yellow->system->get("mastodonapiHashtag"); - $defaultAuthor = $this->yellow->system->get("mastodonapiAuthor"); - $defaultTag = $this->yellow->system->get("mastodonapiTag"); - $targetFolder = $this->yellow->system->get("mastodonapiFolder"); - $postsCreated = 0; - - if (empty($instanceUrl) || empty($userId)) { - $this->yellow->log("error", "MastodonAPI: 'mastodonapiInstanceUrl' or 'mastodonapiUserId' is not configured."); - return false; - } - - // API Endpunkt für die Statusse eines Benutzers - $apiUrl = rtrim($instanceUrl, '/') . "/api/v1/accounts/" . $userId . "/statuses?exclude_replies=true&exclude_reblogs=true&limit=40"; - - $options = [ - 'http' => [ - 'follow_location' => 1, - 'max_redirects' => 10, - 'timeout' => 30, - 'method' => "GET", - 'header' => "User-Agent: Mozilla/5.0 (YellowCMS MastodonAPI Plugin)\r\n" - ], - 'ssl' => [ - 'verify_peer' => true, - 'verify_peer_name' => true, - ], - ]; - - // Wenn ein Access Token vorhanden ist, füge es zum Header hinzu - if (!empty($accessToken)) { - $options['http']['header'] .= "Authorization: Bearer " . $accessToken . "\r\n"; - } - - $context = stream_context_create($options); - - $apiResponse = @file_get_contents($apiUrl, false, $context); - if ($apiResponse === false) { - $this->yellow->log("error", "MastodonAPI: Failed to fetch data from API. Check URL or network issues. URL: " . $apiUrl); - return false; - } - - $mastodonData = json_decode($apiResponse, true); - if (json_last_error() !== JSON_ERROR_NONE) { - $this->yellow->log("error", "MastodonAPI: Failed to parse JSON response: " . json_last_error_msg()); - return false; - } - - if (!is_array($mastodonData)) { - $this->yellow->log("error", "MastodonAPI: API response is not an array of statuses."); - return false; - } - - $config = $this->getYellowConfig(); - $yellowBaseDir = $config->get("coreServerDocument"); - $mediaImagesDir = $yellowBaseDir . 'media/images/'; - - if (!is_dir($mediaImagesDir)) { - if (!mkdir($mediaImagesDir, 0755, true)) { - $this->yellow->log("error", "MastodonAPI: Could not create media images directory: " . $mediaImagesDir); - } - } - - foreach ($mastodonData as $item) { - $contentHtml = $item['content']; - $currentPubDate = $item['created_at']; - $itemUrl = $item['url']; - $imageFilenamesForMarkdown = []; - - // Filtern nach Hashtag (wenn konfiguriert) - $hasRelevantHashtag = empty($hashtag) || - (isset($item['tags']) && is_array($item['tags']) && in_array(['name' => ltrim($hashtag, '#')], array_map(function($t){ return ['name' => $t['name']]; }, $item['tags']))) || // Prüft, ob der Hashtag in den Tag-Objekten ist - (strpos($contentHtml, $hashtag) !== false); - - if (!$hasRelevantHashtag) { - continue; - } - - $title = $this->generateTitle($contentHtml); - $titleSlug = $this->generateTitleSlug($title); - - // Eindeutigen Dateinamen mit Toot-ID - $fileName = date('Y-m-d', strtotime($currentPubDate)) . '-' . $item['id'] . '-' . $titleSlug . '.md'; - $fullPathToMarkdownFile = $config->get("coreServerDocument") . $targetFolder . $fileName; - - if (file_exists($fullPathToMarkdownFile)) { - continue; - } - - $targetPostDir = dirname($fullPathToMarkdownFile); - if (!is_dir($targetPostDir)) { - if (!mkdir($targetPostDir, 0755, true)) { - $this->yellow->log("error", "MastodonAPI: Could not create post directory: " . $targetPostDir); - continue; - } - } - - $authorName = $item['account']['display_name'] ?: $item['account']['username']; - $authorProfileUrl = $item['account']['url']; - - if (isset($item['media_attachments']) && is_array($item['media_attachments'])) { - foreach ($item['media_attachments'] as $media) { - if ($media['type'] === 'image' || $media['type'] === 'gifv') { - $mediaUrl = $media['url']; - $mediaAltText = $media['description'] ?? ''; - $imageFilename = $this->downloadAndSaveImage($mediaUrl, $mediaImagesDir, $media['mime_type'], $mediaAltText); - if ($imageFilename) { - $imageFilenamesForMarkdown[] = ['filename' => $imageFilename, 'alt' => $mediaAltText]; - } - } - } - } - - $markdownContent = $this->generateMarkdown( - $title, - $titleSlug, - $currentPubDate, - $contentHtml, - $authorName, - $authorProfileUrl, - $defaultAuthor, - $defaultTag, - $imageFilenamesForMarkdown, - $itemUrl // Den ursprünglichen Toot-Link übergeben - ); - - if (file_put_contents($fullPathToMarkdownFile, $markdownContent) === false) { - $this->yellow->log("error", "MastodonAPI: Could not write Markdown file: " . $fullPathToMarkdownFile); - } else { - $postsCreated++; - $this->yellow->log("info", "MastodonAPI: Created post: " . $fileName); - } - } - return $postsCreated; - } - - public function processCommandClean($command, $text) { - // Implementiere hier die Logik zum Aufräumen, falls benötigt - return 0; - } - - private function downloadAndSaveImage($imageUrl, $globalImageDirectoryPath, $mediaType = '', $altText = '') { - $fileName = basename(parse_url($imageUrl, PHP_URL_PATH)); - $fileExtension = pathinfo($fileName, PATHINFO_EXTENSION); - - if (empty($fileExtension) && !empty($mediaType)) { - $typeParts = explode('/', $mediaType); - if (count($typeParts) > 1) { - $fileExtension = $typeParts[1]; - } - } - - if (empty($fileExtension)) { - $fileExtension = 'jpeg'; - } - - $counter = 0; - $uniqueFileName = pathinfo($fileName, PATHINFO_FILENAME) . '.' . $fileExtension; - while (file_exists($globalImageDirectoryPath . $uniqueFileName)) { - $counter++; - $uniqueFileName = pathinfo($fileName, PATHINFO_FILENAME) . '-' . $counter . '.' . $fileExtension; - if ($counter > 100) { - $this->yellow->log("error", "MastodonAPI: Could not find unique filename for image: " . $fileName); - return false; - } - } - - $finalFileName = $uniqueFileName; - $fullPathToSavedImage = $globalImageDirectoryPath . $finalFileName; - - if (file_exists($fullPathToSavedImage)) { - return $finalFileName; - } - - $context = stream_context_create([ - 'http' => [ - 'follow_location' => 1, - 'max_redirects' => 10, - 'timeout' => 30, - 'method' => "GET", - 'header' => "User-Agent: Mozilla/5.0 (YellowCMS MastodonAPI Plugin)\r\n" . - "Referer: " . $this->yellow->system->get("mastodonapiInstanceUrl") . "\r\n" - ], - 'ssl' => [ - 'verify_peer' => true, - 'verify_peer_name' => true, - ], - ]); - - $imageData = @file_get_contents($imageUrl, false, $context); - if ($imageData === false) { - $this->yellow->log("error", "MastodonAPI: Failed to download image from URL: " . $imageUrl); - return false; - } - - if (file_put_contents($fullPathToSavedImage, $imageData) === false) { - $this->yellow->log("error", "MastodonAPI: Failed to save image to: " . $fullPathToSavedImage); - return false; - } - - return $finalFileName; - } - - private function generateTitle($content) { - // Da content jetzt HTML ist, strippen wir HTML, bevor wir den Titel generieren - $cleanHtmlContent = strip_tags($content); - $cleanContent = preg_replace('/https?:\/\/[^\s]+|#\w+/u', '', $cleanHtmlContent); // URLs und Hashtags entfernen - $cleanContent = preg_replace('/[^\p{L}\p{N}\s-]/u', '', $cleanContent); // Sonderzeichen entfernen - $cleanContent = trim(preg_replace('/\s+/', ' ', $cleanContent)); // Mehrere Leerzeichen durch eines ersetzen - - $words = explode(' ', $cleanContent); - $firstWords = array_slice($words, 0, 7); // Die ersten 7 Wörter als Titel - $title = implode(' ', $firstWords); - - if (empty($title)) { - $title = "Mastodon Post"; - } - - $finalTitle = trim(mb_substr($title, 0, 100, 'UTF-8')); - return $finalTitle; - } - - private function generateTitleSlug($title) { - $slug = mb_strtolower($title, 'UTF-8'); - $slug = str_replace(['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', 'ß', ' '], ['ae', 'oe', 'ue', 'ae', 'oe', 'ue', 'ss', '-'], $slug); - $slug = preg_replace('/[^a-z0-9-]+/', '', $slug); - $slug = preg_replace('/-+/', '-', $slug); - $slug = trim($slug, '-'); - return $slug; - } - - private function generateMarkdown($title, $titleSlug, $publishedDate, $content, $originalAuthorName, $originalAuthorProfileUrl, $yellowAuthor, $yellowTag, $imageFilenames = [], $itemUrl = '') { - $date = new DateTime($publishedDate); - $formattedDate = $date->format('Y-m-d'); - $formattedTime = $date->format('H:i:s'); - - $processedContent = $content; - - // Ersetze Links (-Tags) im HTML-Inhalt, damit sie als Markdown-Links erscheinen. - // Konvertiert TEXT zu [TEXT](URL) - $processedContent = preg_replace_callback('/]*?href="([^"]*?)".*?>(.*?)<\/a>/isu', function($matches) { - $url = $matches[1]; - $text = strip_tags($matches[2]); // Nur den Text des Links nehmen - - // Wenn der Linktext dem Link selbst entspricht, nur den Link anzeigen (kürzer) - if (trim($text) === trim($url) || trim($text) === 'Show more') { // "Show more" von Mastodon ignorieren - return '[' . $url . ']'; - } - return '[' . $text . '](' . $url . ')'; - }, $processedContent); - - // Entferne Mastodon-spezifische HTML-Hashtag-Links aus dem Content, da sie nicht als Tags - // im Yellow Markdown erscheinen sollen und bereits über die Hashtag-Filterung verarbeitet wurden. - // Diese Regex zielt auf Links ab, die /tags/ im href haben. - $processedContent = preg_replace('/]*?href="[^"]*?\/tags\/[^"]*?".*?>#.*?<\/a>/iu', '', $processedContent); - - // Entfernt verbleibende HTML-Tags (z.B.

, etc.) - $processedContent = strip_tags($processedContent); - $processedContent = html_entity_decode($processedContent, ENT_QUOTES | ENT_HTML5, 'UTF-8'); - $processedContent = trim(preg_replace('/\s+/', ' ', $processedContent)); // Mehrere Leerzeichen durch eines ersetzen - - $imageMarkdown = ''; - foreach ($imageFilenames as $imageData) { - $imageMarkdown .= "\n[image " . $imageData['filename']; - if (!empty($imageData['alt'])) { - $imageMarkdown .= ' alt="' . htmlspecialchars($imageData['alt'], ENT_QUOTES | ENT_HTML5, 'UTF-8') . '"'; - } - $imageMarkdown .= "]\n"; - } - - $featuredImageFrontMatter = ''; - if (!empty($imageFilenames)) { - $featuredImageFrontMatter = "Image: " . $imageFilenames[0]['filename'] . "\n"; - } - - // Ersetze @ mit @ im Autorennamen für die HTML-Ausgabe - $htmlSafeAuthorName = str_replace('@', '@', $originalAuthorName); - - $sourceLinkHtml = ''; - if (!empty($itemUrl)) { - $sourceLinkHtml = "
Originalbeitrag: $itemUrl"; - } - - return << "$processedContent" -> -> — $htmlSafeAuthorName -$sourceLinkHtml - -MARKDOWN; - } - - private function getYellowConfig() { - if ($this->yellow) { - return $this->yellow->system; - } - return null; - } - - private function sendResponse($message, $statusCode = 200) { - $htmlContent = "Umleitung...

" . htmlspecialchars($message) . "

Sie werden in 3 Sekunden weitergeleitet. Falls nicht, klicken Sie hier.

"; - if (!headers_sent()) { - header("HTTP/1.1 " . $statusCode . " " . $this->getHttpStatusMessage($statusCode)); - header("Content-Type: text/html"); - } - echo $htmlContent; - exit; - } - - private function getHttpStatusMessage($statusCode) { - $httpStatus = array( - 100 => 'Continue', 101 => 'Switching Protocols', - 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', - 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', - 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Payload Too Large', 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', 426 => 'Upgrade Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', - 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported' - ); - return ($httpStatus[$statusCode]) ? $httpStatus[$statusCode] : 'Unknown Status'; - } } \ No newline at end of file