coryd.dev/api/mastodon.php

187 lines
5.3 KiB
PHP

<?php
require_once __DIR__.'/../bootstrap.php';
use App\Classes\ApiHandler;
use GuzzleHttp\Client;
class MastodonPostHandler extends ApiHandler
{
private string $mastodonAccessToken;
private string $rssFeedUrl = 'https://www.coryd.dev/feeds/syndication.xml';
private string $baseUrl = 'https://www.coryd.dev';
private const MASTODON_API_STATUS = 'https://follow.coryd.dev/api/v1/statuses';
private Client $httpClient;
public function __construct(?Client $httpClient = null)
{
parent::__construct();
$this->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' => $this->cleanText((string) $item->title),
'link' => (string) $item->link,
'description' => $this->cleanText((string) $item->description),
];
}
return $items;
}
private function cleanText(string $text): string
{
$decoded = html_entity_decode($text, ENT_QUOTES | ENT_XML1, 'UTF-8');
return mb_convert_encoding($decoded, 'UTF-8', 'UTF-8');
}
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()]);
}