/** * @fileoverview Modul Scraper GitHub Trending Repositories. * * Mengimplementasikan web scraper ringan untuk mengekstrak daftar repositori yang sedang tren di GitHub. * Dilengkapi dengan penanganan kesalahan otomatis, fallback data analitik, dan parsing elemen DOM * yang efisien tanpa ketergantungan library eksternal yang berat. * * @module scrapers/github-trending * @version 1.1.0 * @author kyiov * @license MIT */ import * as cheerio from 'cheerio' import { pathToFileURL } from 'node:url' /** * @typedef {Object} TrendingRepo * @property {string} title - Nama lengkap repositori (owner/repo) * @property {string} desc - Deskripsi repositori * @property {string} lang - Bahasa pemrograman utama * @property {string} starsToday - Jumlah perolehan bintang hari ini */ /** * Mengambil daftar repositori trending dari GitHub secara asinkron. * * @param {number} [limit=5] - Jumlah repositori maksimum yang ingin diekstrak * @returns {Promise} Daftar repositori yang sedang tren */ export async function fetchGitHubTrending(limit = 5) { try { const res = await fetch('https://github.com/trending', { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' }, signal: AbortSignal.timeout(8000) }) if (!res.ok) throw new Error(`HTTP ${res.status}`) const html = await res.text() const $ = cheerio.load(html) const repos = [] $('article.Box-row').slice(0, limit).each((_, el) => { const rawTitle = $(el).find('h2 a').text().replace(/\s+/g, ' ').trim() const title = rawTitle.replace(/\s*\/\s*/g, '/') const desc = $(el).find('p').text().replace(/\s+/g, ' ').trim() const lang = $(el).find('[itemprop="programmingLanguage"]').text().trim() || 'Tech' const starsToday = $(el).find('span.d-inline-block.float-sm-right').text().replace(/\s+/g, ' ').trim() || '' if (title) { repos.push({ title, desc: desc || 'Proyek open-source populer di GitHub', lang, starsToday }) } }) if (repos.length > 0) return repos } catch (e) { console.warn('[GitHub Trending Scraper] Galat jaringan/DOM, beralih ke fallback:', e.message) } return [ { title: 'Open-source AI Tools', desc: 'Perkembangan pesat model reasoning AI & tool coding otonom di komunitas open-source.', lang: 'AI / Python', starsToday: '🔥 Trending' }, { title: 'Modern Web & Lightweight Frameworks', desc: 'Tren transisi ke framework fullstack yang lebih cepat dengan memory footprint kecil.', lang: 'TypeScript / Rust', starsToday: '🔥 Trending' }, { title: 'Local LLM & Edge Inference', desc: 'Inovasi menjalankan model AI besar langsung di laptop/HP tanpa GPU server mahal.', lang: 'C++ / ONNX', starsToday: '🔥 Trending' } ] } export default { fetchGitHubTrending } // Eksekusi mandiri untuk pengujian langsung tanpa argumen CLI if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { try { const repos = await fetchGitHubTrending(3) console.log(JSON.stringify(repos, null, 2)) } catch (err) { console.error('Galat eksekusi:', err.message || err) } }