yellow = $yellow; $this->yellow->system->setDefault("mastodonapiInstanceUrl", "https://example.social"); $this->yellow->system->setDefault("mastodonapiAccountId", ""); $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"); } // Webhook-Request public function onRequest($scheme, $address, $base, $location, $fileName) { $triggerPath = rtrim($this->yellow->system->get("mastodonapiTriggerPath"), "/"); $locationClean = rtrim($location, "/"); if ($locationClean === $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; } // CLI Command 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; } } // API-Logik ohne Access Token private function processMastodonFeed() { $instanceUrl = $this->yellow->system->get("mastodonapiInstanceUrl"); $accountId = $this->yellow->system->get("mastodonapiAccountId"); $hashtag = ltrim($this->yellow->system->get("mastodonapiHashtag"), '#'); $defaultAuthor = $this->yellow->system->get("mastodonapiAuthor"); $defaultTag = $this->yellow->system->get("mastodonapiTag"); $targetFolder = $this->yellow->system->get("mastodonapiFolder"); if (empty($instanceUrl) || empty($accountId)) { error_log("ERROR: MastodonAPI: Missing configuration values."); return false; } $apiUrl = rtrim($instanceUrl, "/") . "/api/v1/accounts/" . $accountId . "/statuses?limit=20"; // Abruf mit curl $ch = curl_init($apiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_USERAGENT, "YellowMastodonAPI/0.2-public"); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); $json = curl_exec($ch); if ($json === false) { error_log("ERROR: MastodonAPI: Curl error: " . curl_error($ch)); curl_close($ch); return false; } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { error_log("ERROR: MastodonAPI: HTTP status $httpCode from $apiUrl"); return false; } $posts = json_decode($json, true); if (!is_array($posts)) { error_log("ERROR: MastodonAPI: Invalid JSON returned by API."); return false; } $config = $this->getYellowConfig(); $postsCreated = 0; foreach ($posts as $post) { $contentHtml = $post['content']; $createdAt = $post['created_at']; $tags = array_map('strtolower', array_column($post['tags'], 'name')); if (!empty($hashtag) && !in_array(strtolower($hashtag), $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; 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/'); if ($filename) { $imageFilenames[] = $filename; $imageAltTexts[] = $media['description'] ?? ''; } } } $markdownContent = $this->generateMarkdown( $title, $titleSlug, $createdAt, $contentHtml, $authorName, $authorUrl, $defaultAuthor, $defaultTag, $imageFilenames, $imageAltTexts ); file_put_contents($fullPath, $markdownContent); $postsCreated++; } return $postsCreated; } private function downloadAndSaveImage($imageUrl, $dirPath) { 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); return $fileName; } private function generateTitle($content) { $text = 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); return trim(preg_replace('/[^a-z0-9-]+/', '', $slug), '-'); } private function generateMarkdown($title, $slug, $dateTime, $html, $origAuthor, $origUrl, $yellowAuthor, $yellowTag, $images = [], $alts = []) { $date = new DateTime($dateTime); $formattedDate = $date->format('Y-m-d'); $formattedTime = $date->format('H:i:s'); $content = strip_tags($html); $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8'); $content = trim(preg_replace('/\s+/', ' ', $content)); $imageMarkdown = ''; foreach ($images as $i => $filename) { $alt = isset($alts[$i]) ? $alts[$i] : ''; $imageMarkdown .= "\n![" . htmlspecialchars($alt, ENT_QUOTES) . "](" . $filename . ")\n"; } $featuredImage = ''; if (!empty($images)) { $featuredImage = "Image: " . $images[0] . "\n"; } $htmlSafeAuthorName = str_replace('@', '@', $origAuthor); return << "$content" > > — $htmlSafeAuthorName MD; } private function getYellowConfig() { return $this->yellow ? $this->yellow->system : null; } private function sendResponse($message, $statusCode = 200) { if (!headers_sent()) { header("HTTP/1.1 $statusCode"); header("Content-Type: text/html"); } echo "

" . htmlspecialchars($message) . "

"; exit; } }