fedi-feed-router/app/Modules/Lemmy/Services/LemmyPublisher.php

155 lines
5 KiB
PHP
Raw Normal View History

2025-06-29 21:20:45 +02:00
<?php
namespace App\Modules\Lemmy\Services;
2025-06-30 18:18:30 +02:00
use App\Enums\PlatformEnum;
2025-07-05 02:19:59 +02:00
use App\Exceptions\PlatformAuthException;
2025-06-30 18:18:30 +02:00
use App\Exceptions\PublishException;
2025-06-29 21:20:45 +02:00
use App\Models\Article;
use App\Models\ArticlePublication;
2025-07-05 01:55:53 +02:00
use App\Models\PlatformAccount;
2025-07-02 21:32:37 +02:00
use App\Services\Auth\LemmyAuthService;
2025-06-29 21:20:45 +02:00
use Exception;
2025-07-05 02:19:59 +02:00
use Illuminate\Support\Collection;
2025-06-29 21:20:45 +02:00
use Illuminate\Support\Facades\Cache;
2025-07-05 01:55:53 +02:00
use RuntimeException;
2025-06-29 21:20:45 +02:00
class LemmyPublisher
{
private LemmyApiService $api;
2025-07-05 01:55:53 +02:00
private PlatformAccount $account;
2025-06-29 21:20:45 +02:00
2025-07-05 01:55:53 +02:00
public function __construct(PlatformAccount $account)
2025-06-29 21:20:45 +02:00
{
2025-07-05 01:55:53 +02:00
$this->api = new LemmyApiService($account->instance_url);
$this->account = $account;
2025-06-29 21:20:45 +02:00
}
2025-07-05 01:55:53 +02:00
public static function fromActiveAccount(): self
2025-06-29 21:20:45 +02:00
{
2025-07-05 01:55:53 +02:00
$accounts = PlatformAccount::getActive(PlatformEnum::LEMMY);
if ($accounts->isEmpty()) {
2025-07-05 02:19:59 +02:00
throw new RuntimeException('No active Lemmy accounts configured'); // TODO Also make this into a PublishException
2025-07-05 01:55:53 +02:00
}
return new self($accounts->first());
2025-06-29 21:20:45 +02:00
}
2025-06-30 18:18:30 +02:00
/**
* @throws PublishException
*/
2025-07-05 02:19:59 +02:00
public function publish(Article $article, array $extractedData): Collection
2025-06-29 21:20:45 +02:00
{
2025-07-05 02:19:59 +02:00
$publications = collect();
2025-07-05 02:29:50 +02:00
$activeChannels = $this->account->activeChannels;
2025-07-05 02:19:59 +02:00
2025-07-05 02:29:50 +02:00
if ($activeChannels->isEmpty()) {
throw new PublishException($article, PlatformEnum::LEMMY, new RuntimeException('No active channels configured for account: ' . $this->account->username));
2025-07-05 02:19:59 +02:00
}
2025-07-05 02:29:50 +02:00
$activeChannels->each(function ($channel) use ($article, $extractedData, $publications) {
2025-07-05 02:19:59 +02:00
try {
2025-07-05 02:29:50 +02:00
$publication = $this->publishToChannel($article, $extractedData, $channel);
2025-07-05 02:19:59 +02:00
$publications->push($publication);
} catch (Exception $e) {
2025-07-05 02:29:50 +02:00
logger()->warning('Failed to publish to channel', [
2025-07-05 02:19:59 +02:00
'article_id' => $article->id,
2025-07-05 02:29:50 +02:00
'channel' => $channel->name,
2025-07-05 02:19:59 +02:00
'error' => $e->getMessage()
]);
}
});
if ($publications->isEmpty()) {
2025-07-05 02:29:50 +02:00
throw new PublishException($article, PlatformEnum::LEMMY, new RuntimeException('Failed to publish to any channel'));
2025-06-30 18:18:30 +02:00
}
2025-07-05 02:19:59 +02:00
return $publications;
2025-06-29 21:20:45 +02:00
}
2025-07-05 01:55:53 +02:00
/**
2025-07-05 02:19:59 +02:00
* @throws PlatformAuthException
2025-07-05 01:55:53 +02:00
* @throws Exception
*/
2025-07-05 02:29:50 +02:00
private function publishToChannel(Article $article, array $extractedData, $channel): ArticlePublication
2025-06-29 21:20:45 +02:00
{
2025-07-05 02:19:59 +02:00
$token = LemmyAuthService::getToken($this->account);
$languageId = $this->getLanguageIdForSource($article->url);
$postData = $this->api->createPost(
$token,
$extractedData['title'] ?? 'Untitled',
$extractedData['description'] ?? '',
2025-07-05 02:29:50 +02:00
(int) $channel->channel_id,
2025-07-05 02:19:59 +02:00
$article->url,
$extractedData['thumbnail'] ?? null,
$languageId
);
2025-07-05 02:29:50 +02:00
return $this->createPublicationRecord($article, $postData, (int) $channel->channel_id);
2025-06-29 21:20:45 +02:00
}
2025-07-05 02:29:50 +02:00
private function createPublicationRecord(Article $article, array $postData, int $channelId): ArticlePublication
2025-06-29 21:20:45 +02:00
{
return ArticlePublication::create([
'article_id' => $article->id,
'post_id' => $postData['post_view']['post']['id'],
2025-07-05 02:29:50 +02:00
'community_id' => $channelId,
2025-07-05 01:55:53 +02:00
'published_by' => $this->account->username,
2025-06-29 21:20:45 +02:00
'published_at' => now(),
'platform' => 'lemmy',
'publication_data' => $postData,
]);
}
2025-07-02 20:26:28 +02:00
private function getLanguageIdForSource(string $url): ?int
{
// TODO this will be obsolete when sources can be created from the UI, so we can remove these hard-coded sources
// VRT articles are in Dutch
if (str_contains($url, 'vrt.be')) {
return $this->getLanguageId('nl'); // Dutch
}
// Belga articles are in English (based on URL structure)
if (str_contains($url, 'belganewsagency.eu')) {
return $this->getLanguageId('en'); // English
}
return null; // Default to no language specified
}
private function getLanguageId(string $languageCode): ?int
{
2025-07-05 01:55:53 +02:00
$cacheKey = "lemmy_language_id_$languageCode";
2025-07-02 20:26:28 +02:00
$cachedId = Cache::get($cacheKey);
if ($cachedId !== null) {
return $cachedId;
}
try {
$languages = $this->api->getLanguages();
foreach ($languages as $language) {
if (isset($language['code']) && $language['code'] === $languageCode) {
$languageId = $language['id'];
Cache::put($cacheKey, $languageId, 3600);
return $languageId;
}
}
// Cache null result to avoid repeated API calls
Cache::put($cacheKey, null, 3600);
return null;
} catch (Exception $e) {
logger()->warning('Failed to get language ID', [
'language_code' => $languageCode,
'error' => $e->getMessage()
]);
return null;
}
}
2025-06-30 18:18:30 +02:00
}