const express = require('express'); const { connect } = require("puppeteer-real-browser"); const fs = require('fs'); const path = require('path'); const app = express(); const port = process.env.PORT || 7860; const authToken = process.env.authToken || null; // Set null biar gak perlu auth const domain = process.env.DOMAIN || `https://fourstore-cf.hf.space`; global.browserLimit = Number(process.env.browserLimit) || 20; global.timeOut = Number(process.env.timeOut) || 60000; const CACHE_DIR = path.join(__dirname, "cache"); const CACHE_FILE = path.join(CACHE_DIR, "cache.json"); const CACHE_TTL = 5 * 60 * 1000; function loadCache() { if (!fs.existsSync(CACHE_FILE)) return {}; try { return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8')); } catch { return {}; } } function saveCache(cache) { if (!fs.existsSync(CACHE_DIR)) { fs.mkdirSync(CACHE_DIR, { recursive: true }); } fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf-8'); } function readCache(key) { const cache = loadCache(); const entry = cache[key]; if (entry && Date.now() - entry.timestamp < CACHE_TTL) { return entry.value; } return null; } function writeCache(key, value) { const cache = loadCache(); cache[key] = { timestamp: Date.now(), value }; saveCache(cache); } app.use(express.json()); app.use(express.urlencoded({ extended: true })); // ROUTE UTAMA APP.GET("/") app.get("/", (req, res) => { res.json({ message: "Server is running!", domain: domain, endpoints: { cloudflare: `${domain}/cloudflare`, turnstile: `${domain}/cloudflare`, recaptcha: `${domain}/cloudflare` }, status: { browserLimit: global.browserLimit, timeOut: global.timeOut, authRequired: authToken !== null } }); }); if (process.env.NODE_ENV !== 'development') { let server = app.listen(port, () => { console.log(`Server running on port ${port}`); console.log(`Domain: ${domain}`); console.log(`Auth required: ${authToken !== null}`); }); try { server.timeout = global.timeOut; } catch {} } async function createBrowser(proxyServer = null) { const connectOptions = { headless: false, turnstile: true, connectOption: { defaultViewport: null }, disableXvfb: false, }; if (proxyServer) connectOptions.args = [`--proxy-server=${proxyServer}`]; const { browser } = await connect(connectOptions); const [page] = await browser.pages(); await page.goto('about:blank'); await page.setRequestInterception(true); page.on('request', (req) => { const type = req.resourceType(); if (["image", "stylesheet", "font", "media"].includes(type)) req.abort(); else req.continue(); }); return { browser, page }; } const turnstile = require('./endpoints/turnstile'); const cloudflare = require('./endpoints/cloudflare'); const recaptchaV2 = require('./endpoints/recaptchav2'); app.post('/cloudflare', async (req, res) => { const data = req.body; if (!data || typeof data.mode !== 'string') return res.status(400).json({ message: 'Bad Request: missing or invalid mode' }); // COMMENT/HAPUS pengecekan auth token biar gak perlu auth // if (authToken && data.authToken !== authToken) // return res.status(401).json({ message: 'Unauthorized' }); if (global.browserLimit <= 0) return res.status(429).json({ message: 'Too Many Requests' }); let cacheKey, cached; if (data.mode === "iuam" || data.mode === "recaptcha") { cacheKey = JSON.stringify(data); cached = readCache(cacheKey); if (cached) return res.status(200).json({ ...cached, cached: true }); } global.browserLimit--; let result, browser; try { const proxyServer = data.proxy ? `${data.proxy.hostname}:${data.proxy.port}` : null; const ctx = await createBrowser(proxyServer); browser = ctx.browser; const page = ctx.page; await page.goto('about:blank'); switch (data.mode) { case "turnstile": result = await turnstile(data, page) .then(token => ({ token })) .catch(err => ({ code: 500, message: err.message })); break; case "iuam": result = await cloudflare(data, page) .then(r => ({ ...r })) .catch(err => ({ code: 500, message: err.message })); if (!result.code || result.code === 200) writeCache(cacheKey, result); break; case "recaptcha": result = await recaptchaV2(data, page) .then(r => ({ ...r })) .catch(err => ({ code: 500, message: err.message })); if (!result.code || result.code === 200) writeCache(cacheKey, result); break; default: result = { code: 400, message: 'Invalid mode' }; } } catch (err) { result = { code: 500, message: err.message }; } finally { if (browser) try { await browser.close(); } catch {} global.browserLimit++; } res.status(result.code ?? 200).json(result); }); app.use((req, res) => { res.status(404).json({ message: 'Not Found' }); }); if (process.env.NODE_ENV === 'development') { module.exports = app; }