File size: 5,098 Bytes
83eeab2
 
 
 
f6ffef2
83eeab2
 
10bc490
9cb427d
f6ffef2
83eeab2
 
f6ffef2
83eeab2
 
 
f6ffef2
3828040
83eeab2
f6ffef2
83eeab2
3828040
83eeab2
9cc4df0
f6ffef2
 
3828040
 
83eeab2
9cc4df0
83eeab2
f6ffef2
 
3828040
83eeab2
 
3828040
83eeab2
9cc4df0
83eeab2
3828040
9cc4df0
3828040
83eeab2
 
 
3828040
9cc4df0
83eeab2
 
 
 
 
 
 
 
 
 
10bc490
 
83eeab2
 
 
10bc490
 
02f17f2
83eeab2
 
e3d2740
3828040
83eeab2
 
 
10bc490
83eeab2
9cc4df0
83eeab2
3828040
 
9cc4df0
83eeab2
3828040
83eeab2
3828040
83eeab2
 
 
 
9cc4df0
83eeab2
 
9cc4df0
83eeab2
 
3828040
83eeab2
 
 
 
9cc4df0
83eeab2
3828040
9cc4df0
83eeab2
 
10bc490
9cc4df0
3828040
83eeab2
 
 
10bc490
 
 
 
83eeab2
 
 
 
 
10bc490
83eeab2
 
 
9cc4df0
 
83eeab2
 
9cc4df0
 
83eeab2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10bc490
 
 
 
 
 
 
83eeab2
 
02f17f2
3828040
83eeab2
3828040
83eeab2
 
9cc4df0
 
83eeab2
 
f6ffef2
 
83eeab2
 
02f17f2
3828040
83eeab2
3828040
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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;
}