Spaces:
Sleeping
Sleeping
| const express = require('express'); | |
| const axios = require('axios'); | |
| const cors = require('cors'); | |
| require('dotenv').config(); | |
| const app = express(); | |
| app.use(cors()); | |
| // ββ Embedded Frontend ββββββββββββββββββββββββββββββββββββββββββββββ | |
| app.get('/', (req, res) => { | |
| res.send(`<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"/> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>Trendscout β Find Profitable Product Ideas Fast</title> | |
| <style> | |
| :root{--bg:#0f0f13;--card:#1a1a24;--accent:#7c3aed;--accent2:#a855f7;--text:#f1f1f5;--muted:#9ca3af;--green:#22c55e;--border:#2a2a3a} | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| body{background:var(--bg);color:var(--text);font-family:'Segoe UI',system-ui,sans-serif;min-height:100vh} | |
| header{background:linear-gradient(135deg,#1e0a3c,#2d1464);padding:2rem 1rem;text-align:center;border-bottom:1px solid var(--border)} | |
| header h1{font-size:2rem;font-weight:800;background:linear-gradient(90deg,#a78bfa,#ec4899);-webkit-background-clip:text;-webkit-text-fill-color:transparent} | |
| header p{color:var(--muted);margin-top:.5rem;font-size:.95rem} | |
| .badge{display:inline-block;background:#7c3aed22;color:#a78bfa;border:1px solid #7c3aed44;border-radius:20px;padding:.2rem .8rem;font-size:.75rem;margin-top:.5rem} | |
| .container{max-width:900px;margin:0 auto;padding:2rem 1rem} | |
| .search-bar{display:flex;gap:.75rem;margin-bottom:2rem;flex-wrap:wrap} | |
| .search-bar input{flex:1;min-width:200px;background:var(--card);border:1px solid var(--border);color:var(--text);padding:.85rem 1.2rem;border-radius:10px;font-size:1rem;outline:none;transition:border .2s} | |
| .search-bar input:focus{border-color:var(--accent2)} | |
| .search-bar button{background:linear-gradient(135deg,#7c3aed,#a855f7);color:#fff;border:none;padding:.85rem 1.8rem;border-radius:10px;font-size:1rem;font-weight:700;cursor:pointer;transition:opacity .2s;white-space:nowrap} | |
| .search-bar button:hover{opacity:.88} | |
| .search-bar button:disabled{opacity:.5;cursor:not-allowed} | |
| .sources{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1.5rem} | |
| .src-btn{background:var(--card);border:1px solid var(--border);color:var(--muted);padding:.4rem 1rem;border-radius:20px;font-size:.8rem;cursor:pointer;transition:all .2s} | |
| .src-btn.active{background:#7c3aed22;border-color:var(--accent);color:#a78bfa} | |
| .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(270px,1fr));gap:1rem} | |
| .card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:1.25rem;transition:border .2s} | |
| .card:hover{border-color:var(--accent)} | |
| .card-source{font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-bottom:.5rem;display:flex;align-items:center;gap:.4rem} | |
| .dot{width:7px;height:7px;border-radius:50%;display:inline-block} | |
| .dot.github{background:#22c55e}.dot.huggingface{background:#f59e0b}.dot.reddit{background:#f97316} | |
| .card h3{font-size:.9rem;font-weight:600;margin-bottom:.4rem;line-height:1.4;word-break:break-word} | |
| .card .meta{font-size:.78rem;color:var(--muted);margin-bottom:.8rem} | |
| .card a{display:inline-block;font-size:.78rem;color:#a78bfa;text-decoration:none;border:1px solid #7c3aed44;padding:.3rem .7rem;border-radius:6px;transition:background .2s} | |
| .card a:hover{background:#7c3aed22} | |
| .section-title{font-size:.85rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin:1.5rem 0 .75rem;border-bottom:1px solid var(--border);padding-bottom:.5rem} | |
| .status{text-align:center;color:var(--muted);padding:3rem 1rem;font-size:.95rem} | |
| .spinner{display:inline-block;width:32px;height:32px;border:3px solid #2a2a3a;border-top-color:#a855f7;border-radius:50%;animation:spin .7s linear infinite;margin-bottom:1rem} | |
| @keyframes spin{to{transform:rotate(360deg)}} | |
| .error-msg{background:#3b0a0a;border:1px solid #7f1d1d;color:#fca5a5;padding:1rem;border-radius:8px;margin-bottom:1rem;font-size:.85rem} | |
| footer{text-align:center;padding:2rem;color:var(--muted);font-size:.75rem;border-top:1px solid var(--border);margin-top:3rem} | |
| @media(max-width:500px){header h1{font-size:1.5rem}} | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>π Trendscout</h1> | |
| <p>Find profitable digital product ideas β live from GitHub, HuggingFace & Reddit</p> | |
| <span class="badge">Real-time trend intelligence</span> | |
| </header> | |
| <div class="container"> | |
| <div class="search-bar"> | |
| <input id="query" type="text" placeholder="Enter your niche (e.g. wellness, AI tools, fitnessβ¦)" value="wellness" aria-label="Niche keyword"/> | |
| <button id="scanBtn" onclick="scan()">π Scan Trends</button> | |
| </div> | |
| <div class="sources"> | |
| <span class="src-btn active" data-src="github">β‘ GitHub</span> | |
| <span class="src-btn active" data-src="huggingface">π€ HuggingFace</span> | |
| <span class="src-btn active" data-src="reddit">π¬ Reddit</span> | |
| </div> | |
| <div id="results"><p class="status">Enter a niche above and hit <strong>Scan Trends</strong> to find what's hot right now.</p></div> | |
| </div> | |
| <footer>Trendscout β Built for digital creators, coaches & entrepreneurs. Stop guessing. Start building. π</footer> | |
| <script> | |
| const activeSources=new Set(['github','huggingface','reddit']); | |
| document.querySelectorAll('.src-btn').forEach(b=>{ | |
| b.addEventListener('click',()=>{ | |
| const s=b.dataset.src; | |
| if(activeSources.has(s)){if(activeSources.size>1){activeSources.delete(s);b.classList.remove('active');}} | |
| else{activeSources.add(s);b.classList.add('active');} | |
| }); | |
| }); | |
| async function scan(){ | |
| const q=document.getElementById('query').value.trim()||'wellness'; | |
| const btn=document.getElementById('scanBtn'); | |
| btn.disabled=true; btn.textContent='Scanningβ¦'; | |
| document.getElementById('results').innerHTML='<div class="status"><div class="spinner"></div><br/>Scanning live sources for <strong>'+q+'</strong>β¦</div>'; | |
| const fetches=[]; | |
| if(activeSources.has('github')) fetches.push(fetch('/api/github?topic='+encodeURIComponent(q)).then(r=>r.json()).then(d=>({src:'github',items:d})).catch(()=>({src:'github',items:[]}))); | |
| if(activeSources.has('huggingface')) fetches.push(fetch('/api/huggingface?topic='+encodeURIComponent(q)).then(r=>r.json()).then(d=>({src:'huggingface',items:d})).catch(()=>({src:'huggingface',items:[]}))); | |
| if(activeSources.has('reddit')) fetches.push(fetch('/api/reddit?topic='+encodeURIComponent(q)).then(r=>r.json()).then(d=>({src:'reddit',items:d})).catch(()=>({src:'reddit',items:[]}))); | |
| const results=await Promise.all(fetches); | |
| let html=''; | |
| const colors={github:'#22c55e',huggingface:'#f59e0b',reddit:'#f97316'}; | |
| const labels={github:'GitHub Trending',huggingface:'HuggingFace Models',reddit:'Reddit Buzz'}; | |
| let totalItems=0; | |
| results.forEach(({src,items})=>{ | |
| if(!items||!items.length) return; | |
| totalItems+=items.length; | |
| html+='<div class="section-title"><span class="dot '+src+'" style="background:'+colors[src]+'"></span> '+labels[src]+'</div><div class="grid">'; | |
| items.forEach(item=>{ | |
| html+='<div class="card"><div class="card-source"><span class="dot '+src+'" style="background:'+colors[src]+'"></span>'+src.toUpperCase()+'</div>'; | |
| html+='<h3>'+escHtml(item.title)+'</h3><div class="meta">'+escHtml(item.meta||'')+'</div>'; | |
| html+='<a href="'+escHtml(item.url)+'" target="_blank" rel="noopener">View Source β</a></div>'; | |
| }); | |
| html+='</div>'; | |
| }); | |
| if(!totalItems) html='<p class="status">No results found for <strong>'+escHtml(q)+'</strong>. Try a broader keyword.</p>'; | |
| document.getElementById('results').innerHTML=html; | |
| btn.disabled=false; btn.textContent='π Scan Trends'; | |
| } | |
| function escHtml(s){ | |
| return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); | |
| } | |
| // Auto-scan on load | |
| window.addEventListener('DOMContentLoaded',()=>scan()); | |
| </script> | |
| </body> | |
| </html>`); | |
| }); | |
| // ββ API Proxies βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app.get('/api/github', async (req, res) => { | |
| const topic = req.query.topic || 'wellness'; | |
| try { | |
| const since = new Date(Date.now() - 30*24*60*60*1000).toISOString().split('T')[0]; | |
| const q = `${topic} created:>${since}`; | |
| const response = await axios.get('https://api.github.com/search/repositories', { | |
| params: { q, sort: 'stars', order: 'desc', per_page: 6 }, | |
| headers: { 'User-Agent': 'Trendscout/1.0', ...(process.env.GITHUB_TOKEN ? { Authorization: `token ${process.env.GITHUB_TOKEN}` } : {}) } | |
| }); | |
| const items = (response.data.items || []).map(r => ({ | |
| title: r.full_name, | |
| url: r.html_url, | |
| meta: `β ${r.stargazers_count.toLocaleString()} stars Β· ${r.language || 'multi'} Β· ${r.description ? r.description.slice(0,60)+'β¦' : ''}` | |
| })); | |
| res.json(items); | |
| } catch (err) { | |
| res.status(500).json([]); | |
| } | |
| }); | |
| app.get('/api/huggingface', async (req, res) => { | |
| const topic = req.query.topic || 'wellness'; | |
| try { | |
| const response = await axios.get('https://huggingface.co/api/models', { | |
| params: { search: topic, sort: 'likes', direction: '-1', limit: 6 } | |
| }); | |
| const items = (response.data || []).map(m => ({ | |
| title: m.id, | |
| url: `https://huggingface.co/${m.id}`, | |
| meta: `β₯ ${m.likes ?? 0} likes Β· ${(m.tags||[]).slice(0,3).join(', ')}` | |
| })); | |
| res.json(items); | |
| } catch (err) { | |
| res.status(500).json([]); | |
| } | |
| }); | |
| app.get('/api/reddit', async (req, res) => { | |
| const topic = req.query.topic || 'wellness'; | |
| try { | |
| const response = await axios.get(`https://www.reddit.com/r/Entrepreneur/search.json`, { | |
| params: { q: topic, restrict_sr: 1, sort: 'top', t: 'month', limit: 6 }, | |
| headers: { 'User-Agent': 'Trendscout/1.0' } | |
| }); | |
| const items = (response.data.data?.children || []).map(p => ({ | |
| title: p.data.title, | |
| url: 'https://reddit.com' + p.data.permalink, | |
| meta: `β² ${p.data.ups.toLocaleString()} upvotes Β· ${p.data.num_comments} comments` | |
| })); | |
| res.json(items); | |
| } catch (err) { | |
| res.status(500).json([]); | |
| } | |
| }); | |
| const PORT = process.env.PORT || 7860; | |
| app.listen(PORT, () => console.log(`β Trendscout running on port ${PORT}`)); | |