Spaces:
Sleeping
Sleeping
| const os = require('os'); | |
| const zlib = require('zlib'); | |
| const crypto = require('crypto'); | |
| const { v4: uuidv4 } = require('uuid'); | |
| const $root = require('../proto/message.js'); | |
| const config = require('../config/config'); | |
| function generateCursorBody(messages, modelName) { | |
| const instruction = messages | |
| .filter(msg => msg.role === 'system') | |
| .map(msg => msg.content) | |
| .join('\n') | |
| const formattedMessages = messages | |
| .filter(msg => msg.role !== 'system') | |
| .map(msg => ({ | |
| content: msg.content, | |
| role: msg.role === 'user' ? 1 : 2, | |
| messageId: uuidv4(), | |
| ...(msg.role === 'user' ? { chatModeEnum: 1 } : {}) | |
| //...(msg.role !== 'user' ? { summaryId: uuidv4() } : {}) | |
| })); | |
| const messageIds = formattedMessages.map(msg => { | |
| const { role, messageId, summaryId } = msg; | |
| return summaryId ? { role, messageId, summaryId } : { role, messageId }; | |
| }); | |
| const body = { | |
| request:{ | |
| messages: formattedMessages, | |
| unknown2: 1, | |
| instruction: { | |
| instruction: instruction | |
| }, | |
| unknown4: 1, | |
| model: { | |
| name: modelName, | |
| empty: '', | |
| }, | |
| webTool: "", | |
| unknown13: 1, | |
| cursorSetting: { | |
| name: "cursor\\aisettings", | |
| unknown3: "", | |
| unknown6: { | |
| unknwon1: "", | |
| unknown2: "" | |
| }, | |
| unknown8: 1, | |
| unknown9: 1 | |
| }, | |
| unknown19: 1, | |
| //unknown22: 1, | |
| conversationId: uuidv4(), | |
| metadata: { | |
| os: "win32", | |
| arch: "x64", | |
| version: "10.0.22631", | |
| path: "C:\\Program Files\\PowerShell\\7\\pwsh.exe", | |
| timestamp: new Date().toISOString(), | |
| }, | |
| unknown27: 0, | |
| //unknown29: "", | |
| messageIds: messageIds, | |
| largeContext: 0, | |
| unknown38: 0, | |
| chatModeEnum: 1, | |
| unknown47: "", | |
| unknown48: 0, | |
| unknown49: 0, | |
| unknown51: 0, | |
| unknown53: 1, | |
| chatMode: "Ask" | |
| } | |
| }; | |
| const errMsg = $root.StreamUnifiedChatWithToolsRequest.verify(body); | |
| if (errMsg) throw Error(errMsg); | |
| const instance = $root.StreamUnifiedChatWithToolsRequest.create(body); | |
| let buffer = $root.StreamUnifiedChatWithToolsRequest.encode(instance).finish(); | |
| let magicNumber = 0x00 | |
| if (formattedMessages.length >= 3){ | |
| buffer = zlib.gzipSync(buffer) | |
| magicNumber = 0x01 | |
| } | |
| const finalBody = Buffer.concat([ | |
| Buffer.from([magicNumber]), | |
| Buffer.from(buffer.length.toString(16).padStart(8, '0'), 'hex'), | |
| buffer | |
| ]) | |
| return finalBody | |
| } | |
| function chunkToUtf8String(chunk) { | |
| const thinkingOutput = [] | |
| const textOutput = [] | |
| const buffer = Buffer.from(chunk, 'hex'); | |
| //console.log("Chunk buffer:", buffer.toString('hex')) | |
| try { | |
| for(let i = 0; i < buffer.length; i++){ | |
| const magicNumber = parseInt(buffer.subarray(i, i + 1).toString('hex'), 16) | |
| const dataLength = parseInt(buffer.subarray(i + 1, i + 5).toString('hex'), 16) | |
| const data = buffer.subarray(i + 5, i + 5 + dataLength) | |
| //console.log("Parsed buffer:", magicNumber, dataLength, data.toString('hex')) | |
| if (magicNumber == 0 || magicNumber == 1) { | |
| const gunzipData = magicNumber == 0 ? data : zlib.gunzipSync(data) | |
| const response = $root.StreamUnifiedChatWithToolsResponse.decode(gunzipData); | |
| const thinking = response?.message?.thinking?.content | |
| if (thinking !== undefined){ | |
| thinkingOutput.push(thinking) | |
| //console.log(thinking) | |
| } | |
| const content = response?.message?.content | |
| if (content !== undefined){ | |
| textOutput.push(content) | |
| //console.log(content) | |
| } | |
| } | |
| else if (magicNumber == 2 || magicNumber == 3) { | |
| // Json message | |
| const gunzipData = magicNumber == 2 ? data : zlib.gunzipSync(data) | |
| const utf8 = gunzipData.toString('utf-8') | |
| const message = JSON.parse(utf8) | |
| if (message != null && (typeof message !== 'object' || | |
| (Array.isArray(message) ? message.length > 0 : Object.keys(message).length > 0))){ | |
| //results.push(utf8) | |
| console.error(utf8) | |
| } | |
| } | |
| else { | |
| //console.log('Unknown magic number when parsing chunk response: ' + magicNumber) | |
| } | |
| i += 5 + dataLength - 1 | |
| } | |
| } catch (err) { | |
| console.log('Error parsing chunk response:', err) | |
| } | |
| const text = textOutput.join(''); | |
| const thinking = thinkingOutput.join(''); | |
| // Detectar mensaje de rate limit | |
| const isRateLimited = checkForRateLimit(text); | |
| return { | |
| thinking, | |
| text, | |
| isRateLimited | |
| } | |
| } | |
| // Verifica si el texto contiene el mensaje de rate limit de Cursor | |
| function checkForRateLimit(text) { | |
| // Versi贸n original (texto plano) | |
| const rateLimitMessage = "You've hit your free requests limit. Upgrade to Pro for more usage, frontier models, Background Agents, and more."; | |
| // Versi贸n con formato markdown (con asteriscos y enlace) | |
| const markdownRateLimitMessage = "*You've hit your free requests limit. [Upgrade to Pro](https://www.cursor.com/api/auth/checkoutDeepControl?tier=pro) for more usage, frontier models, Background Agents, and more.*"; | |
| // URL espec铆fica que aparece en el mensaje de rate limit | |
| const rateLimitUrl = "https://www.cursor.com/api/auth/checkoutDeepControl?tier=pro"; | |
| // Verificar cualquiera de las tres condiciones | |
| return text.includes(rateLimitMessage) || | |
| text.includes(markdownRateLimitMessage) || | |
| text.includes(rateLimitUrl); | |
| } | |
| // Verifica si la respuesta contiene el error espec铆fico de actividad sospechosa | |
| function checkForSuspiciousActivityError(response) { | |
| if (!response || !response.body) return false; | |
| try { | |
| // Intentar parsear el cuerpo de la respuesta como JSON | |
| const responseBody = typeof response.body === 'string' ? JSON.parse(response.body) : response.body; | |
| // Verificar si es el error espec铆fico | |
| return responseBody?.error?.code === 'unauthenticated' && | |
| responseBody?.error?.details?.[0]?.debug?.error === 'ERROR_UNAUTHORIZED' && | |
| responseBody?.error?.details?.[0]?.debug?.details?.detail?.includes('Your request has been blocked as our system has detected suspicious activity from your account'); | |
| } catch (error) { | |
| // Si hay un error al parsear el JSON, no es el error que buscamos | |
| return false; | |
| } | |
| } | |
| function generateHashed64Hex(input, salt = '') { | |
| const hash = crypto.createHash('sha256'); | |
| hash.update(input + salt); | |
| return hash.digest('hex'); | |
| } | |
| function obfuscateBytes(byteArray) { | |
| let t = 165; | |
| for (let r = 0; r < byteArray.length; r++) { | |
| byteArray[r] = (byteArray[r] ^ t) + (r % 256); | |
| t = byteArray[r]; | |
| } | |
| return byteArray; | |
| } | |
| function generateCursorChecksum(token) { | |
| const machineId = generateHashed64Hex(token, 'machineId'); | |
| const macMachineId = generateHashed64Hex(token, 'macMachineId'); | |
| const timestamp = Math.floor(Date.now() / 1e6); | |
| const byteArray = new Uint8Array([ | |
| (timestamp >> 40) & 255, | |
| (timestamp >> 32) & 255, | |
| (timestamp >> 24) & 255, | |
| (timestamp >> 16) & 255, | |
| (timestamp >> 8) & 255, | |
| 255 & timestamp, | |
| ]); | |
| const obfuscatedBytes = obfuscateBytes(byteArray); | |
| const encodedChecksum = Buffer.from(obfuscatedBytes).toString('base64'); | |
| return `${encodedChecksum}${machineId}/${macMachineId}`; | |
| } | |
| /** | |
| * Obtiene el token de autenticaci贸n desde la configuraci贸n o headers | |
| * Prioriza API keys autenticadas, luego AUTH_COOKIEs combinadas (entorno + base de datos), luego el header Authorization | |
| * Si se proporciona un modelo y el modo privilegiado est谩 activado, selecciona el tipo de cookie adecuado | |
| * @param {Object} req - Objeto request de Express | |
| * @param {Object} config - Configuraci贸n de la aplicaci贸n | |
| * @param {string} model - Modelo solicitado (opcional) | |
| * @returns {string|null} Token de autenticaci贸n | |
| */ | |
| function getAuthToken(req, config, model = null) { | |
| // Si hay una API key autenticada, usar el AUTH_COOKIE configurado (esto significa que la API key es v谩lida) | |
| if (req.isAPIKeyAuth && config.authCookie) { | |
| console.log('[AUTH] Usando AUTH_COOKIE para API key autenticada:', req.apiKey.name); | |
| // Si estamos en modo privilegiado y se especific贸 un modelo, seleccionar el tipo de cookie adecuado | |
| if (config.isPrivilegedMode && model) { | |
| if (config.premiumModels.includes(model)) { | |
| console.log(`[AUTH] Modelo ${model} requiere cookie premium`); | |
| return config.getPremiumAuthCookies() || config.authCookie; | |
| } else { | |
| console.log(`[AUTH] Modelo ${model} usa cookie normal`); | |
| return config.getNormalAuthCookies() || config.authCookie; | |
| } | |
| } | |
| return config.authCookie; | |
| } | |
| // Si estamos en modo privilegiado y se especific贸 un modelo, seleccionar el tipo de cookie adecuado | |
| if (config.isPrivilegedMode && model && config.authCookie) { | |
| if (config.premiumModels.includes(model)) { | |
| console.log(`[AUTH] Modelo ${model} requiere cookie premium`); | |
| return config.getPremiumAuthCookies() || config.authCookie; | |
| } else { | |
| console.log(`[AUTH] Modelo ${model} usa cookie normal`); | |
| return config.getNormalAuthCookies() || config.authCookie; | |
| } | |
| } | |
| // Priorizar AUTH_COOKIEs combinadas (entorno + base de datos) | |
| if (config.authCookie) { | |
| console.log('[AUTH] Usando AUTH_COOKIEs combinadas (entorno + base de datos)'); | |
| return config.authCookie; | |
| } | |
| // Fallback al header Authorization (si no es una API key v谩lida) | |
| if (!req.isAPIKeyAuth) { | |
| const bearerToken = req.headers.authorization?.replace('Bearer ', ''); | |
| if (bearerToken && !bearerToken.startsWith('sk-')) { | |
| console.log('[AUTH] Usando token del header Authorization'); | |
| return bearerToken; | |
| } | |
| } | |
| console.log('[AUTH] No se encontr贸 token de autenticaci贸n v谩lido'); | |
| return null; | |
| } | |
| /** | |
| * Parsea el tiempo de rotaci贸n de cookies (formato: 5s, 5m, 5h) | |
| * @param {string} timeString - Cadena de tiempo con formato (ej: "5m") | |
| * @returns {number} Tiempo en milisegundos | |
| */ | |
| function parseRotationTime(timeString) { | |
| const match = timeString.match(/^(\d+)([smh])$/); | |
| if (!match) return 5 * 60 * 1000; // Default: 5 minutos | |
| const value = parseInt(match[1]); | |
| const unit = match[2]; | |
| switch (unit) { | |
| case 's': return value * 1000; // segundos | |
| case 'm': return value * 60 * 1000; // minutos | |
| case 'h': return value * 60 * 60 * 1000; // horas | |
| default: return 5 * 60 * 1000; // Default: 5 minutos | |
| } | |
| } | |
| // Variables para seguimiento de rotaci贸n de cookies | |
| let lastUsedTokenIndex = -1; | |
| let lastTokenTime = 0; | |
| let failedTokens = new Set(); // Conjunto para almacenar 铆ndices de tokens fallidos | |
| let lastFailedResetTime = 0; // Tiempo de 煤ltimo reinicio de tokens fallidos | |
| const FAILED_TOKEN_RESET_TIME = 1 * 60 * 1000; // 1 minuto para reiniciar tokens fallidos | |
| let totalAttempts = 0; // Contador de intentos totales en el ciclo actual | |
| const MAX_TOTAL_ATTEMPTS = 50; // M谩ximo n煤mero de intentos totales para evitar bucles infinitos | |
| const ROTATION_DELAY = 2000; // Delay de 2 segundos entre rotaci贸n de cuentas | |
| /** | |
| * Procesa el token de autenticaci贸n para extraer la parte relevante | |
| * Implementa rotaci贸n de cookies si est谩 habilitado en la configuraci贸n | |
| * @param {string} authToken - Token de autenticaci贸n crudo | |
| * @param {boolean} forceRotate - Forzar rotaci贸n a la siguiente cookie (para manejo de errores) | |
| * @returns {string} Token procesado | |
| */ | |
| function processAuthToken(authToken, forceRotate = false) { | |
| if (!authToken) return null; | |
| // Dividir por comas para manejar m煤ltiples tokens | |
| const keys = authToken.split(',').map((key) => key.trim()); | |
| let tokenIndex; | |
| // Limpiar tokens fallidos si ha pasado el tiempo de reinicio | |
| const now = Date.now(); | |
| if (now - lastFailedResetTime > FAILED_TOKEN_RESET_TIME) { | |
| failedTokens.clear(); | |
| totalAttempts = 0; // Reiniciar contador de intentos totales | |
| lastFailedResetTime = now; | |
| } | |
| // Aplicar rotaci贸n si est谩 habilitada y hay m煤ltiples cookies | |
| if (keys.length > 1) { | |
| // Si se fuerza la rotaci贸n o est谩 habilitada la rotaci贸n autom谩tica | |
| if (forceRotate || (config.cookieRotation && config.cookieRotation.enabled)) { | |
| const rotationTime = config.cookieRotation ? parseRotationTime(config.cookieRotation.time) : 5 * 60 * 1000; | |
| if (forceRotate || lastUsedTokenIndex === -1 || now - lastTokenTime >= rotationTime) { | |
| // Incrementar contador de intentos totales | |
| totalAttempts++; | |
| // Verificar si hemos alcanzado el l铆mite m谩ximo de intentos | |
| if (totalAttempts > MAX_TOTAL_ATTEMPTS) { | |
| console.log(`[AUTH] Alcanzado el l铆mite m谩ximo de ${MAX_TOTAL_ATTEMPTS} intentos totales, reiniciando estado`); | |
| failedTokens.clear(); | |
| totalAttempts = 1; // Reiniciar pero contar este intento | |
| } | |
| // Buscar el siguiente token que no haya fallado | |
| let attemptsCount = 0; | |
| let nextIndex = lastUsedTokenIndex; | |
| do { | |
| // Rotar al siguiente token | |
| nextIndex = (nextIndex + 1) % keys.length; | |
| attemptsCount++; | |
| // Si hemos probado todas las cookies y todas han fallado, implementar bucle circular | |
| if (attemptsCount > keys.length) { | |
| console.log(`[AUTH] Todas las cookies han fallado, implementando bucle circular (intento total: ${totalAttempts})`); | |
| // En lugar de reiniciar el estado, volvemos a intentar con la primera cookie | |
| // pero mantenemos el registro de intentos para evitar bucles infinitos | |
| nextIndex = 0; // Volver a la primera cookie | |
| break; | |
| } | |
| } while (failedTokens.has(nextIndex)); | |
| tokenIndex = nextIndex; | |
| lastUsedTokenIndex = tokenIndex; | |
| lastTokenTime = now; | |
| console.log(`[AUTH] ${forceRotate ? 'Forzando rotaci贸n' : 'Rotando'} a cookie #${tokenIndex + 1} de ${keys.length} (intento total: ${totalAttempts})`); | |
| } else { | |
| // Usar el mismo token si no ha pasado el tiempo de rotaci贸n | |
| tokenIndex = lastUsedTokenIndex; | |
| console.log(`[AUTH] Usando cookie #${tokenIndex + 1} (rotaci贸n cada ${config.cookieRotation ? config.cookieRotation.time : '5m'})`); | |
| } | |
| } else { | |
| // Sin rotaci贸n, seleccionar un token aleatorio (comportamiento original) | |
| tokenIndex = Math.floor(Math.random() * keys.length); | |
| console.log(`[AUTH] Seleccionando cookie aleatoria #${tokenIndex + 1} de ${keys.length}`); | |
| } | |
| } else { | |
| // Sin rotaci贸n, seleccionar un token aleatorio (comportamiento original) | |
| tokenIndex = Math.floor(Math.random() * keys.length); | |
| if (keys.length > 1) { | |
| console.log(`[AUTH] Seleccionando cookie aleatoria #${tokenIndex + 1} de ${keys.length}`); | |
| } | |
| } | |
| let processedToken = keys[tokenIndex]; | |
| // Procesar formato de token | |
| if (processedToken && processedToken.includes('%3A%3A')) { | |
| processedToken = processedToken.split('%3A%3A')[1]; | |
| } else if (processedToken && processedToken.includes('::')) { | |
| processedToken = processedToken.split('::')[1]; | |
| } | |
| return processedToken?.trim(); | |
| } | |
| /** | |
| * Marca un token como fallido para que no se use en la pr贸xima rotaci贸n | |
| * @param {string} authToken - Token de autenticaci贸n que fall贸 | |
| */ | |
| function markTokenAsFailed(authToken) { | |
| if (!authToken) return; | |
| const keys = authToken.split(',').map((key) => key.trim()); | |
| if (keys.length <= 1) return; // No hay alternativas para rotar | |
| if (lastUsedTokenIndex >= 0 && lastUsedTokenIndex < keys.length) { | |
| console.log(`[AUTH] Marcando cookie #${lastUsedTokenIndex + 1} como fallida`); | |
| failedTokens.add(lastUsedTokenIndex); | |
| } | |
| } | |
| /** | |
| * Obtiene el siguiente token disponible despu茅s de un fallo | |
| * @param {string} authToken - Token de autenticaci贸n crudo | |
| * @returns {Promise<string>} Promesa con el siguiente token procesado | |
| */ | |
| async function getNextAuthToken(authToken) { | |
| // Esperar el tiempo de delay configurado antes de rotar | |
| console.log(`[AUTH] Esperando ${ROTATION_DELAY/1000} segundos antes de rotar a la siguiente cuenta...`); | |
| await new Promise(resolve => setTimeout(resolve, ROTATION_DELAY)); | |
| return processAuthToken(authToken, true); | |
| } | |
| /** | |
| * Maneja respuestas con rate limit o error de actividad sospechosa, | |
| * marcando la cookie actual como rate-limited y rotando a la siguiente cookie disponible | |
| * @param {string} authToken - Token de autenticaci贸n crudo | |
| * @param {boolean} isRateLimited - Si la respuesta est谩 rate-limited | |
| * @param {boolean} isSuspiciousActivity - Si la respuesta contiene el error de actividad sospechosa | |
| * @param {string} model - Modelo solicitado (para usar premium si es necesario) | |
| * @param {Object} config - Configuraci贸n de la aplicaci贸n | |
| * @returns {Promise<Object>} Promesa con el resultado, nuevo token y si se debe reintentar | |
| */ | |
| async function handleRateLimitedResponse(authToken, isRateLimited, isSuspiciousActivity = false, model = null, config = null) { | |
| if (!isRateLimited && !isSuspiciousActivity) { | |
| return { | |
| shouldRetry: false, | |
| newToken: authToken, | |
| usePremium: false | |
| }; | |
| } | |
| // Mensaje de log seg煤n el tipo de error | |
| if (isRateLimited) { | |
| console.log('[AUTH] Detectado mensaje de rate limit en la respuesta'); | |
| } else if (isSuspiciousActivity) { | |
| console.log('[AUTH] Detectado error de actividad sospechosa en la respuesta'); | |
| } | |
| // Obtener el ID de la cookie actual | |
| const authCookieDB = require('../config/auth_cookies'); | |
| const keys = authToken.split(',').map(key => key.trim()); | |
| if (lastUsedTokenIndex >= 0 && lastUsedTokenIndex < keys.length) { | |
| const currentToken = keys[lastUsedTokenIndex]; | |
| // Buscar la cookie en la base de datos por su valor | |
| for (const cookie of authCookieDB.getAllAuthCookies()) { | |
| // Comparar con el valor completo y tambi茅n con la parte procesada | |
| const processedValue = processAuthToken(cookie.value); | |
| const currentProcessed = processAuthToken(currentToken); | |
| if (cookie.value === currentToken || processedValue === currentProcessed) { | |
| if (isRateLimited) { | |
| console.log(`[AUTH] Marcando cookie ${cookie.name} (${cookie.id.substring(0, 8)}...) como rate-limited por 12 horas`); | |
| authCookieDB.markAsRateLimited(cookie.id); | |
| } else if (isSuspiciousActivity) { | |
| console.log(`[AUTH] Cookie ${cookie.name} (${cookie.id.substring(0, 8)}...) ha encontrado error de actividad sospechosa`); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| // Si es un error de actividad sospechosa y estamos en modo privilegiado, intentar con una cookie premium | |
| if (isSuspiciousActivity && config && config.isPrivilegedMode) { | |
| console.log('[AUTH] Intentando usar cookie premium como fallback para el error de actividad sospechosa'); | |
| const premiumToken = config.getPremiumAuthCookies(); | |
| if (premiumToken) { | |
| console.log('[AUTH] Usando cookie premium como fallback'); | |
| return { | |
| shouldRetry: true, | |
| newToken: premiumToken, | |
| usePremium: true | |
| }; | |
| } | |
| } | |
| // Para rate limit normal o si no hay cookies premium disponibles | |
| // Marcar el token como fallido en la rotaci贸n interna | |
| markTokenAsFailed(authToken); | |
| // Obtener el siguiente token (con delay incorporado) | |
| const nextToken = await getNextAuthToken(authToken); | |
| // Siempre reintentar, incluso si todas las cookies est谩n rate-limited | |
| // El bucle circular se implementa en processAuthToken | |
| console.log('[AUTH] Rotando a la siguiente cookie disponible (bucle circular)'); | |
| return { | |
| shouldRetry: true, | |
| newToken: authToken, // El token original, la rotaci贸n se maneja internamente | |
| usePremium: false | |
| }; | |
| } | |
| /** | |
| * Funci贸n para probar una cookie rate-limited | |
| * @param {string} cookieId - ID de la cookie a probar | |
| * @returns {Promise<boolean>} True si la cookie est谩 funcionando, false si sigue rate-limited | |
| */ | |
| async function testRateLimitedCookie(cookieId) { | |
| const authCookieDB = require('../config/auth_cookies'); | |
| const cookie = authCookieDB.getAuthCookieById(cookieId); | |
| if (!cookie) { | |
| console.log(`[AUTH] No se encontr贸 la cookie con ID ${cookieId}`); | |
| return false; | |
| } | |
| console.log(`[AUTH] Probando cookie rate-limited: ${cookie.name} (${cookieId.substring(0, 8)}...)`); | |
| try { | |
| // Crear un mensaje simple para probar | |
| const messages = [ | |
| { role: "user", content: "Hello, are you working?" } | |
| ]; | |
| // Usar el modelo claude-3.7-sonnet para la prueba | |
| const modelName = "claude-3.7-sonnet"; | |
| // Generar el cuerpo de la solicitud | |
| const cursorBody = generateCursorBody(messages, modelName); | |
| // Configurar el token para la solicitud | |
| const processedToken = processAuthToken(cookie.value); | |
| const cursorChecksum = generateCursorChecksum(processedToken.trim()); | |
| const { v5: uuidv5, v4: uuidv4 } = require('uuid'); | |
| const sessionid = uuidv5(processedToken, uuidv5.DNS); | |
| const clientKey = generateHashed64Hex(processedToken); | |
| // Realizar la solicitud | |
| const response = await fetch('https://api2.cursor.sh/aiserver.v1.ChatService/StreamUnifiedChatWithTools', { | |
| method: 'POST', | |
| headers: { | |
| 'authorization': `Bearer ${processedToken}`, | |
| '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': cursorChecksum, | |
| 'x-cursor-client-version': "0.48.7", | |
| '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, | |
| timeout: { | |
| connect: 5000, | |
| read: 30000 | |
| } | |
| }); | |
| // Procesar la respuesta | |
| if (response.status !== 200) { | |
| console.log(`[AUTH] La prueba de la cookie fall贸 con estado: ${response.status}`); | |
| return false; | |
| } | |
| // Leer el primer chunk para verificar si hay rate limit | |
| const chunk = await response.body.getReader().read(); | |
| if (chunk.done) { | |
| console.log('[AUTH] No se recibi贸 respuesta en la prueba de la cookie'); | |
| return false; | |
| } | |
| const { isRateLimited } = chunkToUtf8String(Buffer.from(chunk.value).toString('hex')); | |
| if (isRateLimited) { | |
| console.log('[AUTH] La cookie sigue rate-limited'); | |
| // Actualizar el tiempo de la pr贸xima prueba | |
| authCookieDB.updateNextTestTime(cookieId); | |
| return false; | |
| } else { | |
| console.log('[AUTH] La cookie ha sido reactivada'); | |
| // Reactivar la cookie | |
| authCookieDB.clearRateLimit(cookieId); | |
| return true; | |
| } | |
| } catch (error) { | |
| console.error('[AUTH] Error al probar la cookie:', error.message); | |
| // Actualizar el tiempo de la pr贸xima prueba | |
| authCookieDB.updateNextTestTime(cookieId); | |
| return false; | |
| } | |
| } | |
| module.exports = { | |
| generateCursorBody, | |
| chunkToUtf8String, | |
| generateHashed64Hex, | |
| generateCursorChecksum, | |
| getAuthToken, | |
| processAuthToken, | |
| parseRotationTime, | |
| markTokenAsFailed, | |
| getNextAuthToken, | |
| checkForRateLimit, | |
| checkForSuspiciousActivityError, | |
| handleRateLimitedResponse, | |
| testRateLimitedCookie | |
| }; | |