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) { $imageUrl = null; if ($item->enclosure && isset($item->enclosure['url'])) { $imageUrl = (string) $item->enclosure['url']; } $items[] = [ "title" => (string) $item->title, "link" => (string) $item->link, "description" => (string) $item->description, "image" => $imageUrl, ]; } return $items; } private function uploadImageToMastodon(string $imageUrl): ?string { $tempFile = tempnam(sys_get_temp_dir(), "mastodon_img"); file_put_contents($tempFile, file_get_contents($imageUrl)); $response = $this->httpClient->request("POST", "https://follow.coryd.dev/api/v2/media", [ "headers" => [ "Authorization" => "Bearer {$this->mastodonAccessToken}" ], "multipart" => [ [ "name" => "file", "contents" => fopen($tempFile, "r"), "filename" => basename($imageUrl) ] ] ]); unlink($tempFile); if ($response->getStatusCode() !== 200) { throw new \Exception("Image upload failed with status {$response->getStatusCode()}"); } $json = json_decode($response->getBody(), true); return $json["id"] ?? null; } private function postToMastodon(string $content, ?string $imageUrl = null): ?string { $headers = [ "Authorization" => "Bearer {$this->mastodonAccessToken}", "Content-Type" => "application/json", ]; $postData = ["status" => $content]; if ($imageUrl) { try { $mediaId = $this->uploadImageToMastodon($imageUrl); if ($mediaId) $postData["media_ids"] = [$mediaId]; } catch (\Exception $e) { echo "Image upload failed: {$e->getMessage()}\n"; } } $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()]); }