Spaces:
Sleeping
Sleeping
const express = require('express'); | |
const morgan = require('morgan'); | |
const { createProxyMiddleware } = require('http-proxy-middleware'); | |
const axios = require('axios'); | |
const fs = require('fs'); | |
const url = require('url'); | |
const app = express(); | |
app.use(morgan('dev')); | |
// 全局代理池 | |
let proxyPool = process.env.PROXY ? process.env.PROXY.split(',').map(p => p.trim()) : []; | |
console.log('Initial proxy pool:', proxyPool); | |
// 从代理池中随机选择一个代理 | |
function getRandomProxy() { | |
if (proxyPool.length === 0) return null; | |
const randomIndex = Math.floor(Math.random() * proxyPool.length); | |
const proxyUrl = proxyPool[randomIndex]; | |
const parsedUrl = url.parse(proxyUrl); | |
return { | |
host: parsedUrl.hostname, | |
port: parsedUrl.port || 80, | |
auth: parsedUrl.auth ? { | |
username: parsedUrl.auth.split(':')[0], | |
password: parsedUrl.auth.split(':')[1] | |
} : undefined | |
}; | |
} | |
// 配置 axios 的代理 | |
function configureAxiosProxy() { | |
const proxy = getRandomProxy(); | |
if (proxy) { | |
axios.defaults.proxy = proxy; | |
console.log(`Axios using proxy: ${proxy.host}:${proxy.port}`); | |
} else { | |
delete axios.defaults.proxy; | |
console.log('No proxy available for axios'); | |
} | |
} | |
// 从外部 API 更新代理池 | |
async function updateProxyPool() { | |
const proxyApiUrl = process.env.PROXY_API_URL || 'http://example.com/api/proxies'; | |
try { | |
const response = await axios.get(proxyApiUrl); | |
// 假设 API 返回格式为 { proxies: ["http://user:pass@host:port", ...] } | |
const newProxies = response.data.proxies || []; | |
if (newProxies.length > 0) { | |
proxyPool = newProxies; | |
console.log('Proxy pool updated:', proxyPool); | |
configureAxiosProxy(); // 更新 axios 代理 | |
} else { | |
console.warn('No proxies received from API'); | |
} | |
} catch (error) { | |
console.error('Failed to update proxy pool:', error.message); | |
} | |
} | |
// 定期更新代理池 | |
const updateInterval = parseInt(process.env.PROXY_UPDATE_INTERVAL) || 300; // 默认 5 分钟 | |
if (updateInterval > 0) { | |
setInterval(updateProxyPool, updateInterval * 1000); | |
console.log(`Proxy pool will update every ${updateInterval} seconds`); | |
// 启动时立即更新一次 | |
updateProxyPool(); | |
} | |
// 模型列表 API | |
app.get('/hf/v1/models', (req, res) => { | |
const models = { | |
"object": "list", | |
"data": [ | |
{ | |
"id": "claude-3.5-sonnet", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "gpt-4", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "gpt-4o", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "claude-3-opus", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "gpt-3.5-turbo", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "gpt-4-turbo-2024-04-09", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "gpt-4o-128k", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "gemini-1.5-flash-500k", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "claude-3-haiku-200k", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "claude-3-5-sonnet-200k", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "claude-3-5-sonnet-20241022", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "gpt-4o-mini", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "o1-mini", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "o1-preview", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "o1", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "claude-3.5-haiku", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "gemini-exp-1206", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "gemini-2.0-flash-thinking-exp", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "gemini-2.0-flash-exp", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "deepseek-v3", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
}, | |
{ | |
"id": "deepseek-r1", | |
"object": "model", | |
"created": 1706745938, | |
"owned_by": "cursor" | |
} | |
] | |
}; | |
res.json(models); | |
}); | |
// 代理转发,使用动态代理池 | |
app.use('/hf/v1/chat/completions', (req, res, next) => { | |
const proxy = getRandomProxy(); | |
const middleware = createProxyMiddleware({ | |
target: 'http://localhost:3010/v1/chat/completions', // 可替换为实际目标服务 | |
changeOrigin: true, | |
proxy: proxy ? proxy : undefined, | |
timeout: 30000, // 超时 30 秒 | |
proxyTimeout: 30000, // 代理超时 30 秒 | |
onProxyReq: (proxyReq, req, res) => { | |
if (req.body) { | |
const bodyData = JSON.stringify(req.body); | |
proxyReq.setHeader('Content-Type', 'application/json'); | |
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData)); | |
proxyReq.write(bodyData); | |
proxyReq.end(); | |
} | |
}, | |
onError: (err, req, res) => { | |
console.error('Proxy error:', err); | |
res.status(500).send('Proxy error occurred'); | |
} | |
}); | |
if (proxy) { | |
console.log(`Request proxied via ${proxy.host}:${proxy.port}`); | |
} else { | |
console.log('No proxy available, direct connection'); | |
} | |
middleware(req, res, next); | |
}); | |
// 首页 | |
app.get('/', (req, res) => { | |
const htmlContent = ` | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Cursor To OpenAI</title> | |
<style> | |
:root { | |
--primary-color: #2563eb; | |
--bg-color: #f8fafc; | |
--card-bg: #ffffff; | |
} | |
body { | |
padding: 20px; | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
max-width: 1000px; | |
margin: 0 auto; | |
line-height: 1.6; | |
background: var(--bg-color); | |
color: #1a1a1a; | |
} | |
.container { | |
padding: 20px; | |
} | |
.header { | |
text-align: center; | |
margin-bottom: 40px; | |
} | |
.header h1 { | |
color: var(--primary-color); | |
font-size: 2.5em; | |
margin-bottom: 10px; | |
} | |
.info { | |
background: #fff; | |
padding: 25px; | |
border-radius: 12px; | |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
margin-bottom: 30px; | |
border: 1px solid #e5e7eb; | |
} | |
.info-item { | |
margin: 15px 0; | |
padding: 10px; | |
background: #f8fafc; | |
border-radius: 8px; | |
border: 1px solid #e5e7eb; | |
} | |
.info-label { | |
color: #4b5563; | |
font-size: 0.9em; | |
margin-bottom: 5px; | |
} | |
.info-value { | |
color: var(--primary-color); | |
font-weight: 500; | |
} | |
.models { | |
background: var(--card-bg); | |
padding: 25px; | |
border-radius: 12px; | |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
border: 1px solid #e5e7eb; | |
} | |
.models h3 { | |
color: #1a1a1a; | |
margin-bottom: 20px; | |
font-size: 1.5em; | |
border-bottom: 2px solid #e5e7eb; | |
padding-bottom: 10px; | |
} | |
.model-item { | |
margin: 12px 0; | |
padding: 15px; | |
background: #f8fafc; | |
border-radius: 8px; | |
border: 1px solid #e5e7eb; | |
transition: all 0.3s ease; | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
} | |
.model-item:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |
} | |
.model-name { | |
font-weight: 500; | |
color: #1a1a1a; | |
} | |
.model-provider { | |
color: #6b7280; | |
font-size: 0.9em; | |
padding: 4px 8px; | |
background: #f1f5f9; | |
border-radius: 4px; | |
} | |
@media (max-width: 768px) { | |
body { | |
padding: 10px; | |
} | |
.container { | |
padding: 10px; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<div class="header"> | |
<h1>Cursor To OpenAI Server</h1> | |
<p>高性能 AI 模型代理服务</p> | |
</div> | |
<div class="info"> | |
<h2>配置信息</h2> | |
<div class="info-item"> | |
<div class="info-label">聊天来源</div> | |
<div class="info-value">自定义(兼容 OpenAI)</div> | |
</div> | |
<div class="info-item"> | |
<div class="info-label">自定义端点(基本URL)</div> | |
<div class="info-value" id="endpoint-url"></div> | |
</div> | |
<div class="info-item"> | |
<div class="info-label">自定义API密钥</div> | |
<div class="info-value">抓取的Cursor Cookie,格式为user_...</div> | |
</div> | |
</div> | |
<div class="models"> | |
<h3>支持的模型列表</h3> | |
<div id="model-list"></div> | |
</div> | |
</div> | |
<script> | |
const url = new URL(window.location.href); | |
const link = url.protocol + '//' + url.host + '/hf/v1'; | |
document.getElementById('endpoint-url').textContent = link; | |
fetch(link + '/models') | |
.then(response => response.json()) | |
.then(data => { | |
const modelList = document.getElementById('model-list'); | |
data.data.forEach(model => { | |
const div = document.createElement('div'); | |
div.className = 'model-item'; | |
div.innerHTML = \` | |
<span class="model-name">\${model.id}</span> | |
<span class="model-provider">\${model.owned_by}</span> | |
\`; | |
modelList.appendChild(div); | |
}); | |
}) | |
.catch(error => { | |
console.error('Error fetching models:', error); | |
document.getElementById('model-list').textContent = '获取模型列表失败'; | |
}); | |
</script> | |
</body> | |
</html> | |
`; | |
res.send(htmlContent); | |
}); | |
// 启动服务前配置初始 axios 代理 | |
configureAxiosProxy(); | |
const port = process.env.HF_PORT || 7860; | |
app.listen(port, () => { | |
console.log(`HF Proxy server is running at PORT: ${port}`); | |
}); | |