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
\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