Update mastodonapi.php

This commit is contained in:
simonpipe 2026-01-07 15:31:26 +01:00
parent ad39532601
commit db6a20325f

View file

@ -1,11 +1,9 @@
<?php <?php
// Public Domain // Mastodonapi extension, https://codeberg.org/simonpipe/yellow-mastodonapi
// MastodonAPI extension, https://codeberg.org/simonpipe/yellow-mastodonapi class YellowMastodonapi extends YellowExtension {
const VERSION = "0.9";
class YellowMastodonapi {
const VERSION = "0.3.3";
public $yellow;
// Initialize extension
public function onLoad($yellow) { public function onLoad($yellow) {
$this->yellow = $yellow; $this->yellow = $yellow;
$this->yellow->system->setDefault("mastodonapiInstanceUrl", "https://example.social"); $this->yellow->system->setDefault("mastodonapiInstanceUrl", "https://example.social");
@ -21,211 +19,190 @@ class YellowMastodonapi {
$this->yellow->system->setDefault("mastodonapiUpdateInterval", "30"); $this->yellow->system->setDefault("mastodonapiUpdateInterval", "30");
} }
// Reagiert auf System-Events und manuelle Aufrufe // Handle update
public function onUpdate($action) { public function onUpdate($action) {
if ($action == "mastodon" || $action == "daily" || $action == "install") { if ( $action=="mastodon" || $action=="daily" || $action=="install" ) {
$this->updateMastodonPosts(); $this->updateMastodonPosts();
} }
} }
// Ermöglicht 'php yellow.php mastodonapi' im Terminal // Handle command
public function onCommand($command, $text) { public function onCommand($command, $text) {
if ($command == "mastodonapi") { if ( $command=="mastodonapi" ) {
echo "Mastodon Import gestartet...\n"; echo "Mastodon Import gestartet...\n";
$count = $this->updateMastodonPosts(); $count = $this->updateMastodonPosts();
if ( $count===false ) {
if ($count === false) {
echo "Fehler: API nicht erreichbar oder falsche ID.\n"; echo "Fehler: API nicht erreichbar oder falsche ID.\n";
return 500; return 500;
} }
echo "Erfolg: " . $count . " neue Posts verarbeitet.\n";
echo "Erfolg: $count neue Posts verarbeitet.\n";
return true; return true;
} }
return 0; return 0;
} }
// Hilfsfunktion für den Pfad zur Status-Datei // Handle periodic update
private function getStatusFile() { public function onTick() {
return $this->yellow->system->get("coreWorkerDirectory")."mastodonapi.status"; $interval = intval($this->yellow->system->get("mastodonapiUpdateInterval")) * 60;
$statusFile = $this->getStatusFile();
$lastUpdate = file_exists($statusFile) ? intval(file_get_contents($statusFile)) : 0;
if ( time() - $lastUpdate >= $interval ) {
$this->updateMastodonPosts();
}
} }
// Die einzige Version dieser Funktion behalten: // Update Mastodon posts
public function updateMastodonPosts() { public function updateMastodonPosts() {
$postsCreated = $this->processMastodonFeed(); $postsCreated = $this->processMastodonFeed();
if ( $postsCreated!==false ) {
if ($postsCreated !== false) {
file_put_contents($this->getStatusFile(), time()); file_put_contents($this->getStatusFile(), time());
} }
return $postsCreated; return $postsCreated;
} }
public function onTick() { // Process Mastodon feed and create pages
$interval = intval($this->yellow->system->get("mastodonapiUpdateInterval")) * 60;
$statusFile = $this->getStatusFile();
$lastUpdate = file_exists($statusFile) ? intval(file_get_contents($statusFile)) : 0;
if (time() - $lastUpdate >= $interval) {
$this->updateMastodonPosts();
}
}
private function processMastodonFeed() { private function processMastodonFeed() {
$instanceUrl = $this->yellow->system->get("mastodonapiInstanceUrl"); $instanceUrl = $this->yellow->system->get("mastodonapiInstanceUrl");
$accountId = $this->yellow->system->get("mastodonapiAccountId"); $accountId = $this->yellow->system->get("mastodonapiAccountId");
$accessToken = $this->yellow->system->get("mastodonapiAccessToken"); $accessToken = $this->yellow->system->get("mastodonapiAccessToken");
$hashtag = ltrim($this->yellow->system->get("mastodonapiHashtag"), '#'); $hashtag = ltrim($this->yellow->system->get("mastodonapiHashtag"), "#");
$defaultAuthor = $this->yellow->system->get("mastodonapiAuthor"); $defaultAuthor = $this->yellow->system->get("mastodonapiAuthor");
$defaultTag = $this->yellow->system->get("mastodonapiTag"); $defaultTag = $this->yellow->system->get("mastodonapiTag");
$targetFolder = $this->yellow->system->get("mastodonapiFolder"); $targetFolder = $this->yellow->system->get("mastodonapiFolder");
$status = $this->yellow->system->get("mastodonapiStatus"); $statusSetting = $this->yellow->system->get("mastodonapiStatus");
$maxAgeHours = intval($this->yellow->system->get("mastodonapiMaxAge")); $maxAgeHours = intval($this->yellow->system->get("mastodonapiMaxAge"));
$limit = intval($this->yellow->system->get("mastodonapiLimit")); $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; $apiUrl = rtrim($instanceUrl, "/") . "/api/v1/accounts/" . $accountId . "/statuses?limit=" . $limit;
$headers = ["User-Agent: YellowMastodonAPI"]; $headers = array("User-Agent: YellowMastodonapi/" . self::VERSION);
if (!empty($accessToken)) $headers[] = "Authorization: Bearer " . $accessToken; if ( !empty($accessToken) ) $headers[] = "Authorization: Bearer " . $accessToken;
$ch = curl_init($apiUrl); $ch = curl_init($apiUrl);
curl_setopt_array($ch, [ curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true, CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 30, CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => $headers, CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2 CURLOPT_SSL_VERIFYHOST => 2
]); ));
$json = curl_exec($ch); $json = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch); 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); $posts = json_decode($json, true);
if (!is_array($posts)) return false; if ( !is_array($posts) ) return false;
$postsCreated = 0; $postsCreated = 0;
foreach ($posts as $post) { foreach ( $posts as $post ) {
if (!empty($post['reblog']) || (!empty($post['reblogged']) && $post['reblogged'] === true)) continue; if ( !empty($post["reblog"]) || ( !empty($post["reblogged"]) && $post["reblogged"]===true ) ) continue;
if (!is_null($post['in_reply_to_id'])) continue; if ( !is_null($post["in_reply_to_id"]) ) continue;
$contentHtml = $post["content"] ?? "";
$contentHtml = $post['content'] ?? ''; $createdAt = $post["created_at"] ?? "";
$createdAt = $post['created_at'] ?? ''; if ( $maxAgeHours > 0 ) {
if ($maxAgeHours > 0) { if ( strtotime($createdAt) < ( time() - ( $maxAgeHours * 3600 ) ) ) continue;
if (strtotime($createdAt) < (time() - ($maxAgeHours * 3600))) continue;
} }
$tags = array_column($post["tags"] ?? array(), "name");
$tags = array_column($post['tags'] ?? [], 'name'); if ( !empty($hashtag) && !in_array(strtolower($hashtag), array_map("strtolower", $tags)) ) continue;
if (!empty($hashtag) && !in_array(strtolower($hashtag), array_map('strtolower', $tags))) continue;
$title = $this->generateTitle($contentHtml); $title = $this->generateTitle($contentHtml);
$titleSlug = $this->generateTitleSlug($title); $titleSlug = $this->generateTitleSlug($title);
$fileName = date('Y-m-d', strtotime($createdAt)) . '-' . $titleSlug . '.md'; $fileName = date("Y-m-d", strtotime($createdAt)) . "-" . $titleSlug . ".md";
$fullPath = $this->yellow->system->get("coreServerDocument") . $targetFolder . $fileName; $fullPath = $this->yellow->system->get("coreServerDocument") . $targetFolder . $fileName;
if ( file_exists($fullPath) ) continue;
if (file_exists($fullPath)) continue; if ( !is_dir(dirname($fullPath)) ) mkdir(dirname($fullPath), 0755, true);
if (!is_dir(dirname($fullPath))) mkdir(dirname($fullPath), 0755, true); $imageFilenames = array();
$imageAltTexts = array();
$imageFilenames = []; foreach ( $post["media_attachments"] ?? array() as $media ) {
$imageAltTexts = []; if ( ( $media["type"] ?? "" )==="image" ) {
foreach ($post['media_attachments'] ?? [] as $media) { $filename = $this->downloadAndSaveImage($media, $this->yellow->system->get("coreServerDocument") . "media/images/");
if (($media['type'] ?? '') === 'image') { if ( $filename ) {
$filename = $this->downloadAndSaveImage($media, $this->yellow->system->get("coreServerDocument") . 'media/images/');
if ($filename) {
$imageFilenames[] = $filename; $imageFilenames[] = $filename;
$imageAltTexts[] = $media['description'] ?? ''; $imageAltTexts[] = $media["description"] ?? "";
} }
} }
} }
$markdownContent = $this->generateMarkdown( $markdownContent = $this->generateMarkdown(
$title, $titleSlug, $createdAt, $contentHtml, $title, $titleSlug, $createdAt, $contentHtml,
$post['account']['display_name'] ?: $defaultAuthor, $post["account"]["display_name"] ?: $defaultAuthor,
$post['account']['url'] ?: "#", $post["account"]["url"] ?: "#",
$defaultAuthor, $defaultTag, $imageFilenames, $imageAltTexts, $status $defaultAuthor, $defaultTag, $imageFilenames, $imageAltTexts, $statusSetting
); );
file_put_contents($fullPath, $markdownContent); file_put_contents($fullPath, $markdownContent);
$postsCreated++; $postsCreated++;
} }
return $postsCreated; return $postsCreated;
} }
// Download and save image
private function downloadAndSaveImage($media, $dirPath) { private function downloadAndSaveImage($media, $dirPath) {
$imageUrl = $media['remote_url'] ?? $media['url'] ?? $media['preview_url'] ?? null; $imageUrl = $media["remote_url"] ?? $media["url"] ?? $media["preview_url"] ?? null;
if (empty($imageUrl)) return false; if ( empty($imageUrl) ) return false;
if (!is_dir($dirPath)) mkdir($dirPath, 0755, true); if ( !is_dir($dirPath) ) mkdir($dirPath, 0755, true);
$fileName = basename(parse_url($imageUrl, PHP_URL_PATH)); $fileName = basename(parse_url($imageUrl, PHP_URL_PATH));
$fullPath = $dirPath . $fileName; $fullPath = $dirPath . $fileName;
if (file_exists($fullPath)) return $fileName; if ( file_exists($fullPath) ) return $fileName;
$ch = curl_init($imageUrl); $ch = curl_init($imageUrl);
curl_setopt_array($ch, [ curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true, CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 30, CURLOPT_TIMEOUT => 30,
CURLOPT_USERAGENT => "YellowMastodonAPI", CURLOPT_USERAGENT => "YellowMastodonapi/" . self::VERSION,
CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYPEER => true,
]); ));
$imgData = curl_exec($ch); $imgData = curl_exec($ch);
curl_close($ch); curl_close($ch);
if ( !$imgData ) return false;
if (!$imgData) return false;
file_put_contents($fullPath, $imgData); file_put_contents($fullPath, $imgData);
return $fileName; return $fileName;
} }
// Generate title from content
private function generateTitle($content) { private function generateTitle($content) {
$text = trim(strip_tags($content)); $text = trim(strip_tags($content));
$text = preg_replace('/https?:\/\/[^\s]+|#\w+/u', '', $text); $text = preg_replace("/https?:\/\/[^\s]+|#\w+/u", "", $text);
$words = explode(' ', $text); $words = explode(" ", $text);
return trim(mb_substr(implode(' ', array_slice($words, 0, 7)), 0, 100, 'UTF-8')) ?: "Mastodon Post"; return trim(mb_substr(implode(" ", array_slice($words, 0, 7)), 0, 100, "UTF-8")) ?: "Mastodon Post";
} }
// Generate slug from title
private function generateTitleSlug($title) { private function generateTitleSlug($title) {
$slug = mb_strtolower($title, 'UTF-8'); $slug = mb_strtolower($title, "UTF-8");
$slug = str_replace(['ä','ö','ü','ß',' '], ['ae','oe','ue','ss','-'], $slug); $slug = str_replace(array("ä","ö","ü","ß"," "), array("ae","oe","ue","ss","-"), $slug);
return trim(preg_replace('/[^a-z0-9-]+/', '', $slug), '-'); return trim(preg_replace("/[^a-z0-9-]+/", "", $slug), "-");
} }
// Generate markdown content
private function generateMarkdown($title, $slug, $dateTime, $html, $origAuthor, $origUrl, $yellowAuthor, $yellowTag, $images, $alts, $status) { private function generateMarkdown($title, $slug, $dateTime, $html, $origAuthor, $origUrl, $yellowAuthor, $yellowTag, $images, $alts, $status) {
$date = new DateTime($dateTime); $date = new DateTime($dateTime);
$pub = $date->format('Y-m-d H:i:s'); $pub = $date->format("Y-m-d H:i:s");
$content = trim(html_entity_decode(strip_tags(str_replace(["<br>", "</p>"], ["\n", "</p>\n"], $html)), ENT_QUOTES | ENT_HTML5, 'UTF-8')); $content = trim(html_entity_decode(strip_tags(str_replace(array("<br>", "</p>"), array("\n", "</p>\n"), $html)), ENT_QUOTES | ENT_HTML5, "UTF-8"));
$content = str_replace("\n", "\n> ", $content); $content = str_replace("\n", "\n> ", $content);
$imageMarkdown = "";
$imageMarkdown = ''; $featuredImage = $featuredImageAlt = "";
$featuredImage = $featuredImageAlt = ''; foreach ( $images as $i => $filename ) {
foreach ($images as $i => $filename) { $alt = isset($alts[$i]) ? htmlspecialchars($alts[$i], ENT_QUOTES) : "";
$alt = isset($alts[$i]) ? htmlspecialchars($alts[$i], ENT_QUOTES) : '';
$imageMarkdown .= "\n<img src=\"/media/images/$filename\" alt=\"$alt\"/>\n"; $imageMarkdown .= "\n<img src=\"/media/images/$filename\" alt=\"$alt\"/>\n";
if ($i === 0) { if ( $i===0 ) {
$featuredImage = "Image: $filename\n"; $featuredImage = "Image: $filename\n";
$featuredImageAlt = "ImageAlt: $alt\n"; $featuredImageAlt = "ImageAlt: $alt\n";
} }
} }
$authorSafe = str_replace("@", "&#64;", $origAuthor);
return "---\n" .
"Title: $title\n" .
"TitleSlug: $slug\n" .
"Published: $pub\n" .
"Author: $yellowAuthor\n" .
"Layout: blog\n" .
"Tag: $yellowTag\n" .
"Status: $status\n" .
$featuredImage . $featuredImageAlt .
"---\n\n" .
$imageMarkdown . "\n" .
"> " . $content . "\n>\n" .
"> — <a target=\"_blank\" href=\"$origUrl\">$authorSafe</a>";
}
$authorSafe = str_replace('@', '&#64;', $origAuthor); // Return status file path
private function getStatusFile() {
return <<<MD return $this->yellow->system->get("coreWorkerDirectory") . "mastodonapi.status";
---
Title: $title
TitleSlug: $slug
Published: $pub
Author: $yellowAuthor
Layout: blog
Tag: $yellowTag
Status: $status
{$featuredImage}{$featuredImageAlt}---
$imageMarkdown
> $content
>
> <a target="_blank" href="$origUrl">$authorSafe</a>
MD;
} }
} }