ensureCliAccess(); $this->mastodonAccessToken = getenv("MASTODON_ACCESS_TOKEN") ?: $_ENV["MASTODON_ACCESS_TOKEN"] ?? ""; $this->httpClient = $httpClient ?: new Client(); $this->validateAuthorization(); } private function validateAuthorization(): void { $authHeader = $_SERVER["HTTP_AUTHORIZATION"] ?? ""; $expectedToken = "Bearer " . getenv("MASTODON_SYNDICATION_TOKEN"); if ($authHeader !== $expectedToken) { http_response_code(401); echo json_encode(["error" => "Unauthorized"]); exit(); } } public function handlePost(): void { if (!$this->isDatabaseAvailable()) { echo "Database is unavailable. Exiting.\n"; return; } $latestItems = $this->fetchRSSFeed($this->rssFeedUrl); foreach (array_reverse($latestItems) as $item) { $existing = $this->fetchFromApi("mastodon_posts", "link=eq." . urlencode($item["link"])); if (!empty($existing)) continue; $content = $this->truncateContent( $item["title"], strip_tags($item["description"]), $item["link"], 500 ); $timestamp = date("Y-m-d H:i:s"); if (!$this->storeInDatabase($item["link"], $timestamp)) { echo "Skipping post: database write failed for {$item["link"]}\n"; continue; } $postedUrl = $this->postToMastodon($content, $item["image"] ?? null); if ($postedUrl) { echo "Posted: {$postedUrl}\n"; } else { echo "Failed to post to Mastodon for: {$item["link"]}\n"; } } echo "RSS processed successfully.\n"; } private function fetchRSSFeed(string $rssFeedUrl): array { $rssText = file_get_contents($rssFeedUrl); if (!$rssText) throw new \Exception("Failed to fetch RSS feed."); $rss = new \SimpleXMLElement($rssText); $items = []; foreach ($rss->channel->item as $item) { $items[] = [ "title" => (string) $item->title, "link" => (string) $item->link, "description" => (string) $item->description, ]; } return $items; } private function postToMastodon(string $content): ?string { $headers = [ "Authorization" => "Bearer {$this->mastodonAccessToken}", "Content-Type" => "application/json", ]; $postData = ["status" => $content]; $response = $this->httpClient->request("POST", self::MASTODON_API_STATUS, [ "headers" => $headers, "json" => $postData ]); if ($response->getStatusCode() >= 400) throw new \Exception("Mastodon post failed: {$response->getBody()}"); $body = json_decode($response->getBody()->getContents(), true); return $body["url"] ?? null; } private function storeInDatabase(string $link, string $timestamp): bool { try { $this->makeRequest("POST", "mastodon_posts", [ "json" => [ "link" => $link, "created_at" => $timestamp ] ]); return true; } catch (\Exception $e) { echo "Error storing post in DB: " . $e->getMessage() . "\n"; return false; } } private function isDatabaseAvailable(): bool { try { $response = $this->fetchFromApi("mastodon_posts", "limit=1"); return is_array($response); } catch (\Exception $e) { echo "Database check failed: " . $e->getMessage() . "\n"; return false; } } private function truncateContent(string $title, string $description, string $link, int $maxLength): string { $baseLength = strlen("$title\n\n$link"); $available = $maxLength - $baseLength - 4; if (strlen($description) > $available) { $description = substr($description, 0, $available); $description = preg_replace('/\s+\S*$/', "", $description) . "..."; } return "$title\n\n$description\n\n$link"; } } try { $handler = new MastodonPostHandler(); $handler->handlePost(); } catch (\Exception $e) { http_response_code(500); echo json_encode(["error" => $e->getMessage()]); }