Split sources, refine publisher
This commit is contained in:
parent
83194cd64b
commit
800df655bf
13 changed files with 271 additions and 57 deletions
|
|
@ -7,9 +7,9 @@
|
||||||
|
|
||||||
class FetchNewArticlesCommand extends Command
|
class FetchNewArticlesCommand extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'articles:fetch';
|
protected $signature = 'article:refresh';
|
||||||
|
|
||||||
protected $description = 'Fetches new articles';
|
protected $description = 'Fetches latest articles';
|
||||||
|
|
||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,27 @@
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Jobs\PublishToLemmyJob;
|
||||||
use App\Models\Article;
|
use App\Models\Article;
|
||||||
use App\Modules\Lemmy\Services\LemmyPublisher;
|
|
||||||
use App\Services\Article\ArticleFetcher;
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
class PublishToLemmyCommand extends Command
|
class PublishToLemmyCommand extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'article:publish-to-lemmy';
|
protected $signature = 'article:publish-to-lemmy';
|
||||||
|
|
||||||
protected $description = 'Publish an article to Lemmy';
|
protected $description = 'Queue an article for publishing to Lemmy';
|
||||||
|
|
||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
$article = Article::all()->firstOrFail();
|
$article = Article::all()
|
||||||
|
->filter(fn (Article $article) => $article->articlePublication === null)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
$this->info('Publishing article: ' . $article->url);
|
$this->info('Queuing article for publishing: ' . $article->url);
|
||||||
|
|
||||||
try {
|
PublishToLemmyJob::dispatch($article);
|
||||||
LemmyPublisher::fromConfig()->publish($article, ArticleFetcher::fetchArticleData($article));
|
|
||||||
} catch (Exception) {
|
$this->info('Article queued successfully');
|
||||||
return self::FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::SUCCESS;
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
app/Enums/PlatformEnum.php
Normal file
8
app/Enums/PlatformEnum.php
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum PlatformEnum: string
|
||||||
|
{
|
||||||
|
case LEMMY = 'lemmy';
|
||||||
|
}
|
||||||
23
app/Exceptions/PlatformAuthException.php
Normal file
23
app/Exceptions/PlatformAuthException.php
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use App\Enums\PlatformEnum;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class PlatformAuthException extends Exception
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly PlatformEnum $platform,
|
||||||
|
string $reason = 'Authentication failed'
|
||||||
|
) {
|
||||||
|
$message = "Failed to authenticate with {$platform->value}: {$reason}";
|
||||||
|
|
||||||
|
parent::__construct($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlatform(): PlatformEnum
|
||||||
|
{
|
||||||
|
return $this->platform;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
app/Exceptions/PublishException.php
Normal file
35
app/Exceptions/PublishException.php
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use App\Enums\PlatformEnum;
|
||||||
|
use App\Models\Article;
|
||||||
|
use Exception;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class PublishException extends Exception
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Article $article,
|
||||||
|
private readonly PlatformEnum $platform,
|
||||||
|
?Throwable $previous = null
|
||||||
|
) {
|
||||||
|
$message = "Failed to publish article #{$article->id} to {$platform->value}";
|
||||||
|
|
||||||
|
if ($previous) {
|
||||||
|
$message .= ": {$previous->getMessage()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($message, 0, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getArticle(): Article
|
||||||
|
{
|
||||||
|
return $this->article;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPlatform(): PlatformEnum
|
||||||
|
{
|
||||||
|
return $this->platform;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/Jobs/PublishToLemmyJob.php
Normal file
50
app/Jobs/PublishToLemmyJob.php
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Exceptions\PublishException;
|
||||||
|
use App\Models\Article;
|
||||||
|
use App\Modules\Lemmy\Services\LemmyPublisher;
|
||||||
|
use App\Services\Article\ArticleFetcher;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
|
||||||
|
class PublishToLemmyJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly Article $article
|
||||||
|
) {
|
||||||
|
$this->onQueue('lemmy-posts');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
if ($this->article->articlePublication !== null) {
|
||||||
|
logger()->info('Article already published, skipping', [
|
||||||
|
'article_id' => $this->article->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extractedData = ArticleFetcher::fetchArticleData($this->article);
|
||||||
|
|
||||||
|
logger()->info('Publishing article to Lemmy', [
|
||||||
|
'article_id' => $this->article->id,
|
||||||
|
'url' => $this->article->url
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
LemmyPublisher::fromConfig()->publish($this->article, $extractedData);
|
||||||
|
|
||||||
|
logger()->info('Article published successfully', [
|
||||||
|
'article_id' => $this->article->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (PublishException $e) {
|
||||||
|
$this->fail($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
namespace App\Listeners;
|
namespace App\Listeners;
|
||||||
|
|
||||||
use App\Events\ArticleReadyToPublish;
|
use App\Events\ArticleReadyToPublish;
|
||||||
use App\Modules\Lemmy\Services\LemmyPublisher;
|
use App\Jobs\PublishToLemmyJob;
|
||||||
use App\Services\Article\ArticleFetcher;
|
|
||||||
|
|
||||||
class PublishArticle
|
class PublishArticle
|
||||||
{
|
{
|
||||||
|
|
@ -16,8 +15,11 @@ public function handle(ArticleReadyToPublish $event): void
|
||||||
{
|
{
|
||||||
$article = $event->article;
|
$article = $event->article;
|
||||||
|
|
||||||
logger('Publishing article: ' . $article->id . ' : ' . $article->url);
|
logger()->info('Article queued for publishing to Lemmy', [
|
||||||
|
'article_id' => $article->id,
|
||||||
LemmyPublisher::fromConfig()->publish($article, ArticleFetcher::fetchArticleData($article));
|
'url' => $article->url
|
||||||
|
]);
|
||||||
|
|
||||||
|
PublishToLemmyJob::dispatch($article);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
use Database\Factories\ArticleFactory;
|
use Database\Factories\ArticleFactory;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -16,6 +18,7 @@
|
||||||
* @property Carbon|null $validated_at
|
* @property Carbon|null $validated_at
|
||||||
* @property Carbon $created_at
|
* @property Carbon $created_at
|
||||||
* @property Carbon $updated_at
|
* @property Carbon $updated_at
|
||||||
|
* @property ArticlePublication $articlePublication
|
||||||
*/
|
*/
|
||||||
class Article extends Model
|
class Article extends Model
|
||||||
{
|
{
|
||||||
|
|
@ -54,6 +57,16 @@ public function isValid(): bool
|
||||||
return $this->is_valid;
|
return $this->is_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function articlePublication(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(ArticlePublication::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function articlePublications(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ArticlePublication::class);
|
||||||
|
}
|
||||||
|
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
{
|
{
|
||||||
static::created(function ($article) {
|
static::created(function ($article) {
|
||||||
|
|
|
||||||
|
|
@ -57,15 +57,26 @@ public function getCommunityId(string $communityName): int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createPost(string $token, string $title, string $body, int $communityId): array
|
public function createPost(string $token, string $title, string $body, int $communityId, ?string $url = null, ?string $thumbnail = null): array
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$request = new LemmyRequest($this->instance, $token);
|
$request = new LemmyRequest($this->instance, $token);
|
||||||
$response = $request->post('post', [
|
|
||||||
|
$postData = [
|
||||||
'name' => $title,
|
'name' => $title,
|
||||||
'body' => $body,
|
'body' => $body,
|
||||||
'community_id' => $communityId,
|
'community_id' => $communityId,
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
if ($url) {
|
||||||
|
$postData['url'] = $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($thumbnail) {
|
||||||
|
$postData['custom_thumbnail'] = $thumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $request->post('post', $postData);
|
||||||
|
|
||||||
if (!$response->successful()) {
|
if (!$response->successful()) {
|
||||||
throw new Exception('Failed to create post: ' . $response->status() . ' - ' . $response->body());
|
throw new Exception('Failed to create post: ' . $response->status() . ' - ' . $response->body());
|
||||||
|
|
@ -77,4 +88,4 @@ public function createPost(string $token, string $title, string $body, int $comm
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
namespace App\Modules\Lemmy\Services;
|
namespace App\Modules\Lemmy\Services;
|
||||||
|
|
||||||
|
use App\Enums\PlatformEnum;
|
||||||
|
use App\Exceptions\PlatformAuthException;
|
||||||
|
use App\Exceptions\PublishException;
|
||||||
use App\Models\Article;
|
use App\Models\Article;
|
||||||
use App\Models\ArticlePublication;
|
use App\Models\ArticlePublication;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
@ -29,38 +32,47 @@ public static function fromConfig(): self
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws PublishException
|
||||||
|
*/
|
||||||
public function publish(Article $article, array $extractedData): ArticlePublication
|
public function publish(Article $article, array $extractedData): ArticlePublication
|
||||||
{
|
{
|
||||||
$token = $this->getAuthToken();
|
try {
|
||||||
|
$token = $this->getAuthToken();
|
||||||
if (!$token) {
|
$communityId = $this->getCommunityId();
|
||||||
throw new Exception('Failed to authenticate with Lemmy');
|
|
||||||
|
$postData = $this->api->createPost(
|
||||||
|
$token,
|
||||||
|
$extractedData['title'] ?? 'Untitled',
|
||||||
|
$extractedData['description'] ?? '',
|
||||||
|
$communityId,
|
||||||
|
$article->url,
|
||||||
|
$extractedData['thumbnail'] ?? null
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->createPublicationRecord($article, $postData, $communityId);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new PublishException($article, PlatformEnum::LEMMY, $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
$communityId = $this->getCommunityId();
|
|
||||||
|
|
||||||
$postData = $this->api->createPost(
|
|
||||||
$token,
|
|
||||||
$extractedData['title'] ?? 'Untitled',
|
|
||||||
$extractedData['description'] ?? '',
|
|
||||||
$communityId
|
|
||||||
);
|
|
||||||
|
|
||||||
return $this->createPublicationRecord($article, $postData, $communityId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getAuthToken(): ?string
|
private function getAuthToken(): string
|
||||||
{
|
{
|
||||||
return Cache::remember('lemmy_jwt_token', 3600, function () {
|
return Cache::remember('lemmy_jwt_token', 3600, function () {
|
||||||
$username = config('lemmy.username');
|
$username = config('lemmy.username');
|
||||||
$password = config('lemmy.password');
|
$password = config('lemmy.password');
|
||||||
|
|
||||||
if (!$username || !$password) {
|
if (!$username || !$password) {
|
||||||
logger()->error('Missing Lemmy credentials');
|
throw new PlatformAuthException(PlatformEnum::LEMMY, 'Missing credentials');
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$token = $this->api->login($username, $password);
|
||||||
|
|
||||||
return $this->api->login($username, $password);
|
if (!$token) {
|
||||||
|
throw new PlatformAuthException(PlatformEnum::LEMMY, 'Login failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $token;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,4 +95,4 @@ private function createPublicationRecord(Article $article, array $postData, int
|
||||||
'publication_data' => $postData,
|
'publication_data' => $postData,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,12 +80,28 @@ public static function extractFullArticle(string $html): ?string
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function extractThumbnail(string $html): ?string
|
||||||
|
{
|
||||||
|
// Try OpenGraph image first
|
||||||
|
if (preg_match('/<meta property="og:image" content="([^"]+)"/i', $html, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try first image in article content
|
||||||
|
if (preg_match('/<img[^>]+src="([^"]+)"/i', $html, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static function extractData(string $html): array
|
public static function extractData(string $html): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'title' => self::extractTitle($html),
|
'title' => self::extractTitle($html),
|
||||||
'description' => self::extractDescription($html),
|
'description' => self::extractDescription($html),
|
||||||
'full_article' => self::extractFullArticle($html),
|
'full_article' => self::extractFullArticle($html),
|
||||||
|
'thumbnail' => self::extractThumbnail($html),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,66 +10,80 @@ public static function extractTitle(string $html): ?string
|
||||||
if (preg_match('/<meta property="og:title" content="([^"]+)"/i', $html, $matches)) {
|
if (preg_match('/<meta property="og:title" content="([^"]+)"/i', $html, $matches)) {
|
||||||
return html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8');
|
return html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try h1 tag
|
// Try h1 tag
|
||||||
if (preg_match('/<h1[^>]*>([^<]+)<\/h1>/i', $html, $matches)) {
|
if (preg_match('/<h1[^>]*>([^<]+)<\/h1>/i', $html, $matches)) {
|
||||||
return html_entity_decode(strip_tags($matches[1]), ENT_QUOTES, 'UTF-8');
|
return html_entity_decode(strip_tags($matches[1]), ENT_QUOTES, 'UTF-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try title tag
|
// Try title tag
|
||||||
if (preg_match('/<title>([^<]+)<\/title>/i', $html, $matches)) {
|
if (preg_match('/<title>([^<]+)<\/title>/i', $html, $matches)) {
|
||||||
return html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8');
|
return html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function extractDescription(string $html): ?string
|
public static function extractDescription(string $html): ?string
|
||||||
{
|
{
|
||||||
// Try meta description first
|
// Try meta description first
|
||||||
if (preg_match('/<meta property="og:description" content="([^"]+)"/i', $html, $matches)) {
|
if (preg_match('/<meta property="og:description" content="([^"]+)"/i', $html, $matches)) {
|
||||||
return html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8');
|
return html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find first paragraph in article content
|
// Try to find first paragraph in article content
|
||||||
if (preg_match('/<p[^>]*>([^<]+(?:<[^\/](?!p)[^>]*>[^<]*<\/[^>]*>[^<]*)*)<\/p>/i', $html, $matches)) {
|
if (preg_match('/<p[^>]*>([^<]+(?:<[^\/](?!p)[^>]*>[^<]*<\/[^>]*>[^<]*)*)<\/p>/i', $html, $matches)) {
|
||||||
return html_entity_decode(strip_tags($matches[1]), ENT_QUOTES, 'UTF-8');
|
return html_entity_decode(strip_tags($matches[1]), ENT_QUOTES, 'UTF-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function extractFullArticle(string $html): ?string
|
public static function extractFullArticle(string $html): ?string
|
||||||
{
|
{
|
||||||
// Remove scripts, styles, and other non-content elements
|
// Remove scripts, styles, and other non-content elements
|
||||||
$cleanHtml = preg_replace('/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', '', $html);
|
$cleanHtml = preg_replace('/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', '', $html);
|
||||||
$cleanHtml = preg_replace('/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/mi', '', $cleanHtml);
|
$cleanHtml = preg_replace('/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/mi', '', $cleanHtml);
|
||||||
|
|
||||||
// Extract all paragraph content
|
// Extract all paragraph content
|
||||||
preg_match_all('/<p[^>]*>(.*?)<\/p>/is', $cleanHtml, $matches);
|
preg_match_all('/<p[^>]*>(.*?)<\/p>/is', $cleanHtml, $matches);
|
||||||
|
|
||||||
if (!empty($matches[1])) {
|
if (!empty($matches[1])) {
|
||||||
$paragraphs = array_map(function($paragraph) {
|
$paragraphs = array_map(function($paragraph) {
|
||||||
return html_entity_decode(strip_tags($paragraph), ENT_QUOTES, 'UTF-8');
|
return html_entity_decode(strip_tags($paragraph), ENT_QUOTES, 'UTF-8');
|
||||||
}, $matches[1]);
|
}, $matches[1]);
|
||||||
|
|
||||||
// Filter out empty paragraphs and join with double newlines
|
// Filter out empty paragraphs and join with double newlines
|
||||||
$fullText = implode("\n\n", array_filter($paragraphs, function($p) {
|
$fullText = implode("\n\n", array_filter($paragraphs, function($p) {
|
||||||
return trim($p) !== '';
|
return trim($p) !== '';
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return $fullText ?: null;
|
return $fullText ?: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function extractThumbnail(string $html): ?string
|
||||||
|
{
|
||||||
|
if (preg_match('/<meta property="og:image" content="([^"]+)"/i', $html, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/<img[^>]+src="([^"]+)"/i', $html, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static function extractData(string $html): array
|
public static function extractData(string $html): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'title' => self::extractTitle($html),
|
'title' => self::extractTitle($html),
|
||||||
'description' => self::extractDescription($html),
|
'description' => self::extractDescription($html),
|
||||||
'full_article' => self::extractFullArticle($html),
|
'full_article' => self::extractFullArticle($html),
|
||||||
|
'thumbnail' => self::extractThumbnail($html),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,38 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Console\Commands\FetchNewArticlesCommand;
|
use App\Console\Commands\FetchNewArticlesCommand;
|
||||||
|
use App\Models\Article;
|
||||||
|
use App\Modules\Lemmy\Services\LemmyPublisher;
|
||||||
|
use App\Services\Article\ArticleFetcher;
|
||||||
use Illuminate\Support\Facades\Schedule;
|
use Illuminate\Support\Facades\Schedule;
|
||||||
|
|
||||||
Schedule::command(FetchNewArticlesCommand::class)->hourly();
|
Schedule::command(FetchNewArticlesCommand::class)->hourly();
|
||||||
|
|
||||||
|
Schedule::call(function () {
|
||||||
|
$article = Article::whereDoesntHave('articlePublications')
|
||||||
|
->where('is_valid', true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($article) {
|
||||||
|
try {
|
||||||
|
logger()->info('Publishing article to Lemmy via scheduler', [
|
||||||
|
'article_id' => $article->id,
|
||||||
|
'url' => $article->url
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extractedData = ArticleFetcher::fetchArticleData($article);
|
||||||
|
LemmyPublisher::fromConfig()->publish($article, $extractedData);
|
||||||
|
|
||||||
|
logger()->info('Successfully published article to Lemmy', [
|
||||||
|
'article_id' => $article->id
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
logger()->error('Failed to publish article to Lemmy via scheduler', [
|
||||||
|
'article_id' => $article->id,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger()->debug('No unpublished valid articles found for Lemmy publishing');
|
||||||
|
}
|
||||||
|
})->everyFifteenMinutes()->name('publish-to-lemmy');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue