Spaces:
Sleeping
Sleeping
| import * as dotenv from 'dotenv'; | |
| dotenv.config(); | |
| import express from 'express'; | |
| import crypto from 'crypto'; | |
| const app = express(); | |
| const PORT = 3000; | |
| const baseUrl = "https://api.x.com"; | |
| const xai_api_key = process.env['API_KEY'];//header | |
| const auth_token = process.env['AUTH_TOKEN'];//cookie | |
| const ct0 = process.env['CT0'];//cookie | |
| const grok_headers = { | |
| 'accept': '*/*', | |
| 'accept-language': 'zh-CN,zh;q=0.9', | |
| 'accept-encoding': 'gzip, deflate, br, zstd', | |
| 'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA', | |
| 'content-type': 'application/json', | |
| 'origin': 'https://x.com', | |
| 'priority': 'u=1, i', | |
| 'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"', | |
| 'sec-ch-ua-mobile': '?0', | |
| 'sec-ch-ua-platform': '"Windows"', | |
| 'sec-fetch-dest': 'empty', | |
| 'sec-fetch-mode': 'cors', | |
| 'sec-fetch-site': 'same-origin', | |
| 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36', | |
| } | |
| function getRandomIDPro(size) { | |
| let random = ''; | |
| const customDict = '0123456789'; | |
| for (; size--;) random += customDict[(Math.random() * customDict.length) | 0]; | |
| return random; | |
| } | |
| class Xgrok2Worker { | |
| constructor(modelId) { | |
| this.modelId = modelId; | |
| if (this.modelId !== "grok-2a") { | |
| throw new Error(`模型: ${this.modelId} 不支持`); | |
| } | |
| } | |
| constructRequestBody(messages,conversationId) { | |
| return { | |
| "responses": messages, | |
| "systemPromptName": "", | |
| "grokModelOptionId": "grok-2a", | |
| "conversationId": conversationId, | |
| "returnSearchResults": false, | |
| "returnCitations": false, | |
| "promptMetadata": { | |
| "promptSource": "NATURAL", | |
| "action": "INPUT" | |
| }, | |
| "imageGenerationCount": 1, | |
| "requestFeatures": { | |
| "eagerTweets": false, | |
| "serverHistory": false | |
| } | |
| } | |
| } | |
| // 转换消息内容格式 | |
| transformMessages(request) { | |
| const transformed = request.messages.map(msg => { | |
| switch (msg.role) { | |
| case 'system': | |
| return { message: msg.content, sender: 1, fileAttachments: [] }; | |
| case 'user': | |
| return { message: msg.content, sender: 1, fileAttachments: [] }; | |
| case 'assistant': | |
| return { message: msg.content, sender: 2 }; | |
| } | |
| }); | |
| return this.mergeUserMessages(transformed); | |
| } | |
| // 合并用户消息 | |
| mergeUserMessages(messages) { | |
| return messages.reduce((merged, current) => { | |
| const prev = merged[merged.length - 1]; | |
| if (prev && prev.sender === 1 && current.sender === 1) { | |
| prev.message += "\n" + current.message; | |
| return merged; | |
| } | |
| merged.push(current); | |
| return merged; | |
| }, []); | |
| } | |
| async sendChatRequest(request) { | |
| const conversationId = `18758${getRandomIDPro(14)}`; | |
| const transformedMessages = this.transformMessages(request); | |
| const requestBody = this.constructRequestBody(transformedMessages,conversationId); | |
| let fullResponse = ""; | |
| const response = await fetch(`${baseUrl}/2/grok/add_response.json`, { | |
| method: 'POST', | |
| headers: { | |
| ...grok_headers, | |
| 'x-csrf-token': ct0, | |
| 'cookie': `auth_token=${auth_token};ct0=${ct0}` | |
| }, | |
| body: JSON.stringify(requestBody) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`上游服务请求失败! status: ${response.status}`); | |
| } | |
| // 创建响应流读取器 | |
| const reader = response.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| fullResponse += decoder.decode(value); | |
| } | |
| //提取合并消息 | |
| const combinedMessage = await this.extractMessages(fullResponse); | |
| // 删除历史会话 | |
| await fetch(`https://x.com/i/api/graphql/TlKHSWVMVeaa-i7dqQqFQA/ConversationItem_DeleteConversationMutation`, { | |
| method: 'POST', | |
| headers: { | |
| ...grok_headers, | |
| 'x-csrf-token': ct0, | |
| 'cookie': `auth_token=${auth_token};ct0=${ct0}` | |
| }, | |
| body: JSON.stringify({ | |
| "variables": { | |
| "conversationId": conversationId | |
| }, | |
| "queryId": "TlKHSWVMVeaa-i7dqQqFQA" | |
| }) | |
| }); | |
| // 转换响应格式为 OpenAI 格式 | |
| return { | |
| id: `chatcmpl-${crypto.randomUUID()}`, | |
| object: "chat.completion", | |
| created: Math.floor(Date.now() / 1000), | |
| model: request.model, | |
| choices: [{ | |
| index: 0, | |
| message: { | |
| role: "assistant", | |
| content: combinedMessage | |
| }, | |
| finish_reason: "stop" | |
| }], | |
| usage: null | |
| }; | |
| } | |
| async extractMessages(data) { | |
| const lines = data.trim().split('\n'); | |
| let messages = []; | |
| for (const line of lines) { | |
| const json = JSON.parse(line); | |
| if (json.result && json.result.message) { | |
| messages.push(json.result.message); | |
| } | |
| } | |
| return messages.join(''); | |
| } | |
| } | |
| // 中间件配置 | |
| app.use(express.json({ limit: '5mb' })); | |
| app.use(express.urlencoded({ extended: true, limit: '5mb' })); | |
| // 路由处理 | |
| app.get('/api/v1/models', (req, res) => { | |
| res.json({ | |
| object: "list", | |
| data: { | |
| id: "grok-2a", | |
| object: "model", | |
| created: 1706745937, | |
| owned_by: "xai", | |
| }, | |
| }); | |
| }); | |
| app.post('/api/v1/chat/completions', async (req, res) => { | |
| let authToken = req.headers.authorization?.replace('Bearer ', ''); | |
| if (authToken !== xai_api_key) { | |
| res.status(401).json({ error: 'Unauthorized' }); | |
| return; | |
| } | |
| try { | |
| const reqBody = req.body; | |
| const xgrok2Worker = new Xgrok2Worker(reqBody.model); | |
| const result = await xgrok2Worker.sendChatRequest(reqBody); | |
| if (reqBody.stream) { | |
| res.setHeader('Content-Type', 'text/event-stream'); | |
| res.setHeader('Cache-Control', 'no-cache'); | |
| res.setHeader('Connection', 'keep-alive'); | |
| const content = result.choices[0].message.content; | |
| const chunks = content.split(/(?<=[\.\?\!。?!\n])\s*/g).filter(chunk => chunk.trim()); | |
| for (const chunkText of chunks) { | |
| const streamPayload = { | |
| id: result.id, | |
| object: 'chat.completion.chunk', | |
| created: Math.floor(Date.now() / 1000), | |
| model: result.model, | |
| choices: [{ | |
| index: 0, | |
| delta: { | |
| content: chunkText.includes('\n') ? chunkText : chunkText + ' ' | |
| }, | |
| finish_reason: null | |
| }] | |
| }; | |
| res.write(`data: ${JSON.stringify(streamPayload)}\n\n`); | |
| await new Promise(resolve => setTimeout(resolve, 50)); | |
| } | |
| const endPayload = { | |
| id: result.id, | |
| object: 'chat.completion.chunk', | |
| created: Math.floor(Date.now() / 1000), | |
| model: result.model, | |
| choices: [{ | |
| index: 0, | |
| delta: {}, | |
| finish_reason: 'stop' | |
| }] | |
| }; | |
| res.write(`data: ${JSON.stringify(endPayload)}\n\n`); | |
| res.end(); | |
| } else { | |
| res.json(result); | |
| } | |
| } catch (error) { | |
| console.error('Error:', error); | |
| res.status(500).json({ | |
| error: { | |
| message: error.message, | |
| type: 'server_error', | |
| param: null, | |
| code: error.code || null | |
| } | |
| }); | |
| } | |
| }); | |
| // 处理 404 路由 | |
| app.use((req, res) => { | |
| res.status(404).send('请使用正确请求路径'); | |
| }); | |
| // 启动服务器 | |
| app.listen(PORT, () => { | |
| console.log(`服务器运行在端口 ${PORT}`); | |
| }); |