Spaces:
Running
Running
const express = require('express'); | |
const router = express.Router(); | |
const { fetch, ProxyAgent, Agent } = require('undici'); | |
const $root = require('../proto/message.js'); | |
const { v4: uuidv4, v5: uuidv5 } = require('uuid'); | |
const { generateCursorBody, chunkToUtf8String, generateHashed64Hex, generateCursorChecksum } = require('../utils/utils.js'); | |
const keyManager = require('../utils/keyManager.js'); | |
const { spawn } = require('child_process'); | |
const path = require('path'); | |
const admin = require('../models/admin'); | |
const config = require('../config/config'); | |
const crypto = require('crypto'); | |
const logger = require('../utils/logger'); | |
// 存储刷新状态的变量 | |
let refreshStatus = { | |
isRunning: false, | |
status: 'idle', // idle, running, completed, failed | |
message: '', | |
startTime: null, | |
endTime: null, | |
error: null | |
}; | |
// 储存当前正在处理的Cookie获取请求 | |
const pendingCookieRequests = new Map(); | |
// 检查是否已有管理员账号 | |
router.get('/admin/check', (req, res) => { | |
try { | |
return res.json({ | |
success: true, | |
exists: admin.hasAdmin() | |
}); | |
} catch (error) { | |
logger.error('检查管理员账号失败:', error); | |
return res.status(500).json({ | |
success: false, | |
message: error.message | |
}); | |
} | |
}); | |
// 注册管理员 | |
router.post('/admin/register', (req, res) => { | |
try { | |
const { username, password } = req.body; | |
if (!username || !password) { | |
return res.status(400).json({ | |
success: false, | |
message: '用户名和密码不能为空' | |
}); | |
} | |
const token = admin.register(username, password); | |
return res.json({ | |
success: true, | |
message: '注册成功', | |
token | |
}); | |
} catch (error) { | |
logger.error('注册管理员失败:', error); | |
return res.status(400).json({ | |
success: false, | |
message: error.message | |
}); | |
} | |
}); | |
// 管理员登录 | |
router.post('/admin/login', (req, res) => { | |
try { | |
const { username, password } = req.body; | |
if (!username || !password) { | |
return res.status(400).json({ | |
success: false, | |
message: '用户名和密码不能为空' | |
}); | |
} | |
const token = admin.login(username, password); | |
return res.json({ | |
success: true, | |
message: '登录成功', | |
token | |
}); | |
} catch (error) { | |
logger.error('登录失败:', error); | |
return res.status(400).json({ | |
success: false, | |
message: error.message | |
}); | |
} | |
}); | |
// 验证token | |
router.get('/admin/verify', (req, res) => { | |
try { | |
const authHeader = req.headers.authorization; | |
if (!authHeader || !authHeader.startsWith('Bearer ')) { | |
return res.status(401).json({ | |
success: false, | |
message: '未提供认证token' | |
}); | |
} | |
const token = authHeader.split(' ')[1]; | |
const result = admin.verifyToken(token); | |
return res.json(result); | |
} catch (error) { | |
logger.error('验证token失败:', error); | |
return res.status(401).json({ | |
success: false, | |
message: error.message | |
}); | |
} | |
}); | |
// 添加API key管理路由 | |
router.post("/api-keys", async (req, res) => { | |
try { | |
const { apiKey, cookieValues } = req.body; | |
if (!apiKey || !cookieValues) { | |
return res.status(400).json({ | |
error: 'API key and cookie values are required', | |
}); | |
} | |
keyManager.addOrUpdateApiKey(apiKey, cookieValues); | |
return res.json({ | |
success: true, | |
message: 'API key added or updated successfully', | |
}); | |
} catch (error) { | |
logger.error(error); | |
return res.status(500).json({ | |
error: 'Internal server error', | |
}); | |
} | |
}); | |
// 获取所有API Keys | |
router.get("/api-keys", async (req, res) => { | |
try { | |
logger.info('收到获取API Keys请求'); | |
const apiKeys = keyManager.getAllApiKeys(); | |
logger.info('获取到的API Keys:', apiKeys); | |
const result = { | |
success: true, | |
apiKeys: apiKeys.map(apiKey => ({ | |
key: apiKey, | |
cookieCount: keyManager.getAllCookiesForApiKey(apiKey).length, | |
})), | |
}; | |
logger.info('返回结果:', result); | |
return res.json(result); | |
} catch (error) { | |
logger.error('获取API Keys失败:', error); | |
return res.status(500).json({ | |
error: 'Internal server error', | |
message: error.message | |
}); | |
} | |
}); | |
// 删除API key | |
router.delete("/api-keys/:apiKey", async (req, res) => { | |
try { | |
const { apiKey } = req.params; | |
keyManager.removeApiKey(apiKey); | |
return res.json({ | |
success: true, | |
message: 'API key removed successfully', | |
}); | |
} catch (error) { | |
logger.error(error); | |
return res.status(500).json({ | |
error: 'Internal server error', | |
}); | |
} | |
}); | |
// 获取特定API Key的Cookie值 | |
router.get("/api-keys/:apiKey/cookies", async (req, res) => { | |
try { | |
const { apiKey } = req.params; | |
logger.info(`收到获取API Key ${apiKey}的Cookie值请求`); | |
const cookies = keyManager.getAllCookiesForApiKey(apiKey); | |
logger.info(`API Key ${apiKey}的Cookie值:`, cookies); | |
return res.json({ | |
success: true, | |
cookies: cookies | |
}); | |
} catch (error) { | |
logger.error(`获取API Key ${req.params.apiKey}的Cookie值失败:`, error); | |
return res.status(500).json({ | |
error: 'Internal server error', | |
message: error.message | |
}); | |
} | |
}); | |
// 获取所有无效的cookie | |
router.get("/invalid-cookies", async (req, res) => { | |
try { | |
const invalidCookies = keyManager.getInvalidCookies(); | |
return res.json({ | |
success: true, | |
invalidCookies: Array.from(invalidCookies) | |
}); | |
} catch (error) { | |
logger.error('获取无效cookie失败:', error); | |
return res.status(500).json({ | |
error: 'Internal server error', | |
message: error.message | |
}); | |
} | |
}); | |
// 清除特定的无效cookie | |
router.delete("/invalid-cookies/:cookie", async (req, res) => { | |
try { | |
const { cookie } = req.params; | |
const success = keyManager.clearInvalidCookie(cookie); | |
return res.json({ | |
success: success, | |
message: success ? '无效cookie已清除' : '未找到指定的无效cookie' | |
}); | |
} catch (error) { | |
logger.error('清除无效cookie失败:', error); | |
return res.status(500).json({ | |
error: 'Internal server error', | |
message: error.message | |
}); | |
} | |
}); | |
// 清除所有无效cookie | |
router.delete("/invalid-cookies", async (req, res) => { | |
try { | |
keyManager.clearAllInvalidCookies(); | |
return res.json({ | |
success: true, | |
message: '所有无效cookie已清除' | |
}); | |
} catch (error) { | |
logger.error('清除所有无效cookie失败:', error); | |
return res.status(500).json({ | |
error: 'Internal server error', | |
message: error.message | |
}); | |
} | |
}); | |
// 批量添加无效cookie | |
router.post("/invalid-cookies", async (req, res) => { | |
try { | |
const { invalidCookies } = req.body; | |
if (!Array.isArray(invalidCookies)) { | |
return res.status(400).json({ | |
success: false, | |
error: 'Invalid request', | |
message: 'invalidCookies必须是一个数组' | |
}); | |
} | |
// 获取当前无效cookie集合 | |
const currentInvalidCookies = keyManager.getInvalidCookies(); | |
// 添加新的无效cookie | |
for (const cookie of invalidCookies) { | |
if (typeof cookie === 'string' && cookie.trim()) { | |
currentInvalidCookies.add(cookie.trim()); | |
} | |
} | |
// 保存到文件 | |
keyManager.saveInvalidCookiesToFile(); | |
return res.json({ | |
success: true, | |
message: `已添加${invalidCookies.length}个无效cookie` | |
}); | |
} catch (error) { | |
logger.error('添加无效cookie失败:', error); | |
return res.status(500).json({ | |
error: 'Internal server error', | |
message: error.message | |
}); | |
} | |
}); | |
router.get("/models", async (req, res) => { | |
try{ | |
let bearerToken = req.headers.authorization?.replace('Bearer ', ''); | |
// 使用keyManager获取实际的cookie | |
let authToken = keyManager.getCookieForApiKey(bearerToken); | |
if (authToken && authToken.includes('%3A%3A')) { | |
authToken = authToken.split('%3A%3A')[1]; | |
} | |
else if (authToken && authToken.includes('::')) { | |
authToken = authToken.split('::')[1]; | |
} | |
const checksum = req.headers['x-cursor-checksum'] | |
?? process.env['x-cursor-checksum'] | |
?? generateCursorChecksum(authToken.trim()); | |
//const cursorClientVersion = "0.45.11" | |
const cursorClientVersion = "0.50.4"; | |
const availableModelsResponse = await fetch("https://api2.cursor.sh/aiserver.v1.AiService/AvailableModels", { | |
method: 'POST', | |
headers: { | |
'accept-encoding': 'gzip', | |
'authorization': `Bearer ${authToken}`, | |
'connect-protocol-version': '1', | |
'content-type': 'application/proto', | |
'user-agent': 'connect-es/1.6.1', | |
'x-cursor-checksum': checksum, | |
'x-cursor-client-version': cursorClientVersion, | |
'x-cursor-config-version': uuidv4(), | |
'x-cursor-timezone': 'Asia/Tokyo', | |
'x-ghost-mode': 'true', | |
'Host': 'api2.cursor.sh', | |
}, | |
}) | |
const data = await availableModelsResponse.arrayBuffer(); | |
const buffer = Buffer.from(data); | |
try{ | |
const models = $root.AvailableModelsResponse.decode(buffer).models; | |
return res.json({ | |
object: "list", | |
data: models.map(model => ({ | |
id: model.name, | |
created: Date.now(), | |
object: 'model', | |
owned_by: 'cursor' | |
})) | |
}) | |
} catch (error) { | |
const text = buffer.toString('utf-8'); | |
throw new Error(text); | |
} | |
} | |
catch (error) { | |
logger.error(error); | |
return res.status(500).json({ | |
error: 'Internal server error', | |
}); | |
} | |
}) | |
router.post('/chat/completions', async (req, res) => { | |
// 检查请求体是否存在 | |
if (!req.body) { | |
return res.status(400).json({ | |
error: '请求体不能为空', | |
}); | |
} | |
// 检查模型属性是否存在 | |
if (!req.body.model) { | |
return res.status(400).json({ | |
error: '缺少必要参数: model', | |
}); | |
} | |
// o1开头的模型,不支持流式输出 | |
if (typeof req.body.model === 'string' && req.body.model.startsWith('o1-') && req.body.stream) { | |
return res.status(400).json({ | |
error: 'Model not supported stream', | |
}); | |
} | |
try { | |
const { model, messages, stream = false } = req.body; | |
let bearerToken = req.headers.authorization?.replace('Bearer ', ''); | |
// 使用keyManager获取实际的cookie | |
let authToken = keyManager.getCookieForApiKey(bearerToken); | |
// 保存原始cookie,用于后续可能的错误处理 | |
const originalAuthToken = authToken; | |
//console.log('原始cookie:', originalAuthToken); | |
if (authToken && authToken.includes('%3A%3A')) { | |
authToken = authToken.split('%3A%3A')[1]; | |
} | |
else if (authToken && authToken.includes('::')) { | |
authToken = authToken.split('::')[1]; | |
} | |
if (!messages || !Array.isArray(messages) || messages.length === 0 || !authToken) { | |
return res.status(400).json({ | |
error: 'Invalid request. Messages should be a non-empty array and authorization is required', | |
}); | |
} | |
const checksum = req.headers['x-cursor-checksum'] | |
?? process.env['x-cursor-checksum'] | |
?? generateCursorChecksum(authToken.trim()); | |
const sessionid = uuidv5(authToken, uuidv5.DNS); | |
const clientKey = generateHashed64Hex(authToken); | |
const cursorClientVersion = "0.50.4"; | |
// 在请求聊天接口前,依次调用6个接口 | |
if (process.env.USE_OTHERS === 'true') { | |
try{ | |
others(authToken, clientKey, checksum, cursorClientVersion, sessionid).then( () => { | |
logger.info("其它接口异步调用成功"); | |
}); | |
} catch (error) { | |
logger.error(error.message); | |
} | |
} | |
const cursorBody = generateCursorBody(messages, model); | |
// 添加代理支持 | |
const dispatcher = config.proxy && config.proxy.enabled | |
? new ProxyAgent(config.proxy.url, { allowH2: true }) | |
: new Agent({ allowH2: true }); | |
// 根据.env配置决定是否使用TLS代理 | |
const useTlsProxy = process.env.USE_TLS_PROXY === 'true'; | |
let response; | |
try { | |
if (useTlsProxy) { | |
// 使用JA3指纹伪造代理服务器 | |
logger.info(`使用TLS代理服务器`); | |
response = await fetch('http://localhost:8080/proxy', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ | |
url: 'https://api2.cursor.sh/aiserver.v1.ChatService/StreamUnifiedChatWithTools', | |
method: 'POST', | |
headers: { | |
'authorization': `Bearer ${authToken}`, | |
'connect-accept-encoding': 'gzip', | |
'connect-content-encoding': 'gzip', | |
'connect-protocol-version': '1', | |
'content-type': 'application/connect+proto', | |
'user-agent': 'connect-es/1.6.1', | |
'x-amzn-trace-id': `Root=${uuidv4()}`, | |
'x-client-key': clientKey, | |
'x-cursor-checksum': checksum, | |
'x-cursor-client-version': cursorClientVersion, | |
'x-cursor-config-version': uuidv4(), | |
'x-cursor-timezone': 'Asia/Tokyo', | |
'x-ghost-mode': 'true', | |
'x-request-id': uuidv4(), | |
'x-session-id': sessionid, | |
'Host': 'api2.cursor.sh', | |
}, | |
body: cursorBody, | |
stream: true // 启用流式响应 | |
}), | |
timeout: { | |
connect: 5000, | |
read: 30000 | |
} | |
}); | |
} else { | |
// 直接调用API,不使用TLS代理 | |
logger.info('不使用TLS代理服务器,直接请求API'); | |
response = await fetch('https://api2.cursor.sh/aiserver.v1.ChatService/StreamUnifiedChatWithTools', { | |
method: 'POST', | |
headers: { | |
'authorization': `Bearer ${authToken}`, | |
'connect-accept-encoding': 'gzip', | |
'connect-content-encoding': 'gzip', | |
'connect-protocol-version': '1', | |
'content-type': 'application/connect+proto', | |
'user-agent': 'connect-es/1.6.1', | |
'x-amzn-trace-id': `Root=${uuidv4()}`, | |
'x-client-key': clientKey, | |
'x-cursor-checksum': checksum, | |
'x-cursor-client-version': cursorClientVersion, | |
'x-cursor-config-version': uuidv4(), | |
'x-cursor-timezone': 'Asia/Shanghai', | |
'x-ghost-mode': 'true', | |
'x-request-id': uuidv4(), | |
'x-session-id': sessionid, | |
'Host': 'api2.cursor.sh', | |
}, | |
body: cursorBody, | |
dispatcher: dispatcher, | |
timeout: { | |
connect: 5000, | |
read: 30000 | |
} | |
}); | |
} | |
} catch (fetchError) { | |
logger.error(`Fetch错误: ${fetchError.message}`); | |
// 处理连接超时错误 | |
const isConnectTimeout = fetchError.cause && | |
(fetchError.cause.code === 'UND_ERR_CONNECT_TIMEOUT' || | |
fetchError.message.includes('Connect Timeout Error')); | |
// 构建错误响应 | |
const errorMessage = isConnectTimeout | |
? `⚠️ 连接超时 ⚠️\n\n无法连接到API服务器(api2.cursor.sh),请检查您的网络连接或尝试使用代理。` | |
: `⚠️ 请求失败 ⚠️\n\n错误: ${fetchError.message}`; | |
if (stream) { | |
// 流式响应格式的错误 | |
const responseId = `chatcmpl-${uuidv4()}`; | |
res.write( | |
`data: ${JSON.stringify({ | |
id: responseId, | |
object: 'chat.completion.chunk', | |
created: Math.floor(Date.now() / 1000), | |
model: req.body.model || 'unknown', | |
choices: [ | |
{ | |
index: 0, | |
delta: { | |
content: errorMessage, | |
}, | |
}, | |
], | |
})}\n\n` | |
); | |
res.write('data: [DONE]\n\n'); | |
res.end(); | |
} else { | |
// 非流式响应格式的错误 | |
res.json({ | |
id: `chatcmpl-${uuidv4()}`, | |
object: 'chat.completion', | |
created: Math.floor(Date.now() / 1000), | |
model: req.body.model || 'unknown', | |
choices: [ | |
{ | |
index: 0, | |
message: { | |
role: 'assistant', | |
content: errorMessage, | |
}, | |
finish_reason: 'stop', | |
}, | |
], | |
usage: { | |
prompt_tokens: 0, | |
completion_tokens: 0, | |
total_tokens: 0, | |
}, | |
}); | |
} | |
return; // 重要:提前返回 | |
} | |
// 处理响应 | |
if (stream) { | |
res.setHeader('Content-Type', 'text/event-stream'); | |
res.setHeader('Cache-Control', 'no-cache'); | |
res.setHeader('Connection', 'keep-alive'); | |
const responseId = `chatcmpl-${uuidv4()}`; | |
try { | |
let responseEnded = false; // 添加标志,标记响应是否已结束 | |
let accumulatedThinking = ''; // 累积thinking内容 | |
let accumulatedContent = ''; // 累积content内容 | |
for await (const chunk of response.body) { | |
// 如果响应已结束,不再处理后续数据 | |
if (responseEnded) { | |
continue; | |
} | |
let result = {}; | |
try { | |
result = chunkToUtf8String(chunk); | |
} catch (error) { | |
logger.error('解析响应块失败:', error); | |
// 提供默认的空结果,避免后续处理出错 | |
result = { | |
reasoning_content: '', | |
content: '', | |
error: `解析错误: ${error.message}` | |
}; | |
} | |
// 检查是否返回了错误对象 | |
if (result && typeof result === 'object' && result.error) { | |
// 检查是否包含特定的无效cookie错误信息 | |
const errorStr = typeof result.error === 'string' ? result.error : JSON.stringify(result.error); | |
// 处理错误并获取结果 | |
const errorResult = handleCursorError(errorStr, bearerToken, originalAuthToken); | |
// 如果是需要移除的cookie,从API Key中移除 | |
if (errorResult.shouldRemoveCookie) { | |
const removed = keyManager.removeCookieFromApiKey(bearerToken, originalAuthToken); | |
logger.info(`Cookie移除${removed ? '成功' : '失败'}`); | |
// 如果成功移除,在错误消息中添加明确提示 | |
if (removed) { | |
errorResult.message = `⚠️ 目前Cookie已从API Key中移除 ⚠️\n\n${errorResult.message}`; | |
} | |
} | |
// 返回错误信息给客户端,作为assistant消息 | |
res.write( | |
`data: ${JSON.stringify({ | |
id: responseId, | |
object: 'chat.completion.chunk', | |
created: Math.floor(Date.now() / 1000), | |
model: req.body.model, | |
choices: [ | |
{ | |
index: 0, | |
delta: { | |
content: errorResult.message, | |
}, | |
}, | |
], | |
})}\n\n` | |
); | |
res.write('data: [DONE]\n\n'); | |
responseEnded = true; // 标记响应已结束 | |
break; // 跳出循环,不再处理后续数据 | |
} | |
// 处理thinking内容 | |
if (result.reasoning_content && result.reasoning_content.length > 0) { | |
// 累积thinking内容 | |
accumulatedThinking += result.reasoning_content; | |
// 发送accumulated thinking内容片段 | |
res.write( | |
`data: ${JSON.stringify({ | |
id: responseId, | |
object: 'chat.completion.chunk', | |
created: Math.floor(Date.now() / 1000), | |
model: req.body.model, | |
choices: [ | |
{ | |
index: 0, | |
delta: { | |
reasoning_content: result.reasoning_content, | |
}, | |
}, | |
], | |
})}\n\n` | |
); | |
} | |
// 处理常规内容 | |
if (result.content && result.content.length > 0) { | |
// 累积content内容 | |
accumulatedContent += result.content; | |
// 发送content内容 | |
res.write( | |
`data: ${JSON.stringify({ | |
id: responseId, | |
object: 'chat.completion.chunk', | |
created: Math.floor(Date.now() / 1000), | |
model: req.body.model, | |
choices: [ | |
{ | |
index: 0, | |
delta: { | |
content: result.content, | |
}, | |
}, | |
], | |
})}\n\n` | |
); | |
} | |
} | |
// 在循环结束后,如果响应尚未结束,发送[DONE]信号并结束响应 | |
if (!responseEnded) { | |
res.write('data: [DONE]\n\n'); | |
res.end(); | |
} | |
} catch (streamError) { | |
logger.error('Stream error:', streamError); | |
// 确保在发送错误信息前检查响应是否已结束 | |
if (!res.writableEnded) { | |
if (streamError.name === 'TimeoutError') { | |
// 将超时错误作为assistant消息发送 | |
const errorMessage = `⚠️ 请求超时 ⚠️\n\n错误:服务器响应超时,请稍后重试。`; | |
res.write( | |
`data: ${JSON.stringify({ | |
id: responseId, | |
object: 'chat.completion.chunk', | |
created: Math.floor(Date.now() / 1000), | |
model: req.body.model, | |
choices: [ | |
{ | |
index: 0, | |
delta: { | |
content: errorMessage, | |
}, | |
}, | |
], | |
})}\n\n` | |
); | |
} else { | |
// 将处理错误作为assistant消息发送 | |
const errorMessage = `⚠️ 处理错误 ⚠️\n\n错误:流处理出错,请稍后重试。\n\n${streamError.message || ''}`; | |
res.write( | |
`data: ${JSON.stringify({ | |
id: responseId, | |
object: 'chat.completion.chunk', | |
created: Math.floor(Date.now() / 1000), | |
model: req.body.model, | |
choices: [ | |
{ | |
index: 0, | |
delta: { | |
content: errorMessage, | |
}, | |
}, | |
], | |
})}\n\n` | |
); | |
} | |
res.write('data: [DONE]\n\n'); | |
res.end(); | |
} | |
} | |
} else { | |
try { | |
let text = ''; | |
let thinkingText = ''; | |
let responseEnded = false; // 添加标志,标记响应是否已结束 | |
for await (const chunk of response.body) { | |
// 如果响应已结束,不再处理后续数据 | |
if (responseEnded) { | |
continue; | |
} | |
let result = {}; | |
try { | |
result = chunkToUtf8String(chunk); | |
} catch (error) { | |
logger.error('非流式响应解析块失败:', error); | |
// 提供默认的空结果,避免后续处理出错 | |
result = { | |
reasoning_content: '', | |
content: '', | |
error: `解析错误: ${error.message}` | |
}; | |
} | |
// 输出完整的result内容和类型,便于调试 | |
//console.log("收到的非流式响应:", typeof result, result && typeof result === 'object' ? JSON.stringify(result) : result); | |
// 检查是否返回了错误对象 | |
if (result && typeof result === 'object' && result.error) { | |
//console.error('检测到错误响应:', result.error); | |
// 检查是否包含特定的无效cookie错误信息 | |
const errorStr = typeof result.error === 'string' ? result.error : JSON.stringify(result.error); | |
// 处理错误并获取结果 | |
const errorResult = handleCursorError(errorStr, bearerToken, originalAuthToken); | |
// 如果是需要移除的cookie,从API Key中移除 | |
if (errorResult.shouldRemoveCookie) { | |
const removed = keyManager.removeCookieFromApiKey(bearerToken, originalAuthToken); | |
logger.info(`Cookie移除${removed ? '成功' : '失败'}`); | |
// 如果成功移除,在错误消息中添加明确提示 | |
if (removed) { | |
errorResult.message = `⚠️ 目前Cookie已从API Key中移除 ⚠️\n\n${errorResult.message}`; | |
} | |
} | |
// 无效cookie错误,格式化为assistant消息 | |
res.json({ | |
id: `chatcmpl-${uuidv4()}`, | |
object: 'chat.completion', | |
created: Math.floor(Date.now() / 1000), | |
model, | |
choices: [ | |
{ | |
index: 0, | |
message: { | |
role: 'assistant', | |
content: errorResult.message, | |
}, | |
finish_reason: 'stop', | |
}, | |
], | |
usage: { | |
prompt_tokens: 0, | |
completion_tokens: 0, | |
total_tokens: 0, | |
}, | |
}); | |
responseEnded = true; // 标记响应已结束 | |
break; // 跳出循环,不再处理后续数据 | |
} | |
// 处理thinking内容 | |
if (result.reasoning_content && result.reasoning_content.length > 0) { | |
thinkingText += result.reasoning_content; | |
} | |
// 处理正常文本内容 | |
if (result.content && typeof result.content === 'string') { | |
text += result.content; | |
} | |
} | |
// 只有在响应尚未结束的情况下,才处理和返回结果 | |
if (!responseEnded) { | |
// 对解析后的字符串进行进一步处理 | |
text = text.replace(/^.*<\|END_USER\|>/s, ''); | |
text = text.replace(/^\n[a-zA-Z]?/, '').trim(); | |
// 用于非酒馆想要显示的思维链的如果存在thinking内容,添加标签 | |
// let finalContent = text; | |
// if (thinkingText.length > 0) { | |
// finalContent = `<think>\n${thinkingText}\n</think>\n${text}`; | |
// logger.info("finalContent:", finalContent); | |
// } | |
res.json({ | |
id: `chatcmpl-${uuidv4()}`, | |
object: 'chat.completion', | |
created: Math.floor(Date.now() / 1000), | |
model, | |
choices: [ | |
{ | |
index: 0, | |
message: { | |
role: 'assistant', | |
reasoning_content: thinkingText, | |
content: text, | |
}, | |
finish_reason: 'stop', | |
}, | |
], | |
usage: { | |
prompt_tokens: 0, | |
completion_tokens: 0, | |
total_tokens: 0, | |
}, | |
}); | |
} | |
} catch (error) { | |
logger.error('Non-stream error:', error); | |
// 确保在发送错误信息前检查响应是否已结束 | |
if (!res.headersSent) { | |
if (error.name === 'TimeoutError') { | |
// 使用统一的错误格式 | |
const errorMessage = `⚠️ 请求超时 ⚠️\n\n错误:服务器响应超时,请稍后重试。`; | |
return res.json({ | |
id: `chatcmpl-${uuidv4()}`, | |
object: 'chat.completion', | |
created: Math.floor(Date.now() / 1000), | |
model: req.body.model || 'unknown', | |
choices: [ | |
{ | |
index: 0, | |
message: { | |
role: 'assistant', | |
content: errorMessage, | |
}, | |
finish_reason: 'stop', | |
}, | |
], | |
usage: { | |
prompt_tokens: 0, | |
completion_tokens: 0, | |
total_tokens: 0, | |
}, | |
}); | |
} | |
throw error; | |
} | |
} | |
} | |
} catch (error) { | |
logger.error('Error:', error); | |
if (!res.headersSent) { | |
const errorText = error.name === 'TimeoutError' ? '请求超时' : '服务器内部错误'; | |
if (req.body.stream) { | |
// 流式响应格式的错误 | |
const responseId = `chatcmpl-${uuidv4()}`; | |
// 添加清晰的错误提示 | |
const errorMessage = `⚠️ 请求失败 ⚠️\n\n错误:${errorText},请稍后重试。\n\n${error.message || ''}`; | |
res.write( | |
`data: ${JSON.stringify({ | |
id: responseId, | |
object: 'chat.completion.chunk', | |
created: Math.floor(Date.now() / 1000), | |
model: req.body.model || 'unknown', | |
choices: [ | |
{ | |
index: 0, | |
delta: { | |
content: errorMessage, | |
}, | |
}, | |
], | |
})}\n\n` | |
); | |
res.write('data: [DONE]\n\n'); | |
res.end(); | |
} else { | |
// 非流式响应格式的错误 | |
// 添加清晰的错误提示 | |
const errorMessage = `⚠️ 请求失败 ⚠️\n\n错误:${errorText},请稍后重试。\n\n${error.message || ''}`; | |
res.json({ | |
id: `chatcmpl-${uuidv4()}`, | |
object: 'chat.completion', | |
created: Math.floor(Date.now() / 1000), | |
model: req.body.model || 'unknown', | |
choices: [ | |
{ | |
index: 0, | |
message: { | |
role: 'assistant', | |
content: errorMessage, | |
}, | |
finish_reason: 'stop', | |
}, | |
], | |
usage: { | |
prompt_tokens: 0, | |
completion_tokens: 0, | |
total_tokens: 0, | |
}, | |
}); | |
} | |
} | |
} | |
}); | |
// 触发Cookie刷新 | |
router.post("/refresh-cookies", async (req, res) => { | |
try { | |
// 如果已经有刷新进程在运行,则返回错误 | |
if (refreshStatus.isRunning) { | |
return res.status(409).json({ | |
success: false, | |
message: '已有刷新进程在运行,请等待完成后再试' | |
}); | |
} | |
// 获取请求参数 | |
const apiKey = req.query.apiKey || ''; | |
// 重置刷新状态 | |
refreshStatus = { | |
isRunning: true, | |
status: 'running', | |
message: '正在启动刷新进程...', | |
startTime: new Date(), | |
endTime: null, | |
error: null | |
}; | |
logger.info(`收到刷新Cookie请求,API Key: ${apiKey || '所有'}`); | |
// 构建命令行参数 | |
const args = []; | |
if (apiKey) { | |
args.push(apiKey); | |
} | |
// 获取auto-refresh-cookies.js的绝对路径 | |
const scriptPath = path.resolve(__dirname, '../../auto-refresh-cookies.js'); | |
// 启动子进程执行刷新脚本 | |
const refreshProcess = spawn('node', [scriptPath, ...args], { | |
stdio: ['ignore', 'pipe', 'pipe'] | |
}); | |
// 收集输出 | |
let output = ''; | |
refreshProcess.stdout.on('data', (data) => { | |
const text = data.toString(); | |
output += text; | |
logger.info(`刷新进程输出: ${text}`); | |
// 更新状态消息 | |
if (text.includes('开始自动刷新')) { | |
refreshStatus.message = '正在刷新Cookie...'; | |
} else if (text.includes('刷新结果:')) { | |
refreshStatus.message = text.trim(); | |
} | |
}); | |
refreshProcess.stderr.on('data', (data) => { | |
const text = data.toString(); | |
output += text; | |
logger.error(`刷新进程错误: ${text}`); | |
// 更新错误信息 | |
refreshStatus.error = text.trim(); | |
refreshStatus.message = `发生错误: ${text.trim()}`; | |
}); | |
refreshProcess.on('close', (code) => { | |
logger.info(`刷新进程退出,代码: ${code}`); | |
refreshStatus.isRunning = false; | |
refreshStatus.endTime = new Date(); | |
if (code === 0) { | |
refreshStatus.status = 'completed'; | |
// 提取成功信息 | |
const successMatch = output.match(/成功刷新 (\d+) 个/); | |
if (successMatch) { | |
refreshStatus.message = `成功刷新 ${successMatch[1]} 个API Key的Cookie`; | |
} else { | |
refreshStatus.message = '刷新完成'; | |
} | |
// 子进程执行完成后,重新初始化API Keys来加载新的Cookie | |
try { | |
const keyManager = require('../utils/keyManager'); | |
logger.info('子进程刷新Cookie完成,重新初始化主进程中的API Keys...'); | |
keyManager.initializeApiKeys(); | |
logger.info('主进程API Keys重新加载完成'); | |
} catch (initError) { | |
logger.error('重新初始化API Keys失败:', initError); | |
} | |
} else { | |
refreshStatus.status = 'failed'; | |
refreshStatus.message = refreshStatus.error || '刷新失败,请查看服务器日志'; | |
} | |
}); | |
// 立即返回响应,不等待刷新完成 | |
return res.json({ | |
success: true, | |
message: '刷新请求已接受,正在后台处理' | |
}); | |
} catch (error) { | |
logger.error('触发刷新Cookie失败:', error); | |
// 更新刷新状态 | |
refreshStatus.isRunning = false; | |
refreshStatus.status = 'failed'; | |
refreshStatus.endTime = new Date(); | |
refreshStatus.error = error.message; | |
refreshStatus.message = `触发刷新失败: ${error.message}`; | |
return res.status(500).json({ | |
success: false, | |
message: `触发刷新失败: ${error.message}` | |
}); | |
} | |
}); | |
// 查询Cookie刷新状态 | |
router.get("/refresh-status", (req, res) => { | |
try { | |
// 返回当前刷新状态 | |
return res.json({ | |
success: true, | |
data: { | |
...refreshStatus, | |
isRunning: refreshStatus.isRunning || false, | |
status: refreshStatus.status || 'unknown', | |
message: refreshStatus.message || '未触发刷新', | |
startTime: refreshStatus.startTime || null, | |
endTime: refreshStatus.endTime || null | |
} | |
}); | |
} catch (error) { | |
logger.error('获取刷新状态失败:', error); | |
return res.status(500).json({ | |
success: false, | |
message: `获取刷新状态失败: ${error.message}` | |
}); | |
} | |
}); | |
// 生成获取Cookie的链接 | |
router.post('/generate-cookie-link', async (req, res) => { | |
try { | |
// 验证管理员权限 | |
const authHeader = req.headers.authorization; | |
if (!authHeader || !authHeader.startsWith('Bearer ')) { | |
return res.status(401).json({ | |
success: false, | |
message: '未提供认证token' | |
}); | |
} | |
const token = authHeader.split(' ')[1]; | |
const authResult = admin.verifyToken(token); | |
if (!authResult.success) { | |
return res.status(401).json({ | |
success: false, | |
message: '认证失败' | |
}); | |
} | |
// 生成UUID和PKCE验证器 | |
const uuid = uuidv4(); | |
const verifier = crypto.randomBytes(32).toString('base64url'); | |
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url'); | |
// 生成登录链接 | |
const loginUrl = `https://www.cursor.com/ja/loginDeepControl?challenge=${challenge}&uuid=${uuid}&mode=login`; | |
// 记录请求信息 | |
pendingCookieRequests.set(uuid, { | |
uuid, | |
verifier, | |
status: 'waiting', | |
created: Date.now(), | |
apiKey: req.body.apiKey || '', // 目标API Key,空字符串表示所有API Key | |
lastCheck: Date.now(), | |
cookie: null | |
}); | |
// 设置60分钟后自动清理 | |
setTimeout(() => { | |
if (pendingCookieRequests.has(uuid)) { | |
pendingCookieRequests.delete(uuid); | |
} | |
}, 60 * 60 * 1000); | |
return res.json({ | |
success: true, | |
url: loginUrl, | |
uuid: uuid | |
}); | |
} catch (error) { | |
logger.error('生成Cookie链接失败:', error); | |
return res.status(500).json({ | |
success: false, | |
message: error.message | |
}); | |
} | |
}); | |
// 查询Cookie获取状态 | |
router.get('/check-cookie-status', async (req, res) => { | |
try { | |
const { uuid } = req.query; | |
if (!uuid || !pendingCookieRequests.has(uuid)) { | |
return res.json({ | |
success: false, | |
status: 'failed', | |
message: '无效的UUID或请求已过期' | |
}); | |
} | |
const request = pendingCookieRequests.get(uuid); | |
request.lastCheck = Date.now(); | |
// 检查状态 | |
if (request.status === 'waiting') { | |
// 检查Cursor API获取token | |
try { | |
const apiUrl = `https://api2.cursor.sh/auth/poll?uuid=${uuid}&verifier=${request.verifier}`; | |
const response = await fetch(apiUrl, { | |
method: 'GET', | |
headers: { | |
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.210 Safari/537.36', | |
'Accept': '*/*', | |
'Origin': 'vscode-file://vscode-app', | |
'x-ghost-mode': 'true' | |
}, | |
timeout: 5000 | |
}); | |
if (response.ok) { | |
const data = await response.json(); | |
if (data && data.accessToken) { | |
// 获取到了Cookie | |
request.cookie = data.accessToken; | |
request.status = 'success'; | |
// 将Cookie添加到目标API Key | |
let message = ''; | |
if (request.apiKey) { | |
// 添加到特定API Key | |
const apiKey = request.apiKey; | |
const cookies = keyManager.getAllCookiesForApiKey(apiKey) || []; | |
cookies.push(request.cookie); | |
keyManager.addOrUpdateApiKey(apiKey, cookies); | |
message = `Cookie已添加到API Key: ${apiKey}`; | |
} else { | |
// 添加到所有API Key | |
const apiKeys = keyManager.getAllApiKeys(); | |
for (const apiKey of apiKeys) { | |
const cookies = keyManager.getAllCookiesForApiKey(apiKey) || []; | |
cookies.push(request.cookie); | |
keyManager.addOrUpdateApiKey(apiKey, cookies); | |
} | |
message = `Cookie已添加到所有API Key,共${apiKeys.length}个`; | |
} | |
// 完成后从等待列表中移除 | |
pendingCookieRequests.delete(uuid); | |
return res.json({ | |
success: true, | |
message: message | |
}); | |
} | |
} | |
// 如果没有获取到Cookie,继续等待 | |
return res.json({ | |
success: false, | |
status: 'waiting' | |
}); | |
} catch (error) { | |
logger.error('查询Cursor API失败:', error); | |
// 发生错误但继续等待,不改变状态 | |
return res.json({ | |
success: false, | |
status: 'waiting', | |
message: '轮询过程中出现错误,继续等待' | |
}); | |
} | |
} else if (request.status === 'success') { | |
// 已成功,返回结果 | |
const message = request.apiKey | |
? `Cookie已添加到API Key: ${request.apiKey}` | |
: `Cookie已添加到所有API Key`; | |
// 完成后从等待列表中移除 | |
pendingCookieRequests.delete(uuid); | |
return res.json({ | |
success: true, | |
message: message | |
}); | |
} else { | |
// 失败 | |
pendingCookieRequests.delete(uuid); | |
return res.json({ | |
success: false, | |
status: 'failed', | |
message: '获取Cookie失败' | |
}); | |
} | |
} catch (error) { | |
logger.error('检查Cookie状态失败:', error); | |
return res.status(500).json({ | |
success: false, | |
status: 'failed', | |
message: error.message | |
}); | |
} | |
}); | |
// 获取日志API | |
router.get("/logs", (req, res) => { | |
try { | |
// 获取查询参数 | |
const level = req.query.level; | |
const search = req.query.search; | |
const page = parseInt(req.query.page) || 1; | |
const pageSize = parseInt(req.query.pageSize) || 100; | |
const startTime = req.query.startTime; | |
const endTime = req.query.endTime; | |
// 过滤参数 | |
const filter = { | |
level, | |
search, | |
page, | |
pageSize, | |
startTime, | |
endTime | |
}; | |
// 获取日志 | |
const logs = logger.getLogs(filter); | |
return res.json({ | |
success: true, | |
data: logs | |
}); | |
} catch (error) { | |
logger.error('获取日志失败:', error); | |
return res.status(500).json({ | |
success: false, | |
message: `获取日志失败: ${error.message}` | |
}); | |
} | |
}); | |
// 清除内存日志 | |
router.delete("/logs", (req, res) => { | |
try { | |
logger.clearMemoryLogs(); | |
return res.json({ | |
success: true, | |
message: '日志已清除' | |
}); | |
} catch (error) { | |
logger.error('清除日志失败:', error); | |
return res.status(500).json({ | |
success: false, | |
message: `清除日志失败: ${error.message}` | |
}); | |
} | |
}); | |
async function others(authToken, clientKey, checksum, cursorClientVersion, sessionid){ | |
try { | |
// 定义所有API端点配置 | |
const endpoints = [ | |
{ | |
url: 'https://api2.cursor.sh/aiserver.v1.AiService/CheckFeatureStatus', | |
method: 'POST', | |
headers: { | |
'accept-encoding': 'gzip', | |
'authorization': `Bearer ${authToken}`, | |
'connect-protocol-version': '1', | |
'content-type': 'application/proto', | |
'user-agent': 'connect-es/1.6.1', | |
'x-client-key': clientKey, | |
'x-cursor-checksum': checksum, | |
'x-cursor-client-version': cursorClientVersion, | |
'x-cursor-config-version': uuidv4(), | |
'x-cursor-timezone': 'Asia/Tokyo', | |
'x-ghost-mode': 'true', | |
'x-new-onboarding-completed': 'false', | |
'x-session-id': sessionid, | |
'Host': 'api2.cursor.sh', | |
}, | |
body: '', // 实际长度为23字节 | |
timeout: { | |
connect: 5000, | |
read: 30000 | |
} | |
}, | |
{ | |
url: 'https://api2.cursor.sh/aiserver.v1.AiService/AvailableDocs', | |
method: 'POST', | |
headers: { | |
'authorization': `Bearer ${authToken}`, | |
'connect-accept-encoding': 'gzip', | |
'connect-protocol-version': '1', | |
'content-type': 'application/proto', | |
'user-agent': 'connect-es/1.6.1', | |
'x-amzn-trace-id': `Root=${uuidv4()}`, | |
'x-client-key': clientKey, | |
'x-cursor-checksum': checksum, | |
'x-cursor-client-version': cursorClientVersion, | |
'x-cursor-config-version': uuidv4(), | |
'x-cursor-timezone': 'Asia/Tokyo', | |
'x-ghost-mode': 'true', | |
'x-request-id': uuidv4(), | |
'x-session-id': sessionid, | |
'Host': 'api2.cursor.sh', | |
}, | |
timeout: { | |
connect: 5000, | |
read: 30000 | |
} | |
}, | |
{ | |
url: 'https://api2.cursor.sh/aiserver.v1.DashboardService/GetTeams', | |
method: 'POST', | |
headers: { | |
'accept-encoding': 'gzip', | |
'authorization': `Bearer ${authToken}`, | |
'connect-protocol-version': '1', | |
'content-type': 'application/proto', | |
'user-agent': 'connect-es/1.6.1', | |
'x-amzn-trace-id': `Root=${uuidv4()}`, | |
'x-client-key': clientKey, | |
'x-cursor-checksum': checksum, | |
'x-cursor-client-version': cursorClientVersion, | |
'x-cursor-config-version': uuidv4(), | |
'x-cursor-timezone': 'Asia/Tokyo', | |
'x-ghost-mode': 'true', | |
'x-new-onboarding-completed': 'false', | |
'x-request-id': uuidv4(), | |
'x-session-id': sessionid, | |
'Host': 'api2.cursor.sh', | |
}, | |
body: '', | |
timeout: { | |
connect: 5000, | |
read: 30000 | |
} | |
}, | |
{ | |
url: 'https://api2.cursor.sh/auth/full_stripe_profile', | |
method: 'GET', | |
headers: { | |
'Host': 'api2.cursor.sh', | |
'Connection': 'keep-alive', | |
'Authorization': `Bearer ${authToken}`, | |
'x-new-onboarding-completed': 'false', | |
'x-ghost-mode': 'true', | |
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Cursor/0.49.4 Chrome/132.0.6834.210 Electron/34.3.4 Safari/537.36', | |
'Accept': '*/*', | |
'Origin': 'vscode-file://vscode-app', | |
'Sec-Fetch-Site': 'cross-site', | |
'Sec-Fetch-Mode': 'cors', | |
'Sec-Fetch-Dest': 'empty', | |
'Accept-Encoding': 'gzip, deflate, br, zstd', | |
'Accept-Language': 'zh-CN' | |
}, | |
timeout: { | |
connect: 5000, | |
read: 30000 | |
} | |
}, | |
{ | |
url: 'https://api2.cursor.sh/aiserver.v1.DashboardService/GetUsageBasedPremiumRequests', | |
method: 'POST', | |
headers: { | |
'accept-encoding': 'gzip', | |
'authorization': `Bearer ${authToken}`, | |
'connect-protocol-version': '1', | |
'content-type': 'application/proto', | |
'user-agent': 'connect-es/1.6.1', | |
'x-client-key': clientKey, | |
'x-cursor-checksum': checksum, | |
'x-cursor-client-version': cursorClientVersion, | |
'x-cursor-config-version': uuidv4(), | |
'x-cursor-timezone': 'Asia/Tokyo', | |
'x-ghost-mode': 'true', | |
'x-new-onboarding-completed': 'false', | |
'x-session-id': sessionid, | |
'Host': 'api2.cursor.sh', | |
}, | |
body: '', | |
timeout: { | |
connect: 5000, | |
read: 30000 | |
} | |
}, | |
{ | |
url: 'https://api2.cursor.sh/aiserver.v1.DashboardService/GetHardLimit', | |
method: 'POST', | |
headers: { | |
'accept-encoding': 'gzip', | |
'authorization': `Bearer ${authToken}`, | |
'connect-protocol-version': '1', | |
'content-type': 'application/proto', | |
'user-agent': 'connect-es/1.6.1', | |
'x-client-key': clientKey, | |
'x-cursor-checksum': checksum, | |
'x-cursor-client-version': cursorClientVersion, | |
'x-cursor-config-version': uuidv4(), | |
'x-cursor-timezone': 'Asia/Tokyo', | |
'x-ghost-mode': 'true', | |
'x-new-onboarding-completed': 'false', | |
'x-session-id': sessionid, | |
'Host': 'api2.cursor.sh', | |
}, | |
body: '', | |
timeout: { | |
connect: 5000, | |
read: 30000 | |
} | |
} | |
]; | |
// 随机选择2-4个接口调用 | |
const minApis = 2; | |
const maxApis = 4; | |
const numApisToCall = Math.floor(Math.random() * (maxApis - minApis + 1)) + minApis; | |
// 随机打乱数组并取前几个元素 | |
const shuffledEndpoints = [...endpoints].sort(() => 0.5 - Math.random()).slice(0, numApisToCall); | |
// 检查是否使用辅助代理服务器 | |
const useOthersProxy = process.env.USE_OTHERS_PROXY === 'true'; | |
// 使用Promise.allSettled确保即使一个请求失败也不会影响其他请求 | |
const results = await Promise.allSettled(shuffledEndpoints.map(async (endpoint) => { | |
try { | |
let response; | |
if (useOthersProxy) { | |
// 使用代理服务器方式 | |
logger.debug(`使用辅助代理服务器请求: ${endpoint.url}`); | |
// 构造代理请求对象 | |
const proxyPayload = { | |
url: endpoint.url, | |
method: endpoint.method, | |
headers: endpoint.headers, | |
body: endpoint.body || undefined, | |
stream: false | |
}; | |
// 使用代理服务器 | |
response = await fetch('http://localhost:10654/proxy', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(proxyPayload), | |
// 保留原超时设置 | |
timeout: endpoint.timeout | |
}); | |
} else { | |
// 直接请求方式 | |
logger.debug(`直接请求: ${endpoint.url}`); | |
response = await fetch(endpoint.url, { | |
method: endpoint.method, | |
headers: endpoint.headers, | |
body: endpoint.body || undefined, | |
timeout: endpoint.timeout | |
}); | |
} | |
return { | |
url: endpoint.url, | |
status: response.status, | |
success: true | |
}; | |
} catch (error) { | |
// 记录单个请求的错误,但不中断整体流程 | |
logger.debug(`其它API调用失败 (${endpoint.url}): ${error.message}`); | |
return { | |
url: endpoint.url, | |
success: false, | |
error: error.message | |
}; | |
} | |
})); | |
// 记录请求结果统计 | |
const successCount = results.filter(r => r.status === 'fulfilled' && r.value.success).length; | |
logger.debug(`其它API调用完成: 成功 ${successCount}/${results.length}`); | |
return true; | |
} catch (error) { | |
// 记录整体错误,但不影响主流程 | |
logger.error(`others函数执行出错: ${error.message}`); | |
return false; | |
} | |
} | |
// 在文件末尾添加错误处理函数 | |
function handleCursorError(errorStr, bearerToken, originalAuthToken) { | |
let message = ''; | |
let shouldRemoveCookie = false; | |
if (errorStr.includes('Not logged in')) { | |
// 更明确的错误日志 | |
if (originalAuthToken === bearerToken) { | |
logger.error(`检测到API Key "${bearerToken}" 中没有可用Cookie,正在尝试以向后兼容模式使用API Key本身`); | |
message = `错误:API Key "${bearerToken}" 中没有可用的Cookie。请添加有效的Cookie到此API Key,或使用其他有效的API Key。\n\n详细信息:${errorStr}`; | |
} else { | |
logger.error('检测到无效cookie:', originalAuthToken); | |
message = `错误:Cookie无效或已过期,请更新Cookie。\n\n详细信息:${errorStr}`; | |
} | |
shouldRemoveCookie = true; | |
} else if (errorStr.includes('You\'ve reached your trial request limit') || errorStr.includes('You\'ve reached the usage limit for free usage')) { | |
logger.error('检测到额度用尽cookie:', originalAuthToken); | |
message = `错误:Cookie使用额度已用完,请更换Cookie或等待刷新。\n\n详细信息:${errorStr}`; | |
shouldRemoveCookie = true; | |
} else if (errorStr.includes('User is unauthorized')) { | |
logger.error('检测到未授权cookie:', originalAuthToken); | |
message = `错误:Cookie已被封禁或失效,请更换Cookie。\n\n详细信息:${errorStr}`; | |
shouldRemoveCookie = true; | |
} else if (errorStr.includes('suspicious activity checks')) { | |
logger.error('检测到IP黑名单:', originalAuthToken); | |
message = `错误:IP可能被列入黑名单,请尝试更换网络环境或使用代理。\n\n详细信息:${errorStr}`; | |
shouldRemoveCookie = false; | |
} else if (errorStr.includes('Too many computers')) { | |
logger.error('检测到账户暂时被封禁:', originalAuthToken); | |
message = `错误:账户因在多台设备登录而暂时被封禁,请稍后再试或更换账户。\n\n详细信息:${errorStr}`; | |
shouldRemoveCookie = true; | |
} else if (errorStr.includes('Login expired') || errorStr.includes('login expired')) { | |
logger.error('检测到登录过期cookie:', originalAuthToken); | |
message = `错误:Cookie登录已过期,请更新Cookie。\n\n详细信息:${errorStr}`; | |
shouldRemoveCookie = true; | |
} else if(errorStr.includes('your request has been blocked due to the use of a temporary email service for this account')) { | |
logger.error('检测到临时邮箱:', originalAuthToken); | |
message = `错误:请求被阻止,检测到临时邮箱服务,请更换邮箱。\n\n详细信息:${errorStr}`; | |
shouldRemoveCookie = true; | |
} else if (errorStr.includes('Your request has been blocked as our system has detected suspicious activity from your account')) { | |
logger.error('检测到账户异常:', originalAuthToken); | |
message = `错误:请求被阻止,可能是假ban,多重试几次/更换cookie/更换设备。\n\n详细信息:${errorStr}`; | |
shouldRemoveCookie = false; | |
} else { | |
// 非Cookie相关错误 | |
logger.error('检测到其他错误:', errorStr); | |
message = `错误:请求失败。\n\n详细信息:${errorStr}`; | |
shouldRemoveCookie = false; | |
} | |
return { | |
message, | |
shouldRemoveCookie | |
}; | |
} | |
module.exports = router; | |