Juanoto2012 commited on
Commit
66908bc
·
verified ·
1 Parent(s): 0bb17cf

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +40 -260
server.js CHANGED
@@ -2,46 +2,26 @@ import express from 'express';
2
  import cors from 'cors';
3
  import rateLimit from 'express-rate-limit';
4
  import { Readable } from 'stream';
5
- import crypto from 'crypto';
6
 
7
  const app = express();
8
  const PORT = 7860;
9
 
10
- // --- 1. CONFIGURACIÓN DE SEGURIDAD PARANOICA ---
11
- // CRÍTICO: trust proxy en 1 es vital para leer la IP real del usuario a través de Cloudflare/HF
12
  app.set('trust proxy', 1);
13
  app.disable('x-powered-by');
14
  app.use(cors());
15
-
16
  app.use((req, res, next) => {
17
  res.setHeader('X-Content-Type-Options', 'nosniff');
18
  res.setHeader('X-Frame-Options', 'DENY');
19
- res.setHeader('X-XSS-Protection', '1; mode=block');
20
- res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
21
- res.setHeader('Referrer-Policy', 'no-referrer'); // Oculta que vienen de Ventarys AI
22
- res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none';");
23
  next();
24
  });
25
 
26
  app.use(express.json({ limit: '50mb' }));
27
  app.use(express.urlencoded({ limit: '50mb', extended: true }));
28
 
29
- function logError(providerId, reason) {
30
- console.error(`[SYSTEM] Provider Error: ${providerId} | ${reason}`);
31
- }
32
-
33
- // Falsificación de huella digital del navegador
34
- function getRandomUserAgent() {
35
- const agents = [
36
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
37
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
38
- "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0",
39
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0"
40
- ];
41
- return agents[Math.floor(Math.random() * agents.length)];
42
- }
43
-
44
- // --- PROVEEDORES PÚBLICOS ---
45
  const PROVIDERS = [
46
  {
47
  id: "pollinations-ai",
@@ -54,278 +34,83 @@ const PROVIDERS = [
54
  },
55
  {
56
  id: "voids-api",
57
- url: "https://api.voids.top/v1/chat/completions"
 
 
58
  }
59
  ];
60
 
61
- // CRÍTICO: Subimos el límite a 500 para permitir alta concurrencia.
62
- // Ahora el proxy dejará pasar todo el tráfico y el proveedor final aplicará el rate limit por IP.
63
- const MAX_PER_PROVIDER = 500;
64
  const QUEUE_TIMEOUT = 45000;
65
  let currentLoad = { "pollinations-ai": 0, "kepler-cloud": 0, "voids-api": 0 };
66
 
67
- // --- RATE LIMITING LOCAL ---
68
  const limiter = rateLimit({
69
  windowMs: 60 * 1000,
70
- max: 50, // 50 peticiones por IP localmente para evitar que alguien tire TU servidor
71
  keyGenerator: (req) => req.ip,
72
- message: { error: { message: "Too many requests.", code: 429 } },
73
- standardHeaders: false,
74
- legacyHeaders: false,
75
  });
76
 
77
- const IMAGE_KEYWORDS = ["flux", "dall", "midjourney", "sdxl", "stable-diffusion", "image", "vision"];
78
- const AUDIO_KEYWORDS = ["suno", "udio", "music", "audio", "song", "voice", "tts", "whisper"];
79
-
80
- function isImageModel(model) {
81
- if (!model) return false;
82
- if (model.type === 'image' || model.supports_images === true) return true;
83
- return IMAGE_KEYWORDS.some(kw => (model.id || model.name || "").toLowerCase().includes(kw));
84
- }
85
-
86
- function isAudioModel(model) {
87
- if (model.type === 'audio' || model.type === 'music') return true;
88
- return AUDIO_KEYWORDS.some(kw => (model.id || model.name || "").toLowerCase().includes(kw));
89
- }
90
-
91
- async function fetchAllModels() {
92
- const fetchPromises = PROVIDERS.map(async (provider) => {
93
- const modelsUrl = provider.modelsUrl || provider.url.replace("/chat/completions", "/models");
94
-
95
- try {
96
- const resp = await fetch(modelsUrl, {
97
- method: "GET",
98
- headers: {
99
- "Content-Type": "application/json",
100
- "User-Agent": getRandomUserAgent()
101
- }
102
- });
103
- if (!resp.ok) return [];
104
-
105
- const json = await resp.json();
106
- let modelsArray = Array.isArray(json) ? json : (json && Array.isArray(json.data) ? json.data : []);
107
-
108
- if (modelsArray.length > 0) {
109
- return modelsArray
110
- .filter(model => !isAudioModel(model))
111
- .map(model => ({ ...model, id: model.id || model.name, owned_by: provider.id }));
112
- }
113
- return [];
114
- } catch (error) {
115
- return [];
116
- }
117
- });
118
-
119
- const results = await Promise.allSettled(fetchPromises);
120
- let allModels = [];
121
- results.forEach(result => {
122
- if (result.status === "fulfilled") allModels = allModels.concat(result.value);
123
- });
124
-
125
- if (allModels.length === 0) {
126
- allModels = [
127
- { id: "gpt-4o", object: "model", type: "text", owned_by: "pollinations-ai" },
128
- { id: "claude-3-5-sonnet", object: "model", type: "text", owned_by: "pollinations-ai" },
129
- { id: "gemini-1.5-pro", object: "model", type: "text", owned_by: "voids-api" },
130
- { id: "gemini-1.5-flash", object: "model", type: "text", owned_by: "voids-api" }
131
- ];
132
- }
133
- return allModels;
134
  }
135
 
136
- app.get('/health', (req, res) => {
137
- res.json({ status: "online" });
138
- });
139
-
140
- app.get('/v1/models', async (req, res) => {
141
- try {
142
- const allModels = await fetchAllModels();
143
- const textModels = allModels
144
- .filter(m => !isImageModel(m) && m.supports_chat !== false)
145
- .map(m => { m.type = 'text'; return m; });
146
- res.json({ object: "list", data: textModels });
147
- } catch (error) {
148
- res.status(500).json({ error: "No models available." });
149
- }
150
- });
151
-
152
- app.get('/v1/images/models', async (req, res) => {
153
- try {
154
- const allModels = await fetchAllModels();
155
- let imageModels = allModels
156
- .filter(m => isImageModel(m))
157
- .map(m => { m.type = 'image'; return m; });
158
-
159
- const baseImages = [
160
- { id: "flux", object: "model", type: "image", owned_by: "system" },
161
- { id: "dall-e-3", object: "model", type: "image", owned_by: "system" }
162
- ];
163
-
164
- baseImages.forEach(baseMod => {
165
- if (!imageModels.find(m => m.id.toLowerCase() === baseMod.id)) imageModels.push(baseMod);
166
- });
167
-
168
- res.json({ object: "list", data: imageModels });
169
- } catch (error) {
170
- res.status(500).json({ error: "No models available." });
171
- }
172
- });
173
-
174
  app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req, res) => {
175
  const isImage = req.path === '/v1/images/generations';
176
- let availableProviders = isImage ? PROVIDERS.filter(p => p.imageUrl) : [...PROVIDERS];
177
- if (isImage && availableProviders.length === 0) availableProviders = [...PROVIDERS];
178
-
179
- const startTime = Date.now();
180
- let responseSent = false;
181
-
182
- // Extraemos la IP del cliente para reenviarla
183
  const clientIp = req.ip || req.headers['x-forwarded-for'] || req.socket.remoteAddress;
184
-
185
  let payload = JSON.stringify(req.body);
186
  req.body = null;
187
 
 
 
 
188
  while (availableProviders.length > 0 && Date.now() - startTime < QUEUE_TIMEOUT) {
189
- let selectedProvider = null;
190
- let shuffled = [...availableProviders].sort(() => Math.random() - 0.5);
191
-
192
- for (let provider of shuffled) {
193
- if (currentLoad[provider.id] < MAX_PER_PROVIDER) {
194
- selectedProvider = provider;
195
- currentLoad[provider.id]++;
196
- break;
197
- }
198
- }
199
 
200
  if (!selectedProvider) {
201
- await new Promise(r => setTimeout(r, 1500));
202
  continue;
203
  }
204
 
205
- let isReleased = false;
206
- const releaseSlot = () => {
207
- if (!isReleased) {
208
- currentLoad[selectedProvider.id] = Math.max(0, currentLoad[selectedProvider.id] - 1);
209
- isReleased = true;
210
- }
211
- };
212
 
213
  try {
214
- let targetUrl = isImage && selectedProvider.imageUrl ? selectedProvider.imageUrl : selectedProvider.url;
215
-
216
- // --- OFUSCACIÓN + DELEGACIÓN DE IP ---
217
  const fetchHeaders = {
218
  "Content-Type": "application/json",
219
- "User-Agent": getRandomUserAgent(),
220
- "Accept": "*/*",
221
- "Connection": "keep-alive",
222
- "X-Forwarded-For": clientIp, // El proveedor rate-limitea esta IP
223
- "X-Real-IP": clientIp // El proveedor rate-limitea esta IP
224
  };
225
 
226
- const response = await fetch(targetUrl, {
 
 
 
 
 
 
 
227
  method: "POST",
228
  headers: fetchHeaders,
229
  body: payload
230
  });
231
 
232
- if (!response.ok) {
233
- releaseSlot();
234
- availableProviders = availableProviders.filter(p => p.id !== selectedProvider.id);
235
- continue;
236
- }
237
-
238
- const responseHeaders = new Headers(response.headers);
239
- responseHeaders.delete('set-cookie');
240
- responseHeaders.delete('server');
241
- responseHeaders.delete('x-powered-by');
242
- responseHeaders.delete('cf-ray');
243
- responseHeaders.delete('access-control-allow-origin');
244
-
245
- if (isImage) {
246
- const contentType = responseHeaders.get("content-type") || "";
247
-
248
- if (contentType.includes("application/json")) {
249
- const jsonResp = await response.json();
250
- let dataArray = jsonResp.data;
251
- if (!dataArray && jsonResp.url) dataArray = [{ url: jsonResp.url }];
252
-
253
- if (dataArray && Array.isArray(dataArray)) {
254
- for (let item of dataArray) {
255
- if (item.url && !item.b64_json) {
256
- try {
257
- const imgRes = await fetch(item.url);
258
- const arrayBuffer = await imgRes.arrayBuffer();
259
- item.b64_json = Buffer.from(arrayBuffer).toString('base64');
260
- delete item.url;
261
- } catch (e) {}
262
- }
263
- }
264
- releaseSlot();
265
- responseSent = true;
266
- payload = null;
267
-
268
- return res.status(response.status).json({
269
- created: Math.floor(Date.now() / 1000),
270
- data: dataArray
271
- });
272
- }
273
-
274
- releaseSlot();
275
- responseSent = true;
276
- payload = null;
277
- return res.status(response.status).json(jsonResp);
278
- }
279
- else if (contentType.includes("image/")) {
280
- const arrayBuffer = await response.arrayBuffer();
281
- const b64 = Buffer.from(arrayBuffer).toString('base64');
282
- releaseSlot();
283
- responseSent = true;
284
- payload = null;
285
- return res.status(200).json({
286
- created: Math.floor(Date.now() / 1000),
287
- data: [{ b64_json: b64 }]
288
- });
289
- }
290
- else {
291
- const textResp = await response.text();
292
- releaseSlot();
293
- responseSent = true;
294
- payload = null;
295
- return res.status(response.status).type(contentType).send(textResp);
296
- }
297
- }
298
-
299
- res.writeHead(response.status, {
300
- 'Content-Type': responseHeaders.get('content-type') || 'text/event-stream',
301
- 'Cache-Control': 'no-cache',
302
- 'Connection': 'keep-alive'
303
- });
304
 
 
305
  if (response.body) {
306
- const stream = Readable.fromWeb(response.body);
307
- stream.pipe(res);
308
-
309
- stream.on('end', () => {
310
- releaseSlot();
311
- payload = null;
312
- });
313
- stream.on('error', () => {
314
- releaseSlot();
315
- payload = null;
316
- });
317
- req.on('close', () => {
318
- releaseSlot();
319
- payload = null;
320
- });
321
  } else {
322
  releaseSlot();
323
- payload = null;
324
  res.end();
325
  }
326
-
327
  responseSent = true;
328
- return;
 
329
 
330
  } catch (err) {
331
  releaseSlot();
@@ -334,12 +119,7 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
334
  }
335
 
336
  payload = null;
337
-
338
- if (!responseSent) {
339
- return res.status(503).json({ error: { message: "Servicio no disponible.", code: 503 } });
340
- }
341
  });
342
 
343
- app.listen(PORT, '0.0.0.0', () => {
344
- console.log(`[SYS] Nivel de cifrado: Tor (Delegando IP). Concurrencia máxima: 500/prov. Puerto: ${PORT}`);
345
- });
 
2
  import cors from 'cors';
3
  import rateLimit from 'express-rate-limit';
4
  import { Readable } from 'stream';
 
5
 
6
  const app = express();
7
  const PORT = 7860;
8
 
9
+ // --- SEGURIDAD ---
 
10
  app.set('trust proxy', 1);
11
  app.disable('x-powered-by');
12
  app.use(cors());
 
13
  app.use((req, res, next) => {
14
  res.setHeader('X-Content-Type-Options', 'nosniff');
15
  res.setHeader('X-Frame-Options', 'DENY');
16
+ res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
17
+ res.setHeader('Referrer-Policy', 'no-referrer');
 
 
18
  next();
19
  });
20
 
21
  app.use(express.json({ limit: '50mb' }));
22
  app.use(express.urlencoded({ limit: '50mb', extended: true }));
23
 
24
+ // --- CONFIGURACIÓN DE PROVEEDORES ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  const PROVIDERS = [
26
  {
27
  id: "pollinations-ai",
 
34
  },
35
  {
36
  id: "voids-api",
37
+ url: "https://api.voids.top/v1/chat/completions",
38
+ strictCloudflare: true,
39
+ needsDummyKey: true
40
  }
41
  ];
42
 
43
+ const MAX_PER_PROVIDER = 1;
 
 
44
  const QUEUE_TIMEOUT = 45000;
45
  let currentLoad = { "pollinations-ai": 0, "kepler-cloud": 0, "voids-api": 0 };
46
 
 
47
  const limiter = rateLimit({
48
  windowMs: 60 * 1000,
49
+ max: 50,
50
  keyGenerator: (req) => req.ip,
51
+ message: { error: { message: "Too many requests.", code: 429 } }
 
 
52
  });
53
 
54
+ function getRandomUserAgent() {
55
+ const agents = ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"];
56
+ return agents[Math.floor(Math.random() * agents.length)];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  }
58
 
59
+ // --- RUTA PRINCIPAL ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req, res) => {
61
  const isImage = req.path === '/v1/images/generations';
62
+ let availableProviders = [...PROVIDERS];
 
 
 
 
 
 
63
  const clientIp = req.ip || req.headers['x-forwarded-for'] || req.socket.remoteAddress;
 
64
  let payload = JSON.stringify(req.body);
65
  req.body = null;
66
 
67
+ const startTime = Date.now();
68
+ let responseSent = false;
69
+
70
  while (availableProviders.length > 0 && Date.now() - startTime < QUEUE_TIMEOUT) {
71
+ let selectedProvider = availableProviders.sort(() => Math.random() - 0.5)
72
+ .find(p => currentLoad[p.id] < MAX_PER_PROVIDER);
 
 
 
 
 
 
 
 
73
 
74
  if (!selectedProvider) {
75
+ await new Promise(r => setTimeout(r, 1000));
76
  continue;
77
  }
78
 
79
+ currentLoad[selectedProvider.id]++;
80
+ const releaseSlot = () => { currentLoad[selectedProvider.id] = Math.max(0, currentLoad[selectedProvider.id] - 1); };
 
 
 
 
 
81
 
82
  try {
 
 
 
83
  const fetchHeaders = {
84
  "Content-Type": "application/json",
85
+ "User-Agent": getRandomUserAgent()
 
 
 
 
86
  };
87
 
88
+ if (!selectedProvider.strictCloudflare) {
89
+ fetchHeaders["X-Forwarded-For"] = clientIp;
90
+ }
91
+ if (selectedProvider.needsDummyKey) {
92
+ fetchHeaders["Authorization"] = "Bearer sk-ventarys-proxy-pass";
93
+ }
94
+
95
+ const response = await fetch(selectedProvider.url, {
96
  method: "POST",
97
  headers: fetchHeaders,
98
  body: payload
99
  });
100
 
101
+ if (!response.ok) throw new Error("Upstream Error");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
+ res.writeHead(response.status, { 'Content-Type': response.headers.get('content-type') });
104
  if (response.body) {
105
+ Readable.fromWeb(response.body).pipe(res);
106
+ res.on('finish', releaseSlot);
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  } else {
108
  releaseSlot();
 
109
  res.end();
110
  }
 
111
  responseSent = true;
112
+ payload = null;
113
+ return;
114
 
115
  } catch (err) {
116
  releaseSlot();
 
119
  }
120
 
121
  payload = null;
122
+ if (!responseSent) res.status(503).json({ error: "Service unavailable" });
 
 
 
123
  });
124
 
125
+ app.listen(PORT, '0.0.0.0', () => console.log(`🚀 Ventarys Proxy Online`));