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

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +89 -70
server.js CHANGED
@@ -2,26 +2,46 @@ import express from 'express';
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,92 +54,91 @@ const PROVIDERS = [
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();
117
- availableProviders = availableProviders.filter(p => p.id !== selectedProvider.id);
118
  }
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`));
 
 
 
 
 
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
  },
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)