/** * game3rb * base: https://game3rb.com * Creator: ShanMolvyr * reupload cantumkan sumber ini ygy * * Note: cek https://snippet.vyr.my.id/shanmolvyr/game3rb/README.md * Sumber: https://whatsapp.com/channel/0029VbB4Kw8EFeXfeExaXc3Q */ const axios = require('axios'); const cheerio = require('cheerio'); const BASE = 'https://game3rb.com'; const http = axios.create({ baseURL: BASE, timeout: 15000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.9', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', }, maxRedirects: 5, }); const ICON_PATHS = ['file-edit', 'database', 'user-1', 'Clock', 'tag', '/T.', '/R.', 'spinner', 'placeholder', 'discord', 'join-us']; function parseCards($) { return $('article.post-hentry').map((_, el) => { const $el = $(el); const titleEl = $el.find('h3.entry-title a, h2.entry-title a').first(); const title = titleEl.text().trim(); const url = titleEl.attr('href') || ''; if (!title || !url) return null; return { postId: ($el.attr('id') || '').replace('post-', '') || null, title, slug: url.replace(BASE, '').replace(/^\/|\/$/g, ''), url, thumbnail: $el.find('img.entry-image').attr('src') || null, date: $el.find('time.entry-date').attr('datetime') || null, categories: $el.find('span.entry-categories-inner a.entry-category').map((_, a) => $(a).text().trim()).get(), badges: $el.find('#carts #cart').map((_, b) => $(b).text().trim()).get().filter(t => t && t !== 'Request'), excerpt: $el.find('.summaryy').text().replace(/\s+/g, ' ').trim() || null, }; }).get().filter(Boolean); } function parsePagination($) { const nums = $('a.page-numbers').map((_, el) => parseInt($(el).text().trim())).get().filter(n => !isNaN(n)); return nums.length ? Math.max(...nums) : 1; } function parseInfo($) { const info = {}; $('.entry-content span.uk-text-meta, .post-content span.uk-text-meta').each((_, el) => { const m = $(el).text().trim().match(/^([^:]+):\s*(.+)$/); if (m) info[m[1].trim().toLowerCase().replace(/\s+/g, '_')] = m[2].trim(); }); return info; } function parseCategories($) { const cats = []; const seen = new Set(); $('select.selectnav option[value*="/category/"], select#selectnav1 option[value*="/category/"]').each((_, el) => { const name = $(el).text().replace(/^-\s*/, '').trim(); const url = $(el).attr('value'); if (name && url && !seen.has(url)) { seen.add(url); cats.push({ name, url }); } }); if (!cats.length) { $('ul.sub-menu a[href*="/category/"], nav a[href*="/category/"]').each((_, el) => { const name = $(el).text().trim(); const url = $(el).attr('href'); if (name && url && !seen.has(url)) { seen.add(url); cats.push({ name, url }); } }); } return cats; } async function getHome() { const { data } = await http.get('/'); const $ = cheerio.load(data); return { latest: parseCards($), categories: parseCategories($) }; } async function search(query) { const { data } = await http.get('/', { params: { s: query } }); return { query, results: parseCards(cheerio.load(data)) }; } async function getCategory(categorySlug, page = 1) { const path = page > 1 ? `/category/${categorySlug}/page/${page}/` : `/category/${categorySlug}/`; const { data } = await http.get(path); const $ = cheerio.load(data); return { category: categorySlug, page, totalPages: parsePagination($), results: parseCards($) }; } async function getGame(slugOrUrl) { const path = slugOrUrl.startsWith('http') ? slugOrUrl.replace(BASE, '') : `/${slugOrUrl}/`; const { data } = await http.get(path); const $ = cheerio.load(data); const seen = new Set(); const categories = $('article[id^="post-"] span.entry-categories-inner a.entry-category, .entry-header span.entry-categories-inner a') .map((_, a) => ({ name: $(a).text().trim(), url: $(a).attr('href') || '' })) .get() .filter(c => c.name && c.url && !seen.has(c.url) && seen.add(c.url)); const screenshots = [...new Set( $('.entry-content img, .post-content img') .map((_, el) => $(el).attr('src') || $(el).attr('data-lazy-src') || '') .get() .filter(src => src && !ICON_PATHS.some(p => src.includes(p)) && !($(`.entry-content img[src="${src}"]`).attr('width') && parseInt($(`.entry-content img[src="${src}"]`).attr('width')) < 50)) )]; const downloadLinks = $('a#download-link') .map((_, el) => { const url = $(el).attr('href'); if (!url || url === '#' || url.startsWith('javascript')) return null; const label = $(el).closest('div').prev().find('strong, h3, h4').last().text().trim() || $(el).text().trim() || 'Download'; return { label, url }; }) .get() .filter(Boolean); return { title: $('h1.post-title, h1.entry-title').first().text().trim(), postId: $('article[id^="post-"]').first().attr('id')?.replace('post-', '') || null, slug: slugOrUrl.startsWith('http') ? slugOrUrl.replace(BASE + '/', '').replace(/\/$/, '') : slugOrUrl, url: `${BASE}${path}`, date: $('time.entry-date, time.updated').first().attr('datetime') || null, thumbnail: $('h4.separator img, .entry-content img').first().attr('src') || $('h4.separator img, .entry-content img').first().attr('data-lazy-src') || null, categories, info: parseInfo($), downloadLinks, screenshots, }; } async function getPopular() { const { data } = await http.get('/'); const $ = cheerio.load(data); return $('.wpp-list li a, [id*="wordpress-popular-posts"] a') .map((_, el) => { const title = $(el).text().trim(); const url = $(el).attr('href') || ''; if (!title || !url.startsWith(BASE)) return null; return { title, url, slug: url.replace(BASE + '/', '').replace(/\/$/, '') }; }) .get() .filter(Boolean); } async function getUpdates(page = 1) { return getCategory('game-updates', page); } async function getOnlineGames(page = 1) { return getCategory('games-online', page); } async function getPCGames(page = 1) { return getCategory('pc-games', page); } const CATEGORIES = { action: 'pc-games/action', adventure: 'pc-games/adventure', rpg: 'pc-games/rpg', simulation: 'pc-games/simulation', strategy: 'pc-games/strategy', horror: 'pc-games/horror', racing: 'pc-games/racing', sports: 'pc-games/sports', indie: 'pc-games/indie', casual: 'pc-games/casual', vr: 'pc-games/vr', gore: 'pc-games/gore', early_access: 'pc-games/early-access', violent: 'pc-games/violent', nostalgia: 'pc-games/nostalgia-games', online: 'games-online', repack: 'pc-repack', updates: 'game-updates', }; module.exports = { getHome, search, getGame, getCategory, getPopular, getUpdates, getOnlineGames, getPCGames, CATEGORIES }; if (require.main === module) { const [,, cmd, ...args] = process.argv; const run = async () => { if (!cmd) { process.stderr.write('Usage: node index.js [args]\nCommands: home | search | game | category [page] | popular | updates [page] | online [page] | pcgames [page]\n'); return; } const map = { home: () => getHome(), search: () => search(args.join(' ')), game: () => getGame(args[0]), category: () => getCategory(args[0], parseInt(args[1]) || 1), popular: () => getPopular(), updates: () => getUpdates(parseInt(args[0]) || 1), online: () => getOnlineGames(parseInt(args[0]) || 1), pcgames: () => getPCGames(parseInt(args[0]) || 1), }; if (!map[cmd]) { process.stderr.write(`Unknown command: ${cmd}\n`); return; } process.stdout.write(JSON.stringify(await map[cmd](), null, 2) + '\n'); }; run().catch(e => { process.stderr.write(e.message + '\n'); process.exit(1); }); }