deeme commited on
Commit
8fd3f2a
·
verified ·
1 Parent(s): 5a83b6c

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +39 -0
  2. README.md +6 -7
  3. index.js +712 -0
  4. package.json +19 -0
Dockerfile ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-slim
2
+
3
+ # 安装 Chrome 依赖
4
+ RUN apt-get update && apt-get install -y \
5
+ wget \
6
+ gnupg \
7
+ ca-certificates \
8
+ procps \
9
+ chromium \
10
+ chromium-sandbox
11
+
12
+ # 设置工作目录
13
+ WORKDIR /app
14
+
15
+ # 复制 package.json 和 package-lock.json
16
+ COPY package*.json ./
17
+
18
+ # 安装依赖
19
+ RUN npm install
20
+
21
+ # 复制源代码
22
+ COPY . .
23
+
24
+ # 设置环境变量
25
+
26
+ # 你的谷歌浏览器地址,使用绝对路径。
27
+ ENV CHROME_PATH=/usr/bin/chromium
28
+
29
+ # 你想部署的端口号
30
+ ENV PORT=7860
31
+
32
+ # 是否显示搜索结果
33
+ ENV ISSHOW_SEARCH_RESULTS=true
34
+
35
+ # 暴露端口
36
+ EXPOSE 7860
37
+
38
+ # 启动应用
39
+ CMD ["npm", "start"]
README.md CHANGED
@@ -1,10 +1,9 @@
1
  ---
2
- title: Grok
3
- emoji: 🐢
4
- colorFrom: red
5
- colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: grok
3
+ emoji: 🤖
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: docker
7
  pinned: false
8
+ app_port: 7860
9
+ ---
 
index.js ADDED
@@ -0,0 +1,712 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import fetch from 'node-fetch';
3
+ import FormData from 'form-data';
4
+ import dotenv from 'dotenv';
5
+ import cors from 'cors';
6
+ import puppeteer from 'puppeteer';
7
+ import { v4 as uuidv4 } from 'uuid';
8
+
9
+ dotenv.config();
10
+ // 配置常量
11
+ const CONFIG = {
12
+ MODELS: {
13
+ 'grok-latest': 'grok-latest',
14
+ 'grok-latest-image': 'grok-latest',
15
+ 'grok-latest-search': 'grok-latest'
16
+ },
17
+ API: {
18
+ BASE_URL: "https://grok.com",
19
+ API_KEY: process.env.API_KEY || "sk-123456",
20
+ SSO_TOKEN: null,//登录时才有的认证cookie,这里暂时用不到,之后可能需要
21
+ SIGNATURE_COOKIE: null,
22
+ PICGO_KEY: process.env.PICGO_KEY || null //想要生图的话需要填入这个PICGO图床的key
23
+ },
24
+ SERVER: {
25
+ PORT: process.env.PORT || 3000,
26
+ BODY_LIMIT: '5mb'
27
+ },
28
+ RETRY: {
29
+ MAX_ATTEMPTS: 1,//重试次数
30
+ DELAY_BASE: 1000 // 基础延迟时间(毫秒)
31
+ },
32
+ ISSHOW_SEARCH_RESULTS: process.env.ISSHOW_SEARCH_RESULTS === 'true',//是否显示搜索结果,默认关闭
33
+ CHROME_PATH: process.env.CHROME_PATH || "/usr/bin/chromium"//chrome路径
34
+ //CHROME_PATH: process.env.CHROME_PATH || "C:\Program Files\Google\Chrome\Application\chrome.exe"//chrome路径
35
+ };
36
+
37
+ // 请求头配置
38
+ const DEFAULT_HEADERS = {
39
+ 'accept': '*/*',
40
+ 'accept-language': 'zh-CN,zh;q=0.9',
41
+ 'accept-encoding': 'gzip, deflate, br, zstd',
42
+ 'content-type': 'text/plain;charset=UTF-8',
43
+ 'Connection': 'keep-alive',
44
+ 'origin': 'https://grok.com',
45
+ 'priority': 'u=1, i',
46
+ 'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
47
+ 'sec-ch-ua-mobile': '?0',
48
+ 'sec-ch-ua-platform': '"Windows"',
49
+ 'sec-fetch-dest': 'empty',
50
+ 'sec-fetch-mode': 'cors',
51
+ 'sec-fetch-site': 'same-origin',
52
+ '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',
53
+ 'baggage': 'sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c'
54
+ };
55
+
56
+ class Utils {
57
+ static async extractGrokHeaders() {
58
+ console.log("开始提取头信息");
59
+ try {
60
+ // 启动浏览器
61
+ const browser = await puppeteer.launch({
62
+ headless: true,
63
+ args: [
64
+ '--no-sandbox',
65
+ '--disable-setuid-sandbox',
66
+ '--disable-dev-shm-usage',
67
+ '--disable-gpu'
68
+ ],
69
+ executablePath: CONFIG.CHROME_PATH
70
+ });
71
+
72
+ const page = await browser.newPage();
73
+ await page.goto('https://grok.com/', { waitUntil: 'networkidle0' });
74
+
75
+ // 获取所有 Cookies
76
+ const cookies = await page.cookies();
77
+ const targetHeaders = ['x-anonuserid', 'x-challenge', 'x-signature'];
78
+ const extractedHeaders = {};
79
+ // 遍历 Cookies
80
+ for (const cookie of cookies) {
81
+ // 检查是否为目标头信息
82
+ if (targetHeaders.includes(cookie.name.toLowerCase())) {
83
+ extractedHeaders[cookie.name.toLowerCase()] = cookie.value;
84
+ }
85
+ }
86
+ // 关闭浏览器
87
+ await browser.close();
88
+ // 打印并返回提取的头信息
89
+ console.log('提取的头信息:', JSON.stringify(extractedHeaders, null, 2));
90
+ return extractedHeaders;
91
+
92
+ } catch (error) {
93
+ console.error('获取头信息出错:', error);
94
+ return null;
95
+ }
96
+ }
97
+ static async get_signature() {
98
+ if (CONFIG.API.SIGNATURE_COOKIE) {
99
+ return CONFIG.API.SIGNATURE_COOKIE;
100
+ }
101
+ console.log("刷新认证信息");
102
+ let retryCount = 0;
103
+ while (retryCount < CONFIG.RETRY.MAX_ATTEMPTS) {
104
+ let headers = await Utils.extractGrokHeaders();
105
+ if (headers) {
106
+ console.log("获取认证信息成功");
107
+ CONFIG.API.SIGNATURE_COOKIE = { cookie: `x-anonuserid=${headers["x-anonuserid"]}; x-challenge=${headers["x-challenge"]}; x-signature=${headers["x-signature"]}` };
108
+ return CONFIG.API.SIGNATURE_COOKIE;
109
+ //return;
110
+ }
111
+ retryCount++;
112
+ if (retryCount >= CONFIG.RETRY.MAX_ATTEMPTS) {
113
+ throw new Error(`获取认证信息失败!`);
114
+ }
115
+ await new Promise(resolve => setTimeout(resolve, CONFIG.RETRY.DELAY_BASE * retryCount));
116
+ }
117
+ }
118
+ static async handleError(error, res) {
119
+ // 如果是500错误且提供了原始请求函数,尝试重新获取签名并重试
120
+ if (String(error).includes("status: 500")) {
121
+ try {
122
+ await Utils.get_signature();
123
+ if (CONFIG.API.SIGNATURE_COOKIE) {
124
+ return res.status(500).json({
125
+ error: {
126
+ message: `${retryError.message}已��新刷新认证信息,请重新对话`,
127
+ type: 'server_error',
128
+ param: null,
129
+ code: error.code || null
130
+ }
131
+ });
132
+ } else {
133
+ return res.status(500).json({
134
+ error: {
135
+ message: "认证信息获取失败",
136
+ type: 'server_error',
137
+ param: null,
138
+ code: null
139
+ }
140
+ });
141
+ }
142
+ } catch (retryError) {
143
+ console.error('重试失败:', retryError);
144
+ return res.status(500).json({
145
+ error: {
146
+ message: `${retryError.message},认证信息获取失败`,
147
+ type: 'server_error',
148
+ param: null,
149
+ code: retryError.code || null
150
+ }
151
+ });
152
+ }
153
+ }
154
+
155
+ // 其他错误直接返回
156
+ res.status(500).json({
157
+ error: {
158
+ message: error.message,
159
+ type: 'server_error',
160
+ param: null,
161
+ code: error.code || null
162
+ }
163
+ });
164
+ }
165
+ static async organizeSearchResults(searchResults) {
166
+ // 确保传入的是有效的搜索结果对象
167
+ if (!searchResults || !searchResults.results) {
168
+ return '';
169
+ }
170
+
171
+ const results = searchResults.results;
172
+ const formattedResults = results.map((result, index) => {
173
+ // 处理可能为空的字段
174
+ const title = result.title || '未知标题';
175
+ const url = result.url || '#';
176
+ const preview = result.preview || '无预览内容';
177
+
178
+ return `\r\n<details><summary>资料[${index}]: ${title}</summary>\r\n${preview}\r\n\n[Link](${url})\r\n</details>`;
179
+ });
180
+ return formattedResults.join('\n\n');
181
+ }
182
+ }
183
+
184
+
185
+ class GrokApiClient {
186
+ constructor(modelId) {
187
+ if (!CONFIG.MODELS[modelId]) {
188
+ throw new Error(`不支持的模型: ${modelId}`);
189
+ }
190
+ this.modelId = CONFIG.MODELS[modelId];
191
+ }
192
+
193
+ processMessageContent(content) {
194
+ if (typeof content === 'string') return content;
195
+ return null;
196
+ }
197
+ // 获取图片类型
198
+ getImageType(base64String) {
199
+ let mimeType = 'image/jpeg';
200
+ if (base64String.includes('data:image')) {
201
+ const matches = base64String.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,/);
202
+ if (matches) {
203
+ mimeType = matches[1];
204
+ }
205
+ }
206
+ const extension = mimeType.split('/')[1];
207
+ const fileName = `image.${extension}`;
208
+
209
+ return {
210
+ mimeType: mimeType,
211
+ fileName: fileName
212
+ };
213
+ }
214
+
215
+ async uploadBase64Image(base64Data, url) {
216
+ try {
217
+ // 处理 base64 数据
218
+ let imageBuffer;
219
+ if (base64Data.includes('data:image')) {
220
+ imageBuffer = base64Data.split(',')[1];
221
+ } else {
222
+ imageBuffer = base64Data
223
+ }
224
+ const { mimeType, fileName } = this.getImageType(base64Data);
225
+ let uploadData = {
226
+ rpc: "uploadFile",
227
+ req: {
228
+ fileName: fileName,
229
+ fileMimeType: mimeType,
230
+ content: imageBuffer
231
+ }
232
+ };
233
+ console.log("发送图片请求");
234
+ // 发送请求
235
+ const response = await fetch(url, {
236
+ method: 'POST',
237
+ headers: {
238
+ ...CONFIG.DEFAULT_HEADERS,
239
+ ...CONFIG.API.SIGNATURE_COOKIE
240
+ },
241
+ body: JSON.stringify(uploadData)
242
+ });
243
+
244
+ if (!response.ok) {
245
+ console.error(`上传图片失败,状态码:${response.status},原因:${response.error}`);
246
+ return '';
247
+ }
248
+
249
+ const result = await response.json();
250
+ console.log('上传图片成功:', result);
251
+ return result.fileMetadataId;
252
+
253
+ } catch (error) {
254
+ console.error('上传图片失败:', error);
255
+ return '';
256
+ }
257
+ }
258
+
259
+ async prepareChatRequest(request) {
260
+
261
+ if (request.model === 'grok-latest-image' && !CONFIG.API.PICGO_KEY) {
262
+ throw new Error(`该模型需要配置PICGO图床密钥!`);
263
+ }
264
+ var todoMessages = request.messages;
265
+ if (request.model === 'grok-latest-image' || request.model === 'grok-latest-search') {
266
+ todoMessages = Array.isArray(todoMessages) ? [todoMessages[todoMessages.length - 1]] : [todoMessages];;
267
+ }
268
+ let fileAttachments = [];
269
+ let messages = '';
270
+ let lastRole = null;
271
+ let lastContent = '';
272
+ let search = false;
273
+
274
+ const processImageUrl = async (content) => {
275
+ if (content.type === 'image_url' && content.image_url.url.includes('data:image')) {
276
+ const imageResponse = await this.uploadBase64Image(
277
+ content.image_url.url,
278
+ `${CONFIG.API.BASE_URL}/api/rpc`
279
+ );
280
+ return imageResponse;
281
+ }
282
+ return null;
283
+ };
284
+
285
+ for (const current of todoMessages) {
286
+ const role = current.role === 'assistant' ? 'assistant' : 'user';
287
+ let textContent = '';
288
+ // 处理消息内容
289
+ if (Array.isArray(current.content)) {
290
+ // 处理数组内的所有内容
291
+ for (const item of current.content) {
292
+ if (item.type === 'image_url') {
293
+ // 如果是图片且是最后一条消息,则处理图片
294
+ if (current === todoMessages[todoMessages.length - 1]) {
295
+ const processedImage = await processImageUrl(item);
296
+ if (processedImage) fileAttachments.push(processedImage);
297
+ }
298
+ textContent += (textContent ? '\n' : '') + "[图片]";//图片占位符
299
+ } else if (item.type === 'text') {
300
+ textContent += (textContent ? '\n' : '') + item.text;
301
+ }
302
+ }
303
+ } else if (typeof current.content === 'object' && current.content !== null) {
304
+ // 处理单个对象内容
305
+ if (current.content.type === 'image_url') {
306
+ // 如果是图片且是最后一条消息,则处理图片
307
+ if (current === todoMessages[todoMessages.length - 1]) {
308
+ const processedImage = await processImageUrl(current.content);
309
+ if (processedImage) fileAttachments.push(processedImage);
310
+ }
311
+ textContent += (textContent ? '\n' : '') + "[图片]";//图片占位符
312
+ } else if (current.content.type === 'text') {
313
+ textContent = current.content.text;
314
+ }
315
+ } else {
316
+ // 处理普通文本内容
317
+ textContent = this.processMessageContent(current.content);
318
+ }
319
+ // 添加文本内容到消息字符串
320
+ if (textContent) {
321
+ if (role === lastRole) {
322
+ // 如果角色相同,合并消息内容
323
+ lastContent += '\n' + textContent;
324
+ messages = messages.substring(0, messages.lastIndexOf(`${role.toUpperCase()}: `)) +
325
+ `${role.toUpperCase()}: ${lastContent}\n`;
326
+ } else {
327
+ // 如果角色不同,添加新的消息
328
+ messages += `${role.toUpperCase()}: ${textContent}\n`;
329
+ lastContent = textContent;
330
+ lastRole = role;
331
+ }
332
+ } else if (current === todoMessages[todoMessages.length - 1] && fileAttachments.length > 0) {
333
+ // 如果是最后一条消息且有图片附件,添加空消息占位
334
+ messages += `${role.toUpperCase()}: [图片]\n`;
335
+ }
336
+ }
337
+
338
+ if (fileAttachments.length > 4) {
339
+ fileAttachments = fileAttachments.slice(0, 4); // 最多上传4张
340
+ }
341
+
342
+ messages = messages.trim();
343
+
344
+ if (request.model === 'grok-latest-search') {
345
+ search = true;
346
+ }
347
+ return {
348
+ message: messages,
349
+ modelName: this.modelId,
350
+ disableSearch: false,
351
+ imageAttachments: [],
352
+ returnImageBytes: false,
353
+ returnRawGrokInXaiRequest: false,
354
+ fileAttachments: fileAttachments,
355
+ enableImageStreaming: false,
356
+ imageGenerationCount: 1,
357
+ toolOverrides: {
358
+ imageGen: request.model === 'grok-latest-image',
359
+ webSearch: search,
360
+ xSearch: search,
361
+ xMediaSearch: search,
362
+ trendsSearch: search,
363
+ xPostAnalyze: search
364
+ }
365
+ };
366
+ }
367
+ }
368
+
369
+ class MessageProcessor {
370
+ static createChatResponse(message, model, isStream = false) {
371
+ const baseResponse = {
372
+ id: `chatcmpl-${uuidv4()}`,
373
+ created: Math.floor(Date.now() / 1000),
374
+ model: model
375
+ };
376
+
377
+ if (isStream) {
378
+ return {
379
+ ...baseResponse,
380
+ object: 'chat.completion.chunk',
381
+ choices: [{
382
+ index: 0,
383
+ delta: {
384
+ content: message
385
+ }
386
+ }]
387
+ };
388
+ }
389
+
390
+ return {
391
+ ...baseResponse,
392
+ object: 'chat.completion',
393
+ choices: [{
394
+ index: 0,
395
+ message: {
396
+ role: 'assistant',
397
+ content: message
398
+ },
399
+ finish_reason: 'stop'
400
+ }],
401
+ usage: null
402
+ };
403
+ }
404
+ }
405
+
406
+ // 中间件配置
407
+ const app = express();
408
+ app.use(express.json({ limit: CONFIG.SERVER.BODY_LIMIT }));
409
+ app.use(express.urlencoded({ extended: true, limit: CONFIG.SERVER.BODY_LIMIT }));
410
+ app.use(cors({
411
+ origin: '*',
412
+ methods: ['GET', 'POST', 'OPTIONS'],
413
+ allowedHeaders: ['Content-Type', 'Authorization']
414
+ }));
415
+ // API路由
416
+ app.get('/hf/v1/models', (req, res) => {
417
+ res.json({
418
+ object: "list",
419
+ data: Object.keys(CONFIG.MODELS).map((model, index) => ({
420
+ id: model,
421
+ object: "model",
422
+ created: Math.floor(Date.now() / 1000),
423
+ owned_by: "xai",
424
+ }))
425
+ });
426
+ });
427
+
428
+ app.post('/hf/v1/chat/completions', async (req, res) => {
429
+ const authToken = req.headers.authorization?.replace('Bearer ', '');
430
+ if (authToken !== CONFIG.API.API_KEY) {
431
+ return res.status(401).json({ error: 'Unauthorized' });
432
+ }
433
+ const makeRequest = async () => {
434
+ if (!CONFIG.API.SIGNATURE_COOKIE) {
435
+ await Utils.get_signature();
436
+ }
437
+ const grokClient = new GrokApiClient(req.body.model);
438
+ const requestPayload = await grokClient.prepareChatRequest(req.body);
439
+ //创建新对话
440
+ const newMessageReq = await fetch(`${CONFIG.API.BASE_URL}/api/rpc`, {
441
+ method: 'POST',
442
+ headers: {
443
+ ...DEFAULT_HEADERS,
444
+ ...CONFIG.API.SIGNATURE_COOKIE
445
+ },
446
+ body: JSON.stringify({
447
+ rpc: "createConversation",
448
+ req: {
449
+ temporary: false
450
+ }
451
+ })
452
+ });
453
+ if (!newMessageReq.ok) {
454
+ throw new Error(`上游服务请求失败! status: ${newMessageReq.status}`);
455
+ }
456
+
457
+ // 获取响应文本
458
+ const responseText = await newMessageReq.json();
459
+ const conversationId = responseText.conversationId;
460
+ console.log("会话ID:conversationId", conversationId);
461
+ if (!conversationId) {
462
+ throw new Error(`创建会话失败! status: ${newMessageReq.status}`);
463
+ }
464
+ //发送对话
465
+ const response = await fetch(`${CONFIG.API.BASE_URL}/api/conversations/${conversationId}/responses`, {
466
+ method: 'POST',
467
+ headers: {
468
+ "accept": "text/event-stream",
469
+ "baggage": "sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c",
470
+ "content-type": "text/plain;charset=UTF-8",
471
+ "Connection": "keep-alive",
472
+ ...CONFIG.API.SIGNATURE_COOKIE
473
+ },
474
+ body: JSON.stringify(requestPayload)
475
+ });
476
+
477
+ if (!response.ok) {
478
+ throw new Error(`上游服务请求失败! status: ${response.status}`);
479
+ }
480
+ if (req.body.stream) {
481
+ await handleStreamResponse(response, req.body.model, res);
482
+ } else {
483
+ await handleNormalResponse(response, req.body.model, res);
484
+ }
485
+ }
486
+ try {
487
+ await makeRequest();
488
+ } catch (error) {
489
+ CONFIG.API.SIGNATURE_COOKIE = null;
490
+ await Utils.handleError(error, res);
491
+ }
492
+ });
493
+
494
+ async function handleStreamResponse(response, model, res) {
495
+ res.setHeader('Content-Type', 'text/event-stream');
496
+ res.setHeader('Cache-Control', 'no-cache');
497
+ res.setHeader('Connection', 'keep-alive');
498
+
499
+ try {
500
+ const stream = response.body;
501
+ let buffer = '';
502
+
503
+ stream.on('data', async (chunk) => {
504
+ buffer += chunk.toString();
505
+ const lines = buffer.split('\n');
506
+ buffer = lines.pop() || '';
507
+
508
+ for (const line of lines) {
509
+ if (!line.trim()) continue;
510
+ const trimmedLine = line.trim();
511
+ if (trimmedLine.startsWith('data: ')) {
512
+ const data = trimmedLine.substring(6);
513
+
514
+ if(data === "[DONE]"){
515
+ console.log("流结束");
516
+ res.write('data: [DONE]\n\n');
517
+ return res.end();
518
+ };
519
+ try {
520
+ if(!data.trim())continue;
521
+ const linejosn = JSON.parse(data);
522
+ if (linejosn?.error?.name === "RateLimitError") {
523
+ var responseData = MessageProcessor.createChatResponse(`${linejosn.error.name},请重新对话`, model, true);
524
+ //fs.unlinkSync(path.resolve(process.cwd(), 'signature.json'));
525
+ CONFIG.API.SIGNATURE_COOKIE = null;
526
+ console.log("认证信息已删除");
527
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
528
+ res.write('data: [DONE]\n\n');
529
+ return res.end();
530
+ }
531
+ switch (model) {
532
+ case 'grok-latest-image':
533
+ if (linejosn.response === "modelResponse" && linejosn?.modelResponse?.generatedImageUrls) {
534
+ const dataImage = await handleImageResponse(linejosn.modelResponse.generatedImageUrls);
535
+ const responseData = MessageProcessor.createChatResponse(dataImage, model, true);
536
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
537
+ }
538
+ break;
539
+ case 'grok-latest':
540
+ if (linejosn.response === "token") {
541
+ const token = linejosn.token;
542
+ if (token && token.length > 0) {
543
+ const responseData = MessageProcessor.createChatResponse(token, model, true);
544
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
545
+ }
546
+ }
547
+ break;
548
+ case 'grok-latest-search':
549
+ if (linejosn.response === "token") {
550
+ const token = linejosn.token;
551
+ if (token && token.length > 0) {
552
+ const responseData = MessageProcessor.createChatResponse(token, model, true);
553
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
554
+ }
555
+ }
556
+ if (linejosn.response === "webSearchResults" && CONFIG.ISSHOW_SEARCH_RESULTS) {
557
+ const searchResults = await Utils.organizeSearchResults(linejosn.webSearchResults);
558
+ const responseData = MessageProcessor.createChatResponse(`<think>\r\n${searchResults}\r\n</think>\r\n`, model, true);
559
+ res.write(`data: ${JSON.stringify(responseData)}\n\n`);
560
+ }
561
+ break;
562
+ }
563
+ } catch (error) {
564
+ console.error('JSON解析错误:', error);
565
+ }
566
+ }
567
+ }
568
+ });
569
+ stream.on('error', (error) => {
570
+ console.error('流处理错误:', error);
571
+ res.write('data: [DONE]\n\n');
572
+ res.end();
573
+ });
574
+
575
+ } catch (error) {
576
+ console.error('处理响应错误:', error);
577
+ res.write('data: [DONE]\n\n');
578
+ res.end();
579
+ }
580
+ }
581
+
582
+ async function handleNormalResponse(response, model, res) {
583
+ let fullResponse = '';
584
+ let imageUrl = '';
585
+
586
+ try {
587
+ const responseText = await response.text();
588
+ const lines = responseText.split('\n');
589
+
590
+ for (const line of lines) {
591
+ if (!line.trim()) continue;
592
+ const data = line.slice(6);
593
+ if (data === '[DONE]') continue;
594
+ let linejosn = JSON.parse(data);
595
+ if (linejosn?.error?.name === "RateLimitError") {
596
+ fullResponse = `${linejosn.error.name},请重新对话`;
597
+ CONFIG.API.SIGNATURE_COOKIE = null;
598
+ return res.json(MessageProcessor.createChatResponse(fullResponse, model));
599
+ }
600
+ switch (model) {
601
+ case 'grok-latest-image':
602
+ if (linejosn.response === "modelResponse" && linejosn?.modelResponse?.generatedImageUrls) {
603
+ imageUrl = linejosn.modelResponse.generatedImageUrls;
604
+ }
605
+ break;
606
+ case 'grok-latest':
607
+ if (linejosn.response === "token") {
608
+ const token = linejosn.token;
609
+ if (token && token.length > 0) {
610
+ fullResponse += token;
611
+ }
612
+ }
613
+ break;
614
+ case 'grok-latest-search':
615
+ if (linejosn.response === "token") {
616
+ const token = linejosn.token;
617
+ if (token && token.length > 0) {
618
+ fullResponse += token;
619
+ }
620
+ }
621
+ if (linejosn.response === "webSearchResults" && CONFIG.ISSHOW_SEARCH_RESULTS) {
622
+ fullResponse += `\r\n<think>${await Utils.organizeSearchResults(linejosn.webSearchResults)}</think>\r\n`;
623
+ }
624
+ break;
625
+ }
626
+ }
627
+ if (imageUrl) {
628
+ const dataImage = await handleImageResponse(imageUrl);
629
+ const responseData = MessageProcessor.createChatResponse(dataImage, model);
630
+ res.json(responseData);
631
+ } else {
632
+ const responseData = MessageProcessor.createChatResponse(fullResponse, model);
633
+ res.json(responseData);
634
+ }
635
+ } catch (error) {
636
+ Utils.handleError(error, res);
637
+ }
638
+ }
639
+ async function handleImageResponse(imageUrl) {
640
+ //对服务器发送图片请求
641
+ const MAX_RETRIES = 3;
642
+ let retryCount = 0;
643
+ let imageBase64Response;
644
+
645
+ while (retryCount < MAX_RETRIES) {
646
+ try {
647
+ //发送图片请求获取图片
648
+ imageBase64Response = await fetch(`https://assets.grok.com/${imageUrl}`, {
649
+ method: 'GET',
650
+ headers: {
651
+ ...DEFAULT_HEADERS,
652
+ ...CONFIG.API.SIGNATURE_COOKIE
653
+ }
654
+ });
655
+
656
+ if (imageBase64Response.ok) {
657
+ break; // 如果请求成功,跳出重试循环
658
+ }
659
+
660
+ retryCount++;
661
+ if (retryCount === MAX_RETRIES) {
662
+ throw new Error(`上游服务请求失败! status: ${imageBase64Response.status}`);
663
+ }
664
+
665
+ // 等待一段时间后重试(可以使用指数退避)
666
+ await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
667
+
668
+ } catch (error) {
669
+ retryCount++;
670
+ if (retryCount === MAX_RETRIES) {
671
+ throw error;
672
+ }
673
+ // 等待一段时间后重试
674
+ await new Promise(resolve => setTimeout(resolve, CONFIG.API.RETRY_TIME * retryCount));
675
+ }
676
+ }
677
+
678
+ const arrayBuffer = await imageBase64Response.arrayBuffer();
679
+ const imageBuffer = Buffer.from(arrayBuffer);
680
+ const formData = new FormData();
681
+
682
+ formData.append('source', imageBuffer, {
683
+ filename: 'new.jpg',
684
+ contentType: 'image/jpeg'
685
+ });
686
+ const formDataHeaders = formData.getHeaders();
687
+ const responseURL = await fetch("https://www.picgo.net/api/1/upload", {
688
+ method: "POST",
689
+ headers: {
690
+ ...formDataHeaders,
691
+ "Content-Type": "multipart/form-data",
692
+ "X-API-Key": CONFIG.API.PICGO_KEY
693
+ },
694
+ body: formData
695
+ });
696
+ if (!responseURL.ok) {
697
+ return "生图失败,请查看图床密钥是否设置正确"
698
+ } else {
699
+ const result = await responseURL.json();
700
+ return `![image](${result.image.url})`
701
+ }
702
+ }
703
+
704
+ // 404处理
705
+ app.use((req, res) => {
706
+ res.status(200).send('api运行正常');
707
+ });
708
+
709
+ // 启动服务器
710
+ app.listen(CONFIG.SERVER.PORT, () => {
711
+ console.log(`服务器已启动,监听端口: ${CONFIG.SERVER.PORT}`);
712
+ });
package.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "grok2api",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "node index.js"
8
+ },
9
+ "author": "yxmiler",
10
+ "dependencies": {
11
+ "express": "^4.18.2",
12
+ "node-fetch": "^3.3.2",
13
+ "dotenv": "^16.3.1",
14
+ "cors": "^2.8.5",
15
+ "form-data": "^4.0.0",
16
+ "puppeteer": "^22.8.2",
17
+ "uuid": "^9.0.0"
18
+ }
19
+ }