$extractedData * * @throws PublishException */ public function publishRouteArticle(RouteArticle $routeArticle, array $extractedData): ?ArticlePublication { $article = $routeArticle->article; $channel = $routeArticle->platformChannel; if (! $channel) { throw new PublishException($article, PlatformEnum::LEMMY, new RuntimeException('ROUTE_ARTICLE_MISSING_CHANNEL')); } if (! $channel->relationLoaded('platformInstance')) { $channel->load(['platformInstance', 'activePlatformAccounts']); } $account = $channel->activePlatformAccounts()->first(); if (! $account) { $this->logSaver->warning('No active account for channel', $channel, [ 'article_id' => $article->id, 'route_article_id' => $routeArticle->id, ]); return null; } return $this->publishToChannel($article, $extractedData, $channel, $account); } /** * @deprecated Use publishRouteArticle() instead. Kept for PublishApprovedArticleListener compatibility. * * @param array $extractedData * @return Collection * * @throws PublishException */ public function publishToRoutedChannels(Article $article, array $extractedData): Collection { $feed = $article->feed; $activeRoutes = Route::where('feed_id', $feed->id) ->where('is_active', true) ->get(); $keywordsByChannel = Keyword::where('feed_id', $feed->id) ->where('is_active', true) ->get() ->groupBy('platform_channel_id'); $matchingRoutes = $activeRoutes->filter(function (Route $route) use ($extractedData, $keywordsByChannel) { $keywords = $keywordsByChannel->get($route->platform_channel_id, collect()); if ($keywords->isEmpty()) { return true; } $articleContent = ($extractedData['full_article'] ?? ''). ' '.($extractedData['title'] ?? ''). ' '.($extractedData['description'] ?? ''); foreach ($keywords as $keyword) { if (stripos($articleContent, $keyword->keyword) !== false) { return true; } } return false; }); return $matchingRoutes->map(function (Route $route) use ($article, $extractedData) { $channel = PlatformChannel::with(['platformInstance', 'activePlatformAccounts'])->find($route->platform_channel_id); $account = $channel?->activePlatformAccounts()->first(); if (! $account) { $this->logSaver->warning('No active account for channel', $channel, [ 'article_id' => $article->id, 'route_priority' => $route->priority, ]); return null; } return $this->publishToChannel($article, $extractedData, $channel, $account); })->filter(); } /** * @param array $extractedData */ private function publishToChannel(Article $article, array $extractedData, PlatformChannel $channel, mixed $account): ?ArticlePublication { try { // Check if this URL or title was already posted to this channel $title = $extractedData['title'] ?? $article->title; if (PlatformChannelPost::duplicateExists( $channel->platformInstance->platform, (string) $channel->channel_id, $article->url, $title )) { $this->logSaver->info('Skipping duplicate: URL or title already posted to channel', $channel, [ 'article_id' => $article->id, 'url' => $article->url, 'title' => $title, ]); return null; } $publisher = $this->makePublisher($account); $postData = $publisher->publishToChannel($article, $extractedData, $channel); $publication = ArticlePublication::create([ 'article_id' => $article->id, 'post_id' => $postData['post_view']['post']['id'], 'platform_channel_id' => $channel->id, 'published_by' => $account->username, 'published_at' => now(), 'platform' => $channel->platformInstance->platform->value, 'publication_data' => $postData, ]); $this->logSaver->info('Published to channel', $channel, [ 'article_id' => $article->id, ]); return $publication; } catch (Exception $e) { $this->logSaver->warning('Failed to publish to channel', $channel, [ 'article_id' => $article->id, 'error' => $e->getMessage(), ]); return null; } } }