$extractedData * @return Collection * @throws PublishException */ public function publishToRoutedChannels(Article $article, array $extractedData): Collection { if (! $article->isValid()) { throw new PublishException($article, PlatformEnum::LEMMY, new RuntimeException('CANNOT_PUBLISH_INVALID_ARTICLE')); } $feed = $article->feed; // Get active routes with keywords instead of just channels $activeRoutes = Route::where('feed_id', $feed->id) ->where('is_active', true) ->with(['platformChannel.platformInstance', 'platformChannel.activePlatformAccounts', 'keywords']) ->orderBy('priority', 'desc') ->get(); // Filter routes based on keyword matches $matchingRoutes = $activeRoutes->filter(function (Route $route) use ($extractedData) { return $this->routeMatchesArticle($route, $extractedData); }); return $matchingRoutes->map(function (Route $route) use ($article, $extractedData) { $channel = $route->platformChannel; $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(); } /** * Check if a route matches an article based on keywords * @param array $extractedData */ private function routeMatchesArticle(Route $route, array $extractedData): bool { // Get active keywords for this route $activeKeywords = $route->keywords->where('is_active', true); // If no keywords are defined for this route, the route matches any article if ($activeKeywords->isEmpty()) { return true; } // Get article content for keyword matching $articleContent = ''; if (isset($extractedData['full_article'])) { $articleContent = $extractedData['full_article']; } if (isset($extractedData['title'])) { $articleContent .= ' ' . $extractedData['title']; } if (isset($extractedData['description'])) { $articleContent .= ' ' . $extractedData['description']; } // Check if any of the route's keywords match the article content foreach ($activeKeywords as $keywordModel) { $keyword = $keywordModel->keyword; if (stripos($articleContent, $keyword) !== false) { return true; } } return false; } /** * @param array $extractedData */ private function publishToChannel(Article $article, array $extractedData, PlatformChannel $channel, mixed $account): ?ArticlePublication { try { $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 via keyword-filtered routing', $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; } } }