{article.title}
{article.description.replace(/<[^>]*>?/gm, '').substring(0, 140)}...
import React, { useState, useEffect, useMemo } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, collection, doc, onSnapshot, setDoc, deleteDoc } from 'firebase/firestore'; import { Rss, Plus, Trash2, ExternalLink, Search, RefreshCw, BookOpen, X, Calendar, Clock, Filter, User, Moon, Sun, LayoutGrid, Hash, ChevronDown, Bookmark, History, Globe, Zap, ShieldAlert } from 'lucide-react'; // Firebase configuration from environment const firebaseConfig = JSON.parse(__firebase_config); const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); const appId = typeof __app_id !== 'undefined' ? __app_id : 'mlu-feed-pro'; const INITIAL_FEEDS = [ { id: '1', name: 'XDA Developers', url: 'https://www.xda-developers.com/tag/rss/', category: 'Tech', type: 'rss' }, { id: '2', name: 'The Verge', url: 'https://www.theverge.com/rss/index.xml', category: 'News', type: 'rss' } ]; const App = () => { const [user, setUser] = useState(null); const [feeds, setFeeds] = useState([]); const [savedArticles, setSavedArticles] = useState([]); const [articles, setArticles] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [newFeedUrl, setNewFeedUrl] = useState(''); const [feedType, setFeedType] = useState('rss'); // 'rss' or 'scrape' const [searchTerm, setSearchTerm] = useState(''); const [selectedArticle, setSelectedArticle] = useState(null); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState('all'); const [timeFilter, setTimeFilter] = useState('any'); const [isDarkMode, setIsDarkMode] = useState(false); const [viewMode, setViewMode] = useState('grid'); // Authentication useEffect(() => { const initAuth = async () => { try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } } catch (err) { setError("Auth failed. Local mode enabled."); } }; initAuth(); const unsubscribe = onAuthStateChanged(auth, setUser); return () => unsubscribe(); }, []); // Firestore Sync useEffect(() => { if (!user) return; const feedsRef = collection(db, 'artifacts', appId, 'users', user.uid, 'feeds'); const unsubFeeds = onSnapshot(feedsRef, (snapshot) => { const feedsList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); if (feedsList.length === 0) { INITIAL_FEEDS.forEach(async (f) => { await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'feeds', f.id), f); }); } else { setFeeds(feedsList); } setLoading(false); }); const savedRef = collection(db, 'artifacts', appId, 'users', user.uid, 'savedArticles'); const unsubSaved = onSnapshot(savedRef, (snapshot) => { setSavedArticles(snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))); }); return () => { unsubFeeds(); unsubSaved(); }; }, [user]); // RSS & Scrape Engine const fetchArticles = async () => { if (feeds.length === 0) return; setRefreshing(true); try { const fetchPromises = feeds.map(async (feed) => { // Option 1: Standard RSS if (feed.type === 'rss') { const response = await fetch(`https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(feed.url)}`); const data = await response.json(); if (data.status === 'ok') { return data.items.map(item => ({ ...item, sourceName: feed.name, sourceId: feed.id, category: feed.category || 'General', timestamp: new Date(item.pubDate).getTime() })); } } // Option 2: Web Scraper (Integrated into the app logic) else if (feed.type === 'scrape') { const response = await fetch(`https://api.allorigins.win/get?url=${encodeURIComponent(feed.url)}`); const data = await response.json(); const html = data.contents; const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); // Enhanced Scraper Logic: Finding actual content containers const selectors = ['article', '.post', '.entry', '.card', '.item']; let items = []; selectors.forEach(selector => { if (items.length > 0) return; const found = doc.querySelectorAll(selector); if (found.length > 3) items = Array.from(found); }); // Fallback to searching for substantial links if (items.length === 0) { items = Array.from(doc.querySelectorAll('a')) .filter(a => a.innerText.trim().length > 30) .slice(0, 15); } return items.map((el, idx) => { const link = el.querySelector('a') || (el.tagName === 'A' ? el : null); const title = el.querySelector('h1, h2, h3, h4, .title') || el; const img = el.querySelector('img'); return { title: title.innerText.trim(), link: link ? (link.href.startsWith('http') ? link.href : new URL(feed.url).origin + link.getAttribute('href')) : feed.url, pubDate: new Date().toISOString(), thumbnail: img ? img.src : null, description: "Live content extracted via Mlu's Scraping Engine.", sourceName: feed.name, sourceId: feed.id, timestamp: Date.now() - (idx * 3600000) }; }).filter(item => item.title && item.link); } return []; }); const results = await Promise.all(fetchPromises); setArticles(results.flat().sort((a, b) => b.timestamp - a.timestamp)); } catch (err) { setError('Connection error. Some sites might be blocking external access.'); } finally { setRefreshing(false); } }; useEffect(() => { if (feeds.length > 0) fetchArticles(); }, [feeds]); const addFeed = async (e) => { e.preventDefault(); if (!newFeedUrl || !user) return; try { const url = new URL(newFeedUrl); const id = Date.now().toString(); const newFeed = { id, name: url.hostname.replace('www.', '').split('.')[0], url: newFeedUrl, category: feedType === 'rss' ? 'Tech' : 'Custom', type: feedType }; await setDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'feeds', id), newFeed); setNewFeedUrl(''); } catch { setError('Please enter a valid URL'); } }; const removeFeed = async (id) => { if (!user || feeds.length <= 1) return; await deleteDoc(doc(db, 'artifacts', appId, 'users', user.uid, 'feeds', id)); }; const toggleSaveArticle = async (e, article) => { e.stopPropagation(); if (!user) return; const articleId = btoa(article.link).substring(0, 50); const isSaved = savedArticles.some(a => a.link === article.link); try { const savedDocRef = doc(db, 'artifacts', appId, 'users', user.uid, 'savedArticles', articleId); if (isSaved) await deleteDoc(savedDocRef); else await setDoc(savedDocRef, { ...article, savedAt: Date.now() }); } catch (err) { setError("Storage error."); } }; const filteredArticles = useMemo(() => { const baseList = activeTab === 'saved' ? savedArticles : articles; const now = Date.now(); const limits = { today: 86400000, week: 604800000, month: 2592000000 }; return baseList.filter(a => { const matchSearch = a.title.toLowerCase().includes(searchTerm.toLowerCase()); const matchTab = activeTab === 'all' || activeTab === 'saved' || a.sourceId === activeTab; const matchDate = timeFilter === 'any' || (now - a.timestamp) < limits[timeFilter]; return matchSearch && matchTab && matchDate; }); }, [articles, savedArticles, searchTerm, activeTab, timeFilter]); if (loading) return (
{article.description.replace(/<[^>]*>?/gm, '').substring(0, 140)}...
Try refreshing your feeds or checking your scraper sources.