'STAT News - Biotech', 'url' => 'https://www.statnews.com/category/biotech/feed/'], ['source' => 'Drug Discovery News', 'url' => 'https://www.drugdiscoverynews.com/rss'], ['source' => 'Labiotech', 'url' => 'https://www.labiotech.eu/feed/'], ['source' => 'BioWorld', 'url' => 'https://www.bioworld.com/rss'], ['source' => 'BioPharma Dive', 'url' => 'https://www.biopharmadive.com/rss/'], ['source' => 'Medical Xpress - Drug Discovery', 'url' => 'https://medicalxpress.com/rss-feed/drug-discovery-news/'], ['source' => 'Drug Target Review', 'url' => 'https://www.drugtargetreview.com/feed/'], ['source' => 'European Pharmaceutical Review', 'url' => 'https://www.europeanpharmaceuticalreview.com/feed/'], ['source' => 'Genetic Engineering & Biotechnology News', 'url' => 'https://www.genengnews.com/feed/'], ['source' => 'Endpoints News', 'url' => 'https://endpts.com/feed/'], ['source' => 'Fierce Biotech', 'url' => 'https://www.fiercebiotech.com/rss/biotech'], ['source' => 'Bioengineer.org', 'url' => 'https://bioengineer.org/feed/'], ['source' => 'ScienceDaily - Biotech', 'url' => 'https://www.sciencedaily.com/rss/plants_animals/biotechnology.xml'], ['source' => 'Phys.org - Biotech', 'url' => 'https://phys.org/rss-feed/biology-news/biotechnology/'], ['source' => 'MIT Tech Review - Biotech', 'url' => 'https://www.technologyreview.com/topic/biotechnology/rss/'], ['source' => 'Bio.News', 'url' => 'https://www.bio.news/feed/'], ['source' => 'BioCentury', 'url' => 'https://www.biocentury.com/rss/BioCentury.rss'], ['source' => 'Nature - Biotechnology', 'url' => 'https://www.nature.com/subjects/biotechnology.rss'], ['source' => 'Pharmaceutical Technology', 'url' => 'https://www.pharmaceutical-technology.com/feed/'], ['source' => 'Fierce Pharma', 'url' => 'https://www.fiercepharma.com/rss/pharma'], ['source' => 'NYT - Pharmaceuticals', 'url' => 'https://rss.nytimes.com/services/xml/rss/nyt/Drugs.xml'], ['source' => 'Economic Times - Pharma', 'url' => 'https://economictimes.indiatimes.com/industry/healthcare/biotech/rssfeeds/13358259.cms'], ['source' => 'Clinical Trials Arena', 'url' => 'https://www.clinicaltrialsarena.com/feed/'], ['source' => 'TrialSiteNews', 'url' => 'https://trialsitenews.com/feed/'], ['source' => 'GenomeWeb', 'url' => 'https://www.genomeweb.com/section/rss/news'], ['source' => 'CDC - Genomics', 'url' => 'https://blogs.cdc.gov/genomics/feed/'], ['source' => 'Drugs.com - Clinical Trials', 'url' => 'https://www.drugs.com/rss/clinical_trials.xml'], ['source' => 'Drugs.com - New Drugs', 'url' => 'https://www.drugs.com/rss/new_drug_approvals.xml'], ['source' => 'Cancer.gov - Trials', 'url' => 'https://www.cancer.gov/syndication/rss/nci-clinical-trials.rss'], ['source' => 'Outsourcing-Pharma', 'url' => 'https://www.outsourcing-pharma.com/feed/'], ['source' => 'BioPharma Reporter', 'url' => 'https://www.biopharma-reporter.com/feed/'], ['source' => 'Life Sciences Connect', 'url' => 'https://www.lifesciencesconnect.com/feed/'], ['source' => 'GEN - News', 'url' => 'https://www.genengnews.com/feed/'], ['source' => 'MedCity News', 'url' => 'https://medcitynews.com/feed/'], ['source' => 'Healthcare IT News', 'url' => 'https://www.healthcareitnews.com/rss.xml'], ['source' => 'MobiHealthNews', 'url' => 'https://www.mobihealthnews.com/feed/'], ['source' => 'HIMSS', 'url' => 'https://www.himss.org/news/rss.xml'], ['source' => 'Health Affairs', 'url' => 'https://www.healthaffairs.org/rss.xml'], ['source' => 'NEJM - Research', 'url' => 'https://www.nejm.org/rss/medical-articles.xml'], ['source' => 'The Lancet', 'url' => 'https://www.thelancet.com/rssfeed/lancet_current_issue.xml'], ['source' => 'JAMA Network', 'url' => 'https://jamanetwork.com/rss/site_3/67.xml'], ['source' => 'BMJ', 'url' => 'https://www.bmj.com/rss.xml'], ['source' => 'PLOS Medicine', 'url' => 'https://journals.plos.org/plosmedicine/feed/atom'], ['source' => 'PubMed - Latest', 'url' => 'https://pubmed.ncbi.nlm.nih.gov/rss/search/1nH-_ZcrhcXRArTxM9h_UEAOS8gzeGr9cPXpj9naNj5L6bp4/?limit=15&utm_campaign=pubmed-2&fc=20250101000000'], ['source' => 'Science Magazine', 'url' => 'https://www.science.org/rss/news_current.xml'], ['source' => 'Cell Press', 'url' => 'https://www.cell.com/rss/Current.xml'], ['source' => 'PNAS', 'url' => 'https://www.pnas.org/rss/News.xml'], ['source' => 'BioRxiv', 'url' => 'https://www.biorxiv.org/rss/recent.xml'], ['source' => 'MedRxiv', 'url' => 'https://www.medrxiv.org/rss/recent.xml'], ['source' => 'FDA News', 'url' => 'https://www.fdanews.com/feed'], ]; private int $articlesPerFetch = 4; private int $maxFetchAttempts = 3; private array $categories = [ 'AI & Digital Health' => [ 'artificial intelligence', 'ai ', 'machine learning', 'deep learning', 'neural network', 'digital health', 'digital therapeutics', 'health tech', 'digital medicine', 'algorithm', 'computational', 'in silico', 'bioinformatics', 'data science', 'predictive model', 'virtual screening', 'computer-aided drug design', ], 'Clinical Trials' => [ 'clinical trial', 'phase i', 'phase ii', 'phase iii', 'phase iv', 'phase 1', 'phase 2', 'phase 3', 'phase 4', 'patient enrollment', 'trial results', 'study results', 'clinical study', 'randomized controlled', 'double-blind', 'placebo-controlled', 'endpoint', 'fda approval', 'regulatory approval', 'pdufa', 'breakthrough therapy', 'fast track', 'priority review', 'orphan drug designation', ], 'Genomics & Gene Therapy' => [ 'genomics', 'genomic', 'genome', 'gene therapy', 'gene editing', 'crispr', 'dna', 'rna', 'mrna', 'genetic', 'genetics', 'sequencing', 'whole genome', 'car-t', 'cell therapy', 'stem cell', 'regenerative medicine', 'tissue engineering', 'personalized medicine', 'precision medicine', 'biomarker', 'companion diagnostic', ], 'Oncology' => [ 'cancer', 'oncology', 'oncologist', 'tumor', 'tumour', 'malignant', 'metastasis', 'chemotherapy', 'radiotherapy', 'immunotherapy', 'checkpoint inhibitor', 'car-t cell', 'antibody-drug conjugate', 'adc ', 'targeted therapy', 'solid tumor', 'hematology', 'leukemia', 'lymphoma', 'melanoma', 'carcinoma', ], 'Neurology & CNS' => [ 'neurology', 'neuroscience', 'brain', 'cns ', 'central nervous system', 'alzheimer', 'parkinson', 'epilepsy', 'multiple sclerosis', 'ms ', 'stroke', 'neurodegenerative', 'neuroprotection', 'cognitive', 'dementia', 'amyloid', 'psychiatry', 'depression', 'anxiety', 'schizophrenia', 'bipolar', 'mental health', ], 'Infectious Diseases' => [ 'infectious disease', 'antibiotic', 'antiviral', 'antimicrobial', 'antifungal', 'vaccine', 'vaccination', 'immunization', 'pandemic', 'epidemic', 'outbreak', 'covid', 'coronavirus', 'influenza', 'flu ', 'hiv ', 'aids', 'hepatitis', 'resistance', 'superbug', 'pathogen', 'bacteria', 'virus', 'fungal', ], 'Cardiovascular & Metabolic' => [ 'cardiovascular', 'heart', 'cardiac', 'hypertension', 'blood pressure', 'diabetes', 'diabetic', 'insulin', 'glucose', 'metabolic', 'obesity', 'weight loss', 'cholesterol', 'lipid', 'atherosclerosis', 'stroke', 'myocardial', 'heart failure', 'arrhythmia', 'anticoagulant', 'blood thinner', ], 'Rare Diseases' => [ 'rare disease', 'orphan drug', 'genetic disorder', 'inherited disease', 'sickle cell', 'hemophilia', 'cystic fibrosis', 'muscular dystrophy', 'lysosomal', ' enzyme replacement', 'gene therapy rare', ], 'Pharma Industry' => [ 'pharma', 'pharmaceutical', 'biotech', 'biopharma', 'merger', 'acquisition', 'partnership', 'collaboration', 'licensing deal', 'drug pricing', 'market access', 'blockbuster', 'patent', 'generic', 'biosimilar', 'cmo ', 'cro ', 'manufacturing', 'supply chain', 'drug shortage', 'recall', 'fda warning', 'complete response letter', ], 'Drug Discovery' => [ 'drug discovery', 'lead compound', 'hit identification', 'target identification', 'high-throughput screening', 'hts ', 'medicinal chemistry', 'structure-based', 'fragment-based', 'protein-protein interaction', 'kinase inhibitor', 'enzyme inhibitor', 'gpcr', 'antibody', 'monoclonal', 'bispecific', 'adc ', 'small molecule', 'biologic', ], 'Regulatory & Policy' => [ 'regulatory', 'fda ', 'ema ', 'ich ', 'guidance', 'compliance', 'gxp ', 'gmp ', 'validation', 'quality control', 'inspection', 'warning letter', '483 ', 'health policy', 'reimbursement', 'payer', 'healthcare reform', 'price regulation', ], ]; private array $generalKeywords = [ 'drug', 'drugs', 'medication', 'medicine', 'medicines', 'therapeutic', 'therapeutics', 'therapy', 'treatment', 'treatments', 'pharma', 'pharmaceutical', 'pharmaceuticals', 'biotech', 'biotechnology', 'medical', 'health', 'healthcare', 'patient', 'patients', 'disease', 'diseases', 'disorder', 'condition', 'diagnosis', 'symptom', 'symptoms', 'research', 'study', 'studies', 'finding', 'findings', 'breakthrough', 'innovation', 'development', 'pipeline', 'product', 'products', 'market', 'launch', 'commercial', ]; /** * Fetch news from all RSS feeds */ public function fetchNews(): Collection { $articles = collect(); foreach ($this->feeds as $feed) { try { $fetched = $this->fetchFromFeed($feed, $this->articlesPerFetch); $articles = $articles->merge($fetched); } catch (\Exception $e) { Log::error("Error fetching {$feed['source']}: " . $e->getMessage()); } } return $articles; } /** * Fetch more news if needed (for pagination) */ public function fetchMoreIfNeeded(int $neededCount): int { $beforeCount = News::count(); $attempts = 0; while ($attempts < $this->maxFetchAttempts && News::count() < $neededCount) { $attempts++; foreach ($this->feeds as $feed) { $this->fetchFromFeed($feed, $this->articlesPerFetch); } $afterCount = News::count(); if ($afterCount > $beforeCount) { break; } if ($attempts < $this->maxFetchAttempts) { sleep(2); } } return News::count() - $beforeCount; } /** * Save article for user */ public function saveArticle(int $userId, int $articleId): SavedArticle { return SavedArticle::firstOrCreate([ 'user_id' => $userId, 'news_id' => $articleId, ]); } /** * Share article (increment share count) */ public function shareArticle(int $articleId): void { $article = News::findOrFail($articleId); $article->increment('share_count'); } /** * Get articles with pagination and optional category filter */ public function getArticles(int $page = 1, int $perPage = 10, ?string $category = null): array { $skip = ($page - 1) * $perPage; $total = News::count(); if ($skip >= $total) { $this->fetchMoreIfNeeded($skip + $perPage); $total = News::count(); } $query = News::orderBy('published_at', 'desc'); if ($category) { $query->where(function ($q) use ($category) { $keywords = $this->categories[$category] ?? []; foreach ($keywords as $keyword) { $q->orWhere('title', 'like', '%' . $keyword . '%') ->orWhere('summary', 'like', '%' . $keyword . '%'); } }); } $news = $query->skip($skip)->take($perPage)->get(); return [ 'articles' => $news->map(fn($item) => NewsArticle::fromModel($item)), 'meta' => [ 'current_page' => $page, 'per_page' => $perPage, 'total' => $total, 'has_more' => ($skip + $perPage) < $total, ] ]; } /** * Get all available categories */ public function getCategories(): array { return array_merge(array_keys($this->categories), ['General Biotech & Pharma', 'Other']); } private function fetchFromFeed(array $feed, int $limit = 4): Collection { $articles = collect(); try { $response = Http::timeout(30)->withOptions(['verify' => false])->get($feed['url']); if (!$response->ok()) { throw new \Exception("HTTP " . $response->status()); } $body = $response->body(); if (empty($body)) { throw new \Exception("Empty response"); } $body = $this->cleanXml($body); $xml = simplexml_load_string($body, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOBLANKS); if (!$xml) { throw new \Exception("XML parse failed"); } $items = $this->getItems($xml); if (empty($items)) { throw new \Exception("No items found"); } $existingUrls = News::pluck('url')->toArray(); $existingUrls = array_flip($existingUrls); $count = 0; foreach ($items as $item) { if ($count >= $limit) break; $article = $this->parseItem($item, $feed['source'], $existingUrls); if (!$article) continue; try { $news = News::create($article); $articles->push(NewsArticle::fromModel($news)); $existingUrls[$article['url']] = true; $count++; } catch (\Exception $e) { Log::error("Insert failed: " . $e->getMessage()); } } } catch (\Exception $e) { Log::error("{$feed['source']}: " . $e->getMessage()); } return $articles; } private function cleanXml(string $body): string { $body = preg_replace('/&(?!#?[a-z0-9]+;)/i', '&', $body); $body = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $body); return $body; } private function getItems($xml): array { $items = []; if (isset($xml->channel->item)) { foreach ($xml->channel->item as $item) $items[] = $item; } elseif (isset($xml->item)) { foreach ($xml->item as $item) $items[] = $item; } elseif (isset($xml->entry)) { foreach ($xml->entry as $item) $items[] = $item; } return $items; } private function parseItem($item, string $source, array $existingUrls): ?array { $url = trim((string) ($item->link ?? $item->id ?? '')); if (empty($url) || isset($existingUrls[$url])) { return null; } $title = trim((string) ($item->title ?? '')); if (empty($title)) { return null; } $content = (string) ($item->description ?? ''); if (empty($content)) { $content = (string) ($item->children('content', true)->encoded ?? ''); } if (empty($content)) { $content = (string) ($item->summary ?? ''); } $cleanContent = strip_tags($content); if (empty(trim($cleanContent))) { return null; } $dateStr = (string) ($item->pubDate ?? $item->published ?? now()); try { $published = Carbon::parse($dateStr); } catch (\Exception $e) { $published = now(); } return [ 'title' => $title, 'summary' => $this->cleanText($cleanContent, 250), 'source' => $source, 'url' => $url, 'published_at' => $published, ]; } private function cleanText(string $text, int $limit): string { $text = strip_tags($text); $text = html_entity_decode($text); $text = preg_replace('/\s+/', ' ', $text); $text = trim($text); if (strlen($text) > $limit) { $text = substr($text, 0, $limit); $text = preg_replace('/\s+\S*$/', '', $text); $text .= '...'; } return $text; } }