/*** @ Base: https://itch.io/ @ Author: Shannz @ Note: Search game and get detail ***/ import axios from 'axios'; import * as cheerio from 'cheerio'; import { wrapper } from 'axios-cookiejar-support'; import { CookieJar } from 'tough-cookie'; const BASE_URL = 'https://itch.io'; const jar = new CookieJar(); const client = wrapper(axios.create({ jar, withCredentials: true, 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,image/avif,image/webp,image/apng,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.9', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-User': '?1' } })); const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const parseGameList = ($) => { const games = []; $('.game_cell').each((i, el) => { const item = $(el); const title = item.find('.game_title a').text().trim(); const link = item.find('.game_title a').attr('href'); const desc = item.find('.game_text').text().trim(); const author = item.find('.game_author a').text().trim(); const thumbEl = item.find('.game_thumb img'); const thumbnail = thumbEl.attr('data-lazy_src') || thumbEl.attr('src'); let price = "Free/NYP"; if (item.find('.price_value').length > 0) price = item.find('.price_value').text().trim(); else if (item.find('.sale_tag').length > 0) price = "On Sale"; games.push({ title, price, author, description: desc, link, thumbnail }); }); return games; }; export const itch = { home: async () => { try { const { data } = await client.get(`${BASE_URL}/games`, { headers: { 'Referer': 'https://itch.io/' } }); const $ = cheerio.load(data); const games = parseGameList($); return { games }; } catch (error) { console.error('❌ Error Home:', error.message); return null; } }, detail: async (gameUrl) => { if (!gameUrl) return null; try { const { data } = await client.get(gameUrl, { headers: { 'Referer': `${BASE_URL}/games` } }); const $ = cheerio.load(data); const title = $('h1.game_title').text().trim() || $('meta[property="og:title"]').attr('content'); const short_desc = $('meta[name="description"]').attr('content'); const cover_image = $('meta[property="og:image"]').attr('content'); let price_info = "Free"; const buyBtn = $('.buy_btn'); if (buyBtn.length > 0) { price_info = buyBtn.text().trim(); } const meta_info = {}; $('.game_info_panel_widget table tr').each((i, el) => { const key = $(el).find('td:nth-child(1)').text().trim(); const val = $(el).find('td:nth-child(2)').text().trim(); if (key) meta_info[key] = val; }); const available_files = []; $('.upload_list_widget .upload').each((i, el) => { const row = $(el); const fileName = row.find('.upload_name strong').text().trim(); const fileSize = row.find('.file_size span').text().trim() || "Unknown size"; const version = row.find('.upload_name .version_name').text().trim(); const platforms = []; row.find('.upload_platforms span, .upload_traits span').each((_, icon) => { const titleAttr = $(icon).attr('title'); if (titleAttr) { platforms.push(titleAttr.replace('Download for ', '').replace('Play in ', '')); } }); let downloadUrl = "Locked/Paid Only"; const lightboxBtn = row.find('.download_btn'); const uploadId = row.attr('data-upload_id'); if (lightboxBtn.length > 0) { const cleanBaseUrl = gameUrl.replace(/\/$/, ""); if (uploadId) { downloadUrl = `${cleanBaseUrl}/file/${uploadId}`; } } else if (row.find('.buy_btn').length > 0) { downloadUrl = "Must Purchase First"; } available_files.push({ id: uploadId || 'unknown', file_name: fileName, version: version || null, size: fileSize, platforms: platforms.length > 0 ? platforms : ['Universal/Unknown'], download_page: downloadUrl }); }); const screenshots = []; $('.screenshot_list a').each((i, el) => { const shot = $(el).attr('href'); if (shot) screenshots.push(shot); }); const links = []; $('.right_col .links a').each((i, el) => { links.push({ text: $(el).text(), url: $(el).attr('href') }); }); return { title, price_info, short_desc, cover_image, meta_info, links, screenshots, file_count: available_files.length, available_files }; } catch (error) { console.error(`❌ Gagal Detail: ${error.message}`); return null; } } };